summaryrefslogtreecommitdiff
path: root/chromium/content/browser
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/content/browser
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/content/browser')
-rw-r--r--chromium/content/browser/DEPS70
-rw-r--r--chromium/content/browser/OWNERS13
-rw-r--r--chromium/content/browser/accessibility/OWNERS2
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter.cc197
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter.h153
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc152
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc108
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm250
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc265
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h27
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc319
-rw-r--r--chromium/content/browser/accessibility/accessibility_ui.cc253
-rw-r--r--chromium/content/browser/accessibility/accessibility_ui.h31
-rw-r--r--chromium/content/browser/accessibility/accessibility_win_browsertest.cc883
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility.cc341
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility.h296
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_android.cc406
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_android.h74
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_cocoa.h112
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_cocoa.mm1505
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h26
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_gtk.cc519
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_gtk.h96
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_mac.h51
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_mac.mm71
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm165
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager.cc419
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager.h236
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_android.cc366
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_android.h92
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc84
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h48
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_mac.h43
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm121
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc603
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_win.cc203
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_win.h82
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_state_impl.cc147
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_state_impl.h76
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc77
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win.cc3626
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win.h899
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc660
-rw-r--r--chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc465
-rw-r--r--chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc456
-rw-r--r--chromium/content/browser/android/DEPS3
-rw-r--r--chromium/content/browser/android/OWNERS8
-rw-r--r--chromium/content/browser/android/android_browser_process.cc47
-rw-r--r--chromium/content/browser/android/android_browser_process.h16
-rw-r--r--chromium/content/browser/android/browser_jni_registrar.cc83
-rw-r--r--chromium/content/browser/android/browser_jni_registrar.h21
-rw-r--r--chromium/content/browser/android/browser_media_player_manager.cc551
-rw-r--r--chromium/content/browser/android/browser_media_player_manager.h182
-rw-r--r--chromium/content/browser/android/browser_startup_config.cc25
-rw-r--r--chromium/content/browser/android/browser_startup_config.h18
-rw-r--r--chromium/content/browser/android/child_process_launcher_android.cc163
-rw-r--r--chromium/content/browser/android/child_process_launcher_android.h36
-rw-r--r--chromium/content/browser/android/content_settings.cc58
-rw-r--r--chromium/content/browser/android/content_settings.h37
-rw-r--r--chromium/content/browser/android/content_startup_flags.cc88
-rw-r--r--chromium/content/browser/android/content_startup_flags.h20
-rw-r--r--chromium/content/browser/android/content_video_view.cc184
-rw-r--r--chromium/content/browser/android/content_video_view.h95
-rw-r--r--chromium/content/browser/android/content_view_core_impl.cc1603
-rw-r--r--chromium/content/browser/android/content_view_core_impl.h373
-rw-r--r--chromium/content/browser/android/content_view_render_view.cc99
-rw-r--r--chromium/content/browser/android/content_view_render_view.h60
-rw-r--r--chromium/content/browser/android/content_view_statics.cc91
-rw-r--r--chromium/content/browser/android/content_view_statics.h14
-rw-r--r--chromium/content/browser/android/date_time_chooser_android.cc121
-rw-r--r--chromium/content/browser/android/date_time_chooser_android.h63
-rw-r--r--chromium/content/browser/android/devtools_auth.cc26
-rw-r--r--chromium/content/browser/android/download_controller_android_impl.cc391
-rw-r--r--chromium/content/browser/android/download_controller_android_impl.h129
-rw-r--r--chromium/content/browser/android/edge_effect.cc367
-rw-r--r--chromium/content/browser/android/edge_effect.h89
-rw-r--r--chromium/content/browser/android/in_process/DEPS6
-rw-r--r--chromium/content/browser/android/in_process/synchronous_compositor_impl.cc296
-rw-r--r--chromium/content/browser/android/in_process/synchronous_compositor_impl.h98
-rw-r--r--chromium/content/browser/android/in_process/synchronous_compositor_output_surface.cc287
-rw-r--r--chromium/content/browser/android/in_process/synchronous_compositor_output_surface.h98
-rw-r--r--chromium/content/browser/android/in_process/synchronous_input_event_filter.cc79
-rw-r--r--chromium/content/browser/android/in_process/synchronous_input_event_filter.h50
-rw-r--r--chromium/content/browser/android/interstitial_page_delegate_android.cc94
-rw-r--r--chromium/content/browser/android/interstitial_page_delegate_android.h56
-rw-r--r--chromium/content/browser/android/load_url_params.cc45
-rw-r--r--chromium/content/browser/android/load_url_params.h16
-rw-r--r--chromium/content/browser/android/media_resource_getter_impl.cc276
-rw-r--r--chromium/content/browser/android/media_resource_getter_impl.h85
-rw-r--r--chromium/content/browser/android/overscroll_glow.cc271
-rw-r--r--chromium/content/browser/android/overscroll_glow.h111
-rw-r--r--chromium/content/browser/android/surface_texture_peer_browser_impl.cc71
-rw-r--r--chromium/content/browser/android/surface_texture_peer_browser_impl.h37
-rw-r--r--chromium/content/browser/android/touch_point.cc115
-rw-r--r--chromium/content/browser/android/touch_point.h30
-rw-r--r--chromium/content/browser/android/tracing_intent_handler.cc57
-rw-r--r--chromium/content/browser/android/tracing_intent_handler.h31
-rw-r--r--chromium/content/browser/android/vibration_message_filter.cc69
-rw-r--r--chromium/content/browser/android/vibration_message_filter.h34
-rw-r--r--chromium/content/browser/android/web_contents_observer_android.cc233
-rw-r--r--chromium/content/browser/android/web_contents_observer_android.h88
-rw-r--r--chromium/content/browser/appcache/OWNERS1
-rw-r--r--chromium/content/browser/appcache/appcache_dispatcher_host.cc230
-rw-r--r--chromium/content/browser/appcache/appcache_dispatcher_host.h81
-rw-r--r--chromium/content/browser/appcache/appcache_frontend_proxy.cc56
-rw-r--r--chromium/content/browser/appcache/appcache_frontend_proxy.h44
-rw-r--r--chromium/content/browser/appcache/chrome_appcache_service.cc79
-rw-r--r--chromium/content/browser/appcache/chrome_appcache_service.h85
-rw-r--r--chromium/content/browser/appcache/chrome_appcache_service_unittest.cc226
-rw-r--r--chromium/content/browser/aura/OWNERS4
-rw-r--r--chromium/content/browser/aura/browser_compositor_output_surface.cc108
-rw-r--r--chromium/content/browser/aura/browser_compositor_output_surface.h57
-rw-r--r--chromium/content/browser/aura/browser_compositor_output_surface_proxy.cc58
-rw-r--r--chromium/content/browser/aura/browser_compositor_output_surface_proxy.h49
-rw-r--r--chromium/content/browser/aura/gpu_process_transport_factory.cc511
-rw-r--r--chromium/content/browser/aura/gpu_process_transport_factory.h106
-rw-r--r--chromium/content/browser/aura/image_transport_factory.cc68
-rw-r--r--chromium/content/browser/aura/image_transport_factory.h99
-rw-r--r--chromium/content/browser/aura/no_transport_image_transport_factory.cc55
-rw-r--r--chromium/content/browser/aura/no_transport_image_transport_factory.h45
-rw-r--r--chromium/content/browser/aura/reflector_impl.cc161
-rw-r--r--chromium/content/browser/aura/reflector_impl.h122
-rw-r--r--chromium/content/browser/aura/software_browser_compositor_output_surface.cc26
-rw-r--r--chromium/content/browser/aura/software_browser_compositor_output_surface.h33
-rw-r--r--chromium/content/browser/aura/software_output_device_win.cc110
-rw-r--r--chromium/content/browser/aura/software_output_device_win.h42
-rw-r--r--chromium/content/browser/aura/software_output_device_x11.cc87
-rw-r--r--chromium/content/browser/aura/software_output_device_x11.h38
-rw-r--r--chromium/content/browser/bookmarklet_browsertest.cc75
-rw-r--r--chromium/content/browser/browser_child_process_host_impl.cc353
-rw-r--r--chromium/content/browser/browser_child_process_host_impl.h120
-rw-r--r--chromium/content/browser/browser_context.cc276
-rw-r--r--chromium/content/browser/browser_ipc_logging.cc51
-rw-r--r--chromium/content/browser/browser_main.cc35
-rw-r--r--chromium/content/browser/browser_main.h21
-rw-r--r--chromium/content/browser/browser_main_loop.cc968
-rw-r--r--chromium/content/browser/browser_main_loop.h167
-rw-r--r--chromium/content/browser/browser_main_runner.cc157
-rw-r--r--chromium/content/browser/browser_plugin/OWNERS1
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_embedder.cc232
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_embedder.h139
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc94
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h44
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest.cc1667
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest.h534
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc55
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h59
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc274
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h137
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc814
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_host_factory.h41
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc72
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_message_filter.h45
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h34
-rw-r--r--chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm23
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc40
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h47
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc250
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_guest.h108
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc35
-rw-r--r--chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h51
-rw-r--r--chromium/content/browser/browser_process_sub_thread.cc75
-rw-r--r--chromium/content/browser/browser_process_sub_thread.h61
-rw-r--r--chromium/content/browser/browser_thread_impl.cc482
-rw-r--r--chromium/content/browser/browser_thread_impl.h73
-rw-r--r--chromium/content/browser/browser_thread_unittest.cc118
-rw-r--r--chromium/content/browser/browser_url_handler_impl.cc135
-rw-r--r--chromium/content/browser/browser_url_handler_impl.h55
-rw-r--r--chromium/content/browser/browser_url_handler_impl_unittest.cc82
-rw-r--r--chromium/content/browser/browsing_instance.cc89
-rw-r--r--chromium/content/browser/browsing_instance.h110
-rw-r--r--chromium/content/browser/byte_stream.cc441
-rw-r--r--chromium/content/browser/byte_stream.h199
-rw-r--r--chromium/content/browser/byte_stream_unittest.cc582
-rw-r--r--chromium/content/browser/cert_store_impl.cc182
-rw-r--r--chromium/content/browser/cert_store_impl.h76
-rw-r--r--chromium/content/browser/child_process_launcher.cc502
-rw-r--r--chromium/content/browser/child_process_launcher.h85
-rw-r--r--chromium/content/browser/child_process_security_policy_browsertest.cc60
-rw-r--r--chromium/content/browser/child_process_security_policy_impl.cc831
-rw-r--r--chromium/content/browser/child_process_security_policy_impl.h274
-rw-r--r--chromium/content/browser/child_process_security_policy_unittest.cc717
-rw-r--r--chromium/content/browser/cross_site_request_manager.cc42
-rw-r--r--chromium/content/browser/cross_site_request_manager.h64
-rw-r--r--chromium/content/browser/database_browsertest.cc270
-rw-r--r--chromium/content/browser/device_monitor_linux.cc87
-rw-r--r--chromium/content/browser/device_monitor_linux.h44
-rw-r--r--chromium/content/browser/device_monitor_mac.h29
-rw-r--r--chromium/content/browser/device_monitor_mac.mm108
-rw-r--r--chromium/content/browser/device_orientation/DEPS3
-rw-r--r--chromium/content/browser/device_orientation/OWNERS4
-rw-r--r--chromium/content/browser/device_orientation/accelerometer_mac.cc105
-rw-r--r--chromium/content/browser/device_orientation/accelerometer_mac.h40
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher.h24
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_impl_android.cc198
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_impl_android.h98
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_impl_android_unittest.cc107
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_impl_win.cc193
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_impl_win.h63
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_orientation_android.cc37
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_orientation_android.h32
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_shared_memory.h56
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_shared_memory_android.cc40
-rw-r--r--chromium/content/browser/device_orientation/data_fetcher_shared_memory_default.cc43
-rw-r--r--chromium/content/browser/device_orientation/device_data.h43
-rw-r--r--chromium/content/browser/device_orientation/device_motion_message_filter.cc61
-rw-r--r--chromium/content/browser/device_orientation/device_motion_message_filter.h38
-rw-r--r--chromium/content/browser/device_orientation/device_motion_provider.cc172
-rw-r--r--chromium/content/browser/device_orientation/device_motion_provider.h56
-rw-r--r--chromium/content/browser/device_orientation/device_motion_provider_unittest.cc98
-rw-r--r--chromium/content/browser/device_orientation/device_motion_service.cc65
-rw-r--r--chromium/content/browser/device_orientation/device_motion_service.h65
-rw-r--r--chromium/content/browser/device_orientation/device_orientation_browsertest.cc79
-rw-r--r--chromium/content/browser/device_orientation/device_orientation_message_filter.cc70
-rw-r--r--chromium/content/browser/device_orientation/device_orientation_message_filter.h37
-rw-r--r--chromium/content/browser/device_orientation/message_filter.cc38
-rw-r--r--chromium/content/browser/device_orientation/message_filter.h44
-rw-r--r--chromium/content/browser/device_orientation/observer_delegate.cc49
-rw-r--r--chromium/content/browser/device_orientation/observer_delegate.h45
-rw-r--r--chromium/content/browser/device_orientation/orientation.cc70
-rw-r--r--chromium/content/browser/device_orientation/orientation.h73
-rw-r--r--chromium/content/browser/device_orientation/orientation_message_filter.cc32
-rw-r--r--chromium/content/browser/device_orientation/orientation_message_filter.h30
-rw-r--r--chromium/content/browser/device_orientation/provider.cc59
-rw-r--r--chromium/content/browser/device_orientation/provider.h67
-rw-r--r--chromium/content/browser/device_orientation/provider_impl.cc292
-rw-r--r--chromium/content/browser/device_orientation/provider_impl.h81
-rw-r--r--chromium/content/browser/device_orientation/provider_unittest.cc591
-rw-r--r--chromium/content/browser/devtools/DEPS4
-rw-r--r--chromium/content/browser/devtools/OWNERS2
-rw-r--r--chromium/content/browser/devtools/devtools_agent_host_impl.cc68
-rw-r--r--chromium/content/browser/devtools/devtools_agent_host_impl.h69
-rw-r--r--chromium/content/browser/devtools/devtools_browser_target.cc141
-rw-r--r--chromium/content/browser/devtools/devtools_browser_target.h79
-rw-r--r--chromium/content/browser/devtools/devtools_external_agent_proxy_impl.cc73
-rw-r--r--chromium/content/browser/devtools/devtools_external_agent_proxy_impl.h35
-rw-r--r--chromium/content/browser/devtools/devtools_frontend_host.cc172
-rw-r--r--chromium/content/browser/devtools/devtools_frontend_host.h66
-rw-r--r--chromium/content/browser/devtools/devtools_http_handler_impl.cc866
-rw-r--r--chromium/content/browser/devtools/devtools_http_handler_impl.h137
-rw-r--r--chromium/content/browser/devtools/devtools_http_handler_unittest.cc118
-rw-r--r--chromium/content/browser/devtools/devtools_manager_impl.cc191
-rw-r--r--chromium/content/browser/devtools/devtools_manager_impl.h104
-rw-r--r--chromium/content/browser/devtools/devtools_manager_unittest.cc290
-rw-r--r--chromium/content/browser/devtools/devtools_netlog_observer.cc328
-rw-r--r--chromium/content/browser/devtools/devtools_netlog_observer.h70
-rw-r--r--chromium/content/browser/devtools/devtools_protocol.cc284
-rw-r--r--chromium/content/browser/devtools/devtools_protocol.h178
-rw-r--r--chromium/content/browser/devtools/devtools_protocol_constants.cc105
-rw-r--r--chromium/content/browser/devtools/devtools_protocol_constants.h111
-rw-r--r--chromium/content/browser/devtools/devtools_resources.gyp49
-rw-r--r--chromium/content/browser/devtools/devtools_tracing_handler.cc113
-rw-r--r--chromium/content/browser/devtools/devtools_tracing_handler.h44
-rw-r--r--chromium/content/browser/devtools/ipc_devtools_agent_host.cc42
-rw-r--r--chromium/content/browser/devtools/ipc_devtools_agent_host.h36
-rw-r--r--chromium/content/browser/devtools/render_view_devtools_agent_host.cc351
-rw-r--r--chromium/content/browser/devtools/render_view_devtools_agent_host.h80
-rw-r--r--chromium/content/browser/devtools/renderer_overrides_handler.cc338
-rw-r--r--chromium/content/browser/devtools/renderer_overrides_handler.h62
-rw-r--r--chromium/content/browser/devtools/tethering_handler.cc308
-rw-r--r--chromium/content/browser/devtools/tethering_handler.h41
-rw-r--r--chromium/content/browser/devtools/worker_devtools_manager.cc421
-rw-r--r--chromium/content/browser/devtools/worker_devtools_manager.h131
-rw-r--r--chromium/content/browser/devtools/worker_devtools_message_filter.cc50
-rw-r--r--chromium/content/browser/devtools/worker_devtools_message_filter.h35
-rw-r--r--chromium/content/browser/dom_storage/DEPS3
-rw-r--r--chromium/content/browser/dom_storage/OWNERS2
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_area.cc403
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_area.h138
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_area_unittest.cc474
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_browsertest.cc52
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_context_impl.cc422
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_context_impl.h234
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_context_impl_unittest.cc263
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_context_wrapper.cc173
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_context_wrapper.h74
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_database.cc295
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_database.h119
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_database_adapter.h29
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_database_unittest.cc393
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_host.cc158
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_host.h72
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_message_filter.cc213
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_message_filter.h95
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_namespace.cc187
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_namespace.h108
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_session.cc84
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_session.h65
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_task_runner.cc107
-rw-r--r--chromium/content/browser/dom_storage/dom_storage_task_runner.h135
-rw-r--r--chromium/content/browser/dom_storage/local_storage_database_adapter.cc40
-rw-r--r--chromium/content/browser/dom_storage/local_storage_database_adapter.h50
-rw-r--r--chromium/content/browser/dom_storage/session_storage_database.cc680
-rw-r--r--chromium/content/browser/dom_storage/session_storage_database.h205
-rw-r--r--chromium/content/browser/dom_storage/session_storage_database_adapter.cc32
-rw-r--r--chromium/content/browser/dom_storage/session_storage_database_adapter.h35
-rw-r--r--chromium/content/browser/dom_storage/session_storage_database_unittest.cc798
-rw-r--r--chromium/content/browser/dom_storage/session_storage_namespace_impl.cc61
-rw-r--r--chromium/content/browser/dom_storage/session_storage_namespace_impl.h57
-rw-r--r--chromium/content/browser/download/OWNERS5
-rw-r--r--chromium/content/browser/download/base_file.cc371
-rw-r--r--chromium/content/browser/download/base_file.h183
-rw-r--r--chromium/content/browser/download/base_file_linux.cc20
-rw-r--r--chromium/content/browser/download/base_file_mac.cc21
-rw-r--r--chromium/content/browser/download/base_file_posix.cc44
-rw-r--r--chromium/content/browser/download/base_file_unittest.cc634
-rw-r--r--chromium/content/browser/download/base_file_win.cc367
-rw-r--r--chromium/content/browser/download/download_browsertest.cc1641
-rw-r--r--chromium/content/browser/download/download_create_info.cc57
-rw-r--r--chromium/content/browser/download/download_create_info.h100
-rw-r--r--chromium/content/browser/download/download_file.h93
-rw-r--r--chromium/content/browser/download/download_file_factory.cc33
-rw-r--r--chromium/content/browser/download/download_file_factory.h44
-rw-r--r--chromium/content/browser/download/download_file_impl.cc321
-rw-r--r--chromium/content/browser/download/download_file_impl.h116
-rw-r--r--chromium/content/browser/download/download_file_unittest.cc606
-rw-r--r--chromium/content/browser/download/download_interrupt_reasons_impl.cc110
-rw-r--r--chromium/content/browser/download/download_interrupt_reasons_impl.h27
-rw-r--r--chromium/content/browser/download/download_item_factory.h77
-rw-r--r--chromium/content/browser/download/download_item_impl.cc1711
-rw-r--r--chromium/content/browser/download/download_item_impl.h546
-rw-r--r--chromium/content/browser/download/download_item_impl_delegate.cc78
-rw-r--r--chromium/content/browser/download/download_item_impl_delegate.h102
-rw-r--r--chromium/content/browser/download/download_item_impl_unittest.cc1276
-rw-r--r--chromium/content/browser/download/download_manager_impl.cc707
-rw-r--r--chromium/content/browser/download/download_manager_impl.h195
-rw-r--r--chromium/content/browser/download/download_manager_impl_unittest.cc685
-rw-r--r--chromium/content/browser/download/download_net_log_parameters.cc208
-rw-r--r--chromium/content/browser/download/download_net_log_parameters.h100
-rw-r--r--chromium/content/browser/download/download_request_handle.cc89
-rw-r--r--chromium/content/browser/download/download_request_handle.h88
-rw-r--r--chromium/content/browser/download/download_resource_handler.cc493
-rw-r--r--chromium/content/browser/download/download_resource_handler.h144
-rw-r--r--chromium/content/browser/download/download_stats.cc462
-rw-r--r--chromium/content/browser/download/download_stats.h211
-rw-r--r--chromium/content/browser/download/drag_download_file.cc242
-rw-r--r--chromium/content/browser/download/drag_download_file.h77
-rw-r--r--chromium/content/browser/download/drag_download_file_browsertest.cc137
-rw-r--r--chromium/content/browser/download/drag_download_util.cc121
-rw-r--r--chromium/content/browser/download/drag_download_util.h69
-rw-r--r--chromium/content/browser/download/file_metadata_linux.cc43
-rw-r--r--chromium/content/browser/download/file_metadata_linux.h34
-rw-r--r--chromium/content/browser/download/file_metadata_mac.h31
-rw-r--r--chromium/content/browser/download/file_metadata_mac.mm168
-rw-r--r--chromium/content/browser/download/file_metadata_unittest_linux.cc164
-rw-r--r--chromium/content/browser/download/mhtml_generation_browsertest.cc73
-rw-r--r--chromium/content/browser/download/mhtml_generation_manager.cc170
-rw-r--r--chromium/content/browser/download/mhtml_generation_manager.h102
-rw-r--r--chromium/content/browser/download/mock_download_file.cc30
-rw-r--r--chromium/content/browser/download/mock_download_file.h56
-rw-r--r--chromium/content/browser/download/rate_estimator.cc122
-rw-r--r--chromium/content/browser/download/rate_estimator.h52
-rw-r--r--chromium/content/browser/download/rate_estimator_unittest.cc58
-rw-r--r--chromium/content/browser/download/save_file.cc88
-rw-r--r--chromium/content/browser/download/save_file.h59
-rw-r--r--chromium/content/browser/download/save_file_manager.cc534
-rw-r--r--chromium/content/browser/download/save_file_manager.h252
-rw-r--r--chromium/content/browser/download/save_file_resource_handler.cc123
-rw-r--r--chromium/content/browser/download/save_file_resource_handler.h93
-rw-r--r--chromium/content/browser/download/save_item.cc133
-rw-r--r--chromium/content/browser/download/save_item.h115
-rw-r--r--chromium/content/browser/download/save_package.cc1455
-rw-r--r--chromium/content/browser/download/save_package.h340
-rw-r--r--chromium/content/browser/download/save_package_browsertest.cc63
-rw-r--r--chromium/content/browser/download/save_package_unittest.cc440
-rw-r--r--chromium/content/browser/download/save_types.cc34
-rw-r--r--chromium/content/browser/download/save_types.h70
-rw-r--r--chromium/content/browser/fileapi/OWNERS4
-rw-r--r--chromium/content/browser/fileapi/browser_file_system_helper.cc151
-rw-r--r--chromium/content/browser/fileapi/browser_file_system_helper.h51
-rw-r--r--chromium/content/browser/fileapi/chrome_blob_storage_context.cc57
-rw-r--r--chromium/content/browser/fileapi/chrome_blob_storage_context.h65
-rw-r--r--chromium/content/browser/fileapi/file_system_browsertest.cc93
-rw-r--r--chromium/content/browser/fileapi/fileapi_message_filter.cc863
-rw-r--r--chromium/content/browser/fileapi/fileapi_message_filter.h248
-rw-r--r--chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc357
-rw-r--r--chromium/content/browser/font_list_async.cc47
-rw-r--r--chromium/content/browser/gamepad/OWNERS1
-rw-r--r--chromium/content/browser/gamepad/gamepad_data_fetcher.h26
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc19
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h55
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc257
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h56
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h106
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm441
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc498
-rw-r--r--chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h100
-rw-r--r--chromium/content/browser/gamepad/gamepad_provider.cc220
-rw-r--r--chromium/content/browser/gamepad/gamepad_provider.h130
-rw-r--r--chromium/content/browser/gamepad/gamepad_provider_unittest.cc158
-rw-r--r--chromium/content/browser/gamepad/gamepad_service.cc69
-rw-r--r--chromium/content/browser/gamepad/gamepad_service.h77
-rw-r--r--chromium/content/browser/gamepad/gamepad_standard_mappings.h61
-rw-r--r--chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc164
-rw-r--r--chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm219
-rw-r--r--chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc124
-rw-r--r--chromium/content/browser/gamepad/gamepad_test_helpers.cc55
-rw-r--r--chromium/content/browser/gamepad/gamepad_test_helpers.h84
-rw-r--r--chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc625
-rw-r--r--chromium/content/browser/gamepad/xbox_data_fetcher_mac.h167
-rw-r--r--chromium/content/browser/geolocation/DEPS4
-rw-r--r--chromium/content/browser/geolocation/OWNERS6
-rw-r--r--chromium/content/browser/geolocation/device_data_provider.cc54
-rw-r--r--chromium/content/browser/geolocation/device_data_provider.h305
-rw-r--r--chromium/content/browser/geolocation/device_data_provider_unittest.cc41
-rw-r--r--chromium/content/browser/geolocation/empty_device_data_provider.cc18
-rw-r--r--chromium/content/browser/geolocation/empty_device_data_provider.h36
-rw-r--r--chromium/content/browser/geolocation/fake_access_token_store.cc55
-rw-r--r--chromium/content/browser/geolocation/fake_access_token_store.h51
-rw-r--r--chromium/content/browser/geolocation/geolocation_dispatcher_host.cc246
-rw-r--r--chromium/content/browser/geolocation/geolocation_dispatcher_host.h31
-rw-r--r--chromium/content/browser/geolocation/geolocation_provider_impl.cc236
-rw-r--r--chromium/content/browser/geolocation/geolocation_provider_impl.h114
-rw-r--r--chromium/content/browser/geolocation/geolocation_provider_unittest.cc249
-rw-r--r--chromium/content/browser/geolocation/gps_location_provider_linux.cc302
-rw-r--r--chromium/content/browser/geolocation/gps_location_provider_linux.h105
-rw-r--r--chromium/content/browser/geolocation/gps_location_provider_unittest_linux.cc208
-rw-r--r--chromium/content/browser/geolocation/location_api_adapter_android.cc163
-rw-r--r--chromium/content/browser/geolocation/location_api_adapter_android.h81
-rw-r--r--chromium/content/browser/geolocation/location_arbitrator.h37
-rw-r--r--chromium/content/browser/geolocation/location_arbitrator_impl.cc204
-rw-r--r--chromium/content/browser/geolocation/location_arbitrator_impl.h108
-rw-r--r--chromium/content/browser/geolocation/location_arbitrator_impl_unittest.cc330
-rw-r--r--chromium/content/browser/geolocation/location_provider_android.cc51
-rw-r--r--chromium/content/browser/geolocation/location_provider_android.h38
-rw-r--r--chromium/content/browser/geolocation/location_provider_base.cc28
-rw-r--r--chromium/content/browser/geolocation/location_provider_base.h35
-rw-r--r--chromium/content/browser/geolocation/mock_location_arbitrator.cc33
-rw-r--r--chromium/content/browser/geolocation/mock_location_arbitrator.h38
-rw-r--r--chromium/content/browser/geolocation/mock_location_provider.cc136
-rw-r--r--chromium/content/browser/geolocation/mock_location_provider.h67
-rw-r--r--chromium/content/browser/geolocation/network_location_provider.cc271
-rw-r--r--chromium/content/browser/geolocation/network_location_provider.h146
-rw-r--r--chromium/content/browser/geolocation/network_location_provider_unittest.cc569
-rw-r--r--chromium/content/browser/geolocation/network_location_request.cc392
-rw-r--r--chromium/content/browser/geolocation/network_location_request.h82
-rw-r--r--chromium/content/browser/geolocation/osx_wifi.h102
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_chromeos.cc176
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_chromeos.h68
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_chromeos_unittest.cc99
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_common.cc113
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_common.h130
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_common_unittest.cc233
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_common_win.cc56
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_common_win.h24
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_corewlan_mac.mm187
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_linux.cc381
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_linux.h39
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_linux_unittest.cc231
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_mac.cc195
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_mac.h35
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_unittest_win.cc23
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_win.cc640
-rw-r--r--chromium/content/browser/geolocation/wifi_data_provider_win.h30
-rw-r--r--chromium/content/browser/gpu.sb24
-rw-r--r--chromium/content/browser/gpu/OWNERS4
-rw-r--r--chromium/content/browser/gpu/browser_gpu_channel_host_factory.cc321
-rw-r--r--chromium/content/browser/gpu/browser_gpu_channel_host_factory.h110
-rw-r--r--chromium/content/browser/gpu/compositor_util.cc117
-rwxr-xr-xchromium/content/browser/gpu/generate_webgl_conformance_test_list.py108
-rw-r--r--chromium/content/browser/gpu/gpu_crash_browsertest.cc76
-rw-r--r--chromium/content/browser/gpu/gpu_data_manager_impl.cc272
-rw-r--r--chromium/content/browser/gpu/gpu_data_manager_impl.h220
-rw-r--r--chromium/content/browser/gpu/gpu_data_manager_impl_private.cc1254
-rw-r--r--chromium/content/browser/gpu/gpu_data_manager_impl_private.h255
-rw-r--r--chromium/content/browser/gpu/gpu_data_manager_impl_private_unittest.cc662
-rw-r--r--chromium/content/browser/gpu/gpu_functional_browsertest.cc143
-rw-r--r--chromium/content/browser/gpu/gpu_info_browsertest.cc110
-rw-r--r--chromium/content/browser/gpu/gpu_internals_ui.cc661
-rw-r--r--chromium/content/browser/gpu/gpu_internals_ui.h23
-rw-r--r--chromium/content/browser/gpu/gpu_ipc_browsertests.cc43
-rw-r--r--chromium/content/browser/gpu/gpu_memory_test.cc241
-rw-r--r--chromium/content/browser/gpu/gpu_pixel_browsertest.cc568
-rw-r--r--chromium/content/browser/gpu/gpu_process_host.cc1285
-rw-r--r--chromium/content/browser/gpu/gpu_process_host.h275
-rw-r--r--chromium/content/browser/gpu/gpu_process_host_ui_shim.cc401
-rw-r--r--chromium/content/browser/gpu/gpu_process_host_ui_shim.h118
-rw-r--r--chromium/content/browser/gpu/gpu_surface_tracker.cc200
-rw-r--r--chromium/content/browser/gpu/gpu_surface_tracker.h138
-rw-r--r--chromium/content/browser/gpu/shader_disk_cache.cc617
-rw-r--r--chromium/content/browser/gpu/shader_disk_cache.h154
-rw-r--r--chromium/content/browser/gpu/shader_disk_cache_unittest.cc75
-rw-r--r--chromium/content/browser/gpu/test_support_gpu.gypi70
-rw-r--r--chromium/content/browser/gpu/webgl_conformance_test.cc96
-rw-r--r--chromium/content/browser/gpu/webgl_conformance_test_list_autogen.h1080
-rw-r--r--chromium/content/browser/histogram_controller.cc121
-rw-r--r--chromium/content/browser/histogram_controller.h72
-rw-r--r--chromium/content/browser/histogram_internals_request_job.cc71
-rw-r--r--chromium/content/browser/histogram_internals_request_job.h36
-rw-r--r--chromium/content/browser/histogram_message_filter.cc68
-rw-r--r--chromium/content/browser/histogram_message_filter.h42
-rw-r--r--chromium/content/browser/histogram_subscriber.h33
-rw-r--r--chromium/content/browser/histogram_synchronizer.cc348
-rw-r--r--chromium/content/browser/histogram_synchronizer.h154
-rw-r--r--chromium/content/browser/host_zoom_map_impl.cc249
-rw-r--r--chromium/content/browser/host_zoom_map_impl.h106
-rw-r--r--chromium/content/browser/host_zoom_map_impl_unittest.cc60
-rw-r--r--chromium/content/browser/indexed_db/DEPS3
-rw-r--r--chromium/content/browser/indexed_db/OWNERS4
-rw-r--r--chromium/content/browser/indexed_db/indexed_db.h34
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_backing_store.cc2640
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_backing_store.h328
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_backing_store_unittest.cc418
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_browsertest.cc434
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_callbacks.cc336
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_callbacks.h127
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc154
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_connection.cc31
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_connection.h39
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_context_impl.cc539
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_context_impl.h145
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_cursor.cc207
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_cursor.h67
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database.cc1951
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database.h260
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database_callbacks.cc68
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database_callbacks.h43
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database_error.h34
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_database_unittest.cc224
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_dispatcher_host.cc857
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_dispatcher_host.h243
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_factory.cc200
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_factory.h79
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_fake_backing_store.cc155
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_fake_backing_store.h120
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_index_writer.cc169
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_index_writer.h76
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_internals_ui.cc357
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_internals_ui.h79
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_leveldb_coding.cc1846
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_leveldb_coding.h427
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc848
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_metadata.cc39
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_metadata.h84
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_quota_client.cc178
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_quota_client.h55
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_quota_client_unittest.cc244
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_tracing.h11
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_transaction.cc297
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_transaction.h144
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.cc156
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.h55
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_unittest.cc193
-rw-r--r--chromium/content/browser/indexed_db/leveldb/avltree.h977
-rw-r--r--chromium/content/browser/indexed_db/leveldb/fixed_array.h63
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_comparator.h23
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_database.cc470
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_database.h79
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_iterator.h26
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_transaction.cc476
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_transaction.h177
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_unittest.cc202
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.cc37
-rw-r--r--chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.h38
-rw-r--r--chromium/content/browser/indexed_db/list_set.h161
-rw-r--r--chromium/content/browser/indexed_db/list_set_unittest.cc240
-rw-r--r--chromium/content/browser/loader/OWNERS1
-rw-r--r--chromium/content/browser/loader/async_resource_handler.cc365
-rw-r--r--chromium/content/browser/loader/async_resource_handler.h99
-rw-r--r--chromium/content/browser/loader/buffered_resource_handler.cc473
-rw-r--r--chromium/content/browser/loader/buffered_resource_handler.h113
-rw-r--r--chromium/content/browser/loader/certificate_resource_handler.cc151
-rw-r--r--chromium/content/browser/loader/certificate_resource_handler.h100
-rw-r--r--chromium/content/browser/loader/cross_site_resource_handler.cc219
-rw-r--r--chromium/content/browser/loader/cross_site_resource_handler.h75
-rw-r--r--chromium/content/browser/loader/doomed_resource_handler.cc78
-rw-r--r--chromium/content/browser/loader/doomed_resource_handler.h60
-rw-r--r--chromium/content/browser/loader/global_routing_id.h42
-rw-r--r--chromium/content/browser/loader/layered_resource_handler.cc83
-rw-r--r--chromium/content/browser/loader/layered_resource_handler.h47
-rw-r--r--chromium/content/browser/loader/offline_policy.cc96
-rw-r--r--chromium/content/browser/loader/offline_policy.h60
-rw-r--r--chromium/content/browser/loader/offline_policy_unittest.cc96
-rw-r--r--chromium/content/browser/loader/power_save_block_resource_throttle.cc43
-rw-r--r--chromium/content/browser/loader/power_save_block_resource_throttle.h39
-rw-r--r--chromium/content/browser/loader/redirect_to_file_resource_handler.cc269
-rw-r--r--chromium/content/browser/loader/redirect_to_file_resource_handler.h107
-rw-r--r--chromium/content/browser/loader/render_view_host_tracker.cc74
-rw-r--r--chromium/content/browser/loader/render_view_host_tracker.h53
-rw-r--r--chromium/content/browser/loader/resource_buffer.cc181
-rw-r--r--chromium/content/browser/loader/resource_buffer.h128
-rw-r--r--chromium/content/browser/loader/resource_buffer_unittest.cc137
-rw-r--r--chromium/content/browser/loader/resource_dispatcher_host_browsertest.cc467
-rw-r--r--chromium/content/browser/loader/resource_dispatcher_host_impl.cc1880
-rw-r--r--chromium/content/browser/loader/resource_dispatcher_host_impl.h515
-rw-r--r--chromium/content/browser/loader/resource_dispatcher_host_unittest.cc2007
-rw-r--r--chromium/content/browser/loader/resource_handler.cc13
-rw-r--r--chromium/content/browser/loader/resource_handler.h114
-rw-r--r--chromium/content/browser/loader/resource_loader.cc675
-rw-r--r--chromium/content/browser/loader/resource_loader.h146
-rw-r--r--chromium/content/browser/loader/resource_loader_delegate.h49
-rw-r--r--chromium/content/browser/loader/resource_loader_unittest.cc252
-rw-r--r--chromium/content/browser/loader/resource_message_delegate.cc24
-rw-r--r--chromium/content/browser/loader/resource_message_delegate.h42
-rw-r--r--chromium/content/browser/loader/resource_message_filter.cc57
-rw-r--r--chromium/content/browser/loader/resource_message_filter.h108
-rw-r--r--chromium/content/browser/loader/resource_request_info_impl.cc234
-rw-r--r--chromium/content/browser/loader/resource_request_info_impl.h157
-rw-r--r--chromium/content/browser/loader/resource_scheduler.cc383
-rw-r--r--chromium/content/browser/loader/resource_scheduler.h126
-rw-r--r--chromium/content/browser/loader/resource_scheduler_filter.cc51
-rw-r--r--chromium/content/browser/loader/resource_scheduler_filter.h33
-rw-r--r--chromium/content/browser/loader/resource_scheduler_unittest.cc436
-rw-r--r--chromium/content/browser/loader/stream_resource_handler.cc118
-rw-r--r--chromium/content/browser/loader/stream_resource_handler.h83
-rw-r--r--chromium/content/browser/loader/sync_resource_handler.cc135
-rw-r--r--chromium/content/browser/loader/sync_resource_handler.h75
-rw-r--r--chromium/content/browser/loader/throttling_resource_handler.cc184
-rw-r--r--chromium/content/browser/loader/throttling_resource_handler.h72
-rw-r--r--chromium/content/browser/loader/transfer_navigation_resource_throttle.cc93
-rw-r--r--chromium/content/browser/loader/transfer_navigation_resource_throttle.h38
-rw-r--r--chromium/content/browser/loader/upload_data_stream_builder.cc146
-rw-r--r--chromium/content/browser/loader/upload_data_stream_builder.h54
-rw-r--r--chromium/content/browser/loader/upload_data_stream_builder_unittest.cc266
-rw-r--r--chromium/content/browser/mach_broker_mac.h121
-rw-r--r--chromium/content/browser/mach_broker_mac.mm309
-rw-r--r--chromium/content/browser/mach_broker_mac_unittest.cc68
-rw-r--r--chromium/content/browser/media/OWNERS11
-rw-r--r--chromium/content/browser/media/encrypted_media_browsertest.cc310
-rw-r--r--chromium/content/browser/media/media_browsertest.cc245
-rw-r--r--chromium/content/browser/media/media_browsertest.h35
-rw-r--r--chromium/content/browser/media/media_internals.cc136
-rw-r--r--chromium/content/browser/media/media_internals.h96
-rw-r--r--chromium/content/browser/media/media_internals_handler.cc46
-rw-r--r--chromium/content/browser/media/media_internals_handler.h43
-rw-r--r--chromium/content/browser/media/media_internals_proxy.cc182
-rw-r--r--chromium/content/browser/media/media_internals_proxy.h90
-rw-r--r--chromium/content/browser/media/media_internals_ui.cc53
-rw-r--r--chromium/content/browser/media/media_internals_ui.h23
-rw-r--r--chromium/content/browser/media/media_internals_unittest.cc134
-rw-r--r--chromium/content/browser/media/media_source_browsertest.cc57
-rw-r--r--chromium/content/browser/media/webrtc_browsertest.cc301
-rw-r--r--chromium/content/browser/media/webrtc_identity_store.cc307
-rw-r--r--chromium/content/browser/media/webrtc_identity_store.h114
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_backend.cc546
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_backend.h111
-rw-r--r--chromium/content/browser/media/webrtc_identity_store_unittest.cc278
-rw-r--r--chromium/content/browser/media/webrtc_internals.cc243
-rw-r--r--chromium/content/browser/media/webrtc_internals.h117
-rw-r--r--chromium/content/browser/media/webrtc_internals_browsertest.cc709
-rw-r--r--chromium/content/browser/media/webrtc_internals_message_handler.cc78
-rw-r--r--chromium/content/browser/media/webrtc_internals_message_handler.h45
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui.cc44
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui.h23
-rw-r--r--chromium/content/browser/media/webrtc_internals_ui_observer.h28
-rw-r--r--chromium/content/browser/media/webrtc_internals_unittest.cc158
-rw-r--r--chromium/content/browser/media_devices_monitor.cc29
-rw-r--r--chromium/content/browser/mime_registry_message_filter.cc48
-rw-r--r--chromium/content/browser/mime_registry_message_filter.h34
-rw-r--r--chromium/content/browser/net/OWNERS1
-rw-r--r--chromium/content/browser/net/browser_online_state_observer.cc29
-rw-r--r--chromium/content/browser/net/browser_online_state_observer.h31
-rw-r--r--chromium/content/browser/net/sqlite_persistent_cookie_store.cc1221
-rw-r--r--chromium/content/browser/net/sqlite_persistent_cookie_store.h77
-rw-r--r--chromium/content/browser/net/sqlite_persistent_cookie_store_perftest.cc142
-rw-r--r--chromium/content/browser/net/sqlite_persistent_cookie_store_unittest.cc488
-rw-r--r--chromium/content/browser/net/view_blob_internals_job_factory.cc29
-rw-r--r--chromium/content/browser/net/view_blob_internals_job_factory.h32
-rw-r--r--chromium/content/browser/net/view_http_cache_job_factory.cc203
-rw-r--r--chromium/content/browser/net/view_http_cache_job_factory.h27
-rw-r--r--chromium/content/browser/notification_service_impl.cc159
-rw-r--r--chromium/content/browser/notification_service_impl.h95
-rw-r--r--chromium/content/browser/notification_service_impl_unittest.cc173
-rw-r--r--chromium/content/browser/pepper_flash_settings_helper_impl.cc72
-rw-r--r--chromium/content/browser/pepper_flash_settings_helper_impl.h44
-rw-r--r--chromium/content/browser/plugin_browsertest.cc462
-rw-r--r--chromium/content/browser/plugin_data_remover_impl.cc318
-rw-r--r--chromium/content/browser/plugin_data_remover_impl.h47
-rw-r--r--chromium/content/browser/plugin_data_remover_impl_browsertest.cc60
-rw-r--r--chromium/content/browser/plugin_loader_posix.cc201
-rw-r--r--chromium/content/browser/plugin_loader_posix.h127
-rw-r--r--chromium/content/browser/plugin_loader_posix_unittest.cc373
-rw-r--r--chromium/content/browser/plugin_process_host.cc417
-rw-r--r--chromium/content/browser/plugin_process_host.h192
-rw-r--r--chromium/content/browser/plugin_process_host_mac.cc114
-rw-r--r--chromium/content/browser/plugin_service_impl.cc846
-rw-r--r--chromium/content/browser/plugin_service_impl.h254
-rw-r--r--chromium/content/browser/plugin_service_impl_browsertest.cc377
-rw-r--r--chromium/content/browser/power_monitor_message_broadcaster.cc39
-rw-r--r--chromium/content/browser/power_monitor_message_broadcaster.h41
-rw-r--r--chromium/content/browser/power_monitor_message_broadcaster_unittest.cc109
-rw-r--r--chromium/content/browser/power_save_blocker_android.cc87
-rw-r--r--chromium/content/browser/power_save_blocker_android.h11
-rw-r--r--chromium/content/browser/power_save_blocker_chromeos.cc84
-rw-r--r--chromium/content/browser/power_save_blocker_impl.cc18
-rw-r--r--chromium/content/browser/power_save_blocker_impl.h46
-rw-r--r--chromium/content/browser/power_save_blocker_mac.cc112
-rw-r--r--chromium/content/browser/power_save_blocker_ozone.cc34
-rw-r--r--chromium/content/browser/power_save_blocker_win.cc174
-rw-r--r--chromium/content/browser/power_save_blocker_x11.cc343
-rw-r--r--chromium/content/browser/ppapi_plugin_process_host.cc438
-rw-r--r--chromium/content/browser/ppapi_plugin_process_host.h198
-rw-r--r--chromium/content/browser/profiler_controller_impl.cc126
-rw-r--r--chromium/content/browser/profiler_controller_impl.h62
-rw-r--r--chromium/content/browser/profiler_message_filter.cc56
-rw-r--r--chromium/content/browser/profiler_message_filter.h50
-rw-r--r--chromium/content/browser/quota_dispatcher_host.cc258
-rw-r--r--chromium/content/browser/quota_dispatcher_host.h68
-rw-r--r--chromium/content/browser/renderer_host/DEPS30
-rw-r--r--chromium/content/browser/renderer_host/OWNERS21
-rw-r--r--chromium/content/browser/renderer_host/backing_store.cc21
-rw-r--r--chromium/content/browser/renderer_host/backing_store.h93
-rw-r--r--chromium/content/browser/renderer_host/backing_store_aura.cc176
-rw-r--r--chromium/content/browser/renderer_host/backing_store_aura.h66
-rw-r--r--chromium/content/browser/renderer_host/backing_store_gtk.cc692
-rw-r--r--chromium/content/browser/renderer_host/backing_store_gtk.h107
-rw-r--r--chromium/content/browser/renderer_host/backing_store_mac.h76
-rw-r--r--chromium/content/browser/renderer_host/backing_store_mac.mm297
-rw-r--r--chromium/content/browser/renderer_host/backing_store_manager.cc282
-rw-r--r--chromium/content/browser/renderer_host/backing_store_manager.h86
-rw-r--r--chromium/content/browser/renderer_host/backing_store_win.cc183
-rw-r--r--chromium/content/browser/renderer_host/backing_store_win.h60
-rw-r--r--chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.cc61
-rw-r--r--chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h37
-rw-r--r--chromium/content/browser/renderer_host/clipboard_message_filter.cc245
-rw-r--r--chromium/content/browser/renderer_host/clipboard_message_filter.h72
-rw-r--r--chromium/content/browser/renderer_host/clipboard_message_filter_mac.mm56
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_context_mac.h71
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_context_mac.mm146
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.h40
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.mm133
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_mac.h365
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_mac.mm1066
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc450
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h81
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc300
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.h123
-rw-r--r--chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc533
-rw-r--r--chromium/content/browser/renderer_host/compositor_impl_android.cc506
-rw-r--r--chromium/content/browser/renderer_host/compositor_impl_android.h125
-rw-r--r--chromium/content/browser/renderer_host/database_message_filter.cc362
-rw-r--r--chromium/content/browser/renderer_host/database_message_filter.h102
-rw-r--r--chromium/content/browser/renderer_host/dip_util.cc66
-rw-r--r--chromium/content/browser/renderer_host/dip_util.h43
-rw-r--r--chromium/content/browser/renderer_host/file_utilities_message_filter.cc55
-rw-r--r--chromium/content/browser/renderer_host/file_utilities_message_filter.h51
-rw-r--r--chromium/content/browser/renderer_host/gamepad_browser_message_filter.cc60
-rw-r--r--chromium/content/browser/renderer_host/gamepad_browser_message_filter.h38
-rw-r--r--chromium/content/browser/renderer_host/gpu_message_filter.cc298
-rw-r--r--chromium/content/browser/renderer_host/gpu_message_filter.h97
-rw-r--r--chromium/content/browser/renderer_host/gtk_im_context_wrapper.cc665
-rw-r--r--chromium/content/browser/renderer_host/gtk_im_context_wrapper.h201
-rw-r--r--chromium/content/browser/renderer_host/gtk_key_bindings_handler.cc293
-rw-r--r--chromium/content/browser/renderer_host/gtk_key_bindings_handler.h132
-rw-r--r--chromium/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc226
-rw-r--r--chromium/content/browser/renderer_host/gtk_plugin_container.cc89
-rw-r--r--chromium/content/browser/renderer_host/gtk_plugin_container.h30
-rw-r--r--chromium/content/browser/renderer_host/gtk_plugin_container_manager.cc161
-rw-r--r--chromium/content/browser/renderer_host/gtk_plugin_container_manager.h58
-rw-r--r--chromium/content/browser/renderer_host/gtk_window_utils.cc88
-rw-r--r--chromium/content/browser/renderer_host/gtk_window_utils.h23
-rw-r--r--chromium/content/browser/renderer_host/image_transport_factory_android.cc240
-rw-r--r--chromium/content/browser/renderer_host/image_transport_factory_android.h56
-rw-r--r--chromium/content/browser/renderer_host/ime_adapter_android.cc286
-rw-r--r--chromium/content/browser/renderer_host/ime_adapter_android.h72
-rw-r--r--chromium/content/browser/renderer_host/input/OWNERS2
-rw-r--r--chromium/content/browser/renderer_host/input/gesture_event_filter.cc434
-rw-r--r--chromium/content/browser/renderer_host/input/gesture_event_filter.h210
-rw-r--r--chromium/content/browser/renderer_host/input/immediate_input_router.cc579
-rw-r--r--chromium/content/browser/renderer_host/input/immediate_input_router.h206
-rw-r--r--chromium/content/browser/renderer_host/input/immediate_input_router_unittest.cc2265
-rw-r--r--chromium/content/browser/renderer_host/input/input_router.h70
-rw-r--r--chromium/content/browser/renderer_host/input/input_router_client.h78
-rw-r--r--chromium/content/browser/renderer_host/input/tap_suppression_controller.cc149
-rw-r--r--chromium/content/browser/renderer_host/input/tap_suppression_controller.h77
-rw-r--r--chromium/content/browser/renderer_host/input/tap_suppression_controller_client.h46
-rw-r--r--chromium/content/browser/renderer_host/input/tap_suppression_controller_unittest.cc546
-rw-r--r--chromium/content/browser/renderer_host/input/touch_event_queue.cc240
-rw-r--r--chromium/content/browser/renderer_host/input/touch_event_queue.h105
-rw-r--r--chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.cc51
-rw-r--r--chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.h63
-rw-r--r--chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller_aura.cc65
-rw-r--r--chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.cc66
-rw-r--r--chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h64
-rw-r--r--chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller_stub.cc53
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_builders_android.cc133
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_builders_android.h58
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_builders_win.cc460
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_builders_win.h34
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_util.cc145
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_util.h25
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_util_posix.cc43
-rw-r--r--chromium/content/browser/renderer_host/input/web_input_event_util_posix.h19
-rw-r--r--chromium/content/browser/renderer_host/java/DEPS4
-rw-r--r--chromium/content/browser/renderer_host/java/OWNERS2
-rw-r--r--chromium/content/browser/renderer_host/java/java_bound_object.cc938
-rw-r--r--chromium/content/browser/renderer_host/java/java_bound_object.h89
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc95
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_channel_host.h52
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc173
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h75
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc156
-rw-r--r--chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h70
-rw-r--r--chromium/content/browser/renderer_host/java/java_method.cc237
-rw-r--r--chromium/content/browser/renderer_host/java/java_method.h47
-rw-r--r--chromium/content/browser/renderer_host/java/java_type.cc114
-rw-r--r--chromium/content/browser/renderer_host/java/java_type.h50
-rw-r--r--chromium/content/browser/renderer_host/media/DEPS9
-rw-r--r--chromium/content/browser/renderer_host/media/OWNERS26
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_device_manager.cc237
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_device_manager.h100
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc291
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_renderer_host.cc407
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_renderer_host.h162
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_sync_writer.cc82
-rw-r--r--chromium/content/browser/renderer_host/media/audio_input_sync_writer.h60
-rw-r--r--chromium/content/browser/renderer_host/media/audio_mirroring_manager.cc164
-rw-r--r--chromium/content/browser/renderer_host/media/audio_mirroring_manager.h108
-rw-r--r--chromium/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc234
-rw-r--r--chromium/content/browser/renderer_host/media/audio_renderer_host.cc477
-rw-r--r--chromium/content/browser/renderer_host/media/audio_renderer_host.h162
-rw-r--r--chromium/content/browser/renderer_host/media/audio_renderer_host_unittest.cc423
-rw-r--r--chromium/content/browser/renderer_host/media/audio_sync_reader.cc200
-rw-r--r--chromium/content/browser/renderer_host/media/audio_sync_reader.h91
-rw-r--r--chromium/content/browser/renderer_host/media/desktop_capture_device.cc462
-rw-r--r--chromium/content/browser/renderer_host/media/desktop_capture_device.h58
-rw-r--r--chromium/content/browser/renderer_host/media/desktop_capture_device_unittest.cc271
-rw-r--r--chromium/content/browser/renderer_host/media/device_request_message_filter.cc219
-rw-r--r--chromium/content/browser/renderer_host/media/device_request_message_filter.h78
-rw-r--r--chromium/content/browser/renderer_host/media/device_request_message_filter_unittest.cc304
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc224
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.h95
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc353
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_manager.cc1115
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_manager.h265
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_manager_unittest.cc174
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_provider.h94
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_requester.h44
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_ui_controller_unittest.cc170
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_ui_proxy.cc216
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_ui_proxy.h88
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc219
-rw-r--r--chromium/content/browser/renderer_host/media/midi_dispatcher_host.cc63
-rw-r--r--chromium/content/browser/renderer_host/media/midi_dispatcher_host.h48
-rw-r--r--chromium/content/browser/renderer_host/media/midi_host.cc167
-rw-r--r--chromium/content/browser/renderer_host/media/midi_host.h79
-rw-r--r--chromium/content/browser/renderer_host/media/mock_media_observer.cc17
-rw-r--r--chromium/content/browser/renderer_host/media/mock_media_observer.h60
-rw-r--r--chromium/content/browser/renderer_host/media/peer_connection_tracker_host.cc69
-rw-r--r--chromium/content/browser/renderer_host/media/peer_connection_tracker_host.h49
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_buffer_pool.cc209
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_buffer_pool.h136
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc159
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_controller.cc732
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_controller.h164
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.cc23
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.h65
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_controller_unittest.cc265
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_host.cc315
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_host.h161
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_host_unittest.cc371
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.cc593
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.h170
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc272
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_oracle.cc165
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_oracle.h107
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_oracle_unittest.cc478
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.cc349
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.h92
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc513
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_capture_util.cc59
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_capture_util.h35
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_tracker.cc102
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_tracker.h86
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_video_capture_device.cc1278
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_video_capture_device.h73
-rw-r--r--chromium/content/browser/renderer_host/media/web_contents_video_capture_device_unittest.cc797
-rw-r--r--chromium/content/browser/renderer_host/media/webrtc_identity_service_host.cc87
-rw-r--r--chromium/content/browser/renderer_host/media/webrtc_identity_service_host.h63
-rw-r--r--chromium/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc187
-rw-r--r--chromium/content/browser/renderer_host/memory_benchmark_message_filter.cc42
-rw-r--r--chromium/content/browser/renderer_host/memory_benchmark_message_filter.h30
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event.cc27
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event_android.cc75
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event_aura.cc98
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event_gtk.cc77
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event_mac.mm60
-rw-r--r--chromium/content/browser/renderer_host/native_web_keyboard_event_win.cc49
-rw-r--r--chromium/content/browser/renderer_host/overscroll_configuration.cc84
-rw-r--r--chromium/content/browser/renderer_host/overscroll_controller.cc372
-rw-r--r--chromium/content/browser/renderer_host/overscroll_controller.h138
-rw-r--r--chromium/content/browser/renderer_host/overscroll_controller_delegate.h37
-rw-r--r--chromium/content/browser/renderer_host/p2p/DEPS3
-rw-r--r--chromium/content/browser/renderer_host/p2p/OWNERS3
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.cc265
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.h93
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host.cc104
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host.h89
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp.cc514
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp.h137
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.cc144
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.h68
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp_server_unittest.cc169
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_tcp_unittest.cc353
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_test_utils.h320
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_udp.cc260
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_udp.h80
-rw-r--r--chromium/content/browser/renderer_host/p2p/socket_host_udp_unittest.cc292
-rw-r--r--chromium/content/browser/renderer_host/pepper/OWNERS3
-rw-r--r--chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc168
-rw-r--r--chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h118
-rw-r--r--chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.cc31
-rw-r--r--chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.h37
-rw-r--r--chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc199
-rw-r--r--chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h50
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.cc111
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h28
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.cc168
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h75
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.cc260
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.h113
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc183
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h76
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.cc323
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h86
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.cc84
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.h61
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_gamepad_host_unittest.cc204
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc222
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h89
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.cc306
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h89
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_lookup_request.h63
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_message_filter.cc491
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_message_filter.h231
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc195
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.h104
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.cc101
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.h47
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_printing_host.cc58
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_printing_host.h49
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_printing_host_unittest.cc128
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.cc177
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.h79
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_security_helper.cc54
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_security_helper.h21
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_socket_utils.cc81
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_socket_utils.h38
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc318
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h106
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.cc525
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.h148
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list.h36
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_android.cc20
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.cc125
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h28
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_linux.cc64
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm82
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_win.cc83
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.cc461
-rw-r--r--chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h131
-rw-r--r--chromium/content/browser/renderer_host/popup_menu_helper_mac.h59
-rw-r--r--chromium/content/browser/renderer_host/popup_menu_helper_mac.mm112
-rw-r--r--chromium/content/browser/renderer_host/render_frame_host_impl.cc34
-rw-r--r--chromium/content/browser/renderer_host/render_frame_host_impl.h41
-rw-r--r--chromium/content/browser/renderer_host/render_message_filter.cc1170
-rw-r--r--chromium/content/browser/renderer_host/render_message_filter.h310
-rw-r--r--chromium/content/browser/renderer_host/render_process_host_browsertest.cc50
-rw-r--r--chromium/content/browser/renderer_host/render_process_host_impl.cc1816
-rw-r--r--chromium/content/browser/renderer_host/render_process_host_impl.h332
-rw-r--r--chromium/content/browser/renderer_host/render_sandbox_host_linux.cc721
-rw-r--r--chromium/content/browser/renderer_host/render_sandbox_host_linux.h56
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_browsertest.cc118
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_delegate.cc53
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_delegate.h429
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_factory.cc44
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_factory.h72
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_impl.cc2059
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_impl.h714
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_manager_browsertest.cc1299
-rw-r--r--chromium/content/browser/renderer_host/render_view_host_unittest.cc291
-rw-r--r--chromium/content/browser/renderer_host/render_widget_helper.cc403
-rw-r--r--chromium/content/browser/renderer_host/render_widget_helper.h251
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_browsertest.cc74
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_delegate.cc27
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_delegate.h63
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_impl.cc2485
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_impl.h925
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_unittest.cc2577
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_android.cc1296
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_android.h330
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_aura.cc3250
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_aura.h708
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc587
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_base.cc564
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_base.h166
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_browsertest.cc880
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_gtk.cc1584
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_gtk.h342
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_guest.cc577
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_guest.h227
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_guest_unittest.cc81
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac.h565
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac.mm3850
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h52
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.mm58
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h73
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm240
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm182
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm785
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_win.cc3201
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_win.h614
-rw-r--r--chromium/content/browser/renderer_host/render_widget_host_view_win_browsertest.cc226
-rw-r--r--chromium/content/browser/renderer_host/smooth_scroll_calculator.cc28
-rw-r--r--chromium/content/browser/renderer_host/smooth_scroll_calculator.h29
-rw-r--r--chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.cc61
-rw-r--r--chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.h52
-rw-r--r--chromium/content/browser/renderer_host/smooth_scroll_gesture_controller_unittest.cc183
-rw-r--r--chromium/content/browser/renderer_host/socket_stream_dispatcher_host.cc284
-rw-r--r--chromium/content/browser/renderer_host/socket_stream_dispatcher_host.h99
-rw-r--r--chromium/content/browser/renderer_host/socket_stream_host.cc102
-rw-r--r--chromium/content/browser/renderer_host/socket_stream_host.h79
-rw-r--r--chromium/content/browser/renderer_host/surface_texture_transport_client_android.cc143
-rw-r--r--chromium/content/browser/renderer_host/surface_texture_transport_client_android.h62
-rw-r--r--chromium/content/browser/renderer_host/test_backing_store.cc38
-rw-r--r--chromium/content/browser/renderer_host/test_backing_store.h39
-rw-r--r--chromium/content/browser/renderer_host/test_render_view_host.cc428
-rw-r--r--chromium/content/browser/renderer_host/test_render_view_host.h383
-rw-r--r--chromium/content/browser/renderer_host/text_input_client_mac.h90
-rw-r--r--chromium/content/browser/renderer_host/text_input_client_mac.mm140
-rw-r--r--chromium/content/browser/renderer_host/text_input_client_mac_unittest.mm233
-rw-r--r--chromium/content/browser/renderer_host/text_input_client_message_filter.h51
-rw-r--r--chromium/content/browser/renderer_host/text_input_client_message_filter.mm68
-rw-r--r--chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.cc75
-rw-r--r--chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.h52
-rw-r--r--chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.cc75
-rw-r--r--chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h45
-rw-r--r--chromium/content/browser/renderer_host/ui_events_helper.cc336
-rw-r--r--chromium/content/browser/renderer_host/ui_events_helper.h57
-rw-r--r--chromium/content/browser/renderer_host/web_input_event_aura.cc268
-rw-r--r--chromium/content/browser/renderer_host/web_input_event_aura.h41
-rw-r--r--chromium/content/browser/renderer_host/web_input_event_aura_unittest.cc85
-rw-r--r--chromium/content/browser/renderer_host/web_input_event_aurawin.cc47
-rw-r--r--chromium/content/browser/renderer_host/web_input_event_aurax11.cc238
-rw-r--r--chromium/content/browser/renderer_host/webmenurunner_mac.h61
-rw-r--r--chromium/content/browser/renderer_host/webmenurunner_mac.mm147
-rw-r--r--chromium/content/browser/resolve_proxy_msg_helper.cc103
-rw-r--r--chromium/content/browser/resolve_proxy_msg_helper.h87
-rw-r--r--chromium/content/browser/resolve_proxy_msg_helper_unittest.cc239
-rw-r--r--chromium/content/browser/resource_context_impl.cc112
-rw-r--r--chromium/content/browser/resource_context_impl.h38
-rw-r--r--chromium/content/browser/resources/accessibility/accessibility.css30
-rw-r--r--chromium/content/browser/resources/accessibility/accessibility.html25
-rw-r--r--chromium/content/browser/resources/accessibility/accessibility.js212
-rw-r--r--chromium/content/browser/resources/gpu/OWNERS1
-rw-r--r--chromium/content/browser/resources/gpu/browser_bridge.js147
-rw-r--r--chromium/content/browser/resources/gpu/browser_bridge_tests.js346
-rw-r--r--chromium/content/browser/resources/gpu/gpu_internals.html54
-rw-r--r--chromium/content/browser/resources/gpu/gpu_internals.js20
-rw-r--r--chromium/content/browser/resources/gpu/info_view.css58
-rw-r--r--chromium/content/browser/resources/gpu/info_view.html73
-rw-r--r--chromium/content/browser/resources/gpu/info_view.js321
-rw-r--r--chromium/content/browser/resources/gpu/timeline_test.html46
-rw-r--r--chromium/content/browser/resources/indexed_db/OWNERS4
-rw-r--r--chromium/content/browser/resources/indexed_db/indexeddb_internals.css72
-rw-r--r--chromium/content/browser/resources/indexed_db/indexeddb_internals.html115
-rw-r--r--chromium/content/browser/resources/indexed_db/indexeddb_internals.js89
-rw-r--r--chromium/content/browser/resources/media/OWNERS11
-rw-r--r--chromium/content/browser/resources/media/cache_entry.js237
-rw-r--r--chromium/content/browser/resources/media/data_series.js132
-rw-r--r--chromium/content/browser/resources/media/disjoint_range_set.js145
-rw-r--r--chromium/content/browser/resources/media/disjoint_range_set_test.html96
-rw-r--r--chromium/content/browser/resources/media/dump_creator.js131
-rw-r--r--chromium/content/browser/resources/media/event_list.js64
-rw-r--r--chromium/content/browser/resources/media/item_store.js70
-rw-r--r--chromium/content/browser/resources/media/media_internals.css83
-rw-r--r--chromium/content/browser/resources/media/media_internals.html28
-rw-r--r--chromium/content/browser/resources/media/media_internals.js281
-rw-r--r--chromium/content/browser/resources/media/media_player.js154
-rw-r--r--chromium/content/browser/resources/media/metrics.js116
-rw-r--r--chromium/content/browser/resources/media/new/integration_test.html86
-rw-r--r--chromium/content/browser/resources/media/new/main.js134
-rw-r--r--chromium/content/browser/resources/media/new/media_internals.html18
-rw-r--r--chromium/content/browser/resources/media/new/media_internals.js18
-rw-r--r--chromium/content/browser/resources/media/new/player_info.js80
-rw-r--r--chromium/content/browser/resources/media/new/player_info_test.html146
-rw-r--r--chromium/content/browser/resources/media/new/player_manager.js111
-rw-r--r--chromium/content/browser/resources/media/new/player_manager_test.html155
-rw-r--r--chromium/content/browser/resources/media/new/util.js34
-rw-r--r--chromium/content/browser/resources/media/new/webui_resource_test.js210
-rw-r--r--chromium/content/browser/resources/media/peer_connection_update_table.js128
-rw-r--r--chromium/content/browser/resources/media/ssrc_info_manager.js166
-rw-r--r--chromium/content/browser/resources/media/stats_graph_helper.js265
-rw-r--r--chromium/content/browser/resources/media/stats_table.js137
-rw-r--r--chromium/content/browser/resources/media/timeline_graph_view.js523
-rw-r--r--chromium/content/browser/resources/media/util.js74
-rw-r--r--chromium/content/browser/resources/media/webrtc_internals.css118
-rw-r--r--chromium/content/browser/resources/media/webrtc_internals.html17
-rw-r--r--chromium/content/browser/resources/media/webrtc_internals.js264
-rw-r--r--chromium/content/browser/safe_util_win.cc93
-rw-r--r--chromium/content/browser/safe_util_win.h53
-rw-r--r--chromium/content/browser/security_exploit_browsertest.cc55
-rw-r--r--chromium/content/browser/session_history_browsertest.cc499
-rw-r--r--chromium/content/browser/site_instance_impl.cc350
-rw-r--r--chromium/content/browser/site_instance_impl.h133
-rw-r--r--chromium/content/browser/site_instance_impl_unittest.cc760
-rw-r--r--chromium/content/browser/site_per_process_browsertest.cc402
-rw-r--r--chromium/content/browser/speech/DEPS4
-rw-r--r--chromium/content/browser/speech/OWNERS3
-rw-r--r--chromium/content/browser/speech/audio_buffer.cc91
-rw-r--r--chromium/content/browser/speech/audio_buffer.h76
-rw-r--r--chromium/content/browser/speech/audio_encoder.cc194
-rw-r--r--chromium/content/browser/speech/audio_encoder.h60
-rw-r--r--chromium/content/browser/speech/chunked_byte_buffer.cc136
-rw-r--r--chromium/content/browser/speech/chunked_byte_buffer.h75
-rw-r--r--chromium/content/browser/speech/chunked_byte_buffer_unittest.cc76
-rw-r--r--chromium/content/browser/speech/endpointer/endpointer.cc169
-rw-r--r--chromium/content/browser/speech/endpointer/endpointer.h153
-rw-r--r--chromium/content/browser/speech/endpointer/endpointer_unittest.cc152
-rw-r--r--chromium/content/browser/speech/endpointer/energy_endpointer.cc376
-rw-r--r--chromium/content/browser/speech/endpointer/energy_endpointer.h155
-rw-r--r--chromium/content/browser/speech/endpointer/energy_endpointer_params.cc53
-rw-r--r--chromium/content/browser/speech/endpointer/energy_endpointer_params.h138
-rw-r--r--chromium/content/browser/speech/google_one_shot_remote_engine.cc295
-rw-r--r--chromium/content/browser/speech/google_one_shot_remote_engine.h61
-rw-r--r--chromium/content/browser/speech/google_one_shot_remote_engine_unittest.cc128
-rw-r--r--chromium/content/browser/speech/google_streaming_remote_engine.cc594
-rw-r--r--chromium/content/browser/speech/google_streaming_remote_engine.h160
-rw-r--r--chromium/content/browser/speech/google_streaming_remote_engine_unittest.cc503
-rw-r--r--chromium/content/browser/speech/input_tag_speech_dispatcher_host.cc210
-rw-r--r--chromium/content/browser/speech/input_tag_speech_dispatcher_host.h83
-rw-r--r--chromium/content/browser/speech/proto/google_streaming_api.proto66
-rw-r--r--chromium/content/browser/speech/proto/speech_proto.gyp19
-rw-r--r--chromium/content/browser/speech/speech_recognition_browsertest.cc136
-rw-r--r--chromium/content/browser/speech/speech_recognition_dispatcher_host.cc198
-rw-r--r--chromium/content/browser/speech/speech_recognition_dispatcher_host.h77
-rw-r--r--chromium/content/browser/speech/speech_recognition_engine.cc27
-rw-r--r--chromium/content/browser/speech/speech_recognition_engine.h112
-rw-r--r--chromium/content/browser/speech/speech_recognition_manager_impl.cc683
-rw-r--r--chromium/content/browser/speech/speech_recognition_manager_impl.h193
-rw-r--r--chromium/content/browser/speech/speech_recognizer.h48
-rw-r--r--chromium/content/browser/speech/speech_recognizer_impl.cc819
-rw-r--r--chromium/content/browser/speech/speech_recognizer_impl.h164
-rw-r--r--chromium/content/browser/speech/speech_recognizer_impl_android.cc206
-rw-r--r--chromium/content/browser/speech/speech_recognizer_impl_android.h66
-rw-r--r--chromium/content/browser/speech/speech_recognizer_impl_unittest.cc499
-rw-r--r--chromium/content/browser/ssl/OWNERS1
-rw-r--r--chromium/content/browser/ssl/ssl_cert_error_handler.cc51
-rw-r--r--chromium/content/browser/ssl/ssl_cert_error_handler.h57
-rw-r--r--chromium/content/browser/ssl/ssl_client_auth_handler.cc96
-rw-r--r--chromium/content/browser/ssl/ssl_client_auth_handler.h78
-rw-r--r--chromium/content/browser/ssl/ssl_error_handler.cc178
-rw-r--r--chromium/content/browser/ssl/ssl_error_handler.h172
-rw-r--r--chromium/content/browser/ssl/ssl_host_state.cc71
-rw-r--r--chromium/content/browser/ssl/ssl_host_state.h84
-rw-r--r--chromium/content/browser/ssl/ssl_host_state_unittest.cc203
-rw-r--r--chromium/content/browser/ssl/ssl_manager.cc249
-rw-r--r--chromium/content/browser/ssl/ssl_manager.h124
-rw-r--r--chromium/content/browser/ssl/ssl_policy.cc225
-rw-r--r--chromium/content/browser/ssl/ssl_policy.h77
-rw-r--r--chromium/content/browser/ssl/ssl_policy_backend.cc48
-rw-r--r--chromium/content/browser/ssl/ssl_policy_backend.h58
-rw-r--r--chromium/content/browser/ssl/ssl_request_info.cc23
-rw-r--r--chromium/content/browser/ssl/ssl_request_info.h50
-rw-r--r--chromium/content/browser/startup_task_runner.cc63
-rw-r--r--chromium/content/browser/startup_task_runner.h66
-rw-r--r--chromium/content/browser/startup_task_runner_unittest.cc281
-rw-r--r--chromium/content/browser/storage_partition_impl.cc669
-rw-r--r--chromium/content/browser/storage_partition_impl.h122
-rw-r--r--chromium/content/browser/storage_partition_impl_map.cc592
-rw-r--r--chromium/content/browser/storage_partition_impl_map.h132
-rw-r--r--chromium/content/browser/storage_partition_impl_map_unittest.cc63
-rw-r--r--chromium/content/browser/storage_partition_impl_unittest.cc120
-rw-r--r--chromium/content/browser/streams/OWNERS1
-rw-r--r--chromium/content/browser/streams/stream.cc156
-rw-r--r--chromium/content/browser/streams/stream.h115
-rw-r--r--chromium/content/browser/streams/stream_context.cc57
-rw-r--r--chromium/content/browser/streams/stream_context.h60
-rw-r--r--chromium/content/browser/streams/stream_handle_impl.cc38
-rw-r--r--chromium/content/browser/streams/stream_handle_impl.h40
-rw-r--r--chromium/content/browser/streams/stream_read_observer.h26
-rw-r--r--chromium/content/browser/streams/stream_registry.cc48
-rw-r--r--chromium/content/browser/streams/stream_registry.h51
-rw-r--r--chromium/content/browser/streams/stream_unittest.cc253
-rw-r--r--chromium/content/browser/streams/stream_url_request_job.cc238
-rw-r--r--chromium/content/browser/streams/stream_url_request_job.h66
-rw-r--r--chromium/content/browser/streams/stream_url_request_job_unittest.cc173
-rw-r--r--chromium/content/browser/streams/stream_write_observer.h27
-rw-r--r--chromium/content/browser/system_message_window_win.cc162
-rw-r--r--chromium/content/browser/system_message_window_win.h51
-rw-r--r--chromium/content/browser/system_message_window_win_unittest.cc45
-rw-r--r--chromium/content/browser/tcmalloc_internals_request_job.cc121
-rw-r--r--chromium/content/browser/tcmalloc_internals_request_job.h67
-rw-r--r--chromium/content/browser/tracing/DEPS4
-rw-r--r--chromium/content/browser/tracing/OWNERS1
-rwxr-xr-xchromium/content/browser/tracing/generate_trace_viewer_grd.py81
-rw-r--r--chromium/content/browser/tracing/trace_controller_impl.cc407
-rw-r--r--chromium/content/browser/tracing/trace_controller_impl.h104
-rw-r--r--chromium/content/browser/tracing/trace_message_filter.cc128
-rw-r--r--chromium/content/browser/tracing/trace_message_filter.h63
-rw-r--r--chromium/content/browser/tracing/trace_subscriber_stdio.cc105
-rw-r--r--chromium/content/browser/tracing/trace_subscriber_stdio.h42
-rw-r--r--chromium/content/browser/tracing/trace_subscriber_stdio_unittest.cc40
-rw-r--r--chromium/content/browser/tracing/tracing_resources.gyp81
-rw-r--r--chromium/content/browser/tracing/tracing_ui.cc561
-rw-r--r--chromium/content/browser/tracing/tracing_ui.h23
-rw-r--r--chromium/content/browser/udev_linux.cc70
-rw-r--r--chromium/content/browser/udev_linux.h97
-rw-r--r--chromium/content/browser/user_metrics.cc62
-rw-r--r--chromium/content/browser/utility_process_host_impl.cc297
-rw-r--r--chromium/content/browser/utility_process_host_impl.h96
-rw-r--r--chromium/content/browser/web_contents/OWNERS4
-rw-r--r--chromium/content/browser/web_contents/aura/image_window_delegate.cc100
-rw-r--r--chromium/content/browser/web_contents/aura/image_window_delegate.h61
-rw-r--r--chromium/content/browser/web_contents/aura/shadow_layer_delegate.cc61
-rw-r--r--chromium/content/browser/web_contents/aura/shadow_layer_delegate.h46
-rw-r--r--chromium/content/browser/web_contents/aura/window_slider.cc303
-rw-r--r--chromium/content/browser/web_contents/aura/window_slider.h131
-rw-r--r--chromium/content/browser/web_contents/aura/window_slider_unittest.cc413
-rw-r--r--chromium/content/browser/web_contents/debug_urls.cc82
-rw-r--r--chromium/content/browser/web_contents/debug_urls.h20
-rw-r--r--chromium/content/browser/web_contents/drag_utils_gtk.cc38
-rw-r--r--chromium/content/browser/web_contents/drag_utils_gtk.h24
-rw-r--r--chromium/content/browser/web_contents/frame_tree_node.cc37
-rw-r--r--chromium/content/browser/web_contents/frame_tree_node.h76
-rw-r--r--chromium/content/browser/web_contents/interstitial_page_impl.cc820
-rw-r--r--chromium/content/browser/web_contents/interstitial_page_impl.h253
-rw-r--r--chromium/content/browser/web_contents/navigation_controller_impl.cc1693
-rw-r--r--chromium/content/browser/web_contents/navigation_controller_impl.h409
-rw-r--r--chromium/content/browser/web_contents/navigation_controller_impl_unittest.cc3960
-rw-r--r--chromium/content/browser/web_contents/navigation_entry_impl.cc321
-rw-r--r--chromium/content/browser/web_contents/navigation_entry_impl.h313
-rw-r--r--chromium/content/browser/web_contents/navigation_entry_impl_unittest.cc241
-rw-r--r--chromium/content/browser/web_contents/render_view_host_manager.cc1062
-rw-r--r--chromium/content/browser/web_contents/render_view_host_manager.h356
-rw-r--r--chromium/content/browser/web_contents/render_view_host_manager_unittest.cc1214
-rw-r--r--chromium/content/browser/web_contents/touch_editable_impl_aura.cc344
-rw-r--r--chromium/content/browser/web_contents/touch_editable_impl_aura.h106
-rw-r--r--chromium/content/browser/web_contents/touch_editable_impl_aura_browsertest.cc357
-rw-r--r--chromium/content/browser/web_contents/web_contents_delegate_unittest.cc66
-rw-r--r--chromium/content/browser/web_contents/web_contents_drag_win.cc443
-rw-r--r--chromium/content/browser/web_contents/web_contents_drag_win.h122
-rw-r--r--chromium/content/browser/web_contents/web_contents_impl.cc3722
-rw-r--r--chromium/content/browser/web_contents/web_contents_impl.h963
-rw-r--r--chromium/content/browser/web_contents/web_contents_impl_browsertest.cc162
-rw-r--r--chromium/content/browser/web_contents/web_contents_impl_unittest.cc2225
-rw-r--r--chromium/content/browser/web_contents/web_contents_screenshot_manager.cc272
-rw-r--r--chromium/content/browser/web_contents/web_contents_screenshot_manager.h89
-rw-r--r--chromium/content/browser/web_contents/web_contents_user_data_unittest.cc108
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_android.cc227
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_android.h98
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_aura.cc1607
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_aura.h237
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_aura_browsertest.cc607
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_gtk.cc418
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_gtk.h147
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_guest.cc265
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_guest.h112
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_mac.h145
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_mac.mm564
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_mac_unittest.mm31
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_win.cc464
-rw-r--r--chromium/content/browser/web_contents/web_contents_view_win.h147
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_gtk.cc338
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_gtk.h114
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_mac.h88
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_mac.mm310
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_mac_unittest.mm165
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_win.cc286
-rw-r--r--chromium/content/browser/web_contents/web_drag_dest_win.h89
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_gtk.cc403
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_gtk.h115
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_mac.h89
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_mac.mm511
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_mac_unittest.mm41
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_win.cc130
-rw-r--r--chromium/content/browser/web_contents/web_drag_source_win.h79
-rw-r--r--chromium/content/browser/web_contents/web_drag_utils_win.cc60
-rw-r--r--chromium/content/browser/web_contents/web_drag_utils_win.h22
-rw-r--r--chromium/content/browser/webkit_browsertest.cc94
-rw-r--r--chromium/content/browser/webui/OWNERS3
-rw-r--r--chromium/content/browser/webui/content_web_ui_controller_factory.cc75
-rw-r--r--chromium/content/browser/webui/content_web_ui_controller_factory.h41
-rw-r--r--chromium/content/browser/webui/generic_handler.cc59
-rw-r--r--chromium/content/browser/webui/generic_handler.h34
-rw-r--r--chromium/content/browser/webui/shared_resources_data_source.cc63
-rw-r--r--chromium/content/browser/webui/shared_resources_data_source.h32
-rw-r--r--chromium/content/browser/webui/url_data_manager.cc133
-rw-r--r--chromium/content/browser/webui/url_data_manager.h82
-rw-r--r--chromium/content/browser/webui/url_data_manager_backend.cc655
-rw-r--r--chromium/content/browser/webui/url_data_manager_backend.h113
-rw-r--r--chromium/content/browser/webui/url_data_source_impl.cc59
-rw-r--r--chromium/content/browser/webui/url_data_source_impl.h97
-rw-r--r--chromium/content/browser/webui/web_ui_controller_factory_registry.cc106
-rw-r--r--chromium/content/browser/webui/web_ui_controller_factory_registry.h49
-rw-r--r--chromium/content/browser/webui/web_ui_data_source_impl.cc219
-rw-r--r--chromium/content/browser/webui/web_ui_data_source_impl.h108
-rw-r--r--chromium/content/browser/webui/web_ui_data_source_unittest.cc154
-rw-r--r--chromium/content/browser/webui/web_ui_impl.cc239
-rw-r--r--chromium/content/browser/webui/web_ui_impl.h111
-rw-r--r--chromium/content/browser/webui/web_ui_message_handler.cc47
-rw-r--r--chromium/content/browser/webui/web_ui_message_handler_unittest.cc97
-rw-r--r--chromium/content/browser/worker.sb12
-rw-r--r--chromium/content/browser/worker_host/OWNERS1
-rw-r--r--chromium/content/browser/worker_host/message_port_service.cc225
-rw-r--r--chromium/content/browser/worker_host/message_port_service.h90
-rw-r--r--chromium/content/browser/worker_host/worker_document_set.cc73
-rw-r--r--chromium/content/browser/worker_host/worker_document_set.h92
-rw-r--r--chromium/content/browser/worker_host/worker_message_filter.cc114
-rw-r--r--chromium/content/browser/worker_host/worker_message_filter.h64
-rw-r--r--chromium/content/browser/worker_host/worker_process_host.cc727
-rw-r--r--chromium/content/browser/worker_host/worker_process_host.h250
-rw-r--r--chromium/content/browser/worker_host/worker_service_impl.cc718
-rw-r--r--chromium/content/browser/worker_host/worker_service_impl.h149
-rw-r--r--chromium/content/browser/worker_host/worker_storage_partition.cc71
-rw-r--r--chromium/content/browser/worker_host/worker_storage_partition.h104
-rw-r--r--chromium/content/browser/zygote_host/OWNERS3
-rw-r--r--chromium/content/browser/zygote_host/zygote_host_impl_linux.cc514
-rw-r--r--chromium/content/browser/zygote_host/zygote_host_impl_linux.h84
1309 files changed, 285898 insertions, 0 deletions
diff --git a/chromium/content/browser/DEPS b/chromium/content/browser/DEPS
new file mode 100644
index 00000000000..39be9891906
--- /dev/null
+++ b/chromium/content/browser/DEPS
@@ -0,0 +1,70 @@
+include_rules = [
+ "+content/gpu", # For gpu_info_collector.h and in-process GPU
+ "+content/port/browser",
+ "+content/public/browser",
+ "+content/child/webkitplatformsupport_impl.h", # For in-process webkit
+ "+media/audio", # For audio input for speech input feature.
+ "+media/base", # For Android JNI registration.
+ "+media/midi", # For Web MIDI API
+ "+sql",
+ "+ui/webui",
+ "+win8/util",
+
+ # For single-process mode.
+ "+content/child/child_process.h",
+ "+content/utility/utility_thread_impl.h",
+
+ # TODO(joi): This was misplaced; need to move it somewhere else,
+ # since //content shouldn't depend on //components, which is a layer
+ # above.
+ "+components/tracing",
+
+ # Other libraries.
+ "+third_party/iaccessible2",
+ "+third_party/isimpledom",
+ "+third_party/khronos", # For enum definitions only
+ "+third_party/speex",
+ "+third_party/re2",
+
+ # Allow non-browser Chrome OS code to be used.
+ "+chromeos",
+ "+third_party/cros_system_api",
+
+ "-webkit/child",
+ "-webkit/renderer",
+
+ # No inclusion of WebKit from the browser, other than strictly enum/POD,
+ # header-only types, and some selected common code.
+ "-third_party/WebKit",
+ "+third_party/WebKit/public/platform/WebGamepads.h",
+ "+third_party/WebKit/public/platform/WebGraphicsContext3D.h",
+ "+third_party/WebKit/public/platform/WebIDBCallbacks.h",
+ "+third_party/WebKit/public/platform/WebIDBDatabaseException.h",
+ "+third_party/WebKit/public/platform/WebIDBTypes.h",
+ "+third_party/WebKit/public/platform/WebReferrerPolicy.h",
+ "+third_party/WebKit/public/platform/WebScreenInfo.h",
+ "+third_party/WebKit/public/platform/WebString.h",
+ "+third_party/WebKit/public/platform/WebVibration.h",
+ "+third_party/WebKit/public/web/WebCompositionUnderline.h",
+ "+third_party/WebKit/public/web/WebConsoleMessage.h",
+ "+third_party/WebKit/public/web/WebCursorInfo.h",
+ "+third_party/WebKit/public/web/WebDragOperation.h",
+ "+third_party/WebKit/public/web/WebDragStatus.h",
+ "+third_party/WebKit/public/web/WebFindOptions.h",
+ "+third_party/WebKit/public/web/WebInputEvent.h",
+ "+third_party/WebKit/public/web/WebMediaPlayerAction.h",
+ "+third_party/WebKit/public/web/WebNotificationPresenter.h",
+ "+third_party/WebKit/public/web/WebPageSerializerClient.h",
+ "+third_party/WebKit/public/web/WebPluginAction.h",
+ "+third_party/WebKit/public/web/WebPopupType.h",
+ "+third_party/WebKit/public/web/WebScreenInfo.h",
+ "+third_party/WebKit/public/web/WebTextDirection.h",
+
+ # These should be burned down. http://crbug.com/237267
+ "!third_party/WebKit/public/web/gtk/WebInputEventFactory.h",
+ "!third_party/WebKit/public/web/mac/WebInputEventFactory.h",
+
+ # DO NOT ADD ANY CHROME OR COMPONENTS INCLUDES HERE!!!
+ # See https://sites.google.com/a/chromium.org/dev/developers/content-module
+ # for more information.
+]
diff --git a/chromium/content/browser/OWNERS b/chromium/content/browser/OWNERS
new file mode 100644
index 00000000000..37b5294c54f
--- /dev/null
+++ b/chromium/content/browser/OWNERS
@@ -0,0 +1,13 @@
+sky@chromium.org
+
+per-file child_process_security_policy_impl.*=tsepez@chromium.org
+per-file child_process_security_policy_impl.*=creis@chromium.org
+per-file child_process_security_policy_impl.*=cevans@chromium.org
+per-file child_process_security_unittest.cc=tsepez@chromium.org
+per-file child_process_security_unittest.cc=creis@chromium.org
+per-file child_process_security_unittest.cc=cevans@chromium.org
+per-file power_save_blocker_chromeos.cc=derat@chromium.org
+
+# Mac Sandbox profiles.
+per-file *.sb=set noparent
+per-file *.sb=jeremy@chromium.org
diff --git a/chromium/content/browser/accessibility/OWNERS b/chromium/content/browser/accessibility/OWNERS
new file mode 100644
index 00000000000..11e8fd837ee
--- /dev/null
+++ b/chromium/content/browser/accessibility/OWNERS
@@ -0,0 +1,2 @@
+dmazzoni@chromium.org
+dtseng@chromium.org
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter.cc
new file mode 100644
index 00000000000..4368cf13217
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter.cc
@@ -0,0 +1,197 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace content {
+namespace {
+const int kIndentSpaces = 4;
+const char* kSkipString = "@NO_DUMP";
+const char* kChildrenDictAttr = "children";
+}
+
+AccessibilityTreeFormatter::AccessibilityTreeFormatter(
+ BrowserAccessibility* root)
+ : root_(root) {
+ Initialize();
+}
+
+// static
+AccessibilityTreeFormatter* AccessibilityTreeFormatter::Create(
+ RenderViewHost* rvh) {
+ RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>(
+ WebContents::FromRenderViewHost(rvh)->GetRenderWidgetHostView());
+
+ BrowserAccessibilityManager* manager =
+ host_view->GetBrowserAccessibilityManager();
+ if (!manager)
+ return NULL;
+
+ BrowserAccessibility* root = manager->GetRoot();
+ return new AccessibilityTreeFormatter(root);
+}
+
+
+AccessibilityTreeFormatter::~AccessibilityTreeFormatter() {
+}
+
+scoped_ptr<base::DictionaryValue>
+AccessibilityTreeFormatter::BuildAccessibilityTree() {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ RecursiveBuildAccessibilityTree(*root_, dict.get());
+ return dict.Pass();
+}
+
+void AccessibilityTreeFormatter::FormatAccessibilityTree(
+ string16* contents) {
+ scoped_ptr<base::DictionaryValue> dict = BuildAccessibilityTree();
+ RecursiveFormatAccessibilityTree(*(dict.get()), contents);
+}
+
+void AccessibilityTreeFormatter::RecursiveBuildAccessibilityTree(
+ const BrowserAccessibility& node, base::DictionaryValue* dict) {
+ AddProperties(node, dict);
+
+ base::ListValue* children = new base::ListValue;
+ dict->Set(kChildrenDictAttr, children);
+ if (!IncludeChildren(node))
+ return;
+
+ for (size_t i = 0; i < node.children().size(); ++i) {
+ BrowserAccessibility* child_node = node.children()[i];
+ base::DictionaryValue* child_dict = new base::DictionaryValue;
+ children->Append(child_dict);
+ RecursiveBuildAccessibilityTree(*child_node, child_dict);
+ }
+}
+
+void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree(
+ const base::DictionaryValue& dict, string16* contents, int depth) {
+ string16 line = ToString(dict, string16(depth * kIndentSpaces, ' '));
+ if (line.find(ASCIIToUTF16(kSkipString)) != string16::npos)
+ return;
+
+ *contents += line;
+ const base::ListValue* children;
+ dict.GetList(kChildrenDictAttr, &children);
+ const base::DictionaryValue* child_dict;
+ for (size_t i = 0; i < children->GetSize(); i++) {
+ children->GetDictionary(i, &child_dict);
+ RecursiveFormatAccessibilityTree(*child_dict, contents, depth + 1);
+ }
+}
+
+#if !defined(OS_ANDROID)
+bool AccessibilityTreeFormatter::IncludeChildren(
+ const BrowserAccessibility& node) {
+ return true;
+}
+#endif
+
+#if (!defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_ANDROID) && \
+ !defined(TOOLKIT_GTK))
+void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
+ base::DictionaryValue* dict) {
+ dict->SetInteger("id", node.renderer_id());
+}
+
+string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& node,
+ const string16& indent) {
+ int id_value;
+ node.GetInteger("id", &id_value);
+ return indent + base::IntToString16(id_value) +
+ ASCIIToUTF16("\n");
+}
+
+void AccessibilityTreeFormatter::Initialize() {}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return base::FilePath::StringType();
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return base::FilePath::StringType();
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return std::string();
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowString() {
+ return std::string();
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetDenyString() {
+ return std::string();
+}
+#endif
+
+void AccessibilityTreeFormatter::SetFilters(
+ const std::vector<Filter>& filters) {
+ filters_ = filters;
+}
+
+bool AccessibilityTreeFormatter::MatchesFilters(
+ const string16& text, bool default_result) const {
+ std::vector<Filter>::const_iterator iter = filters_.begin();
+ bool allow = default_result;
+ for (iter = filters_.begin(); iter != filters_.end(); ++iter) {
+ if (MatchPattern(text, iter->match_str)) {
+ if (iter->type == Filter::ALLOW_EMPTY)
+ allow = true;
+ else if (iter->type == Filter::ALLOW)
+ allow = (!MatchPattern(text, UTF8ToUTF16("*=''")));
+ else
+ allow = false;
+ }
+ }
+ return allow;
+}
+
+string16 AccessibilityTreeFormatter::FormatCoordinates(
+ const char* name, const char* x_name, const char* y_name,
+ const base::DictionaryValue& value) {
+ int x, y;
+ value.GetInteger(x_name, &x);
+ value.GetInteger(y_name, &y);
+ std::string xy_str(base::StringPrintf("%s=(%d, %d)", name, x, y));
+
+ return UTF8ToUTF16(xy_str);
+}
+
+void AccessibilityTreeFormatter::WriteAttribute(
+ bool include_by_default, const std::string& attr, string16* line) {
+ WriteAttribute(include_by_default, UTF8ToUTF16(attr), line);
+}
+
+void AccessibilityTreeFormatter::WriteAttribute(
+ bool include_by_default, const string16& attr, string16* line) {
+ if (attr.empty())
+ return;
+ if (!MatchesFilters(attr, include_by_default))
+ return;
+ if (!line->empty())
+ *line += ASCIIToUTF16(" ");
+ *line += attr;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter.h b/chromium/content/browser/accessibility/accessibility_tree_formatter.h
new file mode 100644
index 00000000000..3c36b2f6e52
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter.h
@@ -0,0 +1,153 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class RenderViewHost;
+
+// A utility class for formatting platform-specific accessibility information,
+// for use in testing, debugging, and developer tools.
+// This is extended by a subclass for each platform where accessibility is
+// implemented.
+class CONTENT_EXPORT AccessibilityTreeFormatter {
+ public:
+ explicit AccessibilityTreeFormatter(BrowserAccessibility* root);
+ virtual ~AccessibilityTreeFormatter();
+
+ static AccessibilityTreeFormatter* Create(RenderViewHost* rvh);
+
+ // Populates the given DictionaryValue with the accessibility tree.
+ // The dictionary contains a key/value pair for each attribute of the node,
+ // plus a "children" attribute containing a list of all child nodes.
+ // {
+ // "AXName": "node", /* actual attributes will vary by platform */
+ // "position": { /* some attributes may be dictionaries */
+ // "x": 0,
+ // "y": 0
+ // },
+ // /* ... more attributes of |node| */
+ // "children": [ { /* list of children created recursively */
+ // "AXName": "child node 1",
+ // /* ... more attributes */
+ // "children": [ ]
+ // }, {
+ // "AXName": "child name 2",
+ // /* ... more attributes */
+ // "children": [ ]
+ // } ]
+ // }
+ scoped_ptr<base::DictionaryValue> BuildAccessibilityTree();
+
+ // Dumps a BrowserAccessibility tree into a string.
+ void FormatAccessibilityTree(string16* contents);
+
+ // A single filter specification. See GetAllowString() and GetDenyString()
+ // for more information.
+ struct Filter {
+ enum Type {
+ ALLOW,
+ ALLOW_EMPTY,
+ DENY
+ };
+ string16 match_str;
+ Type type;
+
+ Filter(string16 match_str, Type type)
+ : match_str(match_str), type(type) {}
+ };
+
+ // Set regular expression filters that apply to each component of every
+ // line before it's output.
+ void SetFilters(const std::vector<Filter>& filters);
+
+ // Suffix of the expectation file corresponding to html file.
+ // Example:
+ // HTML test: test-file.html
+ // Expected: test-file-expected-mac.txt.
+ // Auto-generated: test-file-actual-mac.txt
+ static const base::FilePath::StringType GetActualFileSuffix();
+ static const base::FilePath::StringType GetExpectedFileSuffix();
+
+ // A platform-specific string that indicates a given line in a file
+ // is an allow-empty, allow or deny filter. Example:
+ // Mac values:
+ // GetAllowEmptyString() -> "@MAC-ALLOW-EMPTY:"
+ // GetAllowString() -> "@MAC-ALLOW:"
+ // GetDenyString() -> "@MAC-DENY:"
+ // Example html:
+ // <!--
+ // @MAC-ALLOW-EMPTY:description*
+ // @MAC-ALLOW:roleDescription*
+ // @MAC-DENY:subrole*
+ // -->
+ // <p>Text</p>
+ static const std::string GetAllowEmptyString();
+ static const std::string GetAllowString();
+ static const std::string GetDenyString();
+
+ protected:
+ void RecursiveFormatAccessibilityTree(const BrowserAccessibility& node,
+ string16* contents,
+ int indent);
+ void RecursiveBuildAccessibilityTree(const BrowserAccessibility& node,
+ base::DictionaryValue* tree_node);
+ void RecursiveFormatAccessibilityTree(const base::DictionaryValue& tree_node,
+ string16* contents,
+ int depth = 0);
+
+ // Overridden by each platform to add the required attributes for each node
+ // into the given dict.
+ void AddProperties(const BrowserAccessibility& node,
+ base::DictionaryValue* dict);
+
+ // Returns true by default; can be overridden by the platform to
+ // prune some children from the tree when they wouldn't be exposed
+ // natively on that platform.
+ virtual bool IncludeChildren(const BrowserAccessibility& node);
+
+ string16 FormatCoordinates(const char* name,
+ const char* x_name,
+ const char* y_name,
+ const base::DictionaryValue& value);
+
+ // Returns a platform specific representation of a BrowserAccessibility.
+ // Should be zero or more complete lines, each with |prefix| prepended
+ // (to indent each line).
+ string16 ToString(const base::DictionaryValue& node, const string16& indent);
+
+ void Initialize();
+
+ bool MatchesFilters(const string16& text, bool default_result) const;
+
+ // Writes the given attribute string out to |line| if it matches the filters.
+ void WriteAttribute(bool include_by_default,
+ const string16& attr,
+ string16* line);
+ void WriteAttribute(bool include_by_default,
+ const std::string& attr,
+ string16* line);
+
+ BrowserAccessibility* root_;
+
+ // Filters used when formatting the accessibility tree as text.
+ std::vector<Filter> filters_;
+
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityTreeFormatter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_H_
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc
new file mode 100644
index 00000000000..11f4be64627
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc
@@ -0,0 +1,152 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_android.h"
+#include "content/common/accessibility_node_data.h"
+
+using base::StringPrintf;
+
+namespace content {
+
+namespace {
+const char* BOOL_ATTRIBUTES[] = {
+ "checkable",
+ "checked",
+ "clickable",
+ "disabled",
+ "editable_text",
+ "focusable",
+ "focused",
+ "invisible",
+ "password",
+ "scrollable",
+ "selected"
+};
+
+const char* STRING_ATTRIBUTES[] = {
+ "name"
+};
+
+const char* INT_ATTRIBUTES[] = {
+ "item_index",
+ "item_count"
+};
+}
+
+void AccessibilityTreeFormatter::Initialize() {
+}
+
+void AccessibilityTreeFormatter::AddProperties(
+ const BrowserAccessibility& node, DictionaryValue* dict) {
+ const BrowserAccessibilityAndroid* android_node =
+ static_cast<const BrowserAccessibilityAndroid*>(&node);
+
+ // Class name.
+ dict->SetString("class", android_node->GetClassName());
+
+ // Bool attributes.
+ dict->SetBoolean("focusable", android_node->IsFocusable());
+ dict->SetBoolean("focused", android_node->IsFocused());
+ dict->SetBoolean("clickable", android_node->IsClickable());
+ dict->SetBoolean("editable_text", android_node->IsEditableText());
+ dict->SetBoolean("checkable", android_node->IsCheckable());
+ dict->SetBoolean("checked", android_node->IsChecked());
+ dict->SetBoolean("disabled", !android_node->IsEnabled());
+ dict->SetBoolean("scrollable", android_node->IsScrollable());
+ dict->SetBoolean("password", android_node->IsPassword());
+ dict->SetBoolean("selected", android_node->IsSelected());
+ dict->SetBoolean("invisible", !android_node->IsVisibleToUser());
+
+ // String attributes.
+ dict->SetString("name", android_node->GetText());
+
+ // Int attributes.
+ dict->SetInteger("item_index", android_node->GetItemIndex());
+ dict->SetInteger("item_count", android_node->GetItemCount());
+}
+
+bool AccessibilityTreeFormatter::IncludeChildren(
+ const BrowserAccessibility& node) {
+ const BrowserAccessibilityAndroid* android_node =
+ static_cast<const BrowserAccessibilityAndroid*>(&node);
+ return !android_node->IsLeaf();
+}
+
+string16 AccessibilityTreeFormatter::ToString(const DictionaryValue& dict,
+ const string16& indent) {
+ string16 line;
+
+ string16 class_value;
+ dict.GetString("class", &class_value);
+ WriteAttribute(true, UTF16ToUTF8(class_value), &line);
+
+ for (unsigned i = 0; i < arraysize(BOOL_ATTRIBUTES); i++) {
+ const char* attribute_name = BOOL_ATTRIBUTES[i];
+ bool value;
+ if (dict.GetBoolean(attribute_name, &value) && value)
+ WriteAttribute(true, attribute_name, &line);
+ }
+
+ for (unsigned i = 0; i < arraysize(STRING_ATTRIBUTES); i++) {
+ const char* attribute_name = STRING_ATTRIBUTES[i];
+ std::string value;
+ if (!dict.GetString(attribute_name, &value) || value.empty())
+ continue;
+ WriteAttribute(true,
+ StringPrintf("%s='%s'", attribute_name, value.c_str()),
+ &line);
+ }
+
+ for (unsigned i = 0; i < arraysize(INT_ATTRIBUTES); i++) {
+ const char* attribute_name = INT_ATTRIBUTES[i];
+ int value;
+ if (!dict.GetInteger(attribute_name, &value) || value == 0)
+ continue;
+ WriteAttribute(true,
+ StringPrintf("%s=%d", attribute_name, value),
+ &line);
+ }
+
+ return indent + line + ASCIIToUTF16("\n");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return FILE_PATH_LITERAL("-actual-android.txt");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return FILE_PATH_LITERAL("-expected-android.txt");
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return "@ANDROID-ALLOW-EMPTY:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowString() {
+ return "@ANDROID-ALLOW:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetDenyString() {
+ return "@ANDROID-DENY:";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc
new file mode 100644
index 00000000000..8dd6b9328a7
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc
@@ -0,0 +1,108 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#include <atk/atk.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_gtk.h"
+
+namespace content {
+
+void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
+ base::DictionaryValue* dict) {
+ BrowserAccessibilityGtk* node_gtk =
+ const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityGtk();
+ AtkObject* atk_object = node_gtk->GetAtkObject();
+ AtkRole role = atk_object_get_role(atk_object);
+ if (role != ATK_ROLE_UNKNOWN)
+ dict->SetString("role", atk_role_get_name(role));
+ dict->SetString("name", atk_object_get_name(atk_object));
+ dict->SetString("description", atk_object_get_description(atk_object));
+ AtkStateSet* state_set =
+ atk_object_ref_state_set(atk_object);
+ ListValue* states = new base::ListValue;
+ for (int i = ATK_STATE_INVALID; i < ATK_STATE_LAST_DEFINED; i++) {
+ AtkStateType state_type = static_cast<AtkStateType>(i);
+ if (atk_state_set_contains_state(state_set, state_type))
+ states->AppendString(atk_state_type_get_name(state_type));
+ }
+ dict->Set("states", states);
+ dict->SetInteger("id", node.renderer_id());
+}
+
+string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& node,
+ const string16& indent) {
+ string16 line;
+ std::string role_value;
+ node.GetString("role", &role_value);
+ if (!role_value.empty())
+ WriteAttribute(true, base::StringPrintf("[%s]", role_value.c_str()), &line);
+
+ std::string name_value;
+ node.GetString("name", &name_value);
+ WriteAttribute(true, base::StringPrintf("name='%s'", name_value.c_str()),
+ &line);
+
+ std::string description_value;
+ node.GetString("description", &description_value);
+ WriteAttribute(false,
+ base::StringPrintf("description='%s'",
+ description_value.c_str()),
+ &line);
+
+ const base::ListValue* states_value;
+ node.GetList("states", &states_value);
+ for (base::ListValue::const_iterator it = states_value->begin();
+ it != states_value->end();
+ ++it) {
+ std::string state_value;
+ if ((*it)->GetAsString(&state_value))
+ WriteAttribute(true, state_value, &line);
+ }
+
+ int id_value;
+ node.GetInteger("id", &id_value);
+ WriteAttribute(false,
+ base::StringPrintf("id=%d", id_value),
+ &line);
+
+ return indent + line + ASCIIToUTF16("\n");
+}
+
+void AccessibilityTreeFormatter::Initialize() {}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return FILE_PATH_LITERAL("-actual-gtk.txt");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return FILE_PATH_LITERAL("-expected-gtk.txt");
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return "@GTK-ALLOW-EMPTY:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowString() {
+ return "@GTK-ALLOW:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetDenyString() {
+ return "@GTK-DENY:";
+}
+
+}
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm
new file mode 100644
index 00000000000..58881b4af4b
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -0,0 +1,250 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_cocoa.h"
+#include "content/browser/accessibility/browser_accessibility_mac.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+
+using base::StringPrintf;
+using base::SysNSStringToUTF8;
+using base::SysNSStringToUTF16;
+using std::string;
+
+namespace content {
+
+namespace {
+
+const char* kPositionDictAttr = "position";
+const char* kXCoordDictAttr = "x";
+const char* kYCoordDictAttr = "y";
+const char* kSizeDictAttr = "size";
+const char* kWidthDictAttr = "width";
+const char* kHeightDictAttr = "height";
+const char* kRangeLocDictAttr = "loc";
+const char* kRangeLenDictAttr = "len";
+
+scoped_ptr<base::DictionaryValue> PopulatePosition(
+ const BrowserAccessibility& node) {
+ scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue);
+ // The NSAccessibility position of an object is in global coordinates and
+ // based on the lower-left corner of the object. To make this easier and less
+ // confusing, convert it to local window coordinates using the top-left
+ // corner when dumping the position.
+ BrowserAccessibility* root = node.manager()->GetRoot();
+ BrowserAccessibilityCocoa* cocoa_root = root->ToBrowserAccessibilityCocoa();
+ NSPoint root_position = [[cocoa_root position] pointValue];
+ NSSize root_size = [[cocoa_root size] sizeValue];
+ int root_top = -static_cast<int>(root_position.y + root_size.height);
+ int root_left = static_cast<int>(root_position.x);
+
+ BrowserAccessibilityCocoa* cocoa_node =
+ const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
+ NSPoint node_position = [[cocoa_node position] pointValue];
+ NSSize node_size = [[cocoa_node size] sizeValue];
+
+ position->SetInteger(kXCoordDictAttr,
+ static_cast<int>(node_position.x - root_left));
+ position->SetInteger(kYCoordDictAttr,
+ static_cast<int>(-node_position.y - node_size.height - root_top));
+ return position.Pass();
+}
+
+scoped_ptr<base::DictionaryValue>
+PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) {
+ scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue);
+ NSSize node_size = [[cocoa_node size] sizeValue];
+ size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height));
+ size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width));
+ return size.Pass();
+}
+
+scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) {
+ scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue);
+ rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location));
+ rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length));
+ return rangeDict.Pass();
+}
+
+// Returns true if |value| is an NSValue containing a NSRange.
+bool IsRangeValue(id value) {
+ if (![value isKindOfClass:[NSValue class]])
+ return false;
+ return 0 == strcmp([value objCType], @encode(NSRange));
+}
+
+NSArray* BuildAllAttributesArray() {
+ NSArray* array = [NSArray arrayWithObjects:
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilityMinValueAttribute,
+ NSAccessibilityMaxValueAttribute,
+ NSAccessibilityValueDescriptionAttribute,
+ NSAccessibilityDescriptionAttribute,
+ NSAccessibilityHelpAttribute,
+ @"AXInvalid",
+ NSAccessibilityDisclosingAttribute,
+ NSAccessibilityDisclosureLevelAttribute,
+ @"AXAccessKey",
+ @"AXARIAAtomic",
+ @"AXARIABusy",
+ @"AXARIALive",
+ @"AXARIARelevant",
+ NSAccessibilityColumnIndexRangeAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityIndexAttribute,
+ @"AXLoaded",
+ @"AXLoadingProcess",
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilityOrientationAttribute,
+ @"AXRequired",
+ NSAccessibilityRowIndexRangeAttribute,
+ NSAccessibilityURLAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ @"AXVisited",
+ nil];
+ return [array retain];
+}
+
+} // namespace
+
+void AccessibilityTreeFormatter::Initialize() {
+}
+
+
+void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
+ base::DictionaryValue* dict) {
+ BrowserAccessibilityCocoa* cocoa_node =
+ const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
+ NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
+
+ string role = SysNSStringToUTF8(
+ [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
+ dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
+
+ NSString* subrole =
+ [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
+ if (subrole != nil) {
+ dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
+ SysNSStringToUTF8(subrole));
+ }
+
+ CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
+ for (NSString* requestedAttribute in all_attributes) {
+ if (![supportedAttributes containsObject:requestedAttribute]) {
+ continue;
+ }
+ id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
+ if (IsRangeValue(value)) {
+ dict->Set(
+ SysNSStringToUTF8(requestedAttribute),
+ PopulateRange([value rangeValue]).release());
+ } else if (value != nil) {
+ dict->SetString(
+ SysNSStringToUTF8(requestedAttribute),
+ SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]));
+ }
+ }
+ dict->Set(kPositionDictAttr, PopulatePosition(node).release());
+ dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
+}
+
+string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict,
+ const string16& indent) {
+ string16 line;
+ NSArray* defaultAttributes =
+ [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
+ NSAccessibilityValueAttribute,
+ nil];
+ string s_value;
+ dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
+ WriteAttribute(true, UTF8ToUTF16(s_value), &line);
+
+ string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
+ if (dict.GetString(subroleAttribute, &s_value)) {
+ WriteAttribute(false,
+ StringPrintf("%s=%s",
+ subroleAttribute.c_str(), s_value.c_str()),
+ &line);
+ }
+
+ CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
+ for (NSString* requestedAttribute in all_attributes) {
+ string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
+ const base::DictionaryValue* d_value;
+ if (dict.GetDictionary(requestedAttributeUTF8, &d_value)) {
+ std::string json_value;
+ base::JSONWriter::Write(d_value, &json_value);
+ WriteAttribute(
+ [defaultAttributes containsObject:requestedAttribute],
+ StringPrintf("%s=%s",
+ requestedAttributeUTF8.c_str(),
+ json_value.c_str()),
+ &line);
+ }
+ if (!dict.GetString(requestedAttributeUTF8, &s_value))
+ continue;
+ WriteAttribute([defaultAttributes containsObject:requestedAttribute],
+ StringPrintf("%s='%s'",
+ requestedAttributeUTF8.c_str(),
+ s_value.c_str()),
+ &line);
+ }
+ const base::DictionaryValue* d_value = NULL;
+ if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
+ WriteAttribute(false,
+ FormatCoordinates(kPositionDictAttr,
+ kXCoordDictAttr, kYCoordDictAttr,
+ *d_value),
+ &line);
+ }
+ if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
+ WriteAttribute(false,
+ FormatCoordinates(kSizeDictAttr,
+ kWidthDictAttr, kHeightDictAttr, *d_value),
+ &line);
+ }
+
+ return indent + line + ASCIIToUTF16("\n");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return FILE_PATH_LITERAL("-actual-mac.txt");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return FILE_PATH_LITERAL("-expected-mac.txt");
+}
+
+// static
+const string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return "@MAC-ALLOW-EMPTY:";
+}
+
+// static
+const string AccessibilityTreeFormatter::GetAllowString() {
+ return "@MAC-ALLOW:";
+}
+
+// static
+const string AccessibilityTreeFormatter::GetDenyString() {
+ return "@MAC-DENY:";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc
new file mode 100644
index 00000000000..053e39b9b09
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc
@@ -0,0 +1,265 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter_utils_win.h"
+
+#include <oleacc.h>
+
+#include <map>
+#include <string>
+
+#include "base/memory/singleton.h"
+#include "base/strings/string_util.h"
+#include "third_party/iaccessible2/ia2_api_all.h"
+
+namespace content {
+namespace {
+
+class AccessibilityRoleStateMap {
+ public:
+ static AccessibilityRoleStateMap* GetInstance();
+
+ std::map<int32, string16> ia_role_string_map;
+ std::map<int32, string16> ia2_role_string_map;
+ std::map<int32, string16> ia_state_string_map;
+ std::map<int32, string16> ia2_state_string_map;
+
+ private:
+ AccessibilityRoleStateMap();
+ virtual ~AccessibilityRoleStateMap() {}
+
+ friend struct DefaultSingletonTraits<AccessibilityRoleStateMap>;
+
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityRoleStateMap);
+};
+
+// static
+AccessibilityRoleStateMap* AccessibilityRoleStateMap::GetInstance() {
+ return Singleton<AccessibilityRoleStateMap,
+ LeakySingletonTraits<AccessibilityRoleStateMap> >::get();
+}
+
+AccessibilityRoleStateMap::AccessibilityRoleStateMap() {
+// Convenience macros for generating readable strings.
+#define IA_ROLE_MAP(x) ia_role_string_map[x] = L#x; \
+ ia2_role_string_map[x] = L#x;
+#define IA2_ROLE_MAP(x) ia2_role_string_map[x] = L#x;
+#define IA_STATE_MAP(x) ia_state_string_map[STATE_SYSTEM_##x] = L#x;
+#define IA2_STATE_MAP(x) ia2_state_string_map[x] = L#x;
+
+ // MSAA / IAccessible roles. Each one of these is also a valid
+ // IAccessible2 role, the IA_ROLE_MAP macro adds it to both.
+ IA_ROLE_MAP(ROLE_SYSTEM_ALERT)
+ IA_ROLE_MAP(ROLE_SYSTEM_ANIMATION)
+ IA_ROLE_MAP(ROLE_SYSTEM_APPLICATION)
+ IA_ROLE_MAP(ROLE_SYSTEM_BORDER)
+ IA_ROLE_MAP(ROLE_SYSTEM_BUTTONDROPDOWN)
+ IA_ROLE_MAP(ROLE_SYSTEM_BUTTONDROPDOWNGRID)
+ IA_ROLE_MAP(ROLE_SYSTEM_BUTTONMENU)
+ IA_ROLE_MAP(ROLE_SYSTEM_CARET)
+ IA_ROLE_MAP(ROLE_SYSTEM_CELL)
+ IA_ROLE_MAP(ROLE_SYSTEM_CHARACTER)
+ IA_ROLE_MAP(ROLE_SYSTEM_CHART)
+ IA_ROLE_MAP(ROLE_SYSTEM_CHECKBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_CLIENT)
+ IA_ROLE_MAP(ROLE_SYSTEM_CLOCK)
+ IA_ROLE_MAP(ROLE_SYSTEM_COLUMN)
+ IA_ROLE_MAP(ROLE_SYSTEM_COLUMNHEADER)
+ IA_ROLE_MAP(ROLE_SYSTEM_COMBOBOX)
+ IA_ROLE_MAP(ROLE_SYSTEM_CURSOR)
+ IA_ROLE_MAP(ROLE_SYSTEM_DIAGRAM)
+ IA_ROLE_MAP(ROLE_SYSTEM_DIAL)
+ IA_ROLE_MAP(ROLE_SYSTEM_DIALOG)
+ IA_ROLE_MAP(ROLE_SYSTEM_DOCUMENT)
+ IA_ROLE_MAP(ROLE_SYSTEM_DROPLIST)
+ IA_ROLE_MAP(ROLE_SYSTEM_EQUATION)
+ IA_ROLE_MAP(ROLE_SYSTEM_GRAPHIC)
+ IA_ROLE_MAP(ROLE_SYSTEM_GRIP)
+ IA_ROLE_MAP(ROLE_SYSTEM_GROUPING)
+ IA_ROLE_MAP(ROLE_SYSTEM_HELPBALLOON)
+ IA_ROLE_MAP(ROLE_SYSTEM_HOTKEYFIELD)
+ IA_ROLE_MAP(ROLE_SYSTEM_INDICATOR)
+ IA_ROLE_MAP(ROLE_SYSTEM_IPADDRESS)
+ IA_ROLE_MAP(ROLE_SYSTEM_LINK)
+ IA_ROLE_MAP(ROLE_SYSTEM_LIST)
+ IA_ROLE_MAP(ROLE_SYSTEM_LISTITEM)
+ IA_ROLE_MAP(ROLE_SYSTEM_MENUBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_MENUITEM)
+ IA_ROLE_MAP(ROLE_SYSTEM_MENUPOPUP)
+ IA_ROLE_MAP(ROLE_SYSTEM_OUTLINE)
+ IA_ROLE_MAP(ROLE_SYSTEM_OUTLINEBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_OUTLINEITEM)
+ IA_ROLE_MAP(ROLE_SYSTEM_PAGETAB)
+ IA_ROLE_MAP(ROLE_SYSTEM_PAGETABLIST)
+ IA_ROLE_MAP(ROLE_SYSTEM_PANE)
+ IA_ROLE_MAP(ROLE_SYSTEM_PROGRESSBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_PROPERTYPAGE)
+ IA_ROLE_MAP(ROLE_SYSTEM_PUSHBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_RADIOBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_ROW)
+ IA_ROLE_MAP(ROLE_SYSTEM_ROWHEADER)
+ IA_ROLE_MAP(ROLE_SYSTEM_SCROLLBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_SEPARATOR)
+ IA_ROLE_MAP(ROLE_SYSTEM_SLIDER)
+ IA_ROLE_MAP(ROLE_SYSTEM_SOUND)
+ IA_ROLE_MAP(ROLE_SYSTEM_SPINBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_SPLITBUTTON)
+ IA_ROLE_MAP(ROLE_SYSTEM_STATICTEXT)
+ IA_ROLE_MAP(ROLE_SYSTEM_STATUSBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_TABLE)
+ IA_ROLE_MAP(ROLE_SYSTEM_TEXT)
+ IA_ROLE_MAP(ROLE_SYSTEM_TITLEBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_TOOLBAR)
+ IA_ROLE_MAP(ROLE_SYSTEM_TOOLTIP)
+ IA_ROLE_MAP(ROLE_SYSTEM_WHITESPACE)
+ IA_ROLE_MAP(ROLE_SYSTEM_WINDOW)
+
+ // IAccessible2 roles.
+ IA2_ROLE_MAP(IA2_ROLE_CANVAS)
+ IA2_ROLE_MAP(IA2_ROLE_CAPTION)
+ IA2_ROLE_MAP(IA2_ROLE_CHECK_MENU_ITEM)
+ IA2_ROLE_MAP(IA2_ROLE_COLOR_CHOOSER)
+ IA2_ROLE_MAP(IA2_ROLE_DATE_EDITOR)
+ IA2_ROLE_MAP(IA2_ROLE_DESKTOP_ICON)
+ IA2_ROLE_MAP(IA2_ROLE_DESKTOP_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_DIRECTORY_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_EDITBAR)
+ IA2_ROLE_MAP(IA2_ROLE_EMBEDDED_OBJECT)
+ IA2_ROLE_MAP(IA2_ROLE_ENDNOTE)
+ IA2_ROLE_MAP(IA2_ROLE_FILE_CHOOSER)
+ IA2_ROLE_MAP(IA2_ROLE_FONT_CHOOSER)
+ IA2_ROLE_MAP(IA2_ROLE_FOOTER)
+ IA2_ROLE_MAP(IA2_ROLE_FOOTNOTE)
+ IA2_ROLE_MAP(IA2_ROLE_FORM)
+ IA2_ROLE_MAP(IA2_ROLE_FRAME)
+ IA2_ROLE_MAP(IA2_ROLE_GLASS_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_HEADER)
+ IA2_ROLE_MAP(IA2_ROLE_HEADING)
+ IA2_ROLE_MAP(IA2_ROLE_ICON)
+ IA2_ROLE_MAP(IA2_ROLE_IMAGE_MAP)
+ IA2_ROLE_MAP(IA2_ROLE_INPUT_METHOD_WINDOW)
+ IA2_ROLE_MAP(IA2_ROLE_INTERNAL_FRAME)
+ IA2_ROLE_MAP(IA2_ROLE_LABEL)
+ IA2_ROLE_MAP(IA2_ROLE_LAYERED_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_NOTE)
+ IA2_ROLE_MAP(IA2_ROLE_OPTION_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_PAGE)
+ IA2_ROLE_MAP(IA2_ROLE_PARAGRAPH)
+ IA2_ROLE_MAP(IA2_ROLE_RADIO_MENU_ITEM)
+ IA2_ROLE_MAP(IA2_ROLE_REDUNDANT_OBJECT)
+ IA2_ROLE_MAP(IA2_ROLE_ROOT_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_RULER)
+ IA2_ROLE_MAP(IA2_ROLE_SCROLL_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_SECTION)
+ IA2_ROLE_MAP(IA2_ROLE_SHAPE)
+ IA2_ROLE_MAP(IA2_ROLE_SPLIT_PANE)
+ IA2_ROLE_MAP(IA2_ROLE_TEAR_OFF_MENU)
+ IA2_ROLE_MAP(IA2_ROLE_TERMINAL)
+ IA2_ROLE_MAP(IA2_ROLE_TEXT_FRAME)
+ IA2_ROLE_MAP(IA2_ROLE_TOGGLE_BUTTON)
+ IA2_ROLE_MAP(IA2_ROLE_UNKNOWN)
+ IA2_ROLE_MAP(IA2_ROLE_VIEW_PORT)
+
+ // MSAA / IAccessible states. Unlike roles, these are not also IA2 states.
+ IA_STATE_MAP(ALERT_HIGH)
+ IA_STATE_MAP(ALERT_LOW)
+ IA_STATE_MAP(ALERT_MEDIUM)
+ IA_STATE_MAP(ANIMATED)
+ IA_STATE_MAP(BUSY)
+ IA_STATE_MAP(CHECKED)
+ IA_STATE_MAP(COLLAPSED)
+ IA_STATE_MAP(DEFAULT)
+ IA_STATE_MAP(EXPANDED)
+ IA_STATE_MAP(EXTSELECTABLE)
+ IA_STATE_MAP(FLOATING)
+ IA_STATE_MAP(FOCUSABLE)
+ IA_STATE_MAP(FOCUSED)
+ IA_STATE_MAP(HASPOPUP)
+ IA_STATE_MAP(HOTTRACKED)
+ IA_STATE_MAP(INVISIBLE)
+ IA_STATE_MAP(LINKED)
+ IA_STATE_MAP(MARQUEED)
+ IA_STATE_MAP(MIXED)
+ IA_STATE_MAP(MOVEABLE)
+ IA_STATE_MAP(MULTISELECTABLE)
+ IA_STATE_MAP(OFFSCREEN)
+ IA_STATE_MAP(PRESSED)
+ IA_STATE_MAP(PROTECTED)
+ IA_STATE_MAP(READONLY)
+ IA_STATE_MAP(SELECTABLE)
+ IA_STATE_MAP(SELECTED)
+ IA_STATE_MAP(SELFVOICING)
+ IA_STATE_MAP(SIZEABLE)
+ IA_STATE_MAP(TRAVERSED)
+ IA_STATE_MAP(UNAVAILABLE)
+
+ // IAccessible2 states.
+ IA2_STATE_MAP(IA2_STATE_ACTIVE)
+ IA2_STATE_MAP(IA2_STATE_ARMED)
+ IA2_STATE_MAP(IA2_STATE_DEFUNCT)
+ IA2_STATE_MAP(IA2_STATE_EDITABLE)
+ IA2_STATE_MAP(IA2_STATE_ICONIFIED)
+ IA2_STATE_MAP(IA2_STATE_INVALID_ENTRY)
+ IA2_STATE_MAP(IA2_STATE_MANAGES_DESCENDANTS)
+ IA2_STATE_MAP(IA2_STATE_MODAL)
+ IA2_STATE_MAP(IA2_STATE_MULTI_LINE)
+ IA2_STATE_MAP(IA2_STATE_REQUIRED)
+ IA2_STATE_MAP(IA2_STATE_SELECTABLE_TEXT)
+ IA2_STATE_MAP(IA2_STATE_SINGLE_LINE)
+ IA2_STATE_MAP(IA2_STATE_STALE)
+ IA2_STATE_MAP(IA2_STATE_SUPPORTS_AUTOCOMPLETION)
+ IA2_STATE_MAP(IA2_STATE_TRANSIENT)
+
+ // Untested states include those that would be repeated on nearly every node,
+ // or would vary based on window size.
+ // IA2_STATE_MAP(IA2_STATE_HORIZONTAL) // Untested.
+ // IA2_STATE_MAP(IA2_STATE_OPAQUE) // Untested.
+ // IA2_STATE_MAP(IA2_STATE_VERTICAL) // Untested.
+}
+
+} // namespace.
+
+string16 IAccessibleRoleToString(int32 ia_role) {
+ return AccessibilityRoleStateMap::GetInstance()->ia_role_string_map[ia_role];
+}
+
+string16 IAccessible2RoleToString(int32 ia_role) {
+ return AccessibilityRoleStateMap::GetInstance()->ia2_role_string_map[ia_role];
+}
+
+void IAccessibleStateToStringVector(int32 ia_state,
+ std::vector<string16>* result) {
+ const std::map<int32, string16>& state_string_map =
+ AccessibilityRoleStateMap::GetInstance()->ia_state_string_map;
+ std::map<int32, string16>::const_iterator it;
+ for (it = state_string_map.begin(); it != state_string_map.end(); ++it) {
+ if (it->first & ia_state)
+ result->push_back(it->second);
+ }
+}
+
+string16 IAccessibleStateToString(int32 ia_state) {
+ std::vector<string16> strings;
+ IAccessibleStateToStringVector(ia_state, &strings);
+ return JoinString(strings, ',');
+}
+
+void IAccessible2StateToStringVector(int32 ia2_state,
+ std::vector<string16>* result) {
+ const std::map<int32, string16>& state_string_map =
+ AccessibilityRoleStateMap::GetInstance()->ia2_state_string_map;
+ std::map<int32, string16>::const_iterator it;
+ for (it = state_string_map.begin(); it != state_string_map.end(); ++it) {
+ if (it->first & ia2_state)
+ result->push_back(it->second);
+ }
+}
+
+string16 IAccessible2StateToString(int32 ia2_state) {
+ std::vector<string16> strings;
+ IAccessible2StateToStringVector(ia2_state, &strings);
+ return JoinString(strings, ',');
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h
new file mode 100644
index 00000000000..1baf20d54a8
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h
@@ -0,0 +1,27 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+CONTENT_EXPORT string16 IAccessibleRoleToString(int32 ia_role);
+CONTENT_EXPORT string16 IAccessible2RoleToString(int32 ia_role);
+CONTENT_EXPORT string16 IAccessibleStateToString(int32 ia_state);
+CONTENT_EXPORT void IAccessibleStateToStringVector(
+ int32 ia_state, std::vector<string16>* result);
+CONTENT_EXPORT string16 IAccessible2StateToString(int32 ia2_state);
+CONTENT_EXPORT void IAccessible2StateToStringVector(
+ int32 ia_state, std::vector<string16>* result);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc
new file mode 100644
index 00000000000..679843dc42e
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc
@@ -0,0 +1,319 @@
+// 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 "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#include <oleacc.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_win.h"
+#include "content/common/accessibility_node_data.h"
+#include "third_party/iaccessible2/ia2_api_all.h"
+#include "ui/base/win/atl_module.h"
+
+using base::StringPrintf;
+
+namespace content {
+
+const char* ALL_ATTRIBUTES[] = {
+ "name",
+ "value",
+ "states",
+ "attributes",
+ "role_name",
+ "currentValue",
+ "minimumValue",
+ "maximumValue",
+ "description",
+ "default_action",
+ "keyboard_shortcut",
+ "location",
+ "size",
+ "index_in_parent",
+ "n_relations",
+ "group_level",
+ "similar_items_in_group",
+ "position_in_group",
+ "table_rows",
+ "table_columns",
+ "row_index",
+ "column_index",
+ "n_characters",
+ "caret_offset",
+ "n_selections",
+ "selection_start",
+ "selection_end"
+};
+
+void AccessibilityTreeFormatter::Initialize() {
+ ui::win::CreateATLModuleIfNeeded();
+}
+
+void AccessibilityTreeFormatter::AddProperties(
+ const BrowserAccessibility& node, base::DictionaryValue* dict) {
+ BrowserAccessibilityWin* acc_obj =
+ const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityWin();
+
+ VARIANT variant_self;
+ variant_self.vt = VT_I4;
+ variant_self.lVal = CHILDID_SELF;
+
+ dict->SetString("role", IAccessible2RoleToString(acc_obj->ia2_role()));
+
+ CComBSTR msaa_variant;
+ HRESULT hresult = acc_obj->get_accName(variant_self, &msaa_variant);
+ if (hresult == S_OK)
+ dict->SetString("name", msaa_variant.m_str);
+ hresult = acc_obj->get_accValue(variant_self, &msaa_variant);
+ if (hresult == S_OK)
+ dict->SetString("value", msaa_variant.m_str);
+
+ std::vector<string16> state_strings;
+ int32 ia_state = acc_obj->ia_state();
+
+ // Avoid flakiness: these states depend on whether the window is focused
+ // and the position of the mouse cursor.
+ ia_state &= ~STATE_SYSTEM_HOTTRACKED;
+ ia_state &= ~STATE_SYSTEM_OFFSCREEN;
+
+ IAccessibleStateToStringVector(ia_state, &state_strings);
+ IAccessible2StateToStringVector(acc_obj->ia2_state(), &state_strings);
+ base::ListValue* states = new base::ListValue;
+ for (std::vector<string16>::const_iterator it = state_strings.begin();
+ it != state_strings.end();
+ ++it) {
+ states->AppendString(UTF16ToUTF8(*it));
+ }
+ dict->Set("states", states);
+
+ const std::vector<string16>& ia2_attributes = acc_obj->ia2_attributes();
+ base::ListValue* attributes = new base::ListValue;
+ for (std::vector<string16>::const_iterator it = ia2_attributes.begin();
+ it != ia2_attributes.end();
+ ++it) {
+ attributes->AppendString(UTF16ToUTF8(*it));
+ }
+ dict->Set("attributes", attributes);
+
+ dict->SetString("role_name", acc_obj->role_name());
+
+ VARIANT currentValue;
+ if (acc_obj->get_currentValue(&currentValue) == S_OK)
+ dict->SetDouble("currentValue", V_R8(&currentValue));
+
+ VARIANT minimumValue;
+ if (acc_obj->get_minimumValue(&minimumValue) == S_OK)
+ dict->SetDouble("minimumValue", V_R8(&minimumValue));
+
+ VARIANT maximumValue;
+ if (acc_obj->get_maximumValue(&maximumValue) == S_OK)
+ dict->SetDouble("maximumValue", V_R8(&maximumValue));
+
+ hresult = acc_obj->get_accDescription(variant_self, &msaa_variant);
+ if (hresult == S_OK)
+ dict->SetString("description", msaa_variant.m_str);
+
+ hresult = acc_obj->get_accDefaultAction(variant_self, &msaa_variant);
+ if (hresult == S_OK)
+ dict->SetString("default_action", msaa_variant.m_str);
+
+ hresult = acc_obj->get_accKeyboardShortcut(variant_self, &msaa_variant);
+ if (hresult == S_OK)
+ dict->SetString("keyboard_shortcut", msaa_variant.m_str);
+
+ hresult = acc_obj->get_accHelp(variant_self, &msaa_variant);
+ if (S_OK == hresult)
+ dict->SetString("help", msaa_variant.m_str);
+
+ BrowserAccessibility* root = node.manager()->GetRoot();
+ LONG left, top, width, height;
+ LONG root_left, root_top, root_width, root_height;
+ if (acc_obj->accLocation(&left, &top, &width, &height, variant_self)
+ != S_FALSE
+ && root->ToBrowserAccessibilityWin()->accLocation(
+ &root_left, &root_top, &root_width, &root_height, variant_self)
+ != S_FALSE) {
+ base::DictionaryValue* location = new base::DictionaryValue;
+ location->SetInteger("x", left - root_left);
+ location->SetInteger("y", top - root_top);
+ dict->Set("location", location);
+
+ base::DictionaryValue* size = new base::DictionaryValue;
+ size->SetInteger("width", width);
+ size->SetInteger("height", height);
+ dict->Set("size", size);
+ }
+
+ LONG index_in_parent;
+ if (acc_obj->get_indexInParent(&index_in_parent) == S_OK)
+ dict->SetInteger("index_in_parent", index_in_parent);
+
+ LONG n_relations;
+ if (acc_obj->get_nRelations(&n_relations) == S_OK)
+ dict->SetInteger("n_relations", n_relations);
+
+ LONG group_level, similar_items_in_group, position_in_group;
+ if (acc_obj->get_groupPosition(&group_level,
+ &similar_items_in_group,
+ &position_in_group) == S_OK) {
+ dict->SetInteger("group_level", group_level);
+ dict->SetInteger("similar_items_in_group", similar_items_in_group);
+ dict->SetInteger("position_in_group", position_in_group);
+ }
+ LONG table_rows;
+ if (acc_obj->get_nRows(&table_rows) == S_OK)
+ dict->SetInteger("table_rows", table_rows);
+ LONG table_columns;
+ if (acc_obj->get_nRows(&table_columns) == S_OK)
+ dict->SetInteger("table_columns", table_columns);
+ LONG row_index;
+ if (acc_obj->get_rowIndex(&row_index) == S_OK)
+ dict->SetInteger("row_index", row_index);
+ LONG column_index;
+ if (acc_obj->get_columnIndex(&column_index) == S_OK)
+ dict->SetInteger("column_index", column_index);
+ LONG n_characters;
+ if (acc_obj->get_nCharacters(&n_characters) == S_OK)
+ dict->SetInteger("n_characters", n_characters);
+ LONG caret_offset;
+ if (acc_obj->get_caretOffset(&caret_offset) == S_OK)
+ dict->SetInteger("caret_offset", caret_offset);
+ LONG n_selections;
+ if (acc_obj->get_nSelections(&n_selections) == S_OK) {
+ dict->SetInteger("n_selections", n_selections);
+ if (n_selections > 0) {
+ LONG start, end;
+ if (acc_obj->get_selection(0, &start, &end) == S_OK) {
+ dict->SetInteger("selection_start", start);
+ dict->SetInteger("selection_end", end);
+ }
+ }
+ }
+}
+
+string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict,
+ const string16& indent) {
+ string16 line;
+
+ string16 role_value;
+ dict.GetString("role", &role_value);
+ WriteAttribute(true, UTF16ToUTF8(role_value), &line);
+
+ string16 name_value;
+ dict.GetString("name", &name_value);
+ WriteAttribute(true, base::StringPrintf(L"name='%ls'", name_value.c_str()),
+ &line);
+
+ for (int i = 0; i < arraysize(ALL_ATTRIBUTES); i++) {
+ const char* attribute_name = ALL_ATTRIBUTES[i];
+ const base::Value* value;
+ if (!dict.Get(attribute_name, &value))
+ continue;
+
+ switch (value->GetType()) {
+ case base::Value::TYPE_STRING: {
+ string16 string_value;
+ value->GetAsString(&string_value);
+ WriteAttribute(false,
+ StringPrintf(L"%ls='%ls'",
+ UTF8ToUTF16(attribute_name).c_str(),
+ string_value.c_str()),
+ &line);
+ break;
+ }
+ case base::Value::TYPE_INTEGER: {
+ int int_value;
+ value->GetAsInteger(&int_value);
+ WriteAttribute(false,
+ base::StringPrintf(L"%ls=%d",
+ UTF8ToUTF16(attribute_name).c_str(),
+ int_value),
+ &line);
+ break;
+ }
+ case base::Value::TYPE_DOUBLE: {
+ double double_value;
+ value->GetAsDouble(&double_value);
+ WriteAttribute(false,
+ base::StringPrintf(L"%ls=%.2f",
+ UTF8ToUTF16(attribute_name).c_str(),
+ double_value),
+ &line);
+ break;
+ }
+ case base::Value::TYPE_LIST: {
+ // Currently all list values are string and are written without
+ // attribute names.
+ const base::ListValue* list_value;
+ value->GetAsList(&list_value);
+ for (base::ListValue::const_iterator it = list_value->begin();
+ it != list_value->end();
+ ++it) {
+ string16 string_value;
+ if ((*it)->GetAsString(&string_value))
+ WriteAttribute(false, string_value, &line);
+ }
+ break;
+ }
+ case base::Value::TYPE_DICTIONARY: {
+ // Currently all dictionary values are coordinates.
+ // Revisit this if that changes.
+ const base::DictionaryValue* dict_value;
+ value->GetAsDictionary(&dict_value);
+ if (strcmp(attribute_name, "size") == 0) {
+ WriteAttribute(false,
+ FormatCoordinates("size", "width", "height",
+ *dict_value),
+ &line);
+ } else if (strcmp(attribute_name, "location") == 0) {
+ WriteAttribute(false,
+ FormatCoordinates("location", "x", "y", *dict_value),
+ &line);
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ return indent + line + ASCIIToUTF16("\n");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return FILE_PATH_LITERAL("-actual-win.txt");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return FILE_PATH_LITERAL("-expected-win.txt");
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return "@WIN-ALLOW-EMPTY:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowString() {
+ return "@WIN-ALLOW:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetDenyString() {
+ return "@WIN-DENY:";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_ui.cc b/chromium/content/browser/accessibility/accessibility_ui.cc
new file mode 100644
index 00000000000..585be6806a1
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_ui.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 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 "content/browser/accessibility/accessibility_ui.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/accessibility/accessibility_tree_formatter.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_state_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/view_message_enums.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+#include "net/base/escape.h"
+
+static const char kDataFile[] = "targets-data.json";
+
+static const char kProcessIdField[] = "processId";
+static const char kRouteIdField[] = "routeId";
+static const char kUrlField[] = "url";
+static const char kNameField[] = "name";
+static const char kFaviconUrlField[] = "favicon_url";
+static const char kPidField[] = "pid";
+static const char kAccessibilityModeField[] = "a11y_mode";
+
+namespace content {
+
+namespace {
+
+base::DictionaryValue* BuildTargetDescriptor(
+ const GURL& url,
+ const std::string& name,
+ const GURL& favicon_url,
+ int process_id,
+ int route_id,
+ AccessibilityMode accessibility_mode,
+ base::ProcessHandle handle = base::kNullProcessHandle) {
+ base::DictionaryValue* target_data = new base::DictionaryValue();
+ target_data->SetInteger(kProcessIdField, process_id);
+ target_data->SetInteger(kRouteIdField, route_id);
+ target_data->SetString(kUrlField, url.spec());
+ target_data->SetString(kNameField, net::EscapeForHTML(name));
+ target_data->SetInteger(kPidField, base::GetProcId(handle));
+ target_data->SetString(kFaviconUrlField, favicon_url.spec());
+ target_data->SetInteger(kAccessibilityModeField,
+ accessibility_mode);
+ return target_data;
+}
+
+base::DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh) {
+ WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
+ std::string title;
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rvh);
+ AccessibilityMode accessibility_mode = rwhi->accessibility_mode();
+
+ GURL url;
+ GURL favicon_url;
+ if (web_contents) {
+ url = web_contents->GetURL();
+ title = UTF16ToUTF8(web_contents->GetTitle());
+ NavigationController& controller = web_contents->GetController();
+ NavigationEntry* entry = controller.GetActiveEntry();
+ if (entry != NULL && entry->GetURL().is_valid())
+ favicon_url = entry->GetFavicon().url;
+ }
+
+ return BuildTargetDescriptor(url,
+ title,
+ favicon_url,
+ rvh->GetProcess()->GetID(),
+ rvh->GetRoutingID(),
+ accessibility_mode);
+}
+
+void SendTargetsData(
+ const WebUIDataSource::GotDataCallback& callback) {
+ scoped_ptr<base::ListValue> rvh_list(new base::ListValue());
+
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ // Ignore processes that don't have a connection, such as crashed tabs.
+ if (!widgets[i]->GetProcess()->HasConnection())
+ continue;
+ if (!widgets[i]->IsRenderView())
+ continue;
+
+ RenderViewHost* rvh = RenderViewHost::From(widgets[i]);
+ rvh_list->Append(BuildTargetDescriptor(rvh));
+ }
+
+ scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
+ data->Set("list", rvh_list.release());
+ scoped_ptr<base::FundamentalValue> a11y_mode(new base::FundamentalValue(
+ BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode()));
+ data->Set("global_a11y_mode", a11y_mode.release());
+
+ std::string json_string;
+ base::JSONWriter::Write(data.get(), &json_string);
+
+ callback.Run(base::RefCountedString::TakeString(&json_string));
+}
+
+bool HandleRequestCallback(
+ const std::string& path,
+ const WebUIDataSource::GotDataCallback& callback) {
+ if (path != kDataFile)
+ return false;
+
+ SendTargetsData(callback);
+ return true;
+}
+
+} // namespace
+
+AccessibilityUI::AccessibilityUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ // Set up the chrome://accessibility source.
+ WebUIDataSource* html_source =
+ WebUIDataSource::Create(kChromeUIAccessibilityHost);
+ html_source->SetUseJsonJSFormatV2();
+
+ web_ui->RegisterMessageCallback(
+ "toggleAccessibility",
+ base::Bind(&AccessibilityUI::ToggleAccessibility,
+ base::Unretained(this)));
+ web_ui->RegisterMessageCallback(
+ "toggleGlobalAccessibility",
+ base::Bind(&AccessibilityUI::ToggleGlobalAccessibility,
+ base::Unretained(this)));
+ web_ui->RegisterMessageCallback(
+ "requestAccessibilityTree",
+ base::Bind(&AccessibilityUI::RequestAccessibilityTree,
+ base::Unretained(this)));
+
+ // Add required resources.
+ html_source->SetJsonPath("strings.js");
+ html_source->AddResourcePath("accessibility.css", IDR_ACCESSIBILITY_CSS);
+ html_source->AddResourcePath("accessibility.js", IDR_ACCESSIBILITY_JS);
+ html_source->SetDefaultResource(IDR_ACCESSIBILITY_HTML);
+ html_source->SetRequestFilter(base::Bind(&HandleRequestCallback));
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, html_source);
+}
+
+AccessibilityUI::~AccessibilityUI() {
+}
+
+void AccessibilityUI::ToggleAccessibility(const base::ListValue* args) {
+ std::string process_id_str;
+ std::string route_id_str;
+ int process_id;
+ int route_id;
+ CHECK(args->GetSize() == 2);
+ CHECK(args->GetString(0, &process_id_str));
+ CHECK(args->GetString(1, &route_id_str));
+ CHECK(base::StringToInt(process_id_str,
+ &process_id));
+ CHECK(base::StringToInt(route_id_str, &route_id));
+
+ RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
+ if (!rvh)
+ return;
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rvh);
+ if (!rwhi)
+ return;
+ AccessibilityMode mode = rwhi->accessibility_mode();
+ if (mode == AccessibilityModeOff)
+ rwhi->SetAccessibilityMode(AccessibilityModeComplete);
+ else
+ rwhi->SetAccessibilityMode(AccessibilityModeOff);
+}
+
+void AccessibilityUI::ToggleGlobalAccessibility(const base::ListValue* args) {
+ BrowserAccessibilityStateImpl* state =
+ BrowserAccessibilityStateImpl::GetInstance();
+ AccessibilityMode mode = state->accessibility_mode();
+ AccessibilityMode new_mode = (mode == AccessibilityModeOff
+ ? AccessibilityModeComplete
+ : AccessibilityModeOff);
+ state->SetAccessibilityMode(new_mode);
+}
+
+void AccessibilityUI::RequestAccessibilityTree(const base::ListValue* args) {
+ std::string process_id_str;
+ std::string route_id_str;
+ int process_id;
+ int route_id;
+ CHECK(args->GetSize() == 2);
+ CHECK(args->GetString(0, &process_id_str));
+ CHECK(args->GetString(1, &route_id_str));
+ CHECK(base::StringToInt(process_id_str, &process_id));
+ CHECK(base::StringToInt(route_id_str, &route_id));
+
+ RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
+ if (!rvh) {
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
+ result->SetInteger(kProcessIdField, process_id);
+ result->SetInteger(kRouteIdField, route_id);
+ result->Set("error", new base::StringValue("Renderer no longer exists."));
+ web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get()));
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> result(BuildTargetDescriptor(rvh));
+ RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>(
+ WebContents::FromRenderViewHost(rvh)->GetRenderWidgetHostView());
+ if (!host_view) {
+ result->Set("error",
+ new base::StringValue("Could not get accessibility tree."));
+ web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get()));
+ return;
+ }
+ scoped_ptr<AccessibilityTreeFormatter> formatter(
+ AccessibilityTreeFormatter::Create(rvh));
+ string16 accessibility_contents_utf16;
+ BrowserAccessibilityManager* manager =
+ host_view->GetBrowserAccessibilityManager();
+ if (!manager) {
+ result->Set("error",
+ new base::StringValue("Could not get accessibility tree."));
+ web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get()));
+ return;
+ }
+ std::vector<AccessibilityTreeFormatter::Filter> filters;
+ filters.push_back(AccessibilityTreeFormatter::Filter(
+ ASCIIToUTF16("*"),
+ AccessibilityTreeFormatter::Filter::ALLOW));
+ formatter->SetFilters(filters);
+ formatter->FormatAccessibilityTree(&accessibility_contents_utf16);
+
+ result->Set("tree",
+ new base::StringValue(UTF16ToUTF8(accessibility_contents_utf16)));
+ web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get()));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_ui.h b/chromium/content/browser/accessibility/accessibility_ui.h
new file mode 100644
index 00000000000..1b239f8033b
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_ui.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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 CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace base {
+ class ListValue;
+} // namespace base
+
+namespace content {
+
+class AccessibilityUI : public WebUIController {
+ public:
+ explicit AccessibilityUI(WebUI* web_ui);
+ virtual ~AccessibilityUI();
+
+ private:
+ void ToggleAccessibility(const base::ListValue* args);
+ void ToggleGlobalAccessibility(const base::ListValue* args);
+ void RequestAccessibilityTree(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityUI);
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_
diff --git a/chromium/content/browser/accessibility/accessibility_win_browsertest.cc b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc
new file mode 100644
index 00000000000..0be9ba5067d
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -0,0 +1,883 @@
+// 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 <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_bstr.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/scoped_variant.h"
+#include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/shell/shell.h"
+#include "content/test/accessibility_browser_test_utils.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "third_party/iaccessible2/ia2_api_all.h"
+#include "third_party/isimpledom/ISimpleDOMNode.h"
+
+// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717
+#if defined(ARCH_CPU_X86_64)
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+namespace content {
+
+namespace {
+
+
+// Helpers --------------------------------------------------------------------
+
+base::win::ScopedComPtr<IAccessible> GetAccessibleFromResultVariant(
+ IAccessible* parent,
+ VARIANT* var) {
+ base::win::ScopedComPtr<IAccessible> ptr;
+ switch (V_VT(var)) {
+ case VT_DISPATCH: {
+ IDispatch* dispatch = V_DISPATCH(var);
+ if (dispatch)
+ ptr.QueryFrom(dispatch);
+ break;
+ }
+
+ case VT_I4: {
+ base::win::ScopedComPtr<IDispatch> dispatch;
+ HRESULT hr = parent->get_accChild(*var, dispatch.Receive());
+ EXPECT_TRUE(SUCCEEDED(hr));
+ if (dispatch)
+ dispatch.QueryInterface(ptr.Receive());
+ break;
+ }
+ }
+ return ptr;
+}
+
+HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) {
+ // TODO(ctguil): For some reason querying the IAccessible2 interface from
+ // IAccessible fails.
+ base::win::ScopedComPtr<IServiceProvider> service_provider;
+ HRESULT hr = accessible->QueryInterface(service_provider.Receive());
+ return SUCCEEDED(hr) ?
+ service_provider->QueryService(IID_IAccessible2, accessible2) : hr;
+}
+
+// Recursively search through all of the descendants reachable from an
+// IAccessible node and return true if we find one with the given role
+// and name.
+void RecursiveFindNodeInAccessibilityTree(IAccessible* node,
+ int32 expected_role,
+ const std::wstring& expected_name,
+ int32 depth,
+ bool* found) {
+ base::win::ScopedBstr name_bstr;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ node->get_accName(childid_self, name_bstr.Receive());
+ std::wstring name(name_bstr, name_bstr.Length());
+ base::win::ScopedVariant role;
+ node->get_accRole(childid_self, role.Receive());
+ ASSERT_EQ(VT_I4, role.type());
+
+ // Print the accessibility tree as we go, because if this test fails
+ // on the bots, this is really helpful in figuring out why.
+ for (int i = 0; i < depth; i++)
+ printf(" ");
+ printf("role=%d name=%s\n", V_I4(&role), WideToUTF8(name).c_str());
+
+ if (expected_role == V_I4(&role) && expected_name == name) {
+ *found = true;
+ return;
+ }
+
+ LONG child_count = 0;
+ HRESULT hr = node->get_accChildCount(&child_count);
+ ASSERT_EQ(S_OK, hr);
+
+ scoped_ptr<VARIANT[]> child_array(new VARIANT[child_count]);
+ LONG obtained_count = 0;
+ hr = AccessibleChildren(
+ node, 0, child_count, child_array.get(), &obtained_count);
+ ASSERT_EQ(S_OK, hr);
+ ASSERT_EQ(child_count, obtained_count);
+
+ for (int index = 0; index < obtained_count; index++) {
+ base::win::ScopedComPtr<IAccessible> child_accessible(
+ GetAccessibleFromResultVariant(node, &child_array.get()[index]));
+ if (child_accessible) {
+ RecursiveFindNodeInAccessibilityTree(
+ child_accessible.get(), expected_role, expected_name, depth + 1,
+ found);
+ if (*found)
+ return;
+ }
+ }
+}
+
+
+// AccessibilityWinBrowserTest ------------------------------------------------
+
+class AccessibilityWinBrowserTest : public ContentBrowserTest {
+ public:
+ AccessibilityWinBrowserTest();
+ virtual ~AccessibilityWinBrowserTest();
+
+ protected:
+ void LoadInitialAccessibilityTreeFromHtml(const std::string& html);
+ IAccessible* GetRendererAccessible();
+ void ExecuteScript(const std::wstring& script);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityWinBrowserTest);
+};
+
+AccessibilityWinBrowserTest::AccessibilityWinBrowserTest() {
+}
+
+AccessibilityWinBrowserTest::~AccessibilityWinBrowserTest() {
+}
+
+void AccessibilityWinBrowserTest::LoadInitialAccessibilityTreeFromHtml(
+ const std::string& html) {
+ AccessibilityNotificationWaiter waiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationLoadComplete);
+ GURL html_data_url("data:text/html," + html);
+ NavigateToURL(shell(), html_data_url);
+ waiter.WaitForNotification();
+}
+
+// Retrieve the MSAA client accessibility object for the Render Widget Host View
+// of the selected tab.
+IAccessible* AccessibilityWinBrowserTest::GetRendererAccessible() {
+ HWND hwnd_render_widget_host_view =
+ shell()->web_contents()->GetRenderWidgetHostView()->GetNativeView();
+
+ // Invoke windows screen reader detection by sending the WM_GETOBJECT message
+ // with kIdCustom as the LPARAM.
+ const int32 kIdCustom = 1;
+ SendMessage(
+ hwnd_render_widget_host_view, WM_GETOBJECT, OBJID_CLIENT, kIdCustom);
+
+ IAccessible* accessible;
+ HRESULT hr = AccessibleObjectFromWindow(
+ hwnd_render_widget_host_view, OBJID_CLIENT,
+ IID_IAccessible, reinterpret_cast<void**>(&accessible));
+
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_NE(accessible, reinterpret_cast<IAccessible*>(NULL));
+
+ return accessible;
+}
+
+void AccessibilityWinBrowserTest::ExecuteScript(const std::wstring& script) {
+ shell()->web_contents()->GetRenderViewHost()->ExecuteJavascriptInWebFrame(
+ std::wstring(), script);
+}
+
+
+// AccessibleChecker ----------------------------------------------------------
+
+class AccessibleChecker {
+ public:
+ // This constructor can be used if the IA2 role will be the same as the MSAA
+ // role.
+ AccessibleChecker(const std::wstring& expected_name,
+ int32 expected_role,
+ const std::wstring& expected_value);
+ AccessibleChecker(const std::wstring& expected_name,
+ int32 expected_role,
+ int32 expected_ia2_role,
+ const std::wstring& expected_value);
+ AccessibleChecker(const std::wstring& expected_name,
+ const std::wstring& expected_role,
+ int32 expected_ia2_role,
+ const std::wstring& expected_value);
+
+ // Append an AccessibleChecker that verifies accessibility information for
+ // a child IAccessible. Order is important.
+ void AppendExpectedChild(AccessibleChecker* expected_child);
+
+ // Check that the name and role of the given IAccessible instance and its
+ // descendants match the expected names and roles that this object was
+ // initialized with.
+ void CheckAccessible(IAccessible* accessible);
+
+ // Set the expected value for this AccessibleChecker.
+ void SetExpectedValue(const std::wstring& expected_value);
+
+ // Set the expected state for this AccessibleChecker.
+ void SetExpectedState(LONG expected_state);
+
+ private:
+ typedef std::vector<AccessibleChecker*> AccessibleCheckerVector;
+
+ void CheckAccessibleName(IAccessible* accessible);
+ void CheckAccessibleRole(IAccessible* accessible);
+ void CheckIA2Role(IAccessible* accessible);
+ void CheckAccessibleValue(IAccessible* accessible);
+ void CheckAccessibleState(IAccessible* accessible);
+ void CheckAccessibleChildren(IAccessible* accessible);
+ string16 RoleVariantToString(const base::win::ScopedVariant& role);
+
+ // Expected accessible name. Checked against IAccessible::get_accName.
+ std::wstring name_;
+
+ // Expected accessible role. Checked against IAccessible::get_accRole.
+ base::win::ScopedVariant role_;
+
+ // Expected IAccessible2 role. Checked against IAccessible2::role.
+ int32 ia2_role_;
+
+ // Expected accessible value. Checked against IAccessible::get_accValue.
+ std::wstring value_;
+
+ // Expected accessible state. Checked against IAccessible::get_accState.
+ LONG state_;
+
+ // Expected accessible children. Checked using IAccessible::get_accChildCount
+ // and ::AccessibleChildren.
+ AccessibleCheckerVector children_;
+};
+
+AccessibleChecker::AccessibleChecker(const std::wstring& expected_name,
+ int32 expected_role,
+ const std::wstring& expected_value)
+ : name_(expected_name),
+ role_(expected_role),
+ ia2_role_(expected_role),
+ value_(expected_value),
+ state_(-1) {
+}
+
+AccessibleChecker::AccessibleChecker(const std::wstring& expected_name,
+ int32 expected_role,
+ int32 expected_ia2_role,
+ const std::wstring& expected_value)
+ : name_(expected_name),
+ role_(expected_role),
+ ia2_role_(expected_ia2_role),
+ value_(expected_value),
+ state_(-1) {
+}
+
+AccessibleChecker::AccessibleChecker(const std::wstring& expected_name,
+ const std::wstring& expected_role,
+ int32 expected_ia2_role,
+ const std::wstring& expected_value)
+ : name_(expected_name),
+ role_(expected_role.c_str()),
+ ia2_role_(expected_ia2_role),
+ value_(expected_value),
+ state_(-1) {
+}
+
+void AccessibleChecker::AppendExpectedChild(
+ AccessibleChecker* expected_child) {
+ children_.push_back(expected_child);
+}
+
+void AccessibleChecker::CheckAccessible(IAccessible* accessible) {
+ SCOPED_TRACE("while checking " + UTF16ToUTF8(RoleVariantToString(role_)));
+ CheckAccessibleName(accessible);
+ CheckAccessibleRole(accessible);
+ CheckIA2Role(accessible);
+ CheckAccessibleValue(accessible);
+ CheckAccessibleState(accessible);
+ CheckAccessibleChildren(accessible);
+}
+
+void AccessibleChecker::SetExpectedValue(const std::wstring& expected_value) {
+ value_ = expected_value;
+}
+
+void AccessibleChecker::SetExpectedState(LONG expected_state) {
+ state_ = expected_state;
+}
+
+void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) {
+ base::win::ScopedBstr name;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = accessible->get_accName(childid_self, name.Receive());
+
+ if (name_.empty()) {
+ // If the object doesn't have name S_FALSE should be returned.
+ EXPECT_EQ(S_FALSE, hr);
+ } else {
+ // Test that the correct string was returned.
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(name_, std::wstring(name, name.Length()));
+ }
+}
+
+void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) {
+ base::win::ScopedVariant role;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = accessible->get_accRole(childid_self, role.Receive());
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(0, role_.Compare(role))
+ << "Expected role: " << RoleVariantToString(role_)
+ << "\nGot role: " << RoleVariantToString(role);
+}
+
+void AccessibleChecker::CheckIA2Role(IAccessible* accessible) {
+ base::win::ScopedComPtr<IAccessible2> accessible2;
+ HRESULT hr = QueryIAccessible2(accessible, accessible2.Receive());
+ ASSERT_EQ(S_OK, hr);
+ long ia2_role = 0;
+ hr = accessible2->role(&ia2_role);
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(ia2_role_, ia2_role)
+ << "Expected ia2 role: " << IAccessible2RoleToString(ia2_role_)
+ << "\nGot ia2 role: " << IAccessible2RoleToString(ia2_role);
+}
+
+void AccessibleChecker::CheckAccessibleValue(IAccessible* accessible) {
+ // Don't check the value if if's a DOCUMENT role, because the value
+ // is supposed to be the url (and we don't keep track of that in the
+ // test expectations).
+ base::win::ScopedVariant role;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = accessible->get_accRole(childid_self, role.Receive());
+ ASSERT_EQ(S_OK, hr);
+ if (role.type() == VT_I4 && V_I4(&role) == ROLE_SYSTEM_DOCUMENT)
+ return;
+
+ // Get the value.
+ base::win::ScopedBstr value;
+ hr = accessible->get_accValue(childid_self, value.Receive());
+ EXPECT_EQ(S_OK, hr);
+
+ // Test that the correct string was returned.
+ EXPECT_EQ(value_, std::wstring(value, value.Length()));
+}
+
+void AccessibleChecker::CheckAccessibleState(IAccessible* accessible) {
+ if (state_ < 0)
+ return;
+
+ base::win::ScopedVariant state;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = accessible->get_accState(childid_self, state.Receive());
+ EXPECT_EQ(S_OK, hr);
+ ASSERT_EQ(VT_I4, state.type());
+ LONG obj_state = V_I4(&state);
+ // Avoid flakiness. The "offscreen" state depends on whether the browser
+ // window is frontmost or not, and "hottracked" depends on whether the
+ // mouse cursor happens to be over the element.
+ obj_state &= ~(STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_HOTTRACKED);
+ EXPECT_EQ(state_, obj_state)
+ << "Expected state: " << IAccessibleStateToString(state_)
+ << "\nGot state: " << IAccessibleStateToString(obj_state);
+}
+
+void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) {
+ LONG child_count = 0;
+ HRESULT hr = parent->get_accChildCount(&child_count);
+ EXPECT_EQ(S_OK, hr);
+ ASSERT_EQ(child_count, children_.size());
+
+ scoped_ptr<VARIANT[]> child_array(new VARIANT[child_count]);
+ LONG obtained_count = 0;
+ hr = AccessibleChildren(parent, 0, child_count,
+ child_array.get(), &obtained_count);
+ ASSERT_EQ(S_OK, hr);
+ ASSERT_EQ(child_count, obtained_count);
+
+ VARIANT* child = child_array.get();
+ for (AccessibleCheckerVector::iterator child_checker = children_.begin();
+ child_checker != children_.end();
+ ++child_checker, ++child) {
+ base::win::ScopedComPtr<IAccessible> child_accessible(
+ GetAccessibleFromResultVariant(parent, child));
+ ASSERT_TRUE(child_accessible.get());
+ (*child_checker)->CheckAccessible(child_accessible);
+ }
+}
+
+string16 AccessibleChecker::RoleVariantToString(
+ const base::win::ScopedVariant& role) {
+ if (role.type() == VT_I4)
+ return IAccessibleRoleToString(V_I4(&role));
+ if (role.type() == VT_BSTR)
+ return string16(V_BSTR(&role), SysStringLen(V_BSTR(&role)));
+ return string16();
+}
+
+} // namespace
+
+
+// Tests ----------------------------------------------------------------------
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestBusyAccessibilityTree)) {
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+
+ // The initial accessible returned should have state STATE_SYSTEM_BUSY while
+ // the accessibility tree is being requested from the renderer.
+ AccessibleChecker document1_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ document1_checker.SetExpectedState(
+ STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED |
+ STATE_SYSTEM_BUSY);
+ document1_checker.CheckAccessible(GetRendererAccessible());
+}
+
+// Flaky, http://crbug.com/167320 .
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ DISABLED_TestRendererAccessibilityTree) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<html><head><title>Accessibility Win Test</title></head>"
+ "<body><input type='button' value='push' /><input type='checkbox' />"
+ "</body></html>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON,
+ std::wstring());
+ AccessibleChecker checkbox_checker(std::wstring(), ROLE_SYSTEM_CHECKBUTTON,
+ std::wstring());
+ AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION,
+ std::wstring());
+ AccessibleChecker document2_checker(L"Accessibility Win Test",
+ ROLE_SYSTEM_DOCUMENT, std::wstring());
+ body_checker.AppendExpectedChild(&button_checker);
+ body_checker.AppendExpectedChild(&checkbox_checker);
+ document2_checker.AppendExpectedChild(&body_checker);
+ document2_checker.CheckAccessible(GetRendererAccessible());
+
+ // Check that document accessible has a parent accessible.
+ base::win::ScopedComPtr<IAccessible> document_accessible(
+ GetRendererAccessible());
+ ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL));
+ base::win::ScopedComPtr<IDispatch> parent_dispatch;
+ HRESULT hr = document_accessible->get_accParent(parent_dispatch.Receive());
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_NE(parent_dispatch, reinterpret_cast<IDispatch*>(NULL));
+
+ // Navigate to another page.
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+
+ // Verify that the IAccessible reference still points to a valid object and
+ // that calls to its methods fail since the tree is no longer valid after
+ // the page navagation.
+ base::win::ScopedBstr name;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ hr = document_accessible->get_accName(childid_self, name.Receive());
+ ASSERT_EQ(E_FAIL, hr);
+}
+
+// Periodically failing. See crbug.com/145537
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ DISABLED_TestNotificationActiveDescendantChanged) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<ul tabindex='-1' role='radiogroup' aria-label='ul'>"
+ "<li id='li'>li</li></ul>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker list_marker_checker(L"\x2022", ROLE_SYSTEM_TEXT,
+ std::wstring());
+ AccessibleChecker static_text_checker(L"li", ROLE_SYSTEM_TEXT,
+ std::wstring());
+ AccessibleChecker list_item_checker(std::wstring(), ROLE_SYSTEM_LISTITEM,
+ std::wstring());
+ list_item_checker.SetExpectedState(STATE_SYSTEM_READONLY);
+ AccessibleChecker radio_group_checker(L"ul", ROLE_SYSTEM_GROUPING,
+ IA2_ROLE_SECTION, std::wstring());
+ radio_group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ list_item_checker.AppendExpectedChild(&list_marker_checker);
+ list_item_checker.AppendExpectedChild(&static_text_checker);
+ radio_group_checker.AppendExpectedChild(&list_item_checker);
+ document_checker.AppendExpectedChild(&radio_group_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Set focus to the radio group.
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationFocusChanged));
+ ExecuteScript(L"document.body.children[0].focus()");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ radio_group_checker.SetExpectedState(
+ STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Set the active descendant of the radio group
+ waiter.reset(new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationFocusChanged));
+ ExecuteScript(
+ L"document.body.children[0].setAttribute('aria-activedescendant', 'li')");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ list_item_checker.SetExpectedState(
+ STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSED);
+ radio_group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestNotificationCheckedStateChanged)) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<body><input type='checkbox' /></body>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker checkbox_checker(std::wstring(), ROLE_SYSTEM_CHECKBUTTON,
+ std::wstring());
+ checkbox_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION,
+ std::wstring());
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ body_checker.AppendExpectedChild(&checkbox_checker);
+ document_checker.AppendExpectedChild(&body_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Check the checkbox.
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationCheckStateChanged));
+ ExecuteScript(L"document.body.children[0].checked=true");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ checkbox_checker.SetExpectedState(
+ STATE_SYSTEM_CHECKED | STATE_SYSTEM_FOCUSABLE);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestNotificationChildrenChanged)) {
+ // The role attribute causes the node to be in the accessibility tree.
+ LoadInitialAccessibilityTreeFromHtml("<body role=group></body>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
+ std::wstring());
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ document_checker.AppendExpectedChild(&group_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Change the children of the document body.
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(),
+ AccessibilityModeComplete,
+ AccessibilityNotificationChildrenChanged));
+ ExecuteScript(L"document.body.innerHTML='<b>new text</b>'");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ AccessibleChecker text_checker(L"new text", ROLE_SYSTEM_TEXT, std::wstring());
+ group_checker.AppendExpectedChild(&text_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestNotificationChildrenChanged2)) {
+ // The role attribute causes the node to be in the accessibility tree.
+ LoadInitialAccessibilityTreeFromHtml(
+ "<div role=group style='visibility: hidden'>text</div>");
+
+ // Check the accessible tree of the browser.
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Change the children of the document body.
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationChildrenChanged));
+ ExecuteScript(L"document.body.children[0].style.visibility='visible'");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ AccessibleChecker static_text_checker(L"text", ROLE_SYSTEM_TEXT,
+ std::wstring());
+ AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
+ std::wstring());
+ document_checker.AppendExpectedChild(&group_checker);
+ group_checker.AppendExpectedChild(&static_text_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestNotificationFocusChanged)) {
+ // The role attribute causes the node to be in the accessibility tree.
+ LoadInitialAccessibilityTreeFromHtml("<div role=group tabindex='-1'></div>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ SCOPED_TRACE("Check initial tree");
+ AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
+ std::wstring());
+ group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ document_checker.AppendExpectedChild(&group_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Focus the div in the document
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationFocusChanged));
+ ExecuteScript(L"document.body.children[0].focus()");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ SCOPED_TRACE("Check updated tree after focusing div");
+ group_checker.SetExpectedState(
+ STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Focus the document accessible. This will un-focus the current node.
+ waiter.reset(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationBlur));
+ base::win::ScopedComPtr<IAccessible> document_accessible(
+ GetRendererAccessible());
+ ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL));
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = document_accessible->accSelect(SELFLAG_TAKEFOCUS, childid_self);
+ ASSERT_EQ(S_OK, hr);
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ SCOPED_TRACE("Check updated tree after focusing document again");
+ group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(TestNotificationValueChanged)) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<body><input type='text' value='old value'/></body>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker text_field_checker(std::wstring(), ROLE_SYSTEM_TEXT,
+ L"old value");
+ text_field_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE);
+ AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION,
+ std::wstring());
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ body_checker.AppendExpectedChild(&text_field_checker);
+ document_checker.AppendExpectedChild(&body_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+
+ // Set the value of the text control
+ scoped_ptr<AccessibilityNotificationWaiter> waiter(
+ new AccessibilityNotificationWaiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationValueChanged));
+ ExecuteScript(L"document.body.children[0].value='new value'");
+ waiter->WaitForNotification();
+
+ // Check that the accessibility tree of the browser has been updated.
+ text_field_checker.SetExpectedValue(L"new value");
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+// This test verifies that the web content's accessibility tree is a
+// descendant of the main browser window's accessibility tree, so that
+// tools like AccExplorer32 or AccProbe can be used to examine Chrome's
+// accessibility support.
+//
+// If you made a change and this test now fails, check that the NativeViewHost
+// that wraps the tab contents returns the IAccessible implementation
+// provided by RenderWidgetHostViewWin in GetNativeViewAccessible().
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(ContainsRendererAccessibilityTree)) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<html><head><title>MyDocument</title></head>"
+ "<body>Content</body></html>");
+
+ // Get the accessibility object for the browser window.
+ HWND browser_hwnd = shell()->window();
+ base::win::ScopedComPtr<IAccessible> browser_accessible;
+ HRESULT hr = AccessibleObjectFromWindow(
+ browser_hwnd,
+ OBJID_WINDOW,
+ IID_IAccessible,
+ reinterpret_cast<void**>(browser_accessible.Receive()));
+ ASSERT_EQ(S_OK, hr);
+
+ bool found = false;
+ RecursiveFindNodeInAccessibilityTree(
+ browser_accessible.get(), ROLE_SYSTEM_DOCUMENT, L"MyDocument", 0, &found);
+ ASSERT_EQ(found, true);
+}
+
+// Disabled because of http://crbug.com/144390.
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ DISABLED_TestToggleButtonRoleAndStates) {
+ AccessibleChecker* button_checker;
+ std::string button_html("data:text/html,");
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION,
+ std::wstring());
+ document_checker.AppendExpectedChild(&body_checker);
+
+// Temporary macro
+#define ADD_BUTTON(html, ia2_role, state) \
+ button_html += html; \
+ button_checker = new AccessibleChecker(L"x", ROLE_SYSTEM_PUSHBUTTON, \
+ ia2_role, std::wstring()); \
+ button_checker->SetExpectedState(state); \
+ body_checker.AppendExpectedChild(button_checker)
+
+ // If aria-pressed is 'undefined', empty or not present, use PUSHBUTTON
+ // Otherwise use TOGGLE_BUTTON, even if the value is invalid.
+ // The spec does this in an attempt future-proof in case new values are added.
+ ADD_BUTTON("<span role='button' aria-pressed='false'>x</span>",
+ IA2_ROLE_TOGGLE_BUTTON, 0);
+ ADD_BUTTON("<span role='button' aria-pressed='true'>x</span>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_PRESSED);
+ ADD_BUTTON("<span role='button' aria-pressed='mixed'>x</span>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_MIXED);
+ ADD_BUTTON("<span role='button' aria-pressed='xyz'>x</span>",
+ IA2_ROLE_TOGGLE_BUTTON, 0);
+ ADD_BUTTON("<span role='button' aria-pressed=''>x</span>",
+ ROLE_SYSTEM_PUSHBUTTON, 0);
+ ADD_BUTTON("<span role='button' aria-pressed>x</span>",
+ ROLE_SYSTEM_PUSHBUTTON, 0);
+ ADD_BUTTON("<span role='button' aria-pressed='undefined'>x</span>",
+ ROLE_SYSTEM_PUSHBUTTON, 0);
+ ADD_BUTTON("<span role='button'>x</span>", ROLE_SYSTEM_PUSHBUTTON, 0);
+ ADD_BUTTON("<input type='button' aria-pressed='true' value='x'/>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_PRESSED);
+ ADD_BUTTON("<input type='button' aria-pressed='false' value='x'/>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<input type='button' aria-pressed='mixed' value='x'>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_MIXED);
+ ADD_BUTTON("<input type='button' aria-pressed='xyz' value='x'/>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<input type='button' aria-pressed='' value='x'/>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<input type='button' aria-pressed value='x'>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<input type='button' aria-pressed='undefined' value='x'>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<input type='button' value='x'>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button aria-pressed='true'>x</button>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_PRESSED);
+ ADD_BUTTON("<button aria-pressed='false'>x</button>",
+ IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button aria-pressed='mixed'>x</button>", IA2_ROLE_TOGGLE_BUTTON,
+ STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_MIXED);
+ ADD_BUTTON("<button aria-pressed='xyz'>x</button>", IA2_ROLE_TOGGLE_BUTTON,
+ STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button aria-pressed=''>x</button>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button aria-pressed>x</button>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button aria-pressed='undefined'>x</button>",
+ ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE);
+ ADD_BUTTON("<button>x</button>", ROLE_SYSTEM_PUSHBUTTON,
+ STATE_SYSTEM_FOCUSABLE);
+#undef ADD_BUTTON // Temporary macro
+
+ LoadInitialAccessibilityTreeFromHtml(button_html);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ MAYBE(SupportsISimpleDOM)) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<body><input type='checkbox' /></body>");
+
+ // Get the IAccessible object for the document.
+ base::win::ScopedComPtr<IAccessible> document_accessible(
+ GetRendererAccessible());
+ ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL));
+
+ // Get the ISimpleDOM object for the document.
+ base::win::ScopedComPtr<IServiceProvider> service_provider;
+ HRESULT hr = static_cast<IAccessible*>(document_accessible)->QueryInterface(
+ service_provider.Receive());
+ ASSERT_EQ(S_OK, hr);
+ const GUID refguid = {0x0c539790, 0x12e4, 0x11cf,
+ 0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8};
+ base::win::ScopedComPtr<ISimpleDOMNode> document_isimpledomnode;
+ hr = static_cast<IServiceProvider *>(service_provider)->QueryService(
+ refguid, IID_ISimpleDOMNode,
+ reinterpret_cast<void**>(document_isimpledomnode.Receive()));
+ ASSERT_EQ(S_OK, hr);
+
+ base::win::ScopedBstr node_name;
+ short name_space_id; // NOLINT
+ base::win::ScopedBstr node_value;
+ unsigned int num_children;
+ unsigned int unique_id;
+ unsigned short node_type; // NOLINT
+ hr = document_isimpledomnode->get_nodeInfo(
+ node_name.Receive(), &name_space_id, node_value.Receive(), &num_children,
+ &unique_id, &node_type);
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(NODETYPE_DOCUMENT, node_type);
+ EXPECT_EQ(1, num_children);
+ node_name.Reset();
+ node_value.Reset();
+
+ base::win::ScopedComPtr<ISimpleDOMNode> body_isimpledomnode;
+ hr = document_isimpledomnode->get_firstChild(
+ body_isimpledomnode.Receive());
+ ASSERT_EQ(S_OK, hr);
+ hr = body_isimpledomnode->get_nodeInfo(
+ node_name.Receive(), &name_space_id, node_value.Receive(), &num_children,
+ &unique_id, &node_type);
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(L"body", std::wstring(node_name, node_name.Length()));
+ EXPECT_EQ(NODETYPE_ELEMENT, node_type);
+ EXPECT_EQ(1, num_children);
+ node_name.Reset();
+ node_value.Reset();
+
+ base::win::ScopedComPtr<ISimpleDOMNode> checkbox_isimpledomnode;
+ hr = body_isimpledomnode->get_firstChild(
+ checkbox_isimpledomnode.Receive());
+ ASSERT_EQ(S_OK, hr);
+ hr = checkbox_isimpledomnode->get_nodeInfo(
+ node_name.Receive(), &name_space_id, node_value.Receive(), &num_children,
+ &unique_id, &node_type);
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(L"input", std::wstring(node_name, node_name.Length()));
+ EXPECT_EQ(NODETYPE_ELEMENT, node_type);
+ EXPECT_EQ(0, num_children);
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, MAYBE(TestRoleGroup)) {
+ LoadInitialAccessibilityTreeFromHtml(
+ "<fieldset></fieldset><div role=group></div>");
+
+ // Check the browser's copy of the renderer accessibility tree.
+ AccessibleChecker grouping1_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
+ std::wstring());
+ AccessibleChecker grouping2_checker(std::wstring(), ROLE_SYSTEM_GROUPING,
+ std::wstring());
+ AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT,
+ std::wstring());
+ document_checker.AppendExpectedChild(&grouping1_checker);
+ document_checker.AppendExpectedChild(&grouping2_checker);
+ document_checker.CheckAccessible(GetRendererAccessible());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility.cc b/chromium/content/browser/accessibility/browser_accessibility.cc
new file mode 100644
index 00000000000..ae7efe2f660
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility.cc
@@ -0,0 +1,341 @@
+// 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 "content/browser/accessibility/browser_accessibility.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+typedef AccessibilityNodeData::BoolAttribute BoolAttribute;
+typedef AccessibilityNodeData::FloatAttribute FloatAttribute;
+typedef AccessibilityNodeData::IntAttribute IntAttribute;
+typedef AccessibilityNodeData::StringAttribute StringAttribute;
+
+#if !defined(OS_MACOSX) && \
+ !defined(OS_WIN) && \
+ !defined(TOOLKIT_GTK) && \
+ !defined(OS_ANDROID)
+// We have subclassess of BrowserAccessibility on Mac, Linux/GTK,
+// and Win. For any other platform, instantiate the base class.
+// static
+BrowserAccessibility* BrowserAccessibility::Create() {
+ return new BrowserAccessibility();
+}
+#endif
+
+BrowserAccessibility::BrowserAccessibility()
+ : manager_(NULL),
+ parent_(NULL),
+ index_in_parent_(0),
+ renderer_id_(0),
+ role_(0),
+ state_(0),
+ instance_active_(false) {
+}
+
+BrowserAccessibility::~BrowserAccessibility() {
+}
+
+void BrowserAccessibility::DetachTree(
+ std::vector<BrowserAccessibility*>* nodes) {
+ nodes->push_back(this);
+ for (size_t i = 0; i < children_.size(); i++)
+ children_[i]->DetachTree(nodes);
+ children_.clear();
+ parent_ = NULL;
+}
+
+void BrowserAccessibility::InitializeTreeStructure(
+ BrowserAccessibilityManager* manager,
+ BrowserAccessibility* parent,
+ int32 renderer_id,
+ int32 index_in_parent) {
+ manager_ = manager;
+ parent_ = parent;
+ renderer_id_ = renderer_id;
+ index_in_parent_ = index_in_parent;
+}
+
+void BrowserAccessibility::InitializeData(const AccessibilityNodeData& src) {
+ DCHECK_EQ(renderer_id_, src.id);
+ name_ = src.name;
+ value_ = src.value;
+ role_ = src.role;
+ state_ = src.state;
+ string_attributes_ = src.string_attributes;
+ int_attributes_ = src.int_attributes;
+ float_attributes_ = src.float_attributes;
+ bool_attributes_ = src.bool_attributes;
+ html_attributes_ = src.html_attributes;
+ location_ = src.location;
+ indirect_child_ids_ = src.indirect_child_ids;
+ line_breaks_ = src.line_breaks;
+ cell_ids_ = src.cell_ids;
+ unique_cell_ids_ = src.unique_cell_ids;
+ instance_active_ = true;
+
+ PreInitialize();
+}
+
+bool BrowserAccessibility::IsNative() const {
+ return false;
+}
+
+void BrowserAccessibility::SwapChildren(
+ std::vector<BrowserAccessibility*>& children) {
+ children.swap(children_);
+}
+
+void BrowserAccessibility::UpdateParent(BrowserAccessibility* parent,
+ int index_in_parent) {
+ parent_ = parent;
+ index_in_parent_ = index_in_parent;
+}
+
+void BrowserAccessibility::SetLocation(const gfx::Rect& new_location) {
+ location_ = new_location;
+}
+
+bool BrowserAccessibility::IsDescendantOf(
+ BrowserAccessibility* ancestor) {
+ if (this == ancestor) {
+ return true;
+ } else if (parent_) {
+ return parent_->IsDescendantOf(ancestor);
+ }
+
+ return false;
+}
+
+BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) const {
+ DCHECK(child_index < children_.size());
+ return children_[child_index];
+}
+
+BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() {
+ if (parent_ && index_in_parent_ > 0)
+ return parent_->children_[index_in_parent_ - 1];
+
+ return NULL;
+}
+
+BrowserAccessibility* BrowserAccessibility::GetNextSibling() {
+ if (parent_ &&
+ index_in_parent_ >= 0 &&
+ index_in_parent_ < static_cast<int>(parent_->children_.size() - 1)) {
+ return parent_->children_[index_in_parent_ + 1];
+ }
+
+ return NULL;
+}
+
+gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const {
+ gfx::Rect bounds = location_;
+
+ // Walk up the parent chain. Every time we encounter a Web Area, offset
+ // based on the scroll bars and then offset based on the origin of that
+ // nested web area.
+ BrowserAccessibility* parent = parent_;
+ bool need_to_offset_web_area =
+ (role_ == AccessibilityNodeData::ROLE_WEB_AREA ||
+ role_ == AccessibilityNodeData::ROLE_ROOT_WEB_AREA);
+ while (parent) {
+ if (need_to_offset_web_area &&
+ parent->location().width() > 0 &&
+ parent->location().height() > 0) {
+ bounds.Offset(parent->location().x(), parent->location().y());
+ need_to_offset_web_area = false;
+ }
+
+ // On some platforms, we don't want to take the root scroll offsets
+ // into account.
+ if (parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA &&
+ !manager()->UseRootScrollOffsetsWhenComputingBounds()) {
+ break;
+ }
+
+ if (parent->role() == AccessibilityNodeData::ROLE_WEB_AREA ||
+ parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA) {
+ int sx = 0;
+ int sy = 0;
+ if (parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &sx) &&
+ parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &sy)) {
+ bounds.Offset(-sx, -sy);
+ }
+ need_to_offset_web_area = true;
+ }
+ parent = parent->parent();
+ }
+
+ return bounds;
+}
+
+gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const {
+ gfx::Rect bounds = GetLocalBoundsRect();
+
+ // Adjust the bounds by the top left corner of the containing view's bounds
+ // in screen coordinates.
+ bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin());
+
+ return bounds;
+}
+
+BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint(
+ const gfx::Point& point) {
+ // Walk the children recursively looking for the BrowserAccessibility that
+ // most tightly encloses the specified point.
+ for (int i = children_.size() - 1; i >= 0; --i) {
+ BrowserAccessibility* child = children_[i];
+ if (child->GetGlobalBoundsRect().Contains(point))
+ return child->BrowserAccessibilityForPoint(point);
+ }
+ return this;
+}
+
+void BrowserAccessibility::Destroy() {
+ for (std::vector<BrowserAccessibility*>::iterator iter = children_.begin();
+ iter != children_.end();
+ ++iter) {
+ (*iter)->Destroy();
+ }
+ children_.clear();
+
+ // Allow the object to fire a TextRemoved notification.
+ name_.clear();
+ value_.clear();
+ PostInitialize();
+
+ manager_->NotifyAccessibilityEvent(
+ AccessibilityNotificationObjectHide, this);
+
+ instance_active_ = false;
+ manager_->RemoveNode(this);
+ NativeReleaseReference();
+}
+
+void BrowserAccessibility::NativeReleaseReference() {
+ delete this;
+}
+
+bool BrowserAccessibility::GetBoolAttribute(
+ BoolAttribute attribute, bool* value) const {
+ BoolAttrMap::const_iterator iter = bool_attributes_.find(attribute);
+ if (iter != bool_attributes_.end()) {
+ *value = iter->second;
+ return true;
+ }
+
+ return false;
+}
+
+bool BrowserAccessibility::GetFloatAttribute(
+ FloatAttribute attribute, float* value) const {
+ FloatAttrMap::const_iterator iter = float_attributes_.find(attribute);
+ if (iter != float_attributes_.end()) {
+ *value = iter->second;
+ return true;
+ }
+
+ return false;
+}
+
+bool BrowserAccessibility::GetIntAttribute(
+ IntAttribute attribute, int* value) const {
+ IntAttrMap::const_iterator iter = int_attributes_.find(attribute);
+ if (iter != int_attributes_.end()) {
+ *value = iter->second;
+ return true;
+ }
+
+ return false;
+}
+
+bool BrowserAccessibility::GetStringAttribute(
+ StringAttribute attribute,
+ string16* value) const {
+ StringAttrMap::const_iterator iter = string_attributes_.find(attribute);
+ if (iter != string_attributes_.end()) {
+ *value = iter->second;
+ return true;
+ }
+
+ return false;
+}
+
+bool BrowserAccessibility::GetHtmlAttribute(
+ const char* html_attr, string16* value) const {
+ for (size_t i = 0; i < html_attributes_.size(); i++) {
+ const string16& attr = html_attributes_[i].first;
+ if (LowerCaseEqualsASCII(attr, html_attr)) {
+ *value = html_attributes_[i].second;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool BrowserAccessibility::GetAriaTristate(
+ const char* html_attr,
+ bool* is_defined,
+ bool* is_mixed) const {
+ *is_defined = false;
+ *is_mixed = false;
+
+ string16 value;
+ if (!GetHtmlAttribute(html_attr, &value) ||
+ value.empty() ||
+ EqualsASCII(value, "undefined")) {
+ return false; // Not set (and *is_defined is also false)
+ }
+
+ *is_defined = true;
+
+ if (EqualsASCII(value, "true"))
+ return true;
+
+ if (EqualsASCII(value, "mixed"))
+ *is_mixed = true;
+
+ return false; // Not set
+}
+
+bool BrowserAccessibility::HasState(
+ AccessibilityNodeData::State state_enum) const {
+ return (state_ >> state_enum) & 1;
+}
+
+bool BrowserAccessibility::IsEditableText() const {
+ // These roles don't have readonly set, but they're not editable text.
+ if (role_ == AccessibilityNodeData::ROLE_SCROLLAREA ||
+ role_ == AccessibilityNodeData::ROLE_COLUMN ||
+ role_ == AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER) {
+ return false;
+ }
+
+ // Note: STATE_READONLY being false means it's either a text control,
+ // or contenteditable. We also check for editable text roles to cover
+ // another element that has role=textbox set on it.
+ return (!HasState(AccessibilityNodeData::STATE_READONLY) ||
+ role_ == AccessibilityNodeData::ROLE_TEXT_FIELD ||
+ role_ == AccessibilityNodeData::ROLE_TEXTAREA);
+}
+
+string16 BrowserAccessibility::GetTextRecursive() const {
+ if (!name_.empty()) {
+ return name_;
+ }
+
+ string16 result;
+ for (size_t i = 0; i < children_.size(); ++i)
+ result += children_[i]->GetTextRecursive();
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility.h b/chromium/content/browser/accessibility/browser_accessibility.h
new file mode 100644
index 00000000000..8fda68fc079
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility.h
@@ -0,0 +1,296 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "build/build_config.h"
+#include "content/common/accessibility_node_data.h"
+#include "content/common/content_export.h"
+
+#if defined(OS_MACOSX) && __OBJC__
+@class BrowserAccessibilityCocoa;
+#endif
+
+namespace content {
+class BrowserAccessibilityManager;
+#if defined(OS_WIN)
+class BrowserAccessibilityWin;
+#elif defined(TOOLKIT_GTK)
+class BrowserAccessibilityGtk;
+#endif
+
+typedef std::map<AccessibilityNodeData::BoolAttribute, bool> BoolAttrMap;
+typedef std::map<AccessibilityNodeData::FloatAttribute, float> FloatAttrMap;
+typedef std::map<AccessibilityNodeData::IntAttribute, int> IntAttrMap;
+typedef std::map<AccessibilityNodeData::StringAttribute, string16>
+ StringAttrMap;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// BrowserAccessibility
+//
+// Class implementing the cross platform interface for the Browser-Renderer
+// communication of accessibility information, providing accessibility
+// to be used by screen readers and other assistive technology (AT).
+//
+// An implementation for each platform handles platform specific accessibility
+// APIs.
+//
+////////////////////////////////////////////////////////////////////////////////
+class CONTENT_EXPORT BrowserAccessibility {
+ public:
+ // Creates a platform specific BrowserAccessibility. Ownership passes to the
+ // caller.
+ static BrowserAccessibility* Create();
+
+ virtual ~BrowserAccessibility();
+
+ // Detach all descendants of this subtree and push all of the node pointers,
+ // including this node, onto the end of |nodes|.
+ virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes);
+
+ // Perform platform-specific initialization. This can be called multiple times
+ // during the lifetime of this instance after the members of this base object
+ // have been reset with new values from the renderer process.
+ // Child dependent initialization can be done here.
+ virtual void PostInitialize() {}
+
+ // Returns true if this is a native platform-specific object, vs a
+ // cross-platform generic object.
+ virtual bool IsNative() const;
+
+ // Initialize the tree structure of this object.
+ void InitializeTreeStructure(
+ BrowserAccessibilityManager* manager,
+ BrowserAccessibility* parent,
+ int32 renderer_id,
+ int32 index_in_parent);
+
+ // Initialize this object's data.
+ void InitializeData(const AccessibilityNodeData& src);
+
+ virtual void SwapChildren(std::vector<BrowserAccessibility*>& children);
+
+ // Update the parent and index in parent if this node has been moved.
+ void UpdateParent(BrowserAccessibility* parent, int index_in_parent);
+
+ // Update this node's location, leaving everything else the same.
+ virtual void SetLocation(const gfx::Rect& new_location);
+
+ // Return true if this object is equal to or a descendant of |ancestor|.
+ bool IsDescendantOf(BrowserAccessibility* ancestor);
+
+ // Returns the parent of this object, or NULL if it's the root.
+ BrowserAccessibility* parent() const { return parent_; }
+
+ // Returns the number of children of this object.
+ uint32 child_count() const { return children_.size(); }
+
+ // Return a pointer to the child with the given index.
+ BrowserAccessibility* GetChild(uint32 child_index) const;
+
+ // Return the previous sibling of this object, or NULL if it's the first
+ // child of its parent.
+ BrowserAccessibility* GetPreviousSibling();
+
+ // Return the next sibling of this object, or NULL if it's the last child
+ // of its parent.
+ BrowserAccessibility* GetNextSibling();
+
+ // Returns the bounds of this object in coordinates relative to the
+ // top-left corner of the overall web area.
+ gfx::Rect GetLocalBoundsRect() const;
+
+ // Returns the bounds of this object in screen coordinates.
+ gfx::Rect GetGlobalBoundsRect() const;
+
+ // Returns the deepest descendant that contains the specified point
+ // (in global screen coordinates).
+ BrowserAccessibility* BrowserAccessibilityForPoint(const gfx::Point& point);
+
+ // Marks this object for deletion, releases our reference to it, and
+ // recursively calls Destroy() on its children. May not delete
+ // immediately due to reference counting.
+ //
+ // Reference counting is used on some platforms because the
+ // operating system may hold onto a reference to a BrowserAccessibility
+ // object even after we're through with it. When a BrowserAccessibility
+ // has had Destroy() called but its reference count is not yet zero,
+ // queries on this object return failure
+ virtual void Destroy();
+
+ // Subclasses should override this to support platform reference counting.
+ virtual void NativeAddReference() { }
+
+ // Subclasses should override this to support platform reference counting.
+ virtual void NativeReleaseReference();
+
+ //
+ // Accessors
+ //
+
+ const BoolAttrMap& bool_attributes() const {
+ return bool_attributes_;
+ }
+
+ const FloatAttrMap& float_attributes() const {
+ return float_attributes_;
+ }
+
+ const IntAttrMap& int_attributes() const {
+ return int_attributes_;
+ }
+
+ const StringAttrMap& string_attributes() const {
+ return string_attributes_;
+ }
+
+ const std::vector<BrowserAccessibility*>& children() const {
+ return children_;
+ }
+ const std::vector<std::pair<string16, string16> >& html_attributes() const {
+ return html_attributes_;
+ }
+ int32 index_in_parent() const { return index_in_parent_; }
+ const std::vector<int32>& indirect_child_ids() const {
+ return indirect_child_ids_;
+ }
+ const std::vector<int32>& line_breaks() const {
+ return line_breaks_;
+ }
+ const std::vector<int32>& cell_ids() const {
+ return cell_ids_;
+ }
+ const std::vector<int32>& unique_cell_ids() const {
+ return unique_cell_ids_;
+ }
+ gfx::Rect location() const { return location_; }
+ BrowserAccessibilityManager* manager() const { return manager_; }
+ const string16& name() const { return name_; }
+ int32 renderer_id() const { return renderer_id_; }
+ int32 role() const { return role_; }
+ const string16& role_name() const { return role_name_; }
+ int32 state() const { return state_; }
+ const string16& value() const { return value_; }
+ bool instance_active() const { return instance_active_; }
+
+#if defined(OS_MACOSX) && __OBJC__
+ BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa();
+#elif defined(OS_WIN)
+ BrowserAccessibilityWin* ToBrowserAccessibilityWin();
+#elif defined(TOOLKIT_GTK)
+ BrowserAccessibilityGtk* ToBrowserAccessibilityGtk();
+#endif
+
+ // Retrieve the value of a bool attribute from the bool attribute
+ // map and returns true if found.
+ bool GetBoolAttribute(
+ AccessibilityNodeData::BoolAttribute attr, bool* value) const;
+
+ // Retrieve the value of a float attribute from the float attribute
+ // map and returns true if found.
+ bool GetFloatAttribute(AccessibilityNodeData::FloatAttribute attr,
+ float* value) const;
+
+ // Retrieve the value of an integer attribute from the integer attribute
+ // map and returns true if found.
+ bool GetIntAttribute(AccessibilityNodeData::IntAttribute attribute,
+ int* value) const;
+
+ // Retrieve the value of a string attribute from the attribute map and
+ // returns true if found.
+ bool GetStringAttribute(
+ AccessibilityNodeData::StringAttribute attribute, string16* value) const;
+
+ // Retrieve the value of a html attribute from the attribute map and
+ // returns true if found.
+ bool GetHtmlAttribute(const char* attr, string16* value) const;
+
+ // Utility method to handle special cases for ARIA booleans, tristates and
+ // booleans which have a "mixed" state.
+ //
+ // Warning: the term "Tristate" is used loosely by the spec and here,
+ // as some attributes support a 4th state.
+ //
+ // The following attributes are appropriate to use with this method:
+ // aria-selected (selectable)
+ // aria-grabbed (grabbable)
+ // aria-expanded (expandable)
+ // aria-pressed (toggleable/pressable) -- supports 4th "mixed" state
+ // aria-checked (checkable) -- supports 4th "mixed state"
+ bool GetAriaTristate(const char* attr_name,
+ bool* is_defined,
+ bool* is_mixed) const;
+
+ // Returns true if the bit corresponding to the given state enum is 1.
+ bool HasState(AccessibilityNodeData::State state_enum) const;
+
+ // Returns true if this node is an editable text field of any kind.
+ bool IsEditableText() const;
+
+ // Append the text from this node and its children.
+ string16 GetTextRecursive() const;
+
+ protected:
+ // Perform platform specific initialization. This can be called multiple times
+ // during the lifetime of this instance after the members of this base object
+ // have been reset with new values from the renderer process.
+ // Perform child independent initialization in this method.
+ virtual void PreInitialize() {}
+
+ BrowserAccessibility();
+
+ // The manager of this tree of accessibility objects; needed for
+ // global operations like focus tracking.
+ BrowserAccessibilityManager* manager_;
+
+ // The parent of this object, may be NULL if we're the root object.
+ BrowserAccessibility* parent_;
+
+ // The index of this within its parent object.
+ int32 index_in_parent_;
+
+ // The ID of this object in the renderer process.
+ int32 renderer_id_;
+
+ // The children of this object.
+ std::vector<BrowserAccessibility*> children_;
+
+ // Accessibility metadata from the renderer
+ string16 name_;
+ string16 value_;
+ BoolAttrMap bool_attributes_;
+ IntAttrMap int_attributes_;
+ FloatAttrMap float_attributes_;
+ StringAttrMap string_attributes_;
+ std::vector<std::pair<string16, string16> > html_attributes_;
+ int32 role_;
+ int32 state_;
+ string16 role_name_;
+ gfx::Rect location_;
+ std::vector<int32> indirect_child_ids_;
+ std::vector<int32> line_breaks_;
+ std::vector<int32> cell_ids_;
+ std::vector<int32> unique_cell_ids_;
+
+ // BrowserAccessibility objects are reference-counted on some platforms.
+ // When we're done with this object and it's removed from our accessibility
+ // tree, a client may still be holding onto a pointer to this object, so
+ // we mark it as inactive so that calls to any of this object's methods
+ // immediately return failure.
+ bool instance_active_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibility);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.cc b/chromium/content/browser/accessibility/browser_accessibility_android.cc
new file mode 100644
index 00000000000..8ff37ec0c7d
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_android.cc
@@ -0,0 +1,406 @@
+// 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 "content/browser/accessibility/browser_accessibility_android.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager_android.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/accessibility_node_data.h"
+
+namespace content {
+
+// static
+BrowserAccessibility* BrowserAccessibility::Create() {
+ return new BrowserAccessibilityAndroid();
+}
+
+BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
+ first_time_ = true;
+}
+
+bool BrowserAccessibilityAndroid::IsNative() const {
+ return true;
+}
+
+bool BrowserAccessibilityAndroid::IsLeaf() const {
+ if (child_count() == 0)
+ return true;
+
+ // Iframes are always allowed to contain children.
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ return false;
+ }
+
+ // If it has a focusable child, we definitely can't leave out children.
+ if (HasFocusableChild())
+ return false;
+
+ // Headings with text can drop their children.
+ string16 name = GetText();
+ if (role() == AccessibilityNodeData::ROLE_HEADING && !name.empty())
+ return true;
+
+ // Focusable nodes with text can drop their children.
+ if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && !name.empty())
+ return true;
+
+ // Nodes with only static text as children can drop their children.
+ if (HasOnlyStaticTextChildren())
+ return true;
+
+ return false;
+}
+
+bool BrowserAccessibilityAndroid::IsCheckable() const {
+ bool checkable = false;
+ bool is_aria_pressed_defined;
+ bool is_mixed;
+ GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
+ if (role() == AccessibilityNodeData::ROLE_CHECKBOX ||
+ role() == AccessibilityNodeData::ROLE_RADIO_BUTTON ||
+ is_aria_pressed_defined) {
+ checkable = true;
+ }
+ if (HasState(AccessibilityNodeData::STATE_CHECKED))
+ checkable = true;
+ return checkable;
+}
+
+bool BrowserAccessibilityAndroid::IsChecked() const {
+ return HasState(AccessibilityNodeData::STATE_CHECKED);
+}
+
+bool BrowserAccessibilityAndroid::IsClickable() const {
+ return (IsLeaf() && !GetText().empty());
+}
+
+bool BrowserAccessibilityAndroid::IsEnabled() const {
+ return !HasState(AccessibilityNodeData::STATE_UNAVAILABLE);
+}
+
+bool BrowserAccessibilityAndroid::IsFocusable() const {
+ bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE);
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ focusable = false;
+ }
+ return focusable;
+}
+
+bool BrowserAccessibilityAndroid::IsFocused() const {
+ return manager()->GetFocus(manager()->GetRoot()) == this;
+}
+
+bool BrowserAccessibilityAndroid::IsPassword() const {
+ return HasState(AccessibilityNodeData::STATE_PROTECTED);
+}
+
+bool BrowserAccessibilityAndroid::IsScrollable() const {
+ int dummy;
+ return GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
+}
+
+bool BrowserAccessibilityAndroid::IsSelected() const {
+ return HasState(AccessibilityNodeData::STATE_SELECTED);
+}
+
+bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
+ return !HasState(AccessibilityNodeData::STATE_INVISIBLE);
+}
+
+const char* BrowserAccessibilityAndroid::GetClassName() const {
+ const char* class_name = NULL;
+
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_EDITABLE_TEXT:
+ case AccessibilityNodeData::ROLE_SPIN_BUTTON:
+ case AccessibilityNodeData::ROLE_TEXTAREA:
+ case AccessibilityNodeData::ROLE_TEXT_FIELD:
+ class_name = "android.widget.EditText";
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ class_name = "android.widget.SeekBar";
+ break;
+ case AccessibilityNodeData::ROLE_COMBO_BOX:
+ class_name = "android.widget.Spinner";
+ break;
+ case AccessibilityNodeData::ROLE_BUTTON:
+ case AccessibilityNodeData::ROLE_MENU_BUTTON:
+ case AccessibilityNodeData::ROLE_POPUP_BUTTON:
+ class_name = "android.widget.Button";
+ break;
+ case AccessibilityNodeData::ROLE_CHECKBOX:
+ class_name = "android.widget.CheckBox";
+ break;
+ case AccessibilityNodeData::ROLE_RADIO_BUTTON:
+ class_name = "android.widget.RadioButton";
+ break;
+ case AccessibilityNodeData::ROLE_TOGGLE_BUTTON:
+ class_name = "android.widget.ToggleButton";
+ break;
+ case AccessibilityNodeData::ROLE_CANVAS:
+ case AccessibilityNodeData::ROLE_IMAGE:
+ class_name = "android.widget.Image";
+ break;
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR:
+ class_name = "android.widget.ProgressBar";
+ break;
+ case AccessibilityNodeData::ROLE_TAB_LIST:
+ class_name = "android.widget.TabWidget";
+ break;
+ case AccessibilityNodeData::ROLE_GRID:
+ case AccessibilityNodeData::ROLE_TABLE:
+ class_name = "android.widget.GridView";
+ break;
+ case AccessibilityNodeData::ROLE_LIST:
+ case AccessibilityNodeData::ROLE_LISTBOX:
+ class_name = "android.widget.ListView";
+ break;
+ default:
+ class_name = "android.view.View";
+ break;
+ }
+
+ return class_name;
+}
+
+string16 BrowserAccessibilityAndroid::GetText() const {
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ return string16();
+ }
+
+ string16 description;
+ GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description);
+
+ string16 text;
+ if (!name().empty())
+ text = name();
+ else if (!description.empty())
+ text = description;
+ else if (!value().empty())
+ text = value();
+
+ if (text.empty() && HasOnlyStaticTextChildren()) {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ text += static_cast<BrowserAccessibilityAndroid*>(child)->GetText();
+ }
+ }
+
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK:
+ case AccessibilityNodeData::ROLE_LINK:
+ case AccessibilityNodeData::ROLE_WEBCORE_LINK:
+ if (!text.empty())
+ text += ASCIIToUTF16(" ");
+ text += ASCIIToUTF16("Link");
+ break;
+ case AccessibilityNodeData::ROLE_HEADING:
+ // Only append "heading" if this node already has text.
+ if (!text.empty())
+ text += ASCIIToUTF16(" Heading");
+ break;
+ }
+
+ return text;
+}
+
+int BrowserAccessibilityAndroid::GetItemIndex() const {
+ int index = 0;
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_LIST_ITEM:
+ case AccessibilityNodeData::ROLE_LISTBOX_OPTION:
+ index = index_in_parent();
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
+ float value_for_range;
+ if (GetFloatAttribute(
+ AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) {
+ index = static_cast<int>(value_for_range);
+ }
+ break;
+ }
+ }
+ return index;
+}
+
+int BrowserAccessibilityAndroid::GetItemCount() const {
+ int count = 0;
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_LIST:
+ case AccessibilityNodeData::ROLE_LISTBOX:
+ count = child_count();
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
+ float max_value_for_range;
+ if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
+ &max_value_for_range)) {
+ count = static_cast<int>(max_value_for_range);
+ }
+ break;
+ }
+ }
+ return count;
+}
+
+int BrowserAccessibilityAndroid::GetScrollX() const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
+ return value;
+}
+
+int BrowserAccessibilityAndroid::GetScrollY() const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
+ return value;
+}
+
+int BrowserAccessibilityAndroid::GetMaxScrollX() const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
+ return value;
+}
+
+int BrowserAccessibilityAndroid::GetMaxScrollY() const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
+ return value;
+}
+
+int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const {
+ size_t index = 0;
+ while (index < old_value_.length() &&
+ index < new_value_.length() &&
+ old_value_[index] == new_value_[index]) {
+ index++;
+ }
+ return index;
+}
+
+int BrowserAccessibilityAndroid::GetTextChangeAddedCount() const {
+ size_t old_len = old_value_.length();
+ size_t new_len = new_value_.length();
+ size_t left = 0;
+ while (left < old_len &&
+ left < new_len &&
+ old_value_[left] == new_value_[left]) {
+ left++;
+ }
+ size_t right = 0;
+ while (right < old_len &&
+ right < new_len &&
+ old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
+ right++;
+ }
+ return (new_len - left - right);
+}
+
+int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() const {
+ size_t old_len = old_value_.length();
+ size_t new_len = new_value_.length();
+ size_t left = 0;
+ while (left < old_len &&
+ left < new_len &&
+ old_value_[left] == new_value_[left]) {
+ left++;
+ }
+ size_t right = 0;
+ while (right < old_len &&
+ right < new_len &&
+ old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
+ right++;
+ }
+ return (old_len - left - right);
+}
+
+string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
+ return old_value_;
+}
+
+int BrowserAccessibilityAndroid::GetSelectionStart() const {
+ int sel_start = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
+ return sel_start;
+}
+
+int BrowserAccessibilityAndroid::GetSelectionEnd() const {
+ int sel_end = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
+ return sel_end;
+}
+
+int BrowserAccessibilityAndroid::GetEditableTextLength() const {
+ return value().length();
+}
+
+bool BrowserAccessibilityAndroid::HasFocusableChild() const {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ if (child->HasState(AccessibilityNodeData::STATE_FOCUSABLE))
+ return true;
+ if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
+ return true;
+ }
+ return false;
+}
+
+bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ if (child->role() != AccessibilityNodeData::ROLE_STATIC_TEXT)
+ return false;
+ }
+ return true;
+}
+
+bool BrowserAccessibilityAndroid::IsIframe() const {
+ string16 html_tag;
+ GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag);
+ return html_tag == ASCIIToUTF16("iframe");
+}
+
+void BrowserAccessibilityAndroid::PostInitialize() {
+ BrowserAccessibility::PostInitialize();
+
+ if (IsEditableText()) {
+ if (value_ != new_value_) {
+ old_value_ = new_value_;
+ new_value_ = value_;
+ }
+ }
+
+ if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_)
+ manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this);
+
+ string16 live;
+ if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
+ &live)) {
+ NotifyLiveRegionUpdate(live);
+ }
+
+ first_time_ = false;
+}
+
+void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(string16& aria_live) {
+ if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
+ !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
+ return;
+
+ string16 text = GetText();
+ if (cached_text_ != text) {
+ if (!text.empty()) {
+ manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow,
+ this);
+ }
+ cached_text_ = text;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.h b/chromium/content/browser/accessibility/browser_accessibility_android.h
new file mode 100644
index 00000000000..8b4ed84bc9e
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_android.h
@@ -0,0 +1,74 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+
+namespace content {
+
+class BrowserAccessibilityAndroid : public BrowserAccessibility {
+ public:
+ // Overrides from BrowserAccessibility.
+ virtual void PostInitialize() OVERRIDE;
+ virtual bool IsNative() const OVERRIDE;
+
+ bool IsLeaf() const;
+
+ bool IsCheckable() const;
+ bool IsChecked() const;
+ bool IsClickable() const;
+ bool IsEnabled() const;
+ bool IsFocusable() const;
+ bool IsFocused() const;
+ bool IsPassword() const;
+ bool IsScrollable() const;
+ bool IsSelected() const;
+ bool IsVisibleToUser() const;
+
+ const char* GetClassName() const;
+ string16 GetText() const;
+
+ int GetItemIndex() const;
+ int GetItemCount() const;
+
+ int GetScrollX() const;
+ int GetScrollY() const;
+ int GetMaxScrollX() const;
+ int GetMaxScrollY() const;
+
+ int GetTextChangeFromIndex() const;
+ int GetTextChangeAddedCount() const;
+ int GetTextChangeRemovedCount() const;
+ string16 GetTextChangeBeforeText() const;
+
+ int GetSelectionStart() const;
+ int GetSelectionEnd() const;
+ int GetEditableTextLength() const;
+
+ private:
+ // This gives BrowserAccessibility::Create access to the class constructor.
+ friend class BrowserAccessibility;
+
+ BrowserAccessibilityAndroid();
+
+ bool HasFocusableChild() const;
+ bool HasOnlyStaticTextChildren() const;
+ bool IsIframe() const;
+
+ void NotifyLiveRegionUpdate(string16& aria_live);
+
+ string16 cached_text_;
+ bool first_time_;
+ string16 old_value_;
+ string16 new_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.h b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h
new file mode 100644
index 00000000000..a394822961c
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h
@@ -0,0 +1,112 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_
+
+#import <Cocoa/Cocoa.h>
+
+#import "base/mac/scoped_nsobject.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#import "content/browser/accessibility/browser_accessibility_delegate_mac.h"
+#include "content/common/accessibility_node_data.h"
+
+// BrowserAccessibilityCocoa is a cocoa wrapper around the BrowserAccessibility
+// object. The renderer converts webkit's accessibility tree into a
+// WebAccessibility tree and passes it to the browser process over IPC.
+// This class converts it into a format Cocoa can query.
+@interface BrowserAccessibilityCocoa : NSObject {
+ @private
+ content::BrowserAccessibility* browserAccessibility_;
+ base::scoped_nsobject<NSMutableArray> children_;
+ id<BrowserAccessibilityDelegateCocoa> delegate_;
+}
+
+// This creates a cocoa browser accessibility object around
+// the cross platform BrowserAccessibility object. The delegate is
+// used to communicate with the host renderer. None of these
+// parameters can be null.
+- (id)initWithObject:(content::BrowserAccessibility*)accessibility
+ delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate;
+
+// Clear this object's pointer to the wrapped BrowserAccessibility object
+// because the wrapped object has been deleted, but this object may
+// persist if the system still has references to it.
+- (void)detach;
+
+// Invalidate children for a non-ignored ancestor (including self).
+- (void)childrenChanged;
+
+// Convenience method to get the internal, cross-platform role
+// from browserAccessibility_.
+- (content::AccessibilityNodeData::Role)internalRole;
+
+// Return the method name for the given attribute. For testing only.
+- (NSString*)methodNameForAttribute:(NSString*)attribute;
+
+// Internally-used method.
+@property(nonatomic, readonly) NSPoint origin;
+
+// Children is an array of BrowserAccessibility objects, representing
+// the accessibility children of this object.
+@property(nonatomic, readonly) NSString* accessKey;
+@property(nonatomic, readonly) NSNumber* ariaAtomic;
+@property(nonatomic, readonly) NSNumber* ariaBusy;
+@property(nonatomic, readonly) NSString* ariaLive;
+@property(nonatomic, readonly) NSString* ariaRelevant;
+@property(nonatomic, readonly) NSArray* children;
+@property(nonatomic, readonly) NSArray* columns;
+@property(nonatomic, readonly) NSArray* columnHeaders;
+@property(nonatomic, readonly) NSValue* columnIndexRange;
+@property(nonatomic, readonly) NSString* description;
+@property(nonatomic, readonly) NSNumber* disclosing;
+@property(nonatomic, readonly) id disclosedByRow;
+@property(nonatomic, readonly) NSNumber* disclosureLevel;
+@property(nonatomic, readonly) id disclosedRows;
+@property(nonatomic, readonly) NSNumber* enabled;
+@property(nonatomic, readonly) NSNumber* focused;
+@property(nonatomic, readonly) NSString* help;
+// isIgnored returns whether or not the accessibility object
+// should be ignored by the accessibility hierarchy.
+@property(nonatomic, readonly, getter=isIgnored) BOOL ignored;
+// Index of a row, column, or tree item.
+@property(nonatomic, readonly) NSNumber* index;
+@property(nonatomic, readonly) NSString* invalid;
+@property(nonatomic, readonly) NSNumber* loaded;
+@property(nonatomic, readonly) NSNumber* loadingProgress;
+@property(nonatomic, readonly) NSNumber* maxValue;
+@property(nonatomic, readonly) NSNumber* minValue;
+@property(nonatomic, readonly) NSNumber* numberOfCharacters;
+@property(nonatomic, readonly) NSString* orientation;
+@property(nonatomic, readonly) id parent;
+@property(nonatomic, readonly) NSValue* position;
+@property(nonatomic, readonly) NSNumber* required;
+// A string indicating the role of this object as far as accessibility
+// is concerned.
+@property(nonatomic, readonly) NSString* role;
+@property(nonatomic, readonly) NSString* roleDescription;
+@property(nonatomic, readonly) NSArray* rowHeaders;
+@property(nonatomic, readonly) NSValue* rowIndexRange;
+@property(nonatomic, readonly) NSArray* rows;
+// The size of this object.
+@property(nonatomic, readonly) NSValue* size;
+// A string indicating the subrole of this object as far as accessibility
+// is concerned.
+@property(nonatomic, readonly) NSString* subrole;
+// The tabs owned by a tablist.
+@property(nonatomic, readonly) NSArray* tabs;
+@property(nonatomic, readonly) NSString* title;
+@property(nonatomic, readonly) id titleUIElement;
+@property(nonatomic, readonly) NSString* url;
+@property(nonatomic, readonly) NSString* value;
+@property(nonatomic, readonly) NSString* valueDescription;
+@property(nonatomic, readonly) NSValue* visibleCharacterRange;
+@property(nonatomic, readonly) NSArray* visibleCells;
+@property(nonatomic, readonly) NSArray* visibleColumns;
+@property(nonatomic, readonly) NSArray* visibleRows;
+@property(nonatomic, readonly) NSNumber* visited;
+@property(nonatomic, readonly) id window;
+@end
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm
new file mode 100644
index 00000000000..9d49767d266
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -0,0 +1,1505 @@
+// 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 <execinfo.h>
+
+#import "content/browser/accessibility/browser_accessibility_cocoa.h"
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
+#include "content/public/common/content_client.h"
+#include "grit/webkit_strings.h"
+
+// See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5,
+// 10.6, and 10.7. It allows accessibility clients to observe events posted on
+// this object.
+extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
+
+using content::AccessibilityNodeData;
+using content::BrowserAccessibility;
+using content::BrowserAccessibilityManager;
+using content::BrowserAccessibilityManagerMac;
+using content::ContentClient;
+typedef AccessibilityNodeData::StringAttribute StringAttribute;
+
+namespace {
+
+// Returns an autoreleased copy of the AccessibilityNodeData's attribute.
+NSString* NSStringForStringAttribute(
+ const std::map<StringAttribute, string16>& attributes,
+ StringAttribute attribute) {
+ std::map<StringAttribute, string16>::const_iterator iter =
+ attributes.find(attribute);
+ NSString* returnValue = @"";
+ if (iter != attributes.end()) {
+ returnValue = base::SysUTF16ToNSString(iter->second);
+ }
+ return returnValue;
+}
+
+struct MapEntry {
+ AccessibilityNodeData::Role webKitValue;
+ NSString* nativeValue;
+};
+
+typedef std::map<AccessibilityNodeData::Role, NSString*> RoleMap;
+
+// GetState checks the bitmask used in AccessibilityNodeData to check
+// if the given state was set on the accessibility object.
+bool GetState(BrowserAccessibility* accessibility, int state) {
+ return ((accessibility->state() >> state) & 1);
+}
+
+RoleMap BuildRoleMap() {
+ const MapEntry roles[] = {
+ { AccessibilityNodeData::ROLE_ALERT, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_ALERT_DIALOG, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_ANNOTATION, NSAccessibilityUnknownRole },
+ { AccessibilityNodeData::ROLE_APPLICATION, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_ARTICLE, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_BROWSER, NSAccessibilityBrowserRole },
+ { AccessibilityNodeData::ROLE_BUSY_INDICATOR,
+ NSAccessibilityBusyIndicatorRole },
+ { AccessibilityNodeData::ROLE_BUTTON, NSAccessibilityButtonRole },
+ { AccessibilityNodeData::ROLE_CANVAS, NSAccessibilityImageRole },
+ { AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_CELL, @"AXCell" },
+ { AccessibilityNodeData::ROLE_CHECKBOX, NSAccessibilityCheckBoxRole },
+ { AccessibilityNodeData::ROLE_COLOR_WELL, NSAccessibilityColorWellRole },
+ { AccessibilityNodeData::ROLE_COMBO_BOX, NSAccessibilityComboBoxRole },
+ { AccessibilityNodeData::ROLE_COLUMN, NSAccessibilityColumnRole },
+ { AccessibilityNodeData::ROLE_COLUMN_HEADER, @"AXCell" },
+ { AccessibilityNodeData::ROLE_DEFINITION, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DIALOG, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DIRECTORY, NSAccessibilityListRole },
+ { AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE,
+ NSAccessibilityDisclosureTriangleRole },
+ { AccessibilityNodeData::ROLE_DIV, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DOCUMENT, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_DRAWER, NSAccessibilityDrawerRole },
+ { AccessibilityNodeData::ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole },
+ { AccessibilityNodeData::ROLE_FOOTER, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_FORM, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_GRID, NSAccessibilityGridRole },
+ { AccessibilityNodeData::ROLE_GROUP, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_GROW_AREA, NSAccessibilityGrowAreaRole },
+ { AccessibilityNodeData::ROLE_HEADING, @"AXHeading" },
+ { AccessibilityNodeData::ROLE_HELP_TAG, NSAccessibilityHelpTagRole },
+ { AccessibilityNodeData::ROLE_HORIZONTAL_RULE, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_IGNORED, NSAccessibilityUnknownRole },
+ { AccessibilityNodeData::ROLE_IMAGE, NSAccessibilityImageRole },
+ { AccessibilityNodeData::ROLE_IMAGE_MAP, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole },
+ { AccessibilityNodeData::ROLE_INCREMENTOR, NSAccessibilityIncrementorRole },
+ { AccessibilityNodeData::ROLE_LABEL, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_APPLICATION,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_BANNER, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_MAIN, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LANDMARK_SEARCH, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LINK, NSAccessibilityLinkRole },
+ { AccessibilityNodeData::ROLE_LIST, NSAccessibilityListRole },
+ { AccessibilityNodeData::ROLE_LIST_ITEM, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_LIST_MARKER, @"AXListMarker" },
+ { AccessibilityNodeData::ROLE_LISTBOX, NSAccessibilityListRole },
+ { AccessibilityNodeData::ROLE_LISTBOX_OPTION,
+ NSAccessibilityStaticTextRole },
+ { AccessibilityNodeData::ROLE_LOG, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_MARQUEE, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_MATH, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_MATTE, NSAccessibilityMatteRole },
+ { AccessibilityNodeData::ROLE_MENU, NSAccessibilityMenuRole },
+ { AccessibilityNodeData::ROLE_MENU_BAR, NSAccessibilityMenuBarRole },
+ { AccessibilityNodeData::ROLE_MENU_ITEM, NSAccessibilityMenuItemRole },
+ { AccessibilityNodeData::ROLE_MENU_BUTTON, NSAccessibilityButtonRole },
+ { AccessibilityNodeData::ROLE_MENU_LIST_OPTION,
+ NSAccessibilityMenuItemRole },
+ { AccessibilityNodeData::ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole },
+ { AccessibilityNodeData::ROLE_NOTE, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_OUTLINE, NSAccessibilityOutlineRole },
+ { AccessibilityNodeData::ROLE_PARAGRAPH, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_POPUP_BUTTON,
+ NSAccessibilityPopUpButtonRole },
+ { AccessibilityNodeData::ROLE_PRESENTATIONAL, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_PROGRESS_INDICATOR,
+ NSAccessibilityProgressIndicatorRole },
+ { AccessibilityNodeData::ROLE_RADIO_BUTTON,
+ NSAccessibilityRadioButtonRole },
+ { AccessibilityNodeData::ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole },
+ { AccessibilityNodeData::ROLE_REGION, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_ROOT_WEB_AREA, @"AXWebArea" },
+ { AccessibilityNodeData::ROLE_ROW, NSAccessibilityRowRole },
+ { AccessibilityNodeData::ROLE_ROW_HEADER, @"AXCell" },
+ { AccessibilityNodeData::ROLE_RULER, NSAccessibilityRulerRole },
+ { AccessibilityNodeData::ROLE_RULER_MARKER,
+ NSAccessibilityRulerMarkerRole },
+ // TODO(dtseng): we don't correctly support the attributes for these roles.
+ // { AccessibilityNodeData::ROLE_SCROLLAREA,
+ // NSAccessibilityScrollAreaRole },
+ { AccessibilityNodeData::ROLE_SCROLLBAR, NSAccessibilityScrollBarRole },
+ { AccessibilityNodeData::ROLE_SHEET, NSAccessibilitySheetRole },
+ { AccessibilityNodeData::ROLE_SLIDER, NSAccessibilitySliderRole },
+ { AccessibilityNodeData::ROLE_SLIDER_THUMB,
+ NSAccessibilityValueIndicatorRole },
+ { AccessibilityNodeData::ROLE_SPIN_BUTTON, NSAccessibilitySliderRole },
+ { AccessibilityNodeData::ROLE_SPLITTER, NSAccessibilitySplitterRole },
+ { AccessibilityNodeData::ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole },
+ { AccessibilityNodeData::ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole },
+ { AccessibilityNodeData::ROLE_STATUS, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_SVG_ROOT, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole },
+ { AccessibilityNodeData::ROLE_TAB, NSAccessibilityRadioButtonRole },
+ { AccessibilityNodeData::ROLE_TAB_LIST, NSAccessibilityTabGroupRole },
+ { AccessibilityNodeData::ROLE_TAB_PANEL, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_TABLE, NSAccessibilityTableRole },
+ { AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER,
+ NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED,
+ NSAccessibilityTabGroupRole },
+ { AccessibilityNodeData::ROLE_TEXTAREA, NSAccessibilityTextAreaRole },
+ { AccessibilityNodeData::ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole },
+ { AccessibilityNodeData::ROLE_TIMER, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_TOGGLE_BUTTON, NSAccessibilityButtonRole },
+ { AccessibilityNodeData::ROLE_TOOLBAR, NSAccessibilityToolbarRole },
+ { AccessibilityNodeData::ROLE_TOOLTIP, NSAccessibilityGroupRole },
+ { AccessibilityNodeData::ROLE_TREE, NSAccessibilityOutlineRole },
+ { AccessibilityNodeData::ROLE_TREE_GRID, NSAccessibilityTableRole },
+ { AccessibilityNodeData::ROLE_TREE_ITEM, NSAccessibilityRowRole },
+ { AccessibilityNodeData::ROLE_VALUE_INDICATOR,
+ NSAccessibilityValueIndicatorRole },
+ { AccessibilityNodeData::ROLE_WEBCORE_LINK, NSAccessibilityLinkRole },
+ { AccessibilityNodeData::ROLE_WEB_AREA, @"AXWebArea" },
+ { AccessibilityNodeData::ROLE_WINDOW, NSAccessibilityWindowRole },
+ };
+
+ RoleMap role_map;
+ for (size_t i = 0; i < arraysize(roles); ++i)
+ role_map[roles[i].webKitValue] = roles[i].nativeValue;
+ return role_map;
+}
+
+// A mapping of webkit roles to native roles.
+NSString* NativeRoleFromAccessibilityNodeDataRole(
+ const AccessibilityNodeData::Role& role) {
+ CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_role,
+ (BuildRoleMap()));
+ RoleMap::iterator it = web_accessibility_to_native_role.find(role);
+ if (it != web_accessibility_to_native_role.end())
+ return it->second;
+ else
+ return NSAccessibilityUnknownRole;
+}
+
+RoleMap BuildSubroleMap() {
+ const MapEntry subroles[] = {
+ { AccessibilityNodeData::ROLE_ALERT, @"AXApplicationAlert" },
+ { AccessibilityNodeData::ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" },
+ { AccessibilityNodeData::ROLE_ARTICLE, @"AXDocumentArticle" },
+ { AccessibilityNodeData::ROLE_DEFINITION, @"AXDefinition" },
+ { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL, @"AXDescription" },
+ { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM, @"AXTerm" },
+ { AccessibilityNodeData::ROLE_DIALOG, @"AXApplicationDialog" },
+ { AccessibilityNodeData::ROLE_DOCUMENT, @"AXDocument" },
+ { AccessibilityNodeData::ROLE_FOOTER, @"AXLandmarkContentInfo" },
+ { AccessibilityNodeData::ROLE_LANDMARK_APPLICATION,
+ @"AXLandmarkApplication" },
+ { AccessibilityNodeData::ROLE_LANDMARK_BANNER, @"AXLandmarkBanner" },
+ { AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY,
+ @"AXLandmarkComplementary" },
+ { AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO,
+ @"AXLandmarkContentInfo" },
+ { AccessibilityNodeData::ROLE_LANDMARK_MAIN, @"AXLandmarkMain" },
+ { AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION,
+ @"AXLandmarkNavigation" },
+ { AccessibilityNodeData::ROLE_LANDMARK_SEARCH, @"AXLandmarkSearch" },
+ { AccessibilityNodeData::ROLE_LOG, @"AXApplicationLog" },
+ { AccessibilityNodeData::ROLE_MARQUEE, @"AXApplicationMarquee" },
+ { AccessibilityNodeData::ROLE_MATH, @"AXDocumentMath" },
+ { AccessibilityNodeData::ROLE_NOTE, @"AXDocumentNote" },
+ { AccessibilityNodeData::ROLE_REGION, @"AXDocumentRegion" },
+ { AccessibilityNodeData::ROLE_STATUS, @"AXApplicationStatus" },
+ { AccessibilityNodeData::ROLE_TAB_PANEL, @"AXTabPanel" },
+ { AccessibilityNodeData::ROLE_TIMER, @"AXApplicationTimer" },
+ { AccessibilityNodeData::ROLE_TOOLTIP, @"AXUserInterfaceTooltip" },
+ { AccessibilityNodeData::ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole },
+ };
+
+ RoleMap subrole_map;
+ for (size_t i = 0; i < arraysize(subroles); ++i)
+ subrole_map[subroles[i].webKitValue] = subroles[i].nativeValue;
+ return subrole_map;
+}
+
+// A mapping of webkit roles to native subroles.
+NSString* NativeSubroleFromAccessibilityNodeDataRole(
+ const AccessibilityNodeData::Role& role) {
+ CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_subrole,
+ (BuildSubroleMap()));
+ RoleMap::iterator it = web_accessibility_to_native_subrole.find(role);
+ if (it != web_accessibility_to_native_subrole.end())
+ return it->second;
+ else
+ return nil;
+}
+
+// A mapping from an accessibility attribute to its method name.
+NSDictionary* attributeToMethodNameMap = nil;
+
+} // namespace
+
+@implementation BrowserAccessibilityCocoa
+
++ (void)initialize {
+ const struct {
+ NSString* attribute;
+ NSString* methodName;
+ } attributeToMethodNameContainer[] = {
+ { NSAccessibilityChildrenAttribute, @"children" },
+ { NSAccessibilityColumnsAttribute, @"columns" },
+ { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" },
+ { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" },
+ { NSAccessibilityContentsAttribute, @"contents" },
+ { NSAccessibilityDescriptionAttribute, @"description" },
+ { NSAccessibilityDisclosingAttribute, @"disclosing" },
+ { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" },
+ { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" },
+ { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" },
+ { NSAccessibilityEnabledAttribute, @"enabled" },
+ { NSAccessibilityFocusedAttribute, @"focused" },
+ { NSAccessibilityHeaderAttribute, @"header" },
+ { NSAccessibilityHelpAttribute, @"help" },
+ { NSAccessibilityIndexAttribute, @"index" },
+ { NSAccessibilityMaxValueAttribute, @"maxValue" },
+ { NSAccessibilityMinValueAttribute, @"minValue" },
+ { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" },
+ { NSAccessibilityOrientationAttribute, @"orientation" },
+ { NSAccessibilityParentAttribute, @"parent" },
+ { NSAccessibilityPositionAttribute, @"position" },
+ { NSAccessibilityRoleAttribute, @"role" },
+ { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" },
+ { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" },
+ { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" },
+ { NSAccessibilityRowsAttribute, @"rows" },
+ { NSAccessibilitySizeAttribute, @"size" },
+ { NSAccessibilitySubroleAttribute, @"subrole" },
+ { NSAccessibilityTabsAttribute, @"tabs" },
+ { NSAccessibilityTitleAttribute, @"title" },
+ { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" },
+ { NSAccessibilityTopLevelUIElementAttribute, @"window" },
+ { NSAccessibilityURLAttribute, @"url" },
+ { NSAccessibilityValueAttribute, @"value" },
+ { NSAccessibilityValueDescriptionAttribute, @"valueDescription" },
+ { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" },
+ { NSAccessibilityVisibleCellsAttribute, @"visibleCells" },
+ { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" },
+ { NSAccessibilityVisibleRowsAttribute, @"visibleRows" },
+ { NSAccessibilityWindowAttribute, @"window" },
+ { @"AXAccessKey", @"accessKey" },
+ { @"AXARIAAtomic", @"ariaAtomic" },
+ { @"AXARIABusy", @"ariaBusy" },
+ { @"AXARIALive", @"ariaLive" },
+ { @"AXARIARelevant", @"ariaRelevant" },
+ { @"AXInvalid", @"invalid" },
+ { @"AXLoaded", @"loaded" },
+ { @"AXLoadingProgress", @"loadingProgress" },
+ { @"AXRequired", @"required" },
+ { @"AXVisited", @"visited" },
+ };
+
+ NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
+ const size_t numAttributes = sizeof(attributeToMethodNameContainer) /
+ sizeof(attributeToMethodNameContainer[0]);
+ for (size_t i = 0; i < numAttributes; ++i) {
+ [dict setObject:attributeToMethodNameContainer[i].methodName
+ forKey:attributeToMethodNameContainer[i].attribute];
+ }
+ attributeToMethodNameMap = dict;
+ dict = nil;
+}
+
+- (id)initWithObject:(BrowserAccessibility*)accessibility
+ delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate {
+ if ((self = [super init])) {
+ browserAccessibility_ = accessibility;
+ delegate_ = delegate;
+ }
+ return self;
+}
+
+- (void)detach {
+ if (browserAccessibility_) {
+ NSAccessibilityUnregisterUniqueIdForUIElement(self);
+ browserAccessibility_ = NULL;
+ }
+}
+
+- (NSString*)accessKey {
+ return NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ AccessibilityNodeData::ATTR_ACCESS_KEY);
+}
+
+- (NSNumber*)ariaAtomic {
+ bool boolValue = false;
+ browserAccessibility_->GetBoolAttribute(
+ AccessibilityNodeData::ATTR_LIVE_ATOMIC, &boolValue);
+ return [NSNumber numberWithBool:boolValue];
+}
+
+- (NSNumber*)ariaBusy {
+ bool boolValue = false;
+ browserAccessibility_->GetBoolAttribute(
+ AccessibilityNodeData::ATTR_LIVE_BUSY, &boolValue);
+ return [NSNumber numberWithBool:boolValue];
+}
+
+- (NSString*)ariaLive {
+ return NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ AccessibilityNodeData::ATTR_LIVE_STATUS);
+}
+
+- (NSString*)ariaRelevant {
+ return NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ AccessibilityNodeData::ATTR_LIVE_RELEVANT);
+}
+
+// Returns an array of BrowserAccessibilityCocoa objects, representing the
+// accessibility children of this object.
+- (NSArray*)children {
+ if (!children_) {
+ children_.reset([[NSMutableArray alloc]
+ initWithCapacity:browserAccessibility_->child_count()] );
+ for (uint32 index = 0;
+ index < browserAccessibility_->child_count();
+ ++index) {
+ BrowserAccessibilityCocoa* child =
+ browserAccessibility_->GetChild(index)->ToBrowserAccessibilityCocoa();
+ if ([child isIgnored])
+ [children_ addObjectsFromArray:[child children]];
+ else
+ [children_ addObject:child];
+ }
+
+ // Also, add indirect children (if any).
+ for (uint32 i = 0;
+ i < browserAccessibility_->indirect_child_ids().size();
+ ++i) {
+ int32 child_id = browserAccessibility_->indirect_child_ids()[i];
+ BrowserAccessibility* child =
+ browserAccessibility_->manager()->GetFromRendererID(child_id);
+
+ // This only became necessary as a result of crbug.com/93095. It should be
+ // a DCHECK in the future.
+ if (child) {
+ BrowserAccessibilityCocoa* child_cocoa =
+ child->ToBrowserAccessibilityCocoa();
+ [children_ addObject:child_cocoa];
+ }
+ }
+ }
+ return children_;
+}
+
+- (void)childrenChanged {
+ if (![self isIgnored]) {
+ children_.reset();
+ } else {
+ [browserAccessibility_->parent()->ToBrowserAccessibilityCocoa()
+ childrenChanged];
+ }
+}
+
+- (NSArray*)columnHeaders {
+ if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE &&
+ [self internalRole] != AccessibilityNodeData::ROLE_GRID) {
+ return nil;
+ }
+
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ const std::vector<int32>& uniqueCellIds =
+ browserAccessibility_->unique_cell_ids();
+ for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
+ int id = uniqueCellIds[i];
+ BrowserAccessibility* cell =
+ browserAccessibility_->manager()->GetFromRendererID(id);
+ if (cell && cell->role() == AccessibilityNodeData::ROLE_COLUMN_HEADER)
+ [ret addObject:cell->ToBrowserAccessibilityCocoa()];
+ }
+ return ret;
+}
+
+- (NSValue*)columnIndexRange {
+ if ([self internalRole] != AccessibilityNodeData::ROLE_CELL)
+ return nil;
+
+ int column = -1;
+ int colspan = -1;
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column);
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan);
+ if (column >= 0 && colspan >= 1)
+ return [NSValue valueWithRange:NSMakeRange(column, colspan)];
+ return nil;
+}
+
+- (NSArray*)columns {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ for (BrowserAccessibilityCocoa* child in [self children]) {
+ if ([[child role] isEqualToString:NSAccessibilityColumnRole])
+ [ret addObject:child];
+ }
+ return ret;
+}
+
+- (NSString*)description {
+ const std::map<StringAttribute, string16>& attributes =
+ browserAccessibility_->string_attributes();
+ std::map<StringAttribute, string16>::const_iterator iter =
+ attributes.find(AccessibilityNodeData::ATTR_DESCRIPTION);
+ if (iter != attributes.end())
+ return base::SysUTF16ToNSString(iter->second);
+
+ // If the role is anything other than an image, or if there's
+ // a title or title UI element, just return an empty string.
+ if (![[self role] isEqualToString:NSAccessibilityImageRole])
+ return @"";
+ if (!browserAccessibility_->name().empty())
+ return @"";
+ if ([self titleUIElement])
+ return @"";
+
+ // The remaining case is an image where there's no other title.
+ // Return the base part of the filename as the description.
+ iter = attributes.find(AccessibilityNodeData::ATTR_URL);
+ if (iter != attributes.end()) {
+ string16 filename = iter->second;
+ // Given a url like http://foo.com/bar/baz.png, just return the
+ // base name, e.g., "baz.png".
+ size_t leftIndex = filename.size();
+ while (leftIndex > 0 && filename[leftIndex - 1] != '/')
+ leftIndex--;
+ string16 basename = filename.substr(leftIndex);
+
+ return base::SysUTF16ToNSString(basename);
+ }
+
+ return @"";
+}
+
+- (NSNumber*)disclosing {
+ if ([self internalRole] == AccessibilityNodeData::ROLE_TREE_ITEM) {
+ return [NSNumber numberWithBool:
+ GetState(browserAccessibility_, AccessibilityNodeData::STATE_EXPANDED)];
+ } else {
+ return nil;
+ }
+}
+
+- (id)disclosedByRow {
+ // The row that contains this row.
+ // It should be the same as the first parent that is a treeitem.
+ return nil;
+}
+
+- (NSNumber*)disclosureLevel {
+ AccessibilityNodeData::Role role = [self internalRole];
+ if (role == AccessibilityNodeData::ROLE_ROW ||
+ role == AccessibilityNodeData::ROLE_TREE_ITEM) {
+ int level = 0;
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, &level);
+ // Mac disclosureLevel is 0-based, but web levels are 1-based.
+ if (level > 0)
+ level--;
+ return [NSNumber numberWithInt:level];
+ } else {
+ return nil;
+ }
+}
+
+- (id)disclosedRows {
+ // The rows that are considered inside this row.
+ return nil;
+}
+
+- (NSNumber*)enabled {
+ return [NSNumber numberWithBool:
+ !GetState(browserAccessibility_,
+ AccessibilityNodeData::STATE_UNAVAILABLE)];
+}
+
+- (NSNumber*)focused {
+ BrowserAccessibilityManager* manager = browserAccessibility_->manager();
+ NSNumber* ret = [NSNumber numberWithBool:
+ manager->GetFocus(NULL) == browserAccessibility_];
+ return ret;
+}
+
+- (id)header {
+ int headerElementId = -1;
+ if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE ||
+ [self internalRole] == AccessibilityNodeData::ROLE_GRID) {
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_HEADER_ID, &headerElementId);
+ } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) {
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId);
+ } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) {
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_HEADER_ID, &headerElementId);
+ }
+
+ if (headerElementId > 0) {
+ BrowserAccessibility* headerObject =
+ browserAccessibility_->manager()->GetFromRendererID(headerElementId);
+ if (headerObject)
+ return headerObject->ToBrowserAccessibilityCocoa();
+ }
+ return nil;
+}
+
+- (NSString*)help {
+ return NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ AccessibilityNodeData::ATTR_HELP);
+}
+
+- (NSNumber*)index {
+ if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) {
+ int columnIndex;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_INDEX, &columnIndex)) {
+ return [NSNumber numberWithInt:columnIndex];
+ }
+ } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) {
+ int rowIndex;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) {
+ return [NSNumber numberWithInt:rowIndex];
+ }
+ }
+
+ return nil;
+}
+
+// Returns whether or not this node should be ignored in the
+// accessibility tree.
+- (BOOL)isIgnored {
+ return [[self role] isEqualToString:NSAccessibilityUnknownRole];
+}
+
+- (NSString*)invalid {
+ string16 invalidUTF;
+ if (!browserAccessibility_->GetHtmlAttribute("aria-invalid", &invalidUTF))
+ return NULL;
+ NSString* invalid = base::SysUTF16ToNSString(invalidUTF);
+ if ([invalid isEqualToString:@"false"] ||
+ [invalid isEqualToString:@""]) {
+ return @"false";
+ }
+ return invalid;
+}
+
+- (NSNumber*)loaded {
+ return [NSNumber numberWithBool:YES];
+}
+
+- (NSNumber*)loadingProgress {
+ float floatValue = 0.0;
+ browserAccessibility_->GetFloatAttribute(
+ AccessibilityNodeData::ATTR_DOC_LOADING_PROGRESS, &floatValue);
+ return [NSNumber numberWithFloat:floatValue];
+}
+
+- (NSNumber*)maxValue {
+ float floatValue = 0.0;
+ browserAccessibility_->GetFloatAttribute(
+ AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, &floatValue);
+ return [NSNumber numberWithFloat:floatValue];
+}
+
+- (NSNumber*)minValue {
+ float floatValue = 0.0;
+ browserAccessibility_->GetFloatAttribute(
+ AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE, &floatValue);
+ return [NSNumber numberWithFloat:floatValue];
+}
+
+- (NSString*)orientation {
+ // We present a spin button as a vertical slider, with a role description
+ // of "spin button".
+ if ([self internalRole] == AccessibilityNodeData::ROLE_SPIN_BUTTON)
+ return NSAccessibilityVerticalOrientationValue;
+
+ if (GetState(browserAccessibility_, AccessibilityNodeData::STATE_VERTICAL))
+ return NSAccessibilityVerticalOrientationValue;
+ else
+ return NSAccessibilityHorizontalOrientationValue;
+}
+
+- (NSNumber*)numberOfCharacters {
+ return [NSNumber numberWithInt:browserAccessibility_->value().length()];
+}
+
+// The origin of this accessibility object in the page's document.
+// This is relative to webkit's top-left origin, not Cocoa's
+// bottom-left origin.
+- (NSPoint)origin {
+ gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
+ return NSMakePoint(bounds.x(), bounds.y());
+}
+
+- (id)parent {
+ // A nil parent means we're the root.
+ if (browserAccessibility_->parent()) {
+ return NSAccessibilityUnignoredAncestor(
+ browserAccessibility_->parent()->ToBrowserAccessibilityCocoa());
+ } else {
+ // Hook back up to RenderWidgetHostViewCocoa.
+ BrowserAccessibilityManagerMac* manager =
+ static_cast<BrowserAccessibilityManagerMac*>(
+ browserAccessibility_->manager());
+ return manager->parent_view();
+ }
+}
+
+- (NSValue*)position {
+ return [NSValue valueWithPoint:[delegate_ accessibilityPointInScreen:self]];
+}
+
+- (NSNumber*)required {
+ return [NSNumber numberWithBool:
+ GetState(browserAccessibility_, AccessibilityNodeData::STATE_REQUIRED)];
+}
+
+// Returns an enum indicating the role from browserAccessibility_.
+- (AccessibilityNodeData::Role)internalRole {
+ return static_cast<AccessibilityNodeData::Role>(
+ browserAccessibility_->role());
+}
+
+// Returns a string indicating the NSAccessibility role of this object.
+- (NSString*)role {
+ return NativeRoleFromAccessibilityNodeDataRole([self internalRole]);
+}
+
+// Returns a string indicating the role description of this object.
+- (NSString*)roleDescription {
+ NSString* role = [self role];
+
+ ContentClient* content_client = content::GetContentClient();
+
+ // The following descriptions are specific to webkit.
+ if ([role isEqualToString:@"AXWebArea"]) {
+ return base::SysUTF16ToNSString(content_client->GetLocalizedString(
+ IDS_AX_ROLE_WEB_AREA));
+ }
+
+ if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
+ return base::SysUTF16ToNSString(content_client->GetLocalizedString(
+ IDS_AX_ROLE_LINK));
+ }
+
+ if ([role isEqualToString:@"AXHeading"]) {
+ return base::SysUTF16ToNSString(content_client->GetLocalizedString(
+ IDS_AX_ROLE_HEADING));
+ }
+
+ if ([role isEqualToString:NSAccessibilityGroupRole] ||
+ [role isEqualToString:NSAccessibilityRadioButtonRole]) {
+ const std::vector<std::pair<string16, string16> >& htmlAttributes =
+ browserAccessibility_->html_attributes();
+ AccessibilityNodeData::Role browserAccessibilityRole = [self internalRole];
+ if ((browserAccessibilityRole != AccessibilityNodeData::ROLE_GROUP &&
+ browserAccessibilityRole != AccessibilityNodeData::ROLE_LIST_ITEM) ||
+ browserAccessibilityRole == AccessibilityNodeData::ROLE_TAB) {
+ for (size_t i = 0; i < htmlAttributes.size(); ++i) {
+ const std::pair<string16, string16>& htmlAttribute = htmlAttributes[i];
+ if (htmlAttribute.first == ASCIIToUTF16("role")) {
+ // TODO(dtseng): This is not localized; see crbug/84814.
+ return base::SysUTF16ToNSString(htmlAttribute.second);
+ }
+ }
+ }
+ }
+
+ switch([self internalRole]) {
+ case AccessibilityNodeData::ROLE_FOOTER:
+ return base::SysUTF16ToNSString(content_client->GetLocalizedString(
+ IDS_AX_ROLE_FOOTER));
+ case AccessibilityNodeData::ROLE_SPIN_BUTTON:
+ // This control is similar to what VoiceOver calls a "stepper".
+ return base::SysUTF16ToNSString(content_client->GetLocalizedString(
+ IDS_AX_ROLE_STEPPER));
+ default:
+ break;
+ }
+
+ return NSAccessibilityRoleDescription(role, nil);
+}
+
+- (NSArray*)rowHeaders {
+ if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE &&
+ [self internalRole] != AccessibilityNodeData::ROLE_GRID) {
+ return nil;
+ }
+
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ const std::vector<int32>& uniqueCellIds =
+ browserAccessibility_->unique_cell_ids();
+ for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
+ int id = uniqueCellIds[i];
+ BrowserAccessibility* cell =
+ browserAccessibility_->manager()->GetFromRendererID(id);
+ if (cell && cell->role() == AccessibilityNodeData::ROLE_ROW_HEADER)
+ [ret addObject:cell->ToBrowserAccessibilityCocoa()];
+ }
+ return ret;
+}
+
+- (NSValue*)rowIndexRange {
+ if ([self internalRole] != AccessibilityNodeData::ROLE_CELL)
+ return nil;
+
+ int row = -1;
+ int rowspan = -1;
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row);
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan);
+ if (row >= 0 && rowspan >= 1)
+ return [NSValue valueWithRange:NSMakeRange(row, rowspan)];
+ return nil;
+}
+
+- (NSArray*)rows {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+
+ if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE||
+ [self internalRole] == AccessibilityNodeData::ROLE_GRID) {
+ for (BrowserAccessibilityCocoa* child in [self children]) {
+ if ([[child role] isEqualToString:NSAccessibilityRowRole])
+ [ret addObject:child];
+ }
+ } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) {
+ const std::vector<int32>& indirectChildIds =
+ browserAccessibility_->indirect_child_ids();
+ for (uint32 i = 0; i < indirectChildIds.size(); ++i) {
+ int id = indirectChildIds[i];
+ BrowserAccessibility* rowElement =
+ browserAccessibility_->manager()->GetFromRendererID(id);
+ if (rowElement)
+ [ret addObject:rowElement->ToBrowserAccessibilityCocoa()];
+ }
+ }
+
+ return ret;
+}
+
+// Returns the size of this object.
+- (NSValue*)size {
+ gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect();
+ return [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())];
+}
+
+// Returns a subrole based upon the role.
+- (NSString*) subrole {
+ AccessibilityNodeData::Role browserAccessibilityRole = [self internalRole];
+ if (browserAccessibilityRole == AccessibilityNodeData::ROLE_TEXT_FIELD &&
+ GetState(browserAccessibility_, AccessibilityNodeData::STATE_PROTECTED)) {
+ return @"AXSecureTextField";
+ }
+
+ NSString* htmlTag = NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ AccessibilityNodeData::ATTR_HTML_TAG);
+
+ if (browserAccessibilityRole == AccessibilityNodeData::ROLE_LIST) {
+ if ([htmlTag isEqualToString:@"ul"] ||
+ [htmlTag isEqualToString:@"ol"]) {
+ return @"AXContentList";
+ } else if ([htmlTag isEqualToString:@"dl"]) {
+ return @"AXDescriptionList";
+ }
+ }
+
+ return NativeSubroleFromAccessibilityNodeDataRole(browserAccessibilityRole);
+}
+
+// Returns all tabs in this subtree.
+- (NSArray*)tabs {
+ NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease];
+
+ if ([self internalRole] == AccessibilityNodeData::ROLE_TAB)
+ [tabSubtree addObject:self];
+
+ for (uint i=0; i < [[self children] count]; ++i) {
+ NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs];
+ if ([tabChildren count] > 0)
+ [tabSubtree addObjectsFromArray:tabChildren];
+ }
+
+ return tabSubtree;
+}
+
+- (NSString*)title {
+ return base::SysUTF16ToNSString(browserAccessibility_->name());
+}
+
+- (id)titleUIElement {
+ int titleElementId;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &titleElementId)) {
+ BrowserAccessibility* titleElement =
+ browserAccessibility_->manager()->GetFromRendererID(titleElementId);
+ if (titleElement)
+ return titleElement->ToBrowserAccessibilityCocoa();
+ }
+ return nil;
+}
+
+- (NSString*)url {
+ StringAttribute urlAttribute =
+ [[self role] isEqualToString:@"AXWebArea"] ?
+ AccessibilityNodeData::ATTR_DOC_URL :
+ AccessibilityNodeData::ATTR_URL;
+ return NSStringForStringAttribute(
+ browserAccessibility_->string_attributes(),
+ urlAttribute);
+}
+
+- (id)value {
+ // WebCore uses an attachmentView to get the below behavior.
+ // We do not have any native views backing this object, so need
+ // to approximate Cocoa ax behavior best as we can.
+ NSString* role = [self role];
+ if ([role isEqualToString:@"AXHeading"]) {
+ int level;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, &level)) {
+ return [NSNumber numberWithInt:level];
+ }
+ } else if ([role isEqualToString:NSAccessibilityButtonRole]) {
+ // AXValue does not make sense for pure buttons.
+ return @"";
+ } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] ||
+ [role isEqualToString:NSAccessibilityRadioButtonRole]) {
+ int value = 0;
+ value = GetState(
+ browserAccessibility_, AccessibilityNodeData::STATE_CHECKED) ? 1 : 0;
+ value = GetState(
+ browserAccessibility_, AccessibilityNodeData::STATE_SELECTED) ?
+ 1 :
+ value;
+
+ bool mixed = false;
+ browserAccessibility_->GetBoolAttribute(
+ AccessibilityNodeData::ATTR_BUTTON_MIXED, &mixed);
+ if (mixed)
+ value = 2;
+ return [NSNumber numberWithInt:value];
+ } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
+ [role isEqualToString:NSAccessibilitySliderRole] ||
+ [role isEqualToString:NSAccessibilityScrollBarRole]) {
+ float floatValue;
+ if (browserAccessibility_->GetFloatAttribute(
+ AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &floatValue)) {
+ return [NSNumber numberWithFloat:floatValue];
+ }
+ } else if ([role isEqualToString:NSAccessibilityColorWellRole]) {
+ int r, g, b;
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_COLOR_VALUE_RED, &r);
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_COLOR_VALUE_GREEN, &g);
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_COLOR_VALUE_BLUE, &b);
+ // This string matches the one returned by a native Mac color well.
+ return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1",
+ r / 255., g / 255., b / 255.];
+ }
+
+ return base::SysUTF16ToNSString(browserAccessibility_->value());
+}
+
+- (NSString*)valueDescription {
+ if (!browserAccessibility_->value().empty())
+ return base::SysUTF16ToNSString(browserAccessibility_->value());
+ else
+ return nil;
+}
+
+- (NSValue*)visibleCharacterRange {
+ return [NSValue valueWithRange:
+ NSMakeRange(0, browserAccessibility_->value().length())];
+}
+
+- (NSArray*)visibleCells {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ const std::vector<int32>& uniqueCellIds =
+ browserAccessibility_->unique_cell_ids();
+ for (size_t i = 0; i < uniqueCellIds.size(); ++i) {
+ int id = uniqueCellIds[i];
+ BrowserAccessibility* cell =
+ browserAccessibility_->manager()->GetFromRendererID(id);
+ if (cell)
+ [ret addObject:cell->ToBrowserAccessibilityCocoa()];
+ }
+ return ret;
+}
+
+- (NSArray*)visibleColumns {
+ return [self columns];
+}
+
+- (NSArray*)visibleRows {
+ return [self rows];
+}
+
+- (NSNumber*)visited {
+ return [NSNumber numberWithBool:
+ GetState(browserAccessibility_, AccessibilityNodeData::STATE_TRAVERSED)];
+}
+
+- (id)window {
+ return [delegate_ window];
+}
+
+- (NSString*)methodNameForAttribute:(NSString*)attribute {
+ return [attributeToMethodNameMap objectForKey:attribute];
+}
+
+// Returns the accessibility value for the given attribute. If the value isn't
+// supported this will return nil.
+- (id)accessibilityAttributeValue:(NSString*)attribute {
+ if (!browserAccessibility_)
+ return nil;
+
+ SEL selector =
+ NSSelectorFromString([self methodNameForAttribute:attribute]);
+ if (selector)
+ return [self performSelector:selector];
+
+ // TODO(dtseng): refactor remaining attributes.
+ int selStart, selEnd;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TEXT_SEL_START, &selStart) &&
+ browserAccessibility_->
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &selEnd)) {
+ if (selStart > selEnd)
+ std::swap(selStart, selEnd);
+ int selLength = selEnd - selStart;
+ if ([attribute isEqualToString:
+ NSAccessibilityInsertionPointLineNumberAttribute]) {
+ const std::vector<int32>& line_breaks =
+ browserAccessibility_->line_breaks();
+ for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
+ if (line_breaks[i] > selStart)
+ return [NSNumber numberWithInt:i];
+ }
+ return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
+ }
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
+ return base::SysUTF16ToNSString(browserAccessibility_->value().substr(
+ selStart, selLength));
+ }
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
+ return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
+ }
+ }
+ return nil;
+}
+
+// Returns the accessibility value for the given attribute and parameter. If the
+// value isn't supported this will return nil.
+- (id)accessibilityAttributeValue:(NSString*)attribute
+ forParameter:(id)parameter {
+ if (!browserAccessibility_)
+ return nil;
+
+ const std::vector<int32>& line_breaks = browserAccessibility_->line_breaks();
+ int len = static_cast<int>(browserAccessibility_->value().size());
+
+ if ([attribute isEqualToString:
+ NSAccessibilityStringForRangeParameterizedAttribute]) {
+ NSRange range = [(NSValue*)parameter rangeValue];
+ return base::SysUTF16ToNSString(
+ browserAccessibility_->value().substr(range.location, range.length));
+ }
+
+ if ([attribute isEqualToString:
+ NSAccessibilityLineForIndexParameterizedAttribute]) {
+ int index = [(NSNumber*)parameter intValue];
+ for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) {
+ if (line_breaks[i] > index)
+ return [NSNumber numberWithInt:i];
+ }
+ return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
+ }
+
+ if ([attribute isEqualToString:
+ NSAccessibilityRangeForLineParameterizedAttribute]) {
+ int line_index = [(NSNumber*)parameter intValue];
+ int line_count = static_cast<int>(line_breaks.size()) + 1;
+ if (line_index < 0 || line_index >= line_count)
+ return nil;
+ int start = line_index > 0 ? line_breaks[line_index - 1] : 0;
+ int end = line_index < line_count - 1 ? line_breaks[line_index] : len;
+ return [NSValue valueWithRange:
+ NSMakeRange(start, end - start)];
+ }
+
+ if ([attribute isEqualToString:
+ NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
+ if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE &&
+ [self internalRole] != AccessibilityNodeData::ROLE_GRID) {
+ return nil;
+ }
+ if (![parameter isKindOfClass:[NSArray self]])
+ return nil;
+ NSArray* array = parameter;
+ int column = [[array objectAtIndex:0] intValue];
+ int row = [[array objectAtIndex:1] intValue];
+ int num_columns = 0;
+ int num_rows = 0;
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &num_columns);
+ browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &num_rows);
+ if (column < 0 || column >= num_columns ||
+ row < 0 || row >= num_rows) {
+ return nil;
+ }
+ for (size_t i = 0;
+ i < browserAccessibility_->child_count();
+ ++i) {
+ BrowserAccessibility* child = browserAccessibility_->GetChild(i);
+ if (child->role() != AccessibilityNodeData::ROLE_ROW)
+ continue;
+ int rowIndex;
+ if (!child->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) {
+ continue;
+ }
+ if (rowIndex < row)
+ continue;
+ if (rowIndex > row)
+ break;
+ for (size_t j = 0;
+ j < child->child_count();
+ ++j) {
+ BrowserAccessibility* cell = child->GetChild(j);
+ if (cell->role() != AccessibilityNodeData::ROLE_CELL)
+ continue;
+ int colIndex;
+ if (!cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX,
+ &colIndex)) {
+ continue;
+ }
+ if (colIndex == column)
+ return cell->ToBrowserAccessibilityCocoa();
+ if (colIndex > column)
+ break;
+ }
+ }
+ return nil;
+ }
+
+ // TODO(dtseng): support the following attributes.
+ if ([attribute isEqualTo:
+ NSAccessibilityRangeForPositionParameterizedAttribute] ||
+ [attribute isEqualTo:
+ NSAccessibilityRangeForIndexParameterizedAttribute] ||
+ [attribute isEqualTo:
+ NSAccessibilityBoundsForRangeParameterizedAttribute] ||
+ [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] ||
+ [attribute isEqualTo:
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
+ return nil;
+ }
+ return nil;
+}
+
+// Returns an array of parameterized attributes names that this object will
+// respond to.
+- (NSArray*)accessibilityParameterizedAttributeNames {
+ if (!browserAccessibility_)
+ return nil;
+
+ if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
+ [[self role] isEqualToString:NSAccessibilityGridRole]) {
+ return [NSArray arrayWithObjects:
+ NSAccessibilityCellForColumnAndRowParameterizedAttribute,
+ nil];
+ }
+ if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
+ [[self role] isEqualToString:NSAccessibilityTextAreaRole]) {
+ return [NSArray arrayWithObjects:
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+ nil];
+ }
+ return nil;
+}
+
+// Returns an array of action names that this object will respond to.
+- (NSArray*)accessibilityActionNames {
+ if (!browserAccessibility_)
+ return nil;
+
+ NSMutableArray* ret =
+ [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction];
+ NSString* role = [self role];
+ // TODO(dtseng): this should only get set when there's a default action.
+ if (![role isEqualToString:NSAccessibilityStaticTextRole] &&
+ ![role isEqualToString:NSAccessibilityTextAreaRole] &&
+ ![role isEqualToString:NSAccessibilityTextFieldRole]) {
+ [ret addObject:NSAccessibilityPressAction];
+ }
+
+ return ret;
+}
+
+// Returns a sub-array of values for the given attribute value, starting at
+// index, with up to maxCount items. If the given index is out of bounds,
+// or there are no values for the given attribute, it will return nil.
+// This method is used for querying subsets of values, without having to
+// return a large set of data, such as elements with a large number of
+// children.
+- (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute
+ index:(NSUInteger)index
+ maxCount:(NSUInteger)maxCount {
+ if (!browserAccessibility_)
+ return nil;
+
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ if (!fullArray)
+ return nil;
+ NSUInteger arrayCount = [fullArray count];
+ if (index >= arrayCount)
+ return nil;
+ NSRange subRange;
+ if ((index + maxCount) > arrayCount) {
+ subRange = NSMakeRange(index, arrayCount - index);
+ } else {
+ subRange = NSMakeRange(index, maxCount);
+ }
+ return [fullArray subarrayWithRange:subRange];
+}
+
+// Returns the count of the specified accessibility array attribute.
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute {
+ if (!browserAccessibility_)
+ return nil;
+
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ return [fullArray count];
+}
+
+// Returns the list of accessibility attributes that this object supports.
+- (NSArray*)accessibilityAttributeNames {
+ if (!browserAccessibility_)
+ return nil;
+
+ // General attributes.
+ NSMutableArray* ret = [NSMutableArray arrayWithObjects:
+ NSAccessibilityChildrenAttribute,
+ NSAccessibilityDescriptionAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilitySizeAttribute,
+ NSAccessibilitySubroleAttribute,
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityURLAttribute,
+ @"AXAccessKey",
+ @"AXInvalid",
+ @"AXRequired",
+ @"AXVisited",
+ nil];
+
+ // Specific role attributes.
+ NSString* role = [self role];
+ NSString* subrole = [self subrole];
+ if ([role isEqualToString:NSAccessibilityTableRole] ||
+ [role isEqualToString:NSAccessibilityGridRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityColumnsAttribute,
+ NSAccessibilityVisibleColumnsAttribute,
+ NSAccessibilityRowsAttribute,
+ NSAccessibilityVisibleRowsAttribute,
+ NSAccessibilityVisibleCellsAttribute,
+ NSAccessibilityHeaderAttribute,
+ NSAccessibilityColumnHeaderUIElementsAttribute,
+ NSAccessibilityRowHeaderUIElementsAttribute,
+ nil]];
+ } else if ([role isEqualToString:NSAccessibilityColumnRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityIndexAttribute,
+ NSAccessibilityHeaderAttribute,
+ NSAccessibilityRowsAttribute,
+ NSAccessibilityVisibleRowsAttribute,
+ nil]];
+ } else if ([role isEqualToString:NSAccessibilityCellRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityColumnIndexRangeAttribute,
+ NSAccessibilityRowIndexRangeAttribute,
+ nil]];
+ } else if ([role isEqualToString:@"AXWebArea"]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ @"AXLoaded",
+ @"AXLoadingProgress",
+ nil]];
+ } else if ([role isEqualToString:NSAccessibilityTextFieldRole] ||
+ [role isEqualToString:NSAccessibilityTextAreaRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityInsertionPointLineNumberAttribute,
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilitySelectedTextAttribute,
+ NSAccessibilitySelectedTextRangeAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ nil]];
+ } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) {
+ [ret addObject:NSAccessibilityTabsAttribute];
+ } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] ||
+ [role isEqualToString:NSAccessibilitySliderRole] ||
+ [role isEqualToString:NSAccessibilityScrollBarRole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityMaxValueAttribute,
+ NSAccessibilityMinValueAttribute,
+ NSAccessibilityOrientationAttribute,
+ NSAccessibilityValueDescriptionAttribute,
+ nil]];
+ } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityDisclosingAttribute,
+ NSAccessibilityDisclosedByRowAttribute,
+ NSAccessibilityDisclosureLevelAttribute,
+ NSAccessibilityDisclosedRowsAttribute,
+ nil]];
+ } else if ([role isEqualToString:NSAccessibilityRowRole]) {
+ if (browserAccessibility_->parent()) {
+ string16 parentRole;
+ browserAccessibility_->parent()->GetHtmlAttribute(
+ "role", &parentRole);
+ const string16 treegridRole(ASCIIToUTF16("treegrid"));
+ if (parentRole == treegridRole) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityDisclosingAttribute,
+ NSAccessibilityDisclosedByRowAttribute,
+ NSAccessibilityDisclosureLevelAttribute,
+ NSAccessibilityDisclosedRowsAttribute,
+ nil]];
+ } else {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityIndexAttribute,
+ nil]];
+ }
+ }
+ }
+
+ // Live regions.
+ string16 s;
+ if (browserAccessibility_->GetStringAttribute(
+ AccessibilityNodeData::ATTR_LIVE_STATUS, &s)) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ @"AXARIALive",
+ @"AXARIARelevant",
+ nil]];
+ }
+ if (browserAccessibility_->GetStringAttribute(
+ AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, &s)) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ @"AXARIAAtomic",
+ @"AXARIABusy",
+ nil]];
+ }
+
+ // Title UI Element.
+ int i;
+ if (browserAccessibility_->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &i)) {
+ [ret addObjectsFromArray:[NSArray arrayWithObjects:
+ NSAccessibilityTitleUIElementAttribute,
+ nil]];
+ }
+
+ return ret;
+}
+
+// Returns the index of the child in this objects array of children.
+- (NSUInteger)accessibilityGetIndexOf:(id)child {
+ if (!browserAccessibility_)
+ return nil;
+
+ NSUInteger index = 0;
+ for (BrowserAccessibilityCocoa* childToCheck in [self children]) {
+ if ([child isEqual:childToCheck])
+ return index;
+ ++index;
+ }
+ return NSNotFound;
+}
+
+// Returns whether or not the specified attribute can be set by the
+// accessibility API via |accessibilitySetValue:forAttribute:|.
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
+ if (!browserAccessibility_)
+ return nil;
+
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return GetState(browserAccessibility_,
+ AccessibilityNodeData::STATE_FOCUSABLE);
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
+ bool canSetValue = false;
+ browserAccessibility_->GetBoolAttribute(
+ AccessibilityNodeData::ATTR_CAN_SET_VALUE, &canSetValue);
+ return canSetValue;
+ }
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] &&
+ ([[self role] isEqualToString:NSAccessibilityTextFieldRole] ||
+ [[self role] isEqualToString:NSAccessibilityTextAreaRole]))
+ return YES;
+
+ return NO;
+}
+
+// Returns whether or not this object should be ignored in the accessibilty
+// tree.
+- (BOOL)accessibilityIsIgnored {
+ if (!browserAccessibility_)
+ return true;
+
+ return [self isIgnored];
+}
+
+// Performs the given accessibilty action on the webkit accessibility object
+// that backs this object.
+- (void)accessibilityPerformAction:(NSString*)action {
+ if (!browserAccessibility_)
+ return;
+
+ // TODO(feldstein): Support more actions.
+ if ([action isEqualToString:NSAccessibilityPressAction])
+ [delegate_ doDefaultAction:browserAccessibility_->renderer_id()];
+ else if ([action isEqualToString:NSAccessibilityShowMenuAction])
+ [delegate_ performShowMenuAction:self];
+}
+
+// Returns the description of the given action.
+- (NSString*)accessibilityActionDescription:(NSString*)action {
+ if (!browserAccessibility_)
+ return nil;
+
+ return NSAccessibilityActionDescription(action);
+}
+
+// Sets an override value for a specific accessibility attribute.
+// This class does not support this.
+- (BOOL)accessibilitySetOverrideValue:(id)value
+ forAttribute:(NSString*)attribute {
+ return NO;
+}
+
+// Sets the value for an accessibility attribute via the accessibility API.
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
+ if (!browserAccessibility_)
+ return;
+
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
+ NSNumber* focusedNumber = value;
+ BOOL focused = [focusedNumber intValue];
+ [delegate_ setAccessibilityFocus:focused
+ accessibilityId:browserAccessibility_->renderer_id()];
+ }
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
+ NSRange range = [(NSValue*)value rangeValue];
+ [delegate_
+ accessibilitySetTextSelection:browserAccessibility_->renderer_id()
+ startOffset:range.location
+ endOffset:range.location + range.length];
+ }
+}
+
+// Returns the deepest accessibility child that should not be ignored.
+// It is assumed that the hit test has been narrowed down to this object
+// or one of its children, so this will never return nil unless this
+// object is invalid.
+- (id)accessibilityHitTest:(NSPoint)point {
+ if (!browserAccessibility_)
+ return nil;
+
+ BrowserAccessibilityCocoa* hit = self;
+ for (BrowserAccessibilityCocoa* child in [self children]) {
+ NSPoint origin = [child origin];
+ NSSize size = [[child size] sizeValue];
+ NSRect rect;
+ rect.origin = origin;
+ rect.size = size;
+ if (NSPointInRect(point, rect)) {
+ hit = child;
+ id childResult = [child accessibilityHitTest:point];
+ if (![childResult accessibilityIsIgnored]) {
+ hit = childResult;
+ break;
+ }
+ }
+ }
+ return NSAccessibilityUnignoredAncestor(hit);
+}
+
+- (BOOL)isEqual:(id)object {
+ if (![object isKindOfClass:[BrowserAccessibilityCocoa class]])
+ return NO;
+ return ([self hash] == [object hash]);
+}
+
+- (NSUInteger)hash {
+ // Potentially called during dealloc.
+ if (!browserAccessibility_)
+ return [super hash];
+ return browserAccessibility_->renderer_id();
+}
+
+- (BOOL)accessibilityShouldUseUniqueId {
+ return YES;
+}
+
+@end
+
diff --git a/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h b/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h
new file mode 100644
index 00000000000..8b9abff3a73
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h
@@ -0,0 +1,26 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_
+
+@class BrowserAccessibilityCocoa;
+@class NSWindow;
+
+// This protocol is used by the BrowserAccessibility objects to pass messages
+// to, or otherwise communicate with, their underlying WebAccessibility
+// objects over the IPC boundary.
+@protocol BrowserAccessibilityDelegateCocoa
+- (NSPoint)accessibilityPointInScreen:(BrowserAccessibilityCocoa*)accessibility;
+- (void)doDefaultAction:(int32)accessibilityObjectId;
+- (void)accessibilitySetTextSelection:(int32)accId
+ startOffset:(int32)startOffset
+ endOffset:(int32)endOffset;
+- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility;
+- (void)setAccessibilityFocus:(BOOL)focus
+ accessibilityId:(int32)accessibilityObjectId;
+- (NSWindow*)window;
+@end
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_gtk.cc b/chromium/content/browser/accessibility/browser_accessibility_gtk.cc
new file mode 100644
index 00000000000..50364282b3d
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_gtk.cc
@@ -0,0 +1,519 @@
+// 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 "content/browser/accessibility/browser_accessibility_gtk.h"
+
+#include <gtk/gtk.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager_gtk.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+static gpointer browser_accessibility_parent_class = NULL;
+
+static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
+ BrowserAccessibilityAtk* atk_object) {
+ if (!atk_object)
+ return NULL;
+
+ return atk_object->m_object;
+}
+
+//
+// AtkComponent interface.
+//
+
+static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
+ AtkComponent* atk_object) {
+ if (!IS_BROWSER_ACCESSIBILITY(atk_object))
+ return NULL;
+
+ return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
+}
+
+static AtkObject* browser_accessibility_accessible_at_point(
+ AtkComponent* component, gint x, gint y, AtkCoordType coord_type) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
+ if (!obj)
+ return NULL;
+
+ gfx::Point point(x, y);
+ if (!obj->GetGlobalBoundsRect().Contains(point))
+ return NULL;
+
+ BrowserAccessibility* result = obj->BrowserAccessibilityForPoint(point);
+ if (!result)
+ return NULL;
+
+ AtkObject* atk_result = result->ToBrowserAccessibilityGtk()->GetAtkObject();
+ g_object_ref(atk_result);
+ return atk_result;
+}
+
+static void browser_accessibility_get_extents(
+ AtkComponent* component, gint* x, gint* y, gint* width, gint* height,
+ AtkCoordType coord_type) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
+ if (!obj)
+ return;
+
+ gfx::Rect bounds = obj->GetGlobalBoundsRect();
+ *x = bounds.x();
+ *y = bounds.y();
+ *width = bounds.width();
+ *height = bounds.height();
+}
+
+static gboolean browser_accessibility_grab_focus(AtkComponent* component) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
+ if (!obj)
+ return false;
+
+ obj->manager()->SetFocus(obj, true);
+ return true;
+}
+
+static void ComponentInterfaceInit(AtkComponentIface* iface) {
+ iface->ref_accessible_at_point = browser_accessibility_accessible_at_point;
+ iface->get_extents = browser_accessibility_get_extents;
+ iface->grab_focus = browser_accessibility_grab_focus;
+}
+
+static const GInterfaceInfo ComponentInfo = {
+ reinterpret_cast<GInterfaceInitFunc>(ComponentInterfaceInit), 0, 0
+};
+
+//
+// AtkValue interface.
+//
+
+static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
+ AtkValue* atk_object) {
+ if (!IS_BROWSER_ACCESSIBILITY(atk_object))
+ return NULL;
+
+ return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
+}
+
+static void browser_accessibility_get_current_value(
+ AtkValue* atk_object, GValue* value) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return;
+
+ float float_val;
+ if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE,
+ &float_val)) {
+ memset(value, 0, sizeof(*value));
+ g_value_init(value, G_TYPE_FLOAT);
+ g_value_set_float(value, float_val);
+ }
+}
+
+static void browser_accessibility_get_minimum_value(
+ AtkValue* atk_object, GValue* value) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return;
+
+ float float_val;
+ if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE,
+ &float_val)) {
+ memset(value, 0, sizeof(*value));
+ g_value_init(value, G_TYPE_FLOAT);
+ g_value_set_float(value, float_val);
+ }
+}
+
+static void browser_accessibility_get_maximum_value(
+ AtkValue* atk_object, GValue* value) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return;
+
+ float float_val;
+ if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
+ &float_val)) {
+ memset(value, 0, sizeof(*value));
+ g_value_init(value, G_TYPE_FLOAT);
+ g_value_set_float(value, float_val);
+ }
+}
+
+static void browser_accessibility_get_minimum_increment(
+ AtkValue* atk_object, GValue* value) {
+ // TODO(dmazzoni): get the correct value from an <input type=range>.
+ memset(value, 0, sizeof(*value));
+ g_value_init(value, G_TYPE_FLOAT);
+ g_value_set_float(value, 1.0);
+}
+
+static void ValueInterfaceInit(AtkValueIface* iface) {
+ iface->get_current_value = browser_accessibility_get_current_value;
+ iface->get_minimum_value = browser_accessibility_get_minimum_value;
+ iface->get_maximum_value = browser_accessibility_get_maximum_value;
+ iface->get_minimum_increment = browser_accessibility_get_minimum_increment;
+}
+
+static const GInterfaceInfo ValueInfo = {
+ reinterpret_cast<GInterfaceInitFunc>(ValueInterfaceInit), 0, 0
+};
+
+//
+// AtkObject interface
+//
+
+static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
+ AtkObject* atk_object) {
+ if (!IS_BROWSER_ACCESSIBILITY(atk_object))
+ return NULL;
+
+ return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
+}
+
+static const gchar* browser_accessibility_get_name(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return NULL;
+ return obj->atk_acc_name().c_str();
+}
+
+static const gchar* browser_accessibility_get_description(
+ AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return NULL;
+ return obj->atk_acc_description().c_str();
+}
+
+static AtkObject* browser_accessibility_get_parent(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return NULL;
+ if (obj->parent())
+ return obj->parent()->ToBrowserAccessibilityGtk()->GetAtkObject();
+
+ BrowserAccessibilityManagerGtk* manager =
+ static_cast<BrowserAccessibilityManagerGtk*>(obj->manager());
+ return gtk_widget_get_accessible(manager->parent_widget());
+}
+
+static gint browser_accessibility_get_n_children(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return 0;
+ return obj->children().size();
+}
+
+static AtkObject* browser_accessibility_ref_child(
+ AtkObject* atk_object, gint index) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return NULL;
+ AtkObject* result =
+ obj->children()[index]->ToBrowserAccessibilityGtk()->GetAtkObject();
+ g_object_ref(result);
+ return result;
+}
+
+static gint browser_accessibility_get_index_in_parent(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return 0;
+ return obj->index_in_parent();
+}
+
+static AtkAttributeSet* browser_accessibility_get_attributes(
+ AtkObject* atk_object) {
+ return NULL;
+}
+
+static AtkRole browser_accessibility_get_role(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return ATK_ROLE_INVALID;
+ return obj->atk_role();
+}
+
+static AtkStateSet* browser_accessibility_ref_state_set(AtkObject* atk_object) {
+ BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
+ if (!obj)
+ return NULL;
+ AtkStateSet* state_set =
+ ATK_OBJECT_CLASS(browser_accessibility_parent_class)->
+ ref_state_set(atk_object);
+ int32 state = obj->state();
+
+ if (state & (1 << AccessibilityNodeData::STATE_FOCUSABLE))
+ atk_state_set_add_state(state_set, ATK_STATE_FOCUSABLE);
+ if (obj->manager()->GetFocus(NULL) == obj)
+ atk_state_set_add_state(state_set, ATK_STATE_FOCUSED);
+ if (!(state & (1 << AccessibilityNodeData::STATE_UNAVAILABLE)))
+ atk_state_set_add_state(state_set, ATK_STATE_ENABLED);
+
+ return state_set;
+}
+
+static AtkRelationSet* browser_accessibility_ref_relation_set(
+ AtkObject* atk_object) {
+ AtkRelationSet* relation_set =
+ ATK_OBJECT_CLASS(browser_accessibility_parent_class)
+ ->ref_relation_set(atk_object);
+ return relation_set;
+}
+
+//
+// The rest of the BrowserAccessibilityGtk code, not specific to one
+// of the Atk* interfaces.
+//
+
+static void browser_accessibility_init(AtkObject* atk_object, gpointer data) {
+ if (ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize) {
+ ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize(
+ atk_object, data);
+ }
+
+ BROWSER_ACCESSIBILITY(atk_object)->m_object =
+ reinterpret_cast<BrowserAccessibilityGtk*>(data);
+}
+
+static void browser_accessibility_finalize(GObject* atk_object) {
+ G_OBJECT_CLASS(browser_accessibility_parent_class)->finalize(atk_object);
+}
+
+static void browser_accessibility_class_init(AtkObjectClass* klass) {
+ GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
+ browser_accessibility_parent_class = g_type_class_peek_parent(klass);
+
+ gobject_class->finalize = browser_accessibility_finalize;
+ klass->initialize = browser_accessibility_init;
+ klass->get_name = browser_accessibility_get_name;
+ klass->get_description = browser_accessibility_get_description;
+ klass->get_parent = browser_accessibility_get_parent;
+ klass->get_n_children = browser_accessibility_get_n_children;
+ klass->ref_child = browser_accessibility_ref_child;
+ klass->get_role = browser_accessibility_get_role;
+ klass->ref_state_set = browser_accessibility_ref_state_set;
+ klass->get_index_in_parent = browser_accessibility_get_index_in_parent;
+ klass->get_attributes = browser_accessibility_get_attributes;
+ klass->ref_relation_set = browser_accessibility_ref_relation_set;
+}
+
+GType browser_accessibility_get_type() {
+ static volatile gsize type_volatile = 0;
+
+ if (g_once_init_enter(&type_volatile)) {
+ static const GTypeInfo tinfo = {
+ sizeof(BrowserAccessibilityAtkClass),
+ (GBaseInitFunc) 0,
+ (GBaseFinalizeFunc) 0,
+ (GClassInitFunc) browser_accessibility_class_init,
+ (GClassFinalizeFunc) 0,
+ 0, /* class data */
+ sizeof(BrowserAccessibilityAtk), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc) 0,
+ 0 /* value table */
+ };
+
+ GType type = g_type_register_static(
+ ATK_TYPE_OBJECT, "BrowserAccessibility", &tinfo, GTypeFlags(0));
+ g_once_init_leave(&type_volatile, type);
+ }
+
+ return type_volatile;
+}
+
+static const char* GetUniqueAccessibilityTypeName(int interface_mask)
+{
+ // 20 characters is enough for "Chrome%x" with any integer value.
+ static char name[20];
+ snprintf(name, sizeof(name), "Chrome%x", interface_mask);
+ return name;
+}
+
+enum AtkInterfaces {
+ ATK_ACTION_INTERFACE,
+ ATK_COMPONENT_INTERFACE,
+ ATK_DOCUMENT_INTERFACE,
+ ATK_EDITABLE_TEXT_INTERFACE,
+ ATK_HYPERLINK_INTERFACE,
+ ATK_HYPERTEXT_INTERFACE,
+ ATK_IMAGE_INTERFACE,
+ ATK_SELECTION_INTERFACE,
+ ATK_TABLE_INTERFACE,
+ ATK_TEXT_INTERFACE,
+ ATK_VALUE_INTERFACE,
+};
+
+static int GetInterfaceMaskFromObject(BrowserAccessibilityGtk* obj) {
+ int interface_mask = 0;
+
+ // Component interface is always supported.
+ interface_mask |= 1 << ATK_COMPONENT_INTERFACE;
+
+ int role = obj->role();
+ if (role == AccessibilityNodeData::ROLE_PROGRESS_INDICATOR ||
+ role == AccessibilityNodeData::ROLE_SCROLLBAR ||
+ role == AccessibilityNodeData::ROLE_SLIDER) {
+ interface_mask |= 1 << ATK_VALUE_INTERFACE;
+ }
+
+ return interface_mask;
+}
+
+static GType GetAccessibilityTypeFromObject(BrowserAccessibilityGtk* obj) {
+ static const GTypeInfo type_info = {
+ sizeof(BrowserAccessibilityAtkClass),
+ (GBaseInitFunc) 0,
+ (GBaseFinalizeFunc) 0,
+ (GClassInitFunc) 0,
+ (GClassFinalizeFunc) 0,
+ 0, /* class data */
+ sizeof(BrowserAccessibilityAtk), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc) 0,
+ 0 /* value table */
+ };
+
+ int interface_mask = GetInterfaceMaskFromObject(obj);
+ const char* atk_type_name = GetUniqueAccessibilityTypeName(interface_mask);
+ GType type = g_type_from_name(atk_type_name);
+ if (type)
+ return type;
+
+ type = g_type_register_static(BROWSER_ACCESSIBILITY_TYPE,
+ atk_type_name,
+ &type_info,
+ GTypeFlags(0));
+ if (interface_mask & (1 << ATK_COMPONENT_INTERFACE))
+ g_type_add_interface_static(type, ATK_TYPE_COMPONENT, &ComponentInfo);
+ if (interface_mask & (1 << ATK_VALUE_INTERFACE))
+ g_type_add_interface_static(type, ATK_TYPE_VALUE, &ValueInfo);
+
+ return type;
+}
+
+BrowserAccessibilityAtk* browser_accessibility_new(
+ BrowserAccessibilityGtk* obj) {
+ GType type = GetAccessibilityTypeFromObject(obj);
+ AtkObject* atk_object = static_cast<AtkObject*>(g_object_new(type, 0));
+
+ atk_object_initialize(atk_object, obj);
+
+ return BROWSER_ACCESSIBILITY(atk_object);
+}
+
+void browser_accessibility_detach(BrowserAccessibilityAtk* atk_object) {
+ atk_object->m_object = NULL;
+}
+
+// static
+BrowserAccessibility* BrowserAccessibility::Create() {
+ return new BrowserAccessibilityGtk();
+}
+
+BrowserAccessibilityGtk* BrowserAccessibility::ToBrowserAccessibilityGtk() {
+ return static_cast<BrowserAccessibilityGtk*>(this);
+}
+
+BrowserAccessibilityGtk::BrowserAccessibilityGtk()
+ : atk_object_(NULL) {
+}
+
+BrowserAccessibilityGtk::~BrowserAccessibilityGtk() {
+ browser_accessibility_detach(BROWSER_ACCESSIBILITY(atk_object_));
+ if (atk_object_)
+ g_object_unref(atk_object_);
+}
+
+AtkObject* BrowserAccessibilityGtk::GetAtkObject() const {
+ if (!G_IS_OBJECT(atk_object_))
+ return NULL;
+ return atk_object_;
+}
+
+void BrowserAccessibilityGtk::PreInitialize() {
+ BrowserAccessibility::PreInitialize();
+ InitRoleAndState();
+
+ if (atk_object_) {
+ // If the object's role changes and that causes its
+ // interface mask to change, we need to create a new
+ // AtkObject for it.
+ int interface_mask = GetInterfaceMaskFromObject(this);
+ if (interface_mask != interface_mask_) {
+ g_object_unref(atk_object_);
+ atk_object_ = NULL;
+ }
+ }
+
+ if (!atk_object_) {
+ interface_mask_ = GetInterfaceMaskFromObject(this);
+ atk_object_ = ATK_OBJECT(browser_accessibility_new(this));
+ if (this->parent()) {
+ atk_object_set_parent(
+ atk_object_,
+ this->parent()->ToBrowserAccessibilityGtk()->GetAtkObject());
+ }
+ }
+}
+
+bool BrowserAccessibilityGtk::IsNative() const {
+ return true;
+}
+
+void BrowserAccessibilityGtk::InitRoleAndState() {
+ atk_acc_name_ = UTF16ToUTF8(name());
+
+ string16 description;
+ GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description);
+ atk_acc_description_ = UTF16ToUTF8(description);
+
+ switch(role_) {
+ case AccessibilityNodeData::ROLE_DOCUMENT:
+ case AccessibilityNodeData::ROLE_ROOT_WEB_AREA:
+ case AccessibilityNodeData::ROLE_WEB_AREA:
+ atk_role_ = ATK_ROLE_DOCUMENT_WEB;
+ break;
+ case AccessibilityNodeData::ROLE_GROUP:
+ case AccessibilityNodeData::ROLE_DIV:
+ atk_role_ = ATK_ROLE_SECTION;
+ break;
+ case AccessibilityNodeData::ROLE_BUTTON:
+ atk_role_ = ATK_ROLE_PUSH_BUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_CHECKBOX:
+ atk_role_ = ATK_ROLE_CHECK_BOX;
+ break;
+ case AccessibilityNodeData::ROLE_COMBO_BOX:
+ atk_role_ = ATK_ROLE_COMBO_BOX;
+ break;
+ case AccessibilityNodeData::ROLE_LINK:
+ atk_role_ = ATK_ROLE_LINK;
+ break;
+ case AccessibilityNodeData::ROLE_RADIO_BUTTON:
+ atk_role_ = ATK_ROLE_RADIO_BUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_STATIC_TEXT:
+ atk_role_ = ATK_ROLE_TEXT;
+ break;
+ case AccessibilityNodeData::ROLE_TEXTAREA:
+ atk_role_ = ATK_ROLE_ENTRY;
+ break;
+ case AccessibilityNodeData::ROLE_TEXT_FIELD:
+ atk_role_ = ATK_ROLE_ENTRY;
+ break;
+ case AccessibilityNodeData::ROLE_WEBCORE_LINK:
+ atk_role_ = ATK_ROLE_LINK;
+ break;
+ default:
+ atk_role_ = ATK_ROLE_UNKNOWN;
+ break;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_gtk.h b/chromium/content/browser/accessibility/browser_accessibility_gtk.h
new file mode 100644
index 00000000000..8f3a2e31e5b
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_gtk.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_GTK_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_GTK_H_
+
+#include <atk/atk.h>
+
+#include "base/compiler_specific.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+
+namespace content {
+
+class BrowserAccessibilityGtk;
+class BrowserAccessibilityManagerGtk;
+
+G_BEGIN_DECLS
+
+#define BROWSER_ACCESSIBILITY_TYPE (browser_accessibility_get_type())
+#define BROWSER_ACCESSIBILITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST( \
+ (obj), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtk))
+#define BROWSER_ACCESSIBILITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST( \
+ (klass), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtkClass))
+#define IS_BROWSER_ACCESSIBILITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), BROWSER_ACCESSIBILITY_TYPE))
+#define IS_BROWSER_ACCESSIBILITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), BROWSER_ACCESSIBILITY_TYPE))
+#define BROWSER_ACCESSIBILITY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS( \
+ (obj), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtkClass))
+
+typedef struct _BrowserAccessibilityAtk BrowserAccessibilityAtk;
+typedef struct _BrowserAccessibilityAtkClass BrowserAccessibilityAtkClass;
+
+struct _BrowserAccessibilityAtk {
+ AtkObject parent;
+ BrowserAccessibilityGtk* m_object;
+};
+
+struct _BrowserAccessibilityAtkClass {
+ AtkObjectClass parent_class;
+};
+
+GType browser_accessibility_get_type (void) G_GNUC_CONST;
+
+BrowserAccessibilityAtk* browser_accessibility_new(
+ BrowserAccessibilityGtk* object);
+
+BrowserAccessibilityGtk* browser_accessibility_get_object(
+ BrowserAccessibilityAtk* atk_object);
+
+void browser_accessibility_detach (BrowserAccessibilityAtk* atk_object);
+
+AtkObject* browser_accessibility_get_focused_element(
+ BrowserAccessibilityAtk* atk_object);
+
+G_END_DECLS
+
+class BrowserAccessibilityGtk : public BrowserAccessibility {
+ public:
+ BrowserAccessibilityGtk();
+
+ virtual ~BrowserAccessibilityGtk();
+
+ AtkObject* GetAtkObject() const;
+
+ AtkRole atk_role() { return atk_role_; }
+ const std::string& atk_acc_name() { return atk_acc_name_; }
+ const std::string& atk_acc_description() { return atk_acc_description_; }
+
+ // BrowserAccessibility methods.
+ virtual void PreInitialize() OVERRIDE;
+ virtual bool IsNative() const OVERRIDE;
+
+ private:
+ virtual void InitRoleAndState();
+
+ // Give BrowserAccessibility::Create access to our constructor.
+ friend class BrowserAccessibility;
+
+ AtkObject* atk_object_;
+ AtkRole atk_role_;
+ std::string atk_acc_name_;
+ std::string atk_acc_description_;
+ int interface_mask_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_GTK_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac.h b/chromium/content/browser/accessibility/browser_accessibility_mac.h
new file mode 100644
index 00000000000..63a9d007d3e
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_mac.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "content/browser/accessibility/browser_accessibility.h"
+
+@class BrowserAccessibilityCocoa;
+
+namespace content {
+
+class BrowserAccessibilityMac : public BrowserAccessibility {
+ public:
+ // Implementation of BrowserAccessibility.
+ virtual void PreInitialize() OVERRIDE;
+ virtual void NativeReleaseReference() OVERRIDE;
+ virtual bool IsNative() const OVERRIDE;
+
+ // Overrides from BrowserAccessibility.
+ virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes) OVERRIDE;
+ virtual void SwapChildren(std::vector<BrowserAccessibility*>& children)
+ OVERRIDE;
+
+ // The BrowserAccessibilityCocoa associated with us.
+ BrowserAccessibilityCocoa* native_view() const {
+ return browser_accessibility_cocoa_;
+ }
+
+ private:
+ // This gives BrowserAccessibility::Create access to the class constructor.
+ friend class BrowserAccessibility;
+
+ BrowserAccessibilityMac();
+
+ // Allows access to the BrowserAccessibilityCocoa which wraps this.
+ // BrowserAccessibility.
+ // We own this object until our manager calls ReleaseReference;
+ // thereafter, the cocoa object owns us.
+ BrowserAccessibilityCocoa* browser_accessibility_cocoa_;
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_mac.mm
new file mode 100644
index 00000000000..11595a10e28
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_mac.mm
@@ -0,0 +1,71 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#import "content/browser/accessibility/browser_accessibility_mac.h"
+
+#import "content/browser/accessibility/browser_accessibility_cocoa.h"
+#import "content/browser/accessibility/browser_accessibility_delegate_mac.h"
+#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
+
+namespace content {
+
+// Static.
+BrowserAccessibility* BrowserAccessibility::Create() {
+ return new BrowserAccessibilityMac();
+}
+
+BrowserAccessibilityMac::BrowserAccessibilityMac()
+ : browser_accessibility_cocoa_(NULL) {
+}
+
+void BrowserAccessibilityMac::PreInitialize() {
+ BrowserAccessibility::PreInitialize();
+
+ if (browser_accessibility_cocoa_)
+ return;
+
+ // We take ownership of the cocoa obj here.
+ BrowserAccessibilityManagerMac* manager =
+ static_cast<BrowserAccessibilityManagerMac*>(manager_);
+ browser_accessibility_cocoa_ = [[BrowserAccessibilityCocoa alloc]
+ initWithObject:this
+ delegate:
+ (id<BrowserAccessibilityDelegateCocoa>)manager->parent_view()];
+}
+
+void BrowserAccessibilityMac::NativeReleaseReference() {
+ // Detach this object from |browser_accessibility_cocoa_| so it
+ // no longer has a pointer to this object.
+ [browser_accessibility_cocoa_ detach];
+ // Now, release it - but at this point, other processes may have a
+ // reference to the cocoa object.
+ [browser_accessibility_cocoa_ release];
+ // Finally, it's safe to delete this since we've detached.
+ delete this;
+}
+
+bool BrowserAccessibilityMac::IsNative() const {
+ return true;
+}
+
+void BrowserAccessibilityMac::DetachTree(
+ std::vector<BrowserAccessibility*>* nodes) {
+ [browser_accessibility_cocoa_ childrenChanged];
+ BrowserAccessibility::DetachTree(nodes);
+}
+
+void BrowserAccessibilityMac::SwapChildren(
+ std::vector<BrowserAccessibility*>& children) {
+ [browser_accessibility_cocoa_ childrenChanged];
+ BrowserAccessibility::SwapChildren(children);
+}
+
+BrowserAccessibilityCocoa* BrowserAccessibility::ToBrowserAccessibilityCocoa() {
+ return static_cast<BrowserAccessibilityMac*>(this)->
+ native_view();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm
new file mode 100644
index 00000000000..45137ec677a
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm
@@ -0,0 +1,165 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_cocoa.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+@interface MockAccessibilityDelegate :
+ NSView<BrowserAccessibilityDelegateCocoa>
+
+- (NSPoint)accessibilityPointInScreen:(BrowserAccessibilityCocoa*)accessibility;
+- (void)doDefaultAction:(int32)accessibilityObjectId;
+- (void)accessibilitySetTextSelection:(int32)accId
+ startOffset:(int32)startOffset
+ endOffset:(int32)endOffset;
+- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility;
+- (void)setAccessibilityFocus:(BOOL)focus
+ accessibilityId:(int32)accessibilityObjectId;
+- (NSWindow*)window;
+
+@end
+
+@implementation MockAccessibilityDelegate
+
+- (NSPoint)accessibilityPointInScreen:
+ (BrowserAccessibilityCocoa*)accessibility {
+ return NSZeroPoint;
+}
+- (void)doDefaultAction:(int32)accessibilityObjectId {
+}
+- (void)accessibilitySetTextSelection:(int32)accId
+ startOffset:(int32)startOffset
+ endOffset:(int32)endOffset {
+}
+- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
+}
+- (void)setAccessibilityFocus:(BOOL)focus
+ accessibilityId:(int32)accessibilityObjectId {
+}
+- (NSWindow*)window {
+ return nil;
+}
+
+@end
+
+namespace content {
+
+class BrowserAccessibilityTest : public ui::CocoaTest {
+ public:
+ virtual void SetUp() {
+ CocoaTest::SetUp();
+ RebuildAccessibilityTree();
+ }
+
+ protected:
+ void RebuildAccessibilityTree() {
+ AccessibilityNodeData root;
+ root.id = 1000;
+ root.location.set_width(500);
+ root.location.set_height(100);
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.string_attributes[AccessibilityNodeData::ATTR_HELP] =
+ ASCIIToUTF16("HelpText");
+ root.child_ids.push_back(1001);
+ root.child_ids.push_back(1002);
+
+ AccessibilityNodeData child1;
+ child1.id = 1001;
+ child1.name = ASCIIToUTF16("Child1");
+ child1.location.set_width(250);
+ child1.location.set_height(100);
+ child1.role = AccessibilityNodeData::ROLE_BUTTON;
+
+ AccessibilityNodeData child2;
+ child2.id = 1002;
+ child2.location.set_x(250);
+ child2.location.set_width(250);
+ child2.location.set_height(100);
+ child2.role = AccessibilityNodeData::ROLE_HEADING;
+
+ delegate_.reset([[MockAccessibilityDelegate alloc] init]);
+ manager_.reset(
+ new BrowserAccessibilityManagerMac(delegate_, root, NULL));
+ manager_->UpdateNodesForTesting(child1, child2);
+ accessibility_.reset([manager_->GetRoot()->ToBrowserAccessibilityCocoa()
+ retain]);
+ }
+
+ base::scoped_nsobject<MockAccessibilityDelegate> delegate_;
+ base::scoped_nsobject<BrowserAccessibilityCocoa> accessibility_;
+ scoped_ptr<BrowserAccessibilityManager> manager_;
+};
+
+// Standard hit test.
+TEST_F(BrowserAccessibilityTest, HitTestTest) {
+ BrowserAccessibilityCocoa* firstChild =
+ [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)];
+ EXPECT_NSEQ(@"Child1",
+ [firstChild accessibilityAttributeValue:NSAccessibilityTitleAttribute]);
+}
+
+// Test doing a hit test on the edge of a child.
+TEST_F(BrowserAccessibilityTest, EdgeHitTest) {
+ BrowserAccessibilityCocoa* firstChild =
+ [accessibility_ accessibilityHitTest:NSZeroPoint];
+ EXPECT_NSEQ(@"Child1",
+ [firstChild accessibilityAttributeValue:NSAccessibilityTitleAttribute]);
+}
+
+// This will test a hit test with invalid coordinates. It is assumed that
+// the hit test has been narrowed down to this object or one of its children
+// so it should return itself since it has no better hit result.
+TEST_F(BrowserAccessibilityTest, InvalidHitTestCoordsTest) {
+ BrowserAccessibilityCocoa* hitTestResult =
+ [accessibility_ accessibilityHitTest:NSMakePoint(-50, 50)];
+ EXPECT_NSEQ(accessibility_, hitTestResult);
+}
+
+// Test to ensure querying standard attributes works.
+// http://crbug.com/173983 Test fails on Mac ASan bot
+TEST_F(BrowserAccessibilityTest, DISABLED_BasicAttributeTest) {
+ NSString* helpText = [accessibility_
+ accessibilityAttributeValue:NSAccessibilityHelpAttribute];
+ EXPECT_NSEQ(@"HelpText", helpText);
+}
+
+// Test querying for an invalid attribute to ensure it doesn't crash.
+TEST_F(BrowserAccessibilityTest, InvalidAttributeTest) {
+ NSString* shouldBeNil = [accessibility_
+ accessibilityAttributeValue:@"NSAnInvalidAttribute"];
+ EXPECT_TRUE(shouldBeNil == nil);
+}
+
+TEST_F(BrowserAccessibilityTest, RetainedDetachedObjectsReturnNil) {
+ // Get the first child.
+ BrowserAccessibilityCocoa* retainedFirstChild =
+ [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)];
+ EXPECT_NSEQ(@"Child1", [retainedFirstChild
+ accessibilityAttributeValue:NSAccessibilityTitleAttribute]);
+
+ // Retain it. This simulates what the system might do with an
+ // accessibility object.
+ [retainedFirstChild retain];
+
+ // Rebuild the accessibility tree, which should detach |retainedFirstChild|.
+ RebuildAccessibilityTree();
+
+ // Now any attributes we query should return nil.
+ EXPECT_EQ(nil, [retainedFirstChild
+ accessibilityAttributeValue:NSAccessibilityTitleAttribute]);
+
+ // Don't leak memory in the test.
+ [retainedFirstChild release];
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.cc b/chromium/content/browser/accessibility/browser_accessibility_manager.cc
new file mode 100644
index 00000000000..7def57459f9
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager.cc
@@ -0,0 +1,419 @@
+// 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 "content/browser/accessibility/browser_accessibility_manager.h"
+
+#include "base/logging.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+BrowserAccessibility* BrowserAccessibilityFactory::Create() {
+ return BrowserAccessibility::Create();
+}
+
+#if !defined(OS_MACOSX) && \
+ !defined(OS_WIN) && \
+ !defined(TOOLKIT_GTK) && \
+ !defined(OS_ANDROID) \
+// We have subclassess of BrowserAccessibilityManager on Mac, Linux/GTK,
+// and Win. For any other platform, instantiate the base class.
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManager(src, delegate, factory);
+}
+#endif
+
+BrowserAccessibilityManager::BrowserAccessibilityManager(
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : delegate_(delegate),
+ factory_(factory),
+ root_(NULL),
+ focus_(NULL),
+ osk_state_(OSK_ALLOWED) {
+}
+
+BrowserAccessibilityManager::BrowserAccessibilityManager(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : delegate_(delegate),
+ factory_(factory),
+ root_(NULL),
+ focus_(NULL),
+ osk_state_(OSK_ALLOWED) {
+ Initialize(src);
+}
+
+BrowserAccessibilityManager::~BrowserAccessibilityManager() {
+ if (root_)
+ root_->Destroy();
+}
+
+void BrowserAccessibilityManager::Initialize(const AccessibilityNodeData src) {
+ std::vector<AccessibilityNodeData> nodes;
+ nodes.push_back(src);
+ if (!UpdateNodes(nodes))
+ return;
+ if (!focus_)
+ SetFocus(root_, false);
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManager::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ return empty_document;
+}
+
+BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
+ return root_;
+}
+
+BrowserAccessibility* BrowserAccessibilityManager::GetFromRendererID(
+ int32 renderer_id) {
+ base::hash_map<int32, BrowserAccessibility*>::iterator iter =
+ renderer_id_map_.find(renderer_id);
+ if (iter != renderer_id_map_.end())
+ return iter->second;
+ return NULL;
+}
+
+void BrowserAccessibilityManager::GotFocus(bool touch_event_context) {
+ if (!touch_event_context)
+ osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED;
+
+ if (!focus_)
+ return;
+
+ NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_);
+}
+
+void BrowserAccessibilityManager::WasHidden() {
+ osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_HIDDEN;
+}
+
+void BrowserAccessibilityManager::GotMouseDown() {
+ osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT;
+ NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_);
+}
+
+bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) {
+ if (!delegate_ || !delegate_->HasFocus())
+ return false;
+
+ gfx::Point touch_point = delegate_->GetLastTouchEventLocation();
+ return bounds.Contains(touch_point);
+}
+
+bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() {
+ return true;
+}
+
+void BrowserAccessibilityManager::RemoveNode(BrowserAccessibility* node) {
+ if (node == focus_)
+ SetFocus(root_, false);
+ int renderer_id = node->renderer_id();
+ renderer_id_map_.erase(renderer_id);
+}
+
+void BrowserAccessibilityManager::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ for (uint32 index = 0; index < params.size(); index++) {
+ const AccessibilityHostMsg_NotificationParams& param = params[index];
+
+ // Update nodes that changed.
+ if (!UpdateNodes(param.nodes))
+ return;
+
+ // Find the node corresponding to the id that's the target of the
+ // notification (which may not be the root of the update tree).
+ BrowserAccessibility* node = GetFromRendererID(param.id);
+ if (!node)
+ continue;
+
+ int notification_type = param.notification_type;
+ if (notification_type == AccessibilityNotificationFocusChanged ||
+ notification_type == AccessibilityNotificationBlur) {
+ SetFocus(node, false);
+
+ if (osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_HIDDEN &&
+ osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED)
+ osk_state_ = OSK_ALLOWED;
+
+ // Don't send a native focus event if the window itself doesn't
+ // have focus.
+ if (delegate_ && !delegate_->HasFocus())
+ continue;
+ }
+
+ // Send the notification event to the operating system.
+ NotifyAccessibilityEvent(notification_type, node);
+
+ // Set initial focus when a page is loaded.
+ if (notification_type == AccessibilityNotificationLoadComplete) {
+ if (!focus_)
+ SetFocus(root_, false);
+ if (!delegate_ || delegate_->HasFocus())
+ NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_);
+ }
+ }
+}
+
+BrowserAccessibility* BrowserAccessibilityManager::GetFocus(
+ BrowserAccessibility* root) {
+ if (focus_ && (!root || focus_->IsDescendantOf(root)))
+ return focus_;
+
+ return NULL;
+}
+
+void BrowserAccessibilityManager::SetFocus(
+ BrowserAccessibility* node, bool notify) {
+ if (focus_ != node)
+ focus_ = node;
+
+ if (notify && node && delegate_)
+ delegate_->SetAccessibilityFocus(node->renderer_id());
+}
+
+void BrowserAccessibilityManager::SetRoot(BrowserAccessibility* node) {
+ root_ = node;
+ NotifyRootChanged();
+}
+
+void BrowserAccessibilityManager::DoDefaultAction(
+ const BrowserAccessibility& node) {
+ if (delegate_)
+ delegate_->AccessibilityDoDefaultAction(node.renderer_id());
+}
+
+void BrowserAccessibilityManager::ScrollToMakeVisible(
+ const BrowserAccessibility& node, gfx::Rect subfocus) {
+ if (delegate_) {
+ delegate_->AccessibilityScrollToMakeVisible(node.renderer_id(), subfocus);
+ }
+}
+
+void BrowserAccessibilityManager::ScrollToPoint(
+ const BrowserAccessibility& node, gfx::Point point) {
+ if (delegate_) {
+ delegate_->AccessibilityScrollToPoint(node.renderer_id(), point);
+ }
+}
+
+void BrowserAccessibilityManager::SetTextSelection(
+ const BrowserAccessibility& node, int start_offset, int end_offset) {
+ if (delegate_) {
+ delegate_->AccessibilitySetTextSelection(
+ node.renderer_id(), start_offset, end_offset);
+ }
+}
+
+gfx::Rect BrowserAccessibilityManager::GetViewBounds() {
+ if (delegate_)
+ return delegate_->GetViewBounds();
+ return gfx::Rect();
+}
+
+void BrowserAccessibilityManager::UpdateNodesForTesting(
+ const AccessibilityNodeData& node1,
+ const AccessibilityNodeData& node2 /* = AccessibilityNodeData() */,
+ const AccessibilityNodeData& node3 /* = AccessibilityNodeData() */,
+ const AccessibilityNodeData& node4 /* = AccessibilityNodeData() */,
+ const AccessibilityNodeData& node5 /* = AccessibilityNodeData() */,
+ const AccessibilityNodeData& node6 /* = AccessibilityNodeData() */,
+ const AccessibilityNodeData& node7 /* = AccessibilityNodeData() */) {
+ std::vector<AccessibilityNodeData> nodes;
+ nodes.push_back(node1);
+ if (node2.id != AccessibilityNodeData().id)
+ nodes.push_back(node2);
+ if (node3.id != AccessibilityNodeData().id)
+ nodes.push_back(node3);
+ if (node4.id != AccessibilityNodeData().id)
+ nodes.push_back(node4);
+ if (node5.id != AccessibilityNodeData().id)
+ nodes.push_back(node5);
+ if (node6.id != AccessibilityNodeData().id)
+ nodes.push_back(node6);
+ if (node7.id != AccessibilityNodeData().id)
+ nodes.push_back(node7);
+ UpdateNodes(nodes);
+}
+
+bool BrowserAccessibilityManager::UpdateNodes(
+ const std::vector<AccessibilityNodeData>& nodes) {
+ bool success = true;
+
+ // First, update all of the nodes in the tree.
+ for (size_t i = 0; i < nodes.size() && success; i++) {
+ if (!UpdateNode(nodes[i]))
+ success = false;
+ }
+
+ // In a second pass, call PostInitialize on each one - this must
+ // be called after all of each node's children are initialized too.
+ for (size_t i = 0; i < nodes.size() && success; i++) {
+ // Note: it's not a bug for nodes[i].id to not be found in the tree.
+ // Consider this example:
+ // Before:
+ // A
+ // B
+ // C
+ // D
+ // E
+ // F
+ // After:
+ // A
+ // B
+ // C
+ // F
+ // D
+ // In this example, F is being reparented. The renderer scans the tree
+ // in order. If can't update "C" to add "F" as a child, when "F" is still
+ // a child of "E". So it first updates "E", to remove "F" as a child.
+ // Later, it ends up deleting "E". So when we get here, "E" was updated as
+ // part of this sequence but it no longer exists in the final tree, so
+ // there's nothing to postinitialize.
+ BrowserAccessibility* instance = GetFromRendererID(nodes[i].id);
+ if (instance)
+ instance->PostInitialize();
+ }
+
+ if (!success) {
+ // A bad accessibility tree could lead to memory corruption.
+ // Ask the delegate to crash the renderer, or if not available,
+ // crash the browser.
+ if (delegate_)
+ delegate_->FatalAccessibilityTreeError();
+ else
+ CHECK(false);
+ }
+
+ return success;
+}
+
+BrowserAccessibility* BrowserAccessibilityManager::CreateNode(
+ BrowserAccessibility* parent,
+ int32 renderer_id,
+ int32 index_in_parent) {
+ BrowserAccessibility* node = factory_->Create();
+ node->InitializeTreeStructure(
+ this, parent, renderer_id, index_in_parent);
+ AddNodeToMap(node);
+ return node;
+}
+
+void BrowserAccessibilityManager::AddNodeToMap(BrowserAccessibility* node) {
+ renderer_id_map_[node->renderer_id()] = node;
+}
+
+bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) {
+ // This method updates one node in the tree based on serialized data
+ // received from the renderer.
+
+ // Create a set of child ids in |src| for fast lookup. If a duplicate id is
+ // found, exit now with a fatal error before changing anything else.
+ std::set<int32> new_child_ids;
+ for (size_t i = 0; i < src.child_ids.size(); ++i) {
+ if (new_child_ids.find(src.child_ids[i]) != new_child_ids.end())
+ return false;
+ new_child_ids.insert(src.child_ids[i]);
+ }
+
+ // Look up the node by id. If it's not found, then either the root
+ // of the tree is being swapped, or we're out of sync with the renderer
+ // and this is a serious error.
+ BrowserAccessibility* instance = GetFromRendererID(src.id);
+ if (!instance) {
+ if (src.role != AccessibilityNodeData::ROLE_ROOT_WEB_AREA)
+ return false;
+ instance = CreateNode(NULL, src.id, 0);
+ }
+
+ if (src.bool_attributes.find(
+ AccessibilityNodeData::ATTR_UPDATE_LOCATION_ONLY) !=
+ src.bool_attributes.end()) {
+ instance->SetLocation(src.location);
+ return true;
+ }
+
+ // Update all of the node-specific data, like its role, state, name, etc.
+ instance->InitializeData(src);
+
+ //
+ // Update the children in three steps:
+ //
+ // 1. Iterate over the old children and delete nodes that are no longer
+ // in the tree.
+ // 2. Build up a vector of new children, reusing children that haven't
+ // changed (but may have been reordered) and adding new empty
+ // objects for new children.
+ // 3. Swap in the new children vector for the old one.
+
+ // Delete any previous children of this instance that are no longer
+ // children first. We make a deletion-only pass first to prevent a
+ // node that's being reparented from being the child of both its old
+ // parent and new parent, which could lead to a double-free.
+ // If a node is reparented, the renderer will always send us a fresh
+ // copy of the node.
+ const std::vector<BrowserAccessibility*>& old_children = instance->children();
+ for (size_t i = 0; i < old_children.size(); ++i) {
+ int old_id = old_children[i]->renderer_id();
+ if (new_child_ids.find(old_id) == new_child_ids.end())
+ old_children[i]->Destroy();
+ }
+
+ // Now build a vector of new children, reusing objects that were already
+ // children of this node before.
+ std::vector<BrowserAccessibility*> new_children;
+ bool success = true;
+ for (size_t i = 0; i < src.child_ids.size(); i++) {
+ int32 child_renderer_id = src.child_ids[i];
+ int32 index_in_parent = static_cast<int32>(i);
+ BrowserAccessibility* child = GetFromRendererID(child_renderer_id);
+ if (child) {
+ if (child->parent() != instance) {
+ // This is a serious error - nodes should never be reparented.
+ // If this case occurs, continue so this node isn't left in an
+ // inconsistent state, but return failure at the end.
+ success = false;
+ continue;
+ }
+ child->UpdateParent(instance, index_in_parent);
+ } else {
+ child = CreateNode(instance, child_renderer_id, index_in_parent);
+ }
+ new_children.push_back(child);
+ }
+
+ // Finally, swap in the new children vector for the old.
+ instance->SwapChildren(new_children);
+
+ // Handle the case where this node is the new root of the tree.
+ if (src.role == AccessibilityNodeData::ROLE_ROOT_WEB_AREA &&
+ (!root_ || root_->renderer_id() != src.id)) {
+ if (root_)
+ root_->Destroy();
+ if (focus_ == root_)
+ SetFocus(instance, false);
+ SetRoot(instance);
+ }
+
+ // Keep track of what node is focused.
+ if (src.role != AccessibilityNodeData::ROLE_ROOT_WEB_AREA &&
+ src.role != AccessibilityNodeData::ROLE_WEB_AREA &&
+ (src.state >> AccessibilityNodeData::STATE_FOCUSED & 1)) {
+ SetFocus(instance, false);
+ }
+ return success;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.h b/chromium/content/browser/accessibility/browser_accessibility_manager.h
new file mode 100644
index 00000000000..5fac3e5e3f3
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager.h
@@ -0,0 +1,236 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "content/common/accessibility_node_data.h"
+#include "content/common/content_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+struct AccessibilityHostMsg_NotificationParams;
+
+namespace content {
+class BrowserAccessibility;
+#if defined(OS_WIN)
+class BrowserAccessibilityManagerWin;
+#endif
+
+// Class that can perform actions on behalf of the BrowserAccessibilityManager.
+class CONTENT_EXPORT BrowserAccessibilityDelegate {
+ public:
+ virtual ~BrowserAccessibilityDelegate() {}
+ virtual void SetAccessibilityFocus(int acc_obj_id) = 0;
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) = 0;
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) = 0;
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) = 0;
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) = 0;
+ virtual bool HasFocus() const = 0;
+ virtual gfx::Rect GetViewBounds() const = 0;
+ virtual gfx::Point GetLastTouchEventLocation() const = 0;
+ virtual void FatalAccessibilityTreeError() = 0;
+};
+
+class CONTENT_EXPORT BrowserAccessibilityFactory {
+ public:
+ virtual ~BrowserAccessibilityFactory() {}
+
+ // Create an instance of BrowserAccessibility and return a new
+ // reference to it.
+ virtual BrowserAccessibility* Create();
+};
+
+// Manages a tree of BrowserAccessibility objects.
+class CONTENT_EXPORT BrowserAccessibilityManager {
+ public:
+ // Creates the platform-specific BrowserAccessibilityManager, but
+ // with no parent window pointer. Only useful for unit tests.
+ static BrowserAccessibilityManager* Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ virtual ~BrowserAccessibilityManager();
+
+ void Initialize(const AccessibilityNodeData src);
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // Type is enum AccessibilityNotification.
+ // We pass it as int so that we don't include the message declaration
+ // header here.
+ virtual void NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) { }
+
+ // Return a pointer to the root of the tree, does not make a new reference.
+ BrowserAccessibility* GetRoot();
+
+ // Removes a node from the manager.
+ virtual void RemoveNode(BrowserAccessibility* node);
+
+ // Return a pointer to the object corresponding to the given renderer_id,
+ // does not make a new reference.
+ BrowserAccessibility* GetFromRendererID(int32 renderer_id);
+
+ // Called to notify the accessibility manager that its associated native
+ // view got focused. This implies that it is shown (opposite of WasHidden,
+ // below).
+ // The touch_event_context parameter indicates that we were called in the
+ // context of a touch event.
+ void GotFocus(bool touch_event_context);
+
+ // Called to notify the accessibility manager that its associated native
+ // view was hidden. When it's no longer hidden, GotFocus will be called.
+ void WasHidden();
+
+ // Called to notify the accessibility manager that a mouse down event
+ // occurred in the tab.
+ void GotMouseDown();
+
+ // Update the focused node to |node|, which may be null.
+ // If |notify| is true, send a message to the renderer to set focus
+ // to this node.
+ void SetFocus(BrowserAccessibility* node, bool notify);
+
+ // Tell the renderer to do the default action for this node.
+ void DoDefaultAction(const BrowserAccessibility& node);
+
+ // Tell the renderer to scroll to make |node| visible.
+ // In addition, if it's not possible to make the entire object visible,
+ // scroll so that the |subfocus| rect is visible at least. The subfocus
+ // rect is in local coordinates of the object itself.
+ void ScrollToMakeVisible(
+ const BrowserAccessibility& node, gfx::Rect subfocus);
+
+ // Tell the renderer to scroll such that |node| is at |point|,
+ // where |point| is in global coordinates of the WebContents.
+ void ScrollToPoint(
+ const BrowserAccessibility& node, gfx::Point point);
+
+ // Tell the renderer to set the text selection on a node.
+ void SetTextSelection(
+ const BrowserAccessibility& node, int start_offset, int end_offset);
+
+ // Retrieve the bounds of the parent View in screen coordinates.
+ gfx::Rect GetViewBounds();
+
+ // Called when the renderer process has notified us of about tree changes.
+ // Send a notification to MSAA clients of the change.
+ void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params);
+
+#if defined(OS_WIN)
+ BrowserAccessibilityManagerWin* ToBrowserAccessibilityManagerWin();
+#endif
+
+ // Return the object that has focus, if it's a descandant of the
+ // given root (inclusive). Does not make a new reference.
+ BrowserAccessibility* GetFocus(BrowserAccessibility* root);
+
+ // Is the on-screen keyboard allowed to be shown, in response to a
+ // focus event on a text box?
+ bool IsOSKAllowed(const gfx::Rect& bounds);
+
+ // True by default, but some platforms want to treat the root
+ // scroll offsets separately.
+ virtual bool UseRootScrollOffsetsWhenComputingBounds();
+
+ // For testing only: update the given nodes as if they were
+ // received from the renderer process in OnAccessibilityNotifications.
+ // Takes up to 7 nodes at once so tests don't need to create a vector
+ // each time.
+ void UpdateNodesForTesting(
+ const AccessibilityNodeData& node,
+ const AccessibilityNodeData& node2 = AccessibilityNodeData(),
+ const AccessibilityNodeData& node3 = AccessibilityNodeData(),
+ const AccessibilityNodeData& node4 = AccessibilityNodeData(),
+ const AccessibilityNodeData& node5 = AccessibilityNodeData(),
+ const AccessibilityNodeData& node6 = AccessibilityNodeData(),
+ const AccessibilityNodeData& node7 = AccessibilityNodeData());
+
+ protected:
+ BrowserAccessibilityManager(
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory);
+
+ BrowserAccessibilityManager(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory);
+
+ virtual void AddNodeToMap(BrowserAccessibility* node);
+
+ virtual void NotifyRootChanged() {}
+
+ private:
+ // The following states keep track of whether or not the
+ // on-screen keyboard is allowed to be shown.
+ enum OnScreenKeyboardState {
+ // Never show the on-screen keyboard because this tab is hidden.
+ OSK_DISALLOWED_BECAUSE_TAB_HIDDEN,
+
+ // This tab was just shown, so don't pop-up the on-screen keyboard if a
+ // text field gets focus that wasn't the result of an explicit touch.
+ OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED,
+
+ // A touch event has occurred within the window, but focus has not
+ // explicitly changed. Allow the on-screen keyboard to be shown if the
+ // touch event was within the bounds of the currently focused object.
+ // Otherwise we'll just wait to see if focus changes.
+ OSK_ALLOWED_WITHIN_FOCUSED_OBJECT,
+
+ // Focus has changed within a tab that's already visible. Allow the
+ // on-screen keyboard to show anytime that a touch event leads to an
+ // editable text control getting focus.
+ OSK_ALLOWED
+ };
+
+ // Update a set of nodes using data received from the renderer
+ // process.
+ bool UpdateNodes(const std::vector<AccessibilityNodeData>& nodes);
+
+ // Update one node from the tree using data received from the renderer
+ // process. Returns true on success, false on fatal error.
+ bool UpdateNode(const AccessibilityNodeData& src);
+
+ void SetRoot(BrowserAccessibility* root);
+
+ BrowserAccessibility* CreateNode(
+ BrowserAccessibility* parent,
+ int32 renderer_id,
+ int32 index_in_parent);
+
+ protected:
+ // The object that can perform actions on our behalf.
+ BrowserAccessibilityDelegate* delegate_;
+
+ // Factory to create BrowserAccessibility objects (for dependency injection).
+ scoped_ptr<BrowserAccessibilityFactory> factory_;
+
+ // The root of the tree of accessible objects and the element that
+ // currently has focus, if any.
+ BrowserAccessibility* root_;
+ BrowserAccessibility* focus_;
+
+ // The on-screen keyboard state.
+ OnScreenKeyboardState osk_state_;
+
+ // A mapping from renderer IDs to BrowserAccessibility objects.
+ base::hash_map<int32, BrowserAccessibility*> renderer_id_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc
new file mode 100644
index 00000000000..e14aa0bb977
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -0,0 +1,366 @@
+// 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 "content/browser/accessibility/browser_accessibility_manager_android.h"
+
+#include <cmath>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/accessibility/browser_accessibility_android.h"
+#include "content/common/accessibility_messages.h"
+#include "jni/BrowserAccessibilityManager_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+// These are enums from android.view.accessibility.AccessibilityEvent in Java:
+enum {
+ ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED = 16,
+ ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192
+};
+
+// Restricts |val| to the range [min, max].
+int Clamp(int val, int min, int max) {
+ return std::min(std::max(val, min), max);
+}
+
+} // anonymous namespace
+
+namespace content {
+
+namespace aria_strings {
+ const char kAriaLivePolite[] = "polite";
+ const char kAriaLiveAssertive[] = "assertive";
+}
+
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(),
+ src, delegate, factory);
+}
+
+BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
+ ScopedJavaLocalRef<jobject> content_view_core,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : BrowserAccessibilityManager(src, delegate, factory) {
+ if (content_view_core.is_null())
+ return;
+
+ JNIEnv* env = AttachCurrentThread();
+ java_ref_ = JavaObjectWeakGlobalRef(
+ env, Java_BrowserAccessibilityManager_create(
+ env, reinterpret_cast<jint>(this), content_view_core.obj()).obj());
+}
+
+BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ empty_document.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ return empty_document;
+}
+
+void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ switch (type) {
+ case AccessibilityNotificationLoadComplete:
+ Java_BrowserAccessibilityManager_handlePageLoaded(
+ env, obj.obj(), focus_->renderer_id());
+ break;
+ case AccessibilityNotificationFocusChanged:
+ Java_BrowserAccessibilityManager_handleFocusChanged(
+ env, obj.obj(), node->renderer_id());
+ break;
+ case AccessibilityNotificationCheckStateChanged:
+ Java_BrowserAccessibilityManager_handleCheckStateChanged(
+ env, obj.obj(), node->renderer_id());
+ break;
+ case AccessibilityNotificationScrolledToAnchor:
+ Java_BrowserAccessibilityManager_handleScrolledToAnchor(
+ env, obj.obj(), node->renderer_id());
+ break;
+ case AccessibilityNotificationAlert:
+ // An alert is a special case of live region. Fall through to the
+ // next case to handle it.
+ case AccessibilityNotificationObjectShow: {
+ // This event is fired when an object appears in a live region.
+ // Speak its text.
+ BrowserAccessibilityAndroid* android_node =
+ static_cast<BrowserAccessibilityAndroid*>(node);
+ Java_BrowserAccessibilityManager_announceLiveRegionText(
+ env, obj.obj(),
+ base::android::ConvertUTF16ToJavaString(
+ env, android_node->GetText()).obj());
+ break;
+ }
+ case AccessibilityNotificationSelectedTextChanged:
+ Java_BrowserAccessibilityManager_handleTextSelectionChanged(
+ env, obj.obj(), node->renderer_id());
+ break;
+ case AccessibilityNotificationChildrenChanged:
+ case AccessibilityNotificationTextChanged:
+ case AccessibilityNotificationValueChanged:
+ if (node->IsEditableText()) {
+ Java_BrowserAccessibilityManager_handleEditableTextChanged(
+ env, obj.obj(), node->renderer_id());
+ } else {
+ Java_BrowserAccessibilityManager_handleContentChanged(
+ env, obj.obj(), node->renderer_id());
+ }
+ break;
+ default:
+ // There are some notifications that aren't meaningful on Android.
+ // It's okay to skip them.
+ break;
+ }
+}
+
+jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
+ return static_cast<jint>(root_->renderer_id());
+}
+
+jint BrowserAccessibilityManagerAndroid::HitTest(
+ JNIEnv* env, jobject obj, jint x, jint y) {
+ BrowserAccessibilityAndroid* result =
+ static_cast<BrowserAccessibilityAndroid*>(
+ root_->BrowserAccessibilityForPoint(gfx::Point(x, y)));
+
+ if (!result)
+ return root_->renderer_id();
+
+ if (result->IsFocusable())
+ return result->renderer_id();
+
+ // Examine the children of |result| to find the nearest accessibility focus
+ // candidate
+ BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
+ if (nearest_node)
+ return nearest_node->renderer_id();
+
+ return root_->renderer_id();
+}
+
+jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
+ JNIEnv* env, jobject obj, jobject info, jint id) {
+ BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
+ GetFromRendererID(id));
+ if (!node)
+ return false;
+
+ if (node->parent()) {
+ Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
+ env, obj, info, node->parent()->renderer_id());
+ }
+ if (!node->IsLeaf()) {
+ for (unsigned i = 0; i < node->child_count(); ++i) {
+ Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
+ env, obj, info, node->children()[i]->renderer_id());
+ }
+ }
+ Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
+ env, obj, info,
+ id,
+ node->IsCheckable(),
+ node->IsChecked(),
+ node->IsClickable(),
+ node->IsEnabled(),
+ node->IsFocusable(),
+ node->IsFocused(),
+ node->IsPassword(),
+ node->IsScrollable(),
+ node->IsSelected(),
+ node->IsVisibleToUser());
+ Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes(
+ env, obj, info,
+ base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(),
+ base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
+
+ gfx::Rect absolute_rect = node->GetLocalBoundsRect();
+ gfx::Rect parent_relative_rect = absolute_rect;
+ if (node->parent()) {
+ gfx::Rect parent_rect = node->parent()->GetLocalBoundsRect();
+ parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
+ }
+ bool is_root = node->parent() == NULL;
+ Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
+ env, obj, info,
+ absolute_rect.x(), absolute_rect.y(),
+ parent_relative_rect.x(), parent_relative_rect.y(),
+ absolute_rect.width(), absolute_rect.height(),
+ is_root);
+
+ return true;
+}
+
+jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
+ JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
+ BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
+ GetFromRendererID(id));
+ if (!node)
+ return false;
+
+ Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
+ env, obj, event,
+ node->IsChecked(),
+ node->IsEnabled(),
+ node->IsPassword(),
+ node->IsScrollable());
+ Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
+ env, obj, event,
+ base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
+ Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
+ env, obj, event,
+ node->GetItemIndex(),
+ node->GetItemCount());
+ Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
+ env, obj, event,
+ node->GetScrollX(),
+ node->GetScrollY(),
+ node->GetMaxScrollX(),
+ node->GetMaxScrollY());
+
+ switch (event_type) {
+ case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
+ Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
+ env, obj, event,
+ node->GetTextChangeFromIndex(),
+ node->GetTextChangeAddedCount(),
+ node->GetTextChangeRemovedCount(),
+ base::android::ConvertUTF16ToJavaString(
+ env, node->GetTextChangeBeforeText()).obj(),
+ base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
+ break;
+ case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
+ Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
+ env, obj, event,
+ node->GetSelectionStart(),
+ node->GetSelectionEnd(),
+ node->GetEditableTextLength(),
+ base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void BrowserAccessibilityManagerAndroid::Click(
+ JNIEnv* env, jobject obj, jint id) {
+ BrowserAccessibility* node = GetFromRendererID(id);
+ if (node)
+ DoDefaultAction(*node);
+}
+
+void BrowserAccessibilityManagerAndroid::Focus(
+ JNIEnv* env, jobject obj, jint id) {
+ BrowserAccessibility* node = GetFromRendererID(id);
+ if (node)
+ SetFocus(node, true);
+}
+
+void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
+ SetFocus(root_, true);
+}
+
+BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
+ int x, int y, BrowserAccessibility* start_node) {
+ BrowserAccessibility* nearest_node = NULL;
+ int min_distance = INT_MAX;
+ FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
+ return nearest_node;
+}
+
+// static
+void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
+ int x, int y, BrowserAccessibility* start_node,
+ BrowserAccessibility** nearest_candidate, int* nearest_distance) {
+ BrowserAccessibilityAndroid* node =
+ static_cast<BrowserAccessibilityAndroid*>(start_node);
+ int distance = CalculateDistanceSquared(x, y, node);
+
+ if (node->IsFocusable()) {
+ if (distance < *nearest_distance) {
+ *nearest_candidate = node;
+ *nearest_distance = distance;
+ }
+ // Don't examine any more children of focusable node
+ // TODO(aboxhall): what about focusable children?
+ return;
+ }
+
+ if (!node->GetText().empty()) {
+ if (distance < *nearest_distance) {
+ *nearest_candidate = node;
+ *nearest_distance = distance;
+ }
+ return;
+ }
+
+ if (!node->IsLeaf()) {
+ for (uint32 i = 0; i < node->child_count(); i++) {
+ BrowserAccessibility* child = node->GetChild(i);
+ FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
+ }
+ }
+}
+
+// static
+int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
+ int x, int y, BrowserAccessibility* node) {
+ gfx::Rect node_bounds = node->GetLocalBoundsRect();
+ int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
+ int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
+ int dx = std::abs(x - nearest_x);
+ int dy = std::abs(y - nearest_y);
+ return dx * dx + dy * dy;
+}
+
+void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
+}
+
+bool
+BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
+ // The Java layer handles the root scroll offset.
+ return false;
+}
+
+bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.h b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h
new file mode 100644
index 00000000000..2a6c291bacb
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -0,0 +1,92 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/android/content_view_core_impl.h"
+
+namespace content {
+
+namespace aria_strings {
+ extern const char kAriaLivePolite[];
+ extern const char kAriaLiveAssertive[];
+}
+
+class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
+ : public BrowserAccessibilityManager {
+ public:
+ BrowserAccessibilityManagerAndroid(
+ base::android::ScopedJavaLocalRef<jobject> content_view_core,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ virtual ~BrowserAccessibilityManagerAndroid();
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // Implementation of BrowserAccessibilityManager.
+ virtual void NotifyAccessibilityEvent(int type,
+ BrowserAccessibility* node) OVERRIDE;
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+
+ // Tree methods.
+ jint GetRootId(JNIEnv* env, jobject obj);
+ jint HitTest(JNIEnv* env, jobject obj, jint x, jint y);
+
+ // Populate Java accessibility data structures with info about a node.
+ jboolean PopulateAccessibilityNodeInfo(
+ JNIEnv* env, jobject obj, jobject info, jint id);
+ jboolean PopulateAccessibilityEvent(
+ JNIEnv* env, jobject obj, jobject event, jint id, jint event_type);
+
+ // Perform actions.
+ void Click(JNIEnv* env, jobject obj, jint id);
+ void Focus(JNIEnv* env, jobject obj, jint id);
+ void Blur(JNIEnv* env, jobject obj);
+
+ protected:
+ virtual void NotifyRootChanged() OVERRIDE;
+
+ virtual bool UseRootScrollOffsetsWhenComputingBounds() OVERRIDE;
+
+ private:
+ // This gives BrowserAccessibilityManager::Create access to the class
+ // constructor.
+ friend class BrowserAccessibilityManager;
+
+ // A weak reference to the Java BrowserAccessibilityManager object.
+ // This avoids adding another reference to BrowserAccessibilityManager and
+ // preventing garbage collection.
+ // Premature garbage collection is prevented by the long-lived reference in
+ // ContentViewCore.
+ JavaObjectWeakGlobalRef java_ref_;
+
+ // Searches through the children of start_node to find the nearest
+ // accessibility focus candidate for a touch which has not landed directly on
+ // an accessibility focus candidate.
+ BrowserAccessibility* FuzzyHitTest(
+ int x, int y, BrowserAccessibility* start_node);
+
+ static void FuzzyHitTestImpl(int x, int y, BrowserAccessibility* start_node,
+ BrowserAccessibility** nearest_candidate, int* min_distance);
+
+ // Calculates the distance from the point (x, y) to the nearest point on the
+ // edge of |node|.
+ static int CalculateDistanceSquared(int x, int y, BrowserAccessibility* node);
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerAndroid);
+};
+
+bool RegisterBrowserAccessibilityManager(JNIEnv* env);
+
+}
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc
new file mode 100644
index 00000000000..15191c6a35b
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc
@@ -0,0 +1,84 @@
+// 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 "content/browser/accessibility/browser_accessibility_manager_gtk.h"
+
+#include "content/browser/accessibility/browser_accessibility_gtk.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManagerGtk(
+ NULL,
+ src,
+ delegate,
+ factory);
+}
+
+BrowserAccessibilityManagerGtk::BrowserAccessibilityManagerGtk(
+ GtkWidget* parent_widget,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : BrowserAccessibilityManager(delegate, factory),
+ parent_widget_(parent_widget) {
+ Initialize(src);
+}
+
+BrowserAccessibilityManagerGtk::~BrowserAccessibilityManagerGtk() {
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManagerGtk::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ empty_document.state =
+ 1 << AccessibilityNodeData::STATE_READONLY;
+ return empty_document;
+}
+
+void BrowserAccessibilityManagerGtk::NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) {
+ if (!node->IsNative())
+ return;
+ AtkObject* atk_object = node->ToBrowserAccessibilityGtk()->GetAtkObject();
+
+ switch (type) {
+ case AccessibilityNotificationChildrenChanged:
+ RecursivelySendChildrenChanged(GetRoot()->ToBrowserAccessibilityGtk());
+ break;
+ case AccessibilityNotificationFocusChanged:
+ // Note: atk_focus_tracker_notify may be deprecated in the future;
+ // follow this bug for the replacement:
+ // https://bugzilla.gnome.org/show_bug.cgi?id=649575#c4
+ g_signal_emit_by_name(atk_object, "focus-event", true);
+ atk_focus_tracker_notify(atk_object);
+ break;
+ default:
+ break;
+ }
+}
+
+void BrowserAccessibilityManagerGtk::RecursivelySendChildrenChanged(
+ BrowserAccessibilityGtk* node) {
+ AtkObject* atkObject = node->ToBrowserAccessibilityGtk()->GetAtkObject();
+ for (unsigned int i = 0; i < node->children().size(); ++i) {
+ BrowserAccessibilityGtk* child =
+ node->children()[i]->ToBrowserAccessibilityGtk();
+ g_signal_emit_by_name(atkObject,
+ "children-changed::add",
+ i,
+ child->GetAtkObject());
+ RecursivelySendChildrenChanged(child);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h
new file mode 100644
index 00000000000..eedae7ca121
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h
@@ -0,0 +1,48 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_GTK_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_GTK_H_
+
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+
+struct ViewHostMsg_AccessibilityNotification_Params;
+
+namespace content {
+class BrowserAccessibilityGtk;
+
+// Manages a tree of BrowserAccessibilityGtk objects.
+class CONTENT_EXPORT BrowserAccessibilityManagerGtk
+ : public BrowserAccessibilityManager {
+ public:
+ BrowserAccessibilityManagerGtk(
+ GtkWidget* parent_widget,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ virtual ~BrowserAccessibilityManagerGtk();
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // BrowserAccessibilityManager methods
+ virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node)
+ OVERRIDE;
+
+ GtkWidget* parent_widget() { return parent_widget_; }
+
+ private:
+ void RecursivelySendChildrenChanged(BrowserAccessibilityGtk* node);
+
+ GtkWidget* parent_widget_;
+
+ // Give BrowserAccessibilityManager::Create access to our constructor.
+ friend class BrowserAccessibilityManager;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_GTK_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h
new file mode 100644
index 00000000000..d94c2c73df8
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h
@@ -0,0 +1,43 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+
+namespace content {
+
+class CONTENT_EXPORT BrowserAccessibilityManagerMac
+ : public BrowserAccessibilityManager {
+ public:
+ BrowserAccessibilityManagerMac(
+ NSView* parent_view,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // Implementation of BrowserAccessibilityManager.
+ virtual void NotifyAccessibilityEvent(int type,
+ BrowserAccessibility* node) OVERRIDE;
+
+ NSView* parent_view() { return parent_view_; }
+
+ private:
+ // This gives BrowserAccessibilityManager::Create access to the class
+ // constructor.
+ friend class BrowserAccessibilityManager;
+
+ NSView* parent_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerMac);
+};
+
+}
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm
new file mode 100644
index 00000000000..e3280997a25
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -0,0 +1,121 @@
+// 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 "content/browser/accessibility/browser_accessibility_manager_mac.h"
+
+#import "base/logging.h"
+#import "content/browser/accessibility/browser_accessibility_cocoa.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManagerMac(NULL, src, delegate, factory);
+}
+
+BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac(
+ NSView* parent_view,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : BrowserAccessibilityManager(src, delegate, factory),
+ parent_view_(parent_view) {
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManagerMac::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ empty_document.state =
+ 1 << AccessibilityNodeData::STATE_READONLY;
+ return empty_document;
+}
+
+void BrowserAccessibilityManagerMac::NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) {
+ if (!node->IsNative())
+ return;
+
+ // Refer to AXObjectCache.mm (webkit).
+ NSString* event_id = @"";
+ switch (type) {
+ case AccessibilityNotificationActiveDescendantChanged:
+ if (node->role() == AccessibilityNodeData::ROLE_TREE)
+ event_id = NSAccessibilitySelectedRowsChangedNotification;
+ else
+ event_id = NSAccessibilityFocusedUIElementChangedNotification;
+ break;
+ case AccessibilityNotificationAlert:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationBlur:
+ // A no-op on Mac.
+ return;
+ case AccessibilityNotificationCheckStateChanged:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationChildrenChanged:
+ // TODO(dtseng): no clear equivalent on Mac.
+ return;
+ case AccessibilityNotificationFocusChanged:
+ event_id = NSAccessibilityFocusedUIElementChangedNotification;
+ break;
+ case AccessibilityNotificationLayoutComplete:
+ event_id = @"AXLayoutComplete";
+ break;
+ case AccessibilityNotificationLiveRegionChanged:
+ event_id = @"AXLiveRegionChanged";
+ break;
+ case AccessibilityNotificationLoadComplete:
+ event_id = @"AXLoadComplete";
+ break;
+ case AccessibilityNotificationMenuListValueChanged:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationObjectShow:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationObjectHide:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationRowCountChanged:
+ event_id = NSAccessibilityRowCountChangedNotification;
+ break;
+ case AccessibilityNotificationRowCollapsed:
+ event_id = @"AXRowCollapsed";
+ break;
+ case AccessibilityNotificationRowExpanded:
+ event_id = @"AXRowExpanded";
+ break;
+ case AccessibilityNotificationScrolledToAnchor:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationSelectedChildrenChanged:
+ event_id = NSAccessibilitySelectedChildrenChangedNotification;
+ break;
+ case AccessibilityNotificationSelectedTextChanged:
+ event_id = NSAccessibilitySelectedTextChangedNotification;
+ break;
+ case AccessibilityNotificationTextInserted:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationTextRemoved:
+ // Not used on Mac.
+ return;
+ case AccessibilityNotificationValueChanged:
+ event_id = NSAccessibilityValueChangedNotification;
+ break;
+ }
+ BrowserAccessibilityCocoa* native_node = node->ToBrowserAccessibilityCocoa();
+ DCHECK(native_node);
+ NSAccessibilityPostNotification(native_node, event_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc
new file mode 100644
index 00000000000..5cb03cd25ce
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -0,0 +1,603 @@
+// 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 "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/accessibility_node_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+// Subclass of BrowserAccessibility that counts the number of instances.
+class CountedBrowserAccessibility : public BrowserAccessibility {
+ public:
+ CountedBrowserAccessibility() {
+ global_obj_count_++;
+ native_ref_count_ = 1;
+ }
+ virtual ~CountedBrowserAccessibility() {
+ global_obj_count_--;
+ }
+
+ virtual void NativeAddReference() OVERRIDE {
+ native_ref_count_++;
+ }
+
+ virtual void NativeReleaseReference() OVERRIDE {
+ native_ref_count_--;
+ if (native_ref_count_ == 0)
+ delete this;
+ }
+
+ int native_ref_count_;
+ static int global_obj_count_;
+};
+
+int CountedBrowserAccessibility::global_obj_count_ = 0;
+
+// Factory that creates a CountedBrowserAccessibility.
+class CountedBrowserAccessibilityFactory
+ : public BrowserAccessibilityFactory {
+ public:
+ virtual ~CountedBrowserAccessibilityFactory() {}
+ virtual BrowserAccessibility* Create() OVERRIDE {
+ return new CountedBrowserAccessibility();
+ }
+};
+
+class TestBrowserAccessibilityDelegate
+ : public BrowserAccessibilityDelegate {
+ public:
+ TestBrowserAccessibilityDelegate()
+ : got_fatal_error_(false) {}
+
+ virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE {}
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {}
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) OVERRIDE {}
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) OVERRIDE {}
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) OVERRIDE {}
+ virtual bool HasFocus() const OVERRIDE {
+ return false;
+ }
+ virtual gfx::Rect GetViewBounds() const OVERRIDE {
+ return gfx::Rect();
+ }
+ virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE {
+ return gfx::Point();
+ }
+ virtual void FatalAccessibilityTreeError() OVERRIDE {
+ got_fatal_error_ = true;
+ }
+
+ bool got_fatal_error() const { return got_fatal_error_; }
+ void reset_got_fatal_error() { got_fatal_error_ = false; }
+
+private:
+ bool got_fatal_error_;
+};
+
+} // anonymous namespace
+
+TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
+ // Create AccessibilityNodeData objects for a simple document tree,
+ // representing the accessibility information used to initialize
+ // BrowserAccessibilityManager.
+ AccessibilityNodeData button;
+ button.id = 2;
+ button.name = UTF8ToUTF16("Button");
+ button.role = AccessibilityNodeData::ROLE_BUTTON;
+ button.state = 0;
+
+ AccessibilityNodeData checkbox;
+ checkbox.id = 3;
+ checkbox.name = UTF8ToUTF16("Checkbox");
+ checkbox.role = AccessibilityNodeData::ROLE_CHECKBOX;
+ checkbox.state = 0;
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.name = UTF8ToUTF16("Document");
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(2);
+ root.child_ids.push_back(3);
+
+ // Construct a BrowserAccessibilityManager with this
+ // AccessibilityNodeData tree and a factory for an instance-counting
+ // BrowserAccessibility, and ensure that exactly 3 instances were
+ // created. Note that the manager takes ownership of the factory.
+ CountedBrowserAccessibility::global_obj_count_ = 0;
+ BrowserAccessibilityManager* manager =
+ BrowserAccessibilityManager::Create(
+ root,
+ NULL,
+ new CountedBrowserAccessibilityFactory());
+ manager->UpdateNodesForTesting(button, checkbox);
+
+ ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
+
+ // Delete the manager and test that all 3 instances are deleted.
+ delete manager;
+ ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
+
+ // Construct a manager again, and this time save references to two of
+ // the three nodes in the tree.
+ manager =
+ BrowserAccessibilityManager::Create(
+ root,
+ NULL,
+ new CountedBrowserAccessibilityFactory());
+ manager->UpdateNodesForTesting(button, checkbox);
+ ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
+
+ CountedBrowserAccessibility* root_accessible =
+ static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
+ root_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child1_accessible =
+ static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1));
+ child1_accessible->NativeAddReference();
+
+ // Now delete the manager, and only one of the three nodes in the tree
+ // should be released.
+ delete manager;
+ ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
+
+ // Release each of our references and make sure that each one results in
+ // the instance being deleted as its reference count hits zero.
+ root_accessible->NativeReleaseReference();
+ ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
+ child1_accessible->NativeReleaseReference();
+ ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
+}
+
+TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
+ // Make sure that changes to a subtree reuse as many objects as possible.
+
+ // Tree 1:
+ //
+ // root
+ // child1
+ // child2
+ // child3
+
+ AccessibilityNodeData tree1_child1;
+ tree1_child1.id = 2;
+ tree1_child1.name = UTF8ToUTF16("Child1");
+ tree1_child1.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child1.state = 0;
+
+ AccessibilityNodeData tree1_child2;
+ tree1_child2.id = 3;
+ tree1_child2.name = UTF8ToUTF16("Child2");
+ tree1_child2.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child2.state = 0;
+
+ AccessibilityNodeData tree1_child3;
+ tree1_child3.id = 4;
+ tree1_child3.name = UTF8ToUTF16("Child3");
+ tree1_child3.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child3.state = 0;
+
+ AccessibilityNodeData tree1_root;
+ tree1_root.id = 1;
+ tree1_root.name = UTF8ToUTF16("Document");
+ tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree1_root.state = 0;
+ tree1_root.child_ids.push_back(2);
+ tree1_root.child_ids.push_back(3);
+ tree1_root.child_ids.push_back(4);
+
+ // Tree 2:
+ //
+ // root
+ // child0 <-- inserted
+ // child1
+ // child2
+ // <-- child3 deleted
+
+ AccessibilityNodeData tree2_child0;
+ tree2_child0.id = 5;
+ tree2_child0.name = UTF8ToUTF16("Child0");
+ tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree2_child0.state = 0;
+
+ AccessibilityNodeData tree2_root;
+ tree2_root.id = 1;
+ tree2_root.name = UTF8ToUTF16("DocumentChanged");
+ tree2_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree2_root.state = 0;
+ tree2_root.child_ids.push_back(5);
+ tree2_root.child_ids.push_back(2);
+ tree2_root.child_ids.push_back(3);
+
+ // Construct a BrowserAccessibilityManager with tree1.
+ CountedBrowserAccessibility::global_obj_count_ = 0;
+ BrowserAccessibilityManager* manager =
+ BrowserAccessibilityManager::Create(
+ tree1_root,
+ NULL,
+ new CountedBrowserAccessibilityFactory());
+ manager->UpdateNodesForTesting(tree1_child1, tree1_child2, tree1_child3);
+ ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
+
+ // Save references to all of the objects.
+ CountedBrowserAccessibility* root_accessible =
+ static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
+ root_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child1_accessible =
+ static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0));
+ child1_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child2_accessible =
+ static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1));
+ child2_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child3_accessible =
+ static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(2));
+ child3_accessible->NativeAddReference();
+
+ // Check the index in parent.
+ EXPECT_EQ(0, child1_accessible->index_in_parent());
+ EXPECT_EQ(1, child2_accessible->index_in_parent());
+ EXPECT_EQ(2, child3_accessible->index_in_parent());
+
+ // Process a notification containing the changed subtree.
+ std::vector<AccessibilityHostMsg_NotificationParams> params;
+ params.push_back(AccessibilityHostMsg_NotificationParams());
+ AccessibilityHostMsg_NotificationParams* msg = &params[0];
+ msg->notification_type = AccessibilityNotificationChildrenChanged;
+ msg->nodes.push_back(tree2_root);
+ msg->nodes.push_back(tree2_child0);
+ msg->id = tree2_root.id;
+ manager->OnAccessibilityNotifications(params);
+
+ // There should be 5 objects now: the 4 from the new tree, plus the
+ // reference to child3 we kept.
+ EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_);
+
+ // Check that our references to the root, child1, and child2 are still valid,
+ // but that the reference to child3 is now invalid.
+ EXPECT_TRUE(root_accessible->instance_active());
+ EXPECT_TRUE(child1_accessible->instance_active());
+ EXPECT_TRUE(child2_accessible->instance_active());
+ EXPECT_FALSE(child3_accessible->instance_active());
+
+ // Check that the index in parent has been updated.
+ EXPECT_EQ(1, child1_accessible->index_in_parent());
+ EXPECT_EQ(2, child2_accessible->index_in_parent());
+
+ // Release our references. The object count should only decrease by 1
+ // for child3.
+ root_accessible->NativeReleaseReference();
+ child1_accessible->NativeReleaseReference();
+ child2_accessible->NativeReleaseReference();
+ child3_accessible->NativeReleaseReference();
+
+ EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
+
+ // Delete the manager and make sure all memory is cleaned up.
+ delete manager;
+ ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
+}
+
+TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
+ // Similar to the test above, but with a more complicated tree.
+
+ // Tree 1:
+ //
+ // root
+ // container
+ // child1
+ // grandchild1
+ // child2
+ // grandchild2
+ // child3
+ // grandchild3
+
+ AccessibilityNodeData tree1_grandchild1;
+ tree1_grandchild1.id = 4;
+ tree1_grandchild1.name = UTF8ToUTF16("GrandChild1");
+ tree1_grandchild1.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_grandchild1.state = 0;
+
+ AccessibilityNodeData tree1_child1;
+ tree1_child1.id = 3;
+ tree1_child1.name = UTF8ToUTF16("Child1");
+ tree1_child1.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child1.state = 0;
+ tree1_child1.child_ids.push_back(4);
+
+ AccessibilityNodeData tree1_grandchild2;
+ tree1_grandchild2.id = 6;
+ tree1_grandchild2.name = UTF8ToUTF16("GrandChild1");
+ tree1_grandchild2.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_grandchild2.state = 0;
+
+ AccessibilityNodeData tree1_child2;
+ tree1_child2.id = 5;
+ tree1_child2.name = UTF8ToUTF16("Child2");
+ tree1_child2.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child2.state = 0;
+ tree1_child2.child_ids.push_back(6);
+
+ AccessibilityNodeData tree1_grandchild3;
+ tree1_grandchild3.id = 8;
+ tree1_grandchild3.name = UTF8ToUTF16("GrandChild3");
+ tree1_grandchild3.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_grandchild3.state = 0;
+
+ AccessibilityNodeData tree1_child3;
+ tree1_child3.id = 7;
+ tree1_child3.name = UTF8ToUTF16("Child3");
+ tree1_child3.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree1_child3.state = 0;
+ tree1_child3.child_ids.push_back(8);
+
+ AccessibilityNodeData tree1_container;
+ tree1_container.id = 2;
+ tree1_container.name = UTF8ToUTF16("Container");
+ tree1_container.role = AccessibilityNodeData::ROLE_GROUP;
+ tree1_container.state = 0;
+ tree1_container.child_ids.push_back(3);
+ tree1_container.child_ids.push_back(5);
+ tree1_container.child_ids.push_back(7);
+
+ AccessibilityNodeData tree1_root;
+ tree1_root.id = 1;
+ tree1_root.name = UTF8ToUTF16("Document");
+ tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree1_root.state = 0;
+ tree1_root.child_ids.push_back(2);
+
+ // Tree 2:
+ //
+ // root
+ // container
+ // child0 <-- inserted
+ // grandchild0 <--
+ // child1
+ // grandchild1
+ // child2
+ // grandchild2
+ // <-- child3 (and grandchild3) deleted
+
+ AccessibilityNodeData tree2_grandchild0;
+ tree2_grandchild0.id = 9;
+ tree2_grandchild0.name = UTF8ToUTF16("GrandChild0");
+ tree2_grandchild0.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree2_grandchild0.state = 0;
+
+ AccessibilityNodeData tree2_child0;
+ tree2_child0.id = 10;
+ tree2_child0.name = UTF8ToUTF16("Child0");
+ tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON;
+ tree2_child0.state = 0;
+ tree2_child0.child_ids.push_back(9);
+
+ AccessibilityNodeData tree2_container;
+ tree2_container.id = 2;
+ tree2_container.name = UTF8ToUTF16("Container");
+ tree2_container.role = AccessibilityNodeData::ROLE_GROUP;
+ tree2_container.state = 0;
+ tree2_container.child_ids.push_back(10);
+ tree2_container.child_ids.push_back(3);
+ tree2_container.child_ids.push_back(5);
+
+ // Construct a BrowserAccessibilityManager with tree1.
+ CountedBrowserAccessibility::global_obj_count_ = 0;
+ BrowserAccessibilityManager* manager =
+ BrowserAccessibilityManager::Create(
+ tree1_root,
+ NULL,
+ new CountedBrowserAccessibilityFactory());
+ manager->UpdateNodesForTesting(tree1_container,
+ tree1_child1, tree1_grandchild1,
+ tree1_child2, tree1_grandchild2,
+ tree1_child3, tree1_grandchild3);
+ ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
+
+ // Save references to some objects.
+ CountedBrowserAccessibility* root_accessible =
+ static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
+ root_accessible->NativeAddReference();
+ CountedBrowserAccessibility* container_accessible =
+ static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0));
+ container_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child2_accessible =
+ static_cast<CountedBrowserAccessibility*>(
+ container_accessible->GetChild(1));
+ child2_accessible->NativeAddReference();
+ CountedBrowserAccessibility* child3_accessible =
+ static_cast<CountedBrowserAccessibility*>(
+ container_accessible->GetChild(2));
+ child3_accessible->NativeAddReference();
+
+ // Check the index in parent.
+ EXPECT_EQ(1, child2_accessible->index_in_parent());
+ EXPECT_EQ(2, child3_accessible->index_in_parent());
+
+ // Process a notification containing the changed subtree rooted at
+ // the container.
+ std::vector<AccessibilityHostMsg_NotificationParams> params;
+ params.push_back(AccessibilityHostMsg_NotificationParams());
+ AccessibilityHostMsg_NotificationParams* msg = &params[0];
+ msg->notification_type = AccessibilityNotificationChildrenChanged;
+ msg->nodes.push_back(tree2_container);
+ msg->nodes.push_back(tree2_child0);
+ msg->nodes.push_back(tree2_grandchild0);
+ msg->id = tree2_container.id;
+ manager->OnAccessibilityNotifications(params);
+
+ // There should be 9 objects now: the 8 from the new tree, plus the
+ // reference to child3 we kept.
+ EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_);
+
+ // Check that our references to the root and container and child2 are
+ // still valid, but that the reference to child3 is now invalid.
+ EXPECT_TRUE(root_accessible->instance_active());
+ EXPECT_TRUE(container_accessible->instance_active());
+ EXPECT_TRUE(child2_accessible->instance_active());
+ EXPECT_FALSE(child3_accessible->instance_active());
+
+ // Ensure that we retain the parent of the detached subtree.
+ EXPECT_EQ(root_accessible, container_accessible->parent());
+ EXPECT_EQ(0, container_accessible->index_in_parent());
+
+ // Check that the index in parent has been updated.
+ EXPECT_EQ(2, child2_accessible->index_in_parent());
+
+ // Release our references. The object count should only decrease by 1
+ // for child3.
+ root_accessible->NativeReleaseReference();
+ container_accessible->NativeReleaseReference();
+ child2_accessible->NativeReleaseReference();
+ child3_accessible->NativeReleaseReference();
+
+ EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
+
+ // Delete the manager and make sure all memory is cleaned up.
+ delete manager;
+ ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
+}
+
+TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
+ // Tree 1:
+ //
+ // 1
+ // 2
+ // 3
+ // 4
+
+ AccessibilityNodeData tree1_4;
+ tree1_4.id = 4;
+ tree1_4.state = 0;
+
+ AccessibilityNodeData tree1_3;
+ tree1_3.id = 3;
+ tree1_3.state = 0;
+ tree1_3.child_ids.push_back(4);
+
+ AccessibilityNodeData tree1_2;
+ tree1_2.id = 2;
+ tree1_2.state = 0;
+
+ AccessibilityNodeData tree1_1;
+ tree1_1.id = 1;
+ tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree1_1.state = 0;
+ tree1_1.child_ids.push_back(2);
+ tree1_1.child_ids.push_back(3);
+
+ // Tree 2:
+ //
+ // 1
+ // 4 <-- moves up a level and gains child
+ // 6 <-- new
+ // 5 <-- new
+
+ AccessibilityNodeData tree2_6;
+ tree2_6.id = 6;
+ tree2_6.state = 0;
+
+ AccessibilityNodeData tree2_5;
+ tree2_5.id = 5;
+ tree2_5.state = 0;
+
+ AccessibilityNodeData tree2_4;
+ tree2_4.id = 4;
+ tree2_4.state = 0;
+ tree2_4.child_ids.push_back(6);
+
+ AccessibilityNodeData tree2_1;
+ tree2_1.id = 1;
+ tree2_1.state = 0;
+ tree2_1.child_ids.push_back(4);
+ tree2_1.child_ids.push_back(5);
+
+ // Construct a BrowserAccessibilityManager with tree1.
+ CountedBrowserAccessibility::global_obj_count_ = 0;
+ BrowserAccessibilityManager* manager =
+ BrowserAccessibilityManager::Create(
+ tree1_1,
+ NULL,
+ new CountedBrowserAccessibilityFactory());
+ manager->UpdateNodesForTesting(tree1_2, tree1_3, tree1_4);
+ ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
+
+ // Process a notification containing the changed subtree.
+ std::vector<AccessibilityHostMsg_NotificationParams> params;
+ params.push_back(AccessibilityHostMsg_NotificationParams());
+ AccessibilityHostMsg_NotificationParams* msg = &params[0];
+ msg->notification_type = AccessibilityNotificationChildrenChanged;
+ msg->nodes.push_back(tree2_1);
+ msg->nodes.push_back(tree2_4);
+ msg->nodes.push_back(tree2_5);
+ msg->nodes.push_back(tree2_6);
+ msg->id = tree2_1.id;
+ manager->OnAccessibilityNotifications(params);
+
+ // There should be 4 objects now.
+ EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
+
+ // Delete the manager and make sure all memory is cleaned up.
+ delete manager;
+ ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
+}
+
+TEST(BrowserAccessibilityManagerTest, TestFatalError) {
+ // Test that BrowserAccessibilityManager raises a fatal error
+ // (which will crash the renderer) if the same id is used in
+ // two places in the tree.
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.child_ids.push_back(2);
+ root.child_ids.push_back(2);
+
+ CountedBrowserAccessibilityFactory* factory =
+ new CountedBrowserAccessibilityFactory();
+ scoped_ptr<TestBrowserAccessibilityDelegate> delegate(
+ new TestBrowserAccessibilityDelegate());
+ scoped_ptr<BrowserAccessibilityManager> manager;
+ ASSERT_FALSE(delegate->got_fatal_error());
+ manager.reset(BrowserAccessibilityManager::Create(
+ root,
+ delegate.get(),
+ factory));
+ ASSERT_TRUE(delegate->got_fatal_error());
+
+ AccessibilityNodeData root2;
+ root2.id = 1;
+ root2.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root2.child_ids.push_back(2);
+ root2.child_ids.push_back(3);
+
+ AccessibilityNodeData child1;
+ child1.id = 2;
+ child1.child_ids.push_back(4);
+ child1.child_ids.push_back(5);
+
+ AccessibilityNodeData child2;
+ child2.id = 3;
+ child2.child_ids.push_back(6);
+ child2.child_ids.push_back(5); // Duplicate
+
+ delegate->reset_got_fatal_error();
+ factory = new CountedBrowserAccessibilityFactory();
+ manager.reset(BrowserAccessibilityManager::Create(
+ root2,
+ delegate.get(),
+ factory));
+ ASSERT_FALSE(delegate->got_fatal_error());
+ manager->UpdateNodesForTesting(child1, child2);
+ ASSERT_TRUE(delegate->got_fatal_error());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc
new file mode 100644
index 00000000000..b956d218d33
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -0,0 +1,203 @@
+// 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 "content/browser/accessibility/browser_accessibility_manager_win.h"
+
+#include "content/browser/accessibility/browser_accessibility_win.h"
+#include "content/common/accessibility_messages.h"
+
+namespace content {
+
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManagerWin(
+ GetDesktopWindow(), NULL, src, delegate, factory);
+}
+
+BrowserAccessibilityManagerWin*
+BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
+ return static_cast<BrowserAccessibilityManagerWin*>(this);
+}
+
+BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
+ HWND parent_hwnd,
+ IAccessible* parent_iaccessible,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : BrowserAccessibilityManager(src, delegate, factory),
+ parent_hwnd_(parent_hwnd),
+ parent_iaccessible_(parent_iaccessible),
+ tracked_scroll_object_(NULL) {
+}
+
+BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
+ if (tracked_scroll_object_) {
+ tracked_scroll_object_->Release();
+ tracked_scroll_object_ = NULL;
+ }
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManagerWin::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ empty_document.state =
+ (1 << AccessibilityNodeData::STATE_READONLY) |
+ (1 << AccessibilityNodeData::STATE_BUSY);
+ return empty_document;
+}
+
+void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
+ LONG child_id) {
+ if (parent_iaccessible())
+ ::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id);
+}
+
+void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility* node) {
+ BrowserAccessibilityManager::AddNodeToMap(node);
+ LONG unique_id_win = node->ToBrowserAccessibilityWin()->unique_id_win();
+ unique_id_to_renderer_id_map_[unique_id_win] = node->renderer_id();
+}
+
+void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility* node) {
+ unique_id_to_renderer_id_map_.erase(
+ node->ToBrowserAccessibilityWin()->unique_id_win());
+ BrowserAccessibilityManager::RemoveNode(node);
+ if (node == tracked_scroll_object_) {
+ tracked_scroll_object_->Release();
+ tracked_scroll_object_ = NULL;
+ }
+}
+
+void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) {
+ LONG event_id = EVENT_MIN;
+ switch (type) {
+ case AccessibilityNotificationActiveDescendantChanged:
+ event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
+ break;
+ case AccessibilityNotificationAlert:
+ event_id = EVENT_SYSTEM_ALERT;
+ break;
+ case AccessibilityNotificationAriaAttributeChanged:
+ event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
+ break;
+ case AccessibilityNotificationAutocorrectionOccurred:
+ event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
+ break;
+ case AccessibilityNotificationBlur:
+ // Equivalent to focus on the root.
+ event_id = EVENT_OBJECT_FOCUS;
+ node = GetRoot();
+ break;
+ case AccessibilityNotificationCheckStateChanged:
+ event_id = EVENT_OBJECT_STATECHANGE;
+ break;
+ case AccessibilityNotificationChildrenChanged:
+ event_id = EVENT_OBJECT_REORDER;
+ break;
+ case AccessibilityNotificationFocusChanged:
+ event_id = EVENT_OBJECT_FOCUS;
+ break;
+ case AccessibilityNotificationInvalidStatusChanged:
+ event_id = EVENT_OBJECT_STATECHANGE;
+ break;
+ case AccessibilityNotificationLiveRegionChanged:
+ // TODO: try not firing a native notification at all, since
+ // on Windows, each individual item in a live region that changes
+ // already gets its own notification.
+ event_id = EVENT_OBJECT_REORDER;
+ break;
+ case AccessibilityNotificationLoadComplete:
+ event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
+ break;
+ case AccessibilityNotificationMenuListItemSelected:
+ event_id = EVENT_OBJECT_FOCUS;
+ break;
+ case AccessibilityNotificationMenuListValueChanged:
+ event_id = EVENT_OBJECT_VALUECHANGE;
+ break;
+ case AccessibilityNotificationObjectHide:
+ event_id = EVENT_OBJECT_HIDE;
+ break;
+ case AccessibilityNotificationObjectShow:
+ event_id = EVENT_OBJECT_SHOW;
+ break;
+ case AccessibilityNotificationScrolledToAnchor:
+ event_id = EVENT_SYSTEM_SCROLLINGSTART;
+ break;
+ case AccessibilityNotificationSelectedChildrenChanged:
+ event_id = EVENT_OBJECT_SELECTIONWITHIN;
+ break;
+ case AccessibilityNotificationSelectedTextChanged:
+ event_id = IA2_EVENT_TEXT_CARET_MOVED;
+ break;
+ case AccessibilityNotificationTextChanged:
+ event_id = EVENT_OBJECT_NAMECHANGE;
+ break;
+ case AccessibilityNotificationTextInserted:
+ event_id = IA2_EVENT_TEXT_INSERTED;
+ break;
+ case AccessibilityNotificationTextRemoved:
+ event_id = IA2_EVENT_TEXT_REMOVED;
+ break;
+ case AccessibilityNotificationValueChanged:
+ event_id = EVENT_OBJECT_VALUECHANGE;
+ break;
+ default:
+ // Not all WebKit accessibility events result in a Windows
+ // accessibility notification.
+ break;
+ }
+
+ if (event_id != EVENT_MIN) {
+ // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
+ // the AT client will then call get_accChild on the HWND's accessibility
+ // object and pass it that same id, which we can use to retrieve the
+ // IAccessible for this node.
+ LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
+ MaybeCallNotifyWinEvent(event_id, child_id);
+ }
+
+ // If this is a layout complete notification (sent when a container scrolls)
+ // and there is a descendant tracked object, send a notification on it.
+ // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
+ if (type == AccessibilityNotificationLayoutComplete &&
+ tracked_scroll_object_ &&
+ tracked_scroll_object_->IsDescendantOf(node)) {
+ MaybeCallNotifyWinEvent(
+ IA2_EVENT_VISIBLE_DATA_CHANGED,
+ tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
+ tracked_scroll_object_->Release();
+ tracked_scroll_object_ = NULL;
+ }
+}
+
+void BrowserAccessibilityManagerWin::TrackScrollingObject(
+ BrowserAccessibilityWin* node) {
+ if (tracked_scroll_object_)
+ tracked_scroll_object_->Release();
+ tracked_scroll_object_ = node;
+ tracked_scroll_object_->AddRef();
+}
+
+BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
+ LONG unique_id_win) {
+ base::hash_map<LONG, int32>::iterator iter =
+ unique_id_to_renderer_id_map_.find(unique_id_win);
+ if (iter != unique_id_to_renderer_id_map_.end()) {
+ BrowserAccessibility* result = GetFromRendererID(iter->second);
+ if (result)
+ return result->ToBrowserAccessibilityWin();
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.h b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h
new file mode 100644
index 00000000000..7aec2e1c5e9
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_
+
+#include <oleacc.h>
+
+#include "base/win/scoped_comptr.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+
+namespace content {
+class BrowserAccessibilityWin;
+
+// Manages a tree of BrowserAccessibilityWin objects.
+class CONTENT_EXPORT BrowserAccessibilityManagerWin
+ : public BrowserAccessibilityManager {
+ public:
+ BrowserAccessibilityManagerWin(
+ HWND parent_hwnd,
+ IAccessible* parent_iaccessible,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ virtual ~BrowserAccessibilityManagerWin();
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // Get the closest containing HWND.
+ HWND parent_hwnd() { return parent_hwnd_; }
+
+ // The IAccessible for the parent window.
+ IAccessible* parent_iaccessible() { return parent_iaccessible_; }
+ void set_parent_iaccessible(IAccessible* parent_iaccessible) {
+ parent_iaccessible_ = parent_iaccessible;
+ }
+
+ // Calls NotifyWinEvent if the parent window's IAccessible pointer is known.
+ void MaybeCallNotifyWinEvent(DWORD event, LONG child_id);
+
+ // BrowserAccessibilityManager methods
+ virtual void AddNodeToMap(BrowserAccessibility* node);
+ virtual void RemoveNode(BrowserAccessibility* node) OVERRIDE;
+ virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node)
+ OVERRIDE;
+
+ // Track this object and post a VISIBLE_DATA_CHANGED notification when
+ // its container scrolls.
+ // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
+ void TrackScrollingObject(BrowserAccessibilityWin* node);
+
+ // Return a pointer to the object corresponding to the given windows-specific
+ // unique id, does not make a new reference.
+ BrowserAccessibilityWin* GetFromUniqueIdWin(LONG unique_id_win);
+
+ private:
+ // The closest ancestor HWND.
+ HWND parent_hwnd_;
+
+ // The accessibility instance for the parent window.
+ IAccessible* parent_iaccessible_;
+
+ // Give BrowserAccessibilityManager::Create access to our constructor.
+ friend class BrowserAccessibilityManager;
+
+ // Track the most recent object that has been asked to scroll and
+ // post a notification directly on it when it reaches its destination.
+ // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
+ BrowserAccessibilityWin* tracked_scroll_object_;
+
+ // A mapping from the Windows-specific unique IDs (unique within the
+ // browser process) to renderer ids within this page.
+ base::hash_map<long, int32> unique_id_to_renderer_id_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc
new file mode 100644
index 00000000000..befd717bc0c
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc
@@ -0,0 +1,147 @@
+// 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 "content/browser/accessibility/browser_accessibility_state_impl.h"
+
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "base/timer/timer.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "ui/gfx/sys_color_change_listener.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace content {
+
+// Update the accessibility histogram 45 seconds after initialization.
+static const int kAccessibilityHistogramDelaySecs = 45;
+
+// static
+BrowserAccessibilityState* BrowserAccessibilityState::GetInstance() {
+ return BrowserAccessibilityStateImpl::GetInstance();
+}
+
+// static
+BrowserAccessibilityStateImpl* BrowserAccessibilityStateImpl::GetInstance() {
+ return Singleton<BrowserAccessibilityStateImpl,
+ LeakySingletonTraits<BrowserAccessibilityStateImpl> >::get();
+}
+
+BrowserAccessibilityStateImpl::BrowserAccessibilityStateImpl()
+ : BrowserAccessibilityState(),
+ accessibility_mode_(AccessibilityModeOff) {
+#if defined(OS_WIN)
+ // On Windows 8, always enable accessibility for editable text controls
+ // so we can show the virtual keyboard when one is enabled.
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableRendererAccessibility)) {
+ accessibility_mode_ = AccessibilityModeEditableTextOnly;
+ }
+#endif // defined(OS_WIN)
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceRendererAccessibility)) {
+ accessibility_mode_ = AccessibilityModeComplete;
+ }
+
+#if defined(OS_WIN)
+ // On Windows, UpdateHistograms calls some system functions with unknown
+ // runtime, so call it on the file thread to ensure there's no jank.
+ // Everything in that method must be safe to call on another thread.
+ BrowserThread::ID update_histogram_thread = BrowserThread::FILE;
+#else
+ // On all other platforms, UpdateHistograms should be called on the main
+ // thread.
+ BrowserThread::ID update_histogram_thread = BrowserThread::UI;
+#endif
+
+ // We need to AddRef() the leaky singleton so that Bind doesn't
+ // delete it prematurely.
+ AddRef();
+ BrowserThread::PostDelayedTask(
+ update_histogram_thread, FROM_HERE,
+ base::Bind(&BrowserAccessibilityStateImpl::UpdateHistograms, this),
+ base::TimeDelta::FromSeconds(kAccessibilityHistogramDelaySecs));
+}
+
+BrowserAccessibilityStateImpl::~BrowserAccessibilityStateImpl() {
+}
+
+void BrowserAccessibilityStateImpl::OnScreenReaderDetected() {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableRendererAccessibility)) {
+ return;
+ }
+ SetAccessibilityMode(AccessibilityModeComplete);
+}
+
+void BrowserAccessibilityStateImpl::EnableAccessibility() {
+ // We may want to do something different with this later.
+ SetAccessibilityMode(AccessibilityModeComplete);
+}
+
+void BrowserAccessibilityStateImpl::DisableAccessibility() {
+ SetAccessibilityMode(AccessibilityModeOff);
+}
+
+bool BrowserAccessibilityStateImpl::IsAccessibleBrowser() {
+ return (accessibility_mode_ == AccessibilityModeComplete);
+}
+
+void BrowserAccessibilityStateImpl::AddHistogramCallback(
+ base::Closure callback) {
+ histogram_callbacks_.push_back(callback);
+}
+
+void BrowserAccessibilityStateImpl::UpdateHistogramsForTesting() {
+ UpdateHistograms();
+}
+
+void BrowserAccessibilityStateImpl::UpdateHistograms() {
+ UpdatePlatformSpecificHistograms();
+
+ for (size_t i = 0; i < histogram_callbacks_.size(); ++i)
+ histogram_callbacks_[i].Run();
+
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.State", IsAccessibleBrowser());
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.InvertedColors",
+ gfx::IsInvertedColorScheme());
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.ManuallyEnabled",
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceRendererAccessibility));
+}
+
+#if !defined(OS_WIN)
+void BrowserAccessibilityStateImpl::UpdatePlatformSpecificHistograms() {
+}
+#endif
+
+void BrowserAccessibilityStateImpl::SetAccessibilityMode(
+ AccessibilityMode mode) {
+ if (accessibility_mode_ == mode)
+ return;
+ accessibility_mode_ = mode;
+
+ // Iterate over all RenderWidgetHosts, even swapped out ones in case
+ // they become active again.
+ RenderWidgetHost::List widgets =
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ // Ignore processes that don't have a connection, such as crashed tabs.
+ if (!widgets[i]->GetProcess()->HasConnection())
+ continue;
+ if (!widgets[i]->IsRenderView())
+ continue;
+
+ RenderWidgetHostImpl::From(widgets[i])->SetAccessibilityMode(mode);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl.h b/chromium/content/browser/accessibility/browser_accessibility_state_impl.h
new file mode 100644
index 00000000000..3098341ef25
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "content/common/view_message_enums.h"
+#include "content/public/browser/browser_accessibility_state.h"
+
+namespace content {
+
+// The BrowserAccessibilityState class is used to determine if Chrome should be
+// customized for users with assistive technology, such as screen readers. We
+// modify the behavior of certain user interfaces to provide a better experience
+// for screen reader users. The way we detect a screen reader program is
+// different for each platform.
+//
+// Screen Reader Detection
+// (1) On windows many screen reader detection mechinisms will give false
+// positives like relying on the SPI_GETSCREENREADER system parameter. In Chrome
+// we attempt to dynamically detect a MSAA client screen reader by calling
+// NotifiyWinEvent in NativeWidgetWin with a custom ID and wait to see if the ID
+// is requested by a subsequent call to WM_GETOBJECT.
+// (2) On mac we detect dynamically if VoiceOver is running. We rely upon the
+// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set
+// when VoiceOver is launched and unset when VoiceOver is closed. This is an
+// improvement over reading defaults preference values (which has no callback
+// mechanism).
+class CONTENT_EXPORT BrowserAccessibilityStateImpl
+ : public base::RefCountedThreadSafe<BrowserAccessibilityStateImpl>,
+ public BrowserAccessibilityState {
+ public:
+ BrowserAccessibilityStateImpl();
+
+ static BrowserAccessibilityStateImpl* GetInstance();
+
+ virtual void EnableAccessibility() OVERRIDE;
+ virtual void DisableAccessibility() OVERRIDE;
+ virtual void OnScreenReaderDetected() OVERRIDE;
+ virtual bool IsAccessibleBrowser() OVERRIDE;
+ virtual void AddHistogramCallback(base::Closure callback) OVERRIDE;
+
+ virtual void UpdateHistogramsForTesting() OVERRIDE;
+
+ AccessibilityMode accessibility_mode() const { return accessibility_mode_; };
+ void SetAccessibilityMode(AccessibilityMode mode);
+
+ private:
+ friend class base::RefCountedThreadSafe<BrowserAccessibilityStateImpl>;
+ friend struct DefaultSingletonTraits<BrowserAccessibilityStateImpl>;
+
+ // Called a short while after startup to allow time for the accessibility
+ // state to be determined. Updates histograms with the current state.
+ void UpdateHistograms();
+
+ // Leaky singleton, destructor generally won't be called.
+ virtual ~BrowserAccessibilityStateImpl();
+
+ void UpdatePlatformSpecificHistograms();
+
+ AccessibilityMode accessibility_mode_;
+
+ std::vector<base::Closure> histogram_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityStateImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc b/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc
new file mode 100644
index 00000000000..824055f6e5d
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc
@@ -0,0 +1,77 @@
+// 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 "content/browser/accessibility/browser_accessibility_state_impl.h"
+
+#include <windows.h>
+#include <psapi.h>
+
+#include "base/files/file_path.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+
+namespace content {
+
+void BrowserAccessibilityStateImpl::UpdatePlatformSpecificHistograms() {
+ // NOTE: this method is run from the file thread to reduce jank, since
+ // there's no guarantee these system calls will return quickly. Be careful
+ // not to add any code that isn't safe to run from a non-main thread!
+
+ AUDIODESCRIPTION audio_description = {0};
+ audio_description.cbSize = sizeof(AUDIODESCRIPTION);
+ SystemParametersInfo(SPI_GETAUDIODESCRIPTION, 0, &audio_description, 0);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinAudioDescription",
+ !!audio_description.Enabled);
+
+ BOOL win_screen_reader = FALSE;
+ SystemParametersInfo(SPI_GETSCREENREADER, 0, &win_screen_reader, 0);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinScreenReader",
+ !!win_screen_reader);
+
+ STICKYKEYS sticky_keys = {0};
+ sticky_keys.cbSize = sizeof(STICKYKEYS);
+ SystemParametersInfo(SPI_GETSTICKYKEYS, 0, &sticky_keys, 0);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinStickyKeys",
+ 0 != (sticky_keys.dwFlags & SKF_STICKYKEYSON));
+
+ // Get the file paths of all DLLs loaded.
+ HANDLE process = GetCurrentProcess();
+ HMODULE* modules = NULL;
+ DWORD bytes_required;
+ if (!EnumProcessModules(process, modules, 0, &bytes_required))
+ return;
+
+ scoped_ptr<char[]> buffer(new char[bytes_required]);
+ modules = reinterpret_cast<HMODULE*>(buffer.get());
+ DWORD ignore;
+ if (!EnumProcessModules(process, modules, bytes_required, &ignore))
+ return;
+
+ // Look for DLLs of assistive technology known to work with Chrome.
+ bool jaws = false;
+ bool nvda = false;
+ bool satogo = false;
+ bool zoomtext = false;
+ size_t module_count = bytes_required / sizeof(HMODULE);
+ for (size_t i = 0; i < module_count; i++) {
+ TCHAR filename[MAX_PATH];
+ GetModuleFileName(modules[i], filename, sizeof(filename));
+ string16 module_name(base::FilePath(filename).BaseName().value());
+ if (LowerCaseEqualsASCII(module_name, "fsdomsrv.dll"))
+ jaws = true;
+ if (LowerCaseEqualsASCII(module_name, "vbufbackend_gecko_ia2.dll"))
+ nvda = true;
+ if (LowerCaseEqualsASCII(module_name, "stsaw32.dll"))
+ satogo = true;
+ if (LowerCaseEqualsASCII(module_name, "zslhook.dll"))
+ zoomtext = true;
+ }
+
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinJAWS", jaws);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinNVDA", nvda);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinSAToGo", satogo);
+ UMA_HISTOGRAM_BOOLEAN("Accessibility.WinZoomText", zoomtext);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.cc b/chromium/content/browser/accessibility/browser_accessibility_win.cc
new file mode 100644
index 00000000000..792adc4440d
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_win.cc
@@ -0,0 +1,3626 @@
+// 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 "content/browser/accessibility/browser_accessibility_win.h"
+
+#include <UIAutomationClient.h>
+#include <UIAutomationCoreApi.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/enum_variant.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/windows_version.h"
+#include "content/browser/accessibility/browser_accessibility_manager_win.h"
+#include "content/common/accessibility_messages.h"
+#include "content/public/common/content_client.h"
+#include "ui/base/accessibility/accessible_text_utils.h"
+#include "ui/base/win/accessibility_ids_win.h"
+#include "ui/base/win/accessibility_misc_utils.h"
+
+namespace content {
+
+// These nonstandard GUIDs are taken directly from the Mozilla sources
+// (accessible/src/msaa/nsAccessNodeWrap.cpp); some documentation is here:
+// http://developer.mozilla.org/en/Accessibility/AT-APIs/ImplementationFeatures/MSAA
+const GUID GUID_ISimpleDOM = {
+ 0x0c539790, 0x12e4, 0x11cf,
+ 0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8};
+const GUID GUID_IAccessibleContentDocument = {
+ 0xa5d8e1f3, 0x3571, 0x4d8f,
+ 0x95, 0x21, 0x07, 0xed, 0x28, 0xfb, 0x07, 0x2e};
+
+const char16 BrowserAccessibilityWin::kEmbeddedCharacter[] = L"\xfffc";
+
+// static
+LONG BrowserAccessibilityWin::next_unique_id_win_ =
+ base::win::kFirstBrowserAccessibilityManagerAccessibilityId;
+
+//
+// BrowserAccessibilityRelation
+//
+// A simple implementation of IAccessibleRelation, used to represent
+// a relationship between two accessible nodes in the tree.
+//
+
+class BrowserAccessibilityRelation
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public IAccessibleRelation {
+ BEGIN_COM_MAP(BrowserAccessibilityRelation)
+ COM_INTERFACE_ENTRY(IAccessibleRelation)
+ END_COM_MAP()
+
+ CONTENT_EXPORT BrowserAccessibilityRelation() {}
+ CONTENT_EXPORT virtual ~BrowserAccessibilityRelation() {}
+
+ CONTENT_EXPORT void Initialize(BrowserAccessibilityWin* owner,
+ const string16& type);
+ CONTENT_EXPORT void AddTarget(int target_id);
+
+ // IAccessibleRelation methods.
+ CONTENT_EXPORT STDMETHODIMP get_relationType(BSTR* relation_type);
+ CONTENT_EXPORT STDMETHODIMP get_nTargets(long* n_targets);
+ CONTENT_EXPORT STDMETHODIMP get_target(long target_index, IUnknown** target);
+ CONTENT_EXPORT STDMETHODIMP get_targets(long max_targets,
+ IUnknown** targets,
+ long* n_targets);
+
+ // IAccessibleRelation methods not implemented.
+ CONTENT_EXPORT STDMETHODIMP get_localizedRelationType(BSTR* relation_type) {
+ return E_NOTIMPL;
+ }
+
+ private:
+ string16 type_;
+ base::win::ScopedComPtr<BrowserAccessibilityWin> owner_;
+ std::vector<int> target_ids_;
+};
+
+void BrowserAccessibilityRelation::Initialize(BrowserAccessibilityWin* owner,
+ const string16& type) {
+ owner_ = owner;
+ type_ = type;
+}
+
+void BrowserAccessibilityRelation::AddTarget(int target_id) {
+ target_ids_.push_back(target_id);
+}
+
+STDMETHODIMP BrowserAccessibilityRelation::get_relationType(
+ BSTR* relation_type) {
+ if (!relation_type)
+ return E_INVALIDARG;
+
+ if (!owner_->instance_active())
+ return E_FAIL;
+
+ *relation_type = SysAllocString(type_.c_str());
+ DCHECK(*relation_type);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityRelation::get_nTargets(long* n_targets) {
+ if (!n_targets)
+ return E_INVALIDARG;
+
+ if (!owner_->instance_active())
+ return E_FAIL;
+
+ *n_targets = static_cast<long>(target_ids_.size());
+
+ BrowserAccessibilityManager* manager = owner_->manager();
+ for (long i = *n_targets - 1; i >= 0; --i) {
+ BrowserAccessibility* result = manager->GetFromRendererID(target_ids_[i]);
+ if (!result || !result->instance_active()) {
+ *n_targets = 0;
+ break;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityRelation::get_target(long target_index,
+ IUnknown** target) {
+ if (!target)
+ return E_INVALIDARG;
+
+ if (!owner_->instance_active())
+ return E_FAIL;
+
+ if (target_index < 0 ||
+ target_index >= static_cast<long>(target_ids_.size())) {
+ return E_INVALIDARG;
+ }
+
+ BrowserAccessibilityManager* manager = owner_->manager();
+ BrowserAccessibility* result =
+ manager->GetFromRendererID(target_ids_[target_index]);
+ if (!result || !result->instance_active())
+ return E_FAIL;
+
+ *target = static_cast<IAccessible*>(
+ result->ToBrowserAccessibilityWin()->NewReference());
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityRelation::get_targets(long max_targets,
+ IUnknown** targets,
+ long* n_targets) {
+ if (!targets || !n_targets)
+ return E_INVALIDARG;
+
+ if (!owner_->instance_active())
+ return E_FAIL;
+
+ long count = static_cast<long>(target_ids_.size());
+ if (count > max_targets)
+ count = max_targets;
+
+ *n_targets = count;
+ if (count == 0)
+ return S_FALSE;
+
+ for (long i = 0; i < count; ++i) {
+ HRESULT result = get_target(i, &targets[i]);
+ if (result != S_OK)
+ return result;
+ }
+
+ return S_OK;
+}
+
+//
+// BrowserAccessibilityWin
+//
+
+// static
+BrowserAccessibility* BrowserAccessibility::Create() {
+ CComObject<BrowserAccessibilityWin>* instance;
+ HRESULT hr = CComObject<BrowserAccessibilityWin>::CreateInstance(&instance);
+ DCHECK(SUCCEEDED(hr));
+ return instance->NewReference();
+}
+
+BrowserAccessibilityWin* BrowserAccessibility::ToBrowserAccessibilityWin() {
+ return static_cast<BrowserAccessibilityWin*>(this);
+}
+
+BrowserAccessibilityWin::BrowserAccessibilityWin()
+ : ia_role_(0),
+ ia_state_(0),
+ ia2_role_(0),
+ ia2_state_(0),
+ first_time_(true),
+ old_ia_state_(0) {
+ // Start unique IDs at -1 and decrement each time, because get_accChild
+ // uses positive IDs to enumerate children, so we use negative IDs to
+ // clearly distinguish between indices and unique IDs.
+ unique_id_win_ = next_unique_id_win_;
+ if (next_unique_id_win_ ==
+ base::win::kLastBrowserAccessibilityManagerAccessibilityId) {
+ next_unique_id_win_ =
+ base::win::kFirstBrowserAccessibilityManagerAccessibilityId;
+ }
+ next_unique_id_win_--;
+}
+
+BrowserAccessibilityWin::~BrowserAccessibilityWin() {
+ for (size_t i = 0; i < relations_.size(); ++i)
+ relations_[i]->Release();
+}
+
+//
+// IAccessible methods.
+//
+// Conventions:
+// * Always test for instance_active_ first and return E_FAIL if it's false.
+// * Always check for invalid arguments first, even if they're unused.
+// * Return S_FALSE if the only output is a string argument and it's empty.
+//
+
+HRESULT BrowserAccessibilityWin::accDoDefaultAction(VARIANT var_id) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ manager_->DoDefaultAction(*target);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::accHitTest(LONG x_left,
+ LONG y_top,
+ VARIANT* child) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!child)
+ return E_INVALIDARG;
+
+ gfx::Point point(x_left, y_top);
+ if (!GetGlobalBoundsRect().Contains(point)) {
+ // Return S_FALSE and VT_EMPTY when the outside the object's boundaries.
+ child->vt = VT_EMPTY;
+ return S_FALSE;
+ }
+
+ BrowserAccessibility* result = BrowserAccessibilityForPoint(point);
+ if (result == this) {
+ // Point is within this object.
+ child->vt = VT_I4;
+ child->lVal = CHILDID_SELF;
+ } else {
+ child->vt = VT_DISPATCH;
+ child->pdispVal = result->ToBrowserAccessibilityWin()->NewReference();
+ }
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::accLocation(LONG* x_left,
+ LONG* y_top,
+ LONG* width,
+ LONG* height,
+ VARIANT var_id) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!x_left || !y_top || !width || !height)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ gfx::Rect bounds = target->GetGlobalBoundsRect();
+ *x_left = bounds.x();
+ *y_top = bounds.y();
+ *width = bounds.width();
+ *height = bounds.height();
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::accNavigate(LONG nav_dir,
+ VARIANT start,
+ VARIANT* end) {
+ BrowserAccessibilityWin* target = GetTargetFromChildID(start);
+ if (!target)
+ return E_INVALIDARG;
+
+ if ((nav_dir == NAVDIR_LASTCHILD || nav_dir == NAVDIR_FIRSTCHILD) &&
+ start.lVal != CHILDID_SELF) {
+ // MSAA states that navigating to first/last child can only be from self.
+ return E_INVALIDARG;
+ }
+
+ BrowserAccessibility* result = NULL;
+ switch (nav_dir) {
+ case NAVDIR_DOWN:
+ case NAVDIR_UP:
+ case NAVDIR_LEFT:
+ case NAVDIR_RIGHT:
+ // These directions are not implemented, matching Mozilla and IE.
+ return E_NOTIMPL;
+ case NAVDIR_FIRSTCHILD:
+ if (!target->children_.empty())
+ result = target->children_.front();
+ break;
+ case NAVDIR_LASTCHILD:
+ if (!target->children_.empty())
+ result = target->children_.back();
+ break;
+ case NAVDIR_NEXT:
+ result = target->GetNextSibling();
+ break;
+ case NAVDIR_PREVIOUS:
+ result = target->GetPreviousSibling();
+ break;
+ }
+
+ if (!result) {
+ end->vt = VT_EMPTY;
+ return S_FALSE;
+ }
+
+ end->vt = VT_DISPATCH;
+ end->pdispVal = result->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accChild(VARIANT var_child,
+ IDispatch** disp_child) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!disp_child)
+ return E_INVALIDARG;
+
+ *disp_child = NULL;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_child);
+ if (!target)
+ return E_INVALIDARG;
+
+ (*disp_child) = target->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accChildCount(LONG* child_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!child_count)
+ return E_INVALIDARG;
+
+ *child_count = children_.size();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accDefaultAction(VARIANT var_id,
+ BSTR* def_action) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!def_action)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ return target->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_SHORTCUT, def_action);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accDescription(VARIANT var_id,
+ BSTR* desc) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!desc)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ return target->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DESCRIPTION, desc);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accFocus(VARIANT* focus_child) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!focus_child)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* focus = static_cast<BrowserAccessibilityWin*>(
+ manager_->GetFocus(this));
+ if (focus == this) {
+ focus_child->vt = VT_I4;
+ focus_child->lVal = CHILDID_SELF;
+ } else if (focus == NULL) {
+ focus_child->vt = VT_EMPTY;
+ } else {
+ focus_child->vt = VT_DISPATCH;
+ focus_child->pdispVal = focus->NewReference();
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accHelp(VARIANT var_id, BSTR* help) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!help)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ return target->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_HELP, help);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accKeyboardShortcut(VARIANT var_id,
+ BSTR* acc_key) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!acc_key)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ return target->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_SHORTCUT, acc_key);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accName(VARIANT var_id, BSTR* name) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!name)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ string16 name_str = target->name_;
+
+ // If the name is empty, see if it's labeled by another element.
+ if (name_str.empty()) {
+ int title_elem_id;
+ if (target->GetIntAttribute(AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT,
+ &title_elem_id)) {
+ BrowserAccessibility* title_elem =
+ manager_->GetFromRendererID(title_elem_id);
+ if (title_elem)
+ name_str = title_elem->GetTextRecursive();
+ }
+ }
+
+ if (name_str.empty())
+ return S_FALSE;
+
+ *name = SysAllocString(name_str.c_str());
+
+ DCHECK(*name);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accParent(IDispatch** disp_parent) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!disp_parent)
+ return E_INVALIDARG;
+
+ IAccessible* parent = parent_->ToBrowserAccessibilityWin();
+ if (parent == NULL) {
+ // This happens if we're the root of the tree;
+ // return the IAccessible for the window.
+ parent = manager_->ToBrowserAccessibilityManagerWin()->parent_iaccessible();
+ // |parent| can only be NULL if the manager was created before the parent
+ // IAccessible was known and it wasn't subsequently set before a client
+ // requested it. Crash hard if this happens so that we get crash reports.
+ CHECK(parent);
+ }
+
+ parent->AddRef();
+ *disp_parent = parent;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accRole(VARIANT var_id,
+ VARIANT* role) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!role)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ if (!target->role_name_.empty()) {
+ role->vt = VT_BSTR;
+ role->bstrVal = SysAllocString(target->role_name_.c_str());
+ } else {
+ role->vt = VT_I4;
+ role->lVal = target->ia_role_;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accState(VARIANT var_id,
+ VARIANT* state) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!state)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ state->vt = VT_I4;
+ state->lVal = target->ia_state_;
+ if (manager_->GetFocus(NULL) == this)
+ state->lVal |= STATE_SYSTEM_FOCUSED;
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accValue(VARIANT var_id,
+ BSTR* value) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!value)
+ return E_INVALIDARG;
+
+ BrowserAccessibilityWin* target = GetTargetFromChildID(var_id);
+ if (!target)
+ return E_INVALIDARG;
+
+ *value = SysAllocString(target->value_.c_str());
+
+ DCHECK(*value);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accHelpTopic(BSTR* help_file,
+ VARIANT var_id,
+ LONG* topic_id) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_accSelection(VARIANT* selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (role_ != AccessibilityNodeData::ROLE_LISTBOX)
+ return E_NOTIMPL;
+
+ unsigned long selected_count = 0;
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED))
+ ++selected_count;
+ }
+
+ if (selected_count == 0) {
+ selected->vt = VT_EMPTY;
+ return S_OK;
+ }
+
+ if (selected_count == 1) {
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED)) {
+ selected->vt = VT_DISPATCH;
+ selected->pdispVal =
+ children_[i]->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+ }
+ }
+ }
+
+ // Multiple items are selected.
+ base::win::EnumVariant* enum_variant =
+ new base::win::EnumVariant(selected_count);
+ enum_variant->AddRef();
+ unsigned long index = 0;
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED)) {
+ enum_variant->ItemAt(index)->vt = VT_DISPATCH;
+ enum_variant->ItemAt(index)->pdispVal =
+ children_[i]->ToBrowserAccessibilityWin()->NewReference();
+ ++index;
+ }
+ }
+ selected->vt = VT_UNKNOWN;
+ selected->punkVal = static_cast<IUnknown*>(
+ static_cast<base::win::IUnknownImpl*>(enum_variant));
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::accSelect(
+ LONG flags_sel, VARIANT var_id) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (flags_sel & SELFLAG_TAKEFOCUS) {
+ manager_->SetFocus(this, true);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+//
+// IAccessible2 methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::role(LONG* role) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!role)
+ return E_INVALIDARG;
+
+ *role = ia2_role_;
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_attributes(BSTR* attributes) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!attributes)
+ return E_INVALIDARG;
+
+ // The iaccessible2 attributes are a set of key-value pairs
+ // separated by semicolons, with a colon between the key and the value.
+ string16 str;
+ for (unsigned int i = 0; i < ia2_attributes_.size(); ++i) {
+ if (i != 0)
+ str += L';';
+ str += ia2_attributes_[i];
+ }
+
+ if (str.empty())
+ return S_FALSE;
+
+ *attributes = SysAllocString(str.c_str());
+ DCHECK(*attributes);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_states(AccessibleStates* states) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!states)
+ return E_INVALIDARG;
+
+ *states = ia2_state_;
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_uniqueID(LONG* unique_id) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!unique_id)
+ return E_INVALIDARG;
+
+ *unique_id = unique_id_win_;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_windowHandle(HWND* window_handle) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!window_handle)
+ return E_INVALIDARG;
+
+ *window_handle = manager_->ToBrowserAccessibilityManagerWin()->parent_hwnd();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_indexInParent(LONG* index_in_parent) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!index_in_parent)
+ return E_INVALIDARG;
+
+ *index_in_parent = index_in_parent_;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nRelations(LONG* n_relations) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_relations)
+ return E_INVALIDARG;
+
+ *n_relations = relations_.size();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_relation(
+ LONG relation_index,
+ IAccessibleRelation** relation) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (relation_index < 0 ||
+ relation_index >= static_cast<long>(relations_.size())) {
+ return E_INVALIDARG;
+ }
+
+ if (!relation)
+ return E_INVALIDARG;
+
+ relations_[relation_index]->AddRef();
+ *relation = relations_[relation_index];
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_relations(
+ LONG max_relations,
+ IAccessibleRelation** relations,
+ LONG* n_relations) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!relations || !n_relations)
+ return E_INVALIDARG;
+
+ long count = static_cast<long>(relations_.size());
+ *n_relations = count;
+ if (count == 0)
+ return S_FALSE;
+
+ for (long i = 0; i < count; ++i) {
+ relations_[i]->AddRef();
+ relations[i] = relations_[i];
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::scrollTo(enum IA2ScrollType scroll_type) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ gfx::Rect r = location_;
+ switch(scroll_type) {
+ case IA2_SCROLL_TYPE_TOP_LEFT:
+ manager_->ScrollToMakeVisible(*this, gfx::Rect(r.x(), r.y(), 0, 0));
+ break;
+ case IA2_SCROLL_TYPE_BOTTOM_RIGHT:
+ manager_->ScrollToMakeVisible(
+ *this, gfx::Rect(r.right(), r.bottom(), 0, 0));
+ break;
+ case IA2_SCROLL_TYPE_TOP_EDGE:
+ manager_->ScrollToMakeVisible(
+ *this, gfx::Rect(r.x(), r.y(), r.width(), 0));
+ break;
+ case IA2_SCROLL_TYPE_BOTTOM_EDGE:
+ manager_->ScrollToMakeVisible(
+ *this, gfx::Rect(r.x(), r.bottom(), r.width(), 0));
+ break;
+ case IA2_SCROLL_TYPE_LEFT_EDGE:
+ manager_->ScrollToMakeVisible(
+ *this, gfx::Rect(r.x(), r.y(), 0, r.height()));
+ break;
+ case IA2_SCROLL_TYPE_RIGHT_EDGE:
+ manager_->ScrollToMakeVisible(
+ *this, gfx::Rect(r.right(), r.y(), 0, r.height()));
+ break;
+ case IA2_SCROLL_TYPE_ANYWHERE:
+ default:
+ manager_->ScrollToMakeVisible(*this, r);
+ break;
+ }
+
+ static_cast<BrowserAccessibilityManagerWin*>(manager_)
+ ->TrackScrollingObject(this);
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::scrollToPoint(
+ enum IA2CoordinateType coordinate_type,
+ LONG x,
+ LONG y) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ gfx::Point scroll_to(x, y);
+
+ if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) {
+ scroll_to -= manager_->GetViewBounds().OffsetFromOrigin();
+ } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) {
+ if (parent_)
+ scroll_to += parent_->location().OffsetFromOrigin();
+ } else {
+ return E_INVALIDARG;
+ }
+
+ manager_->ScrollToPoint(*this, scroll_to);
+
+ static_cast<BrowserAccessibilityManagerWin*>(manager_)
+ ->TrackScrollingObject(this);
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_groupPosition(
+ LONG* group_level,
+ LONG* similar_items_in_group,
+ LONG* position_in_group) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!group_level || !similar_items_in_group || !position_in_group)
+ return E_INVALIDARG;
+
+ if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION &&
+ parent_ &&
+ parent_->role() == AccessibilityNodeData::ROLE_LISTBOX) {
+ *group_level = 0;
+ *similar_items_in_group = parent_->child_count();
+ *position_in_group = index_in_parent_ + 1;
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+//
+// IAccessibleApplication methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_appName(BSTR* app_name) {
+ // No need to check |instance_active_| because this interface is
+ // global, and doesn't depend on any local state.
+
+ if (!app_name)
+ return E_INVALIDARG;
+
+ // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
+ // the part before the "/".
+ std::vector<std::string> product_components;
+ base::SplitString(GetContentClient()->GetProduct(), '/', &product_components);
+ DCHECK_EQ(2U, product_components.size());
+ if (product_components.size() != 2)
+ return E_FAIL;
+ *app_name = SysAllocString(UTF8ToUTF16(product_components[0]).c_str());
+ DCHECK(*app_name);
+ return *app_name ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_appVersion(BSTR* app_version) {
+ // No need to check |instance_active_| because this interface is
+ // global, and doesn't depend on any local state.
+
+ if (!app_version)
+ return E_INVALIDARG;
+
+ // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
+ // the part after the "/".
+ std::vector<std::string> product_components;
+ base::SplitString(GetContentClient()->GetProduct(), '/', &product_components);
+ DCHECK_EQ(2U, product_components.size());
+ if (product_components.size() != 2)
+ return E_FAIL;
+ *app_version = SysAllocString(UTF8ToUTF16(product_components[1]).c_str());
+ DCHECK(*app_version);
+ return *app_version ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_toolkitName(BSTR* toolkit_name) {
+ // No need to check |instance_active_| because this interface is
+ // global, and doesn't depend on any local state.
+
+ if (!toolkit_name)
+ return E_INVALIDARG;
+
+ // This is hard-coded; all products based on the Chromium engine
+ // will have the same toolkit name, so that assistive technology can
+ // detect any Chrome-based product.
+ *toolkit_name = SysAllocString(L"Chrome");
+ DCHECK(*toolkit_name);
+ return *toolkit_name ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_toolkitVersion(
+ BSTR* toolkit_version) {
+ // No need to check |instance_active_| because this interface is
+ // global, and doesn't depend on any local state.
+
+ if (!toolkit_version)
+ return E_INVALIDARG;
+
+ std::string user_agent = GetContentClient()->GetUserAgent();
+ *toolkit_version = SysAllocString(UTF8ToUTF16(user_agent).c_str());
+ DCHECK(*toolkit_version);
+ return *toolkit_version ? S_OK : E_FAIL;
+}
+
+//
+// IAccessibleImage methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_description(BSTR* desc) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!desc)
+ return E_INVALIDARG;
+
+ return GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DESCRIPTION, desc);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_imagePosition(
+ enum IA2CoordinateType coordinate_type,
+ LONG* x,
+ LONG* y) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!x || !y)
+ return E_INVALIDARG;
+
+ if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) {
+ HWND parent_hwnd =
+ manager_->ToBrowserAccessibilityManagerWin()->parent_hwnd();
+ POINT top_left = {0, 0};
+ ::ClientToScreen(parent_hwnd, &top_left);
+ *x = location_.x() + top_left.x;
+ *y = location_.y() + top_left.y;
+ } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) {
+ *x = location_.x();
+ *y = location_.y();
+ if (parent_) {
+ *x -= parent_->location().x();
+ *y -= parent_->location().y();
+ }
+ } else {
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_imageSize(LONG* height, LONG* width) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!height || !width)
+ return E_INVALIDARG;
+
+ *height = location_.height();
+ *width = location_.width();
+ return S_OK;
+}
+
+//
+// IAccessibleTable methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_accessibleAt(
+ long row,
+ long column,
+ IUnknown** accessible) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!accessible)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (row < 0 || row >= rows || column < 0 || column >= columns)
+ return E_INVALIDARG;
+
+ DCHECK_EQ(columns * rows, static_cast<int>(cell_ids_.size()));
+
+ int cell_id = cell_ids_[row * columns + column];
+ BrowserAccessibilityWin* cell = GetFromRendererID(cell_id);
+ if (cell) {
+ *accessible = static_cast<IAccessible*>(cell->NewReference());
+ return S_OK;
+ }
+
+ *accessible = NULL;
+ return E_INVALIDARG;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_caption(IUnknown** accessible) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!accessible)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): implement
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_childIndex(long row,
+ long column,
+ long* cell_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!cell_index)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (row < 0 || row >= rows || column < 0 || column >= columns)
+ return E_INVALIDARG;
+
+ DCHECK_EQ(columns * rows, static_cast<int>(cell_ids_.size()));
+ int cell_id = cell_ids_[row * columns + column];
+ for (size_t i = 0; i < unique_cell_ids_.size(); ++i) {
+ if (unique_cell_ids_[i] == cell_id) {
+ *cell_index = (long)i;
+ return S_OK;
+ }
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnDescription(long column,
+ BSTR* description) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!description)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (column < 0 || column >= columns)
+ return E_INVALIDARG;
+
+ for (int i = 0; i < rows; ++i) {
+ int cell_id = cell_ids_[i * columns + column];
+ BrowserAccessibilityWin* cell = static_cast<BrowserAccessibilityWin*>(
+ manager_->GetFromRendererID(cell_id));
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER) {
+ if (cell->name_.size() > 0) {
+ *description = SysAllocString(cell->name_.c_str());
+ return S_OK;
+ }
+
+ return cell->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DESCRIPTION, description);
+ }
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnExtentAt(
+ long row,
+ long column,
+ long* n_columns_spanned) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_columns_spanned)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (row < 0 || row >= rows || column < 0 || column >= columns)
+ return E_INVALIDARG;
+
+ int cell_id = cell_ids_[row * columns + column];
+ BrowserAccessibilityWin* cell = static_cast<BrowserAccessibilityWin*>(
+ manager_->GetFromRendererID(cell_id));
+ int colspan;
+ if (cell &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) &&
+ colspan >= 1) {
+ *n_columns_spanned = colspan;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnHeader(
+ IAccessibleTable** accessible_table,
+ long* starting_row_index) {
+ // TODO(dmazzoni): implement
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnIndex(long cell_index,
+ long* column_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!column_index)
+ return E_INVALIDARG;
+
+ int cell_id_count = static_cast<int>(unique_cell_ids_.size());
+ if (cell_index < 0)
+ return E_INVALIDARG;
+ if (cell_index >= cell_id_count)
+ return S_FALSE;
+
+ int cell_id = unique_cell_ids_[cell_index];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ int col_index;
+ if (cell &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &col_index)) {
+ *column_index = col_index;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nColumns(long* column_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!column_count)
+ return E_INVALIDARG;
+
+ int columns;
+ if (GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns)) {
+ *column_count = columns;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nRows(long* row_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row_count)
+ return E_INVALIDARG;
+
+ int rows;
+ if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) {
+ *row_count = rows;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nSelectedChildren(long* cell_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!cell_count)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): add support for selected cells/rows/columns in tables.
+ *cell_count = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nSelectedColumns(long* column_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!column_count)
+ return E_INVALIDARG;
+
+ *column_count = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nSelectedRows(long* row_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row_count)
+ return E_INVALIDARG;
+
+ *row_count = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowDescription(long row,
+ BSTR* description) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!description)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (row < 0 || row >= rows)
+ return E_INVALIDARG;
+
+ for (int i = 0; i < columns; ++i) {
+ int cell_id = cell_ids_[row * columns + i];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER) {
+ if (cell->name_.size() > 0) {
+ *description = SysAllocString(cell->name_.c_str());
+ return S_OK;
+ }
+
+ return cell->GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DESCRIPTION, description);
+ }
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowExtentAt(long row,
+ long column,
+ long* n_rows_spanned) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_rows_spanned)
+ return E_INVALIDARG;
+
+ int columns;
+ int rows;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) ||
+ columns <= 0 ||
+ rows <= 0) {
+ return S_FALSE;
+ }
+
+ if (row < 0 || row >= rows || column < 0 || column >= columns)
+ return E_INVALIDARG;
+
+ int cell_id = cell_ids_[row * columns + column];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ int rowspan;
+ if (cell &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) &&
+ rowspan >= 1) {
+ *n_rows_spanned = rowspan;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowHeader(
+ IAccessibleTable** accessible_table,
+ long* starting_column_index) {
+ // TODO(dmazzoni): implement
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowIndex(long cell_index,
+ long* row_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row_index)
+ return E_INVALIDARG;
+
+ int cell_id_count = static_cast<int>(unique_cell_ids_.size());
+ if (cell_index < 0)
+ return E_INVALIDARG;
+ if (cell_index >= cell_id_count)
+ return S_FALSE;
+
+ int cell_id = unique_cell_ids_[cell_index];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ int cell_row_index;
+ if (cell &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &cell_row_index)) {
+ *row_index = cell_row_index;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedChildren(long max_children,
+ long** children,
+ long* n_children) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!children || !n_children)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_children = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedColumns(long max_columns,
+ long** columns,
+ long* n_columns) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!columns || !n_columns)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_columns = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedRows(long max_rows,
+ long** rows,
+ long* n_rows) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!rows || !n_rows)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_rows = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_summary(IUnknown** accessible) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!accessible)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): implement
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_isColumnSelected(
+ long column,
+ boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!is_selected)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *is_selected = false;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_isRowSelected(long row,
+ boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!is_selected)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *is_selected = false;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_isSelected(long row,
+ long column,
+ boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!is_selected)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *is_selected = false;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowColumnExtentsAtIndex(
+ long index,
+ long* row,
+ long* column,
+ long* row_extents,
+ long* column_extents,
+ boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row || !column || !row_extents || !column_extents || !is_selected)
+ return E_INVALIDARG;
+
+ int cell_id_count = static_cast<int>(unique_cell_ids_.size());
+ if (index < 0)
+ return E_INVALIDARG;
+ if (index >= cell_id_count)
+ return S_FALSE;
+
+ int cell_id = unique_cell_ids_[index];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ int rowspan;
+ int colspan;
+ if (cell &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) &&
+ cell->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) &&
+ rowspan >= 1 &&
+ colspan >= 1) {
+ *row_extents = rowspan;
+ *column_extents = colspan;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+//
+// IAccessibleTable2 methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_cellAt(long row,
+ long column,
+ IUnknown** cell) {
+ return get_accessibleAt(row, column, cell);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nSelectedCells(long* cell_count) {
+ return get_nSelectedChildren(cell_count);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedCells(
+ IUnknown*** cells,
+ long* n_selected_cells) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!cells || !n_selected_cells)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_selected_cells = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedColumns(long** columns,
+ long* n_columns) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!columns || !n_columns)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_columns = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selectedRows(long** rows,
+ long* n_rows) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!rows || !n_rows)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): Implement this.
+ *n_rows = 0;
+ return S_OK;
+}
+
+
+//
+// IAccessibleTableCell methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnExtent(
+ long* n_columns_spanned) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_columns_spanned)
+ return E_INVALIDARG;
+
+ int colspan;
+ if (GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) &&
+ colspan >= 1) {
+ *n_columns_spanned = colspan;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnHeaderCells(
+ IUnknown*** cell_accessibles,
+ long* n_column_header_cells) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!cell_accessibles || !n_column_header_cells)
+ return E_INVALIDARG;
+
+ *n_column_header_cells = 0;
+
+ int column;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column)) {
+ return S_FALSE;
+ }
+
+ BrowserAccessibility* table = parent();
+ while (table && table->role() != AccessibilityNodeData::ROLE_TABLE)
+ table = table->parent();
+ if (!table) {
+ NOTREACHED();
+ return S_FALSE;
+ }
+
+ int columns;
+ int rows;
+ if (!table->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !table->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) {
+ return S_FALSE;
+ }
+ if (columns <= 0 || rows <= 0 || column < 0 || column >= columns)
+ return S_FALSE;
+
+ for (int i = 0; i < rows; ++i) {
+ int cell_id = table->cell_ids()[i * columns + column];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER)
+ (*n_column_header_cells)++;
+ }
+
+ *cell_accessibles = static_cast<IUnknown**>(CoTaskMemAlloc(
+ (*n_column_header_cells) * sizeof(cell_accessibles[0])));
+ int index = 0;
+ for (int i = 0; i < rows; ++i) {
+ int cell_id = table->cell_ids()[i * columns + column];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER) {
+ (*cell_accessibles)[index] =
+ static_cast<IAccessible*>(cell->NewReference());
+ ++index;
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_columnIndex(long* column_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!column_index)
+ return E_INVALIDARG;
+
+ int column;
+ if (GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column)) {
+ *column_index = column;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowExtent(long* n_rows_spanned) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_rows_spanned)
+ return E_INVALIDARG;
+
+ int rowspan;
+ if (GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) &&
+ rowspan >= 1) {
+ *n_rows_spanned = rowspan;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowHeaderCells(
+ IUnknown*** cell_accessibles,
+ long* n_row_header_cells) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!cell_accessibles || !n_row_header_cells)
+ return E_INVALIDARG;
+
+ *n_row_header_cells = 0;
+
+ int row;
+ if (!GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row)) {
+ return S_FALSE;
+ }
+
+ BrowserAccessibility* table = parent();
+ while (table && table->role() != AccessibilityNodeData::ROLE_TABLE)
+ table = table->parent();
+ if (!table) {
+ NOTREACHED();
+ return S_FALSE;
+ }
+
+ int columns;
+ int rows;
+ if (!table->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) ||
+ !table->GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) {
+ return S_FALSE;
+ }
+ if (columns <= 0 || rows <= 0 || row < 0 || row >= rows)
+ return S_FALSE;
+
+ for (int i = 0; i < columns; ++i) {
+ int cell_id = table->cell_ids()[row * columns + i];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER)
+ (*n_row_header_cells)++;
+ }
+
+ *cell_accessibles = static_cast<IUnknown**>(CoTaskMemAlloc(
+ (*n_row_header_cells) * sizeof(cell_accessibles[0])));
+ int index = 0;
+ for (int i = 0; i < columns; ++i) {
+ int cell_id = table->cell_ids()[row * columns + i];
+ BrowserAccessibilityWin* cell =
+ manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin();
+ if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER) {
+ (*cell_accessibles)[index] =
+ static_cast<IAccessible*>(cell->NewReference());
+ ++index;
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowIndex(long* row_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row_index)
+ return E_INVALIDARG;
+
+ int row;
+ if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row)) {
+ *row_index = row;
+ return S_OK;
+ }
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_isSelected(boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!is_selected)
+ return E_INVALIDARG;
+
+ *is_selected = false;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_rowColumnExtents(
+ long* row_index,
+ long* column_index,
+ long* row_extents,
+ long* column_extents,
+ boolean* is_selected) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!row_index ||
+ !column_index ||
+ !row_extents ||
+ !column_extents ||
+ !is_selected) {
+ return E_INVALIDARG;
+ }
+
+ int row;
+ int column;
+ int rowspan;
+ int colspan;
+ if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row) &&
+ GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column) &&
+ GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) &&
+ GetIntAttribute(
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan)) {
+ *row_index = row;
+ *column_index = column;
+ *row_extents = rowspan;
+ *column_extents = colspan;
+ *is_selected = false;
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_table(IUnknown** table) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!table)
+ return E_INVALIDARG;
+
+
+ int row;
+ int column;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row);
+ GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column);
+
+ BrowserAccessibility* find_table = parent();
+ while (find_table && find_table->role() != AccessibilityNodeData::ROLE_TABLE)
+ find_table = find_table->parent();
+ if (!find_table) {
+ NOTREACHED();
+ return S_FALSE;
+ }
+
+ *table = static_cast<IAccessibleTable*>(
+ find_table->ToBrowserAccessibilityWin()->NewReference());
+
+ return S_OK;
+}
+
+//
+// IAccessibleText methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_nCharacters(LONG* n_characters) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_characters)
+ return E_INVALIDARG;
+
+ *n_characters = TextForIAccessibleText().length();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_caretOffset(LONG* offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!offset)
+ return E_INVALIDARG;
+
+ *offset = 0;
+ if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD ||
+ role_ == AccessibilityNodeData::ROLE_TEXTAREA) {
+ int sel_start = 0;
+ if (GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START,
+ &sel_start))
+ *offset = sel_start;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!n_selections)
+ return E_INVALIDARG;
+
+ *n_selections = 0;
+ if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD ||
+ role_ == AccessibilityNodeData::ROLE_TEXTAREA) {
+ int sel_start = 0;
+ int sel_end = 0;
+ if (GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START,
+ &sel_start) &&
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end) &&
+ sel_start != sel_end)
+ *n_selections = 1;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index,
+ LONG* start_offset,
+ LONG* end_offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!start_offset || !end_offset || selection_index != 0)
+ return E_INVALIDARG;
+
+ *start_offset = 0;
+ *end_offset = 0;
+ if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD ||
+ role_ == AccessibilityNodeData::ROLE_TEXTAREA) {
+ int sel_start = 0;
+ int sel_end = 0;
+ if (GetIntAttribute(
+ AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start) &&
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end)) {
+ *start_offset = sel_start;
+ *end_offset = sel_end;
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_text(LONG start_offset,
+ LONG end_offset,
+ BSTR* text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!text)
+ return E_INVALIDARG;
+
+ const string16& text_str = TextForIAccessibleText();
+
+ // Handle special text offsets.
+ HandleSpecialTextOffset(text_str, &start_offset);
+ HandleSpecialTextOffset(text_str, &end_offset);
+
+ // The spec allows the arguments to be reversed.
+ if (start_offset > end_offset) {
+ LONG tmp = start_offset;
+ start_offset = end_offset;
+ end_offset = tmp;
+ }
+
+ // The spec does not allow the start or end offsets to be out or range;
+ // we must return an error if so.
+ LONG len = text_str.length();
+ if (start_offset < 0)
+ return E_INVALIDARG;
+ if (end_offset > len)
+ return E_INVALIDARG;
+
+ string16 substr = text_str.substr(start_offset, end_offset - start_offset);
+ if (substr.empty())
+ return S_FALSE;
+
+ *text = SysAllocString(substr.c_str());
+ DCHECK(*text);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_textAtOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!start_offset || !end_offset || !text)
+ return E_INVALIDARG;
+
+ // The IAccessible2 spec says we don't have to implement the "sentence"
+ // boundary type, we can just let the screenreader handle it.
+ if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
+ *start_offset = 0;
+ *end_offset = 0;
+ *text = NULL;
+ return S_FALSE;
+ }
+
+ const string16& text_str = TextForIAccessibleText();
+
+ *start_offset = FindBoundary(
+ text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION);
+ *end_offset = FindBoundary(
+ text_str, boundary_type, offset, ui::FORWARDS_DIRECTION);
+ return get_text(*start_offset, *end_offset, text);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_textBeforeOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!start_offset || !end_offset || !text)
+ return E_INVALIDARG;
+
+ // The IAccessible2 spec says we don't have to implement the "sentence"
+ // boundary type, we can just let the screenreader handle it.
+ if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
+ *start_offset = 0;
+ *end_offset = 0;
+ *text = NULL;
+ return S_FALSE;
+ }
+
+ const string16& text_str = TextForIAccessibleText();
+
+ *start_offset = FindBoundary(
+ text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION);
+ *end_offset = offset;
+ return get_text(*start_offset, *end_offset, text);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_textAfterOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!start_offset || !end_offset || !text)
+ return E_INVALIDARG;
+
+ // The IAccessible2 spec says we don't have to implement the "sentence"
+ // boundary type, we can just let the screenreader handle it.
+ if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
+ *start_offset = 0;
+ *end_offset = 0;
+ *text = NULL;
+ return S_FALSE;
+ }
+
+ const string16& text_str = TextForIAccessibleText();
+
+ *start_offset = offset;
+ *end_offset = FindBoundary(
+ text_str, boundary_type, offset, ui::FORWARDS_DIRECTION);
+ return get_text(*start_offset, *end_offset, text);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_newText(IA2TextSegment* new_text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!new_text)
+ return E_INVALIDARG;
+
+ string16 text = TextForIAccessibleText();
+
+ new_text->text = SysAllocString(text.c_str());
+ new_text->start = 0;
+ new_text->end = static_cast<long>(text.size());
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_oldText(IA2TextSegment* old_text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!old_text)
+ return E_INVALIDARG;
+
+ old_text->text = SysAllocString(old_text_.c_str());
+ old_text->start = 0;
+ old_text->end = static_cast<long>(old_text_.size());
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_offsetAtPoint(
+ LONG x,
+ LONG y,
+ enum IA2CoordinateType coord_type,
+ LONG* offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!offset)
+ return E_INVALIDARG;
+
+ // TODO(dmazzoni): implement this. We're returning S_OK for now so that
+ // screen readers still return partially accurate results rather than
+ // completely failing.
+ *offset = 0;
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::scrollSubstringTo(
+ LONG start_index,
+ LONG end_index,
+ enum IA2ScrollType scroll_type) {
+ // TODO(dmazzoni): adjust this for the start and end index, too.
+ return scrollTo(scroll_type);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::scrollSubstringToPoint(
+ LONG start_index,
+ LONG end_index,
+ enum IA2CoordinateType coordinate_type,
+ LONG x, LONG y) {
+ // TODO(dmazzoni): adjust this for the start and end index, too.
+ return scrollToPoint(coordinate_type, x, y);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::addSelection(LONG start_offset,
+ LONG end_offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ const string16& text_str = TextForIAccessibleText();
+ HandleSpecialTextOffset(text_str, &start_offset);
+ HandleSpecialTextOffset(text_str, &end_offset);
+
+ manager_->SetTextSelection(*this, start_offset, end_offset);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::removeSelection(LONG selection_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (selection_index != 0)
+ return E_INVALIDARG;
+
+ manager_->SetTextSelection(*this, 0, 0);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::setCaretOffset(LONG offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ const string16& text_str = TextForIAccessibleText();
+ HandleSpecialTextOffset(text_str, &offset);
+ manager_->SetTextSelection(*this, offset, offset);
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::setSelection(LONG selection_index,
+ LONG start_offset,
+ LONG end_offset) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (selection_index != 0)
+ return E_INVALIDARG;
+
+ const string16& text_str = TextForIAccessibleText();
+ HandleSpecialTextOffset(text_str, &start_offset);
+ HandleSpecialTextOffset(text_str, &end_offset);
+
+ manager_->SetTextSelection(*this, start_offset, end_offset);
+ return S_OK;
+}
+
+//
+// IAccessibleHypertext methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_nHyperlinks(long* hyperlink_count) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!hyperlink_count)
+ return E_INVALIDARG;
+
+ *hyperlink_count = hyperlink_offset_to_index_.size();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_hyperlink(
+ long index,
+ IAccessibleHyperlink** hyperlink) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!hyperlink ||
+ index < 0 ||
+ index >= static_cast<long>(hyperlinks_.size())) {
+ return E_INVALIDARG;
+ }
+
+ BrowserAccessibilityWin* child =
+ children_[hyperlinks_[index]]->ToBrowserAccessibilityWin();
+ *hyperlink = static_cast<IAccessibleHyperlink*>(child->NewReference());
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_hyperlinkIndex(
+ long char_index,
+ long* hyperlink_index) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!hyperlink_index)
+ return E_INVALIDARG;
+
+ *hyperlink_index = -1;
+
+ if (char_index < 0 || char_index >= static_cast<long>(hypertext_.size()))
+ return E_INVALIDARG;
+
+ std::map<int32, int32>::iterator it =
+ hyperlink_offset_to_index_.find(char_index);
+ if (it == hyperlink_offset_to_index_.end())
+ return E_FAIL;
+
+ *hyperlink_index = it->second;
+ return S_OK;
+}
+
+//
+// IAccessibleValue methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_currentValue(VARIANT* value) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!value)
+ return E_INVALIDARG;
+
+ float float_val;
+ if (GetFloatAttribute(
+ AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &float_val)) {
+ value->vt = VT_R8;
+ value->dblVal = float_val;
+ return S_OK;
+ }
+
+ value->vt = VT_EMPTY;
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_minimumValue(VARIANT* value) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!value)
+ return E_INVALIDARG;
+
+ float float_val;
+ if (GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE,
+ &float_val)) {
+ value->vt = VT_R8;
+ value->dblVal = float_val;
+ return S_OK;
+ }
+
+ value->vt = VT_EMPTY;
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_maximumValue(VARIANT* value) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!value)
+ return E_INVALIDARG;
+
+ float float_val;
+ if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
+ &float_val)) {
+ value->vt = VT_R8;
+ value->dblVal = float_val;
+ return S_OK;
+ }
+
+ value->vt = VT_EMPTY;
+ return S_FALSE;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::setCurrentValue(VARIANT new_value) {
+ // TODO(dmazzoni): Implement this.
+ return E_NOTIMPL;
+}
+
+//
+// ISimpleDOMDocument methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_URL(BSTR* url) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!url)
+ return E_INVALIDARG;
+
+ return GetStringAttributeAsBstr(AccessibilityNodeData::ATTR_DOC_URL, url);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_title(BSTR* title) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!title)
+ return E_INVALIDARG;
+
+ return GetStringAttributeAsBstr(AccessibilityNodeData::ATTR_DOC_TITLE, title);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_mimeType(BSTR* mime_type) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!mime_type)
+ return E_INVALIDARG;
+
+ return GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DOC_MIMETYPE, mime_type);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_docType(BSTR* doc_type) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!doc_type)
+ return E_INVALIDARG;
+
+ return GetStringAttributeAsBstr(
+ AccessibilityNodeData::ATTR_DOC_DOCTYPE, doc_type);
+}
+
+//
+// ISimpleDOMNode methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_nodeInfo(
+ BSTR* node_name,
+ short* name_space_id,
+ BSTR* node_value,
+ unsigned int* num_children,
+ unsigned int* unique_id,
+ unsigned short* node_type) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node_name || !name_space_id || !node_value || !num_children ||
+ !unique_id || !node_type) {
+ return E_INVALIDARG;
+ }
+
+ string16 tag;
+ if (GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &tag))
+ *node_name = SysAllocString(tag.c_str());
+ else
+ *node_name = NULL;
+
+ *name_space_id = 0;
+ *node_value = SysAllocString(value_.c_str());
+ *num_children = children_.size();
+ *unique_id = unique_id_win_;
+
+ if (ia_role_ == ROLE_SYSTEM_DOCUMENT) {
+ *node_type = NODETYPE_DOCUMENT;
+ } else if (ia_role_ == ROLE_SYSTEM_TEXT &&
+ ((ia2_state_ & IA2_STATE_EDITABLE) == 0)) {
+ *node_type = NODETYPE_TEXT;
+ } else {
+ *node_type = NODETYPE_ELEMENT;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_attributes(
+ unsigned short max_attribs,
+ BSTR* attrib_names,
+ short* name_space_id,
+ BSTR* attrib_values,
+ unsigned short* num_attribs) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!attrib_names || !name_space_id || !attrib_values || !num_attribs)
+ return E_INVALIDARG;
+
+ *num_attribs = max_attribs;
+ if (*num_attribs > html_attributes_.size())
+ *num_attribs = html_attributes_.size();
+
+ for (unsigned short i = 0; i < *num_attribs; ++i) {
+ attrib_names[i] = SysAllocString(html_attributes_[i].first.c_str());
+ name_space_id[i] = 0;
+ attrib_values[i] = SysAllocString(html_attributes_[i].second.c_str());
+ }
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_attributesForNames(
+ unsigned short num_attribs,
+ BSTR* attrib_names,
+ short* name_space_id,
+ BSTR* attrib_values) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!attrib_names || !name_space_id || !attrib_values)
+ return E_INVALIDARG;
+
+ for (unsigned short i = 0; i < num_attribs; ++i) {
+ name_space_id[i] = 0;
+ bool found = false;
+ string16 name = (LPCWSTR)attrib_names[i];
+ for (unsigned int j = 0; j < html_attributes_.size(); ++j) {
+ if (html_attributes_[j].first == name) {
+ attrib_values[i] = SysAllocString(html_attributes_[j].second.c_str());
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ attrib_values[i] = NULL;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_computedStyle(
+ unsigned short max_style_properties,
+ boolean use_alternate_view,
+ BSTR* style_properties,
+ BSTR* style_values,
+ unsigned short *num_style_properties) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!style_properties || !style_values)
+ return E_INVALIDARG;
+
+ // We only cache a single style property for now: DISPLAY
+
+ string16 display;
+ if (max_style_properties == 0 ||
+ !GetStringAttribute(AccessibilityNodeData::ATTR_DISPLAY, &display)) {
+ *num_style_properties = 0;
+ return S_OK;
+ }
+
+ *num_style_properties = 1;
+ style_properties[0] = SysAllocString(L"display");
+ style_values[0] = SysAllocString(display.c_str());
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_computedStyleForProperties(
+ unsigned short num_style_properties,
+ boolean use_alternate_view,
+ BSTR* style_properties,
+ BSTR* style_values) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!style_properties || !style_values)
+ return E_INVALIDARG;
+
+ // We only cache a single style property for now: DISPLAY
+
+ for (unsigned short i = 0; i < num_style_properties; ++i) {
+ string16 name = (LPCWSTR)style_properties[i];
+ StringToLowerASCII(&name);
+ if (name == L"display") {
+ string16 display;
+ GetStringAttribute(AccessibilityNodeData::ATTR_DISPLAY, &display);
+ style_values[i] = SysAllocString(display.c_str());
+ } else {
+ style_values[i] = NULL;
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::scrollTo(boolean placeTopLeft) {
+ return scrollTo(placeTopLeft ?
+ IA2_SCROLL_TYPE_TOP_LEFT : IA2_SCROLL_TYPE_ANYWHERE);
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_parentNode(ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ *node = parent_->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_firstChild(ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ if (children_.empty()) {
+ *node = NULL;
+ return S_FALSE;
+ }
+
+ *node = children_[0]->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_lastChild(ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ if (children_.empty()) {
+ *node = NULL;
+ return S_FALSE;
+ }
+
+ *node = (*children_.rbegin())->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_previousSibling(
+ ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ if (!parent_ || index_in_parent_ <= 0) {
+ *node = NULL;
+ return S_FALSE;
+ }
+
+ *node = parent_->children()[index_in_parent_ - 1]->
+ ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_nextSibling(ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ if (!parent_ ||
+ index_in_parent_ < 0 ||
+ index_in_parent_ >= static_cast<int>(parent_->children().size()) - 1) {
+ *node = NULL;
+ return S_FALSE;
+ }
+
+ *node = parent_->children()[index_in_parent_ + 1]->
+ ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::get_childAt(
+ unsigned int child_index,
+ ISimpleDOMNode** node) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!node)
+ return E_INVALIDARG;
+
+ if (child_index < children_.size()) {
+ *node = NULL;
+ return S_FALSE;
+ }
+
+ *node = children_[child_index]->ToBrowserAccessibilityWin()->NewReference();
+ return S_OK;
+}
+
+//
+// ISimpleDOMText methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::get_domText(BSTR* dom_text) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (!dom_text)
+ return E_INVALIDARG;
+
+ if (name_.empty())
+ return S_FALSE;
+
+ *dom_text = SysAllocString(name_.c_str());
+ DCHECK(*dom_text);
+ return S_OK;
+}
+
+//
+// IServiceProvider methods.
+//
+
+STDMETHODIMP BrowserAccessibilityWin::QueryService(REFGUID guidService,
+ REFIID riid,
+ void** object) {
+ if (!instance_active_)
+ return E_FAIL;
+
+ if (guidService == GUID_IAccessibleContentDocument) {
+ // Special Mozilla extension: return the accessible for the root document.
+ // Screen readers use this to distinguish between a document loaded event
+ // on the root document vs on an iframe.
+ return manager_->GetRoot()->ToBrowserAccessibilityWin()->QueryInterface(
+ IID_IAccessible2, object);
+ }
+
+ if (guidService == IID_IAccessible ||
+ guidService == IID_IAccessible2 ||
+ guidService == IID_IAccessibleAction ||
+ guidService == IID_IAccessibleApplication ||
+ guidService == IID_IAccessibleHyperlink ||
+ guidService == IID_IAccessibleHypertext ||
+ guidService == IID_IAccessibleImage ||
+ guidService == IID_IAccessibleTable ||
+ guidService == IID_IAccessibleTable2 ||
+ guidService == IID_IAccessibleTableCell ||
+ guidService == IID_IAccessibleText ||
+ guidService == IID_IAccessibleValue ||
+ guidService == IID_ISimpleDOMDocument ||
+ guidService == IID_ISimpleDOMNode ||
+ guidService == IID_ISimpleDOMText ||
+ guidService == GUID_ISimpleDOM) {
+ return QueryInterface(riid, object);
+ }
+
+ // We only support the IAccessibleEx interface on Windows 8 and above. This
+ // is needed for the on-screen Keyboard to show up in metro mode, when the
+ // user taps an editable portion on the page.
+ // All methods in the IAccessibleEx interface are unimplemented.
+ if (riid == IID_IAccessibleEx &&
+ base::win::GetVersion() >= base::win::VERSION_WIN8) {
+ return QueryInterface(riid, object);
+ }
+
+ *object = NULL;
+ return E_FAIL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::GetPatternProvider(PATTERNID id,
+ IUnknown** provider) {
+ DVLOG(1) << "In Function: "
+ << __FUNCTION__
+ << " for pattern id: "
+ << id;
+ if (id == UIA_ValuePatternId || id == UIA_TextPatternId) {
+ if (IsEditableText()) {
+ // The BrowserAccessibilityManager keeps track of instances when
+ // we don't want to show the on-screen keyboard.
+ if (!manager_->IsOSKAllowed(GetGlobalBoundsRect()))
+ return E_NOTIMPL;
+
+ DVLOG(1) << "Returning UIA text provider";
+ base::win::UIATextProvider::CreateTextProvider(true, provider);
+ return S_OK;
+ }
+ }
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP BrowserAccessibilityWin::GetPropertyValue(PROPERTYID id,
+ VARIANT* ret) {
+ DVLOG(1) << "In Function: "
+ << __FUNCTION__
+ << " for property id: "
+ << id;
+ V_VT(ret) = VT_EMPTY;
+ if (id == UIA_ControlTypePropertyId) {
+ if (IsEditableText()) {
+ V_VT(ret) = VT_I4;
+ ret->lVal = UIA_EditControlTypeId;
+ DVLOG(1) << "Returning Edit control type";
+ } else {
+ DVLOG(1) << "Returning empty control type";
+ }
+ }
+ return S_OK;
+}
+
+//
+// CComObjectRootEx methods.
+//
+
+HRESULT WINAPI BrowserAccessibilityWin::InternalQueryInterface(
+ void* this_ptr,
+ const _ATL_INTMAP_ENTRY* entries,
+ REFIID iid,
+ void** object) {
+ if (iid == IID_IAccessibleImage) {
+ if (ia_role_ != ROLE_SYSTEM_GRAPHIC) {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ } else if (iid == IID_IAccessibleTable || iid == IID_IAccessibleTable2) {
+ if (ia_role_ != ROLE_SYSTEM_TABLE) {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ } else if (iid == IID_IAccessibleTableCell) {
+ if (ia_role_ != ROLE_SYSTEM_CELL) {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ } else if (iid == IID_IAccessibleValue) {
+ if (ia_role_ != ROLE_SYSTEM_PROGRESSBAR &&
+ ia_role_ != ROLE_SYSTEM_SCROLLBAR &&
+ ia_role_ != ROLE_SYSTEM_SLIDER) {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ } else if (iid == IID_ISimpleDOMDocument) {
+ if (ia_role_ != ROLE_SYSTEM_DOCUMENT) {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+ }
+
+ return CComObjectRootBase::InternalQueryInterface(
+ this_ptr, entries, iid, object);
+}
+
+//
+// Private methods.
+//
+
+// Initialize this object and mark it as active.
+void BrowserAccessibilityWin::PreInitialize() {
+ BrowserAccessibility::PreInitialize();
+
+ InitRoleAndState();
+
+ // Expose the "display" and "tag" attributes.
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_DISPLAY, "display");
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_HTML_TAG, "tag");
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_ROLE, "xml-roles");
+
+ // Expose "level" attribute for headings, trees, etc.
+ IntAttributeToIA2(AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, "level");
+
+ // Expose the set size and position in set for listbox options.
+ if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION &&
+ parent_ &&
+ parent_->role() == AccessibilityNodeData::ROLE_LISTBOX) {
+ ia2_attributes_.push_back(
+ L"setsize:" + base::IntToString16(parent_->child_count()));
+ ia2_attributes_.push_back(
+ L"setsize:" + base::IntToString16(index_in_parent_ + 1));
+ }
+
+ if (ia_role_ == ROLE_SYSTEM_CHECKBUTTON ||
+ ia_role_ == ROLE_SYSTEM_RADIOBUTTON ||
+ ia2_role_ == IA2_ROLE_TOGGLE_BUTTON) {
+ ia2_attributes_.push_back(L"checkable:true");
+ }
+
+ // Expose live region attributes.
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_STATUS, "live");
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_RELEVANT, "relevant");
+ BoolAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_ATOMIC, "atomic");
+ BoolAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_BUSY, "busy");
+
+ // Expose container live region attributes.
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
+ "container-live");
+ StringAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_RELEVANT,
+ "container-relevant");
+ BoolAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_ATOMIC,
+ "container-atomic");
+ BoolAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_BUSY,
+ "container-busy");
+
+ // Expose slider value.
+ if (ia_role_ == ROLE_SYSTEM_PROGRESSBAR ||
+ ia_role_ == ROLE_SYSTEM_SCROLLBAR ||
+ ia_role_ == ROLE_SYSTEM_SLIDER) {
+ float fval;
+ if (value_.empty() &&
+ GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &fval)) {
+ // TODO(dmazzoni): Use ICU to localize this?
+ value_ = UTF8ToUTF16(base::DoubleToString(fval));
+ }
+ ia2_attributes_.push_back(L"valuetext:" + value_);
+ }
+
+ // Expose color well value.
+ if (ia2_role_ == IA2_ROLE_COLOR_CHOOSER) {
+ int r, g, b;
+ GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_RED, &r);
+ GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_GREEN, &g);
+ GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_BLUE, &b);
+ value_ = base::IntToString16((r * 100) / 255) + L"% red " +
+ base::IntToString16((g * 100) / 255) + L"% green " +
+ base::IntToString16((b * 100) / 255) + L"% blue";
+ }
+
+ // Expose table cell index.
+ if (ia_role_ == ROLE_SYSTEM_CELL) {
+ BrowserAccessibility* table = parent();
+ while (table && table->role() != AccessibilityNodeData::ROLE_TABLE)
+ table = table->parent();
+ if (table) {
+ const std::vector<int32>& unique_cell_ids = table->unique_cell_ids();
+ for (size_t i = 0; i < unique_cell_ids.size(); ++i) {
+ if (unique_cell_ids[i] == renderer_id_) {
+ ia2_attributes_.push_back(
+ string16(L"table-cell-index:") + base::IntToString16(i));
+ }
+ }
+ }
+ }
+
+ // The calculation of the accessible name of an element has been
+ // standardized in the HTML to Platform Accessibility APIs Implementation
+ // Guide (http://www.w3.org/TR/html-aapi/). In order to return the
+ // appropriate accessible name on Windows, we need to apply some logic
+ // to the fields we get from WebKit.
+ //
+ // TODO(dmazzoni): move most of this logic into WebKit.
+ //
+ // WebKit gives us:
+ //
+ // name: the default name, e.g. inner text
+ // title ui element: a reference to a <label> element on the same
+ // page that labels this node.
+ // description: accessible labels that override the default name:
+ // aria-label or aria-labelledby or aria-describedby
+ // help: the value of the "title" attribute
+ //
+ // On Windows, the logic we apply lets some fields take precedence and
+ // always returns the primary name in "name" and the secondary name,
+ // if any, in "description".
+
+ string16 description, help, title_attr;
+ int title_elem_id = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &title_elem_id);
+ GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description);
+ GetStringAttribute(AccessibilityNodeData::ATTR_HELP, &help);
+
+ // WebKit annoyingly puts the title in the description if there's no other
+ // description, which just confuses the rest of the logic. Put it back.
+ // Now "help" is always the value of the "title" attribute, if present.
+ if (GetHtmlAttribute("title", &title_attr) &&
+ description == title_attr &&
+ help.empty()) {
+ help = description;
+ description.clear();
+ string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION].clear();
+ string_attributes_[AccessibilityNodeData::ATTR_HELP] = help;
+ }
+
+ // Now implement the main logic: the descripion should become the name if
+ // it's nonempty, and the help should become the description if
+ // there's no description - or the name if there's no name or description.
+ if (!description.empty()) {
+ name_ = description;
+ description.clear();
+ string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = description;
+ }
+ if (!help.empty() && description.empty()) {
+ description = help;
+ string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = help;
+ string_attributes_[AccessibilityNodeData::ATTR_HELP].clear();
+ }
+ if (!description.empty() && name_.empty() && !title_elem_id) {
+ name_ = description;
+ description.clear();
+ string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION].clear();
+ }
+
+ // If it's a text field, also consider the placeholder.
+ string16 placeholder;
+ if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD &&
+ HasState(AccessibilityNodeData::STATE_FOCUSABLE) &&
+ GetHtmlAttribute("placeholder", &placeholder)) {
+ if (name_.empty() && !title_elem_id) {
+ name_ = placeholder;
+ } else if (description.empty()) {
+ description = placeholder;
+ string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = description;
+ }
+ }
+
+ // On Windows, the value of a document should be its url.
+ if (role_ == AccessibilityNodeData::ROLE_ROOT_WEB_AREA ||
+ role_ == AccessibilityNodeData::ROLE_WEB_AREA) {
+ GetStringAttribute(AccessibilityNodeData::ATTR_DOC_URL, &value_);
+ }
+
+ // For certain roles (listbox option, static text, and list marker)
+ // WebKit stores the main accessible text in the "value" - swap it so
+ // that it's the "name".
+ if (name_.empty() &&
+ (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION ||
+ role_ == AccessibilityNodeData::ROLE_STATIC_TEXT ||
+ role_ == AccessibilityNodeData::ROLE_LIST_MARKER)) {
+ name_.swap(value_);
+ }
+
+ // If this doesn't have a value and is linked then set its value to the url
+ // attribute. This allows screen readers to read an empty link's destination.
+ string16 url;
+ if (value_.empty() && (ia_state_ & STATE_SYSTEM_LINKED))
+ GetStringAttribute(AccessibilityNodeData::ATTR_URL, &value_);
+
+ // Clear any old relationships between this node and other nodes.
+ for (size_t i = 0; i < relations_.size(); ++i)
+ relations_[i]->Release();
+ relations_.clear();
+
+ // Handle title UI element.
+ if (title_elem_id) {
+ // Add a labelled by relationship.
+ CComObject<BrowserAccessibilityRelation>* relation;
+ HRESULT hr = CComObject<BrowserAccessibilityRelation>::CreateInstance(
+ &relation);
+ DCHECK(SUCCEEDED(hr));
+ relation->AddRef();
+ relation->Initialize(this, IA2_RELATION_LABELLED_BY);
+ relation->AddTarget(title_elem_id);
+ relations_.push_back(relation);
+ }
+}
+
+void BrowserAccessibilityWin::PostInitialize() {
+ BrowserAccessibility::PostInitialize();
+
+ // Construct the hypertext for this node.
+ hyperlink_offset_to_index_.clear();
+ hyperlinks_.clear();
+ hypertext_.clear();
+ for (unsigned int i = 0; i < children().size(); ++i) {
+ BrowserAccessibility* child = children()[i];
+ if (child->role() == AccessibilityNodeData::ROLE_STATIC_TEXT) {
+ hypertext_ += child->name();
+ } else {
+ hyperlink_offset_to_index_[hypertext_.size()] = hyperlinks_.size();
+ hypertext_ += kEmbeddedCharacter;
+ hyperlinks_.push_back(i);
+ }
+ }
+ DCHECK_EQ(hyperlink_offset_to_index_.size(), hyperlinks_.size());
+
+ // Fire an event when an alert first appears.
+ if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_)
+ manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this);
+
+ // Fire events if text has changed.
+ string16 text = TextForIAccessibleText();
+ if (previous_text_ != text) {
+ if (!previous_text_.empty() && !text.empty()) {
+ manager_->NotifyAccessibilityEvent(
+ AccessibilityNotificationObjectShow, this);
+ }
+
+ // TODO(dmazzoni): Look into HIDE events, too.
+
+ old_text_ = previous_text_;
+ previous_text_ = text;
+ }
+
+ // Fire events if the state has changed.
+ if (!first_time_ && ia_state_ != old_ia_state_) {
+ BrowserAccessibilityManagerWin* manager =
+ manager_->ToBrowserAccessibilityManagerWin();
+
+ // Normally focus events are handled elsewhere, however
+ // focus for managed descendants is platform-specific.
+ // Fire a focus event if the focused descendant in a multi-select
+ // list box changes.
+ if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION &&
+ (ia_state_ & STATE_SYSTEM_FOCUSABLE) &&
+ (ia_state_ & STATE_SYSTEM_SELECTABLE) &&
+ (ia_state_ & STATE_SYSTEM_FOCUSED) &&
+ !(old_ia_state_ & STATE_SYSTEM_FOCUSED)) {
+ manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_FOCUS, unique_id_win());
+ }
+
+ if ((ia_state_ & STATE_SYSTEM_SELECTED) &&
+ !(old_ia_state_ & STATE_SYSTEM_SELECTED)) {
+ manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_SELECTIONADD,
+ unique_id_win());
+ } else if (!(ia_state_ & STATE_SYSTEM_SELECTED) &&
+ (old_ia_state_ & STATE_SYSTEM_SELECTED)) {
+ manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_SELECTIONREMOVE,
+ unique_id_win());
+ }
+
+ old_ia_state_ = ia_state_;
+ }
+
+ first_time_ = false;
+}
+
+void BrowserAccessibilityWin::NativeAddReference() {
+ AddRef();
+}
+
+void BrowserAccessibilityWin::NativeReleaseReference() {
+ Release();
+}
+
+bool BrowserAccessibilityWin::IsNative() const {
+ return true;
+}
+
+void BrowserAccessibilityWin::SetLocation(const gfx::Rect& new_location) {
+ BrowserAccessibility::SetLocation(new_location);
+ manager_->ToBrowserAccessibilityManagerWin()->MaybeCallNotifyWinEvent(
+ EVENT_OBJECT_LOCATIONCHANGE, unique_id_win());
+}
+
+BrowserAccessibilityWin* BrowserAccessibilityWin::NewReference() {
+ AddRef();
+ return this;
+}
+
+BrowserAccessibilityWin* BrowserAccessibilityWin::GetTargetFromChildID(
+ const VARIANT& var_id) {
+ if (var_id.vt != VT_I4)
+ return NULL;
+
+ LONG child_id = var_id.lVal;
+ if (child_id == CHILDID_SELF)
+ return this;
+
+ if (child_id >= 1 && child_id <= static_cast<LONG>(children_.size()))
+ return children_[child_id - 1]->ToBrowserAccessibilityWin();
+
+ return manager_->ToBrowserAccessibilityManagerWin()->
+ GetFromUniqueIdWin(child_id);
+}
+
+HRESULT BrowserAccessibilityWin::GetStringAttributeAsBstr(
+ AccessibilityNodeData::StringAttribute attribute,
+ BSTR* value_bstr) {
+ string16 str;
+
+ if (!GetStringAttribute(attribute, &str))
+ return S_FALSE;
+
+ if (str.empty())
+ return S_FALSE;
+
+ *value_bstr = SysAllocString(str.c_str());
+ DCHECK(*value_bstr);
+
+ return S_OK;
+}
+
+void BrowserAccessibilityWin::StringAttributeToIA2(
+ AccessibilityNodeData::StringAttribute attribute,
+ const char* ia2_attr) {
+ string16 value;
+ if (GetStringAttribute(attribute, &value))
+ ia2_attributes_.push_back(ASCIIToUTF16(ia2_attr) + L":" + value);
+}
+
+void BrowserAccessibilityWin::BoolAttributeToIA2(
+ AccessibilityNodeData::BoolAttribute attribute,
+ const char* ia2_attr) {
+ bool value;
+ if (GetBoolAttribute(attribute, &value)) {
+ ia2_attributes_.push_back((ASCIIToUTF16(ia2_attr) + L":") +
+ (value ? L"true" : L"false"));
+ }
+}
+
+void BrowserAccessibilityWin::IntAttributeToIA2(
+ AccessibilityNodeData::IntAttribute attribute,
+ const char* ia2_attr) {
+ int value;
+ if (GetIntAttribute(attribute, &value))
+ ia2_attributes_.push_back(ASCIIToUTF16(ia2_attr) + L":" +
+ base::IntToString16(value));
+}
+
+const string16& BrowserAccessibilityWin::TextForIAccessibleText() {
+ if (IsEditableText())
+ return value_;
+ return (role_ == AccessibilityNodeData::ROLE_STATIC_TEXT) ?
+ name_ : hypertext_;
+}
+
+void BrowserAccessibilityWin::HandleSpecialTextOffset(const string16& text,
+ LONG* offset) {
+ if (*offset == IA2_TEXT_OFFSET_LENGTH)
+ *offset = static_cast<LONG>(text.size());
+ else if (*offset == IA2_TEXT_OFFSET_CARET)
+ get_caretOffset(offset);
+}
+
+ui::TextBoundaryType BrowserAccessibilityWin::IA2TextBoundaryToTextBoundary(
+ IA2TextBoundaryType ia2_boundary) {
+ switch(ia2_boundary) {
+ case IA2_TEXT_BOUNDARY_CHAR: return ui::CHAR_BOUNDARY;
+ case IA2_TEXT_BOUNDARY_WORD: return ui::WORD_BOUNDARY;
+ case IA2_TEXT_BOUNDARY_LINE: return ui::LINE_BOUNDARY;
+ case IA2_TEXT_BOUNDARY_SENTENCE: return ui::SENTENCE_BOUNDARY;
+ case IA2_TEXT_BOUNDARY_PARAGRAPH: return ui::PARAGRAPH_BOUNDARY;
+ case IA2_TEXT_BOUNDARY_ALL: return ui::ALL_BOUNDARY;
+ default:
+ NOTREACHED();
+ return ui::CHAR_BOUNDARY;
+ }
+}
+
+LONG BrowserAccessibilityWin::FindBoundary(
+ const string16& text,
+ IA2TextBoundaryType ia2_boundary,
+ LONG start_offset,
+ ui::TextBoundaryDirection direction) {
+ HandleSpecialTextOffset(text, &start_offset);
+ ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary);
+ return ui::FindAccessibleTextBoundary(
+ text, line_breaks_, boundary, start_offset, direction);
+}
+
+BrowserAccessibilityWin* BrowserAccessibilityWin::GetFromRendererID(
+ int32 renderer_id) {
+ return manager_->GetFromRendererID(renderer_id)->ToBrowserAccessibilityWin();
+}
+
+void BrowserAccessibilityWin::InitRoleAndState() {
+ ia_state_ = 0;
+ ia2_state_ = IA2_STATE_OPAQUE;
+ ia2_attributes_.clear();
+
+ if (HasState(AccessibilityNodeData::STATE_BUSY))
+ ia_state_ |= STATE_SYSTEM_BUSY;
+ if (HasState(AccessibilityNodeData::STATE_CHECKED))
+ ia_state_ |= STATE_SYSTEM_CHECKED;
+ if (HasState(AccessibilityNodeData::STATE_COLLAPSED))
+ ia_state_ |= STATE_SYSTEM_COLLAPSED;
+ if (HasState(AccessibilityNodeData::STATE_EXPANDED))
+ ia_state_ |= STATE_SYSTEM_EXPANDED;
+ if (HasState(AccessibilityNodeData::STATE_FOCUSABLE))
+ ia_state_ |= STATE_SYSTEM_FOCUSABLE;
+ if (HasState(AccessibilityNodeData::STATE_HASPOPUP))
+ ia_state_ |= STATE_SYSTEM_HASPOPUP;
+ if (HasState(AccessibilityNodeData::STATE_HOTTRACKED))
+ ia_state_ |= STATE_SYSTEM_HOTTRACKED;
+ if (HasState(AccessibilityNodeData::STATE_INDETERMINATE))
+ ia_state_ |= STATE_SYSTEM_INDETERMINATE;
+ if (HasState(AccessibilityNodeData::STATE_INVISIBLE))
+ ia_state_ |= STATE_SYSTEM_INVISIBLE;
+ if (HasState(AccessibilityNodeData::STATE_LINKED))
+ ia_state_ |= STATE_SYSTEM_LINKED;
+ if (HasState(AccessibilityNodeData::STATE_MULTISELECTABLE)) {
+ ia_state_ |= STATE_SYSTEM_EXTSELECTABLE;
+ ia_state_ |= STATE_SYSTEM_MULTISELECTABLE;
+ }
+ // TODO(ctguil): Support STATE_SYSTEM_EXTSELECTABLE/accSelect.
+ if (HasState(AccessibilityNodeData::STATE_OFFSCREEN))
+ ia_state_ |= STATE_SYSTEM_OFFSCREEN;
+ if (HasState(AccessibilityNodeData::STATE_PRESSED))
+ ia_state_ |= STATE_SYSTEM_PRESSED;
+ if (HasState(AccessibilityNodeData::STATE_PROTECTED))
+ ia_state_ |= STATE_SYSTEM_PROTECTED;
+ if (HasState(AccessibilityNodeData::STATE_REQUIRED))
+ ia2_state_ |= IA2_STATE_REQUIRED;
+ if (HasState(AccessibilityNodeData::STATE_SELECTABLE))
+ ia_state_ |= STATE_SYSTEM_SELECTABLE;
+ if (HasState(AccessibilityNodeData::STATE_SELECTED))
+ ia_state_ |= STATE_SYSTEM_SELECTED;
+ if (HasState(AccessibilityNodeData::STATE_TRAVERSED))
+ ia_state_ |= STATE_SYSTEM_TRAVERSED;
+ if (HasState(AccessibilityNodeData::STATE_UNAVAILABLE))
+ ia_state_ |= STATE_SYSTEM_UNAVAILABLE;
+ if (HasState(AccessibilityNodeData::STATE_VERTICAL)) {
+ ia2_state_ |= IA2_STATE_VERTICAL;
+ } else {
+ ia2_state_ |= IA2_STATE_HORIZONTAL;
+ }
+ if (HasState(AccessibilityNodeData::STATE_VISITED))
+ ia_state_ |= STATE_SYSTEM_TRAVERSED;
+
+ // WebKit marks everything as readonly unless it's editable text, so if it's
+ // not readonly, mark it as editable now. The final computation of the
+ // READONLY state for MSAA is below, after the switch.
+ if (!HasState(AccessibilityNodeData::STATE_READONLY))
+ ia2_state_ |= IA2_STATE_EDITABLE;
+
+ string16 invalid;
+ if (GetHtmlAttribute("aria-invalid", &invalid))
+ ia2_state_ |= IA2_STATE_INVALID_ENTRY;
+
+ bool mixed = false;
+ GetBoolAttribute(AccessibilityNodeData::ATTR_BUTTON_MIXED, &mixed);
+ if (mixed)
+ ia_state_ |= STATE_SYSTEM_MIXED;
+
+ bool editable = false;
+ GetBoolAttribute(AccessibilityNodeData::ATTR_CAN_SET_VALUE, &editable);
+ if (editable)
+ ia2_state_ |= IA2_STATE_EDITABLE;
+
+ string16 html_tag;
+ GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag);
+ ia_role_ = 0;
+ ia2_role_ = 0;
+ switch (role_) {
+ case AccessibilityNodeData::ROLE_ALERT:
+ ia_role_ = ROLE_SYSTEM_ALERT;
+ break;
+ case AccessibilityNodeData::ROLE_ALERT_DIALOG:
+ ia_role_ = ROLE_SYSTEM_DIALOG;
+ break;
+ case AccessibilityNodeData::ROLE_APPLICATION:
+ ia_role_ = ROLE_SYSTEM_APPLICATION;
+ break;
+ case AccessibilityNodeData::ROLE_ARTICLE:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_SECTION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_BUSY_INDICATOR:
+ ia_role_ = ROLE_SYSTEM_ANIMATION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_BUTTON:
+ ia_role_ = ROLE_SYSTEM_PUSHBUTTON;
+ bool is_aria_pressed_defined;
+ bool is_mixed;
+ if (GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed))
+ ia_state_ |= STATE_SYSTEM_PRESSED;
+ if (is_aria_pressed_defined)
+ ia2_role_ = IA2_ROLE_TOGGLE_BUTTON;
+ if (is_mixed)
+ ia_state_ |= STATE_SYSTEM_MIXED;
+ break;
+ case AccessibilityNodeData::ROLE_CANVAS:
+ ia_role_ = ROLE_SYSTEM_GRAPHIC;
+ break;
+ case AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT:
+ role_name_ = L"canvas";
+ ia2_role_ = IA2_ROLE_CANVAS;
+ break;
+ case AccessibilityNodeData::ROLE_CELL:
+ ia_role_ = ROLE_SYSTEM_CELL;
+ break;
+ case AccessibilityNodeData::ROLE_CHECKBOX:
+ ia_role_ = ROLE_SYSTEM_CHECKBUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_COLOR_WELL:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ ia2_role_ = IA2_ROLE_COLOR_CHOOSER;
+ break;
+ case AccessibilityNodeData::ROLE_COLUMN:
+ ia_role_ = ROLE_SYSTEM_COLUMN;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_COLUMN_HEADER:
+ ia_role_ = ROLE_SYSTEM_COLUMNHEADER;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_COMBO_BOX:
+ ia_role_ = ROLE_SYSTEM_COMBOBOX;
+ break;
+ case AccessibilityNodeData::ROLE_DIV:
+ role_name_ = L"div";
+ ia2_role_ = IA2_ROLE_SECTION;
+ break;
+ case AccessibilityNodeData::ROLE_DEFINITION:
+ role_name_ = html_tag;
+ ia2_role_ = IA2_ROLE_PARAGRAPH;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL:
+ role_name_ = html_tag;
+ ia2_role_ = IA2_ROLE_PARAGRAPH;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM:
+ ia_role_ = ROLE_SYSTEM_LISTITEM;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_DIALOG:
+ ia_role_ = ROLE_SYSTEM_DIALOG;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE:
+ ia_role_ = ROLE_SYSTEM_OUTLINEBUTTON;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_DOCUMENT:
+ case AccessibilityNodeData::ROLE_ROOT_WEB_AREA:
+ case AccessibilityNodeData::ROLE_WEB_AREA:
+ ia_role_ = ROLE_SYSTEM_DOCUMENT;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ ia_state_ |= STATE_SYSTEM_FOCUSABLE;
+ break;
+ case AccessibilityNodeData::ROLE_EDITABLE_TEXT:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia2_state_ |= IA2_STATE_SINGLE_LINE;
+ ia2_state_ |= IA2_STATE_EDITABLE;
+ break;
+ case AccessibilityNodeData::ROLE_FORM:
+ role_name_ = L"form";
+ ia2_role_ = IA2_ROLE_FORM;
+ break;
+ case AccessibilityNodeData::ROLE_FOOTER:
+ ia_role_ = IA2_ROLE_FOOTER;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_GRID:
+ ia_role_ = ROLE_SYSTEM_TABLE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_GROUP: {
+ string16 aria_role;
+ GetStringAttribute(AccessibilityNodeData::ATTR_ROLE, &aria_role);
+ if (aria_role == L"group" || html_tag == L"fieldset") {
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ } else if (html_tag == L"li") {
+ ia_role_ = ROLE_SYSTEM_LISTITEM;
+ } else {
+ if (html_tag.empty())
+ role_name_ = L"div";
+ else
+ role_name_ = html_tag;
+ ia2_role_ = IA2_ROLE_SECTION;
+ }
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ }
+ case AccessibilityNodeData::ROLE_GROW_AREA:
+ ia_role_ = ROLE_SYSTEM_GRIP;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_HEADING:
+ role_name_ = html_tag;
+ ia2_role_ = IA2_ROLE_HEADING;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_HORIZONTAL_RULE:
+ ia_role_ = ROLE_SYSTEM_SEPARATOR;
+ break;
+ case AccessibilityNodeData::ROLE_IMAGE:
+ ia_role_ = ROLE_SYSTEM_GRAPHIC;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_IMAGE_MAP:
+ role_name_ = html_tag;
+ ia2_role_ = IA2_ROLE_IMAGE_MAP;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK:
+ ia_role_ = ROLE_SYSTEM_LINK;
+ ia_state_ |= STATE_SYSTEM_LINKED;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_LABEL:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia2_role_ = IA2_ROLE_LABEL;
+ break;
+ case AccessibilityNodeData::ROLE_LANDMARK_APPLICATION:
+ case AccessibilityNodeData::ROLE_LANDMARK_BANNER:
+ case AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY:
+ case AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO:
+ case AccessibilityNodeData::ROLE_LANDMARK_MAIN:
+ case AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION:
+ case AccessibilityNodeData::ROLE_LANDMARK_SEARCH:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_SECTION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_LINK:
+ case AccessibilityNodeData::ROLE_WEBCORE_LINK:
+ ia_role_ = ROLE_SYSTEM_LINK;
+ ia_state_ |= STATE_SYSTEM_LINKED;
+ break;
+ case AccessibilityNodeData::ROLE_LIST:
+ ia_role_ = ROLE_SYSTEM_LIST;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_LISTBOX:
+ ia_role_ = ROLE_SYSTEM_LIST;
+ break;
+ case AccessibilityNodeData::ROLE_LISTBOX_OPTION:
+ ia_role_ = ROLE_SYSTEM_LISTITEM;
+ if (ia_state_ & STATE_SYSTEM_SELECTABLE) {
+ ia_state_ |= STATE_SYSTEM_FOCUSABLE;
+ if (HasState(AccessibilityNodeData::STATE_FOCUSED))
+ ia_state_ |= STATE_SYSTEM_FOCUSED;
+ }
+ break;
+ case AccessibilityNodeData::ROLE_LIST_ITEM:
+ ia_role_ = ROLE_SYSTEM_LISTITEM;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_LIST_MARKER:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_MATH:
+ ia_role_ = ROLE_SYSTEM_EQUATION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_MENU:
+ case AccessibilityNodeData::ROLE_MENU_BUTTON:
+ ia_role_ = ROLE_SYSTEM_MENUPOPUP;
+ break;
+ case AccessibilityNodeData::ROLE_MENU_BAR:
+ ia_role_ = ROLE_SYSTEM_MENUBAR;
+ break;
+ case AccessibilityNodeData::ROLE_MENU_ITEM:
+ ia_role_ = ROLE_SYSTEM_MENUITEM;
+ break;
+ case AccessibilityNodeData::ROLE_MENU_LIST_POPUP:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ break;
+ case AccessibilityNodeData::ROLE_MENU_LIST_OPTION:
+ ia_role_ = ROLE_SYSTEM_LISTITEM;
+ if (ia_state_ & STATE_SYSTEM_SELECTABLE) {
+ ia_state_ |= STATE_SYSTEM_FOCUSABLE;
+ if (HasState(AccessibilityNodeData::STATE_FOCUSED))
+ ia_state_ |= STATE_SYSTEM_FOCUSED;
+ }
+ break;
+ case AccessibilityNodeData::ROLE_NOTE:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_NOTE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_OUTLINE:
+ ia_role_ = ROLE_SYSTEM_OUTLINE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_PARAGRAPH:
+ role_name_ = L"P";
+ ia2_role_ = IA2_ROLE_PARAGRAPH;
+ break;
+ case AccessibilityNodeData::ROLE_POPUP_BUTTON:
+ if (html_tag == L"select") {
+ ia_role_ = ROLE_SYSTEM_COMBOBOX;
+ } else {
+ ia_role_ = ROLE_SYSTEM_BUTTONMENU;
+ }
+ break;
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR:
+ ia_role_ = ROLE_SYSTEM_PROGRESSBAR;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_RADIO_BUTTON:
+ ia_role_ = ROLE_SYSTEM_RADIOBUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_RADIO_GROUP:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_SECTION;
+ break;
+ case AccessibilityNodeData::ROLE_REGION:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_SECTION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_ROW:
+ ia_role_ = ROLE_SYSTEM_ROW;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_ROW_HEADER:
+ ia_role_ = ROLE_SYSTEM_ROWHEADER;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_RULER:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ ia2_role_ = IA2_ROLE_RULER;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_SCROLLAREA:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ ia2_role_ = IA2_ROLE_SCROLL_PANE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_SCROLLBAR:
+ ia_role_ = ROLE_SYSTEM_SCROLLBAR;
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ ia_role_ = ROLE_SYSTEM_SLIDER;
+ break;
+ case AccessibilityNodeData::ROLE_SPIN_BUTTON:
+ ia_role_ = ROLE_SYSTEM_SPINBUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_SPIN_BUTTON_PART:
+ ia_role_ = ROLE_SYSTEM_PUSHBUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_SPLIT_GROUP:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ ia2_role_ = IA2_ROLE_SPLIT_PANE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_ANNOTATION:
+ case AccessibilityNodeData::ROLE_STATIC_TEXT:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_STATUS:
+ ia_role_ = ROLE_SYSTEM_STATUSBAR;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_SPLITTER:
+ ia_role_ = ROLE_SYSTEM_SEPARATOR;
+ break;
+ case AccessibilityNodeData::ROLE_SVG_ROOT:
+ ia_role_ = ROLE_SYSTEM_GRAPHIC;
+ break;
+ case AccessibilityNodeData::ROLE_TAB:
+ ia_role_ = ROLE_SYSTEM_PAGETAB;
+ break;
+ case AccessibilityNodeData::ROLE_TABLE: {
+ string16 aria_role;
+ GetStringAttribute(AccessibilityNodeData::ATTR_ROLE, &aria_role);
+ if (aria_role == L"treegrid") {
+ ia_role_ = ROLE_SYSTEM_OUTLINE;
+ } else {
+ ia_role_ = ROLE_SYSTEM_TABLE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ }
+ break;
+ }
+ case AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER:
+ ia_role_ = ROLE_SYSTEM_GROUPING;
+ ia2_role_ = IA2_ROLE_SECTION;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED:
+ NOTREACHED();
+ ia_role_ = ROLE_SYSTEM_PAGETABLIST;
+ break;
+ case AccessibilityNodeData::ROLE_TAB_LIST:
+ ia_role_ = ROLE_SYSTEM_PAGETABLIST;
+ break;
+ case AccessibilityNodeData::ROLE_TAB_PANEL:
+ ia_role_ = ROLE_SYSTEM_PROPERTYPAGE;
+ break;
+ case AccessibilityNodeData::ROLE_TOGGLE_BUTTON:
+ ia_role_ = ROLE_SYSTEM_PUSHBUTTON;
+ ia2_role_ = IA2_ROLE_TOGGLE_BUTTON;
+ break;
+ case AccessibilityNodeData::ROLE_TEXTAREA:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia2_state_ |= IA2_STATE_MULTI_LINE;
+ ia2_state_ |= IA2_STATE_EDITABLE;
+ ia2_state_ |= IA2_STATE_SELECTABLE_TEXT;
+ break;
+ case AccessibilityNodeData::ROLE_TEXT_FIELD:
+ ia_role_ = ROLE_SYSTEM_TEXT;
+ ia2_state_ |= IA2_STATE_SINGLE_LINE;
+ ia2_state_ |= IA2_STATE_EDITABLE;
+ ia2_state_ |= IA2_STATE_SELECTABLE_TEXT;
+ break;
+ case AccessibilityNodeData::ROLE_TIMER:
+ ia_role_ = ROLE_SYSTEM_CLOCK;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TOOLBAR:
+ ia_role_ = ROLE_SYSTEM_TOOLBAR;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TOOLTIP:
+ ia_role_ = ROLE_SYSTEM_TOOLTIP;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TREE:
+ ia_role_ = ROLE_SYSTEM_OUTLINE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TREE_GRID:
+ ia_role_ = ROLE_SYSTEM_OUTLINE;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_TREE_ITEM:
+ ia_role_ = ROLE_SYSTEM_OUTLINEITEM;
+ ia_state_ |= STATE_SYSTEM_READONLY;
+ break;
+ case AccessibilityNodeData::ROLE_WINDOW:
+ ia_role_ = ROLE_SYSTEM_WINDOW;
+ break;
+
+ // TODO(dmazzoni): figure out the proper MSAA role for all of these.
+ case AccessibilityNodeData::ROLE_BROWSER:
+ case AccessibilityNodeData::ROLE_DIRECTORY:
+ case AccessibilityNodeData::ROLE_DRAWER:
+ case AccessibilityNodeData::ROLE_HELP_TAG:
+ case AccessibilityNodeData::ROLE_IGNORED:
+ case AccessibilityNodeData::ROLE_INCREMENTOR:
+ case AccessibilityNodeData::ROLE_LOG:
+ case AccessibilityNodeData::ROLE_MARQUEE:
+ case AccessibilityNodeData::ROLE_MATTE:
+ case AccessibilityNodeData::ROLE_PRESENTATIONAL:
+ case AccessibilityNodeData::ROLE_RULER_MARKER:
+ case AccessibilityNodeData::ROLE_SHEET:
+ case AccessibilityNodeData::ROLE_SLIDER_THUMB:
+ case AccessibilityNodeData::ROLE_SYSTEM_WIDE:
+ case AccessibilityNodeData::ROLE_VALUE_INDICATOR:
+ default:
+ ia_role_ = ROLE_SYSTEM_CLIENT;
+ break;
+ }
+
+ // Compute the final value of READONLY for MSAA.
+ //
+ // We always set the READONLY state for elements that have the
+ // aria-readonly attribute and for a few roles (in the switch above).
+ // We clear the READONLY state on focusable controls and on a document.
+ // Everything else, the majority of objects, do not have this state set.
+ if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) &&
+ ia_role_ != ROLE_SYSTEM_DOCUMENT) {
+ ia_state_ &= ~(STATE_SYSTEM_READONLY);
+ }
+ if (!HasState(AccessibilityNodeData::STATE_READONLY))
+ ia_state_ &= ~(STATE_SYSTEM_READONLY);
+ bool aria_readonly = false;
+ GetBoolAttribute(AccessibilityNodeData::ATTR_ARIA_READONLY, &aria_readonly);
+ if (aria_readonly)
+ ia_state_ |= STATE_SYSTEM_READONLY;
+
+ // The role should always be set.
+ DCHECK(!role_name_.empty() || ia_role_);
+
+ // If we didn't explicitly set the IAccessible2 role, make it the same
+ // as the MSAA role.
+ if (!ia2_role_)
+ ia2_role_ = ia_role_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.h b/chromium/content/browser/accessibility/browser_accessibility_win.h
new file mode 100644
index 00000000000..b6599325da8
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_win.h
@@ -0,0 +1,899 @@
+// 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 CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <oleacc.h>
+#include <UIAutomationCore.h>
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/common/content_export.h"
+#include "third_party/iaccessible2/ia2_api_all.h"
+#include "third_party/isimpledom/ISimpleDOMDocument.h"
+#include "third_party/isimpledom/ISimpleDOMNode.h"
+#include "third_party/isimpledom/ISimpleDOMText.h"
+
+namespace ui {
+enum TextBoundaryDirection;
+enum TextBoundaryType;
+}
+
+namespace content {
+class BrowserAccessibilityRelation;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// BrowserAccessibilityWin
+//
+// Class implementing the windows accessible interface for the Browser-Renderer
+// communication of accessibility information, providing accessibility
+// to be used by screen readers and other assistive technology (AT).
+//
+////////////////////////////////////////////////////////////////////////////////
+class __declspec(uuid("562072fe-3390-43b1-9e2c-dd4118f5ac79"))
+BrowserAccessibilityWin
+ : public BrowserAccessibility,
+ public CComObjectRootEx<CComMultiThreadModel>,
+ public IDispatchImpl<IAccessible2, &IID_IAccessible2,
+ &LIBID_IAccessible2Lib>,
+ public IAccessibleApplication,
+ public IAccessibleHyperlink,
+ public IAccessibleHypertext,
+ public IAccessibleImage,
+ public IAccessibleTable,
+ public IAccessibleTable2,
+ public IAccessibleTableCell,
+ public IAccessibleValue,
+ public IServiceProvider,
+ public ISimpleDOMDocument,
+ public ISimpleDOMNode,
+ public ISimpleDOMText,
+ public IAccessibleEx,
+ public IRawElementProviderSimple {
+ public:
+ BEGIN_COM_MAP(BrowserAccessibilityWin)
+ COM_INTERFACE_ENTRY2(IDispatch, IAccessible2)
+ COM_INTERFACE_ENTRY2(IAccessible, IAccessible2)
+ COM_INTERFACE_ENTRY2(IAccessibleText, IAccessibleHypertext)
+ COM_INTERFACE_ENTRY(IAccessible2)
+ COM_INTERFACE_ENTRY(IAccessibleApplication)
+ COM_INTERFACE_ENTRY(IAccessibleHyperlink)
+ COM_INTERFACE_ENTRY(IAccessibleHypertext)
+ COM_INTERFACE_ENTRY(IAccessibleImage)
+ COM_INTERFACE_ENTRY(IAccessibleTable)
+ COM_INTERFACE_ENTRY(IAccessibleTable2)
+ COM_INTERFACE_ENTRY(IAccessibleTableCell)
+ COM_INTERFACE_ENTRY(IAccessibleValue)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(ISimpleDOMDocument)
+ COM_INTERFACE_ENTRY(ISimpleDOMNode)
+ COM_INTERFACE_ENTRY(ISimpleDOMText)
+ COM_INTERFACE_ENTRY(IAccessibleEx)
+ COM_INTERFACE_ENTRY(IRawElementProviderSimple)
+ END_COM_MAP()
+
+ // Represents a non-static text node in IAccessibleHypertext. This character
+ // is embedded in the response to IAccessibleText::get_text, indicating the
+ // position where a non-static text child object appears.
+ CONTENT_EXPORT static const char16 kEmbeddedCharacter[];
+
+ // Mappings from roles and states to human readable strings. Initialize
+ // with |InitializeStringMaps|.
+ static std::map<int32, string16> role_string_map;
+ static std::map<int32, string16> state_string_map;
+
+ CONTENT_EXPORT BrowserAccessibilityWin();
+
+ CONTENT_EXPORT virtual ~BrowserAccessibilityWin();
+
+ // The Windows-specific unique ID, used as the child ID for MSAA methods
+ // like NotifyWinEvent, and as the unique ID for IAccessible2 and ISimpleDOM.
+ LONG unique_id_win() const { return unique_id_win_; }
+
+ //
+ // BrowserAccessibility methods.
+ //
+ CONTENT_EXPORT virtual void PreInitialize() OVERRIDE;
+ CONTENT_EXPORT virtual void PostInitialize() OVERRIDE;
+ CONTENT_EXPORT virtual void NativeAddReference() OVERRIDE;
+ CONTENT_EXPORT virtual void NativeReleaseReference() OVERRIDE;
+ CONTENT_EXPORT virtual bool IsNative() const OVERRIDE;
+ CONTENT_EXPORT virtual void SetLocation(const gfx::Rect& new_location)
+ OVERRIDE;
+
+ //
+ // IAccessible methods.
+ //
+
+ // Performs the default action on a given object.
+ CONTENT_EXPORT STDMETHODIMP accDoDefaultAction(VARIANT var_id);
+
+ // Retrieves the child element or child object at a given point on the screen.
+ CONTENT_EXPORT STDMETHODIMP accHitTest(LONG x_left, LONG y_top,
+ VARIANT* child);
+
+ // Retrieves the specified object's current screen location.
+ CONTENT_EXPORT STDMETHODIMP accLocation(LONG* x_left,
+ LONG* y_top,
+ LONG* width,
+ LONG* height,
+ VARIANT var_id);
+
+ // Traverses to another UI element and retrieves the object.
+ CONTENT_EXPORT STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start,
+ VARIANT* end);
+
+ // Retrieves an IDispatch interface pointer for the specified child.
+ CONTENT_EXPORT STDMETHODIMP get_accChild(VARIANT var_child,
+ IDispatch** disp_child);
+
+ // Retrieves the number of accessible children.
+ CONTENT_EXPORT STDMETHODIMP get_accChildCount(LONG* child_count);
+
+ // Retrieves a string that describes the object's default action.
+ CONTENT_EXPORT STDMETHODIMP get_accDefaultAction(VARIANT var_id,
+ BSTR* default_action);
+
+ // Retrieves the object's description.
+ CONTENT_EXPORT STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc);
+
+ // Retrieves the object that has the keyboard focus.
+ CONTENT_EXPORT STDMETHODIMP get_accFocus(VARIANT* focus_child);
+
+ // Retrieves the help information associated with the object.
+ CONTENT_EXPORT STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* heflp);
+
+ // Retrieves the specified object's shortcut.
+ CONTENT_EXPORT STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id,
+ BSTR* access_key);
+
+ // Retrieves the name of the specified object.
+ CONTENT_EXPORT STDMETHODIMP get_accName(VARIANT var_id, BSTR* name);
+
+ // Retrieves the IDispatch interface of the object's parent.
+ CONTENT_EXPORT STDMETHODIMP get_accParent(IDispatch** disp_parent);
+
+ // Retrieves information describing the role of the specified object.
+ CONTENT_EXPORT STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role);
+
+ // Retrieves the current state of the specified object.
+ CONTENT_EXPORT STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state);
+
+ // Returns the value associated with the object.
+ CONTENT_EXPORT STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value);
+
+ // Make an object take focus or extend the selection.
+ CONTENT_EXPORT STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id);
+
+ CONTENT_EXPORT STDMETHODIMP get_accHelpTopic(BSTR* help_file,
+ VARIANT var_id,
+ LONG* topic_id);
+
+ CONTENT_EXPORT STDMETHODIMP get_accSelection(VARIANT* selected);
+
+ // Deprecated methods, not implemented.
+ CONTENT_EXPORT STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IAccessible2 methods.
+ //
+
+ // Returns role from a longer list of possible roles.
+ CONTENT_EXPORT STDMETHODIMP role(LONG* role);
+
+ // Returns the state bitmask from a larger set of possible states.
+ CONTENT_EXPORT STDMETHODIMP get_states(AccessibleStates* states);
+
+ // Returns the attributes specific to this IAccessible2 object,
+ // such as a cell's formula.
+ CONTENT_EXPORT STDMETHODIMP get_attributes(BSTR* attributes);
+
+ // Get the unique ID of this object so that the client knows if it's
+ // been encountered previously.
+ CONTENT_EXPORT STDMETHODIMP get_uniqueID(LONG* unique_id);
+
+ // Get the window handle of the enclosing window.
+ CONTENT_EXPORT STDMETHODIMP get_windowHandle(HWND* window_handle);
+
+ // Get this object's index in its parent object.
+ CONTENT_EXPORT STDMETHODIMP get_indexInParent(LONG* index_in_parent);
+
+ CONTENT_EXPORT STDMETHODIMP get_nRelations(LONG* n_relations);
+
+ CONTENT_EXPORT STDMETHODIMP get_relation(LONG relation_index,
+ IAccessibleRelation** relation);
+
+ CONTENT_EXPORT STDMETHODIMP get_relations(LONG max_relations,
+ IAccessibleRelation** relations,
+ LONG* n_relations);
+
+ CONTENT_EXPORT STDMETHODIMP scrollTo(enum IA2ScrollType scroll_type);
+
+ CONTENT_EXPORT STDMETHODIMP scrollToPoint(
+ enum IA2CoordinateType coordinate_type,
+ LONG x,
+ LONG y);
+
+ CONTENT_EXPORT STDMETHODIMP get_groupPosition(LONG* group_level,
+ LONG* similar_items_in_group,
+ LONG* position_in_group);
+
+ //
+ // IAccessibleEx methods not implemented.
+ //
+ CONTENT_EXPORT STDMETHODIMP get_extendedRole(BSTR* extended_role) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_localizedExtendedRole(
+ BSTR* localized_extended_role) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_nExtendedStates(LONG* n_extended_states) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_extendedStates(LONG max_extended_states,
+ BSTR** extended_states,
+ LONG* n_extended_states) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_localizedExtendedStates(
+ LONG max_localized_extended_states,
+ BSTR** localized_extended_states,
+ LONG* n_localized_extended_states) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_locale(IA2Locale* locale) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IAccessibleApplication methods.
+ //
+ CONTENT_EXPORT STDMETHODIMP get_appName(BSTR* app_name);
+
+ CONTENT_EXPORT STDMETHODIMP get_appVersion(BSTR* app_version);
+
+ CONTENT_EXPORT STDMETHODIMP get_toolkitName(BSTR* toolkit_name);
+
+ CONTENT_EXPORT STDMETHODIMP get_toolkitVersion(BSTR* toolkit_version);
+
+ //
+ // IAccessibleImage methods.
+ //
+ CONTENT_EXPORT STDMETHODIMP get_description(BSTR* description);
+
+ CONTENT_EXPORT STDMETHODIMP get_imagePosition(
+ enum IA2CoordinateType coordinate_type,
+ LONG* x,
+ LONG* y);
+
+ CONTENT_EXPORT STDMETHODIMP get_imageSize(LONG* height, LONG* width);
+
+ //
+ // IAccessibleTable methods.
+ //
+
+ // get_description - also used by IAccessibleImage
+
+ CONTENT_EXPORT STDMETHODIMP get_accessibleAt(long row,
+ long column,
+ IUnknown** accessible);
+
+ CONTENT_EXPORT STDMETHODIMP get_caption(IUnknown** accessible);
+
+ CONTENT_EXPORT STDMETHODIMP get_childIndex(long row_index,
+ long column_index,
+ long* cell_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnDescription(long column,
+ BSTR* description);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnExtentAt(long row,
+ long column,
+ long* n_columns_spanned);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnHeader(
+ IAccessibleTable** accessible_table,
+ long* starting_row_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnIndex(long cell_index,
+ long* column_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_nColumns(long* column_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_nRows(long* row_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_nSelectedChildren(long* cell_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_nSelectedColumns(long* column_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_nSelectedRows(long *row_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowDescription(long row,
+ BSTR* description);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowExtentAt(long row,
+ long column,
+ long* n_rows_spanned);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowHeader(IAccessibleTable** accessible_table,
+ long* starting_column_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowIndex(long cell_index,
+ long* row_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedChildren(long max_children,
+ long** children,
+ long* n_children);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedColumns(long max_columns,
+ long** columns,
+ long* n_columns);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedRows(long max_rows,
+ long** rows,
+ long* n_rows);
+
+ CONTENT_EXPORT STDMETHODIMP get_summary(IUnknown** accessible);
+
+ CONTENT_EXPORT STDMETHODIMP get_isColumnSelected(long column,
+ boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP get_isRowSelected(long row,
+ boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP get_isSelected(long row,
+ long column,
+ boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowColumnExtentsAtIndex(long index,
+ long* row,
+ long* column,
+ long* row_extents,
+ long* column_extents,
+ boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP selectRow(long row) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP selectColumn(long column) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP unselectRow(long row) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP unselectColumn(long column) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_modelChange(
+ IA2TableModelChange* model_change) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IAccessibleTable2 methods.
+ //
+ // (Most of these are duplicates of IAccessibleTable methods, only the
+ // unique ones are included here.)
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_cellAt(long row,
+ long column,
+ IUnknown** cell);
+
+ CONTENT_EXPORT STDMETHODIMP get_nSelectedCells(long* cell_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedCells(IUnknown*** cells,
+ long* n_selected_cells);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedColumns(long** columns,
+ long* n_columns);
+
+ CONTENT_EXPORT STDMETHODIMP get_selectedRows(long** rows,
+ long* n_rows);
+
+ //
+ // IAccessibleTableCell methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_columnExtent(long* n_columns_spanned);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnHeaderCells(
+ IUnknown*** cell_accessibles,
+ long* n_column_header_cells);
+
+ CONTENT_EXPORT STDMETHODIMP get_columnIndex(long* column_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowExtent(long* n_rows_spanned);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowHeaderCells(IUnknown*** cell_accessibles,
+ long* n_row_header_cells);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowIndex(long* row_index);
+
+ CONTENT_EXPORT STDMETHODIMP get_isSelected(boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP get_rowColumnExtents(long* row,
+ long* column,
+ long* row_extents,
+ long* column_extents,
+ boolean* is_selected);
+
+ CONTENT_EXPORT STDMETHODIMP get_table(IUnknown** table);
+
+ //
+ // IAccessibleText methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_nCharacters(LONG* n_characters);
+
+ CONTENT_EXPORT STDMETHODIMP get_caretOffset(LONG* offset);
+
+ CONTENT_EXPORT STDMETHODIMP get_nSelections(LONG* n_selections);
+
+ CONTENT_EXPORT STDMETHODIMP get_selection(LONG selection_index,
+ LONG* start_offset,
+ LONG* end_offset);
+
+ CONTENT_EXPORT STDMETHODIMP get_text(LONG start_offset,
+ LONG end_offset,
+ BSTR* text);
+
+ CONTENT_EXPORT STDMETHODIMP get_textAtOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text);
+
+ CONTENT_EXPORT STDMETHODIMP get_textBeforeOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text);
+
+ CONTENT_EXPORT STDMETHODIMP get_textAfterOffset(
+ LONG offset,
+ enum IA2TextBoundaryType boundary_type,
+ LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text);
+
+ CONTENT_EXPORT STDMETHODIMP get_newText(IA2TextSegment* new_text);
+
+ CONTENT_EXPORT STDMETHODIMP get_oldText(IA2TextSegment* old_text);
+
+ CONTENT_EXPORT STDMETHODIMP get_offsetAtPoint(
+ LONG x,
+ LONG y,
+ enum IA2CoordinateType coord_type,
+ LONG* offset);
+
+ CONTENT_EXPORT STDMETHODIMP scrollSubstringTo(
+ LONG start_index,
+ LONG end_index,
+ enum IA2ScrollType scroll_type);
+
+ CONTENT_EXPORT STDMETHODIMP scrollSubstringToPoint(
+ LONG start_index,
+ LONG end_index,
+ enum IA2CoordinateType coordinate_type,
+ LONG x, LONG y);
+
+ CONTENT_EXPORT STDMETHODIMP addSelection(LONG start_offset, LONG end_offset);
+
+ CONTENT_EXPORT STDMETHODIMP removeSelection(LONG selection_index);
+
+ CONTENT_EXPORT STDMETHODIMP setCaretOffset(LONG offset);
+
+ CONTENT_EXPORT STDMETHODIMP setSelection(LONG selection_index,
+ LONG start_offset,
+ LONG end_offset);
+
+ // IAccessibleText methods not implemented.
+ CONTENT_EXPORT STDMETHODIMP get_attributes(LONG offset, LONG* start_offset,
+ LONG* end_offset,
+ BSTR* text_attributes) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_characterExtents(LONG offset,
+ enum IA2CoordinateType coord_type,
+ LONG* x,
+ LONG* y,
+ LONG* width,
+ LONG* height) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IAccessibleHypertext methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_nHyperlinks(long* hyperlink_count);
+
+ CONTENT_EXPORT STDMETHODIMP get_hyperlink(long index,
+ IAccessibleHyperlink** hyperlink);
+
+ CONTENT_EXPORT STDMETHODIMP get_hyperlinkIndex(long char_index,
+ long* hyperlink_index);
+
+ // IAccessibleHyperlink not implemented.
+ CONTENT_EXPORT STDMETHODIMP get_anchor(long index, VARIANT* anchor) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_anchorTarget(long index,
+ VARIANT* anchor_target) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_startIndex( long* index) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_endIndex( long* index) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_valid(boolean* valid) {
+ return E_NOTIMPL;
+ }
+
+ // IAccessibleAction not implemented.
+ CONTENT_EXPORT STDMETHODIMP nActions(long* n_actions) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP doAction(long action_index) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_description(long action_index,
+ BSTR* description) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_keyBinding(long action_index,
+ long n_max_bindings,
+ BSTR** key_bindings,
+ long* n_bindings) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_name(long action_index, BSTR* name) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP get_localizedName(long action_index,
+ BSTR* localized_name) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IAccessibleValue methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_currentValue(VARIANT* value);
+
+ CONTENT_EXPORT STDMETHODIMP get_minimumValue(VARIANT* value);
+
+ CONTENT_EXPORT STDMETHODIMP get_maximumValue(VARIANT* value);
+
+ CONTENT_EXPORT STDMETHODIMP setCurrentValue(VARIANT new_value);
+
+ //
+ // ISimpleDOMDocument methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_URL(BSTR* url);
+
+ CONTENT_EXPORT STDMETHODIMP get_title(BSTR* title);
+
+ CONTENT_EXPORT STDMETHODIMP get_mimeType(BSTR* mime_type);
+
+ CONTENT_EXPORT STDMETHODIMP get_docType(BSTR* doc_type);
+
+ CONTENT_EXPORT STDMETHODIMP get_nameSpaceURIForID(short name_space_id,
+ BSTR* name_space_uri) {
+ return E_NOTIMPL;
+ }
+ CONTENT_EXPORT STDMETHODIMP put_alternateViewMediaTypes(
+ BSTR* comma_separated_media_types) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // ISimpleDOMNode methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_nodeInfo(BSTR* node_name,
+ short* name_space_id,
+ BSTR* node_value,
+ unsigned int* num_children,
+ unsigned int* unique_id,
+ unsigned short* node_type);
+
+ CONTENT_EXPORT STDMETHODIMP get_attributes(unsigned short max_attribs,
+ BSTR* attrib_names,
+ short* name_space_id,
+ BSTR* attrib_values,
+ unsigned short* num_attribs);
+
+ CONTENT_EXPORT STDMETHODIMP get_attributesForNames(
+ unsigned short num_attribs,
+ BSTR* attrib_names,
+ short* name_space_id,
+ BSTR* attrib_values);
+
+ CONTENT_EXPORT STDMETHODIMP get_computedStyle(
+ unsigned short max_style_properties,
+ boolean use_alternate_view,
+ BSTR *style_properties,
+ BSTR *style_values,
+ unsigned short *num_style_properties);
+
+ CONTENT_EXPORT STDMETHODIMP get_computedStyleForProperties(
+ unsigned short num_style_properties,
+ boolean use_alternate_view,
+ BSTR* style_properties,
+ BSTR* style_values);
+
+ CONTENT_EXPORT STDMETHODIMP scrollTo(boolean placeTopLeft);
+
+ CONTENT_EXPORT STDMETHODIMP get_parentNode(ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_firstChild(ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_lastChild(ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_previousSibling(ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_nextSibling(ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_childAt(unsigned int child_index,
+ ISimpleDOMNode** node);
+
+ CONTENT_EXPORT STDMETHODIMP get_innerHTML(BSTR* innerHTML) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_localInterface(void** local_interface) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_language(BSTR* language) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // ISimpleDOMText methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP get_domText(BSTR* dom_text);
+
+ CONTENT_EXPORT STDMETHODIMP get_clippedSubstringBounds(
+ unsigned int start_index,
+ unsigned int end_index,
+ int* x,
+ int* y,
+ int* width,
+ int* height) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_unclippedSubstringBounds(
+ unsigned int start_index,
+ unsigned int end_index,
+ int* x,
+ int* y,
+ int* width,
+ int* height) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP scrollToSubstring(unsigned int start_index,
+ unsigned int end_index) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_fontFamily(BSTR *font_family) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IServiceProvider methods.
+ //
+
+ CONTENT_EXPORT STDMETHODIMP QueryService(REFGUID guidService,
+ REFIID riid,
+ void** object);
+
+ // IAccessibleEx methods not implemented.
+ CONTENT_EXPORT STDMETHODIMP GetObjectForChild(long child_id,
+ IAccessibleEx** ret) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP GetIAccessiblePair(IAccessible** acc,
+ long* child_id) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP GetRuntimeId(SAFEARRAY** runtime_id) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP ConvertReturnedElement(
+ IRawElementProviderSimple* element,
+ IAccessibleEx** acc) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // IRawElementProviderSimple methods.
+ //
+ // The GetPatternProvider/GetPropertyValue methods need to be implemented for
+ // the on-screen keyboard to show up in Windows 8 metro.
+ CONTENT_EXPORT STDMETHODIMP GetPatternProvider(PATTERNID id,
+ IUnknown** provider);
+ CONTENT_EXPORT STDMETHODIMP GetPropertyValue(PROPERTYID id, VARIANT* ret);
+
+ //
+ // IRawElementProviderSimple methods not implemented
+ //
+ CONTENT_EXPORT STDMETHODIMP get_ProviderOptions(enum ProviderOptions* ret) {
+ return E_NOTIMPL;
+ }
+
+ CONTENT_EXPORT STDMETHODIMP get_HostRawElementProvider(
+ IRawElementProviderSimple** provider) {
+ return E_NOTIMPL;
+ }
+
+ //
+ // CComObjectRootEx methods.
+ //
+
+ CONTENT_EXPORT HRESULT WINAPI InternalQueryInterface(
+ void* this_ptr,
+ const _ATL_INTMAP_ENTRY* entries,
+ REFIID iid,
+ void** object);
+
+ // Accessors.
+ int32 ia_role() const { return ia_role_; }
+ int32 ia_state() const { return ia_state_; }
+ int32 ia2_role() const { return ia2_role_; }
+ int32 ia2_state() const { return ia2_state_; }
+ const std::vector<string16>& ia2_attributes() const {
+ return ia2_attributes_;
+ }
+
+ private:
+ // Add one to the reference count and return the same object. Always
+ // use this method when returning a BrowserAccessibilityWin object as
+ // an output parameter to a COM interface, never use it otherwise.
+ BrowserAccessibilityWin* NewReference();
+
+ // Many MSAA methods take a var_id parameter indicating that the operation
+ // should be performed on a particular child ID, rather than this object.
+ // This method tries to figure out the target object from |var_id| and
+ // returns a pointer to the target object if it exists, otherwise NULL.
+ // Does not return a new reference.
+ BrowserAccessibilityWin* GetTargetFromChildID(const VARIANT& var_id);
+
+ // Initialize the role and state metadata from the role enum and state
+ // bitmasks defined in AccessibilityNodeData.
+ void InitRoleAndState();
+
+ // Retrieve the value of an attribute from the string attribute map and
+ // if found and nonempty, allocate a new BSTR (with SysAllocString)
+ // and return S_OK. If not found or empty, return S_FALSE.
+ HRESULT GetStringAttributeAsBstr(
+ AccessibilityNodeData::StringAttribute attribute,
+ BSTR* value_bstr);
+
+ // If the string attribute |attribute| is present, add its value as an
+ // IAccessible2 attribute with the name |ia2_attr|.
+ void StringAttributeToIA2(AccessibilityNodeData::StringAttribute attribute,
+ const char* ia2_attr);
+
+ // If the bool attribute |attribute| is present, add its value as an
+ // IAccessible2 attribute with the name |ia2_attr|.
+ void BoolAttributeToIA2(AccessibilityNodeData::BoolAttribute attribute,
+ const char* ia2_attr);
+
+ // If the int attribute |attribute| is present, add its value as an
+ // IAccessible2 attribute with the name |ia2_attr|.
+ void IntAttributeToIA2(AccessibilityNodeData::IntAttribute attribute,
+ const char* ia2_attr);
+
+ // Get the text of this node for the purposes of IAccessibleText - it may
+ // be the name, it may be the value, etc. depending on the role.
+ const string16& TextForIAccessibleText();
+
+ // If offset is a member of IA2TextSpecialOffsets this function updates the
+ // value of offset and returns, otherwise offset remains unchanged.
+ void HandleSpecialTextOffset(const string16& text, LONG* offset);
+
+ // Convert from a IA2TextBoundaryType to a ui::TextBoundaryType.
+ ui::TextBoundaryType IA2TextBoundaryToTextBoundary(IA2TextBoundaryType type);
+
+ // Search forwards (direction == 1) or backwards (direction == -1)
+ // from the given offset until the given boundary is found, and
+ // return the offset of that boundary.
+ LONG FindBoundary(const string16& text,
+ IA2TextBoundaryType ia2_boundary,
+ LONG start_offset,
+ ui::TextBoundaryDirection direction);
+
+ // Return a pointer to the object corresponding to the given renderer_id,
+ // does not make a new reference.
+ BrowserAccessibilityWin* GetFromRendererID(int32 renderer_id);
+
+ // Windows-specific unique ID (unique within the browser process),
+ // used for get_accChild, NotifyWinEvent, and as the unique ID for
+ // IAccessible2 and ISimpleDOM.
+ LONG unique_id_win_;
+
+ // IAccessible role and state.
+ int32 ia_role_;
+ int32 ia_state_;
+
+ // IAccessible2 role and state.
+ int32 ia2_role_;
+ int32 ia2_state_;
+
+ // IAccessible2 attributes.
+ std::vector<string16> ia2_attributes_;
+
+ // True in Initialize when the object is first created, and false
+ // subsequent times.
+ bool first_time_;
+
+ // The previous text, before the last update to this object.
+ string16 previous_text_;
+
+ // The old text to return in IAccessibleText::get_oldText - this is like
+ // previous_text_ except that it's NOT updated when the object
+ // is initialized again but the text doesn't change.
+ string16 old_text_;
+
+ // The previous state, used to see if there was a state change.
+ int32 old_ia_state_;
+
+ // Relationships between this node and other nodes.
+ std::vector<BrowserAccessibilityRelation*> relations_;
+
+ // The text of this node including embedded hyperlink characters.
+ string16 hypertext_;
+
+ // Maps the |hypertext_| embedded character offset to an index in
+ // |hyperlinks_|.
+ std::map<int32, int32> hyperlink_offset_to_index_;
+
+ // Collection of non-static text child indicies, each of which corresponds to
+ // a hyperlink.
+ std::vector<int32> hyperlinks_;
+
+ // The next unique id to use.
+ static LONG next_unique_id_win_;
+
+ // Give BrowserAccessibility::Create access to our constructor.
+ friend class BrowserAccessibility;
+ friend class BrowserAccessibilityRelation;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc
new file mode 100644
index 00000000000..98a5d404a43
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc
@@ -0,0 +1,660 @@
+// 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 "base/memory/scoped_ptr.h"
+#include "base/win/scoped_bstr.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/scoped_variant.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_manager_win.h"
+#include "content/browser/accessibility/browser_accessibility_win.h"
+#include "content/common/accessibility_messages.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/win/atl_module.h"
+
+namespace content {
+namespace {
+
+
+// CountedBrowserAccessibility ------------------------------------------------
+
+// Subclass of BrowserAccessibilityWin that counts the number of instances.
+class CountedBrowserAccessibility : public BrowserAccessibilityWin {
+ public:
+ CountedBrowserAccessibility();
+ virtual ~CountedBrowserAccessibility();
+
+ static void reset() { num_instances_ = 0; }
+ static int num_instances() { return num_instances_; }
+
+ private:
+ static int num_instances_;
+
+ DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility);
+};
+
+// static
+int CountedBrowserAccessibility::num_instances_ = 0;
+
+CountedBrowserAccessibility::CountedBrowserAccessibility() {
+ ++num_instances_;
+}
+
+CountedBrowserAccessibility::~CountedBrowserAccessibility() {
+ --num_instances_;
+}
+
+
+// CountedBrowserAccessibilityFactory -----------------------------------------
+
+// Factory that creates a CountedBrowserAccessibility.
+class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory {
+ public:
+ CountedBrowserAccessibilityFactory();
+
+ private:
+ virtual ~CountedBrowserAccessibilityFactory();
+
+ virtual BrowserAccessibility* Create() OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory);
+};
+
+CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() {
+}
+
+CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() {
+}
+
+BrowserAccessibility* CountedBrowserAccessibilityFactory::Create() {
+ CComObject<CountedBrowserAccessibility>* instance;
+ HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance(
+ &instance);
+ DCHECK(SUCCEEDED(hr));
+ instance->AddRef();
+ return instance;
+}
+
+} // namespace
+
+
+// BrowserAccessibilityTest ---------------------------------------------------
+
+class BrowserAccessibilityTest : public testing::Test {
+ public:
+ BrowserAccessibilityTest();
+ virtual ~BrowserAccessibilityTest();
+
+ private:
+ virtual void SetUp() OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest);
+};
+
+BrowserAccessibilityTest::BrowserAccessibilityTest() {
+}
+
+BrowserAccessibilityTest::~BrowserAccessibilityTest() {
+}
+
+void BrowserAccessibilityTest::SetUp() {
+ ui::win::CreateATLModuleIfNeeded();
+}
+
+
+// Actual tests ---------------------------------------------------------------
+
+// Test that BrowserAccessibilityManager correctly releases the tree of
+// BrowserAccessibility instances upon delete.
+TEST_F(BrowserAccessibilityTest, TestNoLeaks) {
+ // Create AccessibilityNodeData objects for a simple document tree,
+ // representing the accessibility information used to initialize
+ // BrowserAccessibilityManager.
+ AccessibilityNodeData button;
+ button.id = 2;
+ button.name = L"Button";
+ button.role = AccessibilityNodeData::ROLE_BUTTON;
+ button.state = 0;
+
+ AccessibilityNodeData checkbox;
+ checkbox.id = 3;
+ checkbox.name = L"Checkbox";
+ checkbox.role = AccessibilityNodeData::ROLE_CHECKBOX;
+ checkbox.state = 0;
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.name = L"Document";
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(2);
+ root.child_ids.push_back(3);
+
+ // Construct a BrowserAccessibilityManager with this
+ // AccessibilityNodeData tree and a factory for an instance-counting
+ // BrowserAccessibility, and ensure that exactly 3 instances were
+ // created. Note that the manager takes ownership of the factory.
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(button, checkbox);
+ ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
+
+ // Delete the manager and test that all 3 instances are deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+
+ // Construct a manager again, and this time use the IAccessible interface
+ // to get new references to two of the three nodes in the tree.
+ manager.reset(BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(button, checkbox);
+ ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
+ IAccessible* root_accessible =
+ manager->GetRoot()->ToBrowserAccessibilityWin();
+ IDispatch* root_iaccessible = NULL;
+ IDispatch* child1_iaccessible = NULL;
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible);
+ ASSERT_EQ(S_OK, hr);
+ base::win::ScopedVariant one(1);
+ hr = root_accessible->get_accChild(one, &child1_iaccessible);
+ ASSERT_EQ(S_OK, hr);
+
+ // Now delete the manager, and only one of the three nodes in the tree
+ // should be released.
+ manager.reset();
+ ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
+
+ // Release each of our references and make sure that each one results in
+ // the instance being deleted as its reference count hits zero.
+ root_iaccessible->Release();
+ ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
+ child1_iaccessible->Release();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestChildrenChange) {
+ // Create AccessibilityNodeData objects for a simple document tree,
+ // representing the accessibility information used to initialize
+ // BrowserAccessibilityManager.
+ AccessibilityNodeData text;
+ text.id = 2;
+ text.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text.name = L"old text";
+ text.state = 0;
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.name = L"Document";
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(2);
+
+ // Construct a BrowserAccessibilityManager with this
+ // AccessibilityNodeData tree and a factory for an instance-counting
+ // BrowserAccessibility.
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(text);
+
+ // Query for the text IAccessible and verify that it returns "old text" as its
+ // value.
+ base::win::ScopedVariant one(1);
+ base::win::ScopedComPtr<IDispatch> text_dispatch;
+ HRESULT hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
+ one, text_dispatch.Receive());
+ ASSERT_EQ(S_OK, hr);
+
+ base::win::ScopedComPtr<IAccessible> text_accessible;
+ hr = text_dispatch.QueryInterface(text_accessible.Receive());
+ ASSERT_EQ(S_OK, hr);
+
+ base::win::ScopedVariant childid_self(CHILDID_SELF);
+ base::win::ScopedBstr name;
+ hr = text_accessible->get_accName(childid_self, name.Receive());
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(L"old text", string16(name));
+ name.Reset();
+
+ text_dispatch.Release();
+ text_accessible.Release();
+
+ // Notify the BrowserAccessibilityManager that the text child has changed.
+ text.name = L"new text";
+ AccessibilityHostMsg_NotificationParams param;
+ param.notification_type = AccessibilityNotificationChildrenChanged;
+ param.nodes.push_back(text);
+ param.id = text.id;
+ std::vector<AccessibilityHostMsg_NotificationParams> notifications;
+ notifications.push_back(param);
+ manager->OnAccessibilityNotifications(notifications);
+
+ // Query for the text IAccessible and verify that it now returns "new text"
+ // as its value.
+ hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
+ one, text_dispatch.Receive());
+ ASSERT_EQ(S_OK, hr);
+
+ hr = text_dispatch.QueryInterface(text_accessible.Receive());
+ ASSERT_EQ(S_OK, hr);
+
+ hr = text_accessible->get_accName(childid_self, name.Receive());
+ ASSERT_EQ(S_OK, hr);
+ EXPECT_EQ(L"new text", string16(name));
+
+ text_dispatch.Release();
+ text_accessible.Release();
+
+ // Delete the manager and test that all BrowserAccessibility instances are
+ // deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) {
+ // Create AccessibilityNodeData objects for a simple document tree,
+ // representing the accessibility information used to initialize
+ // BrowserAccessibilityManager.
+ AccessibilityNodeData div;
+ div.id = 2;
+ div.role = AccessibilityNodeData::ROLE_GROUP;
+ div.state = 0;
+
+ AccessibilityNodeData text3;
+ text3.id = 3;
+ text3.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text3.state = 0;
+
+ AccessibilityNodeData text4;
+ text4.id = 4;
+ text4.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text4.state = 0;
+
+ div.child_ids.push_back(3);
+ div.child_ids.push_back(4);
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(2);
+
+ // Construct a BrowserAccessibilityManager with this
+ // AccessibilityNodeData tree and a factory for an instance-counting
+ // BrowserAccessibility and ensure that exactly 4 instances were
+ // created. Note that the manager takes ownership of the factory.
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(div, text3, text4);
+ ASSERT_EQ(4, CountedBrowserAccessibility::num_instances());
+
+ // Notify the BrowserAccessibilityManager that the div node and its children
+ // were removed and ensure that only one BrowserAccessibility instance exists.
+ root.child_ids.clear();
+ AccessibilityHostMsg_NotificationParams param;
+ param.notification_type = AccessibilityNotificationChildrenChanged;
+ param.nodes.push_back(root);
+ param.id = root.id;
+ std::vector<AccessibilityHostMsg_NotificationParams> notifications;
+ notifications.push_back(param);
+ manager->OnAccessibilityNotifications(notifications);
+ ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
+
+ // Delete the manager and test that all BrowserAccessibility instances are
+ // deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestTextBoundaries) {
+ AccessibilityNodeData text1;
+ text1.id = 11;
+ text1.role = AccessibilityNodeData::ROLE_TEXT_FIELD;
+ text1.state = 0;
+ text1.value = L"One two three.\nFour five six.";
+ text1.line_breaks.push_back(15);
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 0;
+ root.child_ids.push_back(11);
+
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(text1);
+ ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
+
+ BrowserAccessibilityWin* root_obj =
+ manager->GetRoot()->ToBrowserAccessibilityWin();
+ BrowserAccessibilityWin* text1_obj =
+ root_obj->GetChild(0)->ToBrowserAccessibilityWin();
+
+ long text1_len;
+ ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len));
+
+ base::win::ScopedBstr text;
+ ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, text.Receive()));
+ ASSERT_EQ(text1.value, string16(text));
+ text.Reset();
+
+ ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, text.Receive()));
+ ASSERT_STREQ(L"One ", text);
+ text.Reset();
+
+ long start;
+ long end;
+ ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
+ 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
+ ASSERT_EQ(1, start);
+ ASSERT_EQ(2, end);
+ ASSERT_STREQ(L"n", text);
+ text.Reset();
+
+ ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset(
+ text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
+ ASSERT_EQ(text1_len, start);
+ ASSERT_EQ(text1_len, end);
+ text.Reset();
+
+ ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
+ 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
+ ASSERT_EQ(0, start);
+ ASSERT_EQ(3, end);
+ ASSERT_STREQ(L"One", text);
+ text.Reset();
+
+ ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
+ 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
+ ASSERT_EQ(4, start);
+ ASSERT_EQ(7, end);
+ ASSERT_STREQ(L"two", text);
+ text.Reset();
+
+ ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
+ text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
+ ASSERT_EQ(25, start);
+ ASSERT_EQ(29, end);
+ ASSERT_STREQ(L"six.", text);
+ text.Reset();
+
+ ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
+ 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
+ ASSERT_EQ(0, start);
+ ASSERT_EQ(15, end);
+ ASSERT_STREQ(L"One two three.\n", text);
+ text.Reset();
+
+ ASSERT_EQ(S_OK,
+ text1_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
+ ASSERT_STREQ(L"One two three.\nFour five six.", text);
+
+ // Delete the manager and test that all BrowserAccessibility instances are
+ // deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) {
+ AccessibilityNodeData text1;
+ text1.id = 11;
+ text1.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text1.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ text1.name = L"One two three.";
+
+ AccessibilityNodeData text2;
+ text2.id = 12;
+ text2.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text2.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ text2.name = L" Four five six.";
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ root.child_ids.push_back(11);
+ root.child_ids.push_back(12);
+
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(root, text1, text2);
+ ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
+
+ BrowserAccessibilityWin* root_obj =
+ manager->GetRoot()->ToBrowserAccessibilityWin();
+
+ long text_len;
+ ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
+
+ base::win::ScopedBstr text;
+ ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
+ EXPECT_EQ(text1.name + text2.name, string16(text));
+
+ long hyperlink_count;
+ ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
+ EXPECT_EQ(0, hyperlink_count);
+
+ base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive()));
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive()));
+
+ long hyperlink_index;
+ EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+ EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+
+ // Delete the manager and test that all BrowserAccessibility instances are
+ // deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestComplexHypertext) {
+ AccessibilityNodeData text1;
+ text1.id = 11;
+ text1.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text1.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ text1.name = L"One two three.";
+
+ AccessibilityNodeData text2;
+ text2.id = 12;
+ text2.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ text2.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ text2.name = L" Four five six.";
+
+ AccessibilityNodeData button1, button1_text;
+ button1.id = 13;
+ button1_text.id = 15;
+ button1_text.name = L"red";
+ button1.role = AccessibilityNodeData::ROLE_BUTTON;
+ button1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ button1.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ button1_text.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ button1.child_ids.push_back(15);
+
+ AccessibilityNodeData link1, link1_text;
+ link1.id = 14;
+ link1_text.id = 16;
+ link1_text.name = L"blue";
+ link1.role = AccessibilityNodeData::ROLE_LINK;
+ link1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT;
+ link1.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ link1_text.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ link1.child_ids.push_back(16);
+
+ AccessibilityNodeData root;
+ root.id = 1;
+ root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ root.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ root.child_ids.push_back(11);
+ root.child_ids.push_back(13);
+ root.child_ids.push_back(12);
+ root.child_ids.push_back(14);
+
+ CountedBrowserAccessibility::reset();
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ root, NULL, new CountedBrowserAccessibilityFactory()));
+ manager->UpdateNodesForTesting(root,
+ text1, button1, button1_text,
+ text2, link1, link1_text);
+
+ ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());
+
+ BrowserAccessibilityWin* root_obj =
+ manager->GetRoot()->ToBrowserAccessibilityWin();
+
+ long text_len;
+ ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
+
+ base::win::ScopedBstr text;
+ ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
+ const string16 embed = BrowserAccessibilityWin::kEmbeddedCharacter;
+ EXPECT_EQ(text1.name + embed + text2.name + embed, string16(text));
+ text.Reset();
+
+ long hyperlink_count;
+ ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
+ EXPECT_EQ(2, hyperlink_count);
+
+ base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
+ base::win::ScopedComPtr<IAccessibleText> hypertext;
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive()));
+ EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
+
+ EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive()));
+ EXPECT_EQ(S_OK,
+ hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
+ EXPECT_EQ(S_OK, hypertext->get_text(0, 3, text.Receive()));
+ EXPECT_STREQ(L"red", text);
+ text.Reset();
+ hyperlink.Release();
+ hypertext.Release();
+
+ EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive()));
+ EXPECT_EQ(S_OK,
+ hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
+ EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive()));
+ EXPECT_STREQ(L"blue", text);
+ text.Reset();
+ hyperlink.Release();
+ hypertext.Release();
+
+ long hyperlink_index;
+ EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+ EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
+ EXPECT_EQ(-1, hyperlink_index);
+ EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index));
+ EXPECT_EQ(0, hyperlink_index);
+ EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index));
+ EXPECT_EQ(1, hyperlink_index);
+
+ // Delete the manager and test that all BrowserAccessibility instances are
+ // deleted.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+TEST_F(BrowserAccessibilityTest, TestCreateEmptyDocument) {
+ // Try creating an empty document with busy state. Readonly is
+ // set automatically.
+ CountedBrowserAccessibility::reset();
+ const int32 busy_state = 1 << AccessibilityNodeData::STATE_BUSY;
+ const int32 readonly_state = 1 << AccessibilityNodeData::STATE_READONLY;
+ scoped_ptr<BrowserAccessibilityManager> manager(
+ new BrowserAccessibilityManagerWin(
+ GetDesktopWindow(),
+ NULL,
+ BrowserAccessibilityManagerWin::GetEmptyDocument(),
+ NULL,
+ new CountedBrowserAccessibilityFactory()));
+
+ // Verify the root is as we expect by default.
+ BrowserAccessibility* root = manager->GetRoot();
+ EXPECT_EQ(0, root->renderer_id());
+ EXPECT_EQ(AccessibilityNodeData::ROLE_ROOT_WEB_AREA, root->role());
+ EXPECT_EQ(busy_state | readonly_state, root->state());
+
+ // Tree with a child textfield.
+ AccessibilityNodeData tree1_1;
+ tree1_1.id = 1;
+ tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree1_1.child_ids.push_back(2);
+
+ AccessibilityNodeData tree1_2;
+ tree1_2.id = 2;
+ tree1_2.role = AccessibilityNodeData::ROLE_TEXT_FIELD;
+
+ // Process a load complete.
+ std::vector<AccessibilityHostMsg_NotificationParams> params;
+ params.push_back(AccessibilityHostMsg_NotificationParams());
+ AccessibilityHostMsg_NotificationParams* msg = &params[0];
+ msg->notification_type = AccessibilityNotificationLoadComplete;
+ msg->nodes.push_back(tree1_1);
+ msg->nodes.push_back(tree1_2);
+ msg->id = tree1_1.id;
+ manager->OnAccessibilityNotifications(params);
+
+ // Save for later comparison.
+ BrowserAccessibility* acc1_2 = manager->GetFromRendererID(2);
+
+ // Verify the root has changed.
+ EXPECT_NE(root, manager->GetRoot());
+
+ // And the proper child remains.
+ EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, acc1_2->role());
+ EXPECT_EQ(2, acc1_2->renderer_id());
+
+ // Tree with a child button.
+ AccessibilityNodeData tree2_1;
+ tree2_1.id = 1;
+ tree2_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ tree2_1.child_ids.push_back(3);
+
+ AccessibilityNodeData tree2_2;
+ tree2_2.id = 3;
+ tree2_2.role = AccessibilityNodeData::ROLE_BUTTON;
+
+ msg->nodes.clear();
+ msg->nodes.push_back(tree2_1);
+ msg->nodes.push_back(tree2_2);
+ msg->id = tree2_1.id;
+
+ // Fire another load complete.
+ manager->OnAccessibilityNotifications(params);
+
+ BrowserAccessibility* acc2_2 = manager->GetFromRendererID(3);
+
+ // Verify the root has changed.
+ EXPECT_NE(root, manager->GetRoot());
+
+ // And the new child exists.
+ EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, acc2_2->role());
+ EXPECT_EQ(3, acc2_2->renderer_id());
+
+ // Ensure we properly cleaned up.
+ manager.reset();
+ ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
new file mode 100644
index 00000000000..5e3effe3c2a
--- /dev/null
+++ b/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -0,0 +1,465 @@
+// 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 <string>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/shell/shell.h"
+#include "content/test/accessibility_browser_test_utils.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+#if defined(OS_WIN)
+#include <atlbase.h>
+#include <atlcom.h>
+#include "base/win/scoped_com_initializer.h"
+#include "ui/base/win/atl_module.h"
+#endif
+
+// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64)
+#define MAYBE_TableSpan DISABLED_TableSpan
+#else
+#define MAYBE_TableSpan TableSpan
+#endif
+
+namespace content {
+
+class CrossPlatformAccessibilityBrowserTest : public ContentBrowserTest {
+ public:
+ CrossPlatformAccessibilityBrowserTest() {}
+
+ // Tell the renderer to send an accessibility tree, then wait for the
+ // notification that it's been received.
+ const AccessibilityNodeDataTreeNode& GetAccessibilityNodeDataTree(
+ AccessibilityMode accessibility_mode = AccessibilityModeComplete) {
+ AccessibilityNotificationWaiter waiter(
+ shell(), accessibility_mode, AccessibilityNotificationLayoutComplete);
+ waiter.WaitForNotification();
+ return waiter.GetAccessibilityNodeDataTree();
+ }
+
+ // Make sure each node in the tree has an unique id.
+ void RecursiveAssertUniqueIds(
+ const AccessibilityNodeDataTreeNode& node, base::hash_set<int>* ids) {
+ ASSERT_TRUE(ids->find(node.id) == ids->end());
+ ids->insert(node.id);
+ for (size_t i = 0; i < node.children.size(); i++)
+ RecursiveAssertUniqueIds(node.children[i], ids);
+ }
+
+ // ContentBrowserTest
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE;
+ virtual void TearDownInProcessBrowserTestFixture() OVERRIDE;
+
+ protected:
+ std::string GetAttr(const AccessibilityNodeData& node,
+ const AccessibilityNodeData::StringAttribute attr);
+ int GetIntAttr(const AccessibilityNodeData& node,
+ const AccessibilityNodeData::IntAttribute attr);
+ bool GetBoolAttr(const AccessibilityNodeData& node,
+ const AccessibilityNodeData::BoolAttribute attr);
+
+ private:
+#if defined(OS_WIN)
+ scoped_ptr<base::win::ScopedCOMInitializer> com_initializer_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(CrossPlatformAccessibilityBrowserTest);
+};
+
+void CrossPlatformAccessibilityBrowserTest::SetUpInProcessBrowserTestFixture() {
+#if defined(OS_WIN)
+ ui::win::CreateATLModuleIfNeeded();
+ com_initializer_.reset(new base::win::ScopedCOMInitializer());
+#endif
+}
+
+void
+CrossPlatformAccessibilityBrowserTest::TearDownInProcessBrowserTestFixture() {
+#if defined(OS_WIN)
+ com_initializer_.reset();
+#endif
+}
+
+// Convenience method to get the value of a particular AccessibilityNodeData
+// node attribute as a UTF-8 const char*.
+std::string CrossPlatformAccessibilityBrowserTest::GetAttr(
+ const AccessibilityNodeData& node,
+ const AccessibilityNodeData::StringAttribute attr) {
+ std::map<AccessibilityNodeData::StringAttribute, string16>::const_iterator
+ iter = node.string_attributes.find(attr);
+ if (iter != node.string_attributes.end())
+ return UTF16ToUTF8(iter->second);
+ else
+ return std::string();
+}
+
+// Convenience method to get the value of a particular AccessibilityNodeData
+// node integer attribute.
+int CrossPlatformAccessibilityBrowserTest::GetIntAttr(
+ const AccessibilityNodeData& node,
+ const AccessibilityNodeData::IntAttribute attr) {
+ std::map<AccessibilityNodeData::IntAttribute, int32>::const_iterator iter =
+ node.int_attributes.find(attr);
+ if (iter != node.int_attributes.end())
+ return iter->second;
+ else
+ return -1;
+}
+
+// Convenience method to get the value of a particular AccessibilityNodeData
+// node boolean attribute.
+bool CrossPlatformAccessibilityBrowserTest::GetBoolAttr(
+ const AccessibilityNodeData& node,
+ const AccessibilityNodeData::BoolAttribute attr) {
+ std::map<AccessibilityNodeData::BoolAttribute, bool>::const_iterator iter =
+ node.bool_attributes.find(attr);
+ if (iter != node.bool_attributes.end())
+ return iter->second;
+ else
+ return false;
+}
+
+// Marked flaky per http://crbug.com/101984
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ DISABLED_WebpageAccessibility) {
+ // Create a data url and load it.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<html><head><title>Accessibility Test</title></head>"
+ "<body><input type='button' value='push' /><input type='checkbox' />"
+ "</body></html>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+
+ // Check properties of the root element of the tree.
+ EXPECT_STREQ(url_str,
+ GetAttr(tree, AccessibilityNodeData::ATTR_DOC_URL).c_str());
+ EXPECT_STREQ(
+ "Accessibility Test",
+ GetAttr(tree, AccessibilityNodeData::ATTR_DOC_TITLE).c_str());
+ EXPECT_STREQ(
+ "html", GetAttr(tree, AccessibilityNodeData::ATTR_DOC_DOCTYPE).c_str());
+ EXPECT_STREQ(
+ "text/html",
+ GetAttr(tree, AccessibilityNodeData::ATTR_DOC_MIMETYPE).c_str());
+ EXPECT_STREQ("Accessibility Test", UTF16ToUTF8(tree.name).c_str());
+ EXPECT_EQ(AccessibilityNodeData::ROLE_ROOT_WEB_AREA, tree.role);
+
+ // Check properites of the BODY element.
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& body = tree.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_GROUP, body.role);
+ EXPECT_STREQ("body",
+ GetAttr(body, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ EXPECT_STREQ("block",
+ GetAttr(body, AccessibilityNodeData::ATTR_DISPLAY).c_str());
+
+ // Check properties of the two children of the BODY element.
+ ASSERT_EQ(2U, body.children.size());
+
+ const AccessibilityNodeDataTreeNode& button = body.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button.role);
+ EXPECT_STREQ(
+ "input", GetAttr(button, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ EXPECT_STREQ("push", UTF16ToUTF8(button.name).c_str());
+ EXPECT_STREQ(
+ "inline-block",
+ GetAttr(button, AccessibilityNodeData::ATTR_DISPLAY).c_str());
+ ASSERT_EQ(2U, button.html_attributes.size());
+ EXPECT_STREQ("type", UTF16ToUTF8(button.html_attributes[0].first).c_str());
+ EXPECT_STREQ("button", UTF16ToUTF8(button.html_attributes[0].second).c_str());
+ EXPECT_STREQ("value", UTF16ToUTF8(button.html_attributes[1].first).c_str());
+ EXPECT_STREQ("push", UTF16ToUTF8(button.html_attributes[1].second).c_str());
+
+ const AccessibilityNodeDataTreeNode& checkbox = body.children[1];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_CHECKBOX, checkbox.role);
+ EXPECT_STREQ(
+ "input", GetAttr(checkbox, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ EXPECT_STREQ(
+ "inline-block",
+ GetAttr(checkbox, AccessibilityNodeData::ATTR_DISPLAY).c_str());
+ ASSERT_EQ(1U, checkbox.html_attributes.size());
+ EXPECT_STREQ(
+ "type", UTF16ToUTF8(checkbox.html_attributes[0].first).c_str());
+ EXPECT_STREQ(
+ "checkbox", UTF16ToUTF8(checkbox.html_attributes[0].second).c_str());
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ UnselectedEditableTextAccessibility) {
+ // Create a data url and load it.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<body>"
+ "<input value=\"Hello, world.\"/>"
+ "</body></html>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& body = tree.children[0];
+ ASSERT_EQ(1U, body.children.size());
+ const AccessibilityNodeDataTreeNode& text = body.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role);
+ EXPECT_STREQ(
+ "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START));
+ EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END));
+ EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str());
+
+ // TODO(dmazzoni): as soon as more accessibility code is cross-platform,
+ // this code should test that the accessible info is dynamically updated
+ // if the selection or value changes.
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ SelectedEditableTextAccessibility) {
+ // Create a data url and load it.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<body onload=\"document.body.children[0].select();\">"
+ "<input value=\"Hello, world.\"/>"
+ "</body></html>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& body = tree.children[0];
+ ASSERT_EQ(1U, body.children.size());
+ const AccessibilityNodeDataTreeNode& text = body.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role);
+ EXPECT_STREQ(
+ "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START));
+ EXPECT_EQ(13, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END));
+ EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str());
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ MultipleInheritanceAccessibility) {
+ // In a WebKit accessibility render tree for a table, each cell is a
+ // child of both a row and a column, so it appears to use multiple
+ // inheritance. Make sure that the AccessibilityNodeDataObject tree only
+ // keeps one copy of each cell, and uses an indirect child id for the
+ // additional reference to it.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<table border=1><tr><td>1</td><td>2</td></tr></table>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& table = tree.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role);
+ const AccessibilityNodeDataTreeNode& row = table.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, row.role);
+ const AccessibilityNodeDataTreeNode& cell1 = row.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell1.role);
+ const AccessibilityNodeDataTreeNode& cell2 = row.children[1];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell2.role);
+ const AccessibilityNodeDataTreeNode& column1 = table.children[1];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column1.role);
+ EXPECT_EQ(0U, column1.children.size());
+ EXPECT_EQ(1U, column1.indirect_child_ids.size());
+ EXPECT_EQ(cell1.id, column1.indirect_child_ids[0]);
+ const AccessibilityNodeDataTreeNode& column2 = table.children[2];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column2.role);
+ EXPECT_EQ(0U, column2.children.size());
+ EXPECT_EQ(1U, column2.indirect_child_ids.size());
+ EXPECT_EQ(cell2.id, column2.indirect_child_ids[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ MultipleInheritanceAccessibility2) {
+ // Here's another html snippet where WebKit puts the same node as a child
+ // of two different parents. Instead of checking the exact output, just
+ // make sure that no id is reused in the resulting tree.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<script>\n"
+ " document.writeln('<q><section></section></q><q><li>');\n"
+ " setTimeout(function() {\n"
+ " document.close();\n"
+ " }, 1);\n"
+ "</script>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ base::hash_set<int> ids;
+ RecursiveAssertUniqueIds(tree, &ids);
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ IframeAccessibility) {
+ // Create a data url and load it.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html><html><body>"
+ "<button>Button 1</button>"
+ "<iframe src='data:text/html,"
+ "<!doctype html><html><body><button>Button 2</button></body></html>"
+ "'></iframe>"
+ "<button>Button 3</button>"
+ "</body></html>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& body = tree.children[0];
+ ASSERT_EQ(3U, body.children.size());
+
+ const AccessibilityNodeDataTreeNode& button1 = body.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button1.role);
+ EXPECT_STREQ("Button 1", UTF16ToUTF8(button1.name).c_str());
+
+ const AccessibilityNodeDataTreeNode& iframe = body.children[1];
+ EXPECT_STREQ("iframe",
+ GetAttr(iframe, AccessibilityNodeData::ATTR_HTML_TAG).c_str());
+ ASSERT_EQ(1U, iframe.children.size());
+
+ const AccessibilityNodeDataTreeNode& scroll_area = iframe.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_SCROLLAREA, scroll_area.role);
+ ASSERT_EQ(1U, scroll_area.children.size());
+
+ const AccessibilityNodeDataTreeNode& sub_document = scroll_area.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_WEB_AREA, sub_document.role);
+ ASSERT_EQ(1U, sub_document.children.size());
+
+ const AccessibilityNodeDataTreeNode& sub_body = sub_document.children[0];
+ ASSERT_EQ(1U, sub_body.children.size());
+
+ const AccessibilityNodeDataTreeNode& button2 = sub_body.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button2.role);
+ EXPECT_STREQ("Button 2", UTF16ToUTF8(button2.name).c_str());
+
+ const AccessibilityNodeDataTreeNode& button3 = body.children[2];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button3.role);
+ EXPECT_STREQ("Button 3", UTF16ToUTF8(button3.name).c_str());
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ DuplicateChildrenAccessibility) {
+ // Here's another html snippet where WebKit has a parent node containing
+ // two duplicate child nodes. Instead of checking the exact output, just
+ // make sure that no id is reused in the resulting tree.
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<em><code ><h4 ></em>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ base::hash_set<int> ids;
+ RecursiveAssertUniqueIds(tree, &ids);
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ MAYBE_TableSpan) {
+ // +---+---+---+
+ // | 1 | 2 |
+ // +---+---+---+
+ // | 3 | 4 |
+ // +---+---+---+
+
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<table border=1>"
+ " <tr>"
+ " <td colspan=2>1</td><td>2</td>"
+ " </tr>"
+ " <tr>"
+ " <td>3</td><td colspan=2>4</td>"
+ " </tr>"
+ "</table>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+ const AccessibilityNodeDataTreeNode& table = tree.children[0];
+ EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role);
+ ASSERT_GE(table.children.size(), 5U);
+ EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, table.children[0].role);
+ EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, table.children[1].role);
+ EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[2].role);
+ EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[3].role);
+ EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[4].role);
+ EXPECT_EQ(3,
+ GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT));
+ EXPECT_EQ(2, GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_ROW_COUNT));
+
+ const AccessibilityNodeDataTreeNode& cell1 = table.children[0].children[0];
+ const AccessibilityNodeDataTreeNode& cell2 = table.children[0].children[1];
+ const AccessibilityNodeDataTreeNode& cell3 = table.children[1].children[0];
+ const AccessibilityNodeDataTreeNode& cell4 = table.children[1].children[1];
+
+ ASSERT_EQ(6U, table.cell_ids.size());
+ EXPECT_EQ(cell1.id, table.cell_ids[0]);
+ EXPECT_EQ(cell1.id, table.cell_ids[1]);
+ EXPECT_EQ(cell2.id, table.cell_ids[2]);
+ EXPECT_EQ(cell3.id, table.cell_ids[3]);
+ EXPECT_EQ(cell4.id, table.cell_ids[4]);
+ EXPECT_EQ(cell4.id, table.cell_ids[5]);
+
+ EXPECT_EQ(0, GetIntAttr(cell1,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
+ EXPECT_EQ(0, GetIntAttr(cell1,
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX));
+ EXPECT_EQ(2, GetIntAttr(cell1,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
+ EXPECT_EQ(1, GetIntAttr(cell1,
+ AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN));
+ EXPECT_EQ(2, GetIntAttr(cell2,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
+ EXPECT_EQ(1, GetIntAttr(cell2,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
+ EXPECT_EQ(0, GetIntAttr(cell3,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
+ EXPECT_EQ(1, GetIntAttr(cell3,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
+ EXPECT_EQ(1, GetIntAttr(cell4,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX));
+ EXPECT_EQ(2, GetIntAttr(cell4,
+ AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN));
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+ WritableElement) {
+ const char url_str[] =
+ "data:text/html,"
+ "<!doctype html>"
+ "<div role='textbox' tabindex=0>"
+ " Some text"
+ "</div>";
+ GURL url(url_str);
+ NavigateToURL(shell(), url);
+ const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree();
+
+ ASSERT_EQ(1U, tree.children.size());
+ const AccessibilityNodeDataTreeNode& textbox = tree.children[0];
+
+ EXPECT_EQ(
+ true, GetBoolAttr(textbox, AccessibilityNodeData::ATTR_CAN_SET_VALUE));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
new file mode 100644
index 00000000000..d0ff119fd24
--- /dev/null
+++ b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -0,0 +1,456 @@
+// 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 <set>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/accessibility/accessibility_tree_formatter.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/url_constants.h"
+#include "content/shell/shell.h"
+#include "content/test/accessibility_browser_test_utils.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64)
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+namespace content {
+
+namespace {
+
+const char kCommentToken = '#';
+const char kMarkSkipFile[] = "#<skip";
+const char kMarkEndOfFile[] = "<-- End-of-file -->";
+const char kSignalDiff[] = "*";
+
+} // namespace
+
+typedef AccessibilityTreeFormatter::Filter Filter;
+
+// This test takes a snapshot of the platform BrowserAccessibility tree and
+// tests it against an expected baseline.
+//
+// The flow of the test is as outlined below.
+// 1. Load an html file from chrome/test/data/accessibility.
+// 2. Read the expectation.
+// 3. Browse to the page and serialize the platform specific tree into a human
+// readable string.
+// 4. Perform a comparison between actual and expected and fail if they do not
+// exactly match.
+class DumpAccessibilityTreeTest : public ContentBrowserTest {
+ public:
+ // Utility helper that does a comment aware equality check.
+ // Returns array of lines from expected file which are different.
+ std::vector<int> DiffLines(const std::vector<std::string>& expected_lines,
+ const std::vector<std::string>& actual_lines) {
+ int actual_lines_count = actual_lines.size();
+ int expected_lines_count = expected_lines.size();
+ std::vector<int> diff_lines;
+ int i = 0, j = 0;
+ while (i < actual_lines_count && j < expected_lines_count) {
+ if (expected_lines[j].size() == 0 ||
+ expected_lines[j][0] == kCommentToken) {
+ // Skip comment lines and blank lines in expected output.
+ ++j;
+ continue;
+ }
+
+ if (actual_lines[i] != expected_lines[j])
+ diff_lines.push_back(j);
+ ++i;
+ ++j;
+ }
+
+ // Actual file has been fully checked.
+ return diff_lines;
+ }
+
+ void AddDefaultFilters(std::vector<Filter>* filters) {
+ filters->push_back(Filter(ASCIIToUTF16("FOCUSABLE"), Filter::ALLOW));
+ filters->push_back(Filter(ASCIIToUTF16("READONLY"), Filter::ALLOW));
+ filters->push_back(Filter(ASCIIToUTF16("*=''"), Filter::DENY));
+ }
+
+ void ParseFilters(const std::string& test_html,
+ std::vector<Filter>* filters) {
+ std::vector<std::string> lines;
+ base::SplitString(test_html, '\n', &lines);
+ for (std::vector<std::string>::const_iterator iter = lines.begin();
+ iter != lines.end();
+ ++iter) {
+ const std::string& line = *iter;
+ const std::string& allow_empty_str =
+ AccessibilityTreeFormatter::GetAllowEmptyString();
+ const std::string& allow_str =
+ AccessibilityTreeFormatter::GetAllowString();
+ const std::string& deny_str =
+ AccessibilityTreeFormatter::GetDenyString();
+ if (StartsWithASCII(line, allow_empty_str, true)) {
+ filters->push_back(
+ Filter(UTF8ToUTF16(line.substr(allow_empty_str.size())),
+ Filter::ALLOW_EMPTY));
+ } else if (StartsWithASCII(line, allow_str, true)) {
+ filters->push_back(Filter(UTF8ToUTF16(line.substr(allow_str.size())),
+ Filter::ALLOW));
+ } else if (StartsWithASCII(line, deny_str, true)) {
+ filters->push_back(Filter(UTF8ToUTF16(line.substr(deny_str.size())),
+ Filter::DENY));
+ }
+ }
+ }
+
+ void RunTest(const base::FilePath::CharType* file_path);
+};
+
+void DumpAccessibilityTreeTest::RunTest(
+ const base::FilePath::CharType* file_path) {
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+
+ // Setup test paths.
+ base::FilePath dir_test_data;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &dir_test_data));
+ base::FilePath test_path(
+ dir_test_data.Append(FILE_PATH_LITERAL("accessibility")));
+ ASSERT_TRUE(base::PathExists(test_path))
+ << test_path.LossyDisplayName();
+
+ base::FilePath html_file = test_path.Append(base::FilePath(file_path));
+ // Output the test path to help anyone who encounters a failure and needs
+ // to know where to look.
+ printf("Testing: %s\n", html_file.MaybeAsASCII().c_str());
+
+ std::string html_contents;
+ file_util::ReadFileToString(html_file, &html_contents);
+
+ // Read the expected file.
+ std::string expected_contents_raw;
+ base::FilePath expected_file =
+ base::FilePath(html_file.RemoveExtension().value() +
+ AccessibilityTreeFormatter::GetExpectedFileSuffix());
+ file_util::ReadFileToString(expected_file, &expected_contents_raw);
+
+ // Tolerate Windows-style line endings (\r\n) in the expected file:
+ // normalize by deleting all \r from the file (if any) to leave only \n.
+ std::string expected_contents;
+ RemoveChars(expected_contents_raw, "\r", &expected_contents);
+
+ if (!expected_contents.compare(0, strlen(kMarkSkipFile), kMarkSkipFile)) {
+ printf("Skipping this test on this platform.\n");
+ return;
+ }
+
+ // Load the page.
+ string16 html_contents16;
+ html_contents16 = UTF8ToUTF16(html_contents);
+ GURL url = GetTestUrl("accessibility",
+ html_file.BaseName().MaybeAsASCII().c_str());
+ AccessibilityNotificationWaiter waiter(
+ shell(), AccessibilityModeComplete,
+ AccessibilityNotificationLoadComplete);
+ NavigateToURL(shell(), url);
+ waiter.WaitForNotification();
+
+ RenderWidgetHostViewPort* host_view = RenderWidgetHostViewPort::FromRWHV(
+ shell()->web_contents()->GetRenderWidgetHostView());
+ AccessibilityTreeFormatter formatter(
+ host_view->GetBrowserAccessibilityManager()->GetRoot());
+
+ // Parse filters in the test file.
+ std::vector<Filter> filters;
+ AddDefaultFilters(&filters);
+ ParseFilters(html_contents, &filters);
+ formatter.SetFilters(filters);
+
+ // Perform a diff (or write the initial baseline).
+ string16 actual_contents_utf16;
+ formatter.FormatAccessibilityTree(&actual_contents_utf16);
+ std::string actual_contents = UTF16ToUTF8(actual_contents_utf16);
+ std::vector<std::string> actual_lines, expected_lines;
+ Tokenize(actual_contents, "\n", &actual_lines);
+ Tokenize(expected_contents, "\n", &expected_lines);
+ // Marking the end of the file with a line of text ensures that
+ // file length differences are found.
+ expected_lines.push_back(kMarkEndOfFile);
+ actual_lines.push_back(kMarkEndOfFile);
+
+ std::vector<int> diff_lines = DiffLines(expected_lines, actual_lines);
+ bool is_different = diff_lines.size() > 0;
+ EXPECT_FALSE(is_different);
+ if (is_different) {
+ // Mark the expected lines which did not match actual output with a *.
+ printf("* Line Expected\n");
+ printf("- ---- --------\n");
+ for (int line = 0, diff_index = 0;
+ line < static_cast<int>(expected_lines.size());
+ ++line) {
+ bool is_diff = false;
+ if (diff_index < static_cast<int>(diff_lines.size()) &&
+ diff_lines[diff_index] == line) {
+ is_diff = true;
+ ++diff_index;
+ }
+ printf("%1s %4d %s\n", is_diff? kSignalDiff : "", line + 1,
+ expected_lines[line].c_str());
+ }
+ printf("\nActual\n");
+ printf("------\n");
+ printf("%s\n", actual_contents.c_str());
+ }
+
+ if (!base::PathExists(expected_file)) {
+ base::FilePath actual_file =
+ base::FilePath(html_file.RemoveExtension().value() +
+ AccessibilityTreeFormatter::GetActualFileSuffix());
+
+ EXPECT_TRUE(file_util::WriteFile(
+ actual_file, actual_contents.c_str(), actual_contents.size()));
+
+ ADD_FAILURE() << "No expectation found. Create it by doing:\n"
+ << "mv " << actual_file.LossyDisplayName() << " "
+ << expected_file.LossyDisplayName();
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityA) {
+ RunTest(FILE_PATH_LITERAL("a.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAddress) {
+ RunTest(FILE_PATH_LITERAL("address.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAName) {
+ RunTest(FILE_PATH_LITERAL("a-name.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAOnclick) {
+ RunTest(FILE_PATH_LITERAL("a-onclick.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaApplication) {
+ RunTest(FILE_PATH_LITERAL("aria-application.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaAutocomplete) {
+ RunTest(FILE_PATH_LITERAL("aria-autocomplete.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaCombobox) {
+ RunTest(FILE_PATH_LITERAL("aria-combobox.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaInvalid) {
+ RunTest(FILE_PATH_LITERAL("aria-invalid.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaLevel) {
+ RunTest(FILE_PATH_LITERAL("aria-level.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaMenu) {
+ RunTest(FILE_PATH_LITERAL("aria-menu.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaMenuitemradio) {
+ RunTest(FILE_PATH_LITERAL("aria-menuitemradio.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaPressed) {
+ RunTest(FILE_PATH_LITERAL("aria-pressed.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaProgressbar) {
+ RunTest(FILE_PATH_LITERAL("aria-progressbar.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaToolbar) {
+ RunTest(FILE_PATH_LITERAL("toolbar.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaValueMin) {
+ RunTest(FILE_PATH_LITERAL("aria-valuemin.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityAriaValueMax) {
+ RunTest(FILE_PATH_LITERAL("aria-valuemax.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityArticle) {
+ RunTest(FILE_PATH_LITERAL("article.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAWithImg) {
+ RunTest(FILE_PATH_LITERAL("a-with-img.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityBdo) {
+ RunTest(FILE_PATH_LITERAL("bdo.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityBR) {
+ RunTest(FILE_PATH_LITERAL("br.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityButtonNameCalc) {
+ RunTest(FILE_PATH_LITERAL("button-name-calc.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityCanvas) {
+ RunTest(FILE_PATH_LITERAL("canvas.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityCheckboxNameCalc) {
+ RunTest(FILE_PATH_LITERAL("checkbox-name-calc.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDiv) {
+ RunTest(FILE_PATH_LITERAL("div.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDl) {
+ RunTest(FILE_PATH_LITERAL("dl.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityContenteditableDescendants) {
+ RunTest(FILE_PATH_LITERAL("contenteditable-descendants.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityEm) {
+ RunTest(FILE_PATH_LITERAL("em.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityFooter) {
+ RunTest(FILE_PATH_LITERAL("footer.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityForm) {
+ RunTest(FILE_PATH_LITERAL("form.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHeading) {
+ RunTest(FILE_PATH_LITERAL("heading.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHR) {
+ RunTest(FILE_PATH_LITERAL("hr.html"));
+}
+
+// crbug.com/179717 and crbug.com/224659
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ DISABLED_AccessibilityIframeCoordinates) {
+ RunTest(FILE_PATH_LITERAL("iframe-coordinates.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputButton) {
+ RunTest(FILE_PATH_LITERAL("input-button.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityInputButtonInMenu) {
+ RunTest(FILE_PATH_LITERAL("input-button-in-menu.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputColor) {
+ RunTest(FILE_PATH_LITERAL("input-color.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityInputImageButtonInMenu) {
+ RunTest(FILE_PATH_LITERAL("input-image-button-in-menu.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputRange) {
+ RunTest(FILE_PATH_LITERAL("input-range.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityInputTextNameCalc) {
+ RunTest(FILE_PATH_LITERAL("input-text-name-calc.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityLabel) {
+ RunTest(FILE_PATH_LITERAL("label.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityListMarkers) {
+ RunTest(FILE_PATH_LITERAL("list-markers.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityP) {
+ RunTest(FILE_PATH_LITERAL("p.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySelect) {
+ RunTest(FILE_PATH_LITERAL("select.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpan) {
+ RunTest(FILE_PATH_LITERAL("span.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpinButton) {
+ RunTest(FILE_PATH_LITERAL("spinbutton.html"));
+}
+
+// TODO(dmazzoni): Rebaseline this test after Blink rolls past r155083.
+// See http://crbug.com/265619
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, DISABLED_AccessibilitySvg) {
+ RunTest(FILE_PATH_LITERAL("svg.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTab) {
+ RunTest(FILE_PATH_LITERAL("tab.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSimple) {
+ RunTest(FILE_PATH_LITERAL("table-simple.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSpans) {
+ RunTest(FILE_PATH_LITERAL("table-spans.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+ AccessibilityToggleButton) {
+ RunTest(FILE_PATH_LITERAL("togglebutton.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityUl) {
+ RunTest(FILE_PATH_LITERAL("ul.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityWbr) {
+ RunTest(FILE_PATH_LITERAL("wbr.html"));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/DEPS b/chromium/content/browser/android/DEPS
new file mode 100644
index 00000000000..0410b36ac82
--- /dev/null
+++ b/chromium/content/browser/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/WebKit/public/web/WebBindings.h", # For java bridge bindings
+]
diff --git a/chromium/content/browser/android/OWNERS b/chromium/content/browser/android/OWNERS
new file mode 100644
index 00000000000..6084aca7150
--- /dev/null
+++ b/chromium/content/browser/android/OWNERS
@@ -0,0 +1,8 @@
+bulach@chromium.org
+joth@chromium.org
+tedchoc@chromium.org
+yfriedman@chromium.org
+sievers@chromium.org
+
+# per-file rules:
+per-file *media*=qinmin@chromium.org
diff --git a/chromium/content/browser/android/android_browser_process.cc b/chromium/content/browser/android/android_browser_process.cc
new file mode 100644
index 00000000000..edc48e62619
--- /dev/null
+++ b/chromium/content/browser/android/android_browser_process.cc
@@ -0,0 +1,47 @@
+// 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 "content/browser/android/android_browser_process.h"
+
+#include "base/android/jni_string.h"
+#include "base/debug/debugger.h"
+#include "base/logging.h"
+#include "content/browser/android/content_startup_flags.h"
+#include "content/public/common/content_constants.h"
+#include "jni/AndroidBrowserProcess_jni.h"
+
+using base::android::ConvertJavaStringToUTF8;
+
+namespace content {
+
+static void SetCommandLineFlags(JNIEnv*env,
+ jclass clazz,
+ jint max_render_process_count,
+ jstring plugin_descriptor) {
+ std::string plugin_str = (plugin_descriptor == NULL ?
+ std::string() : ConvertJavaStringToUTF8(env, plugin_descriptor));
+ SetContentCommandLineFlags(max_render_process_count, plugin_str);
+}
+
+static jboolean IsOfficialBuild(JNIEnv* env, jclass clazz) {
+#if defined(OFFICIAL_BUILD)
+ return true;
+#else
+ return false;
+#endif
+}
+
+static jboolean IsPluginEnabled(JNIEnv* env, jclass clazz) {
+#if defined(ENABLE_PLUGINS)
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool RegisterAndroidBrowserProcess(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace
diff --git a/chromium/content/browser/android/android_browser_process.h b/chromium/content/browser/android/android_browser_process.h
new file mode 100644
index 00000000000..fbd06e822b7
--- /dev/null
+++ b/chromium/content/browser/android/android_browser_process.h
@@ -0,0 +1,16 @@
+// 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 CONTENT_APP_ANDROID_ANDROID_BROWSER_PROCESS_H_
+#define CONTENT_APP_ANDROID_ANDROID_BROWSER_PROCESS_H_
+
+#include <jni.h>
+
+namespace content {
+
+bool RegisterAndroidBrowserProcess(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_APP_ANDROID_ANDROID_BROWSER_PROCESS_H_
diff --git a/chromium/content/browser/android/browser_jni_registrar.cc b/chromium/content/browser/android/browser_jni_registrar.cc
new file mode 100644
index 00000000000..35da3af3f3f
--- /dev/null
+++ b/chromium/content/browser/android/browser_jni_registrar.cc
@@ -0,0 +1,83 @@
+// 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 "content/browser/android/browser_jni_registrar.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "content/browser/accessibility/browser_accessibility_android.h"
+#include "content/browser/accessibility/browser_accessibility_manager_android.h"
+#include "content/browser/android/android_browser_process.h"
+#include "content/browser/android/browser_startup_config.h"
+#include "content/browser/android/child_process_launcher_android.h"
+#include "content/browser/android/content_settings.h"
+#include "content/browser/android/content_video_view.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/android/content_view_render_view.h"
+#include "content/browser/android/content_view_statics.h"
+#include "content/browser/android/date_time_chooser_android.h"
+#include "content/browser/android/download_controller_android_impl.h"
+#include "content/browser/android/interstitial_page_delegate_android.h"
+#include "content/browser/android/load_url_params.h"
+#include "content/browser/android/media_resource_getter_impl.h"
+#include "content/browser/android/surface_texture_peer_browser_impl.h"
+#include "content/browser/android/touch_point.h"
+#include "content/browser/android/tracing_intent_handler.h"
+#include "content/browser/android/vibration_message_filter.h"
+#include "content/browser/android/web_contents_observer_android.h"
+#include "content/browser/device_orientation/data_fetcher_impl_android.h"
+#include "content/browser/geolocation/location_api_adapter_android.h"
+#include "content/browser/power_save_blocker_android.h"
+#include "content/browser/renderer_host/ime_adapter_android.h"
+#include "content/browser/renderer_host/java/java_bound_object.h"
+#include "content/browser/speech/speech_recognizer_impl_android.h"
+
+using content::SurfaceTexturePeerBrowserImpl;
+
+namespace {
+base::android::RegistrationMethod kContentRegisteredMethods[] = {
+ {"AndroidLocationApiAdapter",
+ content::AndroidLocationApiAdapter::RegisterGeolocationService},
+ {"AndroidBrowserProcess", content::RegisterAndroidBrowserProcess},
+ {"BrowserAccessibilityManager",
+ content::RegisterBrowserAccessibilityManager},
+ {"BrowserStartupConfiguration", content::RegisterBrowserStartupConfig},
+ {"ChildProcessLauncher", content::RegisterChildProcessLauncher},
+ {"ContentSettings", content::ContentSettings::RegisterContentSettings},
+ {"ContentViewRenderView",
+ content::ContentViewRenderView::RegisterContentViewRenderView},
+ {"ContentVideoView", content::ContentVideoView::RegisterContentVideoView},
+ {"ContentViewCore", content::RegisterContentViewCore},
+ {"DataFetcherImplAndroid", content::DataFetcherImplAndroid::Register},
+ {"DateTimePickerAndroid", content::RegisterDateTimeChooserAndroid},
+ {"DownloadControllerAndroidImpl",
+ content::DownloadControllerAndroidImpl::RegisterDownloadController},
+ {"InterstitialPageDelegateAndroid",
+ content::InterstitialPageDelegateAndroid::
+ RegisterInterstitialPageDelegateAndroid},
+ {"MediaResourceGetterImpl",
+ content::MediaResourceGetterImpl::RegisterMediaResourceGetter},
+ {"LoadUrlParams", content::RegisterLoadUrlParams},
+ {"PowerSaveBlock", content::RegisterPowerSaveBlocker},
+ {"RegisterImeAdapter", content::RegisterImeAdapter},
+ {"SpeechRecognizerImplAndroid",
+ content::SpeechRecognizerImplAndroid::RegisterSpeechRecognizer},
+ {"TouchPoint", content::RegisterTouchPoint},
+ {"TracingIntentHandler", content::RegisterTracingIntentHandler},
+ {"VibrationMessageFilter", content::VibrationMessageFilter::Register},
+ {"WebContentsObserverAndroid", content::RegisterWebContentsObserverAndroid},
+ {"WebViewStatics", content::RegisterWebViewStatics}, };
+
+} // namespace
+
+namespace content {
+namespace android {
+
+bool RegisterBrowserJni(JNIEnv* env) {
+ return RegisterNativeMethods(env, kContentRegisteredMethods,
+ arraysize(kContentRegisteredMethods));
+}
+
+} // namespace android
+} // namespace content
diff --git a/chromium/content/browser/android/browser_jni_registrar.h b/chromium/content/browser/android/browser_jni_registrar.h
new file mode 100644
index 00000000000..15f4fb34e10
--- /dev/null
+++ b/chromium/content/browser/android/browser_jni_registrar.h
@@ -0,0 +1,21 @@
+// 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 CONTENT_BROWSER_ANDROID_BROWSER_JNI_REGISTRAR_H_
+#define CONTENT_BROWSER_ANDROID_BROWSER_JNI_REGISTRAR_H_
+
+#include <jni.h>
+
+#include "content/common/content_export.h"
+
+namespace content {
+namespace android {
+
+// Register all JNI bindings necessary for content browser.
+CONTENT_EXPORT bool RegisterBrowserJni(JNIEnv* env);
+
+} // namespace android
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_BROWSER_JNI_REGISTRAR_H_
diff --git a/chromium/content/browser/android/browser_media_player_manager.cc b/chromium/content/browser/android/browser_media_player_manager.cc
new file mode 100644
index 00000000000..dbf0c8f303d
--- /dev/null
+++ b/chromium/content/browser/android/browser_media_player_manager.cc
@@ -0,0 +1,551 @@
+// 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 "content/browser/android/browser_media_player_manager.h"
+
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/android/media_resource_getter_impl.h"
+#include "content/browser/web_contents/web_contents_view_android.h"
+#include "content/common/media/media_player_messages_android.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "media/base/android/media_drm_bridge.h"
+
+using media::MediaDrmBridge;
+using media::MediaPlayerAndroid;
+
+// Threshold on the number of media players per renderer before we start
+// attempting to release inactive media players.
+static const int kMediaPlayerThreshold = 1;
+
+namespace media {
+
+static MediaPlayerManager::FactoryFunction g_factory_function = NULL;
+
+// static
+CONTENT_EXPORT void MediaPlayerManager::RegisterFactoryFunction(
+ FactoryFunction factory_function) {
+ g_factory_function = factory_function;
+}
+
+// static
+media::MediaPlayerManager* MediaPlayerManager::Create(
+ content::RenderViewHost* render_view_host) {
+ if (g_factory_function)
+ return g_factory_function(render_view_host);
+ return new content::BrowserMediaPlayerManager(render_view_host);
+}
+
+} // namespace media
+
+namespace content {
+
+BrowserMediaPlayerManager::BrowserMediaPlayerManager(
+ RenderViewHost* render_view_host)
+ : RenderViewHostObserver(render_view_host),
+ fullscreen_player_id_(-1),
+ web_contents_(WebContents::FromRenderViewHost(render_view_host)) {
+}
+
+BrowserMediaPlayerManager::~BrowserMediaPlayerManager() {}
+
+bool BrowserMediaPlayerManager::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(BrowserMediaPlayerManager, msg)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_EnterFullscreen, OnEnterFullscreen)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_ExitFullscreen, OnExitFullscreen)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_Initialize, OnInitialize)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_Start, OnStart)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_Seek, OnSeek)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_Pause, OnPause)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_SetVolume, OnSetVolume)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_Release, OnReleaseResources)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_DestroyMediaPlayer, OnDestroyPlayer)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_DestroyAllMediaPlayers,
+ DestroyAllMediaPlayers)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_DemuxerReady,
+ OnDemuxerReady)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_ReadFromDemuxerAck,
+ OnReadFromDemuxerAck)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_DurationChanged,
+ OnDurationChanged)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_MediaSeekRequestAck,
+ OnMediaSeekRequestAck)
+ IPC_MESSAGE_HANDLER(MediaKeysHostMsg_InitializeCDM,
+ OnInitializeCDM)
+ IPC_MESSAGE_HANDLER(MediaKeysHostMsg_GenerateKeyRequest,
+ OnGenerateKeyRequest)
+ IPC_MESSAGE_HANDLER(MediaKeysHostMsg_AddKey, OnAddKey)
+ IPC_MESSAGE_HANDLER(MediaKeysHostMsg_CancelKeyRequest,
+ OnCancelKeyRequest)
+#if defined(GOOGLE_TV)
+ IPC_MESSAGE_HANDLER(MediaPlayerHostMsg_NotifyExternalSurface,
+ OnNotifyExternalSurface)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void BrowserMediaPlayerManager::FullscreenPlayerPlay() {
+ MediaPlayerAndroid* player = GetFullscreenPlayer();
+ if (player) {
+ player->Start();
+ Send(new MediaPlayerMsg_DidMediaPlayerPlay(
+ routing_id(), fullscreen_player_id_));
+ }
+}
+
+void BrowserMediaPlayerManager::FullscreenPlayerPause() {
+ MediaPlayerAndroid* player = GetFullscreenPlayer();
+ if (player) {
+ player->Pause();
+ Send(new MediaPlayerMsg_DidMediaPlayerPause(
+ routing_id(), fullscreen_player_id_));
+ }
+}
+
+void BrowserMediaPlayerManager::FullscreenPlayerSeek(int msec) {
+ MediaPlayerAndroid* player = GetFullscreenPlayer();
+ if (player)
+ player->SeekTo(base::TimeDelta::FromMilliseconds(msec));
+}
+
+void BrowserMediaPlayerManager::ExitFullscreen(bool release_media_player) {
+ Send(new MediaPlayerMsg_DidExitFullscreen(
+ routing_id(), fullscreen_player_id_));
+ video_view_.reset();
+ MediaPlayerAndroid* player = GetFullscreenPlayer();
+ fullscreen_player_id_ = -1;
+ if (!player)
+ return;
+ if (release_media_player)
+ player->Release();
+ else
+ player->SetVideoSurface(gfx::ScopedJavaSurface());
+}
+
+void BrowserMediaPlayerManager::OnTimeUpdate(int player_id,
+ base::TimeDelta current_time) {
+ Send(new MediaPlayerMsg_MediaTimeUpdate(
+ routing_id(), player_id, current_time));
+}
+
+void BrowserMediaPlayerManager::SetVideoSurface(
+ gfx::ScopedJavaSurface surface) {
+ MediaPlayerAndroid* player = GetFullscreenPlayer();
+ if (player) {
+ player->SetVideoSurface(surface.Pass());
+ Send(new MediaPlayerMsg_DidEnterFullscreen(
+ routing_id(), player->player_id()));
+ }
+}
+
+void BrowserMediaPlayerManager::OnMediaMetadataChanged(
+ int player_id, base::TimeDelta duration, int width, int height,
+ bool success) {
+ Send(new MediaPlayerMsg_MediaMetadataChanged(
+ routing_id(), player_id, duration, width, height, success));
+ if (fullscreen_player_id_ != -1)
+ video_view_->UpdateMediaMetadata();
+}
+
+void BrowserMediaPlayerManager::OnPlaybackComplete(int player_id) {
+ Send(new MediaPlayerMsg_MediaPlaybackCompleted(routing_id(), player_id));
+ if (fullscreen_player_id_ != -1)
+ video_view_->OnPlaybackComplete();
+}
+
+void BrowserMediaPlayerManager::OnMediaInterrupted(int player_id) {
+ // Tell WebKit that the audio should be paused, then release all resources
+ Send(new MediaPlayerMsg_DidMediaPlayerPause(routing_id(), player_id));
+ OnReleaseResources(player_id);
+}
+
+void BrowserMediaPlayerManager::OnBufferingUpdate(
+ int player_id, int percentage) {
+ Send(new MediaPlayerMsg_MediaBufferingUpdate(
+ routing_id(), player_id, percentage));
+ if (fullscreen_player_id_ != -1)
+ video_view_->OnBufferingUpdate(percentage);
+}
+
+void BrowserMediaPlayerManager::OnSeekComplete(int player_id,
+ base::TimeDelta current_time) {
+ Send(new MediaPlayerMsg_MediaSeekCompleted(
+ routing_id(), player_id, current_time));
+}
+
+void BrowserMediaPlayerManager::OnError(int player_id, int error) {
+ Send(new MediaPlayerMsg_MediaError(routing_id(), player_id, error));
+ if (fullscreen_player_id_ != -1)
+ video_view_->OnMediaPlayerError(error);
+}
+
+void BrowserMediaPlayerManager::OnVideoSizeChanged(
+ int player_id, int width, int height) {
+ Send(new MediaPlayerMsg_MediaVideoSizeChanged(routing_id(), player_id,
+ width, height));
+ if (fullscreen_player_id_ != -1)
+ video_view_->OnVideoSizeChanged(width, height);
+}
+
+void BrowserMediaPlayerManager::OnReadFromDemuxer(
+ int player_id, media::DemuxerStream::Type type) {
+ Send(new MediaPlayerMsg_ReadFromDemuxer(routing_id(), player_id, type));
+}
+
+void BrowserMediaPlayerManager::RequestMediaResources(int player_id) {
+ int num_active_player = 0;
+ ScopedVector<MediaPlayerAndroid>::iterator it;
+ for (it = players_.begin(); it != players_.end(); ++it) {
+ if (!(*it)->IsPlayerReady())
+ continue;
+
+ // The player is already active, ignore it.
+ if ((*it)->player_id() == player_id)
+ return;
+ else
+ num_active_player++;
+ }
+
+ // Number of active players are less than the threshold, do nothing.
+ if (num_active_player < kMediaPlayerThreshold)
+ return;
+
+ for (it = players_.begin(); it != players_.end(); ++it) {
+ if ((*it)->IsPlayerReady() && !(*it)->IsPlaying() &&
+ fullscreen_player_id_ != (*it)->player_id()) {
+ (*it)->Release();
+ Send(new MediaPlayerMsg_MediaPlayerReleased(
+ routing_id(), (*it)->player_id()));
+ }
+ }
+}
+
+void BrowserMediaPlayerManager::ReleaseMediaResources(int player_id) {
+ // Nothing needs to be done.
+}
+
+media::MediaResourceGetter*
+BrowserMediaPlayerManager::GetMediaResourceGetter() {
+ if (!media_resource_getter_.get()) {
+ RenderProcessHost* host = render_view_host()->GetProcess();
+ BrowserContext* context = host->GetBrowserContext();
+ StoragePartition* partition = host->GetStoragePartition();
+ fileapi::FileSystemContext* file_system_context =
+ partition ? partition->GetFileSystemContext() : NULL;
+ media_resource_getter_.reset(new MediaResourceGetterImpl(
+ context, file_system_context, host->GetID(), routing_id()));
+ }
+ return media_resource_getter_.get();
+}
+
+MediaPlayerAndroid* BrowserMediaPlayerManager::GetFullscreenPlayer() {
+ return GetPlayer(fullscreen_player_id_);
+}
+
+MediaPlayerAndroid* BrowserMediaPlayerManager::GetPlayer(int player_id) {
+ for (ScopedVector<MediaPlayerAndroid>::iterator it = players_.begin();
+ it != players_.end(); ++it) {
+ if ((*it)->player_id() == player_id)
+ return *it;
+ }
+ return NULL;
+}
+
+MediaDrmBridge* BrowserMediaPlayerManager::GetDrmBridge(int media_keys_id) {
+ for (ScopedVector<MediaDrmBridge>::iterator it = drm_bridges_.begin();
+ it != drm_bridges_.end(); ++it) {
+ if ((*it)->media_keys_id() == media_keys_id)
+ return *it;
+ }
+ return NULL;
+}
+
+void BrowserMediaPlayerManager::DestroyAllMediaPlayers() {
+ players_.clear();
+ if (fullscreen_player_id_ != -1) {
+ video_view_.reset();
+ fullscreen_player_id_ = -1;
+ }
+}
+
+void BrowserMediaPlayerManager::OnMediaSeekRequest(
+ int player_id, base::TimeDelta time_to_seek, unsigned seek_request_id) {
+ Send(new MediaPlayerMsg_MediaSeekRequest(
+ routing_id(), player_id, time_to_seek, seek_request_id));
+}
+
+void BrowserMediaPlayerManager::OnMediaConfigRequest(int player_id) {
+ Send(new MediaPlayerMsg_MediaConfigRequest(routing_id(), player_id));
+}
+
+void BrowserMediaPlayerManager::OnProtectedSurfaceRequested(int player_id) {
+ if (fullscreen_player_id_ == player_id)
+ return;
+ if (fullscreen_player_id_ != -1) {
+ // TODO(qinmin): Determine the correct error code we should report to WMPA.
+ OnError(player_id, MediaPlayerAndroid::MEDIA_ERROR_DECODE);
+ return;
+ }
+ OnEnterFullscreen(player_id);
+}
+
+void BrowserMediaPlayerManager::OnKeyAdded(int media_keys_id,
+ const std::string& session_id) {
+ Send(new MediaKeysMsg_KeyAdded(routing_id(), media_keys_id, session_id));
+}
+
+void BrowserMediaPlayerManager::OnKeyError(
+ int media_keys_id,
+ const std::string& session_id,
+ media::MediaKeys::KeyError error_code,
+ int system_code) {
+ Send(new MediaKeysMsg_KeyError(routing_id(), media_keys_id,
+ session_id, error_code, system_code));
+}
+
+void BrowserMediaPlayerManager::OnKeyMessage(
+ int media_keys_id,
+ const std::string& session_id,
+ const std::vector<uint8>& message,
+ const std::string& destination_url) {
+ Send(new MediaKeysMsg_KeyMessage(routing_id(), media_keys_id,
+ session_id, message, destination_url));
+}
+
+#if defined(GOOGLE_TV)
+void BrowserMediaPlayerManager::AttachExternalVideoSurface(int player_id,
+ jobject surface) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player) {
+ player->SetVideoSurface(
+ gfx::ScopedJavaSurface::AcquireExternalSurface(surface));
+ }
+}
+
+void BrowserMediaPlayerManager::DetachExternalVideoSurface(int player_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->SetVideoSurface(gfx::ScopedJavaSurface());
+}
+
+void BrowserMediaPlayerManager::OnNotifyExternalSurface(
+ int player_id, bool is_request, const gfx::RectF& rect) {
+ if (!web_contents_)
+ return;
+
+ WebContentsViewAndroid* view =
+ static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
+ if (view)
+ view->NotifyExternalSurface(player_id, is_request, rect);
+}
+#endif
+
+void BrowserMediaPlayerManager::OnEnterFullscreen(int player_id) {
+ DCHECK_EQ(fullscreen_player_id_, -1);
+
+ if (video_view_.get()) {
+ fullscreen_player_id_ = player_id;
+ video_view_->OpenVideo();
+ } else if (!ContentVideoView::HasContentVideoView()) {
+ // In Android WebView, two ContentViewCores could both try to enter
+ // fullscreen video, we just ignore the second one.
+ fullscreen_player_id_ = player_id;
+ WebContents* web_contents =
+ WebContents::FromRenderViewHost(render_view_host());
+ ContentViewCoreImpl* content_view_core_impl =
+ ContentViewCoreImpl::FromWebContents(web_contents);
+ video_view_.reset(new ContentVideoView(content_view_core_impl->GetContext(),
+ content_view_core_impl->GetContentVideoViewClient(), this));
+ }
+}
+
+void BrowserMediaPlayerManager::OnExitFullscreen(int player_id) {
+ if (fullscreen_player_id_ == player_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->SetVideoSurface(gfx::ScopedJavaSurface());
+ video_view_->OnExitFullscreen();
+ }
+}
+
+void BrowserMediaPlayerManager::OnInitialize(
+ int player_id,
+ const GURL& url,
+ media::MediaPlayerAndroid::SourceType source_type,
+ const GURL& first_party_for_cookies) {
+ RemovePlayer(player_id);
+
+ RenderProcessHost* host = render_view_host()->GetProcess();
+ AddPlayer(media::MediaPlayerAndroid::Create(
+ player_id, url, source_type, first_party_for_cookies,
+ host->GetBrowserContext()->IsOffTheRecord(), this));
+}
+
+void BrowserMediaPlayerManager::OnStart(int player_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->Start();
+}
+
+void BrowserMediaPlayerManager::OnSeek(int player_id, base::TimeDelta time) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->SeekTo(time);
+}
+
+void BrowserMediaPlayerManager::OnPause(int player_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->Pause();
+}
+
+void BrowserMediaPlayerManager::OnSetVolume(int player_id, double volume) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->SetVolume(volume);
+}
+
+void BrowserMediaPlayerManager::OnReleaseResources(int player_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ // Don't release the fullscreen player when tab visibility changes,
+ // it will be released when user hit the back/home button or when
+ // OnDestroyPlayer is called.
+ if (player && player_id != fullscreen_player_id_)
+ player->Release();
+
+#if defined(GOOGLE_TV)
+ WebContentsViewAndroid* view =
+ static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
+ if (view)
+ view->NotifyExternalSurface(player_id, false, gfx::RectF());
+#endif
+}
+
+void BrowserMediaPlayerManager::OnDestroyPlayer(int player_id) {
+ RemovePlayer(player_id);
+ if (fullscreen_player_id_ == player_id)
+ fullscreen_player_id_ = -1;
+}
+
+void BrowserMediaPlayerManager::OnDemuxerReady(
+ int player_id,
+ const media::MediaPlayerHostMsg_DemuxerReady_Params& params) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->DemuxerReady(params);
+}
+
+void BrowserMediaPlayerManager::OnReadFromDemuxerAck(
+ int player_id,
+ const media::MediaPlayerHostMsg_ReadFromDemuxerAck_Params& params) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->ReadFromDemuxerAck(params);
+}
+
+void BrowserMediaPlayerManager::OnMediaSeekRequestAck(
+ int player_id, unsigned seek_request_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->OnSeekRequestAck(seek_request_id);
+}
+
+void BrowserMediaPlayerManager::OnInitializeCDM(
+ int media_keys_id,
+ const std::vector<uint8>& uuid) {
+ AddDrmBridge(media_keys_id, uuid);
+ // In EME v0.1b MediaKeys lives in the media element. So the |media_keys_id|
+ // is the same as the |player_id|.
+ OnSetMediaKeys(media_keys_id, media_keys_id);
+}
+
+void BrowserMediaPlayerManager::OnGenerateKeyRequest(
+ int media_keys_id,
+ const std::string& type,
+ const std::vector<uint8>& init_data) {
+ MediaDrmBridge* drm_bridge = GetDrmBridge(media_keys_id);
+ if (drm_bridge)
+ drm_bridge->GenerateKeyRequest(type, &init_data[0], init_data.size());
+}
+
+void BrowserMediaPlayerManager::OnAddKey(int media_keys_id,
+ const std::vector<uint8>& key,
+ const std::vector<uint8>& init_data,
+ const std::string& session_id) {
+ MediaDrmBridge* drm_bridge = GetDrmBridge(media_keys_id);
+ if (drm_bridge) {
+ drm_bridge->AddKey(&key[0], key.size(), &init_data[0], init_data.size(),
+ session_id);
+ }
+}
+
+void BrowserMediaPlayerManager::OnCancelKeyRequest(
+ int media_keys_id,
+ const std::string& session_id) {
+ MediaDrmBridge* drm_bridge = GetDrmBridge(media_keys_id);
+ if (drm_bridge)
+ drm_bridge->CancelKeyRequest(session_id);
+}
+
+void BrowserMediaPlayerManager::OnDurationChanged(
+ int player_id, const base::TimeDelta& duration) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ if (player)
+ player->DurationChanged(duration);
+}
+
+void BrowserMediaPlayerManager::AddPlayer(MediaPlayerAndroid* player) {
+ DCHECK(!GetPlayer(player->player_id()));
+ players_.push_back(player);
+}
+
+void BrowserMediaPlayerManager::RemovePlayer(int player_id) {
+ for (ScopedVector<MediaPlayerAndroid>::iterator it = players_.begin();
+ it != players_.end(); ++it) {
+ if ((*it)->player_id() == player_id) {
+ players_.erase(it);
+ break;
+ }
+ }
+}
+
+void BrowserMediaPlayerManager::AddDrmBridge(int media_keys_id,
+ const std::vector<uint8>& uuid) {
+ DCHECK(!GetDrmBridge(media_keys_id));
+ scoped_ptr<MediaDrmBridge> drm_bridge(
+ MediaDrmBridge::Create(media_keys_id, uuid, this));
+ DCHECK(drm_bridge) << "failed to create drm bridge. ";
+ drm_bridges_.push_back(drm_bridge.release());
+}
+
+void BrowserMediaPlayerManager::RemoveDrmBridge(int media_keys_id) {
+ for (ScopedVector<MediaDrmBridge>::iterator it = drm_bridges_.begin();
+ it != drm_bridges_.end(); ++it) {
+ if ((*it)->media_keys_id() == media_keys_id) {
+ drm_bridges_.erase(it);
+ break;
+ }
+ }
+}
+
+void BrowserMediaPlayerManager::OnSetMediaKeys(int player_id,
+ int media_keys_id) {
+ MediaPlayerAndroid* player = GetPlayer(player_id);
+ MediaDrmBridge* drm_bridge = GetDrmBridge(media_keys_id);
+ if (!player || !drm_bridge) {
+ NOTREACHED() << "OnSetMediaKeys(): Player and MediaKeys must be present.";
+ return;
+ }
+ // TODO(qinmin): add the logic to decide whether we should create the
+ // fullscreen surface for EME lv1.
+ player->SetDrmBridge(drm_bridge);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/browser_media_player_manager.h b/chromium/content/browser/android/browser_media_player_manager.h
new file mode 100644
index 00000000000..7f193e96175
--- /dev/null
+++ b/chromium/content/browser/android/browser_media_player_manager.h
@@ -0,0 +1,182 @@
+// 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 CONTENT_BROWSER_ANDROID_BROWSER_MEDIA_PLAYER_MANAGER_H_
+#define CONTENT_BROWSER_ANDROID_BROWSER_MEDIA_PLAYER_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/time/time.h"
+#include "content/browser/android/content_video_view.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "media/base/android/demuxer_stream_player_params.h"
+#include "media/base/android/media_player_android.h"
+#include "media/base/android/media_player_manager.h"
+#include "ui/gfx/rect_f.h"
+#include "url/gurl.h"
+
+namespace media {
+class MediaDrmBridge;
+}
+
+namespace content {
+
+class WebContents;
+
+// This class manages all the MediaPlayerAndroid objects. It receives
+// control operations from the the render process, and forwards
+// them to corresponding MediaPlayerAndroid object. Callbacks from
+// MediaPlayerAndroid objects are converted to IPCs and then sent to the
+// render process.
+class CONTENT_EXPORT BrowserMediaPlayerManager
+ : public RenderViewHostObserver,
+ public media::MediaPlayerManager {
+ public:
+ virtual ~BrowserMediaPlayerManager();
+
+ // RenderViewHostObserver overrides.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // Fullscreen video playback controls.
+ virtual void FullscreenPlayerPlay();
+ virtual void FullscreenPlayerPause();
+ virtual void FullscreenPlayerSeek(int msec);
+ virtual void ExitFullscreen(bool release_media_player);
+ virtual void SetVideoSurface(gfx::ScopedJavaSurface surface);
+
+ // media::MediaPlayerManager overrides.
+ virtual void OnTimeUpdate(
+ int player_id, base::TimeDelta current_time) OVERRIDE;
+ virtual void OnMediaMetadataChanged(
+ int player_id,
+ base::TimeDelta duration,
+ int width,
+ int height,
+ bool success) OVERRIDE;
+ virtual void OnPlaybackComplete(int player_id) OVERRIDE;
+ virtual void OnMediaInterrupted(int player_id) OVERRIDE;
+ virtual void OnBufferingUpdate(int player_id, int percentage) OVERRIDE;
+ virtual void OnSeekComplete(
+ int player_id, base::TimeDelta current_time) OVERRIDE;
+ virtual void OnError(int player_id, int error) OVERRIDE;
+ virtual void OnVideoSizeChanged(
+ int player_id, int width, int height) OVERRIDE;
+ virtual void OnReadFromDemuxer(int player_id,
+ media::DemuxerStream::Type type) OVERRIDE;
+ virtual void RequestMediaResources(int player_id) OVERRIDE;
+ virtual void ReleaseMediaResources(int player_id) OVERRIDE;
+ virtual media::MediaResourceGetter* GetMediaResourceGetter() OVERRIDE;
+ virtual media::MediaPlayerAndroid* GetFullscreenPlayer() OVERRIDE;
+ virtual media::MediaPlayerAndroid* GetPlayer(int player_id) OVERRIDE;
+ virtual media::MediaDrmBridge* GetDrmBridge(int media_keys_id) OVERRIDE;
+ virtual void DestroyAllMediaPlayers() OVERRIDE;
+ virtual void OnMediaSeekRequest(int player_id, base::TimeDelta time_to_seek,
+ unsigned seek_request_id) OVERRIDE;
+ virtual void OnMediaConfigRequest(int player_id) OVERRIDE;
+ virtual void OnProtectedSurfaceRequested(int player_id) OVERRIDE;
+ virtual void OnKeyAdded(int media_keys_id,
+ const std::string& session_id) OVERRIDE;
+ virtual void OnKeyError(int media_keys_id,
+ const std::string& session_id,
+ media::MediaKeys::KeyError error_code,
+ int system_code) OVERRIDE;
+ virtual void OnKeyMessage(int media_keys_id,
+ const std::string& session_id,
+ const std::vector<uint8>& message,
+ const std::string& destination_url) OVERRIDE;
+
+#if defined(GOOGLE_TV)
+ void AttachExternalVideoSurface(int player_id, jobject surface);
+ void DetachExternalVideoSurface(int player_id);
+#endif
+
+ protected:
+ friend MediaPlayerManager* MediaPlayerManager::Create(
+ content::RenderViewHost*);
+
+ // The instance of this class is supposed to be created by either Create()
+ // method of MediaPlayerManager or the derived classes constructors.
+ explicit BrowserMediaPlayerManager(RenderViewHost* render_view_host);
+
+ // Message handlers.
+ virtual void OnEnterFullscreen(int player_id);
+ virtual void OnExitFullscreen(int player_id);
+ virtual void OnInitialize(
+ int player_id,
+ const GURL& url,
+ media::MediaPlayerAndroid::SourceType source_type,
+ const GURL& first_party_for_cookies);
+ virtual void OnStart(int player_id);
+ virtual void OnSeek(int player_id, base::TimeDelta time);
+ virtual void OnPause(int player_id);
+ virtual void OnSetVolume(int player_id, double volume);
+ virtual void OnReleaseResources(int player_id);
+ virtual void OnDestroyPlayer(int player_id);
+ virtual void OnDemuxerReady(
+ int player_id,
+ const media::MediaPlayerHostMsg_DemuxerReady_Params& params);
+ virtual void OnReadFromDemuxerAck(
+ int player_id,
+ const media::MediaPlayerHostMsg_ReadFromDemuxerAck_Params& params);
+ void OnMediaSeekRequestAck(int player_id, unsigned seek_request_id);
+ void OnInitializeCDM(int media_keys_id, const std::vector<uint8>& uuid);
+ void OnGenerateKeyRequest(int media_keys_id,
+ const std::string& type,
+ const std::vector<uint8>& init_data);
+ void OnAddKey(int media_keys_id,
+ const std::vector<uint8>& key,
+ const std::vector<uint8>& init_data,
+ const std::string& session_id);
+ void OnCancelKeyRequest(int media_keys_id, const std::string& session_id);
+ void OnDurationChanged(int player_id, const base::TimeDelta& duration);
+ void OnSetMediaKeys(int player_id, int media_keys_id);
+
+#if defined(GOOGLE_TV)
+ virtual void OnNotifyExternalSurface(
+ int player_id, bool is_request, const gfx::RectF& rect);
+#endif
+
+ // Adds a given player to the list.
+ void AddPlayer(media::MediaPlayerAndroid* player);
+
+ // Removes the player with the specified id.
+ void RemovePlayer(int player_id);
+
+ // Add a new MediaDrmBridge for the given |uuid| and |media_keys_id|.
+ void AddDrmBridge(int media_keys_id, const std::vector<uint8>& uuid);
+
+ // Removes the DRM bridge with the specified id.
+ void RemoveDrmBridge(int media_keys_id);
+
+ private:
+ // An array of managed players.
+ ScopedVector<media::MediaPlayerAndroid> players_;
+
+ // An array of managed media DRM bridges.
+ ScopedVector<media::MediaDrmBridge> drm_bridges_;
+
+ // The fullscreen video view object or NULL if video is not played in
+ // fullscreen.
+ scoped_ptr<ContentVideoView> video_view_;
+
+ // Player ID of the fullscreen media player.
+ int fullscreen_player_id_;
+
+ WebContents* web_contents_;
+
+ // Object for retrieving resources media players.
+ scoped_ptr<media::MediaResourceGetter> media_resource_getter_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserMediaPlayerManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_BROWSER_MEDIA_PLAYER_MANAGER_H_
diff --git a/chromium/content/browser/android/browser_startup_config.cc b/chromium/content/browser/android/browser_startup_config.cc
new file mode 100644
index 00000000000..3e742080695
--- /dev/null
+++ b/chromium/content/browser/android/browser_startup_config.cc
@@ -0,0 +1,25 @@
+// 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 "content/browser/android/browser_startup_config.h"
+
+#include "base/android/jni_android.h"
+#include "jni/BrowserStartupConfig_jni.h"
+
+namespace content {
+
+bool BrowserMayStartAsynchronously() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_BrowserStartupConfig_browserMayStartAsynchonously(env);
+}
+
+void BrowserStartupComplete(int result) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_BrowserStartupConfig_browserStartupComplete(env, result);
+}
+
+bool RegisterBrowserStartupConfig(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+} // namespace content
diff --git a/chromium/content/browser/android/browser_startup_config.h b/chromium/content/browser/android/browser_startup_config.h
new file mode 100644
index 00000000000..95575f2bc2d
--- /dev/null
+++ b/chromium/content/browser/android/browser_startup_config.h
@@ -0,0 +1,18 @@
+// 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 CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_
+#define CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_
+
+#include <jni.h>
+
+namespace content {
+
+bool BrowserMayStartAsynchronously();
+void BrowserStartupComplete(int result);
+
+bool RegisterBrowserStartupConfig(JNIEnv* env);
+
+} // namespace content
+#endif // CONTENT_BROWSER_BROWSER_STARTUP_CONFIGURATION_H_
diff --git a/chromium/content/browser/android/child_process_launcher_android.cc b/chromium/content/browser/android/child_process_launcher_android.cc
new file mode 100644
index 00000000000..d73e2992112
--- /dev/null
+++ b/chromium/content/browser/android/child_process_launcher_android.cc
@@ -0,0 +1,163 @@
+// 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 "content/browser/android/child_process_launcher_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/compositor_impl_android.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "jni/ChildProcessLauncher_jni.h"
+#include "media/base/android/media_player_android.h"
+#include "media/base/android/media_player_manager.h"
+#include "ui/gl/android/scoped_java_surface.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ToJavaArrayOfStrings;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using content::StartChildProcessCallback;
+
+namespace content {
+
+namespace {
+
+// Pass a java surface object to the MediaPlayerAndroid object
+// identified by render process handle, render view ID and player ID.
+static void SetSurfacePeer(
+ const base::android::JavaRef<jobject>& surface,
+ base::ProcessHandle render_process_handle,
+ int render_view_id,
+ int player_id) {
+ int renderer_id = 0;
+ RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator();
+ while (!it.IsAtEnd()) {
+ if (it.GetCurrentValue()->GetHandle() == render_process_handle) {
+ renderer_id = it.GetCurrentValue()->GetID();
+ break;
+ }
+ it.Advance();
+ }
+
+ if (renderer_id) {
+ RenderViewHostImpl* host = RenderViewHostImpl::FromID(
+ renderer_id, render_view_id);
+ if (host) {
+ media::MediaPlayerAndroid* player =
+ host->media_player_manager()->GetPlayer(player_id);
+ if (player &&
+ player != host->media_player_manager()->GetFullscreenPlayer()) {
+ gfx::ScopedJavaSurface scoped_surface(surface);
+ player->SetVideoSurface(scoped_surface.Pass());
+ }
+ }
+ }
+}
+
+} // anonymous namespace
+
+// Called from ChildProcessLauncher.java when the ChildProcess was
+// started.
+// |client_context| is the pointer to StartChildProcessCallback which was
+// passed in from StartChildProcess.
+// |handle| is the processID of the child process as originated in Java, 0 if
+// the ChildProcess could not be created.
+static void OnChildProcessStarted(JNIEnv*,
+ jclass,
+ jint client_context,
+ jint handle) {
+ StartChildProcessCallback* callback =
+ reinterpret_cast<StartChildProcessCallback*>(client_context);
+ if (handle)
+ callback->Run(static_cast<base::ProcessHandle>(handle));
+ delete callback;
+}
+
+void StartChildProcess(
+ const CommandLine::StringVector& argv,
+ const std::vector<content::FileDescriptorInfo>& files_to_register,
+ const StartChildProcessCallback& callback) {
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+
+ // Create the Command line String[]
+ ScopedJavaLocalRef<jobjectArray> j_argv = ToJavaArrayOfStrings(env, argv);
+
+ size_t file_count = files_to_register.size();
+ DCHECK(file_count > 0);
+
+ ScopedJavaLocalRef<jintArray> j_file_ids(env, env->NewIntArray(file_count));
+ base::android::CheckException(env);
+ jint* file_ids = env->GetIntArrayElements(j_file_ids.obj(), NULL);
+ base::android::CheckException(env);
+ ScopedJavaLocalRef<jintArray> j_file_fds(env, env->NewIntArray(file_count));
+ base::android::CheckException(env);
+ jint* file_fds = env->GetIntArrayElements(j_file_fds.obj(), NULL);
+ base::android::CheckException(env);
+ ScopedJavaLocalRef<jbooleanArray> j_file_auto_close(
+ env, env->NewBooleanArray(file_count));
+ base::android::CheckException(env);
+ jboolean* file_auto_close =
+ env->GetBooleanArrayElements(j_file_auto_close.obj(), NULL);
+ base::android::CheckException(env);
+ for (size_t i = 0; i < file_count; ++i) {
+ const content::FileDescriptorInfo& fd_info = files_to_register[i];
+ file_ids[i] = fd_info.id;
+ file_fds[i] = fd_info.fd.fd;
+ file_auto_close[i] = fd_info.fd.auto_close;
+ }
+ env->ReleaseIntArrayElements(j_file_ids.obj(), file_ids, 0);
+ env->ReleaseIntArrayElements(j_file_fds.obj(), file_fds, 0);
+ env->ReleaseBooleanArrayElements(j_file_auto_close.obj(), file_auto_close, 0);
+
+ Java_ChildProcessLauncher_start(env,
+ base::android::GetApplicationContext(),
+ j_argv.obj(),
+ j_file_ids.obj(),
+ j_file_fds.obj(),
+ j_file_auto_close.obj(),
+ reinterpret_cast<jint>(new StartChildProcessCallback(callback)));
+}
+
+void StopChildProcess(base::ProcessHandle handle) {
+ JNIEnv* env = AttachCurrentThread();
+ DCHECK(env);
+ Java_ChildProcessLauncher_stop(env, static_cast<jint>(handle));
+}
+
+void EstablishSurfacePeer(
+ JNIEnv* env, jclass clazz,
+ jint pid, jobject surface, jint primary_id, jint secondary_id) {
+ ScopedJavaGlobalRef<jobject> jsurface;
+ jsurface.Reset(env, surface);
+ if (jsurface.is_null())
+ return;
+
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &SetSurfacePeer, jsurface, pid, primary_id, secondary_id));
+}
+
+jobject GetViewSurface(JNIEnv* env, jclass clazz, jint surface_id) {
+ // This is a synchronous call from the GPU process and is expected to be
+ // handled on a binder thread. Handling this on the UI thread will lead
+ // to deadlocks.
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return CompositorImpl::GetSurface(surface_id);
+}
+
+jboolean IsSingleProcess(JNIEnv* env, jclass clazz) {
+ return CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
+}
+
+bool RegisterChildProcessLauncher(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/child_process_launcher_android.h b/chromium/content/browser/android/child_process_launcher_android.h
new file mode 100644
index 00000000000..b90ff2338c6
--- /dev/null
+++ b/chromium/content/browser/android/child_process_launcher_android.h
@@ -0,0 +1,36 @@
+// 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 CONTENT_BROWSER_ANDROID_CHILD_PROCESS_LAUNCHER_ANDROID_H_
+#define CONTENT_BROWSER_ANDROID_CHILD_PROCESS_LAUNCHER_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/platform_file.h"
+#include "base/process/process.h"
+#include "content/public/browser/file_descriptor_info.h"
+
+namespace content {
+
+typedef base::Callback<void(base::ProcessHandle)> StartChildProcessCallback;
+// Starts a process as a child process spawned by the Android
+// ActivityManager.
+// The created process handle is returned to the |callback| on success, 0 is
+// retuned if the process could not be created.
+void StartChildProcess(
+ const CommandLine::StringVector& argv,
+ const std::vector<FileDescriptorInfo>& files_to_register,
+ const StartChildProcessCallback& callback);
+
+// Stops a child process based on the handle returned form
+// StartChildProcess.
+void StopChildProcess(base::ProcessHandle handle);
+
+bool RegisterChildProcessLauncher(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CHILD_PROCESS_LAUNCHER_ANDROID_H_
diff --git a/chromium/content/browser/android/content_settings.cc b/chromium/content/browser/android/content_settings.cc
new file mode 100644
index 00000000000..d087d6bd2c2
--- /dev/null
+++ b/chromium/content/browser/android/content_settings.cc
@@ -0,0 +1,58 @@
+// 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 "content/browser/android/content_settings.h"
+
+#include "base/android/jni_android.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/web_contents.h"
+#include "jni/ContentSettings_jni.h"
+#include "webkit/common/webpreferences.h"
+
+namespace content {
+
+ContentSettings::ContentSettings(JNIEnv* env,
+ jobject obj,
+ WebContents* contents)
+ : WebContentsObserver(contents),
+ content_settings_(env, obj) {
+}
+
+ContentSettings::~ContentSettings() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = content_settings_.get(env);
+ if (obj.obj()) {
+ Java_ContentSettings_onNativeContentSettingsDestroyed(env, obj.obj(),
+ reinterpret_cast<jint>(this));
+ }
+}
+
+// static
+bool ContentSettings::RegisterContentSettings(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+bool ContentSettings::GetJavaScriptEnabled(JNIEnv* env, jobject obj) {
+ RenderViewHost* render_view_host = web_contents()->GetRenderViewHost();
+ if (!render_view_host)
+ return false;
+ return render_view_host->GetDelegate()->GetWebkitPrefs().javascript_enabled;
+}
+
+void ContentSettings::WebContentsDestroyed(WebContents* web_contents) {
+ delete this;
+}
+
+static jint Init(JNIEnv* env, jobject obj, jint nativeContentViewCore) {
+ WebContents* web_contents =
+ reinterpret_cast<ContentViewCoreImpl*>(nativeContentViewCore)
+ ->GetWebContents();
+ ContentSettings* content_settings =
+ new ContentSettings(env, obj, web_contents);
+ return reinterpret_cast<jint>(content_settings);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/content_settings.h b/chromium/content/browser/android/content_settings.h
new file mode 100644
index 00000000000..5c0f9a025ba
--- /dev/null
+++ b/chromium/content/browser/android/content_settings.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_ANDROID_CONTENT_SETTINGS_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_SETTINGS_H_
+
+#include <jni.h>
+
+#include "base/android/jni_helper.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+
+class ContentSettings : public WebContentsObserver {
+ public:
+ ContentSettings(JNIEnv* env, jobject obj,
+ WebContents* contents);
+
+ static bool RegisterContentSettings(JNIEnv* env);
+
+ bool GetJavaScriptEnabled(JNIEnv* env, jobject obj);
+
+ private:
+ // Self-deletes when the underlying WebContents is destroyed.
+ virtual ~ContentSettings();
+
+ // WebContentsObserver overrides:
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+
+ // The Java counterpart to this class.
+ JavaObjectWeakGlobalRef content_settings_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_SETTINGS_H_
diff --git a/chromium/content/browser/android/content_startup_flags.cc b/chromium/content/browser/android/content_startup_flags.cc
new file mode 100644
index 00000000000..065b3e70b3f
--- /dev/null
+++ b/chromium/content/browser/android/content_startup_flags.cc
@@ -0,0 +1,88 @@
+// 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 "content/browser/android/content_startup_flags.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "cc/base/switches.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "ui/base/ui_base_switches.h"
+
+namespace content {
+
+void SetContentCommandLineFlags(int max_render_process_count,
+ const std::string& plugin_descriptor) {
+ // May be called multiple times, to cover all possible program entry points.
+ static bool already_initialized = false;
+ if (already_initialized)
+ return;
+ already_initialized = true;
+
+ CommandLine* parsed_command_line = CommandLine::ForCurrentProcess();
+
+ if (parsed_command_line->HasSwitch(switches::kRendererProcessLimit)) {
+ std::string limit = parsed_command_line->GetSwitchValueASCII(
+ switches::kRendererProcessLimit);
+ int value;
+ if (base::StringToInt(limit, &value))
+ max_render_process_count = value;
+ }
+
+ if (max_render_process_count <= 0) {
+ // Need to ensure the command line flag is consistent as a lot of chrome
+ // internal code checks this directly, but it wouldn't normally get set when
+ // we are implementing an embedded WebView.
+ parsed_command_line->AppendSwitch(switches::kSingleProcess);
+ } else {
+ max_render_process_count =
+ std::min(max_render_process_count,
+ static_cast<int>(content::kMaxRendererProcessCount));
+ content::RenderProcessHost::SetMaxRendererProcessCount(
+ max_render_process_count);
+ }
+
+ parsed_command_line->AppendSwitch(switches::kForceCompositingMode);
+ parsed_command_line->AppendSwitch(switches::kAllowWebUICompositing);
+ parsed_command_line->AppendSwitch(switches::kEnableThreadedCompositing);
+ parsed_command_line->AppendSwitch(
+ switches::kEnableCompositingForFixedPosition);
+ parsed_command_line->AppendSwitch(switches::kEnableAcceleratedOverflowScroll);
+ parsed_command_line->AppendSwitch(
+ switches::kEnableAcceleratedScrollableFrames);
+ parsed_command_line->AppendSwitch(
+ switches::kEnableCompositedScrollingForFrames);
+ parsed_command_line->AppendSwitch(switches::kEnableBeginFrameScheduling);
+
+ parsed_command_line->AppendSwitch(switches::kEnableGestureTapHighlight);
+ parsed_command_line->AppendSwitch(switches::kEnablePinch);
+ parsed_command_line->AppendSwitch(switches::kEnableOverscrollNotifications);
+
+ // Run the GPU service as a thread in the browser instead of as a
+ // standalone process.
+ parsed_command_line->AppendSwitch(switches::kInProcessGPU);
+ parsed_command_line->AppendSwitch(switches::kDisableGpuShaderDiskCache);
+
+ // Always use fixed layout and viewport tag.
+ parsed_command_line->AppendSwitch(switches::kEnableFixedLayout);
+ parsed_command_line->AppendSwitch(switches::kEnableViewport);
+
+ // Disable <canvas> path antialiasing.
+ parsed_command_line->AppendSwitch(switches::kDisable2dCanvasAntialiasing);
+
+ // Disable anti-aliasing.
+ parsed_command_line->AppendSwitch(
+ cc::switches::kDisableCompositedAntialiasing);
+
+ if (!plugin_descriptor.empty()) {
+ parsed_command_line->AppendSwitchNative(
+ switches::kRegisterPepperPlugins, plugin_descriptor);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/content_startup_flags.h b/chromium/content/browser/android/content_startup_flags.h
new file mode 100644
index 00000000000..836ea3c1206
--- /dev/null
+++ b/chromium/content/browser/android/content_startup_flags.h
@@ -0,0 +1,20 @@
+// 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 CONTENT_BROWSER_ANDROID_CONTENT_STARTUP_FLAGS_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_STARTUP_FLAGS_H_
+
+#include <string>
+
+namespace content {
+
+// Force-appends flags to the command line turning on Android-specific
+// features owned by Content. This is called as soon as possible during
+// initialization to make sure code sees the new flags.
+void SetContentCommandLineFlags(int max_render_process_count,
+ const std::string& plugin_descriptor);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_STARTUP_FLAGS_H_
diff --git a/chromium/content/browser/android/content_video_view.cc b/chromium/content/browser/android/content_video_view.cc
new file mode 100644
index 00000000000..4a0fdc67a44
--- /dev/null
+++ b/chromium/content/browser/android/content_video_view.cc
@@ -0,0 +1,184 @@
+// 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 "content/browser/android/content_video_view.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "content/browser/android/browser_media_player_manager.h"
+#include "content/common/android/surface_texture_peer.h"
+#include "content/public/common/content_switches.h"
+#include "jni/ContentVideoView_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::CheckException;
+using base::android::ScopedJavaGlobalRef;
+
+namespace content {
+
+namespace {
+// There can only be one content video view at a time, this holds onto that
+// singleton instance.
+ContentVideoView* g_content_video_view = NULL;
+
+} // namespace
+
+static jobject GetSingletonJavaContentVideoView(JNIEnv*env, jclass) {
+ if (g_content_video_view)
+ return g_content_video_view->GetJavaObject(env).Release();
+ else
+ return NULL;
+}
+
+bool ContentVideoView::RegisterContentVideoView(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+bool ContentVideoView::HasContentVideoView() {
+ return g_content_video_view;
+}
+
+ContentVideoView::ContentVideoView(
+ const ScopedJavaLocalRef<jobject>& context,
+ const ScopedJavaLocalRef<jobject>& client,
+ BrowserMediaPlayerManager* manager)
+ : manager_(manager) {
+ DCHECK(!g_content_video_view);
+ JNIEnv *env = AttachCurrentThread();
+ j_content_video_view_ = JavaObjectWeakGlobalRef(env,
+ Java_ContentVideoView_createContentVideoView(env, context.obj(),
+ reinterpret_cast<int>(this), client.obj()).obj());
+ g_content_video_view = this;
+}
+
+ContentVideoView::~ContentVideoView() {
+ DCHECK(g_content_video_view);
+ DestroyContentVideoView(true);
+ g_content_video_view = NULL;
+}
+
+void ContentVideoView::OpenVideo() {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null())
+ Java_ContentVideoView_openVideo(env, content_video_view.obj());
+}
+
+void ContentVideoView::OnMediaPlayerError(int error_type) {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null()) {
+ Java_ContentVideoView_onMediaPlayerError(env, content_video_view.obj(),
+ error_type);
+ }
+}
+
+void ContentVideoView::OnVideoSizeChanged(int width, int height) {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null()) {
+ Java_ContentVideoView_onVideoSizeChanged(env, content_video_view.obj(),
+ width, height);
+ }
+}
+
+void ContentVideoView::OnBufferingUpdate(int percent) {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null()) {
+ Java_ContentVideoView_onBufferingUpdate(env, content_video_view.obj(),
+ percent);
+ }
+}
+
+void ContentVideoView::OnPlaybackComplete() {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null())
+ Java_ContentVideoView_onPlaybackComplete(env, content_video_view.obj());
+}
+
+void ContentVideoView::OnExitFullscreen() {
+ DestroyContentVideoView(false);
+}
+
+void ContentVideoView::UpdateMediaMetadata() {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null())
+ UpdateMediaMetadata(env, content_video_view.obj());
+}
+
+int ContentVideoView::GetVideoWidth(JNIEnv*, jobject obj) const {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ return player ? player->GetVideoWidth() : 0;
+}
+
+int ContentVideoView::GetVideoHeight(JNIEnv*, jobject obj) const {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ return player ? player->GetVideoHeight() : 0;
+}
+
+int ContentVideoView::GetDurationInMilliSeconds(JNIEnv*, jobject obj) const {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ return player ? player->GetDuration().InMilliseconds() : -1;
+}
+
+int ContentVideoView::GetCurrentPosition(JNIEnv*, jobject obj) const {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ return player ? player->GetCurrentTime().InMilliseconds() : 0;
+}
+
+bool ContentVideoView::IsPlaying(JNIEnv*, jobject obj) {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ return player ? player->IsPlaying() : false;
+}
+
+void ContentVideoView::SeekTo(JNIEnv*, jobject obj, jint msec) {
+ manager_->FullscreenPlayerSeek(msec);
+}
+
+void ContentVideoView::Play(JNIEnv*, jobject obj) {
+ manager_->FullscreenPlayerPlay();
+}
+
+void ContentVideoView::Pause(JNIEnv*, jobject obj) {
+ manager_->FullscreenPlayerPause();
+}
+
+void ContentVideoView::ExitFullscreen(
+ JNIEnv*, jobject, jboolean release_media_player) {
+ j_content_video_view_.reset();
+ manager_->ExitFullscreen(release_media_player);
+}
+
+void ContentVideoView::SetSurface(JNIEnv* env, jobject obj,
+ jobject surface) {
+ manager_->SetVideoSurface(
+ gfx::ScopedJavaSurface::AcquireExternalSurface(surface));
+}
+
+void ContentVideoView::UpdateMediaMetadata(JNIEnv* env, jobject obj) {
+ media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
+ if (player && player->IsPlayerReady())
+ Java_ContentVideoView_onUpdateMediaMetadata(
+ env, obj, player->GetVideoWidth(), player->GetVideoHeight(),
+ player->GetDuration().InMilliseconds(), player->CanPause(),
+ player->CanSeekForward(), player->CanSeekBackward());
+}
+
+ScopedJavaLocalRef<jobject> ContentVideoView::GetJavaObject(JNIEnv* env) {
+ return j_content_video_view_.get(env);
+}
+
+void ContentVideoView::DestroyContentVideoView(bool native_view_destroyed) {
+ JNIEnv *env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
+ if (!content_video_view.is_null()) {
+ Java_ContentVideoView_destroyContentVideoView(env,
+ content_video_view.obj(), native_view_destroyed);
+ j_content_video_view_.reset();
+ }
+}
+} // namespace content
diff --git a/chromium/content/browser/android/content_video_view.h b/chromium/content/browser/android/content_video_view.h
new file mode 100644
index 00000000000..2a86830def3
--- /dev/null
+++ b/chromium/content/browser/android/content_video_view.h
@@ -0,0 +1,95 @@
+// 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 CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_
+
+#include <jni.h>
+
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+
+namespace content {
+
+class BrowserMediaPlayerManager;
+
+// Native mirror of ContentVideoView.java. This class is responsible for
+// creating the Java video view and pass all the player status change to
+// it. It accepts media control from Java class, and forwards it to
+// MediaPlayerManagerImpl.
+class ContentVideoView {
+ public:
+ // Construct a ContentVideoView object. The |manager| will handle all the
+ // playback controls from the Java class.
+ ContentVideoView(
+ const base::android::ScopedJavaLocalRef<jobject>& context,
+ const base::android::ScopedJavaLocalRef<jobject>& client,
+ BrowserMediaPlayerManager* manager);
+
+ ~ContentVideoView();
+
+ // To open another video on existing ContentVideoView.
+ void OpenVideo();
+
+ static bool RegisterContentVideoView(JNIEnv* env);
+ static void KeepScreenOn(bool screen_on);
+
+ // Return true if there is existing ContentVideoView object.
+ static bool HasContentVideoView();
+
+ // Getter method called by the Java class to get the media information.
+ int GetVideoWidth(JNIEnv*, jobject obj) const;
+ int GetVideoHeight(JNIEnv*, jobject obj) const;
+ int GetDurationInMilliSeconds(JNIEnv*, jobject obj) const;
+ int GetCurrentPosition(JNIEnv*, jobject obj) const;
+ bool IsPlaying(JNIEnv*, jobject obj);
+ void UpdateMediaMetadata(JNIEnv*, jobject obj);
+
+ // Called when the Java fullscreen view is destroyed. If
+ // |release_media_player| is true, |manager_| needs to release the player
+ // as we are quitting the app.
+ void ExitFullscreen(JNIEnv*, jobject, jboolean release_media_player);
+
+ // Media control method called by the Java class.
+ void SeekTo(JNIEnv*, jobject obj, jint msec);
+ void Play(JNIEnv*, jobject obj);
+ void Pause(JNIEnv*, jobject obj);
+
+ // Called by the Java class to pass the surface object to the player.
+ void SetSurface(JNIEnv*, jobject obj, jobject surface);
+
+ // Method called by |manager_| to inform the Java class about player status
+ // change.
+ void UpdateMediaMetadata();
+ void OnMediaPlayerError(int errorType);
+ void OnVideoSizeChanged(int width, int height);
+ void OnBufferingUpdate(int percent);
+ void OnPlaybackComplete();
+ void OnExitFullscreen();
+
+ // Return the corresponing ContentVideoView Java object if any.
+ base::android::ScopedJavaLocalRef<jobject> GetJavaObject(JNIEnv* env);
+
+ private:
+ // Destroy the |j_content_video_view_|. If |native_view_destroyed| is true,
+ // no further calls to the native object is allowed.
+ void DestroyContentVideoView(bool native_view_destroyed);
+
+ // Object that manages the fullscreen media player. It is responsible for
+ // handling all the playback controls.
+ BrowserMediaPlayerManager* manager_;
+
+ // Weak reference of corresponding Java object.
+ JavaObjectWeakGlobalRef j_content_video_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentVideoView);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_VIDEO_VIEW_H_
diff --git a/chromium/content/browser/android/content_view_core_impl.cc b/chromium/content/browser/android/content_view_core_impl.cc
new file mode 100644
index 00000000000..80fe628dd02
--- /dev/null
+++ b/chromium/content/browser/android/content_view_core_impl.cc
@@ -0,0 +1,1603 @@
+// Copyright 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 "content/browser/android/content_view_core_impl.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/command_line.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "cc/layers/layer.h"
+#include "cc/output/begin_frame_args.h"
+#include "content/browser/android/browser_media_player_manager.h"
+#include "content/browser/android/interstitial_page_delegate_android.h"
+#include "content/browser/android/load_url_params.h"
+#include "content/browser/android/touch_point.h"
+#include "content/browser/renderer_host/compositor_impl_android.h"
+#include "content/browser/renderer_host/input/web_input_event_builders_android.h"
+#include "content/browser/renderer_host/java/java_bound_object.h"
+#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_android.h"
+#include "content/browser/ssl/ssl_host_state.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_view_android.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_accessibility_state.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/menu_item.h"
+#include "content/public/common/page_transition_types.h"
+#include "jni/ContentViewCore_jni.h"
+#include "third_party/WebKit/public/web/WebBindings.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/android/view_android.h"
+#include "ui/android/window_android.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/size_f.h"
+#include "webkit/common/user_agent/user_agent_util.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF16;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF16ToJavaString;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+
+// Describes the type and enabled state of a select popup item.
+// Keep in sync with the value defined in SelectPopupDialog.java
+enum PopupItemType {
+ POPUP_ITEM_TYPE_GROUP = 0,
+ POPUP_ITEM_TYPE_DISABLED,
+ POPUP_ITEM_TYPE_ENABLED
+};
+
+namespace content {
+
+namespace {
+
+const unsigned int kDefaultVSyncIntervalMicros = 16666u;
+// TODO(brianderson): Use adaptive draw-time estimation.
+const float kDefaultBrowserCompositeVSyncFraction = 1.0f / 3;
+
+const void* kContentViewUserDataKey = &kContentViewUserDataKey;
+
+int GetRenderProcessIdFromRenderViewHost(RenderViewHost* host) {
+ DCHECK(host);
+ RenderProcessHost* render_process = host->GetProcess();
+ DCHECK(render_process);
+ if (render_process->HasConnection())
+ return render_process->GetHandle();
+ else
+ return 0;
+}
+
+ScopedJavaLocalRef<jobject> CreateJavaRect(
+ JNIEnv* env,
+ const gfx::Rect& rect) {
+ return ScopedJavaLocalRef<jobject>(
+ Java_ContentViewCore_createRect(env,
+ static_cast<int>(rect.x()),
+ static_cast<int>(rect.y()),
+ static_cast<int>(rect.right()),
+ static_cast<int>(rect.bottom())));
+};
+} // namespace
+
+// Enables a callback when the underlying WebContents is destroyed, to enable
+// nulling the back-pointer.
+class ContentViewCoreImpl::ContentViewUserData
+ : public base::SupportsUserData::Data {
+ public:
+ explicit ContentViewUserData(ContentViewCoreImpl* content_view_core)
+ : content_view_core_(content_view_core) {
+ }
+
+ virtual ~ContentViewUserData() {
+ // TODO(joth): When chrome has finished removing the TabContents class (see
+ // crbug.com/107201) consider inverting relationship, so ContentViewCore
+ // would own WebContents. That effectively implies making the WebContents
+ // destructor private on Android.
+ delete content_view_core_;
+ }
+
+ ContentViewCoreImpl* get() const { return content_view_core_; }
+
+ private:
+ // Not using scoped_ptr as ContentViewCoreImpl destructor is private.
+ ContentViewCoreImpl* content_view_core_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ContentViewUserData);
+};
+
+// static
+ContentViewCoreImpl* ContentViewCoreImpl::FromWebContents(
+ content::WebContents* web_contents) {
+ ContentViewCoreImpl::ContentViewUserData* data =
+ reinterpret_cast<ContentViewCoreImpl::ContentViewUserData*>(
+ web_contents->GetUserData(kContentViewUserDataKey));
+ return data ? data->get() : NULL;
+}
+
+// static
+ContentViewCore* ContentViewCore::FromWebContents(
+ content::WebContents* web_contents) {
+ return ContentViewCoreImpl::FromWebContents(web_contents);
+}
+
+// static
+ContentViewCore* ContentViewCore::GetNativeContentViewCore(JNIEnv* env,
+ jobject obj) {
+ return reinterpret_cast<ContentViewCore*>(
+ Java_ContentViewCore_getNativeContentViewCore(env, obj));
+}
+
+ContentViewCoreImpl::ContentViewCoreImpl(JNIEnv* env, jobject obj,
+ bool hardware_accelerated,
+ WebContents* web_contents,
+ ui::ViewAndroid* view_android,
+ ui::WindowAndroid* window_android)
+ : java_ref_(env, obj),
+ web_contents_(static_cast<WebContentsImpl*>(web_contents)),
+ root_layer_(cc::Layer::Create()),
+ tab_crashed_(false),
+ vsync_interval_(base::TimeDelta::FromMicroseconds(
+ kDefaultVSyncIntervalMicros)),
+ expected_browser_composite_time_(base::TimeDelta::FromMicroseconds(
+ kDefaultVSyncIntervalMicros * kDefaultBrowserCompositeVSyncFraction)),
+ view_android_(view_android),
+ window_android_(window_android) {
+ CHECK(web_contents) <<
+ "A ContentViewCoreImpl should be created with a valid WebContents.";
+
+ // When a tab is restored (from a saved state), it does not have a renderer
+ // process. We treat it like the tab is crashed. If the content is loaded
+ // when the tab is shown, tab_crashed_ will be reset.
+ UpdateTabCrashedFlag();
+
+ // TODO(leandrogracia): make use of the hardware_accelerated argument.
+
+ const gfx::Display& display =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+ dpi_scale_ = display.device_scale_factor();
+
+ // Currently, the only use case we have for overriding a user agent involves
+ // spoofing a desktop Linux user agent for "Request desktop site".
+ // Automatically set it for all WebContents so that it is available when a
+ // NavigationEntry requires the user agent to be overridden.
+ const char kLinuxInfoStr[] = "X11; Linux x86_64";
+ std::string product = content::GetContentClient()->GetProduct();
+ std::string spoofed_ua =
+ webkit_glue::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
+ web_contents->SetUserAgentOverride(spoofed_ua);
+
+ InitWebContents();
+}
+
+ContentViewCoreImpl::~ContentViewCoreImpl() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ java_ref_.reset();
+ if (!j_obj.is_null()) {
+ Java_ContentViewCore_onNativeContentViewCoreDestroyed(
+ env, j_obj.obj(), reinterpret_cast<jint>(this));
+ }
+ // Make sure nobody calls back into this object while we are tearing things
+ // down.
+ notification_registrar_.RemoveAll();
+}
+
+void ContentViewCoreImpl::OnJavaContentViewCoreDestroyed(JNIEnv* env,
+ jobject obj) {
+ DCHECK(env->IsSameObject(java_ref_.get(env).obj(), obj));
+ java_ref_.reset();
+}
+
+void ContentViewCoreImpl::InitWebContents() {
+ DCHECK(web_contents_);
+ notification_registrar_.Add(
+ this, NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&web_contents_->GetController()));
+ notification_registrar_.Add(
+ this, NOTIFICATION_RENDERER_PROCESS_CREATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ notification_registrar_.Add(
+ this, NOTIFICATION_WEB_CONTENTS_CONNECTED,
+ Source<WebContents>(web_contents_));
+ notification_registrar_.Add(
+ this, NOTIFICATION_WEB_CONTENTS_SWAPPED,
+ Source<WebContents>(web_contents_));
+
+ static_cast<WebContentsViewAndroid*>(web_contents_->GetView())->
+ SetContentViewCore(this);
+ DCHECK(!web_contents_->GetUserData(kContentViewUserDataKey));
+ web_contents_->SetUserData(kContentViewUserDataKey,
+ new ContentViewUserData(this));
+}
+
+void ContentViewCoreImpl::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_RENDER_VIEW_HOST_CHANGED: {
+ std::pair<RenderViewHost*, RenderViewHost*>* switched_details =
+ Details<std::pair<RenderViewHost*, RenderViewHost*> >(details).ptr();
+ int old_pid = 0;
+ if (switched_details->first) {
+ old_pid = GetRenderProcessIdFromRenderViewHost(
+ switched_details->first);
+ }
+ int new_pid = GetRenderProcessIdFromRenderViewHost(
+ web_contents_->GetRenderViewHost());
+ if (new_pid != old_pid) {
+ // Notify the Java side of the change of the current renderer process.
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (!obj.is_null()) {
+ Java_ContentViewCore_onRenderProcessSwap(
+ env, obj.obj(), old_pid, new_pid);
+ }
+ }
+ break;
+ }
+ case NOTIFICATION_RENDERER_PROCESS_CREATED: {
+ // Notify the Java side of the current renderer process.
+ RenderProcessHost* source_process_host =
+ Source<RenderProcessHost>(source).ptr();
+ RenderProcessHost* current_process_host =
+ web_contents_->GetRenderViewHost()->GetProcess();
+
+ if (source_process_host == current_process_host) {
+ int pid = GetRenderProcessIdFromRenderViewHost(
+ web_contents_->GetRenderViewHost());
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (!obj.is_null()) {
+ Java_ContentViewCore_onRenderProcessSwap(env, obj.obj(), 0, pid);
+ }
+ }
+ break;
+ }
+ case NOTIFICATION_WEB_CONTENTS_CONNECTED: {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (!obj.is_null()) {
+ Java_ContentViewCore_onWebContentsConnected(env, obj.obj());
+ }
+ break;
+ }
+ case NOTIFICATION_WEB_CONTENTS_SWAPPED: {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (!obj.is_null()) {
+ Java_ContentViewCore_onWebContentsSwapped(env, obj.obj());
+ }
+ }
+ }
+}
+
+RenderWidgetHostViewAndroid*
+ ContentViewCoreImpl::GetRenderWidgetHostViewAndroid() {
+ RenderWidgetHostView* rwhv = NULL;
+ if (web_contents_) {
+ rwhv = web_contents_->GetRenderWidgetHostView();
+ if (web_contents_->ShowingInterstitialPage()) {
+ rwhv = static_cast<InterstitialPageImpl*>(
+ web_contents_->GetInterstitialPage())->
+ GetRenderViewHost()->GetView();
+ }
+ }
+ return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
+}
+
+ScopedJavaLocalRef<jobject> ContentViewCoreImpl::GetJavaObject() {
+ JNIEnv* env = AttachCurrentThread();
+ return java_ref_.get(env);
+}
+
+jint ContentViewCoreImpl::GetBackgroundColor(JNIEnv* env, jobject obj) {
+ RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
+ if (!rwhva)
+ return SK_ColorWHITE;
+ return rwhva->GetCachedBackgroundColor();
+}
+
+void ContentViewCoreImpl::OnHide(JNIEnv* env, jobject obj) {
+ Hide();
+}
+
+void ContentViewCoreImpl::OnShow(JNIEnv* env, jobject obj) {
+ Show();
+}
+
+void ContentViewCoreImpl::Show() {
+ GetWebContents()->WasShown();
+ // Displaying WebContents may trigger a lazy reload, spawning a new renderer
+ // for the tab.
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::Hide() {
+ GetWebContents()->WasHidden();
+}
+
+void ContentViewCoreImpl::OnTabCrashed() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_resetVSyncNotification(env, obj.obj());
+
+ // If |tab_crashed_| is already true, just return. e.g. if two tabs share the
+ // render process, this will be called for each tab when the render process
+ // crashed. If user reload one tab, a new render process is created. It can be
+ // shared by the other tab. But if user closes the tab before reload the other
+ // tab, the new render process will be shut down. This will trigger the other
+ // tab's OnTabCrashed() called again as two tabs share the same
+ // BrowserRenderProcessHost.
+ if (tab_crashed_)
+ return;
+ tab_crashed_ = true;
+ Java_ContentViewCore_onTabCrash(env, obj.obj());
+}
+
+// All positions and sizes are in CSS pixels.
+// Note that viewport_width/height is a best effort based.
+// ContentViewCore has the actual information about the physical viewport size.
+void ContentViewCoreImpl::UpdateFrameInfo(
+ const gfx::Vector2dF& scroll_offset,
+ float page_scale_factor,
+ const gfx::Vector2dF& page_scale_factor_limits,
+ const gfx::SizeF& content_size,
+ const gfx::SizeF& viewport_size,
+ const gfx::Vector2dF& controls_offset,
+ const gfx::Vector2dF& content_offset,
+ float overdraw_bottom_height) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ Java_ContentViewCore_updateFrameInfo(
+ env, obj.obj(),
+ scroll_offset.x(),
+ scroll_offset.y(),
+ page_scale_factor,
+ page_scale_factor_limits.x(),
+ page_scale_factor_limits.y(),
+ content_size.width(),
+ content_size.height(),
+ viewport_size.width(),
+ viewport_size.height(),
+ controls_offset.y(),
+ content_offset.y(),
+ overdraw_bottom_height);
+}
+
+void ContentViewCoreImpl::SetTitle(const string16& title) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jtitle =
+ ConvertUTF8ToJavaString(env, UTF16ToUTF8(title));
+ Java_ContentViewCore_setTitle(env, obj.obj(), jtitle.obj());
+}
+
+void ContentViewCoreImpl::OnBackgroundColorChanged(SkColor color) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_onBackgroundColorChanged(env, obj.obj(), color);
+}
+
+void ContentViewCoreImpl::ShowSelectPopupMenu(
+ const std::vector<MenuItem>& items, int selected_item, bool multiple) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+
+ // For multi-select list popups we find the list of previous selections by
+ // iterating through the items. But for single selection popups we take the
+ // given |selected_item| as is.
+ ScopedJavaLocalRef<jintArray> selected_array;
+ if (multiple) {
+ scoped_ptr<jint[]> native_selected_array(new jint[items.size()]);
+ size_t selected_count = 0;
+ for (size_t i = 0; i < items.size(); ++i) {
+ if (items[i].checked)
+ native_selected_array[selected_count++] = i;
+ }
+
+ selected_array = ScopedJavaLocalRef<jintArray>(
+ env, env->NewIntArray(selected_count));
+ env->SetIntArrayRegion(selected_array.obj(), 0, selected_count,
+ native_selected_array.get());
+ } else {
+ selected_array = ScopedJavaLocalRef<jintArray>(env, env->NewIntArray(1));
+ jint value = selected_item;
+ env->SetIntArrayRegion(selected_array.obj(), 0, 1, &value);
+ }
+
+ ScopedJavaLocalRef<jintArray> enabled_array(env,
+ env->NewIntArray(items.size()));
+ std::vector<string16> labels;
+ labels.reserve(items.size());
+ for (size_t i = 0; i < items.size(); ++i) {
+ labels.push_back(items[i].label);
+ jint enabled =
+ (items[i].type == MenuItem::GROUP ? POPUP_ITEM_TYPE_GROUP :
+ (items[i].enabled ? POPUP_ITEM_TYPE_ENABLED :
+ POPUP_ITEM_TYPE_DISABLED));
+ env->SetIntArrayRegion(enabled_array.obj(), i, 1, &enabled);
+ }
+ ScopedJavaLocalRef<jobjectArray> items_array(
+ base::android::ToJavaArrayOfStrings(env, labels));
+ Java_ContentViewCore_showSelectPopup(env, j_obj.obj(),
+ items_array.obj(), enabled_array.obj(),
+ multiple, selected_array.obj());
+}
+
+void ContentViewCoreImpl::ConfirmTouchEvent(InputEventAckState ack_result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+ Java_ContentViewCore_confirmTouchEvent(env, j_obj.obj(),
+ static_cast<jint>(ack_result));
+}
+
+void ContentViewCoreImpl::UnhandledFlingStartEvent() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+ Java_ContentViewCore_unhandledFlingStartEvent(env, j_obj.obj());
+}
+
+void ContentViewCoreImpl::HasTouchEventHandlers(bool need_touch_events) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+ Java_ContentViewCore_hasTouchEventHandlers(env,
+ j_obj.obj(),
+ need_touch_events);
+}
+
+bool ContentViewCoreImpl::HasFocus() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return false;
+ return Java_ContentViewCore_hasFocus(env, obj.obj());
+}
+
+void ContentViewCoreImpl::OnSelectionChanged(const std::string& text) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jtext = ConvertUTF8ToJavaString(env, text);
+ Java_ContentViewCore_onSelectionChanged(env, obj.obj(), jtext.obj());
+}
+
+void ContentViewCoreImpl::OnSelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jobject> anchor_rect_dip(
+ CreateJavaRect(env, params.anchor_rect));
+ ScopedJavaLocalRef<jobject> focus_rect_dip(
+ CreateJavaRect(env, params.focus_rect));
+ Java_ContentViewCore_onSelectionBoundsChanged(env, obj.obj(),
+ anchor_rect_dip.obj(),
+ params.anchor_dir,
+ focus_rect_dip.obj(),
+ params.focus_dir,
+ params.is_anchor_first);
+}
+
+void ContentViewCoreImpl::ShowPastePopup(int x_dip, int y_dip) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_showPastePopup(env, obj.obj(),
+ static_cast<jint>(x_dip),
+ static_cast<jint>(y_dip));
+}
+
+unsigned int ContentViewCoreImpl::GetScaledContentTexture(
+ float scale,
+ gfx::Size* out_size) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (!view)
+ return 0;
+
+ return view->GetScaledContentTexture(scale, out_size);
+}
+
+void ContentViewCoreImpl::StartContentIntent(const GURL& content_url) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jcontent_url =
+ ConvertUTF8ToJavaString(env, content_url.spec());
+ Java_ContentViewCore_startContentIntent(env,
+ j_obj.obj(),
+ jcontent_url.obj());
+}
+
+void ContentViewCoreImpl::ShowDisambiguationPopup(
+ const gfx::Rect& target_rect,
+ const SkBitmap& zoomed_bitmap) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ ScopedJavaLocalRef<jobject> rect_object(CreateJavaRect(env, target_rect));
+
+ ScopedJavaLocalRef<jobject> java_bitmap =
+ gfx::ConvertToJavaBitmap(&zoomed_bitmap);
+ DCHECK(!java_bitmap.is_null());
+
+ Java_ContentViewCore_showDisambiguationPopup(env,
+ obj.obj(),
+ rect_object.obj(),
+ java_bitmap.obj());
+}
+
+ScopedJavaLocalRef<jobject> ContentViewCoreImpl::CreateSmoothScroller(
+ bool scroll_down, int mouse_event_x, int mouse_event_y) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return ScopedJavaLocalRef<jobject>();
+ return Java_ContentViewCore_createSmoothScroller(
+ env, obj.obj(), scroll_down, mouse_event_x, mouse_event_y);
+}
+
+void ContentViewCoreImpl::NotifyExternalSurface(
+ int player_id, bool is_request, const gfx::RectF& rect) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ Java_ContentViewCore_notifyExternalSurface(
+ env,
+ obj.obj(),
+ static_cast<jint>(player_id),
+ static_cast<jboolean>(is_request),
+ static_cast<jfloat>(rect.x()),
+ static_cast<jfloat>(rect.y()),
+ static_cast<jfloat>(rect.width()),
+ static_cast<jfloat>(rect.height()));
+}
+
+ScopedJavaLocalRef<jobject> ContentViewCoreImpl::GetContentVideoViewClient() {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return ScopedJavaLocalRef<jobject>();
+
+ return Java_ContentViewCore_getContentVideoViewClient(env, obj.obj());
+}
+
+ScopedJavaLocalRef<jobject> ContentViewCoreImpl::GetContext() {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return ScopedJavaLocalRef<jobject>();
+
+ return Java_ContentViewCore_getContext(env, obj.obj());
+}
+
+gfx::Size ContentViewCoreImpl::GetPhysicalBackingSize() const {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return gfx::Size();
+ return gfx::Size(
+ Java_ContentViewCore_getPhysicalBackingWidthPix(env, j_obj.obj()),
+ Java_ContentViewCore_getPhysicalBackingHeightPix(env, j_obj.obj()));
+}
+
+gfx::Size ContentViewCoreImpl::GetViewportSizePix() const {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return gfx::Size();
+ return gfx::Size(
+ Java_ContentViewCore_getViewportWidthPix(env, j_obj.obj()),
+ Java_ContentViewCore_getViewportHeightPix(env, j_obj.obj()));
+}
+
+gfx::Size ContentViewCoreImpl::GetViewportSizeOffsetPix() const {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return gfx::Size();
+ return gfx::Size(
+ Java_ContentViewCore_getViewportSizeOffsetWidthPix(env, j_obj.obj()),
+ Java_ContentViewCore_getViewportSizeOffsetHeightPix(env, j_obj.obj()));
+}
+
+gfx::Size ContentViewCoreImpl::GetViewportSizeDip() const {
+ return gfx::ToCeiledSize(
+ gfx::ScaleSize(GetViewportSizePix(), 1.0f / GetDpiScale()));
+}
+
+gfx::Size ContentViewCoreImpl::GetViewportSizeOffsetDip() const {
+ return gfx::ToCeiledSize(
+ gfx::ScaleSize(GetViewportSizeOffsetPix(), 1.0f / GetDpiScale()));
+}
+
+float ContentViewCoreImpl::GetOverdrawBottomHeightDip() const {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return 0.f;
+ return Java_ContentViewCore_getOverdrawBottomHeightPix(env, j_obj.obj())
+ / GetDpiScale();
+}
+
+void ContentViewCoreImpl::AttachLayer(scoped_refptr<cc::Layer> layer) {
+ root_layer_->AddChild(layer);
+}
+
+void ContentViewCoreImpl::RemoveLayer(scoped_refptr<cc::Layer> layer) {
+ layer->RemoveFromParent();
+}
+
+void ContentViewCoreImpl::LoadUrl(
+ NavigationController::LoadURLParams& params) {
+ GetWebContents()->GetController().LoadURLWithParams(params);
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::SetNeedsBeginFrame(bool enabled) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_setVSyncNotificationEnabled(
+ env, obj.obj(), static_cast<jboolean>(enabled));
+}
+
+void ContentViewCoreImpl::SetNeedsAnimate() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_setNeedsAnimate(env, obj.obj());
+}
+
+ui::ViewAndroid* ContentViewCoreImpl::GetViewAndroid() const {
+ // view_android_ should never be null for Chrome.
+ DCHECK(view_android_);
+ return view_android_;
+}
+
+ui::WindowAndroid* ContentViewCoreImpl::GetWindowAndroid() const {
+ // This should never be NULL for Chrome, but will be NULL for WebView.
+ DCHECK(window_android_);
+ return window_android_;
+}
+
+scoped_refptr<cc::Layer> ContentViewCoreImpl::GetLayer() const {
+ return root_layer_.get();
+}
+
+// ----------------------------------------------------------------------------
+// Methods called from Java via JNI
+// ----------------------------------------------------------------------------
+
+void ContentViewCoreImpl::SelectPopupMenuItems(JNIEnv* env, jobject obj,
+ jintArray indices) {
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ DCHECK(rvhi);
+ if (indices == NULL) {
+ rvhi->DidCancelPopupMenu();
+ return;
+ }
+
+ int selected_count = env->GetArrayLength(indices);
+ std::vector<int> selected_indices;
+ jint* indices_ptr = env->GetIntArrayElements(indices, NULL);
+ for (int i = 0; i < selected_count; ++i)
+ selected_indices.push_back(indices_ptr[i]);
+ env->ReleaseIntArrayElements(indices, indices_ptr, JNI_ABORT);
+ rvhi->DidSelectPopupMenuItems(selected_indices);
+}
+
+void ContentViewCoreImpl::LoadUrl(
+ JNIEnv* env, jobject obj,
+ jstring url,
+ jint load_url_type,
+ jint transition_type,
+ jint ua_override_option,
+ jstring extra_headers,
+ jbyteArray post_data,
+ jstring base_url_for_data_url,
+ jstring virtual_url_for_data_url,
+ jboolean can_load_local_resources) {
+ DCHECK(url);
+ NavigationController::LoadURLParams params(
+ GURL(ConvertJavaStringToUTF8(env, url)));
+
+ params.load_type = static_cast<NavigationController::LoadURLType>(
+ load_url_type);
+ params.transition_type = PageTransitionFromInt(transition_type);
+ params.override_user_agent =
+ static_cast<NavigationController::UserAgentOverrideOption>(
+ ua_override_option);
+
+ if (extra_headers)
+ params.extra_headers = ConvertJavaStringToUTF8(env, extra_headers);
+
+ if (post_data) {
+ std::vector<uint8> http_body_vector;
+ base::android::JavaByteArrayToByteVector(env, post_data, &http_body_vector);
+ params.browser_initiated_post_data =
+ base::RefCountedBytes::TakeVector(&http_body_vector);
+ }
+
+ if (base_url_for_data_url) {
+ params.base_url_for_data_url =
+ GURL(ConvertJavaStringToUTF8(env, base_url_for_data_url));
+ }
+
+ if (virtual_url_for_data_url) {
+ params.virtual_url_for_data_url =
+ GURL(ConvertJavaStringToUTF8(env, virtual_url_for_data_url));
+ }
+
+ params.can_load_local_resources = can_load_local_resources;
+
+ LoadUrl(params);
+}
+
+jint ContentViewCoreImpl::GetCurrentRenderProcessId(JNIEnv* env, jobject obj) {
+ return GetRenderProcessIdFromRenderViewHost(
+ web_contents_->GetRenderViewHost());
+}
+
+ScopedJavaLocalRef<jstring> ContentViewCoreImpl::GetURL(
+ JNIEnv* env, jobject) const {
+ // The current users of the Java API expect to use the active entry
+ // rather than the visible entry, which is exposed by WebContents::GetURL.
+ content::NavigationEntry* entry =
+ web_contents_->GetController().GetActiveEntry();
+ GURL url = entry ? entry->GetVirtualURL() : GURL::EmptyGURL();
+ return ConvertUTF8ToJavaString(env, url.spec());
+}
+
+ScopedJavaLocalRef<jstring> ContentViewCoreImpl::GetTitle(
+ JNIEnv* env, jobject obj) const {
+ return ConvertUTF16ToJavaString(env, GetWebContents()->GetTitle());
+}
+
+jboolean ContentViewCoreImpl::IsIncognito(JNIEnv* env, jobject obj) {
+ return GetWebContents()->GetBrowserContext()->IsOffTheRecord();
+}
+
+WebContents* ContentViewCoreImpl::GetWebContents() const {
+ return web_contents_;
+}
+
+void ContentViewCoreImpl::SetFocus(JNIEnv* env, jobject obj, jboolean focused) {
+ if (!GetRenderWidgetHostViewAndroid())
+ return;
+
+ if (focused)
+ GetRenderWidgetHostViewAndroid()->Focus();
+ else
+ GetRenderWidgetHostViewAndroid()->Blur();
+}
+
+void ContentViewCoreImpl::SendOrientationChangeEvent(JNIEnv* env,
+ jobject obj,
+ jint orientation) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (rwhv)
+ rwhv->UpdateScreenInfo(rwhv->GetNativeView());
+
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ rvhi->SendOrientationChangeEvent(orientation);
+}
+
+jboolean ContentViewCoreImpl::SendTouchEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jint type,
+ jobjectArray pts) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (rwhv) {
+ using WebKit::WebTouchEvent;
+ WebKit::WebTouchEvent event;
+ TouchPoint::BuildWebTouchEvent(env, type, time_ms, GetDpiScale(), pts,
+ event);
+ rwhv->SendTouchEvent(event);
+ return true;
+ }
+ return false;
+}
+
+float ContentViewCoreImpl::GetTouchPaddingDip() {
+ return 48.0f / GetDpiScale();
+}
+
+float ContentViewCoreImpl::GetDpiScale() const {
+ return dpi_scale_;
+}
+
+void ContentViewCoreImpl::RequestContentClipping(
+ const gfx::Rect& clipping,
+ const gfx::Size& content_size) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (rwhv)
+ rwhv->RequestContentClipping(clipping, content_size);
+}
+
+jboolean ContentViewCoreImpl::SendMouseMoveEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jfloat x,
+ jfloat y) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (!rwhv)
+ return false;
+
+ WebKit::WebMouseEvent event = WebMouseEventBuilder::Build(
+ WebInputEvent::MouseMove,
+ WebKit::WebMouseEvent::ButtonNone,
+ time_ms / 1000.0, x / GetDpiScale(), y / GetDpiScale(), 0, 1);
+
+ rwhv->SendMouseEvent(event);
+ return true;
+}
+
+jboolean ContentViewCoreImpl::SendMouseWheelEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jfloat x,
+ jfloat y,
+ jfloat vertical_axis) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (!rwhv)
+ return false;
+
+ WebMouseWheelEventBuilder::Direction direction;
+ if (vertical_axis > 0) {
+ direction = WebMouseWheelEventBuilder::DIRECTION_UP;
+ } else if (vertical_axis < 0) {
+ direction = WebMouseWheelEventBuilder::DIRECTION_DOWN;
+ } else {
+ return false;
+ }
+ WebKit::WebMouseWheelEvent event = WebMouseWheelEventBuilder::Build(
+ direction, time_ms / 1000.0, x / GetDpiScale(), y / GetDpiScale());
+
+ rwhv->SendMouseWheelEvent(event);
+ return true;
+}
+
+WebGestureEvent ContentViewCoreImpl::MakeGestureEvent(
+ WebInputEvent::Type type, long time_ms, float x, float y) const {
+ return WebGestureEventBuilder::Build(
+ type, time_ms / 1000.0, x / GetDpiScale(), y / GetDpiScale());
+}
+
+void ContentViewCoreImpl::SendGestureEvent(
+ const WebKit::WebGestureEvent& event) {
+ RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
+ if (rwhv)
+ rwhv->SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::UpdateTabCrashedFlag() {
+ // Since RenderWidgetHostView is associated with the lifetime of the renderer
+ // process, we use it to test whether there is a renderer process.
+ tab_crashed_ = !(web_contents_->GetRenderWidgetHostView());
+}
+
+void ContentViewCoreImpl::ScrollBegin(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureScrollBegin, time_ms, x, y);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::ScrollEnd(JNIEnv* env, jobject obj, jlong time_ms) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureScrollEnd, time_ms, 0, 0);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::ScrollBy(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y, jfloat dx, jfloat dy,
+ jboolean last_input_event_for_vsync) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureScrollUpdate, time_ms, x, y);
+ event.data.scrollUpdate.deltaX = -dx / GetDpiScale();
+ event.data.scrollUpdate.deltaY = -dy / GetDpiScale();
+
+ SendGestureEvent(event);
+
+ // TODO(brianderson): Clean up last_input_event_for_vsync. crbug.com/247043
+ if (last_input_event_for_vsync) {
+ SendBeginFrame(base::TimeTicks() +
+ base::TimeDelta::FromMilliseconds(time_ms));
+ }
+}
+
+void ContentViewCoreImpl::FlingStart(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y, jfloat vx, jfloat vy) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureFlingStart, time_ms, x, y);
+
+ // Velocity should not be scaled by DIP since that interacts poorly with the
+ // deceleration constants. The DIP scaling is done on the renderer.
+ event.data.flingStart.velocityX = vx;
+ event.data.flingStart.velocityY = vy;
+
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::FlingCancel(JNIEnv* env, jobject obj, jlong time_ms) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureFlingCancel, time_ms, 0, 0);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::SingleTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureTap, time_ms, x, y);
+
+ event.data.tap.tapCount = 1;
+ if (!disambiguation_popup_tap) {
+ const float touch_padding_dip = GetTouchPaddingDip();
+ event.data.tap.width = touch_padding_dip;
+ event.data.tap.height = touch_padding_dip;
+ }
+
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::SingleTapUnconfirmed(JNIEnv* env, jobject obj,
+ jlong time_ms,
+ jfloat x, jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureTapUnconfirmed, time_ms, x, y);
+
+ event.data.tap.tapCount = 1;
+
+ const float touch_padding_dip = GetTouchPaddingDip();
+ event.data.tap.width = touch_padding_dip;
+ event.data.tap.height = touch_padding_dip;
+
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::ShowPressState(JNIEnv* env, jobject obj,
+ jlong time_ms,
+ jfloat x, jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureTapDown, time_ms, x, y);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::ShowPressCancel(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jfloat x,
+ jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureTapCancel, time_ms, x, y);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::DoubleTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureDoubleTap, time_ms, x, y);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::LongPress(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureLongPress, time_ms, x, y);
+
+ if (!disambiguation_popup_tap) {
+ const float touch_padding_dip = GetTouchPaddingDip();
+ event.data.longPress.width = touch_padding_dip;
+ event.data.longPress.height = touch_padding_dip;
+ }
+
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::LongTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureLongTap, time_ms, x, y);
+
+ if (!disambiguation_popup_tap) {
+ const float touch_padding_dip = GetTouchPaddingDip();
+ event.data.longPress.width = touch_padding_dip;
+ event.data.longPress.height = touch_padding_dip;
+ }
+
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::PinchBegin(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GesturePinchBegin, time_ms, x, y);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::PinchEnd(JNIEnv* env, jobject obj, jlong time_ms) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GesturePinchEnd, time_ms, 0, 0);
+ SendGestureEvent(event);
+}
+
+void ContentViewCoreImpl::PinchBy(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat anchor_x, jfloat anchor_y,
+ jfloat delta,
+ jboolean last_input_event_for_vsync) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GesturePinchUpdate, time_ms, anchor_x, anchor_y);
+ event.data.pinchUpdate.scale = delta;
+
+ SendGestureEvent(event);
+
+ // TODO(brianderson): Clean up last_input_event_for_vsync. crbug.com/247043
+ if (last_input_event_for_vsync) {
+ SendBeginFrame(base::TimeTicks() +
+ base::TimeDelta::FromMilliseconds(time_ms));
+ }
+}
+
+void ContentViewCoreImpl::SelectBetweenCoordinates(JNIEnv* env, jobject obj,
+ jfloat x1, jfloat y1,
+ jfloat x2, jfloat y2) {
+ if (GetRenderWidgetHostViewAndroid()) {
+ GetRenderWidgetHostViewAndroid()->SelectRange(
+ gfx::Point(x1 / GetDpiScale(), y1 / GetDpiScale()),
+ gfx::Point(x2 / GetDpiScale(), y2 / GetDpiScale()));
+ }
+}
+
+void ContentViewCoreImpl::MoveCaret(JNIEnv* env, jobject obj,
+ jfloat x, jfloat y) {
+ if (GetRenderWidgetHostViewAndroid()) {
+ GetRenderWidgetHostViewAndroid()->MoveCaret(
+ gfx::Point(x / GetDpiScale(), y / GetDpiScale()));
+ }
+}
+
+jboolean ContentViewCoreImpl::CanGoBack(JNIEnv* env, jobject obj) {
+ return web_contents_->GetController().CanGoBack();
+}
+
+jboolean ContentViewCoreImpl::CanGoForward(JNIEnv* env, jobject obj) {
+ return web_contents_->GetController().CanGoForward();
+}
+
+jboolean ContentViewCoreImpl::CanGoToOffset(JNIEnv* env, jobject obj,
+ jint offset) {
+ return web_contents_->GetController().CanGoToOffset(offset);
+}
+
+void ContentViewCoreImpl::GoBack(JNIEnv* env, jobject obj) {
+ web_contents_->GetController().GoBack();
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::GoForward(JNIEnv* env, jobject obj) {
+ web_contents_->GetController().GoForward();
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::GoToOffset(JNIEnv* env, jobject obj, jint offset) {
+ web_contents_->GetController().GoToOffset(offset);
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::GoToNavigationIndex(JNIEnv* env,
+ jobject obj,
+ jint index) {
+ web_contents_->GetController().GoToIndex(index);
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::StopLoading(JNIEnv* env, jobject obj) {
+ web_contents_->Stop();
+}
+
+void ContentViewCoreImpl::Reload(JNIEnv* env, jobject obj) {
+ // Set check_for_repost parameter to false as we have no repost confirmation
+ // dialog ("confirm form resubmission" screen will still appear, however).
+ if (web_contents_->GetController().NeedsReload())
+ web_contents_->GetController().LoadIfNecessary();
+ else
+ web_contents_->GetController().Reload(true);
+ UpdateTabCrashedFlag();
+}
+
+void ContentViewCoreImpl::CancelPendingReload(JNIEnv* env, jobject obj) {
+ web_contents_->GetController().CancelPendingReload();
+}
+
+void ContentViewCoreImpl::ContinuePendingReload(JNIEnv* env, jobject obj) {
+ web_contents_->GetController().ContinuePendingReload();
+}
+
+void ContentViewCoreImpl::ClearHistory(JNIEnv* env, jobject obj) {
+ // TODO(creis): Do callers of this need to know if it fails?
+ if (web_contents_->GetController().CanPruneAllButVisible())
+ web_contents_->GetController().PruneAllButVisible();
+}
+
+void ContentViewCoreImpl::AddJavascriptInterface(
+ JNIEnv* env,
+ jobject /* obj */,
+ jobject object,
+ jstring name,
+ jclass safe_annotation_clazz,
+ jobject retained_object_set) {
+ ScopedJavaLocalRef<jobject> scoped_object(env, object);
+ ScopedJavaLocalRef<jclass> scoped_clazz(env, safe_annotation_clazz);
+ JavaObjectWeakGlobalRef weak_retained_object_set(env, retained_object_set);
+
+ // JavaBoundObject creates the NPObject with a ref count of 1, and
+ // JavaBridgeDispatcherHostManager takes its own ref.
+ JavaBridgeDispatcherHostManager* java_bridge =
+ web_contents_->java_bridge_dispatcher_host_manager();
+ java_bridge->SetRetainedObjectSet(weak_retained_object_set);
+ NPObject* bound_object = JavaBoundObject::Create(scoped_object, scoped_clazz,
+ java_bridge->AsWeakPtr());
+ java_bridge->AddNamedObject(ConvertJavaStringToUTF16(env, name),
+ bound_object);
+ WebKit::WebBindings::releaseObject(bound_object);
+}
+
+void ContentViewCoreImpl::RemoveJavascriptInterface(JNIEnv* env,
+ jobject /* obj */,
+ jstring name) {
+ web_contents_->java_bridge_dispatcher_host_manager()->RemoveNamedObject(
+ ConvertJavaStringToUTF16(env, name));
+}
+
+void ContentViewCoreImpl::UpdateVSyncParameters(JNIEnv* env, jobject /* obj */,
+ jlong timebase_micros,
+ jlong interval_micros) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (!view)
+ return;
+
+ RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
+ view->GetRenderWidgetHost());
+
+ host->UpdateVSyncParameters(
+ base::TimeTicks::FromInternalValue(timebase_micros),
+ base::TimeDelta::FromMicroseconds(interval_micros));
+
+ vsync_interval_ =
+ base::TimeDelta::FromMicroseconds(interval_micros);
+ expected_browser_composite_time_ =
+ vsync_interval_ * kDefaultBrowserCompositeVSyncFraction;
+}
+
+void ContentViewCoreImpl::OnVSync(JNIEnv* env, jobject /* obj */,
+ jlong frame_time_micros) {
+ base::TimeTicks frame_time =
+ base::TimeTicks::FromInternalValue(frame_time_micros);
+ SendBeginFrame(frame_time);
+}
+
+void ContentViewCoreImpl::SendBeginFrame(base::TimeTicks frame_time) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (!view)
+ return;
+
+ base::TimeTicks display_time = frame_time + vsync_interval_;
+ base::TimeTicks deadline = display_time - expected_browser_composite_time_;
+
+ view->SendBeginFrame(
+ cc::BeginFrameArgs::Create(frame_time, deadline, vsync_interval_));
+}
+
+jboolean ContentViewCoreImpl::OnAnimate(JNIEnv* env, jobject /* obj */,
+ jlong frame_time_micros) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (!view)
+ return false;
+
+ return view->Animate(base::TimeTicks::FromInternalValue(frame_time_micros));
+}
+
+jboolean ContentViewCoreImpl::PopulateBitmapFromCompositor(JNIEnv* env,
+ jobject obj,
+ jobject jbitmap) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (!view)
+ return false;
+
+ return view->PopulateBitmapWithContents(jbitmap);
+}
+
+void ContentViewCoreImpl::WasResized(JNIEnv* env, jobject obj) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ if (view)
+ view->WasResized();
+}
+
+void ContentViewCoreImpl::ShowInterstitialPage(
+ JNIEnv* env, jobject obj, jstring jurl, jint delegate_ptr) {
+ GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
+ InterstitialPageDelegateAndroid* delegate =
+ reinterpret_cast<InterstitialPageDelegateAndroid*>(delegate_ptr);
+ InterstitialPage* interstitial = InterstitialPage::Create(
+ web_contents_, false, url, delegate);
+ delegate->set_interstitial_page(interstitial);
+ interstitial->Show();
+}
+
+jboolean ContentViewCoreImpl::IsShowingInterstitialPage(JNIEnv* env,
+ jobject obj) {
+ return web_contents_->ShowingInterstitialPage();
+}
+
+void ContentViewCoreImpl::AttachExternalVideoSurface(JNIEnv* env,
+ jobject obj,
+ jint player_id,
+ jobject jsurface) {
+#if defined(GOOGLE_TV)
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ BrowserMediaPlayerManager* browser_media_player_manager =
+ rvhi ? static_cast<BrowserMediaPlayerManager*>(
+ rvhi->media_player_manager())
+ : NULL;
+ if (browser_media_player_manager) {
+ browser_media_player_manager->AttachExternalVideoSurface(
+ static_cast<int>(player_id), jsurface);
+ }
+#endif
+}
+
+void ContentViewCoreImpl::DetachExternalVideoSurface(JNIEnv* env,
+ jobject obj,
+ jint player_id) {
+#if defined(GOOGLE_TV)
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ BrowserMediaPlayerManager* browser_media_player_manager =
+ rvhi ? static_cast<BrowserMediaPlayerManager*>(
+ rvhi->media_player_manager())
+ : NULL;
+ if (browser_media_player_manager) {
+ browser_media_player_manager->DetachExternalVideoSurface(
+ static_cast<int>(player_id));
+ }
+#endif
+}
+
+jboolean ContentViewCoreImpl::IsRenderWidgetHostViewReady(JNIEnv* env,
+ jobject obj) {
+ RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
+ return view && view->HasValidFrame();
+}
+
+void ContentViewCoreImpl::ExitFullscreen(JNIEnv* env, jobject obj) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ host->ExitFullscreen();
+}
+
+void ContentViewCoreImpl::UpdateTopControlsState(JNIEnv* env,
+ jobject obj,
+ bool enable_hiding,
+ bool enable_showing,
+ bool animate) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ host->Send(new ViewMsg_UpdateTopControlsState(host->GetRoutingID(),
+ enable_hiding,
+ enable_showing,
+ animate));
+}
+
+void ContentViewCoreImpl::ShowImeIfNeeded(JNIEnv* env, jobject obj) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ host->Send(new ViewMsg_ShowImeIfNeeded(host->GetRoutingID()));
+}
+
+void ContentViewCoreImpl::ScrollFocusedEditableNodeIntoView(JNIEnv* env,
+ jobject obj) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ host->Send(new InputMsg_ScrollFocusedEditableNodeIntoRect(
+ host->GetRoutingID(), gfx::Rect()));
+}
+
+namespace {
+
+static void AddNavigationEntryToHistory(JNIEnv* env, jobject obj,
+ jobject history,
+ NavigationEntry* entry,
+ int index) {
+ // Get the details of the current entry
+ ScopedJavaLocalRef<jstring> j_url(
+ ConvertUTF8ToJavaString(env, entry->GetURL().spec()));
+ ScopedJavaLocalRef<jstring> j_virtual_url(
+ ConvertUTF8ToJavaString(env, entry->GetVirtualURL().spec()));
+ ScopedJavaLocalRef<jstring> j_original_url(
+ ConvertUTF8ToJavaString(env, entry->GetOriginalRequestURL().spec()));
+ ScopedJavaLocalRef<jstring> j_title(
+ ConvertUTF16ToJavaString(env, entry->GetTitle()));
+ ScopedJavaLocalRef<jobject> j_bitmap;
+ const FaviconStatus& status = entry->GetFavicon();
+ if (status.valid && status.image.ToSkBitmap()->getSize() > 0)
+ j_bitmap = gfx::ConvertToJavaBitmap(status.image.ToSkBitmap());
+
+ // Add the item to the list
+ Java_ContentViewCore_addToNavigationHistory(
+ env, obj, history, index, j_url.obj(), j_virtual_url.obj(),
+ j_original_url.obj(), j_title.obj(), j_bitmap.obj());
+}
+
+} // namespace
+
+int ContentViewCoreImpl::GetNavigationHistory(JNIEnv* env,
+ jobject obj,
+ jobject history) {
+ // Iterate through navigation entries to populate the list
+ const NavigationController& controller = web_contents_->GetController();
+ int count = controller.GetEntryCount();
+ for (int i = 0; i < count; ++i) {
+ AddNavigationEntryToHistory(
+ env, obj, history, controller.GetEntryAtIndex(i), i);
+ }
+
+ return controller.GetCurrentEntryIndex();
+}
+
+void ContentViewCoreImpl::GetDirectedNavigationHistory(JNIEnv* env,
+ jobject obj,
+ jobject history,
+ jboolean is_forward,
+ jint max_entries) {
+ // Iterate through navigation entries to populate the list
+ const NavigationController& controller = web_contents_->GetController();
+ int count = controller.GetEntryCount();
+ int num_added = 0;
+ int increment_value = is_forward ? 1 : -1;
+ for (int i = controller.GetCurrentEntryIndex() + increment_value;
+ i >= 0 && i < count;
+ i += increment_value) {
+ if (num_added >= max_entries)
+ break;
+
+ AddNavigationEntryToHistory(
+ env, obj, history, controller.GetEntryAtIndex(i), i);
+ num_added++;
+ }
+}
+
+ScopedJavaLocalRef<jstring>
+ContentViewCoreImpl::GetOriginalUrlForActiveNavigationEntry(JNIEnv* env,
+ jobject obj) {
+ NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
+ if (entry == NULL)
+ return ScopedJavaLocalRef<jstring>(env, NULL);
+ return ConvertUTF8ToJavaString(env, entry->GetOriginalRequestURL().spec());
+}
+
+int ContentViewCoreImpl::GetNativeImeAdapter(JNIEnv* env, jobject obj) {
+ RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
+ if (!rwhva)
+ return 0;
+ return rwhva->GetNativeImeAdapter();
+}
+
+jboolean ContentViewCoreImpl::NeedsReload(JNIEnv* env, jobject obj) {
+ return web_contents_->GetController().NeedsReload();
+}
+
+void ContentViewCoreImpl::UndoScrollFocusedEditableNodeIntoView(
+ JNIEnv* env,
+ jobject obj) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ host->Send(
+ new ViewMsg_UndoScrollFocusedEditableNodeIntoView(host->GetRoutingID()));
+}
+
+namespace {
+void JavaScriptResultCallback(const ScopedJavaGlobalRef<jobject>& callback,
+ const base::Value* result) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ std::string json;
+ base::JSONWriter::Write(result, &json);
+ ScopedJavaLocalRef<jstring> j_json = ConvertUTF8ToJavaString(env, json);
+ Java_ContentViewCore_onEvaluateJavaScriptResult(env,
+ j_json.obj(),
+ callback.obj());
+}
+} // namespace
+
+void ContentViewCoreImpl::EvaluateJavaScript(JNIEnv* env,
+ jobject obj,
+ jstring script,
+ jobject callback) {
+ RenderViewHost* host = web_contents_->GetRenderViewHost();
+ DCHECK(host);
+
+ if (!callback) {
+ // No callback requested.
+ host->ExecuteJavascriptInWebFrame(string16(), // frame_xpath
+ ConvertJavaStringToUTF16(env, script));
+ return;
+ }
+
+ // Secure the Java callback in a scoped object and give ownership of it to the
+ // base::Callback.
+ ScopedJavaGlobalRef<jobject> j_callback;
+ j_callback.Reset(env, callback);
+ content::RenderViewHost::JavascriptResultCallback c_callback =
+ base::Bind(&JavaScriptResultCallback, j_callback);
+
+ host->ExecuteJavascriptInWebFrameCallbackResult(
+ string16(), // frame_xpath
+ ConvertJavaStringToUTF16(env, script),
+ c_callback);
+}
+
+bool ContentViewCoreImpl::GetUseDesktopUserAgent(
+ JNIEnv* env, jobject obj) {
+ NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
+ return entry && entry->GetIsOverridingUserAgent();
+}
+
+void ContentViewCoreImpl::UpdateImeAdapter(int native_ime_adapter,
+ int text_input_type,
+ const std::string& text,
+ int selection_start,
+ int selection_end,
+ int composition_start,
+ int composition_end,
+ bool show_ime_if_needed) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ ScopedJavaLocalRef<jstring> jstring_text = ConvertUTF8ToJavaString(env, text);
+ Java_ContentViewCore_updateImeAdapter(env, obj.obj(),
+ native_ime_adapter, text_input_type,
+ jstring_text.obj(),
+ selection_start, selection_end,
+ composition_start, composition_end,
+ show_ime_if_needed);
+}
+
+void ContentViewCoreImpl::ProcessImeBatchStateAck(bool is_begin) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ Java_ContentViewCore_processImeBatchStateAck(env, obj.obj(), is_begin);
+}
+
+void ContentViewCoreImpl::ClearSslPreferences(JNIEnv* env, jobject obj) {
+ SSLHostState* state = SSLHostState::GetFor(
+ web_contents_->GetController().GetBrowserContext());
+ state->Clear();
+}
+
+void ContentViewCoreImpl::SetUseDesktopUserAgent(
+ JNIEnv* env,
+ jobject obj,
+ jboolean enabled,
+ jboolean reload_on_state_change) {
+ if (GetUseDesktopUserAgent(env, obj) == enabled)
+ return;
+
+ // Make sure the navigation entry actually exists.
+ NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
+ if (!entry)
+ return;
+
+ // Set the flag in the NavigationEntry.
+ entry->SetIsOverridingUserAgent(enabled);
+
+ // Send the override to the renderer.
+ if (reload_on_state_change) {
+ // Reloading the page will send the override down as part of the
+ // navigation IPC message.
+ NavigationControllerImpl& controller =
+ static_cast<NavigationControllerImpl&>(web_contents_->GetController());
+ controller.ReloadOriginalRequestURL(false);
+ }
+}
+
+void ContentViewCoreImpl::SetAccessibilityEnabled(JNIEnv* env, jobject obj,
+ bool enabled) {
+ RenderWidgetHostViewAndroid* host_view = GetRenderWidgetHostViewAndroid();
+ if (!host_view)
+ return;
+ RenderWidgetHostImpl* host_impl = RenderWidgetHostImpl::From(
+ host_view->GetRenderWidgetHost());
+ if (enabled) {
+ BrowserAccessibilityState::GetInstance()->EnableAccessibility();
+ if (host_impl)
+ host_impl->SetAccessibilityMode(AccessibilityModeComplete);
+ } else {
+ BrowserAccessibilityState::GetInstance()->DisableAccessibility();
+ if (host_impl)
+ host_impl->SetAccessibilityMode(AccessibilityModeOff);
+ }
+}
+
+// This is called for each ContentView.
+jint Init(JNIEnv* env, jobject obj,
+ jboolean hardware_accelerated,
+ jint native_web_contents,
+ jint view_android,
+ jint window_android) {
+ ContentViewCoreImpl* view = new ContentViewCoreImpl(
+ env, obj, hardware_accelerated,
+ reinterpret_cast<WebContents*>(native_web_contents),
+ reinterpret_cast<ui::ViewAndroid*>(view_android),
+ reinterpret_cast<ui::WindowAndroid*>(window_android));
+ return reinterpret_cast<jint>(view);
+}
+
+bool RegisterContentViewCore(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/content_view_core_impl.h b/chromium/content/browser/android/content_view_core_impl.h
new file mode 100644
index 00000000000..ab7da6d2439
--- /dev/null
+++ b/chromium/content/browser/android/content_view_core_impl.h
@@ -0,0 +1,373 @@
+// Copyright 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 CONTENT_BROWSER_ANDROID_CONTENT_VIEW_CORE_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_VIEW_CORE_IMPL_H_
+
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_helper.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/i18n/rtl.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "content/browser/renderer_host/render_widget_host_view_android.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/android/content_view_core.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "url/gurl.h"
+
+namespace ui {
+class ViewAndroid;
+class WindowAndroid;
+}
+
+namespace content {
+class RenderWidgetHostViewAndroid;
+struct MenuItem;
+
+// TODO(jrg): this is a shell. Upstream the rest.
+class ContentViewCoreImpl : public ContentViewCore,
+ public NotificationObserver {
+ public:
+ static ContentViewCoreImpl* FromWebContents(WebContents* web_contents);
+ ContentViewCoreImpl(JNIEnv* env,
+ jobject obj,
+ bool hardware_accelerated,
+ WebContents* web_contents,
+ ui::ViewAndroid* view_android,
+ ui::WindowAndroid* window_android);
+
+ // ContentViewCore implementation.
+ virtual base::android::ScopedJavaLocalRef<jobject> GetJavaObject() OVERRIDE;
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual ui::ViewAndroid* GetViewAndroid() const OVERRIDE;
+ virtual ui::WindowAndroid* GetWindowAndroid() const OVERRIDE;
+ virtual scoped_refptr<cc::Layer> GetLayer() const OVERRIDE;
+ virtual void LoadUrl(NavigationController::LoadURLParams& params) OVERRIDE;
+ virtual jint GetCurrentRenderProcessId(JNIEnv* env, jobject obj) OVERRIDE;
+ virtual void ShowPastePopup(int x, int y) OVERRIDE;
+ virtual unsigned int GetScaledContentTexture(
+ float scale,
+ gfx::Size* out_size) OVERRIDE;
+ virtual float GetDpiScale() const OVERRIDE;
+ virtual void RequestContentClipping(const gfx::Rect& clipping,
+ const gfx::Size& content_size) OVERRIDE;
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+
+ void OnJavaContentViewCoreDestroyed(JNIEnv* env, jobject obj);
+
+ // Notifies the ContentViewCore that items were selected in the currently
+ // showing select popup.
+ void SelectPopupMenuItems(JNIEnv* env, jobject obj, jintArray indices);
+
+ void LoadUrl(
+ JNIEnv* env, jobject obj,
+ jstring url,
+ jint load_url_type,
+ jint transition_type,
+ jint ua_override_option,
+ jstring extra_headers,
+ jbyteArray post_data,
+ jstring base_url_for_data_url,
+ jstring virtual_url_for_data_url,
+ jboolean can_load_local_resources);
+ base::android::ScopedJavaLocalRef<jstring> GetURL(JNIEnv* env, jobject) const;
+ base::android::ScopedJavaLocalRef<jstring> GetTitle(
+ JNIEnv* env, jobject obj) const;
+ jboolean IsIncognito(JNIEnv* env, jobject obj);
+ jboolean Crashed(JNIEnv* env, jobject obj) const { return tab_crashed_; }
+ void SendOrientationChangeEvent(JNIEnv* env, jobject obj, jint orientation);
+ jboolean SendTouchEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jint type,
+ jobjectArray pts);
+ jboolean SendMouseMoveEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jfloat x,
+ jfloat y);
+ jboolean SendMouseWheelEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jfloat x,
+ jfloat y,
+ jfloat vertical_axis);
+ void ScrollBegin(JNIEnv* env, jobject obj, jlong time_ms, jfloat x, jfloat y);
+ void ScrollEnd(JNIEnv* env, jobject obj, jlong time_ms);
+ void ScrollBy(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y, jfloat dx, jfloat dy,
+ jboolean last_input_event_for_vsync);
+ void FlingStart(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y, jfloat vx, jfloat vy);
+ void FlingCancel(JNIEnv* env, jobject obj, jlong time_ms);
+ void SingleTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap);
+ void SingleTapUnconfirmed(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y);
+ void ShowPressState(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y);
+ void ShowPressCancel(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y);
+ void DoubleTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y) ;
+ void LongPress(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap);
+ void LongTap(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y,
+ jboolean disambiguation_popup_tap);
+ void PinchBegin(JNIEnv* env, jobject obj, jlong time_ms, jfloat x, jfloat y);
+ void PinchEnd(JNIEnv* env, jobject obj, jlong time_ms);
+ void PinchBy(JNIEnv* env, jobject obj, jlong time_ms,
+ jfloat x, jfloat y, jfloat delta,
+ jboolean last_input_event_for_vsync);
+ void SelectBetweenCoordinates(JNIEnv* env, jobject obj,
+ jfloat x1, jfloat y1,
+ jfloat x2, jfloat y2);
+ void MoveCaret(JNIEnv* env, jobject obj, jfloat x, jfloat y);
+
+ jboolean CanGoBack(JNIEnv* env, jobject obj);
+ jboolean CanGoForward(JNIEnv* env, jobject obj);
+ jboolean CanGoToOffset(JNIEnv* env, jobject obj, jint offset);
+ void GoBack(JNIEnv* env, jobject obj);
+ void GoForward(JNIEnv* env, jobject obj);
+ void GoToOffset(JNIEnv* env, jobject obj, jint offset);
+ void GoToNavigationIndex(JNIEnv* env, jobject obj, jint index);
+ void StopLoading(JNIEnv* env, jobject obj);
+ void Reload(JNIEnv* env, jobject obj);
+ void CancelPendingReload(JNIEnv* env, jobject obj);
+ void ContinuePendingReload(JNIEnv* env, jobject obj);
+ jboolean NeedsReload(JNIEnv* env, jobject obj);
+ void ClearHistory(JNIEnv* env, jobject obj);
+ void EvaluateJavaScript(JNIEnv* env,
+ jobject obj,
+ jstring script,
+ jobject callback);
+ int GetNativeImeAdapter(JNIEnv* env, jobject obj);
+ void SetFocus(JNIEnv* env, jobject obj, jboolean focused);
+ void ScrollFocusedEditableNodeIntoView(JNIEnv* env, jobject obj);
+ void UndoScrollFocusedEditableNodeIntoView(JNIEnv* env, jobject obj);
+
+ jint GetBackgroundColor(JNIEnv* env, jobject obj);
+ void SetBackgroundColor(JNIEnv* env, jobject obj, jint color);
+ void OnShow(JNIEnv* env, jobject obj);
+ void OnHide(JNIEnv* env, jobject obj);
+ void ClearSslPreferences(JNIEnv* env, jobject /* obj */);
+ void SetUseDesktopUserAgent(JNIEnv* env,
+ jobject /* obj */,
+ jboolean state,
+ jboolean reload_on_state_change);
+ bool GetUseDesktopUserAgent(JNIEnv* env, jobject /* obj */);
+ void Show();
+ void Hide();
+ void AddJavascriptInterface(JNIEnv* env,
+ jobject obj,
+ jobject object,
+ jstring name,
+ jclass safe_annotation_clazz,
+ jobject retained_object_set);
+ void RemoveJavascriptInterface(JNIEnv* env, jobject obj, jstring name);
+ int GetNavigationHistory(JNIEnv* env, jobject obj, jobject history);
+ void GetDirectedNavigationHistory(JNIEnv* env,
+ jobject obj,
+ jobject history,
+ jboolean is_forward,
+ jint max_entries);
+ base::android::ScopedJavaLocalRef<jstring>
+ GetOriginalUrlForActiveNavigationEntry(JNIEnv* env, jobject obj);
+ void UpdateVSyncParameters(JNIEnv* env, jobject obj, jlong timebase_micros,
+ jlong interval_micros);
+ void OnVSync(JNIEnv* env, jobject /* obj */, jlong frame_time_micros);
+ jboolean OnAnimate(JNIEnv* env, jobject /* obj */, jlong frame_time_micros);
+ jboolean PopulateBitmapFromCompositor(JNIEnv* env,
+ jobject obj,
+ jobject jbitmap);
+ void WasResized(JNIEnv* env, jobject obj);
+ jboolean IsRenderWidgetHostViewReady(JNIEnv* env, jobject obj);
+ void ExitFullscreen(JNIEnv* env, jobject obj);
+ void UpdateTopControlsState(JNIEnv* env,
+ jobject obj,
+ bool enable_hiding,
+ bool enable_showing,
+ bool animate);
+ void ShowImeIfNeeded(JNIEnv* env, jobject obj);
+
+ void ShowInterstitialPage(JNIEnv* env,
+ jobject obj,
+ jstring jurl,
+ jint delegate);
+ jboolean IsShowingInterstitialPage(JNIEnv* env, jobject obj);
+
+ void AttachExternalVideoSurface(JNIEnv* env,
+ jobject obj,
+ jint player_id,
+ jobject jsurface);
+ void DetachExternalVideoSurface(JNIEnv* env, jobject obj, jint player_id);
+ void SetAccessibilityEnabled(JNIEnv* env, jobject obj, bool enabled);
+
+ // --------------------------------------------------------------------------
+ // Public methods that call to Java via JNI
+ // --------------------------------------------------------------------------
+
+ // Creates a popup menu with |items|.
+ // |multiple| defines if it should support multi-select.
+ // If not |multiple|, |selected_item| sets the initially selected item.
+ // Otherwise, item's "checked" flag selects it.
+ void ShowSelectPopupMenu(const std::vector<MenuItem>& items,
+ int selected_item,
+ bool multiple);
+
+ void OnTabCrashed();
+
+ // All sizes and offsets are in CSS pixels as cached by the renderer.
+ void UpdateFrameInfo(const gfx::Vector2dF& scroll_offset,
+ float page_scale_factor,
+ const gfx::Vector2dF& page_scale_factor_limits,
+ const gfx::SizeF& content_size,
+ const gfx::SizeF& viewport_size,
+ const gfx::Vector2dF& controls_offset,
+ const gfx::Vector2dF& content_offset,
+ float overdraw_bottom_height);
+
+ void UpdateImeAdapter(int native_ime_adapter, int text_input_type,
+ const std::string& text,
+ int selection_start, int selection_end,
+ int composition_start, int composition_end,
+ bool show_ime_if_needed);
+ void ProcessImeBatchStateAck(bool is_begin);
+ void SetTitle(const string16& title);
+ void OnBackgroundColorChanged(SkColor color);
+
+ bool HasFocus();
+ void ConfirmTouchEvent(InputEventAckState ack_result);
+ void UnhandledFlingStartEvent();
+ void HasTouchEventHandlers(bool need_touch_events);
+ void OnSelectionChanged(const std::string& text);
+ void OnSelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params);
+
+ void StartContentIntent(const GURL& content_url);
+
+ // Shows the disambiguation popup
+ // |target_rect| --> window coordinates which |zoomed_bitmap| represents
+ // |zoomed_bitmap| --> magnified image of potential touch targets
+ void ShowDisambiguationPopup(
+ const gfx::Rect& target_rect, const SkBitmap& zoomed_bitmap);
+
+ // Creates a java-side smooth scroller. Used by
+ // chrome.gpuBenchmarking.smoothScrollBy.
+ base::android::ScopedJavaLocalRef<jobject> CreateSmoothScroller(
+ bool scroll_down, int mouse_event_x, int mouse_event_y);
+
+ // Notifies the java object about the external surface, requesting for one if
+ // necessary.
+ void NotifyExternalSurface(
+ int player_id, bool is_request, const gfx::RectF& rect);
+
+ base::android::ScopedJavaLocalRef<jobject> GetContentVideoViewClient();
+
+ // Returns the context that the ContentViewCore was created with, it would
+ // typically be an Activity context for an on screen view.
+ base::android::ScopedJavaLocalRef<jobject> GetContext();
+
+ // --------------------------------------------------------------------------
+ // Methods called from native code
+ // --------------------------------------------------------------------------
+
+ gfx::Size GetPhysicalBackingSize() const;
+ gfx::Size GetViewportSizeDip() const;
+ gfx::Size GetViewportSizeOffsetDip() const;
+ float GetOverdrawBottomHeightDip() const;
+
+ void AttachLayer(scoped_refptr<cc::Layer> layer);
+ void RemoveLayer(scoped_refptr<cc::Layer> layer);
+ void SetNeedsBeginFrame(bool enabled);
+ void SetNeedsAnimate();
+
+ private:
+ class ContentViewUserData;
+
+ friend class ContentViewUserData;
+ virtual ~ContentViewCoreImpl();
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // --------------------------------------------------------------------------
+ // Other private methods and data
+ // --------------------------------------------------------------------------
+
+ void InitWebContents();
+
+ RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid();
+
+ float GetTouchPaddingDip();
+
+ WebKit::WebGestureEvent MakeGestureEvent(
+ WebKit::WebInputEvent::Type type, long time_ms, float x, float y) const;
+
+ void SendBeginFrame(base::TimeTicks frame_time);
+
+ gfx::Size GetViewportSizePix() const;
+ gfx::Size GetViewportSizeOffsetPix() const;
+
+ void DeleteScaledSnapshotTexture();
+
+ void SendGestureEvent(const WebKit::WebGestureEvent& event);
+
+ // Checks if there there is a corresponding renderer process and updates
+ // |tab_crashed_| accordingly.
+ void UpdateTabCrashedFlag();
+
+ // A weak reference to the Java ContentViewCore object.
+ JavaObjectWeakGlobalRef java_ref_;
+
+ NotificationRegistrar notification_registrar_;
+
+ // Reference to the current WebContents used to determine how and what to
+ // display in the ContentViewCore.
+ WebContentsImpl* web_contents_;
+
+ // A compositor layer containing any layer that should be shown.
+ scoped_refptr<cc::Layer> root_layer_;
+
+ // Whether the renderer backing this ContentViewCore has crashed.
+ bool tab_crashed_;
+
+ // Device scale factor.
+ float dpi_scale_;
+
+ // Variables used to keep track of frame timestamps and deadlines.
+ base::TimeDelta vsync_interval_;
+ base::TimeDelta expected_browser_composite_time_;
+
+ // The Android view that can be used to add and remove decoration layers
+ // like AutofillPopup.
+ ui::ViewAndroid* view_android_;
+
+ // The owning window that has a hold of main application activity.
+ ui::WindowAndroid* window_android_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentViewCoreImpl);
+};
+
+bool RegisterContentViewCore(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_VIEW_CORE_IMPL_H_
diff --git a/chromium/content/browser/android/content_view_render_view.cc b/chromium/content/browser/android/content_view_render_view.cc
new file mode 100644
index 00000000000..37a24aa91c8
--- /dev/null
+++ b/chromium/content/browser/android/content_view_render_view.cc
@@ -0,0 +1,99 @@
+// 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 "content/browser/android/content_view_render_view.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "cc/layers/layer.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/public/browser/android/compositor.h"
+#include "content/public/browser/android/content_view_layer_renderer.h"
+#include "jni/ContentViewRenderView_jni.h"
+#include "ui/gfx/size.h"
+
+#include <android/native_window_jni.h>
+
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+// static
+bool ContentViewRenderView::RegisterContentViewRenderView(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+ContentViewRenderView::ContentViewRenderView()
+ : scheduled_composite_(false),
+ weak_factory_(this) {
+}
+
+ContentViewRenderView::~ContentViewRenderView() {
+}
+
+// static
+jint Init(JNIEnv* env, jclass clazz) {
+ ContentViewRenderView* content_view_render_view =
+ new ContentViewRenderView();
+ return reinterpret_cast<jint>(content_view_render_view);
+}
+
+void ContentViewRenderView::Destroy(JNIEnv* env, jobject obj) {
+ delete this;
+}
+
+void ContentViewRenderView::SetCurrentContentView(
+ JNIEnv* env, jobject obj, int native_content_view) {
+ InitCompositor();
+ ContentViewCoreImpl* content_view =
+ reinterpret_cast<ContentViewCoreImpl*>(native_content_view);
+ if (content_view)
+ compositor_->SetRootLayer(content_view->GetLayer());
+}
+
+void ContentViewRenderView::SurfaceCreated(
+ JNIEnv* env, jobject obj, jobject jsurface) {
+ InitCompositor();
+ compositor_->SetSurface(jsurface);
+}
+
+void ContentViewRenderView::SurfaceDestroyed(JNIEnv* env, jobject obj) {
+ compositor_->SetSurface(NULL);
+}
+
+void ContentViewRenderView::SurfaceSetSize(
+ JNIEnv* env, jobject obj, jint width, jint height) {
+ compositor_->SetWindowBounds(gfx::Size(width, height));
+}
+
+void ContentViewRenderView::ScheduleComposite() {
+ if (scheduled_composite_)
+ return;
+
+ scheduled_composite_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ContentViewRenderView::Composite,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ContentViewRenderView::InitCompositor() {
+ if (!compositor_)
+ compositor_.reset(Compositor::Create(this));
+}
+
+void ContentViewRenderView::Composite() {
+ if (!compositor_)
+ return;
+
+ scheduled_composite_ = false;
+ compositor_->Composite();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/content_view_render_view.h b/chromium/content/browser/android/content_view_render_view.h
new file mode 100644
index 00000000000..dbaeccf7514
--- /dev/null
+++ b/chromium/content/browser/android/content_view_render_view.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_ANDROID_CONTENT_VIEW_RENDER_VIEW_H_
+#define CONTENT_BROWSER_ANDROID_CONTENT_VIEW_RENDER_VIEW_H_
+
+#include <jni.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/android/compositor_client.h"
+
+namespace content {
+class Compositor;
+
+class ContentViewRenderView : public CompositorClient {
+ public:
+ // Registers the JNI methods for ContentViewRender.
+ static bool RegisterContentViewRenderView(JNIEnv* env);
+
+ ContentViewRenderView();
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+ static jint Init(JNIEnv* env, jclass clazz);
+ void Destroy(JNIEnv* env, jobject obj);
+ void SetCurrentContentView(JNIEnv* env, jobject obj, int native_content_view);
+ void SurfaceCreated(JNIEnv* env, jobject obj, jobject jsurface);
+ void SurfaceDestroyed(JNIEnv* env, jobject obj);
+ void SurfaceSetSize(JNIEnv* env, jobject obj, jint width, jint height);
+
+ private:
+ friend class base::RefCounted<ContentViewRenderView>;
+ virtual ~ContentViewRenderView();
+
+ // CompositorClient implementation.
+ virtual void ScheduleComposite() OVERRIDE;
+
+ void InitCompositor();
+ void Composite();
+
+ scoped_ptr<content::Compositor> compositor_;
+ bool scheduled_composite_;
+
+ base::WeakPtrFactory<ContentViewRenderView> weak_factory_;
+
+ // Note that this class does not call back to Java and as a result does not
+ // have a reference to its Java object.
+
+ DISALLOW_COPY_AND_ASSIGN(ContentViewRenderView);
+};
+
+
+
+}
+
+#endif // CONTENT_BROWSER_ANDROID_CONTENT_VIEW_RENDER_VIEW_H_
diff --git a/chromium/content/browser/android/content_view_statics.cc b/chromium/content/browser/android/content_view_statics.cc
new file mode 100644
index 00000000000..66af8652d49
--- /dev/null
+++ b/chromium/content/browser/android/content_view_statics.cc
@@ -0,0 +1,91 @@
+// 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 <jni.h>
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "content/browser/android/content_view_statics.h"
+#include "content/common/android/address_parser.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/render_process_host.h"
+#include "jni/ContentViewStatics_jni.h"
+
+using base::android::ConvertJavaStringToUTF16;
+using base::android::ConvertUTF16ToJavaString;
+
+namespace {
+
+// TODO(pliard): http://crbug.com/235909. Move WebKit shared timer toggling
+// functionality out of ContentViewStatistics and not be build on top of
+// WebKit::Platform::SuspendSharedTimer.
+// TODO(pliard): http://crbug.com/235912. Add unit tests for WebKit shared timer
+// toggling.
+
+// This tracks the renderer processes that received a suspend request. It's
+// important on resume to only resume the renderer processes that were actually
+// suspended as opposed to all the current renderer processes because the
+// suspend calls are refcounted within WebKitPlatformSupport and it expects a
+// perfectly matched number of resume calls.
+// Note that this vector is only accessed from the UI thread.
+base::LazyInstance<std::vector<int /* process id */> > g_suspended_processes =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Suspends timers in all current render processes.
+void SuspendWebKitSharedTimers(std::vector<int>* suspended_processes) {
+ for (content::RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ content::RenderProcessHost* host = i.GetCurrentValue();
+ suspended_processes->push_back(host->GetID());
+ host->Send(new ViewMsg_SetWebKitSharedTimersSuspended(true));
+ }
+}
+
+// Resumes timers in processes that were previously stopped.
+void ResumeWebkitSharedTimers(const std::vector<int>& suspended_processes) {
+ for (std::vector<int>::const_iterator it = suspended_processes.begin();
+ it != suspended_processes.end(); ++it) {
+ content::RenderProcessHost* host = content::RenderProcessHost::FromID(*it);
+ if (host) // The process might have been killed since it was suspended.
+ host->Send(new ViewMsg_SetWebKitSharedTimersSuspended(false));
+ }
+}
+
+} // namespace
+
+// Returns the first substring consisting of the address of a physical location.
+static jstring FindAddress(JNIEnv* env, jclass clazz, jstring addr) {
+ string16 content_16 = ConvertJavaStringToUTF16(env, addr);
+ string16 result_16;
+ if (content::address_parser::FindAddress(content_16, &result_16))
+ return ConvertUTF16ToJavaString(env, result_16).Release();
+ return NULL;
+}
+
+static void SetWebKitSharedTimersSuspended(JNIEnv* env,
+ jclass obj,
+ jboolean suspend) {
+ std::vector<int>* suspended_processes = g_suspended_processes.Pointer();
+ if (suspend) {
+ DCHECK(suspended_processes->empty());
+ SuspendWebKitSharedTimers(suspended_processes);
+ } else {
+ ResumeWebkitSharedTimers(*suspended_processes);
+ suspended_processes->clear();
+ }
+}
+
+namespace content {
+
+bool RegisterWebViewStatics(JNIEnv* env) {
+ return RegisterNativesImpl(env) >= 0;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/content_view_statics.h b/chromium/content/browser/android/content_view_statics.h
new file mode 100644
index 00000000000..e640fec551e
--- /dev/null
+++ b/chromium/content/browser/android/content_view_statics.h
@@ -0,0 +1,14 @@
+// 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 CONTENT_BROWSER_ANDROID_VIEW_STATICS_H_
+#define CONTENT_BROWSER_ANDROID_VIEW_STATICS_H_
+
+namespace content {
+
+bool RegisterWebViewStatics(JNIEnv* env);
+
+}
+
+#endif // CONTENT_BROWSER_ANDROID_VIEW_STATICS_H_
diff --git a/chromium/content/browser/android/date_time_chooser_android.cc b/chromium/content/browser/android/date_time_chooser_android.cc
new file mode 100644
index 00000000000..0c3ba2d7327
--- /dev/null
+++ b/chromium/content/browser/android/date_time_chooser_android.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 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 "content/browser/android/date_time_chooser_android.h"
+
+#include "base/android/jni_string.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/android/content_view_core.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "jni/DateTimeChooserAndroid_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF16;
+using base::android::ConvertUTF8ToJavaString;
+
+
+namespace content {
+
+// Updates date/time via IPC to the RenderView
+class DateTimeChooserAndroid::DateTimeIPCSender :
+ public RenderViewHostObserver {
+ public:
+ explicit DateTimeIPCSender(RenderViewHost* sender);
+ virtual ~DateTimeIPCSender() {}
+ void ReplaceDateTime(int dialog_type,
+ int year, int month, int day, int hour, int minute, int second, int week);
+ void CancelDialog();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DateTimeIPCSender);
+};
+
+DateTimeChooserAndroid::DateTimeIPCSender::DateTimeIPCSender(
+ RenderViewHost* sender)
+ : RenderViewHostObserver(sender) {
+}
+
+void DateTimeChooserAndroid::DateTimeIPCSender::ReplaceDateTime(
+ int dialog_type,
+ int year, int month, int day, int hour, int minute, int second, int week) {
+ ViewHostMsg_DateTimeDialogValue_Params value;
+ value.year = year;
+ value.month = month;
+ value.day = day;
+ value.hour = hour;
+ value.minute = minute;
+ value.second = second;
+ value.week = week;
+ value.dialog_type = dialog_type;
+ Send(new ViewMsg_ReplaceDateTime(routing_id(), value));
+}
+
+void DateTimeChooserAndroid::DateTimeIPCSender::CancelDialog() {
+ Send(new ViewMsg_CancelDateTimeDialog(routing_id()));
+}
+
+// DateTimeChooserAndroid implementation
+DateTimeChooserAndroid::DateTimeChooserAndroid()
+ : sender_(NULL) {
+}
+
+DateTimeChooserAndroid::~DateTimeChooserAndroid() {
+}
+
+// static
+void DateTimeChooserAndroid::InitializeDateInputTypes(
+ int text_input_type_date, int text_input_type_date_time,
+ int text_input_type_date_time_local, int text_input_type_month,
+ int text_input_type_time, int text_input_type_week) {
+ JNIEnv* env = AttachCurrentThread();
+ Java_DateTimeChooserAndroid_initializeDateInputTypes(
+ env,
+ text_input_type_date, text_input_type_date_time,
+ text_input_type_date_time_local, text_input_type_month,
+ text_input_type_time, text_input_type_week);
+}
+
+void DateTimeChooserAndroid::ReplaceDateTime(
+ JNIEnv* env, jobject, int dialog_type,
+ int year, int month, int day, int hour, int minute, int second, int week) {
+ sender_->ReplaceDateTime(
+ dialog_type, year, month, day, hour, minute, second, week);
+}
+
+void DateTimeChooserAndroid::CancelDialog(JNIEnv* env, jobject) {
+ sender_->CancelDialog();
+}
+
+void DateTimeChooserAndroid::ShowDialog(
+ ContentViewCore* content, RenderViewHost* sender,
+ int type, int year, int month, int day,
+ int hour, int minute, int second, int week, double min, double max) {
+ if (sender_)
+ delete sender_;
+ sender_ = new DateTimeIPCSender(sender);
+
+ JNIEnv* env = AttachCurrentThread();
+ j_date_time_chooser_.Reset(Java_DateTimeChooserAndroid_createDateTimeChooser(
+ env, content->GetJavaObject().obj(),
+ reinterpret_cast<intptr_t>(this),
+ type, year, month, day, hour, minute, second, week, min, max));
+}
+
+// ----------------------------------------------------------------------------
+// Native JNI methods
+// ----------------------------------------------------------------------------
+bool RegisterDateTimeChooserAndroid(JNIEnv* env) {
+ bool registered = RegisterNativesImpl(env);
+ if (registered)
+ DateTimeChooserAndroid::InitializeDateInputTypes(
+ ui::TEXT_INPUT_TYPE_DATE,
+ ui::TEXT_INPUT_TYPE_DATE_TIME,
+ ui::TEXT_INPUT_TYPE_DATE_TIME_LOCAL,
+ ui::TEXT_INPUT_TYPE_MONTH,
+ ui::TEXT_INPUT_TYPE_TIME,
+ ui::TEXT_INPUT_TYPE_WEEK);
+ return registered;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/date_time_chooser_android.h b/chromium/content/browser/android/date_time_chooser_android.h
new file mode 100644
index 00000000000..646ba3c14ea
--- /dev/null
+++ b/chromium/content/browser/android/date_time_chooser_android.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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 CONTENT_BROWSER_ANDROID_DATE_TIME_CHOOSER_ANDROID_H_
+#define CONTENT_BROWSER_ANDROID_DATE_TIME_CHOOSER_ANDROID_H_
+
+#include <string>
+
+#include "base/android/jni_helper.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace content {
+
+class ContentViewCore;
+class RenderViewHost;
+
+// Android implementation for DateTimeChooser dialogs.
+class DateTimeChooserAndroid {
+ public:
+ DateTimeChooserAndroid();
+ ~DateTimeChooserAndroid();
+
+ // DateTimeChooser implementation:
+ void ShowDialog(ContentViewCore* content,
+ RenderViewHost* sender,
+ int type, int year, int month, int day,
+ int hour, int minute, int second, int week,
+ double min, double max);
+
+ // Replaces the current value with the one passed the different fields
+ void ReplaceDateTime(JNIEnv* env, jobject, jint dialog_type,
+ jint year, jint month, jint day,
+ jint hour, jint minute, jint second, jint week);
+
+ // Closes the dialog without propagating any changes.
+ void CancelDialog(JNIEnv* env, jobject);
+
+ // Propagates the different types of accepted date/time values to the
+ // java side.
+ static void InitializeDateInputTypes(
+ int text_input_type_date, int text_input_type_date_time,
+ int text_input_type_date_time_local, int text_input_type_month,
+ int text_input_type_time, int text_input_type_week);
+
+ private:
+ class DateTimeIPCSender;
+
+ // The DateTimeIPCSender class is a render view observer, so it will take care
+ // of its own deletion.
+ DateTimeIPCSender* sender_;
+
+ base::android::ScopedJavaGlobalRef<jobject> j_date_time_chooser_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateTimeChooserAndroid);
+};
+
+// Native JNI methods
+bool RegisterDateTimeChooserAndroid(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_DATE_TIME_CHOOSER_ANDROID_H_
diff --git a/chromium/content/browser/android/devtools_auth.cc b/chromium/content/browser/android/devtools_auth.cc
new file mode 100644
index 00000000000..c89cdc0cbf1
--- /dev/null
+++ b/chromium/content/browser/android/devtools_auth.cc
@@ -0,0 +1,26 @@
+// 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 "content/public/browser/android/devtools_auth.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+bool CanUserConnectToDevTools(uid_t uid, gid_t gid) {
+ struct passwd* creds = getpwuid(uid);
+ if (!creds || !creds->pw_name) {
+ LOG(WARNING) << "DevTools: can't obtain creds for uid " << uid;
+ return false;
+ }
+ if (gid == uid &&
+ (strcmp("root", creds->pw_name) == 0 || // For rooted devices
+ strcmp("shell", creds->pw_name) == 0)) { // For non-rooted devices
+ return true;
+ }
+ LOG(WARNING) << "DevTools: connection attempt from " << creds->pw_name;
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/download_controller_android_impl.cc b/chromium/content/browser/android/download_controller_android_impl.cc
new file mode 100644
index 00000000000..7d27e64a193
--- /dev/null
+++ b/chromium/content/browser/android/download_controller_android_impl.cc
@@ -0,0 +1,391 @@
+// 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 "content/browser/android/download_controller_android_impl.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/referrer.h"
+#include "jni/DownloadController_jni.h"
+#include "net/cookies/cookie_options.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+// JNI methods
+static void Init(JNIEnv* env, jobject obj) {
+ DownloadControllerAndroidImpl::GetInstance()->Init(env, obj);
+}
+
+struct DownloadControllerAndroidImpl::JavaObject {
+ ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) {
+ return GetRealObject(env, obj);
+ }
+ jweak obj;
+};
+
+// static
+bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+// static
+DownloadControllerAndroid* DownloadControllerAndroid::Get() {
+ return DownloadControllerAndroidImpl::GetInstance();
+}
+
+// static
+DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() {
+ return Singleton<DownloadControllerAndroidImpl>::get();
+}
+
+DownloadControllerAndroidImpl::DownloadControllerAndroidImpl()
+ : java_object_(NULL) {
+}
+
+DownloadControllerAndroidImpl::~DownloadControllerAndroidImpl() {
+ if (java_object_) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ env->DeleteWeakGlobalRef(java_object_->obj);
+ delete java_object_;
+ base::android::CheckException(env);
+ }
+}
+
+// Initialize references to Java object.
+void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) {
+ java_object_ = new JavaObject;
+ java_object_->obj = env->NewWeakGlobalRef(obj);
+}
+
+void DownloadControllerAndroidImpl::CreateGETDownload(
+ int render_process_id, int render_view_id, int request_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GlobalRequestID global_id(render_process_id, request_id);
+
+ // We are yielding the UI thread and render_view_host may go away by
+ // the time we come back. Pass along render_process_id and render_view_id
+ // to retrieve it later (if it still exists).
+ GetDownloadInfoCB cb = base::Bind(
+ &DownloadControllerAndroidImpl::StartAndroidDownload,
+ base::Unretained(this), render_process_id,
+ render_view_id);
+
+ PrepareDownloadInfo(
+ global_id,
+ base::Bind(&DownloadControllerAndroidImpl::StartDownloadOnUIThread,
+ base::Unretained(this), cb));
+}
+
+void DownloadControllerAndroidImpl::PrepareDownloadInfo(
+ const GlobalRequestID& global_id,
+ const GetDownloadInfoCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::URLRequest* request =
+ ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
+ if (!request) {
+ LOG(ERROR) << "Request to download not found.";
+ return;
+ }
+
+ DownloadInfoAndroid info_android(request);
+
+ net::CookieStore* cookie_store = request->context()->cookie_store();
+ if (cookie_store) {
+ net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster();
+ if (cookie_monster) {
+ cookie_monster->GetAllCookiesForURLAsync(
+ request->url(),
+ base::Bind(&DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies,
+ base::Unretained(this), info_android, callback,
+ global_id));
+ } else {
+ DoLoadCookies(info_android, callback, global_id);
+ }
+ } else {
+ // Can't get any cookies, start android download.
+ callback.Run(info_android);
+ }
+}
+
+void DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies(
+ const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback,
+ const GlobalRequestID& global_id, const net::CookieList& cookie_list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::URLRequest* request =
+ ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
+ if (!request) {
+ LOG(ERROR) << "Request to download not found.";
+ return;
+ }
+
+ if (request->context()->network_delegate()->CanGetCookies(
+ *request, cookie_list)) {
+ DoLoadCookies(info, callback, global_id);
+ } else {
+ callback.Run(info);
+ }
+}
+
+void DownloadControllerAndroidImpl::DoLoadCookies(
+ const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback,
+ const GlobalRequestID& global_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::CookieOptions options;
+ options.set_include_httponly();
+
+ net::URLRequest* request =
+ ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
+ if (!request) {
+ LOG(ERROR) << "Request to download not found.";
+ return;
+ }
+
+ request->context()->cookie_store()->GetCookiesWithOptionsAsync(
+ info.url, options,
+ base::Bind(&DownloadControllerAndroidImpl::OnCookieResponse,
+ base::Unretained(this), info, callback));
+}
+
+void DownloadControllerAndroidImpl::OnCookieResponse(
+ DownloadInfoAndroid download_info,
+ const GetDownloadInfoCB& callback,
+ const std::string& cookie) {
+ download_info.cookie = cookie;
+
+ // We have everything we need, start Android download.
+ callback.Run(download_info);
+}
+
+void DownloadControllerAndroidImpl::StartDownloadOnUIThread(
+ const GetDownloadInfoCB& callback,
+ const DownloadInfoAndroid& info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(callback, info));
+}
+
+void DownloadControllerAndroidImpl::StartAndroidDownload(
+ int render_process_id, int render_view_id,
+ const DownloadInfoAndroid& info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ // Call newHttpGetDownload
+ ScopedJavaLocalRef<jobject> view = GetContentView(render_process_id,
+ render_view_id);
+ if (view.is_null()) {
+ // The view went away. Can't proceed.
+ LOG(ERROR) << "Download failed on URL:" << info.url.spec();
+ return;
+ }
+
+ ScopedJavaLocalRef<jstring> jurl =
+ ConvertUTF8ToJavaString(env, info.url.spec());
+ ScopedJavaLocalRef<jstring> juser_agent =
+ ConvertUTF8ToJavaString(env, info.user_agent);
+ ScopedJavaLocalRef<jstring> jcontent_disposition =
+ ConvertUTF8ToJavaString(env, info.content_disposition);
+ ScopedJavaLocalRef<jstring> jmime_type =
+ ConvertUTF8ToJavaString(env, info.original_mime_type);
+ ScopedJavaLocalRef<jstring> jcookie =
+ ConvertUTF8ToJavaString(env, info.cookie);
+ ScopedJavaLocalRef<jstring> jreferer =
+ ConvertUTF8ToJavaString(env, info.referer);
+
+ Java_DownloadController_newHttpGetDownload(
+ env, GetJavaObject()->Controller(env).obj(), view.obj(), jurl.obj(),
+ juser_agent.obj(), jcontent_disposition.obj(), jmime_type.obj(),
+ jcookie.obj(), jreferer.obj(), info.total_bytes);
+}
+
+void DownloadControllerAndroidImpl::OnDownloadStarted(
+ DownloadItem* download_item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!download_item->GetWebContents())
+ return;
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ // Register for updates to the DownloadItem.
+ download_item->AddObserver(this);
+
+ ScopedJavaLocalRef<jobject> view =
+ GetContentViewCoreFromWebContents(download_item->GetWebContents());
+ // The view went away. Can't proceed.
+ if (view.is_null())
+ return;
+
+ ScopedJavaLocalRef<jstring> jmime_type =
+ ConvertUTF8ToJavaString(env, download_item->GetMimeType());
+ ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
+ env, download_item->GetTargetFilePath().BaseName().value());
+ Java_DownloadController_onDownloadStarted(
+ env, GetJavaObject()->Controller(env).obj(), view.obj(), jfilename.obj(),
+ jmime_type.obj());
+}
+
+void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (item->IsDangerous() &&
+ (item->GetState() != DownloadItem::CANCELLED))
+ OnDangerousDownload(item);
+
+ if (item->GetState() != DownloadItem::COMPLETE)
+ return;
+
+ // Multiple OnDownloadUpdated() notifications may be issued while the download
+ // is in the COMPLETE state. Only handle one.
+ item->RemoveObserver(this);
+
+ // Call onDownloadCompleted
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> jurl =
+ ConvertUTF8ToJavaString(env, item->GetURL().spec());
+ ScopedJavaLocalRef<jstring> jmime_type =
+ ConvertUTF8ToJavaString(env, item->GetMimeType());
+ ScopedJavaLocalRef<jstring> jpath =
+ ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value());
+ ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
+ env, item->GetTargetFilePath().BaseName().value());
+
+ Java_DownloadController_onDownloadCompleted(
+ env, GetJavaObject()->Controller(env).obj(),
+ base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(),
+ jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true);
+}
+
+void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem* item) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
+ env, item->GetTargetFilePath().BaseName().value());
+ ScopedJavaLocalRef<jobject> view_core = GetContentViewCoreFromWebContents(
+ item->GetWebContents());
+ if (!view_core.is_null()) {
+ Java_DownloadController_onDangerousDownload(
+ env, GetJavaObject()->Controller(env).obj(), view_core.obj(),
+ jfilename.obj(), item->GetId());
+ }
+}
+
+ScopedJavaLocalRef<jobject> DownloadControllerAndroidImpl::GetContentView(
+ int render_process_id, int render_view_id) {
+ RenderViewHost* render_view_host =
+ RenderViewHost::FromID(render_process_id, render_view_id);
+
+ if (!render_view_host)
+ return ScopedJavaLocalRef<jobject>();
+
+ WebContents* web_contents =
+ render_view_host->GetDelegate()->GetAsWebContents();
+
+ return GetContentViewCoreFromWebContents(web_contents);
+}
+
+ScopedJavaLocalRef<jobject>
+ DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents(
+ WebContents* web_contents) {
+ if (!web_contents)
+ return ScopedJavaLocalRef<jobject>();
+
+ ContentViewCore* view_core = ContentViewCore::FromWebContents(web_contents);
+ return view_core ? view_core->GetJavaObject() :
+ ScopedJavaLocalRef<jobject>();
+}
+
+DownloadControllerAndroidImpl::JavaObject*
+ DownloadControllerAndroidImpl::GetJavaObject() {
+ if (!java_object_) {
+ // Initialize Java DownloadController by calling
+ // DownloadController.getInstance(), which will call Init()
+ // if Java DownloadController is not instantiated already.
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_DownloadController_getInstance(env);
+ }
+
+ DCHECK(java_object_);
+ return java_object_;
+}
+
+void DownloadControllerAndroidImpl::StartContextMenuDownload(
+ const ContextMenuParams& params, WebContents* web_contents, bool is_link) {
+ const GURL& url = is_link ? params.link_url : params.src_url;
+ const GURL& referrer = params.frame_url.is_empty() ?
+ params.page_url : params.frame_url;
+ DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>(
+ BrowserContext::GetDownloadManager(web_contents->GetBrowserContext()));
+ scoped_ptr<DownloadUrlParameters> dl_params(
+ DownloadUrlParameters::FromWebContents(web_contents, url));
+ dl_params->set_referrer(
+ Referrer(referrer, params.referrer_policy));
+ if (is_link)
+ dl_params->set_referrer_encoding(params.frame_charset);
+ else
+ dl_params->set_prefer_cache(true);
+ dl_params->set_prompt(false);
+ dlm->DownloadUrl(dl_params.Pass());
+}
+
+void DownloadControllerAndroidImpl::DangerousDownloadValidated(
+ WebContents* web_contents, int download_id, bool accept) {
+ if (!web_contents)
+ return;
+ DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>(
+ BrowserContext::GetDownloadManager(web_contents->GetBrowserContext()));
+ DownloadItem* item = dlm->GetDownload(download_id);
+ if (!item)
+ return;
+ if (accept)
+ item->ValidateDangerousDownload();
+ else
+ item->Remove();
+}
+
+DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid(
+ net::URLRequest* request) {
+ request->GetResponseHeaderByName("content-disposition", &content_disposition);
+
+ if (request->response_headers())
+ request->response_headers()->GetMimeType(&original_mime_type);
+
+ request->extra_request_headers().GetHeader(
+ net::HttpRequestHeaders::kUserAgent, &user_agent);
+ GURL referer_url(request->referrer());
+ if (referer_url.is_valid())
+ referer = referer_url.spec();
+ if (!request->url_chain().empty()) {
+ original_url = request->url_chain().front();
+ url = request->url_chain().back();
+ }
+}
+
+DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {}
+
+} // namespace content
diff --git a/chromium/content/browser/android/download_controller_android_impl.h b/chromium/content/browser/android/download_controller_android_impl.h
new file mode 100644
index 00000000000..db9b9cf99bb
--- /dev/null
+++ b/chromium/content/browser/android/download_controller_android_impl.h
@@ -0,0 +1,129 @@
+// 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 class pairs with DownloadController on Java side to forward requests
+// for GET downloads to the current DownloadListener. POST downloads are
+// handled on the native side.
+//
+// Both classes are Singleton classes. C++ object owns Java object.
+//
+// Call sequence
+// GET downloads:
+// DownloadControllerAndroid::CreateGETDownload() =>
+// DownloadController.newHttpGetDownload() =>
+// DownloadListener.onDownloadStart() /
+// DownloadListener2.requestHttpGetDownload()
+//
+
+#ifndef CONTENT_BROWSER_ANDROID_DOWNLOAD_CONTROLLER_ANDROID_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_DOWNLOAD_CONTROLLER_ANDROID_IMPL_H_
+
+#include <string>
+
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
+#include "base/memory/singleton.h"
+#include "content/public/browser/android/download_controller_android.h"
+#include "content/public/browser/download_item.h"
+#include "net/cookies/cookie_monster.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+struct GlobalRequestID;
+class RenderViewHost;
+class WebContents;
+
+class DownloadControllerAndroidImpl : public DownloadControllerAndroid,
+ public DownloadItem::Observer {
+ public:
+ static DownloadControllerAndroidImpl* GetInstance();
+
+ static bool RegisterDownloadController(JNIEnv* env);
+
+ // Called when DownloadController Java object is instantiated.
+ void Init(JNIEnv* env, jobject obj);
+ private:
+ // Used to store all the information about an Android download.
+ struct DownloadInfoAndroid {
+ explicit DownloadInfoAndroid(net::URLRequest* request);
+ ~DownloadInfoAndroid();
+
+ // The URL from which we are downloading. This is the final URL after any
+ // redirection by the server for |original_url_|.
+ GURL url;
+ // The original URL before any redirection by the server for this URL.
+ GURL original_url;
+ int64 total_bytes;
+ std::string content_disposition;
+ std::string original_mime_type;
+ std::string user_agent;
+ std::string cookie;
+ std::string referer;
+
+ WebContents* web_contents;
+ // Default copy constructor is used for passing this struct by value.
+ };
+ struct JavaObject;
+ friend struct DefaultSingletonTraits<DownloadControllerAndroidImpl>;
+ DownloadControllerAndroidImpl();
+ virtual ~DownloadControllerAndroidImpl();
+
+ // DownloadControllerAndroid implementation.
+ virtual void CreateGETDownload(int render_process_id, int render_view_id,
+ int request_id) OVERRIDE;
+ virtual void OnDownloadStarted(DownloadItem* download_item) OVERRIDE;
+ virtual void StartContextMenuDownload(
+ const ContextMenuParams& params, WebContents* web_contents,
+ bool is_link) OVERRIDE;
+ virtual void DangerousDownloadValidated(
+ WebContents* web_contents, int download_id, bool accept) OVERRIDE;
+
+ // DownloadItem::Observer interface.
+ virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE;
+
+ typedef base::Callback<void(const DownloadInfoAndroid&)>
+ GetDownloadInfoCB;
+ void PrepareDownloadInfo(const GlobalRequestID& global_id,
+ const GetDownloadInfoCB& callback);
+ void CheckPolicyAndLoadCookies(const DownloadInfoAndroid& info,
+ const GetDownloadInfoCB& callback,
+ const GlobalRequestID& global_id,
+ const net::CookieList& cookie_list);
+ void DoLoadCookies(const DownloadInfoAndroid& info,
+ const GetDownloadInfoCB& callback,
+ const GlobalRequestID& global_id);
+ void OnCookieResponse(DownloadInfoAndroid info,
+ const GetDownloadInfoCB& callback,
+ const std::string& cookie);
+ void StartDownloadOnUIThread(const GetDownloadInfoCB& callback,
+ const DownloadInfoAndroid& info);
+ void StartAndroidDownload(int render_process_id,
+ int render_view_id,
+ const DownloadInfoAndroid& info);
+
+ // The download item contains dangerous file types.
+ void OnDangerousDownload(DownloadItem *item);
+
+ base::android::ScopedJavaLocalRef<jobject> GetContentViewCoreFromWebContents(
+ WebContents* web_contents);
+
+ base::android::ScopedJavaLocalRef<jobject> GetContentView(
+ int render_process_id, int render_view_id);
+
+ // Creates Java object if it is not created already and returns it.
+ JavaObject* GetJavaObject();
+
+ JavaObject* java_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadControllerAndroidImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_DOWNLOAD_CONTROLLER_ANDROID_IMPL_H_
diff --git a/chromium/content/browser/android/edge_effect.cc b/chromium/content/browser/android/edge_effect.cc
new file mode 100644
index 00000000000..d8411bc1f89
--- /dev/null
+++ b/chromium/content/browser/android/edge_effect.cc
@@ -0,0 +1,367 @@
+// Copyright (c) 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 "content/browser/android/edge_effect.h"
+
+#include "cc/layers/layer.h"
+#include "ui/gfx/screen.h"
+
+namespace content {
+
+namespace {
+
+enum State {
+ STATE_IDLE = 0,
+ STATE_PULL,
+ STATE_ABSORB,
+ STATE_RECEDE,
+ STATE_PULL_DECAY
+};
+
+// Time it will take the effect to fully recede in ms
+const int kRecedeTime = 1000;
+
+// Time it will take before a pulled glow begins receding in ms
+const int kPullTime = 167;
+
+// Time it will take in ms for a pulled glow to decay before release
+const int kPullDecayTime = 1000;
+
+const float kMaxAlpha = 1.f;
+const float kHeldEdgeScaleY = .5f;
+
+const float kMaxGlowHeight = 4.f;
+
+const float kPullGlowBegin = 1.f;
+const float kPullEdgeBegin = 0.6f;
+
+// Minimum velocity that will be absorbed
+const float kMinVelocity = 100.f;
+
+const float kEpsilon = 0.001f;
+
+// How much dragging should effect the height of the edge image.
+// Number determined by user testing.
+const int kPullDistanceEdgeFactor = 7;
+
+// How much dragging should effect the height of the glow image.
+// Number determined by user testing.
+const int kPullDistanceGlowFactor = 7;
+const float kPullDistanceAlphaGlowFactor = 1.1f;
+
+const int kVelocityEdgeFactor = 8;
+const int kVelocityGlowFactor = 16;
+
+template <typename T>
+T Lerp(T a, T b, T t) {
+ return a + (b - a) * t;
+}
+
+template <typename T>
+T Clamp(T value, T low, T high) {
+ return value < low ? low : (value > high ? high : value);
+}
+
+template <typename T>
+T Damp(T input, T factor) {
+ T result;
+ if (factor == 1) {
+ result = 1 - (1 - input) * (1 - input);
+ } else {
+ result = 1 - std::pow(1 - input, 2 * factor);
+ }
+ return result;
+}
+
+gfx::Transform ComputeTransform(EdgeEffect::Edge edge,
+ gfx::SizeF size, int height) {
+ switch (edge) {
+ default:
+ case EdgeEffect::EDGE_TOP:
+ return gfx::Transform(1, 0, 0, 1, 0, 0);
+ case EdgeEffect::EDGE_LEFT:
+ return gfx::Transform(0, 1, -1, 0,
+ (-size.width() + height) / 2 ,
+ (size.width() - height) / 2);
+ case EdgeEffect::EDGE_BOTTOM:
+ return gfx::Transform(-1, 0, 0, -1, 0, size.height() - height);
+ case EdgeEffect::EDGE_RIGHT:
+ return gfx::Transform(0, -1, 1, 0,
+ (-size.width() - height) / 2 + size.height(),
+ (size.width() - height) / 2);
+ };
+}
+
+void DisableLayer(cc::Layer* layer) {
+ DCHECK(layer);
+ layer->SetIsDrawable(false);
+ layer->SetTransform(gfx::Transform());
+ layer->SetOpacity(1.f);
+}
+
+void UpdateLayer(cc::Layer* layer,
+ EdgeEffect::Edge edge,
+ gfx::SizeF size,
+ int height,
+ float opacity) {
+ DCHECK(layer);
+ layer->SetIsDrawable(true);
+ layer->SetTransform(ComputeTransform(edge, size, height));
+ layer->SetBounds(gfx::Size(size.width(), height));
+ layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
+}
+
+} // namespace
+
+EdgeEffect::EdgeEffect(scoped_refptr<cc::Layer> edge,
+ scoped_refptr<cc::Layer> glow)
+ : edge_(edge)
+ , glow_(glow)
+ , edge_alpha_(0)
+ , edge_scale_y_(0)
+ , glow_alpha_(0)
+ , glow_scale_y_(0)
+ , edge_alpha_start_(0)
+ , edge_alpha_finish_(0)
+ , edge_scale_y_start_(0)
+ , edge_scale_y_finish_(0)
+ , glow_alpha_start_(0)
+ , glow_alpha_finish_(0)
+ , glow_scale_y_start_(0)
+ , glow_scale_y_finish_(0)
+ , state_(STATE_IDLE)
+ , pull_distance_(0)
+ , dpi_scale_(1) {
+ // Prevent the provided layers from drawing until the effect is activated.
+ DisableLayer(edge_.get());
+ DisableLayer(glow_.get());
+
+ dpi_scale_ =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().device_scale_factor();
+}
+
+EdgeEffect::~EdgeEffect() { }
+
+bool EdgeEffect::IsFinished() const {
+ return state_ == STATE_IDLE;
+}
+
+void EdgeEffect::Finish() {
+ DisableLayer(edge_.get());
+ DisableLayer(glow_.get());
+ pull_distance_ = 0;
+ state_ = STATE_IDLE;
+}
+
+void EdgeEffect::Pull(base::TimeTicks current_time, float delta_distance) {
+ if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) {
+ return;
+ }
+ if (state_ != STATE_PULL) {
+ glow_scale_y_ = kPullGlowBegin;
+ }
+ state_ = STATE_PULL;
+
+ start_time_ = current_time;
+ duration_ = base::TimeDelta::FromMilliseconds(kPullTime);
+
+ delta_distance *= dpi_scale_;
+ float abs_delta_distance = std::abs(delta_distance);
+ pull_distance_ += delta_distance;
+ float distance = std::abs(pull_distance_);
+
+ edge_alpha_ = edge_alpha_start_ = Clamp(distance, kPullEdgeBegin, kMaxAlpha);
+ edge_scale_y_ = edge_scale_y_start_
+ = Clamp(distance * kPullDistanceEdgeFactor, kHeldEdgeScaleY, 1.f);
+
+ glow_alpha_ = glow_alpha_start_ =
+ std::min(kMaxAlpha,
+ glow_alpha_ + abs_delta_distance * kPullDistanceAlphaGlowFactor);
+
+ float glow_change = abs_delta_distance;
+ if (delta_distance > 0 && pull_distance_ < 0)
+ glow_change = -glow_change;
+ if (pull_distance_ == 0)
+ glow_scale_y_ = 0;
+
+ // Do not allow glow to get larger than kMaxGlowHeight.
+ glow_scale_y_ = glow_scale_y_start_ =
+ Clamp(glow_scale_y_ + glow_change * kPullDistanceGlowFactor,
+ 0.f, kMaxGlowHeight);
+
+ edge_alpha_finish_ = edge_alpha_;
+ edge_scale_y_finish_ = edge_scale_y_;
+ glow_alpha_finish_ = glow_alpha_;
+ glow_scale_y_finish_ = glow_scale_y_;
+}
+
+void EdgeEffect::Release(base::TimeTicks current_time) {
+ pull_distance_ = 0;
+
+ if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY)
+ return;
+
+ state_ = STATE_RECEDE;
+ edge_alpha_start_ = edge_alpha_;
+ edge_scale_y_start_ = edge_scale_y_;
+ glow_alpha_start_ = glow_alpha_;
+ glow_scale_y_start_ = glow_scale_y_;
+
+ edge_alpha_finish_ = 0.f;
+ edge_scale_y_finish_ = 0.f;
+ glow_alpha_finish_ = 0.f;
+ glow_scale_y_finish_ = 0.f;
+
+ start_time_ = current_time;
+ duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
+}
+
+void EdgeEffect::Absorb(base::TimeTicks current_time, float velocity) {
+ state_ = STATE_ABSORB;
+ float scaled_velocity =
+ dpi_scale_ * std::max(kMinVelocity, std::abs(velocity));
+
+ start_time_ = current_time;
+ // This should never be less than 1 millisecond.
+ duration_ = base::TimeDelta::FromMilliseconds(0.1f + (velocity * 0.03f));
+
+ // The edge should always be at least partially visible, regardless
+ // of velocity.
+ edge_alpha_start_ = 0.f;
+ edge_scale_y_ = edge_scale_y_start_ = 0.f;
+ // The glow depends more on the velocity, and therefore starts out
+ // nearly invisible.
+ glow_alpha_start_ = 0.5f;
+ glow_scale_y_start_ = 0.f;
+
+ // Factor the velocity by 8. Testing on device shows this works best to
+ // reflect the strength of the user's scrolling.
+ edge_alpha_finish_ = Clamp(scaled_velocity * kVelocityEdgeFactor, 0.f, 1.f);
+ // Edge should never get larger than the size of its asset.
+ edge_scale_y_finish_ = Clamp(scaled_velocity * kVelocityEdgeFactor,
+ kHeldEdgeScaleY, 1.f);
+
+ // Growth for the size of the glow should be quadratic to properly
+ // respond
+ // to a user's scrolling speed. The faster the scrolling speed, the more
+ // intense the effect should be for both the size and the saturation.
+ glow_scale_y_finish_ = std::min(
+ 0.025f + (scaled_velocity * (scaled_velocity / 100) * 0.00015f), 1.75f);
+ // Alpha should change for the glow as well as size.
+ glow_alpha_finish_ = Clamp(glow_alpha_start_,
+ scaled_velocity * kVelocityGlowFactor * .00001f,
+ kMaxAlpha);
+}
+
+bool EdgeEffect::Update(base::TimeTicks current_time) {
+ if (IsFinished())
+ return false;
+
+ const double dt = (current_time - start_time_).InMilliseconds();
+ const double t = std::min(dt / duration_.InMilliseconds(), 1.);
+ const float interp = static_cast<float>(Damp(t, 1.));
+
+ edge_alpha_ = Lerp(edge_alpha_start_, edge_alpha_finish_, interp);
+ edge_scale_y_ = Lerp(edge_scale_y_start_, edge_scale_y_finish_, interp);
+ glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp);
+ glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp);
+
+ if (t >= 1.f - kEpsilon) {
+ switch (state_) {
+ case STATE_ABSORB:
+ state_ = STATE_RECEDE;
+ start_time_ = current_time;
+ duration_ = base::TimeDelta::FromMilliseconds(kRecedeTime);
+
+ edge_alpha_start_ = edge_alpha_;
+ edge_scale_y_start_ = edge_scale_y_;
+ glow_alpha_start_ = glow_alpha_;
+ glow_scale_y_start_ = glow_scale_y_;
+
+ // After absorb, the glow and edge should fade to nothing.
+ edge_alpha_finish_ = 0.f;
+ edge_scale_y_finish_ = 0.f;
+ glow_alpha_finish_ = 0.f;
+ glow_scale_y_finish_ = 0.f;
+ break;
+ case STATE_PULL:
+ state_ = STATE_PULL_DECAY;
+ start_time_ = current_time;
+ duration_ = base::TimeDelta::FromMilliseconds(kPullDecayTime);
+
+ edge_alpha_start_ = edge_alpha_;
+ edge_scale_y_start_ = edge_scale_y_;
+ glow_alpha_start_ = glow_alpha_;
+ glow_scale_y_start_ = glow_scale_y_;
+
+ // After pull, the glow and edge should fade to nothing.
+ edge_alpha_finish_ = 0.f;
+ edge_scale_y_finish_ = 0.f;
+ glow_alpha_finish_ = 0.f;
+ glow_scale_y_finish_ = 0.f;
+ break;
+ case STATE_PULL_DECAY:
+ {
+ // When receding, we want edge to decrease more slowly
+ // than the glow.
+ float factor = glow_scale_y_finish_ != 0 ?
+ 1 / (glow_scale_y_finish_ * glow_scale_y_finish_) :
+ std::numeric_limits<float>::max();
+ edge_scale_y_ = edge_scale_y_start_ +
+ (edge_scale_y_finish_ - edge_scale_y_start_) * interp * factor;
+ state_ = STATE_RECEDE;
+ }
+ break;
+ case STATE_RECEDE:
+ Finish();
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (state_ == STATE_RECEDE && glow_scale_y_ <= 0 && edge_scale_y_ <= 0)
+ Finish();
+
+ return !IsFinished();
+}
+
+void EdgeEffect::ApplyToLayers(gfx::SizeF size, Edge edge) {
+ if (IsFinished())
+ return;
+
+ // An empty effect size, while meaningless, is also relatively harmless, and
+ // will simply prevent any drawing of the layers.
+ if (size.IsEmpty()) {
+ DisableLayer(edge_.get());
+ DisableLayer(glow_.get());
+ return;
+ }
+
+ float dummy_scale_x, dummy_scale_y;
+
+ // Glow
+ gfx::Size glow_image_bounds;
+ glow_->CalculateContentsScale(1.f, 1.f, 1.f, false,
+ &dummy_scale_x, &dummy_scale_y,
+ &glow_image_bounds);
+ const int glow_height = glow_image_bounds.height();
+ const int glow_width = glow_image_bounds.width();
+ const int glow_bottom = static_cast<int>(std::min(
+ glow_height * glow_scale_y_ * glow_height / glow_width * 0.6f,
+ glow_height * kMaxGlowHeight) * dpi_scale_ + 0.5f);
+ UpdateLayer(glow_.get(), edge, size, glow_bottom, glow_alpha_);
+
+ // Edge
+ gfx::Size edge_image_bounds;
+ edge_->CalculateContentsScale(1.f, 1.f, 1.f, false,
+ &dummy_scale_x, &dummy_scale_y,
+ &edge_image_bounds);
+ const int edge_height = edge_image_bounds.height();
+ const int edge_bottom = static_cast<int>(
+ edge_height * edge_scale_y_ * dpi_scale_);
+ UpdateLayer(edge_.get(), edge, size, edge_bottom, edge_alpha_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/edge_effect.h b/chromium/content/browser/android/edge_effect.h
new file mode 100644
index 00000000000..f47a9b1a0e9
--- /dev/null
+++ b/chromium/content/browser/android/edge_effect.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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 CONTENT_BROWSER_ANDROID_EDGE_EFFECT_H_
+#define CONTENT_BROWSER_ANDROID_EDGE_EFFECT_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "ui/gfx/size_f.h"
+
+namespace cc {
+class Layer;
+}
+
+namespace content {
+
+/* |EdgeEffect| mirrors its Android counterpart, EdgeEffect.java.
+ * The primary difference is ownership; the Android version manages render
+ * resources directly, while this version simply applies the effect to
+ * existing resources. Conscious tradeoffs were made to align this as closely
+ * as possible with the original Android java version.
+ */
+class EdgeEffect {
+public:
+ enum Edge {
+ EDGE_TOP = 0,
+ EDGE_LEFT,
+ EDGE_BOTTOM,
+ EDGE_RIGHT,
+ EDGE_COUNT
+ };
+
+ EdgeEffect(scoped_refptr<cc::Layer> edge, scoped_refptr<cc::Layer> glow);
+ ~EdgeEffect();
+
+ void Pull(base::TimeTicks current_time, float delta_distance);
+ void Absorb(base::TimeTicks current_time, float velocity);
+ bool Update(base::TimeTicks current_time);
+ void Release(base::TimeTicks current_time);
+
+ void Finish();
+ bool IsFinished() const;
+
+ void ApplyToLayers(gfx::SizeF size, Edge edge);
+
+private:
+
+ enum State {
+ STATE_IDLE = 0,
+ STATE_PULL,
+ STATE_ABSORB,
+ STATE_RECEDE,
+ STATE_PULL_DECAY
+ };
+
+ scoped_refptr<cc::Layer> edge_;
+ scoped_refptr<cc::Layer> glow_;
+
+ float edge_alpha_;
+ float edge_scale_y_;
+ float glow_alpha_;
+ float glow_scale_y_;
+
+ float edge_alpha_start_;
+ float edge_alpha_finish_;
+ float edge_scale_y_start_;
+ float edge_scale_y_finish_;
+ float glow_alpha_start_;
+ float glow_alpha_finish_;
+ float glow_scale_y_start_;
+ float glow_scale_y_finish_;
+
+ base::TimeTicks start_time_;
+ base::TimeDelta duration_;
+
+ State state_;
+
+ float pull_distance_;
+
+ float dpi_scale_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeEffect);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_EDGE_EFFECT_H_
diff --git a/chromium/content/browser/android/in_process/DEPS b/chromium/content/browser/android/in_process/DEPS
new file mode 100644
index 00000000000..a8ba3f4cb57
--- /dev/null
+++ b/chromium/content/browser/android/in_process/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ # Required for SynchronousCompositor (in --single-process mode only).
+ "+content/public/renderer/android",
+ "+content/renderer",
+ # Include joth@chromium.org on the review for any additions to this file.
+]
diff --git a/chromium/content/browser/android/in_process/synchronous_compositor_impl.cc b/chromium/content/browser/android/in_process/synchronous_compositor_impl.cc
new file mode 100644
index 00000000000..678f622875b
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_compositor_impl.cc
@@ -0,0 +1,296 @@
+// 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 "content/browser/android/in_process/synchronous_compositor_impl.h"
+
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "cc/input/input_handler.h"
+#include "cc/input/layer_scroll_offset_delegate.h"
+#include "content/browser/android/in_process/synchronous_input_event_filter.h"
+#include "content/browser/renderer_host/render_widget_host_view_android.h"
+#include "content/public/browser/android/synchronous_compositor_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/renderer/android/synchronous_compositor_factory.h"
+#include "ui/gl/gl_surface.h"
+#include "webkit/common/gpu/context_provider_in_process.h"
+
+namespace content {
+
+namespace {
+
+int GetInProcessRendererId() {
+ content::RenderProcessHost::iterator it =
+ content::RenderProcessHost::AllHostsIterator();
+ if (it.IsAtEnd()) {
+ // There should always be one RPH in single process mode.
+ NOTREACHED();
+ return 0;
+ }
+
+ int id = it.GetCurrentValue()->GetID();
+ it.Advance();
+ DCHECK(it.IsAtEnd()); // Not multiprocess compatible.
+ return id;
+}
+
+class SynchronousCompositorFactoryImpl : public SynchronousCompositorFactory {
+ public:
+ SynchronousCompositorFactoryImpl() {
+ SynchronousCompositorFactory::SetInstance(this);
+ }
+
+ // SynchronousCompositorFactory
+ virtual scoped_refptr<base::MessageLoopProxy>
+ GetCompositorMessageLoop() OVERRIDE {
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
+ }
+
+ virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface(
+ int routing_id) OVERRIDE {
+ scoped_ptr<SynchronousCompositorOutputSurface> output_surface(
+ new SynchronousCompositorOutputSurface(routing_id));
+ return output_surface.PassAs<cc::OutputSurface>();
+ }
+
+ virtual InputHandlerManagerClient* GetInputHandlerManagerClient() OVERRIDE {
+ return synchronous_input_event_filter();
+ }
+
+ SynchronousInputEventFilter* synchronous_input_event_filter() {
+ return &synchronous_input_event_filter_;
+ }
+
+ virtual scoped_refptr<cc::ContextProvider>
+ GetOffscreenContextProviderForMainThread() OVERRIDE {
+ if (!offscreen_context_for_main_thread_.get() ||
+ offscreen_context_for_main_thread_->DestroyedOnMainThread()) {
+ offscreen_context_for_main_thread_ =
+ webkit::gpu::ContextProviderInProcess::CreateOffscreen();
+ if (offscreen_context_for_main_thread_.get() &&
+ !offscreen_context_for_main_thread_->BindToCurrentThread())
+ offscreen_context_for_main_thread_ = NULL;
+ }
+ return offscreen_context_for_main_thread_;
+ }
+
+ // This is called on both renderer main thread (offscreen context creation
+ // path shared between cross-process and in-process platforms) and renderer
+ // compositor impl thread (InitializeHwDraw) in order to support Android
+ // WebView synchronously enable and disable hardware mode multiple times in
+ // the same task. This is ok because in-process WGC3D creation may happen on
+ // any thread and is lightweight.
+ virtual scoped_refptr<cc::ContextProvider>
+ GetOffscreenContextProviderForCompositorThread() OVERRIDE {
+ base::AutoLock lock(offscreen_context_for_compositor_thread_creation_lock_);
+ if (!offscreen_context_for_compositor_thread_.get() ||
+ offscreen_context_for_compositor_thread_->DestroyedOnMainThread()) {
+ offscreen_context_for_compositor_thread_ =
+ webkit::gpu::ContextProviderInProcess::CreateOffscreen();
+ }
+ return offscreen_context_for_compositor_thread_;
+ }
+
+ private:
+ SynchronousInputEventFilter synchronous_input_event_filter_;
+
+ // Only guards construction of |offscreen_context_for_compositor_thread_|,
+ // not usage.
+ base::Lock offscreen_context_for_compositor_thread_creation_lock_;
+ scoped_refptr<cc::ContextProvider> offscreen_context_for_main_thread_;
+ scoped_refptr<cc::ContextProvider> offscreen_context_for_compositor_thread_;
+};
+
+base::LazyInstance<SynchronousCompositorFactoryImpl>::Leaky g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(SynchronousCompositorImpl);
+
+// static
+SynchronousCompositorImpl* SynchronousCompositorImpl::FromID(int process_id,
+ int routing_id) {
+ if (g_factory == NULL)
+ return NULL;
+ RenderViewHost* rvh = RenderViewHost::FromID(process_id, routing_id);
+ if (!rvh)
+ return NULL;
+ WebContents* contents = WebContents::FromRenderViewHost(rvh);
+ if (!contents)
+ return NULL;
+ return FromWebContents(contents);
+}
+
+SynchronousCompositorImpl* SynchronousCompositorImpl::FromRoutingID(
+ int routing_id) {
+ return FromID(GetInProcessRendererId(), routing_id);
+}
+
+SynchronousCompositorImpl::SynchronousCompositorImpl(WebContents* contents)
+ : compositor_client_(NULL),
+ output_surface_(NULL),
+ contents_(contents),
+ input_handler_(NULL) {
+ DCHECK(contents);
+}
+
+SynchronousCompositorImpl::~SynchronousCompositorImpl() {
+ if (compositor_client_)
+ compositor_client_->DidDestroyCompositor(this);
+ SetInputHandler(NULL);
+}
+
+void SynchronousCompositorImpl::SetClient(
+ SynchronousCompositorClient* compositor_client) {
+ DCHECK(CalledOnValidThread());
+ compositor_client_ = compositor_client;
+}
+
+bool SynchronousCompositorImpl::InitializeHwDraw(
+ scoped_refptr<gfx::GLSurface> surface) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(output_surface_);
+ return output_surface_->InitializeHwDraw(
+ surface,
+ g_factory.Get().GetOffscreenContextProviderForCompositorThread());
+}
+
+void SynchronousCompositorImpl::ReleaseHwDraw() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(output_surface_);
+ return output_surface_->ReleaseHwDraw();
+}
+
+bool SynchronousCompositorImpl::DemandDrawHw(
+ gfx::Size view_size,
+ const gfx::Transform& transform,
+ gfx::Rect damage_area,
+ bool stencil_enabled) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(output_surface_);
+
+ return output_surface_->DemandDrawHw(
+ view_size, transform, damage_area, stencil_enabled);
+}
+
+bool SynchronousCompositorImpl::DemandDrawSw(SkCanvas* canvas) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(output_surface_);
+
+ return output_surface_->DemandDrawSw(canvas);
+}
+
+void SynchronousCompositorImpl::DidChangeRootLayerScrollOffset() {
+ if (input_handler_)
+ input_handler_->OnRootLayerDelegatedScrollOffsetChanged();
+}
+
+void SynchronousCompositorImpl::DidBindOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) {
+ DCHECK(CalledOnValidThread());
+ output_surface_ = output_surface;
+ if (compositor_client_)
+ compositor_client_->DidInitializeCompositor(this);
+}
+
+void SynchronousCompositorImpl::DidDestroySynchronousOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) {
+ DCHECK(CalledOnValidThread());
+
+ // Allow for transient hand-over when two output surfaces may refer to
+ // a single delegate.
+ if (output_surface_ == output_surface) {
+ output_surface_ = NULL;
+ if (compositor_client_)
+ compositor_client_->DidDestroyCompositor(this);
+ compositor_client_ = NULL;
+ }
+}
+
+void SynchronousCompositorImpl::SetInputHandler(
+ cc::InputHandler* input_handler) {
+ DCHECK(CalledOnValidThread());
+
+ if (input_handler_)
+ input_handler_->SetRootLayerScrollOffsetDelegate(NULL);
+
+ input_handler_ = input_handler;
+
+ if (input_handler_)
+ input_handler_->SetRootLayerScrollOffsetDelegate(this);
+}
+
+void SynchronousCompositorImpl::DidOverscroll(
+ const cc::DidOverscrollParams& params) {
+ if (compositor_client_) {
+ compositor_client_->DidOverscroll(params.latest_overscroll_delta,
+ params.current_fling_velocity);
+ }
+}
+
+void SynchronousCompositorImpl::SetContinuousInvalidate(bool enable) {
+ DCHECK(CalledOnValidThread());
+ if (compositor_client_)
+ compositor_client_->SetContinuousInvalidate(enable);
+}
+
+InputEventAckState SynchronousCompositorImpl::HandleInputEvent(
+ const WebKit::WebInputEvent& input_event) {
+ DCHECK(CalledOnValidThread());
+ return g_factory.Get().synchronous_input_event_filter()->HandleInputEvent(
+ contents_->GetRoutingID(), input_event);
+}
+
+void SynchronousCompositorImpl::UpdateFrameMetaData(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ RenderWidgetHostViewAndroid* rwhv = static_cast<RenderWidgetHostViewAndroid*>(
+ contents_->GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->SynchronousFrameMetadata(frame_metadata);
+}
+
+void SynchronousCompositorImpl::DidActivatePendingTree() {
+ if (compositor_client_)
+ compositor_client_->DidUpdateContent();
+}
+
+void SynchronousCompositorImpl::SetTotalScrollOffset(gfx::Vector2dF new_value) {
+ DCHECK(CalledOnValidThread());
+ if (compositor_client_)
+ compositor_client_->SetTotalRootLayerScrollOffset(new_value);
+}
+
+gfx::Vector2dF SynchronousCompositorImpl::GetTotalScrollOffset() {
+ DCHECK(CalledOnValidThread());
+ if (compositor_client_)
+ return compositor_client_->GetTotalRootLayerScrollOffset();
+ return gfx::Vector2dF();
+}
+
+// Not using base::NonThreadSafe as we want to enforce a more exacting threading
+// requirement: SynchronousCompositorImpl() must only be used on the UI thread.
+bool SynchronousCompositorImpl::CalledOnValidThread() const {
+ return BrowserThread::CurrentlyOn(BrowserThread::UI);
+}
+
+// static
+void SynchronousCompositor::SetClientForWebContents(
+ WebContents* contents,
+ SynchronousCompositorClient* client) {
+ DCHECK(contents);
+ if (client) {
+ g_factory.Get(); // Ensure it's initialized.
+ SynchronousCompositorImpl::CreateForWebContents(contents);
+ }
+ if (SynchronousCompositorImpl* instance =
+ SynchronousCompositorImpl::FromWebContents(contents)) {
+ instance->SetClient(client);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/in_process/synchronous_compositor_impl.h b/chromium/content/browser/android/in_process/synchronous_compositor_impl.h
new file mode 100644
index 00000000000..f0ea2c6e824
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_compositor_impl.h
@@ -0,0 +1,98 @@
+// 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 CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/input/layer_scroll_offset_delegate.h"
+#include "content/browser/android/in_process/synchronous_compositor_output_surface.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/android/synchronous_compositor.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace cc {
+class InputHandler;
+struct DidOverscrollParams;
+}
+
+namespace WebKit {
+class WebInputEvent;
+}
+
+namespace content {
+class InputHandlerManager;
+
+// The purpose of this class is to act as the intermediary between the various
+// components that make up the 'synchronous compositor mode' implementation and
+// expose their functionality via the SynchronousCompositor interface.
+// This class is created on the main thread but most of the APIs are called
+// from the Compositor thread.
+class SynchronousCompositorImpl
+ : public cc::LayerScrollOffsetDelegate,
+ public SynchronousCompositor,
+ public SynchronousCompositorOutputSurfaceDelegate,
+ public WebContentsUserData<SynchronousCompositorImpl> {
+ public:
+ // When used from browser code, use both |process_id| and |routing_id|.
+ static SynchronousCompositorImpl* FromID(int process_id, int routing_id);
+ // When handling upcalls from renderer code, use this version; the process id
+ // is implicitly that of the in-process renderer.
+ static SynchronousCompositorImpl* FromRoutingID(int routing_id);
+
+ InputEventAckState HandleInputEvent(const WebKit::WebInputEvent& input_event);
+
+ // SynchronousCompositor
+ virtual void SetClient(SynchronousCompositorClient* compositor_client)
+ OVERRIDE;
+ virtual bool InitializeHwDraw(
+ scoped_refptr<gfx::GLSurface> surface) OVERRIDE;
+ virtual void ReleaseHwDraw() OVERRIDE;
+ virtual bool DemandDrawHw(
+ gfx::Size view_size,
+ const gfx::Transform& transform,
+ gfx::Rect clip,
+ bool stencil_enabled) OVERRIDE;
+ virtual bool DemandDrawSw(SkCanvas* canvas) OVERRIDE;
+ virtual void DidChangeRootLayerScrollOffset() OVERRIDE;
+
+ // SynchronousCompositorOutputSurfaceDelegate
+ virtual void DidBindOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) OVERRIDE;
+ virtual void DidDestroySynchronousOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) OVERRIDE;
+ virtual void SetContinuousInvalidate(bool enable) OVERRIDE;
+ virtual void UpdateFrameMetaData(
+ const cc::CompositorFrameMetadata& frame_info) OVERRIDE;
+ virtual void DidActivatePendingTree() OVERRIDE;
+
+ // LayerScrollOffsetDelegate
+ virtual void SetTotalScrollOffset(gfx::Vector2dF new_value) OVERRIDE;
+ virtual gfx::Vector2dF GetTotalScrollOffset() OVERRIDE;
+
+ void SetInputHandler(cc::InputHandler* input_handler);
+ void DidOverscroll(const cc::DidOverscrollParams& params);
+
+ private:
+ explicit SynchronousCompositorImpl(WebContents* contents);
+ virtual ~SynchronousCompositorImpl();
+ friend class WebContentsUserData<SynchronousCompositorImpl>;
+
+ void DidCreateSynchronousOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface);
+ bool CalledOnValidThread() const;
+
+ SynchronousCompositorClient* compositor_client_;
+ SynchronousCompositorOutputSurface* output_surface_;
+ WebContents* contents_;
+ cc::InputHandler* input_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(SynchronousCompositorImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_IMPL_H_
diff --git a/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.cc b/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.cc
new file mode 100644
index 00000000000..a6115e98994
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.cc
@@ -0,0 +1,287 @@
+// 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 "content/browser/android/in_process/synchronous_compositor_output_surface.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/managed_memory_policy.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/output/software_output_device.h"
+#include "content/browser/android/in_process/synchronous_compositor_impl.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "gpu/command_buffer/client/gl_in_process_context.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform.h"
+#include "ui/gl/gl_surface.h"
+#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h"
+
+
+namespace content {
+
+namespace {
+
+scoped_ptr<WebKit::WebGraphicsContext3D> CreateWebGraphicsContext3D(
+ scoped_refptr<gfx::GLSurface> surface) {
+ using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl;
+ if (!gfx::GLSurface::InitializeOneOff())
+ return scoped_ptr<WebKit::WebGraphicsContext3D>();
+
+ const char* allowed_extensions = "*";
+ const gfx::GpuPreference gpu_preference = gfx::PreferDiscreteGpu;
+
+ WebKit::WebGraphicsContext3D::Attributes attributes;
+ attributes.antialias = false;
+ attributes.shareResources = true;
+ attributes.noAutomaticFlushes = true;
+
+ gpu::GLInProcessContextAttribs in_process_attribs;
+ WebGraphicsContext3DInProcessCommandBufferImpl::ConvertAttributes(
+ attributes, &in_process_attribs);
+ scoped_ptr<gpu::GLInProcessContext> context(
+ gpu::GLInProcessContext::CreateWithSurface(surface,
+ attributes.shareResources,
+ allowed_extensions,
+ in_process_attribs,
+ gpu_preference));
+
+ if (!context.get())
+ return scoped_ptr<WebKit::WebGraphicsContext3D>();
+
+ return scoped_ptr<WebKit::WebGraphicsContext3D>(
+ WebGraphicsContext3DInProcessCommandBufferImpl::WrapContext(
+ context.Pass(), attributes));
+}
+
+void DidActivatePendingTree(int routing_id) {
+ SynchronousCompositorOutputSurfaceDelegate* delegate =
+ SynchronousCompositorImpl::FromRoutingID(routing_id);
+ if (delegate)
+ delegate->DidActivatePendingTree();
+}
+
+} // namespace
+
+class SynchronousCompositorOutputSurface::SoftwareDevice
+ : public cc::SoftwareOutputDevice {
+ public:
+ SoftwareDevice(SynchronousCompositorOutputSurface* surface)
+ : surface_(surface),
+ null_device_(SkBitmap::kARGB_8888_Config, 1, 1),
+ null_canvas_(&null_device_) {
+ }
+ virtual void Resize(gfx::Size size) OVERRIDE {
+ // Intentional no-op: canvas size is controlled by the embedder.
+ }
+ virtual SkCanvas* BeginPaint(gfx::Rect damage_rect) OVERRIDE {
+ if (!surface_->current_sw_canvas_) {
+ NOTREACHED() << "BeginPaint with no canvas set";
+ return &null_canvas_;
+ }
+ LOG_IF(WARNING, surface_->did_swap_buffer_)
+ << "Mutliple calls to BeginPaint per frame";
+ return surface_->current_sw_canvas_;
+ }
+ virtual void EndPaint(cc::SoftwareFrameData* frame_data) OVERRIDE {
+ }
+ virtual void CopyToBitmap(gfx::Rect rect, SkBitmap* output) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ private:
+ SynchronousCompositorOutputSurface* surface_;
+ SkDevice null_device_;
+ SkCanvas null_canvas_;
+
+ DISALLOW_COPY_AND_ASSIGN(SoftwareDevice);
+};
+
+SynchronousCompositorOutputSurface::SynchronousCompositorOutputSurface(
+ int routing_id)
+ : cc::OutputSurface(
+ scoped_ptr<cc::SoftwareOutputDevice>(new SoftwareDevice(this))),
+ routing_id_(routing_id),
+ needs_begin_frame_(false),
+ invoking_composite_(false),
+ did_swap_buffer_(false),
+ current_sw_canvas_(NULL) {
+ capabilities_.deferred_gl_initialization = true;
+ capabilities_.draw_and_swap_full_viewport_every_frame = true;
+ capabilities_.adjust_deadline_for_parent = false;
+ // Cannot call out to GetDelegate() here as the output surface is not
+ // constructed on the correct thread.
+}
+
+SynchronousCompositorOutputSurface::~SynchronousCompositorOutputSurface() {
+ DCHECK(CalledOnValidThread());
+ SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate();
+ if (delegate)
+ delegate->DidDestroySynchronousOutputSurface(this);
+}
+
+bool SynchronousCompositorOutputSurface::ForcedDrawToSoftwareDevice() const {
+ // |current_sw_canvas_| indicates we're in a DemandDrawSw call. In addition
+ // |invoking_composite_| == false indicates an attempt to draw outside of
+ // the synchronous compositor's control: force it into SW path and hence to
+ // the null canvas (and will log a warning there).
+ return current_sw_canvas_ != NULL || !invoking_composite_;
+}
+
+bool SynchronousCompositorOutputSurface::BindToClient(
+ cc::OutputSurfaceClient* surface_client) {
+ DCHECK(CalledOnValidThread());
+ if (!cc::OutputSurface::BindToClient(surface_client))
+ return false;
+ surface_client->SetTreeActivationCallback(
+ base::Bind(&DidActivatePendingTree, routing_id_));
+ SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate();
+ if (delegate)
+ delegate->DidBindOutputSurface(this);
+
+ const int bytes_limit = 64 * 1024 * 1024;
+ const int num_resources_limit = 100;
+ surface_client->SetMemoryPolicy(
+ cc::ManagedMemoryPolicy(bytes_limit,
+ cc::ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 0,
+ cc::ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING,
+ num_resources_limit));
+
+ return true;
+}
+
+void SynchronousCompositorOutputSurface::Reshape(
+ gfx::Size size, float scale_factor) {
+ // Intentional no-op: surface size is controlled by the embedder.
+}
+
+void SynchronousCompositorOutputSurface::SetNeedsBeginFrame(
+ bool enable) {
+ DCHECK(CalledOnValidThread());
+ cc::OutputSurface::SetNeedsBeginFrame(enable);
+ needs_begin_frame_ = enable;
+ SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate();
+ if (delegate)
+ delegate->SetContinuousInvalidate(needs_begin_frame_);
+}
+
+void SynchronousCompositorOutputSurface::SwapBuffers(
+ cc::CompositorFrame* frame) {
+ if (!ForcedDrawToSoftwareDevice()) {
+ DCHECK(context3d());
+ context3d()->shallowFlushCHROMIUM();
+ }
+ SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate();
+ if (delegate)
+ delegate->UpdateFrameMetaData(frame->metadata);
+
+ did_swap_buffer_ = true;
+ DidSwapBuffers();
+}
+
+namespace {
+void AdjustTransformForClip(gfx::Transform* transform, gfx::Rect clip) {
+ // The system-provided transform translates us from the screen origin to the
+ // origin of the clip rect, but CC's draw origin starts at the clip.
+ transform->matrix().postTranslate(-clip.x(), -clip.y(), 0);
+}
+} // namespace
+
+bool SynchronousCompositorOutputSurface::InitializeHwDraw(
+ scoped_refptr<gfx::GLSurface> surface,
+ scoped_refptr<cc::ContextProvider> offscreen_context) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(HasClient());
+ DCHECK(!context3d_);
+ DCHECK(surface);
+
+ return InitializeAndSetContext3D(
+ CreateWebGraphicsContext3D(surface).Pass(), offscreen_context);
+}
+
+void SynchronousCompositorOutputSurface::ReleaseHwDraw() {
+ cc::OutputSurface::ReleaseGL();
+}
+
+bool SynchronousCompositorOutputSurface::DemandDrawHw(
+ gfx::Size surface_size,
+ const gfx::Transform& transform,
+ gfx::Rect clip,
+ bool stencil_enabled) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(HasClient());
+ DCHECK(context3d());
+
+ gfx::Transform adjusted_transform = transform;
+ AdjustTransformForClip(&adjusted_transform, clip);
+ surface_size_ = surface_size;
+ SetExternalDrawConstraints(adjusted_transform, clip);
+ SetExternalStencilTest(stencil_enabled);
+ InvokeComposite(clip.size());
+
+ return did_swap_buffer_;
+}
+
+bool SynchronousCompositorOutputSurface::DemandDrawSw(SkCanvas* canvas) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(canvas);
+ DCHECK(!current_sw_canvas_);
+ base::AutoReset<SkCanvas*> canvas_resetter(&current_sw_canvas_, canvas);
+
+ SkIRect canvas_clip;
+ canvas->getClipDeviceBounds(&canvas_clip);
+ gfx::Rect clip = gfx::SkIRectToRect(canvas_clip);
+
+ gfx::Transform transform(gfx::Transform::kSkipInitialization);
+ transform.matrix() = canvas->getTotalMatrix(); // Converts 3x3 matrix to 4x4.
+ AdjustTransformForClip(&transform, clip);
+
+ surface_size_ = gfx::Size(canvas->getDeviceSize().width(),
+ canvas->getDeviceSize().height());
+ SetExternalDrawConstraints(transform, clip);
+ SetExternalStencilTest(false);
+
+ InvokeComposite(clip.size());
+
+ return did_swap_buffer_;
+}
+
+void SynchronousCompositorOutputSurface::InvokeComposite(
+ gfx::Size damage_size) {
+ DCHECK(!invoking_composite_);
+ base::AutoReset<bool> invoking_composite_resetter(&invoking_composite_, true);
+ did_swap_buffer_ = false;
+ SetNeedsRedrawRect(gfx::Rect(damage_size));
+ if (needs_begin_frame_)
+ BeginFrame(cc::BeginFrameArgs::CreateForSynchronousCompositor());
+
+ if (did_swap_buffer_)
+ OnSwapBuffersComplete(NULL);
+}
+
+void SynchronousCompositorOutputSurface::PostCheckForRetroactiveBeginFrame() {
+ // Synchronous compositor cannot perform retroactive begin frames, so
+ // intentionally no-op here.
+}
+
+// Not using base::NonThreadSafe as we want to enforce a more exacting threading
+// requirement: SynchronousCompositorOutputSurface() must only be used on the UI
+// thread.
+bool SynchronousCompositorOutputSurface::CalledOnValidThread() const {
+ return BrowserThread::CurrentlyOn(BrowserThread::UI);
+}
+
+SynchronousCompositorOutputSurfaceDelegate*
+SynchronousCompositorOutputSurface::GetDelegate() {
+ return SynchronousCompositorImpl::FromRoutingID(routing_id_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.h b/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.h
new file mode 100644
index 00000000000..3ab149818ae
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_compositor_output_surface.h
@@ -0,0 +1,98 @@
+// 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 CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_OUTPUT_SURFACE_H_
+#define CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_OUTPUT_SURFACE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/output/output_surface.h"
+#include "content/public/browser/android/synchronous_compositor.h"
+
+namespace cc {
+class ContextProvider;
+class CompositorFrameMetadata;
+}
+
+namespace content {
+
+class SynchronousCompositorClient;
+class SynchronousCompositorOutputSurface;
+class WebGraphicsContext3DCommandBufferImpl;
+
+class SynchronousCompositorOutputSurfaceDelegate {
+ public:
+ virtual void DidBindOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) = 0;
+ virtual void DidDestroySynchronousOutputSurface(
+ SynchronousCompositorOutputSurface* output_surface) = 0;
+ virtual void SetContinuousInvalidate(bool enable) = 0;
+ virtual void UpdateFrameMetaData(
+ const cc::CompositorFrameMetadata& frame_metadata) = 0;
+ virtual void DidActivatePendingTree() = 0;
+
+ protected:
+ SynchronousCompositorOutputSurfaceDelegate() {}
+ virtual ~SynchronousCompositorOutputSurfaceDelegate() {}
+};
+
+// Specialization of the output surface that adapts it to implement the
+// content::SynchronousCompositor public API. This class effects an "inversion
+// of control" - enabling drawing to be orchestrated by the embedding
+// layer, instead of driven by the compositor internals - hence it holds two
+// 'client' pointers (|client_| in the OutputSurface baseclass and
+// GetDelegate()) which represent the consumers of the two roles in plays.
+// This class can be created only on the main thread, but then becomes pinned
+// to a fixed thread when BindToClient is called.
+class SynchronousCompositorOutputSurface
+ : NON_EXPORTED_BASE(public cc::OutputSurface) {
+ public:
+ explicit SynchronousCompositorOutputSurface(int routing_id);
+ virtual ~SynchronousCompositorOutputSurface();
+
+ // OutputSurface.
+ virtual bool ForcedDrawToSoftwareDevice() const OVERRIDE;
+ virtual bool BindToClient(cc::OutputSurfaceClient* surface_client) OVERRIDE;
+ virtual void Reshape(gfx::Size size, float scale_factor) OVERRIDE;
+ virtual void SetNeedsBeginFrame(bool enable) OVERRIDE;
+ virtual void SwapBuffers(cc::CompositorFrame* frame) OVERRIDE;
+
+ // Partial SynchronousCompositor API implementation.
+ bool InitializeHwDraw(
+ scoped_refptr<gfx::GLSurface> surface,
+ scoped_refptr<cc::ContextProvider> offscreen_context);
+ void ReleaseHwDraw();
+ bool DemandDrawHw(gfx::Size surface_size,
+ const gfx::Transform& transform,
+ gfx::Rect clip,
+ bool stencil_enabled);
+ bool DemandDrawSw(SkCanvas* canvas);
+
+ private:
+ class SoftwareDevice;
+ friend class SoftwareDevice;
+
+ // Private OutputSurface overrides.
+ virtual void PostCheckForRetroactiveBeginFrame() OVERRIDE;
+
+ void InvokeComposite(gfx::Size damage_size);
+ bool CalledOnValidThread() const;
+ SynchronousCompositorOutputSurfaceDelegate* GetDelegate();
+
+ int routing_id_;
+ bool needs_begin_frame_;
+ bool invoking_composite_;
+ bool did_swap_buffer_;
+
+ // Only valid (non-NULL) during a DemandDrawSw() call.
+ SkCanvas* current_sw_canvas_;
+
+ DISALLOW_COPY_AND_ASSIGN(SynchronousCompositorOutputSurface);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_COMPOSITOR_OUTPUT_SURFACE_H_
diff --git a/chromium/content/browser/android/in_process/synchronous_input_event_filter.cc b/chromium/content/browser/android/in_process/synchronous_input_event_filter.cc
new file mode 100644
index 00000000000..17c909d42d9
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_input_event_filter.cc
@@ -0,0 +1,79 @@
+// 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 "content/browser/android/in_process/synchronous_input_event_filter.h"
+
+#include "base/callback.h"
+#include "cc/input/input_handler.h"
+#include "content/browser/android/in_process/synchronous_compositor_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/latency_info.h"
+
+using WebKit::WebInputEvent;
+
+namespace content {
+
+SynchronousInputEventFilter::SynchronousInputEventFilter() {
+}
+
+SynchronousInputEventFilter::~SynchronousInputEventFilter() {
+}
+
+InputEventAckState SynchronousInputEventFilter::HandleInputEvent(
+ int routing_id,
+ const WebKit::WebInputEvent& input_event) {
+ // The handler will be empty both before renderer initialization and after
+ // renderer destruction. It's possible that this will be reached in such a
+ // state. While not good, it should also not be fatal.
+ if (handler_.is_null())
+ return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
+
+ return handler_.Run(routing_id, &input_event, ui::LatencyInfo());
+}
+
+void SynchronousInputEventFilter::SetBoundHandler(const Handler& handler) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SynchronousInputEventFilter::SetBoundHandlerOnUIThread,
+ base::Unretained(this), handler));
+}
+
+void SynchronousInputEventFilter::SetBoundHandlerOnUIThread(
+ const Handler& handler) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ handler_ = handler;
+}
+
+void SynchronousInputEventFilter::DidAddInputHandler(
+ int routing_id,
+ cc::InputHandler* input_handler) {
+ // The SynchronusCompositorImpl can be NULL if the WebContents that it's
+ // bound to has already been deleted.
+ SynchronousCompositorImpl* compositor =
+ SynchronousCompositorImpl::FromRoutingID(routing_id);
+ if (compositor)
+ compositor->SetInputHandler(input_handler);
+}
+
+void SynchronousInputEventFilter::DidRemoveInputHandler(int routing_id) {
+ // The SynchronusCompositorImpl can be NULL if the WebContents that it's
+ // bound to has already been deleted.
+ SynchronousCompositorImpl* compositor =
+ SynchronousCompositorImpl::FromRoutingID(routing_id);
+ if (compositor)
+ compositor->SetInputHandler(NULL);
+}
+
+void SynchronousInputEventFilter::DidOverscroll(
+ int routing_id,
+ const cc::DidOverscrollParams& params) {
+ // The SynchronusCompositorImpl can be NULL if the WebContents that it's
+ // bound to has already been deleted.
+ SynchronousCompositorImpl* compositor =
+ SynchronousCompositorImpl::FromRoutingID(routing_id);
+ if (compositor)
+ compositor->DidOverscroll(params);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/in_process/synchronous_input_event_filter.h b/chromium/content/browser/android/in_process/synchronous_input_event_filter.h
new file mode 100644
index 00000000000..18601c222ae
--- /dev/null
+++ b/chromium/content/browser/android/in_process/synchronous_input_event_filter.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_INPUT_EVENT_FILTER_H_
+#define CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_INPUT_EVENT_FILTER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/renderer/gpu/input_handler_manager_client.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace WebKit {
+class WebInputEvent;
+}
+
+namespace content {
+
+// This class perform synchronous, in-process InputEvent handling.
+//
+// The provided |handler| process WebInputEvents synchronously on the merged
+// UI and compositing thread. If the event goes unhandled, that is reflected in
+// the InputEventAckState; no forwarding is performed.
+class SynchronousInputEventFilter : public InputHandlerManagerClient {
+ public:
+ SynchronousInputEventFilter();
+ virtual ~SynchronousInputEventFilter();
+
+ InputEventAckState HandleInputEvent(int routing_id,
+ const WebKit::WebInputEvent& input_event);
+
+ // InputHandlerManagerClient implementation.
+ virtual void SetBoundHandler(const Handler& handler) OVERRIDE;
+ virtual void DidAddInputHandler(int routing_id,
+ cc::InputHandler* input_handler) OVERRIDE;
+ virtual void DidRemoveInputHandler(int routing_id) OVERRIDE;
+ virtual void DidOverscroll(int routing_id,
+ const cc::DidOverscrollParams& params) OVERRIDE;
+
+ private:
+ void SetBoundHandlerOnUIThread(const Handler& handler);
+
+ Handler handler_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_IN_PROCESS_SYNCHRONOUS_INPUT_EVENT_FILTER_H_
diff --git a/chromium/content/browser/android/interstitial_page_delegate_android.cc b/chromium/content/browser/android/interstitial_page_delegate_android.cc
new file mode 100644
index 00000000000..4ed48907bed
--- /dev/null
+++ b/chromium/content/browser/android/interstitial_page_delegate_android.cc
@@ -0,0 +1,94 @@
+// 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 "content/browser/android/interstitial_page_delegate_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "content/public/browser/interstitial_page.h"
+#include "jni/InterstitialPageDelegateAndroid_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+InterstitialPageDelegateAndroid::InterstitialPageDelegateAndroid(
+ JNIEnv* env,
+ jobject obj,
+ const std::string& html_content)
+ : weak_java_obj_(env, obj),
+ html_content_(html_content),
+ page_(NULL) {
+}
+
+InterstitialPageDelegateAndroid::~InterstitialPageDelegateAndroid() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = weak_java_obj_.get(env);
+ if (obj.obj())
+ Java_InterstitialPageDelegateAndroid_onNativeDestroyed(env, obj.obj());
+}
+
+void InterstitialPageDelegateAndroid::Proceed(JNIEnv* env, jobject obj) {
+ if (page_)
+ page_->Proceed();
+}
+
+void InterstitialPageDelegateAndroid::DontProceed(JNIEnv* env,
+ jobject obj) {
+ if (page_)
+ page_->DontProceed();
+}
+
+std::string InterstitialPageDelegateAndroid::GetHTMLContents() {
+ return html_content_;
+}
+
+void InterstitialPageDelegateAndroid::OnProceed() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = weak_java_obj_.get(env);
+ if (obj.obj())
+ Java_InterstitialPageDelegateAndroid_onProceed(env, obj.obj());
+}
+
+void InterstitialPageDelegateAndroid::OnDontProceed() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = weak_java_obj_.get(env);
+ if (obj.obj())
+ Java_InterstitialPageDelegateAndroid_onDontProceed(env, obj.obj());
+}
+
+void InterstitialPageDelegateAndroid::CommandReceived(
+ const std::string& command) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = weak_java_obj_.get(env);
+ if (obj.obj()) {
+ std::string sanitized_command(command);
+ // The JSONified response has quotes, remove them.
+ if (sanitized_command.length() > 1 && sanitized_command[0] == '"') {
+ sanitized_command = sanitized_command.substr(
+ 1, sanitized_command.length() - 2);
+ }
+
+ Java_InterstitialPageDelegateAndroid_commandReceived(
+ env, obj.obj(),
+ base::android::ConvertUTF8ToJavaString(env, sanitized_command).obj());
+ }
+}
+
+// static
+bool InterstitialPageDelegateAndroid
+ ::RegisterInterstitialPageDelegateAndroid(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+static jint Init(JNIEnv* env, jobject obj, jstring html_content) {
+ InterstitialPageDelegateAndroid* delegate =
+ new InterstitialPageDelegateAndroid(
+ env, obj, base::android::ConvertJavaStringToUTF8(env, html_content));
+ return reinterpret_cast<jint>(delegate);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/interstitial_page_delegate_android.h b/chromium/content/browser/android/interstitial_page_delegate_android.h
new file mode 100644
index 00000000000..f1ce12cb95e
--- /dev/null
+++ b/chromium/content/browser/android/interstitial_page_delegate_android.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_ANDROID_INTERSTITIAL_PAGE_DELEGATE_ANDROID_H_
+#define CONTENT_BROWSER_ANDROID_INTERSTITIAL_PAGE_DELEGATE_ANDROID_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/jni_helper.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/interstitial_page_delegate.h"
+
+namespace content {
+
+class InterstitialPage;
+class WebContents;
+
+// Native counterpart that allows interstitial pages to be constructed and
+// managed from Java.
+class InterstitialPageDelegateAndroid : public InterstitialPageDelegate {
+ public:
+ InterstitialPageDelegateAndroid(JNIEnv* env,
+ jobject obj,
+ const std::string& html_content);
+ virtual ~InterstitialPageDelegateAndroid();
+
+ void set_interstitial_page(InterstitialPage* page) { page_ = page; }
+
+ // Methods called from Java.
+ void Proceed(JNIEnv* env, jobject obj);
+ void DontProceed(JNIEnv* env, jobject obj);
+
+ // Implementation of InterstitialPageDelegate
+ virtual std::string GetHTMLContents() OVERRIDE;
+ virtual void OnProceed() OVERRIDE;
+ virtual void OnDontProceed() OVERRIDE;
+ virtual void CommandReceived(const std::string& command) OVERRIDE;
+
+ static bool RegisterInterstitialPageDelegateAndroid(JNIEnv* env);
+
+ private:
+ JavaObjectWeakGlobalRef weak_java_obj_;
+
+ std::string html_content_;
+ InterstitialPage* page_; // Owns this.
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageDelegateAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_INTERSTITIAL_PAGE_DELEGATE_ANDROID_H_
diff --git a/chromium/content/browser/android/load_url_params.cc b/chromium/content/browser/android/load_url_params.cc
new file mode 100644
index 00000000000..57eff2d5fae
--- /dev/null
+++ b/chromium/content/browser/android/load_url_params.cc
@@ -0,0 +1,45 @@
+// 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 "content/browser/android/load_url_params.h"
+
+#include <jni.h>
+
+#include "base/android/jni_string.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/common/url_constants.h"
+#include "jni/LoadUrlParams_jni.h"
+#include "url/gurl.h"
+
+namespace {
+
+using content::NavigationController;
+
+void RegisterConstants(JNIEnv* env) {
+ Java_LoadUrlParams_initializeConstants(env,
+ NavigationController::LOAD_TYPE_DEFAULT,
+ NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST,
+ NavigationController::LOAD_TYPE_DATA,
+ NavigationController::UA_OVERRIDE_INHERIT,
+ NavigationController::UA_OVERRIDE_FALSE,
+ NavigationController::UA_OVERRIDE_TRUE);
+}
+
+} // namespace
+
+namespace content {
+
+bool RegisterLoadUrlParams(JNIEnv* env) {
+ if (!RegisterNativesImpl(env))
+ return false;
+ RegisterConstants(env);
+ return true;
+}
+
+jboolean IsDataScheme(JNIEnv* env, jclass clazz, jstring jurl) {
+ GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
+ return url.SchemeIs(chrome::kDataScheme);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/load_url_params.h b/chromium/content/browser/android/load_url_params.h
new file mode 100644
index 00000000000..77a68f15637
--- /dev/null
+++ b/chromium/content/browser/android/load_url_params.h
@@ -0,0 +1,16 @@
+// 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 CONTENT_BROWSER_ANDROID_LOAD_URL_PARAMS_H_
+#define CONTENT_BROWSER_ANDROID_LOAD_URL_PARAMS_H_
+
+#include <jni.h>
+
+namespace content {
+
+bool RegisterLoadUrlParams(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_LOAD_URL_PARAMS_H_
diff --git a/chromium/content/browser/android/media_resource_getter_impl.cc b/chromium/content/browser/android/media_resource_getter_impl.cc
new file mode 100644
index 00000000000..49703101b24
--- /dev/null
+++ b/chromium/content/browser/android/media_resource_getter_impl.cc
@@ -0,0 +1,276 @@
+// Copyright (c) 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 "content/browser/android/media_resource_getter_impl.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/path_service.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "jni/MediaResourceGetter_jni.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace content {
+
+static void ReturnResultOnUIThread(
+ const base::Callback<void(const std::string&)>& callback,
+ const std::string& result) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(callback, result));
+}
+
+// Get the metadata from a media URL. When finished, a task is posted to the UI
+// thread to run the callback function.
+static void GetMediaMetadata(
+ const std::string& url, const std::string& cookies,
+ const media::MediaResourceGetter::ExtractMediaMetadataCB& callback) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ base::android::ScopedJavaLocalRef<jstring> j_url_string =
+ base::android::ConvertUTF8ToJavaString(env, url);
+ base::android::ScopedJavaLocalRef<jstring> j_cookies =
+ base::android::ConvertUTF8ToJavaString(env, cookies);
+ jobject j_context = base::android::GetApplicationContext();
+ base::android::ScopedJavaLocalRef<jobject> j_metadata =
+ Java_MediaResourceGetter_extractMediaMetadata(
+ env, j_context, j_url_string.obj(), j_cookies.obj());
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, base::TimeDelta::FromMilliseconds(
+ Java_MediaMetadata_getDurationInMilliseconds(
+ env, j_metadata.obj())),
+ Java_MediaMetadata_getWidth(env, j_metadata.obj()),
+ Java_MediaMetadata_getHeight(env, j_metadata.obj()),
+ Java_MediaMetadata_isSuccess(env, j_metadata.obj())));
+}
+
+// The task object that retrieves cookie on the IO thread.
+// TODO(qinmin): refactor this class to make the code reusable by others as
+// there are lots of duplicated functionalities elsewhere.
+class CookieGetterTask
+ : public base::RefCountedThreadSafe<CookieGetterTask> {
+ public:
+ CookieGetterTask(BrowserContext* browser_context,
+ int renderer_id, int routing_id);
+
+ // Called by CookieGetterImpl to start getting cookies for a URL.
+ void RequestCookies(
+ const GURL& url, const GURL& first_party_for_cookies,
+ const media::MediaResourceGetter::GetCookieCB& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<CookieGetterTask>;
+ virtual ~CookieGetterTask();
+
+ void CheckPolicyForCookies(
+ const GURL& url, const GURL& first_party_for_cookies,
+ const media::MediaResourceGetter::GetCookieCB& callback,
+ const net::CookieList& cookie_list);
+
+ // Context getter used to get the CookieStore.
+ net::URLRequestContextGetter* context_getter_;
+
+ // Resource context for checking cookie policies.
+ ResourceContext* resource_context_;
+
+ // Render process id, used to check whether the process can access cookies.
+ int renderer_id_;
+
+ // Routing id for the render view, used to check tab specific cookie policy.
+ int routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieGetterTask);
+};
+
+CookieGetterTask::CookieGetterTask(
+ BrowserContext* browser_context, int renderer_id, int routing_id)
+ : context_getter_(browser_context->GetRequestContext()),
+ resource_context_(browser_context->GetResourceContext()),
+ renderer_id_(renderer_id),
+ routing_id_(routing_id) {
+}
+
+CookieGetterTask::~CookieGetterTask() {}
+
+void CookieGetterTask::RequestCookies(
+ const GURL& url, const GURL& first_party_for_cookies,
+ const media::MediaResourceGetter::GetCookieCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanAccessCookiesForOrigin(renderer_id_, url)) {
+ callback.Run(std::string());
+ return;
+ }
+
+ net::CookieStore* cookie_store =
+ context_getter_->GetURLRequestContext()->cookie_store();
+ if (!cookie_store) {
+ callback.Run(std::string());
+ return;
+ }
+
+ net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster();
+ if (cookie_monster) {
+ cookie_monster->GetAllCookiesForURLAsync(url, base::Bind(
+ &CookieGetterTask::CheckPolicyForCookies, this,
+ url, first_party_for_cookies, callback));
+ } else {
+ callback.Run(std::string());
+ }
+}
+
+void CookieGetterTask::CheckPolicyForCookies(
+ const GURL& url, const GURL& first_party_for_cookies,
+ const media::MediaResourceGetter::GetCookieCB& callback,
+ const net::CookieList& cookie_list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (GetContentClient()->browser()->AllowGetCookie(
+ url, first_party_for_cookies, cookie_list,
+ resource_context_, renderer_id_, routing_id_)) {
+ net::CookieStore* cookie_store =
+ context_getter_->GetURLRequestContext()->cookie_store();
+ cookie_store->GetCookiesWithOptionsAsync(
+ url, net::CookieOptions(), callback);
+ } else {
+ callback.Run(std::string());
+ }
+}
+
+// The task object that retrieves platform path on the FILE thread.
+class PlatformPathGetterTask
+ : public base::RefCountedThreadSafe<PlatformPathGetterTask> {
+ public:
+ PlatformPathGetterTask(fileapi::FileSystemContext* file_system_context,
+ int renderer_id);
+
+ // Called by MediaResourceGetterImpl to get the platform path from a file
+ // system URL.
+ void RequestPlaformPath(
+ const GURL& url,
+ const media::MediaResourceGetter::GetPlatformPathCB& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<PlatformPathGetterTask>;
+ virtual ~PlatformPathGetterTask();
+
+ // File system context for getting the platform path.
+ fileapi::FileSystemContext* file_system_context_;
+
+ // Render process id, used to check whether the process can access the URL.
+ int renderer_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformPathGetterTask);
+};
+
+PlatformPathGetterTask::PlatformPathGetterTask(
+ fileapi::FileSystemContext* file_system_context, int renderer_id)
+ : file_system_context_(file_system_context),
+ renderer_id_(renderer_id) {
+}
+
+PlatformPathGetterTask::~PlatformPathGetterTask() {}
+
+void PlatformPathGetterTask::RequestPlaformPath(
+ const GURL& url,
+ const media::MediaResourceGetter::GetPlatformPathCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::FilePath platform_path;
+ SyncGetPlatformPath(file_system_context_,
+ renderer_id_,
+ url,
+ &platform_path);
+ base::FilePath data_storage_path;
+ PathService::Get(base::DIR_ANDROID_APP_DATA, &data_storage_path);
+ if (data_storage_path.IsParent(platform_path))
+ callback.Run(platform_path.value());
+ else
+ callback.Run(std::string());
+}
+
+MediaResourceGetterImpl::MediaResourceGetterImpl(
+ BrowserContext* browser_context,
+ fileapi::FileSystemContext* file_system_context,
+ int renderer_id, int routing_id)
+ : browser_context_(browser_context),
+ file_system_context_(file_system_context),
+ weak_this_(this),
+ renderer_id_(renderer_id),
+ routing_id_(routing_id) {
+}
+
+MediaResourceGetterImpl::~MediaResourceGetterImpl() {}
+
+void MediaResourceGetterImpl::GetCookies(
+ const GURL& url, const GURL& first_party_for_cookies,
+ const GetCookieCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_refptr<CookieGetterTask> task = new CookieGetterTask(
+ browser_context_, renderer_id_, routing_id_);
+
+ GetCookieCB cb = base::Bind(&MediaResourceGetterImpl::GetCookiesCallback,
+ weak_this_.GetWeakPtr(), callback);
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&CookieGetterTask::RequestCookies,
+ task, url, first_party_for_cookies,
+ base::Bind(&ReturnResultOnUIThread, cb)));
+}
+
+void MediaResourceGetterImpl::GetCookiesCallback(
+ const GetCookieCB& callback, const std::string& cookies) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ callback.Run(cookies);
+}
+
+void MediaResourceGetterImpl::GetPlatformPathFromFileSystemURL(
+ const GURL& url, const GetPlatformPathCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_refptr<PlatformPathGetterTask> task = new PlatformPathGetterTask(
+ file_system_context_, renderer_id_);
+
+ GetPlatformPathCB cb = base::Bind(
+ &MediaResourceGetterImpl::GetPlatformPathCallback,
+ weak_this_.GetWeakPtr(), callback);
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&PlatformPathGetterTask::RequestPlaformPath,
+ task, url,
+ base::Bind(&ReturnResultOnUIThread, cb)));
+}
+
+void MediaResourceGetterImpl::GetPlatformPathCallback(
+ const GetPlatformPathCB& callback, const std::string& platform_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ callback.Run(platform_path);
+}
+
+void MediaResourceGetterImpl::ExtractMediaMetadata(
+ const std::string& url, const std::string& cookies,
+ const ExtractMediaMetadataCB& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
+ pool->PostWorkerTask(
+ FROM_HERE, base::Bind(&GetMediaMetadata, url, cookies, callback));
+}
+
+// static
+bool MediaResourceGetterImpl::RegisterMediaResourceGetter(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/media_resource_getter_impl.h b/chromium/content/browser/android/media_resource_getter_impl.h
new file mode 100644
index 00000000000..1d152447e7c
--- /dev/null
+++ b/chromium/content/browser/android/media_resource_getter_impl.h
@@ -0,0 +1,85 @@
+// Copyright (c) 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 CONTENT_BROWSER_ANDROID_MEDIA_RESOURCE_GETTER_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_MEDIA_RESOURCE_GETTER_IMPL_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "media/base/android/media_resource_getter.h"
+#include "net/cookies/canonical_cookie.h"
+
+namespace fileapi {
+class FileSystemContext;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+class BrowserContext;
+class ResourceContext;
+
+// This class implements media::MediaResourceGetter to retrieve resources
+// asynchronously on the UI thread.
+class MediaResourceGetterImpl : public media::MediaResourceGetter {
+ public:
+ // Construct a MediaResourceGetterImpl object. |browser_context| and
+ // |renderer_id| are passed to retrieve the CookieStore.
+ // |file_system_context| are used to get the platform path.
+ MediaResourceGetterImpl(BrowserContext* browser_context,
+ fileapi::FileSystemContext* file_system_context,
+ int renderer_id, int routing_id);
+ virtual ~MediaResourceGetterImpl();
+
+ // media::MediaResourceGetter implementation.
+ // Must be called on the UI thread.
+ virtual void GetCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ const GetCookieCB& callback) OVERRIDE;
+ virtual void GetPlatformPathFromFileSystemURL(
+ const GURL& url,
+ const GetPlatformPathCB& callback) OVERRIDE;
+ virtual void ExtractMediaMetadata(
+ const std::string& url, const std::string& cookies,
+ const ExtractMediaMetadataCB& callback) OVERRIDE;
+
+ static bool RegisterMediaResourceGetter(JNIEnv* env);
+
+ private:
+ // Called when GetCookies() finishes.
+ void GetCookiesCallback(
+ const GetCookieCB& callback, const std::string& cookies);
+
+ // Called when GetPlatformPathFromFileSystemURL() finishes.
+ void GetPlatformPathCallback(
+ const GetPlatformPathCB& callback, const std::string& platform_path);
+
+ // BrowserContext to retrieve URLRequestContext and ResourceContext.
+ BrowserContext* browser_context_;
+
+ // FileSystemContext to be used on FILE thread.
+ fileapi::FileSystemContext* file_system_context_;
+
+ // Used to post tasks.
+ base::WeakPtrFactory<MediaResourceGetterImpl> weak_this_;
+
+ // Render process id, used to check whether the process can access cookies.
+ int renderer_id_;
+
+ // Routing id for the render view, used to check tab specific cookie policy.
+ int routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaResourceGetterImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_MEDIA_RESOURCE_GETTER_IMPL_H_
diff --git a/chromium/content/browser/android/overscroll_glow.cc b/chromium/content/browser/android/overscroll_glow.cc
new file mode 100644
index 00000000000..2c9efeea72d
--- /dev/null
+++ b/chromium/content/browser/android/overscroll_glow.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 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 "content/browser/android/overscroll_glow.h"
+
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "cc/layers/image_layer.h"
+#include "content/browser/android/edge_effect.h"
+#include "ui/gfx/android/java_bitmap.h"
+
+using std::max;
+using std::min;
+
+namespace content {
+
+namespace {
+
+const float kEpsilon = 1e-3f;
+
+class OverscrollResources {
+ public:
+ OverscrollResources() {
+ TRACE_EVENT0("browser", "OverscrollResources::Create");
+ edge_bitmap_ =
+ gfx::CreateSkBitmapFromResource("android:drawable/overscroll_edge",
+ gfx::Size(128, 12));
+ glow_bitmap_ =
+ gfx::CreateSkBitmapFromResource("android:drawable/overscroll_glow",
+ gfx::Size(128, 64));
+ }
+
+ const SkBitmap& edge_bitmap() { return edge_bitmap_; }
+ const SkBitmap& glow_bitmap() { return glow_bitmap_; }
+
+ private:
+ SkBitmap edge_bitmap_;
+ SkBitmap glow_bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverscrollResources);
+};
+
+// Leaky to allow access from a worker thread.
+base::LazyInstance<OverscrollResources>::Leaky g_overscroll_resources =
+ LAZY_INSTANCE_INITIALIZER;
+
+scoped_refptr<cc::Layer> CreateImageLayer(const SkBitmap& bitmap) {
+ scoped_refptr<cc::ImageLayer> layer = cc::ImageLayer::Create();
+ layer->SetBitmap(bitmap);
+ return layer;
+}
+
+bool IsApproxZero(float value) {
+ return std::abs(value) < kEpsilon;
+}
+
+gfx::Vector2dF ZeroSmallComponents(gfx::Vector2dF vector) {
+ if (IsApproxZero(vector.x()))
+ vector.set_x(0);
+ if (IsApproxZero(vector.y()))
+ vector.set_y(0);
+ return vector;
+}
+
+} // namespace
+
+scoped_ptr<OverscrollGlow> OverscrollGlow::Create(bool enabled) {
+ const SkBitmap& edge = g_overscroll_resources.Get().edge_bitmap();
+ const SkBitmap& glow = g_overscroll_resources.Get().glow_bitmap();
+ if (edge.isNull() || glow.isNull())
+ return scoped_ptr<OverscrollGlow>();
+
+ return make_scoped_ptr(new OverscrollGlow(enabled, edge, glow));
+}
+
+void OverscrollGlow::EnsureResources() {
+ g_overscroll_resources.Get();
+}
+
+OverscrollGlow::OverscrollGlow(bool enabled,
+ const SkBitmap& edge,
+ const SkBitmap& glow)
+ : enabled_(enabled),
+ horizontal_overscroll_enabled_(true),
+ vertical_overscroll_enabled_(true),
+ root_layer_(cc::Layer::Create()) {
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ scoped_refptr<cc::Layer> edge_layer = CreateImageLayer(edge);
+ scoped_refptr<cc::Layer> glow_layer = CreateImageLayer(glow);
+ root_layer_->AddChild(edge_layer);
+ root_layer_->AddChild(glow_layer);
+ edge_effects_[i] = make_scoped_ptr(new EdgeEffect(edge_layer, glow_layer));
+ }
+}
+
+OverscrollGlow::~OverscrollGlow() {
+ root_layer_->RemoveFromParent();
+}
+
+void OverscrollGlow::OnOverscrolled(base::TimeTicks current_time,
+ gfx::Vector2dF overscroll,
+ gfx::Vector2dF velocity) {
+ if (!enabled_)
+ return;
+
+ // The size of the glow determines the relative effect of the inputs; an
+ // empty-sized effect is effectively disabled.
+ if (size_.IsEmpty())
+ return;
+
+ if (!horizontal_overscroll_enabled_) {
+ overscroll.set_x(0);
+ velocity.set_x(0);
+ }
+ if (!vertical_overscroll_enabled_) {
+ overscroll.set_y(0);
+ velocity.set_y(0);
+ }
+
+ // Ignore sufficiently small values that won't meaningfuly affect animation.
+ overscroll = ZeroSmallComponents(overscroll);
+ velocity = ZeroSmallComponents(velocity);
+
+ if (overscroll.IsZero()) {
+ Release(current_time);
+ return;
+ }
+
+ if (!velocity.IsZero()) {
+ // Release effects if scrolling has changed directions.
+ if (velocity.x() * old_velocity_.x() < 0)
+ ReleaseAxis(AXIS_X, current_time);
+ if (velocity.y() * old_velocity_.y() < 0)
+ ReleaseAxis(AXIS_Y, current_time);
+
+ Absorb(current_time, velocity, overscroll, old_overscroll_);
+ } else {
+ // Release effects when overscroll accumulation violates monotonicity.
+ if (overscroll.x() * old_overscroll_.x() < 0 ||
+ std::abs(overscroll.x()) < std::abs(old_overscroll_.x()))
+ ReleaseAxis(AXIS_X, current_time);
+ if (overscroll.y() * old_overscroll_.y() < 0 ||
+ std::abs(overscroll.y()) < std::abs(old_overscroll_.y()))
+ ReleaseAxis(AXIS_Y, current_time);
+
+ Pull(current_time, overscroll - old_overscroll_);
+ }
+
+ old_velocity_ = velocity;
+ old_overscroll_ = overscroll;
+}
+
+void OverscrollGlow::Release(base::TimeTicks current_time) {
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ edge_effects_[i]->Release(current_time);
+ }
+ old_overscroll_ = old_velocity_ = gfx::Vector2dF();
+}
+
+bool OverscrollGlow::Animate(base::TimeTicks current_time) {
+ if (!NeedsAnimate())
+ return false;
+
+ const gfx::SizeF sizes[EdgeEffect::EDGE_COUNT] = {
+ size_, gfx::SizeF(size_.height(), size_.width()),
+ size_, gfx::SizeF(size_.height(), size_.width())
+ };
+
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ if (edge_effects_[i]->Update(current_time)) {
+ edge_effects_[i]->ApplyToLayers(sizes[i],
+ static_cast<EdgeEffect::Edge>(i));
+ }
+ }
+
+ return NeedsAnimate();
+}
+
+void OverscrollGlow::SetEnabled(bool enabled) {
+ if (enabled_ == enabled)
+ return;
+ enabled_ = enabled;
+ if (!enabled_) {
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i)
+ edge_effects_[i]->Finish();
+ }
+}
+
+bool OverscrollGlow::NeedsAnimate() const {
+ if (!enabled_)
+ return false;
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ if (!edge_effects_[i]->IsFinished())
+ return true;
+ }
+ return false;
+}
+
+void OverscrollGlow::Pull(base::TimeTicks current_time,
+ gfx::Vector2dF overscroll_delta) {
+ overscroll_delta = ZeroSmallComponents(overscroll_delta);
+ if (overscroll_delta.IsZero())
+ return;
+
+ gfx::Vector2dF overscroll_pull = gfx::ScaleVector2d(overscroll_delta,
+ 1.f / size_.width(),
+ 1.f / size_.height());
+ float edge_overscroll_pull[EdgeEffect::EDGE_COUNT] = {
+ min(overscroll_pull.y(), 0.f), // Top
+ min(overscroll_pull.x(), 0.f), // Left
+ max(overscroll_pull.y(), 0.f), // Bottom
+ max(overscroll_pull.x(), 0.f) // Right
+ };
+
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ if (!edge_overscroll_pull[i])
+ continue;
+
+ edge_effects_[i]->Pull(current_time, std::abs(edge_overscroll_pull[i]));
+ GetOppositeEdge(i)->Release(current_time);
+ }
+}
+
+void OverscrollGlow::Absorb(base::TimeTicks current_time,
+ gfx::Vector2dF velocity,
+ gfx::Vector2dF overscroll,
+ gfx::Vector2dF old_overscroll) {
+ if (overscroll.IsZero() || velocity.IsZero())
+ return;
+
+ // Only trigger on initial overscroll at a non-zero velocity
+ const float overscroll_velocities[EdgeEffect::EDGE_COUNT] = {
+ old_overscroll.y() >= 0 && overscroll.y() < 0 ? min(velocity.y(), 0.f) : 0,
+ old_overscroll.x() >= 0 && overscroll.x() < 0 ? min(velocity.x(), 0.f) : 0,
+ old_overscroll.y() <= 0 && overscroll.y() > 0 ? max(velocity.y(), 0.f) : 0,
+ old_overscroll.x() <= 0 && overscroll.x() > 0 ? max(velocity.x(), 0.f) : 0
+ };
+
+ for (size_t i = 0; i < EdgeEffect::EDGE_COUNT; ++i) {
+ if (!overscroll_velocities[i])
+ continue;
+
+ edge_effects_[i]->Absorb(current_time, std::abs(overscroll_velocities[i]));
+ GetOppositeEdge(i)->Release(current_time);
+ }
+}
+
+void OverscrollGlow::ReleaseAxis(Axis axis, base::TimeTicks current_time) {
+ switch (axis) {
+ case AXIS_X:
+ edge_effects_[EdgeEffect::EDGE_LEFT]->Release(current_time);
+ edge_effects_[EdgeEffect::EDGE_RIGHT]->Release(current_time);
+ old_overscroll_.set_x(0);
+ old_velocity_.set_x(0);
+ break;
+ case AXIS_Y:
+ edge_effects_[EdgeEffect::EDGE_TOP]->Release(current_time);
+ edge_effects_[EdgeEffect::EDGE_BOTTOM]->Release(current_time);
+ old_overscroll_.set_y(0);
+ old_velocity_.set_y(0);
+ break;
+ };
+}
+
+EdgeEffect* OverscrollGlow::GetOppositeEdge(int edge_index) {
+ return edge_effects_[(edge_index + 2) % EdgeEffect::EDGE_COUNT].get();
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/android/overscroll_glow.h b/chromium/content/browser/android/overscroll_glow.h
new file mode 100644
index 00000000000..4ea929e9d3e
--- /dev/null
+++ b/chromium/content/browser/android/overscroll_glow.h
@@ -0,0 +1,111 @@
+// 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 CONTENT_BROWSER_ANDROID_OVERSCROLL_GLOW_H_
+#define CONTENT_BROWSER_ANDROID_OVERSCROLL_GLOW_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/android/edge_effect.h"
+#include "ui/gfx/size_f.h"
+#include "ui/gfx/vector2d_f.h"
+
+class SkBitmap;
+
+namespace cc {
+class Layer;
+}
+
+namespace content {
+
+/* |OverscrollGlow| mirrors its Android counterpart, OverscrollGlow.java.
+ * Conscious tradeoffs were made to align this as closely as possible with the
+ * original Android java version.
+ */
+class OverscrollGlow {
+ public:
+ // Create and initialize a new effect with the necessary resources.
+ // If |enabled| is false, the effect will be be deactivated until
+ // SetEnabled(true) is called.
+ // The caller should attach |root_layer| to the desired layer tree.
+ static scoped_ptr<OverscrollGlow> Create(bool enabled);
+
+ // Force loading of any necessary resources. This function is thread-safe.
+ static void EnsureResources();
+
+ ~OverscrollGlow();
+
+ // If false, the glow will be deactivated, and subsequent calls to
+ // OnOverscrolled or Animate will have no effect.
+ void SetEnabled(bool enabled);
+
+ // |overscroll| is the accumulated overscroll for the current gesture.
+ // |velocity| is the instantaneous velocity for the overscroll.
+ void OnOverscrolled(base::TimeTicks current_time,
+ gfx::Vector2dF overscroll,
+ gfx::Vector2dF velocity);
+
+ // Triggers glow recession for any active edges.
+ // Note: This does not actually release any resources; the name mirrors that
+ // in Android's OverscrollGlow class.
+ void Release(base::TimeTicks current_time);
+
+ // Returns true if the effect still needs animation ticks.
+ bool Animate(base::TimeTicks current_time);
+
+ // Returns true if the effect needs animation ticks.
+ bool NeedsAnimate() const;
+
+ // The root layer of the effect (not necessarily of the tree).
+ scoped_refptr<cc::Layer> root_layer() const {
+ return root_layer_;
+ }
+
+ // Horizontal overscroll will be ignored when false.
+ void set_horizontal_overscroll_enabled(bool enabled) {
+ horizontal_overscroll_enabled_ = enabled;
+ }
+ // Vertical overscroll will be ignored when false.
+ void set_vertical_overscroll_enabled(bool enabled) {
+ vertical_overscroll_enabled_ = enabled;
+ }
+ // The size of the layer for which edges will be animated.
+ void set_size(gfx::SizeF size) {
+ size_ = size;
+ }
+
+ private:
+ enum Axis { AXIS_X, AXIS_Y };
+
+ OverscrollGlow(bool enabled, const SkBitmap& edge, const SkBitmap& glow);
+
+ void Pull(base::TimeTicks current_time,
+ gfx::Vector2dF added_overscroll);
+ void Absorb(base::TimeTicks current_time,
+ gfx::Vector2dF velocity,
+ gfx::Vector2dF overscroll,
+ gfx::Vector2dF old_overscroll);
+
+ void ReleaseAxis(Axis axis, base::TimeTicks current_time);
+
+ EdgeEffect* GetOppositeEdge(int edge_index);
+
+ scoped_ptr<EdgeEffect> edge_effects_[EdgeEffect::EDGE_COUNT];
+
+ bool enabled_;
+ gfx::SizeF size_;
+ gfx::Vector2dF old_overscroll_;
+ gfx::Vector2dF old_velocity_;
+ bool horizontal_overscroll_enabled_;
+ bool vertical_overscroll_enabled_;
+
+ scoped_refptr<cc::Layer> root_layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverscrollGlow);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_SCROLL_GLOW_H_
diff --git a/chromium/content/browser/android/surface_texture_peer_browser_impl.cc b/chromium/content/browser/android/surface_texture_peer_browser_impl.cc
new file mode 100644
index 00000000000..faaf4a2821d
--- /dev/null
+++ b/chromium/content/browser/android/surface_texture_peer_browser_impl.cc
@@ -0,0 +1,71 @@
+// 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 "content/browser/android/surface_texture_peer_browser_impl.h"
+
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "media/base/android/media_player_android.h"
+#include "media/base/android/media_player_manager.h"
+#include "ui/gl/android/scoped_java_surface.h"
+
+namespace content {
+
+namespace {
+
+// Pass a java surface object to the MediaPlayerAndroid object
+// identified by render process handle, render view ID and player ID.
+static void SetSurfacePeer(
+ scoped_refptr<gfx::SurfaceTextureBridge> surface_texture_bridge,
+ base::ProcessHandle render_process_handle,
+ int render_view_id,
+ int player_id) {
+ int renderer_id = 0;
+ RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator();
+ while (!it.IsAtEnd()) {
+ if (it.GetCurrentValue()->GetHandle() == render_process_handle) {
+ renderer_id = it.GetCurrentValue()->GetID();
+ break;
+ }
+ it.Advance();
+ }
+
+ if (renderer_id) {
+ RenderViewHostImpl* host = RenderViewHostImpl::FromID(
+ renderer_id, render_view_id);
+ if (host) {
+ media::MediaPlayerAndroid* player =
+ host->media_player_manager()->GetPlayer(player_id);
+ if (player &&
+ player != host->media_player_manager()->GetFullscreenPlayer()) {
+ gfx::ScopedJavaSurface surface(surface_texture_bridge.get());
+ player->SetVideoSurface(surface.Pass());
+ }
+ }
+ }
+}
+
+} // anonymous namespace
+
+SurfaceTexturePeerBrowserImpl::SurfaceTexturePeerBrowserImpl() {
+}
+
+SurfaceTexturePeerBrowserImpl::~SurfaceTexturePeerBrowserImpl() {
+}
+
+void SurfaceTexturePeerBrowserImpl::EstablishSurfaceTexturePeer(
+ base::ProcessHandle render_process_handle,
+ scoped_refptr<gfx::SurfaceTextureBridge> surface_texture_bridge,
+ int render_view_id,
+ int player_id) {
+ if (!surface_texture_bridge.get())
+ return;
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &SetSurfacePeer, surface_texture_bridge, render_process_handle,
+ render_view_id, player_id));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/surface_texture_peer_browser_impl.h b/chromium/content/browser/android/surface_texture_peer_browser_impl.h
new file mode 100644
index 00000000000..644bf2c92e0
--- /dev/null
+++ b/chromium/content/browser/android/surface_texture_peer_browser_impl.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_ANDROID_SURFACE_TEXTURE_PEER_BROWSER_IMPL_H_
+#define CONTENT_BROWSER_ANDROID_SURFACE_TEXTURE_PEER_BROWSER_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "content/common/android/surface_texture_peer.h"
+
+namespace content {
+
+// The SurfaceTexturePeer implementation for browser process.
+class SurfaceTexturePeerBrowserImpl : public SurfaceTexturePeer {
+ public:
+ // Construct a SurfaceTexturePeerBrowserImpl object. If
+ // |player_in_render_process| is true, calling EstablishSurfaceTexturePeer()
+ // will send the java surface texture object to the render process through
+ // ChildProcessService. Otherwise, it will pass the surface texture
+ // to the MediaPlayerBridge object in the browser process.
+ SurfaceTexturePeerBrowserImpl();
+ virtual ~SurfaceTexturePeerBrowserImpl();
+
+ // SurfaceTexturePeer implementation.
+ virtual void EstablishSurfaceTexturePeer(
+ base::ProcessHandle render_process_handle,
+ scoped_refptr<gfx::SurfaceTextureBridge> surface_texture_bridge,
+ int render_view_id,
+ int player_id) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SurfaceTexturePeerBrowserImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_SURFACE_TEXTURE_PEER_BROWSER_IMPL_H_
diff --git a/chromium/content/browser/android/touch_point.cc b/chromium/content/browser/android/touch_point.cc
new file mode 100644
index 00000000000..1a10e6ecfca
--- /dev/null
+++ b/chromium/content/browser/android/touch_point.cc
@@ -0,0 +1,115 @@
+// 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 "content/browser/android/touch_point.h"
+
+#include "base/debug/debugger.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+#include "jni/TouchPoint_jni.h"
+
+using WebKit::WebTouchEvent;
+using WebKit::WebTouchPoint;
+
+namespace {
+
+void MaybeAddTouchPoint(JNIEnv* env,
+ jobject pt,
+ float dpi_scale,
+ WebKit::WebTouchEvent& event) {
+ WebTouchPoint::State state = static_cast<WebTouchPoint::State>(
+ Java_TouchPoint_getState(env, pt));
+ if (state == WebTouchPoint::StateUndefined)
+ return;
+
+ // When generating a cancel event from an event of a different type, the
+ // touch points are out of sync, so we ensure the points are marked as
+ // canceled as well.
+ if (event.type == WebTouchEvent::TouchCancel)
+ state = WebTouchPoint::StateCancelled;
+
+ // Record the current number of points in the WebTouchEvent
+ const int idx = event.touchesLength;
+ DCHECK_LT(idx, WebKit::WebTouchEvent::touchesLengthCap);
+
+ WebTouchPoint wtp;
+ wtp.id = Java_TouchPoint_getId(env, pt);
+ wtp.state = state;
+ wtp.position.x = Java_TouchPoint_getX(env, pt) / dpi_scale;
+ wtp.position.y = Java_TouchPoint_getY(env, pt) / dpi_scale;
+ // TODO(joth): Raw event co-ordinates.
+ wtp.screenPosition = wtp.position;
+ wtp.force = Java_TouchPoint_getPressure(env, pt);
+
+ // TODO(djsollen): WebKit stores touch point size as a pair of radii, which
+ // are integers. We receive touch point size from Android as a float
+ // between 0 and 1 and interpret 'size' as an elliptical area. We convert
+ // size to a radius and then scale up to avoid truncating away all of the
+ // data. W3C spec is for the radii to be in units of screen pixels. Need to
+ // change.
+ const static double PI = 3.1415926;
+ const static double SCALE_FACTOR = 1024.0;
+ const int radius = static_cast<int>(
+ (sqrt(Java_TouchPoint_getSize(env, pt)) / PI) * SCALE_FACTOR);
+ wtp.radiusX = radius / dpi_scale;
+ wtp.radiusY = radius / dpi_scale;
+ // Since our radii are equal, a rotation angle doesn't mean anything.
+ wtp.rotationAngle = 0.0;
+
+ // Add the newly created WebTouchPoint to the event
+ event.touches[idx] = wtp;
+ ++(event.touchesLength);
+}
+
+} // namespace
+
+namespace content {
+
+void TouchPoint::BuildWebTouchEvent(JNIEnv* env,
+ jint type,
+ jlong time_ms,
+ float dpi_scale,
+ jobjectArray pts,
+ WebKit::WebTouchEvent& event) {
+ event.type = static_cast<WebTouchEvent::Type>(type);
+ event.timeStampSeconds =
+ static_cast<double>(time_ms) / base::Time::kMillisecondsPerSecond;
+ int arrayLength = env->GetArrayLength(pts);
+ // Loop until either all of the input points have been consumed or the output
+ // array has been filled
+ for (int i = 0; i < arrayLength; i++) {
+ jobject pt = env->GetObjectArrayElement(pts, i);
+ MaybeAddTouchPoint(env, pt, dpi_scale, event);
+ if (event.touchesLength >= event.touchesLengthCap)
+ break;
+ }
+ DCHECK_GT(event.touchesLength, 0U);
+}
+
+static void RegisterConstants(JNIEnv* env) {
+ Java_TouchPoint_initializeConstants(
+ env,
+ WebKit::WebTouchEvent::TouchStart,
+ WebKit::WebTouchEvent::TouchMove,
+ WebKit::WebTouchEvent::TouchEnd,
+ WebKit::WebTouchEvent::TouchCancel,
+ WebKit::WebTouchPoint::StateUndefined,
+ WebKit::WebTouchPoint::StateReleased,
+ WebKit::WebTouchPoint::StatePressed,
+ WebKit::WebTouchPoint::StateMoved,
+ WebKit::WebTouchPoint::StateStationary,
+ WebKit::WebTouchPoint::StateCancelled);
+}
+
+bool RegisterTouchPoint(JNIEnv* env) {
+ if (!RegisterNativesImpl(env))
+ return false;
+
+ RegisterConstants(env);
+
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/touch_point.h b/chromium/content/browser/android/touch_point.h
new file mode 100644
index 00000000000..8e9971bad5c
--- /dev/null
+++ b/chromium/content/browser/android/touch_point.h
@@ -0,0 +1,30 @@
+// 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 CONTENT_BROWSER_ANDROID_TOUCH_POINT_H_
+#define CONTENT_BROWSER_ANDROID_TOUCH_POINT_H_
+
+#include <jni.h>
+
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+// This class provides a helper method to convert a java object array of touch
+// events (in physical pixdels) into a WebKit::WebTouchEvent (in dip).
+class TouchPoint {
+ public:
+ static void BuildWebTouchEvent(JNIEnv* env,
+ jint type,
+ jlong time_ms,
+ float dpi_scale,
+ jobjectArray pts,
+ WebKit::WebTouchEvent& event);
+};
+
+bool RegisterTouchPoint(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_CHROME_VIEW_H_
diff --git a/chromium/content/browser/android/tracing_intent_handler.cc b/chromium/content/browser/android/tracing_intent_handler.cc
new file mode 100644
index 00000000000..276091619cc
--- /dev/null
+++ b/chromium/content/browser/android/tracing_intent_handler.cc
@@ -0,0 +1,57 @@
+// 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 "content/browser/android/tracing_intent_handler.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "content/public/browser/trace_controller.h"
+#include "jni/TracingIntentHandler_jni.h"
+
+namespace content {
+
+TracingIntentHandler* g_trace_intent_handler = NULL;
+
+TracingIntentHandler::TracingIntentHandler(const base::FilePath& path)
+ : TraceSubscriberStdio(path) {
+ TraceController::GetInstance()->BeginTracing(
+ this,
+ std::string("-test*"),
+ base::debug::TraceLog::RECORD_UNTIL_FULL);
+}
+
+TracingIntentHandler::~TracingIntentHandler() {
+}
+
+void TracingIntentHandler::OnEndTracingComplete() {
+ TraceSubscriberStdio::OnEndTracingComplete();
+ delete this;
+}
+
+void TracingIntentHandler::OnEndTracing() {
+ if (!TraceController::GetInstance()->EndTracingAsync(this)) {
+ delete this;
+ }
+}
+
+static void BeginTracing(JNIEnv* env, jclass clazz, jstring jspath) {
+ std::string path(base::android::ConvertJavaStringToUTF8(env, jspath));
+ if (g_trace_intent_handler != NULL)
+ return;
+ g_trace_intent_handler = new TracingIntentHandler(base::FilePath(path));
+}
+
+static void EndTracing(JNIEnv* env, jclass clazz) {
+ DCHECK(!g_trace_intent_handler);
+ g_trace_intent_handler->OnEndTracing();
+ g_trace_intent_handler = NULL;
+}
+
+bool RegisterTracingIntentHandler(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/tracing_intent_handler.h b/chromium/content/browser/android/tracing_intent_handler.h
new file mode 100644
index 00000000000..787f854e628
--- /dev/null
+++ b/chromium/content/browser/android/tracing_intent_handler.h
@@ -0,0 +1,31 @@
+// 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 CONTENT_BROWSER_ANDROID_TRACING_INTENT_HANDLER_H_
+#define CONTENT_BROWSER_ANDROID_TRACING_INTENT_HANDLER_H_
+
+#include <jni.h>
+#include <string>
+
+#include "content/browser/tracing/trace_subscriber_stdio.h"
+
+namespace content {
+
+// Registers the TracingIntentHandler native methods.
+bool RegisterTracingIntentHandler(JNIEnv* env);
+
+class TracingIntentHandler : public TraceSubscriberStdio {
+ public:
+ explicit TracingIntentHandler(const base::FilePath& path);
+ virtual ~TracingIntentHandler();
+
+ // TraceSubscriber implementation
+ virtual void OnEndTracingComplete() OVERRIDE;
+
+ // IntentHandler
+ void OnEndTracing();
+};
+
+} // namespace content
+#endif // CONTENT_BROWSER_ANDROID_TRACING_INTENT_HANDLER_H_
diff --git a/chromium/content/browser/android/vibration_message_filter.cc b/chromium/content/browser/android/vibration_message_filter.cc
new file mode 100644
index 00000000000..578fbfdf6e9
--- /dev/null
+++ b/chromium/content/browser/android/vibration_message_filter.cc
@@ -0,0 +1,69 @@
+// 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 "content/browser/android/vibration_message_filter.h"
+
+#include <algorithm>
+
+#include "base/safe_numerics.h"
+#include "content/common/view_messages.h"
+#include "jni/VibrationMessageFilter_jni.h"
+#include "third_party/WebKit/public/platform/WebVibration.h"
+
+using base::android::AttachCurrentThread;
+
+namespace content {
+
+// Minimum duration of a vibration is 1 millisecond.
+const int64 kMinimumVibrationDurationMs = 1;
+
+VibrationMessageFilter::VibrationMessageFilter() {
+}
+
+VibrationMessageFilter::~VibrationMessageFilter() {
+}
+
+// static
+bool VibrationMessageFilter::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+bool VibrationMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(VibrationMessageFilter,
+ message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Vibrate, OnVibrate)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CancelVibration, OnCancelVibration)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void VibrationMessageFilter::OnVibrate(int64 milliseconds) {
+ // Though the Blink implementation already sanitizes vibration times, don't
+ // trust any values passed from the renderer.
+ milliseconds = std::max(kMinimumVibrationDurationMs,
+ std::min(milliseconds,
+ base::checked_numeric_cast<int64>(WebKit::kVibrationDurationMax)));
+
+ if (j_vibration_message_filter_.is_null()) {
+ j_vibration_message_filter_.Reset(
+ Java_VibrationMessageFilter_create(
+ AttachCurrentThread(),
+ base::android::GetApplicationContext()));
+ }
+ Java_VibrationMessageFilter_vibrate(AttachCurrentThread(),
+ j_vibration_message_filter_.obj(),
+ milliseconds);
+}
+
+void VibrationMessageFilter::OnCancelVibration() {
+ Java_VibrationMessageFilter_cancelVibration(AttachCurrentThread(),
+ j_vibration_message_filter_.obj());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/android/vibration_message_filter.h b/chromium/content/browser/android/vibration_message_filter.h
new file mode 100644
index 00000000000..935b6106625
--- /dev/null
+++ b/chromium/content/browser/android/vibration_message_filter.h
@@ -0,0 +1,34 @@
+// 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 CONTENT_BROWSER_ANDROID_VIBRATION_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_ANDROID_VIBRATION_MESSAGE_FILTER_H_
+
+#include "base/android/jni_android.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class VibrationMessageFilter : public BrowserMessageFilter {
+ public:
+ VibrationMessageFilter();
+
+ static bool Register(JNIEnv* env);
+
+ private:
+ virtual ~VibrationMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ void OnVibrate(int64 milliseconds);
+ void OnCancelVibration();
+
+ base::android::ScopedJavaGlobalRef<jobject> j_vibration_message_filter_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_VIBRATION_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/android/web_contents_observer_android.cc b/chromium/content/browser/android/web_contents_observer_android.cc
new file mode 100644
index 00000000000..d9a150742d1
--- /dev/null
+++ b/chromium/content/browser/android/web_contents_observer_android.cc
@@ -0,0 +1,233 @@
+// 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 "content/browser/android/web_contents_observer_android.h"
+
+#include <string>
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/navigation_details.h"
+#include "jni/WebContentsObserverAndroid_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ConvertUTF16ToJavaString;
+using base::android::HasClass;
+
+namespace content {
+
+WebContentsObserverAndroid::WebContentsObserverAndroid(
+ JNIEnv* env,
+ jobject obj,
+ WebContents* web_contents)
+ : WebContentsObserver(web_contents),
+ weak_java_observer_(env, obj){
+}
+
+WebContentsObserverAndroid::~WebContentsObserverAndroid() {
+}
+
+jint Init(JNIEnv* env, jobject obj, jint native_content_view_core) {
+ ContentViewCore* content_view_core =
+ reinterpret_cast<ContentViewCore*>(native_content_view_core);
+ WebContentsObserverAndroid* native_observer = new WebContentsObserverAndroid(
+ env, obj, content_view_core->GetWebContents());
+ return reinterpret_cast<jint>(native_observer);
+}
+
+void WebContentsObserverAndroid::Destroy(JNIEnv* env, jobject obj) {
+ delete this;
+}
+
+void WebContentsObserverAndroid::WebContentsDestroyed(
+ WebContents* web_contents) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null()) {
+ delete this;
+ } else {
+ // The java side will destroy |this|
+ Java_WebContentsObserverAndroid_detachFromWebContents(env, obj.obj());
+ }
+}
+
+void WebContentsObserverAndroid::DidStartLoading(
+ RenderViewHost* render_view_host) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
+ env, web_contents()->GetVisibleURL().spec()));
+ Java_WebContentsObserverAndroid_didStartLoading(
+ env, obj.obj(), jstring_url.obj());
+}
+
+void WebContentsObserverAndroid::DidStopLoading(
+ RenderViewHost* render_view_host) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
+ env, web_contents()->GetLastCommittedURL().spec()));
+ Java_WebContentsObserverAndroid_didStopLoading(
+ env, obj.obj(), jstring_url.obj());
+}
+
+void WebContentsObserverAndroid::DidFailProvisionalLoad(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) {
+ DidFailLoadInternal(
+ true, is_main_frame, error_code, error_description, validated_url);
+}
+
+void WebContentsObserverAndroid::DidFailLoad(
+ int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) {
+ DidFailLoadInternal(
+ false, is_main_frame, error_code, error_description, validated_url);
+}
+
+void WebContentsObserverAndroid::DidNavigateMainFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, params.url.spec()));
+ ScopedJavaLocalRef<jstring> jstring_base_url(
+ ConvertUTF8ToJavaString(env, params.base_url.spec()));
+ Java_WebContentsObserverAndroid_didNavigateMainFrame(
+ env, obj.obj(), jstring_url.obj(), jstring_base_url.obj(),
+ details.is_navigation_to_different_page());
+}
+
+void WebContentsObserverAndroid::DidNavigateAnyFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, params.url.spec()));
+ ScopedJavaLocalRef<jstring> jstring_base_url(
+ ConvertUTF8ToJavaString(env, params.base_url.spec()));
+ jboolean jboolean_is_reload =
+ PageTransitionCoreTypeIs(params.transition, PAGE_TRANSITION_RELOAD);
+
+ Java_WebContentsObserverAndroid_didNavigateAnyFrame(
+ env, obj.obj(), jstring_url.obj(), jstring_base_url.obj(),
+ jboolean_is_reload);
+}
+
+void WebContentsObserverAndroid::DidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc,
+ RenderViewHost* render_view_host) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, validated_url.spec()));
+ Java_WebContentsObserverAndroid_didStartProvisionalLoadForFrame(
+ env, obj.obj(), frame_id, parent_frame_id, is_main_frame,
+ jstring_url.obj(), is_error_page, is_iframe_srcdoc);
+}
+
+void WebContentsObserverAndroid::DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, url.spec()));
+ Java_WebContentsObserverAndroid_didCommitProvisionalLoadForFrame(
+ env, obj.obj(), frame_id, is_main_frame, jstring_url.obj(),
+ transition_type);
+}
+
+void WebContentsObserverAndroid::DidFinishLoad(
+ int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ RenderViewHost* render_view_host) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, validated_url.spec()));
+ Java_WebContentsObserverAndroid_didFinishLoad(
+ env, obj.obj(), frame_id, jstring_url.obj(), is_main_frame);
+}
+
+void WebContentsObserverAndroid::DidChangeVisibleSSLState() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ Java_WebContentsObserverAndroid_didChangeVisibleSSLState(env, obj.obj());
+}
+
+void WebContentsObserverAndroid::DidFailLoadInternal(
+ bool is_provisional_load,
+ bool is_main_frame,
+ int error_code,
+ const string16& description,
+ const GURL& url) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jstring_error_description(
+ ConvertUTF16ToJavaString(env, description));
+ ScopedJavaLocalRef<jstring> jstring_url(
+ ConvertUTF8ToJavaString(env, url.spec()));
+
+ Java_WebContentsObserverAndroid_didFailLoad(
+ env, obj.obj(),
+ is_provisional_load,
+ is_main_frame,
+ error_code,
+ jstring_error_description.obj(), jstring_url.obj());
+}
+
+bool RegisterWebContentsObserverAndroid(JNIEnv* env) {
+ if (!HasClass(env, kWebContentsObserverAndroidClassPath)) {
+ DLOG(ERROR) << "Unable to find class WebContentsObserverAndroid!";
+ return false;
+ }
+ return RegisterNativesImpl(env);
+}
+} // namespace content
diff --git a/chromium/content/browser/android/web_contents_observer_android.h b/chromium/content/browser/android/web_contents_observer_android.h
new file mode 100644
index 00000000000..35e5526148c
--- /dev/null
+++ b/chromium/content/browser/android/web_contents_observer_android.h
@@ -0,0 +1,88 @@
+// 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 CONTENT_BROWSER_ANDROID_WEB_CONTENTS_OBSERVER_ANDROID_H_
+#define CONTENT_BROWSER_ANDROID_WEB_CONTENTS_OBSERVER_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/jni_helper.h"
+#include "base/basictypes.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/frame_navigate_params.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class RenderViewHost;
+class WebContents;
+
+// Extends WebContentsObserver for providing a public Java API for some of the
+// the calls it receives.
+class WebContentsObserverAndroid : public WebContentsObserver {
+ public:
+ WebContentsObserverAndroid(JNIEnv* env,
+ jobject obj,
+ WebContents* web_contents);
+ virtual ~WebContentsObserverAndroid();
+
+ void Destroy(JNIEnv* env, jobject obj);
+
+ private:
+ virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidFailProvisionalLoad(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidFailLoad(int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidNavigateMainFrame(const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE;
+ virtual void DidNavigateAnyFrame(const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE;
+ virtual void DidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidFinishLoad(int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+ virtual void DidChangeVisibleSSLState() OVERRIDE;
+
+ void DidFailLoadInternal(bool is_provisional_load,
+ bool is_main_frame,
+ int error_code,
+ const string16& description,
+ const GURL& url);
+
+ JavaObjectWeakGlobalRef weak_java_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsObserverAndroid);
+};
+
+bool RegisterWebContentsObserverAndroid(JNIEnv* env);
+} // namespace content
+
+#endif // CONTENT_BROWSER_ANDROID_WEB_CONTENTS_OBSERVER_ANDROID_H_
diff --git a/chromium/content/browser/appcache/OWNERS b/chromium/content/browser/appcache/OWNERS
new file mode 100644
index 00000000000..3723f40c276
--- /dev/null
+++ b/chromium/content/browser/appcache/OWNERS
@@ -0,0 +1 @@
+michaeln@chromium.org
diff --git a/chromium/content/browser/appcache/appcache_dispatcher_host.cc b/chromium/content/browser/appcache/appcache_dispatcher_host.cc
new file mode 100644
index 00000000000..b68a73d466e
--- /dev/null
+++ b/chromium/content/browser/appcache/appcache_dispatcher_host.cc
@@ -0,0 +1,230 @@
+// 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 "content/browser/appcache/appcache_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/common/appcache_messages.h"
+#include "content/public/browser/user_metrics.h"
+
+namespace content {
+
+AppCacheDispatcherHost::AppCacheDispatcherHost(
+ ChromeAppCacheService* appcache_service,
+ int process_id)
+ : appcache_service_(appcache_service),
+ frontend_proxy_(this),
+ process_id_(process_id) {
+}
+
+void AppCacheDispatcherHost::OnChannelConnected(int32 peer_pid) {
+ BrowserMessageFilter::OnChannelConnected(peer_pid);
+ if (appcache_service_.get()) {
+ backend_impl_.Initialize(
+ appcache_service_.get(), &frontend_proxy_, process_id_);
+ get_status_callback_ =
+ base::Bind(&AppCacheDispatcherHost::GetStatusCallback,
+ base::Unretained(this));
+ start_update_callback_ =
+ base::Bind(&AppCacheDispatcherHost::StartUpdateCallback,
+ base::Unretained(this));
+ swap_cache_callback_ =
+ base::Bind(&AppCacheDispatcherHost::SwapCacheCallback,
+ base::Unretained(this));
+ }
+}
+
+bool AppCacheDispatcherHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(AppCacheDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_RegisterHost, OnRegisterHost)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_UnregisterHost, OnUnregisterHost)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_SetSpawningHostId, OnSetSpawningHostId)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_GetResourceList, OnGetResourceList)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_SelectCache, OnSelectCache)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_SelectCacheForWorker,
+ OnSelectCacheForWorker)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_SelectCacheForSharedWorker,
+ OnSelectCacheForSharedWorker)
+ IPC_MESSAGE_HANDLER(AppCacheHostMsg_MarkAsForeignEntry,
+ OnMarkAsForeignEntry)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(AppCacheHostMsg_GetStatus, OnGetStatus)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(AppCacheHostMsg_StartUpdate, OnStartUpdate)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(AppCacheHostMsg_SwapCache, OnSwapCache)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+AppCacheDispatcherHost::~AppCacheDispatcherHost() {}
+
+void AppCacheDispatcherHost::BadMessageReceived() {
+ RecordAction(UserMetricsAction("BadMessageTerminate_ACDH"));
+ BrowserMessageFilter::BadMessageReceived();
+}
+
+void AppCacheDispatcherHost::OnRegisterHost(int host_id) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.RegisterHost(host_id)) {
+ BadMessageReceived();
+ }
+ }
+}
+
+void AppCacheDispatcherHost::OnUnregisterHost(int host_id) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.UnregisterHost(host_id)) {
+ BadMessageReceived();
+ }
+ }
+}
+
+void AppCacheDispatcherHost::OnSetSpawningHostId(
+ int host_id, int spawning_host_id) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.SetSpawningHostId(host_id, spawning_host_id))
+ BadMessageReceived();
+ }
+}
+
+void AppCacheDispatcherHost::OnSelectCache(
+ int host_id, const GURL& document_url,
+ int64 cache_document_was_loaded_from,
+ const GURL& opt_manifest_url) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.SelectCache(host_id,
+ document_url,
+ cache_document_was_loaded_from,
+ opt_manifest_url)) {
+ BadMessageReceived();
+ }
+ } else {
+ frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo());
+ }
+}
+
+void AppCacheDispatcherHost::OnSelectCacheForWorker(
+ int host_id, int parent_process_id, int parent_host_id) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.SelectCacheForWorker(
+ host_id, parent_process_id, parent_host_id)) {
+ BadMessageReceived();
+ }
+ } else {
+ frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo());
+ }
+}
+
+void AppCacheDispatcherHost::OnSelectCacheForSharedWorker(
+ int host_id, int64 appcache_id) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.SelectCacheForSharedWorker(host_id, appcache_id))
+ BadMessageReceived();
+ } else {
+ frontend_proxy_.OnCacheSelected(host_id, appcache::AppCacheInfo());
+ }
+}
+
+void AppCacheDispatcherHost::OnMarkAsForeignEntry(
+ int host_id, const GURL& document_url,
+ int64 cache_document_was_loaded_from) {
+ if (appcache_service_.get()) {
+ if (!backend_impl_.MarkAsForeignEntry(
+ host_id, document_url, cache_document_was_loaded_from)) {
+ BadMessageReceived();
+ }
+ }
+}
+
+void AppCacheDispatcherHost::OnGetResourceList(
+ int host_id, std::vector<appcache::AppCacheResourceInfo>* params) {
+ if (appcache_service_.get())
+ backend_impl_.GetResourceList(host_id, params);
+}
+
+void AppCacheDispatcherHost::OnGetStatus(int host_id, IPC::Message* reply_msg) {
+ if (pending_reply_msg_) {
+ BadMessageReceived();
+ delete reply_msg;
+ return;
+ }
+
+ pending_reply_msg_.reset(reply_msg);
+ if (appcache_service_.get()) {
+ if (!backend_impl_.GetStatusWithCallback(
+ host_id, get_status_callback_, reply_msg)) {
+ BadMessageReceived();
+ }
+ return;
+ }
+
+ GetStatusCallback(appcache::UNCACHED, reply_msg);
+}
+
+void AppCacheDispatcherHost::OnStartUpdate(int host_id,
+ IPC::Message* reply_msg) {
+ if (pending_reply_msg_) {
+ BadMessageReceived();
+ delete reply_msg;
+ return;
+ }
+
+ pending_reply_msg_.reset(reply_msg);
+ if (appcache_service_.get()) {
+ if (!backend_impl_.StartUpdateWithCallback(
+ host_id, start_update_callback_, reply_msg)) {
+ BadMessageReceived();
+ }
+ return;
+ }
+
+ StartUpdateCallback(false, reply_msg);
+}
+
+void AppCacheDispatcherHost::OnSwapCache(int host_id, IPC::Message* reply_msg) {
+ if (pending_reply_msg_) {
+ BadMessageReceived();
+ delete reply_msg;
+ return;
+ }
+
+ pending_reply_msg_.reset(reply_msg);
+ if (appcache_service_.get()) {
+ if (!backend_impl_.SwapCacheWithCallback(
+ host_id, swap_cache_callback_, reply_msg)) {
+ BadMessageReceived();
+ }
+ return;
+ }
+
+ SwapCacheCallback(false, reply_msg);
+}
+
+void AppCacheDispatcherHost::GetStatusCallback(
+ appcache::Status status, void* param) {
+ IPC::Message* reply_msg = reinterpret_cast<IPC::Message*>(param);
+ DCHECK_EQ(pending_reply_msg_.get(), reply_msg);
+ AppCacheHostMsg_GetStatus::WriteReplyParams(reply_msg, status);
+ Send(pending_reply_msg_.release());
+}
+
+void AppCacheDispatcherHost::StartUpdateCallback(bool result, void* param) {
+ IPC::Message* reply_msg = reinterpret_cast<IPC::Message*>(param);
+ DCHECK_EQ(pending_reply_msg_.get(), reply_msg);
+ AppCacheHostMsg_StartUpdate::WriteReplyParams(reply_msg, result);
+ Send(pending_reply_msg_.release());
+}
+
+void AppCacheDispatcherHost::SwapCacheCallback(bool result, void* param) {
+ IPC::Message* reply_msg = reinterpret_cast<IPC::Message*>(param);
+ DCHECK_EQ(pending_reply_msg_.get(), reply_msg);
+ AppCacheHostMsg_SwapCache::WriteReplyParams(reply_msg, result);
+ Send(pending_reply_msg_.release());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/appcache/appcache_dispatcher_host.h b/chromium/content/browser/appcache/appcache_dispatcher_host.h
new file mode 100644
index 00000000000..29ed69ecf8b
--- /dev/null
+++ b/chromium/content/browser/appcache/appcache_dispatcher_host.h
@@ -0,0 +1,81 @@
+// 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 CONTENT_BROWSER_APPCACHE_APPCACHE_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_APPCACHE_APPCACHE_DISPATCHER_HOST_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "content/browser/appcache/appcache_frontend_proxy.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "webkit/browser/appcache/appcache_backend_impl.h"
+
+namespace content {
+class ChromeAppCacheService;
+
+// Handles appcache related messages sent to the main browser process from
+// its child processes. There is a distinct host for each child process.
+// Messages are handled on the IO thread. The BrowserRenderProcessHost and
+// WorkerProcessHost create an instance and delegates calls to it.
+class AppCacheDispatcherHost : public BrowserMessageFilter {
+ public:
+ AppCacheDispatcherHost(ChromeAppCacheService* appcache_service,
+ int process_id);
+
+ // BrowserIOMessageFilter implementation
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ protected:
+ virtual ~AppCacheDispatcherHost();
+
+ // BrowserMessageFilter override.
+ virtual void BadMessageReceived() OVERRIDE;
+
+ private:
+ // IPC message handlers
+ void OnRegisterHost(int host_id);
+ void OnUnregisterHost(int host_id);
+ void OnSetSpawningHostId(int host_id, int spawning_host_id);
+ void OnSelectCache(int host_id, const GURL& document_url,
+ int64 cache_document_was_loaded_from,
+ const GURL& opt_manifest_url);
+ void OnSelectCacheForWorker(int host_id, int parent_process_id,
+ int parent_host_id);
+ void OnSelectCacheForSharedWorker(int host_id, int64 appcache_id);
+ void OnMarkAsForeignEntry(int host_id, const GURL& document_url,
+ int64 cache_document_was_loaded_from);
+ void OnGetStatus(int host_id, IPC::Message* reply_msg);
+ void OnStartUpdate(int host_id, IPC::Message* reply_msg);
+ void OnSwapCache(int host_id, IPC::Message* reply_msg);
+ void OnGetResourceList(
+ int host_id,
+ std::vector<appcache::AppCacheResourceInfo>* resource_infos);
+ void GetStatusCallback(appcache::Status status, void* param);
+ void StartUpdateCallback(bool result, void* param);
+ void SwapCacheCallback(bool result, void* param);
+
+
+ scoped_refptr<ChromeAppCacheService> appcache_service_;
+ AppCacheFrontendProxy frontend_proxy_;
+ appcache::AppCacheBackendImpl backend_impl_;
+
+ appcache::GetStatusCallback get_status_callback_;
+ appcache::StartUpdateCallback start_update_callback_;
+ appcache::SwapCacheCallback swap_cache_callback_;
+ scoped_ptr<IPC::Message> pending_reply_msg_;
+
+ // The corresponding ChildProcessHost object's id().
+ int process_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppCacheDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/appcache/appcache_frontend_proxy.cc b/chromium/content/browser/appcache/appcache_frontend_proxy.cc
new file mode 100644
index 00000000000..0ef1e32b1b8
--- /dev/null
+++ b/chromium/content/browser/appcache/appcache_frontend_proxy.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/appcache/appcache_frontend_proxy.h"
+
+#include "content/common/appcache_messages.h"
+
+namespace content {
+
+AppCacheFrontendProxy::AppCacheFrontendProxy(IPC::Sender* sender)
+ : sender_(sender) {
+}
+
+void AppCacheFrontendProxy::OnCacheSelected(
+ int host_id, const appcache::AppCacheInfo& info) {
+ sender_->Send(new AppCacheMsg_CacheSelected(host_id, info));
+}
+
+void AppCacheFrontendProxy::OnStatusChanged(const std::vector<int>& host_ids,
+ appcache::Status status) {
+ sender_->Send(new AppCacheMsg_StatusChanged(host_ids, status));
+}
+
+void AppCacheFrontendProxy::OnEventRaised(const std::vector<int>& host_ids,
+ appcache::EventID event_id) {
+ DCHECK_NE(appcache::PROGRESS_EVENT, event_id); // See OnProgressEventRaised.
+ sender_->Send(new AppCacheMsg_EventRaised(host_ids, event_id));
+}
+
+void AppCacheFrontendProxy::OnProgressEventRaised(
+ const std::vector<int>& host_ids,
+ const GURL& url, int num_total, int num_complete) {
+ sender_->Send(new AppCacheMsg_ProgressEventRaised(
+ host_ids, url, num_total, num_complete));
+}
+
+void AppCacheFrontendProxy::OnErrorEventRaised(
+ const std::vector<int>& host_ids,
+ const std::string& message) {
+ sender_->Send(new AppCacheMsg_ErrorEventRaised(
+ host_ids, message));
+}
+
+void AppCacheFrontendProxy::OnLogMessage(int host_id,
+ appcache::LogLevel log_level,
+ const std::string& message) {
+ sender_->Send(new AppCacheMsg_LogMessage(host_id, log_level, message));
+}
+
+void AppCacheFrontendProxy::OnContentBlocked(int host_id,
+ const GURL& manifest_url) {
+ sender_->Send(new AppCacheMsg_ContentBlocked(host_id, manifest_url));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/appcache/appcache_frontend_proxy.h b/chromium/content/browser/appcache/appcache_frontend_proxy.h
new file mode 100644
index 00000000000..2576e05d508
--- /dev/null
+++ b/chromium/content/browser/appcache/appcache_frontend_proxy.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_APPCACHE_APPCACHE_FRONTEND_PROXY_H_
+#define CONTENT_BROWSER_APPCACHE_APPCACHE_FRONTEND_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "ipc/ipc_sender.h"
+#include "webkit/common/appcache/appcache_interfaces.h"
+
+namespace content {
+
+// Sends appcache related messages to a child process.
+class AppCacheFrontendProxy : public appcache::AppCacheFrontend {
+ public:
+ explicit AppCacheFrontendProxy(IPC::Sender* sender);
+
+ // AppCacheFrontend methods
+ virtual void OnCacheSelected(int host_id,
+ const appcache::AppCacheInfo& info) OVERRIDE;
+ virtual void OnStatusChanged(const std::vector<int>& host_ids,
+ appcache::Status status) OVERRIDE;
+ virtual void OnEventRaised(const std::vector<int>& host_ids,
+ appcache::EventID event_id) OVERRIDE;
+ virtual void OnProgressEventRaised(const std::vector<int>& host_ids,
+ const GURL& url,
+ int num_total, int num_complete) OVERRIDE;
+ virtual void OnErrorEventRaised(const std::vector<int>& host_ids,
+ const std::string& message) OVERRIDE;
+ virtual void OnLogMessage(int host_id, appcache::LogLevel log_level,
+ const std::string& message) OVERRIDE;
+ virtual void OnContentBlocked(int host_id,
+ const GURL& manifest_url) OVERRIDE;
+
+ private:
+ IPC::Sender* sender_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_FRONTEND_PROXY_H_
diff --git a/chromium/content/browser/appcache/chrome_appcache_service.cc b/chromium/content/browser/appcache/chrome_appcache_service.cc
new file mode 100644
index 00000000000..1d913b938b5
--- /dev/null
+++ b/chromium/content/browser/appcache/chrome_appcache_service.cc
@@ -0,0 +1,79 @@
+// 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 "content/browser/appcache/chrome_appcache_service.h"
+
+#include "base/files/file_path.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/resource_context.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "webkit/browser/appcache/appcache_storage_impl.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+namespace content {
+
+ChromeAppCacheService::ChromeAppCacheService(
+ quota::QuotaManagerProxy* quota_manager_proxy)
+ : AppCacheService(quota_manager_proxy),
+ resource_context_(NULL) {
+}
+
+void ChromeAppCacheService::InitializeOnIOThread(
+ const base::FilePath& cache_path,
+ ResourceContext* resource_context,
+ net::URLRequestContextGetter* request_context_getter,
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ cache_path_ = cache_path;
+ resource_context_ = resource_context;
+
+ // The |request_context_getter| can be NULL in some unit tests.
+ //
+ // TODO(ajwong): TestProfile is difficult to work with. The
+ // SafeBrowsing tests require that GetRequestContext return NULL
+ // so we can't depend on having a non-NULL value here. See crbug/149783.
+ if (request_context_getter)
+ set_request_context(request_context_getter->GetURLRequestContext());
+
+ // Init our base class.
+ Initialize(
+ cache_path_,
+ BrowserThread::GetMessageLoopProxyForThread(
+ BrowserThread::FILE_USER_BLOCKING)
+ .get(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get());
+ set_appcache_policy(this);
+ set_special_storage_policy(special_storage_policy.get());
+}
+
+bool ChromeAppCacheService::CanLoadAppCache(const GURL& manifest_url,
+ const GURL& first_party) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // We don't prompt for read access.
+ return GetContentClient()->browser()->AllowAppCache(
+ manifest_url, first_party, resource_context_);
+}
+
+bool ChromeAppCacheService::CanCreateAppCache(
+ const GURL& manifest_url, const GURL& first_party) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return GetContentClient()->browser()->AllowAppCache(
+ manifest_url, first_party, resource_context_);
+}
+
+ChromeAppCacheService::~ChromeAppCacheService() {}
+
+void ChromeAppCacheService::DeleteOnCorrectThread() const {
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO) &&
+ !BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, this);
+ return;
+ }
+ delete this;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/appcache/chrome_appcache_service.h b/chromium/content/browser/appcache/chrome_appcache_service.h
new file mode 100644
index 00000000000..3c787e5360c
--- /dev/null
+++ b/chromium/content/browser/appcache/chrome_appcache_service.h
@@ -0,0 +1,85 @@
+// 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 CONTENT_BROWSER_APPCACHE_CHROME_APPCACHE_SERVICE_H_
+#define CONTENT_BROWSER_APPCACHE_CHROME_APPCACHE_SERVICE_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/content_export.h"
+#include "webkit/browser/appcache/appcache_policy.h"
+#include "webkit/browser/appcache/appcache_service.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+class ResourceContext;
+
+struct ChromeAppCacheServiceDeleter;
+
+// An AppCacheService subclass used by the chrome. There is an instance
+// associated with each BrowserContext. This derivation adds refcounting
+// semantics since a browser context has multiple URLRequestContexts which refer
+// to the same object, and those URLRequestContexts are refcounted independently
+// of the owning browser context.
+//
+// All methods, except the ctor, are expected to be called on
+// the IO thread (unless specifically called out in doc comments).
+//
+// TODO(dpranke): Fix dependencies on AppCacheService so that we don't have
+// to worry about clients calling AppCacheService methods.
+class CONTENT_EXPORT ChromeAppCacheService
+ : public base::RefCountedThreadSafe<ChromeAppCacheService,
+ ChromeAppCacheServiceDeleter>,
+ NON_EXPORTED_BASE(public appcache::AppCacheService),
+ NON_EXPORTED_BASE(public appcache::AppCachePolicy) {
+ public:
+ explicit ChromeAppCacheService(quota::QuotaManagerProxy* proxy);
+
+ void InitializeOnIOThread(
+ const base::FilePath& cache_path, // May be empty to use in-memory structs.
+ ResourceContext* resource_context,
+ net::URLRequestContextGetter* request_context_getter,
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy);
+
+ // AppCachePolicy overrides
+ virtual bool CanLoadAppCache(const GURL& manifest_url,
+ const GURL& first_party) OVERRIDE;
+ virtual bool CanCreateAppCache(const GURL& manifest_url,
+ const GURL& first_party) OVERRIDE;
+
+ protected:
+ virtual ~ChromeAppCacheService();
+
+ private:
+ friend class base::DeleteHelper<ChromeAppCacheService>;
+ friend class base::RefCountedThreadSafe<ChromeAppCacheService,
+ ChromeAppCacheServiceDeleter>;
+ friend struct ChromeAppCacheServiceDeleter;
+
+ void DeleteOnCorrectThread() const;
+
+ ResourceContext* resource_context_;
+ base::FilePath cache_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeAppCacheService);
+};
+
+struct ChromeAppCacheServiceDeleter {
+ static void Destruct(const ChromeAppCacheService* service) {
+ service->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_APPCACHE_CHROME_APPCACHE_SERVICE_H_
diff --git a/chromium/content/browser/appcache/chrome_appcache_service_unittest.cc b/chromium/content/browser/appcache/chrome_appcache_service_unittest.cc
new file mode 100644
index 00000000000..a4e895c6dd0
--- /dev/null
+++ b/chromium/content/browser/appcache/chrome_appcache_service_unittest.cc
@@ -0,0 +1,226 @@
+// 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 "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/test/test_browser_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/appcache/appcache_database.h"
+#include "webkit/browser/appcache/appcache_storage_impl.h"
+#include "webkit/browser/appcache/appcache_test_helper.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+
+#include <set>
+
+using appcache::AppCacheTestHelper;
+
+namespace content {
+namespace {
+const base::FilePath::CharType kTestingAppCacheDirname[] =
+ FILE_PATH_LITERAL("Application Cache");
+
+// Examples of a protected and an unprotected origin, to be used througout the
+// test.
+const char kProtectedManifest[] = "http://www.protected.com/cache.manifest";
+const char kNormalManifest[] = "http://www.normal.com/cache.manifest";
+const char kSessionOnlyManifest[] = "http://www.sessiononly.com/cache.manifest";
+
+class MockURLRequestContextGetter : public net::URLRequestContextGetter {
+ public:
+ MockURLRequestContextGetter(
+ net::URLRequestContext* context,
+ base::MessageLoopProxy* message_loop_proxy)
+ : context_(context), message_loop_proxy_(message_loop_proxy) {
+ }
+
+ virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE {
+ return context_;
+ }
+
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const OVERRIDE {
+ return message_loop_proxy_;
+ }
+
+ protected:
+ virtual ~MockURLRequestContextGetter() {}
+
+ private:
+ net::URLRequestContext* context_;
+ scoped_refptr<base::SingleThreadTaskRunner> message_loop_proxy_;
+};
+
+} // namespace
+
+class ChromeAppCacheServiceTest : public testing::Test {
+ public:
+ ChromeAppCacheServiceTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ kProtectedManifestURL(kProtectedManifest),
+ kNormalManifestURL(kNormalManifest),
+ kSessionOnlyManifestURL(kSessionOnlyManifest),
+ file_thread_(BrowserThread::FILE, &message_loop_),
+ file_user_blocking_thread_(BrowserThread::FILE_USER_BLOCKING,
+ &message_loop_),
+ cache_thread_(BrowserThread::CACHE, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_) {}
+
+ protected:
+ scoped_refptr<ChromeAppCacheService> CreateAppCacheService(
+ const base::FilePath& appcache_path,
+ bool init_storage);
+ void InsertDataIntoAppCache(ChromeAppCacheService* appcache_service);
+
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+ const GURL kProtectedManifestURL;
+ const GURL kNormalManifestURL;
+ const GURL kSessionOnlyManifestURL;
+
+ private:
+ BrowserThreadImpl file_thread_;
+ BrowserThreadImpl file_user_blocking_thread_;
+ BrowserThreadImpl cache_thread_;
+ BrowserThreadImpl io_thread_;
+ TestBrowserContext browser_context_;
+};
+
+scoped_refptr<ChromeAppCacheService>
+ChromeAppCacheServiceTest::CreateAppCacheService(
+ const base::FilePath& appcache_path,
+ bool init_storage) {
+ scoped_refptr<ChromeAppCacheService> appcache_service =
+ new ChromeAppCacheService(NULL);
+ scoped_refptr<quota::MockSpecialStoragePolicy> mock_policy =
+ new quota::MockSpecialStoragePolicy;
+ mock_policy->AddProtected(kProtectedManifestURL.GetOrigin());
+ mock_policy->AddSessionOnly(kSessionOnlyManifestURL.GetOrigin());
+ scoped_refptr<MockURLRequestContextGetter> mock_request_context_getter =
+ new MockURLRequestContextGetter(
+ browser_context_.GetResourceContext()->GetRequestContext(),
+ message_loop_.message_loop_proxy().get());
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ChromeAppCacheService::InitializeOnIOThread,
+ appcache_service.get(),
+ appcache_path,
+ browser_context_.GetResourceContext(),
+ mock_request_context_getter,
+ mock_policy));
+ // Steps needed to initialize the storage of AppCache data.
+ message_loop_.RunUntilIdle();
+ if (init_storage) {
+ appcache::AppCacheStorageImpl* storage =
+ static_cast<appcache::AppCacheStorageImpl*>(
+ appcache_service->storage());
+ storage->database_->db_connection();
+ storage->disk_cache();
+ message_loop_.RunUntilIdle();
+ }
+ return appcache_service;
+}
+
+void ChromeAppCacheServiceTest::InsertDataIntoAppCache(
+ ChromeAppCacheService* appcache_service) {
+ AppCacheTestHelper appcache_helper;
+ appcache_helper.AddGroupAndCache(appcache_service, kNormalManifestURL);
+ appcache_helper.AddGroupAndCache(appcache_service, kProtectedManifestURL);
+ appcache_helper.AddGroupAndCache(appcache_service, kSessionOnlyManifestURL);
+
+ // Verify that adding the data succeeded
+ std::set<GURL> origins;
+ appcache_helper.GetOriginsWithCaches(appcache_service, &origins);
+ ASSERT_EQ(3UL, origins.size());
+ ASSERT_TRUE(origins.find(kProtectedManifestURL.GetOrigin()) != origins.end());
+ ASSERT_TRUE(origins.find(kNormalManifestURL.GetOrigin()) != origins.end());
+ ASSERT_TRUE(origins.find(kSessionOnlyManifestURL.GetOrigin()) !=
+ origins.end());
+}
+
+TEST_F(ChromeAppCacheServiceTest, KeepOnDestruction) {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ base::FilePath appcache_path =
+ temp_dir_.path().Append(kTestingAppCacheDirname);
+
+ // Create a ChromeAppCacheService and insert data into it
+ scoped_refptr<ChromeAppCacheService> appcache_service =
+ CreateAppCacheService(appcache_path, true);
+ ASSERT_TRUE(base::PathExists(appcache_path));
+ ASSERT_TRUE(base::PathExists(appcache_path.AppendASCII("Index")));
+ InsertDataIntoAppCache(appcache_service.get());
+
+ // Test: delete the ChromeAppCacheService
+ appcache_service = NULL;
+ message_loop_.RunUntilIdle();
+
+ // Recreate the appcache (for reading the data back)
+ appcache_service = CreateAppCacheService(appcache_path, false);
+
+ // The directory is still there
+ ASSERT_TRUE(base::PathExists(appcache_path));
+
+ // The appcache data is also there, except the session-only origin.
+ AppCacheTestHelper appcache_helper;
+ std::set<GURL> origins;
+ appcache_helper.GetOriginsWithCaches(appcache_service.get(), &origins);
+ EXPECT_EQ(2UL, origins.size());
+ EXPECT_TRUE(origins.find(kProtectedManifestURL.GetOrigin()) != origins.end());
+ EXPECT_TRUE(origins.find(kNormalManifestURL.GetOrigin()) != origins.end());
+ EXPECT_TRUE(origins.find(kSessionOnlyManifestURL.GetOrigin()) ==
+ origins.end());
+
+ // Delete and let cleanup tasks run prior to returning.
+ appcache_service = NULL;
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(ChromeAppCacheServiceTest, SaveSessionState) {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ base::FilePath appcache_path =
+ temp_dir_.path().Append(kTestingAppCacheDirname);
+
+ // Create a ChromeAppCacheService and insert data into it
+ scoped_refptr<ChromeAppCacheService> appcache_service =
+ CreateAppCacheService(appcache_path, true);
+ ASSERT_TRUE(base::PathExists(appcache_path));
+ ASSERT_TRUE(base::PathExists(appcache_path.AppendASCII("Index")));
+ InsertDataIntoAppCache(appcache_service.get());
+
+ // Save session state. This should bypass the destruction-time deletion.
+ appcache_service->set_force_keep_session_state();
+
+ // Test: delete the ChromeAppCacheService
+ appcache_service = NULL;
+ message_loop_.RunUntilIdle();
+
+ // Recreate the appcache (for reading the data back)
+ appcache_service = CreateAppCacheService(appcache_path, false);
+
+ // The directory is still there
+ ASSERT_TRUE(base::PathExists(appcache_path));
+
+ // No appcache data was deleted.
+ AppCacheTestHelper appcache_helper;
+ std::set<GURL> origins;
+ appcache_helper.GetOriginsWithCaches(appcache_service.get(), &origins);
+ EXPECT_EQ(3UL, origins.size());
+ EXPECT_TRUE(origins.find(kProtectedManifestURL.GetOrigin()) != origins.end());
+ EXPECT_TRUE(origins.find(kNormalManifestURL.GetOrigin()) != origins.end());
+ EXPECT_TRUE(origins.find(kSessionOnlyManifestURL.GetOrigin()) !=
+ origins.end());
+
+ // Delete and let cleanup tasks run prior to returning.
+ appcache_service = NULL;
+ message_loop_.RunUntilIdle();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/OWNERS b/chromium/content/browser/aura/OWNERS
new file mode 100644
index 00000000000..3b61cda8291
--- /dev/null
+++ b/chromium/content/browser/aura/OWNERS
@@ -0,0 +1,4 @@
+piman@chromium.org
+danakj@chromium.org
+sievers@chromium.org
+jbauman@chromium.org
diff --git a/chromium/content/browser/aura/browser_compositor_output_surface.cc b/chromium/content/browser/aura/browser_compositor_output_surface.cc
new file mode 100644
index 00000000000..11fbe0c42a9
--- /dev/null
+++ b/chromium/content/browser/aura/browser_compositor_output_surface.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 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 "content/browser/aura/browser_compositor_output_surface.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_number_conversions.h"
+#include "cc/output/compositor_frame.h"
+#include "content/browser/aura/reflector_impl.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_switches.h"
+
+namespace content {
+
+BrowserCompositorOutputSurface::BrowserCompositorOutputSurface(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context,
+ int surface_id,
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map,
+ base::MessageLoopProxy* compositor_message_loop,
+ base::WeakPtr<ui::Compositor> compositor)
+ : OutputSurface(context.Pass()),
+ surface_id_(surface_id),
+ output_surface_map_(output_surface_map),
+ compositor_message_loop_(compositor_message_loop),
+ compositor_(compositor) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kUIMaxFramesPending)) {
+ std::string string_value = command_line->GetSwitchValueASCII(
+ switches::kUIMaxFramesPending);
+ int int_value;
+ if (base::StringToInt(string_value, &int_value))
+ capabilities_.max_frames_pending = int_value;
+ else
+ LOG(ERROR) << "Trouble parsing --" << switches::kUIMaxFramesPending;
+ }
+ capabilities_.adjust_deadline_for_parent = false;
+ DetachFromThread();
+}
+
+BrowserCompositorOutputSurface::~BrowserCompositorOutputSurface() {
+ DCHECK(CalledOnValidThread());
+ if (!HasClient())
+ return;
+ output_surface_map_->Remove(surface_id_);
+}
+
+bool BrowserCompositorOutputSurface::BindToClient(
+ cc::OutputSurfaceClient* client) {
+ DCHECK(CalledOnValidThread());
+
+ if (!OutputSurface::BindToClient(client))
+ return false;
+
+ output_surface_map_->AddWithID(this, surface_id_);
+ return true;
+}
+
+void BrowserCompositorOutputSurface::Reshape(gfx::Size size,
+ float scale_factor) {
+ OutputSurface::Reshape(size, scale_factor);
+ if (reflector_.get())
+ reflector_->OnReshape(size);
+}
+
+void BrowserCompositorOutputSurface::SwapBuffers(cc::CompositorFrame* frame) {
+ DCHECK(frame->gl_frame_data);
+
+ WebGraphicsContext3DCommandBufferImpl* command_buffer =
+ static_cast<WebGraphicsContext3DCommandBufferImpl*>(context3d());
+ CommandBufferProxyImpl* command_buffer_proxy =
+ command_buffer->GetCommandBufferProxy();
+ DCHECK(command_buffer_proxy);
+ context3d()->shallowFlushCHROMIUM();
+ command_buffer_proxy->SetLatencyInfo(frame->metadata.latency_info);
+
+ if (reflector_.get()) {
+ if (frame->gl_frame_data->sub_buffer_rect ==
+ gfx::Rect(frame->gl_frame_data->size))
+ reflector_->OnSwapBuffers();
+ else
+ reflector_->OnPostSubBuffer(frame->gl_frame_data->sub_buffer_rect);
+ }
+
+ OutputSurface::SwapBuffers(frame);
+}
+
+void BrowserCompositorOutputSurface::OnUpdateVSyncParameters(
+ base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(HasClient());
+ OnVSyncParametersChanged(timebase, interval);
+ compositor_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ui::Compositor::OnUpdateVSyncParameters,
+ compositor_, timebase, interval));
+}
+
+void BrowserCompositorOutputSurface::SetReflector(ReflectorImpl* reflector) {
+ reflector_ = reflector;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/browser_compositor_output_surface.h b/chromium/content/browser/aura/browser_compositor_output_surface.h
new file mode 100644
index 00000000000..4b891bdc2ef
--- /dev/null
+++ b/chromium/content/browser/aura/browser_compositor_output_surface.h
@@ -0,0 +1,57 @@
+// Copyright (c) 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 CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_H_
+#define CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_H_
+
+#include "base/id_map.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "cc/output/output_surface.h"
+
+namespace base { class MessageLoopProxy; }
+
+namespace ui { class Compositor; }
+
+namespace content {
+class ReflectorImpl;
+
+// Adapts a WebGraphicsContext3DCommandBufferImpl into a
+// cc::OutputSurface that also handles vsync parameter updates
+// arriving from the GPU process.
+class BrowserCompositorOutputSurface
+ : public cc::OutputSurface,
+ public base::NonThreadSafe {
+ public:
+ BrowserCompositorOutputSurface(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context,
+ int surface_id,
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map,
+ base::MessageLoopProxy* compositor_message_loop,
+ base::WeakPtr<ui::Compositor> compositor);
+
+ virtual ~BrowserCompositorOutputSurface();
+
+ // cc::OutputSurface implementation.
+ virtual bool BindToClient(cc::OutputSurfaceClient* client) OVERRIDE;
+ virtual void Reshape(gfx::Size size, float scale_factor) OVERRIDE;
+ virtual void SwapBuffers(cc::CompositorFrame* frame) OVERRIDE;
+
+ void OnUpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ void SetReflector(ReflectorImpl* reflector);
+
+ private:
+ int surface_id_;
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map_;
+
+ scoped_refptr<base::MessageLoopProxy> compositor_message_loop_;
+ base::WeakPtr<ui::Compositor> compositor_;
+ scoped_refptr<ReflectorImpl> reflector_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_H_
diff --git a/chromium/content/browser/aura/browser_compositor_output_surface_proxy.cc b/chromium/content/browser/aura/browser_compositor_output_surface_proxy.cc
new file mode 100644
index 00000000000..e86e5b8c69c
--- /dev/null
+++ b/chromium/content/browser/aura/browser_compositor_output_surface_proxy.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 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 "content/browser/aura/browser_compositor_output_surface_proxy.h"
+
+#include "base/bind.h"
+#include "content/browser/aura/browser_compositor_output_surface.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/common/gpu/gpu_messages.h"
+
+namespace content {
+
+BrowserCompositorOutputSurfaceProxy::BrowserCompositorOutputSurfaceProxy(
+ IDMap<BrowserCompositorOutputSurface>* surface_map)
+ : surface_map_(surface_map),
+ connected_to_gpu_process_host_id_(0) {}
+
+BrowserCompositorOutputSurfaceProxy::~BrowserCompositorOutputSurfaceProxy() {}
+
+void BrowserCompositorOutputSurfaceProxy::ConnectToGpuProcessHost(
+ base::SingleThreadTaskRunner* compositor_thread_task_runner) {
+ BrowserGpuChannelHostFactory* factory =
+ BrowserGpuChannelHostFactory::instance();
+
+ int gpu_process_host_id = factory->GpuProcessHostId();
+ if (connected_to_gpu_process_host_id_ == gpu_process_host_id)
+ return;
+
+ const uint32 kMessagesToFilter[] = { GpuHostMsg_UpdateVSyncParameters::ID };
+ factory->SetHandlerForControlMessages(
+ kMessagesToFilter,
+ arraysize(kMessagesToFilter),
+ base::Bind(&BrowserCompositorOutputSurfaceProxy::
+ OnMessageReceivedOnCompositorThread,
+ this),
+ compositor_thread_task_runner);
+ connected_to_gpu_process_host_id_ = gpu_process_host_id;
+}
+
+void BrowserCompositorOutputSurfaceProxy::OnMessageReceivedOnCompositorThread(
+ const IPC::Message& message) {
+ IPC_BEGIN_MESSAGE_MAP(BrowserCompositorOutputSurfaceProxy, message)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_UpdateVSyncParameters,
+ OnUpdateVSyncParametersOnCompositorThread);
+ IPC_END_MESSAGE_MAP()
+}
+
+void
+BrowserCompositorOutputSurfaceProxy::OnUpdateVSyncParametersOnCompositorThread(
+ int surface_id,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ BrowserCompositorOutputSurface* surface = surface_map_->Lookup(surface_id);
+ if (surface)
+ surface->OnUpdateVSyncParameters(timebase, interval);
+}
+} // namespace content
diff --git a/chromium/content/browser/aura/browser_compositor_output_surface_proxy.h b/chromium/content/browser/aura/browser_compositor_output_surface_proxy.h
new file mode 100644
index 00000000000..2a08d0a89dd
--- /dev/null
+++ b/chromium/content/browser/aura/browser_compositor_output_surface_proxy.h
@@ -0,0 +1,49 @@
+// Copyright (c) 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 CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_PROXY_H_
+#define CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_PROXY_H_
+
+#include "base/id_map.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace IPC { class Message; }
+
+namespace content {
+class BrowserCompositorOutputSurface;
+
+// Directs vsync updates to the appropriate BrowserCompositorOutputSurface.
+class BrowserCompositorOutputSurfaceProxy
+ : public base::RefCountedThreadSafe<BrowserCompositorOutputSurfaceProxy> {
+ public:
+ BrowserCompositorOutputSurfaceProxy(
+ IDMap<BrowserCompositorOutputSurface>* surface_map);
+
+ // Call this before each OutputSurface is created to ensure that the
+ // proxy is connected to the current host.
+ void ConnectToGpuProcessHost(
+ base::SingleThreadTaskRunner* compositor_thread_task_runner);
+
+ private:
+ friend class base::RefCountedThreadSafe<BrowserCompositorOutputSurfaceProxy>;
+ ~BrowserCompositorOutputSurfaceProxy();
+
+ void OnMessageReceivedOnCompositorThread(const IPC::Message& message);
+
+ void OnUpdateVSyncParametersOnCompositorThread(int surface_id,
+ base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ IDMap<BrowserCompositorOutputSurface>* surface_map_;
+ int connected_to_gpu_process_host_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserCompositorOutputSurfaceProxy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_BROWSER_COMPOSITOR_OUTPUT_SURFACE_PROXY_H_
diff --git a/chromium/content/browser/aura/gpu_process_transport_factory.cc b/chromium/content/browser/aura/gpu_process_transport_factory.cc
new file mode 100644
index 00000000000..eeb6f3bc578
--- /dev/null
+++ b/chromium/content/browser/aura/gpu_process_transport_factory.cc
@@ -0,0 +1,511 @@
+// Copyright (c) 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 "content/browser/aura/gpu_process_transport_factory.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/output_surface.h"
+#include "content/browser/aura/browser_compositor_output_surface.h"
+#include "content/browser/aura/browser_compositor_output_surface_proxy.h"
+#include "content/browser/aura/reflector_impl.h"
+#include "content/browser/aura/software_browser_compositor_output_surface.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/gpu/client/context_provider_command_buffer.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "content/common/gpu/client/gpu_channel_host.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/common/gpu/gpu_process_launch_causes.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_switches.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+#if defined(OS_WIN)
+#include "content/browser/aura/software_output_device_win.h"
+#include "ui/surface/accelerated_surface_win.h"
+#elif defined(USE_X11)
+#include "content/browser/aura/software_output_device_x11.h"
+#endif
+
+namespace content {
+
+struct GpuProcessTransportFactory::PerCompositorData {
+ int surface_id;
+ scoped_ptr<CompositorSwapClient> swap_client;
+#if defined(OS_WIN)
+ scoped_ptr<AcceleratedSurface> accelerated_surface;
+#endif
+ scoped_refptr<ReflectorImpl> reflector;
+};
+
+class OwnedTexture : public ui::Texture, ImageTransportFactoryObserver {
+ public:
+ OwnedTexture(WebKit::WebGraphicsContext3D* host_context,
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id)
+ : ui::Texture(true, size, device_scale_factor),
+ host_context_(host_context),
+ texture_id_(texture_id) {
+ ImageTransportFactory::GetInstance()->AddObserver(this);
+ }
+
+ // ui::Texture overrides:
+ virtual unsigned int PrepareTexture() OVERRIDE {
+ return texture_id_;
+ }
+
+ virtual WebKit::WebGraphicsContext3D* HostContext3D() OVERRIDE {
+ return host_context_;
+ }
+
+ // ImageTransportFactory overrides:
+ virtual void OnLostResources() OVERRIDE {
+ DeleteTexture();
+ }
+
+ protected:
+ virtual ~OwnedTexture() {
+ ImageTransportFactory::GetInstance()->RemoveObserver(this);
+ DeleteTexture();
+ }
+
+ protected:
+ void DeleteTexture() {
+ if (texture_id_) {
+ host_context_->deleteTexture(texture_id_);
+ texture_id_ = 0;
+ }
+ }
+
+ // A raw pointer. This |ImageTransportClientTexture| will be destroyed
+ // before the |host_context_| via
+ // |ImageTransportFactoryObserver::OnLostContext()| handlers.
+ WebKit::WebGraphicsContext3D* host_context_;
+ unsigned texture_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(OwnedTexture);
+};
+
+class ImageTransportClientTexture : public OwnedTexture {
+ public:
+ ImageTransportClientTexture(
+ WebKit::WebGraphicsContext3D* host_context,
+ float device_scale_factor)
+ : OwnedTexture(host_context,
+ gfx::Size(0, 0),
+ device_scale_factor,
+ host_context->createTexture()) {
+ }
+
+ virtual void Consume(const std::string& mailbox_name,
+ const gfx::Size& new_size) OVERRIDE {
+ DCHECK(mailbox_name.size() == GL_MAILBOX_SIZE_CHROMIUM);
+ mailbox_name_ = mailbox_name;
+ if (mailbox_name.empty())
+ return;
+
+ DCHECK(host_context_ && texture_id_);
+ host_context_->bindTexture(GL_TEXTURE_2D, texture_id_);
+ host_context_->consumeTextureCHROMIUM(
+ GL_TEXTURE_2D,
+ reinterpret_cast<const signed char*>(mailbox_name.c_str()));
+ size_ = new_size;
+ host_context_->shallowFlushCHROMIUM();
+ }
+
+ virtual std::string Produce() OVERRIDE {
+ return mailbox_name_;
+ }
+
+ protected:
+ virtual ~ImageTransportClientTexture() {}
+
+ private:
+ std::string mailbox_name_;
+ DISALLOW_COPY_AND_ASSIGN(ImageTransportClientTexture);
+};
+
+class CompositorSwapClient
+ : public base::SupportsWeakPtr<CompositorSwapClient>,
+ public WebGraphicsContext3DSwapBuffersClient {
+ public:
+ CompositorSwapClient(ui::Compositor* compositor,
+ GpuProcessTransportFactory* factory)
+ : compositor_(compositor),
+ factory_(factory) {
+ }
+
+ virtual ~CompositorSwapClient() {
+ }
+
+ virtual void OnViewContextSwapBuffersPosted() OVERRIDE {
+ compositor_->OnSwapBuffersPosted();
+ }
+
+ virtual void OnViewContextSwapBuffersComplete() OVERRIDE {
+ compositor_->OnSwapBuffersComplete();
+ }
+
+ virtual void OnViewContextSwapBuffersAborted() OVERRIDE {
+ // Recreating contexts directly from here causes issues, so post a task
+ // instead.
+ // TODO(piman): Fix the underlying issues.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CompositorSwapClient::OnLostContext, this->AsWeakPtr()));
+ }
+
+ private:
+ void OnLostContext() {
+ factory_->OnLostContext(compositor_);
+ // Note: previous line destroyed this. Don't access members from now on.
+ }
+
+ ui::Compositor* compositor_;
+ GpuProcessTransportFactory* factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositorSwapClient);
+};
+
+GpuProcessTransportFactory::GpuProcessTransportFactory()
+ : callback_factory_(this) {
+ output_surface_proxy_ = new BrowserCompositorOutputSurfaceProxy(
+ &output_surface_map_);
+}
+
+GpuProcessTransportFactory::~GpuProcessTransportFactory() {
+ DCHECK(per_compositor_data_.empty());
+
+ // Make sure the lost context callback doesn't try to run during destruction.
+ callback_factory_.InvalidateWeakPtrs();
+}
+
+scoped_ptr<WebGraphicsContext3DCommandBufferImpl>
+GpuProcessTransportFactory::CreateOffscreenCommandBufferContext() {
+ base::WeakPtr<WebGraphicsContext3DSwapBuffersClient> swap_client;
+ return CreateContextCommon(swap_client, 0);
+}
+
+scoped_ptr<cc::SoftwareOutputDevice> CreateSoftwareOutputDevice(
+ ui::Compositor* compositor) {
+#if defined(OS_WIN)
+ return scoped_ptr<cc::SoftwareOutputDevice>(new SoftwareOutputDeviceWin(
+ compositor));
+#elif defined(USE_X11)
+ return scoped_ptr<cc::SoftwareOutputDevice>(new SoftwareOutputDeviceX11(
+ compositor));
+#endif
+
+ NOTREACHED();
+ return scoped_ptr<cc::SoftwareOutputDevice>();
+}
+
+scoped_ptr<cc::OutputSurface> GpuProcessTransportFactory::CreateOutputSurface(
+ ui::Compositor* compositor) {
+ PerCompositorData* data = per_compositor_data_[compositor];
+ if (!data)
+ data = CreatePerCompositorData(compositor);
+
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context;
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (!command_line->HasSwitch(switches::kUIEnableSoftwareCompositing)) {
+ context =
+ CreateContextCommon(data->swap_client->AsWeakPtr(), data->surface_id);
+ }
+ if (!context) {
+ if (ui::Compositor::WasInitializedWithThread()) {
+ LOG(FATAL) << "Failed to create UI context, but can't use software "
+ " compositing with browser threaded compositing. Aborting.";
+ }
+
+ scoped_ptr<SoftwareBrowserCompositorOutputSurface> surface =
+ SoftwareBrowserCompositorOutputSurface::Create(
+ CreateSoftwareOutputDevice(compositor));
+ return surface.PassAs<cc::OutputSurface>();
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> compositor_thread_task_runner =
+ ui::Compositor::GetCompositorMessageLoop();
+ if (!compositor_thread_task_runner.get())
+ compositor_thread_task_runner = base::MessageLoopProxy::current();
+
+ // Here we know the GpuProcessHost has been set up, because we created a
+ // context.
+ output_surface_proxy_->ConnectToGpuProcessHost(
+ compositor_thread_task_runner.get());
+
+ scoped_ptr<BrowserCompositorOutputSurface> surface(
+ new BrowserCompositorOutputSurface(
+ context.PassAs<WebKit::WebGraphicsContext3D>(),
+ per_compositor_data_[compositor]->surface_id,
+ &output_surface_map_,
+ base::MessageLoopProxy::current().get(),
+ compositor->AsWeakPtr()));
+ if (data->reflector.get()) {
+ data->reflector->CreateSharedTexture();
+ data->reflector->AttachToOutputSurface(surface.get());
+ }
+
+ return surface.PassAs<cc::OutputSurface>();
+}
+
+scoped_refptr<ui::Reflector> GpuProcessTransportFactory::CreateReflector(
+ ui::Compositor* source,
+ ui::Layer* target) {
+ PerCompositorData* data = per_compositor_data_[source];
+ DCHECK(data);
+
+ if (data->reflector.get())
+ RemoveObserver(data->reflector.get());
+
+ data->reflector = new ReflectorImpl(
+ source, target, &output_surface_map_, data->surface_id);
+ AddObserver(data->reflector.get());
+ return data->reflector;
+}
+
+void GpuProcessTransportFactory::RemoveReflector(
+ scoped_refptr<ui::Reflector> reflector) {
+ ReflectorImpl* reflector_impl =
+ static_cast<ReflectorImpl*>(reflector.get());
+ PerCompositorData* data =
+ per_compositor_data_[reflector_impl->mirrored_compositor()];
+ DCHECK(data);
+ RemoveObserver(reflector_impl);
+ data->reflector->Shutdown();
+ data->reflector = NULL;
+}
+
+void GpuProcessTransportFactory::RemoveCompositor(ui::Compositor* compositor) {
+ PerCompositorDataMap::iterator it = per_compositor_data_.find(compositor);
+ if (it == per_compositor_data_.end())
+ return;
+ PerCompositorData* data = it->second;
+ DCHECK(data);
+ GpuSurfaceTracker::Get()->RemoveSurface(data->surface_id);
+ delete data;
+ per_compositor_data_.erase(it);
+ if (per_compositor_data_.empty())
+ gl_helper_.reset();
+}
+
+bool GpuProcessTransportFactory::DoesCreateTestContexts() { return false; }
+
+ui::ContextFactory* GpuProcessTransportFactory::AsContextFactory() {
+ return this;
+}
+
+gfx::GLSurfaceHandle GpuProcessTransportFactory::CreateSharedSurfaceHandle() {
+ CreateSharedContextLazy();
+ if (!shared_contexts_main_thread_ ||
+ !shared_contexts_main_thread_->Context3d())
+ return gfx::GLSurfaceHandle();
+ gfx::GLSurfaceHandle handle = gfx::GLSurfaceHandle(
+ gfx::kNullPluginWindow, gfx::TEXTURE_TRANSPORT);
+ handle.parent_gpu_process_id =
+ shared_contexts_main_thread_->Context3d()->GetGPUProcessID();
+ handle.parent_client_id =
+ shared_contexts_main_thread_->Context3d()->GetChannelID();
+ return handle;
+}
+
+void GpuProcessTransportFactory::DestroySharedSurfaceHandle(
+ gfx::GLSurfaceHandle surface) {}
+
+scoped_refptr<ui::Texture> GpuProcessTransportFactory::CreateTransportClient(
+ float device_scale_factor) {
+ if (!shared_contexts_main_thread_.get())
+ return NULL;
+ scoped_refptr<ImageTransportClientTexture> image(
+ new ImageTransportClientTexture(
+ shared_contexts_main_thread_->Context3d(),
+ device_scale_factor));
+ return image;
+}
+
+scoped_refptr<ui::Texture> GpuProcessTransportFactory::CreateOwnedTexture(
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id) {
+ if (!shared_contexts_main_thread_.get())
+ return NULL;
+ scoped_refptr<OwnedTexture> image(new OwnedTexture(
+ shared_contexts_main_thread_->Context3d(),
+ size,
+ device_scale_factor,
+ texture_id));
+ return image;
+}
+
+GLHelper* GpuProcessTransportFactory::GetGLHelper() {
+ if (!gl_helper_) {
+ CreateSharedContextLazy();
+ WebGraphicsContext3DCommandBufferImpl* context_for_main_thread =
+ shared_contexts_main_thread_->Context3d();
+ gl_helper_.reset(new GLHelper(context_for_main_thread));
+ }
+ return gl_helper_.get();
+}
+
+uint32 GpuProcessTransportFactory::InsertSyncPoint() {
+ if (!shared_contexts_main_thread_.get())
+ return 0;
+ return shared_contexts_main_thread_->Context3d()->insertSyncPoint();
+}
+
+void GpuProcessTransportFactory::WaitSyncPoint(uint32 sync_point) {
+ if (!shared_contexts_main_thread_.get())
+ return;
+ shared_contexts_main_thread_->Context3d()->waitSyncPoint(sync_point);
+}
+
+void GpuProcessTransportFactory::AddObserver(
+ ImageTransportFactoryObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void GpuProcessTransportFactory::RemoveObserver(
+ ImageTransportFactoryObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+scoped_refptr<cc::ContextProvider>
+GpuProcessTransportFactory::OffscreenContextProviderForMainThread() {
+ if (!shared_contexts_main_thread_.get() ||
+ shared_contexts_main_thread_->DestroyedOnMainThread()) {
+ shared_contexts_main_thread_ = ContextProviderCommandBuffer::Create(
+ base::Bind(&GpuProcessTransportFactory::
+ CreateOffscreenCommandBufferContext,
+ base::Unretained(this)));
+ if (shared_contexts_main_thread_) {
+ shared_contexts_main_thread_->SetLostContextCallback(base::Bind(
+ &GpuProcessTransportFactory::
+ OnLostMainThreadSharedContextInsideCallback,
+ callback_factory_.GetWeakPtr()));
+
+ if (!shared_contexts_main_thread_->BindToCurrentThread())
+ shared_contexts_main_thread_ = NULL;
+ }
+ }
+ return shared_contexts_main_thread_;
+}
+
+scoped_refptr<cc::ContextProvider>
+GpuProcessTransportFactory::OffscreenContextProviderForCompositorThread() {
+ if (!shared_contexts_compositor_thread_.get() ||
+ shared_contexts_compositor_thread_->DestroyedOnMainThread()) {
+ shared_contexts_compositor_thread_ = ContextProviderCommandBuffer::Create(
+ base::Bind(&GpuProcessTransportFactory::
+ CreateOffscreenCommandBufferContext,
+ base::Unretained(this)));
+ }
+ return shared_contexts_compositor_thread_;
+}
+
+void GpuProcessTransportFactory::OnLostContext(ui::Compositor* compositor) {
+ LOG(ERROR) << "Lost UI compositor context.";
+ PerCompositorData* data = per_compositor_data_[compositor];
+ DCHECK(data);
+
+ // Prevent callbacks from other contexts in the same share group from
+ // calling us again.
+ data->swap_client.reset(new CompositorSwapClient(compositor, this));
+ compositor->OnSwapBuffersAborted();
+}
+
+GpuProcessTransportFactory::PerCompositorData*
+GpuProcessTransportFactory::CreatePerCompositorData(
+ ui::Compositor* compositor) {
+ DCHECK(!per_compositor_data_[compositor]);
+
+ CreateSharedContextLazy();
+
+ gfx::AcceleratedWidget widget = compositor->widget();
+ GpuSurfaceTracker* tracker = GpuSurfaceTracker::Get();
+
+ PerCompositorData* data = new PerCompositorData;
+ data->surface_id = tracker->AddSurfaceForNativeWidget(widget);
+ data->swap_client.reset(new CompositorSwapClient(compositor, this));
+#if defined(OS_WIN)
+ if (GpuDataManagerImpl::GetInstance()->IsUsingAcceleratedSurface())
+ data->accelerated_surface.reset(new AcceleratedSurface(widget));
+#endif
+ tracker->SetSurfaceHandle(
+ data->surface_id,
+ gfx::GLSurfaceHandle(widget, gfx::NATIVE_DIRECT));
+
+ per_compositor_data_[compositor] = data;
+
+ return data;
+}
+
+scoped_ptr<WebGraphicsContext3DCommandBufferImpl>
+GpuProcessTransportFactory::CreateContextCommon(
+ const base::WeakPtr<WebGraphicsContext3DSwapBuffersClient>& swap_client,
+ int surface_id) {
+ if (!GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor())
+ return scoped_ptr<WebGraphicsContext3DCommandBufferImpl>();
+ WebKit::WebGraphicsContext3D::Attributes attrs;
+ attrs.shareResources = true;
+ attrs.depth = false;
+ attrs.stencil = false;
+ attrs.antialias = false;
+ attrs.noAutomaticFlushes = true;
+ GpuChannelHostFactory* factory = BrowserGpuChannelHostFactory::instance();
+ GURL url("chrome://gpu/GpuProcessTransportFactory::CreateContextCommon");
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context(
+ new WebGraphicsContext3DCommandBufferImpl(
+ surface_id,
+ url,
+ factory,
+ swap_client));
+ if (!context->InitializeWithDefaultBufferSizes(
+ attrs,
+ false,
+ CAUSE_FOR_GPU_LAUNCH_WEBGRAPHICSCONTEXT3DCOMMANDBUFFERIMPL_INITIALIZE))
+ return scoped_ptr<WebGraphicsContext3DCommandBufferImpl>();
+ return context.Pass();
+}
+
+void GpuProcessTransportFactory::CreateSharedContextLazy() {
+ scoped_refptr<cc::ContextProvider> provider =
+ OffscreenContextProviderForMainThread();
+}
+
+void GpuProcessTransportFactory::OnLostMainThreadSharedContextInsideCallback() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&GpuProcessTransportFactory::OnLostMainThreadSharedContext,
+ callback_factory_.GetWeakPtr()));
+}
+
+void GpuProcessTransportFactory::OnLostMainThreadSharedContext() {
+ LOG(ERROR) << "Lost UI shared context.";
+ // Keep old resources around while we call the observers, but ensure that
+ // new resources are created if needed.
+
+ scoped_refptr<ContextProviderCommandBuffer> old_contexts_main_thread =
+ shared_contexts_main_thread_;
+ shared_contexts_main_thread_ = NULL;
+
+ scoped_ptr<GLHelper> old_helper(gl_helper_.release());
+
+ FOR_EACH_OBSERVER(ImageTransportFactoryObserver,
+ observer_list_,
+ OnLostResources());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/gpu_process_transport_factory.h b/chromium/content/browser/aura/gpu_process_transport_factory.h
new file mode 100644
index 00000000000..56d3e55498a
--- /dev/null
+++ b/chromium/content/browser/aura/gpu_process_transport_factory.h
@@ -0,0 +1,106 @@
+// Copyright (c) 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 CONTENT_BROWSER_AURA_GPU_PROCESS_TRANSPORT_FACTORY_H_
+#define CONTENT_BROWSER_AURA_GPU_PROCESS_TRANSPORT_FACTORY_H_
+
+#include <map>
+
+#include "base/id_map.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "content/browser/aura/image_transport_factory.h"
+#include "ui/compositor/compositor.h"
+
+namespace content {
+class BrowserCompositorOutputSurface;
+class BrowserCompositorOutputSurfaceProxy;
+class CompositorSwapClient;
+class ContextProviderCommandBuffer;
+class ReflectorImpl;
+class WebGraphicsContext3DCommandBufferImpl;
+class WebGraphicsContext3DSwapBuffersClient;
+
+class GpuProcessTransportFactory
+ : public ui::ContextFactory,
+ public ImageTransportFactory {
+ public:
+ GpuProcessTransportFactory();
+
+ virtual ~GpuProcessTransportFactory();
+
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl>
+ CreateOffscreenCommandBufferContext();
+
+ // ui::ContextFactory implementation.
+ virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface(
+ ui::Compositor* compositor) OVERRIDE;
+ virtual scoped_refptr<ui::Reflector> CreateReflector(
+ ui::Compositor* source,
+ ui::Layer* target) OVERRIDE;
+ virtual void RemoveReflector(
+ scoped_refptr<ui::Reflector> reflector) OVERRIDE;
+ virtual void RemoveCompositor(ui::Compositor* compositor) OVERRIDE;
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForMainThread() OVERRIDE;
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForCompositorThread() OVERRIDE;
+ virtual bool DoesCreateTestContexts() OVERRIDE;
+
+ // ImageTransportFactory implementation.
+ virtual ui::ContextFactory* AsContextFactory() OVERRIDE;
+ virtual gfx::GLSurfaceHandle CreateSharedSurfaceHandle() OVERRIDE;
+ virtual void DestroySharedSurfaceHandle(
+ gfx::GLSurfaceHandle surface) OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CreateTransportClient(
+ float device_scale_factor) OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CreateOwnedTexture(
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id) OVERRIDE;
+ virtual GLHelper* GetGLHelper() OVERRIDE;
+ virtual uint32 InsertSyncPoint() OVERRIDE;
+ virtual void WaitSyncPoint(uint32 sync_point) OVERRIDE;
+ virtual void AddObserver(ImageTransportFactoryObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(
+ ImageTransportFactoryObserver* observer) OVERRIDE;
+
+ void OnLostContext(ui::Compositor* compositor);
+
+ private:
+ struct PerCompositorData;
+
+ PerCompositorData* CreatePerCompositorData(ui::Compositor* compositor);
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl> CreateContextCommon(
+ const base::WeakPtr<WebGraphicsContext3DSwapBuffersClient>& swap_client,
+ int surface_id);
+
+ void CreateSharedContextLazy();
+
+ void OnLostMainThreadSharedContextInsideCallback();
+ void OnLostMainThreadSharedContext();
+
+ typedef std::map<ui::Compositor*, PerCompositorData*> PerCompositorDataMap;
+ PerCompositorDataMap per_compositor_data_;
+ scoped_refptr<ContextProviderCommandBuffer> shared_contexts_main_thread_;
+ scoped_refptr<ContextProviderCommandBuffer>
+ shared_contexts_compositor_thread_;
+ scoped_ptr<GLHelper> gl_helper_;
+ ObserverList<ImageTransportFactoryObserver> observer_list_;
+ base::WeakPtrFactory<GpuProcessTransportFactory> callback_factory_;
+
+ // The contents of this map and its methods may only be used on the compositor
+ // thread.
+ IDMap<BrowserCompositorOutputSurface> output_surface_map_;
+
+ scoped_refptr<BrowserCompositorOutputSurfaceProxy> output_surface_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuProcessTransportFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_GPU_PROCESS_TRANSPORT_FACTORY_H_
diff --git a/chromium/content/browser/aura/image_transport_factory.cc b/chromium/content/browser/aura/image_transport_factory.cc
new file mode 100644
index 00000000000..28b04bf7ea4
--- /dev/null
+++ b/chromium/content/browser/aura/image_transport_factory.cc
@@ -0,0 +1,68 @@
+// 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 "content/browser/aura/image_transport_factory.h"
+
+#include "base/command_line.h"
+#include "content/browser/aura/gpu_process_transport_factory.h"
+#include "content/browser/aura/no_transport_image_transport_factory.h"
+#include "content/public/common/content_switches.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_switches.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/chromeos/chromeos_version.h"
+#endif
+
+namespace content {
+
+namespace {
+ImageTransportFactory* g_factory;
+}
+
+
+static bool UseTestContextAndTransportFactory() {
+#if defined(OS_CHROMEOS)
+ // If the test is running on the chromeos envrionment (such as
+ // device or vm bots), always use real contexts.
+ if (base::chromeos::IsRunningOnChromeOS())
+ return false;
+#endif
+
+ // Only used if the enable command line flag is used.
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (!command_line->HasSwitch(switches::kTestCompositor))
+ return false;
+
+ // The disable command line flag preempts the enable flag.
+ if (!command_line->HasSwitch(switches::kDisableTestCompositor))
+ return true;
+
+ return false;
+}
+
+// static
+void ImageTransportFactory::Initialize() {
+ if (UseTestContextAndTransportFactory()) {
+ g_factory =
+ new NoTransportImageTransportFactory(new ui::TestContextFactory);
+ } else {
+ g_factory = new GpuProcessTransportFactory;
+ }
+ ui::ContextFactory::SetInstance(g_factory->AsContextFactory());
+}
+
+// static
+void ImageTransportFactory::Terminate() {
+ ui::ContextFactory::SetInstance(NULL);
+ delete g_factory;
+ g_factory = NULL;
+}
+
+// static
+ImageTransportFactory* ImageTransportFactory::GetInstance() {
+ return g_factory;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/image_transport_factory.h b/chromium/content/browser/aura/image_transport_factory.h
new file mode 100644
index 00000000000..85fba7d5300
--- /dev/null
+++ b/chromium/content/browser/aura/image_transport_factory.h
@@ -0,0 +1,99 @@
+// 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 CONTENT_BROWSER_AURA_IMAGE_TRANSPORT_FACTORY_H_
+#define CONTENT_BROWSER_AURA_IMAGE_TRANSPORT_FACTORY_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace ui {
+class ContextFactory;
+class Texture;
+}
+
+namespace WebKit {
+class WebGraphicsContext3D;
+}
+
+namespace content {
+class GLHelper;
+
+// This class provides a way to get notified when surface handles get lost.
+class ImageTransportFactoryObserver {
+ public:
+ virtual ~ImageTransportFactoryObserver() {}
+
+ // Notifies that the surface handles generated by ImageTransportFactory were
+ // lost.
+ // When this is called, the old resources (e.g. shared context, GL helper)
+ // still exist, but are about to be destroyed. Getting a reference to those
+ // resources from the ImageTransportFactory (e.g. through GetGLHelper) will
+ // return newly recreated, valid resources.
+ virtual void OnLostResources() = 0;
+};
+
+// This class provides the interface for creating the support for the
+// cross-process image transport, both for creating the shared surface handle
+// (destination surface for the GPU process) and the transport client (logic for
+// using that surface as a texture). The factory is a process-wide singleton.
+class ImageTransportFactory {
+ public:
+ virtual ~ImageTransportFactory() {}
+
+ // Initialize the global transport factory.
+ static void Initialize();
+
+ // Terminates the global transport factory.
+ static void Terminate();
+
+ // Gets the factory instance.
+ static ImageTransportFactory* GetInstance();
+
+ // Gets the image transport factory as a context factory for the compositor.
+ virtual ui::ContextFactory* AsContextFactory() = 0;
+
+ // Creates a shared surface handle.
+ // Note: the handle may get lost at any time, a state that an
+ // ImageTransportFactoryObserver gets notified of.
+ virtual gfx::GLSurfaceHandle CreateSharedSurfaceHandle() = 0;
+
+ // Destroys a shared surface handle.
+ virtual void DestroySharedSurfaceHandle(gfx::GLSurfaceHandle surface) = 0;
+
+ // Creates a transport texture for a given scale factor.
+ virtual scoped_refptr<ui::Texture> CreateTransportClient(
+ float device_scale_factor) = 0;
+
+ // Variant of CreateTransportClient() that deletes the texture on the GPU when
+ // the returned value is deleted.
+ virtual scoped_refptr<ui::Texture> CreateOwnedTexture(
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id) = 0;
+
+ // Gets a GLHelper instance, associated with the shared context. This
+ // GLHelper will get destroyed whenever the shared context is lost
+ // (ImageTransportFactoryObserver::OnLostResources is called).
+ virtual GLHelper* GetGLHelper() = 0;
+
+ // Inserts a SyncPoint into the shared context.
+ virtual uint32 InsertSyncPoint() = 0;
+
+ // Blocks waiting for the sync point on the service side.
+ virtual void WaitSyncPoint(uint32 sync_point) = 0;
+
+ virtual void AddObserver(ImageTransportFactoryObserver* observer) = 0;
+ virtual void RemoveObserver(ImageTransportFactoryObserver* observer) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_IMAGE_TRANSPORT_FACTORY_H_
diff --git a/chromium/content/browser/aura/no_transport_image_transport_factory.cc b/chromium/content/browser/aura/no_transport_image_transport_factory.cc
new file mode 100644
index 00000000000..18e8dad9072
--- /dev/null
+++ b/chromium/content/browser/aura/no_transport_image_transport_factory.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 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 "content/browser/aura/no_transport_image_transport_factory.h"
+#include "ui/compositor/compositor.h"
+
+namespace content {
+
+NoTransportImageTransportFactory::NoTransportImageTransportFactory(
+ ui::ContextFactory* context_factory)
+ : context_factory_(context_factory) {}
+
+NoTransportImageTransportFactory::~NoTransportImageTransportFactory() {}
+
+ui::ContextFactory* NoTransportImageTransportFactory::AsContextFactory() {
+ return context_factory_.get();
+}
+
+gfx::GLSurfaceHandle
+NoTransportImageTransportFactory::CreateSharedSurfaceHandle() {
+ return gfx::GLSurfaceHandle();
+}
+
+void NoTransportImageTransportFactory::DestroySharedSurfaceHandle(
+ gfx::GLSurfaceHandle surface) {}
+
+scoped_refptr<ui::Texture>
+NoTransportImageTransportFactory::CreateTransportClient(
+ float device_scale_factor) {
+ return NULL;
+}
+
+scoped_refptr<ui::Texture> NoTransportImageTransportFactory::CreateOwnedTexture(
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id) {
+ return NULL;
+}
+
+GLHelper* NoTransportImageTransportFactory::GetGLHelper() { return NULL; }
+
+uint32 NoTransportImageTransportFactory::InsertSyncPoint() { return 0; }
+
+void NoTransportImageTransportFactory::WaitSyncPoint(uint32 sync_point) {}
+
+// We don't generate lost context events, so we don't need to keep track of
+// observers
+void NoTransportImageTransportFactory::AddObserver(
+ ImageTransportFactoryObserver* observer) {}
+
+void NoTransportImageTransportFactory::RemoveObserver(
+ ImageTransportFactoryObserver* observer) {}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/no_transport_image_transport_factory.h b/chromium/content/browser/aura/no_transport_image_transport_factory.h
new file mode 100644
index 00000000000..8254faf5fa5
--- /dev/null
+++ b/chromium/content/browser/aura/no_transport_image_transport_factory.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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 CONTENT_BROWSER_AURA_NO_TRANSPORT_IMAGE_TRANSPORT_FACTORY_H_
+#define CONTENT_BROWSER_AURA_NO_TRANSPORT_IMAGE_TRANSPORT_FACTORY_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/aura/image_transport_factory.h"
+
+namespace content {
+
+// An ImageTransportFactory that disables transport.
+class NoTransportImageTransportFactory : public ImageTransportFactory {
+ public:
+ explicit NoTransportImageTransportFactory(
+ ui::ContextFactory* context_factory);
+ virtual ~NoTransportImageTransportFactory();
+
+ // ImageTransportFactory implementation.
+ virtual ui::ContextFactory* AsContextFactory() OVERRIDE;
+ virtual gfx::GLSurfaceHandle CreateSharedSurfaceHandle() OVERRIDE;
+ virtual void DestroySharedSurfaceHandle(gfx::GLSurfaceHandle surface)
+ OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CreateTransportClient(
+ float device_scale_factor) OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CreateOwnedTexture(
+ const gfx::Size& size,
+ float device_scale_factor,
+ unsigned int texture_id) OVERRIDE;
+ virtual GLHelper* GetGLHelper() OVERRIDE;
+ virtual uint32 InsertSyncPoint() OVERRIDE;
+ virtual void WaitSyncPoint(uint32 sync_point) OVERRIDE;
+ virtual void AddObserver(ImageTransportFactoryObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(ImageTransportFactoryObserver* observer) OVERRIDE;
+
+ private:
+ scoped_ptr<ui::ContextFactory> context_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NoTransportImageTransportFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_NO_TRANSPORT_IMAGE_TRANSPORT_FACTORY_H_
diff --git a/chromium/content/browser/aura/reflector_impl.cc b/chromium/content/browser/aura/reflector_impl.cc
new file mode 100644
index 00000000000..1e26cf8d18e
--- /dev/null
+++ b/chromium/content/browser/aura/reflector_impl.cc
@@ -0,0 +1,161 @@
+// Copyright (c) 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 "content/browser/aura/reflector_impl.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "content/browser/aura/browser_compositor_output_surface.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "ui/compositor/layer.h"
+
+namespace content {
+
+ReflectorImpl::ReflectorImpl(
+ ui::Compositor* mirrored_compositor,
+ ui::Layer* mirroring_layer,
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map,
+ int surface_id)
+ : texture_id_(0),
+ texture_size_(mirrored_compositor->size()),
+ output_surface_map_(output_surface_map),
+ mirrored_compositor_(mirrored_compositor),
+ mirroring_compositor_(mirroring_layer->GetCompositor()),
+ mirroring_layer_(mirroring_layer),
+ impl_message_loop_(ui::Compositor::GetCompositorMessageLoop()),
+ main_message_loop_(base::MessageLoopProxy::current()),
+ surface_id_(surface_id) {
+ CreateSharedTexture();
+ impl_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::InitOnImplThread, this));
+}
+
+void ReflectorImpl::InitOnImplThread() {
+ AttachToOutputSurface(output_surface_map_->Lookup(surface_id_));
+ gl_helper_->CopyTextureFullImage(texture_id_, texture_size_);
+ // The shared texture doesn't have the data, so invokes full redraw
+ // now.
+ main_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::FullRedrawContentOnMainThread,
+ scoped_refptr<ReflectorImpl>(this)));
+}
+
+void ReflectorImpl::Shutdown() {
+ mirroring_compositor_ = NULL;
+ mirroring_layer_ = NULL;
+ shared_texture_ = NULL;
+ impl_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::ShutdownOnImplThread, this));
+}
+
+void ReflectorImpl::ShutdownOnImplThread() {
+ BrowserCompositorOutputSurface* output_surface =
+ output_surface_map_->Lookup(surface_id_);
+ output_surface->SetReflector(NULL);
+ gl_helper_.reset();
+ // The instance must be deleted on main thread.
+ main_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::DeleteOnMainThread,
+ scoped_refptr<ReflectorImpl>(this)));
+}
+
+// This must be called on ImplThread, or before the surface is passed to
+// ImplThread.
+void ReflectorImpl::AttachToOutputSurface(
+ BrowserCompositorOutputSurface* output_surface) {
+ gl_helper_.reset(new GLHelper(output_surface->context3d()));
+ output_surface->SetReflector(this);
+}
+
+void ReflectorImpl::OnMirroringCompositorResized() {
+ mirroring_compositor_->ScheduleFullRedraw();
+}
+
+void ReflectorImpl::OnLostResources() {
+ shared_texture_ = NULL;
+ mirroring_layer_->SetExternalTexture(NULL);
+}
+
+void ReflectorImpl::OnReshape(gfx::Size size) {
+ if (texture_size_ == size)
+ return;
+ texture_size_ = size;
+ DCHECK(texture_id_);
+ gl_helper_->ResizeTexture(texture_id_, size);
+ main_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::UpdateTextureSizeOnMainThread,
+ this->AsWeakPtr(),
+ texture_size_));
+}
+
+void ReflectorImpl::OnSwapBuffers() {
+ DCHECK(texture_id_);
+ gl_helper_->CopyTextureFullImage(texture_id_, texture_size_);
+ main_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::FullRedrawOnMainThread,
+ this->AsWeakPtr(),
+ texture_size_));
+}
+
+void ReflectorImpl::OnPostSubBuffer(gfx::Rect rect) {
+ DCHECK(texture_id_);
+ gl_helper_->CopyTextureSubImage(texture_id_, rect);
+ main_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ReflectorImpl::UpdateSubBufferOnMainThread,
+ this->AsWeakPtr(),
+ texture_size_,
+ rect));
+}
+
+void ReflectorImpl::CreateSharedTexture() {
+ texture_id_ =
+ ImageTransportFactory::GetInstance()->GetGLHelper()->CreateTexture();
+ shared_texture_ =
+ ImageTransportFactory::GetInstance()->CreateOwnedTexture(
+ texture_size_, 1.0f, texture_id_);
+ mirroring_layer_->SetExternalTexture(shared_texture_.get());
+}
+
+ReflectorImpl::~ReflectorImpl() {
+ // Make sure the reflector is deleted on main thread.
+ DCHECK_EQ(main_message_loop_.get(),
+ base::MessageLoopProxy::current().get());
+}
+
+void ReflectorImpl::UpdateTextureSizeOnMainThread(gfx::Size size) {
+ if (!mirroring_layer_)
+ return;
+ mirroring_layer_->SetBounds(gfx::Rect(size));
+}
+
+void ReflectorImpl::FullRedrawOnMainThread(gfx::Size size) {
+ if (!mirroring_compositor_)
+ return;
+ UpdateTextureSizeOnMainThread(size);
+ mirroring_compositor_->ScheduleFullRedraw();
+}
+
+void ReflectorImpl::UpdateSubBufferOnMainThread(gfx::Size size,
+ gfx::Rect rect) {
+ if (!mirroring_compositor_)
+ return;
+ UpdateTextureSizeOnMainThread(size);
+ // Flip the coordinates to compositor's one.
+ int y = size.height() - rect.y() - rect.height();
+ gfx::Rect new_rect(rect.x(), y, rect.width(), rect.height());
+ mirroring_layer_->SchedulePaint(new_rect);
+}
+
+void ReflectorImpl::FullRedrawContentOnMainThread() {
+ mirrored_compositor_->ScheduleFullRedraw();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/reflector_impl.h b/chromium/content/browser/aura/reflector_impl.h
new file mode 100644
index 00000000000..b24acffd404
--- /dev/null
+++ b/chromium/content/browser/aura/reflector_impl.h
@@ -0,0 +1,122 @@
+// Copyright (c) 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 CONTENT_BROWSER_AURA_REFLECTOR_IMPL_H_
+#define CONTENT_BROWSER_AURA_REFLECTOR_IMPL_H_
+
+#include "base/id_map.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/aura/image_transport_factory.h"
+#include "ui/compositor/reflector.h"
+#include "ui/gfx/size.h"
+
+namespace base { class MessageLoopProxy; }
+
+namespace gfx { class Rect; }
+
+namespace ui {
+class Compositor;
+class Layer;
+}
+
+namespace content {
+
+class BrowserCompositorOutputSurface;
+
+// A reflector implementation that copies the framebuffer content
+// to the texture, then draw it onto the mirroring compositor.
+class ReflectorImpl : public ImageTransportFactoryObserver,
+ public base::SupportsWeakPtr<ReflectorImpl>,
+ public ui::Reflector {
+ public:
+ ReflectorImpl(
+ ui::Compositor* mirrored_compositor,
+ ui::Layer* mirroring_layer,
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map,
+ int surface_id);
+
+ ui::Compositor* mirrored_compositor() {
+ return mirrored_compositor_;
+ }
+
+ void InitOnImplThread();
+ void Shutdown();
+ void ShutdownOnImplThread();
+
+ // This must be called on ImplThread, or before the surface is passed to
+ // ImplThread.
+ void AttachToOutputSurface(BrowserCompositorOutputSurface* surface);
+
+ // ui::Reflector implementation.
+ virtual void OnMirroringCompositorResized() OVERRIDE;
+
+ // ImageTransportFactoryObsever implementation.
+ virtual void OnLostResources() OVERRIDE;
+
+ // Called when the output surface's size has changed.
+ // This must be called on ImplThread.
+ void OnReshape(gfx::Size size);
+
+ // Called in |BrowserCompositorOutputSurface::SwapBuffers| to copy
+ // the full screen image to the |texture_id_|. This must be called
+ // on ImplThread.
+ void OnSwapBuffers();
+
+ // Called in |BrowserCompositorOutputSurface::PostSubBuffer| copy
+ // the sub image given by |rect| to the texture.This must be called
+ // on ImplThread.
+ void OnPostSubBuffer(gfx::Rect rect);
+
+ // Create a shared texture that will be used to copy the content of
+ // mirrored compositor to the mirroring compositor. This must be
+ // called before the reflector is attached to OutputSurface to avoid
+ // race with ImplThread accessing |texture_id_|.
+ void CreateSharedTexture();
+
+
+ private:
+ virtual ~ReflectorImpl();
+
+ void UpdateTextureSizeOnMainThread(gfx::Size size);
+
+ // Request full redraw on mirroring compositor.
+ void FullRedrawOnMainThread(gfx::Size size);
+
+ void UpdateSubBufferOnMainThread(gfx::Size size, gfx::Rect rect);
+
+ // Request full redraw on mirrored compositor so that
+ // the full content will be copied to mirroring compositor.
+ void FullRedrawContentOnMainThread();
+
+ // This exists just to hold a reference to a ReflectorImpl in a post task,
+ // so the ReflectorImpl gets deleted when the function returns.
+ static void DeleteOnMainThread(scoped_refptr<ReflectorImpl> reflector) {}
+
+ // These variables are initialized on MainThread before
+ // the reflector is attached to the output surface. Once
+ // attached, they must be accessed only on ImplThraed unless
+ // the context is lost. When the context is lost, these
+ // will be re-ininitiailzed when the new output-surface
+ // is created on MainThread.
+ int texture_id_;
+ gfx::Size texture_size_;
+
+ // Must be accessed only on ImplThread.
+ IDMap<BrowserCompositorOutputSurface>* output_surface_map_;
+ scoped_ptr<GLHelper> gl_helper_;
+
+ // Must be accessed only on MainThread.
+ ui::Compositor* mirrored_compositor_;
+ ui::Compositor* mirroring_compositor_;
+ ui::Layer* mirroring_layer_;
+ scoped_refptr<ui::Texture> shared_texture_;
+ scoped_refptr<base::MessageLoopProxy> impl_message_loop_;
+ scoped_refptr<base::MessageLoopProxy> main_message_loop_;
+ int surface_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_REFLECTOR_IMPL_H_
diff --git a/chromium/content/browser/aura/software_browser_compositor_output_surface.cc b/chromium/content/browser/aura/software_browser_compositor_output_surface.cc
new file mode 100644
index 00000000000..c59806e83f9
--- /dev/null
+++ b/chromium/content/browser/aura/software_browser_compositor_output_surface.cc
@@ -0,0 +1,26 @@
+// 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 "content/browser/aura/software_browser_compositor_output_surface.h"
+
+#include "base/time/time.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/software_output_device.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "ui/base/latency_info.h"
+
+namespace content {
+
+SoftwareBrowserCompositorOutputSurface::SoftwareBrowserCompositorOutputSurface(
+ scoped_ptr<cc::SoftwareOutputDevice> software_device)
+ : cc::OutputSurface(software_device.Pass()) {}
+
+void SoftwareBrowserCompositorOutputSurface::SwapBuffers(
+ cc::CompositorFrame* frame) {
+ ui::LatencyInfo latency_info = frame->metadata.latency_info;
+ latency_info.swap_timestamp = base::TimeTicks::HighResNow();
+ RenderWidgetHostImpl::CompositorFrameDrawn(latency_info);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/software_browser_compositor_output_surface.h b/chromium/content/browser/aura/software_browser_compositor_output_surface.h
new file mode 100644
index 00000000000..ac499da5eb2
--- /dev/null
+++ b/chromium/content/browser/aura/software_browser_compositor_output_surface.h
@@ -0,0 +1,33 @@
+// 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 CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_SURFACE_H_
+#define CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_SURFACE_H_
+
+#include "cc/output/output_surface.h"
+
+namespace cc { class SoftwareOutputDevice; }
+
+namespace content {
+
+// TODO(danakj): Inherit from BrowserCompositorOutputSurface to share stuff like
+// reflectors, when we split the GL-specific stuff out of the class.
+class SoftwareBrowserCompositorOutputSurface : public cc::OutputSurface {
+ public:
+ static scoped_ptr<SoftwareBrowserCompositorOutputSurface> Create(
+ scoped_ptr<cc::SoftwareOutputDevice> software_device) {
+ return make_scoped_ptr(
+ new SoftwareBrowserCompositorOutputSurface(software_device.Pass()));
+ }
+
+ private:
+ explicit SoftwareBrowserCompositorOutputSurface(
+ scoped_ptr<cc::SoftwareOutputDevice> software_device);
+
+ virtual void SwapBuffers(cc::CompositorFrame* frame) OVERRIDE;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_SURFACE_H_
diff --git a/chromium/content/browser/aura/software_output_device_win.cc b/chromium/content/browser/aura/software_output_device_win.cc
new file mode 100644
index 00000000000..1a5af467b4e
--- /dev/null
+++ b/chromium/content/browser/aura/software_output_device_win.cc
@@ -0,0 +1,110 @@
+// 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 "content/browser/aura/software_output_device_win.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/compositor/compositor.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/gdi_util.h"
+#include "ui/gfx/skia_util.h"
+
+namespace content {
+
+SoftwareOutputDeviceWin::SoftwareOutputDeviceWin(ui::Compositor* compositor)
+ : hwnd_(compositor->widget()),
+ is_hwnd_composited_(false) {
+ // TODO(skaslev) Remove this when crbug.com/180702 is fixed.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ LONG style = GetWindowLong(hwnd_, GWL_EXSTYLE);
+ is_hwnd_composited_ = !!(style & WS_EX_COMPOSITED);
+}
+
+SoftwareOutputDeviceWin::~SoftwareOutputDeviceWin() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+void SoftwareOutputDeviceWin::Resize(gfx::Size viewport_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (viewport_size_ == viewport_size)
+ return;
+
+ viewport_size_ = viewport_size;
+ contents_.reset(new gfx::Canvas(viewport_size, ui::SCALE_FACTOR_100P, false));
+ memset(&bitmap_info_, 0, sizeof(bitmap_info_));
+ gfx::CreateBitmapHeader(viewport_size_.width(), viewport_size_.height(),
+ &bitmap_info_.bmiHeader);
+}
+
+SkCanvas* SoftwareOutputDeviceWin::BeginPaint(gfx::Rect damage_rect) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(contents_);
+
+ damage_rect_ = damage_rect;
+ return contents_ ? contents_->sk_canvas() : NULL;
+}
+
+void SoftwareOutputDeviceWin::EndPaint(cc::SoftwareFrameData* frame_data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(contents_);
+ DCHECK(frame_data);
+
+ if (!contents_)
+ return;
+
+ SoftwareOutputDevice::EndPaint(frame_data);
+
+ gfx::Rect rect = damage_rect_;
+ rect.Intersect(gfx::Rect(viewport_size_));
+ if (rect.IsEmpty())
+ return;
+
+ SkCanvas* canvas = contents_->sk_canvas();
+ DCHECK(canvas);
+ if (is_hwnd_composited_) {
+ RECT wr;
+ GetWindowRect(hwnd_, &wr);
+ SIZE size = {wr.right - wr.left, wr.bottom - wr.top};
+ POINT position = {wr.left, wr.top};
+ POINT zero = {0, 0};
+ BLENDFUNCTION blend = {AC_SRC_OVER, 0x00, 0xFF, AC_SRC_ALPHA};
+
+ DWORD style = GetWindowLong(hwnd_, GWL_EXSTYLE);
+ style &= ~WS_EX_COMPOSITED;
+ style |= WS_EX_LAYERED;
+ SetWindowLong(hwnd_, GWL_EXSTYLE, style);
+
+ HDC dib_dc = skia::BeginPlatformPaint(canvas);
+ ::UpdateLayeredWindow(hwnd_, NULL, &position, &size, dib_dc, &zero,
+ RGB(0xFF, 0xFF, 0xFF), &blend, ULW_ALPHA);
+ skia::EndPlatformPaint(canvas);
+ } else {
+ SkDevice* device = canvas->getDevice();
+ const SkBitmap& bitmap = device->accessBitmap(false);
+ HDC hdc = ::GetDC(hwnd_);
+ gfx::StretchDIBits(hdc,
+ rect.x(), rect.y(),
+ rect.width(), rect.height(),
+ rect.x(), rect.y(),
+ rect.width(), rect.height(),
+ bitmap.getPixels(),
+ &bitmap_info_);
+ ::ReleaseDC(hwnd_, hdc);
+ }
+}
+
+void SoftwareOutputDeviceWin::CopyToBitmap(
+ gfx::Rect rect, SkBitmap* output) {
+ DCHECK(contents_);
+ SkDevice* device = contents_->sk_canvas()->getDevice();
+ const SkBitmap& bitmap = device->accessBitmap(false);
+ bitmap.extractSubset(output, gfx::RectToSkIRect(rect));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/software_output_device_win.h b/chromium/content/browser/aura/software_output_device_win.h
new file mode 100644
index 00000000000..9c0655b6f67
--- /dev/null
+++ b/chromium/content/browser/aura/software_output_device_win.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_WIN_H_
+#define CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_WIN_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/output/software_output_device.h"
+
+#include <windows.h>
+
+namespace gfx {
+class Canvas;
+}
+
+namespace ui {
+class Compositor;
+}
+
+namespace content {
+
+class SoftwareOutputDeviceWin : public cc::SoftwareOutputDevice {
+ public:
+ explicit SoftwareOutputDeviceWin(ui::Compositor* compositor);
+ virtual ~SoftwareOutputDeviceWin();
+
+ virtual void Resize(gfx::Size viewport_size) OVERRIDE;
+ virtual SkCanvas* BeginPaint(gfx::Rect damage_rect) OVERRIDE;
+ virtual void EndPaint(cc::SoftwareFrameData* frame_data) OVERRIDE;
+ virtual void CopyToBitmap(gfx::Rect rect, SkBitmap* output) OVERRIDE;
+
+ private:
+ HWND hwnd_;
+ BITMAPINFO bitmap_info_;
+ scoped_ptr<gfx::Canvas> contents_;
+ bool is_hwnd_composited_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_WIN_H_
diff --git a/chromium/content/browser/aura/software_output_device_x11.cc b/chromium/content/browser/aura/software_output_device_x11.cc
new file mode 100644
index 00000000000..229755b6ffb
--- /dev/null
+++ b/chromium/content/browser/aura/software_output_device_x11.cc
@@ -0,0 +1,87 @@
+// 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 "content/browser/aura/software_output_device_x11.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "content/public/browser/browser_thread.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/compositor/compositor.h"
+
+namespace content {
+
+SoftwareOutputDeviceX11::SoftwareOutputDeviceX11(ui::Compositor* compositor)
+ : compositor_(compositor),
+ display_(ui::GetXDisplay()),
+ gc_(NULL),
+ image_(NULL) {
+ // TODO(skaslev) Remove this when crbug.com/180702 is fixed.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ gc_ = XCreateGC(display_, compositor_->widget(), 0, NULL);
+}
+
+SoftwareOutputDeviceX11::~SoftwareOutputDeviceX11() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ XFreeGC(display_, gc_);
+ ClearImage();
+}
+
+void SoftwareOutputDeviceX11::ClearImage() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (image_) {
+ // XDestroyImage deletes the data referenced by the image which
+ // is actually owned by the device_. So we have to reset data here.
+ image_->data = NULL;
+ XDestroyImage(image_);
+ image_ = NULL;
+ }
+}
+
+void SoftwareOutputDeviceX11::Resize(gfx::Size viewport_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ cc::SoftwareOutputDevice::Resize(viewport_size);
+
+ ClearImage();
+ if (!device_)
+ return;
+
+ const SkBitmap& bitmap = device_->accessBitmap(false);
+ image_ = XCreateImage(display_, CopyFromParent,
+ DefaultDepth(display_, DefaultScreen(display_)),
+ ZPixmap, 0,
+ static_cast<char*>(bitmap.getPixels()),
+ viewport_size_.width(), viewport_size_.height(),
+ 32, 4 * viewport_size_.width());
+}
+
+void SoftwareOutputDeviceX11::EndPaint(cc::SoftwareFrameData* frame_data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(device_);
+ DCHECK(frame_data);
+
+ if (!device_)
+ return;
+
+ SoftwareOutputDevice::EndPaint(frame_data);
+
+ gfx::Rect rect = damage_rect_;
+ rect.Intersect(gfx::Rect(viewport_size_));
+ if (rect.IsEmpty())
+ return;
+
+ // TODO(skaslev): Maybe switch XShmPutImage since it's async.
+ XPutImage(display_, compositor_->widget(), gc_, image_,
+ rect.x(), rect.y(),
+ rect.x(), rect.y(),
+ rect.width(), rect.height());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/aura/software_output_device_x11.h b/chromium/content/browser/aura/software_output_device_x11.h
new file mode 100644
index 00000000000..986a486d754
--- /dev/null
+++ b/chromium/content/browser/aura/software_output_device_x11.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_X11_H_
+#define CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_X11_H_
+
+#include "cc/output/software_output_device.h"
+#include "ui/base/x/x11_util.h"
+
+namespace ui {
+class Compositor;
+}
+
+namespace content {
+
+class SoftwareOutputDeviceX11 : public cc::SoftwareOutputDevice {
+ public:
+ explicit SoftwareOutputDeviceX11(ui::Compositor* compositor);
+
+ virtual ~SoftwareOutputDeviceX11();
+
+ virtual void Resize(gfx::Size viewport_size) OVERRIDE;
+
+ virtual void EndPaint(cc::SoftwareFrameData* frame_data) OVERRIDE;
+
+ private:
+ void ClearImage();
+
+ ui::Compositor* compositor_;
+ Display* display_;
+ GC gc_;
+ XImage* image_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_AURA_SOFTWARE_OUTPUT_DEVICE_X11_H_
diff --git a/chromium/content/browser/bookmarklet_browsertest.cc b/chromium/content/browser/bookmarklet_browsertest.cc
new file mode 100644
index 00000000000..02f419302a2
--- /dev/null
+++ b/chromium/content/browser/bookmarklet_browsertest.cc
@@ -0,0 +1,75 @@
+// 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 "base/strings/string_util.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class BookmarkletTest : public ContentBrowserTest {
+ public:
+ void NavigateToStartPage() {
+ NavigateToURL(shell(), GURL("data:text/html,start page"));
+ EXPECT_EQ("start page", GetBodyText());
+ }
+
+ std::string GetBodyText() {
+ std::string body_text;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send(document.body.innerText);",
+ &body_text));
+ return body_text;
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(BookmarkletTest, Redirect) {
+ NavigateToStartPage();
+
+ NavigateToURL(shell(), GURL(
+ "javascript:location.href='data:text/plain,SUCCESS'"));
+ EXPECT_EQ("SUCCESS", GetBodyText());
+}
+
+IN_PROC_BROWSER_TEST_F(BookmarkletTest, RedirectVoided) {
+ NavigateToStartPage();
+
+ // This test should be redundant with the Redirect test above. The point
+ // here is to emphasize that in either case the assignment to location during
+ // the evaluation of the script should suppress loading the script result.
+ // Here, because of the void() wrapping there is no script result.
+ NavigateToURL(shell(), GURL(
+ "javascript:void(location.href='data:text/plain,SUCCESS')"));
+ EXPECT_EQ("SUCCESS", GetBodyText());
+}
+
+// http://crbug.com/177957
+IN_PROC_BROWSER_TEST_F(BookmarkletTest, NonEmptyResult) {
+ NavigateToStartPage();
+ // If there's no navigation, javascript: URLs are run synchronously.
+ shell()->LoadURL(GURL("javascript:'hello world'"));
+
+ EXPECT_EQ("hello world", GetBodyText());
+}
+
+IN_PROC_BROWSER_TEST_F(BookmarkletTest, DocumentWrite) {
+ NavigateToStartPage();
+
+ // If there's no navigation, javascript: URLs are run synchronously.
+ shell()->LoadURL(GURL(
+ "javascript:document.open();"
+ "document.write('hello world');"
+ "document.close();"));
+ EXPECT_EQ("hello world", GetBodyText());
+}
+
+
+} // namespace content
+
diff --git a/chromium/content/browser/browser_child_process_host_impl.cc b/chromium/content/browser/browser_child_process_host_impl.cc
new file mode 100644
index 00000000000..780dc6a2d42
--- /dev/null
+++ b/chromium/content/browser/browser_child_process_host_impl.cc
@@ -0,0 +1,353 @@
+// 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 "content/browser/browser_child_process_host_impl.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/browser/histogram_message_filter.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/profiler_message_filter.h"
+#include "content/browser/tracing/trace_message_filter.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/browser_child_process_observer.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/result_codes.h"
+
+#if defined(OS_MACOSX)
+#include "content/browser/mach_broker_mac.h"
+#endif
+
+namespace content {
+namespace {
+
+static base::LazyInstance<BrowserChildProcessHostImpl::BrowserChildProcessList>
+ g_child_process_list = LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<ObserverList<BrowserChildProcessObserver> >
+ g_observers = LAZY_INSTANCE_INITIALIZER;
+
+void NotifyProcessHostConnected(const ChildProcessData& data) {
+ FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(),
+ BrowserChildProcessHostConnected(data));
+}
+
+void NotifyProcessHostDisconnected(const ChildProcessData& data) {
+ FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(),
+ BrowserChildProcessHostDisconnected(data));
+}
+
+void NotifyProcessCrashed(const ChildProcessData& data) {
+ FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(),
+ BrowserChildProcessCrashed(data));
+}
+
+} // namespace
+
+BrowserChildProcessHost* BrowserChildProcessHost::Create(
+ int process_type,
+ BrowserChildProcessHostDelegate* delegate) {
+ return new BrowserChildProcessHostImpl(process_type, delegate);
+}
+
+#if defined(OS_MACOSX)
+base::ProcessMetrics::PortProvider* BrowserChildProcessHost::GetPortProvider() {
+ return MachBroker::GetInstance();
+}
+#endif
+
+// static
+BrowserChildProcessHostImpl::BrowserChildProcessList*
+ BrowserChildProcessHostImpl::GetIterator() {
+ return g_child_process_list.Pointer();
+}
+
+// static
+void BrowserChildProcessHostImpl::AddObserver(
+ BrowserChildProcessObserver* observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ g_observers.Get().AddObserver(observer);
+}
+
+// static
+void BrowserChildProcessHostImpl::RemoveObserver(
+ BrowserChildProcessObserver* observer) {
+ // TODO(phajdan.jr): Check thread after fixing http://crbug.com/167126.
+ g_observers.Get().RemoveObserver(observer);
+}
+
+BrowserChildProcessHostImpl::BrowserChildProcessHostImpl(
+ int process_type,
+ BrowserChildProcessHostDelegate* delegate)
+ : data_(process_type),
+ delegate_(delegate),
+ power_monitor_message_broadcaster_(this) {
+ data_.id = ChildProcessHostImpl::GenerateChildProcessUniqueId();
+
+ child_process_host_.reset(ChildProcessHost::Create(this));
+ child_process_host_->AddFilter(new TraceMessageFilter);
+ child_process_host_->AddFilter(new ProfilerMessageFilter(process_type));
+ child_process_host_->AddFilter(new HistogramMessageFilter());
+
+ g_child_process_list.Get().push_back(this);
+ GetContentClient()->browser()->BrowserChildProcessHostCreated(this);
+}
+
+BrowserChildProcessHostImpl::~BrowserChildProcessHostImpl() {
+ g_child_process_list.Get().remove(this);
+
+#if defined(OS_WIN)
+ DeleteProcessWaitableEvent(early_exit_watcher_.GetWatchedEvent());
+#endif
+}
+
+// static
+void BrowserChildProcessHostImpl::TerminateAll() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Make a copy since the BrowserChildProcessHost dtor mutates the original
+ // list.
+ BrowserChildProcessList copy = g_child_process_list.Get();
+ for (BrowserChildProcessList::iterator it = copy.begin();
+ it != copy.end(); ++it) {
+ delete (*it)->delegate(); // ~*HostDelegate deletes *HostImpl.
+ }
+}
+
+void BrowserChildProcessHostImpl::Launch(
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& environ,
+#endif
+ CommandLine* cmd_line) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ GetContentClient()->browser()->AppendExtraCommandLineSwitches(
+ cmd_line, data_.id);
+
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ static const char* kForwardSwitches[] = {
+ switches::kDisableLogging,
+ switches::kEnableDCHECK,
+ switches::kEnableLogging,
+ switches::kLoggingLevel,
+ switches::kTraceToConsole,
+ switches::kV,
+ switches::kVModule,
+#if defined(OS_POSIX)
+ switches::kChildCleanExit,
+#endif
+ };
+ cmd_line->CopySwitchesFrom(browser_command_line, kForwardSwitches,
+ arraysize(kForwardSwitches));
+
+ child_process_.reset(new ChildProcessLauncher(
+#if defined(OS_WIN)
+ delegate,
+#elif defined(OS_POSIX)
+ use_zygote,
+ environ,
+ child_process_host_->TakeClientFileDescriptor(),
+#endif
+ cmd_line,
+ data_.id,
+ this));
+}
+
+const ChildProcessData& BrowserChildProcessHostImpl::GetData() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return data_;
+}
+
+ChildProcessHost* BrowserChildProcessHostImpl::GetHost() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return child_process_host_.get();
+}
+
+base::ProcessHandle BrowserChildProcessHostImpl::GetHandle() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(child_process_.get())
+ << "Requesting a child process handle before launching.";
+ DCHECK(child_process_->GetHandle())
+ << "Requesting a child process handle before launch has completed OK.";
+ return child_process_->GetHandle();
+}
+
+void BrowserChildProcessHostImpl::SetName(const string16& name) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ data_.name = name;
+}
+
+void BrowserChildProcessHostImpl::SetHandle(base::ProcessHandle handle) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ data_.handle = handle;
+}
+
+void BrowserChildProcessHostImpl::ForceShutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ g_child_process_list.Get().remove(this);
+ child_process_host_->ForceShutdown();
+}
+
+void BrowserChildProcessHostImpl::SetBackgrounded(bool backgrounded) {
+ child_process_->SetProcessBackgrounded(backgrounded);
+}
+
+void BrowserChildProcessHostImpl::SetTerminateChildOnShutdown(
+ bool terminate_on_shutdown) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ child_process_->SetTerminateChildOnShutdown(terminate_on_shutdown);
+}
+
+void BrowserChildProcessHostImpl::NotifyProcessInstanceCreated(
+ const ChildProcessData& data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(),
+ BrowserChildProcessInstanceCreated(data));
+}
+
+base::TerminationStatus BrowserChildProcessHostImpl::GetTerminationStatus(
+ int* exit_code) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!child_process_) // If the delegate doesn't use Launch() helper.
+ return base::GetTerminationStatus(data_.handle, exit_code);
+ return child_process_->GetChildTerminationStatus(false /* known_dead */,
+ exit_code);
+}
+
+bool BrowserChildProcessHostImpl::OnMessageReceived(
+ const IPC::Message& message) {
+ return delegate_->OnMessageReceived(message);
+}
+
+void BrowserChildProcessHostImpl::OnChannelConnected(int32 peer_pid) {
+#if defined(OS_WIN)
+ // From this point onward, the exit of the child process is detected by an
+ // error on the IPC channel.
+ DeleteProcessWaitableEvent(early_exit_watcher_.GetWatchedEvent());
+ early_exit_watcher_.StopWatching();
+#endif
+
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&NotifyProcessHostConnected, data_));
+
+ delegate_->OnChannelConnected(peer_pid);
+}
+
+void BrowserChildProcessHostImpl::OnChannelError() {
+ delegate_->OnChannelError();
+}
+
+bool BrowserChildProcessHostImpl::CanShutdown() {
+ return delegate_->CanShutdown();
+}
+
+void BrowserChildProcessHostImpl::OnChildDisconnected() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (child_process_.get() || data_.handle) {
+ DCHECK(data_.handle != base::kNullProcessHandle);
+ int exit_code;
+ base::TerminationStatus status = GetTerminationStatus(&exit_code);
+ switch (status) {
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: {
+ delegate_->OnProcessCrashed(exit_code);
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&NotifyProcessCrashed, data_));
+ UMA_HISTOGRAM_ENUMERATION("ChildProcess.Crashed2",
+ data_.process_type,
+ PROCESS_TYPE_MAX);
+ break;
+ }
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: {
+ delegate_->OnProcessCrashed(exit_code);
+ // Report that this child process was killed.
+ UMA_HISTOGRAM_ENUMERATION("ChildProcess.Killed2",
+ data_.process_type,
+ PROCESS_TYPE_MAX);
+ break;
+ }
+ case base::TERMINATION_STATUS_STILL_RUNNING: {
+ UMA_HISTOGRAM_ENUMERATION("ChildProcess.DisconnectedAlive2",
+ data_.process_type,
+ PROCESS_TYPE_MAX);
+ }
+ default:
+ break;
+ }
+ UMA_HISTOGRAM_ENUMERATION("ChildProcess.Disconnected2",
+ data_.process_type,
+ PROCESS_TYPE_MAX);
+ }
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&NotifyProcessHostDisconnected, data_));
+ delete delegate_; // Will delete us
+}
+
+bool BrowserChildProcessHostImpl::Send(IPC::Message* message) {
+ return child_process_host_->Send(message);
+}
+
+void BrowserChildProcessHostImpl::OnProcessLaunched() {
+ base::ProcessHandle handle = child_process_->GetHandle();
+ if (!handle) {
+ delete delegate_; // Will delete us
+ return;
+ }
+
+#if defined(OS_WIN)
+ // Start a WaitableEventWatcher that will invoke OnProcessExitedEarly if the
+ // child process exits. This watcher is stopped once the IPC channel is
+ // connected and the exit of the child process is detecter by an error on the
+ // IPC channel thereafter.
+ DCHECK(!early_exit_watcher_.GetWatchedEvent());
+ early_exit_watcher_.StartWatching(
+ new base::WaitableEvent(handle),
+ base::Bind(&BrowserChildProcessHostImpl::OnProcessExitedEarly,
+ base::Unretained(this)));
+#endif
+
+ data_.handle = handle;
+ delegate_->OnProcessLaunched();
+}
+
+#if defined(OS_WIN)
+
+void BrowserChildProcessHostImpl::DeleteProcessWaitableEvent(
+ base::WaitableEvent* event) {
+ if (!event)
+ return;
+
+ // The WaitableEvent does not own the process handle so ensure it does not
+ // close it.
+ event->Release();
+
+ delete event;
+}
+
+void BrowserChildProcessHostImpl::OnProcessExitedEarly(
+ base::WaitableEvent* event) {
+ DeleteProcessWaitableEvent(event);
+ OnChildDisconnected();
+}
+
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/browser_child_process_host_impl.h b/chromium/content/browser/browser_child_process_host_impl.h
new file mode 100644
index 00000000000..61971d6d978
--- /dev/null
+++ b/chromium/content/browser/browser_child_process_host_impl.h
@@ -0,0 +1,120 @@
+// 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 CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_IMPL_H_
+#define CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_IMPL_H_
+
+#include <list>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "base/synchronization/waitable_event_watcher.h"
+#include "content/browser/child_process_launcher.h"
+#include "content/browser/power_monitor_message_broadcaster.h"
+#include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/common/child_process_host_delegate.h"
+
+namespace content {
+
+class BrowserChildProcessHostIterator;
+class BrowserChildProcessObserver;
+
+// Plugins/workers and other child processes that live on the IO thread use this
+// class. RenderProcessHostImpl is the main exception that doesn't use this
+/// class because it lives on the UI thread.
+class CONTENT_EXPORT BrowserChildProcessHostImpl
+ : public BrowserChildProcessHost,
+ public NON_EXPORTED_BASE(ChildProcessHostDelegate),
+ public ChildProcessLauncher::Client {
+ public:
+ BrowserChildProcessHostImpl(
+ int process_type,
+ BrowserChildProcessHostDelegate* delegate);
+ virtual ~BrowserChildProcessHostImpl();
+
+ // Terminates all child processes and deletes each BrowserChildProcessHost
+ // instance.
+ static void TerminateAll();
+
+ // BrowserChildProcessHost implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+ virtual void Launch(
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& environ,
+#endif
+ CommandLine* cmd_line) OVERRIDE;
+ virtual const ChildProcessData& GetData() const OVERRIDE;
+ virtual ChildProcessHost* GetHost() const OVERRIDE;
+ virtual base::TerminationStatus GetTerminationStatus(int* exit_code) OVERRIDE;
+ virtual void SetName(const string16& name) OVERRIDE;
+ virtual void SetHandle(base::ProcessHandle handle) OVERRIDE;
+
+ // Returns the handle of the child process. This can be called only after
+ // OnProcessLaunched is called or it will be invalid and may crash.
+ base::ProcessHandle GetHandle() const;
+
+ // Removes this host from the host list. Calls ChildProcessHost::ForceShutdown
+ void ForceShutdown();
+
+ // Callers can reduce the BrowserChildProcess' priority.
+ void SetBackgrounded(bool backgrounded);
+
+ // Controls whether the child process should be terminated on browser
+ // shutdown. Default is to always terminate.
+ void SetTerminateChildOnShutdown(bool terminate_on_shutdown);
+
+ // Called when an instance of a particular child is created in a page.
+ static void NotifyProcessInstanceCreated(const ChildProcessData& data);
+
+ BrowserChildProcessHostDelegate* delegate() const { return delegate_; }
+
+ typedef std::list<BrowserChildProcessHostImpl*> BrowserChildProcessList;
+ private:
+ friend class BrowserChildProcessHostIterator;
+ friend class BrowserChildProcessObserver;
+
+ static BrowserChildProcessList* GetIterator();
+
+ static void AddObserver(BrowserChildProcessObserver* observer);
+ static void RemoveObserver(BrowserChildProcessObserver* observer);
+
+ // ChildProcessHostDelegate implementation:
+ virtual bool CanShutdown() OVERRIDE;
+ virtual void OnChildDisconnected() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ // ChildProcessLauncher::Client implementation.
+ virtual void OnProcessLaunched() OVERRIDE;
+
+#if defined(OS_WIN)
+ void DeleteProcessWaitableEvent(base::WaitableEvent* event);
+ void OnProcessExitedEarly(base::WaitableEvent* event);
+#endif
+
+ ChildProcessData data_;
+ BrowserChildProcessHostDelegate* delegate_;
+ scoped_ptr<ChildProcessHost> child_process_host_;
+
+ scoped_ptr<ChildProcessLauncher> child_process_;
+
+ PowerMonitorMessageBroadcaster power_monitor_message_broadcaster_;
+
+#if defined(OS_WIN)
+ // Watches to see if the child process exits before the IPC channel has
+ // been connected. Thereafter, its exit is determined by an error on the
+ // IPC channel.
+ base::WaitableEventWatcher early_exit_watcher_;
+#endif
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_IMPL_H_
diff --git a/chromium/content/browser/browser_context.cc b/chromium/content/browser/browser_context.cc
new file mode 100644
index 00000000000..2e76dc00ad5
--- /dev/null
+++ b/chromium/content/browser/browser_context.cc
@@ -0,0 +1,276 @@
+// 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 "content/public/browser/browser_context.h"
+
+#if !defined(OS_IOS)
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/browser/storage_partition_impl_map.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/site_instance.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "net/ssl/server_bound_cert_store.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "webkit/browser/database/database_tracker.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#endif // !OS_IOS
+
+using base::UserDataAdapter;
+
+namespace content {
+
+// Only ~BrowserContext() is needed on iOS.
+#if !defined(OS_IOS)
+namespace {
+
+// Key names on BrowserContext.
+const char kDownloadManagerKeyName[] = "download_manager";
+const char kMountPointsKey[] = "mount_points";
+const char kStorageParitionMapKeyName[] = "content_storage_partition_map";
+
+StoragePartitionImplMap* GetStoragePartitionMap(
+ BrowserContext* browser_context) {
+ StoragePartitionImplMap* partition_map =
+ static_cast<StoragePartitionImplMap*>(
+ browser_context->GetUserData(kStorageParitionMapKeyName));
+ if (!partition_map) {
+ partition_map = new StoragePartitionImplMap(browser_context);
+ browser_context->SetUserData(kStorageParitionMapKeyName, partition_map);
+ }
+ return partition_map;
+}
+
+StoragePartition* GetStoragePartitionFromConfig(
+ BrowserContext* browser_context,
+ const std::string& partition_domain,
+ const std::string& partition_name,
+ bool in_memory) {
+ StoragePartitionImplMap* partition_map =
+ GetStoragePartitionMap(browser_context);
+
+ if (browser_context->IsOffTheRecord())
+ in_memory = true;
+
+ return partition_map->Get(partition_domain, partition_name, in_memory);
+}
+
+// Run |callback| on each DOMStorageContextWrapper in |browser_context|.
+void PurgeDOMStorageContextInPartition(StoragePartition* storage_partition) {
+ static_cast<StoragePartitionImpl*>(storage_partition)->
+ GetDOMStorageContext()->PurgeMemory();
+}
+
+void SaveSessionStateOnIOThread(
+ const scoped_refptr<net::URLRequestContextGetter>& context_getter,
+ appcache::AppCacheService* appcache_service) {
+ net::URLRequestContext* context = context_getter->GetURLRequestContext();
+ context->cookie_store()->GetCookieMonster()->
+ SetForceKeepSessionState();
+ context->server_bound_cert_service()->GetCertStore()->
+ SetForceKeepSessionState();
+ appcache_service->set_force_keep_session_state();
+}
+
+void SaveSessionStateOnIndexedDBThread(
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context) {
+ indexed_db_context->SetForceKeepSessionState();
+}
+
+void PurgeMemoryOnIOThread(appcache::AppCacheService* appcache_service) {
+ appcache_service->PurgeMemory();
+}
+
+} // namespace
+
+// static
+void BrowserContext::AsyncObliterateStoragePartition(
+ BrowserContext* browser_context,
+ const GURL& site,
+ const base::Closure& on_gc_required) {
+ GetStoragePartitionMap(browser_context)->AsyncObliterate(site,
+ on_gc_required);
+}
+
+// static
+void BrowserContext::GarbageCollectStoragePartitions(
+ BrowserContext* browser_context,
+ scoped_ptr<base::hash_set<base::FilePath> > active_paths,
+ const base::Closure& done) {
+ GetStoragePartitionMap(browser_context)->GarbageCollect(
+ active_paths.Pass(), done);
+}
+
+DownloadManager* BrowserContext::GetDownloadManager(
+ BrowserContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!context->GetUserData(kDownloadManagerKeyName)) {
+ ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
+ DCHECK(rdh);
+ DownloadManager* download_manager =
+ new DownloadManagerImpl(
+ GetContentClient()->browser()->GetNetLog(), context);
+
+ context->SetUserData(
+ kDownloadManagerKeyName,
+ download_manager);
+ download_manager->SetDelegate(context->GetDownloadManagerDelegate());
+ }
+
+ return static_cast<DownloadManager*>(
+ context->GetUserData(kDownloadManagerKeyName));
+}
+
+// static
+fileapi::ExternalMountPoints* BrowserContext::GetMountPoints(
+ BrowserContext* context) {
+ // Ensure that these methods are called on the UI thread, except for
+ // unittests where a UI thread might not have been created.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
+
+#if defined(OS_CHROMEOS)
+ if (!context->GetUserData(kMountPointsKey)) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points =
+ fileapi::ExternalMountPoints::CreateRefCounted();
+ context->SetUserData(
+ kMountPointsKey,
+ new UserDataAdapter<fileapi::ExternalMountPoints>(mount_points.get()));
+ }
+
+ return UserDataAdapter<fileapi::ExternalMountPoints>::Get(
+ context, kMountPointsKey);
+#else
+ return NULL;
+#endif
+}
+
+StoragePartition* BrowserContext::GetStoragePartition(
+ BrowserContext* browser_context,
+ SiteInstance* site_instance) {
+ std::string partition_domain;
+ std::string partition_name;
+ bool in_memory = false;
+
+ // TODO(ajwong): After GetDefaultStoragePartition() is removed, get rid of
+ // this conditional and require that |site_instance| is non-NULL.
+ if (site_instance) {
+ GetContentClient()->browser()->GetStoragePartitionConfigForSite(
+ browser_context, site_instance->GetSiteURL(), true,
+ &partition_domain, &partition_name, &in_memory);
+ }
+
+ return GetStoragePartitionFromConfig(
+ browser_context, partition_domain, partition_name, in_memory);
+}
+
+StoragePartition* BrowserContext::GetStoragePartitionForSite(
+ BrowserContext* browser_context,
+ const GURL& site) {
+ std::string partition_domain;
+ std::string partition_name;
+ bool in_memory;
+
+ GetContentClient()->browser()->GetStoragePartitionConfigForSite(
+ browser_context, site, true, &partition_domain, &partition_name,
+ &in_memory);
+
+ return GetStoragePartitionFromConfig(
+ browser_context, partition_domain, partition_name, in_memory);
+}
+
+void BrowserContext::ForEachStoragePartition(
+ BrowserContext* browser_context,
+ const StoragePartitionCallback& callback) {
+ StoragePartitionImplMap* partition_map =
+ static_cast<StoragePartitionImplMap*>(
+ browser_context->GetUserData(kStorageParitionMapKeyName));
+ if (!partition_map)
+ return;
+
+ partition_map->ForEach(callback);
+}
+
+StoragePartition* BrowserContext::GetDefaultStoragePartition(
+ BrowserContext* browser_context) {
+ return GetStoragePartition(browser_context, NULL);
+}
+
+void BrowserContext::EnsureResourceContextInitialized(BrowserContext* context) {
+ // This will be enough to tickle initialization of BrowserContext if
+ // necessary, which initializes ResourceContext. The reason we don't call
+ // ResourceContext::InitializeResourceContext() directly here is that
+ // ResourceContext initialization may call back into BrowserContext
+ // and when that call returns it'll end rewriting its UserData map. It will
+ // end up rewriting the same value but this still causes a race condition.
+ //
+ // See http://crbug.com/115678.
+ GetDefaultStoragePartition(context);
+}
+
+void BrowserContext::SaveSessionState(BrowserContext* browser_context) {
+ GetDefaultStoragePartition(browser_context)->GetDatabaseTracker()->
+ SetForceKeepSessionState();
+ StoragePartition* storage_partition =
+ BrowserContext::GetDefaultStoragePartition(browser_context);
+
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &SaveSessionStateOnIOThread,
+ make_scoped_refptr(browser_context->GetRequestContext()),
+ storage_partition->GetAppCacheService()));
+ }
+
+ DOMStorageContextWrapper* dom_storage_context_proxy =
+ static_cast<DOMStorageContextWrapper*>(
+ storage_partition->GetDOMStorageContext());
+ dom_storage_context_proxy->SetForceKeepSessionState();
+
+ IndexedDBContextImpl* indexed_db_context_impl =
+ static_cast<IndexedDBContextImpl*>(
+ storage_partition->GetIndexedDBContext());
+ // No task runner in unit tests.
+ if (indexed_db_context_impl->TaskRunner()) {
+ indexed_db_context_impl->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&SaveSessionStateOnIndexedDBThread,
+ make_scoped_refptr(indexed_db_context_impl)));
+ }
+}
+
+void BrowserContext::PurgeMemory(BrowserContext* browser_context) {
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &PurgeMemoryOnIOThread,
+ BrowserContext::GetDefaultStoragePartition(browser_context)->
+ GetAppCacheService()));
+ }
+
+ ForEachStoragePartition(browser_context,
+ base::Bind(&PurgeDOMStorageContextInPartition));
+}
+
+#endif // !OS_IOS
+
+BrowserContext::~BrowserContext() {
+#if !defined(OS_IOS)
+ if (GetUserData(kDownloadManagerKeyName))
+ GetDownloadManager(this)->Shutdown();
+#endif
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_ipc_logging.cc b/chromium/content/browser/browser_ipc_logging.cc
new file mode 100644
index 00000000000..20a56f6f6d5
--- /dev/null
+++ b/chromium/content/browser/browser_ipc_logging.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/browser_ipc_logging.h"
+
+#include "base/bind.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "ipc/ipc_logging.h"
+
+namespace content {
+
+#if defined(IPC_MESSAGE_LOG_ENABLED)
+
+void EnableIPCLoggingForChildProcesses(bool enabled) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ BrowserChildProcessHostIterator i; // default constr references a singleton
+ while (!i.Done()) {
+ i.Send(new ChildProcessMsg_SetIPCLoggingEnabled(enabled));
+ ++i;
+ }
+}
+
+void EnableIPCLogging(bool enable) {
+ // First enable myself.
+ if (enable)
+ IPC::Logging::GetInstance()->Enable();
+ else
+ IPC::Logging::GetInstance()->Disable();
+
+ // Now tell subprocesses. Messages to ChildProcess-derived
+ // processes must be done on the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(EnableIPCLoggingForChildProcesses, enable));
+
+ // Finally, tell the renderers which don't derive from ChildProcess.
+ // Messages to the renderers must be done on the UI (main) thread.
+ for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance())
+ i.GetCurrentValue()->Send(new ChildProcessMsg_SetIPCLoggingEnabled(enable));
+}
+
+#endif // IPC_MESSAGE_LOG_ENABLED
+
+} // namespace content
diff --git a/chromium/content/browser/browser_main.cc b/chromium/content/browser/browser_main.cc
new file mode 100644
index 00000000000..640ccee5ac7
--- /dev/null
+++ b/chromium/content/browser/browser_main.cc
@@ -0,0 +1,35 @@
+// 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 "content/browser/browser_main.h"
+
+#include "base/debug/trace_event.h"
+#include "content/common/content_constants_internal.h"
+#include "content/public/browser/browser_main_runner.h"
+
+namespace content {
+
+// Main routine for running as the Browser process.
+int BrowserMain(const MainFunctionParams& parameters) {
+ TRACE_EVENT_BEGIN_ETW("BrowserMain", 0, "");
+ base::debug::TraceLog::GetInstance()->SetProcessName("Browser");
+ base::debug::TraceLog::GetInstance()->SetProcessSortIndex(
+ kTraceEventBrowserProcessSortIndex);
+
+ scoped_ptr<BrowserMainRunner> main_runner(BrowserMainRunner::Create());
+
+ int exit_code = main_runner->Initialize(parameters);
+ if (exit_code >= 0)
+ return exit_code;
+
+ exit_code = main_runner->Run();
+
+ main_runner->Shutdown();
+
+ TRACE_EVENT_END_ETW("BrowserMain", 0, 0);
+
+ return exit_code;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_main.h b/chromium/content/browser/browser_main.h
new file mode 100644
index 00000000000..59347787478
--- /dev/null
+++ b/chromium/content/browser/browser_main.h
@@ -0,0 +1,21 @@
+// 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 CONTENT_BROWSER_BROWSER_MAIN_H_
+#define CONTENT_BROWSER_BROWSER_MAIN_H_
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+struct MainFunctionParams;
+
+bool ExitedMainMessageLoop();
+
+CONTENT_EXPORT int BrowserMain(const content::MainFunctionParams& parameters);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_MAIN_H_
diff --git a/chromium/content/browser/browser_main_loop.cc b/chromium/content/browser/browser_main_loop.cc
new file mode 100644
index 00000000000..c51b5c59122
--- /dev/null
+++ b/chromium/content/browser/browser_main_loop.cc
@@ -0,0 +1,968 @@
+// 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 "content/browser/browser_main_loop.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/pending_task.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_device_source.h"
+#include "base/process/process_metrics.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/system_monitor/system_monitor.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/timer/hi_res_timer_manager.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/device_orientation/device_motion_service.h"
+#include "content/browser/download/save_file_manager.h"
+#include "content/browser/gamepad/gamepad_service.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/histogram_synchronizer.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/net/browser_online_state_observer.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/speech/speech_recognition_manager_impl.h"
+#include "content/browser/startup_task_runner.h"
+#include "content/browser/tracing/trace_controller_impl.h"
+#include "content/browser/webui/content_web_ui_controller_factory.h"
+#include "content/browser/webui/url_data_manager.h"
+#include "content/public/browser/browser_main_parts.h"
+#include "content/public/browser/browser_shutdown.h"
+#include "content/public/browser/compositor_util.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/main_function_params.h"
+#include "content/public/common/result_codes.h"
+#include "crypto/nss_util.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/media.h"
+#include "media/midi/midi_manager.h"
+#include "net/base/network_change_notifier.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/ssl/ssl_config_service.h"
+#include "ui/base/clipboard/clipboard.h"
+
+#if defined(USE_AURA)
+#include "content/browser/aura/image_transport_factory.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "content/browser/android/browser_startup_config.h"
+#include "content/browser/android/surface_texture_peer_browser_impl.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <commctrl.h>
+#include <shellapi.h>
+
+#include "base/win/text_services_message_filter.h"
+#include "content/browser/system_message_window_win.h"
+#include "content/common/sandbox_win.h"
+#include "net/base/winsock_init.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#endif
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+#include <glib-object.h>
+#endif
+
+#if defined(OS_LINUX)
+#include "content/browser/device_monitor_linux.h"
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include "content/browser/device_monitor_mac.h"
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include "ui/gfx/gtk_util.h"
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+#include <sys/stat.h>
+
+#include "content/browser/renderer_host/render_sandbox_host_linux.h"
+#include "content/browser/zygote_host/zygote_host_impl_linux.h"
+#endif
+
+#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED)
+#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
+#endif
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#endif
+
+// One of the linux specific headers defines this as a macro.
+#ifdef DestroyAll
+#undef DestroyAll
+#endif
+
+namespace content {
+namespace {
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+void SetupSandbox(const CommandLine& parsed_command_line) {
+ TRACE_EVENT0("startup", "SetupSandbox");
+ // TODO(evanm): move this into SandboxWrapper; I'm just trying to move this
+ // code en masse out of chrome_main for now.
+ base::FilePath sandbox_binary;
+ bool env_chrome_devel_sandbox_set = false;
+ struct stat st;
+
+ const bool want_setuid_sandbox =
+ !parsed_command_line.HasSwitch(switches::kNoSandbox) &&
+ !parsed_command_line.HasSwitch(switches::kDisableSetuidSandbox);
+
+ if (want_setuid_sandbox) {
+ base::FilePath exe_dir;
+ if (PathService::Get(base::DIR_EXE, &exe_dir)) {
+ base::FilePath sandbox_candidate = exe_dir.AppendASCII("chrome-sandbox");
+ if (base::PathExists(sandbox_candidate))
+ sandbox_binary = sandbox_candidate;
+ }
+
+ // In user-managed builds, including development builds, an environment
+ // variable is required to enable the sandbox. See
+ // http://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment
+ if (sandbox_binary.empty() &&
+ stat(base::kProcSelfExe, &st) == 0 && st.st_uid == getuid()) {
+ const char* devel_sandbox_path = getenv("CHROME_DEVEL_SANDBOX");
+ if (devel_sandbox_path) {
+ env_chrome_devel_sandbox_set = true;
+ sandbox_binary = base::FilePath(devel_sandbox_path);
+ }
+ }
+
+ static const char no_suid_error[] = "Running without the SUID sandbox! See "
+ "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment "
+ "for more information on developing with the sandbox on.";
+ if (sandbox_binary.empty()) {
+ if (!env_chrome_devel_sandbox_set) {
+ // This needs to be fatal. Talk to security@chromium.org if you feel
+ // otherwise.
+ LOG(FATAL) << no_suid_error;
+ }
+
+ // TODO(jln): an empty CHROME_DEVEL_SANDBOX environment variable (as
+ // opposed to a non existing one) is not fatal yet. This is needed
+ // because of existing bots and scripts. Fix it (crbug.com/245376).
+ LOG(ERROR) << no_suid_error;
+ }
+ }
+
+ // Tickle the sandbox host and zygote host so they fork now.
+ RenderSandboxHostLinux::GetInstance()->Init(sandbox_binary.value());
+ ZygoteHostImpl::GetInstance()->Init(sandbox_binary.value());
+}
+#endif
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+static void GLibLogHandler(const gchar* log_domain,
+ GLogLevelFlags log_level,
+ const gchar* message,
+ gpointer userdata) {
+ if (!log_domain)
+ log_domain = "<unknown>";
+ if (!message)
+ message = "<no message>";
+
+ if (strstr(message, "Loading IM context type") ||
+ strstr(message, "wrong ELF class: ELFCLASS64")) {
+ // http://crbug.com/9643
+ // Until we have a real 64-bit build or all of these 32-bit package issues
+ // are sorted out, don't fatal on ELF 32/64-bit mismatch warnings and don't
+ // spam the user with more than one of them.
+ static bool alerted = false;
+ if (!alerted) {
+ LOG(ERROR) << "Bug 9643: " << log_domain << ": " << message;
+ alerted = true;
+ }
+ } else if (strstr(message, "Unable to retrieve the file info for")) {
+ LOG(ERROR) << "GTK File code error: " << message;
+ } else if (strstr(message, "Could not find the icon") &&
+ strstr(log_domain, "Gtk")) {
+ LOG(ERROR) << "GTK icon error: " << message;
+ } else if (strstr(message, "Theme file for default has no") ||
+ strstr(message, "Theme directory") ||
+ strstr(message, "theme pixmap") ||
+ strstr(message, "locate theme engine")) {
+ LOG(ERROR) << "GTK theme error: " << message;
+ } else if (strstr(message, "Unable to create Ubuntu Menu Proxy") &&
+ strstr(log_domain, "<unknown>")) {
+ LOG(ERROR) << "GTK menu proxy create failed";
+ } else if (strstr(message, "gtk_drag_dest_leave: assertion")) {
+ LOG(ERROR) << "Drag destination deleted: http://crbug.com/18557";
+ } else if (strstr(message, "Out of memory") &&
+ strstr(log_domain, "<unknown>")) {
+ LOG(ERROR) << "DBus call timeout or out of memory: "
+ << "http://crosbug.com/15496";
+ } else if (strstr(message, "Could not connect: Connection refused") &&
+ strstr(log_domain, "<unknown>")) {
+ LOG(ERROR) << "DConf settings backend could not connect to session bus: "
+ << "http://crbug.com/179797";
+ } else if (strstr(message, "XDG_RUNTIME_DIR variable not set")) {
+ LOG(ERROR) << message << " (http://bugs.chromium.org/97293)";
+ } else if (strstr(message, "Attempting to store changes into") ||
+ strstr(message, "Attempting to set the permissions of")) {
+ LOG(ERROR) << message << " (http://bugs.chromium.org/161366)";
+ } else {
+ LOG(DFATAL) << log_domain << ": " << message;
+ }
+}
+
+static void SetUpGLibLogHandler() {
+ // Register GLib-handled assertions to go through our logging system.
+ const char* kLogDomains[] = { NULL, "Gtk", "Gdk", "GLib", "GLib-GObject" };
+ for (size_t i = 0; i < arraysize(kLogDomains); i++) {
+ g_log_set_handler(kLogDomains[i],
+ static_cast<GLogLevelFlags>(G_LOG_FLAG_RECURSION |
+ G_LOG_FLAG_FATAL |
+ G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL |
+ G_LOG_LEVEL_WARNING),
+ GLibLogHandler,
+ NULL);
+ }
+}
+#endif
+
+} // namespace
+
+// The currently-running BrowserMainLoop. There can be one or zero.
+BrowserMainLoop* g_current_browser_main_loop = NULL;
+
+// This is just to be able to keep ShutdownThreadsAndCleanUp out of
+// the public interface of BrowserMainLoop.
+class BrowserShutdownImpl {
+ public:
+ static void ImmediateShutdownAndExitProcess() {
+ DCHECK(g_current_browser_main_loop);
+ g_current_browser_main_loop->ShutdownThreadsAndCleanUp();
+
+#if defined(OS_WIN)
+ // At this point the message loop is still running yet we've shut everything
+ // down. If any messages are processed we'll likely crash. Exit now.
+ ExitProcess(RESULT_CODE_NORMAL_EXIT);
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+ _exit(RESULT_CODE_NORMAL_EXIT);
+#else
+ NOTIMPLEMENTED();
+#endif
+ }
+};
+
+void ImmediateShutdownAndExitProcess() {
+ BrowserShutdownImpl::ImmediateShutdownAndExitProcess();
+}
+
+// For measuring memory usage after each task. Behind a command line flag.
+class BrowserMainLoop::MemoryObserver : public base::MessageLoop::TaskObserver {
+ public:
+ MemoryObserver() {}
+ virtual ~MemoryObserver() {}
+
+ virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE {
+ }
+
+ virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {
+#if !defined(OS_IOS) // No ProcessMetrics on IOS.
+ scoped_ptr<base::ProcessMetrics> process_metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+#if defined(OS_MACOSX)
+ base::GetCurrentProcessHandle(), NULL));
+#else
+ base::GetCurrentProcessHandle()));
+#endif
+ size_t private_bytes;
+ process_metrics->GetMemoryBytes(&private_bytes, NULL);
+ HISTOGRAM_MEMORY_KB("Memory.BrowserUsed", private_bytes >> 10);
+#endif
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MemoryObserver);
+};
+
+
+// BrowserMainLoop construction / destruction =============================
+
+BrowserMainLoop* BrowserMainLoop::GetInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return g_current_browser_main_loop;
+}
+
+BrowserMainLoop::BrowserMainLoop(const MainFunctionParams& parameters)
+ : parameters_(parameters),
+ parsed_command_line_(parameters.command_line),
+ result_code_(RESULT_CODE_NORMAL_EXIT),
+ created_threads_(false) {
+ DCHECK(!g_current_browser_main_loop);
+ g_current_browser_main_loop = this;
+}
+
+BrowserMainLoop::~BrowserMainLoop() {
+ DCHECK_EQ(this, g_current_browser_main_loop);
+#if !defined(OS_IOS)
+ ui::Clipboard::DestroyClipboardForCurrentThread();
+#endif // !defined(OS_IOS)
+ g_current_browser_main_loop = NULL;
+}
+
+void BrowserMainLoop::Init() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Init")
+ parts_.reset(
+ GetContentClient()->browser()->CreateBrowserMainParts(parameters_));
+}
+
+// BrowserMainLoop stages ==================================================
+
+void BrowserMainLoop::EarlyInitialization() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::EarlyInitialization");
+#if defined(USE_X11)
+ if (parsed_command_line_.HasSwitch(switches::kSingleProcess) ||
+ parsed_command_line_.HasSwitch(switches::kInProcessGPU)) {
+ if (!XInitThreads()) {
+ LOG(ERROR) << "Failed to put Xlib into threaded mode.";
+ }
+ }
+#endif
+
+ if (parts_)
+ parts_->PreEarlyInitialization();
+
+#if defined(OS_WIN)
+ net::EnsureWinsockInit();
+#endif
+
+#if !defined(USE_OPENSSL)
+ // We want to be sure to init NSPR on the main thread.
+ crypto::EnsureNSPRInit();
+#endif // !defined(USE_OPENSSL)
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ SetupSandbox(parsed_command_line_);
+#endif
+
+ if (parsed_command_line_.HasSwitch(switches::kEnableSSLCachedInfo))
+ net::SSLConfigService::EnableCachedInfo();
+
+#if !defined(OS_IOS)
+ if (parsed_command_line_.HasSwitch(switches::kRendererProcessLimit)) {
+ std::string limit_string = parsed_command_line_.GetSwitchValueASCII(
+ switches::kRendererProcessLimit);
+ size_t process_limit;
+ if (base::StringToSizeT(limit_string, &process_limit)) {
+ RenderProcessHost::SetMaxRendererProcessCount(process_limit);
+ }
+ }
+#endif // !defined(OS_IOS)
+
+ if (parts_)
+ parts_->PostEarlyInitialization();
+}
+
+void BrowserMainLoop::MainMessageLoopStart() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::MainMessageLoopStart")
+ if (parts_) {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::MainMessageLoopStart:PreMainMessageLoopStart");
+ parts_->PreMainMessageLoopStart();
+ }
+
+#if defined(OS_WIN)
+ // If we're running tests (ui_task is non-null), then the ResourceBundle
+ // has already been initialized.
+ if (!parameters_.ui_task) {
+ // Override the configured locale with the user's preferred UI language.
+ l10n_util::OverrideLocaleWithUILanguageList();
+ }
+#endif
+
+ // Create a MessageLoop if one does not already exist for the current thread.
+ if (!base::MessageLoop::current())
+ main_message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_UI));
+
+ InitializeMainThread();
+
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:SystemMonitor")
+ system_monitor_.reset(new base::SystemMonitor);
+ }
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:PowerMonitor")
+ scoped_ptr<base::PowerMonitorSource> power_monitor_source(
+ new base::PowerMonitorDeviceSource());
+ power_monitor_.reset(new base::PowerMonitor(power_monitor_source.Pass()));
+ }
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:HighResTimerManager")
+ hi_res_timer_manager_.reset(new base::HighResolutionTimerManager);
+ }
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:NetworkChangeNotifier")
+ network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
+ }
+
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:MediaFeatures")
+ media::InitializeCPUSpecificMediaFeatures();
+ }
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:AudioMan")
+ audio_manager_.reset(media::AudioManager::Create());
+ }
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:MIDIManager")
+ midi_manager_.reset(media::MIDIManager::Create());
+ }
+
+#if !defined(OS_IOS)
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:ContentWebUIController")
+ WebUIControllerFactory::RegisterFactory(
+ ContentWebUIControllerFactory::GetInstance());
+ }
+
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:AudioMirroringManager")
+ audio_mirroring_manager_.reset(new AudioMirroringManager());
+ }
+
+ // Start tracing to a file if needed.
+ if (base::debug::TraceLog::GetInstance()->IsEnabled()) {
+ TRACE_EVENT0("startup", "BrowserMainLoop::InitStartupTracing")
+ TraceControllerImpl::GetInstance()->InitStartupTracing(
+ parsed_command_line_);
+ }
+
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:OnlineStateObserver")
+ online_state_observer_.reset(new BrowserOnlineStateObserver);
+ }
+#endif // !defined(OS_IOS)
+
+#if defined(OS_WIN)
+ system_message_window_.reset(new SystemMessageWindowWin);
+
+ if (base::win::IsTSFAwareRequired()) {
+ // Create a TSF message filter for the message loop. MessageLoop takes
+ // ownership of the filter.
+ scoped_ptr<base::win::TextServicesMessageFilter> tsf_message_filter(
+ new base::win::TextServicesMessageFilter);
+ if (tsf_message_filter->Init()) {
+ base::MessageLoopForUI::current()->SetMessageFilter(
+ tsf_message_filter.PassAs<base::MessageLoopForUI::MessageFilter>());
+ }
+ }
+#endif
+
+ if (parts_)
+ parts_->PostMainMessageLoopStart();
+
+#if defined(OS_ANDROID)
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:SurfaceTexturePeer")
+ SurfaceTexturePeer::InitInstance(new SurfaceTexturePeerBrowserImpl());
+ }
+#endif
+
+ if (parsed_command_line_.HasSwitch(switches::kMemoryMetrics)) {
+ TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:MemoryObserver")
+ memory_observer_.reset(new MemoryObserver());
+ base::MessageLoop::current()->AddTaskObserver(memory_observer_.get());
+ }
+
+#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED)
+ trace_memory_controller_.reset(new base::debug::TraceMemoryController(
+ base::MessageLoop::current()->message_loop_proxy(),
+ ::HeapProfilerWithPseudoStackStart,
+ ::HeapProfilerStop,
+ ::GetHeapProfile));
+#endif
+}
+
+int BrowserMainLoop::PreCreateThreads() {
+
+ if (parts_) {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::CreateThreads:PreCreateThreads");
+ result_code_ = parts_->PreCreateThreads();
+ }
+
+#if defined(ENABLE_PLUGINS)
+ // Prior to any processing happening on the io thread, we create the
+ // plugin service as it is predominantly used from the io thread,
+ // but must be created on the main thread. The service ctor is
+ // inexpensive and does not invoke the io_thread() accessor.
+ {
+ TRACE_EVENT0("startup", "BrowserMainLoop::CreateThreads:PluginService")
+ PluginService::GetInstance()->Init();
+ }
+#endif
+
+#if !defined(OS_IOS) && (!defined(GOOGLE_CHROME_BUILD) || defined(OS_ANDROID))
+ // Single-process is an unsupported and not fully tested mode, so
+ // don't enable it for official Chrome builds (except on Android).
+ if (parsed_command_line_.HasSwitch(switches::kSingleProcess))
+ RenderProcessHost::SetRunRendererInProcess(true);
+#endif
+ return result_code_;
+}
+
+void BrowserMainLoop::CreateStartupTasks() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::CreateStartupTasks")
+
+#if defined(OS_ANDROID)
+ scoped_refptr<StartupTaskRunner> task_runner =
+ new StartupTaskRunner(BrowserMayStartAsynchronously(),
+ base::Bind(&BrowserStartupComplete),
+ base::MessageLoop::current()->message_loop_proxy());
+#else
+ scoped_refptr<StartupTaskRunner> task_runner =
+ new StartupTaskRunner(false,
+ base::Callback<void(int)>(),
+ base::MessageLoop::current()->message_loop_proxy());
+#endif
+ StartupTask pre_create_threads =
+ base::Bind(&BrowserMainLoop::PreCreateThreads, base::Unretained(this));
+ task_runner->AddTask(pre_create_threads);
+
+ StartupTask create_threads =
+ base::Bind(&BrowserMainLoop::CreateThreads, base::Unretained(this));
+ task_runner->AddTask(create_threads);
+
+ StartupTask browser_thread_started = base::Bind(
+ &BrowserMainLoop::BrowserThreadsStarted, base::Unretained(this));
+ task_runner->AddTask(browser_thread_started);
+
+ StartupTask pre_main_message_loop_run = base::Bind(
+ &BrowserMainLoop::PreMainMessageLoopRun, base::Unretained(this));
+ task_runner->AddTask(pre_main_message_loop_run);
+
+ task_runner->StartRunningTasks();
+}
+
+int BrowserMainLoop::CreateThreads() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::CreateThreads");
+
+ base::Thread::Options default_options;
+ base::Thread::Options io_message_loop_options;
+ io_message_loop_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ base::Thread::Options ui_message_loop_options;
+ ui_message_loop_options.message_loop_type = base::MessageLoop::TYPE_UI;
+
+ // Start threads in the order they occur in the BrowserThread::ID
+ // enumeration, except for BrowserThread::UI which is the main
+ // thread.
+ //
+ // Must be size_t so we can increment it.
+ for (size_t thread_id = BrowserThread::UI + 1;
+ thread_id < BrowserThread::ID_COUNT;
+ ++thread_id) {
+ scoped_ptr<BrowserProcessSubThread>* thread_to_start = NULL;
+ base::Thread::Options* options = &default_options;
+
+ switch (thread_id) {
+ case BrowserThread::DB:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::DB");
+ thread_to_start = &db_thread_;
+ break;
+ case BrowserThread::FILE_USER_BLOCKING:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::FILE_USER_BLOCKING");
+ thread_to_start = &file_user_blocking_thread_;
+ break;
+ case BrowserThread::FILE:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::FILE");
+ thread_to_start = &file_thread_;
+#if defined(OS_WIN)
+ // On Windows, the FILE thread needs to be have a UI message loop
+ // which pumps messages in such a way that Google Update can
+ // communicate back to us.
+ options = &ui_message_loop_options;
+#else
+ options = &io_message_loop_options;
+#endif
+ break;
+ case BrowserThread::PROCESS_LAUNCHER:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::PROCESS_LAUNCHER");
+ thread_to_start = &process_launcher_thread_;
+ break;
+ case BrowserThread::CACHE:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::CACHE");
+ thread_to_start = &cache_thread_;
+ options = &io_message_loop_options;
+ break;
+ case BrowserThread::IO:
+ TRACE_EVENT_BEGIN1("startup",
+ "BrowserMainLoop::CreateThreads:start",
+ "Thread", "BrowserThread::IO");
+ thread_to_start = &io_thread_;
+ options = &io_message_loop_options;
+ break;
+ case BrowserThread::UI:
+ case BrowserThread::ID_COUNT:
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ BrowserThread::ID id = static_cast<BrowserThread::ID>(thread_id);
+
+ if (thread_to_start) {
+ (*thread_to_start).reset(new BrowserProcessSubThread(id));
+ (*thread_to_start)->StartWithOptions(*options);
+ } else {
+ NOTREACHED();
+ }
+
+ TRACE_EVENT_END0("startup", "BrowserMainLoop::CreateThreads:start");
+
+ }
+ created_threads_ = true;
+ return result_code_;
+}
+
+int BrowserMainLoop::PreMainMessageLoopRun() {
+ if (parts_) {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::CreateThreads:PreMainMessageLoopRun");
+ parts_->PreMainMessageLoopRun();
+ }
+
+ // If the UI thread blocks, the whole UI is unresponsive.
+ // Do not allow disk IO from the UI thread.
+ base::ThreadRestrictions::SetIOAllowed(false);
+ base::ThreadRestrictions::DisallowWaiting();
+ return result_code_;
+}
+
+void BrowserMainLoop::RunMainMessageLoopParts() {
+ TRACE_EVENT_BEGIN_ETW("BrowserMain:MESSAGE_LOOP", 0, "");
+
+ bool ran_main_loop = false;
+ if (parts_)
+ ran_main_loop = parts_->MainMessageLoopRun(&result_code_);
+
+ if (!ran_main_loop)
+ MainMessageLoopRun();
+
+ TRACE_EVENT_END_ETW("BrowserMain:MESSAGE_LOOP", 0, "");
+}
+
+void BrowserMainLoop::ShutdownThreadsAndCleanUp() {
+
+ if (!created_threads_) {
+ // Called early, nothing to do
+ return;
+ }
+ // Teardown may start in PostMainMessageLoopRun, and during teardown we
+ // need to be able to perform IO.
+ base::ThreadRestrictions::SetIOAllowed(true);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(base::IgnoreResult(&base::ThreadRestrictions::SetIOAllowed),
+ true));
+
+ if (parts_)
+ parts_->PostMainMessageLoopRun();
+
+ trace_memory_controller_.reset();
+
+#if !defined(OS_IOS)
+ // Destroying the GpuProcessHostUIShims on the UI thread posts a task to
+ // delete related objects on the GPU thread. This must be done before
+ // stopping the GPU thread. The GPU thread will close IPC channels to renderer
+ // processes so this has to happen before stopping the IO thread.
+ GpuProcessHostUIShim::DestroyAll();
+
+ // Cancel pending requests and prevent new requests.
+ if (resource_dispatcher_host_)
+ resource_dispatcher_host_.get()->Shutdown();
+
+#if defined(USE_AURA)
+ ImageTransportFactory::Terminate();
+#endif
+
+ // The device monitors are using |system_monitor_| as dependency, so delete
+ // them before |system_monitor_| goes away.
+ // On Mac and windows, the monitor needs to be destroyed on the same thread
+ // as they were created. On Linux, the monitor will be deleted when IO thread
+ // goes away.
+#if defined(OS_WIN)
+ system_message_window_.reset();
+#elif defined(OS_MACOSX)
+ device_monitor_mac_.reset();
+#endif
+#endif // !defined(OS_IOS)
+
+ // Must be size_t so we can subtract from it.
+ for (size_t thread_id = BrowserThread::ID_COUNT - 1;
+ thread_id >= (BrowserThread::UI + 1);
+ --thread_id) {
+ // Find the thread object we want to stop. Looping over all valid
+ // BrowserThread IDs and DCHECKing on a missing case in the switch
+ // statement helps avoid a mismatch between this code and the
+ // BrowserThread::ID enumeration.
+ //
+ // The destruction order is the reverse order of occurrence in the
+ // BrowserThread::ID list. The rationale for the order is as
+ // follows (need to be filled in a bit):
+ //
+ //
+ // - The IO thread is the only user of the CACHE thread.
+ //
+ // - The PROCESS_LAUNCHER thread must be stopped after IO in case
+ // the IO thread posted a task to terminate a process on the
+ // process launcher thread.
+ //
+ // - (Not sure why DB stops last.)
+ switch (thread_id) {
+ case BrowserThread::DB:
+ db_thread_.reset();
+ break;
+ case BrowserThread::FILE_USER_BLOCKING:
+ file_user_blocking_thread_.reset();
+ break;
+ case BrowserThread::FILE:
+#if !defined(OS_IOS)
+ // Clean up state that lives on or uses the file_thread_ before
+ // it goes away.
+ if (resource_dispatcher_host_)
+ resource_dispatcher_host_.get()->save_file_manager()->Shutdown();
+#endif // !defined(OS_IOS)
+ file_thread_.reset();
+ break;
+ case BrowserThread::PROCESS_LAUNCHER:
+ process_launcher_thread_.reset();
+ break;
+ case BrowserThread::CACHE:
+ cache_thread_.reset();
+ break;
+ case BrowserThread::IO:
+ io_thread_.reset();
+ break;
+ case BrowserThread::UI:
+ case BrowserThread::ID_COUNT:
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+#if !defined(OS_IOS)
+ indexed_db_thread_.reset();
+#endif
+
+ // Close the blocking I/O pool after the other threads. Other threads such
+ // as the I/O thread may need to schedule work like closing files or flushing
+ // data during shutdown, so the blocking pool needs to be available. There
+ // may also be slow operations pending that will blcok shutdown, so closing
+ // it here (which will block until required operations are complete) gives
+ // more head start for those operations to finish.
+ BrowserThreadImpl::ShutdownThreadPool();
+
+#if !defined(OS_IOS)
+ // Must happen after the IO thread is shutdown since this may be accessed from
+ // it.
+ BrowserGpuChannelHostFactory::Terminate();
+
+ // Must happen after the I/O thread is shutdown since this class lives on the
+ // I/O thread and isn't threadsafe.
+ GamepadService::GetInstance()->Terminate();
+ DeviceMotionService::GetInstance()->Shutdown();
+
+ URLDataManager::DeleteDataSources();
+#endif // !defined(OS_IOS)
+
+ if (parts_)
+ parts_->PostDestroyThreads();
+}
+
+void BrowserMainLoop::InitializeMainThread() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::InitializeMainThread")
+ const char* kThreadName = "CrBrowserMain";
+ base::PlatformThread::SetName(kThreadName);
+ if (main_message_loop_)
+ main_message_loop_->set_thread_name(kThreadName);
+
+ // Register the main thread by instantiating it, but don't call any methods.
+ main_thread_.reset(
+ new BrowserThreadImpl(BrowserThread::UI, base::MessageLoop::current()));
+}
+
+int BrowserMainLoop::BrowserThreadsStarted() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::BrowserThreadsStarted")
+
+#if !defined(OS_IOS)
+ indexed_db_thread_.reset(new base::Thread("IndexedDB"));
+ indexed_db_thread_->Start();
+#endif
+
+#if defined(OS_ANDROID)
+ // Up the priority of anything that touches with display tasks
+ // (this thread is UI thread, and io_thread_ is for IPCs).
+ io_thread_->SetPriority(base::kThreadPriority_Display);
+ base::PlatformThread::SetThreadPriority(
+ base::PlatformThread::CurrentHandle(),
+ base::kThreadPriority_Display);
+#endif
+
+#if !defined(OS_IOS)
+ HistogramSynchronizer::GetInstance();
+
+ BrowserGpuChannelHostFactory::Initialize();
+#if defined(USE_AURA)
+ ImageTransportFactory::Initialize();
+#endif
+
+#if defined(OS_LINUX)
+ device_monitor_linux_.reset(new DeviceMonitorLinux());
+#elif defined(OS_MACOSX)
+ device_monitor_mac_.reset(new DeviceMonitorMac());
+#endif
+
+ // RDH needs the IO thread to be created
+ {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::BrowserThreadsStarted:InitResourceDispatcherHost");
+ resource_dispatcher_host_.reset(new ResourceDispatcherHostImpl());
+ }
+
+ // MediaStreamManager needs the IO thread to be created.
+ {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::BrowserThreadsStarted:InitMediaStreamManager");
+ media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
+ }
+
+ // Initialize the GpuDataManager before we set up the MessageLoops because
+ // otherwise we'll trigger the assertion about doing IO on the UI thread.
+ GpuDataManagerImpl::GetInstance()->Initialize();
+
+ {
+ TRACE_EVENT0("startup",
+ "BrowserMainLoop::BrowserThreadsStarted:InitSpeechRecognition");
+ speech_recognition_manager_.reset(new SpeechRecognitionManagerImpl(
+ audio_manager_.get(), media_stream_manager_.get()));
+ }
+
+ // Alert the clipboard class to which threads are allowed to access the
+ // clipboard:
+ std::vector<base::PlatformThreadId> allowed_clipboard_threads;
+ // The current thread is the UI thread.
+ allowed_clipboard_threads.push_back(base::PlatformThread::CurrentId());
+#if defined(OS_WIN)
+ // On Windows, clipboards are also used on the File or IO threads.
+ allowed_clipboard_threads.push_back(file_thread_->thread_id());
+ allowed_clipboard_threads.push_back(io_thread_->thread_id());
+#endif
+ ui::Clipboard::SetAllowedThreads(allowed_clipboard_threads);
+
+ // When running the GPU thread in-process, avoid optimistically starting it
+ // since creating the GPU thread races against creation of the one-and-only
+ // ChildProcess instance which is created by the renderer thread.
+ if (GpuDataManagerImpl::GetInstance()->GpuAccessAllowed(NULL) &&
+ IsForceCompositingModeEnabled() &&
+ !parsed_command_line_.HasSwitch(switches::kDisableGpuProcessPrelaunch) &&
+ !parsed_command_line_.HasSwitch(switches::kSingleProcess) &&
+ !parsed_command_line_.HasSwitch(switches::kInProcessGPU)) {
+ TRACE_EVENT_INSTANT0("gpu", "Post task to launch GPU process",
+ TRACE_EVENT_SCOPE_THREAD);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(
+ base::IgnoreResult(&GpuProcessHost::Get),
+ GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ CAUSE_FOR_GPU_LAUNCH_BROWSER_STARTUP));
+ }
+#endif // !defined(OS_IOS)
+ return result_code_;
+}
+
+void BrowserMainLoop::InitializeToolkit() {
+ TRACE_EVENT0("startup", "BrowserMainLoop::InitializeToolkit")
+ // TODO(evan): this function is rather subtle, due to the variety
+ // of intersecting ifdefs we have. To keep it easy to follow, there
+ // are no #else branches on any #ifs.
+ // TODO(stevenjb): Move platform specific code into platform specific Parts
+ // (Need to add InitializeToolkit stage to BrowserParts).
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+ // g_type_init will be deprecated in 2.36. 2.35 is the development
+ // version for 2.36, hence do not call g_type_init starting 2.35.
+ // http://developer.gnome.org/gobject/unstable/gobject-Type-Information.html#g-type-init
+#if !GLIB_CHECK_VERSION(2, 35, 0)
+ // Glib type system initialization. Needed at least for gconf,
+ // used in net/proxy/proxy_config_service_linux.cc. Most likely
+ // this is superfluous as gtk_init() ought to do this. It's
+ // definitely harmless, so retained as a reminder of this
+ // requirement for gconf.
+ g_type_init();
+#endif
+
+#if !defined(USE_AURA)
+ gfx::GtkInitFromCommandLine(parsed_command_line_);
+#endif
+
+ SetUpGLibLogHandler();
+#endif
+
+#if defined(TOOLKIT_GTK)
+ // It is important for this to happen before the first run dialog, as it
+ // styles the dialog as well.
+ gfx::InitRCStyles();
+#endif
+
+#if defined(OS_WIN)
+ // Init common control sex.
+ INITCOMMONCONTROLSEX config;
+ config.dwSize = sizeof(config);
+ config.dwICC = ICC_WIN95_CLASSES;
+ if (!InitCommonControlsEx(&config))
+ LOG_GETLASTERROR(FATAL);
+#endif
+
+ if (parts_)
+ parts_->ToolkitInitialized();
+}
+
+void BrowserMainLoop::MainMessageLoopRun() {
+#if defined(OS_ANDROID)
+ // Android's main message loop is the Java message loop.
+ NOTREACHED();
+#else
+ DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
+ if (parameters_.ui_task)
+ base::MessageLoopForUI::current()->PostTask(FROM_HERE,
+ *parameters_.ui_task);
+
+ base::RunLoop run_loop;
+ run_loop.Run();
+#endif
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_main_loop.h b/chromium/content/browser/browser_main_loop.h
new file mode 100644
index 00000000000..7b2879f70e4
--- /dev/null
+++ b/chromium/content/browser/browser_main_loop.h
@@ -0,0 +1,167 @@
+// 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 CONTENT_BROWSER_BROWSER_MAIN_LOOP_H_
+#define CONTENT_BROWSER_BROWSER_MAIN_LOOP_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/browser_process_sub_thread.h"
+#include "content/public/browser/browser_main_runner.h"
+
+class CommandLine;
+
+namespace base {
+class HighResolutionTimerManager;
+class MessageLoop;
+class PowerMonitor;
+class SystemMonitor;
+namespace debug {
+class TraceMemoryController;
+} // namespace debug
+} // namespace base
+
+namespace media {
+class AudioManager;
+class MIDIManager;
+} // namespace media
+
+namespace net {
+class NetworkChangeNotifier;
+} // namespace net
+
+namespace content {
+class AudioMirroringManager;
+class BrowserMainParts;
+class BrowserOnlineStateObserver;
+class BrowserShutdownImpl;
+class BrowserThreadImpl;
+class MediaStreamManager;
+class ResourceDispatcherHostImpl;
+class SpeechRecognitionManagerImpl;
+class SystemMessageWindowWin;
+struct MainFunctionParams;
+
+#if defined(OS_LINUX)
+class DeviceMonitorLinux;
+#elif defined(OS_MACOSX)
+class DeviceMonitorMac;
+#endif
+
+// Implements the main browser loop stages called from BrowserMainRunner.
+// See comments in browser_main_parts.h for additional info.
+class CONTENT_EXPORT BrowserMainLoop {
+ public:
+ // Returns the current instance. This is used to get access to the getters
+ // that return objects which are owned by this class.
+ static BrowserMainLoop* GetInstance();
+
+ explicit BrowserMainLoop(const MainFunctionParams& parameters);
+ virtual ~BrowserMainLoop();
+
+ void Init();
+
+ void EarlyInitialization();
+ void InitializeToolkit();
+ void MainMessageLoopStart();
+
+ // Create the tasks we need to complete startup.
+ void CreateStartupTasks();
+
+ // Perform the default message loop run logic.
+ void RunMainMessageLoopParts();
+
+ // Performs the shutdown sequence, starting with PostMainMessageLoopRun
+ // through stopping threads to PostDestroyThreads.
+ void ShutdownThreadsAndCleanUp();
+
+ int GetResultCode() const { return result_code_; }
+
+ media::AudioManager* audio_manager() const { return audio_manager_.get(); }
+ AudioMirroringManager* audio_mirroring_manager() const {
+ return audio_mirroring_manager_.get();
+ }
+ MediaStreamManager* media_stream_manager() const {
+ return media_stream_manager_.get();
+ }
+ media::MIDIManager* midi_manager() const { return midi_manager_.get(); }
+ base::Thread* indexed_db_thread() const { return indexed_db_thread_.get(); }
+
+ private:
+ class MemoryObserver;
+ // For ShutdownThreadsAndCleanUp.
+ friend class BrowserShutdownImpl;
+
+ void InitializeMainThread();
+
+ // Called just before creating the threads
+ int PreCreateThreads();
+
+ // Create all secondary threads.
+ int CreateThreads();
+
+ // Called right after the browser threads have been started.
+ int BrowserThreadsStarted();
+
+ int PreMainMessageLoopRun();
+
+ void MainMessageLoopRun();
+
+ // Members initialized on construction ---------------------------------------
+ const MainFunctionParams& parameters_;
+ const CommandLine& parsed_command_line_;
+ int result_code_;
+ // True if the non-UI threads were created.
+ bool created_threads_;
+
+ // Members initialized in |MainMessageLoopStart()| ---------------------------
+ scoped_ptr<base::MessageLoop> main_message_loop_;
+ scoped_ptr<base::SystemMonitor> system_monitor_;
+ scoped_ptr<base::PowerMonitor> power_monitor_;
+ scoped_ptr<base::HighResolutionTimerManager> hi_res_timer_manager_;
+ scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+ scoped_ptr<media::MIDIManager> midi_manager_;
+ scoped_ptr<AudioMirroringManager> audio_mirroring_manager_;
+ scoped_ptr<MediaStreamManager> media_stream_manager_;
+ // Per-process listener for online state changes.
+ scoped_ptr<BrowserOnlineStateObserver> online_state_observer_;
+#if defined(OS_WIN)
+ scoped_ptr<SystemMessageWindowWin> system_message_window_;
+#elif defined(OS_LINUX)
+ scoped_ptr<DeviceMonitorLinux> device_monitor_linux_;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ scoped_ptr<DeviceMonitorMac> device_monitor_mac_;
+#endif
+
+ // Destroy parts_ before main_message_loop_ (required) and before other
+ // classes constructed in content (but after main_thread_).
+ scoped_ptr<BrowserMainParts> parts_;
+
+ // Members initialized in |InitializeMainThread()| ---------------------------
+ // This must get destroyed before other threads that are created in parts_.
+ scoped_ptr<BrowserThreadImpl> main_thread_;
+
+ // Members initialized in |BrowserThreadsStarted()| --------------------------
+ scoped_ptr<ResourceDispatcherHostImpl> resource_dispatcher_host_;
+ scoped_ptr<SpeechRecognitionManagerImpl> speech_recognition_manager_;
+
+ // Members initialized in |RunMainMessageLoopParts()| ------------------------
+ scoped_ptr<BrowserProcessSubThread> db_thread_;
+ scoped_ptr<BrowserProcessSubThread> file_user_blocking_thread_;
+ scoped_ptr<BrowserProcessSubThread> file_thread_;
+ scoped_ptr<BrowserProcessSubThread> process_launcher_thread_;
+ scoped_ptr<BrowserProcessSubThread> cache_thread_;
+ scoped_ptr<BrowserProcessSubThread> io_thread_;
+ scoped_ptr<base::Thread> indexed_db_thread_;
+ scoped_ptr<MemoryObserver> memory_observer_;
+ scoped_ptr<base::debug::TraceMemoryController> trace_memory_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserMainLoop);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_MAIN_LOOP_H_
diff --git a/chromium/content/browser/browser_main_runner.cc b/chromium/content/browser/browser_main_runner.cc
new file mode 100644
index 00000000000..cca1466d9bb
--- /dev/null
+++ b/chromium/content/browser/browser_main_runner.cc
@@ -0,0 +1,157 @@
+// 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 "content/public/browser/browser_main_runner.h"
+
+#include "base/allocator/allocator_shim.h"
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/notification_service_impl.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/main_function_params.h"
+#include "ui/base/ime/input_method_initializer.h"
+
+#if defined(OS_WIN)
+#include "base/win/metro.h"
+#include "base/win/windows_version.h"
+#include "ui/base/win/scoped_ole_initializer.h"
+#endif
+
+bool g_exited_main_message_loop = false;
+
+namespace content {
+
+class BrowserMainRunnerImpl : public BrowserMainRunner {
+ public:
+ BrowserMainRunnerImpl() : is_initialized_(false), is_shutdown_(false) {}
+
+ virtual ~BrowserMainRunnerImpl() {
+ if (is_initialized_ && !is_shutdown_)
+ Shutdown();
+ }
+
+ virtual int Initialize(const MainFunctionParams& parameters)
+ OVERRIDE {
+ TRACE_EVENT0("startup", "BrowserMainRunnerImpl::Initialize")
+ is_initialized_ = true;
+
+#if !defined(OS_IOS)
+ if (parameters.command_line.HasSwitch(switches::kWaitForDebugger))
+ base::debug::WaitForDebugger(60, true);
+#endif
+
+#if defined(OS_WIN)
+ if (parameters.command_line.HasSwitch(
+ switches::kEnableTextServicesFramework)) {
+ base::win::SetForceToUseTSF();
+ } else if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ // When "Extend support of advanced text services to all programs"
+ // (a.k.a. Cicero Unaware Application Support; CUAS) is enabled on
+ // Windows XP and handwriting modules shipped with Office 2003 are
+ // installed, "penjpn.dll" and "skchui.dll" will be loaded and then crash
+ // unless a user installs Office 2003 SP3. To prevent these modules from
+ // being loaded, disable TSF entirely. crbug/160914.
+ // TODO(yukawa): Add a high-level wrapper for this instead of calling
+ // Win32 API here directly.
+ ImmDisableTextFrameService(static_cast<DWORD>(-1));
+ }
+#endif // OS_WIN
+
+ base::StatisticsRecorder::Initialize();
+
+ notification_service_.reset(new NotificationServiceImpl);
+
+#if defined(OS_WIN)
+ // Ole must be initialized before starting message pump, so that TSF
+ // (Text Services Framework) module can interact with the message pump
+ // on Windows 8 Metro mode.
+ ole_initializer_.reset(new ui::ScopedOleInitializer);
+#endif // OS_WIN
+
+ main_loop_.reset(new BrowserMainLoop(parameters));
+
+ main_loop_->Init();
+
+ main_loop_->EarlyInitialization();
+
+ // Must happen before we try to use a message loop or display any UI.
+ main_loop_->InitializeToolkit();
+
+ main_loop_->MainMessageLoopStart();
+
+ // WARNING: If we get a WM_ENDSESSION, objects created on the stack here
+ // are NOT deleted. If you need something to run during WM_ENDSESSION add it
+ // to browser_shutdown::Shutdown or BrowserProcess::EndSession.
+
+#if defined(OS_WIN) && !defined(NO_TCMALLOC)
+ // When linking shared libraries, NO_TCMALLOC is defined, and dynamic
+ // allocator selection is not supported.
+
+ // Make this call before going multithreaded, or spawning any subprocesses.
+ base::allocator::SetupSubprocessAllocator();
+#endif
+ ui::InitializeInputMethod();
+
+ main_loop_->CreateStartupTasks();
+ int result_code = main_loop_->GetResultCode();
+ if (result_code > 0)
+ return result_code;
+
+ // Return -1 to indicate no early termination.
+ return -1;
+ }
+
+ virtual int Run() OVERRIDE {
+ DCHECK(is_initialized_);
+ DCHECK(!is_shutdown_);
+ main_loop_->RunMainMessageLoopParts();
+ return main_loop_->GetResultCode();
+ }
+
+ virtual void Shutdown() OVERRIDE {
+ DCHECK(is_initialized_);
+ DCHECK(!is_shutdown_);
+ g_exited_main_message_loop = true;
+
+ main_loop_->ShutdownThreadsAndCleanUp();
+
+ ui::ShutdownInputMethod();
+#if defined(OS_WIN)
+ ole_initializer_.reset(NULL);
+#endif
+
+ main_loop_.reset(NULL);
+
+ notification_service_.reset(NULL);
+
+ is_shutdown_ = true;
+ }
+
+ protected:
+ // True if the runner has been initialized.
+ bool is_initialized_;
+
+ // True if the runner has been shut down.
+ bool is_shutdown_;
+
+ scoped_ptr<NotificationServiceImpl> notification_service_;
+ scoped_ptr<BrowserMainLoop> main_loop_;
+#if defined(OS_WIN)
+ scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserMainRunnerImpl);
+};
+
+// static
+BrowserMainRunner* BrowserMainRunner::Create() {
+ return new BrowserMainRunnerImpl();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/OWNERS b/chromium/content/browser/browser_plugin/OWNERS
new file mode 100644
index 00000000000..c92691f05e4
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/OWNERS
@@ -0,0 +1 @@
+fsamuel@chromium.org
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc b/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc
new file mode 100644
index 00000000000..4a06aca32c9
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc
@@ -0,0 +1,232 @@
+// 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 "content/browser/browser_plugin/browser_plugin_embedder.h"
+
+#include "base/values.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+#include "content/browser/browser_plugin/browser_plugin_host_factory.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/browser_plugin/browser_plugin_constants.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/drag_messages.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/escape.h"
+
+namespace content {
+
+// static
+BrowserPluginHostFactory* BrowserPluginEmbedder::factory_ = NULL;
+
+BrowserPluginEmbedder::BrowserPluginEmbedder(WebContentsImpl* web_contents)
+ : WebContentsObserver(web_contents),
+ next_get_render_view_request_id_(0) {
+}
+
+BrowserPluginEmbedder::~BrowserPluginEmbedder() {
+ CleanUp();
+}
+
+// static
+BrowserPluginEmbedder* BrowserPluginEmbedder::Create(
+ WebContentsImpl* web_contents) {
+ if (factory_)
+ return factory_->CreateBrowserPluginEmbedder(web_contents);
+ return new BrowserPluginEmbedder(web_contents);
+}
+
+void BrowserPluginEmbedder::DragEnteredGuest(BrowserPluginGuest* guest) {
+ guest_dragging_over_ = guest->AsWeakPtr();
+}
+
+void BrowserPluginEmbedder::DragLeftGuest(BrowserPluginGuest* guest) {
+ // Avoid race conditions in switching between guests being hovered over by
+ // only un-setting if the caller is marked as the guest being dragged over.
+ if (guest_dragging_over_.get() == guest) {
+ guest_dragging_over_.reset();
+ }
+}
+
+void BrowserPluginEmbedder::StartDrag(BrowserPluginGuest* guest) {
+ guest_started_drag_ = guest->AsWeakPtr();
+}
+
+void BrowserPluginEmbedder::StopDrag(BrowserPluginGuest* guest) {
+ if (guest_started_drag_.get() == guest) {
+ guest_started_drag_.reset();
+ }
+}
+
+void BrowserPluginEmbedder::GetRenderViewHostAtPosition(
+ int x, int y, const WebContents::GetRenderViewHostCallback& callback) {
+ // Store the callback so we can call it later when we have the response.
+ pending_get_render_view_callbacks_.insert(
+ std::make_pair(next_get_render_view_request_id_, callback));
+ Send(new BrowserPluginMsg_PluginAtPositionRequest(
+ routing_id(),
+ next_get_render_view_request_id_,
+ gfx::Point(x, y)));
+ ++next_get_render_view_request_id_;
+}
+
+void BrowserPluginEmbedder::DidSendScreenRects() {
+ GetBrowserPluginGuestManager()->DidSendScreenRects(
+ static_cast<WebContentsImpl*>(web_contents()));
+}
+
+bool BrowserPluginEmbedder::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ return GetBrowserPluginGuestManager()->UnlockMouseIfNecessary(
+ static_cast<WebContentsImpl*>(web_contents()), event);
+}
+
+void BrowserPluginEmbedder::RenderProcessGone(base::TerminationStatus status) {
+ CleanUp();
+}
+
+bool BrowserPluginEmbedder::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(BrowserPluginEmbedder, message)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_AllocateInstanceID,
+ OnAllocateInstanceID)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_Attach, OnAttach)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_PluginAtPositionResponse,
+ OnPluginAtPositionResponse)
+ IPC_MESSAGE_HANDLER_GENERIC(DragHostMsg_UpdateDragCursor,
+ OnUpdateDragCursor(&handled));
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void BrowserPluginEmbedder::DragSourceEndedAt(int client_x, int client_y,
+ int screen_x, int screen_y, WebKit::WebDragOperation operation) {
+ if (guest_started_drag_.get()) {
+ gfx::Point guest_offset =
+ guest_started_drag_->GetScreenCoordinates(gfx::Point());
+ guest_started_drag_->DragSourceEndedAt(client_x - guest_offset.x(),
+ client_y - guest_offset.y(), screen_x, screen_y, operation);
+ }
+}
+
+void BrowserPluginEmbedder::DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y) {
+ if (guest_started_drag_.get()) {
+ gfx::Point guest_offset =
+ guest_started_drag_->GetScreenCoordinates(gfx::Point());
+ guest_started_drag_->DragSourceMovedTo(client_x - guest_offset.x(),
+ client_y - guest_offset.y(), screen_x, screen_y);
+ }
+}
+
+void BrowserPluginEmbedder::SystemDragEnded() {
+ if (guest_started_drag_.get() &&
+ (guest_started_drag_.get() != guest_dragging_over_.get()))
+ guest_started_drag_->EndSystemDrag();
+ guest_started_drag_.reset();
+ guest_dragging_over_.reset();
+}
+
+void BrowserPluginEmbedder::OnUpdateDragCursor(bool* handled) {
+ *handled = (guest_dragging_over_.get() != NULL);
+}
+
+void BrowserPluginEmbedder::CleanUp() {
+ // CleanUp gets called when BrowserPluginEmbedder's WebContents goes away
+ // or the associated RenderViewHost is destroyed or swapped out. Therefore we
+ // don't need to care about the pending callbacks anymore.
+ pending_get_render_view_callbacks_.clear();
+}
+
+BrowserPluginGuestManager*
+ BrowserPluginEmbedder::GetBrowserPluginGuestManager() {
+ BrowserPluginGuestManager* guest_manager = static_cast<WebContentsImpl*>(
+ web_contents())->GetBrowserPluginGuestManager();
+ if (!guest_manager) {
+ guest_manager = BrowserPluginGuestManager::Create();
+ web_contents()->GetBrowserContext()->SetUserData(
+ browser_plugin::kBrowserPluginGuestManagerKeyName, guest_manager);
+ }
+ return guest_manager;
+}
+
+void BrowserPluginEmbedder::OnAllocateInstanceID(int request_id) {
+ int instance_id = GetBrowserPluginGuestManager()->get_next_instance_id();
+ Send(new BrowserPluginMsg_AllocateInstanceID_ACK(
+ routing_id(), request_id, instance_id));
+}
+
+void BrowserPluginEmbedder::OnAttach(
+ int instance_id,
+ const BrowserPluginHostMsg_Attach_Params& params,
+ const base::DictionaryValue& extra_params) {
+ if (!GetBrowserPluginGuestManager()->CanEmbedderAccessInstanceIDMaybeKill(
+ web_contents()->GetRenderProcessHost()->GetID(), instance_id))
+ return;
+
+ BrowserPluginGuest* guest =
+ GetBrowserPluginGuestManager()->GetGuestByInstanceID(
+ instance_id, web_contents()->GetRenderProcessHost()->GetID());
+
+
+ if (guest) {
+ // There is an implicit order expectation here:
+ // 1. The content embedder is made aware of the attachment.
+ // 2. BrowserPluginGuest::Attach is called.
+ // 3. The content embedder issues queued events if any that happened
+ // prior to attachment.
+ GetContentClient()->browser()->GuestWebContentsAttached(
+ guest->GetWebContents(),
+ web_contents(),
+ extra_params);
+ guest->Attach(static_cast<WebContentsImpl*>(web_contents()), params);
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> copy_extra_params(extra_params.DeepCopy());
+ guest = GetBrowserPluginGuestManager()->CreateGuest(
+ web_contents()->GetSiteInstance(),
+ instance_id, params,
+ copy_extra_params.Pass());
+ if (guest) {
+ GetContentClient()->browser()->GuestWebContentsAttached(
+ guest->GetWebContents(),
+ web_contents(),
+ extra_params);
+ guest->Initialize(static_cast<WebContentsImpl*>(web_contents()), params);
+ }
+}
+
+void BrowserPluginEmbedder::OnPluginAtPositionResponse(
+ int instance_id, int request_id, const gfx::Point& position) {
+ const std::map<int, WebContents::GetRenderViewHostCallback>::iterator
+ callback_iter = pending_get_render_view_callbacks_.find(request_id);
+ if (callback_iter == pending_get_render_view_callbacks_.end())
+ return;
+
+ RenderViewHost* render_view_host;
+ BrowserPluginGuest* guest = NULL;
+ if (instance_id != browser_plugin::kInstanceIDNone) {
+ guest = GetBrowserPluginGuestManager()->GetGuestByInstanceID(
+ instance_id, web_contents()->GetRenderProcessHost()->GetID());
+ }
+
+ if (guest)
+ render_view_host = guest->GetWebContents()->GetRenderViewHost();
+ else // No plugin, use embedder's RenderViewHost.
+ render_view_host = web_contents()->GetRenderViewHost();
+
+ callback_iter->second.Run(render_view_host, position.x(), position.y());
+ pending_get_render_view_callbacks_.erase(callback_iter);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_embedder.h b/chromium/content/browser/browser_plugin/browser_plugin_embedder.h
new file mode 100644
index 00000000000..52610632f59
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_embedder.h
@@ -0,0 +1,139 @@
+// 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.
+
+// A BrowserPluginEmbedder handles messages coming from a BrowserPlugin's
+// embedder that are not directed at any particular existing guest process.
+// In the beginning, when a BrowserPlugin instance in the embedder renderer
+// process requests an initial navigation, the WebContents for that renderer
+// renderer creates a BrowserPluginEmbedder for itself. The
+// BrowserPluginEmbedder, in turn, forwards the requests to a
+// BrowserPluginGuestManager, which creates and manages the lifetime of the new
+// guest.
+
+#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+
+struct BrowserPluginHostMsg_Attach_Params;
+struct BrowserPluginHostMsg_ResizeGuest_Params;
+
+namespace gfx {
+class Point;
+}
+
+namespace content {
+
+class BrowserPluginGuest;
+class BrowserPluginGuestManager;
+class BrowserPluginHostFactory;
+class RenderWidgetHostImpl;
+class WebContentsImpl;
+struct NativeWebKeyboardEvent;
+
+class CONTENT_EXPORT BrowserPluginEmbedder : public WebContentsObserver {
+ public:
+ virtual ~BrowserPluginEmbedder();
+
+ static BrowserPluginEmbedder* Create(WebContentsImpl* web_contents);
+
+ // Returns the RenderViewHost at a point (|x|, |y|) asynchronously via
+ // |callback|. We need a roundtrip to renderer process to get this
+ // information.
+ void GetRenderViewHostAtPosition(
+ int x,
+ int y,
+ const WebContents::GetRenderViewHostCallback& callback);
+
+ // Called when embedder's |rwh| has sent screen rects to renderer.
+ void DidSendScreenRects();
+
+ // Called when embedder's WebContentsImpl has unhandled keyboard input.
+ // Returns whether the BrowserPlugin has handled the keyboard event.
+ // Currently we are only interested in checking for the escape key to
+ // unlock hte guest's pointer lock.
+ bool HandleKeyboardEvent(const NativeWebKeyboardEvent& event);
+
+ // Overrides factory for testing. Default (NULL) value indicates regular
+ // (non-test) environment.
+ static void set_factory_for_testing(BrowserPluginHostFactory* factory) {
+ factory_ = factory;
+ }
+
+ // WebContentsObserver implementation.
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ void DragSourceEndedAt(int client_x, int client_y, int screen_x,
+ int screen_y, WebKit::WebDragOperation operation);
+
+ void DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y);
+
+ void OnUpdateDragCursor(bool* handled);
+
+ void DragEnteredGuest(BrowserPluginGuest* guest);
+
+ void DragLeftGuest(BrowserPluginGuest* guest);
+
+ void StartDrag(BrowserPluginGuest* guest);
+
+ void StopDrag(BrowserPluginGuest* guest);
+
+ void SystemDragEnded();
+
+ private:
+ friend class TestBrowserPluginEmbedder;
+
+ BrowserPluginEmbedder(WebContentsImpl* web_contents);
+
+ void CleanUp();
+
+ BrowserPluginGuestManager* GetBrowserPluginGuestManager();
+
+ // Message handlers.
+
+ void OnAllocateInstanceID(int request_id);
+ void OnAttach(int instance_id,
+ const BrowserPluginHostMsg_Attach_Params& params,
+ const base::DictionaryValue& extra_params);
+ void OnPluginAtPositionResponse(int instance_id,
+ int request_id,
+ const gfx::Point& position);
+
+ // Static factory instance (always NULL for non-test).
+ static BrowserPluginHostFactory* factory_;
+
+ // Map that contains outstanding queries to |GetRenderViewHostAtPosition|.
+ // We need a roundtrip to the renderer process to retrieve the answer,
+ // so we store these callbacks until we hear back from the renderer.
+ typedef std::map<int, WebContents::GetRenderViewHostCallback>
+ GetRenderViewHostCallbackMap;
+ GetRenderViewHostCallbackMap pending_get_render_view_callbacks_;
+ // Next request id for BrowserPluginMsg_PluginAtPositionRequest query.
+ int next_get_render_view_request_id_;
+
+ // Used to correctly update the cursor when dragging over a guest, and to
+ // handle a race condition when dropping onto the guest that started the drag
+ // (the race is that the dragend message arrives before the drop message so
+ // the drop never takes place).
+ // crbug.com/233571
+ base::WeakPtr<BrowserPluginGuest> guest_dragging_over_;
+
+ // Pointer to the guest that started the drag, used to forward necessary drag
+ // status messages to the correct guest.
+ base::WeakPtr<BrowserPluginGuest> guest_started_drag_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginEmbedder);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc
new file mode 100644
index 00000000000..8a803c47120
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc
@@ -0,0 +1,94 @@
+// 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 "content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h"
+
+#include "base/bind.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+
+namespace content {
+
+BrowserPluginGeolocationPermissionContext::
+ BrowserPluginGeolocationPermissionContext() {
+}
+
+BrowserPluginGeolocationPermissionContext::
+ ~BrowserPluginGeolocationPermissionContext() {
+}
+
+void BrowserPluginGeolocationPermissionContext::RequestGeolocationPermission(
+ int render_process_id,
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame,
+ base::Callback<void(bool)> callback) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &BrowserPluginGeolocationPermissionContext::
+ RequestGeolocationPermission,
+ this,
+ render_process_id,
+ render_view_id,
+ bridge_id,
+ requesting_frame,
+ callback));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Note that callback.Run(true) allows geolocation access, callback.Run(false)
+ // denies geolocation access.
+ // We need to go to the renderer to ask embedder's js if we are allowed to
+ // have geolocation access.
+ RenderViewHost* rvh = RenderViewHost::FromID(render_process_id,
+ render_view_id);
+ if (rvh) {
+ DCHECK(rvh->GetProcess()->IsGuest());
+ WebContentsImpl* guest_web_contents =
+ static_cast<WebContentsImpl*>(rvh->GetDelegate()->GetAsWebContents());
+ BrowserPluginGuest* guest = guest_web_contents->GetBrowserPluginGuest();
+ guest->AskEmbedderForGeolocationPermission(bridge_id,
+ requesting_frame,
+ callback);
+ }
+}
+
+void BrowserPluginGeolocationPermissionContext::
+ CancelGeolocationPermissionRequest(int render_process_id,
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &BrowserPluginGeolocationPermissionContext::
+ CancelGeolocationPermissionRequest,
+ this,
+ render_process_id,
+ render_view_id,
+ bridge_id,
+ requesting_frame));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderViewHost* rvh = RenderViewHost::FromID(render_process_id,
+ render_view_id);
+ if (rvh) {
+ DCHECK(rvh->GetProcess()->IsGuest());
+ WebContentsImpl* guest_web_contents =
+ static_cast<WebContentsImpl*>(rvh->GetDelegate()->GetAsWebContents());
+ BrowserPluginGuest* guest = guest_web_contents->GetBrowserPluginGuest();
+ if (guest)
+ guest->CancelGeolocationRequest(bridge_id);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h
new file mode 100644
index 00000000000..27cc9e3e4a7
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_
+
+#include "content/public/browser/geolocation_permission_context.h"
+
+namespace content {
+
+// Browser plugin specific implementation of GeolocationPermissionContext.
+// It manages Geolocation permissions flow for BrowserPluginGuest. When a guest
+// requests gelocation permission, it delegates the request to embedder though
+// embedder's javascript api.
+// This runs on the I/O thread. We have to return to UI thread to talk to a
+// BrowserPluginGuest.
+class BrowserPluginGeolocationPermissionContext :
+ public GeolocationPermissionContext {
+ public:
+ BrowserPluginGeolocationPermissionContext();
+
+ // GeolocationPermissionContext implementation:
+ virtual void RequestGeolocationPermission(
+ int render_process_id,
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame,
+ base::Callback<void(bool)> callback) OVERRIDE;
+ virtual void CancelGeolocationPermissionRequest(
+ int render_process_id,
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame) OVERRIDE;
+
+ private:
+ virtual ~BrowserPluginGeolocationPermissionContext();
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginGeolocationPermissionContext);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest.cc
new file mode 100644
index 00000000000..31250b7d626
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest.cc
@@ -0,0 +1,1667 @@
+// 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 "content/browser/browser_plugin/browser_plugin_guest.h"
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_plugin/browser_plugin_embedder.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_helper.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+#include "content/browser/browser_plugin/browser_plugin_host_factory.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_contents_view_guest.h"
+#include "content/common/browser_plugin/browser_plugin_constants.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/content_constants_internal.h"
+#include "content/common/drag_messages.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/geolocation_permission_context.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/resource_request_details.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/common/media_stream_request.h"
+#include "content/public/common/result_codes.h"
+#include "net/url_request/url_request.h"
+#include "third_party/WebKit/public/web/WebCursorInfo.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/surface/transport_dib.h"
+#include "webkit/common/resource_type.h"
+
+#if defined(OS_MACOSX)
+#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h"
+#endif
+
+namespace content {
+
+// static
+BrowserPluginHostFactory* BrowserPluginGuest::factory_ = NULL;
+
+// Parent class for the various types of permission requests, each of which
+// should be able to handle the response to their permission request.
+class BrowserPluginGuest::PermissionRequest :
+ public base::RefCounted<BrowserPluginGuest::PermissionRequest> {
+ public:
+ virtual void Respond(bool should_allow, const std::string& user_input) = 0;
+ protected:
+ PermissionRequest() {
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.PermissionRequest"));
+ }
+ virtual ~PermissionRequest() {}
+ // Friend RefCounted so that the dtor can be non-public.
+ friend class base::RefCounted<BrowserPluginGuest::PermissionRequest>;
+};
+
+class BrowserPluginGuest::DownloadRequest : public PermissionRequest {
+ public:
+ explicit DownloadRequest(base::Callback<void(bool)> callback)
+ : callback_(callback) {
+ RecordAction(
+ UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Download"));
+ }
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ callback_.Run(should_allow);
+ }
+
+ private:
+ virtual ~DownloadRequest() {}
+ base::Callback<void(bool)> callback_;
+};
+
+class BrowserPluginGuest::GeolocationRequest : public PermissionRequest {
+ public:
+ GeolocationRequest(GeolocationCallback callback,
+ int bridge_id,
+ base::WeakPtrFactory<BrowserPluginGuest>* weak_ptr_factory)
+ : callback_(callback),
+ bridge_id_(bridge_id),
+ weak_ptr_factory_(weak_ptr_factory) {
+ RecordAction(
+ UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Geolocation"));
+ }
+
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ base::WeakPtr<BrowserPluginGuest> guest(weak_ptr_factory_->GetWeakPtr());
+
+ WebContents* web_contents = guest->embedder_web_contents();
+ if (should_allow && web_contents) {
+ // If renderer side embedder decides to allow gelocation, we need to check
+ // if the app/embedder itself has geolocation access.
+ BrowserContext* browser_context = web_contents->GetBrowserContext();
+ if (browser_context) {
+ GeolocationPermissionContext* geolocation_context =
+ browser_context->GetGeolocationPermissionContext();
+ if (geolocation_context) {
+ base::Callback<void(bool)> geolocation_callback = base::Bind(
+ &BrowserPluginGuest::SetGeolocationPermission,
+ guest,
+ callback_,
+ bridge_id_);
+ geolocation_context->RequestGeolocationPermission(
+ web_contents->GetRenderProcessHost()->GetID(),
+ web_contents->GetRoutingID(),
+ // The geolocation permission request here is not initiated
+ // through WebGeolocationPermissionRequest. We are only interested
+ // in the fact whether the embedder/app has geolocation
+ // permission. Therefore we use an invalid |bridge_id|.
+ -1 /* bridge_id */,
+ web_contents->GetLastCommittedURL(),
+ geolocation_callback);
+ return;
+ }
+ }
+ }
+ guest->SetGeolocationPermission(callback_, bridge_id_, false);
+ }
+
+ private:
+ virtual ~GeolocationRequest() {}
+ base::Callback<void(bool)> callback_;
+ int bridge_id_;
+ base::WeakPtrFactory<BrowserPluginGuest>* weak_ptr_factory_;
+};
+
+class BrowserPluginGuest::MediaRequest : public PermissionRequest {
+ public:
+ MediaRequest(const MediaStreamRequest& request,
+ const MediaResponseCallback& callback,
+ BrowserPluginGuest* guest)
+ : request_(request),
+ callback_(callback),
+ guest_(guest) {
+ RecordAction(
+ UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Media"));
+ }
+
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ WebContentsImpl* web_contents = guest_->embedder_web_contents();
+ if (should_allow && web_contents) {
+ // Re-route the request to the embedder's WebContents; the guest gets the
+ // permission this way.
+ web_contents->RequestMediaAccessPermission(request_, callback_);
+ } else {
+ // Deny the request.
+ callback_.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>());
+ }
+ }
+
+ private:
+ virtual ~MediaRequest() {}
+ MediaStreamRequest request_;
+ MediaResponseCallback callback_;
+ BrowserPluginGuest* guest_;
+};
+
+class BrowserPluginGuest::NewWindowRequest : public PermissionRequest {
+ public:
+ NewWindowRequest(int instance_id, BrowserPluginGuest* guest)
+ : instance_id_(instance_id),
+ guest_(guest) {
+ RecordAction(
+ UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.NewWindow"));
+ }
+
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ int embedder_render_process_id =
+ guest_->embedder_web_contents()->GetRenderProcessHost()->GetID();
+ BrowserPluginGuest* guest =
+ guest_->GetWebContents()->GetBrowserPluginGuestManager()->
+ GetGuestByInstanceID(instance_id_, embedder_render_process_id);
+ if (!guest) {
+ LOG(INFO) << "Guest not found. Instance ID: " << instance_id_;
+ return;
+ }
+
+ // If we do not destroy the guest then we allow the new window.
+ if (!should_allow)
+ guest->Destroy();
+ }
+
+ private:
+ virtual ~NewWindowRequest() {}
+ int instance_id_;
+ BrowserPluginGuest* guest_;
+};
+
+class BrowserPluginGuest::JavaScriptDialogRequest : public PermissionRequest {
+ public:
+ JavaScriptDialogRequest(const DialogClosedCallback& callback)
+ : callback_(callback) {
+ RecordAction(
+ UserMetricsAction(
+ "BrowserPlugin.Guest.PermissionRequest.JavaScriptDialog"));
+ }
+
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ callback_.Run(should_allow, UTF8ToUTF16(user_input));
+ }
+
+ private:
+ virtual ~JavaScriptDialogRequest() {}
+ DialogClosedCallback callback_;
+};
+
+class BrowserPluginGuest::PointerLockRequest : public PermissionRequest {
+ public:
+ PointerLockRequest(BrowserPluginGuest* guest)
+ : guest_(guest) {
+ RecordAction(
+ UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.PointerLock"));
+ }
+
+ virtual void Respond(bool should_allow,
+ const std::string& user_input) OVERRIDE {
+ guest_->SendMessageToEmbedder(
+ new BrowserPluginMsg_SetMouseLock(guest_->instance_id(), should_allow));
+ }
+
+ private:
+ virtual ~PointerLockRequest() {}
+ BrowserPluginGuest* guest_;
+};
+
+namespace {
+const size_t kNumMaxOutstandingPermissionRequests = 1024;
+
+std::string WindowOpenDispositionToString(
+ WindowOpenDisposition window_open_disposition) {
+ switch (window_open_disposition) {
+ case IGNORE_ACTION:
+ return "ignore";
+ case SAVE_TO_DISK:
+ return "save_to_disk";
+ case CURRENT_TAB:
+ return "current_tab";
+ case NEW_BACKGROUND_TAB:
+ return "new_background_tab";
+ case NEW_FOREGROUND_TAB:
+ return "new_foreground_tab";
+ case NEW_WINDOW:
+ return "new_window";
+ case NEW_POPUP:
+ return "new_popup";
+ default:
+ NOTREACHED() << "Unknown Window Open Disposition";
+ return "ignore";
+ }
+}
+
+std::string JavaScriptMessageTypeToString(JavaScriptMessageType message_type) {
+ switch (message_type) {
+ case JAVASCRIPT_MESSAGE_TYPE_ALERT:
+ return "alert";
+ case JAVASCRIPT_MESSAGE_TYPE_CONFIRM:
+ return "confirm";
+ case JAVASCRIPT_MESSAGE_TYPE_PROMPT:
+ return "prompt";
+ default:
+ NOTREACHED() << "Unknown JavaScript Message Type.";
+ return "unknown";
+ }
+}
+
+// Called on IO thread.
+static std::string RetrieveDownloadURLFromRequestId(
+ RenderViewHost* render_view_host,
+ int url_request_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ int render_process_id = render_view_host->GetProcess()->GetID();
+ GlobalRequestID global_id(render_process_id, url_request_id);
+ net::URLRequest* url_request =
+ ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
+ if (url_request)
+ return url_request->url().possibly_invalid_spec();
+ return std::string();
+}
+
+} // namespace
+
+class BrowserPluginGuest::EmbedderRenderViewHostObserver
+ : public RenderViewHostObserver {
+ public:
+ explicit EmbedderRenderViewHostObserver(BrowserPluginGuest* guest)
+ : RenderViewHostObserver(
+ guest->embedder_web_contents()->GetRenderViewHost()),
+ browser_plugin_guest_(guest) {
+ }
+
+ virtual ~EmbedderRenderViewHostObserver() {
+ }
+
+ // RenderViewHostObserver:
+ virtual void RenderViewHostDestroyed(
+ RenderViewHost* render_view_host) OVERRIDE {
+ browser_plugin_guest_->embedder_web_contents_ = NULL;
+ browser_plugin_guest_->Destroy();
+ }
+
+ private:
+ BrowserPluginGuest* browser_plugin_guest_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmbedderRenderViewHostObserver);
+};
+
+BrowserPluginGuest::BrowserPluginGuest(
+ int instance_id,
+ WebContentsImpl* web_contents,
+ BrowserPluginGuest* opener,
+ bool has_render_view)
+ : WebContentsObserver(web_contents),
+ weak_ptr_factory_(this),
+ embedder_web_contents_(NULL),
+ instance_id_(instance_id),
+ damage_buffer_sequence_id_(0),
+ damage_buffer_size_(0),
+ damage_buffer_scale_factor_(1.0f),
+ guest_device_scale_factor_(1.0f),
+ guest_hang_timeout_(
+ base::TimeDelta::FromMilliseconds(kHungRendererDelayMs)),
+ focused_(false),
+ mouse_locked_(false),
+ pending_lock_request_(false),
+ embedder_visible_(true),
+ next_permission_request_id_(browser_plugin::kInvalidPermissionRequestID),
+ has_render_view_(has_render_view),
+ last_seen_auto_size_enabled_(false) {
+ DCHECK(web_contents);
+ web_contents->SetDelegate(this);
+ if (opener)
+ opener_ = opener->AsWeakPtr();
+ GetWebContents()->GetBrowserPluginGuestManager()->AddGuest(instance_id_,
+ GetWebContents());
+}
+
+bool BrowserPluginGuest::AddMessageToConsole(WebContents* source,
+ int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id) {
+ if (!delegate_)
+ return false;
+
+ delegate_->AddMessageToConsole(level, message, line_no, source_id);
+ return true;
+}
+
+void BrowserPluginGuest::DestroyUnattachedWindows() {
+ // Destroy() reaches in and removes the BrowserPluginGuest from its opener's
+ // pending_new_windows_ set. To avoid mutating the set while iterating, we
+ // create a copy of the pending new windows set and iterate over the copy.
+ PendingWindowMap pending_new_windows(pending_new_windows_);
+ // Clean up unattached new windows opened by this guest.
+ for (PendingWindowMap::const_iterator it = pending_new_windows.begin();
+ it != pending_new_windows.end(); ++it) {
+ it->first->Destroy();
+ }
+ // All pending windows should be removed from the set after Destroy() is
+ // called on all of them.
+ DCHECK_EQ(0ul, pending_new_windows_.size());
+}
+
+void BrowserPluginGuest::RespondToPermissionRequest(
+ int request_id,
+ bool should_allow,
+ const std::string& user_input) {
+ RequestMap::iterator request_itr = permission_request_map_.find(request_id);
+ if (request_itr == permission_request_map_.end()) {
+ LOG(INFO) << "Not a valid request ID.";
+ return;
+ }
+ request_itr->second->Respond(should_allow, user_input);
+ permission_request_map_.erase(request_itr);
+}
+
+int BrowserPluginGuest::RequestPermission(
+ BrowserPluginPermissionType permission_type,
+ scoped_refptr<BrowserPluginGuest::PermissionRequest> request,
+ const base::DictionaryValue& request_info) {
+ if (!delegate_) {
+ request->Respond(false, "");
+ return browser_plugin::kInvalidPermissionRequestID;
+ }
+
+ int request_id = ++next_permission_request_id_;
+ permission_request_map_[request_id] = request;
+
+ BrowserPluginGuestDelegate::PermissionResponseCallback callback =
+ base::Bind(&BrowserPluginGuest::RespondToPermissionRequest,
+ AsWeakPtr(),
+ request_id);
+ // If BrowserPluginGuestDelegate hasn't handled the permission then we simply
+ // reject it immediately.
+ if (!delegate_->RequestPermission(permission_type, request_info, callback))
+ callback.Run(false, "");
+
+ return request_id;
+}
+
+void BrowserPluginGuest::Destroy() {
+ if (!attached() && opener())
+ opener()->pending_new_windows_.erase(this);
+ DestroyUnattachedWindows();
+ GetWebContents()->GetBrowserPluginGuestManager()->RemoveGuest(instance_id_);
+ delete GetWebContents();
+}
+
+bool BrowserPluginGuest::OnMessageReceivedFromEmbedder(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK,
+ OnSwapBuffersACK)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_CompositorFrameACK,
+ OnCompositorFrameACK)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_DragStatusUpdate,
+ OnDragStatusUpdate)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ExecuteEditCommand,
+ OnExecuteEditCommand)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_HandleInputEvent,
+ OnHandleInputEvent)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_LockMouse_ACK, OnLockMouseAck)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_NavigateGuest, OnNavigateGuest)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_PluginDestroyed, OnPluginDestroyed)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ResizeGuest, OnResizeGuest)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetAutoSize, OnSetSize)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent,
+ OnSetEditCommandsForNextKeyEvent)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetFocus, OnSetFocus)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetName, OnSetName)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetVisibility, OnSetVisibility)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UnlockMouse_ACK, OnUnlockMouseAck)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateGeometry, OnUpdateGeometry)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateRect_ACK, OnUpdateRectACK)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void BrowserPluginGuest::Initialize(
+ WebContentsImpl* embedder_web_contents,
+ const BrowserPluginHostMsg_Attach_Params& params) {
+ focused_ = params.focused;
+ guest_visible_ = params.visible;
+ guest_window_rect_ = params.resize_guest_params.view_rect;
+
+ if (!params.name.empty())
+ name_ = params.name;
+ auto_size_enabled_ = params.auto_size_params.enable;
+ max_auto_size_ = params.auto_size_params.max_size;
+ min_auto_size_ = params.auto_size_params.min_size;
+
+ // Once a BrowserPluginGuest has an embedder WebContents, it's considered to
+ // be attached.
+ embedder_web_contents_ = embedder_web_contents;
+
+ WebContentsViewGuest* new_view =
+ static_cast<WebContentsViewGuest*>(GetWebContents()->GetView());
+ new_view->OnGuestInitialized(embedder_web_contents->GetView());
+
+ // |render_view_host| manages the ownership of this BrowserPluginGuestHelper.
+ new BrowserPluginGuestHelper(this, GetWebContents()->GetRenderViewHost());
+
+ RendererPreferences* renderer_prefs =
+ GetWebContents()->GetMutableRendererPrefs();
+ // Copy renderer preferences (and nothing else) from the embedder's
+ // WebContents to the guest.
+ //
+ // For GTK and Aura this is necessary to get proper renderer configuration
+ // values for caret blinking interval, colors related to selection and
+ // focus.
+ *renderer_prefs = *embedder_web_contents_->GetMutableRendererPrefs();
+
+ // We would like the guest to report changes to frame names so that we can
+ // update the BrowserPlugin's corresponding 'name' attribute.
+ // TODO(fsamuel): Remove this once http://crbug.com/169110 is addressed.
+ renderer_prefs->report_frame_name_changes = true;
+ // Navigation is disabled in Chrome Apps. We want to make sure guest-initiated
+ // navigations still continue to function inside the app.
+ renderer_prefs->browser_handles_all_top_level_requests = false;
+
+ // Listen to embedder visibility changes so that the guest is in a 'shown'
+ // state if both the embedder is visible and the BrowserPlugin is marked as
+ // visible.
+ notification_registrar_.Add(
+ this, NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
+ Source<WebContents>(embedder_web_contents_));
+
+ embedder_rvh_observer_.reset(new EmbedderRenderViewHostObserver(this));
+
+ OnSetSize(instance_id_, params.auto_size_params, params.resize_guest_params);
+
+ // Create a swapped out RenderView for the guest in the embedder render
+ // process, so that the embedder can access the guest's window object.
+ int guest_routing_id =
+ GetWebContents()->CreateSwappedOutRenderView(
+ embedder_web_contents_->GetSiteInstance());
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_GuestContentWindowReady(instance_id_,
+ guest_routing_id));
+
+ if (!params.src.empty())
+ OnNavigateGuest(instance_id_, params.src);
+
+ has_render_view_ = true;
+
+ if (!embedder_web_contents_->
+ GetWebkitPrefs().accelerated_compositing_enabled) {
+ WebPreferences prefs = GetWebContents()->GetWebkitPrefs();
+ prefs.accelerated_compositing_enabled = false;
+ GetWebContents()->GetRenderViewHost()->UpdateWebkitPreferences(prefs);
+ }
+
+ // Enable input method for guest if it's enabled for the embedder.
+ if (static_cast<RenderViewHostImpl*>(
+ embedder_web_contents_->GetRenderViewHost())->input_method_active()) {
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ GetWebContents()->GetRenderViewHost());
+ guest_rvh->SetInputMethodActive(true);
+ }
+}
+
+BrowserPluginGuest::~BrowserPluginGuest() {
+ while (!pending_messages_.empty()) {
+ delete pending_messages_.front();
+ pending_messages_.pop();
+ }
+}
+
+// static
+BrowserPluginGuest* BrowserPluginGuest::Create(
+ int instance_id,
+ WebContentsImpl* web_contents,
+ scoped_ptr<base::DictionaryValue> extra_params) {
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Create"));
+ BrowserPluginGuest* guest = NULL;
+ if (factory_) {
+ guest = factory_->CreateBrowserPluginGuest(instance_id, web_contents);
+ } else {
+ guest = new BrowserPluginGuest(instance_id, web_contents, NULL, false);
+ }
+ web_contents->SetBrowserPluginGuest(guest);
+ BrowserPluginGuestDelegate* delegate = NULL;
+ GetContentClient()->browser()->GuestWebContentsCreated(
+ web_contents, NULL, &delegate, extra_params.Pass());
+ guest->SetDelegate(delegate);
+ return guest;
+}
+
+// static
+BrowserPluginGuest* BrowserPluginGuest::CreateWithOpener(
+ int instance_id,
+ WebContentsImpl* web_contents,
+ BrowserPluginGuest* opener,
+ bool has_render_view) {
+ BrowserPluginGuest* guest =
+ new BrowserPluginGuest(
+ instance_id, web_contents, opener, has_render_view);
+ web_contents->SetBrowserPluginGuest(guest);
+ BrowserPluginGuestDelegate* delegate = NULL;
+ GetContentClient()->browser()->GuestWebContentsCreated(
+ web_contents, opener->GetWebContents(), &delegate,
+ scoped_ptr<base::DictionaryValue>());
+ guest->SetDelegate(delegate);
+ return guest;
+}
+
+RenderWidgetHostView* BrowserPluginGuest::GetEmbedderRenderWidgetHostView() {
+ return embedder_web_contents_->GetRenderWidgetHostView();
+}
+
+void BrowserPluginGuest::UpdateVisibility() {
+ OnSetVisibility(instance_id_, visible());
+}
+
+// screen.
+gfx::Rect BrowserPluginGuest::ToGuestRect(const gfx::Rect& bounds) {
+ gfx::Rect guest_rect(bounds);
+ guest_rect.Offset(guest_window_rect_.OffsetFromOrigin());
+ return guest_rect;
+}
+
+void BrowserPluginGuest::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED: {
+ DCHECK_EQ(Source<WebContents>(source).ptr(), embedder_web_contents_);
+ embedder_visible_ = *Details<bool>(details).ptr();
+ UpdateVisibility();
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification sent.";
+ break;
+ }
+}
+
+void BrowserPluginGuest::AddNewContents(WebContents* source,
+ WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture,
+ bool* was_blocked) {
+ if (was_blocked)
+ *was_blocked = false;
+ RequestNewWindowPermission(static_cast<WebContentsImpl*>(new_contents),
+ disposition, initial_pos, user_gesture);
+}
+
+void BrowserPluginGuest::CanDownload(
+ RenderViewHost* render_view_host,
+ int request_id,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) {
+ if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) {
+ // Deny the download request.
+ callback.Run(false);
+ return;
+ }
+
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&RetrieveDownloadURLFromRequestId,
+ render_view_host, request_id),
+ base::Bind(&BrowserPluginGuest::DidRetrieveDownloadURLFromRequestId,
+ weak_ptr_factory_.GetWeakPtr(),
+ request_method,
+ callback));
+}
+
+void BrowserPluginGuest::CloseContents(WebContents* source) {
+ if (!delegate_)
+ return;
+
+ delegate_->Close();
+}
+
+JavaScriptDialogManager* BrowserPluginGuest::GetJavaScriptDialogManager() {
+ return this;
+}
+
+bool BrowserPluginGuest::HandleContextMenu(const ContextMenuParams& params) {
+ // TODO(fsamuel): We show the regular page context menu handler for now until
+ // we implement the Apps Context Menu API for Browser Plugin (see
+ // http://crbug.com/140315).
+ return false; // Will be handled by WebContentsViewGuest.
+}
+
+void BrowserPluginGuest::HandleKeyboardEvent(
+ WebContents* source,
+ const NativeWebKeyboardEvent& event) {
+ if (!attached())
+ return;
+
+ if (UnlockMouseIfNecessary(event))
+ return;
+
+ if (delegate_ && delegate_->HandleKeyboardEvent(event))
+ return;
+
+ // Send the unhandled keyboard events back to the embedder to reprocess them.
+ // TODO(fsamuel): This introduces the possibility of out-of-order keyboard
+ // events because the guest may be arbitrarily delayed when responding to
+ // keyboard events. In that time, the embedder may have received and processed
+ // additional key events. This needs to be fixed as soon as possible.
+ // See http://crbug.com/229882.
+ embedder_web_contents_->GetDelegate()->HandleKeyboardEvent(
+ web_contents(), event);
+}
+
+WebContents* BrowserPluginGuest::OpenURLFromTab(WebContents* source,
+ const OpenURLParams& params) {
+ // If the guest wishes to navigate away prior to attachment then we save the
+ // navigation to perform upon attachment. Navigation initializes a lot of
+ // state that assumes an embedder exists, such as RenderWidgetHostViewGuest.
+ // Navigation also resumes resource loading which we don't want to allow
+ // until attachment.
+ if (!attached()) {
+ PendingWindowMap::iterator it = opener()->pending_new_windows_.find(this);
+ if (it == opener()->pending_new_windows_.end())
+ return NULL;
+ const NewWindowInfo& old_target_url = it->second;
+ NewWindowInfo new_window_info(params.url, old_target_url.name);
+ new_window_info.changed = new_window_info.url != old_target_url.url;
+ it->second = new_window_info;
+ return NULL;
+ }
+ // This can happen for cross-site redirects.
+ source->GetController().LoadURL(
+ params.url, params.referrer, params.transition, std::string());
+ return source;
+}
+
+void BrowserPluginGuest::WebContentsCreated(WebContents* source_contents,
+ int64 source_frame_id,
+ const string16& frame_name,
+ const GURL& target_url,
+ WebContents* new_contents) {
+ WebContentsImpl* new_contents_impl =
+ static_cast<WebContentsImpl*>(new_contents);
+ BrowserPluginGuest* guest = new_contents_impl->GetBrowserPluginGuest();
+ guest->opener_ = AsWeakPtr();
+ std::string guest_name = UTF16ToUTF8(frame_name);
+ guest->name_ = guest_name;
+ // Take ownership of the new guest until it is attached to the embedder's DOM
+ // tree to avoid leaking a guest if this guest is destroyed before attaching
+ // the new guest.
+ pending_new_windows_.insert(
+ std::make_pair(guest, NewWindowInfo(target_url, guest_name)));
+}
+
+void BrowserPluginGuest::RendererUnresponsive(WebContents* source) {
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Hung"));
+ if (!delegate_)
+ return;
+ delegate_->RendererUnresponsive();
+}
+
+void BrowserPluginGuest::RendererResponsive(WebContents* source) {
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Responsive"));
+ if (!delegate_)
+ return;
+ delegate_->RendererResponsive();
+}
+
+void BrowserPluginGuest::RunFileChooser(WebContents* web_contents,
+ const FileChooserParams& params) {
+ embedder_web_contents_->GetDelegate()->RunFileChooser(web_contents, params);
+}
+
+bool BrowserPluginGuest::ShouldFocusPageAfterCrash() {
+ // Rather than managing focus in WebContentsImpl::RenderViewReady, we will
+ // manage the focus ourselves.
+ return false;
+}
+
+WebContentsImpl* BrowserPluginGuest::GetWebContents() {
+ return static_cast<WebContentsImpl*>(web_contents());
+}
+
+base::SharedMemory* BrowserPluginGuest::GetDamageBufferFromEmbedder(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params) {
+ if (!attached()) {
+ LOG(WARNING) << "Attempting to map a damage buffer prior to attachment.";
+ return NULL;
+ }
+#if defined(OS_WIN)
+ base::ProcessHandle handle =
+ embedder_web_contents_->GetRenderProcessHost()->GetHandle();
+ scoped_ptr<base::SharedMemory> shared_buf(
+ new base::SharedMemory(params.damage_buffer_handle, false, handle));
+#elif defined(OS_POSIX)
+ scoped_ptr<base::SharedMemory> shared_buf(
+ new base::SharedMemory(params.damage_buffer_handle, false));
+#endif
+ if (!shared_buf->Map(params.damage_buffer_size)) {
+ LOG(WARNING) << "Unable to map the embedder's damage buffer.";
+ return NULL;
+ }
+ return shared_buf.release();
+}
+
+void BrowserPluginGuest::SetDamageBuffer(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params) {
+ damage_buffer_.reset(GetDamageBufferFromEmbedder(params));
+ // Sanity check: Verify that we've correctly shared the damage buffer memory
+ // between the embedder and browser processes.
+ DCHECK(!damage_buffer_ ||
+ *static_cast<unsigned int*>(damage_buffer_->memory()) == 0xdeadbeef);
+ damage_buffer_sequence_id_ = params.damage_buffer_sequence_id;
+ damage_buffer_size_ = params.damage_buffer_size;
+ damage_view_size_ = params.view_rect.size();
+ damage_buffer_scale_factor_ = params.scale_factor;
+}
+
+gfx::Point BrowserPluginGuest::GetScreenCoordinates(
+ const gfx::Point& relative_position) const {
+ gfx::Point screen_pos(relative_position);
+ screen_pos += guest_window_rect_.OffsetFromOrigin();
+ return screen_pos;
+}
+
+bool BrowserPluginGuest::InAutoSizeBounds(const gfx::Size& size) const {
+ return size.width() <= max_auto_size_.width() &&
+ size.height() <= max_auto_size_.height();
+}
+
+void BrowserPluginGuest::RequestNewWindowPermission(
+ WebContentsImpl* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_bounds,
+ bool user_gesture) {
+ BrowserPluginGuest* guest = new_contents->GetBrowserPluginGuest();
+ PendingWindowMap::iterator it = pending_new_windows_.find(guest);
+ if (it == pending_new_windows_.end())
+ return;
+ const NewWindowInfo& new_window_info = it->second;
+
+ base::DictionaryValue request_info;
+ request_info.Set(browser_plugin::kInitialHeight,
+ base::Value::CreateIntegerValue(initial_bounds.height()));
+ request_info.Set(browser_plugin::kInitialWidth,
+ base::Value::CreateIntegerValue(initial_bounds.width()));
+ request_info.Set(browser_plugin::kTargetURL,
+ base::Value::CreateStringValue(new_window_info.url.spec()));
+ request_info.Set(browser_plugin::kName,
+ base::Value::CreateStringValue(new_window_info.name));
+ request_info.Set(browser_plugin::kWindowID,
+ base::Value::CreateIntegerValue(guest->instance_id()));
+ request_info.Set(browser_plugin::kWindowOpenDisposition,
+ base::Value::CreateStringValue(
+ WindowOpenDispositionToString(disposition)));
+
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW,
+ new NewWindowRequest(guest->instance_id(), this),
+ request_info);
+}
+
+bool BrowserPluginGuest::UnlockMouseIfNecessary(
+ const NativeWebKeyboardEvent& event) {
+ if (!mouse_locked_)
+ return false;
+
+ embedder_web_contents()->GotResponseToLockMouseRequest(false);
+ return true;
+}
+
+void BrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) {
+ if (!attached()) {
+ // Some pages such as data URLs, javascript URLs, and about:blank
+ // do not load external resources and so they load prior to attachment.
+ // As a result, we must save all these IPCs until attachment and then
+ // forward them so that the embedder gets a chance to see and process
+ // the load events.
+ pending_messages_.push(msg);
+ return;
+ }
+ msg->set_routing_id(embedder_web_contents_->GetRoutingID());
+ embedder_web_contents_->Send(msg);
+}
+
+void BrowserPluginGuest::DragSourceEndedAt(int client_x, int client_y,
+ int screen_x, int screen_y, WebKit::WebDragOperation operation) {
+ web_contents()->GetRenderViewHost()->DragSourceEndedAt(client_x, client_y,
+ screen_x, screen_y, operation);
+}
+
+void BrowserPluginGuest::DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y) {
+ web_contents()->GetRenderViewHost()->DragSourceMovedTo(client_x, client_y,
+ screen_x, screen_y);
+}
+
+void BrowserPluginGuest::EndSystemDrag() {
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ GetWebContents()->GetRenderViewHost());
+ guest_rvh->DragSourceSystemDragEnded();
+ // Issue a MouseUp event to get out of a selection state.
+ WebKit::WebMouseEvent mouse_event;
+ mouse_event.type = WebKit::WebInputEvent::MouseUp;
+ mouse_event.button = WebKit::WebMouseEvent::ButtonLeft;
+ guest_rvh->ForwardMouseEvent(mouse_event);
+}
+
+void BrowserPluginGuest::SetDelegate(BrowserPluginGuestDelegate* delegate) {
+ DCHECK(!delegate_);
+ delegate_.reset(delegate);
+}
+
+void BrowserPluginGuest::AskEmbedderForGeolocationPermission(
+ int bridge_id,
+ const GURL& requesting_frame,
+ const GeolocationCallback& callback) {
+ if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) {
+ // Deny the geolocation request.
+ callback.Run(false);
+ return;
+ }
+
+ base::DictionaryValue request_info;
+ request_info.Set(browser_plugin::kURL,
+ base::Value::CreateStringValue(requesting_frame.spec()));
+
+ int request_id =
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_GEOLOCATION,
+ new GeolocationRequest(
+ callback, bridge_id, &weak_ptr_factory_),
+ request_info);
+
+ DCHECK(bridge_id_to_request_id_map_.find(bridge_id) ==
+ bridge_id_to_request_id_map_.end());
+ bridge_id_to_request_id_map_[bridge_id] = request_id;
+}
+
+int BrowserPluginGuest::RemoveBridgeID(int bridge_id) {
+ std::map<int, int>::iterator bridge_itr =
+ bridge_id_to_request_id_map_.find(bridge_id);
+ if (bridge_itr == bridge_id_to_request_id_map_.end())
+ return browser_plugin::kInvalidPermissionRequestID;
+
+ int request_id = bridge_itr->second;
+ bridge_id_to_request_id_map_.erase(bridge_itr);
+ return request_id;
+}
+
+void BrowserPluginGuest::CancelGeolocationRequest(int bridge_id) {
+ int request_id = RemoveBridgeID(bridge_id);
+ RequestMap::iterator request_itr = permission_request_map_.find(request_id);
+ if (request_itr == permission_request_map_.end())
+ return;
+ permission_request_map_.erase(request_itr);
+}
+
+void BrowserPluginGuest::SetGeolocationPermission(GeolocationCallback callback,
+ int bridge_id,
+ bool allowed) {
+ callback.Run(allowed);
+ RemoveBridgeID(bridge_id);
+}
+
+void BrowserPluginGuest::SendQueuedMessages() {
+ if (!attached())
+ return;
+
+ while (!pending_messages_.empty()) {
+ IPC::Message* message = pending_messages_.front();
+ pending_messages_.pop();
+ SendMessageToEmbedder(message);
+ }
+}
+
+void BrowserPluginGuest::DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) {
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.DidNavigate"));
+}
+
+void BrowserPluginGuest::DidStopLoading(RenderViewHost* render_view_host) {
+ bool disable_dragdrop = !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableBrowserPluginDragDrop);
+ if (disable_dragdrop) {
+ // Initiating a drag from inside a guest is currently not supported without
+ // the kEnableBrowserPluginDragDrop flag on a linux platform. So inject some
+ // JS to disable it. http://crbug.com/161112
+ const char script[] = "window.addEventListener('dragstart', function() { "
+ " window.event.preventDefault(); "
+ "});";
+ render_view_host->ExecuteJavascriptInWebFrame(string16(),
+ ASCIIToUTF16(script));
+ }
+}
+
+void BrowserPluginGuest::RenderViewReady() {
+ // TODO(fsamuel): Investigate whether it's possible to update state earlier
+ // here (see http://crbug.com/158151).
+ Send(new InputMsg_SetFocus(routing_id(), focused_));
+ UpdateVisibility();
+ RenderViewHost* rvh = GetWebContents()->GetRenderViewHost();
+ if (auto_size_enabled_)
+ rvh->EnableAutoResize(min_auto_size_, max_auto_size_);
+ else
+ rvh->DisableAutoResize(damage_view_size_);
+
+ Send(new ViewMsg_SetName(routing_id(), name_));
+
+ RenderWidgetHostImpl::From(rvh)->
+ set_hung_renderer_delay_ms(guest_hang_timeout_);
+}
+
+void BrowserPluginGuest::RenderProcessGone(base::TerminationStatus status) {
+ SendMessageToEmbedder(new BrowserPluginMsg_GuestGone(instance_id()));
+ switch (status) {
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Killed"));
+ break;
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Crashed"));
+ break;
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.AbnormalDeath"));
+ break;
+ default:
+ break;
+ }
+ // TODO(fsamuel): Consider whether we should be clearing
+ // |permission_request_map_| here.
+ if (delegate_)
+ delegate_->GuestProcessGone(status);
+}
+
+// static
+void BrowserPluginGuest::AcknowledgeBufferPresent(
+ int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.mailbox_name = mailbox_name;
+ ack_params.sync_point = sync_point;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(route_id,
+ gpu_host_id,
+ ack_params);
+}
+
+// static
+bool BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(
+ const IPC::Message& message) {
+ switch (message.type()) {
+ case BrowserPluginHostMsg_BuffersSwappedACK::ID:
+ case BrowserPluginHostMsg_CompositorFrameACK::ID:
+ case BrowserPluginHostMsg_DragStatusUpdate::ID:
+ case BrowserPluginHostMsg_ExecuteEditCommand::ID:
+ case BrowserPluginHostMsg_HandleInputEvent::ID:
+ case BrowserPluginHostMsg_LockMouse_ACK::ID:
+ case BrowserPluginHostMsg_NavigateGuest::ID:
+ case BrowserPluginHostMsg_PluginDestroyed::ID:
+ case BrowserPluginHostMsg_ResizeGuest::ID:
+ case BrowserPluginHostMsg_SetAutoSize::ID:
+ case BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent::ID:
+ case BrowserPluginHostMsg_SetFocus::ID:
+ case BrowserPluginHostMsg_SetName::ID:
+ case BrowserPluginHostMsg_SetVisibility::ID:
+ case BrowserPluginHostMsg_UnlockMouse_ACK::ID:
+ case BrowserPluginHostMsg_UpdateGeometry::ID:
+ case BrowserPluginHostMsg_UpdateRect_ACK::ID:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool BrowserPluginGuest::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers,
+ OnHasTouchEventHandlers)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_LockMouse, OnLockMouse)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnSetCursor)
+ #if defined(OS_MACOSX)
+ // MacOSX creates and populates platform-specific select drop-down menus
+ // whereas other platforms merely create a popup window that the guest
+ // renderer process paints inside.
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowPopup, OnShowPopup)
+ #endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UnlockMouse, OnUnlockMouse)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFrameName, OnUpdateFrameName)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnUpdateRect)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void BrowserPluginGuest::Attach(
+ WebContentsImpl* embedder_web_contents,
+ BrowserPluginHostMsg_Attach_Params params) {
+ if (attached())
+ return;
+
+ // Clear parameters that get inherited from the opener.
+ params.storage_partition_id.clear();
+ params.persist_storage = false;
+ params.src.clear();
+
+ // If a RenderView has already been created for this new window, then we need
+ // to initialize the browser-side state now so that the RenderViewHostManager
+ // does not create a new RenderView on navigation.
+ if (has_render_view_) {
+ static_cast<RenderViewHostImpl*>(
+ GetWebContents()->GetRenderViewHost())->Init();
+ WebContentsViewGuest* new_view =
+ static_cast<WebContentsViewGuest*>(GetWebContents()->GetView());
+ new_view->CreateViewForWidget(web_contents()->GetRenderViewHost());
+ }
+
+ // We need to do a navigation here if the target URL has changed between
+ // the time the WebContents was created and the time it was attached.
+ // We also need to do an initial navigation if a RenderView was never
+ // created for the new window in cases where there is no referrer.
+ PendingWindowMap::iterator it = opener()->pending_new_windows_.find(this);
+ if (it != opener()->pending_new_windows_.end()) {
+ const NewWindowInfo& new_window_info = it->second;
+ if (new_window_info.changed || !has_render_view_)
+ params.src = it->second.url.spec();
+ } else {
+ NOTREACHED();
+ }
+
+ // Once a new guest is attached to the DOM of the embedder page, then the
+ // lifetime of the new guest is no longer managed by the opener guest.
+ opener()->pending_new_windows_.erase(this);
+
+ // The guest's frame name takes precedence over the BrowserPlugin's name.
+ // The guest's frame name is assigned in
+ // BrowserPluginGuest::WebContentsCreated.
+ if (!name_.empty())
+ params.name.clear();
+
+ Initialize(embedder_web_contents, params);
+
+ // Inform the embedder of the guest's information.
+ // We pull the partition information from the site's URL, which is of the form
+ // guest://site/{persist}?{partition_name}.
+ const GURL& site_url = GetWebContents()->GetSiteInstance()->GetSiteURL();
+ BrowserPluginMsg_Attach_ACK_Params ack_params;
+ ack_params.storage_partition_id = site_url.query();
+ ack_params.persist_storage =
+ site_url.path().find("persist") != std::string::npos;
+ ack_params.name = name_;
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_Attach_ACK(instance_id_, ack_params));
+
+ SendQueuedMessages();
+
+ RecordAction(UserMetricsAction("BrowserPlugin.Guest.Attached"));
+}
+
+void BrowserPluginGuest::OnCompositorFrameACK(
+ int instance_id,
+ int route_id,
+ uint32 output_surface_id,
+ int renderer_host_id,
+ const cc::CompositorFrameAck& ack) {
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(route_id,
+ output_surface_id,
+ renderer_host_id,
+ ack);
+}
+
+void BrowserPluginGuest::OnDragStatusUpdate(int instance_id,
+ WebKit::WebDragStatus drag_status,
+ const DropData& drop_data,
+ WebKit::WebDragOperationsMask mask,
+ const gfx::Point& location) {
+ RenderViewHost* host = GetWebContents()->GetRenderViewHost();
+ switch (drag_status) {
+ case WebKit::WebDragStatusEnter:
+ embedder_web_contents_->GetBrowserPluginEmbedder()->DragEnteredGuest(
+ this);
+ host->DragTargetDragEnter(drop_data, location, location, mask, 0);
+ break;
+ case WebKit::WebDragStatusOver:
+ host->DragTargetDragOver(location, location, mask, 0);
+ break;
+ case WebKit::WebDragStatusLeave:
+ embedder_web_contents_->GetBrowserPluginEmbedder()->DragLeftGuest(this);
+ host->DragTargetDragLeave();
+ break;
+ case WebKit::WebDragStatusDrop:
+ host->DragTargetDrop(location, location, 0);
+ EndSystemDrag();
+ break;
+ case WebKit::WebDragStatusUnknown:
+ NOTREACHED();
+ }
+}
+
+void BrowserPluginGuest::OnExecuteEditCommand(int instance_id,
+ const std::string& name) {
+ Send(new InputMsg_ExecuteEditCommand(routing_id(), name, std::string()));
+}
+
+void BrowserPluginGuest::OnHandleInputEvent(
+ int instance_id,
+ const gfx::Rect& guest_window_rect,
+ const WebKit::WebInputEvent* event) {
+ guest_window_rect_ = guest_window_rect;
+ // If the embedder's RWHV is destroyed then that means that the embedder's
+ // window has been closed but the embedder's WebContents has not yet been
+ // destroyed. Computing screen coordinates of a BrowserPlugin only makes sense
+ // if there is a visible embedder.
+ if (embedder_web_contents_->GetRenderWidgetHostView()) {
+ guest_screen_rect_ = guest_window_rect;
+ guest_screen_rect_.Offset(
+ embedder_web_contents_->GetRenderWidgetHostView()->
+ GetViewBounds().OffsetFromOrigin());
+ }
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ GetWebContents()->GetRenderViewHost());
+
+ if (WebKit::WebInputEvent::isMouseEventType(event->type)) {
+ guest_rvh->ForwardMouseEvent(
+ *static_cast<const WebKit::WebMouseEvent*>(event));
+ return;
+ }
+
+ if (event->type == WebKit::WebInputEvent::MouseWheel) {
+ guest_rvh->ForwardWheelEvent(
+ *static_cast<const WebKit::WebMouseWheelEvent*>(event));
+ return;
+ }
+
+ if (WebKit::WebInputEvent::isKeyboardEventType(event->type)) {
+ RenderViewHostImpl* embedder_rvh = static_cast<RenderViewHostImpl*>(
+ embedder_web_contents_->GetRenderViewHost());
+ if (!embedder_rvh->GetLastKeyboardEvent())
+ return;
+ NativeWebKeyboardEvent keyboard_event(
+ *embedder_rvh->GetLastKeyboardEvent());
+ guest_rvh->ForwardKeyboardEvent(keyboard_event);
+ return;
+ }
+
+ if (WebKit::WebInputEvent::isTouchEventType(event->type)) {
+ guest_rvh->ForwardTouchEventWithLatencyInfo(
+ *static_cast<const WebKit::WebTouchEvent*>(event),
+ ui::LatencyInfo());
+ return;
+ }
+
+ if (WebKit::WebInputEvent::isGestureEventType(event->type)) {
+ guest_rvh->ForwardGestureEvent(
+ *static_cast<const WebKit::WebGestureEvent*>(event));
+ return;
+ }
+}
+
+void BrowserPluginGuest::OnLockMouse(bool user_gesture,
+ bool last_unlocked_by_target,
+ bool privileged) {
+ if (pending_lock_request_ ||
+ (permission_request_map_.size() >=
+ kNumMaxOutstandingPermissionRequests)) {
+ // Immediately reject the lock because only one pointerLock may be active
+ // at a time.
+ Send(new ViewMsg_LockMouse_ACK(routing_id(), false));
+ return;
+ }
+ pending_lock_request_ = true;
+ base::DictionaryValue request_info;
+ request_info.Set(browser_plugin::kUserGesture,
+ base::Value::CreateBooleanValue(user_gesture));
+ request_info.Set(browser_plugin::kLastUnlockedBySelf,
+ base::Value::CreateBooleanValue(last_unlocked_by_target));
+ request_info.Set(browser_plugin::kURL,
+ base::Value::CreateStringValue(
+ web_contents()->GetLastCommittedURL().spec()));
+
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_POINTER_LOCK,
+ new PointerLockRequest(this),
+ request_info);
+}
+
+void BrowserPluginGuest::OnLockMouseAck(int instance_id, bool succeeded) {
+ Send(new ViewMsg_LockMouse_ACK(routing_id(), succeeded));
+ pending_lock_request_ = false;
+ if (succeeded)
+ mouse_locked_ = true;
+}
+
+void BrowserPluginGuest::OnNavigateGuest(
+ int instance_id,
+ const std::string& src) {
+ GURL url(src);
+ // We do not load empty urls in web_contents.
+ // If a guest sets empty src attribute after it has navigated to some
+ // non-empty page, the action is considered no-op. This empty src navigation
+ // should never be sent to BrowserPluginGuest (browser process).
+ DCHECK(!src.empty());
+ if (!src.empty()) {
+ // As guests do not swap processes on navigation, only navigations to
+ // normal web URLs are supported. No protocol handlers are installed for
+ // other schemes (e.g., WebUI or extensions), and no permissions or bindings
+ // can be granted to the guest process.
+ GetWebContents()->GetController().LoadURL(url, Referrer(),
+ PAGE_TRANSITION_AUTO_TOPLEVEL,
+ std::string());
+ }
+}
+
+void BrowserPluginGuest::OnPluginDestroyed(int instance_id) {
+ Destroy();
+}
+
+void BrowserPluginGuest::OnResizeGuest(
+ int instance_id,
+ const BrowserPluginHostMsg_ResizeGuest_Params& params) {
+ if (!params.size_changed)
+ return;
+ // BrowserPlugin manages resize flow control itself and does not depend
+ // on RenderWidgetHost's mechanisms for flow control, so we reset those flags
+ // here. If we are setting the size for the first time before navigating then
+ // BrowserPluginGuest does not yet have a RenderViewHost.
+ if (GetWebContents()->GetRenderViewHost()) {
+ RenderWidgetHostImpl* render_widget_host =
+ RenderWidgetHostImpl::From(GetWebContents()->GetRenderViewHost());
+ render_widget_host->ResetSizeAndRepaintPendingFlags();
+
+ if (guest_device_scale_factor_ != params.scale_factor) {
+ guest_device_scale_factor_ = params.scale_factor;
+ render_widget_host->NotifyScreenInfoChanged();
+ }
+ }
+ // When autosize is turned off and as a result there is a layout change, we
+ // send a sizechanged event.
+ if (!auto_size_enabled_ && last_seen_auto_size_enabled_ &&
+ !params.view_rect.size().IsEmpty() && delegate_) {
+ delegate_->SizeChanged(last_seen_view_size_, params.view_rect.size());
+ last_seen_auto_size_enabled_ = false;
+ }
+ // Invalid damage buffer means we are in HW compositing mode,
+ // so just resize the WebContents and repaint if needed.
+ if (!base::SharedMemory::IsHandleValid(params.damage_buffer_handle)) {
+ if (!params.view_rect.size().IsEmpty())
+ GetWebContents()->GetView()->SizeContents(params.view_rect.size());
+ if (params.repaint)
+ Send(new ViewMsg_Repaint(routing_id(), params.view_rect.size()));
+ return;
+ }
+ SetDamageBuffer(params);
+ GetWebContents()->GetView()->SizeContents(params.view_rect.size());
+ if (params.repaint)
+ Send(new ViewMsg_Repaint(routing_id(), params.view_rect.size()));
+}
+
+void BrowserPluginGuest::OnSetFocus(int instance_id, bool focused) {
+ if (focused_ == focused)
+ return;
+ focused_ = focused;
+ Send(new InputMsg_SetFocus(routing_id(), focused));
+ if (!focused && mouse_locked_)
+ OnUnlockMouse();
+}
+
+void BrowserPluginGuest::OnSetName(int instance_id, const std::string& name) {
+ if (name == name_)
+ return;
+ name_ = name;
+ Send(new ViewMsg_SetName(routing_id(), name));
+}
+
+void BrowserPluginGuest::OnSetSize(
+ int instance_id,
+ const BrowserPluginHostMsg_AutoSize_Params& auto_size_params,
+ const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params) {
+ bool old_auto_size_enabled = auto_size_enabled_;
+ gfx::Size old_max_size = max_auto_size_;
+ gfx::Size old_min_size = min_auto_size_;
+ auto_size_enabled_ = auto_size_params.enable;
+ max_auto_size_ = auto_size_params.max_size;
+ min_auto_size_ = auto_size_params.min_size;
+ if (auto_size_enabled_ && (!old_auto_size_enabled ||
+ (old_max_size != max_auto_size_) ||
+ (old_min_size != min_auto_size_))) {
+ GetWebContents()->GetRenderViewHost()->EnableAutoResize(
+ min_auto_size_, max_auto_size_);
+ // TODO(fsamuel): If we're changing autosize parameters, then we force
+ // the guest to completely repaint itself, because BrowserPlugin has
+ // allocated a new damage buffer and expects a full frame of pixels.
+ // Ideally, we shouldn't need to do this because we shouldn't need to
+ // allocate a new damage buffer unless |max_auto_size_| has changed.
+ // However, even in that case, layout may not change and so we may
+ // not get a full frame worth of pixels.
+ Send(new ViewMsg_Repaint(routing_id(), max_auto_size_));
+ } else if (!auto_size_enabled_ && old_auto_size_enabled) {
+ GetWebContents()->GetRenderViewHost()->DisableAutoResize(
+ resize_guest_params.view_rect.size());
+ }
+ OnResizeGuest(instance_id_, resize_guest_params);
+}
+
+void BrowserPluginGuest::OnSetEditCommandsForNextKeyEvent(
+ int instance_id,
+ const std::vector<EditCommand>& edit_commands) {
+ Send(new InputMsg_SetEditCommandsForNextKeyEvent(routing_id(),
+ edit_commands));
+}
+
+void BrowserPluginGuest::OnSetVisibility(int instance_id, bool visible) {
+ guest_visible_ = visible;
+ if (embedder_visible_ && guest_visible_)
+ GetWebContents()->WasShown();
+ else
+ GetWebContents()->WasHidden();
+}
+
+void BrowserPluginGuest::OnSwapBuffersACK(int instance_id,
+ int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point) {
+ AcknowledgeBufferPresent(route_id, gpu_host_id, mailbox_name, sync_point);
+
+// This is only relevant on MACOSX and WIN when threaded compositing
+// is not enabled. In threaded mode, above ACK is sufficient.
+#if defined(OS_MACOSX) || defined(OS_WIN)
+ RenderWidgetHostImpl* render_widget_host =
+ RenderWidgetHostImpl::From(GetWebContents()->GetRenderViewHost());
+ render_widget_host->AcknowledgeSwapBuffersToRenderer();
+#endif // defined(OS_MACOSX) || defined(OS_WIN)
+}
+
+void BrowserPluginGuest::OnUnlockMouse() {
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_SetMouseLock(instance_id(), false));
+}
+
+void BrowserPluginGuest::OnUnlockMouseAck(int instance_id) {
+ // mouse_locked_ could be false here if the lock attempt was cancelled due
+ // to window focus, or for various other reasons before the guest was informed
+ // of the lock's success.
+ if (mouse_locked_)
+ Send(new ViewMsg_MouseLockLost(routing_id()));
+ mouse_locked_ = false;
+}
+
+void BrowserPluginGuest::OnUpdateRectACK(
+ int instance_id,
+ bool needs_ack,
+ const BrowserPluginHostMsg_AutoSize_Params& auto_size_params,
+ const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params) {
+ // Only the software path expects an ACK.
+ if (needs_ack)
+ Send(new ViewMsg_UpdateRect_ACK(routing_id()));
+ OnSetSize(instance_id_, auto_size_params, resize_guest_params);
+}
+
+void BrowserPluginGuest::OnUpdateGeometry(int instance_id,
+ const gfx::Rect& view_rect) {
+ // The plugin has moved within the embedder without resizing or the
+ // embedder/container's view rect changing.
+ guest_window_rect_ = view_rect;
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ GetWebContents()->GetRenderViewHost());
+ if (rvh)
+ rvh->SendScreenRects();
+}
+
+void BrowserPluginGuest::OnHasTouchEventHandlers(bool accept) {
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_ShouldAcceptTouchEvents(instance_id(), accept));
+}
+
+void BrowserPluginGuest::OnSetCursor(const WebCursor& cursor) {
+ SendMessageToEmbedder(new BrowserPluginMsg_SetCursor(instance_id(), cursor));
+}
+
+#if defined(OS_MACOSX)
+void BrowserPluginGuest::OnShowPopup(
+ const ViewHostMsg_ShowPopup_Params& params) {
+ gfx::Rect translated_bounds(params.bounds);
+ translated_bounds.Offset(guest_window_rect_.OffsetFromOrigin());
+ BrowserPluginPopupMenuHelper popup_menu_helper(
+ embedder_web_contents_->GetRenderViewHost(),
+ GetWebContents()->GetRenderViewHost());
+ popup_menu_helper.ShowPopupMenu(translated_bounds,
+ params.item_height,
+ params.item_font_size,
+ params.selected_item,
+ params.popup_items,
+ params.right_aligned,
+ params.allow_multiple_selection);
+}
+#endif
+
+void BrowserPluginGuest::OnShowWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ GetWebContents()->ShowCreatedWidget(route_id, initial_pos);
+}
+
+void BrowserPluginGuest::OnTakeFocus(bool reverse) {
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_AdvanceFocus(instance_id(), reverse));
+}
+
+void BrowserPluginGuest::OnUpdateFrameName(int frame_id,
+ bool is_top_level,
+ const std::string& name) {
+ if (!is_top_level)
+ return;
+
+ name_ = name;
+ SendMessageToEmbedder(new BrowserPluginMsg_UpdatedName(instance_id_, name));
+}
+
+void BrowserPluginGuest::RequestMediaAccessPermission(
+ WebContents* web_contents,
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) {
+ if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) {
+ // Deny the media request.
+ callback.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>());
+ return;
+ }
+
+ base::DictionaryValue request_info;
+ request_info.Set(
+ browser_plugin::kURL,
+ base::Value::CreateStringValue(request.security_origin.spec()));
+
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_MEDIA,
+ new MediaRequest(request, callback, this),
+ request_info);
+}
+
+void BrowserPluginGuest::RunJavaScriptDialog(
+ WebContents* web_contents,
+ const GURL& origin_url,
+ const std::string& accept_lang,
+ JavaScriptMessageType javascript_message_type,
+ const string16& message_text,
+ const string16& default_prompt_text,
+ const DialogClosedCallback& callback,
+ bool* did_suppress_message) {
+ if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) {
+ // Cancel the dialog.
+ callback.Run(false, string16());
+ return;
+ }
+ base::DictionaryValue request_info;
+ request_info.Set(
+ browser_plugin::kDefaultPromptText,
+ base::Value::CreateStringValue(UTF16ToUTF8(default_prompt_text)));
+ request_info.Set(
+ browser_plugin::kMessageText,
+ base::Value::CreateStringValue(UTF16ToUTF8(message_text)));
+ request_info.Set(
+ browser_plugin::kMessageType,
+ base::Value::CreateStringValue(
+ JavaScriptMessageTypeToString(javascript_message_type)));
+ request_info.Set(
+ browser_plugin::kURL,
+ base::Value::CreateStringValue(origin_url.spec()));
+
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_JAVASCRIPT_DIALOG,
+ new JavaScriptDialogRequest(callback),
+ request_info);
+}
+
+void BrowserPluginGuest::RunBeforeUnloadDialog(
+ WebContents* web_contents,
+ const string16& message_text,
+ bool is_reload,
+ const DialogClosedCallback& callback) {
+ // This is called if the guest has a beforeunload event handler.
+ // This callback allows navigation to proceed.
+ callback.Run(true, string16());
+}
+
+bool BrowserPluginGuest::HandleJavaScriptDialog(
+ WebContents* web_contents,
+ bool accept,
+ const string16* prompt_override) {
+ return false;
+}
+
+void BrowserPluginGuest::CancelActiveAndPendingDialogs(
+ WebContents* web_contents) {
+}
+
+void BrowserPluginGuest::WebContentsDestroyed(WebContents* web_contents) {
+}
+
+void BrowserPluginGuest::OnUpdateRect(
+ const ViewHostMsg_UpdateRect_Params& params) {
+ BrowserPluginMsg_UpdateRect_Params relay_params;
+ relay_params.view_size = params.view_size;
+ relay_params.scale_factor = params.scale_factor;
+ relay_params.is_resize_ack = ViewHostMsg_UpdateRect_Flags::is_resize_ack(
+ params.flags);
+ relay_params.needs_ack = params.needs_ack;
+
+ bool size_changed = last_seen_view_size_ != params.view_size;
+ gfx::Size old_size = last_seen_view_size_;
+ last_seen_view_size_ = params.view_size;
+
+ if ((auto_size_enabled_ || last_seen_auto_size_enabled_) &&
+ size_changed && delegate_) {
+ delegate_->SizeChanged(old_size, last_seen_view_size_);
+ }
+ last_seen_auto_size_enabled_ = auto_size_enabled_;
+
+ // HW accelerated case, acknowledge resize only
+ if (!params.needs_ack || !damage_buffer_) {
+ relay_params.damage_buffer_sequence_id = 0;
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_UpdateRect(instance_id(), relay_params));
+ return;
+ }
+
+ // Only copy damage if the guest is in autosize mode and the guest's view size
+ // is less than the maximum size or the guest's view size is equal to the
+ // damage buffer's size and the guest's scale factor is equal to the damage
+ // buffer's scale factor.
+ // The scaling change can happen due to asynchronous updates of the DPI on a
+ // resolution change.
+ if (((auto_size_enabled_ && InAutoSizeBounds(params.view_size)) ||
+ (params.view_size == damage_view_size())) &&
+ params.scale_factor == damage_buffer_scale_factor()) {
+ TransportDIB* dib = GetWebContents()->GetRenderProcessHost()->
+ GetTransportDIB(params.bitmap);
+ if (dib) {
+ size_t guest_damage_buffer_size =
+#if defined(OS_WIN)
+ params.bitmap_rect.width() *
+ params.bitmap_rect.height() * 4;
+#else
+ dib->size();
+#endif
+ size_t embedder_damage_buffer_size = damage_buffer_size_;
+ void* guest_memory = dib->memory();
+ void* embedder_memory = damage_buffer_->memory();
+ size_t size = std::min(guest_damage_buffer_size,
+ embedder_damage_buffer_size);
+ memcpy(embedder_memory, guest_memory, size);
+ }
+ }
+ relay_params.damage_buffer_sequence_id = damage_buffer_sequence_id_;
+ relay_params.bitmap_rect = params.bitmap_rect;
+ relay_params.scroll_delta = params.scroll_delta;
+ relay_params.scroll_rect = params.scroll_rect;
+ relay_params.copy_rects = params.copy_rects;
+
+ SendMessageToEmbedder(
+ new BrowserPluginMsg_UpdateRect(instance_id(), relay_params));
+}
+
+void BrowserPluginGuest::DidRetrieveDownloadURLFromRequestId(
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback,
+ const std::string& url) {
+ if (url.empty()) {
+ callback.Run(false);
+ return;
+ }
+
+ base::DictionaryValue request_info;
+ request_info.Set(browser_plugin::kRequestMethod,
+ base::Value::CreateStringValue(request_method));
+ request_info.Set(browser_plugin::kURL, base::Value::CreateStringValue(url));
+
+ RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_DOWNLOAD,
+ new DownloadRequest(callback),
+ request_info);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest.h b/chromium/content/browser/browser_plugin/browser_plugin_guest.h
new file mode 100644
index 00000000000..ffe8d566145
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest.h
@@ -0,0 +1,534 @@
+// 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.
+
+// A BrowserPluginGuest is the browser side of a browser <--> embedder
+// renderer channel. A BrowserPlugin (a WebPlugin) is on the embedder
+// renderer side of browser <--> embedder renderer communication.
+//
+// BrowserPluginGuest lives on the UI thread of the browser process. It has a
+// helper, BrowserPluginGuestHelper, which is a RenderViewHostObserver. The
+// helper object intercepts messages (ViewHostMsg_*) directed at the browser
+// process and redirects them to this class. Any messages about the guest render
+// process that the embedder might be interested in receiving should be listened
+// for here.
+//
+// BrowserPluginGuest is a WebContentsDelegate and WebContentsObserver for the
+// guest WebContents. BrowserPluginGuest operates under the assumption that the
+// guest will be accessible through only one RenderViewHost for the lifetime of
+// the guest WebContents. Thus, cross-process navigation is not supported.
+
+#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_
+
+#include <map>
+#include <queue>
+
+#include "base/compiler_specific.h"
+#include "base/id_map.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/common/edit_command.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/browser_plugin_guest_delegate.h"
+#include "content/public/browser/javascript_dialog_manager.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/browser_plugin_permission_type.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "third_party/WebKit/public/web/WebDragStatus.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/gfx/rect.h"
+#include "ui/surface/transport_dib.h"
+
+struct BrowserPluginHostMsg_AutoSize_Params;
+struct BrowserPluginHostMsg_Attach_Params;
+struct BrowserPluginHostMsg_ResizeGuest_Params;
+struct ViewHostMsg_CreateWindow_Params;
+#if defined(OS_MACOSX)
+struct ViewHostMsg_ShowPopup_Params;
+#endif
+struct ViewHostMsg_UpdateRect_Params;
+class WebCursor;
+
+namespace cc {
+class CompositorFrameAck;
+}
+
+namespace WebKit {
+class WebInputEvent;
+}
+
+namespace content {
+
+class BrowserPluginHostFactory;
+class BrowserPluginEmbedder;
+class BrowserPluginGuestManager;
+class RenderProcessHost;
+class RenderWidgetHostView;
+struct DropData;
+struct MediaStreamRequest;
+
+// A browser plugin guest provides functionality for WebContents to operate in
+// the guest role and implements guest-specific overrides for ViewHostMsg_*
+// messages.
+//
+// When a guest is initially created, it is in an unattached state. That is,
+// it is not visible anywhere and has no embedder WebContents assigned.
+// A BrowserPluginGuest is said to be "attached" if it has an embedder.
+// A BrowserPluginGuest can also create a new unattached guest via
+// CreateNewWindow. The newly created guest will live in the same partition,
+// which means it can share storage and can script this guest.
+class CONTENT_EXPORT BrowserPluginGuest
+ : public JavaScriptDialogManager,
+ public NotificationObserver,
+ public WebContentsDelegate,
+ public WebContentsObserver,
+ public base::SupportsWeakPtr<BrowserPluginGuest> {
+ public:
+ typedef base::Callback<void(bool)> GeolocationCallback;
+ virtual ~BrowserPluginGuest();
+
+ static BrowserPluginGuest* Create(
+ int instance_id,
+ WebContentsImpl* web_contents,
+ scoped_ptr<base::DictionaryValue> extra_params);
+
+ static BrowserPluginGuest* CreateWithOpener(
+ int instance_id,
+ WebContentsImpl* web_contents,
+ BrowserPluginGuest* opener,
+ bool has_render_view);
+
+ // Destroys the guest WebContents and all its associated state, including
+ // this BrowserPluginGuest, and its new unattached windows.
+ void Destroy();
+
+ // Returns the identifier that uniquely identifies a browser plugin guest
+ // within an embedder.
+ int instance_id() const { return instance_id_; }
+
+ // Overrides factory for testing. Default (NULL) value indicates regular
+ // (non-test) environment.
+ static void set_factory_for_testing(BrowserPluginHostFactory* factory) {
+ BrowserPluginGuest::factory_ = factory;
+ }
+
+ bool OnMessageReceivedFromEmbedder(const IPC::Message& message);
+
+ void Initialize(WebContentsImpl* embedder_web_contents,
+ const BrowserPluginHostMsg_Attach_Params& params);
+
+ void set_guest_hang_timeout_for_testing(const base::TimeDelta& timeout) {
+ guest_hang_timeout_ = timeout;
+ }
+
+ WebContentsImpl* embedder_web_contents() const {
+ return embedder_web_contents_;
+ }
+
+ RenderWidgetHostView* GetEmbedderRenderWidgetHostView();
+
+ bool focused() const { return focused_; }
+ bool visible() const { return guest_visible_; }
+ void clear_damage_buffer() { damage_buffer_.reset(); }
+
+ BrowserPluginGuest* opener() const { return opener_.get(); }
+
+ // Returns whether the mouse pointer was unlocked.
+ bool UnlockMouseIfNecessary(const NativeWebKeyboardEvent& event);
+
+ void UpdateVisibility();
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // WebContentsObserver implementation.
+ virtual void DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE;
+
+ virtual void RenderViewReady() OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // WebContentsDelegate implementation.
+ virtual bool AddMessageToConsole(WebContents* source,
+ int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id) OVERRIDE;
+ // If a new window is created with target="_blank" and rel="noreferrer", then
+ // this method is called, indicating that the new WebContents is ready to be
+ // attached.
+ virtual void AddNewContents(WebContents* source,
+ WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture,
+ bool* was_blocked) OVERRIDE;
+ virtual void CanDownload(RenderViewHost* render_view_host,
+ int request_id,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual void CloseContents(WebContents* source) OVERRIDE;
+ virtual JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE;
+ virtual bool HandleContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void HandleKeyboardEvent(
+ WebContents* source,
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+ virtual WebContents* OpenURLFromTab(WebContents* source,
+ const OpenURLParams& params) OVERRIDE;
+ virtual void WebContentsCreated(WebContents* source_contents,
+ int64 source_frame_id,
+ const string16& frame_name,
+ const GURL& target_url,
+ WebContents* new_contents) OVERRIDE;
+ virtual void RendererUnresponsive(WebContents* source) OVERRIDE;
+ virtual void RendererResponsive(WebContents* source) OVERRIDE;
+ virtual void RunFileChooser(WebContents* web_contents,
+ const FileChooserParams& params) OVERRIDE;
+ virtual bool ShouldFocusPageAfterCrash() OVERRIDE;
+ virtual void RequestMediaAccessPermission(
+ WebContents* web_contents,
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) OVERRIDE;
+
+ // JavaScriptDialogManager implementation.
+ virtual void RunJavaScriptDialog(
+ WebContents* web_contents,
+ const GURL& origin_url,
+ const std::string& accept_lang,
+ JavaScriptMessageType javascript_message_type,
+ const string16& message_text,
+ const string16& default_prompt_text,
+ const DialogClosedCallback& callback,
+ bool* did_suppress_message) OVERRIDE;
+ virtual void RunBeforeUnloadDialog(
+ WebContents* web_contents,
+ const string16& message_text,
+ bool is_reload,
+ const DialogClosedCallback& callback) OVERRIDE;
+ virtual bool HandleJavaScriptDialog(WebContents* web_contents,
+ bool accept,
+ const string16* prompt_override) OVERRIDE;
+ virtual void CancelActiveAndPendingDialogs(
+ WebContents* web_contents) OVERRIDE;
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+
+ // Exposes the protected web_contents() from WebContentsObserver.
+ WebContentsImpl* GetWebContents();
+
+ // Overridden in tests.
+ virtual void SetDamageBuffer(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params);
+
+ gfx::Point GetScreenCoordinates(const gfx::Point& relative_position) const;
+
+ // Helper to send messages to embedder. This methods fills the message with
+ // the correct routing id.
+ // Overridden in test implementation since we want to intercept certain
+ // messages for testing.
+ virtual void SendMessageToEmbedder(IPC::Message* msg);
+
+ // Returns whether the guest is attached to an embedder.
+ bool attached() const { return !!embedder_web_contents_; }
+
+ // Attaches this BrowserPluginGuest to the provided |embedder_web_contents|
+ // and initializes the guest with the provided |params|. Attaching a guest
+ // to an embedder implies that this guest's lifetime is no longer managed
+ // by its opener, and it can begin loading resources.
+ void Attach(WebContentsImpl* embedder_web_contents,
+ BrowserPluginHostMsg_Attach_Params params);
+
+ // Requests geolocation permission through Embedder JavaScript API.
+ void AskEmbedderForGeolocationPermission(int bridge_id,
+ const GURL& requesting_frame,
+ const GeolocationCallback& callback);
+ // Cancels pending geolocation request.
+ void CancelGeolocationRequest(int bridge_id);
+
+ // Allow the embedder to call this for unhandled messages when
+ // BrowserPluginGuest is already destroyed.
+ static void AcknowledgeBufferPresent(int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point);
+
+ // Returns whether BrowserPluginGuest is interested in receiving the given
+ // |message|.
+ static bool ShouldForwardToBrowserPluginGuest(const IPC::Message& message);
+ gfx::Rect ToGuestRect(const gfx::Rect& rect);
+
+ void DragSourceEndedAt(int client_x, int client_y, int screen_x,
+ int screen_y, WebKit::WebDragOperation operation);
+
+ void DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y);
+
+ // Called when the drag started by this guest ends at an OS-level.
+ void EndSystemDrag();
+
+ // |this| takes ownership of |delegate|.
+ void SetDelegate(BrowserPluginGuestDelegate* delegate);
+
+ void RespondToPermissionRequest(int request_id,
+ bool should_allow,
+ const std::string& user_input);
+
+ private:
+ class EmbedderRenderViewHostObserver;
+ friend class TestBrowserPluginGuest;
+
+ class DownloadRequest;
+ class GeolocationRequest;
+ class JavaScriptDialogRequest;
+ // MediaRequest because of naming conflicts with MediaStreamRequest.
+ class MediaRequest;
+ class NewWindowRequest;
+ class PermissionRequest;
+ class PointerLockRequest;
+
+ BrowserPluginGuest(int instance_id,
+ WebContentsImpl* web_contents,
+ BrowserPluginGuest* opener,
+ bool has_render_view);
+
+ // Destroy unattached new windows that have been opened by this
+ // BrowserPluginGuest.
+ void DestroyUnattachedWindows();
+
+ // Bridge IDs correspond to a geolocation request. This method will remove
+ // the bookkeeping for a particular geolocation request associated with the
+ // provided |bridge_id|. It returns the request ID of the geolocation request.
+ int RemoveBridgeID(int bridge_id);
+
+ // Returns the |request_id| generated for the |request| provided.
+ int RequestPermission(
+ BrowserPluginPermissionType permission_type,
+ scoped_refptr<BrowserPluginGuest::PermissionRequest> request,
+ const base::DictionaryValue& request_info);
+
+ base::SharedMemory* damage_buffer() const { return damage_buffer_.get(); }
+ const gfx::Size& damage_view_size() const { return damage_view_size_; }
+ float damage_buffer_scale_factor() const {
+ return damage_buffer_scale_factor_;
+ }
+ // Returns the damage buffer corresponding to the handle in resize |params|.
+ base::SharedMemory* GetDamageBufferFromEmbedder(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params);
+
+ bool InAutoSizeBounds(const gfx::Size& size) const;
+
+ void RequestNewWindowPermission(WebContentsImpl* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_bounds,
+ bool user_gesture);
+
+ // Message handlers for messages from embedder.
+
+ void OnCompositorFrameACK(int instance_id,
+ int route_id,
+ uint32 output_surface_id,
+ int renderer_host_id,
+ const cc::CompositorFrameAck& ack);
+
+ // Handles drag events from the embedder.
+ // When dragging, the drag events go to the embedder first, and if the drag
+ // happens on the browser plugin, then the plugin sends a corresponding
+ // drag-message to the guest. This routes the drag-message to the guest
+ // renderer.
+ void OnDragStatusUpdate(int instance_id,
+ WebKit::WebDragStatus drag_status,
+ const DropData& drop_data,
+ WebKit::WebDragOperationsMask drag_mask,
+ const gfx::Point& location);
+ // Instructs the guest to execute an edit command decoded in the embedder.
+ void OnExecuteEditCommand(int instance_id,
+ const std::string& command);
+ // Overriden in tests.
+ virtual void OnHandleInputEvent(int instance_id,
+ const gfx::Rect& guest_window_rect,
+ const WebKit::WebInputEvent* event);
+ void OnLockMouse(bool user_gesture,
+ bool last_unlocked_by_target,
+ bool privileged);
+ void OnLockMouseAck(int instance_id, bool succeeded);
+ void OnNavigateGuest(int instance_id, const std::string& src);
+ void OnPluginDestroyed(int instance_id);
+ // Grab the new damage buffer from the embedder, and resize the guest's
+ // web contents.
+ void OnResizeGuest(int instance_id,
+ const BrowserPluginHostMsg_ResizeGuest_Params& params);
+ // Overriden in tests.
+ virtual void OnSetFocus(int instance_id, bool focused);
+ // Sets the name of the guest so that other guests in the same partition can
+ // access it.
+ void OnSetName(int instance_id, const std::string& name);
+ // Updates the size state of the guest.
+ void OnSetSize(
+ int instance_id,
+ const BrowserPluginHostMsg_AutoSize_Params& auto_size_params,
+ const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params);
+ void OnSetEditCommandsForNextKeyEvent(
+ int instance_id,
+ const std::vector<EditCommand>& edit_commands);
+ // The guest WebContents is visible if both its embedder is visible and
+ // the browser plugin element is visible. If either one is not then the
+ // WebContents is marked as hidden. A hidden WebContents will consume
+ // fewer GPU and CPU resources.
+ //
+ // When every WebContents in a RenderProcessHost is hidden, it will lower
+ // the priority of the process (see RenderProcessHostImpl::WidgetHidden).
+ //
+ // It will also send a message to the guest renderer process to cleanup
+ // resources such as dropping back buffers and adjusting memory limits (if in
+ // compositing mode, see CCLayerTreeHost::setVisible).
+ //
+ // Additionally, it will slow down Javascript execution and garbage
+ // collection. See RenderThreadImpl::IdleHandler (executed when hidden) and
+ // RenderThreadImpl::IdleHandlerInForegroundTab (executed when visible).
+ void OnSetVisibility(int instance_id, bool visible);
+ // Message from embedder acknowledging last HW buffer.
+ void OnSwapBuffersACK(int instance_id,
+ int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point);
+ void OnUnlockMouse();
+ void OnUnlockMouseAck(int instance_id);
+ void OnUpdateGeometry(int instance_id, const gfx::Rect& view_rect);
+ void OnUpdateRectACK(
+ int instance_id,
+ bool needs_ack,
+ const BrowserPluginHostMsg_AutoSize_Params& auto_size_params,
+ const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params);
+
+
+ // Message handlers for messages from guest.
+
+ void OnDragStopped();
+ void OnHandleInputEventAck(
+ WebKit::WebInputEvent::Type event_type,
+ InputEventAckState ack_result);
+ void OnHasTouchEventHandlers(bool accept);
+ void OnSetCursor(const WebCursor& cursor);
+ // On MacOSX popups are painted by the browser process. We handle them here
+ // so that they are positioned correctly.
+#if defined(OS_MACOSX)
+ void OnShowPopup(const ViewHostMsg_ShowPopup_Params& params);
+#endif
+ void OnShowWidget(int route_id, const gfx::Rect& initial_pos);
+ // Overriden in tests.
+ virtual void OnTakeFocus(bool reverse);
+ void OnUpdateFrameName(int frame_id,
+ bool is_top_level,
+ const std::string& name);
+ void OnUpdateRect(const ViewHostMsg_UpdateRect_Params& params);
+
+ // Requests download permission through embedder JavaScript API after
+ // retrieving url information from IO thread.
+ void DidRetrieveDownloadURLFromRequestId(
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback,
+ const std::string& url);
+
+ // Embedder sets permission to allow or deny geolocation request.
+ void SetGeolocationPermission(
+ GeolocationCallback callback, int bridge_id, bool allowed);
+
+ // Forwards all messages from the |pending_messages_| queue to the embedder.
+ void SendQueuedMessages();
+
+ // Weak pointer used to ask GeolocationPermissionContext about geolocation
+ // permission.
+ base::WeakPtrFactory<BrowserPluginGuest> weak_ptr_factory_;
+
+ // Static factory instance (always NULL for non-test).
+ static BrowserPluginHostFactory* factory_;
+
+ NotificationRegistrar notification_registrar_;
+ scoped_ptr<EmbedderRenderViewHostObserver> embedder_rvh_observer_;
+ WebContentsImpl* embedder_web_contents_;
+
+ std::map<int, int> bridge_id_to_request_id_map_;
+
+ // An identifier that uniquely identifies a browser plugin guest within an
+ // embedder.
+ int instance_id_;
+ scoped_ptr<base::SharedMemory> damage_buffer_;
+ // An identifier that uniquely identifies a damage buffer.
+ uint32 damage_buffer_sequence_id_;
+ size_t damage_buffer_size_;
+ gfx::Size damage_view_size_;
+ float damage_buffer_scale_factor_;
+ float guest_device_scale_factor_;
+ gfx::Rect guest_window_rect_;
+ gfx::Rect guest_screen_rect_;
+ base::TimeDelta guest_hang_timeout_;
+ bool focused_;
+ bool mouse_locked_;
+ bool pending_lock_request_;
+ bool guest_visible_;
+ bool embedder_visible_;
+ std::string name_;
+ bool auto_size_enabled_;
+ gfx::Size max_auto_size_;
+ gfx::Size min_auto_size_;
+
+ // Tracks the name, and target URL of the new window and whether or not it has
+ // changed since the WebContents has been created and before the new window
+ // has been attached to a BrowserPlugin. Once the first navigation commits, we
+ // no longer track this information.
+ struct NewWindowInfo {
+ bool changed;
+ GURL url;
+ std::string name;
+ NewWindowInfo(const GURL& url, const std::string& name) :
+ changed(false),
+ url(url),
+ name(name) {}
+ };
+ typedef std::map<BrowserPluginGuest*, NewWindowInfo> PendingWindowMap;
+ PendingWindowMap pending_new_windows_;
+ base::WeakPtr<BrowserPluginGuest> opener_;
+ // A counter to generate a unique request id for a permission request.
+ // We only need the ids to be unique for a given BrowserPluginGuest.
+ int next_permission_request_id_;
+
+ // A map to store relevant info for a request keyed by the request's id.
+ typedef std::map<int, scoped_refptr<PermissionRequest> > RequestMap;
+ RequestMap permission_request_map_;
+
+ // Indicates that this BrowserPluginGuest has associated renderer-side state.
+ // This is used to determine whether or not to create a new RenderView when
+ // this guest is attached.
+ bool has_render_view_;
+
+ // Last seen size of guest contents (by OnUpdateRect).
+ gfx::Size last_seen_view_size_;
+ // Last seen autosize attribute state (by OnUpdateRect).
+ bool last_seen_auto_size_enabled_;
+
+ // This is a queue of messages that are destined to be sent to the embedder
+ // once the guest is attached to a particular embedder.
+ std::queue<IPC::Message*> pending_messages_;
+
+ scoped_ptr<BrowserPluginGuestDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc
new file mode 100644
index 00000000000..ee18b6e92e3
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc
@@ -0,0 +1,55 @@
+// 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 "content/browser/browser_plugin/browser_plugin_guest_helper.h"
+
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/common/drag_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/render_view_host.h"
+
+namespace content {
+
+BrowserPluginGuestHelper::BrowserPluginGuestHelper(
+ BrowserPluginGuest* guest,
+ RenderViewHost* render_view_host)
+ : RenderViewHostObserver(render_view_host),
+ guest_(guest) {
+}
+
+BrowserPluginGuestHelper::~BrowserPluginGuestHelper() {
+}
+
+bool BrowserPluginGuestHelper::OnMessageReceived(
+ const IPC::Message& message) {
+ if (ShouldForwardToBrowserPluginGuest(message))
+ return guest_->OnMessageReceived(message);
+ return false;
+}
+
+// static
+bool BrowserPluginGuestHelper::ShouldForwardToBrowserPluginGuest(
+ const IPC::Message& message) {
+ switch (message.type()) {
+ case DragHostMsg_StartDragging::ID:
+ case DragHostMsg_TargetDrop_ACK::ID:
+ case ViewHostMsg_HasTouchEventHandlers::ID:
+ case ViewHostMsg_SetCursor::ID:
+ #if defined(OS_MACOSX)
+ case ViewHostMsg_ShowPopup::ID:
+ #endif
+ case ViewHostMsg_ShowWidget::ID:
+ case ViewHostMsg_TakeFocus::ID:
+ case ViewHostMsg_UpdateFrameName::ID:
+ case ViewHostMsg_UpdateRect::ID:
+ case ViewHostMsg_LockMouse::ID:
+ case ViewHostMsg_UnlockMouse::ID:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h
new file mode 100644
index 00000000000..dfe2e321691
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h
@@ -0,0 +1,59 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_
+
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+class WebCursor;
+#if defined(OS_MACOSX)
+struct ViewHostMsg_ShowPopup_Params;
+#endif
+struct ViewHostMsg_UpdateRect_Params;
+
+namespace gfx {
+class Size;
+}
+
+namespace content {
+class BrowserPluginGuest;
+class RenderViewHost;
+
+// Helper for BrowserPluginGuest.
+//
+// The purpose of this class is to intercept messages from the guest RenderView
+// before they are handled by the standard message handlers in the browser
+// process. This permits overriding standard behavior with BrowserPlugin-
+// specific behavior.
+//
+// The lifetime of this class is managed by the associated RenderViewHost. A
+// BrowserPluginGuestHelper is created whenever a BrowserPluginGuest is created.
+class BrowserPluginGuestHelper : public RenderViewHostObserver {
+ public:
+ BrowserPluginGuestHelper(BrowserPluginGuest* guest,
+ RenderViewHost* render_view_host);
+ virtual ~BrowserPluginGuestHelper();
+
+ protected:
+ // RenderViewHostObserver implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ private:
+ // Returns whether a message should be forward to the helper's associated
+ // BrowserPluginGuest.
+ static bool ShouldForwardToBrowserPluginGuest(const IPC::Message& message);
+
+ BrowserPluginGuest* guest_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuestHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc
new file mode 100644
index 00000000000..75de2031333
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 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 "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+
+#include "base/command_line.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/browser_plugin/browser_plugin_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/browser_plugin/browser_plugin_constants.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/escape.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace content {
+
+// static
+BrowserPluginHostFactory* BrowserPluginGuestManager::factory_ = NULL;
+
+BrowserPluginGuestManager::BrowserPluginGuestManager()
+ : next_instance_id_(browser_plugin::kInstanceIDNone) {
+}
+
+BrowserPluginGuestManager::~BrowserPluginGuestManager() {
+}
+
+// static
+BrowserPluginGuestManager* BrowserPluginGuestManager::Create() {
+ if (factory_)
+ return factory_->CreateBrowserPluginGuestManager();
+ return new BrowserPluginGuestManager();
+}
+
+BrowserPluginGuest* BrowserPluginGuestManager::CreateGuest(
+ SiteInstance* embedder_site_instance,
+ int instance_id,
+ const BrowserPluginHostMsg_Attach_Params& params,
+ scoped_ptr<base::DictionaryValue> extra_params) {
+ SiteInstance* guest_site_instance = NULL;
+ // Validate that the partition id coming from the renderer is valid UTF-8,
+ // since we depend on this in other parts of the code, such as FilePath
+ // creation. If the validation fails, treat it as a bad message and kill the
+ // renderer process.
+ if (!IsStringUTF8(params.storage_partition_id)) {
+ content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
+ base::KillProcess(
+ embedder_site_instance->GetProcess()->GetHandle(),
+ content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
+ return NULL;
+ }
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kSitePerProcess)) {
+ // When --site-per-process is specified, the behavior of BrowserPlugin
+ // as <webview> is broken and we use it for rendering out-of-process
+ // iframes instead. We use the src URL sent by the renderer to find the
+ // right process in which to place this instance.
+ // Note: Since BrowserPlugin doesn't support cross-process navigation,
+ // the instance will stay in the initially assigned process, regardless
+ // of the site it is navigated to.
+ // TODO(nasko): Fix this, and such that cross-process navigations are
+ // supported.
+ guest_site_instance =
+ embedder_site_instance->GetRelatedSiteInstance(GURL(params.src));
+ } else {
+ const std::string& host = embedder_site_instance->GetSiteURL().host();
+
+ std::string url_encoded_partition = net::EscapeQueryParamValue(
+ params.storage_partition_id, false);
+ // The SiteInstance of a given webview tag is based on the fact that it's
+ // a guest process in addition to which platform application the tag
+ // belongs to and what storage partition is in use, rather than the URL
+ // that the tag is being navigated to.
+ GURL guest_site(
+ base::StringPrintf("%s://%s/%s?%s", chrome::kGuestScheme,
+ host.c_str(),
+ params.persist_storage ? "persist" : "",
+ url_encoded_partition.c_str()));
+
+ // If we already have a webview tag in the same app using the same storage
+ // partition, we should use the same SiteInstance so the existing tag and
+ // the new tag can script each other.
+ guest_site_instance = GetGuestSiteInstance(guest_site);
+ if (!guest_site_instance) {
+ // Create the SiteInstance in a new BrowsingInstance, which will ensure
+ // that webview tags are also not allowed to send messages across
+ // different partitions.
+ guest_site_instance = SiteInstance::CreateForURL(
+ embedder_site_instance->GetBrowserContext(), guest_site);
+ }
+ }
+
+ return WebContentsImpl::CreateGuest(
+ embedder_site_instance->GetBrowserContext(),
+ guest_site_instance,
+ instance_id,
+ extra_params.Pass());
+}
+
+BrowserPluginGuest* BrowserPluginGuestManager::GetGuestByInstanceID(
+ int instance_id,
+ int embedder_render_process_id) const {
+ if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id,
+ instance_id)) {
+ return NULL;
+ }
+ GuestInstanceMap::const_iterator it =
+ guest_web_contents_by_instance_id_.find(instance_id);
+ if (it == guest_web_contents_by_instance_id_.end())
+ return NULL;
+ return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
+}
+
+void BrowserPluginGuestManager::AddGuest(int instance_id,
+ WebContentsImpl* guest_web_contents) {
+ DCHECK(guest_web_contents_by_instance_id_.find(instance_id) ==
+ guest_web_contents_by_instance_id_.end());
+ guest_web_contents_by_instance_id_[instance_id] = guest_web_contents;
+}
+
+void BrowserPluginGuestManager::RemoveGuest(int instance_id) {
+ DCHECK(guest_web_contents_by_instance_id_.find(instance_id) !=
+ guest_web_contents_by_instance_id_.end());
+ guest_web_contents_by_instance_id_.erase(instance_id);
+}
+
+bool BrowserPluginGuestManager::CanEmbedderAccessInstanceIDMaybeKill(
+ int embedder_render_process_id,
+ int instance_id) const {
+ if (!CanEmbedderAccessInstanceID(embedder_render_process_id, instance_id)) {
+ // The embedder process is trying to access a guest it does not own.
+ content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
+ base::KillProcess(
+ RenderProcessHost::FromID(embedder_render_process_id)->GetHandle(),
+ content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
+ return false;
+ }
+ return true;
+}
+
+void BrowserPluginGuestManager::OnMessageReceived(const IPC::Message& message,
+ int render_process_id) {
+ if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) {
+ int instance_id = 0;
+ // All allowed messages must have instance_id as their first parameter.
+ PickleIterator iter(message);
+ bool success = iter.ReadInt(&instance_id);
+ DCHECK(success);
+ BrowserPluginGuest* guest =
+ GetGuestByInstanceID(instance_id, render_process_id);
+ if (guest && guest->OnMessageReceivedFromEmbedder(message))
+ return;
+ }
+ IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestManager, message)
+ IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK,
+ OnUnhandledSwapBuffersACK)
+ IPC_END_MESSAGE_MAP()
+}
+
+// static
+bool BrowserPluginGuestManager::CanEmbedderAccessGuest(
+ int embedder_render_process_id,
+ BrowserPluginGuest* guest) {
+ // The embedder can access the guest if it has not been attached and its
+ // opener's embedder lives in the same process as the given embedder.
+ if (!guest->attached()) {
+ if (!guest->opener())
+ return false;
+
+ return embedder_render_process_id ==
+ guest->opener()->embedder_web_contents()->GetRenderProcessHost()->
+ GetID();
+ }
+
+ return embedder_render_process_id ==
+ guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
+}
+
+bool BrowserPluginGuestManager::CanEmbedderAccessInstanceID(
+ int embedder_render_process_id,
+ int instance_id) const {
+ // The embedder is trying to access a guest with a negative or zero
+ // instance ID.
+ if (instance_id <= browser_plugin::kInstanceIDNone)
+ return false;
+
+ // The embedder is trying to access an instance ID that has not yet been
+ // allocated by BrowserPluginGuestManager. This could cause instance ID
+ // collisions in the future, and potentially give one embedder access to a
+ // guest it does not own.
+ if (instance_id > next_instance_id_)
+ return false;
+
+ GuestInstanceMap::const_iterator it =
+ guest_web_contents_by_instance_id_.find(instance_id);
+ if (it == guest_web_contents_by_instance_id_.end())
+ return true;
+ BrowserPluginGuest* guest =
+ static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
+
+ return CanEmbedderAccessGuest(embedder_render_process_id, guest);
+}
+
+SiteInstance* BrowserPluginGuestManager::GetGuestSiteInstance(
+ const GURL& guest_site) {
+ for (GuestInstanceMap::const_iterator it =
+ guest_web_contents_by_instance_id_.begin();
+ it != guest_web_contents_by_instance_id_.end(); ++it) {
+ if (it->second->GetSiteInstance()->GetSiteURL() == guest_site)
+ return it->second->GetSiteInstance();
+ }
+ return NULL;
+}
+
+// We only get here during teardown if we have one last buffer pending,
+// otherwise the ACK is handled by the guest.
+void BrowserPluginGuestManager::OnUnhandledSwapBuffersACK(
+ int instance_id,
+ int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point) {
+ BrowserPluginGuest::AcknowledgeBufferPresent(route_id,
+ gpu_host_id,
+ mailbox_name,
+ sync_point);
+}
+
+void BrowserPluginGuestManager::DidSendScreenRects(
+ WebContentsImpl* embedder_web_contents) {
+ // TODO(lazyboy): Generalize iterating over guest instances and performing
+ // actions on the guests.
+ for (GuestInstanceMap::iterator it =
+ guest_web_contents_by_instance_id_.begin();
+ it != guest_web_contents_by_instance_id_.end(); ++it) {
+ BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
+ if (embedder_web_contents == guest->embedder_web_contents()) {
+ static_cast<RenderViewHostImpl*>(
+ guest->GetWebContents()->GetRenderViewHost())->SendScreenRects();
+ }
+ }
+}
+
+bool BrowserPluginGuestManager::UnlockMouseIfNecessary(
+ WebContentsImpl* embedder_web_contents,
+ const NativeWebKeyboardEvent& event) {
+ if ((event.type != WebKit::WebInputEvent::RawKeyDown) ||
+ (event.windowsKeyCode != ui::VKEY_ESCAPE) ||
+ (event.modifiers & WebKit::WebInputEvent::InputModifiers)) {
+ return false;
+ }
+
+ // TODO(lazyboy): Generalize iterating over guest instances and performing
+ // actions on the guests.
+ for (GuestInstanceMap::iterator it =
+ guest_web_contents_by_instance_id_.begin();
+ it != guest_web_contents_by_instance_id_.end(); ++it) {
+ BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
+ if (embedder_web_contents == guest->embedder_web_contents()) {
+ if (guest->UnlockMouseIfNecessary(event))
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h
new file mode 100644
index 00000000000..a657bfa7a67
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h
@@ -0,0 +1,137 @@
+// 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.
+
+// A BrowserPluginGuestManager serves as a message router to BrowserPluginGuests
+// for all guests within a given profile.
+// Messages are routed to a particular guest instance based on an instance_id.
+
+#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/supports_user_data.h"
+#include "base/values.h"
+#include "content/common/content_export.h"
+#include "ipc/ipc_message.h"
+
+struct BrowserPluginHostMsg_Attach_Params;
+struct BrowserPluginHostMsg_ResizeGuest_Params;
+class GURL;
+
+namespace gfx {
+class Point;
+}
+
+namespace IPC {
+class Message;
+} // namespace IPC
+
+namespace content {
+
+class BrowserPluginGuest;
+class BrowserPluginHostFactory;
+class RenderProcessHostImpl;
+class RenderWidgetHostImpl;
+class SiteInstance;
+class WebContents;
+class WebContentsImpl;
+struct NativeWebKeyboardEvent;
+
+class CONTENT_EXPORT BrowserPluginGuestManager :
+ public base::SupportsUserData::Data {
+ public:
+ virtual ~BrowserPluginGuestManager();
+
+ static BrowserPluginGuestManager* Create();
+
+ // Overrides factory for testing. Default (NULL) value indicates regular
+ // (non-test) environment.
+ static void set_factory_for_testing(BrowserPluginHostFactory* factory) {
+ content::BrowserPluginGuestManager::factory_ = factory;
+ }
+
+ // Gets the next available instance id.
+ int get_next_instance_id() { return ++next_instance_id_; }
+
+ // Creates a guest WebContents with the provided |instance_id| and |params|.
+ // If params.src is present, the new guest will also be navigated to the
+ // provided URL. Optionally, the new guest may be attached to a
+ // |guest_opener|, and may be attached to a pre-selected |routing_id|.
+ BrowserPluginGuest* CreateGuest(
+ SiteInstance* embedder_site_instance,
+ int instance_id,
+ const BrowserPluginHostMsg_Attach_Params& params,
+ scoped_ptr<base::DictionaryValue> extra_params);
+
+ // Returns a BrowserPluginGuest given an |instance_id|. Returns NULL if the
+ // guest wasn't found. If the embedder is not permitted to access the given
+ // |instance_id|, the embedder is killed, and NULL is returned.
+ BrowserPluginGuest* GetGuestByInstanceID(
+ int instance_id,
+ int embedder_render_process_id) const;
+
+ // Adds a new |guest_web_contents| to the embedder (overridable in test).
+ virtual void AddGuest(int instance_id, WebContentsImpl* guest_web_contents);
+
+ // Removes the guest with the given |instance_id| from this
+ // BrowserPluginGuestManager.
+ void RemoveGuest(int instance_id);
+
+ // Returns whether the specified embedder is permitted to access the given
+ // |instance_id|, and kills the embedder if not.
+ bool CanEmbedderAccessInstanceIDMaybeKill(int embedder_render_process_id,
+ int instance_id) const;
+
+ void DidSendScreenRects(WebContentsImpl* embedder_web_contents);
+
+ bool UnlockMouseIfNecessary(WebContentsImpl* embedder_web_contents_,
+ const NativeWebKeyboardEvent& event);
+
+ void OnMessageReceived(const IPC::Message& message, int render_process_id);
+
+ private:
+ friend class TestBrowserPluginGuestManager;
+
+ BrowserPluginGuestManager();
+
+ // Returns whether the given embedder process is allowed to access the
+ // provided |guest|.
+ static bool CanEmbedderAccessGuest(int embedder_render_process_id,
+ BrowserPluginGuest* guest);
+
+ // Returns whether the given embedder process is allowed to use the provided
+ // |instance_id| or access the guest associated with the |instance_id|. If the
+ // embedder can, the method returns true. If the guest does not exist but the
+ // embedder can use that |instance_id|, then it returns true. If the embedder
+ // is not permitted to use that instance ID or access the associated guest,
+ // then it returns false.
+ bool CanEmbedderAccessInstanceID(int embedder_render_process_id,
+ int instance_id) const;
+
+ // Returns an existing SiteInstance if the current profile has a guest of the
+ // given |guest_site|.
+ SiteInstance* GetGuestSiteInstance(const GURL& guest_site);
+
+ // Message handlers.
+ void OnUnhandledSwapBuffersACK(int instance_id,
+ int route_id,
+ int gpu_host_id,
+ const std::string& mailbox_name,
+ uint32 sync_point);
+
+ // Static factory instance (always NULL outside of tests).
+ static BrowserPluginHostFactory* factory_;
+
+ // Contains guests' WebContents, mapping from their instance ids.
+ typedef std::map<int, WebContentsImpl*> GuestInstanceMap;
+ GuestInstanceMap guest_web_contents_by_instance_id_;
+ int next_instance_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuestManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_
+
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc b/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc
new file mode 100644
index 00000000000..c50e8cfa8a3
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc
@@ -0,0 +1,814 @@
+// Copyright 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 "base/command_line.h"
+#include "base/memory/singleton.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/browser_plugin/browser_plugin_host_factory.h"
+#include "content/browser/browser_plugin/test_browser_plugin_embedder.h"
+#include "content/browser/browser_plugin/test_browser_plugin_guest.h"
+#include "content/browser/browser_plugin/test_browser_plugin_guest_manager.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/base/net_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+using WebKit::WebInputEvent;
+using WebKit::WebMouseEvent;
+using content::BrowserPluginEmbedder;
+using content::BrowserPluginGuest;
+using content::BrowserPluginHostFactory;
+using content::WebContentsImpl;
+
+namespace {
+
+const char kHTMLForGuest[] =
+ "data:text/html,<html><body>hello world</body></html>";
+const char kHTMLForGuestBusyLoop[] =
+ "data:text/html,<html><head><script type=\"text/javascript\">"
+ "function PauseMs(timems) {"
+ " document.title = \"start\";"
+ " var date = new Date();"
+ " var currDate = null;"
+ " do {"
+ " currDate = new Date();"
+ " } while (currDate - date < timems)"
+ "}"
+ "function StartPauseMs(timems) {"
+ " setTimeout(function() { PauseMs(timems); }, 0);"
+ "}"
+ "</script></head><body></body></html>";
+const char kHTMLForGuestTouchHandler[] =
+ "data:text/html,<html><body><div id=\"touch\">With touch</div></body>"
+ "<script type=\"text/javascript\">"
+ "function handler() {}"
+ "function InstallTouchHandler() { "
+ " document.getElementById(\"touch\").addEventListener(\"touchstart\", "
+ " handler);"
+ "}"
+ "function UninstallTouchHandler() { "
+ " document.getElementById(\"touch\").removeEventListener(\"touchstart\", "
+ " handler);"
+ "}"
+ "</script></html>";
+const char kHTMLForGuestWithTitle[] =
+ "data:text/html,"
+ "<html><head><title>%s</title></head>"
+ "<body>hello world</body>"
+ "</html>";
+const char kHTMLForGuestAcceptDrag[] =
+ "data:text/html,<html><body>"
+ "<script>"
+ "function dropped() {"
+ " document.title = \"DROPPED\";"
+ "}"
+ "</script>"
+ "<textarea id=\"text\" style=\"width:100%; height: 100%\""
+ " ondrop=\"dropped();\">"
+ "</textarea>"
+ "</body></html>";
+const char kHTMLForGuestWithSize[] =
+ "data:text/html,"
+ "<html>"
+ "<body style=\"margin: 0px;\">"
+ "<img style=\"width: 100%; height: 400px;\"/>"
+ "</body>"
+ "</html>";
+
+std::string GetHTMLForGuestWithTitle(const std::string& title) {
+ return base::StringPrintf(kHTMLForGuestWithTitle, title.c_str());
+}
+
+} // namespace
+
+namespace content {
+
+// Test factory for creating test instances of BrowserPluginEmbedder and
+// BrowserPluginGuest.
+class TestBrowserPluginHostFactory : public BrowserPluginHostFactory {
+ public:
+ virtual BrowserPluginGuestManager*
+ CreateBrowserPluginGuestManager() OVERRIDE {
+ guest_manager_instance_count_++;
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+ return new TestBrowserPluginGuestManager();
+ }
+
+ virtual BrowserPluginGuest* CreateBrowserPluginGuest(
+ int instance_id,
+ WebContentsImpl* web_contents) OVERRIDE {
+ return new TestBrowserPluginGuest(instance_id, web_contents);
+ }
+
+ // Also keeps track of number of instances created.
+ virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder(
+ WebContentsImpl* web_contents) OVERRIDE {
+
+ return new TestBrowserPluginEmbedder(web_contents);
+ }
+
+ // Singleton getter.
+ static TestBrowserPluginHostFactory* GetInstance() {
+ return Singleton<TestBrowserPluginHostFactory>::get();
+ }
+
+ // Waits for at least one embedder to be created in the test. Returns true if
+ // we have a guest, false if waiting times out.
+ void WaitForGuestManagerCreation() {
+ // Check if already have created an instance.
+ if (guest_manager_instance_count_ > 0)
+ return;
+ // Wait otherwise.
+ message_loop_runner_ = new MessageLoopRunner();
+ message_loop_runner_->Run();
+ }
+
+ protected:
+ TestBrowserPluginHostFactory() : guest_manager_instance_count_(0) {}
+ virtual ~TestBrowserPluginHostFactory() {}
+
+ private:
+ // For Singleton.
+ friend struct DefaultSingletonTraits<TestBrowserPluginHostFactory>;
+
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+ int guest_manager_instance_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginHostFactory);
+};
+
+// Test factory class for browser plugin that creates guests with short hang
+// timeout.
+class TestShortHangTimeoutGuestFactory : public TestBrowserPluginHostFactory {
+ public:
+ virtual BrowserPluginGuest* CreateBrowserPluginGuest(
+ int instance_id, WebContentsImpl* web_contents) OVERRIDE {
+ BrowserPluginGuest* guest =
+ new TestBrowserPluginGuest(instance_id, web_contents);
+ guest->set_guest_hang_timeout_for_testing(TestTimeouts::tiny_timeout());
+ return guest;
+ }
+
+ // Singleton getter.
+ static TestShortHangTimeoutGuestFactory* GetInstance() {
+ return Singleton<TestShortHangTimeoutGuestFactory>::get();
+ }
+
+ protected:
+ TestShortHangTimeoutGuestFactory() {}
+ virtual ~TestShortHangTimeoutGuestFactory() {}
+
+ private:
+ // For Singleton.
+ friend struct DefaultSingletonTraits<TestShortHangTimeoutGuestFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(TestShortHangTimeoutGuestFactory);
+};
+
+// A transparent observer that can be used to verify that a RenderViewHost
+// received a specific message.
+class RenderViewHostMessageObserver : public RenderViewHostObserver {
+ public:
+ RenderViewHostMessageObserver(RenderViewHost* host,
+ uint32 message_id)
+ : RenderViewHostObserver(host),
+ message_id_(message_id),
+ message_received_(false) {
+ }
+
+ virtual ~RenderViewHostMessageObserver() {}
+
+ void WaitUntilMessageReceived() {
+ if (message_received_)
+ return;
+ message_loop_runner_ = new MessageLoopRunner();
+ message_loop_runner_->Run();
+ }
+
+ void ResetState() {
+ message_received_ = false;
+ }
+
+ // IPC::Listener implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ if (message.type() == message_id_) {
+ message_received_ = true;
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+ }
+ return false;
+ }
+
+ private:
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+ uint32 message_id_;
+ bool message_received_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostMessageObserver);
+};
+
+class BrowserPluginHostTest : public ContentBrowserTest {
+ public:
+ BrowserPluginHostTest()
+ : test_embedder_(NULL),
+ test_guest_(NULL),
+ test_guest_manager_(NULL) {}
+
+ virtual void SetUp() OVERRIDE {
+ // Override factory to create tests instances of BrowserPlugin*.
+ content::BrowserPluginEmbedder::set_factory_for_testing(
+ TestBrowserPluginHostFactory::GetInstance());
+ content::BrowserPluginGuest::set_factory_for_testing(
+ TestBrowserPluginHostFactory::GetInstance());
+ content::BrowserPluginGuestManager::set_factory_for_testing(
+ TestBrowserPluginHostFactory::GetInstance());
+
+ // On legacy windows, the AcceptDragEvents test needs this to pass.
+#if defined(OS_WIN) && !defined(USE_AURA)
+ UseRealGLBindings();
+#endif
+
+ ContentBrowserTest::SetUp();
+ }
+ virtual void TearDown() OVERRIDE {
+ content::BrowserPluginEmbedder::set_factory_for_testing(NULL);
+ content::BrowserPluginGuest::set_factory_for_testing(NULL);
+
+ ContentBrowserTest::TearDown();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // Enable browser plugin in content_shell for running test.
+ command_line->AppendSwitch(switches::kEnableBrowserPluginForAllViewTypes);
+ }
+
+ static void SimulateSpaceKeyPress(WebContents* web_contents) {
+ SimulateKeyPress(web_contents,
+ ui::VKEY_SPACE,
+ false, // control.
+ false, // shift.
+ false, // alt.
+ false); // command.
+ }
+
+ static void SimulateTabKeyPress(WebContents* web_contents) {
+ SimulateKeyPress(web_contents,
+ ui::VKEY_TAB,
+ false, // control.
+ false, // shift.
+ false, // alt.
+ false); // command.
+ }
+
+ // Executes the javascript synchronously and makes sure the returned value is
+ // freed properly.
+ void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) {
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(rvh, jscript);
+ }
+
+ bool IsAttributeNull(RenderViewHost* rvh, const std::string& attribute) {
+ scoped_ptr<base::Value> value = content::ExecuteScriptAndGetValue(rvh,
+ "document.getElementById('plugin').getAttribute('" + attribute + "');");
+ return value->GetType() == Value::TYPE_NULL;
+ }
+
+ // Removes all attributes in the comma-delimited string |attributes|.
+ void RemoveAttributes(RenderViewHost* rvh, const std::string& attributes) {
+ std::vector<std::string> attributes_list;
+ base::SplitString(attributes, ',', &attributes_list);
+ std::vector<std::string>::const_iterator itr;
+ for (itr = attributes_list.begin(); itr != attributes_list.end(); ++itr) {
+ ExecuteSyncJSFunction(rvh, "document.getElementById('plugin')"
+ "." + *itr + " = null;");
+ }
+ }
+
+ // This helper method does the following:
+ // 1. Start the test server and navigate the shell to |embedder_url|.
+ // 2. Execute custom pre-navigation |embedder_code| if provided.
+ // 3. Navigate the guest to the |guest_url|.
+ // 4. Verify that the guest has been created and has completed loading.
+ void StartBrowserPluginTest(const std::string& embedder_url,
+ const std::string& guest_url,
+ bool is_guest_data_url,
+ const std::string& embedder_code) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ GURL test_url(embedded_test_server()->GetURL(embedder_url));
+ NavigateToURL(shell(), test_url);
+
+ WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>(
+ shell()->web_contents());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ embedder_web_contents->GetRenderViewHost());
+ // Focus the embedder.
+ rvh->Focus();
+ // Activative IME.
+ rvh->SetInputMethodActive(true);
+
+ // Allow the test to do some operations on the embedder before we perform
+ // the first navigation of the guest.
+ if (!embedder_code.empty())
+ ExecuteSyncJSFunction(rvh, embedder_code);
+
+ if (!is_guest_data_url) {
+ test_url = embedded_test_server()->GetURL(guest_url);
+ ExecuteSyncJSFunction(
+ rvh, base::StringPrintf("SetSrc('%s');", test_url.spec().c_str()));
+ } else {
+ ExecuteSyncJSFunction(
+ rvh, base::StringPrintf("SetSrc('%s');", guest_url.c_str()));
+ }
+
+ // Wait to make sure embedder is created/attached to WebContents.
+ TestBrowserPluginHostFactory::GetInstance()->WaitForGuestManagerCreation();
+
+ test_embedder_ = static_cast<TestBrowserPluginEmbedder*>(
+ embedder_web_contents->GetBrowserPluginEmbedder());
+ ASSERT_TRUE(test_embedder_);
+
+ test_guest_manager_ = static_cast<TestBrowserPluginGuestManager*>(
+ embedder_web_contents->GetBrowserPluginGuestManager());
+ ASSERT_TRUE(test_guest_manager_);
+
+ test_guest_manager_->WaitForGuestAdded();
+
+ // Verify that we have exactly one guest.
+ const TestBrowserPluginGuestManager::GuestInstanceMap& instance_map =
+ test_guest_manager_->guest_web_contents_for_testing();
+ EXPECT_EQ(1u, instance_map.size());
+
+ WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>(
+ instance_map.begin()->second);
+ test_guest_ = static_cast<TestBrowserPluginGuest*>(
+ test_guest_web_contents->GetBrowserPluginGuest());
+ test_guest_->WaitForLoadStop();
+ }
+
+ TestBrowserPluginEmbedder* test_embedder() const { return test_embedder_; }
+ TestBrowserPluginGuest* test_guest() const { return test_guest_; }
+ TestBrowserPluginGuestManager* test_guest_manager() const {
+ return test_guest_manager_;
+ }
+
+ private:
+ TestBrowserPluginEmbedder* test_embedder_;
+ TestBrowserPluginGuest* test_guest_;
+ TestBrowserPluginGuestManager* test_guest_manager_;
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginHostTest);
+};
+
+// This test ensures that if guest isn't there and we resize the guest (from
+// js), it remembers the size correctly.
+//
+// Initially we load an embedder with a guest without a src attribute (which has
+// dimension 640x480), resize it to 100x200, and then we set the source to a
+// sample guest. In the end we verify that the correct size has been set.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, NavigateAfterResize) {
+ const gfx::Size nxt_size = gfx::Size(100, 200);
+ const std::string embedder_code = base::StringPrintf(
+ "SetSize(%d, %d);", nxt_size.width(), nxt_size.height());
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, embedder_code);
+
+ // Wait for the guest to receive a damage buffer of size 100x200.
+ // This means the guest will be painted properly at that size.
+ test_guest()->WaitForDamageBufferWithSize(nxt_size);
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, AdvanceFocus) {
+ const char kEmbedderURL[] = "/browser_plugin_focus.html";
+ const char* kGuestURL = "/browser_plugin_focus_child.html";
+ StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string());
+
+ SimulateMouseClick(test_embedder()->web_contents(), 0,
+ WebKit::WebMouseEvent::ButtonLeft);
+ BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents());
+ // Wait until we focus into the guest.
+ test_guest()->WaitForFocus();
+
+ // TODO(fsamuel): A third Tab key press should not be necessary.
+ // The browser plugin will take keyboard focus but it will not
+ // focus an initial element. The initial element is dependent
+ // upon tab direction which WebKit does not propagate to the plugin.
+ // See http://crbug.com/147644.
+ BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents());
+ BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents());
+ BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents());
+ test_guest()->WaitForAdvanceFocus();
+}
+
+// This test opens a page in http and then opens another page in https, forcing
+// a RenderViewHost swap in the web_contents. We verify that the embedder in the
+// web_contents gets cleared properly.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderChangedAfterSwap) {
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // 1. Load an embedder page with one guest in it.
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+
+ // 2. Navigate to a URL in https, so we trigger a RenderViewHost swap.
+ GURL test_https_url(https_server.GetURL(
+ "files/browser_plugin_title_change.html"));
+ content::WindowedNotificationObserver swap_observer(
+ content::NOTIFICATION_WEB_CONTENTS_SWAPPED,
+ content::Source<WebContents>(test_embedder()->web_contents()));
+ NavigateToURL(shell(), test_https_url);
+ swap_observer.Wait();
+
+ TestBrowserPluginEmbedder* test_embedder_after_swap =
+ static_cast<TestBrowserPluginEmbedder*>(
+ static_cast<WebContentsImpl*>(shell()->web_contents())->
+ GetBrowserPluginEmbedder());
+ // Verify we have a no embedder in web_contents (since the new page doesn't
+ // have any browser plugin).
+ ASSERT_TRUE(!test_embedder_after_swap);
+ ASSERT_NE(test_embedder(), test_embedder_after_swap);
+}
+
+// This test opens two pages in http and there is no RenderViewHost swap,
+// therefore the embedder created on first page navigation stays the same in
+// web_contents.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderSameAfterNav) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+ WebContentsImpl* embedder_web_contents = test_embedder()->web_contents();
+
+ // Navigate to another page in same host and port, so RenderViewHost swap
+ // does not happen and existing embedder doesn't change in web_contents.
+ GURL test_url_new(embedded_test_server()->GetURL(
+ "/browser_plugin_title_change.html"));
+ const string16 expected_title = ASCIIToUTF16("done");
+ content::TitleWatcher title_watcher(shell()->web_contents(), expected_title);
+ NavigateToURL(shell(), test_url_new);
+ LOG(INFO) << "Start waiting for title";
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ LOG(INFO) << "Done navigating to second page";
+
+ TestBrowserPluginEmbedder* test_embedder_after_nav =
+ static_cast<TestBrowserPluginEmbedder*>(
+ embedder_web_contents->GetBrowserPluginEmbedder());
+ // Embedder must not change in web_contents.
+ ASSERT_EQ(test_embedder_after_nav, test_embedder());
+}
+
+// This test verifies that hiding the embedder also hides the guest.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, BrowserPluginVisibilityChanged) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+
+ // Hide the Browser Plugin.
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ ExecuteSyncJSFunction(
+ rvh, "document.getElementById('plugin').style.visibility = 'hidden'");
+
+ // Make sure that the guest is hidden.
+ test_guest()->WaitUntilHidden();
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderVisibilityChanged) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+
+ // Hide the embedder.
+ test_embedder()->web_contents()->WasHidden();
+
+ // Make sure that hiding the embedder also hides the guest.
+ test_guest()->WaitUntilHidden();
+}
+
+// Verifies that installing/uninstalling touch-event handlers in the guest
+// plugin correctly updates the touch-event handling state in the embedder.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, AcceptTouchEvents) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(
+ kEmbedderURL, kHTMLForGuestTouchHandler, true, std::string());
+
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ // The embedder should not have any touch event handlers at this point.
+ EXPECT_FALSE(rvh->has_touch_handler());
+
+ // Install the touch handler in the guest. This should cause the embedder to
+ // start listening for touch events too.
+ RenderViewHostMessageObserver observer(rvh,
+ ViewHostMsg_HasTouchEventHandlers::ID);
+ ExecuteSyncJSFunction(test_guest()->web_contents()->GetRenderViewHost(),
+ "InstallTouchHandler();");
+ observer.WaitUntilMessageReceived();
+ EXPECT_TRUE(rvh->has_touch_handler());
+
+ // Uninstalling the touch-handler in guest should cause the embedder to stop
+ // listening for touch events.
+ observer.ResetState();
+ ExecuteSyncJSFunction(test_guest()->web_contents()->GetRenderViewHost(),
+ "UninstallTouchHandler();");
+ observer.WaitUntilMessageReceived();
+ EXPECT_FALSE(rvh->has_touch_handler());
+}
+
+// This tests verifies that reloading the embedder does not crash the browser
+// and that the guest is reset.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, ReloadEmbedder) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+
+ // Change the title of the page to 'modified' so that we know that
+ // the page has successfully reloaded when it goes back to 'embedder'
+ // in the next step.
+ {
+ const string16 expected_title = ASCIIToUTF16("modified");
+ content::TitleWatcher title_watcher(test_embedder()->web_contents(),
+ expected_title);
+
+ ExecuteSyncJSFunction(rvh,
+ base::StringPrintf("SetTitle('%s');", "modified"));
+
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ }
+
+ // Reload the embedder page, and verify that the reload was successful.
+ // Then navigate the guest to verify that the browser process does not crash.
+ {
+ const string16 expected_title = ASCIIToUTF16("embedder");
+ content::TitleWatcher title_watcher(test_embedder()->web_contents(),
+ expected_title);
+
+ test_embedder()->web_contents()->GetController().Reload(false);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+
+ ExecuteSyncJSFunction(
+ test_embedder()->web_contents()->GetRenderViewHost(),
+ base::StringPrintf("SetSrc('%s');", kHTMLForGuest));
+ test_guest_manager()->WaitForGuestAdded();
+
+ const TestBrowserPluginGuestManager::GuestInstanceMap& instance_map =
+ test_guest_manager()->guest_web_contents_for_testing();
+ WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>(
+ instance_map.begin()->second);
+ TestBrowserPluginGuest* new_test_guest =
+ static_cast<TestBrowserPluginGuest*>(
+ test_guest_web_contents->GetBrowserPluginGuest());
+ ASSERT_TRUE(new_test_guest != NULL);
+
+ // Wait for the guest to send an UpdateRectMsg, meaning it is ready.
+ new_test_guest->WaitForUpdateRectMsg();
+ }
+}
+
+// Always failing in the win7_aura try bot. See http://crbug.com/181107.
+#if defined(OS_WIN) && defined(USE_AURA)
+#define MAYBE_AcceptDragEvents DISABLED_AcceptDragEvents
+#else
+#define MAYBE_AcceptDragEvents AcceptDragEvents
+#endif
+
+// Tests that a drag-n-drop over the browser plugin in the embedder happens
+// correctly.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, MAYBE_AcceptDragEvents) {
+ const char kEmbedderURL[] = "/browser_plugin_dragging.html";
+ StartBrowserPluginTest(
+ kEmbedderURL, kHTMLForGuestAcceptDrag, true, std::string());
+
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+
+ // Get a location in the embedder outside of the plugin.
+ base::ListValue *start, *end;
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(rvh, "dragLocation()");
+ ASSERT_TRUE(value->GetAsList(&start) && start->GetSize() == 2);
+ double start_x, start_y;
+ ASSERT_TRUE(start->GetDouble(0, &start_x) && start->GetDouble(1, &start_y));
+
+ // Get a location in the embedder that falls inside the plugin.
+ value = content::ExecuteScriptAndGetValue(rvh, "dropLocation()");
+ ASSERT_TRUE(value->GetAsList(&end) && end->GetSize() == 2);
+ double end_x, end_y;
+ ASSERT_TRUE(end->GetDouble(0, &end_x) && end->GetDouble(1, &end_y));
+
+ DropData drop_data;
+ GURL url = GURL("https://www.domain.com/index.html");
+ drop_data.url = url;
+
+ // Pretend that the URL is being dragged over the embedder. Start the drag
+ // from outside the plugin, then move the drag inside the plugin and drop.
+ // This should trigger appropriate messages from the embedder to the guest,
+ // and end with a drop on the guest. The guest changes title when a drop
+ // happens.
+ const string16 expected_title = ASCIIToUTF16("DROPPED");
+ content::TitleWatcher title_watcher(test_guest()->web_contents(),
+ expected_title);
+
+ rvh->DragTargetDragEnter(drop_data, gfx::Point(start_x, start_y),
+ gfx::Point(start_x, start_y), WebKit::WebDragOperationEvery, 0);
+ rvh->DragTargetDragOver(gfx::Point(end_x, end_y), gfx::Point(end_x, end_y),
+ WebKit::WebDragOperationEvery, 0);
+ rvh->DragTargetDrop(gfx::Point(end_x, end_y), gfx::Point(end_x, end_y), 0);
+
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+}
+
+// This test verifies that round trip postMessage works as expected.
+// 1. The embedder posts a message 'testing123' to the guest.
+// 2. The guest receives and replies to the message using the event object's
+// source object: event.source.postMessage('foobar', '*')
+// 3. The embedder receives the message and uses the event's source
+// object to do one final reply: 'stop'
+// 4. The guest receives the final 'stop' message.
+// 5. The guest acks the 'stop' message with a 'stop_ack' message.
+// 6. The embedder changes its title to 'main guest' when it sees the 'stop_ack'
+// message.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, PostMessage) {
+ const char* kTesting = "testing123";
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ const char* kGuestURL = "/browser_plugin_post_message_guest.html";
+ StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ {
+ const string16 expected_title = ASCIIToUTF16("main guest");
+ content::TitleWatcher title_watcher(test_embedder()->web_contents(),
+ expected_title);
+
+ // By the time we get here 'contentWindow' should be ready because the
+ // guest has completed loading.
+ ExecuteSyncJSFunction(
+ rvh, base::StringPrintf("PostMessage('%s, false');", kTesting));
+
+ // The title will be updated to "main guest" at the last stage of the
+ // process described above.
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ }
+}
+
+// This is the same as BrowserPluginHostTest.PostMessage but also
+// posts a message to an iframe.
+// TODO(fsamuel): This test should replace the previous test once postMessage
+// iframe targeting is fixed (see http://crbug.com/153701).
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, DISABLED_PostMessageToIFrame) {
+ const char* kTesting = "testing123";
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ const char* kGuestURL = "/browser_plugin_post_message_guest.html";
+ StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ {
+ const string16 expected_title = ASCIIToUTF16("main guest");
+ content::TitleWatcher title_watcher(test_embedder()->web_contents(),
+ expected_title);
+
+ ExecuteSyncJSFunction(
+ rvh, base::StringPrintf("PostMessage('%s, false');", kTesting));
+
+ // The title will be updated to "main guest" at the last stage of the
+ // process described above.
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ }
+ {
+ content::TitleWatcher ready_watcher(test_embedder()->web_contents(),
+ ASCIIToUTF16("ready"));
+
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ test_guest()->web_contents()->GetRenderViewHost());
+ GURL test_url = embedded_test_server()->GetURL(
+ "/browser_plugin_post_message_guest.html");
+ ExecuteSyncJSFunction(
+ guest_rvh,
+ base::StringPrintf(
+ "CreateChildFrame('%s');", test_url.spec().c_str()));
+
+ string16 actual_title = ready_watcher.WaitAndGetTitle();
+ EXPECT_EQ(ASCIIToUTF16("ready"), actual_title);
+
+ content::TitleWatcher iframe_watcher(test_embedder()->web_contents(),
+ ASCIIToUTF16("iframe"));
+ ExecuteSyncJSFunction(
+ rvh, base::StringPrintf("PostMessage('%s', true);", kTesting));
+
+ // The title will be updated to "iframe" at the last stage of the
+ // process described above.
+ actual_title = iframe_watcher.WaitAndGetTitle();
+ EXPECT_EQ(ASCIIToUTF16("iframe"), actual_title);
+ }
+}
+
+// This test verifies that if a browser plugin is hidden before navigation,
+// the guest starts off hidden.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, HiddenBeforeNavigation) {
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ const std::string embedder_code =
+ "document.getElementById('plugin').style.visibility = 'hidden'";
+ StartBrowserPluginTest(
+ kEmbedderURL, kHTMLForGuest, true, embedder_code);
+ EXPECT_FALSE(test_guest()->visible());
+}
+
+// This test verifies that if a browser plugin is focused before navigation then
+// the guest starts off focused.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, FocusBeforeNavigation) {
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ const std::string embedder_code =
+ "document.getElementById('plugin').focus();";
+ StartBrowserPluginTest(
+ kEmbedderURL, kHTMLForGuest, true, embedder_code);
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ test_guest()->web_contents()->GetRenderViewHost());
+ // Verify that the guest is focused.
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(guest_rvh, "document.hasFocus()");
+ bool result = false;
+ ASSERT_TRUE(value->GetAsBoolean(&result));
+ EXPECT_TRUE(result);
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, FocusTracksEmbedder) {
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
+ test_guest()->web_contents()->GetRenderViewHost());
+ {
+ // Focus the BrowserPlugin. This will have the effect of also focusing the
+ // current guest.
+ ExecuteSyncJSFunction(rvh, "document.getElementById('plugin').focus();");
+ // Verify that key presses go to the guest.
+ SimulateSpaceKeyPress(test_embedder()->web_contents());
+ test_guest()->WaitForInput();
+ // Verify that the guest is focused.
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(guest_rvh, "document.hasFocus()");
+ bool result = false;
+ ASSERT_TRUE(value->GetAsBoolean(&result));
+ EXPECT_TRUE(result);
+ }
+ // Blur the embedder.
+ test_embedder()->web_contents()->GetRenderViewHost()->Blur();
+ test_guest()->WaitForBlur();
+}
+
+// Test for regression http://crbug.com/162961.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, GetRenderViewHostAtPositionTest) {
+ const char kEmbedderURL[] = "/browser_plugin_embedder.html";
+ const std::string embedder_code =
+ base::StringPrintf("SetSize(%d, %d);", 100, 100);
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuestWithSize, true,
+ embedder_code);
+ // Check for render view host at position (150, 150) that is outside the
+ // bounds of our guest, so this would respond with the render view host of the
+ // embedder.
+ test_embedder()->WaitForRenderViewHostAtPosition(150, 150);
+ ASSERT_EQ(test_embedder()->web_contents()->GetRenderViewHost(),
+ test_embedder()->last_rvh_at_position_response());
+}
+
+// This test verifies that if IME is enabled in the embedder, it is also enabled
+// in the guest.
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, VerifyInputMethodActive) {
+ const char* kEmbedderURL = "/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_guest()->web_contents()->GetRenderViewHost());
+ EXPECT_TRUE(rvh->input_method_active());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h b/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h
new file mode 100644
index 00000000000..9bfc1e2d740
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h
@@ -0,0 +1,41 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_
+
+#include "base/base_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+
+struct BrowserPluginHostMsg_CreateGuest_Params;
+
+namespace content {
+
+class BrowserPluginEmbedder;
+class BrowserPluginGuest;
+class RenderViewHost;
+class WebContentsImpl;
+
+// Factory to create BrowserPlugin embedder and guest.
+class CONTENT_EXPORT BrowserPluginHostFactory {
+ public:
+ virtual BrowserPluginGuestManager* CreateBrowserPluginGuestManager() = 0;
+
+ virtual BrowserPluginGuest* CreateBrowserPluginGuest(
+ int instance_id,
+ WebContentsImpl* web_contents) = 0;
+
+ virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder(
+ WebContentsImpl* web_contents) = 0;
+
+ protected:
+ virtual ~BrowserPluginHostFactory() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc
new file mode 100644
index 00000000000..031797a46c4
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc
@@ -0,0 +1,72 @@
+// 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 "content/browser/browser_plugin/browser_plugin_message_filter.h"
+
+#include "base/supports_user_data.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/browser_plugin/browser_plugin_constants.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_view_host.h"
+
+namespace content {
+
+BrowserPluginMessageFilter::BrowserPluginMessageFilter(int render_process_id,
+ bool is_guest)
+ : render_process_id_(render_process_id),
+ is_guest_(is_guest) {
+}
+
+BrowserPluginMessageFilter::~BrowserPluginMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+bool BrowserPluginMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ // Any message requested by a BrowserPluginGuest should be routed through
+ // a BrowserPluginGuestManager.
+ if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserPluginGuestManager* guest_manager = GetBrowserPluginGuestManager();
+ if (guest_manager)
+ guest_manager->OnMessageReceived(message, render_process_id_);
+ // We always swallow messages destined for BrowserPluginGuestManager because
+ // we're on the UI thread and fallback code is expected to be run on the IO
+ // thread.
+ return true;
+ }
+ return false;
+}
+
+void BrowserPluginMessageFilter::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+void BrowserPluginMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+ if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message))
+ *thread = BrowserThread::UI;
+}
+
+BrowserPluginGuestManager*
+ BrowserPluginMessageFilter::GetBrowserPluginGuestManager() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderProcessHostImpl* host = static_cast<RenderProcessHostImpl*>(
+ RenderProcessHost::FromID(render_process_id_));
+ if (!host)
+ return NULL;
+
+ BrowserContext* browser_context = host->GetBrowserContext();
+ return static_cast<BrowserPluginGuestManager*>(
+ browser_context->GetUserData(
+ browser_plugin::kBrowserPluginGuestManagerKeyName));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h
new file mode 100644
index 00000000000..829c1badcbf
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h
@@ -0,0 +1,45 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class BrowserContext;
+class BrowserPluginGuestManager;
+
+// This class filters out incoming IPC messages for the guest renderer process
+// on the IPC thread before other message filters handle them.
+class BrowserPluginMessageFilter : public BrowserMessageFilter {
+ public:
+ BrowserPluginMessageFilter(int render_process_id, bool is_guest);
+
+ // BrowserMessageFilter implementation.
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+
+ private:
+ friend class BrowserThread;
+ friend class base::DeleteHelper<BrowserPluginMessageFilter>;
+
+ virtual ~BrowserPluginMessageFilter();
+
+ BrowserPluginGuestManager* GetBrowserPluginGuestManager();
+
+ int render_process_id_;
+ int is_guest_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h
new file mode 100644
index 00000000000..2b462371001
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h
@@ -0,0 +1,34 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
+
+#include "content/browser/renderer_host/popup_menu_helper_mac.h"
+
+namespace content {
+class RenderViewHost;
+class RenderViewHostImpl;
+
+// This class is similiar to PopupMenuHelperMac but positions the popup relative
+// to the embedder, and issues a reply to the guest.
+class BrowserPluginPopupMenuHelper : public PopupMenuHelper {
+ public:
+ // Creates a BrowserPluginPopupMenuHelper that positions popups relative to
+ // |embedder_rvh| and will notify |guest_rvh| when a user selects or cancels
+ // the popup.
+ BrowserPluginPopupMenuHelper(RenderViewHost* embedder_rvh,
+ RenderViewHost* guest_rvh);
+
+ private:
+ virtual RenderWidgetHostViewMac* GetRenderWidgetHostView() const OVERRIDE;
+
+ RenderViewHostImpl* embedder_rvh_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPluginPopupMenuHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
diff --git a/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm
new file mode 100644
index 00000000000..e2b5da55c00
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm
@@ -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.
+
+#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h"
+
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+namespace content {
+
+BrowserPluginPopupMenuHelper::BrowserPluginPopupMenuHelper(
+ RenderViewHost* embedder_rvh, RenderViewHost* guest_rvh)
+ : PopupMenuHelper(guest_rvh),
+ embedder_rvh_(static_cast<RenderViewHostImpl*>(embedder_rvh)) {
+}
+
+RenderWidgetHostViewMac*
+ BrowserPluginPopupMenuHelper::GetRenderWidgetHostView() const {
+ return static_cast<RenderWidgetHostViewMac*>(embedder_rvh_->GetView());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc
new file mode 100644
index 00000000000..a513d0fd4be
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc
@@ -0,0 +1,40 @@
+// 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 "content/browser/browser_plugin/test_browser_plugin_embedder.h"
+
+#include "content/browser/browser_plugin/browser_plugin_embedder.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+
+namespace content {
+
+TestBrowserPluginEmbedder::TestBrowserPluginEmbedder(
+ WebContentsImpl* web_contents)
+ : BrowserPluginEmbedder(web_contents),
+ last_rvh_at_position_response_(NULL) {
+}
+
+TestBrowserPluginEmbedder::~TestBrowserPluginEmbedder() {
+}
+
+void TestBrowserPluginEmbedder::GetRenderViewHostCallback(
+ RenderViewHost* rvh, int x, int y) {
+ last_rvh_at_position_response_ = rvh;
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+}
+
+void TestBrowserPluginEmbedder::WaitForRenderViewHostAtPosition(int x, int y) {
+ GetRenderViewHostAtPosition(x, y,
+ base::Bind(&TestBrowserPluginEmbedder::GetRenderViewHostCallback,
+ base::Unretained(this)));
+ message_loop_runner_ = new MessageLoopRunner();
+ message_loop_runner_->Run();
+}
+
+WebContentsImpl* TestBrowserPluginEmbedder::web_contents() const {
+ return static_cast<WebContentsImpl*>(BrowserPluginEmbedder::web_contents());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h
new file mode 100644
index 00000000000..4673f926305
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h
@@ -0,0 +1,47 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/browser_plugin/browser_plugin_embedder.h"
+#include "content/public/test/test_utils.h"
+
+namespace content {
+
+class BrowserPluginGuest;
+class RenderViewHost;
+class WebContentsImpl;
+
+// Test class for BrowserPluginEmbedder.
+//
+// Provides utilities to wait for certain state/messages in
+// BrowserPluginEmbedder to be used in tests.
+class TestBrowserPluginEmbedder : public BrowserPluginEmbedder {
+ public:
+ TestBrowserPluginEmbedder(WebContentsImpl* web_contents);
+ virtual ~TestBrowserPluginEmbedder();
+
+ // Asks the renderer process for RenderViewHost at (|x|, |y|) and waits until
+ // the response arrives.
+ void WaitForRenderViewHostAtPosition(int x, int y);
+ RenderViewHost* last_rvh_at_position_response() {
+ return last_rvh_at_position_response_;
+ }
+
+ WebContentsImpl* web_contents() const;
+
+ private:
+ void GetRenderViewHostCallback(RenderViewHost* rvh, int x, int y);
+
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+ RenderViewHost* last_rvh_at_position_response_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginEmbedder);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc
new file mode 100644
index 00000000000..a6e62a3ba55
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc
@@ -0,0 +1,250 @@
+// 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 "content/browser/browser_plugin/test_browser_plugin_guest.h"
+
+#include "base/test/test_timeouts.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/public/browser/notification_types.h"
+
+namespace content {
+
+class BrowserPluginGuest;
+
+TestBrowserPluginGuest::TestBrowserPluginGuest(
+ int instance_id,
+ WebContentsImpl* web_contents)
+ : BrowserPluginGuest(instance_id, web_contents, NULL, false),
+ update_rect_count_(0),
+ damage_buffer_call_count_(0),
+ exit_observed_(false),
+ focus_observed_(false),
+ blur_observed_(false),
+ advance_focus_observed_(false),
+ was_hidden_observed_(false),
+ set_damage_buffer_observed_(false),
+ input_observed_(false),
+ load_stop_observed_(false),
+ waiting_for_damage_buffer_with_size_(false),
+ last_damage_buffer_size_(gfx::Size()) {
+ // Listen to visibility changes so that a test can wait for these changes.
+ notification_registrar_.Add(this,
+ NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
+ Source<WebContents>(web_contents));
+}
+
+TestBrowserPluginGuest::~TestBrowserPluginGuest() {
+}
+
+WebContentsImpl* TestBrowserPluginGuest::web_contents() const {
+ return static_cast<WebContentsImpl*>(BrowserPluginGuest::web_contents());
+}
+
+void TestBrowserPluginGuest::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED: {
+ bool visible = *Details<bool>(details).ptr();
+ if (!visible) {
+ was_hidden_observed_ = true;
+ if (was_hidden_message_loop_runner_.get())
+ was_hidden_message_loop_runner_->Quit();
+ }
+ return;
+ }
+ }
+
+ BrowserPluginGuest::Observe(type, source, details);
+}
+
+void TestBrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) {
+ if (msg->type() == BrowserPluginMsg_UpdateRect::ID) {
+ update_rect_count_++;
+ int instance_id = 0;
+ BrowserPluginMsg_UpdateRect_Params params;
+ BrowserPluginMsg_UpdateRect::Read(msg, &instance_id, &params);
+ last_view_size_observed_ = params.view_size;
+ if (!expected_auto_view_size_.IsEmpty() &&
+ expected_auto_view_size_ == params.view_size) {
+ if (auto_view_size_message_loop_runner_.get())
+ auto_view_size_message_loop_runner_->Quit();
+ }
+ if (send_message_loop_runner_.get())
+ send_message_loop_runner_->Quit();
+ }
+ BrowserPluginGuest::SendMessageToEmbedder(msg);
+}
+
+void TestBrowserPluginGuest::WaitForUpdateRectMsg() {
+ // Check if we already got any UpdateRect message.
+ if (update_rect_count_ > 0)
+ return;
+ send_message_loop_runner_ = new MessageLoopRunner();
+ send_message_loop_runner_->Run();
+}
+
+void TestBrowserPluginGuest::ResetUpdateRectCount() {
+ update_rect_count_ = 0;
+}
+
+void TestBrowserPluginGuest::WaitForDamageBufferWithSize(
+ const gfx::Size& size) {
+ if (damage_buffer_call_count_ > 0 && last_damage_buffer_size_ == size)
+ return;
+
+ expected_damage_buffer_size_ = size;
+ waiting_for_damage_buffer_with_size_ = true;
+ damage_buffer_message_loop_runner_ = new MessageLoopRunner();
+ damage_buffer_message_loop_runner_->Run();
+}
+
+void TestBrowserPluginGuest::RenderProcessGone(base::TerminationStatus status) {
+ exit_observed_ = true;
+ if (status != base::TERMINATION_STATUS_NORMAL_TERMINATION &&
+ status != base::TERMINATION_STATUS_STILL_RUNNING)
+ LOG(INFO) << "Guest crashed status: " << status;
+ if (crash_message_loop_runner_.get())
+ crash_message_loop_runner_->Quit();
+ BrowserPluginGuest::RenderProcessGone(status);
+}
+
+void TestBrowserPluginGuest::OnHandleInputEvent(
+ int instance_id,
+ const gfx::Rect& guest_window_rect,
+ const WebKit::WebInputEvent* event) {
+ BrowserPluginGuest::OnHandleInputEvent(instance_id,
+ guest_window_rect,
+ event);
+ input_observed_ = true;
+ if (input_message_loop_runner_.get())
+ input_message_loop_runner_->Quit();
+}
+
+void TestBrowserPluginGuest::WaitForExit() {
+ // Check if we already observed a guest crash, return immediately if so.
+ if (exit_observed_)
+ return;
+
+ crash_message_loop_runner_ = new MessageLoopRunner();
+ crash_message_loop_runner_->Run();
+}
+
+void TestBrowserPluginGuest::WaitForFocus() {
+ if (focus_observed_) {
+ focus_observed_ = false;
+ return;
+ }
+ focus_message_loop_runner_ = new MessageLoopRunner();
+ focus_message_loop_runner_->Run();
+ focus_observed_ = false;
+}
+
+void TestBrowserPluginGuest::WaitForBlur() {
+ if (blur_observed_) {
+ blur_observed_ = false;
+ return;
+ }
+ blur_message_loop_runner_ = new MessageLoopRunner();
+ blur_message_loop_runner_->Run();
+ blur_observed_ = false;
+}
+
+void TestBrowserPluginGuest::WaitForAdvanceFocus() {
+ if (advance_focus_observed_)
+ return;
+ advance_focus_message_loop_runner_ = new MessageLoopRunner();
+ advance_focus_message_loop_runner_->Run();
+}
+
+void TestBrowserPluginGuest::WaitUntilHidden() {
+ if (was_hidden_observed_) {
+ was_hidden_observed_ = false;
+ return;
+ }
+ was_hidden_message_loop_runner_ = new MessageLoopRunner();
+ was_hidden_message_loop_runner_->Run();
+ was_hidden_observed_ = false;
+}
+
+void TestBrowserPluginGuest::WaitForInput() {
+ if (input_observed_) {
+ input_observed_ = false;
+ return;
+ }
+
+ input_message_loop_runner_ = new MessageLoopRunner();
+ input_message_loop_runner_->Run();
+ input_observed_ = false;
+}
+
+void TestBrowserPluginGuest::WaitForLoadStop() {
+ if (load_stop_observed_) {
+ load_stop_observed_ = false;
+ return;
+ }
+
+ load_stop_message_loop_runner_ = new MessageLoopRunner();
+ load_stop_message_loop_runner_->Run();
+ load_stop_observed_ = false;
+}
+
+void TestBrowserPluginGuest::WaitForViewSize(const gfx::Size& view_size) {
+ if (last_view_size_observed_ == view_size) {
+ last_view_size_observed_ = gfx::Size();
+ return;
+ }
+
+ expected_auto_view_size_ = view_size;
+ auto_view_size_message_loop_runner_ = new MessageLoopRunner();
+ auto_view_size_message_loop_runner_->Run();
+ last_view_size_observed_ = gfx::Size();
+}
+
+void TestBrowserPluginGuest::OnSetFocus(int instance_id, bool focused) {
+ if (focused) {
+ focus_observed_ = true;
+ if (focus_message_loop_runner_.get())
+ focus_message_loop_runner_->Quit();
+ } else {
+ blur_observed_ = true;
+ if (blur_message_loop_runner_.get())
+ blur_message_loop_runner_->Quit();
+ }
+ BrowserPluginGuest::OnSetFocus(instance_id, focused);
+}
+
+void TestBrowserPluginGuest::OnTakeFocus(bool reverse) {
+ advance_focus_observed_ = true;
+ if (advance_focus_message_loop_runner_.get())
+ advance_focus_message_loop_runner_->Quit();
+ BrowserPluginGuest::OnTakeFocus(reverse);
+}
+
+void TestBrowserPluginGuest::SetDamageBuffer(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params) {
+ ++damage_buffer_call_count_;
+ last_damage_buffer_size_ = params.view_rect.size();
+
+ if (waiting_for_damage_buffer_with_size_ &&
+ expected_damage_buffer_size_ == params.view_rect.size() &&
+ damage_buffer_message_loop_runner_.get()) {
+ damage_buffer_message_loop_runner_->Quit();
+ waiting_for_damage_buffer_with_size_ = false;
+ }
+
+ BrowserPluginGuest::SetDamageBuffer(params);
+}
+
+void TestBrowserPluginGuest::DidStopLoading(
+ RenderViewHost* render_view_host) {
+ BrowserPluginGuest::DidStopLoading(render_view_host);
+ load_stop_observed_ = true;
+ if (load_stop_message_loop_runner_.get())
+ load_stop_message_loop_runner_->Quit();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h
new file mode 100644
index 00000000000..7fccb002388
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h
@@ -0,0 +1,108 @@
+// 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 CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/public/test/test_utils.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+class RenderProcessHost;
+class RenderViewHost;
+class WebContentsImpl;
+
+// Test class for BrowserPluginGuest.
+//
+// Provides utilities to wait for certain state/messages in BrowserPluginGuest
+// to be used in tests.
+class TestBrowserPluginGuest : public BrowserPluginGuest {
+ public:
+ TestBrowserPluginGuest(int instance_id, WebContentsImpl* web_contents);
+ virtual ~TestBrowserPluginGuest();
+
+ WebContentsImpl* web_contents() const;
+
+ // NotificationObserver method override.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Overridden methods from BrowserPluginGuest to intercept in test objects.
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+ virtual void OnHandleInputEvent(int instance_id,
+ const gfx::Rect& guest_window_rect,
+ const WebKit::WebInputEvent* event) OVERRIDE;
+ virtual void OnSetFocus(int instance_id, bool focused) OVERRIDE;
+ virtual void OnTakeFocus(bool reverse) OVERRIDE;
+ virtual void SetDamageBuffer(
+ const BrowserPluginHostMsg_ResizeGuest_Params& params) OVERRIDE;
+ virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE;
+
+ // Test utilities to wait for a event we are interested in.
+ // Waits until UpdateRect message is sent from the guest, meaning it is
+ // ready/rendered.
+ void WaitForUpdateRectMsg();
+ void ResetUpdateRectCount();
+ // Waits until a guest receives a damage buffer of the specified |size|.
+ void WaitForDamageBufferWithSize(const gfx::Size& size);
+ // Waits for focus to reach this guest.
+ void WaitForFocus();
+ // Waits for blur to reach this guest.
+ void WaitForBlur();
+ // Waits for focus to move out of this guest.
+ void WaitForAdvanceFocus();
+ // Waits until the guest is hidden.
+ void WaitUntilHidden();
+ // Waits until guest exits.
+ void WaitForExit();
+ // Waits until input is observed.
+ void WaitForInput();
+ // Waits until 'loadstop' is observed.
+ void WaitForLoadStop();
+ // Waits until UpdateRect with a particular |view_size| is observed.
+ void WaitForViewSize(const gfx::Size& view_size);
+
+ private:
+ // Overridden methods from BrowserPluginGuest to intercept in test objects.
+ virtual void SendMessageToEmbedder(IPC::Message* msg) OVERRIDE;
+
+ int update_rect_count_;
+ int damage_buffer_call_count_;
+ bool exit_observed_;
+ bool focus_observed_;
+ bool blur_observed_;
+ bool advance_focus_observed_;
+ bool was_hidden_observed_;
+ bool set_damage_buffer_observed_;
+ bool input_observed_;
+ bool load_stop_observed_;
+ gfx::Size last_view_size_observed_;
+ gfx::Size expected_auto_view_size_;
+
+ // For WaitForDamageBufferWithSize().
+ bool waiting_for_damage_buffer_with_size_;
+ gfx::Size expected_damage_buffer_size_;
+ gfx::Size last_damage_buffer_size_;
+
+ scoped_refptr<MessageLoopRunner> send_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> crash_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> focus_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> blur_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> advance_focus_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> was_hidden_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> damage_buffer_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> input_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> load_stop_message_loop_runner_;
+ scoped_refptr<MessageLoopRunner> auto_view_size_message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginGuest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc
new file mode 100644
index 00000000000..7d0b5ac23dd
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 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 "content/browser/browser_plugin/test_browser_plugin_guest_manager.h"
+
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/test/test_utils.h"
+
+namespace content {
+
+TestBrowserPluginGuestManager::TestBrowserPluginGuestManager() {
+}
+
+TestBrowserPluginGuestManager::~TestBrowserPluginGuestManager() {
+}
+
+void TestBrowserPluginGuestManager::AddGuest(
+ int instance_id,
+ WebContentsImpl* guest_web_contents) {
+ BrowserPluginGuestManager::AddGuest(instance_id, guest_web_contents);
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+}
+
+void TestBrowserPluginGuestManager::WaitForGuestAdded() {
+ // Check if guests were already created.
+ if (guest_web_contents_by_instance_id_.size() > 0)
+ return;
+ // Wait otherwise.
+ message_loop_runner_ = new MessageLoopRunner();
+ message_loop_runner_->Run();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h
new file mode 100644
index 00000000000..d7683cf7ae9
--- /dev/null
+++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h
@@ -0,0 +1,51 @@
+// Copyright (c) 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 CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_
+#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+#include "content/public/test/test_utils.h"
+
+FORWARD_DECLARE_TEST(BrowserPluginHostTest, ReloadEmbedder);
+
+namespace content {
+
+class WebContentsImpl;
+
+// Test class for BrowserPluginGuestManager.
+//
+// Provides utilities to wait for certain state/messages in
+// BrowserPluginGuestManager to be used in tests.
+class TestBrowserPluginGuestManager : public BrowserPluginGuestManager {
+ public:
+ typedef BrowserPluginGuestManager::GuestInstanceMap GuestInstanceMap;
+
+ TestBrowserPluginGuestManager();
+ virtual ~TestBrowserPluginGuestManager();
+
+ const GuestInstanceMap& guest_web_contents_for_testing() const {
+ return guest_web_contents_by_instance_id_;
+ }
+
+ // Waits until at least one guest is added to the guest manager.
+ void WaitForGuestAdded();
+
+ private:
+ // BrowserPluginHostTest.ReloadEmbedder needs access to the GuestInstanceMap.
+ FRIEND_TEST_ALL_PREFIXES(BrowserPluginHostTest, ReloadEmbedder);
+
+ // Overriden to intercept in test.
+ virtual void AddGuest(int instance_id,
+ WebContentsImpl* guest_web_contents) OVERRIDE;
+
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+ DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginGuestManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_
diff --git a/chromium/content/browser/browser_process_sub_thread.cc b/chromium/content/browser/browser_process_sub_thread.cc
new file mode 100644
index 00000000000..9a5b78a9edb
--- /dev/null
+++ b/chromium/content/browser/browser_process_sub_thread.cc
@@ -0,0 +1,75 @@
+// 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 "content/browser/browser_process_sub_thread.h"
+
+#include "base/debug/leak_tracker.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/notification_service_impl.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
+namespace content {
+
+BrowserProcessSubThread::BrowserProcessSubThread(BrowserThread::ID identifier)
+ : BrowserThreadImpl(identifier) {
+}
+
+BrowserProcessSubThread::~BrowserProcessSubThread() {
+ Stop();
+}
+
+void BrowserProcessSubThread::Init() {
+#if defined(OS_WIN)
+ com_initializer_.reset(new base::win::ScopedCOMInitializer());
+#endif
+
+ notification_service_.reset(new NotificationServiceImpl());
+
+ BrowserThreadImpl::Init();
+
+ if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ // Though this thread is called the "IO" thread, it actually just routes
+ // messages around; it shouldn't be allowed to perform any blocking disk
+ // I/O.
+ base::ThreadRestrictions::SetIOAllowed(false);
+ base::ThreadRestrictions::DisallowWaiting();
+ }
+}
+
+void BrowserProcessSubThread::CleanUp() {
+ if (BrowserThread::CurrentlyOn(BrowserThread::IO))
+ IOThreadPreCleanUp();
+
+ BrowserThreadImpl::CleanUp();
+
+ notification_service_.reset();
+
+#if defined(OS_WIN)
+ com_initializer_.reset();
+#endif
+}
+
+void BrowserProcessSubThread::IOThreadPreCleanUp() {
+ // Kill all things that might be holding onto
+ // net::URLRequest/net::URLRequestContexts.
+
+ // Destroy all URLRequests started by URLFetchers.
+ net::URLFetcher::CancelAll();
+
+#if !defined(OS_IOS)
+ // If any child processes are still running, terminate them and
+ // and delete the BrowserChildProcessHost instances to release whatever
+ // IO thread only resources they are referencing.
+ BrowserChildProcessHostImpl::TerminateAll();
+#endif // !defined(OS_IOS)
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_process_sub_thread.h b/chromium/content/browser/browser_process_sub_thread.h
new file mode 100644
index 00000000000..e006388783b
--- /dev/null
+++ b/chromium/content/browser/browser_process_sub_thread.h
@@ -0,0 +1,61 @@
+// 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 CONTENT_BROWSER_BROWSER_PROCESS_SUB_THREAD_H_
+#define CONTENT_BROWSER_BROWSER_PROCESS_SUB_THREAD_H_
+
+#include "base/basictypes.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/common/content_export.h"
+
+#if defined(OS_WIN)
+namespace base {
+namespace win {
+class ScopedCOMInitializer;
+}
+}
+#endif
+
+namespace content {
+class NotificationService;
+}
+
+namespace content {
+
+// ----------------------------------------------------------------------------
+// BrowserProcessSubThread
+//
+// This simple thread object is used for the specialized threads that the
+// BrowserProcess spins up.
+//
+// Applications must initialize the COM library before they can call
+// COM library functions other than CoGetMalloc and memory allocation
+// functions, so this class initializes COM for those users.
+class CONTENT_EXPORT BrowserProcessSubThread : public BrowserThreadImpl {
+ public:
+ explicit BrowserProcessSubThread(BrowserThread::ID identifier);
+ virtual ~BrowserProcessSubThread();
+
+ protected:
+ virtual void Init() OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ private:
+ // These methods encapsulate cleanup that needs to happen on the IO thread
+ // before we call the embedder's CleanUp function.
+ void IOThreadPreCleanUp();
+
+#if defined (OS_WIN)
+ scoped_ptr<base::win::ScopedCOMInitializer> com_initializer_;
+#endif
+
+ // Each specialized thread has its own notification service.
+ scoped_ptr<NotificationService> notification_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserProcessSubThread);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_PROCESS_SUB_THREAD_H_
diff --git a/chromium/content/browser/browser_thread_impl.cc b/chromium/content/browser/browser_thread_impl.cc
new file mode 100644
index 00000000000..fb6feac452f
--- /dev/null
+++ b/chromium/content/browser/browser_thread_impl.cc
@@ -0,0 +1,482 @@
+// 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 "content/browser/browser_thread_impl.h"
+
+#include <string>
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/browser/browser_thread_delegate.h"
+
+namespace content {
+
+namespace {
+
+// Friendly names for the well-known threads.
+static const char* g_browser_thread_names[BrowserThread::ID_COUNT] = {
+ "", // UI (name assembled in browser_main.cc).
+ "Chrome_DBThread", // DB
+ "Chrome_FileThread", // FILE
+ "Chrome_FileUserBlockingThread", // FILE_USER_BLOCKING
+ "Chrome_ProcessLauncherThread", // PROCESS_LAUNCHER
+ "Chrome_CacheThread", // CACHE
+ "Chrome_IOThread", // IO
+};
+
+struct BrowserThreadGlobals {
+ BrowserThreadGlobals()
+ : blocking_pool(new base::SequencedWorkerPool(3, "BrowserBlocking")) {
+ memset(threads, 0, BrowserThread::ID_COUNT * sizeof(threads[0]));
+ memset(thread_delegates, 0,
+ BrowserThread::ID_COUNT * sizeof(thread_delegates[0]));
+ }
+
+ // This lock protects |threads|. Do not read or modify that array
+ // without holding this lock. Do not block while holding this lock.
+ base::Lock lock;
+
+ // This array is protected by |lock|. The threads are not owned by this
+ // array. Typically, the threads are owned on the UI thread by
+ // BrowserMainLoop. BrowserThreadImpl objects remove themselves from this
+ // array upon destruction.
+ BrowserThreadImpl* threads[BrowserThread::ID_COUNT];
+
+ // Only atomic operations are used on this array. The delegates are not owned
+ // by this array, rather by whoever calls BrowserThread::SetDelegate.
+ BrowserThreadDelegate* thread_delegates[BrowserThread::ID_COUNT];
+
+ const scoped_refptr<base::SequencedWorkerPool> blocking_pool;
+};
+
+base::LazyInstance<BrowserThreadGlobals>::Leaky
+ g_globals = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+BrowserThreadImpl::BrowserThreadImpl(ID identifier)
+ : Thread(g_browser_thread_names[identifier]),
+ identifier_(identifier) {
+ Initialize();
+}
+
+BrowserThreadImpl::BrowserThreadImpl(ID identifier,
+ base::MessageLoop* message_loop)
+ : Thread(message_loop->thread_name().c_str()), identifier_(identifier) {
+ set_message_loop(message_loop);
+ Initialize();
+}
+
+// static
+void BrowserThreadImpl::ShutdownThreadPool() {
+ // The goal is to make it impossible for chrome to 'infinite loop' during
+ // shutdown, but to reasonably expect that all BLOCKING_SHUTDOWN tasks queued
+ // during shutdown get run. There's nothing particularly scientific about the
+ // number chosen.
+ const int kMaxNewShutdownBlockingTasks = 1000;
+ BrowserThreadGlobals& globals = g_globals.Get();
+ globals.blocking_pool->Shutdown(kMaxNewShutdownBlockingTasks);
+}
+
+// static
+void BrowserThreadImpl::FlushThreadPoolHelper() {
+ // We don't want to create a pool if none exists.
+ if (g_globals == NULL)
+ return;
+ g_globals.Get().blocking_pool->FlushForTesting();
+}
+
+void BrowserThreadImpl::Init() {
+ BrowserThreadGlobals& globals = g_globals.Get();
+
+ using base::subtle::AtomicWord;
+ AtomicWord* storage =
+ reinterpret_cast<AtomicWord*>(&globals.thread_delegates[identifier_]);
+ AtomicWord stored_pointer = base::subtle::NoBarrier_Load(storage);
+ BrowserThreadDelegate* delegate =
+ reinterpret_cast<BrowserThreadDelegate*>(stored_pointer);
+ if (delegate) {
+ delegate->Init();
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&BrowserThreadDelegate::InitAsync,
+ // Delegate is expected to exist for the
+ // duration of the thread's lifetime
+ base::Unretained(delegate)));
+ }
+}
+
+// We disable optimizations for this block of functions so the compiler doesn't
+// merge them all together.
+MSVC_DISABLE_OPTIMIZE()
+MSVC_PUSH_DISABLE_WARNING(4748)
+
+NOINLINE void BrowserThreadImpl::UIThreadRun(base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::DBThreadRun(base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::FileThreadRun(
+ base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::FileUserBlockingThreadRun(
+ base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::ProcessLauncherThreadRun(
+ base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::CacheThreadRun(
+ base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+NOINLINE void BrowserThreadImpl::IOThreadRun(base::MessageLoop* message_loop) {
+ volatile int line_number = __LINE__;
+ Thread::Run(message_loop);
+ CHECK_GT(line_number, 0);
+}
+
+MSVC_POP_WARNING()
+MSVC_ENABLE_OPTIMIZE();
+
+void BrowserThreadImpl::Run(base::MessageLoop* message_loop) {
+ BrowserThread::ID thread_id;
+ if (!GetCurrentThreadIdentifier(&thread_id))
+ return Thread::Run(message_loop);
+
+ switch (thread_id) {
+ case BrowserThread::UI:
+ return UIThreadRun(message_loop);
+ case BrowserThread::DB:
+ return DBThreadRun(message_loop);
+ case BrowserThread::FILE:
+ return FileThreadRun(message_loop);
+ case BrowserThread::FILE_USER_BLOCKING:
+ return FileUserBlockingThreadRun(message_loop);
+ case BrowserThread::PROCESS_LAUNCHER:
+ return ProcessLauncherThreadRun(message_loop);
+ case BrowserThread::CACHE:
+ return CacheThreadRun(message_loop);
+ case BrowserThread::IO:
+ return IOThreadRun(message_loop);
+ case BrowserThread::ID_COUNT:
+ CHECK(false); // This shouldn't actually be reached!
+ break;
+ }
+ Thread::Run(message_loop);
+}
+
+void BrowserThreadImpl::CleanUp() {
+ BrowserThreadGlobals& globals = g_globals.Get();
+
+ using base::subtle::AtomicWord;
+ AtomicWord* storage =
+ reinterpret_cast<AtomicWord*>(&globals.thread_delegates[identifier_]);
+ AtomicWord stored_pointer = base::subtle::NoBarrier_Load(storage);
+ BrowserThreadDelegate* delegate =
+ reinterpret_cast<BrowserThreadDelegate*>(stored_pointer);
+
+ if (delegate)
+ delegate->CleanUp();
+}
+
+void BrowserThreadImpl::Initialize() {
+ BrowserThreadGlobals& globals = g_globals.Get();
+
+ base::AutoLock lock(globals.lock);
+ DCHECK(identifier_ >= 0 && identifier_ < ID_COUNT);
+ DCHECK(globals.threads[identifier_] == NULL);
+ globals.threads[identifier_] = this;
+}
+
+BrowserThreadImpl::~BrowserThreadImpl() {
+ // All Thread subclasses must call Stop() in the destructor. This is
+ // doubly important here as various bits of code check they are on
+ // the right BrowserThread.
+ Stop();
+
+ BrowserThreadGlobals& globals = g_globals.Get();
+ base::AutoLock lock(globals.lock);
+ globals.threads[identifier_] = NULL;
+#ifndef NDEBUG
+ // Double check that the threads are ordered correctly in the enumeration.
+ for (int i = identifier_ + 1; i < ID_COUNT; ++i) {
+ DCHECK(!globals.threads[i]) <<
+ "Threads must be listed in the reverse order that they die";
+ }
+#endif
+}
+
+// static
+bool BrowserThreadImpl::PostTaskHelper(
+ BrowserThread::ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay,
+ bool nestable) {
+ DCHECK(identifier >= 0 && identifier < ID_COUNT);
+ // Optimization: to avoid unnecessary locks, we listed the ID enumeration in
+ // order of lifetime. So no need to lock if we know that the target thread
+ // outlives current thread.
+ // Note: since the array is so small, ok to loop instead of creating a map,
+ // which would require a lock because std::map isn't thread safe, defeating
+ // the whole purpose of this optimization.
+ BrowserThread::ID current_thread;
+ bool target_thread_outlives_current =
+ GetCurrentThreadIdentifier(&current_thread) &&
+ current_thread >= identifier;
+
+ BrowserThreadGlobals& globals = g_globals.Get();
+ if (!target_thread_outlives_current)
+ globals.lock.Acquire();
+
+ base::MessageLoop* message_loop =
+ globals.threads[identifier] ? globals.threads[identifier]->message_loop()
+ : NULL;
+ if (message_loop) {
+ if (nestable) {
+ message_loop->PostDelayedTask(from_here, task, delay);
+ } else {
+ message_loop->PostNonNestableDelayedTask(from_here, task, delay);
+ }
+ }
+
+ if (!target_thread_outlives_current)
+ globals.lock.Release();
+
+ return !!message_loop;
+}
+
+// An implementation of MessageLoopProxy to be used in conjunction
+// with BrowserThread.
+class BrowserThreadMessageLoopProxy : public base::MessageLoopProxy {
+ public:
+ explicit BrowserThreadMessageLoopProxy(BrowserThread::ID identifier)
+ : id_(identifier) {
+ }
+
+ // MessageLoopProxy implementation.
+ virtual bool PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task, base::TimeDelta delay) OVERRIDE {
+ return BrowserThread::PostDelayedTask(id_, from_here, task, delay);
+ }
+
+ virtual bool PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE {
+ return BrowserThread::PostNonNestableDelayedTask(id_, from_here, task,
+ delay);
+ }
+
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE {
+ return BrowserThread::CurrentlyOn(id_);
+ }
+
+ protected:
+ virtual ~BrowserThreadMessageLoopProxy() {}
+
+ private:
+ BrowserThread::ID id_;
+ DISALLOW_COPY_AND_ASSIGN(BrowserThreadMessageLoopProxy);
+};
+
+// static
+bool BrowserThread::PostBlockingPoolTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ return g_globals.Get().blocking_pool->PostWorkerTask(from_here, task);
+}
+
+bool BrowserThread::PostBlockingPoolTaskAndReply(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const base::Closure& reply) {
+ return g_globals.Get().blocking_pool->PostTaskAndReply(
+ from_here, task, reply);
+}
+
+// static
+bool BrowserThread::PostBlockingPoolSequencedTask(
+ const std::string& sequence_token_name,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ return g_globals.Get().blocking_pool->PostNamedSequencedWorkerTask(
+ sequence_token_name, from_here, task);
+}
+
+// static
+base::SequencedWorkerPool* BrowserThread::GetBlockingPool() {
+ return g_globals.Get().blocking_pool.get();
+}
+
+// static
+bool BrowserThread::IsThreadInitialized(ID identifier) {
+ if (g_globals == NULL)
+ return false;
+
+ BrowserThreadGlobals& globals = g_globals.Get();
+ base::AutoLock lock(globals.lock);
+ DCHECK(identifier >= 0 && identifier < ID_COUNT);
+ return globals.threads[identifier] != NULL;
+}
+
+// static
+bool BrowserThread::CurrentlyOn(ID identifier) {
+ // We shouldn't use MessageLoop::current() since it uses LazyInstance which
+ // may be deleted by ~AtExitManager when a WorkerPool thread calls this
+ // function.
+ // http://crbug.com/63678
+ base::ThreadRestrictions::ScopedAllowSingleton allow_singleton;
+ BrowserThreadGlobals& globals = g_globals.Get();
+ base::AutoLock lock(globals.lock);
+ DCHECK(identifier >= 0 && identifier < ID_COUNT);
+ return globals.threads[identifier] &&
+ globals.threads[identifier]->message_loop() ==
+ base::MessageLoop::current();
+}
+
+// static
+bool BrowserThread::IsMessageLoopValid(ID identifier) {
+ if (g_globals == NULL)
+ return false;
+
+ BrowserThreadGlobals& globals = g_globals.Get();
+ base::AutoLock lock(globals.lock);
+ DCHECK(identifier >= 0 && identifier < ID_COUNT);
+ return globals.threads[identifier] &&
+ globals.threads[identifier]->message_loop();
+}
+
+// static
+bool BrowserThread::PostTask(ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ return BrowserThreadImpl::PostTaskHelper(
+ identifier, from_here, task, base::TimeDelta(), true);
+}
+
+// static
+bool BrowserThread::PostDelayedTask(ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ return BrowserThreadImpl::PostTaskHelper(
+ identifier, from_here, task, delay, true);
+}
+
+// static
+bool BrowserThread::PostNonNestableTask(
+ ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ return BrowserThreadImpl::PostTaskHelper(
+ identifier, from_here, task, base::TimeDelta(), false);
+}
+
+// static
+bool BrowserThread::PostNonNestableDelayedTask(
+ ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ return BrowserThreadImpl::PostTaskHelper(
+ identifier, from_here, task, delay, false);
+}
+
+// static
+bool BrowserThread::PostTaskAndReply(
+ ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const base::Closure& reply) {
+ return GetMessageLoopProxyForThread(identifier)->PostTaskAndReply(from_here,
+ task,
+ reply);
+}
+
+// static
+bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) {
+ if (g_globals == NULL)
+ return false;
+
+ // We shouldn't use MessageLoop::current() since it uses LazyInstance which
+ // may be deleted by ~AtExitManager when a WorkerPool thread calls this
+ // function.
+ // http://crbug.com/63678
+ base::ThreadRestrictions::ScopedAllowSingleton allow_singleton;
+ base::MessageLoop* cur_message_loop = base::MessageLoop::current();
+ BrowserThreadGlobals& globals = g_globals.Get();
+ for (int i = 0; i < ID_COUNT; ++i) {
+ if (globals.threads[i] &&
+ globals.threads[i]->message_loop() == cur_message_loop) {
+ *identifier = globals.threads[i]->identifier_;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// static
+scoped_refptr<base::MessageLoopProxy>
+BrowserThread::GetMessageLoopProxyForThread(ID identifier) {
+ return make_scoped_refptr(new BrowserThreadMessageLoopProxy(identifier));
+}
+
+// static
+base::MessageLoop* BrowserThread::UnsafeGetMessageLoopForThread(ID identifier) {
+ if (g_globals == NULL)
+ return NULL;
+
+ BrowserThreadGlobals& globals = g_globals.Get();
+ base::AutoLock lock(globals.lock);
+ base::Thread* thread = globals.threads[identifier];
+ DCHECK(thread);
+ base::MessageLoop* loop = thread->message_loop();
+ return loop;
+}
+
+// static
+void BrowserThread::SetDelegate(ID identifier,
+ BrowserThreadDelegate* delegate) {
+ using base::subtle::AtomicWord;
+ BrowserThreadGlobals& globals = g_globals.Get();
+ AtomicWord* storage = reinterpret_cast<AtomicWord*>(
+ &globals.thread_delegates[identifier]);
+ AtomicWord old_pointer = base::subtle::NoBarrier_AtomicExchange(
+ storage, reinterpret_cast<AtomicWord>(delegate));
+
+ // This catches registration when previously registered.
+ DCHECK(!delegate || !old_pointer);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_thread_impl.h b/chromium/content/browser/browser_thread_impl.h
new file mode 100644
index 00000000000..167cc3b85a1
--- /dev/null
+++ b/chromium/content/browser/browser_thread_impl.h
@@ -0,0 +1,73 @@
+// 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 CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_
+#define CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_
+
+#include "base/threading/thread.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+class CONTENT_EXPORT BrowserThreadImpl : public BrowserThread,
+ public base::Thread {
+ public:
+ // Construct a BrowserThreadImpl with the supplied identifier. It is an error
+ // to construct a BrowserThreadImpl that already exists.
+ explicit BrowserThreadImpl(BrowserThread::ID identifier);
+
+ // Special constructor for the main (UI) thread and unittests. If a
+ // |message_loop| is provied, we use a dummy thread here since the main
+ // thread already exists.
+ BrowserThreadImpl(BrowserThread::ID identifier,
+ base::MessageLoop* message_loop);
+ virtual ~BrowserThreadImpl();
+
+ static void ShutdownThreadPool();
+
+ protected:
+ virtual void Init() OVERRIDE;
+ virtual void Run(base::MessageLoop* message_loop) OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ private:
+ // We implement all the functionality of the public BrowserThread
+ // functions, but state is stored in the BrowserThreadImpl to keep
+ // the API cleaner. Therefore make BrowserThread a friend class.
+ friend class BrowserThread;
+
+ // The following are unique function names that makes it possible to tell
+ // the thread id from the callstack alone in crash dumps.
+ void UIThreadRun(base::MessageLoop* message_loop);
+ void DBThreadRun(base::MessageLoop* message_loop);
+ void FileThreadRun(base::MessageLoop* message_loop);
+ void FileUserBlockingThreadRun(base::MessageLoop* message_loop);
+ void ProcessLauncherThreadRun(base::MessageLoop* message_loop);
+ void CacheThreadRun(base::MessageLoop* message_loop);
+ void IOThreadRun(base::MessageLoop* message_loop);
+
+ static bool PostTaskHelper(
+ BrowserThread::ID identifier,
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay,
+ bool nestable);
+
+ // Common initialization code for the constructors.
+ void Initialize();
+
+ // For testing.
+ friend class ContentTestSuiteBaseListener;
+ friend class TestBrowserThreadBundle;
+ static void FlushThreadPoolHelper();
+
+ // The identifier of this thread. Only one thread can exist with a given
+ // identifier at a given time.
+ ID identifier_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_
diff --git a/chromium/content/browser/browser_thread_unittest.cc b/chromium/content/browser/browser_thread_unittest.cc
new file mode 100644
index 00000000000..4083b782caf
--- /dev/null
+++ b/chromium/content/browser/browser_thread_unittest.cc
@@ -0,0 +1,118 @@
+// 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 "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace content {
+
+class BrowserThreadTest : public testing::Test {
+ public:
+ void Release() const {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ protected:
+ virtual void SetUp() {
+ ui_thread_.reset(new BrowserThreadImpl(BrowserThread::UI));
+ file_thread_.reset(new BrowserThreadImpl(BrowserThread::FILE));
+ ui_thread_->Start();
+ file_thread_->Start();
+ }
+
+ virtual void TearDown() {
+ ui_thread_->Stop();
+ file_thread_->Stop();
+ }
+
+ static void BasicFunction(base::MessageLoop* message_loop) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ class DeletedOnFile
+ : public base::RefCountedThreadSafe<
+ DeletedOnFile, BrowserThread::DeleteOnFileThread> {
+ public:
+ explicit DeletedOnFile(base::MessageLoop* message_loop)
+ : message_loop_(message_loop) {}
+
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
+ friend class base::DeleteHelper<DeletedOnFile>;
+
+ ~DeletedOnFile() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ base::MessageLoop* message_loop_;
+ };
+
+ private:
+ scoped_ptr<BrowserThreadImpl> ui_thread_;
+ scoped_ptr<BrowserThreadImpl> file_thread_;
+ // It's kind of ugly to make this mutable - solely so we can post the Quit
+ // Task from Release(). This should be fixed.
+ mutable base::MessageLoop loop_;
+};
+
+TEST_F(BrowserThreadTest, PostTask) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&BasicFunction, base::MessageLoop::current()));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(BrowserThreadTest, Release) {
+ BrowserThread::ReleaseSoon(BrowserThread::UI, FROM_HERE, this);
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(BrowserThreadTest, ReleasedOnCorrectThread) {
+ {
+ scoped_refptr<DeletedOnFile> test(
+ new DeletedOnFile(base::MessageLoop::current()));
+ }
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(BrowserThreadTest, PostTaskViaMessageLoopProxy) {
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy =
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
+ message_loop_proxy->PostTask(
+ FROM_HERE, base::Bind(&BasicFunction, base::MessageLoop::current()));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(BrowserThreadTest, ReleaseViaMessageLoopProxy) {
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy =
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
+ message_loop_proxy->ReleaseSoon(FROM_HERE, this);
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(BrowserThreadTest, PostTaskAndReply) {
+ // Most of the heavy testing for PostTaskAndReply() is done inside the
+ // MessageLoopProxy test. This just makes sure we get piped through at all.
+ ASSERT_TRUE(BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&base::DoNothing),
+ base::Bind(&base::MessageLoop::Quit,
+ base::Unretained(base::MessageLoop::current()->current()))));
+ base::MessageLoop::current()->Run();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_url_handler_impl.cc b/chromium/content/browser/browser_url_handler_impl.cc
new file mode 100644
index 00000000000..56efe6b71b7
--- /dev/null
+++ b/chromium/content/browser/browser_url_handler_impl.cc
@@ -0,0 +1,135 @@
+// 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 "content/browser/browser_url_handler_impl.h"
+
+#include "base/strings/string_util.h"
+#include "content/browser/webui/web_ui_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/url_constants.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Handles rewriting view-source URLs for what we'll actually load.
+static bool HandleViewSource(GURL* url,
+ BrowserContext* browser_context) {
+ if (url->SchemeIs(kViewSourceScheme)) {
+ // Load the inner URL instead.
+ *url = GURL(url->path());
+
+ // Bug 26129: limit view-source to view the content and not any
+ // other kind of 'active' url scheme like 'javascript' or 'data'.
+ static const char* const allowed_sub_schemes[] = {
+ chrome::kHttpScheme, chrome::kHttpsScheme, chrome::kFtpScheme,
+ chrome::kChromeDevToolsScheme, chrome::kChromeUIScheme,
+ chrome::kFileScheme, chrome::kFileSystemScheme
+ };
+
+ bool is_sub_scheme_allowed = false;
+ for (size_t i = 0; i < arraysize(allowed_sub_schemes); i++) {
+ if (url->SchemeIs(allowed_sub_schemes[i])) {
+ is_sub_scheme_allowed = true;
+ break;
+ }
+ }
+
+ if (!is_sub_scheme_allowed) {
+ *url = GURL(kAboutBlankURL);
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+// Turns a non view-source URL into the corresponding view-source URL.
+static bool ReverseViewSource(GURL* url, BrowserContext* browser_context) {
+ // No action necessary if the URL is already view-source:
+ if (url->SchemeIs(kViewSourceScheme))
+ return false;
+
+ url_canon::Replacements<char> repl;
+ repl.SetScheme(kViewSourceScheme,
+ url_parse::Component(0, strlen(kViewSourceScheme)));
+ repl.SetPath(url->spec().c_str(),
+ url_parse::Component(0, url->spec().size()));
+ *url = url->ReplaceComponents(repl);
+ return true;
+}
+
+static bool HandleDebugUrl(GURL* url, BrowserContext* browser_context) {
+ // Circumvent processing URLs that the renderer process will handle.
+ return *url == GURL(kChromeUICrashURL) ||
+ *url == GURL(kChromeUIHangURL) ||
+ *url == GURL(kChromeUIKillURL) ||
+ *url == GURL(kChromeUIShorthangURL);
+}
+
+// static
+BrowserURLHandler* BrowserURLHandler::GetInstance() {
+ return BrowserURLHandlerImpl::GetInstance();
+}
+
+// static
+BrowserURLHandler::URLHandler BrowserURLHandler::null_handler() {
+ // Required for VS2010: http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
+ return NULL;
+}
+
+// static
+BrowserURLHandlerImpl* BrowserURLHandlerImpl::GetInstance() {
+ return Singleton<BrowserURLHandlerImpl>::get();
+}
+
+BrowserURLHandlerImpl::BrowserURLHandlerImpl() {
+ AddHandlerPair(&HandleDebugUrl, BrowserURLHandlerImpl::null_handler());
+
+ GetContentClient()->browser()->BrowserURLHandlerCreated(this);
+
+ // view-source:
+ AddHandlerPair(&HandleViewSource, &ReverseViewSource);
+}
+
+BrowserURLHandlerImpl::~BrowserURLHandlerImpl() {
+}
+
+void BrowserURLHandlerImpl::AddHandlerPair(URLHandler handler,
+ URLHandler reverse_handler) {
+ url_handlers_.push_back(HandlerPair(handler, reverse_handler));
+}
+
+void BrowserURLHandlerImpl::RewriteURLIfNecessary(
+ GURL* url,
+ BrowserContext* browser_context,
+ bool* reverse_on_redirect) {
+ for (size_t i = 0; i < url_handlers_.size(); ++i) {
+ URLHandler handler = *url_handlers_[i].first;
+ if (handler && handler(url, browser_context)) {
+ *reverse_on_redirect = (url_handlers_[i].second != NULL);
+ return;
+ }
+ }
+}
+
+bool BrowserURLHandlerImpl::ReverseURLRewrite(
+ GURL* url, const GURL& original, BrowserContext* browser_context) {
+ for (size_t i = 0; i < url_handlers_.size(); ++i) {
+ URLHandler reverse_rewriter = *url_handlers_[i].second;
+ if (reverse_rewriter) {
+ GURL test_url(original);
+ URLHandler handler = *url_handlers_[i].first;
+ if (!handler) {
+ if (reverse_rewriter(url, browser_context))
+ return true;
+ } else if (handler(&test_url, browser_context)) {
+ return reverse_rewriter(url, browser_context);
+ }
+ }
+ }
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browser_url_handler_impl.h b/chromium/content/browser/browser_url_handler_impl.h
new file mode 100644
index 00000000000..d0a150452ba
--- /dev/null
+++ b/chromium/content/browser/browser_url_handler_impl.h
@@ -0,0 +1,55 @@
+// 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 CONTENT_BROWSER_BROWSER_URL_HANDLER_IMPL_H_
+#define CONTENT_BROWSER_BROWSER_URL_HANDLER_IMPL_H_
+
+#include <vector>
+#include <utility>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/singleton.h"
+#include "content/public/browser/browser_url_handler.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+
+class CONTENT_EXPORT BrowserURLHandlerImpl : public BrowserURLHandler {
+ public:
+ // Returns the singleton instance.
+ static BrowserURLHandlerImpl* GetInstance();
+
+ // BrowserURLHandler implementation:
+ virtual void RewriteURLIfNecessary(GURL* url,
+ BrowserContext* browser_context,
+ bool* reverse_on_redirect) OVERRIDE;
+ // Add the specified handler pair to the list of URL handlers.
+ virtual void AddHandlerPair(URLHandler handler,
+ URLHandler reverse_handler) OVERRIDE;
+
+ // Reverses the rewriting that was done for |original| using the new |url|.
+ bool ReverseURLRewrite(GURL* url, const GURL& original,
+ BrowserContext* browser_context);
+
+ private:
+ // This object is a singleton:
+ BrowserURLHandlerImpl();
+ virtual ~BrowserURLHandlerImpl();
+ friend struct DefaultSingletonTraits<BrowserURLHandlerImpl>;
+
+ // The list of known URLHandlers, optionally with reverse-rewriters.
+ typedef std::pair<URLHandler, URLHandler> HandlerPair;
+ std::vector<HandlerPair> url_handlers_;
+
+ FRIEND_TEST_ALL_PREFIXES(BrowserURLHandlerImplTest, BasicRewriteAndReverse);
+ FRIEND_TEST_ALL_PREFIXES(BrowserURLHandlerImplTest, NullHandlerReverse);
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserURLHandlerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSER_URL_HANDLER_IMPL_H_
diff --git a/chromium/content/browser/browser_url_handler_impl_unittest.cc b/chromium/content/browser/browser_url_handler_impl_unittest.cc
new file mode 100644
index 00000000000..4e8dc2115fd
--- /dev/null
+++ b/chromium/content/browser/browser_url_handler_impl_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/browser_url_handler_impl.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class BrowserURLHandlerImplTest : public testing::Test {
+};
+
+// Test URL rewriter that rewrites all "foo://" URLs to "bar://bar".
+static bool FooRewriter(GURL* url, BrowserContext* browser_context) {
+ if (url->scheme() == "foo") {
+ *url = GURL("bar://bar");
+ return true;
+ }
+ return false;
+}
+
+// Test URL rewriter that rewrites all "bar://" URLs to "foo://foo".
+static bool BarRewriter(GURL* url, BrowserContext* browser_context) {
+ if (url->scheme() == "bar") {
+ *url = GURL("foo://foo");
+ return true;
+ }
+ return false;
+}
+
+TEST_F(BrowserURLHandlerImplTest, BasicRewriteAndReverse) {
+ TestBrowserContext browser_context;
+ BrowserURLHandlerImpl handler;
+
+ handler.AddHandlerPair(FooRewriter, BarRewriter);
+
+ GURL url("foo://bar");
+ GURL original_url(url);
+ bool reverse_on_redirect = false;
+ handler.RewriteURLIfNecessary(&url, &browser_context, &reverse_on_redirect);
+ ASSERT_TRUE(reverse_on_redirect);
+ ASSERT_EQ("bar://bar", url.spec());
+
+ // Check that reversing the URL works.
+ GURL saved_url(url);
+ bool reversed = handler.ReverseURLRewrite(&url,
+ original_url,
+ &browser_context);
+ ASSERT_TRUE(reversed);
+ ASSERT_EQ("foo://foo", url.spec());
+
+ // Check that reversing the URL only works with a matching |original_url|.
+ url = saved_url;
+ original_url = GURL("bam://bam"); // Won't be matched by FooRewriter.
+ reversed = handler.ReverseURLRewrite(&url, original_url, &browser_context);
+ ASSERT_FALSE(reversed);
+ ASSERT_EQ(saved_url, url);
+}
+
+TEST_F(BrowserURLHandlerImplTest, NullHandlerReverse) {
+ TestBrowserContext browser_context;
+ BrowserURLHandlerImpl handler;
+
+ GURL url("bar://foo");
+ GURL original_url(url);
+
+ handler.AddHandlerPair(BrowserURLHandlerImpl::null_handler(), FooRewriter);
+ bool reversed = handler.ReverseURLRewrite(&url,
+ original_url,
+ &browser_context);
+ ASSERT_FALSE(reversed);
+ ASSERT_EQ(original_url, url);
+
+ handler.AddHandlerPair(BrowserURLHandlerImpl::null_handler(), BarRewriter);
+ reversed = handler.ReverseURLRewrite(&url, original_url, &browser_context);
+ ASSERT_TRUE(reversed);
+ ASSERT_EQ("foo://foo", url.spec());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browsing_instance.cc b/chromium/content/browser/browsing_instance.cc
new file mode 100644
index 00000000000..c70f38b9daa
--- /dev/null
+++ b/chromium/content/browser/browsing_instance.cc
@@ -0,0 +1,89 @@
+// 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 "content/browser/browsing_instance.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+
+namespace content {
+
+BrowsingInstance::BrowsingInstance(BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+}
+
+bool BrowsingInstance::HasSiteInstance(const GURL& url) {
+ std::string site =
+ SiteInstanceImpl::GetSiteForURL(browser_context_, url)
+ .possibly_invalid_spec();
+
+ return site_instance_map_.find(site) != site_instance_map_.end();
+}
+
+SiteInstance* BrowsingInstance::GetSiteInstanceForURL(const GURL& url) {
+ std::string site =
+ SiteInstanceImpl::GetSiteForURL(browser_context_, url)
+ .possibly_invalid_spec();
+
+ SiteInstanceMap::iterator i = site_instance_map_.find(site);
+ if (i != site_instance_map_.end())
+ return i->second;
+
+
+ // No current SiteInstance for this site, so let's create one.
+ SiteInstanceImpl* instance = new SiteInstanceImpl(this);
+
+ // Set the site of this new SiteInstance, which will register it with us.
+ instance->SetSite(url);
+ return instance;
+}
+
+void BrowsingInstance::RegisterSiteInstance(SiteInstance* site_instance) {
+ DCHECK(static_cast<SiteInstanceImpl*>(site_instance)
+ ->browsing_instance_.get() ==
+ this);
+ DCHECK(static_cast<SiteInstanceImpl*>(site_instance)->HasSite());
+ std::string site = site_instance->GetSiteURL().possibly_invalid_spec();
+
+ // Only register if we don't have a SiteInstance for this site already.
+ // It's possible to have two SiteInstances point to the same site if two
+ // tabs are navigated there at the same time. (We don't call SetSite or
+ // register them until DidNavigate.) If there is a previously existing
+ // SiteInstance for this site, we just won't register the new one.
+ SiteInstanceMap::iterator i = site_instance_map_.find(site);
+ if (i == site_instance_map_.end()) {
+ // Not previously registered, so register it.
+ site_instance_map_[site] = site_instance;
+ }
+}
+
+void BrowsingInstance::UnregisterSiteInstance(SiteInstance* site_instance) {
+ DCHECK(static_cast<SiteInstanceImpl*>(site_instance)
+ ->browsing_instance_.get() ==
+ this);
+ DCHECK(static_cast<SiteInstanceImpl*>(site_instance)->HasSite());
+ std::string site = site_instance->GetSiteURL().possibly_invalid_spec();
+
+ // Only unregister the SiteInstance if it is the same one that is registered
+ // for the site. (It might have been an unregistered SiteInstance. See the
+ // comments in RegisterSiteInstance.)
+ SiteInstanceMap::iterator i = site_instance_map_.find(site);
+ if (i != site_instance_map_.end() && i->second == site_instance) {
+ // Matches, so erase it.
+ site_instance_map_.erase(i);
+ }
+}
+
+BrowsingInstance::~BrowsingInstance() {
+ // We should only be deleted when all of the SiteInstances that refer to
+ // us are gone.
+ DCHECK(site_instance_map_.empty());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/browsing_instance.h b/chromium/content/browser/browsing_instance.h
new file mode 100644
index 00000000000..60aa47b19dc
--- /dev/null
+++ b/chromium/content/browser/browsing_instance.h
@@ -0,0 +1,110 @@
+// 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 CONTENT_BROWSER_BROWSING_INSTANCE_H_
+#define CONTENT_BROWSER_BROWSING_INSTANCE_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_context.h"
+
+class GURL;
+
+namespace content {
+class SiteInstance;
+class SiteInstanceImpl;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// BrowsingInstance class
+//
+// A browsing instance corresponds to the notion of a "unit of related browsing
+// contexts" in the HTML 5 spec. Intuitively, it represents a collection of
+// tabs and frames that can have script connections to each other. In that
+// sense, it reflects the user interface, and not the contents of the tabs and
+// frames.
+//
+// We further subdivide a BrowsingInstance into SiteInstances, which represent
+// the documents within each BrowsingInstance that are from the same site and
+// thus can have script access to each other. Different SiteInstances can
+// safely run in different processes, because their documents cannot access
+// each other's contents (due to the same origin policy).
+//
+// It is important to only have one SiteInstance per site within a given
+// BrowsingInstance. This is because any two documents from the same site
+// might be able to script each other if they are in the same BrowsingInstance.
+// Thus, they must be rendered in the same process.
+//
+// A BrowsingInstance is live as long as any SiteInstance has a reference to
+// it. A SiteInstance is live as long as any NavigationEntry or RenderViewHost
+// have references to it. Because both classes are RefCounted, they do not
+// need to be manually deleted.
+//
+// BrowsingInstance has no public members, as it is designed to be
+// visible only from the SiteInstance class. To get a new
+// SiteInstance that is part of the same BrowsingInstance, use
+// SiteInstance::GetRelatedSiteInstance. Because of this,
+// BrowsingInstances and SiteInstances are tested together in
+// site_instance_unittest.cc.
+//
+///////////////////////////////////////////////////////////////////////////////
+class CONTENT_EXPORT BrowsingInstance
+ : public base::RefCounted<BrowsingInstance> {
+ protected:
+ // Create a new BrowsingInstance.
+ explicit BrowsingInstance(BrowserContext* context);
+
+ // Get the browser context to which this BrowsingInstance belongs.
+ BrowserContext* browser_context() const { return browser_context_; }
+
+ // Returns whether this BrowsingInstance has registered a SiteInstance for
+ // the site of the given URL.
+ bool HasSiteInstance(const GURL& url);
+
+ // Get the SiteInstance responsible for rendering the given URL. Should
+ // create a new one if necessary, but should not create more than one
+ // SiteInstance per site.
+ SiteInstance* GetSiteInstanceForURL(const GURL& url);
+
+ // Adds the given SiteInstance to our map, to ensure that we do not create
+ // another SiteInstance for the same site.
+ void RegisterSiteInstance(SiteInstance* site_instance);
+
+ // Removes the given SiteInstance from our map, after all references to it
+ // have been deleted. This means it is safe to create a new SiteInstance
+ // if the user later visits a page from this site, within this
+ // BrowsingInstance.
+ void UnregisterSiteInstance(SiteInstance* site_instance);
+
+ friend class SiteInstanceImpl;
+ friend class SiteInstance;
+
+ friend class base::RefCounted<BrowsingInstance>;
+
+ // Virtual to allow tests to extend it.
+ virtual ~BrowsingInstance();
+
+ private:
+ // Map of site to SiteInstance, to ensure we only have one SiteInstance per
+ typedef base::hash_map<std::string, SiteInstance*> SiteInstanceMap;
+
+ // Common browser context to which all SiteInstances in this BrowsingInstance
+ // must belong.
+ BrowserContext* const browser_context_;
+
+ // Map of site to SiteInstance, to ensure we only have one SiteInstance per
+ // site. The site string should be the possibly_invalid_spec() of a GURL
+ // obtained with SiteInstanceImpl::GetSiteForURL. Note that this map may not
+ // contain every active SiteInstance, because a race exists where two
+ // SiteInstances can be assigned to the same site. This is ok in rare cases.
+ SiteInstanceMap site_instance_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowsingInstance);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BROWSING_INSTANCE_H_
diff --git a/chromium/content/browser/byte_stream.cc b/chromium/content/browser/byte_stream.cc
new file mode 100644
index 00000000000..a8b8f29e9e6
--- /dev/null
+++ b/chromium/content/browser/byte_stream.cc
@@ -0,0 +1,441 @@
+// 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 "content/browser/byte_stream.h"
+
+#include <deque>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+
+namespace content {
+namespace {
+
+typedef std::deque<std::pair<scoped_refptr<net::IOBuffer>, size_t> >
+ContentVector;
+
+class ByteStreamReaderImpl;
+
+// A poor man's weak pointer; a RefCountedThreadSafe boolean that can be
+// cleared in an object destructor and accessed to check for object
+// existence. We can't use weak pointers because they're tightly tied to
+// threads rather than task runners.
+// TODO(rdsmith): A better solution would be extending weak pointers
+// to support SequencedTaskRunners.
+struct LifetimeFlag : public base::RefCountedThreadSafe<LifetimeFlag> {
+ public:
+ LifetimeFlag() : is_alive(true) { }
+ bool is_alive;
+
+ protected:
+ friend class base::RefCountedThreadSafe<LifetimeFlag>;
+ virtual ~LifetimeFlag() { }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LifetimeFlag);
+};
+
+// For both ByteStreamWriterImpl and ByteStreamReaderImpl, Construction and
+// SetPeer may happen anywhere; all other operations on each class must
+// happen in the context of their SequencedTaskRunner.
+class ByteStreamWriterImpl : public ByteStreamWriter {
+ public:
+ ByteStreamWriterImpl(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<LifetimeFlag> lifetime_flag,
+ size_t buffer_size);
+ virtual ~ByteStreamWriterImpl();
+
+ // Must be called before any operations are performed.
+ void SetPeer(ByteStreamReaderImpl* peer,
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner,
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag);
+
+ // Overridden from ByteStreamWriter.
+ virtual bool Write(scoped_refptr<net::IOBuffer> buffer,
+ size_t byte_count) OVERRIDE;
+ virtual void Flush() OVERRIDE;
+ virtual void Close(int status) OVERRIDE;
+ virtual void RegisterCallback(const base::Closure& source_callback) OVERRIDE;
+
+ // PostTask target from |ByteStreamReaderImpl::MaybeUpdateInput|.
+ static void UpdateWindow(scoped_refptr<LifetimeFlag> lifetime_flag,
+ ByteStreamWriterImpl* target,
+ size_t bytes_consumed);
+
+ private:
+ // Called from UpdateWindow when object existence has been validated.
+ void UpdateWindowInternal(size_t bytes_consumed);
+
+ void PostToPeer(bool complete, int status);
+
+ const size_t total_buffer_size_;
+
+ // All data objects in this class are only valid to access on
+ // this task runner except as otherwise noted.
+ scoped_refptr<base::SequencedTaskRunner> my_task_runner_;
+
+ // True while this object is alive.
+ scoped_refptr<LifetimeFlag> my_lifetime_flag_;
+
+ base::Closure space_available_callback_;
+ ContentVector input_contents_;
+ size_t input_contents_size_;
+
+ // ** Peer information.
+
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner_;
+
+ // How much we've sent to the output that for flow control purposes we
+ // must assume hasn't been read yet.
+ size_t output_size_used_;
+
+ // Only valid to access on peer_task_runner_.
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag_;
+
+ // Only valid to access on peer_task_runner_ if
+ // |*peer_lifetime_flag_ == true|
+ ByteStreamReaderImpl* peer_;
+};
+
+class ByteStreamReaderImpl : public ByteStreamReader {
+ public:
+ ByteStreamReaderImpl(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<LifetimeFlag> lifetime_flag,
+ size_t buffer_size);
+ virtual ~ByteStreamReaderImpl();
+
+ // Must be called before any operations are performed.
+ void SetPeer(ByteStreamWriterImpl* peer,
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner,
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag);
+
+ // Overridden from ByteStreamReader.
+ virtual StreamState Read(scoped_refptr<net::IOBuffer>* data,
+ size_t* length) OVERRIDE;
+ virtual int GetStatus() const OVERRIDE;
+ virtual void RegisterCallback(const base::Closure& sink_callback) OVERRIDE;
+
+ // PostTask target from |ByteStreamWriterImpl::Write| and
+ // |ByteStreamWriterImpl::Close|.
+ // Receive data from our peer.
+ // static because it may be called after the object it is targeting
+ // has been destroyed. It may not access |*target|
+ // if |*object_lifetime_flag| is false.
+ static void TransferData(
+ scoped_refptr<LifetimeFlag> object_lifetime_flag,
+ ByteStreamReaderImpl* target,
+ scoped_ptr<ContentVector> transfer_buffer,
+ size_t transfer_buffer_bytes,
+ bool source_complete,
+ int status);
+
+ private:
+ // Called from TransferData once object existence has been validated.
+ void TransferDataInternal(
+ scoped_ptr<ContentVector> transfer_buffer,
+ size_t transfer_buffer_bytes,
+ bool source_complete,
+ int status);
+
+ void MaybeUpdateInput();
+
+ const size_t total_buffer_size_;
+
+ scoped_refptr<base::SequencedTaskRunner> my_task_runner_;
+
+ // True while this object is alive.
+ scoped_refptr<LifetimeFlag> my_lifetime_flag_;
+
+ ContentVector available_contents_;
+
+ bool received_status_;
+ int status_;
+
+ base::Closure data_available_callback_;
+
+ // Time of last point at which data in stream transitioned from full
+ // to non-full. Nulled when a callback is sent.
+ base::Time last_non_full_time_;
+
+ // ** Peer information
+
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner_;
+
+ // How much has been removed from this class that we haven't told
+ // the input about yet.
+ size_t unreported_consumed_bytes_;
+
+ // Only valid to access on peer_task_runner_.
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag_;
+
+ // Only valid to access on peer_task_runner_ if
+ // |*peer_lifetime_flag_ == true|
+ ByteStreamWriterImpl* peer_;
+};
+
+ByteStreamWriterImpl::ByteStreamWriterImpl(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<LifetimeFlag> lifetime_flag,
+ size_t buffer_size)
+ : total_buffer_size_(buffer_size),
+ my_task_runner_(task_runner),
+ my_lifetime_flag_(lifetime_flag),
+ input_contents_size_(0),
+ output_size_used_(0),
+ peer_(NULL) {
+ DCHECK(my_lifetime_flag_.get());
+ my_lifetime_flag_->is_alive = true;
+}
+
+ByteStreamWriterImpl::~ByteStreamWriterImpl() {
+ my_lifetime_flag_->is_alive = false;
+}
+
+void ByteStreamWriterImpl::SetPeer(
+ ByteStreamReaderImpl* peer,
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner,
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag) {
+ peer_ = peer;
+ peer_task_runner_ = peer_task_runner;
+ peer_lifetime_flag_ = peer_lifetime_flag;
+}
+
+bool ByteStreamWriterImpl::Write(
+ scoped_refptr<net::IOBuffer> buffer, size_t byte_count) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+
+ input_contents_.push_back(std::make_pair(buffer, byte_count));
+ input_contents_size_ += byte_count;
+
+ // Arbitrarily, we buffer to a third of the total size before sending.
+ if (input_contents_size_ > total_buffer_size_ / kFractionBufferBeforeSending)
+ PostToPeer(false, 0);
+
+ return (input_contents_size_ + output_size_used_ <= total_buffer_size_);
+}
+
+void ByteStreamWriterImpl::Flush() {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ if (input_contents_size_ > 0)
+ PostToPeer(false, 0);
+}
+
+void ByteStreamWriterImpl::Close(int status) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ PostToPeer(true, status);
+}
+
+void ByteStreamWriterImpl::RegisterCallback(
+ const base::Closure& source_callback) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ space_available_callback_ = source_callback;
+}
+
+// static
+void ByteStreamWriterImpl::UpdateWindow(
+ scoped_refptr<LifetimeFlag> lifetime_flag, ByteStreamWriterImpl* target,
+ size_t bytes_consumed) {
+ // If the target object isn't alive anymore, we do nothing.
+ if (!lifetime_flag->is_alive) return;
+
+ target->UpdateWindowInternal(bytes_consumed);
+}
+
+void ByteStreamWriterImpl::UpdateWindowInternal(size_t bytes_consumed) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK_GE(output_size_used_, bytes_consumed);
+ output_size_used_ -= bytes_consumed;
+
+ // Callback if we were above the limit and we're now <= to it.
+ size_t total_known_size_used =
+ input_contents_size_ + output_size_used_;
+
+ if (total_known_size_used <= total_buffer_size_ &&
+ (total_known_size_used + bytes_consumed > total_buffer_size_) &&
+ !space_available_callback_.is_null())
+ space_available_callback_.Run();
+}
+
+void ByteStreamWriterImpl::PostToPeer(bool complete, int status) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ // Valid contexts in which to call.
+ DCHECK(complete || 0 != input_contents_size_);
+
+ scoped_ptr<ContentVector> transfer_buffer;
+ size_t buffer_size = 0;
+ if (0 != input_contents_size_) {
+ transfer_buffer.reset(new ContentVector);
+ transfer_buffer->swap(input_contents_);
+ buffer_size = input_contents_size_;
+ output_size_used_ += input_contents_size_;
+ input_contents_size_ = 0;
+ }
+ peer_task_runner_->PostTask(
+ FROM_HERE, base::Bind(
+ &ByteStreamReaderImpl::TransferData,
+ peer_lifetime_flag_,
+ peer_,
+ base::Passed(&transfer_buffer),
+ buffer_size,
+ complete,
+ status));
+}
+
+ByteStreamReaderImpl::ByteStreamReaderImpl(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<LifetimeFlag> lifetime_flag,
+ size_t buffer_size)
+ : total_buffer_size_(buffer_size),
+ my_task_runner_(task_runner),
+ my_lifetime_flag_(lifetime_flag),
+ received_status_(false),
+ status_(0),
+ unreported_consumed_bytes_(0),
+ peer_(NULL) {
+ DCHECK(my_lifetime_flag_.get());
+ my_lifetime_flag_->is_alive = true;
+}
+
+ByteStreamReaderImpl::~ByteStreamReaderImpl() {
+ my_lifetime_flag_->is_alive = false;
+}
+
+void ByteStreamReaderImpl::SetPeer(
+ ByteStreamWriterImpl* peer,
+ scoped_refptr<base::SequencedTaskRunner> peer_task_runner,
+ scoped_refptr<LifetimeFlag> peer_lifetime_flag) {
+ peer_ = peer;
+ peer_task_runner_ = peer_task_runner;
+ peer_lifetime_flag_ = peer_lifetime_flag;
+}
+
+ByteStreamReaderImpl::StreamState
+ByteStreamReaderImpl::Read(scoped_refptr<net::IOBuffer>* data,
+ size_t* length) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+
+ if (available_contents_.size()) {
+ *data = available_contents_.front().first;
+ *length = available_contents_.front().second;
+ available_contents_.pop_front();
+ unreported_consumed_bytes_ += *length;
+
+ MaybeUpdateInput();
+ return STREAM_HAS_DATA;
+ }
+ if (received_status_) {
+ return STREAM_COMPLETE;
+ }
+ return STREAM_EMPTY;
+}
+
+int ByteStreamReaderImpl::GetStatus() const {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(received_status_);
+ return status_;
+}
+
+void ByteStreamReaderImpl::RegisterCallback(
+ const base::Closure& sink_callback) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+
+ data_available_callback_ = sink_callback;
+}
+
+// static
+void ByteStreamReaderImpl::TransferData(
+ scoped_refptr<LifetimeFlag> object_lifetime_flag,
+ ByteStreamReaderImpl* target,
+ scoped_ptr<ContentVector> transfer_buffer,
+ size_t buffer_size,
+ bool source_complete,
+ int status) {
+ // If our target is no longer alive, do nothing.
+ if (!object_lifetime_flag->is_alive) return;
+
+ target->TransferDataInternal(
+ transfer_buffer.Pass(), buffer_size, source_complete, status);
+}
+
+void ByteStreamReaderImpl::TransferDataInternal(
+ scoped_ptr<ContentVector> transfer_buffer,
+ size_t buffer_size,
+ bool source_complete,
+ int status) {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+
+ bool was_empty = available_contents_.empty();
+
+ if (transfer_buffer) {
+ available_contents_.insert(available_contents_.end(),
+ transfer_buffer->begin(),
+ transfer_buffer->end());
+ }
+
+ if (source_complete) {
+ received_status_ = true;
+ status_ = status;
+ }
+
+ // Callback on transition from empty to non-empty, or
+ // source complete.
+ if (((was_empty && !available_contents_.empty()) ||
+ source_complete) &&
+ !data_available_callback_.is_null())
+ data_available_callback_.Run();
+}
+
+// Decide whether or not to send the input a window update.
+// Currently we do that whenever we've got unreported consumption
+// greater than 1/3 of total size.
+void ByteStreamReaderImpl::MaybeUpdateInput() {
+ DCHECK(my_task_runner_->RunsTasksOnCurrentThread());
+
+ if (unreported_consumed_bytes_ <=
+ total_buffer_size_ / kFractionReadBeforeWindowUpdate)
+ return;
+
+ peer_task_runner_->PostTask(
+ FROM_HERE, base::Bind(
+ &ByteStreamWriterImpl::UpdateWindow,
+ peer_lifetime_flag_,
+ peer_,
+ unreported_consumed_bytes_));
+ unreported_consumed_bytes_ = 0;
+}
+
+} // namespace
+
+const int ByteStreamWriter::kFractionBufferBeforeSending = 3;
+const int ByteStreamReader::kFractionReadBeforeWindowUpdate = 3;
+
+ByteStreamReader::~ByteStreamReader() { }
+
+ByteStreamWriter::~ByteStreamWriter() { }
+
+void CreateByteStream(
+ scoped_refptr<base::SequencedTaskRunner> input_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> output_task_runner,
+ size_t buffer_size,
+ scoped_ptr<ByteStreamWriter>* input,
+ scoped_ptr<ByteStreamReader>* output) {
+ scoped_refptr<LifetimeFlag> input_flag(new LifetimeFlag());
+ scoped_refptr<LifetimeFlag> output_flag(new LifetimeFlag());
+
+ ByteStreamWriterImpl* in = new ByteStreamWriterImpl(
+ input_task_runner, input_flag, buffer_size);
+ ByteStreamReaderImpl* out = new ByteStreamReaderImpl(
+ output_task_runner, output_flag, buffer_size);
+
+ in->SetPeer(out, output_task_runner, output_flag);
+ out->SetPeer(in, input_task_runner, input_flag);
+ input->reset(in);
+ output->reset(out);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/byte_stream.h b/chromium/content/browser/byte_stream.h
new file mode 100644
index 00000000000..0b5640d0711
--- /dev/null
+++ b/chromium/content/browser/byte_stream.h
@@ -0,0 +1,199 @@
+// 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 CONTENT_BROWSER_BYTE_STREAM_H_
+#define CONTENT_BROWSER_BYTE_STREAM_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "net/base/io_buffer.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+
+// A byte stream is a pipe to transfer bytes between a source and a
+// sink, which may be on different threads. It is intended to be the
+// only connection between source and sink; they need have no
+// direct awareness of each other aside from the byte stream. The source and
+// the sink have different interfaces to a byte stream, |ByteStreamWriter|
+// and |ByteStreamReader|. A pair of connected interfaces is generated by
+// calling |CreateByteStream|.
+//
+// The source adds bytes to the bytestream via |ByteStreamWriter::Write|
+// and the sink retrieves bytes already written via |ByteStreamReader::Read|.
+//
+// When the source has no more data to add, it will call
+// |ByteStreamWriter::Close| to indicate that. Operation status at the source
+// is indicated to the sink via an int passed to the Close() method and returned
+// from the GetStatus() method. Source and sink must agree on the interpretation
+// of this int.
+//
+// Normally the source is not managed after the relationship is setup;
+// it is expected to provide data and then close itself. If an error
+// occurs on the sink, it is not signalled to the source via this
+// mechanism; instead, the source will write data until it exausts the
+// available space. If the source needs to be aware of errors occuring
+// on the sink, this must be signalled in some other fashion (usually
+// through whatever controller setup the relationship).
+//
+// Callback lifetime management: No lifetime management is done in this
+// class to prevent registered callbacks from being called after any
+// objects to which they may refer have been destroyed. It is the
+// responsibility of the callers to avoid use-after-free references.
+// This may be done by any of several mechanisms, including weak
+// pointers, scoped_refptr references, or calling the registration
+// function with a null callback from a destructor. To enable the null
+// callback strategy, callbacks will not be stored between retrieval and
+// evaluation, so setting a null callback will guarantee that the
+// previous callback will not be executed after setting.
+//
+// Class methods are virtual to allow mocking for tests; these classes
+// aren't intended to be base classes for other classes.
+//
+// Sample usage (note that this does not show callback usage):
+//
+// void OriginatingClass::Initialize() {
+// // Create a stream for sending bytes from IO->FILE threads.
+// scoped_ptr<ByteStreamWriter> writer;
+// scoped_ptr<ByteStreamReader> reader;
+// CreateByteStream(
+// BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO),
+// BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
+// kStreamBufferSize /* e.g. 10240. */,
+// &writer,
+// &reader); // Presumed passed to FILE thread for reading.
+//
+// // Setup callback for writing.
+// writer->RegisterCallback(base::Bind(&SpaceAvailable, this));
+//
+// // Do initial round of writing.
+// SpaceAvailable();
+// }
+//
+// // May only be run on first argument task runner, in this case the IO
+// // thread.
+// void OriginatingClass::SpaceAvailable() {
+// while (<data available>) {
+// scoped_ptr<net::IOBuffer> buffer;
+// size_t buffer_length;
+// // Create IOBuffer, fill in with data, and set buffer_length.
+// if (!writer->Write(buffer, buffer_length)) {
+// // No more space; return and we'll be called again
+// // when there is space.
+// return;
+// }
+// }
+// writer->Close(<operation status>);
+// writer.reset(NULL);
+// }
+//
+// // On File thread; containing class setup not shown.
+//
+// void ReceivingClass::Initialize() {
+// // Initialization
+// reader->RegisterCallback(base::Bind(&DataAvailable, obj));
+// }
+//
+// // Called whenever there's something to read.
+// void ReceivingClass::DataAvailable() {
+// scoped_refptr<net::IOBuffer> data;
+// size_t length = 0;
+//
+// while (ByteStreamReader::STREAM_HAS_DATA ==
+// (state = reader->Read(&data, &length))) {
+// // Process |data|.
+// }
+//
+// if (ByteStreamReader::STREAM_COMPLETE == state) {
+// int status = reader->GetStatus();
+// // Process error or successful completion in |status|.
+// }
+//
+// // if |state| is STREAM_EMPTY, we're done for now; we'll be called
+// // again when there's more data.
+// }
+class CONTENT_EXPORT ByteStreamWriter {
+ public:
+ // Inverse of the fraction of the stream buffer that must be full before
+ // a notification is sent to paired Reader that there's more data.
+ static const int kFractionBufferBeforeSending;
+
+ virtual ~ByteStreamWriter() = 0;
+
+ // Always adds the data passed into the ByteStream. Returns true
+ // if more data may be added without exceeding the class limit
+ // on data. Takes ownership of |buffer|.
+ virtual bool Write(scoped_refptr<net::IOBuffer> buffer,
+ size_t byte_count) = 0;
+
+ // Flushes contents buffered in this writer to the corresponding reader
+ // regardless if buffer filling rate is greater than
+ // kFractionBufferBeforeSending or not. Does nothing if there's no contents
+ // buffered.
+ virtual void Flush() = 0;
+
+ // Signal that all data that is going to be sent, has been sent,
+ // and provide a status.
+ virtual void Close(int status) = 0;
+
+ // Register a callback to be called when the stream transitions from
+ // full to having space available. The callback will always be
+ // called on the task runner associated with the ByteStreamWriter.
+ // This callback will only be called if a call to Write has previously
+ // returned false (i.e. the ByteStream has been filled).
+ // Multiple calls to this function are supported, though note that it
+ // is the callers responsibility to handle races with space becoming
+ // available (i.e. in the case of that race either of the before
+ // or after callbacks may be called).
+ // The callback will not be called after ByteStreamWriter destruction.
+ virtual void RegisterCallback(const base::Closure& source_callback) = 0;
+};
+
+class CONTENT_EXPORT ByteStreamReader {
+ public:
+ // Inverse of the fraction of the stream buffer that must be empty before
+ // a notification is send to paired Writer that there's more room.
+ static const int kFractionReadBeforeWindowUpdate;
+
+ enum StreamState { STREAM_EMPTY, STREAM_HAS_DATA, STREAM_COMPLETE };
+
+ virtual ~ByteStreamReader() = 0;
+
+ // Returns STREAM_EMPTY if there is no data on the ByteStream and
+ // Close() has not been called, and STREAM_COMPLETE if there
+ // is no data on the ByteStream and Close() has been called.
+ // If there is data on the ByteStream, returns STREAM_HAS_DATA
+ // and fills in |*data| with a pointer to the data, and |*length|
+ // with its length.
+ virtual StreamState Read(scoped_refptr<net::IOBuffer>* data,
+ size_t* length) = 0;
+
+ // Only valid to call if Read() has returned STREAM_COMPLETE.
+ virtual int GetStatus() const = 0;
+
+ // Register a callback to be called when data is added or the source
+ // completes. The callback will be always be called on the owning
+ // task runner. Multiple calls to this function are supported,
+ // though note that it is the callers responsibility to handle races
+ // with data becoming available (i.e. in the case of that race
+ // either of the before or after callbacks may be called).
+ // The callback will not be called after ByteStreamReader destruction.
+ virtual void RegisterCallback(const base::Closure& sink_callback) = 0;
+};
+
+CONTENT_EXPORT void CreateByteStream(
+ scoped_refptr<base::SequencedTaskRunner> input_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> output_task_runner,
+ size_t buffer_size,
+ scoped_ptr<ByteStreamWriter>* input,
+ scoped_ptr<ByteStreamReader>* output);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_BYTE_STREAM_H_
diff --git a/chromium/content/browser/byte_stream_unittest.cc b/chromium/content/browser/byte_stream_unittest.cc
new file mode 100644
index 00000000000..04c5ae37533
--- /dev/null
+++ b/chromium/content/browser/byte_stream_unittest.cc
@@ -0,0 +1,582 @@
+// 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 "content/browser/byte_stream.h"
+
+#include <deque>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "net/base/io_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+void CountCallbacks(int* counter) {
+ ++*counter;
+}
+
+} // namespace
+
+class ByteStreamTest : public testing::Test {
+ public:
+ ByteStreamTest();
+
+ // Create a new IO buffer of the given |buffer_size|. Details of the
+ // contents of the created buffer will be kept, and can be validated
+ // by ValidateIOBuffer.
+ scoped_refptr<net::IOBuffer> NewIOBuffer(size_t buffer_size) {
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(buffer_size));
+ char *bufferp = buffer->data();
+ for (size_t i = 0; i < buffer_size; i++)
+ bufferp[i] = (i + producing_seed_key_) % (1 << sizeof(char));
+ pointer_queue_.push_back(bufferp);
+ length_queue_.push_back(buffer_size);
+ ++producing_seed_key_;
+ return buffer;
+ }
+
+ // Create an IOBuffer of the appropriate size and add it to the
+ // ByteStream, returning the result of the ByteStream::Write.
+ // Separate function to avoid duplication of buffer_size in test
+ // calls.
+ bool Write(ByteStreamWriter* byte_stream_input, size_t buffer_size) {
+ return byte_stream_input->Write(NewIOBuffer(buffer_size), buffer_size);
+ }
+
+ // Validate that we have the IOBuffer we expect. This routine must be
+ // called on buffers that were allocated from NewIOBuffer, and in the
+ // order that they were allocated. Calls to NewIOBuffer &&
+ // ValidateIOBuffer may be interleaved.
+ bool ValidateIOBuffer(
+ scoped_refptr<net::IOBuffer> buffer, size_t buffer_size) {
+ char *bufferp = buffer->data();
+
+ char *expected_ptr = pointer_queue_.front();
+ size_t expected_length = length_queue_.front();
+ pointer_queue_.pop_front();
+ length_queue_.pop_front();
+ ++consuming_seed_key_;
+
+ EXPECT_EQ(expected_ptr, bufferp);
+ if (expected_ptr != bufferp)
+ return false;
+
+ EXPECT_EQ(expected_length, buffer_size);
+ if (expected_length != buffer_size)
+ return false;
+
+ for (size_t i = 0; i < buffer_size; i++) {
+ // Already incremented, so subtract one from the key.
+ EXPECT_EQ(static_cast<int>((i + consuming_seed_key_ - 1)
+ % (1 << sizeof(char))),
+ bufferp[i]);
+ if (static_cast<int>((i + consuming_seed_key_ - 1) %
+ (1 << sizeof(char))) != bufferp[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+
+ private:
+ int producing_seed_key_;
+ int consuming_seed_key_;
+ std::deque<char*> pointer_queue_;
+ std::deque<size_t> length_queue_;
+};
+
+ByteStreamTest::ByteStreamTest()
+ : producing_seed_key_(0),
+ consuming_seed_key_(0) { }
+
+// Confirm that filling and emptying the stream works properly, and that
+// we get full triggers when we expect.
+TEST_F(ByteStreamTest, ByteStream_PushBack) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+
+ // Push a series of IO buffers on; test pushback happening and
+ // that it's advisory.
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ EXPECT_FALSE(Write(byte_stream_input.get(), 1));
+ EXPECT_FALSE(Write(byte_stream_input.get(), 1024));
+ // Flush
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+
+ // Pull the IO buffers out; do we get the same buffers and do they
+ // have the same contents?
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+// Confirm that Flush() method makes the writer to send written contents to
+// the reader.
+TEST_F(ByteStreamTest, ByteStream_Flush) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 1024, &byte_stream_input, &byte_stream_output);
+
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1));
+ message_loop_.RunUntilIdle();
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length = 0;
+ // Check that data is not sent to the reader yet.
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+
+ byte_stream_input->Flush();
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ // Check that it's ok to Flush() an empty writer.
+ byte_stream_input->Flush();
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+// Same as above, only use knowledge of the internals to confirm
+// that we're getting pushback even when data's split across the two
+// objects
+TEST_F(ByteStreamTest, ByteStream_PushBackSplit) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 9 * 1024, &byte_stream_input, &byte_stream_output);
+
+ // Push a series of IO buffers on; test pushback happening and
+ // that it's advisory.
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ message_loop_.RunUntilIdle();
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ message_loop_.RunUntilIdle();
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ message_loop_.RunUntilIdle();
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ message_loop_.RunUntilIdle();
+ EXPECT_FALSE(Write(byte_stream_input.get(), 6 * 1024));
+ message_loop_.RunUntilIdle();
+
+ // Pull the IO buffers out; do we get the same buffers and do they
+ // have the same contents?
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+// Confirm that a Close() notification transmits in-order
+// with data on the stream.
+TEST_F(ByteStreamTest, ByteStream_CompleteTransmits) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+
+ // Empty stream, non-error case.
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+ ASSERT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_EQ(0, byte_stream_output->GetStatus());
+
+ // Non-empty stream, non-error case.
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ ASSERT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_EQ(0, byte_stream_output->GetStatus());
+
+ const int kFakeErrorCode = 22;
+
+ // Empty stream, error case.
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ byte_stream_input->Close(kFakeErrorCode);
+ message_loop_.RunUntilIdle();
+ ASSERT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_EQ(kFakeErrorCode, byte_stream_output->GetStatus());
+
+ // Non-empty stream, error case.
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 1024));
+ byte_stream_input->Close(kFakeErrorCode);
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ ASSERT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_EQ(kFakeErrorCode, byte_stream_output->GetStatus());
+}
+
+// Confirm that callbacks on the sink side are triggered when they should be.
+TEST_F(ByteStreamTest, ByteStream_SinkCallback) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner(
+ new base::TestSimpleTaskRunner());
+
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), task_runner,
+ 10000, &byte_stream_input, &byte_stream_output);
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+
+ // Note that the specifics of when the callbacks are called with regard
+ // to how much data is pushed onto the stream is not (currently) part
+ // of the interface contract. If it becomes part of the contract, the
+ // tests below should get much more precise.
+
+ // Confirm callback called when you add more than 33% of the buffer.
+
+ // Setup callback
+ int num_callbacks = 0;
+ byte_stream_output->RegisterCallback(
+ base::Bind(CountCallbacks, &num_callbacks));
+
+ EXPECT_TRUE(Write(byte_stream_input.get(), 4000));
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(0, num_callbacks);
+ task_runner->RunUntilIdle();
+ EXPECT_EQ(1, num_callbacks);
+
+ // Check data and stream state.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+
+ // Confirm callback *isn't* called at less than 33% (by lack of
+ // unexpected call on task runner).
+ EXPECT_TRUE(Write(byte_stream_input.get(), 3000));
+ message_loop_.RunUntilIdle();
+
+ // This reflects an implementation artifact that data goes with callbacks,
+ // which should not be considered part of the interface guarantee.
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+// Confirm that callbacks on the source side are triggered when they should
+// be.
+TEST_F(ByteStreamTest, ByteStream_SourceCallback) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner(
+ new base::TestSimpleTaskRunner());
+
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ task_runner, message_loop_.message_loop_proxy(),
+ 10000, &byte_stream_input, &byte_stream_output);
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+
+ // Note that the specifics of when the callbacks are called with regard
+ // to how much data is pulled from the stream is not (currently) part
+ // of the interface contract. If it becomes part of the contract, the
+ // tests below should get much more precise.
+
+ // Confirm callback called when about 33% space available, and not
+ // at other transitions.
+
+ // Add data.
+ int num_callbacks = 0;
+ byte_stream_input->RegisterCallback(
+ base::Bind(CountCallbacks, &num_callbacks));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 2000));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 2001));
+ EXPECT_FALSE(Write(byte_stream_input.get(), 6000));
+
+ // Allow bytes to transition (needed for message passing implementation),
+ // and get and validate the data.
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ // Grab data, triggering callback. Recorded on dispatch, but doesn't
+ // happen because it's caught by the mock.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ // Confirm that the callback passed to the mock does what we expect.
+ EXPECT_EQ(0, num_callbacks);
+ task_runner->RunUntilIdle();
+ EXPECT_EQ(1, num_callbacks);
+
+ // Same drill with final buffer.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_EQ(1, num_callbacks);
+ task_runner->RunUntilIdle();
+ // Should have updated the internal structures but not called the
+ // callback.
+ EXPECT_EQ(1, num_callbacks);
+}
+
+// Confirm that racing a change to a sink callback with a post results
+// in the new callback being called.
+TEST_F(ByteStreamTest, ByteStream_SinkInterrupt) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner(
+ new base::TestSimpleTaskRunner());
+
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), task_runner,
+ 10000, &byte_stream_input, &byte_stream_output);
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ base::Closure intermediate_callback;
+
+ // Record initial state.
+ int num_callbacks = 0;
+ byte_stream_output->RegisterCallback(
+ base::Bind(CountCallbacks, &num_callbacks));
+
+ // Add data, and pass it across.
+ EXPECT_TRUE(Write(byte_stream_input.get(), 4000));
+ message_loop_.RunUntilIdle();
+
+ // The task runner should have been hit, but the callback count
+ // isn't changed until we actually run the callback.
+ EXPECT_EQ(0, num_callbacks);
+
+ // If we change the callback now, the new one should be run
+ // (simulates race with post task).
+ int num_alt_callbacks = 0;
+ byte_stream_output->RegisterCallback(
+ base::Bind(CountCallbacks, &num_alt_callbacks));
+ task_runner->RunUntilIdle();
+ EXPECT_EQ(0, num_callbacks);
+ EXPECT_EQ(1, num_alt_callbacks);
+
+ // Final cleanup.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+
+}
+
+// Confirm that racing a change to a source callback with a post results
+// in the new callback being called.
+TEST_F(ByteStreamTest, ByteStream_SourceInterrupt) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner(
+ new base::TestSimpleTaskRunner());
+
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ task_runner, message_loop_.message_loop_proxy(),
+ 10000, &byte_stream_input, &byte_stream_output);
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ base::Closure intermediate_callback;
+
+ // Setup state for test.
+ int num_callbacks = 0;
+ byte_stream_input->RegisterCallback(
+ base::Bind(CountCallbacks, &num_callbacks));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 2000));
+ EXPECT_TRUE(Write(byte_stream_input.get(), 2001));
+ EXPECT_FALSE(Write(byte_stream_input.get(), 6000));
+ message_loop_.RunUntilIdle();
+
+ // Initial get should not trigger callback.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ message_loop_.RunUntilIdle();
+
+ // Second get *should* trigger callback.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+
+ // Which should do the right thing when it's run.
+ int num_alt_callbacks = 0;
+ byte_stream_input->RegisterCallback(
+ base::Bind(CountCallbacks, &num_alt_callbacks));
+ task_runner->RunUntilIdle();
+ EXPECT_EQ(0, num_callbacks);
+ EXPECT_EQ(1, num_alt_callbacks);
+
+ // Third get should also trigger callback.
+ EXPECT_EQ(ByteStreamReader::STREAM_HAS_DATA,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+ EXPECT_TRUE(ValidateIOBuffer(output_io_buffer, output_length));
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+// Confirm that callback is called on zero data transfer but source
+// complete.
+TEST_F(ByteStreamTest, ByteStream_ZeroCallback) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner(
+ new base::TestSimpleTaskRunner());
+
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), task_runner,
+ 10000, &byte_stream_input, &byte_stream_output);
+
+ base::Closure intermediate_callback;
+
+ // Record initial state.
+ int num_callbacks = 0;
+ byte_stream_output->RegisterCallback(
+ base::Bind(CountCallbacks, &num_callbacks));
+
+ // Immediately close the stream.
+ byte_stream_input->Close(0);
+ task_runner->RunUntilIdle();
+ EXPECT_EQ(1, num_callbacks);
+}
+
+TEST_F(ByteStreamTest, ByteStream_CloseWithoutAnyWrite) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ EXPECT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+TEST_F(ByteStreamTest, ByteStream_FlushWithoutAnyWrite) {
+ scoped_ptr<ByteStreamWriter> byte_stream_input;
+ scoped_ptr<ByteStreamReader> byte_stream_output;
+ CreateByteStream(
+ message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
+ 3 * 1024, &byte_stream_input, &byte_stream_output);
+
+ byte_stream_input->Flush();
+ message_loop_.RunUntilIdle();
+
+ scoped_refptr<net::IOBuffer> output_io_buffer;
+ size_t output_length;
+ EXPECT_EQ(ByteStreamReader::STREAM_EMPTY,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+
+ byte_stream_input->Close(0);
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(ByteStreamReader::STREAM_COMPLETE,
+ byte_stream_output->Read(&output_io_buffer, &output_length));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/cert_store_impl.cc b/chromium/content/browser/cert_store_impl.cc
new file mode 100644
index 00000000000..e7fc1d24060
--- /dev/null
+++ b/chromium/content/browser/cert_store_impl.cc
@@ -0,0 +1,182 @@
+// 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 "content/browser/cert_store_impl.h"
+
+#include <algorithm>
+#include <functional>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+
+template <typename T>
+struct MatchSecond {
+ explicit MatchSecond(const T& t) : value(t) {}
+
+ template<typename Pair>
+ bool operator()(const Pair& p) const {
+ return (value == p.second);
+ }
+ T value;
+};
+
+namespace content {
+
+// static
+CertStore* CertStore::GetInstance() {
+ return CertStoreImpl::GetInstance();
+}
+
+// static
+CertStoreImpl* CertStoreImpl::GetInstance() {
+ return Singleton<CertStoreImpl>::get();
+}
+
+CertStoreImpl::CertStoreImpl() : next_cert_id_(1) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ RegisterForNotification();
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&CertStoreImpl::RegisterForNotification,
+ base::Unretained(this)));
+ }
+}
+
+CertStoreImpl::~CertStoreImpl() {
+}
+
+void CertStoreImpl::RegisterForNotification() {
+ // We watch for RenderProcess termination, as this is how we clear
+ // certificates for now.
+ // TODO(jcampan): we should be listening to events such as resource cached/
+ // removed from cache, and remove the cert when we know it
+ // is not used anymore.
+
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+int CertStoreImpl::StoreCert(net::X509Certificate* cert, int process_id) {
+ DCHECK(cert);
+ base::AutoLock auto_lock(cert_lock_);
+
+ int cert_id;
+
+ // Do we already know this cert?
+ ReverseCertMap::iterator cert_iter = cert_to_id_.find(cert);
+ if (cert_iter == cert_to_id_.end()) {
+ cert_id = next_cert_id_++;
+ // We use 0 as an invalid cert_id value. In the unlikely event that
+ // next_cert_id_ wraps around, we reset it to 1.
+ if (next_cert_id_ == 0)
+ next_cert_id_ = 1;
+ cert->AddRef();
+ id_to_cert_[cert_id] = cert;
+ cert_to_id_[cert] = cert_id;
+ } else {
+ cert_id = cert_iter->second;
+ }
+
+ // Let's update process_id_to_cert_id_.
+ std::pair<IDMap::iterator, IDMap::iterator> process_ids =
+ process_id_to_cert_id_.equal_range(process_id);
+ if (std::find_if(process_ids.first, process_ids.second,
+ MatchSecond<int>(cert_id)) == process_ids.second) {
+ process_id_to_cert_id_.insert(std::make_pair(process_id, cert_id));
+ }
+
+ // And cert_id_to_process_id_.
+ std::pair<IDMap::iterator, IDMap::iterator> cert_ids =
+ cert_id_to_process_id_.equal_range(cert_id);
+ if (std::find_if(cert_ids.first, cert_ids.second,
+ MatchSecond<int>(process_id)) == cert_ids.second) {
+ cert_id_to_process_id_.insert(std::make_pair(cert_id, process_id));
+ }
+
+ return cert_id;
+}
+
+bool CertStoreImpl::RetrieveCert(int cert_id,
+ scoped_refptr<net::X509Certificate>* cert) {
+ base::AutoLock auto_lock(cert_lock_);
+
+ CertMap::iterator iter = id_to_cert_.find(cert_id);
+ if (iter == id_to_cert_.end())
+ return false;
+ if (cert)
+ *cert = iter->second;
+ return true;
+}
+
+void CertStoreImpl::RemoveCertInternal(int cert_id) {
+ CertMap::iterator cert_iter = id_to_cert_.find(cert_id);
+ DCHECK(cert_iter != id_to_cert_.end());
+
+ ReverseCertMap::iterator id_iter = cert_to_id_.find(cert_iter->second.get());
+ DCHECK(id_iter != cert_to_id_.end());
+ cert_to_id_.erase(id_iter);
+
+ cert_iter->second->Release();
+ id_to_cert_.erase(cert_iter);
+}
+
+void CertStoreImpl::RemoveCertsForRenderProcesHost(int process_id) {
+ base::AutoLock auto_lock(cert_lock_);
+
+ // We iterate through all the cert ids for that process.
+ std::pair<IDMap::iterator, IDMap::iterator> process_ids =
+ process_id_to_cert_id_.equal_range(process_id);
+ for (IDMap::iterator ids_iter = process_ids.first;
+ ids_iter != process_ids.second; ++ids_iter) {
+ int cert_id = ids_iter->second;
+ // Find all the processes referring to this cert id in
+ // cert_id_to_process_id_, then locate the process being removed within
+ // that range.
+ std::pair<IDMap::iterator, IDMap::iterator> cert_ids =
+ cert_id_to_process_id_.equal_range(cert_id);
+ IDMap::iterator proc_iter =
+ std::find_if(cert_ids.first, cert_ids.second,
+ MatchSecond<int>(process_id));
+ DCHECK(proc_iter != cert_ids.second);
+
+ // Before removing, determine if no other processes refer to the current
+ // cert id. If |proc_iter| (the current process) is the lower bound of
+ // processes containing the current cert id and if |next_proc_iter| is the
+ // upper bound (the first process that does not), then only one process,
+ // the one being removed, refers to the cert id.
+ IDMap::iterator next_proc_iter = proc_iter;
+ ++next_proc_iter;
+ bool last_process_for_cert_id =
+ (proc_iter == cert_ids.first && next_proc_iter == cert_ids.second);
+ cert_id_to_process_id_.erase(proc_iter);
+
+ if (last_process_for_cert_id) {
+ // The current cert id is not referenced by any other processes, so
+ // remove it from id_to_cert_ and cert_to_id_.
+ RemoveCertInternal(cert_id);
+ }
+ }
+ if (process_ids.first != process_ids.second)
+ process_id_to_cert_id_.erase(process_ids.first, process_ids.second);
+}
+
+void CertStoreImpl::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFICATION_RENDERER_PROCESS_TERMINATED ||
+ type == NOTIFICATION_RENDERER_PROCESS_CLOSED);
+ RenderProcessHost* rph = Source<RenderProcessHost>(source).ptr();
+ DCHECK(rph);
+ RemoveCertsForRenderProcesHost(rph->GetID());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/cert_store_impl.h b/chromium/content/browser/cert_store_impl.h
new file mode 100644
index 00000000000..8a326e95e8f
--- /dev/null
+++ b/chromium/content/browser/cert_store_impl.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_CERT_STORE_IMPL_H_
+#define CONTENT_BROWSER_CERT_STORE_IMPL_H_
+
+#include <map>
+
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/cert_store.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "net/cert/x509_certificate.h"
+
+namespace content {
+
+class CertStoreImpl : public CertStore,
+ public NotificationObserver {
+ public:
+ // Returns the singleton instance of the CertStore.
+ static CertStoreImpl* GetInstance();
+
+ // CertStore implementation:
+ virtual int StoreCert(net::X509Certificate* cert,
+ int render_process_host_id) OVERRIDE;
+ virtual bool RetrieveCert(int cert_id,
+ scoped_refptr<net::X509Certificate>* cert) OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+ protected:
+ CertStoreImpl();
+ virtual ~CertStoreImpl();
+
+ private:
+ friend struct DefaultSingletonTraits<CertStoreImpl>;
+
+ void RegisterForNotification();
+
+ // Remove the specified cert from id_to_cert_ and cert_to_id_.
+ // NOTE: the caller (RemoveCertsForRenderProcesHost) must hold cert_lock_.
+ void RemoveCertInternal(int cert_id);
+
+ // Removes all the certs associated with the specified process from the store.
+ void RemoveCertsForRenderProcesHost(int render_process_host_id);
+
+ typedef std::multimap<int, int> IDMap;
+ typedef std::map<int, scoped_refptr<net::X509Certificate> > CertMap;
+ typedef std::map<net::X509Certificate*, int, net::X509Certificate::LessThan>
+ ReverseCertMap;
+
+ // Is only used on the UI Thread.
+ NotificationRegistrar registrar_;
+
+ IDMap process_id_to_cert_id_;
+ IDMap cert_id_to_process_id_;
+
+ CertMap id_to_cert_;
+ ReverseCertMap cert_to_id_;
+
+ int next_cert_id_;
+
+ // This lock protects: process_to_ids_, id_to_processes_, id_to_cert_ and
+ // cert_to_id_.
+ base::Lock cert_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertStoreImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_CERT_STORE_IMPL_H_
diff --git a/chromium/content/browser/child_process_launcher.cc b/chromium/content/browser/child_process_launcher.cc
new file mode 100644
index 00000000000..b5bb3f491f0
--- /dev/null
+++ b/chromium/content/browser/child_process_launcher.cc
@@ -0,0 +1,502 @@
+// 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 "content/browser/child_process_launcher.h"
+
+#include <utility> // For std::pair.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/process/process.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_descriptors.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+
+#if defined(OS_WIN)
+#include "base/files/file_path.h"
+#include "content/common/sandbox_win.h"
+#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#elif defined(OS_MACOSX)
+#include "content/browser/mach_broker_mac.h"
+#elif defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "content/browser/android/child_process_launcher_android.h"
+#elif defined(OS_POSIX)
+#include "base/memory/singleton.h"
+#include "content/browser/renderer_host/render_sandbox_host_linux.h"
+#include "content/browser/zygote_host/zygote_host_impl_linux.h"
+#endif
+
+#if defined(OS_POSIX)
+#include "base/posix/global_descriptors.h"
+#endif
+
+namespace content {
+
+// Having the functionality of ChildProcessLauncher be in an internal
+// ref counted object allows us to automatically terminate the process when the
+// parent class destructs, while still holding on to state that we need.
+class ChildProcessLauncher::Context
+ : public base::RefCountedThreadSafe<ChildProcessLauncher::Context> {
+ public:
+ Context()
+ : client_(NULL),
+ client_thread_id_(BrowserThread::UI),
+ termination_status_(base::TERMINATION_STATUS_NORMAL_TERMINATION),
+ exit_code_(RESULT_CODE_NORMAL_EXIT),
+ starting_(true)
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ , zygote_(false)
+#endif
+ {
+#if defined(OS_POSIX)
+ terminate_child_on_shutdown_ = !CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kChildCleanExit);
+#else
+ terminate_child_on_shutdown_ = true;
+#endif
+ }
+
+ void Launch(
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_ANDROID)
+ int ipcfd,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& environ,
+ int ipcfd,
+#endif
+ CommandLine* cmd_line,
+ int child_process_id,
+ Client* client) {
+ client_ = client;
+
+ CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_));
+
+#if defined(OS_ANDROID)
+ // We need to close the client end of the IPC channel to reliably detect
+ // child termination. We will close this fd after we create the child
+ // process which is asynchronous on Android.
+ ipcfd_ = ipcfd;
+#endif
+ BrowserThread::PostTask(
+ BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+ base::Bind(
+ &Context::LaunchInternal,
+ make_scoped_refptr(this),
+ client_thread_id_,
+ child_process_id,
+#if defined(OS_WIN)
+ delegate,
+#elif defined(OS_ANDROID)
+ ipcfd,
+#elif defined(OS_POSIX)
+ use_zygote,
+ environ,
+ ipcfd,
+#endif
+ cmd_line));
+ }
+
+#if defined(OS_ANDROID)
+ static void OnChildProcessStarted(
+ // |this_object| is NOT thread safe. Only use it to post a task back.
+ scoped_refptr<Context> this_object,
+ BrowserThread::ID client_thread_id,
+ const base::TimeTicks begin_launch_time,
+ base::ProcessHandle handle) {
+ RecordHistograms(begin_launch_time);
+ if (BrowserThread::CurrentlyOn(client_thread_id)) {
+ // This is always invoked on the UI thread which is commonly the
+ // |client_thread_id| so we can shortcut one PostTask.
+ this_object->Notify(handle);
+ } else {
+ BrowserThread::PostTask(
+ client_thread_id, FROM_HERE,
+ base::Bind(
+ &ChildProcessLauncher::Context::Notify,
+ this_object,
+ handle));
+ }
+ }
+#endif
+
+ void ResetClient() {
+ // No need for locking as this function gets called on the same thread that
+ // client_ would be used.
+ CHECK(BrowserThread::CurrentlyOn(client_thread_id_));
+ client_ = NULL;
+ }
+
+ void set_terminate_child_on_shutdown(bool terminate_on_shutdown) {
+ terminate_child_on_shutdown_ = terminate_on_shutdown;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ChildProcessLauncher::Context>;
+ friend class ChildProcessLauncher;
+
+ ~Context() {
+ Terminate();
+ }
+
+ static void RecordHistograms(const base::TimeTicks begin_launch_time) {
+ base::TimeDelta launch_time = base::TimeTicks::Now() - begin_launch_time;
+ if (BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER)) {
+ RecordLaunchHistograms(launch_time);
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+ base::Bind(&ChildProcessLauncher::Context::RecordLaunchHistograms,
+ launch_time));
+ }
+ }
+
+ static void RecordLaunchHistograms(const base::TimeDelta launch_time) {
+ // Log the launch time, separating out the first one (which will likely be
+ // slower due to the rest of the browser initializing at the same time).
+ static bool done_first_launch = false;
+ if (done_first_launch) {
+ UMA_HISTOGRAM_TIMES("MPArch.ChildProcessLaunchSubsequent", launch_time);
+ } else {
+ UMA_HISTOGRAM_TIMES("MPArch.ChildProcessLaunchFirst", launch_time);
+ done_first_launch = true;
+ }
+ }
+
+ static void LaunchInternal(
+ // |this_object| is NOT thread safe. Only use it to post a task back.
+ scoped_refptr<Context> this_object,
+ BrowserThread::ID client_thread_id,
+ int child_process_id,
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_ANDROID)
+ int ipcfd,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& env,
+ int ipcfd,
+#endif
+ CommandLine* cmd_line) {
+ scoped_ptr<CommandLine> cmd_line_deleter(cmd_line);
+ base::TimeTicks begin_launch_time = base::TimeTicks::Now();
+
+#if defined(OS_WIN)
+ scoped_ptr<SandboxedProcessLauncherDelegate> delegate_deleter(delegate);
+ base::ProcessHandle handle = StartSandboxedProcess(delegate, cmd_line);
+#elif defined(OS_ANDROID)
+ // Android WebView runs in single process, ensure that we never get here
+ // when running in single process mode.
+ CHECK(!cmd_line->HasSwitch(switches::kSingleProcess));
+
+ std::string process_type =
+ cmd_line->GetSwitchValueASCII(switches::kProcessType);
+ std::vector<FileDescriptorInfo> files_to_register;
+ files_to_register.push_back(
+ FileDescriptorInfo(kPrimaryIPCChannel,
+ base::FileDescriptor(ipcfd, false)));
+
+ GetContentClient()->browser()->
+ GetAdditionalMappedFilesForChildProcess(*cmd_line, child_process_id,
+ &files_to_register);
+
+ StartChildProcess(cmd_line->argv(), files_to_register,
+ base::Bind(&ChildProcessLauncher::Context::OnChildProcessStarted,
+ this_object, client_thread_id, begin_launch_time));
+
+#elif defined(OS_POSIX)
+ base::ProcessHandle handle = base::kNullProcessHandle;
+ // We need to close the client end of the IPC channel to reliably detect
+ // child termination.
+ file_util::ScopedFD ipcfd_closer(&ipcfd);
+
+ std::string process_type =
+ cmd_line->GetSwitchValueASCII(switches::kProcessType);
+ std::vector<FileDescriptorInfo> files_to_register;
+ files_to_register.push_back(
+ FileDescriptorInfo(kPrimaryIPCChannel,
+ base::FileDescriptor(ipcfd, false)));
+
+#if !defined(OS_MACOSX)
+ GetContentClient()->browser()->
+ GetAdditionalMappedFilesForChildProcess(*cmd_line, child_process_id,
+ &files_to_register);
+ if (use_zygote) {
+ handle = ZygoteHostImpl::GetInstance()->ForkRequest(cmd_line->argv(),
+ files_to_register,
+ process_type);
+ } else
+ // Fall through to the normal posix case below when we're not zygoting.
+#endif // !defined(OS_MACOSX)
+ {
+ // Convert FD mapping to FileHandleMappingVector
+ base::FileHandleMappingVector fds_to_map;
+ for (size_t i = 0; i < files_to_register.size(); ++i) {
+ fds_to_map.push_back(std::make_pair(
+ files_to_register[i].fd.fd,
+ files_to_register[i].id +
+ base::GlobalDescriptors::kBaseDescriptor));
+ }
+
+#if !defined(OS_MACOSX)
+ if (process_type == switches::kRendererProcess) {
+ const int sandbox_fd =
+ RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
+ fds_to_map.push_back(std::make_pair(
+ sandbox_fd,
+ kSandboxIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
+ }
+#endif // defined(OS_MACOSX)
+
+ // Actually launch the app.
+ base::LaunchOptions options;
+ options.environ = &env;
+ options.fds_to_remap = &fds_to_map;
+
+#if defined(OS_MACOSX)
+ // Hold the MachBroker lock for the duration of LaunchProcess. The child
+ // will send its task port to the parent almost immediately after startup.
+ // The Mach message will be delivered to the parent, but updating the
+ // record of the launch will wait until after the placeholder PID is
+ // inserted below. This ensures that while the child process may send its
+ // port to the parent prior to the parent leaving LaunchProcess, the
+ // order in which the record in MachBroker is updated is correct.
+ MachBroker* broker = MachBroker::GetInstance();
+ broker->GetLock().Acquire();
+
+ // Make sure the MachBroker is running, and inform it to expect a
+ // check-in from the new process.
+ broker->EnsureRunning();
+#endif // defined(OS_MACOSX)
+
+ bool launched = base::LaunchProcess(*cmd_line, options, &handle);
+
+#if defined(OS_MACOSX)
+ if (launched)
+ broker->AddPlaceholderForPid(handle);
+
+ // After updating the broker, release the lock and let the child's
+ // messasge be processed on the broker's thread.
+ broker->GetLock().Release();
+#endif // defined(OS_MACOSX)
+
+ if (!launched)
+ handle = base::kNullProcessHandle;
+ }
+#endif // else defined(OS_POSIX)
+#if !defined(OS_ANDROID)
+ if (handle)
+ RecordHistograms(begin_launch_time);
+ BrowserThread::PostTask(
+ client_thread_id, FROM_HERE,
+ base::Bind(
+ &Context::Notify,
+ this_object.get(),
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ use_zygote,
+#endif
+ handle));
+#endif // !defined(OS_ANDROID)
+ }
+
+ void Notify(
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ bool zygote,
+#endif
+ base::ProcessHandle handle) {
+#if defined(OS_ANDROID)
+ // Finally close the ipcfd
+ file_util::ScopedFD ipcfd_closer(&ipcfd_);
+#endif
+ starting_ = false;
+ process_.set_handle(handle);
+ if (!handle)
+ LOG(ERROR) << "Failed to launch child process";
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ zygote_ = zygote;
+#endif
+ if (client_) {
+ client_->OnProcessLaunched();
+ } else {
+ Terminate();
+ }
+ }
+
+ void Terminate() {
+ if (!process_.handle())
+ return;
+
+ if (!terminate_child_on_shutdown_)
+ return;
+
+ // On Posix, EnsureProcessTerminated can lead to 2 seconds of sleep! So
+ // don't this on the UI/IO threads.
+ BrowserThread::PostTask(
+ BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+ base::Bind(
+ &Context::TerminateInternal,
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ zygote_,
+#endif
+ process_.handle()));
+ process_.set_handle(base::kNullProcessHandle);
+ }
+
+ static void SetProcessBackgrounded(base::ProcessHandle handle,
+ bool background) {
+ base::Process process(handle);
+ process.SetProcessBackgrounded(background);
+ }
+
+ static void TerminateInternal(
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ bool zygote,
+#endif
+ base::ProcessHandle handle) {
+#if defined(OS_ANDROID)
+ LOG(INFO) << "ChromeProcess: Stopping process with handle " << handle;
+ StopChildProcess(handle);
+#else
+ base::Process process(handle);
+ // Client has gone away, so just kill the process. Using exit code 0
+ // means that UMA won't treat this as a crash.
+ process.Terminate(RESULT_CODE_NORMAL_EXIT);
+ // On POSIX, we must additionally reap the child.
+#if defined(OS_POSIX)
+#if !defined(OS_MACOSX)
+ if (zygote) {
+ // If the renderer was created via a zygote, we have to proxy the reaping
+ // through the zygote process.
+ ZygoteHostImpl::GetInstance()->EnsureProcessTerminated(handle);
+ } else
+#endif // !OS_MACOSX
+ {
+ base::EnsureProcessTerminated(handle);
+ }
+#endif // OS_POSIX
+ process.Close();
+#endif // defined(OS_ANDROID)
+ }
+
+ Client* client_;
+ BrowserThread::ID client_thread_id_;
+ base::Process process_;
+ base::TerminationStatus termination_status_;
+ int exit_code_;
+ bool starting_;
+ // Controls whether the child process should be terminated on browser
+ // shutdown. Default behavior is to terminate the child.
+ bool terminate_child_on_shutdown_;
+#if defined(OS_ANDROID)
+ // The fd to close after creating the process.
+ int ipcfd_;
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+ bool zygote_;
+#endif
+};
+
+
+ChildProcessLauncher::ChildProcessLauncher(
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& environ,
+ int ipcfd,
+#endif
+ CommandLine* cmd_line,
+ int child_process_id,
+ Client* client) {
+ context_ = new Context();
+ context_->Launch(
+#if defined(OS_WIN)
+ delegate,
+#elif defined(OS_ANDROID)
+ ipcfd,
+#elif defined(OS_POSIX)
+ use_zygote,
+ environ,
+ ipcfd,
+#endif
+ cmd_line,
+ child_process_id,
+ client);
+}
+
+ChildProcessLauncher::~ChildProcessLauncher() {
+ context_->ResetClient();
+}
+
+bool ChildProcessLauncher::IsStarting() {
+ return context_->starting_;
+}
+
+base::ProcessHandle ChildProcessLauncher::GetHandle() {
+ DCHECK(!context_->starting_);
+ return context_->process_.handle();
+}
+
+base::TerminationStatus ChildProcessLauncher::GetChildTerminationStatus(
+ bool known_dead,
+ int* exit_code) {
+ base::ProcessHandle handle = context_->process_.handle();
+ if (handle == base::kNullProcessHandle) {
+ // Process is already gone, so return the cached termination status.
+ if (exit_code)
+ *exit_code = context_->exit_code_;
+ return context_->termination_status_;
+ }
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ if (context_->zygote_) {
+ context_->termination_status_ = ZygoteHostImpl::GetInstance()->
+ GetTerminationStatus(handle, known_dead, &context_->exit_code_);
+ } else
+#endif
+ {
+ context_->termination_status_ =
+ base::GetTerminationStatus(handle, &context_->exit_code_);
+ }
+
+ if (exit_code)
+ *exit_code = context_->exit_code_;
+
+ // POSIX: If the process crashed, then the kernel closed the socket
+ // for it and so the child has already died by the time we get
+ // here. Since GetTerminationStatus called waitpid with WNOHANG,
+ // it'll reap the process. However, if GetTerminationStatus didn't
+ // reap the child (because it was still running), we'll need to
+ // Terminate via ProcessWatcher. So we can't close the handle here.
+ if (context_->termination_status_ != base::TERMINATION_STATUS_STILL_RUNNING)
+ context_->process_.Close();
+
+ return context_->termination_status_;
+}
+
+void ChildProcessLauncher::SetProcessBackgrounded(bool background) {
+ BrowserThread::PostTask(
+ BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+ base::Bind(
+ &ChildProcessLauncher::Context::SetProcessBackgrounded,
+ GetHandle(), background));
+}
+
+void ChildProcessLauncher::SetTerminateChildOnShutdown(
+ bool terminate_on_shutdown) {
+ if (context_.get())
+ context_->set_terminate_child_on_shutdown(terminate_on_shutdown);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/child_process_launcher.h b/chromium/content/browser/child_process_launcher.h
new file mode 100644
index 00000000000..5a6e1f96e26
--- /dev/null
+++ b/chromium/content/browser/child_process_launcher.h
@@ -0,0 +1,85 @@
+// 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 CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_
+#define CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "content/common/content_export.h"
+
+class CommandLine;
+
+namespace content {
+class SandboxedProcessLauncherDelegate;
+
+// Launches a process asynchronously and notifies the client of the process
+// handle when it's available. It's used to avoid blocking the calling thread
+// on the OS since often it can take > 100 ms to create the process.
+class CONTENT_EXPORT ChildProcessLauncher {
+ public:
+ class CONTENT_EXPORT Client {
+ public:
+ // Will be called on the thread that the ChildProcessLauncher was
+ // constructed on.
+ virtual void OnProcessLaunched() = 0;
+
+ protected:
+ virtual ~Client() {}
+ };
+
+ // Launches the process asynchronously, calling the client when the result is
+ // ready. Deleting this object before the process is created is safe, since
+ // the callback won't be called. If the process is still running by the time
+ // this object destructs, it will be terminated.
+ // Takes ownership of cmd_line.
+ ChildProcessLauncher(
+#if defined(OS_WIN)
+ SandboxedProcessLauncherDelegate* delegate,
+#elif defined(OS_POSIX)
+ bool use_zygote,
+ const base::EnvironmentVector& environ,
+ int ipcfd,
+#endif
+ CommandLine* cmd_line,
+ int child_process_id,
+ Client* client);
+ ~ChildProcessLauncher();
+
+ // True if the process is being launched and so the handle isn't available.
+ bool IsStarting();
+
+ // Getter for the process handle. Only call after the process has started.
+ base::ProcessHandle GetHandle();
+
+ // Call this when the child process exits to know what happened to it.
+ // |known_dead| can be true if we already know the process is dead as it can
+ // help the implemention figure the proper TerminationStatus.
+ // |exit_code| is the exit code of the process if it exited (e.g. status from
+ // waitpid if on posix, from GetExitCodeProcess on Windows). |exit_code| may
+ // be NULL.
+ base::TerminationStatus GetChildTerminationStatus(bool known_dead,
+ int* exit_code);
+
+ // Changes whether the process runs in the background or not. Only call
+ // this after the process has started.
+ void SetProcessBackgrounded(bool background);
+
+ // Controls whether the child process should be terminated on browser
+ // shutdown.
+ void SetTerminateChildOnShutdown(bool terminate_on_shutdown);
+
+ private:
+ class Context;
+
+ scoped_refptr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildProcessLauncher);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_
diff --git a/chromium/content/browser/child_process_security_policy_browsertest.cc b/chromium/content/browser/child_process_security_policy_browsertest.cc
new file mode 100644
index 00000000000..db27a533ea9
--- /dev/null
+++ b/chromium/content/browser/child_process_security_policy_browsertest.cc
@@ -0,0 +1,60 @@
+// 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 <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/result_codes.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class ChildProcessSecurityPolicyInProcessBrowserTest
+ : public ContentBrowserTest {
+ public:
+ virtual void SetUp() {
+ EXPECT_EQ(
+ ChildProcessSecurityPolicyImpl::GetInstance()->security_state_.size(),
+ 0U);
+ ContentBrowserTest::SetUp();
+ }
+
+ virtual void TearDown() {
+ EXPECT_EQ(
+ ChildProcessSecurityPolicyImpl::GetInstance()->security_state_.size(),
+ 0U);
+ ContentBrowserTest::TearDown();
+ }
+};
+
+#if !defined(NDEBUG) && defined(OS_MACOSX)
+IN_PROC_BROWSER_TEST_F(ChildProcessSecurityPolicyInProcessBrowserTest, DISABLED_NoLeak) {
+#else
+IN_PROC_BROWSER_TEST_F(ChildProcessSecurityPolicyInProcessBrowserTest, NoLeak) {
+#endif
+ GURL url = GetTestUrl("", "simple_page.html");
+
+ NavigateToURL(shell(), url);
+ EXPECT_EQ(
+ ChildProcessSecurityPolicyImpl::GetInstance()->security_state_.size(),
+ 1U);
+
+ WebContents* web_contents = shell()->web_contents();
+ base::KillProcess(web_contents->GetRenderProcessHost()->GetHandle(),
+ RESULT_CODE_KILLED, true);
+
+ web_contents->GetController().Reload(true);
+ EXPECT_EQ(
+ 1U,
+ ChildProcessSecurityPolicyImpl::GetInstance()->security_state_.size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/child_process_security_policy_impl.cc b/chromium/content/browser/child_process_security_policy_impl.cc
new file mode 100644
index 00000000000..d7640d344df
--- /dev/null
+++ b/chromium/content/browser/child_process_security_policy_impl.cc
@@ -0,0 +1,831 @@
+// 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 "content/browser/child_process_security_policy_impl.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/platform_file.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace content {
+
+namespace {
+
+const int kReadFilePermissions =
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_EXCLUSIVE_READ |
+ base::PLATFORM_FILE_ASYNC;
+
+const int kWriteFilePermissions =
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE |
+ base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_WRITE_ATTRIBUTES;
+
+const int kCreateFilePermissions =
+ base::PLATFORM_FILE_CREATE;
+
+const int kEnumerateDirectoryPermissions =
+ kReadFilePermissions |
+ base::PLATFORM_FILE_ENUMERATE;
+
+// TODO(tommycli): These flag sets need some work to make more obvious.
+// Why for instance, does Create|Write != Create|Write? http://crbug.com/263150
+const int kCreateReadWriteFilePermissions =
+ kReadFilePermissions |
+ kWriteFilePermissions |
+ kCreateFilePermissions |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_OPEN_TRUNCATED;
+
+const int kCreateWriteFilePermissions =
+ kWriteFilePermissions |
+ kCreateFilePermissions |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_OPEN_TRUNCATED;
+
+} // namespace
+
+// The SecurityState class is used to maintain per-child process security state
+// information.
+class ChildProcessSecurityPolicyImpl::SecurityState {
+ public:
+ SecurityState()
+ : enabled_bindings_(0),
+ can_read_raw_cookies_(false) { }
+
+ ~SecurityState() {
+ scheme_policy_.clear();
+ fileapi::IsolatedContext* isolated_context =
+ fileapi::IsolatedContext::GetInstance();
+ for (FileSystemMap::iterator iter = filesystem_permissions_.begin();
+ iter != filesystem_permissions_.end();
+ ++iter) {
+ isolated_context->RemoveReference(iter->first);
+ }
+ UMA_HISTOGRAM_COUNTS("ChildProcessSecurityPolicy.PerChildFilePermissions",
+ file_permissions_.size());
+ }
+
+ // Grant permission to request URLs with the specified scheme.
+ void GrantScheme(const std::string& scheme) {
+ scheme_policy_[scheme] = true;
+ }
+
+ // Revoke permission to request URLs with the specified scheme.
+ void RevokeScheme(const std::string& scheme) {
+ scheme_policy_[scheme] = false;
+ }
+
+ // Grant certain permissions to a file.
+ void GrantPermissionsForFile(const base::FilePath& file, int permissions) {
+ base::FilePath stripped = file.StripTrailingSeparators();
+ file_permissions_[stripped] |= permissions;
+ UMA_HISTOGRAM_COUNTS("ChildProcessSecurityPolicy.FilePermissionPathLength",
+ stripped.value().size());
+ }
+
+ // Grant navigation to a file but not the file:// scheme in general.
+ void GrantRequestOfSpecificFile(const base::FilePath &file) {
+ request_file_set_.insert(file.StripTrailingSeparators());
+ }
+
+ // Revokes all permissions granted to a file.
+ void RevokeAllPermissionsForFile(const base::FilePath& file) {
+ base::FilePath stripped = file.StripTrailingSeparators();
+ file_permissions_.erase(stripped);
+ request_file_set_.erase(stripped);
+ }
+
+ // Grant certain permissions to a file.
+ void GrantPermissionsForFileSystem(const std::string& filesystem_id,
+ int permissions) {
+ if (filesystem_permissions_.find(filesystem_id) ==
+ filesystem_permissions_.end())
+ fileapi::IsolatedContext::GetInstance()->AddReference(filesystem_id);
+ filesystem_permissions_[filesystem_id] |= permissions;
+ }
+
+ bool HasPermissionsForFileSystem(const std::string& filesystem_id,
+ int permissions) {
+ if (filesystem_permissions_.find(filesystem_id) ==
+ filesystem_permissions_.end())
+ return false;
+ return (filesystem_permissions_[filesystem_id] & permissions) ==
+ permissions;
+ }
+
+ void GrantBindings(int bindings) {
+ enabled_bindings_ |= bindings;
+ }
+
+ void GrantReadRawCookies() {
+ can_read_raw_cookies_ = true;
+ }
+
+ void RevokeReadRawCookies() {
+ can_read_raw_cookies_ = false;
+ }
+
+ // Determine whether permission has been granted to request |url|.
+ bool CanRequestURL(const GURL& url) {
+ // Having permission to a scheme implies permssion to all of its URLs.
+ SchemeMap::const_iterator judgment(scheme_policy_.find(url.scheme()));
+ if (judgment != scheme_policy_.end())
+ return judgment->second;
+
+ // file:// URLs are more granular. The child may have been given
+ // permission to a specific file but not the file:// scheme in general.
+ if (url.SchemeIs(chrome::kFileScheme)) {
+ base::FilePath path;
+ if (net::FileURLToFilePath(url, &path))
+ return request_file_set_.find(path) != request_file_set_.end();
+ }
+
+ return false; // Unmentioned schemes are disallowed.
+ }
+
+ // Determine if the certain permissions have been granted to a file.
+ bool HasPermissionsForFile(const base::FilePath& file, int permissions) {
+ if (!permissions || file.empty() || !file.IsAbsolute())
+ return false;
+ base::FilePath current_path = file.StripTrailingSeparators();
+ base::FilePath last_path;
+ int skip = 0;
+ while (current_path != last_path) {
+ base::FilePath base_name = current_path.BaseName();
+ if (base_name.value() == base::FilePath::kParentDirectory) {
+ ++skip;
+ } else if (skip > 0) {
+ if (base_name.value() != base::FilePath::kCurrentDirectory)
+ --skip;
+ } else {
+ if (file_permissions_.find(current_path) != file_permissions_.end())
+ return (file_permissions_[current_path] & permissions) == permissions;
+ }
+ last_path = current_path;
+ current_path = current_path.DirName();
+ }
+
+ return false;
+ }
+
+ bool CanLoadPage(const GURL& gurl) {
+ if (origin_lock_.is_empty())
+ return true;
+
+ // TODO(creis): We must pass the valid browser_context to convert hosted
+ // apps URLs. Currently, hosted apps cannot be loaded in this mode.
+ // See http://crbug.com/160576.
+ GURL site_gurl = SiteInstanceImpl::GetSiteForURL(NULL, gurl);
+ return origin_lock_ == site_gurl;
+ }
+
+ bool CanAccessCookiesForOrigin(const GURL& gurl) {
+ if (origin_lock_.is_empty())
+ return true;
+ // TODO(creis): We must pass the valid browser_context to convert hosted
+ // apps URLs. Currently, hosted apps cannot set cookies in this mode.
+ // See http://crbug.com/160576.
+ GURL site_gurl = SiteInstanceImpl::GetSiteForURL(NULL, gurl);
+ return origin_lock_ == site_gurl;
+ }
+
+ bool CanSendCookiesForOrigin(const GURL& gurl) {
+ // We only block cross-site cookies on network requests if the
+ // --enable-strict-site-isolation flag is passed. This is expected to break
+ // compatibility with many sites. The similar --site-per-process flag only
+ // blocks JavaScript access to cross-site cookies (in
+ // CanAccessCookiesForOrigin).
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (!command_line.HasSwitch(switches::kEnableStrictSiteIsolation))
+ return true;
+
+ if (origin_lock_.is_empty())
+ return true;
+ // TODO(creis): We must pass the valid browser_context to convert hosted
+ // apps URLs. Currently, hosted apps cannot set cookies in this mode.
+ // See http://crbug.com/160576.
+ GURL site_gurl = SiteInstanceImpl::GetSiteForURL(NULL, gurl);
+ return origin_lock_ == site_gurl;
+ }
+
+ void LockToOrigin(const GURL& gurl) {
+ origin_lock_ = gurl;
+ }
+
+ bool has_web_ui_bindings() const {
+ return enabled_bindings_ & BINDINGS_POLICY_WEB_UI;
+ }
+
+ bool can_read_raw_cookies() const {
+ return can_read_raw_cookies_;
+ }
+
+ private:
+ typedef std::map<std::string, bool> SchemeMap;
+
+ typedef int FilePermissionFlags; // bit-set of PlatformFileFlags
+ typedef std::map<base::FilePath, FilePermissionFlags> FileMap;
+ typedef std::map<std::string, FilePermissionFlags> FileSystemMap;
+ typedef std::set<base::FilePath> FileSet;
+
+ // Maps URL schemes to whether permission has been granted or revoked:
+ // |true| means the scheme has been granted.
+ // |false| means the scheme has been revoked.
+ // If a scheme is not present in the map, then it has never been granted
+ // or revoked.
+ SchemeMap scheme_policy_;
+
+ // The set of files the child process is permited to upload to the web.
+ FileMap file_permissions_;
+
+ // The set of files the child process is permitted to load.
+ FileSet request_file_set_;
+
+ int enabled_bindings_;
+
+ bool can_read_raw_cookies_;
+
+ GURL origin_lock_;
+
+ // The set of isolated filesystems the child process is permitted to access.
+ FileSystemMap filesystem_permissions_;
+
+ DISALLOW_COPY_AND_ASSIGN(SecurityState);
+};
+
+ChildProcessSecurityPolicyImpl::ChildProcessSecurityPolicyImpl() {
+ // We know about these schemes and believe them to be safe.
+ RegisterWebSafeScheme(chrome::kHttpScheme);
+ RegisterWebSafeScheme(chrome::kHttpsScheme);
+ RegisterWebSafeScheme(chrome::kFtpScheme);
+ RegisterWebSafeScheme(chrome::kDataScheme);
+ RegisterWebSafeScheme("feed");
+ RegisterWebSafeScheme(chrome::kBlobScheme);
+ RegisterWebSafeScheme(chrome::kFileSystemScheme);
+
+ // We know about the following pseudo schemes and treat them specially.
+ RegisterPseudoScheme(chrome::kAboutScheme);
+ RegisterPseudoScheme(chrome::kJavaScriptScheme);
+ RegisterPseudoScheme(kViewSourceScheme);
+}
+
+ChildProcessSecurityPolicyImpl::~ChildProcessSecurityPolicyImpl() {
+ web_safe_schemes_.clear();
+ pseudo_schemes_.clear();
+ STLDeleteContainerPairSecondPointers(security_state_.begin(),
+ security_state_.end());
+ security_state_.clear();
+}
+
+// static
+ChildProcessSecurityPolicy* ChildProcessSecurityPolicy::GetInstance() {
+ return ChildProcessSecurityPolicyImpl::GetInstance();
+}
+
+ChildProcessSecurityPolicyImpl* ChildProcessSecurityPolicyImpl::GetInstance() {
+ return Singleton<ChildProcessSecurityPolicyImpl>::get();
+}
+
+void ChildProcessSecurityPolicyImpl::Add(int child_id) {
+ base::AutoLock lock(lock_);
+ AddChild(child_id);
+}
+
+void ChildProcessSecurityPolicyImpl::AddWorker(int child_id,
+ int main_render_process_id) {
+ base::AutoLock lock(lock_);
+ AddChild(child_id);
+ worker_map_[child_id] = main_render_process_id;
+}
+
+void ChildProcessSecurityPolicyImpl::Remove(int child_id) {
+ base::AutoLock lock(lock_);
+ if (!security_state_.count(child_id))
+ return; // May be called multiple times.
+
+ delete security_state_[child_id];
+ security_state_.erase(child_id);
+ worker_map_.erase(child_id);
+}
+
+void ChildProcessSecurityPolicyImpl::RegisterWebSafeScheme(
+ const std::string& scheme) {
+ base::AutoLock lock(lock_);
+ DCHECK(web_safe_schemes_.count(scheme) == 0) << "Add schemes at most once.";
+ DCHECK(pseudo_schemes_.count(scheme) == 0) << "Web-safe implies not pseudo.";
+
+ web_safe_schemes_.insert(scheme);
+}
+
+bool ChildProcessSecurityPolicyImpl::IsWebSafeScheme(
+ const std::string& scheme) {
+ base::AutoLock lock(lock_);
+
+ return (web_safe_schemes_.find(scheme) != web_safe_schemes_.end());
+}
+
+void ChildProcessSecurityPolicyImpl::RegisterPseudoScheme(
+ const std::string& scheme) {
+ base::AutoLock lock(lock_);
+ DCHECK(pseudo_schemes_.count(scheme) == 0) << "Add schemes at most once.";
+ DCHECK(web_safe_schemes_.count(scheme) == 0) <<
+ "Pseudo implies not web-safe.";
+
+ pseudo_schemes_.insert(scheme);
+}
+
+bool ChildProcessSecurityPolicyImpl::IsPseudoScheme(
+ const std::string& scheme) {
+ base::AutoLock lock(lock_);
+
+ return (pseudo_schemes_.find(scheme) != pseudo_schemes_.end());
+}
+
+void ChildProcessSecurityPolicyImpl::GrantRequestURL(
+ int child_id, const GURL& url) {
+
+ if (!url.is_valid())
+ return; // Can't grant the capability to request invalid URLs.
+
+ if (IsWebSafeScheme(url.scheme()))
+ return; // The scheme has already been whitelisted for every child process.
+
+ if (IsPseudoScheme(url.scheme())) {
+ // The view-source scheme is a special case of a pseudo-URL that eventually
+ // results in requesting its embedded URL.
+ if (url.SchemeIs(kViewSourceScheme)) {
+ // URLs with the view-source scheme typically look like:
+ // view-source:http://www.google.com/a
+ // In order to request these URLs, the child_id needs to be able to
+ // request the embedded URL.
+ GrantRequestURL(child_id, GURL(url.path()));
+ }
+
+ return; // Can't grant the capability to request pseudo schemes.
+ }
+
+ {
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ // When the child process has been commanded to request this scheme,
+ // we grant it the capability to request all URLs of that scheme.
+ state->second->GrantScheme(url.scheme());
+ }
+}
+
+void ChildProcessSecurityPolicyImpl::GrantRequestSpecificFileURL(
+ int child_id,
+ const GURL& url) {
+ if (!url.SchemeIs(chrome::kFileScheme))
+ return;
+
+ {
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ // When the child process has been commanded to request a file:// URL,
+ // then we grant it the capability for that URL only.
+ base::FilePath path;
+ if (net::FileURLToFilePath(url, &path))
+ state->second->GrantRequestOfSpecificFile(path);
+ }
+}
+
+void ChildProcessSecurityPolicyImpl::GrantReadFile(int child_id,
+ const base::FilePath& file) {
+ GrantPermissionsForFile(child_id, file, kReadFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantCreateReadWriteFile(
+ int child_id, const base::FilePath& file) {
+ GrantPermissionsForFile(child_id, file, kCreateReadWriteFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantCreateWriteFile(
+ int child_id, const base::FilePath& file) {
+ GrantPermissionsForFile(child_id, file, kCreateWriteFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantReadDirectory(
+ int child_id, const base::FilePath& directory) {
+ GrantPermissionsForFile(child_id, directory, kEnumerateDirectoryPermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantPermissionsForFile(
+ int child_id, const base::FilePath& file, int permissions) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->GrantPermissionsForFile(file, permissions);
+}
+
+void ChildProcessSecurityPolicyImpl::RevokeAllPermissionsForFile(
+ int child_id, const base::FilePath& file) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->RevokeAllPermissionsForFile(file);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantReadFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ GrantPermissionsForFileSystem(child_id, filesystem_id, kReadFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantWriteFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ GrantPermissionsForFileSystem(child_id, filesystem_id, kWriteFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantCreateFileForFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ GrantPermissionsForFileSystem(child_id, filesystem_id,
+ kCreateFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantCopyIntoFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ // TODO(tommycli): These granted permissions a bit too broad, but not abused.
+ // We are fixing in http://crbug.com/262142 and associated CL.
+ GrantPermissionsForFileSystem(child_id, filesystem_id,
+ kCreateFilePermissions);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantScheme(int child_id,
+ const std::string& scheme) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->GrantScheme(scheme);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantWebUIBindings(int child_id) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->GrantBindings(BINDINGS_POLICY_WEB_UI);
+
+ // Web UI bindings need the ability to request chrome: URLs.
+ state->second->GrantScheme(chrome::kChromeUIScheme);
+
+ // Web UI pages can contain links to file:// URLs.
+ state->second->GrantScheme(chrome::kFileScheme);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantReadRawCookies(int child_id) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->GrantReadRawCookies();
+}
+
+void ChildProcessSecurityPolicyImpl::RevokeReadRawCookies(int child_id) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+
+ state->second->RevokeReadRawCookies();
+}
+
+bool ChildProcessSecurityPolicyImpl::CanLoadPage(
+ int child_id,
+ const GURL& url,
+ ResourceType::Type resource_type) {
+ // If --site-per-process flag is passed, we should enforce
+ // stronger security restrictions on page navigation.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess) &&
+ ResourceType::IsFrame(resource_type)) {
+ // TODO(nasko): Do the proper check for site-per-process, once
+ // out-of-process iframes is ready to go.
+ return true;
+ }
+ return true;
+}
+
+bool ChildProcessSecurityPolicyImpl::CanRequestURL(
+ int child_id, const GURL& url) {
+ if (!url.is_valid())
+ return false; // Can't request invalid URLs.
+
+ if (IsWebSafeScheme(url.scheme()))
+ return true; // The scheme has been white-listed for every child process.
+
+ if (IsPseudoScheme(url.scheme())) {
+ // There are a number of special cases for pseudo schemes.
+
+ if (url.SchemeIs(kViewSourceScheme)) {
+ // A view-source URL is allowed if the child process is permitted to
+ // request the embedded URL. Careful to avoid pointless recursion.
+ GURL child_url(url.path());
+ if (child_url.SchemeIs(kViewSourceScheme) &&
+ url.SchemeIs(kViewSourceScheme))
+ return false;
+
+ return CanRequestURL(child_id, child_url);
+ }
+
+ if (LowerCaseEqualsASCII(url.spec(), kAboutBlankURL))
+ return true; // Every child process can request <about:blank>.
+
+ // URLs like <about:memory> and <about:crash> shouldn't be requestable by
+ // any child process. Also, this case covers <javascript:...>, which should
+ // be handled internally by the process and not kicked up to the browser.
+ return false;
+ }
+
+ if (!GetContentClient()->browser()->IsHandledURL(url) &&
+ !net::URLRequest::IsHandledURL(url)) {
+ return true; // This URL request is destined for ShellExecute.
+ }
+
+ {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+
+ // Otherwise, we consult the child process's security state to see if it is
+ // allowed to request the URL.
+ return state->second->CanRequestURL(url);
+ }
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadFile(int child_id,
+ const base::FilePath& file) {
+ return HasPermissionsForFile(child_id, file, kReadFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanWriteFile(int child_id,
+ const base::FilePath& file) {
+ return HasPermissionsForFile(child_id, file, kWriteFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanCreateFile(int child_id,
+ const base::FilePath& file) {
+ return HasPermissionsForFile(child_id, file, kCreateFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanCreateWriteFile(
+ int child_id,
+ const base::FilePath& file) {
+ return HasPermissionsForFile(child_id, file, kCreateWriteFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadDirectory(
+ int child_id, const base::FilePath& directory) {
+ return HasPermissionsForFile(child_id,
+ directory,
+ kEnumerateDirectoryPermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ return HasPermissionsForFileSystem(child_id,
+ filesystem_id,
+ kReadFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadWriteFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ return HasPermissionsForFileSystem(child_id,
+ filesystem_id,
+ kReadFilePermissions |
+ kWriteFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanCopyIntoFileSystem(
+ int child_id, const std::string& filesystem_id) {
+ // TODO(tommycli): These granted permissions a bit too broad, but not abused.
+ // We are fixing in http://crbug.com/262142 and associated CL.
+ return HasPermissionsForFileSystem(child_id,
+ filesystem_id,
+ kCreateFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::HasPermissionsForFile(
+ int child_id, const base::FilePath& file, int permissions) {
+ base::AutoLock lock(lock_);
+ bool result = ChildProcessHasPermissionsForFile(child_id, file, permissions);
+ if (!result) {
+ // If this is a worker thread that has no access to a given file,
+ // let's check that its renderer process has access to that file instead.
+ WorkerToMainProcessMap::iterator iter = worker_map_.find(child_id);
+ if (iter != worker_map_.end() && iter->second != 0) {
+ result = ChildProcessHasPermissionsForFile(iter->second,
+ file,
+ permissions);
+ }
+ }
+ return result;
+}
+
+bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystemFile(
+ int child_id, const fileapi::FileSystemURL& url, int permissions) {
+ if (!url.is_valid())
+ return false;
+
+ if (url.path().ReferencesParent())
+ return false;
+
+ // Any write access is disallowed on the root path.
+ if (fileapi::VirtualPath::IsRootPath(url.path()) &&
+ (permissions & ~kReadFilePermissions)) {
+ return false;
+ }
+
+ if (url.mount_type() == fileapi::kFileSystemTypeIsolated) {
+ // When Isolated filesystems is overlayed on top of another filesystem,
+ // its per-filesystem permission overrides the underlying filesystem
+ // permissions).
+ return HasPermissionsForFileSystem(
+ child_id, url.mount_filesystem_id(), permissions);
+ }
+
+ FileSystemPermissionPolicyMap::iterator found =
+ file_system_policy_map_.find(url.type());
+ if (found == file_system_policy_map_.end())
+ return false;
+
+ if ((found->second & fileapi::FILE_PERMISSION_READ_ONLY) &&
+ permissions & ~kReadFilePermissions) {
+ return false;
+ }
+
+ if (found->second & fileapi::FILE_PERMISSION_USE_FILE_PERMISSION)
+ return HasPermissionsForFile(child_id, url.path(), permissions);
+
+ if (found->second & fileapi::FILE_PERMISSION_SANDBOX)
+ return true;
+
+ return false;
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadFileSystemFile(
+ int child_id,
+ const fileapi::FileSystemURL& url) {
+ return HasPermissionsForFileSystemFile(child_id, url, kReadFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanWriteFileSystemFile(
+ int child_id,
+ const fileapi::FileSystemURL& url) {
+ return HasPermissionsForFileSystemFile(child_id, url, kWriteFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanCreateFileSystemFile(
+ int child_id,
+ const fileapi::FileSystemURL& url) {
+ return HasPermissionsForFileSystemFile(child_id, url, kCreateFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanCreateWriteFileSystemFile(
+ int child_id,
+ const fileapi::FileSystemURL& url) {
+ return HasPermissionsForFileSystemFile(child_id, url,
+ kCreateWriteFilePermissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::HasWebUIBindings(int child_id) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+
+ return state->second->has_web_ui_bindings();
+}
+
+bool ChildProcessSecurityPolicyImpl::CanReadRawCookies(int child_id) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+
+ return state->second->can_read_raw_cookies();
+}
+
+void ChildProcessSecurityPolicyImpl::AddChild(int child_id) {
+ if (security_state_.count(child_id) != 0) {
+ NOTREACHED() << "Add child process at most once.";
+ return;
+ }
+
+ security_state_[child_id] = new SecurityState();
+}
+
+bool ChildProcessSecurityPolicyImpl::ChildProcessHasPermissionsForFile(
+ int child_id, const base::FilePath& file, int permissions) {
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+ return state->second->HasPermissionsForFile(file, permissions);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanAccessCookiesForOrigin(
+ int child_id, const GURL& gurl) {
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+ return state->second->CanAccessCookiesForOrigin(gurl);
+}
+
+bool ChildProcessSecurityPolicyImpl::CanSendCookiesForOrigin(int child_id,
+ const GURL& gurl) {
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+ return state->second->CanSendCookiesForOrigin(gurl);
+}
+
+void ChildProcessSecurityPolicyImpl::LockToOrigin(int child_id,
+ const GURL& gurl) {
+ // "gurl" can be currently empty in some cases, such as file://blah.
+ DCHECK(SiteInstanceImpl::GetSiteForURL(NULL, gurl) == gurl);
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ DCHECK(state != security_state_.end());
+ state->second->LockToOrigin(gurl);
+}
+
+void ChildProcessSecurityPolicyImpl::GrantPermissionsForFileSystem(
+ int child_id,
+ const std::string& filesystem_id,
+ int permission) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return;
+ state->second->GrantPermissionsForFileSystem(filesystem_id, permission);
+}
+
+bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystem(
+ int child_id,
+ const std::string& filesystem_id,
+ int permission) {
+ base::AutoLock lock(lock_);
+
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+ return state->second->HasPermissionsForFileSystem(filesystem_id, permission);
+}
+
+void ChildProcessSecurityPolicyImpl::RegisterFileSystemPermissionPolicy(
+ fileapi::FileSystemType type,
+ int policy) {
+ base::AutoLock lock(lock_);
+ file_system_policy_map_[type] = policy;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/child_process_security_policy_impl.h b/chromium/content/browser/child_process_security_policy_impl.h
new file mode 100644
index 00000000000..3477f1edb1a
--- /dev/null
+++ b/chromium/content/browser/child_process_security_policy_impl.h
@@ -0,0 +1,274 @@
+// 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 CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_IMPL_H_
+#define CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_IMPL_H_
+
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/child_process_security_policy.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/resource_type.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace content {
+
+class CONTENT_EXPORT ChildProcessSecurityPolicyImpl
+ : NON_EXPORTED_BASE(public ChildProcessSecurityPolicy) {
+ public:
+ // Object can only be created through GetInstance() so the constructor is
+ // private.
+ virtual ~ChildProcessSecurityPolicyImpl();
+
+ static ChildProcessSecurityPolicyImpl* GetInstance();
+
+ // ChildProcessSecurityPolicy implementation.
+ virtual void RegisterWebSafeScheme(const std::string& scheme) OVERRIDE;
+ virtual bool IsWebSafeScheme(const std::string& scheme) OVERRIDE;
+ virtual void GrantReadFile(int child_id, const base::FilePath& file) OVERRIDE;
+ virtual void GrantCreateReadWriteFile(int child_id,
+ const base::FilePath& file) OVERRIDE;
+ virtual void GrantCreateWriteFile(int child_id,
+ const base::FilePath& file) OVERRIDE;
+ virtual void GrantReadFileSystem(
+ int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual void GrantWriteFileSystem(
+ int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual void GrantCreateFileForFileSystem(
+ int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual void GrantCopyIntoFileSystem(
+ int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual void GrantScheme(int child_id, const std::string& scheme) OVERRIDE;
+ virtual bool CanReadFile(int child_id, const base::FilePath& file) OVERRIDE;
+ virtual bool CanWriteFile(int child_id, const base::FilePath& file) OVERRIDE;
+ virtual bool CanCreateFile(int child_id, const base::FilePath& file) OVERRIDE;
+ virtual bool CanCreateWriteFile(int child_id,
+ const base::FilePath& file) OVERRIDE;
+ virtual bool CanReadFileSystem(int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual bool CanReadWriteFileSystem(
+ int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+ virtual bool CanCopyIntoFileSystem(int child_id,
+ const std::string& filesystem_id) OVERRIDE;
+
+ // Pseudo schemes are treated differently than other schemes because they
+ // cannot be requested like normal URLs. There is no mechanism for revoking
+ // pseudo schemes.
+ void RegisterPseudoScheme(const std::string& scheme);
+
+ // Returns true iff |scheme| has been registered as pseudo scheme.
+ bool IsPseudoScheme(const std::string& scheme);
+
+ // Upon creation, child processes should register themselves by calling this
+ // this method exactly once.
+ void Add(int child_id);
+
+ // Upon creation, worker thread child processes should register themselves by
+ // calling this this method exactly once. Workers that are not shared will
+ // inherit permissions from their parent renderer process identified with
+ // |main_render_process_id|.
+ void AddWorker(int worker_child_id, int main_render_process_id);
+
+ // Upon destruction, child processess should unregister themselves by caling
+ // this method exactly once.
+ void Remove(int child_id);
+
+ // Whenever the browser processes commands the child process to request a URL,
+ // it should call this method to grant the child process the capability to
+ // request the URL, along with permission to request all URLs of the same
+ // scheme.
+ void GrantRequestURL(int child_id, const GURL& url);
+
+ // Whenever the browser process drops a file icon on a tab, it should call
+ // this method to grant the child process the capability to request this one
+ // file:// URL, but not all urls of the file:// scheme.
+ void GrantRequestSpecificFileURL(int child_id, const GURL& url);
+
+ // Grants the child process permission to enumerate all the files in
+ // this directory and read those files.
+ void GrantReadDirectory(int child_id, const base::FilePath& directory);
+
+ // Revokes all permissions granted to the given file.
+ void RevokeAllPermissionsForFile(int child_id, const base::FilePath& file);
+
+ // Grant the child process the ability to use Web UI Bindings.
+ void GrantWebUIBindings(int child_id);
+
+ // Grant the child process the ability to read raw cookies.
+ void GrantReadRawCookies(int child_id);
+
+ // Revoke read raw cookies permission.
+ void RevokeReadRawCookies(int child_id);
+
+ // Before servicing a child process's request for a URL, the browser should
+ // call this method to determine whether the process has the capability to
+ // request the URL.
+ bool CanRequestURL(int child_id, const GURL& url);
+
+ // Returns true if the process is permitted to load pages from
+ // the given origin in main frames or subframes.
+ // Only might return false if --site-per-process flag is used.
+ bool CanLoadPage(int child_id,
+ const GURL& url,
+ ResourceType::Type resource_type);
+
+ // Before servicing a child process's request to enumerate a directory
+ // the browser should call this method to check for the capability.
+ bool CanReadDirectory(int child_id, const base::FilePath& directory);
+
+ // Deprecated: Use CanReadFile, etc. methods instead.
+ // Determines if certain permissions were granted for a file. |permissions|
+ // must be a bitwise-or'd value of base::PlatformFileFlags.
+ bool HasPermissionsForFile(int child_id,
+ const base::FilePath& file,
+ int permissions);
+
+ // Deprecated: Use CanReadFileSystemFile, etc. methods instead.
+ // Determines if certain permissions were granted for a file in FileSystem
+ // API. |permissions| must be a bitwise-or'd value of base::PlatformFileFlags.
+ bool HasPermissionsForFileSystemFile(int child_id,
+ const fileapi::FileSystemURL& url,
+ int permissions);
+
+ // Explicit permissions checks for FileSystemURL specified files.
+ bool CanReadFileSystemFile(int child_id, const fileapi::FileSystemURL& url);
+ bool CanWriteFileSystemFile(int child_id, const fileapi::FileSystemURL& url);
+ bool CanCreateFileSystemFile(int child_id, const fileapi::FileSystemURL& url);
+ bool CanCreateWriteFileSystemFile(int child_id,
+ const fileapi::FileSystemURL& url);
+
+ // Returns true if the specified child_id has been granted WebUIBindings.
+ // The browser should check this property before assuming the child process is
+ // allowed to use WebUIBindings.
+ bool HasWebUIBindings(int child_id);
+
+ // Returns true if the specified child_id has been granted ReadRawCookies.
+ bool CanReadRawCookies(int child_id);
+
+ // Returns true if the process is permitted to read and modify the cookies for
+ // the given origin. Does not affect cookies attached to or set by network
+ // requests.
+ // Only might return false if the very experimental
+ // --enable-strict-site-isolation or --site-per-process flags are used.
+ bool CanAccessCookiesForOrigin(int child_id, const GURL& gurl);
+
+ // Returns true if the process is permitted to attach cookies to (or have
+ // cookies set by) network requests.
+ // Only might return false if the very experimental
+ // --enable-strict-site-isolation or --site-per-process flags are used.
+ bool CanSendCookiesForOrigin(int child_id, const GURL& gurl);
+
+ // Sets the process as only permitted to use and see the cookies for the
+ // given origin.
+ // Only used if the very experimental --enable-strict-site-isolation or
+ // --site-per-process flags are used.
+ void LockToOrigin(int child_id, const GURL& gurl);
+
+ // Determines if certain permissions were granted for a file fystem.
+ // |permissions| must be a bitwise-or'd value of base::PlatformFileFlags.
+ bool HasPermissionsForFileSystem(
+ int child_id,
+ const std::string& filesystem_id,
+ int permission);
+
+ // Register FileSystem type and permission policy which should be used
+ // for the type. The |policy| must be a bitwise-or'd value of
+ // fileapi::FilePermissionPolicy.
+ void RegisterFileSystemPermissionPolicy(
+ fileapi::FileSystemType type,
+ int policy);
+
+ private:
+ friend class ChildProcessSecurityPolicyInProcessBrowserTest;
+ friend class ChildProcessSecurityPolicyTest;
+ FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyInProcessBrowserTest,
+ NoLeak);
+
+ class SecurityState;
+
+ typedef std::set<std::string> SchemeSet;
+ typedef std::map<int, SecurityState*> SecurityStateMap;
+ typedef std::map<int, int> WorkerToMainProcessMap;
+ typedef std::map<fileapi::FileSystemType, int> FileSystemPermissionPolicyMap;
+
+ // Obtain an instance of ChildProcessSecurityPolicyImpl via GetInstance().
+ ChildProcessSecurityPolicyImpl();
+ friend struct DefaultSingletonTraits<ChildProcessSecurityPolicyImpl>;
+
+ // Adds child process during registration.
+ void AddChild(int child_id);
+
+ // Determines if certain permissions were granted for a file to given child
+ // process. |permissions| must be a bitwise-or'd value of
+ // base::PlatformFileFlags.
+ bool ChildProcessHasPermissionsForFile(int child_id,
+ const base::FilePath& file,
+ int permissions);
+
+ // Grant a particular permission set for a file. |permissions| is a bit-set
+ // of base::PlatformFileFlags.
+ void GrantPermissionsForFile(int child_id,
+ const base::FilePath& file,
+ int permissions);
+
+ // Grants access permission to the given isolated file system
+ // identified by |filesystem_id|. See comments for
+ // ChildProcessSecurityPolicy::GrantReadFileSystem() for more details.
+ void GrantPermissionsForFileSystem(
+ int child_id,
+ const std::string& filesystem_id,
+ int permission);
+
+ // You must acquire this lock before reading or writing any members of this
+ // class. You must not block while holding this lock.
+ base::Lock lock_;
+
+ // These schemes are white-listed for all child processes. This set is
+ // protected by |lock_|.
+ SchemeSet web_safe_schemes_;
+
+ // These schemes do not actually represent retrievable URLs. For example,
+ // the the URLs in the "about" scheme are aliases to other URLs. This set is
+ // protected by |lock_|.
+ SchemeSet pseudo_schemes_;
+
+ // This map holds a SecurityState for each child process. The key for the
+ // map is the ID of the ChildProcessHost. The SecurityState objects are
+ // owned by this object and are protected by |lock_|. References to them must
+ // not escape this class.
+ SecurityStateMap security_state_;
+
+ // This maps keeps the record of which js worker thread child process
+ // corresponds to which main js thread child process.
+ WorkerToMainProcessMap worker_map_;
+
+ FileSystemPermissionPolicyMap file_system_policy_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildProcessSecurityPolicyImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_IMPL_H_
diff --git a/chromium/content/browser/child_process_security_policy_unittest.cc b/chromium/content/browser/child_process_security_policy_unittest.cc
new file mode 100644
index 00000000000..b914eac549a
--- /dev/null
+++ b/chromium/content/browser/child_process_security_policy_unittest.cc
@@ -0,0 +1,717 @@
+// 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 <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/platform_file.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/public/common/url_constants.h"
+#include "content/test/test_content_browser_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace content {
+namespace {
+
+const int kRendererID = 42;
+const int kWorkerRendererID = kRendererID + 1;
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define TEST_PATH(x) FILE_PATH_LITERAL("c:") FILE_PATH_LITERAL(x)
+#else
+#define TEST_PATH(x) FILE_PATH_LITERAL(x)
+#endif
+
+class ChildProcessSecurityPolicyTestBrowserClient
+ : public TestContentBrowserClient {
+ public:
+ ChildProcessSecurityPolicyTestBrowserClient() {}
+
+ virtual bool IsHandledURL(const GURL& url) OVERRIDE {
+ return schemes_.find(url.scheme()) != schemes_.end();
+ }
+
+ void ClearSchemes() {
+ schemes_.clear();
+ }
+
+ void AddScheme(const std::string& scheme) {
+ schemes_.insert(scheme);
+ }
+
+ private:
+ std::set<std::string> schemes_;
+};
+
+} // namespace
+
+class ChildProcessSecurityPolicyTest : public testing::Test {
+ public:
+ ChildProcessSecurityPolicyTest() : old_browser_client_(NULL) {
+ }
+
+ virtual void SetUp() {
+ old_browser_client_ = SetBrowserClientForTesting(&test_browser_client_);
+
+ // Claim to always handle chrome:// URLs because the CPSP's notion of
+ // allowing WebUI bindings is hard-wired to this particular scheme.
+ test_browser_client_.AddScheme(chrome::kChromeUIScheme);
+
+ // Claim to always handle file:// URLs like the browser would.
+ // net::URLRequest::IsHandledURL() no longer claims support for default
+ // protocols as this is the responsibility of the browser (which is
+ // responsible for adding the appropriate ProtocolHandler).
+ test_browser_client_.AddScheme(chrome::kFileScheme);
+ }
+
+ virtual void TearDown() {
+ test_browser_client_.ClearSchemes();
+ SetBrowserClientForTesting(old_browser_client_);
+ }
+
+ protected:
+ void RegisterTestScheme(const std::string& scheme) {
+ test_browser_client_.AddScheme(scheme);
+ }
+
+ void GrantPermissionsForFile(ChildProcessSecurityPolicyImpl* p,
+ int child_id,
+ const base::FilePath& file,
+ int permissions) {
+ p->GrantPermissionsForFile(child_id, file, permissions);
+ }
+
+ private:
+ ChildProcessSecurityPolicyTestBrowserClient test_browser_client_;
+ ContentBrowserClient* old_browser_client_;
+};
+
+
+TEST_F(ChildProcessSecurityPolicyTest, IsWebSafeSchemeTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kHttpScheme));
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kHttpsScheme));
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kFtpScheme));
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kDataScheme));
+ EXPECT_TRUE(p->IsWebSafeScheme("feed"));
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kBlobScheme));
+ EXPECT_TRUE(p->IsWebSafeScheme(chrome::kFileSystemScheme));
+
+ EXPECT_FALSE(p->IsWebSafeScheme("registered-web-safe-scheme"));
+ p->RegisterWebSafeScheme("registered-web-safe-scheme");
+ EXPECT_TRUE(p->IsWebSafeScheme("registered-web-safe-scheme"));
+
+ EXPECT_FALSE(p->IsWebSafeScheme(chrome::kChromeUIScheme));
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, IsPseudoSchemeTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ EXPECT_TRUE(p->IsPseudoScheme(chrome::kAboutScheme));
+ EXPECT_TRUE(p->IsPseudoScheme(chrome::kJavaScriptScheme));
+ EXPECT_TRUE(p->IsPseudoScheme(kViewSourceScheme));
+
+ EXPECT_FALSE(p->IsPseudoScheme("registered-pseudo-scheme"));
+ p->RegisterPseudoScheme("registered-pseudo-scheme");
+ EXPECT_TRUE(p->IsPseudoScheme("registered-pseudo-scheme"));
+
+ EXPECT_FALSE(p->IsPseudoScheme(chrome::kChromeUIScheme));
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, StandardSchemesTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ // Safe
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("http://www.google.com/")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("https://www.paypal.com/")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("ftp://ftp.gnu.org/")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("data:text/html,<b>Hi</b>")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID,
+ GURL("view-source:http://www.google.com/")));
+ EXPECT_TRUE(p->CanRequestURL(
+ kRendererID, GURL("filesystem:http://localhost/temporary/a.gif")));
+
+ // Dangerous
+ EXPECT_FALSE(p->CanRequestURL(kRendererID,
+ GURL("file:///etc/passwd")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID,
+ GURL("chrome://foo/bar")));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, AboutTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:blank")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:BlAnK")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:BlAnK")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:blank")));
+
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:memory")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:cache")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:hang")));
+
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBoUt:memory")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:CrASh")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("abOuT:cAChe")));
+
+ // Requests for about: pages should be denied.
+ p->GrantRequestURL(kRendererID, GURL("about:crash"));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
+
+ // These requests for chrome:// pages should be granted.
+ GURL chrome_url("chrome://foo");
+ p->GrantRequestURL(kRendererID, chrome_url);
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, chrome_url));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, JavaScriptTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
+ p->GrantRequestURL(kRendererID, GURL("javascript:alert('xss')"));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, RegisterWebSafeSchemeTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ // Currently, "asdf" is destined for ShellExecute, so it is allowed.
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
+
+ // Once we register "asdf", we default to deny.
+ RegisterTestScheme("asdf");
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
+
+ // We can allow new schemes by adding them to the whitelist.
+ p->RegisterWebSafeScheme("asdf");
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
+
+ // Cleanup.
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, CanServiceCommandsTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
+ p->GrantRequestURL(kRendererID, GURL("file:///etc/passwd"));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
+
+ // We should forget our state if we repeat a renderer id.
+ p->Remove(kRendererID);
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, ViewSource) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ // View source is determined by the embedded scheme.
+ EXPECT_TRUE(p->CanRequestURL(kRendererID,
+ GURL("view-source:http://www.google.com/")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID,
+ GURL("view-source:file:///etc/passwd")));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
+ EXPECT_FALSE(p->CanRequestURL(
+ kRendererID, GURL("view-source:view-source:http://www.google.com/")));
+
+ p->GrantRequestURL(kRendererID, GURL("view-source:file:///etc/passwd"));
+ // View source needs to be able to request the embedded scheme.
+ EXPECT_TRUE(p->CanRequestURL(kRendererID,
+ GURL("view-source:file:///etc/passwd")));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, SpecificFile) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ GURL icon_url("file:///tmp/foo.png");
+ GURL sensitive_url("file:///etc/passwd");
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, icon_url));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
+
+ p->GrantRequestSpecificFileURL(kRendererID, icon_url);
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
+
+ p->GrantRequestURL(kRendererID, icon_url);
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, sensitive_url));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, FileSystemGrantsTest) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+ std::string read_id = fileapi::IsolatedContext::GetInstance()->
+ RegisterFileSystemForVirtualPath(fileapi::kFileSystemTypeTest,
+ "read_filesystem",
+ base::FilePath());
+ std::string read_write_id = fileapi::IsolatedContext::GetInstance()->
+ RegisterFileSystemForVirtualPath(fileapi::kFileSystemTypeTest,
+ "read_write_filesystem",
+ base::FilePath());
+ std::string copy_into_id = fileapi::IsolatedContext::GetInstance()->
+ RegisterFileSystemForVirtualPath(fileapi::kFileSystemTypeTest,
+ "copy_into_filesystem",
+ base::FilePath());
+
+ // Test initially having no permissions.
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
+
+ // Testing varying combinations of grants and checks.
+ p->GrantReadFileSystem(kRendererID, read_id);
+ EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
+
+ p->GrantReadFileSystem(kRendererID, read_write_id);
+ p->GrantWriteFileSystem(kRendererID, read_write_id);
+ EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_write_id));
+ EXPECT_TRUE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
+
+ p->GrantCopyIntoFileSystem(kRendererID, copy_into_id);
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
+ EXPECT_TRUE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
+
+ // Test revoke permissions on renderer ID removal.
+ p->Remove(kRendererID);
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
+
+ // Test having no permissions upon re-adding same renderer ID.
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
+
+ EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
+ EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
+
+ // Cleanup.
+ p->Remove(kRendererID);
+ fileapi::IsolatedContext::GetInstance()->RevokeFileSystem(read_id);
+ fileapi::IsolatedContext::GetInstance()->RevokeFileSystem(read_write_id);
+ fileapi::IsolatedContext::GetInstance()->RevokeFileSystem(copy_into_id);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, FilePermissionGrantingAndRevoking) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->RegisterFileSystemPermissionPolicy(
+ fileapi::kFileSystemTypeTest,
+ fileapi::FILE_PERMISSION_USE_FILE_PERMISSION);
+
+ p->Add(kRendererID);
+ base::FilePath file(TEST_PATH("/dir/testfile"));
+ file = file.NormalizePathSeparators();
+ fileapi::FileSystemURL url = fileapi::FileSystemURL::CreateForTest(
+ GURL("http://foo/"), fileapi::kFileSystemTypeTest, file);
+
+ // Test initially having no permissions.
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ // Testing every combination of permissions granting and revoking.
+ p->GrantReadFile(kRendererID, file);
+ EXPECT_TRUE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+ p->RevokeAllPermissionsForFile(kRendererID, file);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ p->GrantCreateReadWriteFile(kRendererID, file);
+ EXPECT_TRUE(p->CanReadFile(kRendererID, file));
+ EXPECT_TRUE(p->CanWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+ p->RevokeAllPermissionsForFile(kRendererID, file);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ p->GrantCreateWriteFile(kRendererID, file);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_TRUE(p->CanWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+ p->RevokeAllPermissionsForFile(kRendererID, file);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ // Test revoke permissions on renderer ID removal.
+ p->GrantCreateReadWriteFile(kRendererID, file);
+ EXPECT_TRUE(p->CanReadFile(kRendererID, file));
+ EXPECT_TRUE(p->CanWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateFile(kRendererID, file));
+ EXPECT_TRUE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_TRUE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+ p->Remove(kRendererID);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ // Test having no permissions upon re-adding same renderer ID.
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->CanWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateFile(kRendererID, file));
+ EXPECT_FALSE(p->CanCreateWriteFile(kRendererID, file));
+ EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
+ EXPECT_FALSE(p->CanCreateWriteFileSystemFile(kRendererID, url));
+
+ // Cleanup.
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, CanReadDirectories) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ p->Add(kRendererID);
+
+ EXPECT_FALSE(p->CanReadDirectory(kRendererID,
+ base::FilePath(TEST_PATH("/etc/"))));
+ p->GrantReadDirectory(kRendererID,
+ base::FilePath(TEST_PATH("/etc/")));
+ EXPECT_TRUE(p->CanReadDirectory(kRendererID,
+ base::FilePath(TEST_PATH("/etc/"))));
+ EXPECT_TRUE(p->CanReadFile(kRendererID,
+ base::FilePath(TEST_PATH("/etc/passwd"))));
+
+ p->Remove(kRendererID);
+ p->Add(kRendererID);
+
+ EXPECT_FALSE(p->CanReadDirectory(kRendererID,
+ base::FilePath(TEST_PATH("/etc/"))));
+ EXPECT_FALSE(p->CanReadFile(kRendererID,
+ base::FilePath(TEST_PATH("/etc/passwd"))));
+
+ // Just granting read permission as a file doesn't imply reading as a
+ // directory.
+ p->GrantReadFile(kRendererID, base::FilePath(TEST_PATH("/etc/")));
+ EXPECT_TRUE(p->CanReadFile(kRendererID,
+ base::FilePath(TEST_PATH("/etc/passwd"))));
+ EXPECT_FALSE(p->CanReadDirectory(kRendererID,
+ base::FilePath(TEST_PATH("/etc/"))));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, FilePermissions) {
+ base::FilePath granted_file = base::FilePath(TEST_PATH("/home/joe"));
+ base::FilePath sibling_file = base::FilePath(TEST_PATH("/home/bob"));
+ base::FilePath child_file = base::FilePath(TEST_PATH("/home/joe/file"));
+ base::FilePath parent_file = base::FilePath(TEST_PATH("/home"));
+ base::FilePath parent_slash_file = base::FilePath(TEST_PATH("/home/"));
+ base::FilePath child_traversal1 =
+ base::FilePath(TEST_PATH("/home/joe/././file"));
+ base::FilePath child_traversal2 = base::FilePath(
+ TEST_PATH("/home/joe/file/../otherfile"));
+ base::FilePath evil_traversal1 =
+ base::FilePath(TEST_PATH("/home/joe/../../etc/passwd"));
+ base::FilePath evil_traversal2 = base::FilePath(
+ TEST_PATH("/home/joe/./.././../etc/passwd"));
+ base::FilePath self_traversal =
+ base::FilePath(TEST_PATH("/home/joe/../joe/file"));
+ base::FilePath relative_file = base::FilePath(FILE_PATH_LITERAL("home/joe"));
+
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // Grant permissions for a file.
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+
+ GrantPermissionsForFile(p, kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_OPEN_TRUNCATED |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE);
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_OPEN_TRUNCATED |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE));
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_CREATE));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file, 0));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_OPEN_TRUNCATED |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, sibling_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, parent_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal1,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal2,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal1,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal2,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ // CPSP doesn't allow this case for the sake of simplicity.
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, self_traversal,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ p->Remove(kRendererID);
+
+ // Grant permissions for the directory the file is in.
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+ GrantPermissionsForFile(p, kRendererID, parent_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ);
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE));
+ p->Remove(kRendererID);
+
+ // Grant permissions for the directory the file is in (with trailing '/').
+ p->Add(kRendererID);
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+ GrantPermissionsForFile(p, kRendererID, parent_slash_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ);
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE));
+
+ // Grant permissions for the file (should overwrite the permissions granted
+ // for the directory).
+ GrantPermissionsForFile(p, kRendererID, granted_file,
+ base::PLATFORM_FILE_TEMPORARY);
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN));
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_TEMPORARY));
+
+ // Revoke all permissions for the file (it should inherit its permissions
+ // from the directory again).
+ p->RevokeAllPermissionsForFile(kRendererID, granted_file);
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_TEMPORARY));
+ p->Remove(kRendererID);
+
+ // Grant file permissions for the file to main thread renderer process,
+ // make sure its worker thread renderer process inherits those.
+ p->Add(kRendererID);
+ GrantPermissionsForFile(p, kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ);
+ EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
+ base::PLATFORM_FILE_WRITE));
+ p->AddWorker(kWorkerRendererID, kRendererID);
+ EXPECT_TRUE(p->HasPermissionsForFile(kWorkerRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ EXPECT_FALSE(p->HasPermissionsForFile(kWorkerRendererID, granted_file,
+ base::PLATFORM_FILE_WRITE));
+ p->Remove(kRendererID);
+ EXPECT_FALSE(p->HasPermissionsForFile(kWorkerRendererID, granted_file,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ));
+ p->Remove(kWorkerRendererID);
+
+ p->Add(kRendererID);
+ GrantPermissionsForFile(p, kRendererID, relative_file,
+ base::PLATFORM_FILE_OPEN);
+ EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, relative_file,
+ base::PLATFORM_FILE_OPEN));
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, CanServiceWebUIBindings) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ GURL url("chrome://thumb/http://www.google.com/");
+
+ p->Add(kRendererID);
+
+ EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
+ p->GrantWebUIBindings(kRendererID);
+ EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
+
+ p->Remove(kRendererID);
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, RemoveRace) {
+ ChildProcessSecurityPolicyImpl* p =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ GURL url("file:///etc/passwd");
+ base::FilePath file(TEST_PATH("/etc/passwd"));
+
+ p->Add(kRendererID);
+
+ p->GrantRequestURL(kRendererID, url);
+ p->GrantReadFile(kRendererID, file);
+ p->GrantWebUIBindings(kRendererID);
+
+ EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
+ EXPECT_TRUE(p->CanReadFile(kRendererID, file));
+ EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
+
+ p->Remove(kRendererID);
+
+ // Renderers are added and removed on the UI thread, but the policy can be
+ // queried on the IO thread. The ChildProcessSecurityPolicy needs to be
+ // prepared to answer policy questions about renderers who no longer exist.
+
+ // In this case, we default to secure behavior.
+ EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
+ EXPECT_FALSE(p->CanReadFile(kRendererID, file));
+ EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/cross_site_request_manager.cc b/chromium/content/browser/cross_site_request_manager.cc
new file mode 100644
index 00000000000..2fa38b185ab
--- /dev/null
+++ b/chromium/content/browser/cross_site_request_manager.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/cross_site_request_manager.h"
+
+#include "base/memory/singleton.h"
+
+namespace content {
+
+bool CrossSiteRequestManager::HasPendingCrossSiteRequest(int renderer_id,
+ int render_view_id) {
+ base::AutoLock lock(lock_);
+
+ std::pair<int, int> key(renderer_id, render_view_id);
+ return pending_cross_site_views_.find(key) !=
+ pending_cross_site_views_.end();
+}
+
+void CrossSiteRequestManager::SetHasPendingCrossSiteRequest(int renderer_id,
+ int render_view_id,
+ bool has_pending) {
+ base::AutoLock lock(lock_);
+
+ std::pair<int, int> key(renderer_id, render_view_id);
+ if (has_pending) {
+ pending_cross_site_views_.insert(key);
+ } else {
+ pending_cross_site_views_.erase(key);
+ }
+}
+
+CrossSiteRequestManager::CrossSiteRequestManager() {}
+
+CrossSiteRequestManager::~CrossSiteRequestManager() {}
+
+// static
+CrossSiteRequestManager* CrossSiteRequestManager::GetInstance() {
+ return Singleton<CrossSiteRequestManager>::get();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/cross_site_request_manager.h b/chromium/content/browser/cross_site_request_manager.h
new file mode 100644
index 00000000000..29417d7a868
--- /dev/null
+++ b/chromium/content/browser/cross_site_request_manager.h
@@ -0,0 +1,64 @@
+// 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 CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_
+#define CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_
+
+#include <set>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace content {
+
+// CrossSiteRequestManager is used to handle bookkeeping for cross-site
+// requests and responses between the UI and IO threads. Such requests involve
+// a transition from one RenderViewHost to another within WebContentsImpl, and
+// involve coordination with ResourceDispatcherHost.
+//
+// CrossSiteRequestManager is a singleton that may be used on any thread.
+//
+class CrossSiteRequestManager {
+ public:
+ // Returns the singleton instance.
+ static CrossSiteRequestManager* GetInstance();
+
+ // Returns whether the RenderViewHost specified by the given IDs currently
+ // has a pending cross-site request. If so, we will have to delay the
+ // response until the previous RenderViewHost runs its onunload handler.
+ // Called by ResourceDispatcherHost on the IO thread and RenderViewHost on
+ // the UI thread.
+ bool HasPendingCrossSiteRequest(int renderer_id, int render_view_id);
+
+ // Sets whether the RenderViewHost specified by the given IDs currently has a
+ // pending cross-site request. Called by RenderViewHost on the UI thread.
+ void SetHasPendingCrossSiteRequest(int renderer_id,
+ int render_view_id,
+ bool has_pending);
+
+ private:
+ friend struct DefaultSingletonTraits<CrossSiteRequestManager>;
+ typedef std::set<std::pair<int, int> > RenderViewSet;
+
+ CrossSiteRequestManager();
+ ~CrossSiteRequestManager();
+
+ // You must acquire this lock before reading or writing any members of this
+ // class. You must not block while holding this lock.
+ base::Lock lock_;
+
+ // Set of (render_process_host_id, render_view_id) pairs of all
+ // RenderViewHosts that have pending cross-site requests. Used to pass
+ // information about the RenderViewHosts between the UI and IO threads.
+ RenderViewSet pending_cross_site_views_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrossSiteRequestManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_
diff --git a/chromium/content/browser/database_browsertest.cc b/chromium/content/browser/database_browsertest.cc
new file mode 100644
index 00000000000..305d8ff94c0
--- /dev/null
+++ b/chromium/content/browser/database_browsertest.cc
@@ -0,0 +1,270 @@
+// 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 "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class DatabaseTest : public ContentBrowserTest {
+ public:
+ DatabaseTest() {}
+
+ void RunScriptAndCheckResult(Shell* shell,
+ const std::string& script,
+ const std::string& result) {
+ std::string data;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell->web_contents(),
+ script,
+ &data));
+ ASSERT_EQ(data, result);
+ }
+
+ void Navigate(Shell* shell) {
+ NavigateToURL(shell, GetTestUrl("", "simple_database.html"));
+ }
+
+ void CreateTable(Shell* shell) {
+ RunScriptAndCheckResult(shell, "createTable()", "done");
+ }
+
+ void InsertRecord(Shell* shell, const std::string& data) {
+ RunScriptAndCheckResult(shell, "insertRecord('" + data + "')", "done");
+ }
+
+ void UpdateRecord(Shell* shell, int index, const std::string& data) {
+ RunScriptAndCheckResult(
+ shell,
+ "updateRecord(" + base::IntToString(index) + ", '" + data + "')",
+ "done");
+ }
+
+ void DeleteRecord(Shell* shell, int index) {
+ RunScriptAndCheckResult(
+ shell, "deleteRecord(" + base::IntToString(index) + ")", "done");
+ }
+
+ void CompareRecords(Shell* shell, const std::string& expected) {
+ RunScriptAndCheckResult(shell, "getRecords()", expected);
+ }
+
+ bool HasTable(Shell* shell) {
+ std::string data;
+ CHECK(ExecuteScriptAndExtractString(
+ shell->web_contents(),
+ "getRecords()",
+ &data));
+ return data != "getRecords error: [object SQLError]";
+ }
+};
+
+// Insert records to the database.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, InsertRecord) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+ CompareRecords(shell(), "text");
+ InsertRecord(shell(), "text2");
+ CompareRecords(shell(), "text, text2");
+}
+
+// Update records in the database.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, UpdateRecord) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+ UpdateRecord(shell(), 0, "0");
+ CompareRecords(shell(), "0");
+
+ InsertRecord(shell(), "1");
+ InsertRecord(shell(), "2");
+ UpdateRecord(shell(), 1, "1000");
+ CompareRecords(shell(), "0, 1000, 2");
+}
+
+// Delete records in the database.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, DeleteRecord) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+ DeleteRecord(shell(), 0);
+ CompareRecords(shell(), std::string());
+
+ InsertRecord(shell(), "0");
+ InsertRecord(shell(), "1");
+ InsertRecord(shell(), "2");
+ DeleteRecord(shell(), 1);
+ CompareRecords(shell(), "0, 2");
+}
+
+// Attempts to delete a nonexistent row in the table.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, DeleteNonexistentRow) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+
+ RunScriptAndCheckResult(
+ shell(), "deleteRecord(1)", "could not find row with index: 1");
+
+ CompareRecords(shell(), "text");
+}
+
+// Insert, update, and delete records in the database.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, DatabaseOperations) {
+ Navigate(shell());
+ CreateTable(shell());
+
+ std::string expected;
+ for (int i = 0; i < 10; ++i) {
+ std::string item = base::IntToString(i);
+ InsertRecord(shell(), item);
+ if (!expected.empty())
+ expected += ", ";
+ expected += item;
+ }
+ CompareRecords(shell(), expected);
+
+ expected.clear();
+ for (int i = 0; i < 10; ++i) {
+ std::string item = base::IntToString(i * i);
+ UpdateRecord(shell(), i, item);
+ if (!expected.empty())
+ expected += ", ";
+ expected += item;
+ }
+ CompareRecords(shell(), expected);
+
+ for (int i = 0; i < 10; ++i)
+ DeleteRecord(shell(), 0);
+
+ CompareRecords(shell(), std::string());
+
+ RunScriptAndCheckResult(
+ shell(), "deleteRecord(1)", "could not find row with index: 1");
+
+ CompareRecords(shell(), std::string());
+}
+
+// Create records in the database and verify they persist after reload.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, ReloadPage) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+
+ WindowedNotificationObserver load_stop_observer(
+ NOTIFICATION_LOAD_STOP,
+ NotificationService::AllSources());
+ shell()->Reload();
+ load_stop_observer.Wait();
+
+ CompareRecords(shell(), "text");
+}
+
+// Attempt to read a database created in a regular browser from an off the
+// record browser.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, OffTheRecordCannotReadRegularDatabase) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+
+ Shell* otr = CreateOffTheRecordBrowser();
+ Navigate(otr);
+ ASSERT_FALSE(HasTable(otr));
+
+ CreateTable(otr);
+ CompareRecords(otr, std::string());
+}
+
+// Attempt to read a database created in an off the record browser from a
+// regular browser.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, RegularCannotReadOffTheRecordDatabase) {
+ Shell* otr = CreateOffTheRecordBrowser();
+ Navigate(otr);
+ CreateTable(otr);
+ InsertRecord(otr, "text");
+
+ Navigate(shell());
+ ASSERT_FALSE(HasTable(shell()));
+ CreateTable(shell());
+ CompareRecords(shell(), std::string());
+}
+
+// Verify DB changes within first window are present in the second window.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, ModificationPersistInSecondTab) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+
+ Shell* shell2 = CreateBrowser();
+ Navigate(shell2);
+ UpdateRecord(shell2, 0, "0");
+
+ CompareRecords(shell(), "0");
+ CompareRecords(shell2, "0");
+}
+
+// Verify database modifications persist after restarting browser.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, PRE_DatabasePersistsAfterRelaunch) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "text");
+}
+
+IN_PROC_BROWSER_TEST_F(DatabaseTest, DatabasePersistsAfterRelaunch) {
+ Navigate(shell());
+ CompareRecords(shell(), "text");
+}
+
+// Verify OTR database is removed after OTR window closes.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, PRE_OffTheRecordDatabaseNotPersistent) {
+ Shell* otr = CreateOffTheRecordBrowser();
+ Navigate(otr);
+ CreateTable(otr);
+ InsertRecord(otr, "text");
+}
+
+IN_PROC_BROWSER_TEST_F(DatabaseTest, OffTheRecordDatabaseNotPersistent) {
+ Shell* otr = CreateOffTheRecordBrowser();
+ Navigate(otr);
+ ASSERT_FALSE(HasTable(otr));
+}
+
+// Verify database modifications persist after crashing window.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, ModificationsPersistAfterRendererCrash) {
+ Navigate(shell());
+ CreateTable(shell());
+ InsertRecord(shell(), "1");
+
+ CrashTab(shell()->web_contents());
+ Navigate(shell());
+ CompareRecords(shell(), "1");
+}
+
+// Test to check if database modifications are persistent across windows in
+// off the record window.
+IN_PROC_BROWSER_TEST_F(DatabaseTest, OffTheRecordDBPersistentAcrossWindows) {
+ Shell* otr1 = CreateOffTheRecordBrowser();
+ Navigate(otr1);
+ CreateTable(otr1);
+ InsertRecord(otr1, "text");
+
+ Shell* otr2 = CreateOffTheRecordBrowser();
+ Navigate(otr2);
+ CompareRecords(otr2, "text");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_monitor_linux.cc b/chromium/content/browser/device_monitor_linux.cc
new file mode 100644
index 00000000000..61aad7c113c
--- /dev/null
+++ b/chromium/content/browser/device_monitor_linux.cc
@@ -0,0 +1,87 @@
+// 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.
+
+// libudev is used for monitoring device changes.
+
+#include "content/browser/device_monitor_linux.h"
+
+#include <libudev.h>
+
+#include <string>
+
+#include "base/system_monitor/system_monitor.h"
+#include "content/browser/udev_linux.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace {
+
+struct SubsystemMap {
+ base::SystemMonitor::DeviceType device_type;
+ const char* subsystem;
+ const char* devtype;
+};
+
+const char kAudioSubsystem[] = "sound";
+const char kVideoSubsystem[] = "video4linux";
+
+// Add more subsystems here for monitoring.
+const SubsystemMap kSubsystemMap[] = {
+ { base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE, kAudioSubsystem, NULL },
+ { base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE, kVideoSubsystem, NULL },
+};
+
+} // namespace
+
+namespace content {
+
+DeviceMonitorLinux::DeviceMonitorLinux() {
+ DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO));
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DeviceMonitorLinux::Initialize, base::Unretained(this)));
+}
+
+DeviceMonitorLinux::~DeviceMonitorLinux() {
+}
+
+void DeviceMonitorLinux::Initialize() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // We want to be notified of IO message loop destruction to delete |udev_|.
+ base::MessageLoop::current()->AddDestructionObserver(this);
+
+ std::vector<UdevLinux::UdevMonitorFilter> filters;
+ for (size_t i = 0; i < arraysize(kSubsystemMap); ++i) {
+ filters.push_back(UdevLinux::UdevMonitorFilter(
+ kSubsystemMap[i].subsystem, kSubsystemMap[i].devtype));
+ }
+ udev_.reset(new UdevLinux(filters,
+ base::Bind(&DeviceMonitorLinux::OnDevicesChanged,
+ base::Unretained(this))));
+}
+
+void DeviceMonitorLinux::WillDestroyCurrentMessageLoop() {
+ // Called on IO thread.
+ udev_.reset();
+}
+
+void DeviceMonitorLinux::OnDevicesChanged(udev_device* device) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(device);
+
+ base::SystemMonitor::DeviceType device_type =
+ base::SystemMonitor::DEVTYPE_UNKNOWN;
+ std::string subsystem(udev_device_get_subsystem(device));
+ for (size_t i = 0; i < arraysize(kSubsystemMap); ++i) {
+ if (subsystem == kSubsystemMap[i].subsystem) {
+ device_type = kSubsystemMap[i].device_type;
+ break;
+ }
+ }
+ DCHECK_NE(device_type, base::SystemMonitor::DEVTYPE_UNKNOWN);
+
+ base::SystemMonitor::Get()->ProcessDevicesChanged(device_type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_monitor_linux.h b/chromium/content/browser/device_monitor_linux.h
new file mode 100644
index 00000000000..b56de1ebdc0
--- /dev/null
+++ b/chromium/content/browser/device_monitor_linux.h
@@ -0,0 +1,44 @@
+// 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 class is used to detect device change and notify base::SystemMonitor
+// on Linux.
+
+#ifndef CONTENT_BROWSER_DEVICE_MONITOR_LINUX_H_
+#define CONTENT_BROWSER_DEVICE_MONITOR_LINUX_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+
+extern "C" {
+struct udev_device;
+}
+
+namespace content {
+
+class UdevLinux;
+
+class DeviceMonitorLinux : public base::MessageLoop::DestructionObserver {
+ public:
+ DeviceMonitorLinux();
+ virtual ~DeviceMonitorLinux();
+
+ private:
+ // This object is deleted on the UI thread after the IO thread has been
+ // destroyed. Need to know when IO thread is being destroyed so that
+ // we can delete udev_.
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+ void Initialize();
+ void OnDevicesChanged(udev_device* device);
+
+ scoped_ptr<UdevLinux> udev_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceMonitorLinux);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_MONITOR_LINUX_H_
diff --git a/chromium/content/browser/device_monitor_mac.h b/chromium/content/browser/device_monitor_mac.h
new file mode 100644
index 00000000000..bab522f4dbe
--- /dev/null
+++ b/chromium/content/browser/device_monitor_mac.h
@@ -0,0 +1,29 @@
+// 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 CONTENT_BROWSER_DEVICE_MONITOR_MAC_H_
+#define CONTENT_BROWSER_DEVICE_MONITOR_MAC_H_
+
+#include "base/basictypes.h"
+#include "base/system_monitor/system_monitor.h"
+
+namespace content {
+
+class DeviceMonitorMac {
+ public:
+ DeviceMonitorMac();
+ ~DeviceMonitorMac();
+
+ private:
+ // Forward the notifications to system monitor.
+ void NotifyDeviceChanged(base::SystemMonitor::DeviceType type);
+
+ class QTMonitorImpl;
+ scoped_ptr<DeviceMonitorMac::QTMonitorImpl> qt_monitor_;
+ DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_MONITOR_MAC_H_
diff --git a/chromium/content/browser/device_monitor_mac.mm b/chromium/content/browser/device_monitor_mac.mm
new file mode 100644
index 00000000000..7b12e91cbdf
--- /dev/null
+++ b/chromium/content/browser/device_monitor_mac.mm
@@ -0,0 +1,108 @@
+// 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 "content/browser/device_monitor_mac.h"
+
+#import <QTKit/QTKit.h>
+
+#include "base/logging.h"
+
+namespace content {
+
+class DeviceMonitorMac::QTMonitorImpl {
+ public:
+ explicit QTMonitorImpl(DeviceMonitorMac* monitor);
+ virtual ~QTMonitorImpl() {}
+
+ void Start();
+ void Stop();
+
+ private:
+ void OnDeviceChanged();
+
+ DeviceMonitorMac* monitor_;
+ int number_audio_devices_;
+ int number_video_devices_;
+ id device_arrival_;
+ id device_removal_;
+
+ DISALLOW_COPY_AND_ASSIGN(QTMonitorImpl);
+};
+
+DeviceMonitorMac::QTMonitorImpl::QTMonitorImpl(DeviceMonitorMac* monitor)
+ : monitor_(monitor),
+ number_audio_devices_(0),
+ number_video_devices_(0),
+ device_arrival_(nil),
+ device_removal_(nil) {
+ DCHECK(monitor);
+}
+
+void DeviceMonitorMac::QTMonitorImpl::Start() {
+ NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
+ device_arrival_ =
+ [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification* notification) {
+ OnDeviceChanged();}];
+
+ device_removal_ =
+ [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification* notification) {
+ OnDeviceChanged();}];
+}
+
+void DeviceMonitorMac::QTMonitorImpl::Stop() {
+ if (!monitor_)
+ return;
+
+ NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
+ [nc removeObserver:device_arrival_];
+ [nc removeObserver:device_removal_];
+}
+
+void DeviceMonitorMac::QTMonitorImpl::OnDeviceChanged() {
+ NSArray* devices = [QTCaptureDevice inputDevices];
+ int number_video_devices = 0;
+ int number_audio_devices = 0;
+ for (QTCaptureDevice* device in devices) {
+ if ([device hasMediaType:QTMediaTypeVideo] ||
+ [device hasMediaType:QTMediaTypeMuxed])
+ ++number_video_devices;
+
+ if ([device hasMediaType:QTMediaTypeSound] ||
+ [device hasMediaType:QTMediaTypeMuxed])
+ ++number_audio_devices;
+ }
+
+ if (number_video_devices_ != number_video_devices) {
+ number_video_devices_ = number_video_devices;
+ monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
+ }
+
+ if (number_audio_devices_ != number_audio_devices) {
+ number_audio_devices_ = number_audio_devices;
+ monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
+ }
+}
+
+DeviceMonitorMac::DeviceMonitorMac() {
+ qt_monitor_.reset(new QTMonitorImpl(this));
+ qt_monitor_->Start();
+}
+
+DeviceMonitorMac::~DeviceMonitorMac() {
+ qt_monitor_->Stop();
+}
+
+void DeviceMonitorMac::NotifyDeviceChanged(
+ base::SystemMonitor::DeviceType type) {
+ // TODO(xians): Remove the global variable for SystemMonitor.
+ base::SystemMonitor::Get()->ProcessDevicesChanged(type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/DEPS b/chromium/content/browser/device_orientation/DEPS
new file mode 100644
index 00000000000..e118614c16a
--- /dev/null
+++ b/chromium/content/browser/device_orientation/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/sudden_motion_sensor",
+]
diff --git a/chromium/content/browser/device_orientation/OWNERS b/chromium/content/browser/device_orientation/OWNERS
new file mode 100644
index 00000000000..d523461a382
--- /dev/null
+++ b/chromium/content/browser/device_orientation/OWNERS
@@ -0,0 +1,4 @@
+hans@chromium.org
+bulach@chromium.org
+leandrogracia@chromium.org
+timvolodine@chromium.org
diff --git a/chromium/content/browser/device_orientation/accelerometer_mac.cc b/chromium/content/browser/device_orientation/accelerometer_mac.cc
new file mode 100644
index 00000000000..04c5d71b65c
--- /dev/null
+++ b/chromium/content/browser/device_orientation/accelerometer_mac.cc
@@ -0,0 +1,105 @@
+// 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 "content/browser/device_orientation/accelerometer_mac.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "content/browser/device_orientation/orientation.h"
+#include "third_party/sudden_motion_sensor/sudden_motion_sensor_mac.h"
+
+namespace content {
+
+// Create a AccelerometerMac object and return NULL if no valid sensor found.
+DataFetcher* AccelerometerMac::Create() {
+ scoped_ptr<AccelerometerMac> accelerometer(new AccelerometerMac);
+ return accelerometer->Init() ? accelerometer.release() : NULL;
+}
+
+AccelerometerMac::~AccelerometerMac() {
+}
+
+AccelerometerMac::AccelerometerMac() {
+}
+
+const DeviceData* AccelerometerMac::GetDeviceData(DeviceData::Type type) {
+ if (type != DeviceData::kTypeOrientation)
+ return NULL;
+ return GetOrientation();
+}
+
+// Retrieve per-axis orientation values.
+//
+// Axes and angles are defined according to the W3C DeviceOrientation Draft.
+// See here: http://dev.w3.org/geo/api/spec-source-orientation.html
+//
+// Note: only beta and gamma angles are provided. Alpha is set to zero.
+//
+// Returns false in case of error.
+//
+const Orientation* AccelerometerMac::GetOrientation() {
+ DCHECK(sudden_motion_sensor_.get());
+
+ // Retrieve per-axis calibrated values.
+ float axis_value[3];
+ if (!sudden_motion_sensor_->ReadSensorValues(axis_value))
+ return NULL;
+
+ // Transform the accelerometer values to W3C draft angles.
+ //
+ // Accelerometer values are just dot products of the sensor axes
+ // by the gravity vector 'g' with the result for the z axis inverted.
+ //
+ // To understand this transformation calculate the 3rd row of the z-x-y
+ // Euler angles rotation matrix (because of the 'g' vector, only 3rd row
+ // affects to the result). Note that z-x-y matrix means R = Ry * Rx * Rz.
+ // Then, assume alpha = 0 and you get this:
+ //
+ // x_acc = sin(gamma)
+ // y_acc = - cos(gamma) * sin(beta)
+ // z_acc = cos(beta) * cos(gamma)
+ //
+ // After that the rest is just a bit of trigonometry.
+ //
+ // Also note that alpha can't be provided but it's assumed to be always zero.
+ // This is necessary in order to provide enough information to solve
+ // the equations.
+ //
+ const double kRad2deg = 180.0 / M_PI;
+
+ scoped_refptr<Orientation> orientation(new Orientation());
+
+ orientation->set_beta(kRad2deg * atan2(-axis_value[1], axis_value[2]));
+ orientation->set_gamma(kRad2deg * asin(axis_value[0]));
+ // TODO(aousterh): should absolute_ be set to false here?
+ // See crbug.com/136010.
+
+ // Make sure that the interval boundaries comply with the specification. At
+ // this point, beta is [-180, 180] and gamma is [-90, 90], but the spec has
+ // the upper bound open on both.
+ if (orientation->beta() == 180.0) {
+ orientation->set_beta(-180.0); // -180 == 180 (upside-down)
+ }
+ if (orientation->gamma() == 90.0) {
+ static double just_less_than_90 = nextafter(90, 0);
+ orientation->set_gamma(just_less_than_90);
+ }
+
+ // At this point, DCHECKing is paranoia. Never hurts.
+ DCHECK_GE(orientation->beta(), -180.0);
+ DCHECK_LT(orientation->beta(), 180.0);
+ DCHECK_GE(orientation->gamma(), -90.0);
+ DCHECK_LT(orientation->gamma(), 90.0);
+
+ orientation->AddRef();
+ return orientation.get();
+}
+
+bool AccelerometerMac::Init() {
+ sudden_motion_sensor_.reset(SuddenMotionSensor::Create());
+ return sudden_motion_sensor_.get() != NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/accelerometer_mac.h b/chromium/content/browser/device_orientation/accelerometer_mac.h
new file mode 100644
index 00000000000..654ae83e843
--- /dev/null
+++ b/chromium/content/browser/device_orientation/accelerometer_mac.h
@@ -0,0 +1,40 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_ACCELEROMETER_MAC_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_ACCELEROMETER_MAC_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/device_data.h"
+
+class SuddenMotionSensor;
+
+namespace content {
+
+class Orientation;
+
+class AccelerometerMac : public DataFetcher {
+ public:
+ static DataFetcher* Create();
+
+ // Implement DataFetcher.
+ virtual const DeviceData* GetDeviceData(DeviceData::Type type) OVERRIDE;
+
+ virtual ~AccelerometerMac();
+
+ private:
+ AccelerometerMac();
+ bool Init();
+ const Orientation* GetOrientation();
+
+ scoped_ptr<SuddenMotionSensor> sudden_motion_sensor_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_ACCELEROMETER_MAC_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher.h b/chromium/content/browser/device_orientation/data_fetcher.h
new file mode 100644
index 00000000000..0e739cddbf0
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher.h
@@ -0,0 +1,24 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_H_
+
+#include "content/browser/device_orientation/device_data.h"
+
+namespace content {
+
+class DataFetcher {
+ public:
+ virtual ~DataFetcher() {}
+
+ // Returns NULL if there was a fatal error getting the device data of this
+ // type or if this fetcher can never provide this type of data. Otherwise,
+ // returns a pointer to a DeviceData containing the most recent data.
+ virtual const DeviceData* GetDeviceData(DeviceData::Type type) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher_impl_android.cc b/chromium/content/browser/device_orientation/data_fetcher_impl_android.cc
new file mode 100644
index 00000000000..6748d744b71
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_impl_android.cc
@@ -0,0 +1,198 @@
+// 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 "content/browser/device_orientation/data_fetcher_impl_android.h"
+
+#include <string.h>
+
+#include "base/android/jni_android.h"
+#include "base/memory/singleton.h"
+#include "content/browser/device_orientation/orientation.h"
+#include "jni/DeviceMotionAndOrientation_jni.h"
+
+using base::android::AttachCurrentThread;
+
+namespace content {
+
+namespace {
+
+// This should match ProviderImpl::kDesiredSamplingIntervalMs.
+// TODO(husky): Make that constant public so we can use it directly.
+const int kPeriodInMilliseconds = 100;
+
+} // namespace
+
+DataFetcherImplAndroid::DataFetcherImplAndroid()
+ : number_active_device_motion_sensors_(0),
+ device_motion_buffer_(NULL),
+ is_buffer_ready_(false) {
+ memset(received_motion_data_, 0, sizeof(received_motion_data_));
+ device_orientation_.Reset(
+ Java_DeviceMotionAndOrientation_getInstance(AttachCurrentThread()));
+}
+
+DataFetcherImplAndroid::~DataFetcherImplAndroid() {
+}
+
+bool DataFetcherImplAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+DataFetcherImplAndroid* DataFetcherImplAndroid::GetInstance() {
+ return Singleton<DataFetcherImplAndroid,
+ LeakySingletonTraits<DataFetcherImplAndroid> >::get();
+}
+
+const DeviceData* DataFetcherImplAndroid::GetDeviceData(
+ DeviceData::Type type) {
+ if (type != DeviceData::kTypeOrientation)
+ return NULL;
+ return GetOrientation();
+}
+
+const Orientation* DataFetcherImplAndroid::GetOrientation() {
+ // Do we have a new orientation value? (It's safe to do this outside the lock
+ // because we only skip the lock if the value is null. We always enter the
+ // lock if we're going to make use of the new value.)
+ if (next_orientation_.get()) {
+ base::AutoLock autolock(next_orientation_lock_);
+ next_orientation_.swap(current_orientation_);
+ }
+ if (!current_orientation_.get())
+ return new Orientation();
+ return current_orientation_.get();
+}
+
+void DataFetcherImplAndroid::GotOrientation(
+ JNIEnv*, jobject, double alpha, double beta, double gamma) {
+ base::AutoLock autolock(next_orientation_lock_);
+
+ Orientation* orientation = new Orientation();
+ orientation->set_alpha(alpha);
+ orientation->set_beta(beta);
+ orientation->set_gamma(gamma);
+ orientation->set_absolute(true);
+ next_orientation_ = orientation;
+}
+
+void DataFetcherImplAndroid::GotAcceleration(
+ JNIEnv*, jobject, double x, double y, double z) {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.accelerationX = x;
+ device_motion_buffer_->data.hasAccelerationX = true;
+ device_motion_buffer_->data.accelerationY = y;
+ device_motion_buffer_->data.hasAccelerationY = true;
+ device_motion_buffer_->data.accelerationZ = z;
+ device_motion_buffer_->data.hasAccelerationZ = true;
+ device_motion_buffer_->seqlock.WriteEnd();
+
+ if (!is_buffer_ready_) {
+ received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION] = 1;
+ CheckBufferReadyToRead();
+ }
+}
+
+void DataFetcherImplAndroid::GotAccelerationIncludingGravity(
+ JNIEnv*, jobject, double x, double y, double z) {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.accelerationIncludingGravityX = x;
+ device_motion_buffer_->data.hasAccelerationIncludingGravityX = true;
+ device_motion_buffer_->data.accelerationIncludingGravityY = y;
+ device_motion_buffer_->data.hasAccelerationIncludingGravityY = true;
+ device_motion_buffer_->data.accelerationIncludingGravityZ = z;
+ device_motion_buffer_->data.hasAccelerationIncludingGravityZ = true;
+ device_motion_buffer_->seqlock.WriteEnd();
+
+ if (!is_buffer_ready_) {
+ received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY] = 1;
+ CheckBufferReadyToRead();
+ }
+}
+
+void DataFetcherImplAndroid::GotRotationRate(
+ JNIEnv*, jobject, double alpha, double beta, double gamma) {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.rotationRateAlpha = alpha;
+ device_motion_buffer_->data.hasRotationRateAlpha = true;
+ device_motion_buffer_->data.rotationRateBeta = beta;
+ device_motion_buffer_->data.hasRotationRateBeta = true;
+ device_motion_buffer_->data.rotationRateGamma = gamma;
+ device_motion_buffer_->data.hasRotationRateGamma = true;
+ device_motion_buffer_->seqlock.WriteEnd();
+
+ if (!is_buffer_ready_) {
+ received_motion_data_[RECEIVED_MOTION_DATA_ROTATION_RATE] = 1;
+ CheckBufferReadyToRead();
+ }
+}
+
+bool DataFetcherImplAndroid::Start(DeviceData::Type event_type) {
+ DCHECK(!device_orientation_.is_null());
+ return Java_DeviceMotionAndOrientation_start(
+ AttachCurrentThread(), device_orientation_.obj(),
+ reinterpret_cast<jint>(this), static_cast<jint>(event_type),
+ kPeriodInMilliseconds);
+}
+
+void DataFetcherImplAndroid::Stop(DeviceData::Type event_type) {
+ DCHECK(!device_orientation_.is_null());
+ Java_DeviceMotionAndOrientation_stop(
+ AttachCurrentThread(), device_orientation_.obj(),
+ static_cast<jint>(event_type));
+}
+
+int DataFetcherImplAndroid::GetNumberActiveDeviceMotionSensors() {
+ DCHECK(!device_orientation_.is_null());
+ return Java_DeviceMotionAndOrientation_getNumberActiveDeviceMotionSensors(
+ AttachCurrentThread(), device_orientation_.obj());
+}
+
+
+// ----- Shared memory API methods
+
+bool DataFetcherImplAndroid::StartFetchingDeviceMotionData(
+ DeviceMotionHardwareBuffer* buffer) {
+ device_motion_buffer_ = buffer;
+ ClearInternalBuffers();
+ bool success = Start(DeviceData::kTypeMotion);
+
+ // If no motion data can ever be provided, the number of active device motion
+ // sensors will be zero. In that case flag the shared memory buffer
+ // as ready to read, as it will not change anyway.
+ number_active_device_motion_sensors_ = GetNumberActiveDeviceMotionSensors();
+ CheckBufferReadyToRead();
+ return success;
+}
+
+void DataFetcherImplAndroid::StopFetchingDeviceMotionData() {
+ Stop(DeviceData::kTypeMotion);
+ ClearInternalBuffers();
+}
+
+void DataFetcherImplAndroid::CheckBufferReadyToRead() {
+ if (received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION] +
+ received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY] +
+ received_motion_data_[RECEIVED_MOTION_DATA_ROTATION_RATE] ==
+ number_active_device_motion_sensors_) {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.interval = kPeriodInMilliseconds;
+ device_motion_buffer_->seqlock.WriteEnd();
+ SetBufferReadyStatus(true);
+ }
+}
+
+void DataFetcherImplAndroid::SetBufferReadyStatus(bool ready) {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.allAvailableSensorsAreActive = ready;
+ device_motion_buffer_->seqlock.WriteEnd();
+ is_buffer_ready_ = ready;
+}
+
+void DataFetcherImplAndroid::ClearInternalBuffers() {
+ memset(received_motion_data_, 0, sizeof(received_motion_data_));
+ number_active_device_motion_sensors_ = 0;
+ SetBufferReadyStatus(false);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/data_fetcher_impl_android.h b/chromium/content/browser/device_orientation/data_fetcher_impl_android.h
new file mode 100644
index 00000000000..9926e7d1464
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_impl_android.h
@@ -0,0 +1,98 @@
+// 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 CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_
+#define CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/common/content_export.h"
+#include "content/common/device_motion_hardware_buffer.h"
+
+template<typename T> struct DefaultSingletonTraits;
+
+namespace content {
+class Orientation;
+
+// Android implementation of DeviceOrientation API.
+
+// Android's SensorManager has a push API, whereas Chrome wants to pull data.
+// To fit them together, we store incoming sensor events in a 1-element buffer.
+// SensorManager calls SetOrientation() which pushes a new value (discarding the
+// previous value if any). Chrome calls GetDeviceData() which reads the most
+// recent value. Repeated calls to GetDeviceData() will return the same value.
+
+// TODO(timvolodine): Simplify this class and remove GetDeviceData() method,
+// once Device Orientation switches to shared memory implementation.
+// Also rename this class to SensorManagerAndroid.
+class CONTENT_EXPORT DataFetcherImplAndroid {
+ public:
+ // Must be called at startup, before GetInstance().
+ static bool Register(JNIEnv* env);
+
+ // Needs to be thread-safe, because accessed from different threads.
+ static DataFetcherImplAndroid* GetInstance();
+
+ // Called from Java via JNI.
+ void GotOrientation(JNIEnv*, jobject,
+ double alpha, double beta, double gamma);
+ void GotAcceleration(JNIEnv*, jobject,
+ double x, double y, double z);
+ void GotAccelerationIncludingGravity(JNIEnv*, jobject,
+ double x, double y, double z);
+ void GotRotationRate(JNIEnv*, jobject,
+ double alpha, double beta, double gamma);
+
+ const DeviceData* GetDeviceData(DeviceData::Type type);
+
+ virtual bool Start(DeviceData::Type event_type);
+ virtual void Stop(DeviceData::Type event_type);
+
+ // Shared memory related methods.
+ bool StartFetchingDeviceMotionData(DeviceMotionHardwareBuffer* buffer);
+ void StopFetchingDeviceMotionData();
+
+ protected:
+ DataFetcherImplAndroid();
+ virtual ~DataFetcherImplAndroid();
+
+ virtual int GetNumberActiveDeviceMotionSensors();
+
+ private:
+ friend struct DefaultSingletonTraits<DataFetcherImplAndroid>;
+
+ const Orientation* GetOrientation();
+
+ void CheckBufferReadyToRead();
+ void SetBufferReadyStatus(bool ready);
+ void ClearInternalBuffers();
+
+ enum {
+ RECEIVED_MOTION_DATA_ACCELERATION = 0,
+ RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY = 1,
+ RECEIVED_MOTION_DATA_ROTATION_RATE = 2,
+ RECEIVED_MOTION_DATA_MAX = 3,
+ };
+ // Value returned by GetDeviceData.
+ scoped_refptr<Orientation> current_orientation_;
+
+ // 1-element buffer, written by GotOrientation, read by GetDeviceData.
+ base::Lock next_orientation_lock_;
+ scoped_refptr<Orientation> next_orientation_;
+
+ // The Java provider of orientation info.
+ base::android::ScopedJavaGlobalRef<jobject> device_orientation_;
+ int number_active_device_motion_sensors_;
+ int received_motion_data_[RECEIVED_MOTION_DATA_MAX];
+ DeviceMotionHardwareBuffer* device_motion_buffer_;
+ bool is_buffer_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataFetcherImplAndroid);
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher_impl_android_unittest.cc b/chromium/content/browser/device_orientation/data_fetcher_impl_android_unittest.cc
new file mode 100644
index 00000000000..85d5184689e
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_impl_android_unittest.cc
@@ -0,0 +1,107 @@
+// 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 "content/browser/device_orientation/data_fetcher_impl_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+const int kPeriodInMilliseconds = 100;
+
+class FakeDataFetcherImplAndroid : public DataFetcherImplAndroid {
+ public:
+ FakeDataFetcherImplAndroid() { }
+ virtual ~FakeDataFetcherImplAndroid() { }
+
+ virtual bool Start(DeviceData::Type event_type) OVERRIDE {
+ return true;
+ }
+
+ virtual void Stop(DeviceData::Type event_type) OVERRIDE {
+ }
+
+ virtual int GetNumberActiveDeviceMotionSensors() OVERRIDE {
+ return number_active_sensors_;
+ }
+
+ void SetNumberActiveDeviceMotionSensors(int number_active_sensors) {
+ number_active_sensors_ = number_active_sensors;
+ }
+
+ private:
+ int number_active_sensors_;
+};
+
+class AndroidDataFetcherTest : public testing::Test {
+ protected:
+ AndroidDataFetcherTest() {
+ buffer_.reset(new DeviceMotionHardwareBuffer);
+ }
+
+ scoped_ptr<DeviceMotionHardwareBuffer> buffer_;
+};
+
+TEST_F(AndroidDataFetcherTest, ThreeDeviceMotionSensorsActive) {
+ FakeDataFetcherImplAndroid::Register(base::android::AttachCurrentThread());
+ FakeDataFetcherImplAndroid fetcher;
+ fetcher.SetNumberActiveDeviceMotionSensors(3);
+
+ fetcher.StartFetchingDeviceMotionData(buffer_.get());
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+
+ fetcher.GotAcceleration(0, 0, 1, 2, 3);
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+
+ fetcher.GotAccelerationIncludingGravity(0, 0, 1, 2, 3);
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+
+ fetcher.GotRotationRate(0, 0, 1, 2, 3);
+ ASSERT_TRUE(buffer_->data.allAvailableSensorsAreActive);
+ ASSERT_EQ(kPeriodInMilliseconds, buffer_->data.interval);
+
+ fetcher.StopFetchingDeviceMotionData();
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+}
+
+TEST_F(AndroidDataFetcherTest, TwoDeviceMotionSensorsActive) {
+ FakeDataFetcherImplAndroid::Register(base::android::AttachCurrentThread());
+ FakeDataFetcherImplAndroid fetcher;
+ fetcher.SetNumberActiveDeviceMotionSensors(2);
+
+ fetcher.StartFetchingDeviceMotionData(buffer_.get());
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+
+ fetcher.GotAcceleration(0, 0, 1, 2, 3);
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+
+ fetcher.GotAccelerationIncludingGravity(0, 0, 1, 2, 3);
+ ASSERT_TRUE(buffer_->data.allAvailableSensorsAreActive);
+ ASSERT_EQ(kPeriodInMilliseconds, buffer_->data.interval);
+
+ fetcher.StopFetchingDeviceMotionData();
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+}
+
+TEST_F(AndroidDataFetcherTest, ZeroDeviceMotionSensorsActive) {
+ FakeDataFetcherImplAndroid::Register(base::android::AttachCurrentThread());
+ FakeDataFetcherImplAndroid fetcher;
+ fetcher.SetNumberActiveDeviceMotionSensors(0);
+
+ fetcher.StartFetchingDeviceMotionData(buffer_.get());
+ ASSERT_TRUE(buffer_->data.allAvailableSensorsAreActive);
+ ASSERT_EQ(kPeriodInMilliseconds, buffer_->data.interval);
+
+ fetcher.StopFetchingDeviceMotionData();
+ ASSERT_FALSE(buffer_->data.allAvailableSensorsAreActive);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/data_fetcher_impl_win.cc b/chromium/content/browser/device_orientation/data_fetcher_impl_win.cc
new file mode 100644
index 00000000000..d08ed446ad6
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_impl_win.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 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 "content/browser/device_orientation/data_fetcher_impl_win.h"
+
+#include <InitGuid.h>
+#include <PortableDeviceTypes.h>
+#include <Sensors.h>
+
+#include "base/logging.h"
+#include "base/win/iunknown_impl.h"
+#include "base/win/windows_version.h"
+#include "content/browser/device_orientation/orientation.h"
+
+namespace {
+
+// This should match ProviderImpl::kDesiredSamplingIntervalMs.
+const int kPeriodInMilliseconds = 100;
+
+} // namespace
+
+namespace content {
+
+class DataFetcherImplWin::SensorEventSink : public ISensorEvents,
+ public base::win::IUnknownImpl {
+ public:
+ explicit SensorEventSink(DataFetcherImplWin* const fetcher)
+ : fetcher_(fetcher) {}
+
+ virtual ~SensorEventSink() {}
+
+ // IUnknown interface
+ virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE {
+ return IUnknownImpl::AddRef();
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE {
+ return IUnknownImpl::Release();
+ }
+
+ virtual STDMETHODIMP QueryInterface(REFIID riid, void** ppv) OVERRIDE {
+ if (riid == __uuidof(ISensorEvents)) {
+ *ppv = static_cast<ISensorEvents*>(this);
+ AddRef();
+ return S_OK;
+ }
+ return IUnknownImpl::QueryInterface(riid, ppv);
+ }
+
+ // ISensorEvents interface
+ STDMETHODIMP OnEvent(ISensor* sensor,
+ REFGUID event_id,
+ IPortableDeviceValues* event_data) OVERRIDE {
+ return S_OK;
+ }
+
+ STDMETHODIMP OnDataUpdated(ISensor* sensor,
+ ISensorDataReport* new_data) OVERRIDE {
+ if (NULL == new_data || NULL == sensor)
+ return E_INVALIDARG;
+
+ PROPVARIANT value = {};
+ scoped_refptr<Orientation> orientation = new Orientation();
+
+ if (SUCCEEDED(new_data->GetSensorValue(
+ SENSOR_DATA_TYPE_TILT_X_DEGREES, &value))) {
+ orientation->set_beta(value.fltVal);
+ }
+ PropVariantClear(&value);
+
+ if (SUCCEEDED(new_data->GetSensorValue(
+ SENSOR_DATA_TYPE_TILT_Y_DEGREES, &value))) {
+ orientation->set_gamma(value.fltVal);
+ }
+ PropVariantClear(&value);
+
+ if (SUCCEEDED(new_data->GetSensorValue(
+ SENSOR_DATA_TYPE_TILT_Z_DEGREES, &value))) {
+ orientation->set_alpha(value.fltVal);
+ }
+ PropVariantClear(&value);
+
+ orientation->set_absolute(true);
+ fetcher_->OnOrientationData(orientation.get());
+
+ return S_OK;
+ }
+
+ STDMETHODIMP OnLeave(REFSENSOR_ID sensor_id) OVERRIDE {
+ return S_OK;
+ }
+
+ STDMETHODIMP OnStateChanged(ISensor* sensor, SensorState state) OVERRIDE {
+ return S_OK;
+ }
+
+ private:
+ DataFetcherImplWin* const fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(SensorEventSink);
+};
+
+// Create a DataFetcherImplWin object and return NULL if no valid sensor found.
+// static
+DataFetcher* DataFetcherImplWin::Create() {
+ scoped_ptr<DataFetcherImplWin> fetcher(new DataFetcherImplWin);
+ if (fetcher->Initialize())
+ return fetcher.release();
+
+ LOG(ERROR) << "DataFetcherImplWin::Initialize failed!";
+ return NULL;
+}
+
+DataFetcherImplWin::~DataFetcherImplWin() {
+ if (sensor_)
+ sensor_->SetEventSink(NULL);
+}
+
+DataFetcherImplWin::DataFetcherImplWin() {
+}
+
+void DataFetcherImplWin::OnOrientationData(Orientation* orientation) {
+ // This method is called on Windows sensor thread.
+ base::AutoLock autolock(next_orientation_lock_);
+ next_orientation_ = orientation;
+}
+
+const DeviceData* DataFetcherImplWin::GetDeviceData(DeviceData::Type type) {
+ if (type != DeviceData::kTypeOrientation)
+ return NULL;
+ return GetOrientation();
+}
+
+const Orientation* DataFetcherImplWin::GetOrientation() {
+ if (next_orientation_.get()) {
+ base::AutoLock autolock(next_orientation_lock_);
+ next_orientation_.swap(current_orientation_);
+ }
+ if (!current_orientation_.get())
+ return new Orientation();
+ return current_orientation_.get();
+}
+
+bool DataFetcherImplWin::Initialize() {
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return false;
+
+ base::win::ScopedComPtr<ISensorManager> sensor_manager;
+ HRESULT hr = sensor_manager.CreateInstance(CLSID_SensorManager);
+ if (FAILED(hr) || !sensor_manager)
+ return false;
+
+ base::win::ScopedComPtr<ISensorCollection> sensor_collection;
+ hr = sensor_manager->GetSensorsByType(
+ SENSOR_TYPE_INCLINOMETER_3D, sensor_collection.Receive());
+
+ if (FAILED(hr) || !sensor_collection)
+ return false;
+
+ ULONG count = 0;
+ hr = sensor_collection->GetCount(&count);
+ if (FAILED(hr) || !count)
+ return false;
+
+ hr = sensor_collection->GetAt(0, sensor_.Receive());
+ if (FAILED(hr) || !sensor_)
+ return false;
+
+ base::win::ScopedComPtr<IPortableDeviceValues> device_values;
+ if (SUCCEEDED(device_values.CreateInstance(CLSID_PortableDeviceValues))) {
+ if (SUCCEEDED(device_values->SetUnsignedIntegerValue(
+ SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL, kPeriodInMilliseconds))) {
+ base::win::ScopedComPtr<IPortableDeviceValues> return_values;
+ sensor_->SetProperties(device_values.get(), return_values.Receive());
+ }
+ }
+
+ scoped_refptr<SensorEventSink> sensor_event_impl(new SensorEventSink(this));
+ base::win::ScopedComPtr<ISensorEvents> sensor_events;
+ hr = sensor_event_impl->QueryInterface(
+ __uuidof(ISensorEvents), sensor_events.ReceiveVoid());
+ if (FAILED(hr) || !sensor_events)
+ return false;
+
+ hr = sensor_->SetEventSink(sensor_events);
+ if (FAILED(hr))
+ return false;
+
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/data_fetcher_impl_win.h b/chromium/content/browser/device_orientation/data_fetcher_impl_win.h
new file mode 100644
index 00000000000..9a065066a31
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_impl_win.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_WIN_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_WIN_H_
+
+#include <SensorsApi.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/win/scoped_comptr.h"
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/device_data.h"
+
+namespace content {
+
+class Orientation;
+
+// Windows implementation of DeviceOrientation API.
+// The SensorEventSink is installed to Windows sensor thread to listen for
+// sensor's data. Upon each notification, DataFetcherImplWin buffers the data.
+// Then Chrome sensor polling thread pulls the buffered data via GetDeviceData.
+// The Inclinometer 3D sensor (SENSOR_TYPE_INCLINOMETER_3D) is used to get the
+// orientation data.
+
+class DataFetcherImplWin : public DataFetcher {
+ public:
+ virtual ~DataFetcherImplWin();
+
+ // Factory function. It returns NULL on error.
+ // The created object listens for events for the whole lifetime.
+ static DataFetcher* Create();
+
+ // Implement DataFetcher.
+ virtual const DeviceData* GetDeviceData(DeviceData::Type type) OVERRIDE;
+
+ private:
+ class SensorEventSink;
+ friend SensorEventSink;
+
+ DataFetcherImplWin();
+ bool Initialize();
+ void OnOrientationData(Orientation* orientation);
+ const Orientation* GetOrientation();
+
+ base::win::ScopedComPtr<ISensor> sensor_;
+
+ // Value returned by GetDeviceData.
+ scoped_refptr<Orientation> current_orientation_;
+
+ // The 1-element buffer follows DataFetcherImplAndroid implementation.
+ // It is written by OnOrientationData and read by GetDeviceData.
+ base::Lock next_orientation_lock_;
+ scoped_refptr<Orientation> next_orientation_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataFetcherImplWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_WIN_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher_orientation_android.cc b/chromium/content/browser/device_orientation/data_fetcher_orientation_android.cc
new file mode 100644
index 00000000000..823595651f4
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_orientation_android.cc
@@ -0,0 +1,37 @@
+// 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 "content/browser/device_orientation/data_fetcher_orientation_android.h"
+
+#include "base/logging.h"
+#include "content/browser/device_orientation/data_fetcher_impl_android.h"
+
+namespace content {
+
+DataFetcherOrientationAndroid::DataFetcherOrientationAndroid() {
+}
+
+DataFetcherOrientationAndroid::~DataFetcherOrientationAndroid() {
+ DataFetcherImplAndroid::GetInstance()->Stop(DeviceData::kTypeOrientation);
+}
+
+DataFetcher* DataFetcherOrientationAndroid::Create() {
+ scoped_ptr<DataFetcherOrientationAndroid> fetcher(
+ new DataFetcherOrientationAndroid);
+ if (DataFetcherImplAndroid::GetInstance()->Start(
+ DeviceData::kTypeOrientation))
+ return fetcher.release();
+
+ DVLOG(2) << "DataFetcherImplAndroid::Start failed!";
+ return NULL;
+}
+
+const DeviceData* DataFetcherOrientationAndroid::GetDeviceData(
+ DeviceData::Type type) {
+ if (type != DeviceData::kTypeOrientation)
+ return NULL;
+ return DataFetcherImplAndroid::GetInstance()->GetDeviceData(type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/data_fetcher_orientation_android.h b/chromium/content/browser/device_orientation/data_fetcher_orientation_android.h
new file mode 100644
index 00000000000..b0a17021217
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_orientation_android.h
@@ -0,0 +1,32 @@
+// 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 CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_ORIENTATION_ANDROID_H_
+#define CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_ORIENTATION_ANDROID_H_
+
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/device_data.h"
+
+namespace content {
+
+class DataFetcherOrientationAndroid : public DataFetcher {
+ public:
+ // Factory function. We'll listen for events for the lifetime of this object.
+ // Returns NULL on error.
+ static DataFetcher* Create();
+
+ virtual ~DataFetcherOrientationAndroid();
+
+ // Implementation of DataFetcher.
+ virtual const DeviceData* GetDeviceData(DeviceData::Type type) OVERRIDE;
+
+ protected:
+ DataFetcherOrientationAndroid();
+
+ DISALLOW_COPY_AND_ASSIGN(DataFetcherOrientationAndroid);
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_ORIENTATION_ANDROID_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher_shared_memory.h b/chromium/content/browser/device_orientation/data_fetcher_shared_memory.h
new file mode 100644
index 00000000000..dc5e92ae160
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_shared_memory.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_SHARED_MEMORY_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_SHARED_MEMORY_H_
+
+#include "content/browser/device_orientation/device_data.h"
+#include "content/common/device_motion_hardware_buffer.h"
+
+namespace WebKit {
+class WebDeviceMotionData;
+}
+
+namespace content {
+
+class CONTENT_EXPORT DataFetcherSharedMemory {
+ public:
+ DataFetcherSharedMemory()
+ : device_motion_buffer_(NULL),
+ started_(false) { }
+ virtual ~DataFetcherSharedMemory();
+
+ // Returns true if this fetcher needs explicit calls to fetch the data.
+ // Called from any thread.
+ virtual bool NeedsPolling();
+
+ // If this fetcher NeedsPolling() is true, this method will update the
+ // buffer with the latest device motion data.
+ // Returns true if there was any motion data to update the buffer with.
+ // Called from the DeviceMotionProvider::PollingThread.
+ virtual bool FetchDeviceMotionDataIntoBuffer();
+
+ // Returns true if the relevant sensors could be successfully activated.
+ // This method should be called before any calls to
+ // FetchDeviceMotionDataIntoBuffer().
+ // If NeedsPolling() is true this method should be called from the
+ // PollingThread.
+ virtual bool StartFetchingDeviceMotionData(
+ DeviceMotionHardwareBuffer* buffer);
+
+ // Indicates to the fetcher to stop fetching device data.
+ // If NeedsPolling() is true this method should be called from the
+ // PollingThread.
+ virtual void StopFetchingDeviceMotionData();
+
+ private:
+ DeviceMotionHardwareBuffer* device_motion_buffer_;
+ bool started_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataFetcherSharedMemory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_SHARED_MEMORY_H_
diff --git a/chromium/content/browser/device_orientation/data_fetcher_shared_memory_android.cc b/chromium/content/browser/device_orientation/data_fetcher_shared_memory_android.cc
new file mode 100644
index 00000000000..8f6e202c042
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_shared_memory_android.cc
@@ -0,0 +1,40 @@
+// 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 "data_fetcher_shared_memory.h"
+
+#include "base/logging.h"
+#include "data_fetcher_impl_android.h"
+
+namespace content {
+
+DataFetcherSharedMemory::~DataFetcherSharedMemory() {
+ if (started_)
+ StopFetchingDeviceMotionData();
+}
+
+bool DataFetcherSharedMemory::NeedsPolling() {
+ return false;
+}
+
+bool DataFetcherSharedMemory::FetchDeviceMotionDataIntoBuffer() {
+ NOTREACHED();
+ return false;
+}
+
+bool DataFetcherSharedMemory::StartFetchingDeviceMotionData(
+ DeviceMotionHardwareBuffer* buffer) {
+ DCHECK(buffer);
+ device_motion_buffer_ = buffer;
+ started_ = DataFetcherImplAndroid::GetInstance()->
+ StartFetchingDeviceMotionData(buffer);
+ return started_;
+}
+
+void DataFetcherSharedMemory::StopFetchingDeviceMotionData() {
+ DataFetcherImplAndroid::GetInstance()->StopFetchingDeviceMotionData();
+ started_ = false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/data_fetcher_shared_memory_default.cc b/chromium/content/browser/device_orientation/data_fetcher_shared_memory_default.cc
new file mode 100644
index 00000000000..0f66fc79773
--- /dev/null
+++ b/chromium/content/browser/device_orientation/data_fetcher_shared_memory_default.cc
@@ -0,0 +1,43 @@
+// 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 "data_fetcher_shared_memory.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+DataFetcherSharedMemory::~DataFetcherSharedMemory() {
+ if (started_)
+ StopFetchingDeviceMotionData();
+}
+
+bool DataFetcherSharedMemory::NeedsPolling() {
+ return false;
+}
+
+bool DataFetcherSharedMemory::FetchDeviceMotionDataIntoBuffer() {
+ NOTREACHED();
+ return false;
+}
+
+bool DataFetcherSharedMemory::StartFetchingDeviceMotionData(
+ DeviceMotionHardwareBuffer* buffer) {
+ DCHECK(buffer);
+ device_motion_buffer_ = buffer;
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.allAvailableSensorsAreActive = true;
+ device_motion_buffer_->seqlock.WriteEnd();
+ started_ = true;
+ return true;
+}
+
+void DataFetcherSharedMemory::StopFetchingDeviceMotionData() {
+ device_motion_buffer_->seqlock.WriteBegin();
+ device_motion_buffer_->data.allAvailableSensorsAreActive = false;
+ device_motion_buffer_->seqlock.WriteEnd();
+ started_ = false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_data.h b/chromium/content/browser/device_orientation/device_data.h
new file mode 100644
index 00000000000..e8c79add64c
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_data.h
@@ -0,0 +1,43 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_DATA_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_DATA_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+
+class CONTENT_EXPORT DeviceData :
+ public base::RefCountedThreadSafe<DeviceData> {
+ public:
+ // TODO(timvolodine): move the DeviceData::Type enum to the service class
+ // once it is implemented.
+ enum Type {
+ kTypeOrientation = 0,
+ kTypeMotion = 1,
+ kTypeTest = 100
+ };
+
+ virtual IPC::Message* CreateIPCMessage(int render_view_id) const = 0;
+ virtual bool ShouldFireEvent(const DeviceData* other) const = 0;
+
+ protected:
+ DeviceData() {}
+ virtual ~DeviceData() {}
+
+ private:
+ friend class base::RefCountedThreadSafe<DeviceData>;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceData);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_DATA_H_
diff --git a/chromium/content/browser/device_orientation/device_motion_message_filter.cc b/chromium/content/browser/device_orientation/device_motion_message_filter.cc
new file mode 100644
index 00000000000..3c619c6cd16
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_message_filter.cc
@@ -0,0 +1,61 @@
+// 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 "content/browser/device_orientation/device_motion_message_filter.h"
+
+#include "content/browser/device_orientation/device_motion_service.h"
+#include "content/common/device_orientation/device_motion_messages.h"
+
+namespace content {
+
+DeviceMotionMessageFilter::DeviceMotionMessageFilter()
+ : is_started_(false) {
+}
+
+DeviceMotionMessageFilter::~DeviceMotionMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (is_started_)
+ DeviceMotionService::GetInstance()->RemoveConsumer();
+}
+
+bool DeviceMotionMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(DeviceMotionMessageFilter,
+ message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(DeviceMotionHostMsg_StartPolling,
+ OnDeviceMotionStartPolling)
+ IPC_MESSAGE_HANDLER(DeviceMotionHostMsg_StopPolling,
+ OnDeviceMotionStopPolling)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void DeviceMotionMessageFilter::OnDeviceMotionStartPolling() {
+ DCHECK(!is_started_);
+ if (is_started_)
+ return;
+ is_started_ = true;
+ DeviceMotionService::GetInstance()->AddConsumer();
+ DidStartDeviceMotionPolling();
+}
+
+void DeviceMotionMessageFilter::OnDeviceMotionStopPolling() {
+ DCHECK(is_started_);
+ if (!is_started_)
+ return;
+ is_started_ = false;
+ DeviceMotionService::GetInstance()->RemoveConsumer();
+}
+
+void DeviceMotionMessageFilter::DidStartDeviceMotionPolling() {
+ Send(new DeviceMotionMsg_DidStartPolling(
+ DeviceMotionService::GetInstance()->GetSharedMemoryHandleForProcess(
+ PeerHandle())));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_motion_message_filter.h b/chromium/content/browser/device_orientation/device_motion_message_filter.h
new file mode 100644
index 00000000000..3d078e335cc
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_message_filter.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_DEVICE_MOTION_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_DEVICE_MOTION_MESSAGE_FILTER_H_
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class DeviceMotionService;
+class RenderProcessHost;
+
+class DeviceMotionMessageFilter : public BrowserMessageFilter {
+ public:
+ DeviceMotionMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~DeviceMotionMessageFilter();
+
+ void OnDeviceMotionStartPolling();
+ void OnDeviceMotionStopPolling();
+ void DidStartDeviceMotionPolling();
+
+ bool is_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceMotionMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_DEVICE_MOTION_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/device_orientation/device_motion_provider.cc b/chromium/content/browser/device_orientation/device_motion_provider.cc
new file mode 100644
index 00000000000..e88d5af83f4
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_provider.cc
@@ -0,0 +1,172 @@
+// 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 "content/browser/device_orientation/device_motion_provider.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/threading/thread.h"
+#include "base/timer/timer.h"
+#include "content/browser/device_orientation/data_fetcher_shared_memory.h"
+#include "content/common/device_motion_hardware_buffer.h"
+
+namespace content {
+
+namespace {
+const int kPeriodInMilliseconds = 100;
+}
+
+class DeviceMotionProvider::PollingThread : public base::Thread {
+ public:
+ explicit PollingThread(const char* name);
+ virtual ~PollingThread();
+
+ void StartPolling(DataFetcherSharedMemory* fetcher,
+ DeviceMotionHardwareBuffer* buffer);
+ void StopPolling();
+
+ private:
+ void DoPoll();
+
+ scoped_ptr<base::RepeatingTimer<PollingThread> > timer_;
+ DataFetcherSharedMemory* fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(PollingThread);
+};
+
+// ---- PollingThread methods
+
+DeviceMotionProvider::PollingThread::PollingThread(const char* name)
+ : base::Thread(name) {
+}
+
+DeviceMotionProvider::PollingThread::~PollingThread() {
+}
+
+void DeviceMotionProvider::PollingThread::StartPolling(
+ DataFetcherSharedMemory* fetcher, DeviceMotionHardwareBuffer* buffer) {
+ DCHECK(base::MessageLoop::current() == message_loop());
+ DCHECK(!timer_);
+
+ fetcher_ = fetcher;
+ fetcher_->StartFetchingDeviceMotionData(buffer);
+ timer_.reset(new base::RepeatingTimer<PollingThread>());
+ timer_->Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kPeriodInMilliseconds),
+ this, &PollingThread::DoPoll);
+}
+
+void DeviceMotionProvider::PollingThread::StopPolling() {
+ DCHECK(base::MessageLoop::current() == message_loop());
+ DCHECK(fetcher_);
+ // this will also stop the timer before killing it.
+ timer_.reset();
+ fetcher_->StopFetchingDeviceMotionData();
+}
+
+void DeviceMotionProvider::PollingThread::DoPoll() {
+ DCHECK(base::MessageLoop::current() == message_loop());
+ fetcher_->FetchDeviceMotionDataIntoBuffer();
+}
+
+// ---- end PollingThread methods
+
+DeviceMotionProvider::DeviceMotionProvider()
+ : is_started_(false) {
+ Initialize();
+}
+
+DeviceMotionProvider::DeviceMotionProvider(
+ scoped_ptr<DataFetcherSharedMemory> fetcher)
+ : is_started_(false) {
+ data_fetcher_ = fetcher.Pass();
+ Initialize();
+}
+
+DeviceMotionProvider::~DeviceMotionProvider() {
+ StopFetchingDeviceMotionData();
+ // make sure polling thread stops before data_fetcher_ gets deleted.
+ if (polling_thread_)
+ polling_thread_->Stop();
+ data_fetcher_.reset();
+}
+
+void DeviceMotionProvider::Initialize() {
+ size_t data_size = sizeof(DeviceMotionHardwareBuffer);
+ bool res = device_motion_shared_memory_.CreateAndMapAnonymous(data_size);
+ // TODO(timvolodine): consider not crashing the browser if the check fails.
+ CHECK(res);
+ DeviceMotionHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
+ memset(hwbuf, 0, sizeof(DeviceMotionHardwareBuffer));
+}
+
+base::SharedMemoryHandle DeviceMotionProvider::GetSharedMemoryHandleForProcess(
+ base::ProcessHandle process) {
+ base::SharedMemoryHandle renderer_handle;
+ device_motion_shared_memory_.ShareToProcess(process, &renderer_handle);
+ return renderer_handle;
+}
+
+void DeviceMotionProvider::StartFetchingDeviceMotionData() {
+ if (is_started_)
+ return;
+
+ if (!data_fetcher_)
+ data_fetcher_.reset(new DataFetcherSharedMemory);
+
+ if (data_fetcher_->NeedsPolling()) {
+ if (!polling_thread_)
+ CreateAndStartPollingThread();
+
+ polling_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&PollingThread::StartPolling,
+ base::Unretained(polling_thread_.get()),
+ data_fetcher_.get(),
+ SharedMemoryAsHardwareBuffer()));
+ } else {
+ data_fetcher_->StartFetchingDeviceMotionData(
+ SharedMemoryAsHardwareBuffer());
+ }
+
+ is_started_ = true;
+}
+
+void DeviceMotionProvider::CreateAndStartPollingThread() {
+ polling_thread_.reset(
+ new PollingThread("Device Motion poller"));
+
+ if (!polling_thread_->Start()) {
+ LOG(ERROR) << "Failed to start Device Motion data polling thread";
+ return;
+ }
+}
+
+void DeviceMotionProvider::StopFetchingDeviceMotionData() {
+ if (!is_started_)
+ return;
+
+ DCHECK(data_fetcher_);
+
+ if (data_fetcher_->NeedsPolling()) {
+ DCHECK(polling_thread_);
+ polling_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&PollingThread::StopPolling,
+ base::Unretained(polling_thread_.get())));
+ } else {
+ data_fetcher_->StopFetchingDeviceMotionData();
+ }
+
+ is_started_ = false;
+}
+
+DeviceMotionHardwareBuffer*
+DeviceMotionProvider::SharedMemoryAsHardwareBuffer() {
+ void* mem = device_motion_shared_memory_.memory();
+ CHECK(mem);
+ return static_cast<DeviceMotionHardwareBuffer*>(mem);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_motion_provider.h b/chromium/content/browser/device_orientation/device_motion_provider.h
new file mode 100644
index 00000000000..8fd36de33d6
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_provider.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_PROVIDER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_PROVIDER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "content/common/content_export.h"
+#include "content/common/device_motion_hardware_buffer.h"
+
+namespace content {
+class DataFetcherSharedMemory;
+
+// This class owns the shared memory buffer for Device Motion and makes
+// sure the data is fetched into that buffer.
+// When DataFetcherSharedMemory::NeedsPolling() is true, it starts a
+// background polling thread to make sure the data is fetched at regular
+// intervals.
+class CONTENT_EXPORT DeviceMotionProvider {
+ public:
+ DeviceMotionProvider();
+
+ // Creates provider with a custom fetcher. Used for testing.
+ explicit DeviceMotionProvider(scoped_ptr<DataFetcherSharedMemory> fetcher);
+
+ virtual ~DeviceMotionProvider();
+
+ // Returns the shared memory handle of the device motion data duplicated
+ // into the given process.
+ base::SharedMemoryHandle GetSharedMemoryHandleForProcess(
+ base::ProcessHandle renderer_process);
+
+ void StartFetchingDeviceMotionData();
+ void StopFetchingDeviceMotionData();
+
+ private:
+ class PollingThread;
+
+ void Initialize();
+ void CreateAndStartPollingThread();
+
+ DeviceMotionHardwareBuffer* SharedMemoryAsHardwareBuffer();
+
+ base::SharedMemory device_motion_shared_memory_;
+ scoped_ptr<DataFetcherSharedMemory> data_fetcher_;
+ scoped_ptr<PollingThread> polling_thread_;
+ bool is_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceMotionProvider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_PROVIDER_H_
diff --git a/chromium/content/browser/device_orientation/device_motion_provider_unittest.cc b/chromium/content/browser/device_orientation/device_motion_provider_unittest.cc
new file mode 100644
index 00000000000..cd0f3d843e3
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_provider_unittest.cc
@@ -0,0 +1,98 @@
+// 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 "content/browser/device_orientation/device_motion_provider.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/browser/device_orientation/data_fetcher_shared_memory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+const int kPeriodInMilliseconds = 100;
+
+class FakeDataFetcherSharedMemory : public DataFetcherSharedMemory {
+ public:
+ FakeDataFetcherSharedMemory()
+ : start_fetching_data_(false, false),
+ stop_fetching_data_(false, false),
+ fetched_data_(false, false) {
+ }
+ virtual ~FakeDataFetcherSharedMemory() { }
+
+ virtual bool NeedsPolling() OVERRIDE {
+ return true;
+ }
+
+ virtual bool FetchDeviceMotionDataIntoBuffer() OVERRIDE {
+ buffer_->seqlock.WriteBegin();
+ buffer_->data.interval = kPeriodInMilliseconds;
+ buffer_->seqlock.WriteEnd();
+ fetched_data_.Signal();
+ return true;
+ }
+
+ virtual bool StartFetchingDeviceMotionData(
+ DeviceMotionHardwareBuffer* buffer) OVERRIDE {
+ buffer_ = buffer;
+ start_fetching_data_.Signal();
+ return true;
+ }
+
+ virtual void StopFetchingDeviceMotionData() OVERRIDE {
+ stop_fetching_data_.Signal();
+ }
+
+ void WaitForStart() {
+ start_fetching_data_.Wait();
+ }
+
+ void WaitForStop() {
+ stop_fetching_data_.Wait();
+ }
+
+ void WaitForDataFetch() {
+ fetched_data_.Wait();
+ }
+
+ DeviceMotionHardwareBuffer* GetBuffer() {
+ return buffer_;
+ }
+
+ private:
+ base::WaitableEvent start_fetching_data_;
+ base::WaitableEvent stop_fetching_data_;
+ base::WaitableEvent fetched_data_;
+ DeviceMotionHardwareBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDataFetcherSharedMemory);
+};
+
+
+TEST(DeviceMotionProviderTest, DoesPolling) {
+ FakeDataFetcherSharedMemory* mock_data_fetcher =
+ new FakeDataFetcherSharedMemory();
+ EXPECT_TRUE(mock_data_fetcher->NeedsPolling());
+
+ scoped_ptr<DeviceMotionProvider> provider(new DeviceMotionProvider(
+ scoped_ptr<DataFetcherSharedMemory>(mock_data_fetcher)));
+
+ provider->StartFetchingDeviceMotionData();
+ mock_data_fetcher->WaitForStart();
+ mock_data_fetcher->WaitForDataFetch();
+
+ EXPECT_EQ(kPeriodInMilliseconds,
+ mock_data_fetcher->GetBuffer()->data.interval);
+
+ provider->StopFetchingDeviceMotionData();
+ mock_data_fetcher->WaitForStop();
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_motion_service.cc b/chromium/content/browser/device_orientation/device_motion_service.cc
new file mode 100644
index 00000000000..48b784b925c
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_service.cc
@@ -0,0 +1,65 @@
+// 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 "content/browser/device_orientation/device_motion_service.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "content/browser/device_orientation/device_motion_provider.h"
+#include "content/public/browser/render_process_host.h"
+
+namespace content {
+
+DeviceMotionService::DeviceMotionService()
+ : num_readers_(0),
+ is_shutdown_(false) {
+}
+
+DeviceMotionService::~DeviceMotionService() {
+}
+
+DeviceMotionService* DeviceMotionService::GetInstance() {
+ return Singleton<DeviceMotionService,
+ LeakySingletonTraits<DeviceMotionService> >::get();
+}
+
+void DeviceMotionService::AddConsumer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (is_shutdown_)
+ return;
+
+ num_readers_++;
+ DCHECK(num_readers_ > 0);
+ if (!provider_.get())
+ provider_.reset(new DeviceMotionProvider);
+ provider_->StartFetchingDeviceMotionData();
+}
+
+void DeviceMotionService::RemoveConsumer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (is_shutdown_)
+ return;
+
+ --num_readers_;
+ DCHECK(num_readers_ >= 0);
+
+ if (num_readers_ == 0) {
+ LOG(INFO) << "ACTIVE service stop fetching";
+ provider_->StopFetchingDeviceMotionData();
+ }
+}
+
+void DeviceMotionService::Shutdown() {
+ provider_.reset();
+ is_shutdown_ = true;
+}
+
+base::SharedMemoryHandle DeviceMotionService::GetSharedMemoryHandleForProcess(
+ base::ProcessHandle handle) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return provider_->GetSharedMemoryHandleForProcess(handle);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_motion_service.h b/chromium/content/browser/device_orientation/device_motion_service.h
new file mode 100644
index 00000000000..0720bb95968
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_motion_service.h
@@ -0,0 +1,65 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_SERVICE_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_SERVICE_H_
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/singleton.h"
+#include "base/threading/thread_checker.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class DataFetcherSharedMemory;
+class DeviceMotionProvider;
+class RenderProcessHost;
+
+// Owns the DeviceMotionProvider (the background polling thread) and keeps
+// track of the number of consumers currently using the data (and pausing
+// the provider when not in use).
+class CONTENT_EXPORT DeviceMotionService {
+ public:
+ // Returns the DeviceMotionService singleton.
+ static DeviceMotionService* GetInstance();
+
+ // Increments the number of users of the provider. The Provider is running
+ // when there's > 0 users, and is paused when the count drops to 0.
+ //
+ // Must be called on the I/O thread.
+ void AddConsumer();
+
+ // Removes a consumer. Should be matched with an AddConsumer call.
+ //
+ // Must be called on the I/O thread.
+ void RemoveConsumer();
+
+ // Returns the shared memory handle of the device motion data duplicated
+ // into the given process.
+ base::SharedMemoryHandle GetSharedMemoryHandleForProcess(
+ base::ProcessHandle handle);
+
+ // Stop/join with the background thread in DeviceMotionProvider |provider_|.
+ void Shutdown();
+
+ private:
+ friend struct DefaultSingletonTraits<DeviceMotionService>;
+
+ DeviceMotionService();
+ virtual ~DeviceMotionService();
+
+ int num_readers_;
+ bool is_shutdown_;
+ scoped_ptr<DeviceMotionProvider> provider_;
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceMotionService);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_MOTION_SERVICE_H_
diff --git a/chromium/content/browser/device_orientation/device_orientation_browsertest.cc b/chromium/content/browser/device_orientation/device_orientation_browsertest.cc
new file mode 100644
index 00000000000..095e85f310c
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_orientation_browsertest.cc
@@ -0,0 +1,79 @@
+// 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 "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/browser/device_orientation/orientation.h"
+#include "content/browser/device_orientation/provider.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+class MockProvider : public Provider {
+ public:
+ MockProvider(const DeviceData* device_data, DeviceData::Type type)
+ : device_data_(device_data),
+ device_data_type_(type),
+ added_observer_(false),
+ removed_observer_(false) {
+ }
+
+ virtual void AddObserver(Observer* observer) OVERRIDE {
+ added_observer_ = true;
+ observer->OnDeviceDataUpdate(device_data_.get(), device_data_type_);
+ }
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {
+ removed_observer_ = true;
+ }
+
+ scoped_refptr<const DeviceData> device_data_;
+ DeviceData::Type device_data_type_;
+ bool added_observer_;
+ bool removed_observer_;
+
+ private:
+ virtual ~MockProvider() {}
+};
+
+class DeviceOrientationBrowserTest : public ContentBrowserTest {
+ public:
+ // From ContentBrowserTest.
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ EXPECT_TRUE(!command_line->HasSwitch(switches::kDisableDeviceOrientation));
+ }
+};
+
+// crbug.com/113952
+IN_PROC_BROWSER_TEST_F(DeviceOrientationBrowserTest, BasicTest) {
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+ scoped_refptr<MockProvider> provider(
+ new MockProvider(test_orientation.get(), DeviceData::kTypeOrientation));
+ Provider::SetInstanceForTests(provider.get());
+
+ // The test page will register an event handler for orientation events,
+ // expects to get an event with kTestOrientation orientation,
+ // then removes the event handler and navigates to #pass.
+ GURL test_url = GetTestUrl(
+ "device_orientation", "device_orientation_test.html");
+ NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
+
+ // Check that the page got the event it expected and that the provider
+ // saw requests for adding and removing an observer.
+ EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
+ EXPECT_TRUE(provider->added_observer_);
+ EXPECT_TRUE(provider->removed_observer_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_orientation_message_filter.cc b/chromium/content/browser/device_orientation/device_orientation_message_filter.cc
new file mode 100644
index 00000000000..cbb1e674c0e
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_orientation_message_filter.cc
@@ -0,0 +1,70 @@
+// 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 "content/browser/device_orientation/device_orientation_message_filter.h"
+
+#include "content/browser/device_orientation/device_motion_service.h"
+#include "content/common/device_orientation/device_orientation_messages.h"
+
+namespace content {
+
+DeviceOrientationMessageFilter::DeviceOrientationMessageFilter()
+ : is_started_(false) {
+}
+
+DeviceOrientationMessageFilter::~DeviceOrientationMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (is_started_) {
+ // TODO(timvolodine): insert a proper call to DeviceSensorService here,
+ // similar to DeviceMotionService::GetInstance()->RemoveConsumer();
+ }
+}
+
+bool DeviceOrientationMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(DeviceOrientationMessageFilter,
+ message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(DeviceOrientationHostMsg_StartPolling,
+ OnDeviceOrientationStartPolling)
+ IPC_MESSAGE_HANDLER(DeviceOrientationHostMsg_StopPolling,
+ OnDeviceOrientationStopPolling)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void DeviceOrientationMessageFilter::OnDeviceOrientationStartPolling() {
+ NOTIMPLEMENTED();
+ DCHECK(!is_started_);
+ if (is_started_)
+ return;
+ is_started_ = true;
+ // TODO(timvolodine): insert a proper call to DeviceSensorService here,
+ // similar to DeviceMotionService::GetInstance()->AddConsumer();
+ DidStartDeviceOrientationPolling();
+}
+
+void DeviceOrientationMessageFilter::OnDeviceOrientationStopPolling() {
+ NOTIMPLEMENTED();
+ DCHECK(is_started_);
+ if (!is_started_)
+ return;
+ is_started_ = false;
+ // TODO(timvolodine): insert a proper call to DeviceSensorService here,
+ // similar to DeviceMotionService::GetInstance()->RemoveConsumer();
+}
+
+void DeviceOrientationMessageFilter::DidStartDeviceOrientationPolling() {
+ NOTIMPLEMENTED();
+ // TODO(timvolodine): insert a proper call to the generalized Service here,
+ // similar to
+ // Send(new DeviceOrientationMsg_DidStartPolling(
+ // DeviceMotionService::GetInstance()->GetSharedMemoryHandleForProcess(
+ // PeerHandle())));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/device_orientation_message_filter.h b/chromium/content/browser/device_orientation/device_orientation_message_filter.h
new file mode 100644
index 00000000000..b8ccb84a49c
--- /dev/null
+++ b/chromium/content/browser/device_orientation/device_orientation_message_filter.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class RenderProcessHost;
+
+class DeviceOrientationMessageFilter : public BrowserMessageFilter {
+ public:
+ DeviceOrientationMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~DeviceOrientationMessageFilter();
+
+ void OnDeviceOrientationStartPolling();
+ void OnDeviceOrientationStopPolling();
+ void DidStartDeviceOrientationPolling();
+
+ bool is_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceOrientationMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/device_orientation/message_filter.cc b/chromium/content/browser/device_orientation/message_filter.cc
new file mode 100644
index 00000000000..b20071daeed
--- /dev/null
+++ b/chromium/content/browser/device_orientation/message_filter.cc
@@ -0,0 +1,38 @@
+// 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 "content/browser/device_orientation/message_filter.h"
+
+#include "content/browser/device_orientation/observer_delegate.h"
+#include "content/browser/device_orientation/provider.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+DeviceOrientationMessageFilterOld::DeviceOrientationMessageFilterOld(
+ DeviceData::Type device_data_type)
+ : provider_(NULL),
+ device_data_type_(device_data_type) {
+}
+
+DeviceOrientationMessageFilterOld::~DeviceOrientationMessageFilterOld() {
+}
+
+void DeviceOrientationMessageFilterOld::OnStartUpdating(int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!provider_.get())
+ provider_ = Provider::GetInstance();
+
+ observers_map_[render_view_id] = new ObserverDelegate(
+ device_data_type_, provider_.get(), render_view_id, this);
+}
+
+void DeviceOrientationMessageFilterOld::OnStopUpdating(int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ observers_map_.erase(render_view_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/message_filter.h b/chromium/content/browser/device_orientation/message_filter.h
new file mode 100644
index 00000000000..c5dce140124
--- /dev/null
+++ b/chromium/content/browser/device_orientation/message_filter.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
+
+#include <map>
+
+#include "content/browser/device_orientation/device_data.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+// Helper class that observes a Provider and forwards updates to a RenderView.
+class ObserverDelegate;
+
+class Provider;
+
+class DeviceOrientationMessageFilterOld : public BrowserMessageFilter {
+ public:
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE = 0;
+
+ protected:
+ DeviceOrientationMessageFilterOld(DeviceData::Type device_data_type);
+ virtual ~DeviceOrientationMessageFilterOld();
+
+ void OnStartUpdating(int render_view_id);
+ void OnStopUpdating(int render_view_id);
+
+ private:
+
+ // map from render_view_id to ObserverDelegate.
+ std::map<int, scoped_refptr<ObserverDelegate> > observers_map_;
+
+ scoped_refptr<Provider> provider_;
+ DeviceData::Type device_data_type_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/device_orientation/observer_delegate.cc b/chromium/content/browser/device_orientation/observer_delegate.cc
new file mode 100644
index 00000000000..5c233109dd7
--- /dev/null
+++ b/chromium/content/browser/device_orientation/observer_delegate.cc
@@ -0,0 +1,49 @@
+// 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 "content/browser/device_orientation/observer_delegate.h"
+
+#include "base/logging.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/browser/device_orientation/orientation.h"
+#include "ipc/ipc_sender.h"
+
+namespace content {
+
+ObserverDelegate::ObserverDelegate(DeviceData::Type device_data_type,
+ Provider* provider, int render_view_id,
+ IPC::Sender* sender)
+ : Observer(device_data_type),
+ provider_(provider),
+ render_view_id_(render_view_id),
+ sender_(sender) {
+ provider_->AddObserver(this);
+}
+
+ObserverDelegate::~ObserverDelegate() {
+ provider_->RemoveObserver(this);
+}
+
+void ObserverDelegate::OnDeviceDataUpdate(
+ const DeviceData* device_data, DeviceData::Type device_data_type) {
+ scoped_refptr<const DeviceData> new_device_data(device_data);
+ if (!new_device_data.get())
+ new_device_data = EmptyDeviceData(device_data_type);
+
+ sender_->Send(new_device_data->CreateIPCMessage(render_view_id_));
+}
+
+DeviceData* ObserverDelegate::EmptyDeviceData(DeviceData::Type type) {
+ switch (type) {
+ case DeviceData::kTypeOrientation:
+ return new Orientation();
+ case DeviceData::kTypeMotion:
+ case DeviceData::kTypeTest:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/observer_delegate.h b/chromium/content/browser/device_orientation/observer_delegate.h
new file mode 100644
index 00000000000..8c7dab84b08
--- /dev/null
+++ b/chromium/content/browser/device_orientation/observer_delegate.h
@@ -0,0 +1,45 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_OBSERVER_DELEGATE_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_OBSERVER_DELEGATE_H_
+
+#include "content/browser/device_orientation/device_data.h"
+#include "content/browser/device_orientation/provider.h"
+
+namespace IPC {
+class Sender;
+}
+
+namespace content {
+
+class ObserverDelegate
+ : public base::RefCounted<ObserverDelegate>, public Provider::Observer {
+ public:
+ // Create ObserverDelegate that observes provider and forwards updates to
+ // render_view_id.
+ // Will stop observing provider when destructed.
+ ObserverDelegate(DeviceData::Type device_data_type, Provider* provider,
+ int render_view_id, IPC::Sender* sender);
+
+ // From Provider::Observer.
+ virtual void OnDeviceDataUpdate(const DeviceData* device_data,
+ DeviceData::Type device_data_type) OVERRIDE;
+
+ private:
+ static DeviceData* EmptyDeviceData(DeviceData::Type type);
+
+ friend class base::RefCounted<ObserverDelegate>;
+ virtual ~ObserverDelegate();
+
+ scoped_refptr<Provider> provider_;
+ int render_view_id_;
+ IPC::Sender* sender_; // Weak pointer.
+
+ DISALLOW_COPY_AND_ASSIGN(ObserverDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_OBSERVER_DELEGATE_H_
diff --git a/chromium/content/browser/device_orientation/orientation.cc b/chromium/content/browser/device_orientation/orientation.cc
new file mode 100644
index 00000000000..b0b463fcad0
--- /dev/null
+++ b/chromium/content/browser/device_orientation/orientation.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/device_orientation/orientation.h"
+
+#include <cmath>
+
+#include "content/common/device_orientation/device_orientation_messages.h"
+
+namespace content {
+
+Orientation::Orientation()
+ : can_provide_alpha_(false),
+ can_provide_beta_(false),
+ can_provide_gamma_(false),
+ can_provide_absolute_(false) {
+}
+
+Orientation::~Orientation() {
+}
+
+IPC::Message* Orientation::CreateIPCMessage(int render_view_id) const {
+ DeviceOrientationMsg_Updated_Params params;
+ params.can_provide_alpha = can_provide_alpha_;
+ params.alpha = alpha_;
+ params.can_provide_beta = can_provide_beta_;
+ params.beta = beta_;
+ params.can_provide_gamma = can_provide_gamma_;
+ params.gamma = gamma_;
+ params.can_provide_absolute = can_provide_absolute_;
+ params.absolute = absolute_;
+
+ return new DeviceOrientationMsg_Updated(render_view_id, params);
+}
+
+// Returns true if two orientations are considered different enough that
+// observers should be notified of the new orientation.
+bool Orientation::ShouldFireEvent(const DeviceData* old_data) const {
+ scoped_refptr<const Orientation> old_orientation(
+ static_cast<const Orientation*>(old_data));
+
+ return IsElementSignificantlyDifferent(can_provide_alpha_,
+ old_orientation->can_provide_alpha(),
+ alpha_,
+ old_orientation->alpha()) ||
+ IsElementSignificantlyDifferent(can_provide_beta_,
+ old_orientation->can_provide_beta(),
+ beta_,
+ old_orientation->beta()) ||
+ IsElementSignificantlyDifferent(can_provide_gamma_,
+ old_orientation->can_provide_gamma(),
+ gamma_,
+ old_orientation->gamma()) ||
+ can_provide_absolute_ != old_orientation->can_provide_absolute() ||
+ absolute_ != old_orientation->absolute();
+}
+
+bool Orientation::IsElementSignificantlyDifferent(bool can_provide_element1,
+ bool can_provide_element2, double element1, double element2) {
+ const double kThreshold = 0.1;
+
+ if (can_provide_element1 != can_provide_element2)
+ return true;
+ if (can_provide_element1 && std::fabs(element1 - element2) >= kThreshold)
+ return true;
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/orientation.h b/chromium/content/browser/device_orientation/orientation.h
new file mode 100644
index 00000000000..b3ebd27c6e0
--- /dev/null
+++ b/chromium/content/browser/device_orientation/orientation.h
@@ -0,0 +1,73 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class Orientation : public DeviceData {
+ public:
+ // alpha, beta, gamma and absolute are the rotations around the axes as
+ // specified in http://dev.w3.org/geo/api/spec-source-orientation.html
+ //
+ // can_provide_{alpha,beta,gamma,absolute} is true if data can be provided
+ // for that variable.
+ CONTENT_EXPORT Orientation();
+
+ // From DeviceData.
+ virtual IPC::Message* CreateIPCMessage(int render_view_id) const OVERRIDE;
+ virtual bool ShouldFireEvent(const DeviceData* old_data) const OVERRIDE;
+
+ void set_alpha(double alpha) {
+ can_provide_alpha_ = true;
+ alpha_ = alpha;
+ }
+ bool can_provide_alpha() const { return can_provide_alpha_; }
+ double alpha() const { return alpha_; }
+
+ void set_beta(double beta) {
+ can_provide_beta_ = true;
+ beta_ = beta;
+ }
+ bool can_provide_beta() const { return can_provide_beta_; }
+ double beta() const { return beta_; }
+
+ void set_gamma(double gamma) {
+ can_provide_gamma_ = true;
+ gamma_ = gamma;
+ }
+ bool can_provide_gamma() const { return can_provide_gamma_; }
+ double gamma() const { return gamma_; }
+
+ void set_absolute(bool absolute) {
+ can_provide_absolute_ = true;
+ absolute_ = absolute;
+ }
+ bool can_provide_absolute() const { return can_provide_absolute_; }
+ bool absolute() const { return absolute_; }
+
+ private:
+ virtual ~Orientation();
+
+ static bool IsElementSignificantlyDifferent(bool can_provide_element1,
+ bool can_provide_element2, double element1, double element2);
+
+ double alpha_;
+ double beta_;
+ double gamma_;
+ bool absolute_;
+ bool can_provide_alpha_;
+ bool can_provide_beta_;
+ bool can_provide_gamma_;
+ bool can_provide_absolute_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_H_
diff --git a/chromium/content/browser/device_orientation/orientation_message_filter.cc b/chromium/content/browser/device_orientation/orientation_message_filter.cc
new file mode 100644
index 00000000000..b7cde576981
--- /dev/null
+++ b/chromium/content/browser/device_orientation/orientation_message_filter.cc
@@ -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.
+
+#include "content/browser/device_orientation/orientation_message_filter.h"
+
+#include "content/browser/device_orientation/device_data.h"
+#include "content/common/device_orientation/device_orientation_messages.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+OrientationMessageFilter::OrientationMessageFilter()
+ : DeviceOrientationMessageFilterOld(DeviceData::kTypeOrientation) {
+}
+
+OrientationMessageFilter::~OrientationMessageFilter() {
+}
+
+bool OrientationMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(OrientationMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(DeviceOrientationHostMsg_StartUpdating, OnStartUpdating)
+ IPC_MESSAGE_HANDLER(DeviceOrientationHostMsg_StopUpdating, OnStopUpdating)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/orientation_message_filter.h b/chromium/content/browser/device_orientation/orientation_message_filter.h
new file mode 100644
index 00000000000..80181d52a9f
--- /dev/null
+++ b/chromium/content/browser/device_orientation/orientation_message_filter.h
@@ -0,0 +1,30 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_MESSAGE_FILTER_H_
+
+#include <map>
+
+#include "content/browser/device_orientation/message_filter.h"
+
+namespace content {
+
+class OrientationMessageFilter : public DeviceOrientationMessageFilterOld {
+ public:
+ OrientationMessageFilter();
+
+ // DeviceOrientationMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~OrientationMessageFilter();
+
+ DISALLOW_COPY_AND_ASSIGN(OrientationMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_ORIENTATION_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/device_orientation/provider.cc b/chromium/content/browser/device_orientation/provider.cc
new file mode 100644
index 00000000000..49fc4f2164f
--- /dev/null
+++ b/chromium/content/browser/device_orientation/provider.cc
@@ -0,0 +1,59 @@
+// 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 "content/browser/device_orientation/provider.h"
+
+#include "base/logging.h"
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/provider_impl.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_MACOSX)
+#include "content/browser/device_orientation/accelerometer_mac.h"
+#elif defined(OS_ANDROID)
+#include "content/browser/device_orientation/data_fetcher_orientation_android.h"
+#elif defined(OS_WIN)
+#include "content/browser/device_orientation/data_fetcher_impl_win.h"
+#endif
+
+namespace content {
+
+Provider* Provider::GetInstance() {
+ if (!instance_) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ProviderImpl::DataFetcherFactory default_factory = NULL;
+
+#if defined(OS_MACOSX)
+ default_factory = AccelerometerMac::Create;
+#elif defined(OS_ANDROID)
+ default_factory = DataFetcherOrientationAndroid::Create;
+#elif defined(OS_WIN)
+ default_factory = DataFetcherImplWin::Create;
+#endif
+
+ instance_ = new ProviderImpl(default_factory);
+ }
+ return instance_;
+}
+
+void Provider::SetInstanceForTests(Provider* provider) {
+ DCHECK(!instance_);
+ instance_ = provider;
+}
+
+Provider* Provider::GetInstanceForTests() {
+ return instance_;
+}
+
+Provider::Provider() {
+}
+
+Provider::~Provider() {
+ DCHECK(instance_ == this);
+ instance_ = NULL;
+}
+
+Provider* Provider::instance_ = NULL;
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/provider.h b/chromium/content/browser/device_orientation/provider.h
new file mode 100644
index 00000000000..4bc0635fda6
--- /dev/null
+++ b/chromium/content/browser/device_orientation/provider.h
@@ -0,0 +1,67 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class CONTENT_EXPORT Provider : public base::RefCountedThreadSafe<Provider> {
+ public:
+ class Observer {
+ public:
+ // Called when device data changes.
+ // An Observer must not synchronously call Provider::RemoveObserver
+ // or Provider::AddObserver when this is called.
+ virtual void OnDeviceDataUpdate(const DeviceData* device_data,
+ DeviceData::Type device_data_type) = 0;
+ DeviceData::Type device_data_type() { return device_data_type_; }
+
+ protected:
+ Observer(DeviceData::Type device_data_type)
+ : device_data_type_(device_data_type) {
+ }
+ virtual ~Observer() {}
+
+ private:
+ // Each Observer observes exactly one type of DeviceData.
+ DeviceData::Type device_data_type_;
+ };
+
+ // Returns a pointer to the singleton instance of this class.
+ // The caller should store the returned pointer in a scoped_refptr.
+ // The Provider instance is lazily constructed when GetInstance() is called,
+ // and destructed when the last scoped_refptr referring to it is destructed.
+ static Provider* GetInstance();
+
+ // Inject a mock Provider for testing. Only a weak pointer to the injected
+ // object will be held by Provider, i.e. it does not itself contribute to the
+ // injected object's reference count.
+ static void SetInstanceForTests(Provider* provider);
+
+ // Get the current instance. Used for testing.
+ static Provider* GetInstanceForTests();
+
+ // Note: AddObserver may call back synchronously to the observer with data.
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ protected:
+ Provider();
+ virtual ~Provider();
+
+ private:
+ friend class base::RefCountedThreadSafe<Provider>;
+ static Provider* instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(Provider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_H_
diff --git a/chromium/content/browser/device_orientation/provider_impl.cc b/chromium/content/browser/device_orientation/provider_impl.cc
new file mode 100644
index 00000000000..6cc63212cc4
--- /dev/null
+++ b/chromium/content/browser/device_orientation/provider_impl.cc
@@ -0,0 +1,292 @@
+// 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 "content/browser/device_orientation/provider_impl.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/worker_pool.h"
+
+namespace {
+
+void DeleteThread(base::Thread* thread) {
+ delete thread;
+}
+
+}
+
+namespace content {
+
+class ProviderImpl::PollingThread : public base::Thread {
+ public:
+ PollingThread(const char* name,
+ base::WeakPtr<ProviderImpl> provider,
+ base::MessageLoop* creator_loop);
+ virtual ~PollingThread();
+
+ // Method for creating a DataFetcher and starting the polling, if the fetcher
+ // can provide this type of data.
+ void Initialize(DataFetcherFactory factory, DeviceData::Type type);
+
+ // Method for adding a type of data to poll for.
+ void DoAddPollingDataType(DeviceData::Type type);
+
+ private:
+ // Method for polling a DataFetcher.
+ void DoPoll();
+ void ScheduleDoPoll();
+
+ // Schedule a notification to the |provider_| which lives on a different
+ // thread (|creator_loop_| is its message loop).
+ void ScheduleDoNotify(const scoped_refptr<const DeviceData>& device_data,
+ DeviceData::Type device_data_type);
+
+ enum { kDesiredSamplingIntervalMs = 100 };
+ base::TimeDelta SamplingInterval() const;
+
+ // The Message Loop on which this object was created.
+ // Typically the I/O loop, but may be something else during testing.
+ base::MessageLoop* creator_loop_;
+
+ scoped_ptr<DataFetcher> data_fetcher_;
+ std::map<DeviceData::Type, scoped_refptr<const DeviceData> >
+ last_device_data_map_;
+ std::set<DeviceData::Type> polling_data_types_;
+
+ base::WeakPtr<ProviderImpl> provider_;
+};
+
+ProviderImpl::PollingThread::PollingThread(const char* name,
+ base::WeakPtr<ProviderImpl> provider,
+ base::MessageLoop* creator_loop)
+ : base::Thread(name), creator_loop_(creator_loop), provider_(provider) {}
+
+ProviderImpl::PollingThread::~PollingThread() {
+ Stop();
+}
+
+void ProviderImpl::PollingThread::DoAddPollingDataType(DeviceData::Type type) {
+ DCHECK(base::MessageLoop::current() == message_loop());
+
+ polling_data_types_.insert(type);
+}
+
+void ProviderImpl::PollingThread::Initialize(DataFetcherFactory factory,
+ DeviceData::Type type) {
+ DCHECK(base::MessageLoop::current() == message_loop());
+
+ if (factory != NULL) {
+ // Try to use factory to create a fetcher that can provide this type of
+ // data. If factory creates a fetcher that provides this type of data,
+ // start polling.
+ scoped_ptr<DataFetcher> fetcher(factory());
+
+ if (fetcher) {
+ scoped_refptr<const DeviceData> device_data(fetcher->GetDeviceData(type));
+ if (device_data.get() != NULL) {
+ // Pass ownership of fetcher to provider_.
+ data_fetcher_.swap(fetcher);
+ last_device_data_map_[type] = device_data;
+
+ // Notify observers.
+ ScheduleDoNotify(device_data, type);
+
+ // Start polling.
+ ScheduleDoPoll();
+ return;
+ }
+ }
+ }
+
+ // When no device data can be provided.
+ ScheduleDoNotify(NULL, type);
+}
+
+void ProviderImpl::PollingThread::ScheduleDoNotify(
+ const scoped_refptr<const DeviceData>& device_data,
+ DeviceData::Type device_data_type) {
+ DCHECK(base::MessageLoop::current() == message_loop());
+
+ creator_loop_->PostTask(FROM_HERE,
+ base::Bind(&ProviderImpl::DoNotify, provider_,
+ device_data, device_data_type));
+}
+
+void ProviderImpl::PollingThread::DoPoll() {
+ DCHECK(base::MessageLoop::current() == message_loop());
+
+ // Poll the fetcher for each type of data.
+ typedef std::set<DeviceData::Type>::const_iterator SetIterator;
+ for (SetIterator i = polling_data_types_.begin();
+ i != polling_data_types_.end(); ++i) {
+ DeviceData::Type device_data_type = *i;
+ scoped_refptr<const DeviceData> device_data(data_fetcher_->GetDeviceData(
+ device_data_type));
+
+ if (device_data.get() == NULL) {
+ LOG(ERROR) << "Failed to poll device data fetcher.";
+ ScheduleDoNotify(NULL, device_data_type);
+ continue;
+ }
+
+ const DeviceData* old_data = last_device_data_map_[device_data_type].get();
+ if (old_data != NULL && !device_data->ShouldFireEvent(old_data))
+ continue;
+
+ // Update the last device data of this type and notify observers.
+ last_device_data_map_[device_data_type] = device_data;
+ ScheduleDoNotify(device_data, device_data_type);
+ }
+
+ ScheduleDoPoll();
+}
+
+void ProviderImpl::PollingThread::ScheduleDoPoll() {
+ DCHECK(base::MessageLoop::current() == message_loop());
+
+ message_loop()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&PollingThread::DoPoll, base::Unretained(this)),
+ SamplingInterval());
+}
+
+base::TimeDelta ProviderImpl::PollingThread::SamplingInterval() const {
+ DCHECK(base::MessageLoop::current() == message_loop());
+ DCHECK(data_fetcher_.get());
+
+ // TODO(erg): There used to be unused code here, that called a default
+ // implementation on the DataFetcherInterface that was never defined. I'm
+ // removing unused methods from headers.
+ return base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs);
+}
+
+ProviderImpl::ProviderImpl(DataFetcherFactory factory)
+ : creator_loop_(base::MessageLoop::current()),
+ factory_(factory),
+ weak_factory_(this),
+ polling_thread_(NULL) {
+}
+
+ProviderImpl::~ProviderImpl() {
+ Stop();
+}
+
+void ProviderImpl::ScheduleDoAddPollingDataType(DeviceData::Type type) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ base::MessageLoop* polling_loop = polling_thread_->message_loop();
+ polling_loop->PostTask(FROM_HERE,
+ base::Bind(&PollingThread::DoAddPollingDataType,
+ base::Unretained(polling_thread_),
+ type));
+}
+
+void ProviderImpl::AddObserver(Observer* observer) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ DeviceData::Type type = observer->device_data_type();
+
+ observers_.insert(observer);
+ if (observers_.size() == 1)
+ Start(type);
+ else {
+ // Notify observer of most recent notification if one exists.
+ const DeviceData* last_notification = last_notifications_map_[type].get();
+ if (last_notification != NULL)
+ observer->OnDeviceDataUpdate(last_notification, type);
+ }
+
+ ScheduleDoAddPollingDataType(type);
+}
+
+void ProviderImpl::RemoveObserver(Observer* observer) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ observers_.erase(observer);
+ if (observers_.empty())
+ Stop();
+}
+
+void ProviderImpl::Start(DeviceData::Type type) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+ DCHECK(!polling_thread_);
+
+ polling_thread_ = new PollingThread("Device data polling thread",
+ weak_factory_.GetWeakPtr(),
+ creator_loop_);
+#if defined(OS_WIN)
+ polling_thread_->init_com_with_mta(true);
+#endif
+ if (!polling_thread_->Start()) {
+ LOG(ERROR) << "Failed to start device data polling thread";
+ delete polling_thread_;
+ polling_thread_ = NULL;
+ return;
+ }
+ ScheduleInitializePollingThread(type);
+}
+
+void ProviderImpl::Stop() {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ weak_factory_.InvalidateWeakPtrs();
+ if (polling_thread_) {
+ polling_thread_->StopSoon();
+ bool posted = base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&DeleteThread, base::Unretained(polling_thread_)),
+ true /* task is slow */);
+ DCHECK(posted);
+ polling_thread_ = NULL;
+ }
+}
+
+void ProviderImpl::ScheduleInitializePollingThread(
+ DeviceData::Type device_data_type) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ base::MessageLoop* polling_loop = polling_thread_->message_loop();
+ polling_loop->PostTask(FROM_HERE,
+ base::Bind(&PollingThread::Initialize,
+ base::Unretained(polling_thread_),
+ factory_,
+ device_data_type));
+}
+
+void ProviderImpl::DoNotify(const scoped_refptr<const DeviceData>& data,
+ DeviceData::Type device_data_type) {
+ DCHECK(base::MessageLoop::current() == creator_loop_);
+
+ // Update last notification of this type.
+ last_notifications_map_[device_data_type] = data;
+
+ // Notify observers of this type of the new data.
+ typedef std::set<Observer*>::const_iterator ConstIterator;
+ for (ConstIterator i = observers_.begin(); i != observers_.end(); ++i) {
+ if ((*i)->device_data_type() == device_data_type)
+ (*i)->OnDeviceDataUpdate(data.get(), device_data_type);
+ }
+
+ if (data.get() == NULL) {
+ // Notify observers exactly once about failure to provide data.
+ typedef std::set<Observer*>::iterator Iterator;
+ Iterator i = observers_.begin();
+ while (i != observers_.end()) {
+ Iterator current = i++;
+ if ((*current)->device_data_type() == device_data_type)
+ observers_.erase(current);
+ }
+
+ if (observers_.empty())
+ Stop();
+ }
+}
+
+
+} // namespace content
diff --git a/chromium/content/browser/device_orientation/provider_impl.h b/chromium/content/browser/device_orientation/provider_impl.h
new file mode 100644
index 00000000000..be7cf3b048d
--- /dev/null
+++ b/chromium/content/browser/device_orientation/provider_impl.h
@@ -0,0 +1,81 @@
+// 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 CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_IMPL_H_
+#define CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_IMPL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/browser/device_orientation/provider.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace content {
+
+class ProviderImpl : public Provider {
+ public:
+ typedef DataFetcher* (*DataFetcherFactory)();
+
+ // Create a ProviderImpl that uses the factory to create a DataFetcher that
+ // can provide data. A NULL DataFetcherFactory indicates that there are no
+ // DataFetchers for this OS.
+ CONTENT_EXPORT ProviderImpl(DataFetcherFactory factory);
+
+ // From Provider.
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+
+ private:
+ class PollingThread;
+
+ virtual ~ProviderImpl();
+
+ // Starts or Stops the provider. Called from creator_loop_.
+ void Start(DeviceData::Type type);
+ void Stop();
+
+ void ScheduleInitializePollingThread(DeviceData::Type device_data_type);
+ void ScheduleDoAddPollingDataType(DeviceData::Type type);
+
+ // Method for notifying observers of a data update.
+ // Runs on the creator_thread_.
+ void DoNotify(const scoped_refptr<const DeviceData>& data,
+ DeviceData::Type device_data_type);
+
+ static bool ShouldFireEvent(const DeviceData* old_data,
+ const DeviceData* new_data, DeviceData::Type device_data_type);
+
+ // The Message Loop on which this object was created.
+ // Typically the I/O loop, but may be something else during testing.
+ base::MessageLoop* creator_loop_;
+
+ // Members below are only to be used from the creator_loop_.
+ DataFetcherFactory factory_;
+ std::set<Observer*> observers_;
+ std::map<DeviceData::Type, scoped_refptr<const DeviceData> >
+ last_notifications_map_;
+
+ // When polling_thread_ is running, members below are only to be used
+ // from that thread.
+ base::WeakPtrFactory<ProviderImpl> weak_factory_;
+
+ // Polling is done on this background thread. PollingThread is owned by
+ // the ProviderImpl object. But its deletion doesn't happen synchronously
+ // along with deletion of the ProviderImpl. Thus this should be a raw
+ // pointer instead of scoped_ptr.
+ PollingThread* polling_thread_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVICE_ORIENTATION_PROVIDER_IMPL_H_
diff --git a/chromium/content/browser/device_orientation/provider_unittest.cc b/chromium/content/browser/device_orientation/provider_unittest.cc
new file mode 100644
index 00000000000..1f310ea3ad4
--- /dev/null
+++ b/chromium/content/browser/device_orientation/provider_unittest.cc
@@ -0,0 +1,591 @@
+// 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 <map>
+#include <queue>
+
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/device_orientation/data_fetcher.h"
+#include "content/browser/device_orientation/device_data.h"
+#include "content/browser/device_orientation/orientation.h"
+#include "content/browser/device_orientation/provider.h"
+#include "content/browser/device_orientation/provider_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+// Class for testing multiple types of device data.
+class TestData : public DeviceData {
+ public:
+ TestData()
+ : value_(0) {
+ }
+
+ // From DeviceData.
+ virtual IPC::Message* CreateIPCMessage(int render_view_id) const OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual bool ShouldFireEvent(const DeviceData* old_data) const OVERRIDE {
+ return true;
+ }
+
+ void set_value(double value) { value_ = value; }
+ double value() const { return value_; }
+
+ private:
+ virtual ~TestData() { }
+
+ double value_;
+};
+
+// Class for checking expectations on device_data updates from the Provider.
+class UpdateChecker : public Provider::Observer {
+ public:
+ UpdateChecker(DeviceData::Type device_data_type,
+ int *expectations_count_ptr)
+ : Observer(device_data_type),
+ expectations_count_ptr_(expectations_count_ptr) {
+ }
+ virtual ~UpdateChecker() {}
+
+ // From Provider::Observer.
+ virtual void OnDeviceDataUpdate(const DeviceData* device_data,
+ DeviceData::Type device_data_type) OVERRIDE = 0;
+
+ void AddExpectation(const DeviceData* device_data) {
+ scoped_refptr<const DeviceData> expected_device_data(device_data);
+ expectations_queue_.push(expected_device_data);
+ ++(*expectations_count_ptr_);
+ }
+
+ protected:
+ // Set up by the test fixture, which then blocks while it is accessed
+ // from OnDeviceDataUpdate which is executed on the test fixture's
+ // message_loop_.
+ int* expectations_count_ptr_;
+ std::queue<scoped_refptr<const DeviceData> > expectations_queue_;
+};
+
+// Class for checking expectations on orientation updates from the Provider.
+class OrientationUpdateChecker : public UpdateChecker {
+ public:
+ explicit OrientationUpdateChecker(int* expectations_count_ptr)
+ : UpdateChecker(DeviceData::kTypeOrientation, expectations_count_ptr) {
+ }
+
+ virtual ~OrientationUpdateChecker() {}
+
+ // From UpdateChecker.
+ virtual void OnDeviceDataUpdate(const DeviceData* device_data,
+ DeviceData::Type device_data_type) OVERRIDE {
+ ASSERT_FALSE(expectations_queue_.empty());
+ ASSERT_EQ(DeviceData::kTypeOrientation, device_data_type);
+
+ scoped_refptr<const Orientation> orientation(
+ static_cast<const Orientation*>(device_data));
+ if (orientation.get() == NULL)
+ orientation = new Orientation();
+
+ scoped_refptr<const Orientation> expected(static_cast<const Orientation*>(
+ (expectations_queue_.front().get())));
+ expectations_queue_.pop();
+
+ EXPECT_EQ(expected->can_provide_alpha(), orientation->can_provide_alpha());
+ EXPECT_EQ(expected->can_provide_beta(), orientation->can_provide_beta());
+ EXPECT_EQ(expected->can_provide_gamma(), orientation->can_provide_gamma());
+ EXPECT_EQ(expected->can_provide_absolute(),
+ orientation->can_provide_absolute());
+ if (expected->can_provide_alpha())
+ EXPECT_EQ(expected->alpha(), orientation->alpha());
+ if (expected->can_provide_beta())
+ EXPECT_EQ(expected->beta(), orientation->beta());
+ if (expected->can_provide_gamma())
+ EXPECT_EQ(expected->gamma(), orientation->gamma());
+ if (expected->can_provide_absolute())
+ EXPECT_EQ(expected->absolute(), orientation->absolute());
+
+ --(*expectations_count_ptr_);
+
+ if (*expectations_count_ptr_ == 0) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ }
+ }
+};
+
+// Class for checking expectations on test_data updates from the Provider.
+class TestDataUpdateChecker : public UpdateChecker {
+ public:
+ explicit TestDataUpdateChecker(int* expectations_count_ptr)
+ : UpdateChecker(DeviceData::kTypeTest, expectations_count_ptr) {
+ }
+
+ // From UpdateChecker.
+ virtual void OnDeviceDataUpdate(const DeviceData* device_data,
+ DeviceData::Type device_data_type) OVERRIDE {
+ ASSERT_FALSE(expectations_queue_.empty());
+ ASSERT_EQ(DeviceData::kTypeTest, device_data_type);
+
+ scoped_refptr<const TestData> test_data(
+ static_cast<const TestData*>(device_data));
+ if (test_data.get() == NULL)
+ test_data = new TestData();
+
+ scoped_refptr<const TestData> expected(static_cast<const TestData*>(
+ (expectations_queue_.front().get())));
+ expectations_queue_.pop();
+
+ EXPECT_EQ(expected->value(), test_data->value());
+
+ --(*expectations_count_ptr_);
+
+ if (*expectations_count_ptr_ == 0) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ }
+ }
+};
+
+// Class for injecting test device data into the Provider.
+class MockDeviceDataFactory
+ : public base::RefCountedThreadSafe<MockDeviceDataFactory> {
+ public:
+ MockDeviceDataFactory()
+ : is_failing_(false) {
+ }
+
+ static void SetCurInstance(MockDeviceDataFactory* instance) {
+ if (instance) {
+ EXPECT_FALSE(instance_);
+ }
+ else {
+ EXPECT_TRUE(instance_);
+ }
+ instance_ = instance;
+ }
+
+ static DataFetcher* CreateDataFetcher() {
+ EXPECT_TRUE(instance_);
+ return new MockDataFetcher(instance_);
+ }
+
+ void SetDeviceData(const DeviceData* device_data, DeviceData::Type type) {
+ base::AutoLock auto_lock(lock_);
+ device_data_map_[type] = device_data;
+ }
+
+ void SetFailing(bool is_failing) {
+ base::AutoLock auto_lock(lock_);
+ is_failing_ = is_failing;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockDeviceDataFactory>;
+
+ ~MockDeviceDataFactory() {
+ }
+
+ // Owned by ProviderImpl. Holds a reference back to MockDeviceDataFactory.
+ class MockDataFetcher : public DataFetcher {
+ public:
+ explicit MockDataFetcher(MockDeviceDataFactory* device_data_factory)
+ : device_data_factory_(device_data_factory) { }
+
+ // From DataFetcher. Called by the Provider.
+ virtual const DeviceData* GetDeviceData(
+ DeviceData::Type device_data_type) OVERRIDE {
+ base::AutoLock auto_lock(device_data_factory_->lock_);
+ if (device_data_factory_->is_failing_)
+ return NULL;
+ return device_data_factory_->device_data_map_[device_data_type].get();
+ }
+
+ private:
+ scoped_refptr<MockDeviceDataFactory> device_data_factory_;
+ };
+
+ static MockDeviceDataFactory* instance_;
+ std::map<DeviceData::Type, scoped_refptr<const DeviceData> > device_data_map_;
+ bool is_failing_;
+ base::Lock lock_;
+};
+
+MockDeviceDataFactory* MockDeviceDataFactory::instance_;
+
+class DeviceOrientationProviderTest : public testing::Test {
+ public:
+ DeviceOrientationProviderTest()
+ : pending_expectations_(0) {
+ }
+
+ virtual void TearDown() {
+ provider_ = NULL;
+
+ // Make sure it is really gone.
+ EXPECT_FALSE(Provider::GetInstanceForTests());
+
+ // Clean up in any case, so as to not break subsequent test.
+ Provider::SetInstanceForTests(NULL);
+ }
+
+ // Initialize the test fixture with a ProviderImpl that uses the
+ // DataFetcherFactory factory.
+ void Init(ProviderImpl::DataFetcherFactory factory) {
+ provider_ = new ProviderImpl(factory);
+ Provider::SetInstanceForTests(provider_.get());
+ }
+
+ protected:
+ // Number of pending expectations.
+ int pending_expectations_;
+
+ // Provider instance under test.
+ scoped_refptr<Provider> provider_;
+
+ // Message loop for the test thread.
+ base::MessageLoop message_loop_;
+};
+
+TEST_F(DeviceOrientationProviderTest, FailingTest) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ scoped_ptr<OrientationUpdateChecker> checker_a(
+ new OrientationUpdateChecker(&pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_b(
+ new OrientationUpdateChecker(&pending_expectations_));
+
+ checker_a->AddExpectation(new Orientation());
+ provider_->AddObserver(checker_a.get());
+ base::MessageLoop::current()->Run();
+
+ checker_b->AddExpectation(new Orientation());
+ provider_->AddObserver(checker_b.get());
+ base::MessageLoop::current()->Run();
+
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+TEST_F(DeviceOrientationProviderTest, ProviderIsSingleton) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ scoped_refptr<Provider> provider_a(Provider::GetInstance());
+ scoped_refptr<Provider> provider_b(Provider::GetInstance());
+
+ EXPECT_EQ(provider_a.get(), provider_b.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+TEST_F(DeviceOrientationProviderTest, BasicPushTest) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+
+ scoped_ptr<OrientationUpdateChecker> checker(
+ new OrientationUpdateChecker(&pending_expectations_));
+ checker->AddExpectation(test_orientation.get());
+ device_data_factory->SetDeviceData(test_orientation.get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+// Tests multiple observers observing the same type of data.
+TEST_F(DeviceOrientationProviderTest, MultipleObserversPushTest) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ scoped_refptr<Orientation> test_orientations[] = {new Orientation(),
+ new Orientation(), new Orientation()};
+ test_orientations[0]->set_alpha(1);
+ test_orientations[0]->set_beta(2);
+ test_orientations[0]->set_gamma(3);
+ test_orientations[0]->set_absolute(true);
+
+ test_orientations[1]->set_alpha(4);
+ test_orientations[1]->set_beta(5);
+ test_orientations[1]->set_gamma(6);
+ test_orientations[1]->set_absolute(false);
+
+ test_orientations[2]->set_alpha(7);
+ test_orientations[2]->set_beta(8);
+ test_orientations[2]->set_gamma(9);
+ // can't provide absolute
+
+ scoped_ptr<OrientationUpdateChecker> checker_a(
+ new OrientationUpdateChecker(&pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_b(
+ new OrientationUpdateChecker(&pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_c(
+ new OrientationUpdateChecker(&pending_expectations_));
+
+ checker_a->AddExpectation(test_orientations[0].get());
+ device_data_factory->SetDeviceData(test_orientations[0].get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker_a.get());
+ base::MessageLoop::current()->Run();
+
+ checker_a->AddExpectation(test_orientations[1].get());
+ checker_b->AddExpectation(test_orientations[0].get());
+ checker_b->AddExpectation(test_orientations[1].get());
+ device_data_factory->SetDeviceData(test_orientations[1].get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker_b.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_a.get());
+ checker_b->AddExpectation(test_orientations[2].get());
+ checker_c->AddExpectation(test_orientations[1].get());
+ checker_c->AddExpectation(test_orientations[2].get());
+ device_data_factory->SetDeviceData(test_orientations[2].get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker_c.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_b.get());
+ provider_->RemoveObserver(checker_c.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+// Test for when the fetcher cannot provide the first type of data but can
+// provide the second type.
+TEST_F(DeviceOrientationProviderTest, FailingFirstDataTypeTest) {
+
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ scoped_ptr<TestDataUpdateChecker> test_data_checker(
+ new TestDataUpdateChecker(&pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> orientation_checker(
+ new OrientationUpdateChecker(&pending_expectations_));
+
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+
+ test_data_checker->AddExpectation(new TestData());
+ provider_->AddObserver(test_data_checker.get());
+ base::MessageLoop::current()->Run();
+
+ orientation_checker->AddExpectation(test_orientation.get());
+ device_data_factory->SetDeviceData(test_orientation.get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(orientation_checker.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(test_data_checker.get());
+ provider_->RemoveObserver(orientation_checker.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+#if defined(OS_LINUX) || defined(OS_WIN)
+// Flakily DCHECKs on Linux. See crbug.com/104950.
+// FLAKY on Win. See crbug.com/104950.
+#define MAYBE_ObserverNotRemoved DISABLED_ObserverNotRemoved
+#else
+#define MAYBE_ObserverNotRemoved ObserverNotRemoved
+#endif
+TEST_F(DeviceOrientationProviderTest, MAYBE_ObserverNotRemoved) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+
+ scoped_refptr<Orientation> test_orientation2(new Orientation());
+ test_orientation2->set_alpha(4);
+ test_orientation2->set_beta(5);
+ test_orientation2->set_gamma(6);
+ test_orientation2->set_absolute(false);
+
+ scoped_ptr<OrientationUpdateChecker> checker(
+ new OrientationUpdateChecker(&pending_expectations_));
+ checker->AddExpectation(test_orientation.get());
+ device_data_factory->SetDeviceData(test_orientation.get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker.get());
+ base::MessageLoop::current()->Run();
+
+ checker->AddExpectation(test_orientation2.get());
+ device_data_factory->SetDeviceData(test_orientation2.get(),
+ DeviceData::kTypeOrientation);
+ base::MessageLoop::current()->Run();
+
+ MockDeviceDataFactory::SetCurInstance(NULL);
+
+ // Note that checker is not removed. This should not be a problem.
+}
+
+#if defined(OS_WIN)
+// FLAKY on Win. See crbug.com/104950.
+#define MAYBE_StartFailing DISABLED_StartFailing
+#else
+#define MAYBE_StartFailing StartFailing
+#endif
+TEST_F(DeviceOrientationProviderTest, MAYBE_StartFailing) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+
+ scoped_ptr<OrientationUpdateChecker> checker_a(new OrientationUpdateChecker(
+ &pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_b(new OrientationUpdateChecker(
+ &pending_expectations_));
+
+ device_data_factory->SetDeviceData(test_orientation.get(),
+ DeviceData::kTypeOrientation);
+ checker_a->AddExpectation(test_orientation.get());
+ provider_->AddObserver(checker_a.get());
+ base::MessageLoop::current()->Run();
+
+ checker_a->AddExpectation(new Orientation());
+ device_data_factory->SetFailing(true);
+ base::MessageLoop::current()->Run();
+
+ checker_b->AddExpectation(new Orientation());
+ provider_->AddObserver(checker_b.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_a.get());
+ provider_->RemoveObserver(checker_b.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+TEST_F(DeviceOrientationProviderTest, StartStopStart) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ scoped_refptr<Orientation> test_orientation(new Orientation());
+ test_orientation->set_alpha(1);
+ test_orientation->set_beta(2);
+ test_orientation->set_gamma(3);
+ test_orientation->set_absolute(true);
+
+ scoped_refptr<Orientation> test_orientation2(new Orientation());
+ test_orientation2->set_alpha(4);
+ test_orientation2->set_beta(5);
+ test_orientation2->set_gamma(6);
+ test_orientation2->set_absolute(false);
+
+ scoped_ptr<OrientationUpdateChecker> checker_a(new OrientationUpdateChecker(
+ &pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_b(new OrientationUpdateChecker(
+ &pending_expectations_));
+
+ checker_a->AddExpectation(test_orientation.get());
+ device_data_factory->SetDeviceData(test_orientation.get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker_a.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_a.get()); // This stops the Provider.
+
+ checker_b->AddExpectation(test_orientation2.get());
+ device_data_factory->SetDeviceData(test_orientation2.get(),
+ DeviceData::kTypeOrientation);
+ provider_->AddObserver(checker_b.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_b.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+// Tests that Orientation events only fire if the change is significant.
+TEST_F(DeviceOrientationProviderTest, OrientationSignificantlyDifferent) {
+ scoped_refptr<MockDeviceDataFactory> device_data_factory(
+ new MockDeviceDataFactory());
+ MockDeviceDataFactory::SetCurInstance(device_data_factory.get());
+ Init(MockDeviceDataFactory::CreateDataFetcher);
+
+ // Values that should be well below or above the implementation's
+ // significane threshold.
+ const double kInsignificantDifference = 1e-6;
+ const double kSignificantDifference = 30;
+ const double kAlpha = 4, kBeta = 5, kGamma = 6;
+
+ scoped_refptr<Orientation> first_orientation(new Orientation());
+ first_orientation->set_alpha(kAlpha);
+ first_orientation->set_beta(kBeta);
+ first_orientation->set_gamma(kGamma);
+ first_orientation->set_absolute(true);
+
+ scoped_refptr<Orientation> second_orientation(new Orientation());
+ second_orientation->set_alpha(kAlpha + kInsignificantDifference);
+ second_orientation->set_beta(kBeta + kInsignificantDifference);
+ second_orientation->set_gamma(kGamma + kInsignificantDifference);
+ second_orientation->set_absolute(false);
+
+ scoped_refptr<Orientation> third_orientation(new Orientation());
+ third_orientation->set_alpha(kAlpha + kSignificantDifference);
+ third_orientation->set_beta(kBeta + kSignificantDifference);
+ third_orientation->set_gamma(kGamma + kSignificantDifference);
+ // can't provide absolute
+
+ scoped_ptr<OrientationUpdateChecker> checker_a(new OrientationUpdateChecker(
+ &pending_expectations_));
+ scoped_ptr<OrientationUpdateChecker> checker_b(new OrientationUpdateChecker(
+ &pending_expectations_));
+
+ device_data_factory->SetDeviceData(first_orientation.get(),
+ DeviceData::kTypeOrientation);
+ checker_a->AddExpectation(first_orientation.get());
+ provider_->AddObserver(checker_a.get());
+ base::MessageLoop::current()->Run();
+
+ // The observers should not see this insignificantly different orientation.
+ device_data_factory->SetDeviceData(second_orientation.get(),
+ DeviceData::kTypeOrientation);
+ checker_b->AddExpectation(first_orientation.get());
+ provider_->AddObserver(checker_b.get());
+ base::MessageLoop::current()->Run();
+
+ device_data_factory->SetDeviceData(third_orientation.get(),
+ DeviceData::kTypeOrientation);
+ checker_a->AddExpectation(third_orientation.get());
+ checker_b->AddExpectation(third_orientation.get());
+ base::MessageLoop::current()->Run();
+
+ provider_->RemoveObserver(checker_a.get());
+ provider_->RemoveObserver(checker_b.get());
+ MockDeviceDataFactory::SetCurInstance(NULL);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/DEPS b/chromium/content/browser/devtools/DEPS
new file mode 100644
index 00000000000..3e98aee185b
--- /dev/null
+++ b/chromium/content/browser/devtools/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ # Generated by the local devtools_resources.gyp:devtools_resources
+ "+grit/devtools_resources_map.h",
+]
diff --git a/chromium/content/browser/devtools/OWNERS b/chromium/content/browser/devtools/OWNERS
new file mode 100644
index 00000000000..bb6028e656a
--- /dev/null
+++ b/chromium/content/browser/devtools/OWNERS
@@ -0,0 +1,2 @@
+pfeldman@chromium.org
+yurys@chromium.org
diff --git a/chromium/content/browser/devtools/devtools_agent_host_impl.cc b/chromium/content/browser/devtools/devtools_agent_host_impl.cc
new file mode 100644
index 00000000000..35111e9ab82
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_agent_host_impl.cc
@@ -0,0 +1,68 @@
+// 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 "content/browser/devtools/devtools_agent_host_impl.h"
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/guid.h"
+#include "base/lazy_instance.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+
+namespace content {
+
+namespace {
+typedef std::map<std::string, DevToolsAgentHostImpl*> Instances;
+base::LazyInstance<Instances>::Leaky g_instances = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+DevToolsAgentHostImpl::DevToolsAgentHostImpl()
+ : close_listener_(NULL),
+ id_(base::GenerateGUID()) {
+ g_instances.Get()[id_] = this;
+}
+
+DevToolsAgentHostImpl::~DevToolsAgentHostImpl() {
+ g_instances.Get().erase(g_instances.Get().find(id_));
+}
+
+scoped_refptr<DevToolsAgentHost> DevToolsAgentHost::GetForId(
+ const std::string& id) {
+ if (g_instances == NULL)
+ return NULL;
+ Instances::iterator it = g_instances.Get().find(id);
+ if (it == g_instances.Get().end())
+ return NULL;
+ return it->second;
+}
+
+bool DevToolsAgentHostImpl::IsAttached() {
+ return !!DevToolsManagerImpl::GetInstance()->GetDevToolsClientHostFor(this);
+}
+
+void DevToolsAgentHostImpl::InspectElement(int x, int y) {
+}
+
+std::string DevToolsAgentHostImpl::GetId() {
+ return id_;
+}
+
+RenderViewHost* DevToolsAgentHostImpl::GetRenderViewHost() {
+ return NULL;
+}
+
+void DevToolsAgentHostImpl::DisconnectRenderViewHost() {}
+
+void DevToolsAgentHostImpl::ConnectRenderViewHost(RenderViewHost* rvh) {}
+
+void DevToolsAgentHostImpl::NotifyCloseListener() {
+ if (close_listener_) {
+ scoped_refptr<DevToolsAgentHostImpl> protect(this);
+ close_listener_->AgentHostClosing(this);
+ close_listener_ = NULL;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_agent_host_impl.h b/chromium/content/browser/devtools/devtools_agent_host_impl.h
new file mode 100644
index 00000000000..9bd800dc398
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_agent_host_impl.h
@@ -0,0 +1,69 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_IMPL_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_IMPL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/devtools_agent_host.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+
+// Describes interface for managing devtools agents from the browser process.
+class CONTENT_EXPORT DevToolsAgentHostImpl : public DevToolsAgentHost {
+ public:
+ class CONTENT_EXPORT CloseListener {
+ public:
+ virtual void AgentHostClosing(DevToolsAgentHostImpl*) = 0;
+ protected:
+ virtual ~CloseListener() {}
+ };
+
+ // Informs the hosted agent that a client host has attached.
+ virtual void Attach() = 0;
+
+ // Informs the hosted agent that a client host has detached.
+ virtual void Detach() = 0;
+
+ // Sends a message to the agent hosted by this object.
+ virtual void DispatchOnInspectorBackend(const std::string& message) = 0;
+
+ void set_close_listener(CloseListener* listener) {
+ close_listener_ = listener;
+ }
+
+ // DevToolsAgentHost implementation.
+ virtual bool IsAttached() OVERRIDE;
+
+ virtual void InspectElement(int x, int y) OVERRIDE;
+
+ virtual std::string GetId() OVERRIDE;
+
+ virtual RenderViewHost* GetRenderViewHost() OVERRIDE;
+
+ virtual void DisconnectRenderViewHost() OVERRIDE;
+
+ virtual void ConnectRenderViewHost(RenderViewHost* rvh) OVERRIDE;
+
+ protected:
+ DevToolsAgentHostImpl();
+ virtual ~DevToolsAgentHostImpl();
+
+ void NotifyCloseListener();
+
+ private:
+ CloseListener* close_listener_;
+ const std::string id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_IMPL_H_
diff --git a/chromium/content/browser/devtools/devtools_browser_target.cc b/chromium/content/browser/devtools/devtools_browser_target.cc
new file mode 100644
index 00000000000..a782462ba4c
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_browser_target.cc
@@ -0,0 +1,141 @@
+// 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 "content/browser/devtools/devtools_browser_target.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/server/http_server.h"
+
+namespace content {
+
+DevToolsBrowserTarget::DevToolsBrowserTarget(
+ base::MessageLoopProxy* message_loop_proxy,
+ net::HttpServer* http_server,
+ int connection_id)
+ : message_loop_proxy_(message_loop_proxy),
+ http_server_(http_server),
+ connection_id_(connection_id),
+ handlers_deleter_(&handlers_),
+ weak_factory_(this) {
+}
+
+void DevToolsBrowserTarget::RegisterDomainHandler(
+ const std::string& domain,
+ DevToolsProtocol::Handler* handler,
+ bool handle_on_ui_thread) {
+ DCHECK(handlers_.find(domain) == handlers_.end());
+ handlers_[domain] = handler;
+ if (handle_on_ui_thread) {
+ handle_on_ui_thread_.insert(domain);
+ handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::RespondFromUIThread,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::Respond,
+ base::Unretained(this)));
+ }
+}
+
+void DevToolsBrowserTarget::HandleMessage(const std::string& data) {
+ std::string error_response;
+ scoped_refptr<DevToolsProtocol::Command> command =
+ DevToolsProtocol::ParseCommand(data, &error_response);
+ if (!command) {
+ Respond(error_response);
+ return;
+ }
+
+ DomainHandlerMap::iterator it = handlers_.find(command->domain());
+ if (it == handlers_.end()) {
+ Respond(command->NoSuchMethodErrorResponse()->Serialize());
+ return;
+ }
+
+ DevToolsProtocol::Handler* handler = it->second;
+ bool handle_directly = handle_on_ui_thread_.find(command->domain()) ==
+ handle_on_ui_thread_.end();
+ if (handle_directly) {
+ scoped_refptr<DevToolsProtocol::Response> response =
+ handler->HandleCommand(command);
+ if (response && response->is_async_promise())
+ return;
+ if (response)
+ Respond(response->Serialize());
+ else
+ Respond(command->NoSuchMethodErrorResponse()->Serialize());
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DevToolsBrowserTarget::HandleCommandOnUIThread,
+ this,
+ handler,
+ command));
+}
+
+void DevToolsBrowserTarget::Detach() {
+ message_loop_proxy_ = NULL;
+ http_server_ = NULL;
+
+ std::vector<DevToolsProtocol::Handler*> ui_handlers;
+ for (std::set<std::string>::iterator domain_it = handle_on_ui_thread_.begin();
+ domain_it != handle_on_ui_thread_.end();
+ ++domain_it) {
+ DomainHandlerMap::iterator handler_it = handlers_.find(*domain_it);
+ CHECK(handler_it != handlers_.end());
+ ui_handlers.push_back(handler_it->second);
+ handlers_.erase(handler_it);
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DevToolsBrowserTarget::DeleteHandlersOnUIThread,
+ this,
+ ui_handlers));
+}
+
+DevToolsBrowserTarget::~DevToolsBrowserTarget() {
+}
+
+void DevToolsBrowserTarget::HandleCommandOnUIThread(
+ DevToolsProtocol::Handler* handler,
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ scoped_refptr<DevToolsProtocol::Response> response =
+ handler->HandleCommand(command);
+ if (response && response->is_async_promise())
+ return;
+
+ if (response)
+ RespondFromUIThread(response->Serialize());
+ else
+ RespondFromUIThread(command->NoSuchMethodErrorResponse()->Serialize());
+}
+
+void DevToolsBrowserTarget::DeleteHandlersOnUIThread(
+ std::vector<DevToolsProtocol::Handler*> handlers) {
+ STLDeleteElements(&handlers);
+}
+
+void DevToolsBrowserTarget::Respond(const std::string& message) {
+ if (!http_server_)
+ return;
+ http_server_->SendOverWebSocket(connection_id_, message);
+}
+
+void DevToolsBrowserTarget::RespondFromUIThread(const std::string& message) {
+ if (!message_loop_proxy_)
+ return;
+ message_loop_proxy_->PostTask(
+ FROM_HERE,
+ base::Bind(&DevToolsBrowserTarget::Respond, this, message));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_browser_target.h b/chromium/content/browser/devtools/devtools_browser_target.h
new file mode 100644
index 00000000000..46ed291cfd7
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_browser_target.h
@@ -0,0 +1,79 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/stl_util.h"
+#include "content/browser/devtools/devtools_protocol.h"
+
+namespace base {
+class DictionaryValue;
+class MessageLoopProxy;
+class Value;
+} // namespace base
+
+namespace net {
+class HttpServer;
+} // namespace net
+
+namespace content {
+
+// This class handles the "Browser" target for remote debugging.
+class DevToolsBrowserTarget
+ : public base::RefCountedThreadSafe<DevToolsBrowserTarget> {
+ public:
+ DevToolsBrowserTarget(base::MessageLoopProxy* message_loop_proxy,
+ net::HttpServer* server,
+ int connection_id);
+
+ int connection_id() const { return connection_id_; }
+
+ // Takes ownership of |handler|.
+ void RegisterDomainHandler(const std::string& domain,
+ DevToolsProtocol::Handler* handler,
+ bool handle_on_ui_thread);
+
+ void HandleMessage(const std::string& data);
+
+ void Detach();
+
+ private:
+ friend class base::RefCountedThreadSafe<DevToolsBrowserTarget>;
+ ~DevToolsBrowserTarget();
+
+ void HandleCommandOnUIThread(
+ DevToolsProtocol::Handler*,
+ scoped_refptr<DevToolsProtocol::Command> command);
+
+ void DeleteHandlersOnUIThread(
+ std::vector<DevToolsProtocol::Handler*> handlers);
+
+ void Respond(const std::string& message);
+ void RespondFromUIThread(const std::string& message);
+
+ base::MessageLoopProxy* message_loop_proxy_;
+ net::HttpServer* http_server_;
+ const int connection_id_;
+
+ typedef std::map<std::string, DevToolsProtocol::Handler*> DomainHandlerMap;
+ DomainHandlerMap handlers_;
+ STLValueDeleter<DomainHandlerMap> handlers_deleter_;
+ std::set<std::string> handle_on_ui_thread_;
+ base::WeakPtrFactory<DevToolsBrowserTarget> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsBrowserTarget);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_
diff --git a/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.cc b/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.cc
new file mode 100644
index 00000000000..0513dc238b1
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 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 "content/browser/devtools/devtools_external_agent_proxy_impl.h"
+
+#include "content/browser/devtools/devtools_agent_host_impl.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/public/browser/devtools_external_agent_proxy_delegate.h"
+
+namespace content {
+
+class DevToolsExternalAgentProxyImpl::ForwardingAgentHost
+ : public DevToolsAgentHostImpl {
+ public:
+ ForwardingAgentHost(DevToolsExternalAgentProxyDelegate* delegate)
+ : delegate_(delegate) {
+ }
+
+ void ConnectionClosed() {
+ NotifyCloseListener();
+ }
+
+ private:
+ virtual ~ForwardingAgentHost() {
+ }
+
+ // DevToolsAgentHostImpl implementation.
+ virtual void Attach() OVERRIDE {
+ delegate_->Attach();
+ };
+
+ virtual void Detach() OVERRIDE {
+ delegate_->Detach();
+ };
+
+ virtual void DispatchOnInspectorBackend(const std::string& message) OVERRIDE {
+ delegate_->SendMessageToBackend(message);
+ }
+
+ DevToolsExternalAgentProxyDelegate* delegate_;
+};
+
+//static
+DevToolsExternalAgentProxy* DevToolsExternalAgentProxy::Create(
+ DevToolsExternalAgentProxyDelegate* delegate) {
+ return new DevToolsExternalAgentProxyImpl(delegate);
+}
+
+DevToolsExternalAgentProxyImpl::DevToolsExternalAgentProxyImpl(
+ DevToolsExternalAgentProxyDelegate* delegate)
+ : agent_host_(new ForwardingAgentHost(delegate)) {
+}
+
+DevToolsExternalAgentProxyImpl::~DevToolsExternalAgentProxyImpl() {
+}
+
+scoped_refptr<DevToolsAgentHost> DevToolsExternalAgentProxyImpl::
+ GetAgentHost() {
+ return agent_host_;
+}
+
+void DevToolsExternalAgentProxyImpl::DispatchOnClientHost(
+ const std::string& message) {
+ DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(
+ agent_host_.get(), message);
+}
+
+void DevToolsExternalAgentProxyImpl::ConnectionClosed() {
+ agent_host_->ConnectionClosed();
+}
+
+} // content
diff --git a/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.h b/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.h
new file mode 100644
index 00000000000..cf025ec7b4f
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_external_agent_proxy_impl.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_EXTERNAL_AGENT_PROXY_IMPL_H
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_EXTERNAL_AGENT_PROXY_IMPL_H
+
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/devtools_external_agent_proxy.h"
+
+namespace content {
+
+class DevToolsExternalAgentProxyImpl
+ : public DevToolsExternalAgentProxy {
+ public:
+ explicit DevToolsExternalAgentProxyImpl(
+ DevToolsExternalAgentProxyDelegate* delegate);
+ virtual ~DevToolsExternalAgentProxyImpl();
+
+ // DevToolsExternalAgentProxy implementation.
+ virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() OVERRIDE;
+ virtual void DispatchOnClientHost(const std::string& message) OVERRIDE;
+ virtual void ConnectionClosed() OVERRIDE;
+
+ private:
+ class ForwardingAgentHost;
+
+ scoped_refptr<ForwardingAgentHost> agent_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsExternalAgentProxyImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_EXTERNAL_AGENT_PROXY_IMPL_H
diff --git a/chromium/content/browser/devtools/devtools_frontend_host.cc b/chromium/content/browser/devtools/devtools_frontend_host.cc
new file mode 100644
index 00000000000..8f177b6941c
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_frontend_host.cc
@@ -0,0 +1,172 @@
+// 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 "content/browser/devtools/devtools_frontend_host.h"
+
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/devtools_messages.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/devtools_frontend_host_delegate.h"
+
+namespace content {
+
+// static
+DevToolsClientHost* DevToolsClientHost::CreateDevToolsFrontendHost(
+ WebContents* client_web_contents,
+ DevToolsFrontendHostDelegate* delegate) {
+ return new DevToolsFrontendHost(
+ static_cast<WebContentsImpl*>(client_web_contents), delegate);
+}
+
+// static
+void DevToolsClientHost::SetupDevToolsFrontendClient(
+ RenderViewHost* frontend_rvh) {
+ frontend_rvh->Send(new DevToolsMsg_SetupDevToolsClient(
+ frontend_rvh->GetRoutingID()));
+}
+
+DevToolsFrontendHost::DevToolsFrontendHost(
+ WebContentsImpl* web_contents,
+ DevToolsFrontendHostDelegate* delegate)
+ : WebContentsObserver(web_contents),
+ delegate_(delegate) {
+}
+
+DevToolsFrontendHost::~DevToolsFrontendHost() {
+ DevToolsManager::GetInstance()->ClientHostClosing(this);
+}
+
+void DevToolsFrontendHost::DispatchOnInspectorFrontend(
+ const std::string& message) {
+ if (!web_contents())
+ return;
+ RenderViewHostImpl* target_host =
+ static_cast<RenderViewHostImpl*>(web_contents()->GetRenderViewHost());
+ target_host->Send(new DevToolsClientMsg_DispatchOnInspectorFrontend(
+ target_host->GetRoutingID(),
+ message));
+}
+
+void DevToolsFrontendHost::InspectedContentsClosing() {
+ delegate_->InspectedContentsClosing();
+}
+
+void DevToolsFrontendHost::ReplacedWithAnotherClient() {
+}
+
+bool DevToolsFrontendHost::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(DevToolsFrontendHost, message)
+ IPC_MESSAGE_HANDLER(DevToolsAgentMsg_DispatchOnInspectorBackend,
+ OnDispatchOnInspectorBackend)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_ActivateWindow, OnActivateWindow)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_ChangeAttachedWindowHeight,
+ OnChangeAttachedWindowHeight)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_CloseWindow, OnCloseWindow)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_MoveWindow, OnMoveWindow)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_RequestSetDockSide,
+ OnRequestSetDockSide)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_OpenInNewTab, OnOpenInNewTab)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_Save, OnSave)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_Append, OnAppend)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_RequestFileSystems,
+ OnRequestFileSystems)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_AddFileSystem, OnAddFileSystem)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_RemoveFileSystem, OnRemoveFileSystem)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_IndexPath, OnIndexPath)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_StopIndexing, OnStopIndexing)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_SearchInPath, OnSearchInPath)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void DevToolsFrontendHost::RenderProcessGone(
+ base::TerminationStatus status) {
+ switch(status) {
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ DevToolsManager::GetInstance()->ClientHostClosing(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DevToolsFrontendHost::OnDispatchOnInspectorBackend(
+ const std::string& message) {
+ DevToolsManagerImpl::GetInstance()->DispatchOnInspectorBackend(this, message);
+// delegate_->DispatchOnInspectorBackend(message);
+}
+
+void DevToolsFrontendHost::OnActivateWindow() {
+ delegate_->ActivateWindow();
+}
+
+void DevToolsFrontendHost::OnChangeAttachedWindowHeight(unsigned height) {
+ delegate_->ChangeAttachedWindowHeight(height);
+}
+
+void DevToolsFrontendHost::OnCloseWindow() {
+ delegate_->CloseWindow();
+}
+
+void DevToolsFrontendHost::OnMoveWindow(int x, int y) {
+ delegate_->MoveWindow(x, y);
+}
+
+void DevToolsFrontendHost::OnOpenInNewTab(const std::string& url) {
+ delegate_->OpenInNewTab(url);
+}
+
+void DevToolsFrontendHost::OnSave(
+ const std::string& url,
+ const std::string& content,
+ bool save_as) {
+ delegate_->SaveToFile(url, content, save_as);
+}
+
+void DevToolsFrontendHost::OnAppend(
+ const std::string& url,
+ const std::string& content) {
+ delegate_->AppendToFile(url, content);
+}
+
+void DevToolsFrontendHost::OnRequestFileSystems() {
+ delegate_->RequestFileSystems();
+}
+
+void DevToolsFrontendHost::OnAddFileSystem() {
+ delegate_->AddFileSystem();
+}
+
+void DevToolsFrontendHost::OnRemoveFileSystem(
+ const std::string& file_system_path) {
+ delegate_->RemoveFileSystem(file_system_path);
+}
+
+void DevToolsFrontendHost::OnIndexPath(int request_id,
+ const std::string& file_system_path) {
+ delegate_->IndexPath(request_id, file_system_path);
+}
+
+void DevToolsFrontendHost::OnStopIndexing(int request_id) {
+ delegate_->StopIndexing(request_id);
+}
+
+void DevToolsFrontendHost::OnSearchInPath(int request_id,
+ const std::string& file_system_path,
+ const std::string& query) {
+ delegate_->SearchInPath(request_id, file_system_path, query);
+}
+
+void DevToolsFrontendHost::OnRequestSetDockSide(const std::string& side) {
+ delegate_->SetDockSide(side);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_frontend_host.h b/chromium/content/browser/devtools/devtools_frontend_host.h
new file mode 100644
index 00000000000..646ea4bbdce
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_frontend_host.h
@@ -0,0 +1,66 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+
+class DevToolsFrontendHostDelegate;
+class WebContentsImpl;
+
+// This class handles messages from DevToolsClient and calls corresponding
+// methods on DevToolsFrontendHostDelegate which is implemented by the
+// embedder. This allows us to avoid exposing DevTools client messages through
+// the content public API.
+class DevToolsFrontendHost : public DevToolsClientHost,
+ public WebContentsObserver {
+ public:
+ DevToolsFrontendHost(WebContentsImpl* web_contents,
+ DevToolsFrontendHostDelegate* delegate);
+
+ private:
+ virtual ~DevToolsFrontendHost();
+
+ // DevToolsClientHost implementation.
+ virtual void DispatchOnInspectorFrontend(const std::string& message) OVERRIDE;
+ virtual void InspectedContentsClosing() OVERRIDE;
+ virtual void ReplacedWithAnotherClient() OVERRIDE;
+
+ // WebContentsObserver overrides.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+
+ void OnDispatchOnInspectorBackend(const std::string& message);
+ void OnActivateWindow();
+ void OnChangeAttachedWindowHeight(unsigned height);
+ void OnCloseWindow();
+ void OnMoveWindow(int x, int y);
+ void OnRequestSetDockSide(const std::string& side);
+ void OnOpenInNewTab(const std::string& url);
+ void OnSave(const std::string& url, const std::string& content, bool save_as);
+ void OnAppend(const std::string& url, const std::string& content);
+ void OnRequestFileSystems();
+ void OnAddFileSystem();
+ void OnRemoveFileSystem(const std::string& file_system_path);
+ void OnIndexPath(int request_id, const std::string& file_system_path);
+ void OnStopIndexing(int request_id);
+ void OnSearchInPath(int request_id,
+ const std::string& file_system_path,
+ const std::string& query);
+
+ DevToolsFrontendHostDelegate* delegate_;
+ DISALLOW_COPY_AND_ASSIGN(DevToolsFrontendHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_
diff --git a/chromium/content/browser/devtools/devtools_http_handler_impl.cc b/chromium/content/browser/devtools/devtools_http_handler_impl.cc
new file mode 100644
index 00000000000..47bdfd7b9c8
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_http_handler_impl.cc
@@ -0,0 +1,866 @@
+// 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 "content/browser/devtools/devtools_http_handler_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "content/browser/devtools/devtools_browser_target.h"
+#include "content/browser/devtools/devtools_protocol.h"
+#include "content/browser/devtools/devtools_protocol_constants.h"
+#include "content/browser/devtools/devtools_tracing_handler.h"
+#include "content/browser/devtools/tethering_handler.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/devtools_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/devtools_http_handler_delegate.h"
+#include "content/public/browser/devtools_manager.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/url_constants.h"
+#include "grit/devtools_resources_map.h"
+#include "net/base/escape.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/server/http_server_request_info.h"
+#include "net/server/http_server_response_info.h"
+#include "ui/base/layout.h"
+#include "url/gurl.h"
+#include "webkit/common/user_agent/user_agent.h"
+#include "webkit/common/user_agent/user_agent_util.h"
+
+namespace content {
+
+const int kBufferSize = 16 * 1024;
+
+namespace {
+
+static const char* kProtocolVersion = "1.0";
+
+static const char* kDevToolsHandlerThreadName = "Chrome_DevToolsHandlerThread";
+
+static const char* kThumbUrlPrefix = "/thumb/";
+static const char* kPageUrlPrefix = "/devtools/page/";
+
+static const char* kTargetIdField = "id";
+static const char* kTargetTypeField = "type";
+static const char* kTargetTitleField = "title";
+static const char* kTargetDescriptionField = "description";
+static const char* kTargetUrlField = "url";
+static const char* kTargetThumbnailUrlField = "thumbnailUrl";
+static const char* kTargetFaviconUrlField = "faviconUrl";
+static const char* kTargetWebSocketDebuggerUrlField = "webSocketDebuggerUrl";
+static const char* kTargetDevtoolsFrontendUrlField = "devtoolsFrontendUrl";
+
+static const char* kTargetTypePage = "page";
+static const char* kTargetTypeOther = "other";
+
+class DevToolsDefaultBindingHandler
+ : public DevToolsHttpHandler::DevToolsAgentHostBinding {
+ public:
+ DevToolsDefaultBindingHandler() {
+ }
+
+ virtual std::string GetIdentifier(DevToolsAgentHost* agent_host) OVERRIDE {
+ return agent_host->GetId();
+ }
+
+ virtual DevToolsAgentHost* ForIdentifier(const std::string& id) OVERRIDE {
+ return DevToolsAgentHost::GetForId(id).get();
+ }
+};
+
+// An internal implementation of DevToolsClientHost that delegates
+// messages sent for DevToolsClient to a DebuggerShell instance.
+class DevToolsClientHostImpl : public DevToolsClientHost {
+ public:
+ DevToolsClientHostImpl(base::MessageLoop* message_loop,
+ net::HttpServer* server,
+ int connection_id)
+ : message_loop_(message_loop),
+ server_(server),
+ connection_id_(connection_id),
+ is_closed_(false),
+ detach_reason_("target_closed") {}
+
+ virtual ~DevToolsClientHostImpl() {}
+
+ // DevToolsClientHost interface
+ virtual void InspectedContentsClosing() OVERRIDE {
+ if (is_closed_)
+ return;
+ is_closed_ = true;
+
+ base::DictionaryValue notification;
+ notification.SetString(
+ devtools::Inspector::detached::kParamReason, detach_reason_);
+ std::string response = DevToolsProtocol::CreateNotification(
+ devtools::Inspector::detached::kName,
+ notification.DeepCopy())->Serialize();
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::SendOverWebSocket,
+ server_,
+ connection_id_,
+ response));
+
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::Close, server_, connection_id_));
+ }
+
+ virtual void DispatchOnInspectorFrontend(const std::string& data) OVERRIDE {
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::SendOverWebSocket,
+ server_,
+ connection_id_,
+ data));
+ }
+
+ virtual void ReplacedWithAnotherClient() OVERRIDE {
+ detach_reason_ = "replaced_with_devtools";
+ }
+
+ private:
+ base::MessageLoop* message_loop_;
+ net::HttpServer* server_;
+ int connection_id_;
+ bool is_closed_;
+ std::string detach_reason_;
+};
+
+static base::TimeTicks GetLastSelectedTime(RenderViewHost* rvh) {
+ WebContents* web_contents = rvh->GetDelegate()->GetAsWebContents();
+ if (!web_contents)
+ return base::TimeTicks();
+
+ return web_contents->GetLastSelectedTime();
+}
+
+typedef std::pair<RenderViewHost*, base::TimeTicks> PageInfo;
+
+static bool TimeComparator(const PageInfo& info1, const PageInfo& info2) {
+ return info1.second > info2.second;
+}
+
+} // namespace
+
+// static
+bool DevToolsHttpHandler::IsSupportedProtocolVersion(
+ const std::string& version) {
+ return version == kProtocolVersion;
+}
+
+// static
+int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
+ for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
+ if (name == kDevtoolsResources[i].name)
+ return kDevtoolsResources[i].value;
+ }
+ return -1;
+}
+
+// static
+DevToolsHttpHandler* DevToolsHttpHandler::Start(
+ const net::StreamListenSocketFactory* socket_factory,
+ const std::string& frontend_url,
+ DevToolsHttpHandlerDelegate* delegate) {
+ DevToolsHttpHandlerImpl* http_handler =
+ new DevToolsHttpHandlerImpl(socket_factory,
+ frontend_url,
+ delegate);
+ http_handler->Start();
+ return http_handler;
+}
+
+DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Stop() must be called prior to destruction.
+ DCHECK(server_.get() == NULL);
+ DCHECK(thread_.get() == NULL);
+}
+
+void DevToolsHttpHandlerImpl::Start() {
+ if (thread_)
+ return;
+ thread_.reset(new base::Thread(kDevToolsHandlerThreadName));
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this));
+}
+
+// Runs on FILE thread.
+void DevToolsHttpHandlerImpl::StartHandlerThread() {
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ if (!thread_->StartWithOptions(options)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
+ return;
+ }
+
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::Init, this));
+}
+
+void DevToolsHttpHandlerImpl::ResetHandlerThread() {
+ thread_.reset();
+}
+
+void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
+ ResetHandlerThread();
+ Release();
+}
+
+void DevToolsHttpHandlerImpl::Stop() {
+ if (!thread_)
+ return;
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
+ base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this));
+}
+
+void DevToolsHttpHandlerImpl::SetDevToolsAgentHostBinding(
+ DevToolsAgentHostBinding* binding) {
+ if (binding)
+ binding_ = binding;
+ else
+ binding_ = default_binding_.get();
+}
+
+GURL DevToolsHttpHandlerImpl::GetFrontendURL(DevToolsAgentHost* agent_host) {
+ net::IPEndPoint ip_address;
+ if (server_->GetLocalAddress(&ip_address))
+ return GURL();
+ if (!agent_host) {
+ return GURL(std::string("http://") + ip_address.ToString() +
+ overridden_frontend_url_);
+ }
+ std::string host = ip_address.ToString();
+ std::string id = binding_->GetIdentifier(agent_host);
+ return GURL(std::string("http://") +
+ ip_address.ToString() +
+ GetFrontendURLInternal(id, host));
+}
+
+static std::string PathWithoutParams(const std::string& path) {
+ size_t query_position = path.find("?");
+ if (query_position != std::string::npos)
+ return path.substr(0, query_position);
+ return path;
+}
+
+static std::string GetMimeType(const std::string& filename) {
+ if (EndsWith(filename, ".html", false)) {
+ return "text/html";
+ } else if (EndsWith(filename, ".css", false)) {
+ return "text/css";
+ } else if (EndsWith(filename, ".js", false)) {
+ return "application/javascript";
+ } else if (EndsWith(filename, ".png", false)) {
+ return "image/png";
+ } else if (EndsWith(filename, ".gif", false)) {
+ return "image/gif";
+ }
+ NOTREACHED();
+ return "text/plain";
+}
+
+void DevToolsHttpHandlerImpl::OnHttpRequest(
+ int connection_id,
+ const net::HttpServerRequestInfo& info) {
+ if (info.path.find("/json") == 0) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI,
+ this,
+ connection_id,
+ info));
+ return;
+ }
+
+ if (info.path.find(kThumbUrlPrefix) == 0) {
+ // Thumbnail request.
+ const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
+ DevToolsAgentHost* agent_host = binding_->ForIdentifier(target_id);
+ GURL page_url;
+ if (agent_host) {
+ RenderViewHost* rvh = agent_host->GetRenderViewHost();
+ if (rvh)
+ page_url = rvh->GetDelegate()->GetURL();
+ }
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI,
+ this,
+ connection_id,
+ page_url));
+ return;
+ }
+
+ if (info.path == "" || info.path == "/") {
+ // Discovery page request.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI,
+ this,
+ connection_id));
+ return;
+ }
+
+ if (info.path.find("/devtools/") != 0) {
+ server_->Send404(connection_id);
+ return;
+ }
+
+ std::string filename = PathWithoutParams(info.path.substr(10));
+ std::string mime_type = GetMimeType(filename);
+
+ base::FilePath frontend_dir = delegate_->GetDebugFrontendDir();
+ if (!frontend_dir.empty()) {
+ base::FilePath path = frontend_dir.AppendASCII(filename);
+ std::string data;
+ file_util::ReadFileToString(path, &data);
+ server_->Send200(connection_id, data, mime_type);
+ return;
+ }
+ if (delegate_->BundlesFrontendResources()) {
+ int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
+ if (resource_id != -1) {
+ base::StringPiece data = GetContentClient()->GetDataResource(
+ resource_id, ui::SCALE_FACTOR_NONE);
+ server_->Send200(connection_id, data.as_string(), mime_type);
+ return;
+ }
+ }
+ server_->Send404(connection_id);
+}
+
+void DevToolsHttpHandlerImpl::OnWebSocketRequest(
+ int connection_id,
+ const net::HttpServerRequestInfo& request) {
+ std::string browser_prefix = "/devtools/browser";
+ size_t browser_pos = request.path.find(browser_prefix);
+ if (browser_pos == 0) {
+ if (browser_target_) {
+ server_->Send500(connection_id, "Another client already attached");
+ return;
+ }
+ browser_target_ = new DevToolsBrowserTarget(
+ thread_->message_loop_proxy().get(), server_.get(), connection_id);
+ browser_target_->RegisterDomainHandler(
+ devtools::Tracing::kName,
+ new DevToolsTracingHandler(),
+ true /* handle on UI thread */);
+ browser_target_->RegisterDomainHandler(
+ TetheringHandler::kDomain,
+ new TetheringHandler(delegate_.get()),
+ false /* handle on this thread */);
+
+ server_->AcceptWebSocket(connection_id, request);
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &DevToolsHttpHandlerImpl::OnWebSocketRequestUI,
+ this,
+ connection_id,
+ request));
+}
+
+void DevToolsHttpHandlerImpl::OnWebSocketMessage(
+ int connection_id,
+ const std::string& data) {
+ if (browser_target_ && connection_id == browser_target_->connection_id()) {
+ browser_target_->HandleMessage(data);
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &DevToolsHttpHandlerImpl::OnWebSocketMessageUI,
+ this,
+ connection_id,
+ data));
+}
+
+void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
+ if (browser_target_ && browser_target_->connection_id() == connection_id) {
+ browser_target_->Detach();
+ browser_target_ = NULL;
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &DevToolsHttpHandlerImpl::OnCloseUI,
+ this,
+ connection_id));
+}
+
+std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
+ const std::string id,
+ const std::string& host) {
+ return base::StringPrintf(
+ "%s%sws=%s%s%s",
+ overridden_frontend_url_.c_str(),
+ overridden_frontend_url_.find("?") == std::string::npos ? "?" : "&",
+ host.c_str(),
+ kPageUrlPrefix,
+ id.c_str());
+}
+
+static bool ParseJsonPath(
+ const std::string& path,
+ std::string* command,
+ std::string* target_id) {
+
+ // Fall back to list in case of empty query.
+ if (path.empty()) {
+ *command = "list";
+ return true;
+ }
+
+ if (path.find("/") != 0) {
+ // Malformed command.
+ return false;
+ }
+ *command = path.substr(1);
+
+ size_t separator_pos = command->find("/");
+ if (separator_pos != std::string::npos) {
+ *target_id = command->substr(separator_pos + 1);
+ *command = command->substr(0, separator_pos);
+ }
+ return true;
+}
+
+void DevToolsHttpHandlerImpl::OnJsonRequestUI(
+ int connection_id,
+ const net::HttpServerRequestInfo& info) {
+ // Trim /json
+ std::string path = info.path.substr(5);
+
+ // Trim fragment and query
+ size_t query_pos = path.find("?");
+ if (query_pos != std::string::npos)
+ path = path.substr(0, query_pos);
+
+ size_t fragment_pos = path.find("#");
+ if (fragment_pos != std::string::npos)
+ path = path.substr(0, fragment_pos);
+
+ std::string command;
+ std::string target_id;
+ if (!ParseJsonPath(path, &command, &target_id)) {
+ SendJson(connection_id,
+ net::HTTP_NOT_FOUND,
+ NULL,
+ "Malformed query: " + info.path);
+ return;
+ }
+
+ if (command == "version") {
+ base::DictionaryValue version;
+ version.SetString("Protocol-Version", kProtocolVersion);
+ version.SetString("WebKit-Version", webkit_glue::GetWebKitVersion());
+ version.SetString("Browser", content::GetContentClient()->GetProduct());
+ version.SetString("User-Agent",
+ webkit_glue::GetUserAgent(GURL(kAboutBlankURL)));
+ SendJson(connection_id, net::HTTP_OK, &version, std::string());
+ return;
+ }
+
+ if (command == "list") {
+ typedef std::vector<PageInfo> PageList;
+ PageList page_list;
+
+ std::vector<RenderViewHost*> rvh_list =
+ DevToolsAgentHost::GetValidRenderViewHosts();
+ for (std::vector<RenderViewHost*>::iterator it = rvh_list.begin();
+ it != rvh_list.end(); ++it)
+ page_list.push_back(PageInfo(*it, GetLastSelectedTime(*it)));
+
+ std::sort(page_list.begin(), page_list.end(), TimeComparator);
+
+ base::ListValue* target_list = new base::ListValue();
+ std::string host = info.headers["host"];
+ for (PageList::iterator i = page_list.begin(); i != page_list.end(); ++i)
+ target_list->Append(SerializePageInfo(i->first, host));
+
+ AddRef(); // Balanced in SendTargetList.
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::CollectWorkerInfo,
+ base::Unretained(this),
+ target_list,
+ host),
+ base::Bind(&DevToolsHttpHandlerImpl::SendTargetList,
+ base::Unretained(this),
+ connection_id,
+ target_list));
+ return;
+ }
+
+ if (command == "new") {
+ RenderViewHost* rvh = delegate_->CreateNewTarget();
+ if (!rvh) {
+ SendJson(connection_id,
+ net::HTTP_INTERNAL_SERVER_ERROR,
+ NULL,
+ "Could not create new page");
+ return;
+ }
+ std::string host = info.headers["host"];
+ scoped_ptr<base::DictionaryValue> dictionary(SerializePageInfo(rvh, host));
+ SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
+ return;
+ }
+
+ if (command == "activate" || command == "close") {
+ DevToolsAgentHost* agent_host = binding_->ForIdentifier(target_id);
+ RenderViewHost* rvh = agent_host ? agent_host->GetRenderViewHost() : NULL;
+ if (!rvh) {
+ SendJson(connection_id,
+ net::HTTP_NOT_FOUND,
+ NULL,
+ "No such target id: " + target_id);
+ return;
+ }
+
+ if (command == "activate") {
+ rvh->GetDelegate()->Activate();
+ SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
+ return;
+ }
+
+ if (command == "close") {
+ rvh->ClosePage();
+ SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
+ return;
+ }
+ }
+ SendJson(connection_id,
+ net::HTTP_NOT_FOUND,
+ NULL,
+ "Unknown command: " + command);
+ return;
+}
+
+void DevToolsHttpHandlerImpl::CollectWorkerInfo(base::ListValue* target_list,
+ std::string host) {
+
+ std::vector<WorkerService::WorkerInfo> worker_info =
+ WorkerService::GetInstance()->GetWorkers();
+
+ for (size_t i = 0; i < worker_info.size(); ++i)
+ target_list->Append(SerializeWorkerInfo(worker_info[i], host));
+}
+
+void DevToolsHttpHandlerImpl::SendTargetList(int connection_id,
+ base::ListValue* target_list) {
+ SendJson(connection_id, net::HTTP_OK, target_list, std::string());
+ delete target_list;
+ Release(); // Balanced OnJsonRequestUI.
+}
+
+void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
+ int connection_id, const GURL& page_url) {
+ std::string data = delegate_->GetPageThumbnailData(page_url);
+ if (!data.empty())
+ Send200(connection_id, data, "image/png");
+ else
+ Send404(connection_id);
+}
+
+void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) {
+ std::string response = delegate_->GetDiscoveryPageHTML();
+ Send200(connection_id, response, "text/html; charset=UTF-8");
+}
+
+void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
+ int connection_id,
+ const net::HttpServerRequestInfo& request) {
+ if (!thread_)
+ return;
+
+ size_t pos = request.path.find(kPageUrlPrefix);
+ if (pos != 0) {
+ Send404(connection_id);
+ return;
+ }
+
+ std::string page_id = request.path.substr(strlen(kPageUrlPrefix));
+ DevToolsAgentHost* agent = binding_->ForIdentifier(page_id);
+ if (!agent) {
+ Send500(connection_id, "No such target id: " + page_id);
+ return;
+ }
+
+ if (agent->IsAttached()) {
+ Send500(connection_id,
+ "Target with given id is being inspected: " + page_id);
+ return;
+ }
+
+ DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl(
+ thread_->message_loop(), server_.get(), connection_id);
+ connection_to_client_host_ui_[connection_id] = client_host;
+
+ DevToolsManager::GetInstance()->
+ RegisterDevToolsClientHostFor(agent, client_host);
+
+ AcceptWebSocket(connection_id, request);
+}
+
+void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
+ int connection_id,
+ const std::string& data) {
+ ConnectionToClientHostMap::iterator it =
+ connection_to_client_host_ui_.find(connection_id);
+ if (it == connection_to_client_host_ui_.end())
+ return;
+
+ DevToolsManager* manager = DevToolsManager::GetInstance();
+ manager->DispatchOnInspectorBackend(it->second, data);
+}
+
+void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) {
+ ConnectionToClientHostMap::iterator it =
+ connection_to_client_host_ui_.find(connection_id);
+ if (it != connection_to_client_host_ui_.end()) {
+ DevToolsClientHostImpl* client_host =
+ static_cast<DevToolsClientHostImpl*>(it->second);
+ DevToolsManager::GetInstance()->ClientHostClosing(client_host);
+ delete client_host;
+ connection_to_client_host_ui_.erase(connection_id);
+ }
+}
+
+DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
+ const net::StreamListenSocketFactory* socket_factory,
+ const std::string& frontend_url,
+ DevToolsHttpHandlerDelegate* delegate)
+ : overridden_frontend_url_(frontend_url),
+ socket_factory_(socket_factory),
+ delegate_(delegate) {
+ if (overridden_frontend_url_.empty())
+ overridden_frontend_url_ = "/devtools/devtools.html";
+
+ default_binding_.reset(new DevToolsDefaultBindingHandler);
+ binding_ = default_binding_.get();
+
+ // Balanced in ResetHandlerThreadAndRelease().
+ AddRef();
+}
+
+// Runs on the handler thread
+void DevToolsHttpHandlerImpl::Init() {
+ server_ = new net::HttpServer(*socket_factory_.get(), this);
+}
+
+// Runs on the handler thread
+void DevToolsHttpHandlerImpl::Teardown() {
+ server_ = NULL;
+}
+
+// Runs on FILE thread to make sure that it is serialized against
+// {Start|Stop}HandlerThread and to allow calling pthread_join.
+void DevToolsHttpHandlerImpl::StopHandlerThread() {
+ if (!thread_->message_loop())
+ return;
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&DevToolsHttpHandlerImpl::Teardown, this));
+ // Thread::Stop joins the thread.
+ thread_->Stop();
+}
+
+void DevToolsHttpHandlerImpl::SendJson(int connection_id,
+ net::HttpStatusCode status_code,
+ base::Value* value,
+ const std::string& message) {
+ if (!thread_)
+ return;
+
+ // Serialize value and message.
+ std::string json_value;
+ if (value) {
+ base::JSONWriter::WriteWithOptions(value,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &json_value);
+ }
+ std::string json_message;
+ scoped_ptr<base::Value> message_object(new base::StringValue(message));
+ base::JSONWriter::Write(message_object.get(), &json_message);
+
+ net::HttpServerResponseInfo response(status_code);
+ response.SetBody(json_value + message, "application/json; charset=UTF-8");
+
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::SendResponse,
+ server_.get(),
+ connection_id,
+ response));
+}
+
+void DevToolsHttpHandlerImpl::Send200(int connection_id,
+ const std::string& data,
+ const std::string& mime_type) {
+ if (!thread_)
+ return;
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::Send200,
+ server_.get(),
+ connection_id,
+ data,
+ mime_type));
+}
+
+void DevToolsHttpHandlerImpl::Send404(int connection_id) {
+ if (!thread_)
+ return;
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::Send404, server_.get(), connection_id));
+}
+
+void DevToolsHttpHandlerImpl::Send500(int connection_id,
+ const std::string& message) {
+ if (!thread_)
+ return;
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::Send500, server_.get(), connection_id,
+ message));
+}
+
+void DevToolsHttpHandlerImpl::AcceptWebSocket(
+ int connection_id,
+ const net::HttpServerRequestInfo& request) {
+ if (!thread_)
+ return;
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(),
+ connection_id, request));
+}
+
+base::DictionaryValue* DevToolsHttpHandlerImpl::SerializePageInfo(
+ RenderViewHost* rvh,
+ const std::string& host) {
+ base::DictionaryValue* dictionary = new base::DictionaryValue;
+
+ scoped_refptr<DevToolsAgentHost> agent(
+ DevToolsAgentHost::GetOrCreateFor(rvh));
+
+ std::string id = binding_->GetIdentifier(agent.get());
+ dictionary->SetString(kTargetIdField, id);
+
+ switch (delegate_->GetTargetType(rvh)) {
+ case DevToolsHttpHandlerDelegate::kTargetTypeTab:
+ dictionary->SetString(kTargetTypeField, kTargetTypePage);
+ break;
+ default:
+ dictionary->SetString(kTargetTypeField, kTargetTypeOther);
+ }
+
+ WebContents* web_contents = rvh->GetDelegate()->GetAsWebContents();
+ if (web_contents) {
+ dictionary->SetString(kTargetTitleField, UTF16ToUTF8(
+ net::EscapeForHTML(web_contents->GetTitle())));
+ dictionary->SetString(kTargetUrlField, web_contents->GetURL().spec());
+ dictionary->SetString(kTargetThumbnailUrlField,
+ std::string(kThumbUrlPrefix) + id);
+
+ NavigationController& controller = web_contents->GetController();
+ NavigationEntry* entry = controller.GetActiveEntry();
+ if (entry != NULL && entry->GetURL().is_valid()) {
+ dictionary->SetString(kTargetFaviconUrlField,
+ entry->GetFavicon().url.spec());
+ }
+ }
+ dictionary->SetString(kTargetDescriptionField,
+ delegate_->GetViewDescription(rvh));
+
+ if (!agent->IsAttached())
+ SerializeDebuggerURLs(dictionary, id, host);
+ return dictionary;
+}
+
+base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeWorkerInfo(
+ const WorkerService::WorkerInfo& worker,
+ const std::string& host) {
+ base::DictionaryValue* dictionary = new base::DictionaryValue;
+
+ scoped_refptr<DevToolsAgentHost> agent(DevToolsAgentHost::GetForWorker(
+ worker.process_id, worker.route_id));
+
+ std::string id = binding_->GetIdentifier(agent.get());
+
+ dictionary->SetString(kTargetIdField, id);
+ dictionary->SetString(kTargetTypeField, kTargetTypeOther);
+ dictionary->SetString(kTargetTitleField,
+ UTF16ToUTF8(net::EscapeForHTML(worker.name)));
+ dictionary->SetString(kTargetUrlField, worker.url.spec());
+ dictionary->SetString(kTargetDescriptionField,
+ base::StringPrintf("Worker pid:%d", base::GetProcId(worker.handle)));
+
+ if (!agent->IsAttached())
+ SerializeDebuggerURLs(dictionary, id, host);
+ return dictionary;
+}
+
+void DevToolsHttpHandlerImpl::SerializeDebuggerURLs(
+ base::DictionaryValue* dictionary,
+ const std::string& id,
+ const std::string& host) {
+ dictionary->SetString(kTargetWebSocketDebuggerUrlField,
+ base::StringPrintf("ws://%s%s%s",
+ host.c_str(),
+ kPageUrlPrefix,
+ id.c_str()));
+ std::string devtools_frontend_url = GetFrontendURLInternal(
+ id.c_str(),
+ host);
+ dictionary->SetString(kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_http_handler_impl.h b/chromium/content/browser/devtools/devtools_http_handler_impl.h
new file mode 100644
index 00000000000..f7ccca02d45
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_http_handler_impl.h
@@ -0,0 +1,137 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/devtools_http_handler.h"
+#include "content/public/browser/devtools_http_handler_delegate.h"
+#include "content/public/browser/worker_service.h"
+#include "net/http/http_status_code.h"
+#include "net/server/http_server.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Thread;
+class Value;
+}
+
+namespace net {
+class StreamListenSocketFactory;
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+class DevToolsBrowserTarget;
+class DevToolsClientHost;
+
+class DevToolsHttpHandlerImpl
+ : public DevToolsHttpHandler,
+ public base::RefCountedThreadSafe<DevToolsHttpHandlerImpl>,
+ public net::HttpServer::Delegate {
+ private:
+ friend class base::RefCountedThreadSafe<DevToolsHttpHandlerImpl>;
+ friend class DevToolsHttpHandler;
+
+ // Takes ownership over |socket_factory|.
+ DevToolsHttpHandlerImpl(const net::StreamListenSocketFactory* socket_factory,
+ const std::string& frontend_url,
+ DevToolsHttpHandlerDelegate* delegate);
+ virtual ~DevToolsHttpHandlerImpl();
+ void Start();
+
+ // DevToolsHttpHandler implementation.
+ virtual void Stop() OVERRIDE;
+ virtual void SetDevToolsAgentHostBinding(
+ DevToolsAgentHostBinding* binding) OVERRIDE;
+ virtual GURL GetFrontendURL(DevToolsAgentHost* agent_host) OVERRIDE;
+
+ // net::HttpServer::Delegate implementation.
+ virtual void OnHttpRequest(int connection_id,
+ const net::HttpServerRequestInfo& info) OVERRIDE;
+ virtual void OnWebSocketRequest(
+ int connection_id,
+ const net::HttpServerRequestInfo& info) OVERRIDE;
+ virtual void OnWebSocketMessage(int connection_id,
+ const std::string& data) OVERRIDE;
+ virtual void OnClose(int connection_id) OVERRIDE;
+
+ void OnJsonRequestUI(int connection_id,
+ const net::HttpServerRequestInfo& info);
+ void OnThumbnailRequestUI(int connection_id, const GURL& page_url);
+ void OnDiscoveryPageRequestUI(int connection_id);
+
+ void OnWebSocketRequestUI(int connection_id,
+ const net::HttpServerRequestInfo& info);
+ void OnWebSocketMessageUI(int connection_id, const std::string& data);
+ void OnCloseUI(int connection_id);
+
+ void ResetHandlerThread();
+ void ResetHandlerThreadAndRelease();
+
+ void CollectWorkerInfo(base::ListValue* target_list, std::string host);
+ void SendTargetList(int connection_id, base::ListValue* target_list);
+
+ void Init();
+ void Teardown();
+
+ void StartHandlerThread();
+ void StopHandlerThread();
+
+ void SendJson(int connection_id,
+ net::HttpStatusCode status_code,
+ base::Value* value,
+ const std::string& message);
+ void Send200(int connection_id,
+ const std::string& data,
+ const std::string& mime_type);
+ void Send404(int connection_id);
+ void Send500(int connection_id,
+ const std::string& message);
+ void AcceptWebSocket(int connection_id,
+ const net::HttpServerRequestInfo& request);
+
+ // Returns the front end url without the host at the beginning.
+ std::string GetFrontendURLInternal(const std::string rvh_id,
+ const std::string& host);
+
+ base::DictionaryValue* SerializePageInfo(RenderViewHost* rvh,
+ const std::string& host);
+
+ base::DictionaryValue* SerializeWorkerInfo(
+ const WorkerService::WorkerInfo& worker,
+ const std::string& host);
+
+ void SerializeDebuggerURLs(base::DictionaryValue* dictionary,
+ const std::string& id,
+ const std::string& host);
+
+ // The thread used by the devtools handler to run server socket.
+ scoped_ptr<base::Thread> thread_;
+
+ std::string overridden_frontend_url_;
+ scoped_ptr<const net::StreamListenSocketFactory> socket_factory_;
+ scoped_refptr<net::HttpServer> server_;
+ typedef std::map<int, DevToolsClientHost*> ConnectionToClientHostMap;
+ ConnectionToClientHostMap connection_to_client_host_ui_;
+ scoped_ptr<DevToolsHttpHandlerDelegate> delegate_;
+ DevToolsAgentHostBinding* binding_;
+ scoped_ptr<DevToolsAgentHostBinding> default_binding_;
+ scoped_refptr<DevToolsBrowserTarget> browser_target_;
+ DISALLOW_COPY_AND_ASSIGN(DevToolsHttpHandlerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_
diff --git a/chromium/content/browser/devtools/devtools_http_handler_unittest.cc b/chromium/content/browser/devtools/devtools_http_handler_unittest.cc
new file mode 100644
index 00000000000..e770b8fe455
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_http_handler_unittest.cc
@@ -0,0 +1,118 @@
+// 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 "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/public/browser/devtools_http_handler.h"
+#include "content/public/browser/devtools_http_handler_delegate.h"
+#include "net/socket/stream_listen_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+using net::StreamListenSocket;
+
+class DummyListenSocket : public StreamListenSocket,
+ public StreamListenSocket::Delegate {
+ public:
+ DummyListenSocket()
+ : StreamListenSocket(0, this) {}
+
+ // StreamListenSocket::Delegate "implementation"
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) OVERRIDE {}
+ virtual void DidRead(StreamListenSocket* connection,
+ const char* data,
+ int len) OVERRIDE {}
+ virtual void DidClose(StreamListenSocket* sock) OVERRIDE {}
+ protected:
+ virtual ~DummyListenSocket() {}
+ virtual void Accept() OVERRIDE {}
+};
+
+class DummyListenSocketFactory : public net::StreamListenSocketFactory {
+ public:
+ DummyListenSocketFactory(
+ base::Closure quit_closure_1, base::Closure quit_closure_2)
+ : quit_closure_1_(quit_closure_1), quit_closure_2_(quit_closure_2) {}
+ virtual ~DummyListenSocketFactory() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, quit_closure_2_);
+ }
+
+ virtual scoped_refptr<StreamListenSocket> CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const OVERRIDE {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, quit_closure_1_);
+ return new DummyListenSocket();
+ }
+ private:
+ base::Closure quit_closure_1_;
+ base::Closure quit_closure_2_;
+};
+
+class DummyDelegate : public DevToolsHttpHandlerDelegate {
+ public:
+ virtual std::string GetDiscoveryPageHTML() OVERRIDE { return std::string(); }
+ virtual bool BundlesFrontendResources() OVERRIDE { return true; }
+ virtual base::FilePath GetDebugFrontendDir() OVERRIDE {
+ return base::FilePath();
+ }
+ virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE {
+ return std::string();
+ }
+ virtual RenderViewHost* CreateNewTarget() OVERRIDE { return NULL; }
+ virtual TargetType GetTargetType(RenderViewHost*) OVERRIDE {
+ return kTargetTypeTab;
+ }
+ virtual std::string GetViewDescription(content::RenderViewHost*) OVERRIDE {
+ return std::string();
+ }
+ virtual scoped_refptr<net::StreamListenSocket> CreateSocketForTethering(
+ net::StreamListenSocket::Delegate* delegate,
+ std::string* name) OVERRIDE {
+ return NULL;
+ }
+};
+
+}
+
+class DevToolsHttpHandlerTest : public testing::Test {
+ public:
+ DevToolsHttpHandlerTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_) {
+ }
+ protected:
+ virtual void SetUp() {
+ file_thread_.reset(new BrowserThreadImpl(BrowserThread::FILE));
+ file_thread_->Start();
+ }
+ virtual void TearDown() {
+ file_thread_->Stop();
+ }
+ private:
+ base::MessageLoopForIO message_loop_;
+ BrowserThreadImpl ui_thread_;
+ scoped_ptr<BrowserThreadImpl> file_thread_;
+};
+
+TEST_F(DevToolsHttpHandlerTest, TestStartStop) {
+ base::RunLoop run_loop, run_loop_2;
+ content::DevToolsHttpHandler* devtools_http_handler_ =
+ content::DevToolsHttpHandler::Start(
+ new DummyListenSocketFactory(run_loop.QuitClosure(),
+ run_loop_2.QuitClosure()),
+ std::string(),
+ new DummyDelegate());
+ // Our dummy socket factory will post a quit message once the server will
+ // become ready.
+ run_loop.Run();
+ devtools_http_handler_->Stop();
+ // Make sure the handler actually stops.
+ run_loop_2.Run();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_manager_impl.cc b/chromium/content/browser/devtools/devtools_manager_impl.cc
new file mode 100644
index 00000000000..56b2dd7d2f4
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_manager_impl.cc
@@ -0,0 +1,191 @@
+// 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 "content/browser/devtools/devtools_manager_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/devtools/devtools_netlog_observer.h"
+#include "content/browser/devtools/render_view_devtools_agent_host.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/devtools_client_host.h"
+
+namespace content {
+
+// static
+DevToolsManager* DevToolsManager::GetInstance() {
+ return DevToolsManagerImpl::GetInstance();
+}
+
+// static
+DevToolsManagerImpl* DevToolsManagerImpl::GetInstance() {
+ return Singleton<DevToolsManagerImpl>::get();
+}
+
+DevToolsManagerImpl::DevToolsManagerImpl() {
+}
+
+DevToolsManagerImpl::~DevToolsManagerImpl() {
+ DCHECK(agent_to_client_host_.empty());
+ DCHECK(client_to_agent_host_.empty());
+}
+
+DevToolsClientHost* DevToolsManagerImpl::GetDevToolsClientHostFor(
+ DevToolsAgentHostImpl* agent_host_impl) {
+ AgentToClientHostMap::iterator it =
+ agent_to_client_host_.find(agent_host_impl);
+ if (it != agent_to_client_host_.end())
+ return it->second;
+ return NULL;
+}
+
+DevToolsAgentHost* DevToolsManagerImpl::GetDevToolsAgentHostFor(
+ DevToolsClientHost* client_host) {
+ ClientToAgentHostMap::iterator it = client_to_agent_host_.find(client_host);
+ if (it != client_to_agent_host_.end())
+ return it->second.get();
+ return NULL;
+}
+
+void DevToolsManagerImpl::RegisterDevToolsClientHostFor(
+ DevToolsAgentHost* agent_host,
+ DevToolsClientHost* client_host) {
+ DevToolsAgentHostImpl* agent_host_impl =
+ static_cast<DevToolsAgentHostImpl*>(agent_host);
+ DevToolsClientHost* old_client_host =
+ GetDevToolsClientHostFor(agent_host_impl);
+ if (old_client_host) {
+ old_client_host->ReplacedWithAnotherClient();
+ UnregisterDevToolsClientHostFor(agent_host_impl);
+ }
+ BindClientHost(agent_host_impl, client_host);
+ agent_host_impl->Attach();
+}
+
+bool DevToolsManagerImpl::DispatchOnInspectorBackend(
+ DevToolsClientHost* from,
+ const std::string& message) {
+ DevToolsAgentHost* agent_host = GetDevToolsAgentHostFor(from);
+ if (!agent_host)
+ return false;
+ DevToolsAgentHostImpl* agent_host_impl =
+ static_cast<DevToolsAgentHostImpl*>(agent_host);
+ agent_host_impl->DispatchOnInspectorBackend(message);
+ return true;
+}
+
+void DevToolsManagerImpl::DispatchOnInspectorFrontend(
+ DevToolsAgentHost* agent_host,
+ const std::string& message) {
+ DevToolsAgentHostImpl* agent_host_impl =
+ static_cast<DevToolsAgentHostImpl*>(agent_host);
+ DevToolsClientHost* client_host = GetDevToolsClientHostFor(agent_host_impl);
+ if (!client_host) {
+ // Client window was closed while there were messages
+ // being sent to it.
+ return;
+ }
+ client_host->DispatchOnInspectorFrontend(message);
+}
+
+void DevToolsManagerImpl::ClientHostClosing(DevToolsClientHost* client_host) {
+ DevToolsAgentHost* agent_host = GetDevToolsAgentHostFor(client_host);
+ if (!agent_host)
+ return;
+ DevToolsAgentHostImpl* agent_host_impl =
+ static_cast<DevToolsAgentHostImpl*>(agent_host);
+ UnbindClientHost(agent_host_impl, client_host);
+}
+
+void DevToolsManagerImpl::AgentHostClosing(DevToolsAgentHostImpl* agent_host) {
+ UnregisterDevToolsClientHostFor(agent_host);
+}
+
+void DevToolsManagerImpl::UnregisterDevToolsClientHostFor(
+ DevToolsAgentHostImpl* agent_host_impl) {
+ DevToolsClientHost* client_host = GetDevToolsClientHostFor(agent_host_impl);
+ if (!client_host)
+ return;
+ UnbindClientHost(agent_host_impl, client_host);
+ client_host->InspectedContentsClosing();
+}
+
+void DevToolsManagerImpl::BindClientHost(
+ DevToolsAgentHostImpl* agent_host,
+ DevToolsClientHost* client_host) {
+ DCHECK(agent_to_client_host_.find(agent_host) ==
+ agent_to_client_host_.end());
+ DCHECK(client_to_agent_host_.find(client_host) ==
+ client_to_agent_host_.end());
+
+ if (client_to_agent_host_.empty()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DevToolsNetLogObserver::Attach));
+ }
+ agent_to_client_host_[agent_host] = client_host;
+ client_to_agent_host_[client_host] = agent_host;
+ agent_host->set_close_listener(this);
+}
+
+void DevToolsManagerImpl::UnbindClientHost(DevToolsAgentHostImpl* agent_host,
+ DevToolsClientHost* client_host) {
+ DCHECK(agent_host);
+ scoped_refptr<DevToolsAgentHostImpl> protect(agent_host);
+ DCHECK(agent_to_client_host_.find(agent_host)->second ==
+ client_host);
+ DCHECK(client_to_agent_host_.find(client_host)->second.get() == agent_host);
+ agent_host->set_close_listener(NULL);
+
+ agent_to_client_host_.erase(agent_host);
+ client_to_agent_host_.erase(client_host);
+
+ if (client_to_agent_host_.empty()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DevToolsNetLogObserver::Detach));
+ }
+ // Lazy agent hosts can be deleted from within detach.
+ // Do not access agent_host below this line.
+ agent_host->Detach();
+}
+
+void DevToolsManagerImpl::CloseAllClientHosts() {
+ std::vector<DevToolsAgentHostImpl*> agents;
+ for (AgentToClientHostMap::iterator it =
+ agent_to_client_host_.begin();
+ it != agent_to_client_host_.end(); ++it) {
+ agents.push_back(it->first);
+ }
+ for (std::vector<DevToolsAgentHostImpl*>::iterator it = agents.begin();
+ it != agents.end(); ++it) {
+ UnregisterDevToolsClientHostFor(*it);
+ }
+}
+
+void DevToolsManagerImpl::AddAgentStateCallback(const Callback& callback) {
+ callbacks_.push_back(&callback);
+}
+
+void DevToolsManagerImpl::RemoveAgentStateCallback(const Callback& callback) {
+ CallbackContainer::iterator it =
+ std::find(callbacks_.begin(), callbacks_.end(), &callback);
+ DCHECK(it != callbacks_.end());
+ callbacks_.erase(it);
+}
+
+void DevToolsManagerImpl::NotifyObservers(DevToolsAgentHost* agent_host,
+ bool attached) {
+ CallbackContainer copy(callbacks_);
+ for (CallbackContainer::iterator it = copy.begin(); it != copy.end(); ++it)
+ (*it)->Run(agent_host, attached);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_manager_impl.h b/chromium/content/browser/devtools/devtools_manager_impl.h
new file mode 100644
index 00000000000..a14b2f1ca11
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_manager_impl.h
@@ -0,0 +1,104 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "content/browser/devtools/devtools_agent_host_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/devtools_manager.h"
+
+class GURL;
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+
+class RenderViewHost;
+
+// This class is a singleton that manages DevToolsClientHost instances and
+// routes messages between developer tools clients and agents.
+//
+// Methods below that accept inspected RenderViewHost as a parameter are
+// just convenience methods that call corresponding methods accepting
+// DevToolAgentHost.
+class CONTENT_EXPORT DevToolsManagerImpl
+ : public DevToolsAgentHostImpl::CloseListener,
+ public DevToolsManager {
+ public:
+ // Returns single instance of this class. The instance is destroyed on the
+ // browser main loop exit so this method MUST NOT be called after that point.
+ static DevToolsManagerImpl* GetInstance();
+
+ DevToolsManagerImpl();
+ virtual ~DevToolsManagerImpl();
+
+ void DispatchOnInspectorFrontend(DevToolsAgentHost* agent_host,
+ const std::string& message);
+
+ // DevToolsManager implementation
+ virtual bool DispatchOnInspectorBackend(DevToolsClientHost* from,
+ const std::string& message) OVERRIDE;
+ virtual void CloseAllClientHosts() OVERRIDE;
+ virtual DevToolsAgentHost* GetDevToolsAgentHostFor(
+ DevToolsClientHost* client_host) OVERRIDE;
+ virtual void RegisterDevToolsClientHostFor(
+ DevToolsAgentHost* agent_host,
+ DevToolsClientHost* client_host) OVERRIDE;
+ virtual void ClientHostClosing(DevToolsClientHost* host) OVERRIDE;
+ virtual void AddAgentStateCallback(const Callback& callback) OVERRIDE;
+ virtual void RemoveAgentStateCallback(const Callback& callback) OVERRIDE;
+
+ private:
+ friend class DevToolsAgentHostImpl;
+ friend class RenderViewDevToolsAgentHost;
+ friend struct DefaultSingletonTraits<DevToolsManagerImpl>;
+
+ // DevToolsAgentHost::CloseListener implementation.
+ virtual void AgentHostClosing(DevToolsAgentHostImpl* host) OVERRIDE;
+
+ void BindClientHost(DevToolsAgentHostImpl* agent_host,
+ DevToolsClientHost* client_host);
+ void UnbindClientHost(DevToolsAgentHostImpl* agent_host,
+ DevToolsClientHost* client_host);
+
+ DevToolsClientHost* GetDevToolsClientHostFor(
+ DevToolsAgentHostImpl* agent_host);
+
+ void UnregisterDevToolsClientHostFor(DevToolsAgentHostImpl* agent_host);
+
+ void NotifyObservers(DevToolsAgentHost* agent_host, bool attached);
+
+ // These two maps are for tracking dependencies between inspected contents and
+ // their DevToolsClientHosts. They are useful for routing devtools messages
+ // and allow us to have at most one devtools client host per contents.
+ //
+ // DevToolsManagerImpl starts listening to DevToolsClientHosts when they are
+ // put into these maps and removes them when they are closing.
+ typedef std::map<DevToolsAgentHostImpl*, DevToolsClientHost*>
+ AgentToClientHostMap;
+ AgentToClientHostMap agent_to_client_host_;
+
+ typedef std::map<DevToolsClientHost*, scoped_refptr<DevToolsAgentHostImpl> >
+ ClientToAgentHostMap;
+ ClientToAgentHostMap client_to_agent_host_;
+
+ typedef std::vector<const Callback*> CallbackContainer;
+ CallbackContainer callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsManagerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_
diff --git a/chromium/content/browser/devtools/devtools_manager_unittest.cc b/chromium/content/browser/devtools/devtools_manager_unittest.cc
new file mode 100644
index 00000000000..46c198cc05c
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_manager_unittest.cc
@@ -0,0 +1,290 @@
+// 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/browser/devtools/render_view_devtools_agent_host.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/devtools_external_agent_proxy.h"
+#include "content/public/browser/devtools_external_agent_proxy_delegate.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace content {
+namespace {
+
+class TestDevToolsClientHost : public DevToolsClientHost {
+ public:
+ TestDevToolsClientHost()
+ : last_sent_message(NULL),
+ closed_(false) {
+ }
+
+ virtual ~TestDevToolsClientHost() {
+ EXPECT_TRUE(closed_);
+ }
+
+ virtual void Close(DevToolsManager* manager) {
+ EXPECT_FALSE(closed_);
+ close_counter++;
+ manager->ClientHostClosing(this);
+ closed_ = true;
+ }
+ virtual void InspectedContentsClosing() OVERRIDE {
+ FAIL();
+ }
+
+ virtual void DispatchOnInspectorFrontend(
+ const std::string& message) OVERRIDE {
+ last_sent_message = &message;
+ }
+
+ virtual void ReplacedWithAnotherClient() OVERRIDE {
+ }
+
+ static void ResetCounters() {
+ close_counter = 0;
+ }
+
+ static int close_counter;
+
+ const std::string* last_sent_message;
+
+ private:
+ bool closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDevToolsClientHost);
+};
+
+int TestDevToolsClientHost::close_counter = 0;
+
+
+class TestWebContentsDelegate : public WebContentsDelegate {
+ public:
+ TestWebContentsDelegate() : renderer_unresponsive_received_(false) {}
+
+ // Notification that the contents is hung.
+ virtual void RendererUnresponsive(WebContents* source) OVERRIDE {
+ renderer_unresponsive_received_ = true;
+ }
+
+ bool renderer_unresponsive_received() const {
+ return renderer_unresponsive_received_;
+ }
+
+ private:
+ bool renderer_unresponsive_received_;
+};
+
+class DevToolsManagerTestBrowserClient : public TestContentBrowserClient {
+ public:
+ DevToolsManagerTestBrowserClient() {
+ }
+
+ virtual bool ShouldSwapProcessesForNavigation(
+ SiteInstance* site_instance,
+ const GURL& current_url,
+ const GURL& new_url) OVERRIDE {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DevToolsManagerTestBrowserClient);
+};
+
+} // namespace
+
+class DevToolsManagerTest : public RenderViewHostImplTestHarness {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ original_browser_client_ = SetBrowserClientForTesting(&browser_client_);
+
+ RenderViewHostImplTestHarness::SetUp();
+ TestDevToolsClientHost::ResetCounters();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ RenderViewHostImplTestHarness::TearDown();
+ SetBrowserClientForTesting(original_browser_client_);
+ }
+
+ private:
+ ContentBrowserClient* original_browser_client_;
+ DevToolsManagerTestBrowserClient browser_client_;
+};
+
+TEST_F(DevToolsManagerTest, OpenAndManuallyCloseDevToolsClientHost) {
+ DevToolsManager* manager = DevToolsManager::GetInstance();
+
+ scoped_refptr<DevToolsAgentHost> agent(
+ DevToolsAgentHost::GetOrCreateFor(rvh()));
+ EXPECT_FALSE(agent->IsAttached());
+
+ TestDevToolsClientHost client_host;
+ manager->RegisterDevToolsClientHostFor(agent.get(), &client_host);
+ // Test that the connection is established.
+ EXPECT_TRUE(agent->IsAttached());
+ EXPECT_EQ(agent, manager->GetDevToolsAgentHostFor(&client_host));
+ EXPECT_EQ(0, TestDevToolsClientHost::close_counter);
+
+ client_host.Close(manager);
+ EXPECT_EQ(1, TestDevToolsClientHost::close_counter);
+ EXPECT_FALSE(agent->IsAttached());
+}
+
+TEST_F(DevToolsManagerTest, ForwardMessageToClient) {
+ DevToolsManagerImpl* manager = DevToolsManagerImpl::GetInstance();
+
+ TestDevToolsClientHost client_host;
+ scoped_refptr<DevToolsAgentHost> agent_host(
+ DevToolsAgentHost::GetOrCreateFor(rvh()));
+ manager->RegisterDevToolsClientHostFor(agent_host.get(), &client_host);
+ EXPECT_EQ(0, TestDevToolsClientHost::close_counter);
+
+ std::string m = "test message";
+ agent_host = DevToolsAgentHost::GetOrCreateFor(rvh());
+ manager->DispatchOnInspectorFrontend(agent_host.get(), m);
+ EXPECT_TRUE(&m == client_host.last_sent_message);
+
+ client_host.Close(manager);
+ EXPECT_EQ(1, TestDevToolsClientHost::close_counter);
+}
+
+TEST_F(DevToolsManagerTest, NoUnresponsiveDialogInInspectedContents) {
+ TestRenderViewHost* inspected_rvh = test_rvh();
+ inspected_rvh->set_render_view_created(true);
+ EXPECT_FALSE(contents()->GetDelegate());
+ TestWebContentsDelegate delegate;
+ contents()->SetDelegate(&delegate);
+
+ TestDevToolsClientHost client_host;
+ scoped_refptr<DevToolsAgentHost> agent_host(
+ DevToolsAgentHost::GetOrCreateFor(inspected_rvh));
+ DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
+ agent_host.get(), &client_host);
+
+ // Start with a short timeout.
+ inspected_rvh->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10));
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_FALSE(delegate.renderer_unresponsive_received());
+
+ // Now close devtools and check that the notification is delivered.
+ client_host.Close(DevToolsManager::GetInstance());
+ // Start with a short timeout.
+ inspected_rvh->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10));
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(delegate.renderer_unresponsive_received());
+
+ contents()->SetDelegate(NULL);
+}
+
+TEST_F(DevToolsManagerTest, ReattachOnCancelPendingNavigation) {
+ contents()->transition_cross_site = true;
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(rvh(), 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+
+ TestDevToolsClientHost client_host;
+ DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
+ devtools_manager->RegisterDevToolsClientHostFor(
+ DevToolsAgentHost::GetOrCreateFor(rvh()).get(), &client_host);
+
+ // Navigate to new site which should get a new RenderViewHost.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_EQ(devtools_manager->GetDevToolsAgentHostFor(&client_host),
+ DevToolsAgentHost::GetOrCreateFor(pending_rvh()));
+
+ // Interrupt pending navigation and navigate back to the original site.
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(rvh(), 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(devtools_manager->GetDevToolsAgentHostFor(&client_host),
+ DevToolsAgentHost::GetOrCreateFor(rvh()));
+ client_host.Close(DevToolsManager::GetInstance());
+}
+
+class TestExternalAgentDelegate: public DevToolsExternalAgentProxyDelegate {
+ std::map<std::string,int> event_counter_;
+
+ void recordEvent(const std::string& name) {
+ if (event_counter_.find(name) == event_counter_.end())
+ event_counter_[name] = 0;
+ event_counter_[name] = event_counter_[name] + 1;
+ }
+
+ void expectEvent(int count, const std::string& name) {
+ EXPECT_EQ(count, event_counter_[name]);
+ }
+
+ virtual void Attach() OVERRIDE {
+ recordEvent("Attach");
+ };
+
+ virtual void Detach() OVERRIDE {
+ recordEvent("Detach");
+ };
+
+ virtual void SendMessageToBackend(const std::string& message) OVERRIDE {
+ recordEvent(std::string("SendMessageToBackend.") + message);
+ };
+
+ public :
+ virtual ~TestExternalAgentDelegate() {
+ expectEvent(1, "Attach");
+ expectEvent(1, "Detach");
+ expectEvent(0, "SendMessageToBackend.message0");
+ expectEvent(1, "SendMessageToBackend.message1");
+ expectEvent(2, "SendMessageToBackend.message2");
+ }
+};
+
+TEST_F(DevToolsManagerTest, TestExternalProxy) {
+ TestExternalAgentDelegate delegate;
+
+ scoped_ptr<DevToolsExternalAgentProxy> proxy(
+ DevToolsExternalAgentProxy::Create(&delegate));
+
+ scoped_refptr<DevToolsAgentHost> agent_host = proxy->GetAgentHost();
+ EXPECT_EQ(agent_host, DevToolsAgentHost::GetForId(agent_host->GetId()));
+
+ DevToolsManager* manager = DevToolsManager::GetInstance();
+
+ TestDevToolsClientHost client_host;
+ manager->RegisterDevToolsClientHostFor(agent_host.get(), &client_host);
+
+ manager->DispatchOnInspectorBackend(&client_host, "message1");
+ manager->DispatchOnInspectorBackend(&client_host, "message2");
+ manager->DispatchOnInspectorBackend(&client_host, "message2");
+
+ client_host.Close(manager);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_netlog_observer.cc b/chromium/content/browser/devtools/devtools_netlog_observer.cc
new file mode 100644
index 00000000000..21a835b9e99
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_netlog_observer.cc
@@ -0,0 +1,328 @@
+// 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 "content/browser/devtools/devtools_netlog_observer.h"
+
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/resource_response.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_netlog_params.h"
+
+namespace content {
+const size_t kMaxNumEntries = 1000;
+
+DevToolsNetLogObserver* DevToolsNetLogObserver::instance_ = NULL;
+
+DevToolsNetLogObserver::DevToolsNetLogObserver() {
+}
+
+DevToolsNetLogObserver::~DevToolsNetLogObserver() {
+}
+
+DevToolsNetLogObserver::ResourceInfo*
+DevToolsNetLogObserver::GetResourceInfo(uint32 id) {
+ RequestToInfoMap::iterator it = request_to_info_.find(id);
+ if (it != request_to_info_.end())
+ return it->second.get();
+ return NULL;
+}
+
+void DevToolsNetLogObserver::OnAddEntry(const net::NetLog::Entry& entry) {
+ // The events that the Observer is interested in only occur on the IO thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO))
+ return;
+
+ if (entry.source().type == net::NetLog::SOURCE_URL_REQUEST)
+ OnAddURLRequestEntry(entry);
+ else if (entry.source().type == net::NetLog::SOURCE_HTTP_STREAM_JOB)
+ OnAddHTTPStreamJobEntry(entry);
+ else if (entry.source().type == net::NetLog::SOURCE_SOCKET)
+ OnAddSocketEntry(entry);
+}
+
+void DevToolsNetLogObserver::OnAddURLRequestEntry(
+ const net::NetLog::Entry& entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ bool is_begin = entry.phase() == net::NetLog::PHASE_BEGIN;
+ bool is_end = entry.phase() == net::NetLog::PHASE_END;
+
+ if (entry.type() == net::NetLog::TYPE_URL_REQUEST_START_JOB) {
+ if (is_begin) {
+ int load_flags;
+ scoped_ptr<base::Value> event_param(entry.ParametersToValue());
+ if (!net::StartEventLoadFlagsFromEventParams(event_param.get(),
+ &load_flags)) {
+ return;
+ }
+
+ if (!(load_flags & net::LOAD_REPORT_RAW_HEADERS))
+ return;
+
+ if (request_to_info_.size() > kMaxNumEntries) {
+ LOG(WARNING) << "The raw headers observer url request count has grown "
+ "larger than expected, resetting";
+ request_to_info_.clear();
+ }
+
+ request_to_info_[entry.source().id] = new ResourceInfo();
+
+ if (request_to_encoded_data_length_.size() > kMaxNumEntries) {
+ LOG(WARNING) << "The encoded data length observer url request count "
+ "has grown larger than expected, resetting";
+ request_to_encoded_data_length_.clear();
+ }
+
+ request_to_encoded_data_length_[entry.source().id] = 0;
+ }
+ return;
+ } else if (entry.type() == net::NetLog::TYPE_REQUEST_ALIVE) {
+ // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
+ if (is_end) {
+ request_to_info_.erase(entry.source().id);
+ request_to_encoded_data_length_.erase(entry.source().id);
+ }
+ return;
+ }
+
+ ResourceInfo* info = GetResourceInfo(entry.source().id);
+ if (!info)
+ return;
+
+ switch (entry.type()) {
+ case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS: {
+ scoped_ptr<base::Value> event_params(entry.ParametersToValue());
+ std::string request_line;
+ net::HttpRequestHeaders request_headers;
+
+ if (!net::HttpRequestHeaders::FromNetLogParam(event_params.get(),
+ &request_headers,
+ &request_line)) {
+ NOTREACHED();
+ }
+
+ // We need to clear headers in case the same url_request is reused for
+ // several http requests (e.g. see http://crbug.com/80157).
+ info->request_headers.clear();
+
+ for (net::HttpRequestHeaders::Iterator it(request_headers);
+ it.GetNext();) {
+ info->request_headers.push_back(std::make_pair(it.name(), it.value()));
+ }
+ info->request_headers_text = request_line + request_headers.ToString();
+ break;
+ }
+ case net::NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS: {
+ scoped_ptr<base::Value> event_params(entry.ParametersToValue());
+ net::SpdyHeaderBlock request_headers;
+
+ if (!net::SpdyHeaderBlockFromNetLogParam(event_params.get(),
+ &request_headers)) {
+ NOTREACHED();
+ }
+
+ // We need to clear headers in case the same url_request is reused for
+ // several http requests (e.g. see http://crbug.com/80157).
+ info->request_headers.clear();
+
+ for (net::SpdyHeaderBlock::const_iterator it = request_headers.begin();
+ it != request_headers.end(); ++it) {
+ info->request_headers.push_back(std::make_pair(it->first, it->second));
+ }
+ info->request_headers_text = "";
+ break;
+ }
+ case net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS: {
+ scoped_ptr<base::Value> event_params(entry.ParametersToValue());
+
+ scoped_refptr<net::HttpResponseHeaders> response_headers;
+
+ if (!net::HttpResponseHeaders::FromNetLogParam(event_params.get(),
+ &response_headers)) {
+ NOTREACHED();
+ }
+
+ info->http_status_code = response_headers->response_code();
+ info->http_status_text = response_headers->GetStatusText();
+ std::string name, value;
+
+ // We need to clear headers in case the same url_request is reused for
+ // several http requests (e.g. see http://crbug.com/80157).
+ info->response_headers.clear();
+
+ for (void* it = NULL;
+ response_headers->EnumerateHeaderLines(&it, &name, &value); ) {
+ info->response_headers.push_back(std::make_pair(name, value));
+ }
+ info->response_headers_text =
+ net::HttpUtil::ConvertHeadersBackToHTTPResponse(
+ response_headers->raw_headers());
+ break;
+ }
+ case net::NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB: {
+ scoped_ptr<base::Value> event_params(entry.ParametersToValue());
+ net::NetLog::Source http_stream_job_source;
+ if (!net::NetLog::Source::FromEventParameters(event_params.get(),
+ &http_stream_job_source)) {
+ NOTREACHED();
+ break;
+ }
+
+ uint32 http_stream_job_id = http_stream_job_source.id;
+ HTTPStreamJobToSocketMap::iterator it =
+ http_stream_job_to_socket_.find(http_stream_job_id);
+ if (it == http_stream_job_to_socket_.end())
+ return;
+ uint32 socket_id = it->second;
+
+ if (socket_to_request_.size() > kMaxNumEntries) {
+ LOG(WARNING) << "The url request observer socket count has grown "
+ "larger than expected, resetting";
+ socket_to_request_.clear();
+ }
+
+ socket_to_request_[socket_id] = entry.source().id;
+ http_stream_job_to_socket_.erase(http_stream_job_id);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void DevToolsNetLogObserver::OnAddHTTPStreamJobEntry(
+ const net::NetLog::Entry& entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entry.type() == net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET) {
+ scoped_ptr<base::Value> event_params(entry.ParametersToValue());
+ net::NetLog::Source socket_source;
+ if (!net::NetLog::Source::FromEventParameters(event_params.get(),
+ &socket_source)) {
+ NOTREACHED();
+ return;
+ }
+
+ // Prevents us from passively growing the memory unbounded in
+ // case something went wrong. Should not happen.
+ if (http_stream_job_to_socket_.size() > kMaxNumEntries) {
+ LOG(WARNING) << "The load timing observer http stream job count "
+ "has grown larger than expected, resetting";
+ http_stream_job_to_socket_.clear();
+ }
+ http_stream_job_to_socket_[entry.source().id] = socket_source.id;
+ }
+}
+
+void DevToolsNetLogObserver::OnAddSocketEntry(
+ const net::NetLog::Entry& entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ bool is_end = entry.phase() == net::NetLog::PHASE_END;
+
+ SocketToRequestMap::iterator it =
+ socket_to_request_.find(entry.source().id);
+ if (it == socket_to_request_.end())
+ return;
+ uint32 request_id = it->second;
+
+ if (entry.type() == net::NetLog::TYPE_SOCKET_IN_USE) {
+ if (is_end)
+ socket_to_request_.erase(entry.source().id);
+ return;
+ }
+
+ RequestToEncodedDataLengthMap::iterator encoded_data_length_it =
+ request_to_encoded_data_length_.find(request_id);
+ if (encoded_data_length_it == request_to_encoded_data_length_.end())
+ return;
+
+ if (net::NetLog::TYPE_SOCKET_BYTES_RECEIVED == entry.type()) {
+ int byte_count = 0;
+ scoped_ptr<base::Value> value(entry.ParametersToValue());
+ if (!value->IsType(base::Value::TYPE_DICTIONARY))
+ return;
+
+ base::DictionaryValue* dValue =
+ static_cast<base::DictionaryValue*>(value.get());
+ if (!dValue->GetInteger("byte_count", &byte_count))
+ return;
+
+ encoded_data_length_it->second += byte_count;
+ }
+}
+
+void DevToolsNetLogObserver::Attach() {
+ DCHECK(!instance_);
+ net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
+ if (net_log) {
+ instance_ = new DevToolsNetLogObserver();
+ net_log->AddThreadSafeObserver(instance_, net::NetLog::LOG_ALL_BUT_BYTES);
+ }
+}
+
+void DevToolsNetLogObserver::Detach() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (instance_) {
+ // Safest not to do this in the destructor to maintain thread safety across
+ // refactorings.
+ instance_->net_log()->RemoveThreadSafeObserver(instance_);
+ delete instance_;
+ instance_ = NULL;
+ }
+}
+
+DevToolsNetLogObserver* DevToolsNetLogObserver::GetInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ return instance_;
+}
+
+// static
+void DevToolsNetLogObserver::PopulateResponseInfo(
+ net::URLRequest* request,
+ ResourceResponse* response) {
+ if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS))
+ return;
+
+ uint32 source_id = request->net_log().source().id;
+ DevToolsNetLogObserver* dev_tools_net_log_observer =
+ DevToolsNetLogObserver::GetInstance();
+ if (dev_tools_net_log_observer == NULL)
+ return;
+ response->head.devtools_info =
+ dev_tools_net_log_observer->GetResourceInfo(source_id);
+}
+
+// static
+int DevToolsNetLogObserver::GetAndResetEncodedDataLength(
+ net::URLRequest* request) {
+ if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS))
+ return -1;
+
+ uint32 source_id = request->net_log().source().id;
+ DevToolsNetLogObserver* dev_tools_net_log_observer =
+ DevToolsNetLogObserver::GetInstance();
+ if (dev_tools_net_log_observer == NULL)
+ return -1;
+
+ RequestToEncodedDataLengthMap::iterator it =
+ dev_tools_net_log_observer->request_to_encoded_data_length_.find(
+ source_id);
+ if (it == dev_tools_net_log_observer->request_to_encoded_data_length_.end())
+ return -1;
+ int encoded_data_length = it->second;
+ it->second = 0;
+ return encoded_data_length;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_netlog_observer.h b/chromium/content/browser/devtools/devtools_netlog_observer.h
new file mode 100644
index 00000000000..d1d478c3791
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_netlog_observer.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_log.h"
+#include "webkit/common/resource_devtools_info.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace content {
+struct ResourceResponse;
+
+// DevToolsNetLogObserver watches the NetLog event stream and collects the
+// stuff that may be of interest to DevTools. Currently, this only includes
+// actual HTTP/SPDY headers sent and received over the network.
+//
+// As DevToolsNetLogObserver shares live data with objects that live on the
+// IO Thread, it must also reside on the IO Thread. Only OnAddEntry can be
+// called from other threads.
+class DevToolsNetLogObserver : public net::NetLog::ThreadSafeObserver {
+ typedef webkit_glue::ResourceDevToolsInfo ResourceInfo;
+
+ public:
+ // net::NetLog::ThreadSafeObserver implementation:
+ virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE;
+
+ void OnAddURLRequestEntry(const net::NetLog::Entry& entry);
+ void OnAddHTTPStreamJobEntry(const net::NetLog::Entry& entry);
+ void OnAddSocketEntry(const net::NetLog::Entry& entry);
+
+ static void Attach();
+ static void Detach();
+
+ // Must be called on the IO thread. May return NULL if no observers
+ // are active.
+ static DevToolsNetLogObserver* GetInstance();
+ static void PopulateResponseInfo(net::URLRequest*,
+ ResourceResponse*);
+ static int GetAndResetEncodedDataLength(net::URLRequest* request);
+
+ private:
+ static DevToolsNetLogObserver* instance_;
+
+ DevToolsNetLogObserver();
+ virtual ~DevToolsNetLogObserver();
+
+ ResourceInfo* GetResourceInfo(uint32 id);
+
+ typedef base::hash_map<uint32, scoped_refptr<ResourceInfo> > RequestToInfoMap;
+ typedef base::hash_map<uint32, int> RequestToEncodedDataLengthMap;
+ typedef base::hash_map<uint32, uint32> HTTPStreamJobToSocketMap;
+ typedef base::hash_map<uint32, uint32> SocketToRequestMap;
+ RequestToInfoMap request_to_info_;
+ RequestToEncodedDataLengthMap request_to_encoded_data_length_;
+ HTTPStreamJobToSocketMap http_stream_job_to_socket_;
+ SocketToRequestMap socket_to_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsNetLogObserver);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_
diff --git a/chromium/content/browser/devtools/devtools_protocol.cc b/chromium/content/browser/devtools/devtools_protocol.cc
new file mode 100644
index 00000000000..948ed6be8a4
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_protocol.cc
@@ -0,0 +1,284 @@
+// Copyright (c) 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 "content/browser/devtools/devtools_protocol.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/stringprintf.h"
+
+namespace content {
+
+namespace {
+
+const char kIdParam[] = "id";
+const char kMethodParam[] = "method";
+const char kParamsParam[] = "params";
+const char kResultParam[] = "result";
+const char kErrorParam[] = "error";
+const char kErrorCodeParam[] = "code";
+const char kErrorMessageParam[] = "message";
+const int kNoId = -1;
+
+// JSON RPC 2.0 spec: http://www.jsonrpc.org/specification#error_object
+enum Error {
+ kErrorParseError = -32700,
+ kErrorInvalidRequest = -32600,
+ kErrorNoSuchMethod = -32601,
+ kErrorInvalidParams = -32602,
+ kErrorInternalError = -32603
+};
+
+} // namespace
+
+using base::Value;
+
+DevToolsProtocol::Message::~Message() {
+}
+
+DevToolsProtocol::Message::Message(const std::string& method,
+ base::DictionaryValue* params)
+ : method_(method),
+ params_(params) {
+ size_t pos = method.find(".");
+ if (pos != std::string::npos && pos > 0)
+ domain_ = method.substr(0, pos);
+}
+
+DevToolsProtocol::Command::~Command() {
+}
+
+std::string DevToolsProtocol::Command::Serialize() {
+ base::DictionaryValue command;
+ command.SetInteger(kIdParam, id_);
+ command.SetString(kMethodParam, method_);
+ if (params_)
+ command.Set(kParamsParam, params_->DeepCopy());
+
+ std::string json_command;
+ base::JSONWriter::Write(&command, &json_command);
+ return json_command;
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Command::SuccessResponse(base::DictionaryValue* result) {
+ return new DevToolsProtocol::Response(id_, result);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Command::InternalErrorResponse(const std::string& message) {
+ return new DevToolsProtocol::Response(id_, kErrorInternalError, message);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Command::InvalidParamResponse(const std::string& param) {
+ std::string message =
+ base::StringPrintf("Missing or invalid '%s' parameter", param.c_str());
+ return new DevToolsProtocol::Response(id_, kErrorInvalidParams, message);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Command::NoSuchMethodErrorResponse() {
+ return new Response(id_, kErrorNoSuchMethod, "No such method");
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Command::AsyncResponsePromise() {
+ scoped_refptr<DevToolsProtocol::Response> promise =
+ new DevToolsProtocol::Response(0, NULL);
+ promise->is_async_promise_ = true;
+ return promise;
+}
+
+DevToolsProtocol::Command::Command(int id,
+ const std::string& method,
+ base::DictionaryValue* params)
+ : Message(method, params),
+ id_(id) {
+}
+
+DevToolsProtocol::Response::~Response() {
+}
+
+std::string DevToolsProtocol::Response::Serialize() {
+ base::DictionaryValue response;
+
+ if (id_ != kNoId)
+ response.SetInteger(kIdParam, id_);
+
+ if (error_code_) {
+ base::DictionaryValue* error_object = new base::DictionaryValue();
+ response.Set(kErrorParam, error_object);
+ error_object->SetInteger(kErrorCodeParam, error_code_);
+ if (!error_message_.empty())
+ error_object->SetString(kErrorMessageParam, error_message_);
+ } else if (result_) {
+ response.Set(kResultParam, result_->DeepCopy());
+ }
+
+ std::string json_response;
+ base::JSONWriter::Write(&response, &json_response);
+ return json_response;
+}
+
+DevToolsProtocol::Response::Response(int id, base::DictionaryValue* result)
+ : id_(id),
+ result_(result),
+ error_code_(0),
+ is_async_promise_(false) {
+}
+
+DevToolsProtocol::Response::Response(int id,
+ int error_code,
+ const std::string& error_message)
+ : id_(id),
+ error_code_(error_code),
+ error_message_(error_message),
+ is_async_promise_(false) {
+}
+
+DevToolsProtocol::Notification::Notification(const std::string& method,
+ base::DictionaryValue* params)
+ : Message(method, params) {
+}
+
+DevToolsProtocol::Notification::~Notification() {
+}
+
+std::string DevToolsProtocol::Notification::Serialize() {
+ base::DictionaryValue notification;
+ notification.SetString(kMethodParam, method_);
+ if (params_)
+ notification.Set(kParamsParam, params_->DeepCopy());
+
+ std::string json_notification;
+ base::JSONWriter::Write(&notification, &json_notification);
+ return json_notification;
+}
+
+DevToolsProtocol::Handler::~Handler() {
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsProtocol::Handler::HandleCommand(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ CommandHandlers::iterator it = command_handlers_.find(command->method());
+ if (it == command_handlers_.end())
+ return NULL;
+ return (it->second).Run(command);
+}
+
+void DevToolsProtocol::Handler::SetNotifier(const Notifier& notifier) {
+ notifier_ = notifier;
+}
+
+DevToolsProtocol::Handler::Handler() {
+}
+
+void DevToolsProtocol::Handler::RegisterCommandHandler(
+ const std::string& command,
+ const CommandHandler& handler) {
+ command_handlers_[command] = handler;
+}
+
+void DevToolsProtocol::Handler::SendNotification(
+ const std::string& method,
+ base::DictionaryValue* params) {
+ scoped_refptr<DevToolsProtocol::Notification> notification =
+ new DevToolsProtocol::Notification(method, params);
+ SendRawMessage(notification->Serialize());
+}
+
+void DevToolsProtocol::Handler::SendAsyncResponse(
+ scoped_refptr<DevToolsProtocol::Response> response) {
+ SendRawMessage(response->Serialize());
+}
+
+void DevToolsProtocol::Handler::SendRawMessage(const std::string& message) {
+ if (!notifier_.is_null())
+ notifier_.Run(message);
+}
+
+static bool ParseMethod(base::DictionaryValue* command,
+ std::string* method) {
+ if (!command->GetString(kMethodParam, method))
+ return false;
+ size_t pos = method->find(".");
+ if (pos == std::string::npos || pos == 0)
+ return false;
+ return true;
+}
+
+// static
+scoped_refptr<DevToolsProtocol::Command> DevToolsProtocol::ParseCommand(
+ const std::string& json,
+ std::string* error_response) {
+ scoped_ptr<base::DictionaryValue> command_dict(
+ ParseMessage(json, error_response));
+ if (!command_dict)
+ return NULL;
+
+ int id;
+ std::string method;
+ bool ok = command_dict->GetInteger(kIdParam, &id) && id >= 0;
+ ok = ok && ParseMethod(command_dict.get(), &method);
+ if (!ok) {
+ scoped_refptr<Response> response =
+ new Response(kNoId, kErrorInvalidRequest, "No such method");
+ *error_response = response->Serialize();
+ return NULL;
+ }
+
+ base::DictionaryValue* params = NULL;
+ command_dict->GetDictionary(kParamsParam, &params);
+ return new Command(id, method, params ? params->DeepCopy() : NULL);
+}
+
+// static
+scoped_refptr<DevToolsProtocol::Notification>
+DevToolsProtocol::ParseNotification(const std::string& json) {
+ scoped_ptr<base::DictionaryValue> dict(ParseMessage(json, NULL));
+ if (!dict)
+ return NULL;
+
+ std::string method;
+ bool ok = ParseMethod(dict.get(), &method);
+ if (!ok)
+ return NULL;
+
+ base::DictionaryValue* params = NULL;
+ dict->GetDictionary(kParamsParam, &params);
+ return new Notification(method, params ? params->DeepCopy() : NULL);
+}
+
+//static
+scoped_refptr<DevToolsProtocol::Notification>
+DevToolsProtocol::CreateNotification(
+ const std::string& method,
+ base::DictionaryValue* params) {
+ return new Notification(method, params);
+}
+
+// static
+base::DictionaryValue* DevToolsProtocol::ParseMessage(
+ const std::string& json,
+ std::string* error_response) {
+ int parse_error_code;
+ std::string error_message;
+ scoped_ptr<Value> message(
+ base::JSONReader::ReadAndReturnError(
+ json, 0, &parse_error_code, &error_message));
+
+ if (!message || !message->IsType(Value::TYPE_DICTIONARY)) {
+ scoped_refptr<Response> response =
+ new Response(0, kErrorParseError, error_message);
+ if (error_response)
+ *error_response = response->Serialize();
+ return NULL;
+ }
+
+ return static_cast<base::DictionaryValue*>(message.release());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_protocol.h b/chromium/content/browser/devtools/devtools_protocol.h
new file mode 100644
index 00000000000..2f7ce0bfecb
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_protocol.h
@@ -0,0 +1,178 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+
+namespace content {
+
+// Utility classes for processing DevTools remote debugging messages.
+// https://developers.google.com/chrome-developer-tools/docs/debugger-protocol
+class DevToolsProtocol {
+ public:
+ typedef base::Callback<void(const std::string& message)> Notifier;
+
+ class Response;
+
+ class Message : public base::RefCountedThreadSafe<Message> {
+ public:
+ std::string domain() { return domain_; }
+ std::string method() { return method_; }
+ base::DictionaryValue* params() { return params_.get(); }
+ virtual std::string Serialize() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<Message>;
+ virtual ~Message();
+ Message(const std::string& method,
+ base::DictionaryValue* params);
+
+ std::string domain_;
+ std::string method_;
+ scoped_ptr<base::DictionaryValue> params_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Message);
+ };
+
+ class Command : public Message {
+ public:
+ int id() { return id_; }
+
+ virtual std::string Serialize() OVERRIDE;
+
+ // Creates success response. Takes ownership of |result|.
+ scoped_refptr<Response> SuccessResponse(base::DictionaryValue* result);
+
+ // Creates error response.
+ scoped_refptr<Response> InternalErrorResponse(const std::string& message);
+
+ // Creates error response.
+ scoped_refptr<Response> InvalidParamResponse(const std::string& param);
+
+ // Creates error response.
+ scoped_refptr<Response> NoSuchMethodErrorResponse();
+
+ // Creates async response promise.
+ scoped_refptr<Response> AsyncResponsePromise();
+
+ protected:
+ virtual ~Command();
+
+ private:
+ friend class DevToolsProtocol;
+ Command(int id, const std::string& method,
+ base::DictionaryValue* params);
+
+ int id_;
+
+ DISALLOW_COPY_AND_ASSIGN(Command);
+ };
+
+ class Response : public base::RefCountedThreadSafe<Response> {
+ public:
+ std::string Serialize();
+
+ bool is_async_promise() { return is_async_promise_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Response>;
+ friend class Command;
+ friend class DevToolsProtocol;
+ virtual ~Response();
+
+ Response(int id, base::DictionaryValue* result);
+ Response(int id, int error_code, const std::string& error_message);
+
+ int id_;
+ scoped_ptr<base::DictionaryValue> result_;
+ int error_code_;
+ std::string error_message_;
+ bool is_async_promise_;
+
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+
+ class Notification : public Message {
+ public:
+
+ virtual std::string Serialize() OVERRIDE;
+
+ private:
+ friend class DevToolsProtocol;
+ virtual ~Notification();
+
+ // Takes ownership of |params|.
+ Notification(const std::string& method,
+ base::DictionaryValue* params);
+
+ DISALLOW_COPY_AND_ASSIGN(Notification);
+ };
+
+ class Handler {
+ public:
+ typedef base::Callback<scoped_refptr<DevToolsProtocol::Response>(
+ scoped_refptr<DevToolsProtocol::Command> command)> CommandHandler;
+
+ virtual ~Handler();
+
+ virtual scoped_refptr<DevToolsProtocol::Response> HandleCommand(
+ scoped_refptr<DevToolsProtocol::Command> command);
+
+ void SetNotifier(const Notifier& notifier);
+
+ protected:
+ Handler();
+
+ void RegisterCommandHandler(const std::string& command,
+ const CommandHandler& handler);
+
+ // Sends notification to the client. Takes ownership of |params|.
+ void SendNotification(const std::string& method,
+ base::DictionaryValue* params);
+
+ void SendAsyncResponse(scoped_refptr<DevToolsProtocol::Response> response);
+
+ // Sends message to client, the caller is presumed to properly
+ // format the message.
+ void SendRawMessage(const std::string& message);
+
+ private:
+ typedef std::map<std::string, CommandHandler> CommandHandlers;
+
+ Notifier notifier_;
+ CommandHandlers command_handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Handler);
+ };
+
+ static scoped_refptr<Command> ParseCommand(const std::string& json,
+ std::string* error_response);
+
+ static scoped_refptr<Notification> ParseNotification(
+ const std::string& json);
+
+ static scoped_refptr<Notification> CreateNotification(
+ const std::string& method, base::DictionaryValue* params);
+
+ private:
+ static base::DictionaryValue* ParseMessage(const std::string& json,
+ std::string* error_response);
+
+ DevToolsProtocol() {}
+ ~DevToolsProtocol() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_
diff --git a/chromium/content/browser/devtools/devtools_protocol_constants.cc b/chromium/content/browser/devtools/devtools_protocol_constants.cc
new file mode 100644
index 00000000000..7fbec4b665b
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_protocol_constants.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 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 "content/browser/devtools/devtools_protocol_constants.h"
+
+namespace content {
+namespace devtools {
+
+namespace Inspector {
+
+namespace detached {
+ const char kName[] = "Inspector.detached";
+ const char kParamReason[] = "reason";
+} // detached
+
+namespace targetCrashed {
+ const char kName[] = "Inspector.targetCrashed";
+} // targetCrashed
+
+} // Inspector
+
+namespace DOM {
+
+namespace setFileInputFiles {
+ const char kName[] = "DOM.setFileInputFiles";
+ const char kParamFiles[] = "files";
+} // setFileInputFiles
+
+} // DOM
+
+namespace Page {
+
+namespace handleJavaScriptDialog {
+ const char kName[] = "Page.handleJavaScriptDialog";
+ const char kParamAccept[] = "accept";
+ const char kParamPromptText[] = "promptText";
+} // handleJavaScriptDialog
+
+namespace navigate {
+ const char kName[] = "Page.navigate";
+ const char kParamUrl[] = "url";
+} // navigate
+
+namespace captureScreenshot {
+ const char kName[] = "Page.captureScreenshot";
+ const char kParamFormat[] = "format";
+ const char kParamQuality[] = "quality";
+ const char kParamScale[] = "scale";
+ const char kResponseData[] = "data";
+} // captureScreenshot
+
+namespace startScreencast {
+ const char kName[] = "Page.startScreencast";
+ const char kParamFormat[] = "format";
+ const char kParamQuality[] = "quality";
+ const char kParamScale[] = "scale";
+} // startScreencast
+
+namespace stopScreencast {
+ const char kName[] = "Page.stopScreencast";
+} // stopScreencast
+
+namespace screencastFrame {
+ const char kName[] = "Page.screencastFrame";
+ const char kResponseData[] = "data";
+} // screencastFrame
+
+} // Page
+
+namespace Worker {
+
+namespace disconnectedFromWorker {
+ const char kName[] = "Worker.disconnectedFromWorker";
+} // disconnectedFromWorker
+
+} // Worker
+
+namespace Tracing {
+ const char kName[] = "Tracing";
+
+namespace start {
+ const char kName[] = "Tracing.start";
+ const char kCategories[] = "categories";
+ const char kTraceOptions[] = "trace-options";
+} // start
+
+namespace end {
+ const char kName[] = "Tracing.end";
+}
+
+namespace tracingComplete {
+ const char kName[] = "Tracing.tracingComplete";
+}
+
+namespace dataCollected {
+ const char kName[] = "Tracing.dataCollected";
+ const char kValue[] = "value";
+}
+
+} // Tracing
+
+} // devtools
+} // content
+
diff --git a/chromium/content/browser/devtools/devtools_protocol_constants.h b/chromium/content/browser/devtools/devtools_protocol_constants.h
new file mode 100644
index 00000000000..fdf182a3dbb
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_protocol_constants.h
@@ -0,0 +1,111 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTSH_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTSH_
+
+// The constants in this file should be used instead manually constructing
+// strings passed to and from DevTools protocol.
+//
+// There is a plan to generate this file from inspector.json automatically.
+// Until then please feel free to add the constants here as needed.
+
+namespace content {
+namespace devtools {
+
+namespace Inspector {
+
+namespace detached {
+ extern const char kName[];
+ extern const char kParamReason[];
+} // detached
+
+namespace targetCrashed {
+ extern const char kName[];
+} // targetCrashed
+
+} // Inspector
+
+namespace DOM {
+namespace setFileInputFiles {
+ extern const char kName[];
+ extern const char kParamFiles[];
+} // setFileInputFiles
+} // DOM
+
+namespace Page {
+
+namespace handleJavaScriptDialog {
+ extern const char kName[];
+ extern const char kParamAccept[];
+ extern const char kParamPromptText[];
+} // handleJavaScriptDialog
+
+namespace navigate {
+ extern const char kName[];
+ extern const char kParamUrl[];
+} // navigate
+
+namespace captureScreenshot {
+ extern const char kName[];
+ extern const char kParamFormat[];
+ extern const char kParamQuality[];
+ extern const char kParamScale[];
+ extern const char kResponseData[];
+} // captureScreenshot
+
+namespace startScreencast {
+ extern const char kName[];
+ extern const char kParamFormat[];
+ extern const char kParamQuality[];
+ extern const char kParamScale[];
+} // startScreencast
+
+namespace stopScreencast {
+ extern const char kName[];
+} // stopScreencast
+
+namespace screencastFrame {
+ extern const char kName[];
+ extern const char kResponseData[];
+} // screencastFrame
+
+} // Page
+
+namespace Tracing {
+ extern const char kName[];
+
+namespace start {
+ extern const char kName[];
+ extern const char kCategories[];
+ extern const char kTraceOptions[];
+} // start
+
+namespace end {
+ extern const char kName[];
+}
+
+namespace tracingComplete {
+ extern const char kName[];
+}
+
+namespace dataCollected {
+ extern const char kName[];
+ extern const char kValue[];
+}
+} // Tracing
+
+
+namespace Worker {
+
+namespace disconnectedFromWorker {
+ extern const char kName[];
+} // disconnectedFromWorker
+
+} // Worker
+
+} // devtools
+} // content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTSH_
diff --git a/chromium/content/browser/devtools/devtools_resources.gyp b/chromium/content/browser/devtools/devtools_resources.gyp
new file mode 100644
index 00000000000..19b114c9dfa
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_resources.gyp
@@ -0,0 +1,49 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'devtools_resources',
+ 'type': 'none',
+ 'dependencies': [
+ '../../../third_party/WebKit/Source/devtools/devtools.gyp:generate_devtools_grd',
+ ],
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/webkit',
+ },
+ 'actions': [
+ {
+ 'action_name': 'devtools_resources',
+ # This can't use build/grit_action.gypi because the grd file
+ # is generated at build time, so the trick of using grit_info to get
+ # the real inputs/outputs at GYP time isn't possible.
+ 'variables': {
+ 'grit_cmd': ['python', '../../../tools/grit/grit.py'],
+ 'grit_grd_file': '<(SHARED_INTERMEDIATE_DIR)/devtools/devtools_resources.grd',
+ },
+ 'inputs': [
+ '<(grit_grd_file)',
+ '<!@pymod_do_main(grit_info --inputs)',
+ ],
+ 'outputs': [
+ '<(grit_out_dir)/grit/devtools_resources.h',
+ '<(grit_out_dir)/devtools_resources.pak',
+ '<(grit_out_dir)/grit/devtools_resources_map.cc',
+ '<(grit_out_dir)/grit/devtools_resources_map.h',
+ ],
+ 'action': ['<@(grit_cmd)',
+ '-i', '<(grit_grd_file)', 'build',
+ '-f', 'GRIT_DIR/../gritsettings/resource_ids',
+ '-o', '<(grit_out_dir)',
+ '-D', 'SHARED_INTERMEDIATE_DIR=<(SHARED_INTERMEDIATE_DIR)',
+ '<@(grit_defines)' ],
+ 'message': 'Generating resources from <(grit_grd_file)',
+ 'msvs_cygwin_shell': 1,
+ }
+ ],
+ 'includes': [ '../../../build/grit_target.gypi' ],
+ },
+ ],
+}
diff --git a/chromium/content/browser/devtools/devtools_tracing_handler.cc b/chromium/content/browser/devtools/devtools_tracing_handler.cc
new file mode 100644
index 00000000000..0657b8e48d2
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_tracing_handler.cc
@@ -0,0 +1,113 @@
+// 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 "content/browser/devtools/devtools_tracing_handler.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/browser/devtools/devtools_http_handler_impl.h"
+#include "content/browser/devtools/devtools_protocol_constants.h"
+#include "content/public/browser/trace_controller.h"
+#include "content/public/browser/trace_subscriber.h"
+
+namespace content {
+
+namespace {
+
+const char kRecordUntilFull[] = "record-until-full";
+const char kRecordContinuously[] = "record-continuously";
+const char kEnableSampling[] = "enable-sampling";
+
+} // namespace
+
+DevToolsTracingHandler::DevToolsTracingHandler()
+ : is_running_(false) {
+ RegisterCommandHandler(devtools::Tracing::start::kName,
+ base::Bind(&DevToolsTracingHandler::OnStart,
+ base::Unretained(this)));
+ RegisterCommandHandler(devtools::Tracing::end::kName,
+ base::Bind(&DevToolsTracingHandler::OnEnd,
+ base::Unretained(this)));
+}
+
+DevToolsTracingHandler::~DevToolsTracingHandler() {
+}
+
+void DevToolsTracingHandler::OnEndTracingComplete() {
+ is_running_ = false;
+ SendNotification(devtools::Tracing::tracingComplete::kName, NULL);
+}
+
+void DevToolsTracingHandler::OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& trace_fragment) {
+ if (is_running_) {
+ // Hand-craft protocol notification message so we can substitute JSON
+ // that we already got as string as a bare object, not a quoted string.
+ std::string message = base::StringPrintf(
+ "{ \"method\": \"%s\", \"params\": { \"%s\": [ %s ] } }",
+ devtools::Tracing::dataCollected::kName,
+ devtools::Tracing::dataCollected::kValue,
+ trace_fragment->data().c_str());
+ SendRawMessage(message);
+ }
+}
+
+// Note, if you add more options here you also need to update:
+// base/debug/trace_event_impl:TraceOptionsFromString
+base::debug::TraceLog::Options DevToolsTracingHandler::TraceOptionsFromString(
+ const std::string& options) {
+ std::vector<std::string> split;
+ std::vector<std::string>::iterator iter;
+ int ret = 0;
+
+ base::SplitString(options, ',', &split);
+ for (iter = split.begin(); iter != split.end(); ++iter) {
+ if (*iter == kRecordUntilFull) {
+ ret |= base::debug::TraceLog::RECORD_UNTIL_FULL;
+ } else if (*iter == kRecordContinuously) {
+ ret |= base::debug::TraceLog::RECORD_CONTINUOUSLY;
+ } else if (*iter == kEnableSampling) {
+ ret |= base::debug::TraceLog::ENABLE_SAMPLING;
+ }
+ }
+ if (!(ret & base::debug::TraceLog::RECORD_UNTIL_FULL) &&
+ !(ret & base::debug::TraceLog::RECORD_CONTINUOUSLY))
+ ret |= base::debug::TraceLog::RECORD_UNTIL_FULL;
+
+ return static_cast<base::debug::TraceLog::Options>(ret);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsTracingHandler::OnStart(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ std::string categories;
+ base::DictionaryValue* params = command->params();
+ if (params)
+ params->GetString(devtools::Tracing::start::kCategories, &categories);
+
+ base::debug::TraceLog::Options options =
+ base::debug::TraceLog::RECORD_UNTIL_FULL;
+ if (params && params->HasKey(devtools::Tracing::start::kTraceOptions)) {
+ std::string options_param;
+ params->GetString(devtools::Tracing::start::kTraceOptions, &options_param);
+ options = TraceOptionsFromString(options_param);
+ }
+
+ TraceController::GetInstance()->BeginTracing(this, categories, options);
+ is_running_ = true;
+ return command->SuccessResponse(NULL);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+DevToolsTracingHandler::OnEnd(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ TraceController::GetInstance()->EndTracingAsync(this);
+ return command->SuccessResponse(NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/devtools_tracing_handler.h b/chromium/content/browser/devtools/devtools_tracing_handler.h
new file mode 100644
index 00000000000..43cd6150eda
--- /dev/null
+++ b/chromium/content/browser/devtools/devtools_tracing_handler.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_
+#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_
+
+#include "base/debug/trace_event.h"
+#include "content/browser/devtools/devtools_protocol.h"
+#include "content/public/browser/trace_subscriber.h"
+
+namespace content {
+
+// This class bridges DevTools remote debugging server with the trace
+// infrastructure.
+class DevToolsTracingHandler
+ : public TraceSubscriber,
+ public DevToolsProtocol::Handler {
+ public:
+ DevToolsTracingHandler();
+ virtual ~DevToolsTracingHandler();
+
+ // TraceSubscriber:
+ virtual void OnEndTracingComplete() OVERRIDE;;
+ virtual void OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& trace_fragment) OVERRIDE;
+
+ private:
+ scoped_refptr<DevToolsProtocol::Response> OnStart(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> OnEnd(
+ scoped_refptr<DevToolsProtocol::Command> command);
+
+ base::debug::TraceLog::Options TraceOptionsFromString(
+ const std::string& options);
+
+ bool is_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsTracingHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_
diff --git a/chromium/content/browser/devtools/ipc_devtools_agent_host.cc b/chromium/content/browser/devtools/ipc_devtools_agent_host.cc
new file mode 100644
index 00000000000..c7cd3c386f8
--- /dev/null
+++ b/chromium/content/browser/devtools/ipc_devtools_agent_host.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 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 "content/browser/devtools/ipc_devtools_agent_host.h"
+
+#include "content/common/devtools_messages.h"
+
+namespace content {
+
+void IPCDevToolsAgentHost::Attach() {
+ SendMessageToAgent(new DevToolsAgentMsg_Attach(MSG_ROUTING_NONE));
+ OnClientAttached();
+}
+
+void IPCDevToolsAgentHost::Detach() {
+ SendMessageToAgent(new DevToolsAgentMsg_Detach(MSG_ROUTING_NONE));
+ OnClientDetached();
+}
+
+void IPCDevToolsAgentHost::DispatchOnInspectorBackend(
+ const std::string& message) {
+ SendMessageToAgent(new DevToolsAgentMsg_DispatchOnInspectorBackend(
+ MSG_ROUTING_NONE, message));
+}
+
+void IPCDevToolsAgentHost::InspectElement(int x, int y) {
+ SendMessageToAgent(new DevToolsAgentMsg_InspectElement(MSG_ROUTING_NONE,
+ x, y));
+}
+
+IPCDevToolsAgentHost::~IPCDevToolsAgentHost() {
+}
+
+void IPCDevToolsAgentHost::Reattach(const std::string& saved_agent_state) {
+ SendMessageToAgent(new DevToolsAgentMsg_Reattach(
+ MSG_ROUTING_NONE,
+ saved_agent_state));
+ OnClientAttached();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/ipc_devtools_agent_host.h b/chromium/content/browser/devtools/ipc_devtools_agent_host.h
new file mode 100644
index 00000000000..98c2794ac6e
--- /dev/null
+++ b/chromium/content/browser/devtools/ipc_devtools_agent_host.h
@@ -0,0 +1,36 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVTOOLS_IPC_DEVTOOLS_AGENT_HOST_H_
+#define CONTENT_BROWSER_DEVTOOLS_IPC_DEVTOOLS_AGENT_HOST_H_
+
+#include "content/browser/devtools/devtools_agent_host_impl.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+
+class CONTENT_EXPORT IPCDevToolsAgentHost : public DevToolsAgentHostImpl {
+ public:
+ // DevToolsAgentHostImpl implementation.
+ virtual void Attach() OVERRIDE;
+ virtual void Detach() OVERRIDE;
+ virtual void DispatchOnInspectorBackend(const std::string& message) OVERRIDE;
+ virtual void InspectElement(int x, int y) OVERRIDE;
+
+ protected:
+ virtual ~IPCDevToolsAgentHost();
+
+ void Reattach(const std::string& saved_agent_state);
+
+ virtual void SendMessageToAgent(IPC::Message* msg) = 0;
+ virtual void OnClientAttached() = 0;
+ virtual void OnClientDetached() = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_IPC_DEVTOOLS_AGENT_HOST_H_
diff --git a/chromium/content/browser/devtools/render_view_devtools_agent_host.cc b/chromium/content/browser/devtools/render_view_devtools_agent_host.cc
new file mode 100644
index 00000000000..99686a00079
--- /dev/null
+++ b/chromium/content/browser/devtools/render_view_devtools_agent_host.cc
@@ -0,0 +1,351 @@
+// 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 "content/browser/devtools/render_view_devtools_agent_host.h"
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/browser/devtools/devtools_protocol.h"
+#include "content/browser/devtools/devtools_protocol_constants.h"
+#include "content/browser/devtools/devtools_tracing_handler.h"
+#include "content/browser/devtools/renderer_overrides_handler.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/devtools_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+
+namespace content {
+
+typedef std::vector<RenderViewDevToolsAgentHost*> Instances;
+
+namespace {
+base::LazyInstance<Instances>::Leaky g_instances = LAZY_INSTANCE_INITIALIZER;
+
+static RenderViewDevToolsAgentHost* FindAgentHost(RenderViewHost* rvh) {
+ if (g_instances == NULL)
+ return NULL;
+ for (Instances::iterator it = g_instances.Get().begin();
+ it != g_instances.Get().end(); ++it) {
+ if (rvh == (*it)->render_view_host())
+ return *it;
+ }
+ return NULL;
+}
+
+} // namespace
+
+class RenderViewDevToolsAgentHost::DevToolsAgentHostRvhObserver
+ : public RenderViewHostObserver {
+ public:
+ DevToolsAgentHostRvhObserver(RenderViewHost* rvh,
+ RenderViewDevToolsAgentHost* agent_host)
+ : RenderViewHostObserver(rvh),
+ agent_host_(agent_host) {
+ }
+ virtual ~DevToolsAgentHostRvhObserver() {}
+
+ // RenderViewHostObserver overrides.
+ virtual void RenderViewHostDestroyed(RenderViewHost* rvh) OVERRIDE {
+ agent_host_->RenderViewHostDestroyed(rvh);
+ }
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ return agent_host_->OnRvhMessageReceived(message);
+ }
+ private:
+ RenderViewDevToolsAgentHost* agent_host_;
+ DISALLOW_COPY_AND_ASSIGN(DevToolsAgentHostRvhObserver);
+};
+
+// static
+scoped_refptr<DevToolsAgentHost>
+DevToolsAgentHost::GetOrCreateFor(RenderViewHost* rvh) {
+ RenderViewDevToolsAgentHost* result = FindAgentHost(rvh);
+ if (!result)
+ result = new RenderViewDevToolsAgentHost(rvh);
+ return result;
+}
+
+// static
+bool DevToolsAgentHost::HasFor(RenderViewHost* rvh) {
+ return FindAgentHost(rvh) != NULL;
+}
+
+// static
+bool DevToolsAgentHost::IsDebuggerAttached(WebContents* web_contents) {
+ if (g_instances == NULL)
+ return false;
+ DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
+ if (!devtools_manager)
+ return false;
+ RenderViewHostDelegate* delegate =
+ static_cast<WebContentsImpl*>(web_contents);
+ for (Instances::iterator it = g_instances.Get().begin();
+ it != g_instances.Get().end(); ++it) {
+ RenderViewHost* rvh = (*it)->render_view_host_;
+ if (rvh && rvh->GetDelegate() != delegate)
+ continue;
+ if ((*it)->IsAttached())
+ return true;
+ }
+ return false;
+}
+
+//static
+std::vector<RenderViewHost*> DevToolsAgentHost::GetValidRenderViewHosts() {
+ std::vector<RenderViewHost*> result;
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ // Ignore processes that don't have a connection, such as crashed contents.
+ if (!widgets[i]->GetProcess()->HasConnection())
+ continue;
+ if (!widgets[i]->IsRenderView())
+ continue;
+
+ RenderViewHost* rvh = RenderViewHost::From(widgets[i]);
+ WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
+ // Don't report a RenderViewHost if it is not the current RenderViewHost
+ // for some WebContents.
+ if (!web_contents || rvh != web_contents->GetRenderViewHost())
+ continue;
+
+ result.push_back(rvh);
+ }
+ return result;
+}
+
+// static
+void RenderViewDevToolsAgentHost::OnCancelPendingNavigation(
+ RenderViewHost* pending,
+ RenderViewHost* current) {
+ RenderViewDevToolsAgentHost* agent_host = FindAgentHost(pending);
+ if (!agent_host)
+ return;
+ agent_host->DisconnectRenderViewHost();
+ agent_host->ConnectRenderViewHost(current);
+}
+
+RenderViewDevToolsAgentHost::RenderViewDevToolsAgentHost(
+ RenderViewHost* rvh)
+ : overrides_handler_(new RendererOverridesHandler(this)),
+ tracing_handler_(new DevToolsTracingHandler())
+ {
+ SetRenderViewHost(rvh);
+ DevToolsProtocol::Notifier notifier(base::Bind(
+ &RenderViewDevToolsAgentHost::OnDispatchOnInspectorFrontend,
+ base::Unretained(this)));
+ overrides_handler_->SetNotifier(notifier);
+ tracing_handler_->SetNotifier(notifier);
+ g_instances.Get().push_back(this);
+ RenderViewHostDelegate* delegate = render_view_host_->GetDelegate();
+ if (delegate && delegate->GetAsWebContents())
+ Observe(delegate->GetAsWebContents());
+ AddRef(); // Balanced in RenderViewHostDestroyed.
+}
+
+RenderViewHost* RenderViewDevToolsAgentHost::GetRenderViewHost() {
+ return render_view_host_;
+}
+
+void RenderViewDevToolsAgentHost::DispatchOnInspectorBackend(
+ const std::string& message) {
+ std::string error_message;
+ scoped_refptr<DevToolsProtocol::Command> command =
+ DevToolsProtocol::ParseCommand(message, &error_message);
+
+ if (command) {
+ scoped_refptr<DevToolsProtocol::Response> overridden_response =
+ overrides_handler_->HandleCommand(command);
+ if (!overridden_response)
+ overridden_response = tracing_handler_->HandleCommand(command);
+ if (overridden_response) {
+ if (!overridden_response->is_async_promise())
+ OnDispatchOnInspectorFrontend(overridden_response->Serialize());
+ return;
+ }
+ }
+
+ IPCDevToolsAgentHost::DispatchOnInspectorBackend(message);
+}
+
+void RenderViewDevToolsAgentHost::SendMessageToAgent(IPC::Message* msg) {
+ if (!render_view_host_)
+ return;
+ msg->set_routing_id(render_view_host_->GetRoutingID());
+ render_view_host_->Send(msg);
+}
+
+void RenderViewDevToolsAgentHost::OnClientAttached() {
+ if (!render_view_host_)
+ return;
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadRawCookies(
+ render_view_host_->GetProcess()->GetID());
+
+ // TODO(kaznacheev): Move this call back to DevToolsManagerImpl when
+ // ExtensionProcessManager no longer relies on this notification.
+ DevToolsManagerImpl::GetInstance()->NotifyObservers(this, true);
+}
+
+void RenderViewDevToolsAgentHost::OnClientDetached() {
+ if (!render_view_host_)
+ return;
+
+ bool process_has_agents = false;
+ RenderProcessHost* render_process_host = render_view_host_->GetProcess();
+ for (Instances::iterator it = g_instances.Get().begin();
+ it != g_instances.Get().end(); ++it) {
+ if (*it == this || !(*it)->IsAttached())
+ continue;
+ RenderViewHost* rvh = (*it)->render_view_host();
+ if (rvh && rvh->GetProcess() == render_process_host)
+ process_has_agents = true;
+ }
+
+ // We are the last to disconnect from the renderer -> revoke permissions.
+ if (!process_has_agents) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->RevokeReadRawCookies(
+ render_process_host->GetID());
+ }
+
+ // TODO(kaznacheev): Move this call back to DevToolsManagerImpl when
+ // ExtensionProcessManager no longer relies on this notification.
+ DevToolsManagerImpl::GetInstance()->NotifyObservers(this, false);
+}
+
+RenderViewDevToolsAgentHost::~RenderViewDevToolsAgentHost() {
+ Instances::iterator it = std::find(g_instances.Get().begin(),
+ g_instances.Get().end(),
+ this);
+ if (it != g_instances.Get().end())
+ g_instances.Get().erase(it);
+}
+
+void RenderViewDevToolsAgentHost::AboutToNavigateRenderView(
+ RenderViewHost* dest_rvh) {
+ if (!render_view_host_)
+ return;
+
+ if (render_view_host_ == dest_rvh && static_cast<RenderViewHostImpl*>(
+ render_view_host_)->render_view_termination_status() ==
+ base::TERMINATION_STATUS_STILL_RUNNING)
+ return;
+ DisconnectRenderViewHost();
+ ConnectRenderViewHost(dest_rvh);
+}
+
+void RenderViewDevToolsAgentHost::RenderProcessGone(
+ base::TerminationStatus status) {
+ switch(status) {
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ RenderViewCrashed();
+ break;
+ default:
+ break;
+ }
+}
+
+void RenderViewDevToolsAgentHost::DidAttachInterstitialPage() {
+ if (!render_view_host_)
+ return;
+ // The rvh set in AboutToNavigateRenderView turned out to be interstitial.
+ // Connect back to the real one.
+ WebContents* web_contents =
+ WebContents::FromRenderViewHost(render_view_host_);
+ if (!web_contents)
+ return;
+ DisconnectRenderViewHost();
+ ConnectRenderViewHost(web_contents->GetRenderViewHost());
+}
+
+void RenderViewDevToolsAgentHost::SetRenderViewHost(RenderViewHost* rvh) {
+ render_view_host_ = rvh;
+ rvh_observer_.reset(new DevToolsAgentHostRvhObserver(rvh, this));
+}
+
+void RenderViewDevToolsAgentHost::ConnectRenderViewHost(RenderViewHost* rvh) {
+ SetRenderViewHost(rvh);
+ if (IsAttached())
+ Reattach(state_);
+}
+
+void RenderViewDevToolsAgentHost::DisconnectRenderViewHost() {
+ OnClientDetached();
+ rvh_observer_.reset();
+ render_view_host_ = NULL;
+}
+
+void RenderViewDevToolsAgentHost::RenderViewHostDestroyed(
+ RenderViewHost* rvh) {
+ DCHECK(render_view_host_);
+ scoped_refptr<RenderViewDevToolsAgentHost> protect(this);
+ NotifyCloseListener();
+ render_view_host_ = NULL;
+ Release();
+}
+
+void RenderViewDevToolsAgentHost::RenderViewCrashed() {
+ scoped_refptr<DevToolsProtocol::Notification> notification =
+ DevToolsProtocol::CreateNotification(
+ devtools::Inspector::targetCrashed::kName, NULL);
+ DevToolsManagerImpl::GetInstance()->
+ DispatchOnInspectorFrontend(this, notification->Serialize());
+}
+
+bool RenderViewDevToolsAgentHost::OnRvhMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(RenderViewDevToolsAgentHost, message)
+ IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
+ OnDispatchOnInspectorFrontend)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_SaveAgentRuntimeState,
+ OnSaveAgentRuntimeState)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_ClearBrowserCache, OnClearBrowserCache)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_ClearBrowserCookies,
+ OnClearBrowserCookies)
+ IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_SwapCompositorFrame,
+ handled = false; OnSwapCompositorFrame())
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void RenderViewDevToolsAgentHost::OnSwapCompositorFrame() {
+ overrides_handler_->OnSwapCompositorFrame();
+}
+
+void RenderViewDevToolsAgentHost::OnSaveAgentRuntimeState(
+ const std::string& state) {
+ if (!render_view_host_)
+ return;
+ state_ = state;
+}
+
+void RenderViewDevToolsAgentHost::OnDispatchOnInspectorFrontend(
+ const std::string& message) {
+ if (!render_view_host_)
+ return;
+ DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(
+ this, message);
+}
+
+void RenderViewDevToolsAgentHost::OnClearBrowserCache() {
+ if (render_view_host_)
+ GetContentClient()->browser()->ClearCache(render_view_host_);
+}
+
+void RenderViewDevToolsAgentHost::OnClearBrowserCookies() {
+ if (render_view_host_)
+ GetContentClient()->browser()->ClearCookies(render_view_host_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/render_view_devtools_agent_host.h b/chromium/content/browser/devtools/render_view_devtools_agent_host.h
new file mode 100644
index 00000000000..89855441146
--- /dev/null
+++ b/chromium/content/browser/devtools/render_view_devtools_agent_host.h
@@ -0,0 +1,80 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_
+#define CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/devtools/ipc_devtools_agent_host.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+
+class DevToolsTracingHandler;
+class RendererOverridesHandler;
+class RenderViewHost;
+
+class CONTENT_EXPORT RenderViewDevToolsAgentHost
+ : public IPCDevToolsAgentHost,
+ private WebContentsObserver {
+ public:
+ static void OnCancelPendingNavigation(RenderViewHost* pending,
+ RenderViewHost* current);
+
+ RenderViewDevToolsAgentHost(RenderViewHost*);
+
+ RenderViewHost* render_view_host() { return render_view_host_; }
+
+ private:
+ friend class DevToolsAgentHost;
+ class DevToolsAgentHostRvhObserver;
+
+ virtual ~RenderViewDevToolsAgentHost();
+
+ // DevTooolsAgentHost overrides.
+ virtual void DisconnectRenderViewHost() OVERRIDE;
+ virtual void ConnectRenderViewHost(RenderViewHost* rvh) OVERRIDE;
+ virtual RenderViewHost* GetRenderViewHost() OVERRIDE;
+
+ // IPCDevToolsAgentHost overrides.
+ virtual void DispatchOnInspectorBackend(const std::string& message) OVERRIDE;
+ virtual void SendMessageToAgent(IPC::Message* msg) OVERRIDE;
+ virtual void OnClientAttached() OVERRIDE;
+ virtual void OnClientDetached() OVERRIDE;
+
+ // WebContentsObserver overrides.
+ virtual void AboutToNavigateRenderView(RenderViewHost* dest_rvh) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+ virtual void DidAttachInterstitialPage() OVERRIDE;
+
+ void SetRenderViewHost(RenderViewHost* rvh);
+
+ void RenderViewHostDestroyed(RenderViewHost* rvh);
+ void RenderViewCrashed();
+ bool OnRvhMessageReceived(const IPC::Message& message);
+ void OnSwapCompositorFrame();
+
+ void OnDispatchOnInspectorFrontend(const std::string& message);
+ void OnSaveAgentRuntimeState(const std::string& state);
+ void OnClearBrowserCache();
+ void OnClearBrowserCookies();
+
+ RenderViewHost* render_view_host_;
+ scoped_ptr<DevToolsAgentHostRvhObserver> rvh_observer_;
+ scoped_ptr<RendererOverridesHandler> overrides_handler_;
+ scoped_ptr<DevToolsTracingHandler> tracing_handler_;
+ std::string state_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewDevToolsAgentHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_
diff --git a/chromium/content/browser/devtools/renderer_overrides_handler.cc b/chromium/content/browser/devtools/renderer_overrides_handler.cc
new file mode 100644
index 00000000000..f5d6c1cd546
--- /dev/null
+++ b/chromium/content/browser/devtools/renderer_overrides_handler.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 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 "content/browser/devtools/renderer_overrides_handler.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/devtools/devtools_protocol_constants.h"
+#include "content/browser/devtools/devtools_tracing_handler.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/javascript_dialog_manager.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/common/referrer.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/snapshot/snapshot.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+static const char kPng[] = "png";
+static const char kJpeg[] = "jpeg";
+static int kDefaultScreenshotQuality = 80;
+
+void ParseCaptureParameters(DevToolsProtocol::Command* command,
+ std::string* format,
+ int* quality,
+ double* scale) {
+ *quality = kDefaultScreenshotQuality;
+ *scale = 1;
+ base::DictionaryValue* params = command->params();
+ if (params) {
+ params->GetString(devtools::Page::captureScreenshot::kParamFormat,
+ format);
+ params->GetInteger(devtools::Page::captureScreenshot::kParamQuality,
+ quality);
+ params->GetDouble(devtools::Page::captureScreenshot::kParamScale,
+ scale);
+ }
+ if (format->empty())
+ *format = kPng;
+ if (*quality < 0 || *quality > 100)
+ *quality = kDefaultScreenshotQuality;
+ if (*scale <= 0 || *scale > 1)
+ *scale = 1;
+}
+
+} // namespace
+
+RendererOverridesHandler::RendererOverridesHandler(DevToolsAgentHost* agent)
+ : agent_(agent),
+ weak_factory_(this) {
+ RegisterCommandHandler(
+ devtools::DOM::setFileInputFiles::kName,
+ base::Bind(
+ &RendererOverridesHandler::GrantPermissionsForSetFileInputFiles,
+ base::Unretained(this)));
+ RegisterCommandHandler(
+ devtools::Page::handleJavaScriptDialog::kName,
+ base::Bind(
+ &RendererOverridesHandler::PageHandleJavaScriptDialog,
+ base::Unretained(this)));
+ RegisterCommandHandler(
+ devtools::Page::navigate::kName,
+ base::Bind(
+ &RendererOverridesHandler::PageNavigate,
+ base::Unretained(this)));
+ RegisterCommandHandler(
+ devtools::Page::captureScreenshot::kName,
+ base::Bind(
+ &RendererOverridesHandler::PageCaptureScreenshot,
+ base::Unretained(this)));
+ RegisterCommandHandler(
+ devtools::Page::startScreencast::kName,
+ base::Bind(
+ &RendererOverridesHandler::PageStartScreencast,
+ base::Unretained(this)));
+ RegisterCommandHandler(
+ devtools::Page::stopScreencast::kName,
+ base::Bind(
+ &RendererOverridesHandler::PageStopScreencast,
+ base::Unretained(this)));
+}
+
+RendererOverridesHandler::~RendererOverridesHandler() {}
+
+void RendererOverridesHandler::OnSwapCompositorFrame() {
+ if (!screencast_command_)
+ return;
+
+ std::string format;
+ int quality = kDefaultScreenshotQuality;
+ double scale = 1;
+ ParseCaptureParameters(screencast_command_.get(), &format, &quality, &scale);
+
+ RenderViewHost* host = agent_->GetRenderViewHost();
+ RenderWidgetHostViewPort* view_port =
+ RenderWidgetHostViewPort::FromRWHV(host->GetView());
+
+ gfx::Rect view_bounds = host->GetView()->GetViewBounds();
+ gfx::Size snapshot_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(view_bounds.size(), scale));
+
+ view_port->CopyFromCompositingSurface(
+ view_bounds, snapshot_size,
+ base::Bind(&RendererOverridesHandler::ScreenshotCaptured,
+ weak_factory_.GetWeakPtr(),
+ scoped_refptr<DevToolsProtocol::Command>(), format, quality,
+ scale));
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::GrantPermissionsForSetFileInputFiles(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ base::DictionaryValue* params = command->params();
+ base::ListValue* file_list = NULL;
+ const char* param =
+ devtools::DOM::setFileInputFiles::kParamFiles;
+ if (!params || !params->GetList(param, &file_list))
+ return command->InvalidParamResponse(param);
+ RenderViewHost* host = agent_->GetRenderViewHost();
+ if (!host)
+ return NULL;
+
+ for (size_t i = 0; i < file_list->GetSize(); ++i) {
+ base::FilePath::StringType file;
+ if (!file_list->GetString(i, &file))
+ return command->InvalidParamResponse(param);
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ host->GetProcess()->GetID(), base::FilePath(file));
+ }
+ return NULL;
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::PageHandleJavaScriptDialog(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ base::DictionaryValue* params = command->params();
+ const char* paramAccept =
+ devtools::Page::handleJavaScriptDialog::kParamAccept;
+ bool accept;
+ if (!params || !params->GetBoolean(paramAccept, &accept))
+ return command->InvalidParamResponse(paramAccept);
+ string16 prompt_override;
+ string16* prompt_override_ptr = &prompt_override;
+ if (!params || !params->GetString(
+ devtools::Page::handleJavaScriptDialog::kParamPromptText,
+ prompt_override_ptr)) {
+ prompt_override_ptr = NULL;
+ }
+
+ RenderViewHost* host = agent_->GetRenderViewHost();
+ if (host) {
+ WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
+ if (web_contents) {
+ JavaScriptDialogManager* manager =
+ web_contents->GetDelegate()->GetJavaScriptDialogManager();
+ if (manager && manager->HandleJavaScriptDialog(
+ web_contents, accept, prompt_override_ptr)) {
+ return NULL;
+ }
+ }
+ }
+ return command->InternalErrorResponse("No JavaScript dialog to handle");
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::PageNavigate(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ base::DictionaryValue* params = command->params();
+ std::string url;
+ const char* param = devtools::Page::navigate::kParamUrl;
+ if (!params || !params->GetString(param, &url))
+ return command->InvalidParamResponse(param);
+ GURL gurl(url);
+ if (!gurl.is_valid()) {
+ return command->InternalErrorResponse("Cannot navigate to invalid URL");
+ }
+ RenderViewHost* host = agent_->GetRenderViewHost();
+ if (host) {
+ WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
+ if (web_contents) {
+ web_contents->GetController()
+ .LoadURL(gurl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ return command->SuccessResponse(new base::DictionaryValue());
+ }
+ }
+ return command->InternalErrorResponse("No WebContents to navigate");
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::PageCaptureScreenshot(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ std::string format;
+ int quality = kDefaultScreenshotQuality;
+ double scale = 1;
+ ParseCaptureParameters(command.get(), &format, &quality, &scale);
+
+ RenderViewHost* host = agent_->GetRenderViewHost();
+ gfx::Rect view_bounds = host->GetView()->GetViewBounds();
+
+ // Grab screen pixels if available for current platform.
+ // TODO(pfeldman): support format, scale and quality in ui::GrabViewSnapshot.
+ std::vector<unsigned char> png;
+ bool is_unscaled_png = scale == 1 && format == kPng;
+ if (is_unscaled_png && ui::GrabViewSnapshot(host->GetView()->GetNativeView(),
+ &png, view_bounds)) {
+ std::string base64_data;
+ bool success = base::Base64Encode(
+ base::StringPiece(reinterpret_cast<char*>(&*png.begin()), png.size()),
+ &base64_data);
+ if (success) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetString(
+ devtools::Page::captureScreenshot::kResponseData, base64_data);
+ return command->SuccessResponse(result);
+ }
+ return command->InternalErrorResponse("Unable to base64encode screenshot");
+ }
+
+ // Fallback to copying from compositing surface.
+ RenderWidgetHostViewPort* view_port =
+ RenderWidgetHostViewPort::FromRWHV(host->GetView());
+
+ gfx::Size snapshot_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(view_bounds.size(), scale));
+ view_port->CopyFromCompositingSurface(
+ view_bounds, snapshot_size,
+ base::Bind(&RendererOverridesHandler::ScreenshotCaptured,
+ weak_factory_.GetWeakPtr(), command, format, quality, scale));
+ return command->AsyncResponsePromise();
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::PageStartScreencast(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ screencast_command_ = command;
+ OnSwapCompositorFrame();
+ return command->SuccessResponse(NULL);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+RendererOverridesHandler::PageStopScreencast(
+ scoped_refptr<DevToolsProtocol::Command> command) {
+ screencast_command_ = NULL;
+ return command->SuccessResponse(NULL);
+}
+
+void RendererOverridesHandler::ScreenshotCaptured(
+ scoped_refptr<DevToolsProtocol::Command> command,
+ const std::string& format,
+ int quality,
+ double scale,
+ bool success,
+ const SkBitmap& bitmap) {
+ if (!success) {
+ if (command) {
+ SendAsyncResponse(
+ command->InternalErrorResponse("Unable to capture screenshot"));
+ }
+ return;
+ }
+
+ std::vector<unsigned char> data;
+ SkAutoLockPixels lock_image(bitmap);
+ bool encoded;
+ if (format == kPng) {
+ encoded = gfx::PNGCodec::Encode(
+ reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
+ gfx::PNGCodec::FORMAT_SkBitmap,
+ gfx::Size(bitmap.width(), bitmap.height()),
+ bitmap.width() * bitmap.bytesPerPixel(),
+ false, std::vector<gfx::PNGCodec::Comment>(), &data);
+ } else if (format == kJpeg) {
+ encoded = gfx::JPEGCodec::Encode(
+ reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
+ gfx::JPEGCodec::FORMAT_SkBitmap,
+ bitmap.width(),
+ bitmap.height(),
+ bitmap.width() * bitmap.bytesPerPixel(),
+ quality, &data);
+ } else {
+ encoded = false;
+ }
+
+ if (!encoded) {
+ if (command) {
+ SendAsyncResponse(
+ command->InternalErrorResponse("Unable to encode screenshot"));
+ }
+ return;
+ }
+
+ std::string base_64_data;
+ if (!base::Base64Encode(base::StringPiece(
+ reinterpret_cast<char*>(&data[0]),
+ data.size()),
+ &base_64_data)) {
+ if (command) {
+ SendAsyncResponse(
+ command->InternalErrorResponse("Unable to base64 encode"));
+ }
+ return;
+ }
+
+ base::DictionaryValue* response = new base::DictionaryValue();
+ if (command) {
+ response->SetString(
+ devtools::Page::captureScreenshot::kResponseData, base_64_data);
+ SendAsyncResponse(command->SuccessResponse(response));
+ } else {
+ response->SetString(
+ devtools::Page::screencastFrame::kResponseData, base_64_data);
+ SendNotification(devtools::Page::screencastFrame::kName, response);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/renderer_overrides_handler.h b/chromium/content/browser/devtools/renderer_overrides_handler.h
new file mode 100644
index 00000000000..7e5814b9d28
--- /dev/null
+++ b/chromium/content/browser/devtools/renderer_overrides_handler.h
@@ -0,0 +1,62 @@
+// Copyright (c) 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 CONTENT_BROWSER_DEVTOOLS_RENDERER_OVERRIDES_HANDLER_H_
+#define CONTENT_BROWSER_DEVTOOLS_RENDERER_OVERRIDES_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/devtools/devtools_protocol.h"
+
+class SkBitmap;
+
+namespace content {
+
+class DevToolsAgentHost;
+class DevToolsTracingHandler;
+
+// Overrides Inspector commands before they are sent to the renderer.
+// May override the implementation completely, ignore it, or handle
+// additional browser process implementation details.
+class RendererOverridesHandler : public DevToolsProtocol::Handler {
+ public:
+ explicit RendererOverridesHandler(DevToolsAgentHost* agent);
+ virtual ~RendererOverridesHandler();
+
+ void OnSwapCompositorFrame();
+
+ private:
+ scoped_refptr<DevToolsProtocol::Response>
+ GrantPermissionsForSetFileInputFiles(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> PageHandleJavaScriptDialog(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> PageNavigate(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> PageCaptureScreenshot(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> PageStartScreencast(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> PageStopScreencast(
+ scoped_refptr<DevToolsProtocol::Command> command);
+
+ void ScreenshotCaptured(
+ scoped_refptr<DevToolsProtocol::Command> command,
+ const std::string& format,
+ int quality,
+ double scale,
+ bool success,
+ const SkBitmap& bitmap);
+
+ DevToolsAgentHost* agent_;
+ base::WeakPtrFactory<RendererOverridesHandler> weak_factory_;
+ scoped_refptr<DevToolsProtocol::Command> screencast_command_;
+ DISALLOW_COPY_AND_ASSIGN(RendererOverridesHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_RENDERER_OVERRIDES_HANDLER_H_
diff --git a/chromium/content/browser/devtools/tethering_handler.cc b/chromium/content/browser/devtools/tethering_handler.cc
new file mode 100644
index 00000000000..711adcd2d72
--- /dev/null
+++ b/chromium/content/browser/devtools/tethering_handler.cc
@@ -0,0 +1,308 @@
+// 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 "content/browser/devtools/tethering_handler.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "content/browser/devtools/devtools_http_handler_impl.h"
+#include "content/public/browser/devtools_client_host.h"
+#include "content/public/browser/devtools_http_handler_delegate.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/stream_listen_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/socket/tcp_server_socket.h"
+
+namespace content {
+
+namespace {
+
+const char kTetheringBind[] = "Tethering.bind";
+const char kTetheringUnbind[] = "Tethering.unbind";
+
+const char kTetheringAccepted[] = "Tethering.accepted";
+
+const char kPortParam[] = "port";
+const char kConnectionIdParam[] = "connectionId";
+
+const char kLocalhost[] = "127.0.0.1";
+
+const int kListenBacklog = 5;
+const int kBufferSize = 16 * 1024;
+
+const int kMinTetheringPort = 5000;
+const int kMaxTetheringPort = 10000;
+
+class SocketPump : public net::StreamListenSocket::Delegate {
+ public:
+ SocketPump(DevToolsHttpHandlerDelegate* delegate,
+ net::StreamSocket* client_socket)
+ : client_socket_(client_socket),
+ delegate_(delegate),
+ wire_buffer_size_(0),
+ pending_destruction_(false) {
+ }
+
+ std::string Init() {
+ std::string channel_name;
+ server_socket_ = delegate_->CreateSocketForTethering(this, &channel_name);
+ if (!server_socket_.get() || channel_name.empty())
+ SelfDestruct();
+ return channel_name;
+ }
+
+ virtual ~SocketPump() { }
+
+ private:
+ virtual void DidAccept(net::StreamListenSocket* server,
+ net::StreamListenSocket* socket) OVERRIDE {
+ if (accepted_socket_.get())
+ return;
+
+ buffer_ = new net::IOBuffer(kBufferSize);
+ wire_buffer_ = new net::GrowableIOBuffer();
+ wire_buffer_->SetCapacity(kBufferSize);
+
+ accepted_socket_ = socket;
+ int result = client_socket_->Read(
+ buffer_.get(),
+ kBufferSize,
+ base::Bind(&SocketPump::OnClientRead, base::Unretained(this)));
+ if (result != net::ERR_IO_PENDING)
+ OnClientRead(result);
+ }
+
+ virtual void DidRead(net::StreamListenSocket* socket,
+ const char* data,
+ int len) OVERRIDE {
+ int old_size = wire_buffer_size_;
+ wire_buffer_size_ += len;
+ while (wire_buffer_->capacity() < wire_buffer_size_)
+ wire_buffer_->SetCapacity(wire_buffer_->capacity() * 2);
+ memcpy(wire_buffer_->StartOfBuffer() + old_size, data, len);
+ if (old_size != wire_buffer_->offset())
+ return;
+ OnClientWrite(0);
+ }
+
+ virtual void DidClose(net::StreamListenSocket* socket) OVERRIDE {
+ SelfDestruct();
+ }
+
+ void OnClientRead(int result) {
+ if (result <= 0) {
+ SelfDestruct();
+ return;
+ }
+
+ accepted_socket_->Send(buffer_->data(), result);
+ result = client_socket_->Read(
+ buffer_.get(),
+ kBufferSize,
+ base::Bind(&SocketPump::OnClientRead, base::Unretained(this)));
+ if (result != net::ERR_IO_PENDING)
+ OnClientRead(result);
+ }
+
+ void OnClientWrite(int result) {
+ if (result < 0)
+ SelfDestruct();
+
+ wire_buffer_->set_offset(wire_buffer_->offset() + result);
+
+ int remaining = wire_buffer_size_ - wire_buffer_->offset();
+ if (remaining == 0) {
+ if (pending_destruction_)
+ SelfDestruct();
+ return;
+ }
+
+
+ if (remaining > kBufferSize)
+ remaining = kBufferSize;
+
+ scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(remaining);
+ memcpy(buffer->data(), wire_buffer_->data(), remaining);
+ result = client_socket_->Write(
+ buffer.get(),
+ remaining,
+ base::Bind(&SocketPump::OnClientWrite, base::Unretained(this)));
+
+ // Shrink buffer
+ int offset = wire_buffer_->offset();
+ if (offset > kBufferSize) {
+ memcpy(wire_buffer_->StartOfBuffer(), wire_buffer_->data(),
+ wire_buffer_size_ - offset);
+ wire_buffer_size_ -= offset;
+ wire_buffer_->set_offset(0);
+ }
+
+ if (result != net::ERR_IO_PENDING)
+ OnClientWrite(result);
+ return;
+ }
+
+ void SelfDestruct() {
+ if (wire_buffer_->offset() != wire_buffer_size_) {
+ pending_destruction_ = true;
+ return;
+ }
+ delete this;
+ }
+
+ private:
+ scoped_ptr<net::StreamSocket> client_socket_;
+ scoped_refptr<net::StreamListenSocket> server_socket_;
+ scoped_refptr<net::StreamListenSocket> accepted_socket_;
+ scoped_refptr<net::IOBuffer> buffer_;
+ scoped_refptr<net::GrowableIOBuffer> wire_buffer_;
+ DevToolsHttpHandlerDelegate* delegate_;
+ int wire_buffer_size_;
+ bool pending_destruction_;
+};
+
+} // namespace
+
+const char TetheringHandler::kDomain[] = "Tethering";
+
+class TetheringHandler::BoundSocket {
+ public:
+ BoundSocket(TetheringHandler* handler,
+ DevToolsHttpHandlerDelegate* delegate)
+ : handler_(handler),
+ delegate_(delegate),
+ socket_(new net::TCPServerSocket(NULL, net::NetLog::Source())),
+ port_(0) {
+ }
+
+ virtual ~BoundSocket() {
+ }
+
+ bool Listen(int port) {
+ port_ = port;
+ net::IPAddressNumber ip_number;
+ if (!net::ParseIPLiteralToNumber(kLocalhost, &ip_number))
+ return false;
+
+ net::IPEndPoint end_point(ip_number, port);
+ int result = socket_->Listen(end_point, kListenBacklog);
+ if (result < 0)
+ return false;
+
+ net::IPEndPoint local_address;
+ result = socket_->GetLocalAddress(&local_address);
+ if (result < 0)
+ return false;
+
+ DoAccept();
+ return true;
+ }
+
+ private:
+ typedef std::map<net::IPEndPoint, net::StreamSocket*> AcceptedSocketsMap;
+
+ void DoAccept() {
+ while (true) {
+ int result = socket_->Accept(
+ &accept_socket_,
+ base::Bind(&BoundSocket::OnAccepted, base::Unretained(this)));
+ if (result == net::ERR_IO_PENDING)
+ break;
+ else
+ HandleAcceptResult(result);
+ }
+ }
+
+ void OnAccepted(int result) {
+ HandleAcceptResult(result);
+ if (result == net::OK)
+ DoAccept();
+ }
+
+ void HandleAcceptResult(int result) {
+ if (result != net::OK)
+ return;
+
+ SocketPump* pump = new SocketPump(delegate_, accept_socket_.release());
+ std::string name = pump->Init();
+ if (!name.empty())
+ handler_->Accepted(port_, name);
+ }
+
+ TetheringHandler* handler_;
+ DevToolsHttpHandlerDelegate* delegate_;
+ scoped_ptr<net::ServerSocket> socket_;
+ scoped_ptr<net::StreamSocket> accept_socket_;
+ int port_;
+};
+
+TetheringHandler::TetheringHandler(DevToolsHttpHandlerDelegate* delegate)
+ : delegate_(delegate) {
+ RegisterCommandHandler(kTetheringBind,
+ base::Bind(&TetheringHandler::OnBind,
+ base::Unretained(this)));
+ RegisterCommandHandler(kTetheringUnbind,
+ base::Bind(&TetheringHandler::OnUnbind,
+ base::Unretained(this)));
+}
+
+TetheringHandler::~TetheringHandler() {
+ STLDeleteContainerPairSecondPointers(bound_sockets_.begin(),
+ bound_sockets_.end());
+}
+
+void TetheringHandler::Accepted(int port, const std::string& name) {
+ base::DictionaryValue* params = new base::DictionaryValue();
+ params->SetInteger(kPortParam, port);
+ params->SetString(kConnectionIdParam, name);
+ SendNotification(kTetheringAccepted, params);
+}
+
+static int GetPort(scoped_refptr<DevToolsProtocol::Command> command) {
+ base::DictionaryValue* params = command->params();
+ int port = 0;
+ if (!params || !params->GetInteger(kPortParam, &port) ||
+ port < kMinTetheringPort || port > kMaxTetheringPort)
+ return 0;
+ return port;
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+TetheringHandler::OnBind(scoped_refptr<DevToolsProtocol::Command> command) {
+ int port = GetPort(command);
+ if (port == 0)
+ return command->InvalidParamResponse(kPortParam);
+
+ if (bound_sockets_.find(port) != bound_sockets_.end())
+ return command->InternalErrorResponse("Port already bound");
+
+ scoped_ptr<BoundSocket> bound_socket(new BoundSocket(this, delegate_));
+ if (!bound_socket->Listen(port))
+ return command->InternalErrorResponse("Could not bind port");
+
+ bound_sockets_[port] = bound_socket.release();
+ return command->SuccessResponse(NULL);
+}
+
+scoped_refptr<DevToolsProtocol::Response>
+TetheringHandler::OnUnbind(scoped_refptr<DevToolsProtocol::Command> command) {
+ int port = GetPort(command);
+ if (port == 0)
+ return command->InvalidParamResponse(kPortParam);
+
+ BoundSockets::iterator it = bound_sockets_.find(port);
+ if (it == bound_sockets_.end())
+ return command->InternalErrorResponse("Port is not bound");
+
+ delete it->second;
+ bound_sockets_.erase(it);
+ return command->SuccessResponse(NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/tethering_handler.h b/chromium/content/browser/devtools/tethering_handler.h
new file mode 100644
index 00000000000..7086e1747db
--- /dev/null
+++ b/chromium/content/browser/devtools/tethering_handler.h
@@ -0,0 +1,41 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_TETHERING_HANDLER_H_
+#define CONTENT_BROWSER_DEVTOOLS_TETHERING_HANDLER_H_
+
+#include <map>
+
+#include "content/browser/devtools/devtools_protocol.h"
+
+namespace content {
+
+class DevToolsHttpHandlerDelegate;
+
+// This class implements reversed tethering handler.
+class TetheringHandler : public DevToolsProtocol::Handler {
+ public:
+ static const char kDomain[];
+
+ TetheringHandler(DevToolsHttpHandlerDelegate* delegate);
+ virtual ~TetheringHandler();
+
+ void Accepted(int port, const std::string& name);
+
+ private:
+ class BoundSocket;
+ scoped_refptr<DevToolsProtocol::Response> OnBind(
+ scoped_refptr<DevToolsProtocol::Command> command);
+ scoped_refptr<DevToolsProtocol::Response> OnUnbind(
+ scoped_refptr<DevToolsProtocol::Command> command);
+
+ typedef std::map<int, BoundSocket*> BoundSockets;
+ BoundSockets bound_sockets_;
+ DevToolsHttpHandlerDelegate* delegate_;
+ DISALLOW_COPY_AND_ASSIGN(TetheringHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_TETHERING_HANDLER_H_
diff --git a/chromium/content/browser/devtools/worker_devtools_manager.cc b/chromium/content/browser/devtools/worker_devtools_manager.cc
new file mode 100644
index 00000000000..14efaa53d41
--- /dev/null
+++ b/chromium/content/browser/devtools/worker_devtools_manager.cc
@@ -0,0 +1,421 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/devtools/worker_devtools_manager.h"
+
+#include <list>
+#include <map>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/browser/devtools/devtools_protocol.h"
+#include "content/browser/devtools/devtools_protocol_constants.h"
+#include "content/browser/devtools/ipc_devtools_agent_host.h"
+#include "content/browser/devtools/worker_devtools_message_filter.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/devtools_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/common/process_type.h"
+
+namespace content {
+
+// Called on the UI thread.
+// static
+scoped_refptr<DevToolsAgentHost> DevToolsAgentHost::GetForWorker(
+ int worker_process_id,
+ int worker_route_id) {
+ return WorkerDevToolsManager::GetDevToolsAgentHostForWorker(
+ worker_process_id,
+ worker_route_id);
+}
+
+namespace {
+
+typedef std::map<WorkerDevToolsManager::WorkerId,
+ WorkerDevToolsManager::WorkerDevToolsAgentHost*> AgentHosts;
+base::LazyInstance<AgentHosts>::Leaky g_agent_map = LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<AgentHosts>::Leaky g_orphan_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+class WorkerDevToolsManager::WorkerDevToolsAgentHost
+ : public IPCDevToolsAgentHost {
+ public:
+ explicit WorkerDevToolsAgentHost(WorkerId worker_id)
+ : has_worker_id_(false) {
+ SetWorkerId(worker_id, false);
+ }
+
+ void SetWorkerId(WorkerId worker_id, bool reattach) {
+ worker_id_ = worker_id;
+ if (!has_worker_id_)
+ AddRef(); // Balanced in ResetWorkerId.
+ has_worker_id_ = true;
+ g_agent_map.Get()[worker_id_] = this;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ConnectToWorker,
+ worker_id.first,
+ worker_id.second));
+
+ if (reattach)
+ Reattach(state_);
+ }
+
+ void ResetWorkerId() {
+ g_agent_map.Get().erase(worker_id_);
+ has_worker_id_ = false;
+ Release(); // Balanced in SetWorkerId.
+ }
+
+ void SaveAgentRuntimeState(const std::string& state) {
+ state_ = state;
+ }
+
+ void ConnectionFailed() {
+ NotifyCloseListener();
+ // Object can be deleted here.
+ }
+
+ private:
+ virtual ~WorkerDevToolsAgentHost();
+
+ static void ConnectToWorker(
+ int worker_process_id,
+ int worker_route_id) {
+ WorkerDevToolsManager::GetInstance()->ConnectDevToolsAgentHostToWorker(
+ worker_process_id, worker_route_id);
+ }
+
+ static void ForwardToWorkerDevToolsAgent(
+ int worker_process_id,
+ int worker_route_id,
+ IPC::Message* message) {
+ WorkerDevToolsManager::GetInstance()->ForwardToWorkerDevToolsAgent(
+ worker_process_id, worker_route_id, *message);
+ }
+
+ // IPCDevToolsAgentHost implementation.
+ virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE {
+ if (!has_worker_id_) {
+ delete message;
+ return;
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &WorkerDevToolsAgentHost::ForwardToWorkerDevToolsAgent,
+ worker_id_.first,
+ worker_id_.second,
+ base::Owned(message)));
+ }
+
+ virtual void OnClientAttached() OVERRIDE {}
+ virtual void OnClientDetached() OVERRIDE {}
+
+ bool has_worker_id_;
+ WorkerId worker_id_;
+ std::string state_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsAgentHost);
+};
+
+
+class WorkerDevToolsManager::DetachedClientHosts {
+ public:
+ static void WorkerReloaded(WorkerId old_id, WorkerId new_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ AgentHosts::iterator it = g_orphan_map.Get().find(old_id);
+ if (it != g_orphan_map.Get().end()) {
+ it->second->SetWorkerId(new_id, true);
+ g_orphan_map.Get().erase(old_id);
+ return;
+ }
+ RemovePendingWorkerData(old_id);
+ }
+
+ static void WorkerDestroyed(WorkerId id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ AgentHosts::iterator it = g_agent_map.Get().find(id);
+ if (it == g_agent_map.Get().end()) {
+ RemovePendingWorkerData(id);
+ return;
+ }
+
+ WorkerDevToolsAgentHost* agent = it->second;
+ DevToolsManagerImpl* devtools_manager = DevToolsManagerImpl::GetInstance();
+ if (!agent->IsAttached()) {
+ // Agent has no client hosts -> delete it.
+ RemovePendingWorkerData(id);
+ return;
+ }
+
+ // Client host is debugging this worker agent host.
+ std::string notification = DevToolsProtocol::CreateNotification(
+ devtools::Worker::disconnectedFromWorker::kName, NULL)->Serialize();
+ devtools_manager->DispatchOnInspectorFrontend(agent, notification);
+ g_orphan_map.Get()[id] = agent;
+ agent->ResetWorkerId();
+ }
+
+ static void RemovePendingWorkerData(WorkerId id) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&RemoveInspectedWorkerDataOnIOThread, id));
+ }
+
+ private:
+ DetachedClientHosts() {}
+ ~DetachedClientHosts() {}
+
+ static void RemoveInspectedWorkerDataOnIOThread(WorkerId id) {
+ WorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(id);
+ }
+};
+
+// static
+WorkerDevToolsManager* WorkerDevToolsManager::GetInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return Singleton<WorkerDevToolsManager>::get();
+}
+
+// static
+DevToolsAgentHost* WorkerDevToolsManager::GetDevToolsAgentHostForWorker(
+ int worker_process_id,
+ int worker_route_id) {
+ WorkerId id(worker_process_id, worker_route_id);
+ AgentHosts::iterator it = g_agent_map.Get().find(id);
+ if (it == g_agent_map.Get().end())
+ return new WorkerDevToolsAgentHost(id);
+ return it->second;
+}
+
+WorkerDevToolsManager::WorkerDevToolsManager() {
+}
+
+WorkerDevToolsManager::~WorkerDevToolsManager() {
+}
+
+void WorkerDevToolsManager::WorkerCreated(
+ WorkerProcessHost* worker,
+ const WorkerProcessHost::WorkerInstance& instance) {
+ for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin();
+ it != terminated_workers_.end(); ++it) {
+ if (instance.Matches(it->worker_url, it->worker_name,
+ instance.partition(),
+ instance.resource_context())) {
+ worker->Send(new DevToolsAgentMsg_PauseWorkerContextOnStart(
+ instance.worker_route_id()));
+ WorkerId new_worker_id(worker->GetData().id, instance.worker_route_id());
+ paused_workers_[new_worker_id] = it->old_worker_id;
+ terminated_workers_.erase(it);
+ return;
+ }
+ }
+}
+
+void WorkerDevToolsManager::WorkerDestroyed(
+ WorkerProcessHost* worker,
+ int worker_route_id) {
+ InspectedWorkersList::iterator it = FindInspectedWorker(
+ worker->GetData().id,
+ worker_route_id);
+ if (it == inspected_workers_.end())
+ return;
+
+ WorkerId worker_id(worker->GetData().id, worker_route_id);
+ terminated_workers_.push_back(TerminatedInspectedWorker(
+ worker_id,
+ it->worker_url,
+ it->worker_name));
+ inspected_workers_.erase(it);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DetachedClientHosts::WorkerDestroyed, worker_id));
+}
+
+void WorkerDevToolsManager::WorkerContextStarted(WorkerProcessHost* process,
+ int worker_route_id) {
+ WorkerId new_worker_id(process->GetData().id, worker_route_id);
+ PausedWorkers::iterator it = paused_workers_.find(new_worker_id);
+ if (it == paused_workers_.end())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &DetachedClientHosts::WorkerReloaded,
+ it->second,
+ new_worker_id));
+ paused_workers_.erase(it);
+}
+
+void WorkerDevToolsManager::RemoveInspectedWorkerData(
+ const WorkerId& id) {
+ for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin();
+ it != terminated_workers_.end(); ++it) {
+ if (it->old_worker_id == id) {
+ terminated_workers_.erase(it);
+ return;
+ }
+ }
+
+ for (PausedWorkers::iterator it = paused_workers_.begin();
+ it != paused_workers_.end(); ++it) {
+ if (it->second == id) {
+ SendResumeToWorker(it->first);
+ paused_workers_.erase(it);
+ return;
+ }
+ }
+}
+
+WorkerDevToolsManager::InspectedWorkersList::iterator
+WorkerDevToolsManager::FindInspectedWorker(
+ int host_id, int route_id) {
+ InspectedWorkersList::iterator it = inspected_workers_.begin();
+ while (it != inspected_workers_.end()) {
+ if (it->host->GetData().id == host_id && it->route_id == route_id)
+ break;
+ ++it;
+ }
+ return it;
+}
+
+static WorkerProcessHost* FindWorkerProcess(int worker_process_id) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter.GetData().id == worker_process_id)
+ return *iter;
+ }
+ return NULL;
+}
+
+void WorkerDevToolsManager::ConnectDevToolsAgentHostToWorker(
+ int worker_process_id,
+ int worker_route_id) {
+ if (WorkerProcessHost* process = FindWorkerProcess(worker_process_id)) {
+ const WorkerProcessHost::Instances& instances = process->instances();
+ for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
+ i != instances.end(); ++i) {
+ if (i->worker_route_id() == worker_route_id) {
+ DCHECK(FindInspectedWorker(worker_process_id, worker_route_id) ==
+ inspected_workers_.end());
+ inspected_workers_.push_back(
+ InspectedWorker(process, worker_route_id, i->url(), i->name()));
+ return;
+ }
+ }
+ }
+ NotifyConnectionFailedOnIOThread(worker_process_id, worker_route_id);
+}
+
+void WorkerDevToolsManager::ForwardToDevToolsClient(
+ int worker_process_id,
+ int worker_route_id,
+ const std::string& message) {
+ if (FindInspectedWorker(worker_process_id, worker_route_id) ==
+ inspected_workers_.end()) {
+ NOTREACHED();
+ return;
+ }
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &ForwardToDevToolsClientOnUIThread,
+ worker_process_id,
+ worker_route_id,
+ message));
+}
+
+void WorkerDevToolsManager::SaveAgentRuntimeState(int worker_process_id,
+ int worker_route_id,
+ const std::string& state) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &SaveAgentRuntimeStateOnUIThread,
+ worker_process_id,
+ worker_route_id,
+ state));
+}
+
+void WorkerDevToolsManager::ForwardToWorkerDevToolsAgent(
+ int worker_process_id,
+ int worker_route_id,
+ const IPC::Message& message) {
+ InspectedWorkersList::iterator it = FindInspectedWorker(
+ worker_process_id,
+ worker_route_id);
+ if (it == inspected_workers_.end())
+ return;
+ IPC::Message* msg = new IPC::Message(message);
+ msg->set_routing_id(worker_route_id);
+ it->host->Send(msg);
+}
+
+// static
+void WorkerDevToolsManager::ForwardToDevToolsClientOnUIThread(
+ int worker_process_id,
+ int worker_route_id,
+ const std::string& message) {
+ AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
+ worker_route_id));
+ if (it == g_agent_map.Get().end())
+ return;
+ DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(it->second,
+ message);
+}
+
+// static
+void WorkerDevToolsManager::SaveAgentRuntimeStateOnUIThread(
+ int worker_process_id,
+ int worker_route_id,
+ const std::string& state) {
+ AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
+ worker_route_id));
+ if (it == g_agent_map.Get().end())
+ return;
+ it->second->SaveAgentRuntimeState(state);
+}
+
+// static
+void WorkerDevToolsManager::NotifyConnectionFailedOnIOThread(
+ int worker_process_id,
+ int worker_route_id) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &WorkerDevToolsManager::NotifyConnectionFailedOnUIThread,
+ worker_process_id,
+ worker_route_id));
+}
+
+// static
+void WorkerDevToolsManager::NotifyConnectionFailedOnUIThread(
+ int worker_process_id,
+ int worker_route_id) {
+ AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
+ worker_route_id));
+ if (it != g_agent_map.Get().end())
+ it->second->ConnectionFailed();
+}
+
+// static
+void WorkerDevToolsManager::SendResumeToWorker(const WorkerId& id) {
+ if (WorkerProcessHost* process = FindWorkerProcess(id.first))
+ process->Send(new DevToolsAgentMsg_ResumeWorkerContext(id.second));
+}
+
+WorkerDevToolsManager::WorkerDevToolsAgentHost::~WorkerDevToolsAgentHost() {
+ DetachedClientHosts::RemovePendingWorkerData(worker_id_);
+ g_agent_map.Get().erase(worker_id_);
+ g_orphan_map.Get().erase(worker_id_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/worker_devtools_manager.h b/chromium/content/browser/devtools/worker_devtools_manager.h
new file mode 100644
index 00000000000..0f0111ecf30
--- /dev/null
+++ b/chromium/content/browser/devtools/worker_devtools_manager.h
@@ -0,0 +1,131 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_
+#define CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "content/browser/worker_host/worker_process_host.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class DevToolsAgentHost;
+
+// All methods are supposed to be called on the IO thread.
+class WorkerDevToolsManager {
+ public:
+ typedef std::pair<int, int> WorkerId;
+ class WorkerDevToolsAgentHost;
+
+ // Returns the WorkerDevToolsManager singleton.
+ static WorkerDevToolsManager* GetInstance();
+
+ // Called on the UI thread.
+ static DevToolsAgentHost* GetDevToolsAgentHostForWorker(
+ int worker_process_id,
+ int worker_route_id);
+
+ void ForwardToDevToolsClient(int worker_process_id,
+ int worker_route_id,
+ const std::string& message);
+ void SaveAgentRuntimeState(int worker_process_id,
+ int worker_route_id,
+ const std::string& state);
+
+ // Called on the IO thread.
+ void WorkerCreated(
+ WorkerProcessHost* process,
+ const WorkerProcessHost::WorkerInstance& instance);
+ void WorkerDestroyed(WorkerProcessHost* process, int worker_route_id);
+ void WorkerContextStarted(WorkerProcessHost* process, int worker_route_id);
+
+ private:
+ friend struct DefaultSingletonTraits<WorkerDevToolsManager>;
+ class DetachedClientHosts;
+
+ struct InspectedWorker {
+ InspectedWorker(WorkerProcessHost* host, int route_id, const GURL& url,
+ const string16& name)
+ : host(host),
+ route_id(route_id),
+ worker_url(url),
+ worker_name(name) {}
+ WorkerProcessHost* const host;
+ int const route_id;
+ GURL worker_url;
+ string16 worker_name;
+ };
+
+ typedef std::list<InspectedWorker> InspectedWorkersList;
+
+ WorkerDevToolsManager();
+ virtual ~WorkerDevToolsManager();
+
+ void RemoveInspectedWorkerData(const WorkerId& id);
+ InspectedWorkersList::iterator FindInspectedWorker(int host_id, int route_id);
+
+ void ConnectDevToolsAgentHostToWorker(int worker_process_id,
+ int worker_route_id);
+ void ForwardToWorkerDevToolsAgent(int worker_process_host_id,
+ int worker_route_id,
+ const IPC::Message& message);
+ static void ForwardToDevToolsClientOnUIThread(
+ int worker_process_id,
+ int worker_route_id,
+ const std::string& message);
+ static void SaveAgentRuntimeStateOnUIThread(
+ int worker_process_id,
+ int worker_route_id,
+ const std::string& state);
+ static void NotifyConnectionFailedOnIOThread(int worker_process_id,
+ int worker_route_id);
+ static void NotifyConnectionFailedOnUIThread(int worker_process_id,
+ int worker_route_id);
+ static void SendResumeToWorker(const WorkerId& id);
+
+ InspectedWorkersList inspected_workers_;
+
+ struct TerminatedInspectedWorker {
+ TerminatedInspectedWorker(WorkerId id, const GURL& url, const string16& name)
+ : old_worker_id(id),
+ worker_url(url),
+ worker_name(name) {}
+ WorkerId old_worker_id;
+ GURL worker_url;
+ string16 worker_name;
+ };
+
+ typedef std::list<TerminatedInspectedWorker> TerminatedInspectedWorkers;
+ // List of terminated workers for which there may be a devtools client on
+ // the UI thread. Worker entry is added into this list when inspected worker
+ // is terminated and will be removed in one of two cases:
+ // - shared worker with the same URL and name is started(in wich case we will
+ // try to reattach existing DevTools client to the new worker).
+ // - DevTools client which was inspecting terminated worker is closed on the
+ // UI thread and and WorkerDevToolsManager is notified about that on the IO
+ // thread.
+ TerminatedInspectedWorkers terminated_workers_;
+
+ typedef std::map<WorkerId, WorkerId> PausedWorkers;
+ // Map from old to new worker id for the inspected workers that have been
+ // terminated and started again in paused state. Worker data will be removed
+ // from this list in one of two cases:
+ // - DevTools client is closed on the UI thread, WorkerDevToolsManager was
+ // notified about that on the IO thread and sent "resume" message to the
+ // worker.
+ // - Existing DevTools client was reattached to the new worker.
+ PausedWorkers paused_workers_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_
diff --git a/chromium/content/browser/devtools/worker_devtools_message_filter.cc b/chromium/content/browser/devtools/worker_devtools_message_filter.cc
new file mode 100644
index 00000000000..3f5553f120c
--- /dev/null
+++ b/chromium/content/browser/devtools/worker_devtools_message_filter.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/devtools/worker_devtools_message_filter.h"
+
+#include "content/browser/devtools/worker_devtools_manager.h"
+#include "content/common/devtools_messages.h"
+#include "content/common/worker_messages.h"
+
+namespace content {
+
+WorkerDevToolsMessageFilter::WorkerDevToolsMessageFilter(
+ int worker_process_host_id)
+ : worker_process_host_id_(worker_process_host_id),
+ current_routing_id_(0) {
+}
+
+WorkerDevToolsMessageFilter::~WorkerDevToolsMessageFilter() {
+}
+
+bool WorkerDevToolsMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ current_routing_id_ = message.routing_id();
+ IPC_BEGIN_MESSAGE_MAP_EX(WorkerDevToolsMessageFilter, message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
+ OnDispatchOnInspectorFrontend)
+ IPC_MESSAGE_HANDLER(DevToolsHostMsg_SaveAgentRuntimeState,
+ OnSaveAgentRumtimeState)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void WorkerDevToolsMessageFilter::OnDispatchOnInspectorFrontend(
+ const std::string& message) {
+ WorkerDevToolsManager::GetInstance()->ForwardToDevToolsClient(
+ worker_process_host_id_, current_routing_id_, message);
+}
+
+void WorkerDevToolsMessageFilter::OnSaveAgentRumtimeState(
+ const std::string& state) {
+ WorkerDevToolsManager::GetInstance()->SaveAgentRuntimeState(
+ worker_process_host_id_, current_routing_id_, state);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/devtools/worker_devtools_message_filter.h b/chromium/content/browser/devtools/worker_devtools_message_filter.h
new file mode 100644
index 00000000000..73ba6f8442a
--- /dev/null
+++ b/chromium/content/browser/devtools/worker_devtools_message_filter.h
@@ -0,0 +1,35 @@
+// 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 CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_
+
+#include "base/callback_forward.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class WorkerDevToolsMessageFilter : public BrowserMessageFilter {
+ public:
+ explicit WorkerDevToolsMessageFilter(int worker_process_host_id);
+
+ private:
+ virtual ~WorkerDevToolsMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ // Message handlers.
+ void OnDispatchOnInspectorFrontend(const std::string& message);
+ void OnSaveAgentRumtimeState(const std::string& state);
+
+ int worker_process_host_id_;
+ int current_routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/dom_storage/DEPS b/chromium/content/browser/dom_storage/DEPS
new file mode 100644
index 00000000000..743a2f3baec
--- /dev/null
+++ b/chromium/content/browser/dom_storage/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/leveldatabase",
+]
diff --git a/chromium/content/browser/dom_storage/OWNERS b/chromium/content/browser/dom_storage/OWNERS
new file mode 100644
index 00000000000..e765c6f0681
--- /dev/null
+++ b/chromium/content/browser/dom_storage/OWNERS
@@ -0,0 +1,2 @@
+michaeln@chromium.org
+marja@chromium.org
diff --git a/chromium/content/browser/dom_storage/dom_storage_area.cc b/chromium/content/browser/dom_storage/dom_storage_area.cc
new file mode 100644
index 00000000000..e358301b130
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_area.cc
@@ -0,0 +1,403 @@
+// 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 "content/browser/dom_storage/dom_storage_area.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/dom_storage/dom_storage_namespace.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/browser/dom_storage/local_storage_database_adapter.h"
+#include "content/browser/dom_storage/session_storage_database.h"
+#include "content/browser/dom_storage/session_storage_database_adapter.h"
+#include "content/common/dom_storage/dom_storage_map.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "webkit/browser/database/database_util.h"
+#include "webkit/common/database/database_identifier.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using webkit_database::DatabaseUtil;
+
+namespace content {
+
+static const int kCommitTimerSeconds = 1;
+
+DOMStorageArea::CommitBatch::CommitBatch()
+ : clear_all_first(false) {
+}
+DOMStorageArea::CommitBatch::~CommitBatch() {}
+
+
+// static
+const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] =
+ FILE_PATH_LITERAL(".localstorage");
+
+// static
+base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
+ std::string filename = webkit_database::GetIdentifierFromOrigin(origin);
+ // There is no base::FilePath.AppendExtension() method, so start with just the
+ // extension as the filename, and then InsertBeforeExtension the desired
+ // name.
+ return base::FilePath().Append(kDatabaseFileExtension).
+ InsertBeforeExtensionASCII(filename);
+}
+
+// static
+GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) {
+ DCHECK(name.MatchesExtension(kDatabaseFileExtension));
+ std::string origin_id =
+ name.BaseName().RemoveExtension().MaybeAsASCII();
+ return webkit_database::GetOriginFromIdentifier(origin_id);
+}
+
+DOMStorageArea::DOMStorageArea(
+ const GURL& origin, const base::FilePath& directory,
+ DOMStorageTaskRunner* task_runner)
+ : namespace_id_(kLocalStorageNamespaceId), origin_(origin),
+ directory_(directory),
+ task_runner_(task_runner),
+ map_(new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance)),
+ is_initial_import_done_(true),
+ is_shutdown_(false),
+ commit_batches_in_flight_(0) {
+ if (!directory.empty()) {
+ base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_));
+ backing_.reset(new LocalStorageDatabaseAdapter(path));
+ is_initial_import_done_ = false;
+ }
+}
+
+DOMStorageArea::DOMStorageArea(
+ int64 namespace_id,
+ const std::string& persistent_namespace_id,
+ const GURL& origin,
+ SessionStorageDatabase* session_storage_backing,
+ DOMStorageTaskRunner* task_runner)
+ : namespace_id_(namespace_id),
+ persistent_namespace_id_(persistent_namespace_id),
+ origin_(origin),
+ task_runner_(task_runner),
+ map_(new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance)),
+ session_storage_backing_(session_storage_backing),
+ is_initial_import_done_(true),
+ is_shutdown_(false),
+ commit_batches_in_flight_(0) {
+ DCHECK(namespace_id != kLocalStorageNamespaceId);
+ if (session_storage_backing) {
+ backing_.reset(new SessionStorageDatabaseAdapter(
+ session_storage_backing, persistent_namespace_id, origin));
+ is_initial_import_done_ = false;
+ }
+}
+
+DOMStorageArea::~DOMStorageArea() {
+}
+
+void DOMStorageArea::ExtractValues(DOMStorageValuesMap* map) {
+ if (is_shutdown_)
+ return;
+ InitialImportIfNeeded();
+ map_->ExtractValues(map);
+}
+
+unsigned DOMStorageArea::Length() {
+ if (is_shutdown_)
+ return 0;
+ InitialImportIfNeeded();
+ return map_->Length();
+}
+
+base::NullableString16 DOMStorageArea::Key(unsigned index) {
+ if (is_shutdown_)
+ return base::NullableString16();
+ InitialImportIfNeeded();
+ return map_->Key(index);
+}
+
+base::NullableString16 DOMStorageArea::GetItem(const base::string16& key) {
+ if (is_shutdown_)
+ return base::NullableString16();
+ InitialImportIfNeeded();
+ return map_->GetItem(key);
+}
+
+bool DOMStorageArea::SetItem(const base::string16& key,
+ const base::string16& value,
+ base::NullableString16* old_value) {
+ if (is_shutdown_)
+ return false;
+ InitialImportIfNeeded();
+ if (!map_->HasOneRef())
+ map_ = map_->DeepCopy();
+ bool success = map_->SetItem(key, value, old_value);
+ if (success && backing_) {
+ CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
+ commit_batch->changed_values[key] = base::NullableString16(value, false);
+ }
+ return success;
+}
+
+bool DOMStorageArea::RemoveItem(const base::string16& key,
+ base::string16* old_value) {
+ if (is_shutdown_)
+ return false;
+ InitialImportIfNeeded();
+ if (!map_->HasOneRef())
+ map_ = map_->DeepCopy();
+ bool success = map_->RemoveItem(key, old_value);
+ if (success && backing_) {
+ CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
+ commit_batch->changed_values[key] = base::NullableString16();
+ }
+ return success;
+}
+
+bool DOMStorageArea::Clear() {
+ if (is_shutdown_)
+ return false;
+ InitialImportIfNeeded();
+ if (map_->Length() == 0)
+ return false;
+
+ map_ = new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance);
+
+ if (backing_) {
+ CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
+ commit_batch->clear_all_first = true;
+ commit_batch->changed_values.clear();
+ }
+
+ return true;
+}
+
+void DOMStorageArea::FastClear() {
+ // TODO(marja): Unify clearing localStorage and sessionStorage. The problem is
+ // to make the following 3 to work together: 1) FastClear, 2) PurgeMemory and
+ // 3) not creating events when clearing an empty area.
+ if (is_shutdown_)
+ return;
+
+ map_ = new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance);
+ // This ensures no import will happen while we're waiting to clear the data
+ // from the database. This mechanism fails if PurgeMemory is called.
+ is_initial_import_done_ = true;
+
+ if (backing_) {
+ CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
+ commit_batch->clear_all_first = true;
+ commit_batch->changed_values.clear();
+ }
+}
+
+DOMStorageArea* DOMStorageArea::ShallowCopy(
+ int64 destination_namespace_id,
+ const std::string& destination_persistent_namespace_id) {
+ DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
+ DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
+
+ DOMStorageArea* copy = new DOMStorageArea(
+ destination_namespace_id, destination_persistent_namespace_id, origin_,
+ session_storage_backing_.get(), task_runner_.get());
+ copy->map_ = map_;
+ copy->is_shutdown_ = is_shutdown_;
+ copy->is_initial_import_done_ = true;
+
+ // All the uncommitted changes to this area need to happen before the actual
+ // shallow copy is made (scheduled by the upper layer). Another OnCommitTimer
+ // call might be in the event queue at this point, but it's handled gracefully
+ // when it fires.
+ if (commit_batch_)
+ OnCommitTimer();
+ return copy;
+}
+
+bool DOMStorageArea::HasUncommittedChanges() const {
+ DCHECK(!is_shutdown_);
+ return commit_batch_.get() || commit_batches_in_flight_;
+}
+
+void DOMStorageArea::DeleteOrigin() {
+ DCHECK(!is_shutdown_);
+ // This function shouldn't be called for sessionStorage.
+ DCHECK(!session_storage_backing_.get());
+ if (HasUncommittedChanges()) {
+ // TODO(michaeln): This logically deletes the data immediately,
+ // and in a matter of a second, deletes the rows from the backing
+ // database file, but the file itself will linger until shutdown
+ // or purge time. Ideally, this should delete the file more
+ // quickly.
+ Clear();
+ return;
+ }
+ map_ = new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance);
+ if (backing_) {
+ is_initial_import_done_ = false;
+ backing_->Reset();
+ backing_->DeleteFiles();
+ }
+}
+
+void DOMStorageArea::PurgeMemory() {
+ DCHECK(!is_shutdown_);
+ // Purging sessionStorage is not supported; it won't work with FastClear.
+ DCHECK(!session_storage_backing_.get());
+ if (!is_initial_import_done_ || // We're not using any memory.
+ !backing_.get() || // We can't purge anything.
+ HasUncommittedChanges()) // We leave things alone with changes pending.
+ return;
+
+ // Drop the in memory cache, we'll reload when needed.
+ is_initial_import_done_ = false;
+ map_ = new DOMStorageMap(kPerStorageAreaQuota +
+ kPerStorageAreaOverQuotaAllowance);
+
+ // Recreate the database object, this frees up the open sqlite connection
+ // and its page cache.
+ backing_->Reset();
+}
+
+void DOMStorageArea::Shutdown() {
+ DCHECK(!is_shutdown_);
+ is_shutdown_ = true;
+ map_ = NULL;
+ if (!backing_)
+ return;
+
+ bool success = task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(&DOMStorageArea::ShutdownInCommitSequence, this));
+ DCHECK(success);
+}
+
+void DOMStorageArea::InitialImportIfNeeded() {
+ if (is_initial_import_done_)
+ return;
+
+ DCHECK(backing_.get());
+
+ base::TimeTicks before = base::TimeTicks::Now();
+ DOMStorageValuesMap initial_values;
+ backing_->ReadAllValues(&initial_values);
+ map_->SwapValues(&initial_values);
+ is_initial_import_done_ = true;
+ base::TimeDelta time_to_import = base::TimeTicks::Now() - before;
+ UMA_HISTOGRAM_TIMES("LocalStorage.BrowserTimeToPrimeLocalStorage",
+ time_to_import);
+
+ size_t local_storage_size_kb = map_->bytes_used() / 1024;
+ // Track localStorage size, from 0-6MB. Note that the maximum size should be
+ // 5MB, but we add some slop since we want to make sure the max size is always
+ // above what we see in practice, since histograms can't change.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageSizeInKB",
+ local_storage_size_kb,
+ 0, 6 * 1024, 50);
+ if (local_storage_size_kb < 100) {
+ UMA_HISTOGRAM_TIMES(
+ "LocalStorage.BrowserTimeToPrimeLocalStorageUnder100KB",
+ time_to_import);
+ } else if (local_storage_size_kb < 1000) {
+ UMA_HISTOGRAM_TIMES(
+ "LocalStorage.BrowserTimeToPrimeLocalStorage100KBTo1MB",
+ time_to_import);
+ } else {
+ UMA_HISTOGRAM_TIMES(
+ "LocalStorage.BrowserTimeToPrimeLocalStorage1MBTo5MB",
+ time_to_import);
+ }
+}
+
+DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() {
+ DCHECK(!is_shutdown_);
+ if (!commit_batch_) {
+ commit_batch_.reset(new CommitBatch());
+
+ // Start a timer to commit any changes that accrue in the batch, but only if
+ // no commits are currently in flight. In that case the timer will be
+ // started after the commits have happened.
+ if (!commit_batches_in_flight_) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageArea::OnCommitTimer, this),
+ base::TimeDelta::FromSeconds(kCommitTimerSeconds));
+ }
+ }
+ return commit_batch_.get();
+}
+
+void DOMStorageArea::OnCommitTimer() {
+ if (is_shutdown_)
+ return;
+
+ DCHECK(backing_.get());
+
+ // It's possible that there is nothing to commit, since a shallow copy occured
+ // before the timer fired.
+ if (!commit_batch_)
+ return;
+
+ // This method executes on the primary sequence, we schedule
+ // a task for immediate execution on the commit sequence.
+ DCHECK(task_runner_->IsRunningOnPrimarySequence());
+ bool success = task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(&DOMStorageArea::CommitChanges, this,
+ base::Owned(commit_batch_.release())));
+ ++commit_batches_in_flight_;
+ DCHECK(success);
+}
+
+void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) {
+ // This method executes on the commit sequence.
+ DCHECK(task_runner_->IsRunningOnCommitSequence());
+ bool success = backing_->CommitChanges(commit_batch->clear_all_first,
+ commit_batch->changed_values);
+ DCHECK(success); // TODO(michaeln): what if it fails?
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageArea::OnCommitComplete, this));
+}
+
+void DOMStorageArea::OnCommitComplete() {
+ // We're back on the primary sequence in this method.
+ DCHECK(task_runner_->IsRunningOnPrimarySequence());
+ --commit_batches_in_flight_;
+ if (is_shutdown_)
+ return;
+ if (commit_batch_.get() && !commit_batches_in_flight_) {
+ // More changes have accrued, restart the timer.
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageArea::OnCommitTimer, this),
+ base::TimeDelta::FromSeconds(kCommitTimerSeconds));
+ }
+}
+
+void DOMStorageArea::ShutdownInCommitSequence() {
+ // This method executes on the commit sequence.
+ DCHECK(task_runner_->IsRunningOnCommitSequence());
+ DCHECK(backing_.get());
+ if (commit_batch_) {
+ // Commit any changes that accrued prior to the timer firing.
+ bool success = backing_->CommitChanges(
+ commit_batch_->clear_all_first,
+ commit_batch_->changed_values);
+ DCHECK(success);
+ }
+ commit_batch_.reset();
+ backing_.reset();
+ session_storage_backing_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_area.h b/chromium/content/browser/dom_storage/dom_storage_area.h
new file mode 100644
index 00000000000..ca28be1aa14
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_area.h
@@ -0,0 +1,138 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_AREA_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_AREA_H_
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/nullable_string16.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class DOMStorageDatabaseAdapter;
+class DOMStorageMap;
+class DOMStorageTaskRunner;
+class SessionStorageDatabase;
+
+// Container for a per-origin Map of key/value pairs potentially
+// backed by storage on disk and lazily commits changes to disk.
+// See class comments for DOMStorageContextImpl for a larger overview.
+class CONTENT_EXPORT DOMStorageArea
+ : public base::RefCountedThreadSafe<DOMStorageArea> {
+
+ public:
+ static const base::FilePath::CharType kDatabaseFileExtension[];
+ static base::FilePath DatabaseFileNameFromOrigin(const GURL& origin);
+ static GURL OriginFromDatabaseFileName(const base::FilePath& file_name);
+
+ // Local storage. Backed on disk if directory is nonempty.
+ DOMStorageArea(const GURL& origin,
+ const base::FilePath& directory,
+ DOMStorageTaskRunner* task_runner);
+
+ // Session storage. Backed on disk if |session_storage_backing| is not NULL.
+ DOMStorageArea(int64 namespace_id,
+ const std::string& persistent_namespace_id,
+ const GURL& origin,
+ SessionStorageDatabase* session_storage_backing,
+ DOMStorageTaskRunner* task_runner);
+
+ const GURL& origin() const { return origin_; }
+ int64 namespace_id() const { return namespace_id_; }
+
+ // Writes a copy of the current set of values in the area to the |map|.
+ void ExtractValues(DOMStorageValuesMap* map);
+
+ unsigned Length();
+ base::NullableString16 Key(unsigned index);
+ base::NullableString16 GetItem(const base::string16& key);
+ bool SetItem(const base::string16& key, const base::string16& value,
+ base::NullableString16* old_value);
+ bool RemoveItem(const base::string16& key, base::string16* old_value);
+ bool Clear();
+ void FastClear();
+
+ DOMStorageArea* ShallowCopy(
+ int64 destination_namespace_id,
+ const std::string& destination_persistent_namespace_id);
+
+ bool HasUncommittedChanges() const;
+
+ // Similar to Clear() but more optimized for just deleting
+ // without raising events.
+ void DeleteOrigin();
+
+ // Frees up memory when possible. Typically, this method returns
+ // the object to its just constructed state, however if uncommitted
+ // changes are pending, it does nothing.
+ void PurgeMemory();
+
+ // Schedules the commit of any unsaved changes and enters a
+ // shutdown state such that the value getters and setters will
+ // no longer do anything.
+ void Shutdown();
+
+ // Returns true if the data is loaded in memory.
+ bool IsLoadedInMemory() const { return is_initial_import_done_; }
+
+ private:
+ friend class DOMStorageAreaTest;
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, DOMStorageAreaBasics);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, BackingDatabaseOpened);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, TestDatabaseFilePath);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, CommitTasks);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, CommitChangesAtShutdown);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, DeleteOrigin);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, PurgeMemory);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageContextImplTest, PersistentIds);
+ friend class base::RefCountedThreadSafe<DOMStorageArea>;
+
+ struct CommitBatch {
+ bool clear_all_first;
+ DOMStorageValuesMap changed_values;
+ CommitBatch();
+ ~CommitBatch();
+ };
+
+ ~DOMStorageArea();
+
+ // If we haven't done so already and this is a local storage area,
+ // will attempt to read any values for this origin currently
+ // stored on disk.
+ void InitialImportIfNeeded();
+
+ // Post tasks to defer writing a batch of changed values to
+ // disk on the commit sequence, and to call back on the primary
+ // task sequence when complete.
+ CommitBatch* CreateCommitBatchIfNeeded();
+ void OnCommitTimer();
+ void CommitChanges(const CommitBatch* commit_batch);
+ void OnCommitComplete();
+
+ void ShutdownInCommitSequence();
+
+ int64 namespace_id_;
+ std::string persistent_namespace_id_;
+ GURL origin_;
+ base::FilePath directory_;
+ scoped_refptr<DOMStorageTaskRunner> task_runner_;
+ scoped_refptr<DOMStorageMap> map_;
+ scoped_ptr<DOMStorageDatabaseAdapter> backing_;
+ scoped_refptr<SessionStorageDatabase> session_storage_backing_;
+ bool is_initial_import_done_;
+ bool is_shutdown_;
+ scoped_ptr<CommitBatch> commit_batch_;
+ int commit_batches_in_flight_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_AREA_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_area_unittest.cc b/chromium/content/browser/dom_storage/dom_storage_area_unittest.cc
new file mode 100644
index 00000000000..697f6250b1d
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_area_unittest.cc
@@ -0,0 +1,474 @@
+// 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 "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_database.h"
+#include "content/browser/dom_storage/dom_storage_database_adapter.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/browser/dom_storage/local_storage_database_adapter.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+
+class DOMStorageAreaTest : public testing::Test {
+ public:
+ DOMStorageAreaTest()
+ : kOrigin(GURL("http://dom_storage/")),
+ kKey(ASCIIToUTF16("key")),
+ kValue(ASCIIToUTF16("value")),
+ kKey2(ASCIIToUTF16("key2")),
+ kValue2(ASCIIToUTF16("value2")) {
+ }
+
+ const GURL kOrigin;
+ const base::string16 kKey;
+ const base::string16 kValue;
+ const base::string16 kKey2;
+ const base::string16 kValue2;
+
+ // Method used in the CommitTasks test case.
+ void InjectedCommitSequencingTask(DOMStorageArea* area) {
+ // At this point the OnCommitTimer has run.
+ // Verify that it put a commit in flight.
+ EXPECT_EQ(1, area->commit_batches_in_flight_);
+ EXPECT_FALSE(area->commit_batch_.get());
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ // Make additional change and verify that a new commit batch
+ // is created for that change.
+ base::NullableString16 old_value;
+ EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_value));
+ EXPECT_TRUE(area->commit_batch_.get());
+ EXPECT_EQ(1, area->commit_batches_in_flight_);
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ }
+
+ // Class used in the CommitChangesAtShutdown test case.
+ class VerifyChangesCommittedDatabase : public DOMStorageDatabase {
+ public:
+ VerifyChangesCommittedDatabase() {}
+ virtual ~VerifyChangesCommittedDatabase() {
+ const base::string16 kKey(ASCIIToUTF16("key"));
+ const base::string16 kValue(ASCIIToUTF16("value"));
+ DOMStorageValuesMap values;
+ ReadAllValues(&values);
+ EXPECT_EQ(1u, values.size());
+ EXPECT_EQ(kValue, values[kKey].string());
+ }
+ };
+
+ private:
+ base::MessageLoop message_loop_;
+};
+
+TEST_F(DOMStorageAreaTest, DOMStorageAreaBasics) {
+ scoped_refptr<DOMStorageArea> area(
+ new DOMStorageArea(1, std::string(), kOrigin, NULL, NULL));
+ base::string16 old_value;
+ base::NullableString16 old_nullable_value;
+ scoped_refptr<DOMStorageArea> copy;
+
+ // We don't focus on the underlying DOMStorageMap functionality
+ // since that's covered by seperate unit tests.
+ EXPECT_EQ(kOrigin, area->origin());
+ EXPECT_EQ(1, area->namespace_id());
+ EXPECT_EQ(0u, area->Length());
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_nullable_value));
+ EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_nullable_value));
+ EXPECT_FALSE(area->HasUncommittedChanges());
+
+ // Verify that a copy shares the same map.
+ copy = area->ShallowCopy(2, std::string());
+ EXPECT_EQ(kOrigin, copy->origin());
+ EXPECT_EQ(2, copy->namespace_id());
+ EXPECT_EQ(area->Length(), copy->Length());
+ EXPECT_EQ(area->GetItem(kKey).string(), copy->GetItem(kKey).string());
+ EXPECT_EQ(area->Key(0).string(), copy->Key(0).string());
+ EXPECT_EQ(copy->map_.get(), area->map_.get());
+
+ // But will deep copy-on-write as needed.
+ EXPECT_TRUE(area->RemoveItem(kKey, &old_value));
+ EXPECT_NE(copy->map_.get(), area->map_.get());
+ copy = area->ShallowCopy(2, std::string());
+ EXPECT_EQ(copy->map_.get(), area->map_.get());
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_nullable_value));
+ EXPECT_NE(copy->map_.get(), area->map_.get());
+ copy = area->ShallowCopy(2, std::string());
+ EXPECT_EQ(copy->map_.get(), area->map_.get());
+ EXPECT_NE(0u, area->Length());
+ EXPECT_TRUE(area->Clear());
+ EXPECT_EQ(0u, area->Length());
+ EXPECT_NE(copy->map_.get(), area->map_.get());
+
+ // Verify that once Shutdown(), behaves that way.
+ area->Shutdown();
+ EXPECT_TRUE(area->is_shutdown_);
+ EXPECT_FALSE(area->map_.get());
+ EXPECT_EQ(0u, area->Length());
+ EXPECT_TRUE(area->Key(0).is_null());
+ EXPECT_TRUE(area->GetItem(kKey).is_null());
+ EXPECT_FALSE(area->SetItem(kKey, kValue, &old_nullable_value));
+ EXPECT_FALSE(area->RemoveItem(kKey, &old_value));
+ EXPECT_FALSE(area->Clear());
+}
+
+TEST_F(DOMStorageAreaTest, BackingDatabaseOpened) {
+ const int64 kSessionStorageNamespaceId = kLocalStorageNamespaceId + 1;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ const base::FilePath kExpectedOriginFilePath = temp_dir.path().Append(
+ DOMStorageArea::DatabaseFileNameFromOrigin(kOrigin));
+
+ // No directory, backing should be null.
+ {
+ scoped_refptr<DOMStorageArea> area(
+ new DOMStorageArea(kOrigin, base::FilePath(), NULL));
+ EXPECT_EQ(NULL, area->backing_.get());
+ EXPECT_TRUE(area->is_initial_import_done_);
+ EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath));
+ }
+
+ // Valid directory and origin but no session storage backing. Backing should
+ // be null.
+ {
+ scoped_refptr<DOMStorageArea> area(
+ new DOMStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin,
+ NULL, NULL));
+ EXPECT_EQ(NULL, area->backing_.get());
+ EXPECT_TRUE(area->is_initial_import_done_);
+
+ base::NullableString16 old_value;
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
+ ASSERT_TRUE(old_value.is_null());
+
+ // Check that saving a value has still left us without a backing database.
+ EXPECT_EQ(NULL, area->backing_.get());
+ EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath));
+ }
+
+ // This should set up a DOMStorageArea that is correctly backed to disk.
+ {
+ scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
+ kOrigin,
+ temp_dir.path(),
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
+
+ EXPECT_TRUE(area->backing_.get());
+ DOMStorageDatabase* database = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_FALSE(database->IsOpen());
+ EXPECT_FALSE(area->is_initial_import_done_);
+
+ // Inject an in-memory db to speed up the test.
+ // We will verify that something is written into the database but not
+ // that a file is written to disk - DOMStorageDatabase unit tests cover
+ // that.
+ area->backing_.reset(new LocalStorageDatabaseAdapter());
+
+ // Need to write something to ensure that the database is created.
+ base::NullableString16 old_value;
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
+ ASSERT_TRUE(old_value.is_null());
+ EXPECT_TRUE(area->is_initial_import_done_);
+ EXPECT_TRUE(area->commit_batch_.get());
+ EXPECT_EQ(0, area->commit_batches_in_flight_);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(area->commit_batch_.get());
+ EXPECT_EQ(0, area->commit_batches_in_flight_);
+ database = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_TRUE(database->IsOpen());
+ EXPECT_EQ(1u, area->Length());
+ EXPECT_EQ(kValue, area->GetItem(kKey).string());
+
+ // Verify the content made it to the in memory database.
+ DOMStorageValuesMap values;
+ area->backing_->ReadAllValues(&values);
+ EXPECT_EQ(1u, values.size());
+ EXPECT_EQ(kValue, values[kKey].string());
+ }
+}
+
+TEST_F(DOMStorageAreaTest, CommitTasks) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
+ kOrigin,
+ temp_dir.path(),
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
+ // Inject an in-memory db to speed up the test.
+ area->backing_.reset(new LocalStorageDatabaseAdapter());
+
+ // Unrelated to commits, but while we're here, see that querying Length()
+ // causes the backing database to be opened and presumably read from.
+ EXPECT_FALSE(area->is_initial_import_done_);
+ EXPECT_EQ(0u, area->Length());
+ EXPECT_TRUE(area->is_initial_import_done_);
+
+ DOMStorageValuesMap values;
+ base::NullableString16 old_value;
+
+ // See that changes are batched up.
+ EXPECT_FALSE(area->commit_batch_.get());
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ EXPECT_TRUE(area->commit_batch_.get());
+ EXPECT_FALSE(area->commit_batch_->clear_all_first);
+ EXPECT_EQ(1u, area->commit_batch_->changed_values.size());
+ EXPECT_TRUE(area->SetItem(kKey2, kValue2, &old_value));
+ EXPECT_TRUE(area->commit_batch_.get());
+ EXPECT_FALSE(area->commit_batch_->clear_all_first);
+ EXPECT_EQ(2u, area->commit_batch_->changed_values.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(area->HasUncommittedChanges());
+ EXPECT_FALSE(area->commit_batch_.get());
+ EXPECT_EQ(0, area->commit_batches_in_flight_);
+ // Verify the changes made it to the database.
+ values.clear();
+ area->backing_->ReadAllValues(&values);
+ EXPECT_EQ(2u, values.size());
+ EXPECT_EQ(kValue, values[kKey].string());
+ EXPECT_EQ(kValue2, values[kKey2].string());
+
+ // See that clear is handled properly.
+ EXPECT_TRUE(area->Clear());
+ EXPECT_TRUE(area->commit_batch_.get());
+ EXPECT_TRUE(area->commit_batch_->clear_all_first);
+ EXPECT_TRUE(area->commit_batch_->changed_values.empty());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(area->commit_batch_.get());
+ EXPECT_EQ(0, area->commit_batches_in_flight_);
+ // Verify the changes made it to the database.
+ values.clear();
+ area->backing_->ReadAllValues(&values);
+ EXPECT_TRUE(values.empty());
+
+ // See that if changes accrue while a commit is "in flight"
+ // those will also get committed.
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ // At this point the OnCommitTimer task has been posted. We inject
+ // another task in the queue that will execute after the timer task,
+ // but before the CommitChanges task. From within our injected task,
+ // we'll make an additional SetItem() call.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageAreaTest::InjectedCommitSequencingTask,
+ base::Unretained(this),
+ area));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(area->HasOneRef());
+ EXPECT_FALSE(area->HasUncommittedChanges());
+ // Verify the changes made it to the database.
+ values.clear();
+ area->backing_->ReadAllValues(&values);
+ EXPECT_EQ(2u, values.size());
+ EXPECT_EQ(kValue, values[kKey].string());
+ EXPECT_EQ(kValue2, values[kKey2].string());
+}
+
+TEST_F(DOMStorageAreaTest, CommitChangesAtShutdown) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
+ kOrigin,
+ temp_dir.path(),
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
+
+ // Inject an in-memory db to speed up the test and also to verify
+ // the final changes are commited in it's dtor.
+ static_cast<LocalStorageDatabaseAdapter*>(area->backing_.get())->db_.reset(
+ new VerifyChangesCommittedDatabase());
+
+ DOMStorageValuesMap values;
+ base::NullableString16 old_value;
+ EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ area->backing_->ReadAllValues(&values);
+ EXPECT_TRUE(values.empty()); // not committed yet
+ area->Shutdown();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(area->HasOneRef());
+ EXPECT_FALSE(area->backing_.get());
+ // The VerifyChangesCommittedDatabase destructor verifies values
+ // were committed.
+}
+
+TEST_F(DOMStorageAreaTest, DeleteOrigin) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
+ kOrigin,
+ temp_dir.path(),
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
+
+ // This test puts files on disk.
+ base::FilePath db_file_path = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_->file_path();
+ base::FilePath db_journal_file_path =
+ DOMStorageDatabase::GetJournalFilePath(db_file_path);
+
+ // Nothing bad should happen when invoked w/o any files on disk.
+ area->DeleteOrigin();
+ EXPECT_FALSE(base::PathExists(db_file_path));
+
+ // Commit something in the database and then delete.
+ base::NullableString16 old_value;
+ area->SetItem(kKey, kValue, &old_value);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(db_file_path));
+ area->DeleteOrigin();
+ EXPECT_EQ(0u, area->Length());
+ EXPECT_FALSE(base::PathExists(db_file_path));
+ EXPECT_FALSE(base::PathExists(db_journal_file_path));
+
+ // Put some uncommitted changes to a non-existing database in
+ // and then delete. No file ever gets created in this case.
+ area->SetItem(kKey, kValue, &old_value);
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ EXPECT_EQ(1u, area->Length());
+ area->DeleteOrigin();
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ EXPECT_EQ(0u, area->Length());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(area->HasUncommittedChanges());
+ EXPECT_FALSE(base::PathExists(db_file_path));
+
+ // Put some uncommitted changes to a an existing database in
+ // and then delete.
+ area->SetItem(kKey, kValue, &old_value);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(db_file_path));
+ area->SetItem(kKey2, kValue2, &old_value);
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ EXPECT_EQ(2u, area->Length());
+ area->DeleteOrigin();
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ EXPECT_EQ(0u, area->Length());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(area->HasUncommittedChanges());
+ // Since the area had uncommitted changes at the time delete
+ // was called, the file will linger until the shutdown time.
+ EXPECT_TRUE(base::PathExists(db_file_path));
+ area->Shutdown();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(base::PathExists(db_file_path));
+}
+
+TEST_F(DOMStorageAreaTest, PurgeMemory) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
+ kOrigin,
+ temp_dir.path(),
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
+
+ // Inject an in-memory db to speed up the test.
+ area->backing_.reset(new LocalStorageDatabaseAdapter());
+
+ // Unowned ptrs we use to verify that 'purge' has happened.
+ DOMStorageDatabase* original_backing =
+ static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ DOMStorageMap* original_map = area->map_.get();
+
+ // Should do no harm when called on a newly constructed object.
+ EXPECT_FALSE(area->is_initial_import_done_);
+ area->PurgeMemory();
+ EXPECT_FALSE(area->is_initial_import_done_);
+ DOMStorageDatabase* new_backing = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_EQ(original_backing, new_backing);
+ EXPECT_EQ(original_map, area->map_.get());
+
+ // Should not do anything when commits are pending.
+ base::NullableString16 old_value;
+ area->SetItem(kKey, kValue, &old_value);
+ EXPECT_TRUE(area->is_initial_import_done_);
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ area->PurgeMemory();
+ EXPECT_TRUE(area->is_initial_import_done_);
+ EXPECT_TRUE(area->HasUncommittedChanges());
+ new_backing = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_EQ(original_backing, new_backing);
+ EXPECT_EQ(original_map, area->map_.get());
+
+ // Commit the changes from above,
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(area->HasUncommittedChanges());
+ new_backing = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_EQ(original_backing, new_backing);
+ EXPECT_EQ(original_map, area->map_.get());
+
+ // Should drop caches and reset database connections
+ // when invoked on an area that's loaded up primed.
+ area->PurgeMemory();
+ EXPECT_FALSE(area->is_initial_import_done_);
+ new_backing = static_cast<LocalStorageDatabaseAdapter*>(
+ area->backing_.get())->db_.get();
+ EXPECT_NE(original_backing, new_backing);
+ EXPECT_NE(original_map, area->map_.get());
+}
+
+TEST_F(DOMStorageAreaTest, DatabaseFileNames) {
+ struct {
+ const char* origin;
+ const char* file_name;
+ const char* journal_file_name;
+ } kCases[] = {
+ { "https://www.google.com/",
+ "https_www.google.com_0.localstorage",
+ "https_www.google.com_0.localstorage-journal" },
+ { "http://www.google.com:8080/",
+ "http_www.google.com_8080.localstorage",
+ "http_www.google.com_8080.localstorage-journal" },
+ { "file:///",
+ "file__0.localstorage",
+ "file__0.localstorage-journal" },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCases); ++i) {
+ GURL origin = GURL(kCases[i].origin).GetOrigin();
+ base::FilePath file_name =
+ base::FilePath().AppendASCII(kCases[i].file_name);
+ base::FilePath journal_file_name =
+ base::FilePath().AppendASCII(kCases[i].journal_file_name);
+
+ EXPECT_EQ(file_name,
+ DOMStorageArea::DatabaseFileNameFromOrigin(origin));
+ EXPECT_EQ(origin,
+ DOMStorageArea::OriginFromDatabaseFileName(file_name));
+ EXPECT_EQ(journal_file_name,
+ DOMStorageDatabase::GetJournalFilePath(file_name));
+ }
+
+ // Also test some DOMStorageDatabase::GetJournalFilePath cases here.
+ base::FilePath parent = base::FilePath().AppendASCII("a").AppendASCII("b");
+ EXPECT_EQ(
+ parent.AppendASCII("file-journal"),
+ DOMStorageDatabase::GetJournalFilePath(parent.AppendASCII("file")));
+ EXPECT_EQ(
+ base::FilePath().AppendASCII("-journal"),
+ DOMStorageDatabase::GetJournalFilePath(base::FilePath()));
+ EXPECT_EQ(
+ base::FilePath().AppendASCII(".extensiononly-journal"),
+ DOMStorageDatabase::GetJournalFilePath(
+ base::FilePath().AppendASCII(".extensiononly")));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_browsertest.cc b/chromium/content/browser/dom_storage/dom_storage_browsertest.cc
new file mode 100644
index 00000000000..79955a8bf17
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_browsertest.cc
@@ -0,0 +1,52 @@
+// 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 "base/path_service.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/base/net_util.h"
+
+namespace content {
+
+// This browser test is aimed towards exercising the DOMStorage system
+// from end-to-end.
+class DOMStorageBrowserTest : public ContentBrowserTest {
+ public:
+ DOMStorageBrowserTest() {}
+
+ void SimpleTest(const GURL& test_url, bool incognito) {
+ // The test page will perform tests then navigate to either
+ // a #pass or #fail ref.
+ Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell();
+ NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2);
+ std::string result =
+ the_browser->web_contents()->GetLastCommittedURL().ref();
+ if (result != "pass") {
+ std::string js_result;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ the_browser->web_contents(),
+ "window.domAutomationController.send(getLog())",
+ &js_result));
+ FAIL() << "Failed: " << js_result;
+ }
+ }
+};
+
+static const bool kIncognito = true;
+static const bool kNotIncognito = false;
+
+IN_PROC_BROWSER_TEST_F(DOMStorageBrowserTest, SanityCheck) {
+ SimpleTest(GetTestUrl("dom_storage", "sanity_check.html"), kNotIncognito);
+}
+
+IN_PROC_BROWSER_TEST_F(DOMStorageBrowserTest, SanityCheckIncognito) {
+ SimpleTest(GetTestUrl("dom_storage", "sanity_check.html"), kIncognito);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_context_impl.cc b/chromium/content/browser/dom_storage/dom_storage_context_impl.cc
new file mode 100644
index 00000000000..dd1f33e847f
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_context_impl.cc
@@ -0,0 +1,422 @@
+// 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 "content/browser/dom_storage/dom_storage_context_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/time/time.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_database.h"
+#include "content/browser/dom_storage/dom_storage_namespace.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/browser/dom_storage/session_storage_database.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "content/public/browser/dom_storage_context.h"
+#include "content/public/browser/local_storage_usage_info.h"
+#include "content/public/browser/session_storage_usage_info.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+namespace content {
+
+static const int kSessionStoraceScavengingSeconds = 60;
+
+DOMStorageContextImpl::DOMStorageContextImpl(
+ const base::FilePath& localstorage_directory,
+ const base::FilePath& sessionstorage_directory,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ DOMStorageTaskRunner* task_runner)
+ : localstorage_directory_(localstorage_directory),
+ sessionstorage_directory_(sessionstorage_directory),
+ task_runner_(task_runner),
+ is_shutdown_(false),
+ force_keep_session_state_(false),
+ special_storage_policy_(special_storage_policy),
+ scavenging_started_(false) {
+ // AtomicSequenceNum starts at 0 but we want to start session
+ // namespace ids at one since zero is reserved for the
+ // kLocalStorageNamespaceId.
+ session_id_sequence_.GetNext();
+}
+
+DOMStorageContextImpl::~DOMStorageContextImpl() {
+ if (session_storage_database_.get()) {
+ // SessionStorageDatabase shouldn't be deleted right away: deleting it will
+ // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting
+ // shouldn't happen on this thread.
+ SessionStorageDatabase* to_release = session_storage_database_.get();
+ to_release->AddRef();
+ session_storage_database_ = NULL;
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(&SessionStorageDatabase::Release,
+ base::Unretained(to_release)));
+ }
+}
+
+DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace(
+ int64 namespace_id) {
+ if (is_shutdown_)
+ return NULL;
+ StorageNamespaceMap::iterator found = namespaces_.find(namespace_id);
+ if (found == namespaces_.end()) {
+ if (namespace_id == kLocalStorageNamespaceId) {
+ if (!localstorage_directory_.empty()) {
+ if (!file_util::CreateDirectory(localstorage_directory_)) {
+ LOG(ERROR) << "Failed to create 'Local Storage' directory,"
+ " falling back to in-memory only.";
+ localstorage_directory_ = base::FilePath();
+ }
+ }
+ DOMStorageNamespace* local =
+ new DOMStorageNamespace(localstorage_directory_, task_runner_.get());
+ namespaces_[kLocalStorageNamespaceId] = local;
+ return local;
+ }
+ return NULL;
+ }
+ return found->second.get();
+}
+
+void DOMStorageContextImpl::GetLocalStorageUsage(
+ std::vector<LocalStorageUsageInfo>* infos,
+ bool include_file_info) {
+ if (localstorage_directory_.empty())
+ return;
+ base::FileEnumerator enumerator(localstorage_directory_, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) {
+ LocalStorageUsageInfo info;
+ info.origin = DOMStorageArea::OriginFromDatabaseFileName(path);
+ if (include_file_info) {
+ base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
+ info.data_size = find_info.GetSize();
+ info.last_modified = find_info.GetLastModifiedTime();
+ }
+ infos->push_back(info);
+ }
+ }
+}
+
+void DOMStorageContextImpl::GetSessionStorageUsage(
+ std::vector<SessionStorageUsageInfo>* infos) {
+ if (!session_storage_database_.get())
+ return;
+ std::map<std::string, std::vector<GURL> > namespaces_and_origins;
+ session_storage_database_->ReadNamespacesAndOrigins(
+ &namespaces_and_origins);
+ for (std::map<std::string, std::vector<GURL> >::const_iterator it =
+ namespaces_and_origins.begin();
+ it != namespaces_and_origins.end(); ++it) {
+ for (std::vector<GURL>::const_iterator origin_it = it->second.begin();
+ origin_it != it->second.end(); ++origin_it) {
+ SessionStorageUsageInfo info;
+ info.persistent_namespace_id = it->first;
+ info.origin = *origin_it;
+ infos->push_back(info);
+ }
+ }
+}
+
+void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin) {
+ DCHECK(!is_shutdown_);
+ DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
+ local->DeleteLocalStorageOrigin(origin);
+ // Synthesize a 'cleared' event if the area is open so CachedAreas in
+ // renderers get emptied out too.
+ DOMStorageArea* area = local->GetOpenStorageArea(origin);
+ if (area)
+ NotifyAreaCleared(area, origin);
+}
+
+void DOMStorageContextImpl::DeleteSessionStorage(
+ const SessionStorageUsageInfo& usage_info) {
+ DCHECK(!is_shutdown_);
+ DOMStorageNamespace* dom_storage_namespace = NULL;
+ std::map<std::string, int64>::const_iterator it =
+ persistent_namespace_id_to_namespace_id_.find(
+ usage_info.persistent_namespace_id);
+ if (it != persistent_namespace_id_to_namespace_id_.end()) {
+ dom_storage_namespace = GetStorageNamespace(it->second);
+ } else {
+ int64 namespace_id = AllocateSessionId();
+ CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id);
+ dom_storage_namespace = GetStorageNamespace(namespace_id);
+ }
+ dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin);
+ // Synthesize a 'cleared' event if the area is open so CachedAreas in
+ // renderers get emptied out too.
+ DOMStorageArea* area =
+ dom_storage_namespace->GetOpenStorageArea(usage_info.origin);
+ if (area)
+ NotifyAreaCleared(area, usage_info.origin);
+}
+
+void DOMStorageContextImpl::PurgeMemory() {
+ // We can only purge memory from the local storage namespace
+ // which is backed by disk.
+ // TODO(marja): Purge sessionStorage, too. (Requires changes to the FastClear
+ // functionality.)
+ StorageNamespaceMap::iterator found =
+ namespaces_.find(kLocalStorageNamespaceId);
+ if (found != namespaces_.end())
+ found->second->PurgeMemory(DOMStorageNamespace::PURGE_AGGRESSIVE);
+}
+
+void DOMStorageContextImpl::Shutdown() {
+ is_shutdown_ = true;
+ StorageNamespaceMap::const_iterator it = namespaces_.begin();
+ for (; it != namespaces_.end(); ++it)
+ it->second->Shutdown();
+
+ if (localstorage_directory_.empty() && !session_storage_database_.get())
+ return;
+
+ // Respect the content policy settings about what to
+ // keep and what to discard.
+ if (force_keep_session_state_)
+ return; // Keep everything.
+
+ bool has_session_only_origins =
+ special_storage_policy_.get() &&
+ special_storage_policy_->HasSessionOnlyOrigins();
+
+ if (has_session_only_origins) {
+ // We may have to delete something. We continue on the
+ // commit sequence after area shutdown tasks have cycled
+ // thru that sequence (and closed their database files).
+ bool success = task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
+ DCHECK(success);
+ }
+}
+
+void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
+ event_observers_.AddObserver(observer);
+}
+
+void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
+ event_observers_.RemoveObserver(observer);
+}
+
+void DOMStorageContextImpl::NotifyItemSet(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& new_value,
+ const base::NullableString16& old_value,
+ const GURL& page_url) {
+ FOR_EACH_OBSERVER(
+ EventObserver, event_observers_,
+ OnDOMStorageItemSet(area, key, new_value, old_value, page_url));
+}
+
+void DOMStorageContextImpl::NotifyItemRemoved(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& old_value,
+ const GURL& page_url) {
+ FOR_EACH_OBSERVER(
+ EventObserver, event_observers_,
+ OnDOMStorageItemRemoved(area, key, old_value, page_url));
+}
+
+void DOMStorageContextImpl::NotifyAreaCleared(
+ const DOMStorageArea* area,
+ const GURL& page_url) {
+ FOR_EACH_OBSERVER(
+ EventObserver, event_observers_,
+ OnDOMStorageAreaCleared(area, page_url));
+}
+
+std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
+ std::string guid = base::GenerateGUID();
+ std::replace(guid.begin(), guid.end(), '-', '_');
+ return guid;
+}
+
+void DOMStorageContextImpl::CreateSessionNamespace(
+ int64 namespace_id,
+ const std::string& persistent_namespace_id) {
+ if (is_shutdown_)
+ return;
+ DCHECK(namespace_id != kLocalStorageNamespaceId);
+ DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
+ namespaces_[namespace_id] = new DOMStorageNamespace(
+ namespace_id, persistent_namespace_id, session_storage_database_.get(),
+ task_runner_.get());
+ persistent_namespace_id_to_namespace_id_[persistent_namespace_id] =
+ namespace_id;
+}
+
+void DOMStorageContextImpl::DeleteSessionNamespace(
+ int64 namespace_id, bool should_persist_data) {
+ DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
+ StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
+ if (it == namespaces_.end())
+ return;
+ std::string persistent_namespace_id = it->second->persistent_namespace_id();
+ if (session_storage_database_.get()) {
+ if (!should_persist_data) {
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
+ session_storage_database_,
+ persistent_namespace_id));
+ } else {
+ // Ensure that the data gets committed before we shut down.
+ it->second->Shutdown();
+ if (!scavenging_started_) {
+ // Protect the persistent namespace ID from scavenging.
+ protected_persistent_session_ids_.insert(persistent_namespace_id);
+ }
+ }
+ }
+ persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
+ namespaces_.erase(namespace_id);
+}
+
+void DOMStorageContextImpl::CloneSessionNamespace(
+ int64 existing_id, int64 new_id,
+ const std::string& new_persistent_id) {
+ if (is_shutdown_)
+ return;
+ DCHECK_NE(kLocalStorageNamespaceId, existing_id);
+ DCHECK_NE(kLocalStorageNamespaceId, new_id);
+ StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
+ if (found != namespaces_.end())
+ namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id);
+ else
+ CreateSessionNamespace(new_id, new_persistent_id);
+}
+
+void DOMStorageContextImpl::ClearSessionOnlyOrigins() {
+ if (!localstorage_directory_.empty()) {
+ std::vector<LocalStorageUsageInfo> infos;
+ const bool kDontIncludeFileInfo = false;
+ GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
+ for (size_t i = 0; i < infos.size(); ++i) {
+ const GURL& origin = infos[i].origin;
+ if (special_storage_policy_->IsStorageProtected(origin))
+ continue;
+ if (!special_storage_policy_->IsStorageSessionOnly(origin))
+ continue;
+
+ base::FilePath database_file_path = localstorage_directory_.Append(
+ DOMStorageArea::DatabaseFileNameFromOrigin(origin));
+ sql::Connection::Delete(database_file_path);
+ }
+ }
+ if (session_storage_database_.get()) {
+ std::vector<SessionStorageUsageInfo> infos;
+ GetSessionStorageUsage(&infos);
+ for (size_t i = 0; i < infos.size(); ++i) {
+ const GURL& origin = infos[i].origin;
+ if (special_storage_policy_->IsStorageProtected(origin))
+ continue;
+ if (!special_storage_policy_->IsStorageSessionOnly(origin))
+ continue;
+ session_storage_database_->DeleteArea(infos[i].persistent_namespace_id,
+ origin);
+ }
+ }
+}
+
+void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
+ DCHECK(namespaces_.empty());
+ if (!sessionstorage_directory_.empty()) {
+ session_storage_database_ = new SessionStorageDatabase(
+ sessionstorage_directory_);
+ }
+}
+
+void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
+ if (session_storage_database_.get()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces,
+ this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
+ }
+}
+
+void DOMStorageContextImpl::FindUnusedNamespaces() {
+ DCHECK(session_storage_database_.get());
+ if (scavenging_started_)
+ return;
+ scavenging_started_ = true;
+ std::set<std::string> namespace_ids_in_use;
+ for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
+ it != namespaces_.end(); ++it)
+ namespace_ids_in_use.insert(it->second->persistent_namespace_id());
+ std::set<std::string> protected_persistent_session_ids;
+ protected_persistent_session_ids.swap(protected_persistent_session_ids_);
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
+ this, namespace_ids_in_use, protected_persistent_session_ids));
+}
+
+void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence(
+ const std::set<std::string>& namespace_ids_in_use,
+ const std::set<std::string>& protected_persistent_session_ids) {
+ DCHECK(session_storage_database_.get());
+ // Delete all namespaces which don't have an associated DOMStorageNamespace
+ // alive.
+ std::map<std::string, std::vector<GURL> > namespaces_and_origins;
+ session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins);
+ for (std::map<std::string, std::vector<GURL> >::const_iterator it =
+ namespaces_and_origins.begin();
+ it != namespaces_and_origins.end(); ++it) {
+ if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() &&
+ protected_persistent_session_ids.find(it->first) ==
+ protected_persistent_session_ids.end()) {
+ deletable_persistent_namespace_ids_.push_back(it->first);
+ }
+ }
+ if (!deletable_persistent_namespace_ids_.empty()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(
+ &DOMStorageContextImpl::DeleteNextUnusedNamespace,
+ this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
+ }
+}
+
+void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
+ if (is_shutdown_)
+ return;
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(
+ &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
+ this));
+}
+
+void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
+ if (deletable_persistent_namespace_ids_.empty())
+ return;
+ const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
+ session_storage_database_->DeleteNamespace(persistent_id);
+ deletable_persistent_namespace_ids_.pop_back();
+ if (!deletable_persistent_namespace_ids_.empty()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(
+ &DOMStorageContextImpl::DeleteNextUnusedNamespace,
+ this),
+ base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_context_impl.h b/chromium/content/browser/dom_storage/dom_storage_context_impl.h
new file mode 100644
index 00000000000..8f55656cb5b
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_context_impl.h
@@ -0,0 +1,234 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_IMPL_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_IMPL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/atomic_sequence_num.h"
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+class NullableString16;
+class Time;
+}
+
+namespace quota {
+class SpecialStoragePolicy;
+}
+
+namespace content {
+
+class DOMStorageArea;
+class DOMStorageNamespace;
+class DOMStorageSession;
+class DOMStorageTaskRunner;
+class SessionStorageDatabase;
+struct LocalStorageUsageInfo;
+struct SessionStorageUsageInfo;
+
+// The Context is the root of an object containment hierachy for
+// Namespaces and Areas related to the owning profile.
+// One instance is allocated in the main process for each profile,
+// instance methods should be called serially in the background as
+// determined by the task_runner. Specifcally not on chrome's non-blocking
+// IO thread since these methods can result in blocking file io.
+//
+// In general terms, the DOMStorage object relationships are...
+// Contexts (per-profile) own Namespaces which own Areas which share Maps.
+// Hosts(per-renderer) refer to Namespaces and Areas open in its renderer.
+// Sessions (per-tab) cause the creation and deletion of session Namespaces.
+//
+// Session Namespaces are cloned by initially making a shallow copy of
+// all contained Areas, the shallow copies refer to the same refcounted Map,
+// and does a deep copy-on-write if needed.
+//
+// Classes intended to be used by an embedder are DOMStorageContextImpl,
+// DOMStorageHost, and DOMStorageSession. The other classes are for
+// internal consumption.
+class CONTENT_EXPORT DOMStorageContextImpl
+ : public base::RefCountedThreadSafe<DOMStorageContextImpl> {
+ public:
+ // An interface for observing Local and Session Storage events on the
+ // background thread.
+ class EventObserver {
+ public:
+ // |old_value| may be null on initial insert.
+ virtual void OnDOMStorageItemSet(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& new_value,
+ const base::NullableString16& old_value,
+ const GURL& page_url) = 0;
+ virtual void OnDOMStorageItemRemoved(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& old_value,
+ const GURL& page_url) = 0;
+ virtual void OnDOMStorageAreaCleared(
+ const DOMStorageArea* area,
+ const GURL& page_url) = 0;
+
+ protected:
+ virtual ~EventObserver() {}
+ };
+
+ // |localstorage_directory| and |sessionstorage_directory| may be empty
+ // for incognito browser contexts.
+ DOMStorageContextImpl(
+ const base::FilePath& localstorage_directory,
+ const base::FilePath& sessionstorage_directory,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ DOMStorageTaskRunner* task_runner);
+
+ // Returns the directory path for localStorage, or an empty directory, if
+ // there is no backing on disk.
+ const base::FilePath& localstorage_directory() {
+ return localstorage_directory_;
+ }
+
+ // Returns the directory path for sessionStorage, or an empty directory, if
+ // there is no backing on disk.
+ const base::FilePath& sessionstorage_directory() {
+ return sessionstorage_directory_;
+ }
+
+ DOMStorageTaskRunner* task_runner() const { return task_runner_.get(); }
+ DOMStorageNamespace* GetStorageNamespace(int64 namespace_id);
+
+ void GetLocalStorageUsage(std::vector<LocalStorageUsageInfo>* infos,
+ bool include_file_info);
+ void GetSessionStorageUsage(std::vector<SessionStorageUsageInfo>* infos);
+ void DeleteLocalStorage(const GURL& origin);
+ void DeleteSessionStorage(const SessionStorageUsageInfo& usage_info);
+ void PurgeMemory();
+
+ // Used by content settings to alter the behavior around
+ // what data to keep and what data to discard at shutdown.
+ // The policy is not so straight forward to describe, see
+ // the implementation for details.
+ void SetForceKeepSessionState() {
+ force_keep_session_state_ = true;
+ }
+
+ // Called when the owning BrowserContext is ending.
+ // Schedules the commit of any unsaved changes and will delete
+ // and keep data on disk per the content settings and special storage
+ // policies. Contained areas and namespaces will stop functioning after
+ // this method has been called.
+ void Shutdown();
+
+ // Methods to add, remove, and notify EventObservers.
+ void AddEventObserver(EventObserver* observer);
+ void RemoveEventObserver(EventObserver* observer);
+ void NotifyItemSet(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& new_value,
+ const base::NullableString16& old_value,
+ const GURL& page_url);
+ void NotifyItemRemoved(
+ const DOMStorageArea* area,
+ const base::string16& key,
+ const base::string16& old_value,
+ const GURL& page_url);
+ void NotifyAreaCleared(
+ const DOMStorageArea* area,
+ const GURL& page_url);
+
+ // May be called on any thread.
+ int64 AllocateSessionId() {
+ return session_id_sequence_.GetNext();
+ }
+
+ std::string AllocatePersistentSessionId();
+
+ // Must be called on the background thread.
+ void CreateSessionNamespace(int64 namespace_id,
+ const std::string& persistent_namespace_id);
+ void DeleteSessionNamespace(int64 namespace_id, bool should_persist_data);
+ void CloneSessionNamespace(int64 existing_id, int64 new_id,
+ const std::string& new_persistent_id);
+
+ // Starts backing sessionStorage on disk. This function must be called right
+ // after DOMStorageContextImpl is created, before it's used.
+ void SetSaveSessionStorageOnDisk();
+
+ // Deletes all namespaces which don't have an associated DOMStorageNamespace
+ // alive. This function is used for deleting possible leftover data after an
+ // unclean exit.
+ void StartScavengingUnusedSessionStorage();
+
+ private:
+ friend class DOMStorageContextImplTest;
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageContextImplTest, Basics);
+ friend class base::RefCountedThreadSafe<DOMStorageContextImpl>;
+ typedef std::map<int64, scoped_refptr<DOMStorageNamespace> >
+ StorageNamespaceMap;
+
+ ~DOMStorageContextImpl();
+
+ void ClearSessionOnlyOrigins();
+
+ // For scavenging unused sessionStorages.
+ void FindUnusedNamespaces();
+ void FindUnusedNamespacesInCommitSequence(
+ const std::set<std::string>& namespace_ids_in_use,
+ const std::set<std::string>& protected_persistent_session_ids);
+ void DeleteNextUnusedNamespace();
+ void DeleteNextUnusedNamespaceInCommitSequence();
+
+ // Collection of namespaces keyed by id.
+ StorageNamespaceMap namespaces_;
+
+ // Where localstorage data is stored, maybe empty for the incognito use case.
+ base::FilePath localstorage_directory_;
+
+ // Where sessionstorage data is stored, maybe empty for the incognito use
+ // case. Always empty until the file-backed session storage feature is
+ // implemented.
+ base::FilePath sessionstorage_directory_;
+
+ // Used to schedule sequenced background tasks.
+ scoped_refptr<DOMStorageTaskRunner> task_runner_;
+
+ // List of objects observing local storage events.
+ ObserverList<EventObserver> event_observers_;
+
+ // We use a 32 bit identifier for per tab storage sessions.
+ // At a tab per second, this range is large enough for 68 years.
+ base::AtomicSequenceNumber session_id_sequence_;
+
+ bool is_shutdown_;
+ bool force_keep_session_state_;
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ scoped_refptr<SessionStorageDatabase> session_storage_database_;
+
+ // For cleaning up unused namespaces gradually.
+ bool scavenging_started_;
+ std::vector<std::string> deletable_persistent_namespace_ids_;
+
+ // Persistent namespace IDs to protect from gradual deletion (they will
+ // be needed for session restore).
+ std::set<std::string> protected_persistent_session_ids_;
+
+ // Mapping between persistent namespace IDs and namespace IDs for
+ // sessionStorage.
+ std::map<std::string, int64> persistent_namespace_id_to_namespace_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_IMPL_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_context_impl_unittest.cc b/chromium/content/browser/dom_storage/dom_storage_context_impl_unittest.cc
new file mode 100644
index 00000000000..27aaefb24d3
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_context_impl_unittest.cc
@@ -0,0 +1,263 @@
+// 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 "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_context_impl.h"
+#include "content/browser/dom_storage/dom_storage_namespace.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/public/browser/local_storage_usage_info.h"
+#include "content/public/browser/session_storage_usage_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+
+namespace content {
+
+class DOMStorageContextImplTest : public testing::Test {
+ public:
+ DOMStorageContextImplTest()
+ : kOrigin(GURL("http://dom_storage/")),
+ kKey(ASCIIToUTF16("key")),
+ kValue(ASCIIToUTF16("value")),
+ kDontIncludeFileInfo(false),
+ kDoIncludeFileInfo(true) {
+ }
+
+ const GURL kOrigin;
+ const base::string16 kKey;
+ const base::string16 kValue;
+ const bool kDontIncludeFileInfo;
+ const bool kDoIncludeFileInfo;
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ storage_policy_ = new quota::MockSpecialStoragePolicy;
+ task_runner_ =
+ new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get());
+ context_ = new DOMStorageContextImpl(temp_dir_.path(),
+ base::FilePath(),
+ storage_policy_.get(),
+ task_runner_.get());
+ }
+
+ virtual void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void VerifySingleOriginRemains(const GURL& origin) {
+ // Use a new instance to examine the contexts of temp_dir_.
+ scoped_refptr<DOMStorageContextImpl> context =
+ new DOMStorageContextImpl(temp_dir_.path(), base::FilePath(),
+ NULL, NULL);
+ std::vector<LocalStorageUsageInfo> infos;
+ context->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
+ ASSERT_EQ(1u, infos.size());
+ EXPECT_EQ(origin, infos[0].origin);
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy_;
+ scoped_refptr<MockDOMStorageTaskRunner> task_runner_;
+ scoped_refptr<DOMStorageContextImpl> context_;
+ DISALLOW_COPY_AND_ASSIGN(DOMStorageContextImplTest);
+};
+
+TEST_F(DOMStorageContextImplTest, Basics) {
+ // This test doesn't do much, checks that the constructor
+ // initializes members properly and that invoking methods
+ // on a newly created object w/o any data on disk do no harm.
+ EXPECT_EQ(temp_dir_.path(), context_->localstorage_directory());
+ EXPECT_EQ(base::FilePath(), context_->sessionstorage_directory());
+ EXPECT_EQ(storage_policy_.get(), context_->special_storage_policy_.get());
+ context_->PurgeMemory();
+ context_->DeleteLocalStorage(GURL("http://chromium.org/"));
+ const int kFirstSessionStorageNamespaceId = 1;
+ EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId));
+ EXPECT_FALSE(context_->GetStorageNamespace(kFirstSessionStorageNamespaceId));
+ EXPECT_EQ(kFirstSessionStorageNamespaceId, context_->AllocateSessionId());
+ std::vector<LocalStorageUsageInfo> infos;
+ context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
+ EXPECT_TRUE(infos.empty());
+ context_->Shutdown();
+}
+
+TEST_F(DOMStorageContextImplTest, UsageInfo) {
+ // Should be empty initially
+ std::vector<LocalStorageUsageInfo> infos;
+ context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
+ EXPECT_TRUE(infos.empty());
+ context_->GetLocalStorageUsage(&infos, kDoIncludeFileInfo);
+ EXPECT_TRUE(infos.empty());
+
+ // Put some data into local storage and shutdown the context
+ // to ensure data is written to disk.
+ base::NullableString16 old_value;
+ EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
+ OpenStorageArea(kOrigin)->SetItem(kKey, kValue, &old_value));
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Create a new context that points to the same directory, see that
+ // it knows about the origin that we stored data for.
+ context_ = new DOMStorageContextImpl(temp_dir_.path(), base::FilePath(),
+ NULL, NULL);
+ context_->GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
+ EXPECT_EQ(1u, infos.size());
+ EXPECT_EQ(kOrigin, infos[0].origin);
+ EXPECT_EQ(0u, infos[0].data_size);
+ EXPECT_EQ(base::Time(), infos[0].last_modified);
+ infos.clear();
+ context_->GetLocalStorageUsage(&infos, kDoIncludeFileInfo);
+ EXPECT_EQ(1u, infos.size());
+ EXPECT_EQ(kOrigin, infos[0].origin);
+ EXPECT_NE(0u, infos[0].data_size);
+ EXPECT_NE(base::Time(), infos[0].last_modified);
+}
+
+TEST_F(DOMStorageContextImplTest, SessionOnly) {
+ const GURL kSessionOnlyOrigin("http://www.sessiononly.com/");
+ storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
+
+ // Store data for a normal and a session-only origin and then
+ // invoke Shutdown() which should delete data for session-only
+ // origins.
+ base::NullableString16 old_value;
+ EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
+ OpenStorageArea(kOrigin)->SetItem(kKey, kValue, &old_value));
+ EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
+ OpenStorageArea(kSessionOnlyOrigin)->SetItem(kKey, kValue, &old_value));
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify that the session-only origin data is gone.
+ VerifySingleOriginRemains(kOrigin);
+}
+
+TEST_F(DOMStorageContextImplTest, SetForceKeepSessionState) {
+ const GURL kSessionOnlyOrigin("http://www.sessiononly.com/");
+ storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
+
+ // Store data for a session-only origin, setup to save session data, then
+ // shutdown.
+ base::NullableString16 old_value;
+ EXPECT_TRUE(context_->GetStorageNamespace(kLocalStorageNamespaceId)->
+ OpenStorageArea(kSessionOnlyOrigin)->SetItem(kKey, kValue, &old_value));
+ context_->SetForceKeepSessionState(); // Should override clear behavior.
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+
+ VerifySingleOriginRemains(kSessionOnlyOrigin);
+}
+
+TEST_F(DOMStorageContextImplTest, PersistentIds) {
+ const int kFirstSessionStorageNamespaceId = 1;
+ const std::string kPersistentId = "persistent";
+ context_->CreateSessionNamespace(kFirstSessionStorageNamespaceId,
+ kPersistentId);
+ DOMStorageNamespace* dom_namespace =
+ context_->GetStorageNamespace(kFirstSessionStorageNamespaceId);
+ ASSERT_TRUE(dom_namespace);
+ EXPECT_EQ(kPersistentId, dom_namespace->persistent_namespace_id());
+ // Verify that the areas inherit the persistent ID.
+ DOMStorageArea* area = dom_namespace->OpenStorageArea(kOrigin);
+ EXPECT_EQ(kPersistentId, area->persistent_namespace_id_);
+
+ // Verify that the persistent IDs are handled correctly when cloning.
+ const int kClonedSessionStorageNamespaceId = 2;
+ const std::string kClonedPersistentId = "cloned";
+ context_->CloneSessionNamespace(kFirstSessionStorageNamespaceId,
+ kClonedSessionStorageNamespaceId,
+ kClonedPersistentId);
+ DOMStorageNamespace* cloned_dom_namespace =
+ context_->GetStorageNamespace(kClonedSessionStorageNamespaceId);
+ ASSERT_TRUE(dom_namespace);
+ EXPECT_EQ(kClonedPersistentId,
+ cloned_dom_namespace->persistent_namespace_id());
+ // Verify that the areas inherit the persistent ID.
+ DOMStorageArea* cloned_area = cloned_dom_namespace->OpenStorageArea(kOrigin);
+ EXPECT_EQ(kClonedPersistentId, cloned_area->persistent_namespace_id_);
+}
+
+TEST_F(DOMStorageContextImplTest, DeleteSessionStorage) {
+ // Create a DOMStorageContextImpl which will save sessionStorage on disk.
+ context_ = new DOMStorageContextImpl(temp_dir_.path(),
+ temp_dir_.path(),
+ storage_policy_.get(),
+ task_runner_.get());
+ context_->SetSaveSessionStorageOnDisk();
+ ASSERT_EQ(temp_dir_.path(), context_->sessionstorage_directory());
+
+ // Write data.
+ const int kSessionStorageNamespaceId = 1;
+ const std::string kPersistentId = "persistent";
+ context_->CreateSessionNamespace(kSessionStorageNamespaceId,
+ kPersistentId);
+ DOMStorageNamespace* dom_namespace =
+ context_->GetStorageNamespace(kSessionStorageNamespaceId);
+ DOMStorageArea* area = dom_namespace->OpenStorageArea(kOrigin);
+ const base::string16 kKey(ASCIIToUTF16("foo"));
+ const base::string16 kValue(ASCIIToUTF16("bar"));
+ base::NullableString16 old_nullable_value;
+ area->SetItem(kKey, kValue, &old_nullable_value);
+ dom_namespace->CloseStorageArea(area);
+
+ // Destroy and recreate the DOMStorageContextImpl.
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ context_ = new DOMStorageContextImpl(
+ temp_dir_.path(), temp_dir_.path(),
+ storage_policy_.get(), task_runner_.get());
+ context_->SetSaveSessionStorageOnDisk();
+
+ // Read the data back.
+ context_->CreateSessionNamespace(kSessionStorageNamespaceId,
+ kPersistentId);
+ dom_namespace = context_->GetStorageNamespace(kSessionStorageNamespaceId);
+ area = dom_namespace->OpenStorageArea(kOrigin);
+ base::NullableString16 read_value;
+ read_value = area->GetItem(kKey);
+ EXPECT_EQ(kValue, read_value.string());
+ dom_namespace->CloseStorageArea(area);
+
+ SessionStorageUsageInfo info;
+ info.origin = kOrigin;
+ info.persistent_namespace_id = kPersistentId;
+ context_->DeleteSessionStorage(info);
+
+ // Destroy and recreate again.
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ context_ = new DOMStorageContextImpl(
+ temp_dir_.path(), temp_dir_.path(),
+ storage_policy_.get(), task_runner_.get());
+ context_->SetSaveSessionStorageOnDisk();
+
+ // Now there should be no data.
+ context_->CreateSessionNamespace(kSessionStorageNamespaceId,
+ kPersistentId);
+ dom_namespace = context_->GetStorageNamespace(kSessionStorageNamespaceId);
+ area = dom_namespace->OpenStorageArea(kOrigin);
+ read_value = area->GetItem(kKey);
+ EXPECT_TRUE(read_value.is_null());
+ dom_namespace->CloseStorageArea(area);
+ context_->Shutdown();
+ context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_context_wrapper.cc b/chromium/content/browser/dom_storage/dom_storage_context_wrapper.cc
new file mode 100644
index 00000000000..56ea51e3d2a
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_context_wrapper.cc
@@ -0,0 +1,173 @@
+// 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 "content/browser/dom_storage/dom_storage_context_wrapper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_context_impl.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/local_storage_usage_info.h"
+#include "content/public/browser/session_storage_usage_info.h"
+
+namespace content {
+namespace {
+
+const char kLocalStorageDirectory[] = "Local Storage";
+const char kSessionStorageDirectory[] = "Session Storage";
+
+void InvokeLocalStorageUsageCallbackHelper(
+ const DOMStorageContext::GetLocalStorageUsageCallback& callback,
+ const std::vector<LocalStorageUsageInfo>* infos) {
+ callback.Run(*infos);
+}
+
+void GetLocalStorageUsageHelper(
+ base::MessageLoopProxy* reply_loop,
+ DOMStorageContextImpl* context,
+ const DOMStorageContext::GetLocalStorageUsageCallback& callback) {
+ std::vector<LocalStorageUsageInfo>* infos =
+ new std::vector<LocalStorageUsageInfo>;
+ context->GetLocalStorageUsage(infos, true);
+ reply_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&InvokeLocalStorageUsageCallbackHelper,
+ callback, base::Owned(infos)));
+}
+
+void InvokeSessionStorageUsageCallbackHelper(
+ const DOMStorageContext::GetSessionStorageUsageCallback& callback,
+ const std::vector<SessionStorageUsageInfo>* infos) {
+ callback.Run(*infos);
+}
+
+void GetSessionStorageUsageHelper(
+ base::MessageLoopProxy* reply_loop,
+ DOMStorageContextImpl* context,
+ const DOMStorageContext::GetSessionStorageUsageCallback& callback) {
+ std::vector<SessionStorageUsageInfo>* infos =
+ new std::vector<SessionStorageUsageInfo>;
+ context->GetSessionStorageUsage(infos);
+ reply_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&InvokeSessionStorageUsageCallbackHelper,
+ callback, base::Owned(infos)));
+}
+
+} // namespace
+
+DOMStorageContextWrapper::DOMStorageContextWrapper(
+ const base::FilePath& data_path,
+ quota::SpecialStoragePolicy* special_storage_policy) {
+ base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
+ context_ = new DOMStorageContextImpl(
+ data_path.empty() ? data_path
+ : data_path.AppendASCII(kLocalStorageDirectory),
+ data_path.empty() ? data_path
+ : data_path.AppendASCII(kSessionStorageDirectory),
+ special_storage_policy,
+ new DOMStorageWorkerPoolTaskRunner(
+ worker_pool,
+ worker_pool->GetNamedSequenceToken("dom_storage_primary"),
+ worker_pool->GetNamedSequenceToken("dom_storage_commit"),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)
+ .get()));
+}
+
+DOMStorageContextWrapper::~DOMStorageContextWrapper() {
+}
+
+void DOMStorageContextWrapper::GetLocalStorageUsage(
+ const GetLocalStorageUsageCallback& callback) {
+ DCHECK(context_.get());
+ context_->task_runner()
+ ->PostShutdownBlockingTask(FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&GetLocalStorageUsageHelper,
+ base::MessageLoopProxy::current(),
+ context_,
+ callback));
+}
+
+void DOMStorageContextWrapper::GetSessionStorageUsage(
+ const GetSessionStorageUsageCallback& callback) {
+ DCHECK(context_.get());
+ context_->task_runner()
+ ->PostShutdownBlockingTask(FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&GetSessionStorageUsageHelper,
+ base::MessageLoopProxy::current(),
+ context_,
+ callback));
+}
+
+void DOMStorageContextWrapper::DeleteLocalStorage(const GURL& origin) {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::DeleteLocalStorage, context_, origin));
+}
+
+void DOMStorageContextWrapper::DeleteSessionStorage(
+ const SessionStorageUsageInfo& usage_info) {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::DeleteSessionStorage,
+ context_, usage_info));
+}
+
+void DOMStorageContextWrapper::SetSaveSessionStorageOnDisk() {
+ DCHECK(context_.get());
+ context_->SetSaveSessionStorageOnDisk();
+}
+
+scoped_refptr<SessionStorageNamespace>
+DOMStorageContextWrapper::RecreateSessionStorage(
+ const std::string& persistent_id) {
+ return scoped_refptr<SessionStorageNamespace>(
+ new SessionStorageNamespaceImpl(this, persistent_id));
+}
+
+void DOMStorageContextWrapper::StartScavengingUnusedSessionStorage() {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::StartScavengingUnusedSessionStorage,
+ context_));
+}
+
+void DOMStorageContextWrapper::PurgeMemory() {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::PurgeMemory, context_));
+}
+
+void DOMStorageContextWrapper::SetForceKeepSessionState() {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::SetForceKeepSessionState, context_));
+}
+
+void DOMStorageContextWrapper::Shutdown() {
+ DCHECK(context_.get());
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageContextImpl::Shutdown, context_));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_context_wrapper.h b/chromium/content/browser/dom_storage/dom_storage_context_wrapper.h
new file mode 100644
index 00000000000..0971f889835
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_context_wrapper.h
@@ -0,0 +1,74 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_WRAPPER_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_WRAPPER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/dom_storage_context.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace quota {
+class SpecialStoragePolicy;
+}
+
+namespace content {
+
+class DOMStorageContextImpl;
+
+// This is owned by BrowserContext (aka Profile) and encapsulates all
+// per-profile dom storage state.
+class CONTENT_EXPORT DOMStorageContextWrapper :
+ NON_EXPORTED_BASE(public DOMStorageContext),
+ public base::RefCountedThreadSafe<DOMStorageContextWrapper> {
+ public:
+ // If |data_path| is empty, nothing will be saved to disk.
+ DOMStorageContextWrapper(const base::FilePath& data_path,
+ quota::SpecialStoragePolicy* special_storage_policy);
+
+ // DOMStorageContext implementation.
+ virtual void GetLocalStorageUsage(
+ const GetLocalStorageUsageCallback& callback) OVERRIDE;
+ virtual void GetSessionStorageUsage(
+ const GetSessionStorageUsageCallback& callback) OVERRIDE;
+ virtual void DeleteLocalStorage(const GURL& origin) OVERRIDE;
+ virtual void DeleteSessionStorage(
+ const SessionStorageUsageInfo& usage_info) OVERRIDE;
+ virtual void SetSaveSessionStorageOnDisk() OVERRIDE;
+ virtual scoped_refptr<SessionStorageNamespace>
+ RecreateSessionStorage(const std::string& persistent_id) OVERRIDE;
+ virtual void StartScavengingUnusedSessionStorage() OVERRIDE;
+
+ // Called to free up memory that's not strictly needed.
+ void PurgeMemory();
+
+ // Used by content settings to alter the behavior around
+ // what data to keep and what data to discard at shutdown.
+ // The policy is not so straight forward to describe, see
+ // the implementation for details.
+ void SetForceKeepSessionState();
+
+ // Called when the BrowserContext/Profile is going away.
+ void Shutdown();
+
+ private:
+ friend class DOMStorageMessageFilter; // for access to context()
+ friend class SessionStorageNamespaceImpl; // ditto
+ friend class base::RefCountedThreadSafe<DOMStorageContextWrapper>;
+
+ virtual ~DOMStorageContextWrapper();
+ DOMStorageContextImpl* context() const { return context_.get(); }
+
+ scoped_refptr<DOMStorageContextImpl> context_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DOMStorageContextWrapper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_CONTEXT_WRAPPER_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_database.cc b/chromium/content/browser/dom_storage/dom_storage_database.cc
new file mode 100644
index 00000000000..690d02acf2f
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_database.cc
@@ -0,0 +1,295 @@
+// 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 "content/browser/dom_storage/dom_storage_database.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace {
+
+const base::FilePath::CharType kJournal[] = FILE_PATH_LITERAL("-journal");
+
+} // anon namespace
+
+namespace content {
+
+// static
+base::FilePath DOMStorageDatabase::GetJournalFilePath(
+ const base::FilePath& database_path) {
+ base::FilePath::StringType journal_file_name =
+ database_path.BaseName().value() + kJournal;
+ return database_path.DirName().Append(journal_file_name);
+}
+
+DOMStorageDatabase::DOMStorageDatabase(const base::FilePath& file_path)
+ : file_path_(file_path) {
+ // Note: in normal use we should never get an empty backing path here.
+ // However, the unit test for this class can contruct an instance
+ // with an empty path.
+ Init();
+}
+
+DOMStorageDatabase::DOMStorageDatabase() {
+ Init();
+}
+
+void DOMStorageDatabase::Init() {
+ failed_to_open_ = false;
+ tried_to_recreate_ = false;
+ known_to_be_empty_ = false;
+}
+
+DOMStorageDatabase::~DOMStorageDatabase() {
+ if (known_to_be_empty_ && !file_path_.empty()) {
+ // Delete the db and any lingering journal file from disk.
+ Close();
+ sql::Connection::Delete(file_path_);
+ }
+}
+
+void DOMStorageDatabase::ReadAllValues(DOMStorageValuesMap* result) {
+ if (!LazyOpen(false))
+ return;
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
+ "SELECT * from ItemTable"));
+ DCHECK(statement.is_valid());
+
+ while (statement.Step()) {
+ base::string16 key = statement.ColumnString16(0);
+ base::string16 value;
+ statement.ColumnBlobAsString16(1, &value);
+ (*result)[key] = base::NullableString16(value, false);
+ }
+ known_to_be_empty_ = result->empty();
+}
+
+bool DOMStorageDatabase::CommitChanges(bool clear_all_first,
+ const DOMStorageValuesMap& changes) {
+ if (!LazyOpen(!changes.empty())) {
+ // If we're being asked to commit changes that will result in an
+ // empty database, we return true if the database file doesn't exist.
+ return clear_all_first && changes.empty() &&
+ !base::PathExists(file_path_);
+ }
+
+ bool old_known_to_be_empty = known_to_be_empty_;
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ if (clear_all_first) {
+ if (!db_->Execute("DELETE FROM ItemTable"))
+ return false;
+ known_to_be_empty_ = true;
+ }
+
+ bool did_delete = false;
+ bool did_insert = false;
+ DOMStorageValuesMap::const_iterator it = changes.begin();
+ for(; it != changes.end(); ++it) {
+ sql::Statement statement;
+ base::string16 key = it->first;
+ base::NullableString16 value = it->second;
+ if (value.is_null()) {
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM ItemTable WHERE key=?"));
+ statement.BindString16(0, key);
+ did_delete = true;
+ } else {
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO ItemTable VALUES (?,?)"));
+ statement.BindString16(0, key);
+ statement.BindBlob(1, value.string().data(),
+ value.string().length() * sizeof(char16));
+ known_to_be_empty_ = false;
+ did_insert = true;
+ }
+ DCHECK(statement.is_valid());
+ statement.Run();
+ }
+
+ if (!known_to_be_empty_ && did_delete && !did_insert) {
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
+ "SELECT count(key) from ItemTable"));
+ if (statement.Step())
+ known_to_be_empty_ = statement.ColumnInt(0) == 0;
+ }
+
+ bool success = transaction.Commit();
+ if (!success)
+ known_to_be_empty_ = old_known_to_be_empty;
+ return success;
+}
+
+bool DOMStorageDatabase::LazyOpen(bool create_if_needed) {
+ if (failed_to_open_) {
+ // Don't try to open a database that we know has failed
+ // already.
+ return false;
+ }
+
+ if (IsOpen())
+ return true;
+
+ bool database_exists = base::PathExists(file_path_);
+
+ if (!database_exists && !create_if_needed) {
+ // If the file doesn't exist already and we haven't been asked to create
+ // a file on disk, then we don't bother opening the database. This means
+ // we wait until we absolutely need to put something onto disk before we
+ // do so.
+ return false;
+ }
+
+ db_.reset(new sql::Connection());
+ db_->set_histogram_tag("DOMStorageDatabase");
+
+ if (file_path_.empty()) {
+ // This code path should only be triggered by unit tests.
+ if (!db_->OpenInMemory()) {
+ NOTREACHED() << "Unable to open DOM storage database in memory.";
+ failed_to_open_ = true;
+ return false;
+ }
+ } else {
+ if (!db_->Open(file_path_)) {
+ LOG(ERROR) << "Unable to open DOM storage database at "
+ << file_path_.value()
+ << " error: " << db_->GetErrorMessage();
+ if (database_exists && !tried_to_recreate_)
+ return DeleteFileAndRecreate();
+ failed_to_open_ = true;
+ return false;
+ }
+ }
+
+ // sql::Connection uses UTF-8 encoding, but WebCore style databases use
+ // UTF-16, so ensure we match.
+ ignore_result(db_->Execute("PRAGMA encoding=\"UTF-16\""));
+
+ if (!database_exists) {
+ // This is a new database, create the table and we're done!
+ if (CreateTableV2())
+ return true;
+ } else {
+ // The database exists already - check if we need to upgrade
+ // and whether it's usable (i.e. not corrupted).
+ SchemaVersion current_version = DetectSchemaVersion();
+
+ if (current_version == V2) {
+ return true;
+ } else if (current_version == V1) {
+ if (UpgradeVersion1To2())
+ return true;
+ }
+ }
+
+ // This is the exceptional case - to try and recover we'll attempt
+ // to delete the file and start again.
+ Close();
+ return DeleteFileAndRecreate();
+}
+
+DOMStorageDatabase::SchemaVersion DOMStorageDatabase::DetectSchemaVersion() {
+ DCHECK(IsOpen());
+
+ // Connection::Open() may succeed even if the file we try and open is not a
+ // database, however in the case that the database is corrupted to the point
+ // that SQLite doesn't actually think it's a database,
+ // sql::Connection::GetCachedStatement will DCHECK when we later try and
+ // run statements. So we run a query here that will not DCHECK but fail
+ // on an invalid database to verify that what we've opened is usable.
+ if (db_->ExecuteAndReturnErrorCode("PRAGMA auto_vacuum") != SQLITE_OK)
+ return INVALID;
+
+ // Look at the current schema - if it doesn't look right, assume corrupt.
+ if (!db_->DoesTableExist("ItemTable") ||
+ !db_->DoesColumnExist("ItemTable", "key") ||
+ !db_->DoesColumnExist("ItemTable", "value"))
+ return INVALID;
+
+ // We must use a unique statement here as we aren't going to step it.
+ sql::Statement statement(
+ db_->GetUniqueStatement("SELECT key,value from ItemTable LIMIT 1"));
+ if (statement.DeclaredColumnType(0) != sql::COLUMN_TYPE_TEXT)
+ return INVALID;
+
+ switch (statement.DeclaredColumnType(1)) {
+ case sql::COLUMN_TYPE_BLOB:
+ return V2;
+ case sql::COLUMN_TYPE_TEXT:
+ return V1;
+ default:
+ return INVALID;
+ }
+ NOTREACHED();
+ return INVALID;
+}
+
+bool DOMStorageDatabase::CreateTableV2() {
+ DCHECK(IsOpen());
+
+ return db_->Execute(
+ "CREATE TABLE ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value BLOB NOT NULL ON CONFLICT FAIL)");
+}
+
+bool DOMStorageDatabase::DeleteFileAndRecreate() {
+ DCHECK(!IsOpen());
+ DCHECK(base::PathExists(file_path_));
+
+ // We should only try and do this once.
+ if (tried_to_recreate_)
+ return false;
+
+ tried_to_recreate_ = true;
+
+ // If it's not a directory and we can delete the file, try and open it again.
+ if (!base::DirectoryExists(file_path_) &&
+ sql::Connection::Delete(file_path_)) {
+ return LazyOpen(true);
+ }
+
+ failed_to_open_ = true;
+ return false;
+}
+
+bool DOMStorageDatabase::UpgradeVersion1To2() {
+ DCHECK(IsOpen());
+ DCHECK(DetectSchemaVersion() == V1);
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
+ "SELECT * FROM ItemTable"));
+ DCHECK(statement.is_valid());
+
+ // Need to migrate from TEXT value column to BLOB.
+ // Store the current database content so we can re-insert
+ // the data into the new V2 table.
+ DOMStorageValuesMap values;
+ while (statement.Step()) {
+ base::string16 key = statement.ColumnString16(0);
+ base::NullableString16 value(statement.ColumnString16(1), false);
+ values[key] = value;
+ }
+
+ sql::Transaction migration(db_.get());
+ return migration.Begin() &&
+ db_->Execute("DROP TABLE ItemTable") &&
+ CreateTableV2() &&
+ CommitChanges(false, values) &&
+ migration.Commit();
+}
+
+void DOMStorageDatabase::Close() {
+ db_.reset(NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_database.h b/chromium/content/browser/dom_storage/dom_storage_database.h
new file mode 100644
index 00000000000..72f5d737372
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_database.h
@@ -0,0 +1,119 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+
+#include <map>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/nullable_string16.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "sql/connection.h"
+
+namespace content {
+
+// Represents a SQLite based backing for DOM storage data. This
+// class is designed to be used on a single thread.
+class CONTENT_EXPORT DOMStorageDatabase {
+ public:
+ static base::FilePath GetJournalFilePath(const base::FilePath& database_path);
+
+ explicit DOMStorageDatabase(const base::FilePath& file_path);
+ virtual ~DOMStorageDatabase(); // virtual for unit testing
+
+ // Reads all the key, value pairs stored in the database and returns
+ // them. |result| is assumed to be empty and any duplicate keys will
+ // be overwritten. If the database exists on disk then it will be
+ // opened. If it does not exist then it will not be created and
+ // |result| will be unmodified.
+ void ReadAllValues(DOMStorageValuesMap* result);
+
+ // Updates the backing database. Will remove all keys before updating
+ // the database if |clear_all_first| is set. Then all entries in
+ // |changes| will be examined - keys mapped to a null NullableString16
+ // will be removed and all others will be inserted/updated as appropriate.
+ bool CommitChanges(bool clear_all_first, const DOMStorageValuesMap& changes);
+
+ // Simple getter for the path we were constructed with.
+ const base::FilePath& file_path() const { return file_path_; }
+
+ protected:
+ // Constructor that uses an in-memory sqlite database, for testing.
+ DOMStorageDatabase();
+
+ private:
+ friend class LocalStorageDatabaseAdapter;
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, SimpleOpenAndClose);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, TestLazyOpenIsLazy);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, TestDetectSchemaVersion);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest,
+ TestLazyOpenUpgradesDatabase);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, SimpleWriteAndReadBack);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, WriteWithClear);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest,
+ UpgradeFromV1ToV2WithData);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest, TestSimpleRemoveOneValue);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest,
+ TestCanOpenAndReadWebCoreDatabase);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageDatabaseTest,
+ TestCanOpenFileThatIsNotADatabase);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, BackingDatabaseOpened);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, CommitTasks);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, PurgeMemory);
+
+ enum SchemaVersion {
+ INVALID,
+ V1,
+ V2
+ };
+
+ // Open the database at file_path_ if it exists already and creates it if
+ // |create_if_needed| is true.
+ // Ensures we are at the correct database version and creates or updates
+ // tables as necessary. Returns false on failure.
+ bool LazyOpen(bool create_if_needed);
+
+ // Analyses the database to verify that the connection that is open is indeed
+ // a valid database and works out the schema version.
+ SchemaVersion DetectSchemaVersion();
+
+ // Creates the database table at V2. Returns true if the table was created
+ // successfully, false otherwise. Will return false if the table already
+ // exists.
+ bool CreateTableV2();
+
+ // If we have issues while trying to open the file (corrupted databse,
+ // failing to upgrade, that sort of thing) this function will remove
+ // the file from disk and attempt to create a new database from
+ // scratch.
+ bool DeleteFileAndRecreate();
+
+ // Version 1 -> 2 migrates the value column in the ItemTable from a TEXT
+ // to a BLOB. Exisitng data is preserved on success. Returns false if the
+ // upgrade failed. If true is returned, the database is guaranteed to be at
+ // version 2.
+ bool UpgradeVersion1To2();
+
+ void Close();
+ bool IsOpen() const { return db_.get() ? db_->is_open() : false; }
+
+ // Initialization code shared between the two constructors of this class.
+ void Init();
+
+ // Path to the database on disk.
+ const base::FilePath file_path_;
+ scoped_ptr<sql::Connection> db_;
+ bool failed_to_open_;
+ bool tried_to_recreate_;
+ bool known_to_be_empty_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_database_adapter.h b/chromium/content/browser/dom_storage/dom_storage_database_adapter.h
new file mode 100644
index 00000000000..9068af21f97
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_database_adapter.h
@@ -0,0 +1,29 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_ADAPTER_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_ADAPTER_H_
+
+// Database interface used by DOMStorageArea. Abstracts the differences between
+// the per-origin DOMStorageDatabases for localStorage and
+// SessionStorageDatabase which stores multiple origins.
+
+#include "content/common/content_export.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+
+namespace content {
+
+class CONTENT_EXPORT DOMStorageDatabaseAdapter {
+ public:
+ virtual ~DOMStorageDatabaseAdapter() {}
+ virtual void ReadAllValues(DOMStorageValuesMap* result) = 0;
+ virtual bool CommitChanges(
+ bool clear_all_first, const DOMStorageValuesMap& changes) = 0;
+ virtual void DeleteFiles() {}
+ virtual void Reset() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_DATABASE_ADAPTER_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_database_unittest.cc b/chromium/content/browser/dom_storage/dom_storage_database_unittest.cc
new file mode 100644
index 00000000000..f3ba0b64df7
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_database_unittest.cc
@@ -0,0 +1,393 @@
+// 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 "content/browser/dom_storage/dom_storage_database.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "sql/statement.h"
+#include "sql/test/scoped_error_ignorer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace content {
+
+void CreateV1Table(sql::Connection* db) {
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value TEXT NOT NULL ON CONFLICT FAIL)"));
+}
+
+void CreateV2Table(sql::Connection* db) {
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value BLOB NOT NULL ON CONFLICT FAIL)"));
+}
+
+void CreateInvalidKeyColumnTable(sql::Connection* db) {
+ // Create a table with the key type as FLOAT - this is "invalid"
+ // as far as the DOM Storage db is concerned.
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE IF NOT EXISTS ItemTable ("
+ "key FLOAT UNIQUE ON CONFLICT REPLACE, "
+ "value BLOB NOT NULL ON CONFLICT FAIL)"));
+}
+void CreateInvalidValueColumnTable(sql::Connection* db) {
+ // Create a table with the value type as FLOAT - this is "invalid"
+ // as far as the DOM Storage db is concerned.
+ ASSERT_TRUE(db->is_open());
+ ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
+ ASSERT_TRUE(db->Execute(
+ "CREATE TABLE IF NOT EXISTS ItemTable ("
+ "key TEXT UNIQUE ON CONFLICT REPLACE, "
+ "value FLOAT NOT NULL ON CONFLICT FAIL)"));
+}
+
+void InsertDataV1(sql::Connection* db,
+ const base::string16& key,
+ const base::string16& value) {
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO ItemTable VALUES (?,?)"));
+ statement.BindString16(0, key);
+ statement.BindString16(1, value);
+ ASSERT_TRUE(statement.is_valid());
+ statement.Run();
+}
+
+void CheckValuesMatch(DOMStorageDatabase* db,
+ const DOMStorageValuesMap& expected) {
+ DOMStorageValuesMap values_read;
+ db->ReadAllValues(&values_read);
+ EXPECT_EQ(expected.size(), values_read.size());
+
+ DOMStorageValuesMap::const_iterator it = values_read.begin();
+ for (; it != values_read.end(); ++it) {
+ base::string16 key = it->first;
+ base::NullableString16 value = it->second;
+ base::NullableString16 expected_value = expected.find(key)->second;
+ EXPECT_EQ(expected_value.string(), value.string());
+ EXPECT_EQ(expected_value.is_null(), value.is_null());
+ }
+}
+
+void CreateMapWithValues(DOMStorageValuesMap* values) {
+ base::string16 kCannedKeys[] = {
+ ASCIIToUTF16("test"),
+ ASCIIToUTF16("company"),
+ ASCIIToUTF16("date"),
+ ASCIIToUTF16("empty")
+ };
+ base::NullableString16 kCannedValues[] = {
+ base::NullableString16(ASCIIToUTF16("123"), false),
+ base::NullableString16(ASCIIToUTF16("Google"), false),
+ base::NullableString16(ASCIIToUTF16("18-01-2012"), false),
+ base::NullableString16(base::string16(), false)
+ };
+ for (unsigned i = 0; i < sizeof(kCannedKeys) / sizeof(kCannedKeys[0]); i++)
+ (*values)[kCannedKeys[i]] = kCannedValues[i];
+}
+
+TEST(DOMStorageDatabaseTest, SimpleOpenAndClose) {
+ DOMStorageDatabase db;
+ EXPECT_FALSE(db.IsOpen());
+ ASSERT_TRUE(db.LazyOpen(true));
+ EXPECT_TRUE(db.IsOpen());
+ EXPECT_EQ(DOMStorageDatabase::V2, db.DetectSchemaVersion());
+ db.Close();
+ EXPECT_FALSE(db.IsOpen());
+}
+
+TEST(DOMStorageDatabaseTest, CloseEmptyDatabaseDeletesFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_name =
+ temp_dir.path().AppendASCII("TestDOMStorageDatabase.db");
+ DOMStorageValuesMap storage;
+ CreateMapWithValues(&storage);
+
+ // First test the case that explicitly clearing the database will
+ // trigger its deletion from disk.
+ {
+ DOMStorageDatabase db(file_name);
+ EXPECT_EQ(file_name, db.file_path());
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ }
+ EXPECT_TRUE(base::PathExists(file_name));
+
+ {
+ // Check that reading an existing db with data in it
+ // keeps the DB on disk on close.
+ DOMStorageDatabase db(file_name);
+ DOMStorageValuesMap values;
+ db.ReadAllValues(&values);
+ EXPECT_EQ(storage.size(), values.size());
+ }
+
+ EXPECT_TRUE(base::PathExists(file_name));
+ storage.clear();
+
+ {
+ DOMStorageDatabase db(file_name);
+ ASSERT_TRUE(db.CommitChanges(true, storage));
+ }
+ EXPECT_FALSE(base::PathExists(file_name));
+
+ // Now ensure that a series of updates and removals whose net effect
+ // is an empty database also triggers deletion.
+ CreateMapWithValues(&storage);
+ {
+ DOMStorageDatabase db(file_name);
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ }
+
+ EXPECT_TRUE(base::PathExists(file_name));
+
+ {
+ DOMStorageDatabase db(file_name);
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ DOMStorageValuesMap::iterator it = storage.begin();
+ for (; it != storage.end(); ++it)
+ it->second = base::NullableString16();
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ }
+ EXPECT_FALSE(base::PathExists(file_name));
+}
+
+TEST(DOMStorageDatabaseTest, TestLazyOpenIsLazy) {
+ // This test needs to operate with a file on disk to ensure that we will
+ // open a file that already exists when only invoking ReadAllValues.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_name =
+ temp_dir.path().AppendASCII("TestDOMStorageDatabase.db");
+
+ DOMStorageDatabase db(file_name);
+ EXPECT_FALSE(db.IsOpen());
+ DOMStorageValuesMap values;
+ db.ReadAllValues(&values);
+ // Reading an empty db should not open the database.
+ EXPECT_FALSE(db.IsOpen());
+
+ values[ASCIIToUTF16("key")] =
+ base::NullableString16(ASCIIToUTF16("value"), false);
+ db.CommitChanges(false, values);
+ // Writing content should open the database.
+ EXPECT_TRUE(db.IsOpen());
+
+ db.Close();
+ ASSERT_FALSE(db.IsOpen());
+
+ // Reading from an existing database should open the database.
+ CheckValuesMatch(&db, values);
+ EXPECT_TRUE(db.IsOpen());
+}
+
+TEST(DOMStorageDatabaseTest, TestDetectSchemaVersion) {
+ DOMStorageDatabase db;
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->OpenInMemory());
+
+ CreateInvalidValueColumnTable(db.db_.get());
+ EXPECT_EQ(DOMStorageDatabase::INVALID, db.DetectSchemaVersion());
+
+ CreateInvalidKeyColumnTable(db.db_.get());
+ EXPECT_EQ(DOMStorageDatabase::INVALID, db.DetectSchemaVersion());
+
+ CreateV1Table(db.db_.get());
+ EXPECT_EQ(DOMStorageDatabase::V1, db.DetectSchemaVersion());
+
+ CreateV2Table(db.db_.get());
+ EXPECT_EQ(DOMStorageDatabase::V2, db.DetectSchemaVersion());
+}
+
+TEST(DOMStorageDatabaseTest, TestLazyOpenUpgradesDatabase) {
+ // This test needs to operate with a file on disk so that we
+ // can create a table at version 1 and then close it again
+ // so that LazyOpen sees there is work to do (LazyOpen will return
+ // early if the database is already open).
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_name =
+ temp_dir.path().AppendASCII("TestDOMStorageDatabase.db");
+
+ DOMStorageDatabase db(file_name);
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->Open(file_name));
+ CreateV1Table(db.db_.get());
+ db.Close();
+
+ EXPECT_TRUE(db.LazyOpen(true));
+ EXPECT_EQ(DOMStorageDatabase::V2, db.DetectSchemaVersion());
+}
+
+TEST(DOMStorageDatabaseTest, SimpleWriteAndReadBack) {
+ DOMStorageDatabase db;
+
+ DOMStorageValuesMap storage;
+ CreateMapWithValues(&storage);
+
+ EXPECT_TRUE(db.CommitChanges(false, storage));
+ CheckValuesMatch(&db, storage);
+}
+
+TEST(DOMStorageDatabaseTest, WriteWithClear) {
+ DOMStorageDatabase db;
+
+ DOMStorageValuesMap storage;
+ CreateMapWithValues(&storage);
+
+ ASSERT_TRUE(db.CommitChanges(false, storage));
+ CheckValuesMatch(&db, storage);
+
+ // Insert some values, clearing the database first.
+ storage.clear();
+ storage[ASCIIToUTF16("another_key")] =
+ base::NullableString16(ASCIIToUTF16("test"), false);
+ ASSERT_TRUE(db.CommitChanges(true, storage));
+ CheckValuesMatch(&db, storage);
+
+ // Now clear the values without inserting any new ones.
+ storage.clear();
+ ASSERT_TRUE(db.CommitChanges(true, storage));
+ CheckValuesMatch(&db, storage);
+}
+
+TEST(DOMStorageDatabaseTest, UpgradeFromV1ToV2WithData) {
+ const base::string16 kCannedKey = ASCIIToUTF16("foo");
+ const base::NullableString16 kCannedValue(ASCIIToUTF16("bar"), false);
+ DOMStorageValuesMap expected;
+ expected[kCannedKey] = kCannedValue;
+
+ DOMStorageDatabase db;
+ db.db_.reset(new sql::Connection());
+ ASSERT_TRUE(db.db_->OpenInMemory());
+ CreateV1Table(db.db_.get());
+ InsertDataV1(db.db_.get(), kCannedKey, kCannedValue.string());
+
+ ASSERT_TRUE(db.UpgradeVersion1To2());
+
+ EXPECT_EQ(DOMStorageDatabase::V2, db.DetectSchemaVersion());
+
+ CheckValuesMatch(&db, expected);
+}
+
+TEST(DOMStorageDatabaseTest, TestSimpleRemoveOneValue) {
+ DOMStorageDatabase db;
+
+ ASSERT_TRUE(db.LazyOpen(true));
+ const base::string16 kCannedKey = ASCIIToUTF16("test");
+ const base::NullableString16 kCannedValue(ASCIIToUTF16("data"), false);
+ DOMStorageValuesMap expected;
+ expected[kCannedKey] = kCannedValue;
+
+ // First write some data into the database.
+ ASSERT_TRUE(db.CommitChanges(false, expected));
+ CheckValuesMatch(&db, expected);
+
+ DOMStorageValuesMap values;
+ // A null string in the map should mean that that key gets
+ // removed.
+ values[kCannedKey] = base::NullableString16();
+ EXPECT_TRUE(db.CommitChanges(false, values));
+
+ expected.clear();
+ CheckValuesMatch(&db, expected);
+}
+
+TEST(DOMStorageDatabaseTest, TestCanOpenAndReadWebCoreDatabase) {
+ base::FilePath webcore_database;
+ PathService::Get(base::DIR_SOURCE_ROOT, &webcore_database);
+ webcore_database = webcore_database.AppendASCII("webkit");
+ webcore_database = webcore_database.AppendASCII("data");
+ webcore_database = webcore_database.AppendASCII("dom_storage");
+ webcore_database =
+ webcore_database.AppendASCII("webcore_test_database.localstorage");
+
+ ASSERT_TRUE(base::PathExists(webcore_database));
+
+ DOMStorageDatabase db(webcore_database);
+ DOMStorageValuesMap values;
+ db.ReadAllValues(&values);
+ EXPECT_TRUE(db.IsOpen());
+ EXPECT_EQ(2u, values.size());
+
+ DOMStorageValuesMap::const_iterator it =
+ values.find(ASCIIToUTF16("value"));
+ EXPECT_TRUE(it != values.end());
+ EXPECT_EQ(ASCIIToUTF16("I am in local storage!"), it->second.string());
+
+ it = values.find(ASCIIToUTF16("timestamp"));
+ EXPECT_TRUE(it != values.end());
+ EXPECT_EQ(ASCIIToUTF16("1326738338841"), it->second.string());
+
+ it = values.find(ASCIIToUTF16("not_there"));
+ EXPECT_TRUE(it == values.end());
+}
+
+TEST(DOMStorageDatabaseTest, TestCanOpenFileThatIsNotADatabase) {
+ // Write into the temporary file first.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_name =
+ temp_dir.path().AppendASCII("TestDOMStorageDatabase.db");
+
+ const char kData[] = "I am not a database.";
+ file_util::WriteFile(file_name, kData, strlen(kData));
+
+ {
+ sql::ScopedErrorIgnorer ignore_errors;
+ ignore_errors.IgnoreError(SQLITE_IOERR_SHORT_READ);
+
+ // Try and open the file. As it's not a database, we should end up deleting
+ // it and creating a new, valid file, so everything should actually
+ // succeed.
+ DOMStorageDatabase db(file_name);
+ DOMStorageValuesMap values;
+ CreateMapWithValues(&values);
+ EXPECT_TRUE(db.CommitChanges(true, values));
+ EXPECT_TRUE(db.CommitChanges(false, values));
+ EXPECT_TRUE(db.IsOpen());
+
+ CheckValuesMatch(&db, values);
+
+ ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
+ }
+
+ {
+ sql::ScopedErrorIgnorer ignore_errors;
+ ignore_errors.IgnoreError(SQLITE_CANTOPEN);
+
+ // Try to open a directory, we should fail gracefully and not attempt
+ // to delete it.
+ DOMStorageDatabase db(temp_dir.path());
+ DOMStorageValuesMap values;
+ CreateMapWithValues(&values);
+ EXPECT_FALSE(db.CommitChanges(true, values));
+ EXPECT_FALSE(db.CommitChanges(false, values));
+ EXPECT_FALSE(db.IsOpen());
+
+ values.clear();
+
+ db.ReadAllValues(&values);
+ EXPECT_EQ(0u, values.size());
+ EXPECT_FALSE(db.IsOpen());
+
+ EXPECT_TRUE(base::PathExists(temp_dir.path()));
+
+ ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_host.cc b/chromium/content/browser/dom_storage/dom_storage_host.cc
new file mode 100644
index 00000000000..14d288d8653
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_host.cc
@@ -0,0 +1,158 @@
+// 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 "content/browser/dom_storage/dom_storage_host.h"
+
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_context_impl.h"
+#include "content/browser/dom_storage/dom_storage_namespace.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "url/gurl.h"
+
+namespace content {
+
+DOMStorageHost::DOMStorageHost(DOMStorageContextImpl* context)
+ : context_(context) {
+}
+
+DOMStorageHost::~DOMStorageHost() {
+ AreaMap::const_iterator it = connections_.begin();
+ for (; it != connections_.end(); ++it)
+ it->second.namespace_->CloseStorageArea(it->second.area_.get());
+ connections_.clear(); // Clear prior to releasing the context_
+}
+
+bool DOMStorageHost::OpenStorageArea(int connection_id, int namespace_id,
+ const GURL& origin) {
+ DCHECK(!GetOpenArea(connection_id));
+ if (GetOpenArea(connection_id))
+ return false; // Indicates the renderer gave us very bad data.
+ NamespaceAndArea references;
+ references.namespace_ = context_->GetStorageNamespace(namespace_id);
+ if (!references.namespace_.get())
+ return false;
+ references.area_ = references.namespace_->OpenStorageArea(origin);
+ DCHECK(references.area_.get());
+ connections_[connection_id] = references;
+ return true;
+}
+
+void DOMStorageHost::CloseStorageArea(int connection_id) {
+ AreaMap::iterator found = connections_.find(connection_id);
+ if (found == connections_.end())
+ return;
+ found->second.namespace_->CloseStorageArea(found->second.area_.get());
+ connections_.erase(found);
+}
+
+bool DOMStorageHost::ExtractAreaValues(
+ int connection_id, DOMStorageValuesMap* map) {
+ map->clear();
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return false;
+ if (!area->IsLoadedInMemory()) {
+ DOMStorageNamespace* ns = GetNamespace(connection_id);
+ DCHECK(ns);
+ if (ns->CountInMemoryAreas() > kMaxInMemoryStorageAreas) {
+ ns->PurgeMemory(DOMStorageNamespace::PURGE_UNOPENED);
+ if (ns->CountInMemoryAreas() > kMaxInMemoryStorageAreas)
+ ns->PurgeMemory(DOMStorageNamespace::PURGE_AGGRESSIVE);
+ }
+ }
+ area->ExtractValues(map);
+ return true;
+}
+
+unsigned DOMStorageHost::GetAreaLength(int connection_id) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return 0;
+ return area->Length();
+}
+
+base::NullableString16 DOMStorageHost::GetAreaKey(int connection_id,
+ unsigned index) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return base::NullableString16();
+ return area->Key(index);
+}
+
+base::NullableString16 DOMStorageHost::GetAreaItem(int connection_id,
+ const base::string16& key) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return base::NullableString16();
+ return area->GetItem(key);
+}
+
+bool DOMStorageHost::SetAreaItem(
+ int connection_id, const base::string16& key,
+ const base::string16& value, const GURL& page_url,
+ base::NullableString16* old_value) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return false;
+ if (!area->SetItem(key, value, old_value))
+ return false;
+ if (old_value->is_null() || old_value->string() != value)
+ context_->NotifyItemSet(area, key, value, *old_value, page_url);
+ return true;
+}
+
+bool DOMStorageHost::RemoveAreaItem(
+ int connection_id, const base::string16& key, const GURL& page_url,
+ base::string16* old_value) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return false;
+ if (!area->RemoveItem(key, old_value))
+ return false;
+ context_->NotifyItemRemoved(area, key, *old_value, page_url);
+ return true;
+}
+
+bool DOMStorageHost::ClearArea(int connection_id, const GURL& page_url) {
+ DOMStorageArea* area = GetOpenArea(connection_id);
+ if (!area)
+ return false;
+ if (!area->Clear())
+ return false;
+ context_->NotifyAreaCleared(area, page_url);
+ return true;
+}
+
+bool DOMStorageHost::HasAreaOpen(
+ int namespace_id, const GURL& origin) const {
+ AreaMap::const_iterator it = connections_.begin();
+ for (; it != connections_.end(); ++it) {
+ if (namespace_id == it->second.namespace_->namespace_id() &&
+ origin == it->second.area_->origin()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+DOMStorageArea* DOMStorageHost::GetOpenArea(int connection_id) {
+ AreaMap::iterator found = connections_.find(connection_id);
+ if (found == connections_.end())
+ return NULL;
+ return found->second.area_.get();
+}
+
+DOMStorageNamespace* DOMStorageHost::GetNamespace(int connection_id) {
+ AreaMap::iterator found = connections_.find(connection_id);
+ if (found == connections_.end())
+ return NULL;
+ return found->second.namespace_.get();
+}
+
+// NamespaceAndArea
+
+DOMStorageHost::NamespaceAndArea::NamespaceAndArea() {}
+DOMStorageHost::NamespaceAndArea::~NamespaceAndArea() {}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_host.h b/chromium/content/browser/dom_storage/dom_storage_host.h
new file mode 100644
index 00000000000..3aa9d8078f7
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_host.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_HOST_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_HOST_H_
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/nullable_string16.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+
+class GURL;
+
+namespace content {
+
+class DOMStorageContextImpl;
+class DOMStorageHost;
+class DOMStorageNamespace;
+class DOMStorageArea;
+
+// One instance is allocated in the main process for each client process.
+// Used by DOMStorageMessageFilter in Chrome and by SimpleDOMStorage in DRT.
+// This class is single threaded, and performs blocking file reads/writes,
+// so it shouldn't be used on chrome's IO thread.
+// See class comments for DOMStorageContextImpl for a larger overview.
+class CONTENT_EXPORT DOMStorageHost {
+ public:
+ explicit DOMStorageHost(DOMStorageContextImpl* context);
+ ~DOMStorageHost();
+
+ bool OpenStorageArea(int connection_id, int namespace_id,
+ const GURL& origin);
+ void CloseStorageArea(int connection_id);
+ bool ExtractAreaValues(int connection_id, DOMStorageValuesMap* map);
+ unsigned GetAreaLength(int connection_id);
+ base::NullableString16 GetAreaKey(int connection_id, unsigned index);
+ base::NullableString16 GetAreaItem(int connection_id,
+ const base::string16& key);
+ bool SetAreaItem(int connection_id, const base::string16& key,
+ const base::string16& value, const GURL& page_url,
+ base::NullableString16* old_value);
+ bool RemoveAreaItem(int connection_id, const base::string16& key,
+ const GURL& page_url,
+ base::string16* old_value);
+ bool ClearArea(int connection_id, const GURL& page_url);
+ bool HasAreaOpen(int namespace_id, const GURL& origin) const;
+
+ private:
+ // Struct to hold references needed for areas that are open
+ // within our associated client process.
+ struct NamespaceAndArea {
+ scoped_refptr<DOMStorageNamespace> namespace_;
+ scoped_refptr<DOMStorageArea> area_;
+ NamespaceAndArea();
+ ~NamespaceAndArea();
+ };
+ typedef std::map<int, NamespaceAndArea > AreaMap;
+
+ DOMStorageArea* GetOpenArea(int connection_id);
+ DOMStorageNamespace* GetNamespace(int connection_id);
+
+ scoped_refptr<DOMStorageContextImpl> context_;
+ AreaMap connections_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_HOST_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_message_filter.cc b/chromium/content/browser/dom_storage/dom_storage_message_filter.cc
new file mode 100644
index 00000000000..52aabe11018
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_message_filter.cc
@@ -0,0 +1,213 @@
+// 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 "content/browser/dom_storage/dom_storage_message_filter.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/strings/nullable_string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/dom_storage_host.h"
+#include "content/browser/dom_storage/dom_storage_namespace.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/common/dom_storage/dom_storage_messages.h"
+#include "content/public/browser/user_metrics.h"
+#include "url/gurl.h"
+
+namespace content {
+
+DOMStorageMessageFilter::DOMStorageMessageFilter(
+ int unused,
+ DOMStorageContextWrapper* context)
+ : context_(context->context()),
+ connection_dispatching_message_for_(0) {
+}
+
+DOMStorageMessageFilter::~DOMStorageMessageFilter() {
+ DCHECK(!host_.get());
+}
+
+void DOMStorageMessageFilter::InitializeInSequence() {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ host_.reset(new DOMStorageHost(context_.get()));
+ context_->AddEventObserver(this);
+}
+
+void DOMStorageMessageFilter::UninitializeInSequence() {
+ // TODO(michaeln): Restore this DCHECK once crbug/166470 and crbug/164403
+ // are resolved.
+ // DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ context_->RemoveEventObserver(this);
+ host_.reset();
+}
+
+void DOMStorageMessageFilter::OnFilterAdded(IPC::Channel* channel) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserMessageFilter::OnFilterAdded(channel);
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageMessageFilter::InitializeInSequence, this));
+}
+
+void DOMStorageMessageFilter::OnFilterRemoved() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserMessageFilter::OnFilterRemoved();
+ context_->task_runner()->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::PRIMARY_SEQUENCE,
+ base::Bind(&DOMStorageMessageFilter::UninitializeInSequence, this));
+}
+
+base::TaskRunner* DOMStorageMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ if (IPC_MESSAGE_CLASS(message) == DOMStorageMsgStart)
+ return context_->task_runner();
+ return NULL;
+}
+
+bool DOMStorageMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ if (IPC_MESSAGE_CLASS(message) != DOMStorageMsgStart)
+ return false;
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(host_.get());
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(DOMStorageMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_OpenStorageArea, OnOpenStorageArea)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_CloseStorageArea, OnCloseStorageArea)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_LoadStorageArea, OnLoadStorageArea)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_SetItem, OnSetItem)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_RemoveItem, OnRemoveItem)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_Clear, OnClear)
+ IPC_MESSAGE_HANDLER(DOMStorageHostMsg_FlushMessages, OnFlushMessages)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void DOMStorageMessageFilter::OnOpenStorageArea(int connection_id,
+ int64 namespace_id,
+ const GURL& origin) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!host_->OpenStorageArea(connection_id, namespace_id, origin)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DSMF_1"));
+ BadMessageReceived();
+ }
+}
+
+void DOMStorageMessageFilter::OnCloseStorageArea(int connection_id) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ host_->CloseStorageArea(connection_id);
+}
+
+void DOMStorageMessageFilter::OnLoadStorageArea(int connection_id,
+ DOMStorageValuesMap* map) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!host_->ExtractAreaValues(connection_id, map)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DSMF_2"));
+ BadMessageReceived();
+ }
+ Send(new DOMStorageMsg_AsyncOperationComplete(true));
+}
+
+void DOMStorageMessageFilter::OnSetItem(
+ int connection_id, const string16& key,
+ const string16& value, const GURL& page_url) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(0, connection_dispatching_message_for_);
+ base::AutoReset<int> auto_reset(&connection_dispatching_message_for_,
+ connection_id);
+ base::NullableString16 not_used;
+ bool success = host_->SetAreaItem(connection_id, key, value,
+ page_url, &not_used);
+ Send(new DOMStorageMsg_AsyncOperationComplete(success));
+}
+
+void DOMStorageMessageFilter::OnRemoveItem(
+ int connection_id, const string16& key,
+ const GURL& page_url) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(0, connection_dispatching_message_for_);
+ base::AutoReset<int> auto_reset(&connection_dispatching_message_for_,
+ connection_id);
+ string16 not_used;
+ host_->RemoveAreaItem(connection_id, key, page_url, &not_used);
+ Send(new DOMStorageMsg_AsyncOperationComplete(true));
+}
+
+void DOMStorageMessageFilter::OnClear(
+ int connection_id, const GURL& page_url) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(0, connection_dispatching_message_for_);
+ base::AutoReset<int> auto_reset(&connection_dispatching_message_for_,
+ connection_id);
+ host_->ClearArea(connection_id, page_url);
+ Send(new DOMStorageMsg_AsyncOperationComplete(true));
+}
+
+void DOMStorageMessageFilter::OnFlushMessages() {
+ // Intentionally empty method body.
+}
+
+void DOMStorageMessageFilter::OnDOMStorageItemSet(
+ const DOMStorageArea* area,
+ const string16& key,
+ const string16& new_value,
+ const base::NullableString16& old_value,
+ const GURL& page_url) {
+ SendDOMStorageEvent(area, page_url,
+ base::NullableString16(key, false),
+ base::NullableString16(new_value, false),
+ old_value);
+}
+
+void DOMStorageMessageFilter::OnDOMStorageItemRemoved(
+ const DOMStorageArea* area,
+ const string16& key,
+ const string16& old_value,
+ const GURL& page_url) {
+ SendDOMStorageEvent(area, page_url,
+ base::NullableString16(key, false),
+ base::NullableString16(),
+ base::NullableString16(old_value, false));
+}
+
+void DOMStorageMessageFilter::OnDOMStorageAreaCleared(
+ const DOMStorageArea* area,
+ const GURL& page_url) {
+ SendDOMStorageEvent(area, page_url,
+ base::NullableString16(),
+ base::NullableString16(),
+ base::NullableString16());
+}
+
+void DOMStorageMessageFilter::SendDOMStorageEvent(
+ const DOMStorageArea* area,
+ const GURL& page_url,
+ const base::NullableString16& key,
+ const base::NullableString16& new_value,
+ const base::NullableString16& old_value) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Only send mutation events to processes which have the area open.
+ bool originated_in_process = connection_dispatching_message_for_ != 0;
+ if (originated_in_process ||
+ host_->HasAreaOpen(area->namespace_id(), area->origin())) {
+ DOMStorageMsg_Event_Params params;
+ params.origin = area->origin();
+ params.page_url = page_url;
+ params.connection_id = connection_dispatching_message_for_;
+ params.key = key;
+ params.new_value = new_value;
+ params.old_value = old_value;
+ params.namespace_id = area->namespace_id();
+ Send(new DOMStorageMsg_Event(params));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_message_filter.h b/chromium/content/browser/dom_storage/dom_storage_message_filter.h
new file mode 100644
index 00000000000..2ec928a4732
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_message_filter.h
@@ -0,0 +1,95 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_MESSAGE_FILTER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/dom_storage/dom_storage_context_impl.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "content/public/browser/browser_message_filter.h"
+
+class GURL;
+
+namespace base {
+class NullableString16;
+}
+
+namespace content {
+
+class DOMStorageArea;
+class DOMStorageContextImpl;
+class DOMStorageContextWrapper;
+class DOMStorageHost;
+
+// This class handles the logistics of DOM Storage within the browser process.
+// It mostly ferries information between IPCs and the dom_storage classes.
+class DOMStorageMessageFilter
+ : public BrowserMessageFilter,
+ public DOMStorageContextImpl::EventObserver {
+ public:
+ explicit DOMStorageMessageFilter(int unused,
+ DOMStorageContextWrapper* context);
+
+ private:
+ virtual ~DOMStorageMessageFilter();
+
+ void InitializeInSequence();
+ void UninitializeInSequence();
+
+ // BrowserMessageFilter implementation
+ virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE;
+ virtual void OnFilterRemoved() OVERRIDE;
+ virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // Message Handlers.
+ void OnOpenStorageArea(int connection_id, int64 namespace_id,
+ const GURL& origin);
+ void OnCloseStorageArea(int connection_id);
+ void OnLoadStorageArea(int connection_id, DOMStorageValuesMap* map);
+ void OnSetItem(int connection_id, const string16& key,
+ const string16& value, const GURL& page_url);
+ void OnRemoveItem(int connection_id, const string16& key,
+ const GURL& page_url);
+ void OnClear(int connection_id, const GURL& page_url);
+ void OnFlushMessages();
+
+ // DOMStorageContextImpl::EventObserver implementation which
+ // sends events back to our renderer process.
+ virtual void OnDOMStorageItemSet(
+ const DOMStorageArea* area,
+ const string16& key,
+ const string16& new_value,
+ const base::NullableString16& old_value,
+ const GURL& page_url) OVERRIDE;
+ virtual void OnDOMStorageItemRemoved(
+ const DOMStorageArea* area,
+ const string16& key,
+ const string16& old_value,
+ const GURL& page_url) OVERRIDE;
+ virtual void OnDOMStorageAreaCleared(
+ const DOMStorageArea* area,
+ const GURL& page_url) OVERRIDE;
+
+ void SendDOMStorageEvent(
+ const DOMStorageArea* area,
+ const GURL& page_url,
+ const base::NullableString16& key,
+ const base::NullableString16& new_value,
+ const base::NullableString16& old_value);
+
+ scoped_refptr<DOMStorageContextImpl> context_;
+ scoped_ptr<DOMStorageHost> host_;
+ int connection_dispatching_message_for_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DOMStorageMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_namespace.cc b/chromium/content/browser/dom_storage/dom_storage_namespace.cc
new file mode 100644
index 00000000000..75bfdaacb71
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_namespace.cc
@@ -0,0 +1,187 @@
+// 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 "content/browser/dom_storage/dom_storage_namespace.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "content/browser/dom_storage/dom_storage_area.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+#include "content/browser/dom_storage/session_storage_database.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+
+namespace content {
+
+DOMStorageNamespace::DOMStorageNamespace(
+ const base::FilePath& directory,
+ DOMStorageTaskRunner* task_runner)
+ : namespace_id_(kLocalStorageNamespaceId),
+ directory_(directory),
+ task_runner_(task_runner) {
+}
+
+DOMStorageNamespace::DOMStorageNamespace(
+ int64 namespace_id,
+ const std::string& persistent_namespace_id,
+ SessionStorageDatabase* session_storage_database,
+ DOMStorageTaskRunner* task_runner)
+ : namespace_id_(namespace_id),
+ persistent_namespace_id_(persistent_namespace_id),
+ task_runner_(task_runner),
+ session_storage_database_(session_storage_database) {
+ DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
+}
+
+DOMStorageNamespace::~DOMStorageNamespace() {
+}
+
+DOMStorageArea* DOMStorageNamespace::OpenStorageArea(const GURL& origin) {
+ if (AreaHolder* holder = GetAreaHolder(origin)) {
+ ++(holder->open_count_);
+ return holder->area_.get();
+ }
+ DOMStorageArea* area;
+ if (namespace_id_ == kLocalStorageNamespaceId) {
+ area = new DOMStorageArea(origin, directory_, task_runner_.get());
+ } else {
+ area = new DOMStorageArea(
+ namespace_id_, persistent_namespace_id_, origin,
+ session_storage_database_.get(), task_runner_.get());
+ }
+ areas_[origin] = AreaHolder(area, 1);
+ return area;
+}
+
+void DOMStorageNamespace::CloseStorageArea(DOMStorageArea* area) {
+ AreaHolder* holder = GetAreaHolder(area->origin());
+ DCHECK(holder);
+ DCHECK_EQ(holder->area_.get(), area);
+ --(holder->open_count_);
+ // TODO(michaeln): Clean up areas that aren't needed in memory anymore.
+ // The in-process-webkit based impl didn't do this either, but would be nice.
+}
+
+DOMStorageArea* DOMStorageNamespace::GetOpenStorageArea(const GURL& origin) {
+ AreaHolder* holder = GetAreaHolder(origin);
+ if (holder && holder->open_count_)
+ return holder->area_.get();
+ return NULL;
+}
+
+DOMStorageNamespace* DOMStorageNamespace::Clone(
+ int64 clone_namespace_id,
+ const std::string& clone_persistent_namespace_id) {
+ DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
+ DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
+ DOMStorageNamespace* clone = new DOMStorageNamespace(
+ clone_namespace_id, clone_persistent_namespace_id,
+ session_storage_database_.get(), task_runner_.get());
+ AreaMap::const_iterator it = areas_.begin();
+ // Clone the in-memory structures.
+ for (; it != areas_.end(); ++it) {
+ DOMStorageArea* area = it->second.area_->ShallowCopy(
+ clone_namespace_id, clone_persistent_namespace_id);
+ clone->areas_[it->first] = AreaHolder(area, 0);
+ }
+ // And clone the on-disk structures, too.
+ if (session_storage_database_.get()) {
+ task_runner_->PostShutdownBlockingTask(
+ FROM_HERE,
+ DOMStorageTaskRunner::COMMIT_SEQUENCE,
+ base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
+ session_storage_database_.get(), persistent_namespace_id_,
+ clone_persistent_namespace_id));
+ }
+ return clone;
+}
+
+void DOMStorageNamespace::DeleteLocalStorageOrigin(const GURL& origin) {
+ DCHECK(!session_storage_database_.get());
+ AreaHolder* holder = GetAreaHolder(origin);
+ if (holder) {
+ holder->area_->DeleteOrigin();
+ return;
+ }
+ if (!directory_.empty()) {
+ scoped_refptr<DOMStorageArea> area =
+ new DOMStorageArea(origin, directory_, task_runner_.get());
+ area->DeleteOrigin();
+ }
+}
+
+void DOMStorageNamespace::DeleteSessionStorageOrigin(const GURL& origin) {
+ DOMStorageArea* area = OpenStorageArea(origin);
+ area->FastClear();
+ CloseStorageArea(area);
+}
+
+void DOMStorageNamespace::PurgeMemory(PurgeOption option) {
+ if (directory_.empty())
+ return; // We can't purge w/o backing on disk.
+ AreaMap::iterator it = areas_.begin();
+ while (it != areas_.end()) {
+ // Leave it alone if changes are pending
+ if (it->second.area_->HasUncommittedChanges()) {
+ ++it;
+ continue;
+ }
+
+ // If not in use, we can shut it down and remove
+ // it from our collection entirely.
+ if (it->second.open_count_ == 0) {
+ it->second.area_->Shutdown();
+ areas_.erase(it++);
+ continue;
+ }
+
+ if (option == PURGE_AGGRESSIVE) {
+ // If aggressive is true, we clear caches and such
+ // for opened areas.
+ it->second.area_->PurgeMemory();
+ }
+
+ ++it;
+ }
+}
+
+void DOMStorageNamespace::Shutdown() {
+ AreaMap::const_iterator it = areas_.begin();
+ for (; it != areas_.end(); ++it)
+ it->second.area_->Shutdown();
+}
+
+unsigned int DOMStorageNamespace::CountInMemoryAreas() const {
+ unsigned int area_count = 0;
+ for (AreaMap::const_iterator it = areas_.begin(); it != areas_.end(); ++it) {
+ if (it->second.area_->IsLoadedInMemory())
+ ++area_count;
+ }
+ return area_count;
+}
+
+DOMStorageNamespace::AreaHolder*
+DOMStorageNamespace::GetAreaHolder(const GURL& origin) {
+ AreaMap::iterator found = areas_.find(origin);
+ if (found == areas_.end())
+ return NULL;
+ return &(found->second);
+}
+
+// AreaHolder
+
+DOMStorageNamespace::AreaHolder::AreaHolder()
+ : open_count_(0) {
+}
+
+DOMStorageNamespace::AreaHolder::AreaHolder(
+ DOMStorageArea* area, int count)
+ : area_(area), open_count_(count) {
+}
+
+DOMStorageNamespace::AreaHolder::~AreaHolder() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_namespace.h b/chromium/content/browser/dom_storage/dom_storage_namespace.h
new file mode 100644
index 00000000000..8e67b032bb4
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_namespace.h
@@ -0,0 +1,108 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_NAMESPACE_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_NAMESPACE_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+class GURL;
+
+namespace content {
+
+class DOMStorageArea;
+class DOMStorageTaskRunner;
+class SessionStorageDatabase;
+
+// Container for the set of per-origin Areas.
+// See class comments for DOMStorageContextImpl for a larger overview.
+class CONTENT_EXPORT DOMStorageNamespace
+ : public base::RefCountedThreadSafe<DOMStorageNamespace> {
+ public:
+ // Option for PurgeMemory.
+ enum PurgeOption {
+ // Purge unopened areas only.
+ PURGE_UNOPENED,
+
+ // Purge aggressively, i.e. discard cache even for areas that have
+ // non-zero open count.
+ PURGE_AGGRESSIVE,
+ };
+
+ // Constructor for a LocalStorage namespace with id of 0
+ // and an optional backing directory on disk.
+ DOMStorageNamespace(const base::FilePath& directory, // may be empty
+ DOMStorageTaskRunner* task_runner);
+
+ // Constructor for a SessionStorage namespace with a non-zero id and an
+ // optional backing on disk via |session_storage_database| (may be NULL).
+ DOMStorageNamespace(int64 namespace_id,
+ const std::string& persistent_namespace_id,
+ SessionStorageDatabase* session_storage_database,
+ DOMStorageTaskRunner* task_runner);
+
+ int64 namespace_id() const { return namespace_id_; }
+ const std::string& persistent_namespace_id() const {
+ return persistent_namespace_id_;
+ }
+
+ // Returns the storage area for the given origin,
+ // creating instance if needed. Each call to open
+ // must be balanced with a call to CloseStorageArea.
+ DOMStorageArea* OpenStorageArea(const GURL& origin);
+ void CloseStorageArea(DOMStorageArea* area);
+
+ // Returns the area for |origin| if it's open, otherwise NULL.
+ DOMStorageArea* GetOpenStorageArea(const GURL& origin);
+
+ // Creates a clone of |this| namespace including
+ // shallow copies of all contained areas.
+ // Should only be called for session storage namespaces.
+ DOMStorageNamespace* Clone(int64 clone_namespace_id,
+ const std::string& clone_persistent_namespace_id);
+
+ void DeleteLocalStorageOrigin(const GURL& origin);
+ void DeleteSessionStorageOrigin(const GURL& origin);
+ void PurgeMemory(PurgeOption purge);
+ void Shutdown();
+
+ unsigned int CountInMemoryAreas() const;
+
+ private:
+ friend class base::RefCountedThreadSafe<DOMStorageNamespace>;
+
+ // Struct to hold references to our contained areas and
+ // to keep track of how many tabs have a given area open.
+ struct AreaHolder {
+ scoped_refptr<DOMStorageArea> area_;
+ int open_count_;
+ AreaHolder();
+ AreaHolder(DOMStorageArea* area, int count);
+ ~AreaHolder();
+ };
+ typedef std::map<GURL, AreaHolder> AreaMap;
+
+ ~DOMStorageNamespace();
+
+ // Returns a pointer to the area holder in our map or NULL.
+ AreaHolder* GetAreaHolder(const GURL& origin);
+
+ int64 namespace_id_;
+ std::string persistent_namespace_id_;
+ base::FilePath directory_;
+ AreaMap areas_;
+ scoped_refptr<DOMStorageTaskRunner> task_runner_;
+ scoped_refptr<SessionStorageDatabase> session_storage_database_;
+};
+
+} // namespace content
+
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_NAMESPACE_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_session.cc b/chromium/content/browser/dom_storage/dom_storage_session.cc
new file mode 100644
index 00000000000..7706ec85064
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_session.cc
@@ -0,0 +1,84 @@
+// 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 "content/browser/dom_storage/dom_storage_session.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/tracked_objects.h"
+#include "content/browser/dom_storage/dom_storage_context_impl.h"
+#include "content/browser/dom_storage/dom_storage_task_runner.h"
+
+namespace content {
+
+DOMStorageSession::DOMStorageSession(DOMStorageContextImpl* context)
+ : context_(context),
+ namespace_id_(context->AllocateSessionId()),
+ persistent_namespace_id_(context->AllocatePersistentSessionId()),
+ should_persist_(false) {
+ context->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageContextImpl::CreateSessionNamespace,
+ context_, namespace_id_, persistent_namespace_id_));
+}
+
+DOMStorageSession::DOMStorageSession(DOMStorageContextImpl* context,
+ const std::string& persistent_namespace_id)
+ : context_(context),
+ namespace_id_(context->AllocateSessionId()),
+ persistent_namespace_id_(persistent_namespace_id),
+ should_persist_(false) {
+ context->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageContextImpl::CreateSessionNamespace,
+ context_, namespace_id_, persistent_namespace_id_));
+}
+
+void DOMStorageSession::SetShouldPersist(bool should_persist) {
+ should_persist_ = should_persist;
+}
+
+bool DOMStorageSession::should_persist() const {
+ return should_persist_;
+}
+
+bool DOMStorageSession::IsFromContext(DOMStorageContextImpl* context) {
+ return context_.get() == context;
+}
+
+DOMStorageSession* DOMStorageSession::Clone() {
+ return CloneFrom(context_.get(), namespace_id_);
+}
+
+// static
+DOMStorageSession* DOMStorageSession::CloneFrom(DOMStorageContextImpl* context,
+ int64 namepace_id_to_clone) {
+ int64 clone_id = context->AllocateSessionId();
+ std::string persistent_clone_id = context->AllocatePersistentSessionId();
+ context->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageContextImpl::CloneSessionNamespace,
+ context, namepace_id_to_clone, clone_id, persistent_clone_id));
+ return new DOMStorageSession(context, clone_id, persistent_clone_id);
+}
+
+DOMStorageSession::DOMStorageSession(DOMStorageContextImpl* context,
+ int64 namespace_id,
+ const std::string& persistent_namespace_id)
+ : context_(context),
+ namespace_id_(namespace_id),
+ persistent_namespace_id_(persistent_namespace_id),
+ should_persist_(false) {
+ // This ctor is intended for use by the Clone() method.
+}
+
+DOMStorageSession::~DOMStorageSession() {
+ context_->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&DOMStorageContextImpl::DeleteSessionNamespace,
+ context_, namespace_id_, should_persist_));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_session.h b/chromium/content/browser/dom_storage/dom_storage_session.h
new file mode 100644
index 00000000000..62c6d97252c
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_session.h
@@ -0,0 +1,65 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_SESSION_H_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_SESSION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class DOMStorageContextImpl;
+
+// This refcounted class determines the lifetime of a session
+// storage namespace and provides an interface to Clone() an
+// existing session storage namespace. It may be used on any thread.
+// See class comments for DOMStorageContextImpl for a larger overview.
+class CONTENT_EXPORT DOMStorageSession
+ : public base::RefCountedThreadSafe<DOMStorageSession> {
+ public:
+ // Constructs a |DOMStorageSession| and allocates new IDs for it.
+ explicit DOMStorageSession(DOMStorageContextImpl* context);
+
+ // Constructs a |DOMStorageSession| and assigns |persistent_namespace_id|
+ // to it. Allocates a new non-persistent ID.
+ DOMStorageSession(DOMStorageContextImpl* context,
+ const std::string& persistent_namespace_id);
+
+ int64 namespace_id() const { return namespace_id_; }
+ const std::string& persistent_namespace_id() const {
+ return persistent_namespace_id_;
+ }
+ void SetShouldPersist(bool should_persist);
+ bool should_persist() const;
+ bool IsFromContext(DOMStorageContextImpl* context);
+ DOMStorageSession* Clone();
+
+ // Constructs a |DOMStorageSession| by cloning
+ // |namespace_id_to_clone|. Allocates new IDs for it.
+ static DOMStorageSession* CloneFrom(DOMStorageContextImpl* context,
+ int64 namepace_id_to_clone);
+
+ private:
+ friend class base::RefCountedThreadSafe<DOMStorageSession>;
+
+ DOMStorageSession(DOMStorageContextImpl* context,
+ int64 namespace_id,
+ const std::string& persistent_namespace_id);
+ ~DOMStorageSession();
+
+ scoped_refptr<DOMStorageContextImpl> context_;
+ int64 namespace_id_;
+ std::string persistent_namespace_id_;
+ bool should_persist_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DOMStorageSession);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_SESSION_H_
diff --git a/chromium/content/browser/dom_storage/dom_storage_task_runner.cc b/chromium/content/browser/dom_storage/dom_storage_task_runner.cc
new file mode 100644
index 00000000000..aa75c3c530a
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_task_runner.cc
@@ -0,0 +1,107 @@
+// 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 "content/browser/dom_storage/dom_storage_task_runner.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/tracked_objects.h"
+
+namespace content {
+
+// DOMStorageTaskRunner
+
+bool DOMStorageTaskRunner::RunsTasksOnCurrentThread() const {
+ return IsRunningOnSequence(PRIMARY_SEQUENCE);
+}
+
+// DOMStorageWorkerPoolTaskRunner
+
+DOMStorageWorkerPoolTaskRunner::DOMStorageWorkerPoolTaskRunner(
+ base::SequencedWorkerPool* sequenced_worker_pool,
+ base::SequencedWorkerPool::SequenceToken primary_sequence_token,
+ base::SequencedWorkerPool::SequenceToken commit_sequence_token,
+ base::MessageLoopProxy* delayed_task_loop)
+ : message_loop_(delayed_task_loop),
+ sequenced_worker_pool_(sequenced_worker_pool),
+ primary_sequence_token_(primary_sequence_token),
+ commit_sequence_token_(commit_sequence_token) {
+}
+
+DOMStorageWorkerPoolTaskRunner::~DOMStorageWorkerPoolTaskRunner() {
+}
+
+bool DOMStorageWorkerPoolTaskRunner::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ // Note base::TaskRunner implements PostTask in terms of PostDelayedTask
+ // with a delay of zero, we detect that usage and avoid the unecessary
+ // trip thru the message loop.
+ if (delay == base::TimeDelta()) {
+ return sequenced_worker_pool_->PostSequencedWorkerTaskWithShutdownBehavior(
+ primary_sequence_token_, from_here, task,
+ base::SequencedWorkerPool::BLOCK_SHUTDOWN);
+ }
+ // Post a task to call this->PostTask() after the delay.
+ return message_loop_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&DOMStorageWorkerPoolTaskRunner::PostTask),
+ this, from_here, task),
+ delay);
+}
+
+bool DOMStorageWorkerPoolTaskRunner::PostShutdownBlockingTask(
+ const tracked_objects::Location& from_here,
+ SequenceID sequence_id,
+ const base::Closure& task) {
+ return sequenced_worker_pool_->PostSequencedWorkerTaskWithShutdownBehavior(
+ IDtoToken(sequence_id), from_here, task,
+ base::SequencedWorkerPool::BLOCK_SHUTDOWN);
+}
+
+bool DOMStorageWorkerPoolTaskRunner::IsRunningOnSequence(
+ SequenceID sequence_id) const {
+ return sequenced_worker_pool_->IsRunningSequenceOnCurrentThread(
+ IDtoToken(sequence_id));
+}
+
+base::SequencedWorkerPool::SequenceToken
+DOMStorageWorkerPoolTaskRunner::IDtoToken(SequenceID id) const {
+ if (id == PRIMARY_SEQUENCE)
+ return primary_sequence_token_;
+ DCHECK_EQ(COMMIT_SEQUENCE, id);
+ return commit_sequence_token_;
+}
+
+// MockDOMStorageTaskRunner
+
+MockDOMStorageTaskRunner::MockDOMStorageTaskRunner(
+ base::MessageLoopProxy* message_loop)
+ : message_loop_(message_loop) {
+}
+
+MockDOMStorageTaskRunner::~MockDOMStorageTaskRunner() {
+}
+
+bool MockDOMStorageTaskRunner::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ return message_loop_->PostTask(from_here, task);
+}
+
+bool MockDOMStorageTaskRunner::PostShutdownBlockingTask(
+ const tracked_objects::Location& from_here,
+ SequenceID sequence_id,
+ const base::Closure& task) {
+ return message_loop_->PostTask(from_here, task);
+}
+
+bool MockDOMStorageTaskRunner::IsRunningOnSequence(SequenceID) const {
+ return message_loop_->RunsTasksOnCurrentThread();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/dom_storage_task_runner.h b/chromium/content/browser/dom_storage/dom_storage_task_runner.h
new file mode 100644
index 00000000000..1fb8a19e73b
--- /dev/null
+++ b/chromium/content/browser/dom_storage/dom_storage_task_runner.h
@@ -0,0 +1,135 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_TASK_RUNNER_
+#define CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_TASK_RUNNER_
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace content {
+
+// DOMStorage uses two task sequences (primary vs commit) to avoid
+// primary access from queuing up behind commits to disk.
+// * Initialization, shutdown, and administrative tasks are performed as
+// shutdown-blocking primary sequence tasks.
+// * Tasks directly related to the javascript'able interface are performed
+// as shutdown-blocking primary sequence tasks.
+// TODO(michaeln): Skip tasks for reading during shutdown.
+// * Internal tasks related to committing changes to disk are performed as
+// shutdown-blocking commit sequence tasks.
+class CONTENT_EXPORT DOMStorageTaskRunner
+ : public base::TaskRunner {
+ public:
+ enum SequenceID {
+ PRIMARY_SEQUENCE,
+ COMMIT_SEQUENCE
+ };
+
+ // The PostTask() and PostDelayedTask() methods defined by TaskRunner
+ // post shutdown-blocking tasks on the primary sequence.
+ virtual bool PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) = 0;
+
+ // Posts a shutdown blocking task to |sequence_id|.
+ virtual bool PostShutdownBlockingTask(
+ const tracked_objects::Location& from_here,
+ SequenceID sequence_id,
+ const base::Closure& task) = 0;
+
+ // The TaskRunner override returns true if the current thread is running
+ // on the primary sequence.
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
+ // Returns true if the current thread is running on the given |sequence_id|.
+ virtual bool IsRunningOnSequence(SequenceID sequence_id) const = 0;
+ bool IsRunningOnPrimarySequence() const {
+ return IsRunningOnSequence(PRIMARY_SEQUENCE);
+ }
+ bool IsRunningOnCommitSequence() const {
+ return IsRunningOnSequence(COMMIT_SEQUENCE);
+ }
+
+ protected:
+ virtual ~DOMStorageTaskRunner() {}
+};
+
+// A derived class used in chromium that utilizes a SequenceWorkerPool
+// under dom_storage specific SequenceTokens. The |delayed_task_loop|
+// is used to delay scheduling on the worker pool.
+class CONTENT_EXPORT DOMStorageWorkerPoolTaskRunner :
+ public DOMStorageTaskRunner {
+ public:
+ DOMStorageWorkerPoolTaskRunner(
+ base::SequencedWorkerPool* sequenced_worker_pool,
+ base::SequencedWorkerPool::SequenceToken primary_sequence_token,
+ base::SequencedWorkerPool::SequenceToken commit_sequence_token,
+ base::MessageLoopProxy* delayed_task_loop);
+
+ virtual bool PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE;
+
+ virtual bool PostShutdownBlockingTask(
+ const tracked_objects::Location& from_here,
+ SequenceID sequence_id,
+ const base::Closure& task) OVERRIDE;
+
+ virtual bool IsRunningOnSequence(SequenceID sequence_id) const OVERRIDE;
+
+ protected:
+ virtual ~DOMStorageWorkerPoolTaskRunner();
+
+ private:
+
+ base::SequencedWorkerPool::SequenceToken IDtoToken(SequenceID id) const;
+
+ const scoped_refptr<base::MessageLoopProxy> message_loop_;
+ const scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool_;
+ base::SequencedWorkerPool::SequenceToken primary_sequence_token_;
+ base::SequencedWorkerPool::SequenceToken commit_sequence_token_;
+};
+
+// A derived class used in unit tests that ignores all delays so
+// we don't block in unit tests waiting for timeouts to expire.
+// There is no distinction between [non]-shutdown-blocking or
+// the primary sequence vs the commit sequence in the mock,
+// all tasks are scheduled on |message_loop| with zero delay.
+class CONTENT_EXPORT MockDOMStorageTaskRunner :
+ public DOMStorageTaskRunner {
+ public:
+ explicit MockDOMStorageTaskRunner(base::MessageLoopProxy* message_loop);
+
+ virtual bool PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE;
+
+ virtual bool PostShutdownBlockingTask(
+ const tracked_objects::Location& from_here,
+ SequenceID sequence_id,
+ const base::Closure& task) OVERRIDE;
+
+ virtual bool IsRunningOnSequence(SequenceID sequence_id) const OVERRIDE;
+
+ protected:
+ virtual ~MockDOMStorageTaskRunner();
+
+ private:
+ const scoped_refptr<base::MessageLoopProxy> message_loop_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_DOM_STORAGE_TASK_RUNNER_
diff --git a/chromium/content/browser/dom_storage/local_storage_database_adapter.cc b/chromium/content/browser/dom_storage/local_storage_database_adapter.cc
new file mode 100644
index 00000000000..f5d87e31c19
--- /dev/null
+++ b/chromium/content/browser/dom_storage/local_storage_database_adapter.cc
@@ -0,0 +1,40 @@
+// 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 "content/browser/dom_storage/local_storage_database_adapter.h"
+
+#include "base/file_util.h"
+#include "content/browser/dom_storage/dom_storage_database.h"
+
+namespace content {
+
+LocalStorageDatabaseAdapter::LocalStorageDatabaseAdapter(
+ const base::FilePath& path)
+ : db_(new DOMStorageDatabase(path)) {
+}
+
+LocalStorageDatabaseAdapter::~LocalStorageDatabaseAdapter() { }
+
+void LocalStorageDatabaseAdapter::ReadAllValues(DOMStorageValuesMap* result) {
+ db_->ReadAllValues(result);
+}
+
+bool LocalStorageDatabaseAdapter::CommitChanges(
+ bool clear_all_first, const DOMStorageValuesMap& changes) {
+ return db_->CommitChanges(clear_all_first, changes);
+}
+
+void LocalStorageDatabaseAdapter::DeleteFiles() {
+ sql::Connection::Delete(db_->file_path());
+}
+
+void LocalStorageDatabaseAdapter::Reset() {
+ db_.reset(new DOMStorageDatabase(db_->file_path()));
+}
+
+LocalStorageDatabaseAdapter::LocalStorageDatabaseAdapter()
+ : db_(new DOMStorageDatabase()) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/local_storage_database_adapter.h b/chromium/content/browser/dom_storage/local_storage_database_adapter.h
new file mode 100644
index 00000000000..1298d9c3e30
--- /dev/null
+++ b/chromium/content/browser/dom_storage/local_storage_database_adapter.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_LOCAL_STORAGE_DATABASE_ADAPTER_H_
+#define CONTENT_BROWSER_DOM_STORAGE_LOCAL_STORAGE_DATABASE_ADAPTER_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/dom_storage/dom_storage_database_adapter.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+class DOMStorageDatabase;
+
+class CONTENT_EXPORT LocalStorageDatabaseAdapter :
+ public DOMStorageDatabaseAdapter {
+ public:
+ explicit LocalStorageDatabaseAdapter(const base::FilePath& path);
+ virtual ~LocalStorageDatabaseAdapter();
+ virtual void ReadAllValues(DOMStorageValuesMap* result) OVERRIDE;
+ virtual bool CommitChanges(bool clear_all_first,
+ const DOMStorageValuesMap& changes) OVERRIDE;
+ virtual void DeleteFiles() OVERRIDE;
+ virtual void Reset() OVERRIDE;
+
+ protected:
+ // Constructor that uses an in-memory sqlite database, for testing.
+ LocalStorageDatabaseAdapter();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, BackingDatabaseOpened);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, CommitChangesAtShutdown);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, CommitTasks);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, DeleteOrigin);
+ FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, PurgeMemory);
+
+ scoped_ptr<DOMStorageDatabase> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalStorageDatabaseAdapter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_LOCAL_STORAGE_DATABASE_ADAPTER_H_
diff --git a/chromium/content/browser/dom_storage/session_storage_database.cc b/chromium/content/browser/dom_storage/session_storage_database.cc
new file mode 100644
index 00000000000..bac3df7a67b
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_database.cc
@@ -0,0 +1,680 @@
+// 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 "content/browser/dom_storage/session_storage_database.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+#include "url/gurl.h"
+
+
+namespace {
+
+const char session_storage_uma_name[] = "SessionStorageDatabase.Open";
+
+enum SessionStorageUMA {
+ SESSION_STORAGE_UMA_SUCCESS,
+ SESSION_STORAGE_UMA_RECREATED,
+ SESSION_STORAGE_UMA_FAIL,
+ SESSION_STORAGE_UMA_MAX
+};
+
+} // namespace
+
+// Layout of the database:
+// | key | value |
+// -----------------------------------------------------------------------
+// | map-1- | 2 (refcount, start of map-1-* keys)|
+// | map-1-a | b (a = b in map 1) |
+// | ... | |
+// | namespace- | dummy (start of namespace-* keys) |
+// | namespace-1- (1 = namespace id)| dummy (start of namespace-1-* keys)|
+// | namespace-1-origin1 | 1 (mapid) |
+// | namespace-1-origin2 | 2 |
+// | namespace-2- | dummy |
+// | namespace-2-origin1 | 1 (shallow copy) |
+// | namespace-2-origin2 | 2 (shallow copy) |
+// | namespace-3- | dummy |
+// | namespace-3-origin1 | 3 (deep copy) |
+// | namespace-3-origin2 | 2 (shallow copy) |
+// | next-map-id | 4 |
+
+namespace content {
+
+SessionStorageDatabase::SessionStorageDatabase(const base::FilePath& file_path)
+ : file_path_(file_path),
+ db_error_(false),
+ is_inconsistent_(false) {
+}
+
+SessionStorageDatabase::~SessionStorageDatabase() {
+}
+
+void SessionStorageDatabase::ReadAreaValues(const std::string& namespace_id,
+ const GURL& origin,
+ DOMStorageValuesMap* result) {
+ // We don't create a database if it doesn't exist. In that case, there is
+ // nothing to be added to the result.
+ if (!LazyOpen(false))
+ return;
+
+ // While ReadAreaValues is in progress, another thread can call
+ // CommitAreaChanges. CommitAreaChanges might update map ref count key while
+ // this thread is iterating over the map ref count key. To protect the reading
+ // operation, create a snapshot and read from it.
+ leveldb::ReadOptions options;
+ options.snapshot = db_->GetSnapshot();
+
+ std::string map_id;
+ bool exists;
+ if (GetMapForArea(namespace_id, origin.spec(), options, &exists, &map_id) &&
+ exists)
+ ReadMap(map_id, options, result, false);
+ db_->ReleaseSnapshot(options.snapshot);
+}
+
+bool SessionStorageDatabase::CommitAreaChanges(
+ const std::string& namespace_id,
+ const GURL& origin,
+ bool clear_all_first,
+ const DOMStorageValuesMap& changes) {
+ // Even if |changes| is empty, we need to write the appropriate placeholders
+ // in the database, so that it can be later shallow-copied succssfully.
+ if (!LazyOpen(true))
+ return false;
+
+ leveldb::WriteBatch batch;
+ // Ensure that the keys "namespace-" "namespace-N" (see the schema above)
+ // exist.
+ const bool kOkIfExists = true;
+ if (!CreateNamespace(namespace_id, kOkIfExists, &batch))
+ return false;
+
+ std::string map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id, origin.spec(), leveldb::ReadOptions(),
+ &exists, &map_id))
+ return false;
+ if (exists) {
+ int64 ref_count;
+ if (!GetMapRefCount(map_id, &ref_count))
+ return false;
+ if (ref_count > 1) {
+ if (!DeepCopyArea(namespace_id, origin, !clear_all_first,
+ &map_id, &batch))
+ return false;
+ }
+ else if (clear_all_first) {
+ if (!ClearMap(map_id, &batch))
+ return false;
+ }
+ } else {
+ // Map doesn't exist, create it now if needed.
+ if (!changes.empty()) {
+ if (!CreateMapForArea(namespace_id, origin, &map_id, &batch))
+ return false;
+ }
+ }
+
+ WriteValuesToMap(map_id, changes, &batch);
+
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::CloneNamespace(
+ const std::string& namespace_id, const std::string& new_namespace_id) {
+ // Go through all origins in the namespace |namespace_id|, create placeholders
+ // for them in |new_namespace_id|, and associate them with the existing maps.
+
+ // Example, data before shallow copy:
+ // | map-1- | 1 (refcount) |
+ // | map-1-a | b |
+ // | namespace-1- (1 = namespace id)| dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+
+ // Example, data after shallow copy:
+ // | map-1- | 2 (inc. refcount) |
+ // | map-1-a | b |
+ // | namespace-1-(1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2- | dummy |
+ // | namespace-2-origin1 | 1 (mapid) << references the same map
+
+ if (!LazyOpen(true))
+ return false;
+
+ leveldb::WriteBatch batch;
+ const bool kOkIfExists = false;
+ if (!CreateNamespace(new_namespace_id, kOkIfExists, &batch))
+ return false;
+
+ std::map<std::string, std::string> areas;
+ if (!GetAreasInNamespace(namespace_id, &areas))
+ return false;
+
+ for (std::map<std::string, std::string>::const_iterator it = areas.begin();
+ it != areas.end(); ++it) {
+ const std::string& origin = it->first;
+ const std::string& map_id = it->second;
+ if (!IncreaseMapRefCount(map_id, &batch))
+ return false;
+ AddAreaToNamespace(new_namespace_id, origin, map_id, &batch);
+ }
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::DeleteArea(const std::string& namespace_id,
+ const GURL& origin) {
+ if (!LazyOpen(false)) {
+ // No need to create the database if it doesn't exist.
+ return true;
+ }
+ leveldb::WriteBatch batch;
+ if (!DeleteAreaHelper(namespace_id, origin.spec(), &batch))
+ return false;
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::DeleteNamespace(const std::string& namespace_id) {
+ if (!LazyOpen(false)) {
+ // No need to create the database if it doesn't exist.
+ return true;
+ }
+ // Itereate through the areas in the namespace.
+ leveldb::WriteBatch batch;
+ std::map<std::string, std::string> areas;
+ if (!GetAreasInNamespace(namespace_id, &areas))
+ return false;
+ for (std::map<std::string, std::string>::const_iterator it = areas.begin();
+ it != areas.end(); ++it) {
+ const std::string& origin = it->first;
+ if (!DeleteAreaHelper(namespace_id, origin, &batch))
+ return false;
+ }
+ batch.Delete(NamespaceStartKey(namespace_id));
+ leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::ReadNamespacesAndOrigins(
+ std::map<std::string, std::vector<GURL> >* namespaces_and_origins) {
+ if (!LazyOpen(true))
+ return false;
+
+ // While ReadNamespacesAndOrigins is in progress, another thread can call
+ // CommitAreaChanges. To protect the reading operation, create a snapshot and
+ // read from it.
+ leveldb::ReadOptions options;
+ options.snapshot = db_->GetSnapshot();
+
+ std::string namespace_prefix = NamespacePrefix();
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
+ it->Seek(namespace_prefix);
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid.
+ if (!it->Valid()) {
+ db_->ReleaseSnapshot(options.snapshot);
+ return true;
+ }
+
+ if (!DatabaseErrorCheck(it->status().ok())) {
+ db_->ReleaseSnapshot(options.snapshot);
+ return false;
+ }
+
+ // Skip the dummy entry "namespace-" and iterate the namespaces.
+ std::string current_namespace_start_key;
+ std::string current_namespace_id;
+ for (it->Next(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ if (key.find(namespace_prefix) != 0) {
+ // Iterated past the "namespace-" keys.
+ break;
+ }
+ // For each namespace, the first key is "namespace-<namespaceid>-", and the
+ // subsequent keys are "namespace-<namespaceid>-<origin>". Read the unique
+ // "<namespaceid>" parts from the keys.
+ if (current_namespace_start_key.empty() ||
+ key.substr(0, current_namespace_start_key.length()) !=
+ current_namespace_start_key) {
+ // The key is of the form "namespace-<namespaceid>-" for a new
+ // <namespaceid>.
+ current_namespace_start_key = key;
+ current_namespace_id =
+ key.substr(namespace_prefix.length(),
+ key.length() - namespace_prefix.length() - 1);
+ // Ensure that we keep track of the namespace even if it doesn't contain
+ // any origins.
+ namespaces_and_origins->insert(
+ std::make_pair(current_namespace_id, std::vector<GURL>()));
+ } else {
+ // The key is of the form "namespace-<namespaceid>-<origin>".
+ std::string origin = key.substr(current_namespace_start_key.length());
+ (*namespaces_and_origins)[current_namespace_id].push_back(GURL(origin));
+ }
+ }
+ db_->ReleaseSnapshot(options.snapshot);
+ return true;
+}
+
+bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
+ base::AutoLock auto_lock(db_lock_);
+ if (db_error_ || is_inconsistent_) {
+ // Don't try to open a database that we know has failed already.
+ return false;
+ }
+ if (IsOpen())
+ return true;
+
+ if (!create_if_needed &&
+ (!base::PathExists(file_path_) ||
+ file_util::IsDirectoryEmpty(file_path_))) {
+ // If the directory doesn't exist already and we haven't been asked to
+ // create a file on disk, then we don't bother opening the database. This
+ // means we wait until we absolutely need to put something onto disk before
+ // we do so.
+ return false;
+ }
+
+ leveldb::DB* db;
+ leveldb::Status s = TryToOpen(&db);
+ if (!s.ok()) {
+ LOG(WARNING) << "Failed to open leveldb in " << file_path_.value()
+ << ", error: " << s.ToString();
+ DCHECK(db == NULL);
+
+ // Clear the directory and try again.
+ base::DeleteFile(file_path_, true);
+ s = TryToOpen(&db);
+ if (!s.ok()) {
+ LOG(WARNING) << "Failed to open leveldb in " << file_path_.value()
+ << ", error: " << s.ToString();
+ UMA_HISTOGRAM_ENUMERATION(session_storage_uma_name,
+ SESSION_STORAGE_UMA_FAIL,
+ SESSION_STORAGE_UMA_MAX);
+ DCHECK(db == NULL);
+ db_error_ = true;
+ return false;
+ }
+ UMA_HISTOGRAM_ENUMERATION(session_storage_uma_name,
+ SESSION_STORAGE_UMA_RECREATED,
+ SESSION_STORAGE_UMA_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(session_storage_uma_name,
+ SESSION_STORAGE_UMA_SUCCESS,
+ SESSION_STORAGE_UMA_MAX);
+ }
+ db_.reset(db);
+ return true;
+}
+
+leveldb::Status SessionStorageDatabase::TryToOpen(leveldb::DB** db) {
+ leveldb::Options options;
+ // The directory exists but a valid leveldb database might not exist inside it
+ // (e.g., a subset of the needed files might be missing). Handle this
+ // situation gracefully by creating the database now.
+ options.max_open_files = 0; // Use minimum.
+ options.create_if_missing = true;
+#if defined(OS_WIN)
+ return leveldb::DB::Open(options, WideToUTF8(file_path_.value()), db);
+#elif defined(OS_POSIX)
+ return leveldb::DB::Open(options, file_path_.value(), db);
+#endif
+}
+
+bool SessionStorageDatabase::IsOpen() const {
+ return db_.get() != NULL;
+}
+
+bool SessionStorageDatabase::CallerErrorCheck(bool ok) const {
+ DCHECK(ok);
+ return ok;
+}
+
+bool SessionStorageDatabase::ConsistencyCheck(bool ok) {
+ if (ok)
+ return true;
+ base::AutoLock auto_lock(db_lock_);
+ DCHECK(false);
+ is_inconsistent_ = true;
+ // We cannot recover the database during this run, e.g., the upper layer can
+ // have a different understanding of the database state (shallow and deep
+ // copies).
+ // TODO(marja): Error handling.
+ return false;
+}
+
+bool SessionStorageDatabase::DatabaseErrorCheck(bool ok) {
+ if (ok)
+ return true;
+ base::AutoLock auto_lock(db_lock_);
+ db_error_ = true;
+ // TODO(marja): Error handling.
+ return false;
+}
+
+bool SessionStorageDatabase::CreateNamespace(const std::string& namespace_id,
+ bool ok_if_exists,
+ leveldb::WriteBatch* batch) {
+ leveldb::Slice namespace_prefix = NamespacePrefix();
+ std::string dummy;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), namespace_prefix,
+ &dummy);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ if (s.IsNotFound())
+ batch->Put(namespace_prefix, "");
+
+ std::string namespace_start_key = NamespaceStartKey(namespace_id);
+ s = db_->Get(leveldb::ReadOptions(), namespace_start_key, &dummy);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ if (s.IsNotFound()) {
+ batch->Put(namespace_start_key, "");
+ return true;
+ }
+ return CallerErrorCheck(ok_if_exists);
+}
+
+bool SessionStorageDatabase::GetAreasInNamespace(
+ const std::string& namespace_id,
+ std::map<std::string, std::string>* areas) {
+ std::string namespace_start_key = NamespaceStartKey(namespace_id);
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ it->Seek(namespace_start_key);
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid.
+ if (!it->Valid()) {
+ // The namespace_start_key is not found when the namespace doesn't contain
+ // any areas. We don't need to do anything.
+ return true;
+ }
+ if (!DatabaseErrorCheck(it->status().ok()))
+ return false;
+
+ // Skip the dummy entry "namespace-<namespaceid>-" and iterate the origins.
+ for (it->Next(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ if (key.find(namespace_start_key) != 0) {
+ // Iterated past the origins for this namespace.
+ break;
+ }
+ std::string origin = key.substr(namespace_start_key.length());
+ std::string map_id = it->value().ToString();
+ (*areas)[origin] = map_id;
+ }
+ return true;
+}
+
+void SessionStorageDatabase::AddAreaToNamespace(const std::string& namespace_id,
+ const std::string& origin,
+ const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ std::string namespace_key = NamespaceKey(namespace_id, origin);
+ batch->Put(namespace_key, map_id);
+}
+
+bool SessionStorageDatabase::DeleteAreaHelper(
+ const std::string& namespace_id,
+ const std::string& origin,
+ leveldb::WriteBatch* batch) {
+ std::string map_id;
+ bool exists;
+ if (!GetMapForArea(namespace_id, origin, leveldb::ReadOptions(), &exists,
+ &map_id))
+ return false;
+ if (!exists)
+ return true; // Nothing to delete.
+ if (!DecreaseMapRefCount(map_id, 1, batch))
+ return false;
+ std::string namespace_key = NamespaceKey(namespace_id, origin);
+ batch->Delete(namespace_key);
+
+ // If this was the only area in the namespace, delete the namespace start key,
+ // too.
+ std::string namespace_start_key = NamespaceStartKey(namespace_id);
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ it->Seek(namespace_start_key);
+ if (!ConsistencyCheck(it->Valid()))
+ return false;
+ // Advance the iterator 2 times (we still haven't really deleted
+ // namespace_key).
+ it->Next();
+ if (!ConsistencyCheck(it->Valid()))
+ return false;
+ it->Next();
+ if (!it->Valid())
+ return true;
+ std::string key = it->key().ToString();
+ if (key.find(namespace_start_key) != 0)
+ batch->Delete(namespace_start_key);
+ return true;
+}
+
+bool SessionStorageDatabase::GetMapForArea(const std::string& namespace_id,
+ const std::string& origin,
+ const leveldb::ReadOptions& options,
+ bool* exists, std::string* map_id) {
+ std::string namespace_key = NamespaceKey(namespace_id, origin);
+ leveldb::Status s = db_->Get(options, namespace_key, map_id);
+ if (s.IsNotFound()) {
+ *exists = false;
+ return true;
+ }
+ *exists = true;
+ return DatabaseErrorCheck(s.ok());
+}
+
+bool SessionStorageDatabase::CreateMapForArea(const std::string& namespace_id,
+ const GURL& origin,
+ std::string* map_id,
+ leveldb::WriteBatch* batch) {
+ leveldb::Slice next_map_id_key = NextMapIdKey();
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), next_map_id_key, map_id);
+ if (!DatabaseErrorCheck(s.ok() || s.IsNotFound()))
+ return false;
+ int64 next_map_id = 0;
+ if (s.IsNotFound()) {
+ *map_id = "0";
+ } else {
+ bool conversion_ok = base::StringToInt64(*map_id, &next_map_id);
+ if (!ConsistencyCheck(conversion_ok))
+ return false;
+ }
+ batch->Put(next_map_id_key, base::Int64ToString(++next_map_id));
+ std::string namespace_key = NamespaceKey(namespace_id, origin.spec());
+ batch->Put(namespace_key, *map_id);
+ batch->Put(MapRefCountKey(*map_id), "1");
+ return true;
+}
+
+bool SessionStorageDatabase::ReadMap(const std::string& map_id,
+ const leveldb::ReadOptions& options,
+ DOMStorageValuesMap* result,
+ bool only_keys) {
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
+ std::string map_start_key = MapRefCountKey(map_id);
+ it->Seek(map_start_key);
+ // If the key is not found, the status of the iterator won't be IsNotFound(),
+ // but the iterator will be invalid. The map needs to exist, otherwise we have
+ // a stale map_id in the database.
+ if (!ConsistencyCheck(it->Valid()))
+ return false;
+ if (!DatabaseErrorCheck(it->status().ok()))
+ return false;
+ // Skip the dummy entry "map-<mapid>-".
+ for (it->Next(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ if (key.find(map_start_key) != 0) {
+ // Iterated past the keys in this map.
+ break;
+ }
+ // Key is of the form "map-<mapid>-<key>".
+ base::string16 key16 = UTF8ToUTF16(key.substr(map_start_key.length()));
+ if (only_keys) {
+ (*result)[key16] = base::NullableString16();
+ } else {
+ // Convert the raw data stored in std::string (it->value()) to raw data
+ // stored in base::string16.
+ size_t len = it->value().size() / sizeof(char16);
+ const char16* data_ptr =
+ reinterpret_cast<const char16*>(it->value().data());
+ (*result)[key16] =
+ base::NullableString16(base::string16(data_ptr, len), false);
+ }
+ }
+ return true;
+}
+
+void SessionStorageDatabase::WriteValuesToMap(const std::string& map_id,
+ const DOMStorageValuesMap& values,
+ leveldb::WriteBatch* batch) {
+ for (DOMStorageValuesMap::const_iterator it = values.begin();
+ it != values.end();
+ ++it) {
+ base::NullableString16 value = it->second;
+ std::string key = MapKey(map_id, UTF16ToUTF8(it->first));
+ if (value.is_null()) {
+ batch->Delete(key);
+ } else {
+ // Convert the raw data stored in base::string16 to raw data stored in
+ // std::string.
+ const char* data = reinterpret_cast<const char*>(value.string().data());
+ size_t size = value.string().size() * 2;
+ batch->Put(key, leveldb::Slice(data, size));
+ }
+ }
+}
+
+bool SessionStorageDatabase::GetMapRefCount(const std::string& map_id,
+ int64* ref_count) {
+ std::string ref_count_string;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(),
+ MapRefCountKey(map_id), &ref_count_string);
+ if (!ConsistencyCheck(s.ok()))
+ return false;
+ bool conversion_ok = base::StringToInt64(ref_count_string, ref_count);
+ return ConsistencyCheck(conversion_ok);
+}
+
+bool SessionStorageDatabase::IncreaseMapRefCount(const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ // Increase the ref count for the map.
+ int64 old_ref_count;
+ if (!GetMapRefCount(map_id, &old_ref_count))
+ return false;
+ batch->Put(MapRefCountKey(map_id), base::Int64ToString(++old_ref_count));
+ return true;
+}
+
+bool SessionStorageDatabase::DecreaseMapRefCount(const std::string& map_id,
+ int decrease,
+ leveldb::WriteBatch* batch) {
+ // Decrease the ref count for the map.
+ int64 ref_count;
+ if (!GetMapRefCount(map_id, &ref_count))
+ return false;
+ if (!ConsistencyCheck(decrease <= ref_count))
+ return false;
+ ref_count -= decrease;
+ if (ref_count > 0) {
+ batch->Put(MapRefCountKey(map_id), base::Int64ToString(ref_count));
+ } else {
+ // Clear all keys in the map.
+ if (!ClearMap(map_id, batch))
+ return false;
+ batch->Delete(MapRefCountKey(map_id));
+ }
+ return true;
+}
+
+bool SessionStorageDatabase::ClearMap(const std::string& map_id,
+ leveldb::WriteBatch* batch) {
+ DOMStorageValuesMap values;
+ if (!ReadMap(map_id, leveldb::ReadOptions(), &values, true))
+ return false;
+ for (DOMStorageValuesMap::const_iterator it = values.begin();
+ it != values.end(); ++it)
+ batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first)));
+ return true;
+}
+
+bool SessionStorageDatabase::DeepCopyArea(
+ const std::string& namespace_id, const GURL& origin, bool copy_data,
+ std::string* map_id, leveldb::WriteBatch* batch) {
+ // Example, data before deep copy:
+ // | namespace-1- (1 = namespace id)| dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2- | dummy |
+ // | namespace-2-origin1 | 1 (mapid) << references the same map
+ // | map-1- | 2 (refcount) |
+ // | map-1-a | b |
+
+ // Example, data after deep copy copy:
+ // | namespace-1-(1 = namespace id) | dummy |
+ // | namespace-1-origin1 | 1 (mapid) |
+ // | namespace-2- | dummy |
+ // | namespace-2-origin1 | 2 (mapid) << references the new map
+ // | map-1- | 1 (dec. refcount) |
+ // | map-1-a | b |
+ // | map-2- | 1 (refcount) |
+ // | map-2-a | b |
+
+ // Read the values from the old map here. If we don't need to copy the data,
+ // this can stay empty.
+ DOMStorageValuesMap values;
+ if (copy_data && !ReadMap(*map_id, leveldb::ReadOptions(), &values, false))
+ return false;
+ if (!DecreaseMapRefCount(*map_id, 1, batch))
+ return false;
+ // Create a new map (this will also break the association to the old map) and
+ // write the old data into it. This will write the id of the created map into
+ // |map_id|.
+ if (!CreateMapForArea(namespace_id, origin, map_id, batch))
+ return false;
+ WriteValuesToMap(*map_id, values, batch);
+ return true;
+}
+
+std::string SessionStorageDatabase::NamespaceStartKey(
+ const std::string& namespace_id) {
+ return base::StringPrintf("namespace-%s-", namespace_id.c_str());
+}
+
+std::string SessionStorageDatabase::NamespaceKey(
+ const std::string& namespace_id, const std::string& origin) {
+ return base::StringPrintf("namespace-%s-%s", namespace_id.c_str(),
+ origin.c_str());
+}
+
+const char* SessionStorageDatabase::NamespacePrefix() {
+ return "namespace-";
+}
+
+std::string SessionStorageDatabase::MapRefCountKey(const std::string& map_id) {
+ return base::StringPrintf("map-%s-", map_id.c_str());
+}
+
+std::string SessionStorageDatabase::MapKey(const std::string& map_id,
+ const std::string& key) {
+ return base::StringPrintf("map-%s-%s", map_id.c_str(), key.c_str());
+}
+
+const char* SessionStorageDatabase::NextMapIdKey() {
+ return "next-map-id";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/session_storage_database.h b/chromium/content/browser/dom_storage/session_storage_database.h
new file mode 100644
index 00000000000..09d4773fea1
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_database.h
@@ -0,0 +1,205 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
+#define CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
+
+#include <map>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+
+class GURL;
+
+namespace leveldb {
+class DB;
+struct ReadOptions;
+class WriteBatch;
+} // namespace leveldb
+
+namespace content {
+
+// SessionStorageDatabase holds the data from multiple namespaces and multiple
+// origins. All DOMStorageAreas for session storage share the same
+// SessionStorageDatabase.
+
+// Only one thread is allowed to call the public functions other than
+// ReadAreaValues and ReadNamespacesAndOrigins. Other threads are allowed to
+// call ReadAreaValues and ReadNamespacesAndOrigins.
+class CONTENT_EXPORT SessionStorageDatabase :
+ public base::RefCountedThreadSafe<SessionStorageDatabase> {
+ public:
+ explicit SessionStorageDatabase(const base::FilePath& file_path);
+
+ // Reads the (key, value) pairs for |namespace_id| and |origin|. |result| is
+ // assumed to be empty and any duplicate keys will be overwritten. If the
+ // database exists on disk then it will be opened. If it does not exist then
+ // it will not be created and |result| will be unmodified.
+ void ReadAreaValues(const std::string& namespace_id,
+ const GURL& origin,
+ DOMStorageValuesMap* result);
+
+ // Updates the data for |namespace_id| and |origin|. Will remove all keys
+ // before updating the database if |clear_all_first| is set. Then all entries
+ // in |changes| will be examined - keys mapped to a null NullableString16 will
+ // be removed and all others will be inserted/updated as appropriate. It is
+ // allowed to write data into a shallow copy created by CloneNamespace, and in
+ // that case the copy will be made deep before writing the values.
+ bool CommitAreaChanges(const std::string& namespace_id,
+ const GURL& origin,
+ bool clear_all_first,
+ const DOMStorageValuesMap& changes);
+
+ // Creates shallow copies of the areas for |namespace_id| and associates them
+ // with |new_namespace_id|.
+ bool CloneNamespace(const std::string& namespace_id,
+ const std::string& new_namespace_id);
+
+ // Deletes the data for |namespace_id| and |origin|.
+ bool DeleteArea(const std::string& namespace_id, const GURL& origin);
+
+ // Deletes the data for |namespace_id|.
+ bool DeleteNamespace(const std::string& namespace_id);
+
+ // Reads the namespace IDs and origins present in the database.
+ bool ReadNamespacesAndOrigins(
+ std::map<std::string, std::vector<GURL> >* namespaces_and_origins);
+
+ private:
+ friend class base::RefCountedThreadSafe<SessionStorageDatabase>;
+ friend class SessionStorageDatabaseTest;
+
+ ~SessionStorageDatabase();
+
+ // Opens the database at file_path_ if it exists already and creates it if
+ // |create_if_needed| is true. Returns true if the database was opened, false
+ // if the opening failed or was not necessary (the database doesn't exist and
+ // |create_if_needed| is false). The possible failures are:
+ // - leveldb cannot open the database.
+ // - The database is in an inconsistent or errored state.
+ bool LazyOpen(bool create_if_needed);
+
+ // Tries to open the database at file_path_, assigns |db| to point to the
+ // opened leveldb::DB instance.
+ leveldb::Status TryToOpen(leveldb::DB** db);
+
+ // Returns true if the database is already open, false otherwise.
+ bool IsOpen() const;
+
+ // Helpers for checking caller erros, invariants and database errors. All
+ // these return |ok|, for chaining.
+ bool CallerErrorCheck(bool ok) const;
+ bool ConsistencyCheck(bool ok);
+ bool DatabaseErrorCheck(bool ok);
+
+ // Helper functions. All return true if the operation succeeded, and false if
+ // it failed (a database error or a consistency error). If the return type is
+ // void, the operation cannot fail. If they return false, ConsistencyCheck or
+ // DatabaseErrorCheck have already been called.
+
+ // Creates a namespace for |namespace_id| and updates the next namespace id if
+ // needed. If |ok_if_exists| is false, checks that the namespace didn't exist
+ // before.
+ bool CreateNamespace(const std::string& namespace_id,
+ bool ok_if_exists,
+ leveldb::WriteBatch* batch);
+
+ // Reads the areas assoiated with |namespace_id| and puts the (origin, map_id)
+ // pairs into |areas|.
+ bool GetAreasInNamespace(const std::string& namespace_id,
+ std::map<std::string, std::string>* areas);
+
+ // Adds an association between |origin| and |map_id| into the namespace
+ // |namespace_id|.
+ void AddAreaToNamespace(const std::string& namespace_id,
+ const std::string& origin,
+ const std::string& map_id,
+ leveldb::WriteBatch* batch);
+
+ // Helpers for deleting data for |namespace_id| and |origin|.
+ bool DeleteAreaHelper(const std::string& namespace_id,
+ const std::string& origin,
+ leveldb::WriteBatch* batch);
+
+ // Retrieves the map id for |namespace_id| and |origin|. It's not an error if
+ // the map doesn't exist.
+ bool GetMapForArea(const std::string& namespace_id,
+ const std::string& origin,
+ const leveldb::ReadOptions& options,
+ bool* exists,
+ std::string* map_id);
+
+ // Creates a new map for |namespace_id| and |origin|. |map_id| will hold the
+ // id of the created map. If there is a map for |namespace_id| and |origin|,
+ // this just overwrites the map id. The caller is responsible for decreasing
+ // the ref count.
+ bool CreateMapForArea(const std::string& namespace_id,
+ const GURL& origin,
+ std::string* map_id,
+ leveldb::WriteBatch* batch);
+ // Reads the contents of the map |map_id| into |result|. If |only_keys| is
+ // true, only keys are aread from the database and the values in |result| will
+ // be empty.
+ bool ReadMap(const std::string& map_id,
+ const leveldb::ReadOptions& options,
+ DOMStorageValuesMap* result,
+ bool only_keys);
+ // Writes |values| into the map |map_id|.
+ void WriteValuesToMap(const std::string& map_id,
+ const DOMStorageValuesMap& values,
+ leveldb::WriteBatch* batch);
+
+ bool GetMapRefCount(const std::string& map_id, int64* ref_count);
+ bool IncreaseMapRefCount(const std::string& map_id,
+ leveldb::WriteBatch* batch);
+ // Decreases the ref count of a map by |decrease|. If the ref count goes to 0,
+ // deletes the map.
+ bool DecreaseMapRefCount(const std::string& map_id,
+ int decrease,
+ leveldb::WriteBatch* batch);
+
+ // Deletes all values in |map_id|.
+ bool ClearMap(const std::string& map_id, leveldb::WriteBatch* batch);
+
+ // Breaks the association between (|namespace_id|, |origin|) and |map_id| and
+ // creates a new map for (|namespace_id|, |origin|). Copies the data from the
+ // old map if |copy_data| is true.
+ bool DeepCopyArea(const std::string& namespace_id,
+ const GURL& origin,
+ bool copy_data,
+ std::string* map_id,
+ leveldb::WriteBatch* batch);
+
+ // Helper functions for creating the keys needed for the schema.
+ static std::string NamespaceStartKey(const std::string& namespace_id);
+ static std::string NamespaceKey(const std::string& namespace_id,
+ const std::string& origin);
+ static const char* NamespacePrefix();
+ static std::string MapRefCountKey(const std::string& map_id);
+ static std::string MapKey(const std::string& map_id, const std::string& key);
+ static const char* NextMapIdKey();
+
+ scoped_ptr<leveldb::DB> db_;
+ base::FilePath file_path_;
+
+ // For protecting the database opening code.
+ base::Lock db_lock_;
+
+ // True if a database error has occurred (e.g., cannot read data).
+ bool db_error_;
+ // True if the database is in an inconsistent state.
+ bool is_inconsistent_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabase);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_H_
diff --git a/chromium/content/browser/dom_storage/session_storage_database_adapter.cc b/chromium/content/browser/dom_storage/session_storage_database_adapter.cc
new file mode 100644
index 00000000000..69f5bcdc414
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_database_adapter.cc
@@ -0,0 +1,32 @@
+// 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 "content/browser/dom_storage/session_storage_database_adapter.h"
+
+#include "content/browser/dom_storage/session_storage_database.h"
+
+namespace content {
+
+SessionStorageDatabaseAdapter::SessionStorageDatabaseAdapter(
+ SessionStorageDatabase* db,
+ const std::string& permanent_namespace_id,
+ const GURL& origin)
+ : db_(db),
+ permanent_namespace_id_(permanent_namespace_id),
+ origin_(origin) {
+}
+
+SessionStorageDatabaseAdapter::~SessionStorageDatabaseAdapter() { }
+
+void SessionStorageDatabaseAdapter::ReadAllValues(DOMStorageValuesMap* result) {
+ db_->ReadAreaValues(permanent_namespace_id_, origin_, result);
+}
+
+bool SessionStorageDatabaseAdapter::CommitChanges(
+ bool clear_all_first, const DOMStorageValuesMap& changes) {
+ return db_->CommitAreaChanges(permanent_namespace_id_, origin_,
+ clear_all_first, changes);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/session_storage_database_adapter.h b/chromium/content/browser/dom_storage/session_storage_database_adapter.h
new file mode 100644
index 00000000000..9db5f173341
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_database_adapter.h
@@ -0,0 +1,35 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_ADAPTER_H_
+#define CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_ADAPTER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/dom_storage/dom_storage_database_adapter.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class SessionStorageDatabase;
+
+class SessionStorageDatabaseAdapter : public DOMStorageDatabaseAdapter {
+ public:
+ SessionStorageDatabaseAdapter(SessionStorageDatabase* db,
+ const std::string& permanent_namespace_id,
+ const GURL& origin);
+ virtual ~SessionStorageDatabaseAdapter();
+ virtual void ReadAllValues(DOMStorageValuesMap* result) OVERRIDE;
+ virtual bool CommitChanges(bool clear_all_first,
+ const DOMStorageValuesMap& changes) OVERRIDE;
+ private:
+ scoped_refptr<SessionStorageDatabase> db_;
+ std::string permanent_namespace_id_;
+ GURL origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabaseAdapter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_DATABASE_ADAPTER_H_
diff --git a/chromium/content/browser/dom_storage/session_storage_database_unittest.cc b/chromium/content/browser/dom_storage/session_storage_database_unittest.cc
new file mode 100644
index 00000000000..2f86c3b9918
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_database_unittest.cc
@@ -0,0 +1,798 @@
+// 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 "content/browser/dom_storage/session_storage_database.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class SessionStorageDatabaseTest : public testing::Test {
+ public:
+ SessionStorageDatabaseTest();
+ virtual ~SessionStorageDatabaseTest();
+ virtual void SetUp() OVERRIDE;
+
+ protected:
+ typedef std::map<std::string, std::string> DataMap;
+
+ // Helpers.
+ static bool IsNamespaceKey(const std::string& key,
+ std::string* namespace_id);
+ static bool IsNamespaceOriginKey(const std::string& key,
+ std::string* namespace_id);
+ static bool IsMapRefCountKey(const std::string& key,
+ int64* map_id);
+ static bool IsMapValueKey(const std::string& key,
+ int64* map_id);
+ void ResetDatabase();
+ void ReadData(DataMap* data) const;
+ void CheckDatabaseConsistency() const;
+ void CheckEmptyDatabase() const;
+ void DumpData() const;
+ void CheckAreaData(const std::string& namespace_id,
+ const GURL& origin,
+ const DOMStorageValuesMap& reference) const;
+ void CompareValuesMaps(const DOMStorageValuesMap& map1,
+ const DOMStorageValuesMap& map2) const;
+ void CheckNamespaceIds(
+ const std::set<std::string>& expected_namespace_ids) const;
+ void CheckOrigins(
+ const std::string& namespace_id,
+ const std::set<GURL>& expected_origins) const;
+ std::string GetMapForArea(const std::string& namespace_id,
+ const GURL& origin) const;
+ int64 GetMapRefCount(const std::string& map_id) const;
+
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<SessionStorageDatabase> db_;
+
+ // Test data.
+ const GURL kOrigin1;
+ const GURL kOrigin2;
+ const std::string kNamespace1;
+ const std::string kNamespace2;
+ const std::string kNamespaceClone;
+ const base::string16 kKey1;
+ const base::string16 kKey2;
+ const base::string16 kKey3;
+ const base::NullableString16 kValue1;
+ const base::NullableString16 kValue2;
+ const base::NullableString16 kValue3;
+ const base::NullableString16 kValue4;
+ const base::NullableString16 kValueNull;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabaseTest);
+};
+
+SessionStorageDatabaseTest::SessionStorageDatabaseTest()
+ : kOrigin1("http://www.origin1.com"),
+ kOrigin2("http://www.origin2.com"),
+ kNamespace1("namespace1"),
+ kNamespace2("namespace2"),
+ kNamespaceClone("wascloned"),
+ kKey1(ASCIIToUTF16("key1")),
+ kKey2(ASCIIToUTF16("key2")),
+ kKey3(ASCIIToUTF16("key3")),
+ kValue1(ASCIIToUTF16("value1"), false),
+ kValue2(ASCIIToUTF16("value2"), false),
+ kValue3(ASCIIToUTF16("value3"), false),
+ kValue4(ASCIIToUTF16("value4"), false) { }
+
+SessionStorageDatabaseTest::~SessionStorageDatabaseTest() { }
+
+void SessionStorageDatabaseTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ResetDatabase();
+}
+
+void SessionStorageDatabaseTest::ResetDatabase() {
+ db_ = new SessionStorageDatabase(temp_dir_.path());
+ ASSERT_TRUE(db_->LazyOpen(true));
+}
+
+// static
+bool SessionStorageDatabaseTest::IsNamespaceKey(const std::string& key,
+ std::string* namespace_id) {
+ std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
+ if (key.find(namespace_prefix) != 0)
+ return false;
+ if (key == namespace_prefix)
+ return false;
+
+ size_t second_dash = key.find('-', namespace_prefix.length());
+ if (second_dash != key.length() - 1)
+ return false;
+
+ // Key is of the form "namespace-<namespaceid>-".
+ *namespace_id = key.substr(
+ namespace_prefix.length(),
+ second_dash - namespace_prefix.length());
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsNamespaceOriginKey(
+ const std::string& key,
+ std::string* namespace_id) {
+ std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
+ if (key.find(namespace_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', namespace_prefix.length());
+ if (second_dash == std::string::npos || second_dash == key.length() - 1)
+ return false;
+
+ // Key is of the form "namespace-<namespaceid>-<origin>", and the value
+ // is the map id.
+ *namespace_id = key.substr(
+ namespace_prefix.length(),
+ second_dash - namespace_prefix.length());
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsMapRefCountKey(const std::string& key,
+ int64* map_id) {
+ std::string map_prefix = "map-";
+ if (key.find(map_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', map_prefix.length());
+ if (second_dash != key.length() - 1)
+ return false;
+ // Key is of the form "map-<mapid>-" and the value is the ref count.
+ std::string map_id_str = key.substr(map_prefix.length(),
+ second_dash - map_prefix.length());
+ bool conversion_ok = base::StringToInt64(map_id_str, map_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+// static
+bool SessionStorageDatabaseTest::IsMapValueKey(const std::string& key,
+ int64* map_id) {
+ std::string map_prefix = "map-";
+ if (key.find(map_prefix) != 0)
+ return false;
+ size_t second_dash = key.find('-', map_prefix.length());
+ if (second_dash == std::string::npos || second_dash == key.length() - 1)
+ return false;
+ // Key is of the form "map-<mapid>-key".
+ std::string map_id_str = key.substr(map_prefix.length(),
+ second_dash - map_prefix.length());
+ bool conversion_ok = base::StringToInt64(map_id_str, map_id);
+ EXPECT_TRUE(conversion_ok);
+ return true;
+}
+
+void SessionStorageDatabaseTest::ReadData(DataMap* data) const {
+ leveldb::DB* leveldb = db_->db_.get();
+ scoped_ptr<leveldb::Iterator> it(
+ leveldb->NewIterator(leveldb::ReadOptions()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ (*data)[it->key().ToString()] = it->value().ToString();
+ }
+}
+
+void SessionStorageDatabaseTest::CheckDatabaseConsistency() const {
+ DataMap data;
+ ReadData(&data);
+ // Empty db is ok.
+ if (data.empty())
+ return;
+
+ // For detecting rubbish keys.
+ size_t valid_keys = 0;
+
+ std::string next_map_id_key = SessionStorageDatabase::NextMapIdKey();
+ // Check the namespace start key.
+ if (data.find(SessionStorageDatabase::NamespacePrefix()) == data.end()) {
+ // If there is no namespace start key, the database may contain only counter
+ // keys.
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ ASSERT_TRUE(it->first == next_map_id_key);
+ }
+ return;
+ }
+ ++valid_keys;
+
+ // Iterate the "namespace-" keys.
+ std::set<std::string> found_namespace_ids;
+ std::set<std::string> namespaces_with_areas;
+ std::map<int64, int64> expected_map_refcounts;
+ int64 max_map_id = -1;
+
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ std::string namespace_id;
+ if (IsNamespaceKey(it->first, &namespace_id)) {
+ found_namespace_ids.insert(namespace_id);
+ ++valid_keys;
+ } else if (IsNamespaceOriginKey(
+ it->first, &namespace_id)) {
+ // Check that the corresponding "namespace-<namespaceid>-" key exists. It
+ // has been read by now, since the keys are stored in order.
+ ASSERT_TRUE(found_namespace_ids.find(namespace_id) !=
+ found_namespace_ids.end());
+ namespaces_with_areas.insert(namespace_id);
+ int64 map_id;
+ bool conversion_ok = base::StringToInt64(it->second, &map_id);
+ ASSERT_TRUE(conversion_ok);
+ ASSERT_GE(map_id, 0);
+ ++expected_map_refcounts[map_id];
+ max_map_id = std::max(map_id, max_map_id);
+ ++valid_keys;
+ }
+ }
+ // Check that there are no leftover "namespace-namespaceid-" keys without
+ // associated areas.
+ ASSERT_EQ(found_namespace_ids.size(), namespaces_with_areas.size());
+
+ if (max_map_id != -1) {
+ // The database contains maps.
+ ASSERT_TRUE(data.find(next_map_id_key) != data.end());
+ int64 next_map_id;
+ bool conversion_ok =
+ base::StringToInt64(data[next_map_id_key], &next_map_id);
+ ASSERT_TRUE(conversion_ok);
+ ASSERT_GT(next_map_id, max_map_id);
+ }
+
+ // Iterate the "map-" keys.
+ std::set<int64> found_map_ids;
+ for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
+ int64 map_id;
+ if (IsMapRefCountKey(it->first, &map_id)) {
+ int64 ref_count;
+ bool conversion_ok = base::StringToInt64(it->second, &ref_count);
+ ASSERT_TRUE(conversion_ok);
+ // Check that the map is not stale.
+ ASSERT_GT(ref_count, 0);
+ ASSERT_TRUE(expected_map_refcounts.find(map_id) !=
+ expected_map_refcounts.end());
+ ASSERT_EQ(expected_map_refcounts[map_id], ref_count);
+ // Mark the map as existing.
+ expected_map_refcounts.erase(map_id);
+ found_map_ids.insert(map_id);
+ ++valid_keys;
+ } else if (IsMapValueKey(it->first, &map_id)) {
+ ASSERT_TRUE(found_map_ids.find(map_id) != found_map_ids.end());
+ ++valid_keys;
+ }
+ }
+ // Check that all maps referred to exist.
+ ASSERT_TRUE(expected_map_refcounts.empty());
+
+ if (data.find(next_map_id_key) != data.end())
+ ++valid_keys;
+
+ ASSERT_EQ(data.size(), valid_keys);
+}
+
+void SessionStorageDatabaseTest::CheckEmptyDatabase() const {
+ DataMap data;
+ ReadData(&data);
+ size_t valid_keys = 0;
+ if (data.find(SessionStorageDatabase::NamespacePrefix()) != data.end())
+ ++valid_keys;
+ if (data.find(SessionStorageDatabase::NextMapIdKey()) != data.end())
+ ++valid_keys;
+ EXPECT_EQ(valid_keys, data.size());
+}
+
+void SessionStorageDatabaseTest::DumpData() const {
+ LOG(WARNING) << "---- Session storage contents";
+ scoped_ptr<leveldb::Iterator> it(
+ db_->db_->NewIterator(leveldb::ReadOptions()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ int64 dummy_map_id;
+ if (IsMapValueKey(it->key().ToString(), &dummy_map_id)) {
+ // Convert the value back to base::string16.
+ base::string16 value;
+ size_t len = it->value().size() / sizeof(char16);
+ value.resize(len);
+ value.assign(reinterpret_cast<const char16*>(it->value().data()), len);
+ LOG(WARNING) << it->key().ToString() << ": " << value;
+ } else {
+ LOG(WARNING) << it->key().ToString() << ": " << it->value().ToString();
+ }
+ }
+ LOG(WARNING) << "----";
+}
+
+void SessionStorageDatabaseTest::CheckAreaData(
+ const std::string& namespace_id, const GURL& origin,
+ const DOMStorageValuesMap& reference) const {
+ DOMStorageValuesMap values;
+ db_->ReadAreaValues(namespace_id, origin, &values);
+ CompareValuesMaps(values, reference);
+}
+
+void SessionStorageDatabaseTest::CompareValuesMaps(
+ const DOMStorageValuesMap& map1,
+ const DOMStorageValuesMap& map2) const {
+ ASSERT_EQ(map2.size(), map1.size());
+ for (DOMStorageValuesMap::const_iterator it = map1.begin();
+ it != map1.end(); ++it) {
+ base::string16 key = it->first;
+ ASSERT_TRUE(map2.find(key) != map2.end());
+ base::NullableString16 val1 = it->second;
+ base::NullableString16 val2 = map2.find(key)->second;
+ EXPECT_EQ(val2.is_null(), val1.is_null());
+ EXPECT_EQ(val2.string(), val1.string());
+ }
+}
+
+void SessionStorageDatabaseTest::CheckNamespaceIds(
+ const std::set<std::string>& expected_namespace_ids) const {
+ std::map<std::string, std::vector<GURL> > namespaces_and_origins;
+ EXPECT_TRUE(db_->ReadNamespacesAndOrigins(&namespaces_and_origins));
+ EXPECT_EQ(expected_namespace_ids.size(), namespaces_and_origins.size());
+ for (std::map<std::string, std::vector<GURL> >::const_iterator it =
+ namespaces_and_origins.begin();
+ it != namespaces_and_origins.end(); ++it) {
+ EXPECT_TRUE(expected_namespace_ids.find(it->first) !=
+ expected_namespace_ids.end());
+ }
+}
+
+void SessionStorageDatabaseTest::CheckOrigins(
+ const std::string& namespace_id,
+ const std::set<GURL>& expected_origins) const {
+ std::map<std::string, std::vector<GURL> > namespaces_and_origins;
+ EXPECT_TRUE(db_->ReadNamespacesAndOrigins(&namespaces_and_origins));
+ const std::vector<GURL>& origins = namespaces_and_origins[namespace_id];
+ EXPECT_EQ(expected_origins.size(), origins.size());
+ for (std::vector<GURL>::const_iterator it = origins.begin();
+ it != origins.end(); ++it) {
+ EXPECT_TRUE(expected_origins.find(*it) != expected_origins.end());
+ }
+}
+
+std::string SessionStorageDatabaseTest::GetMapForArea(
+ const std::string& namespace_id, const GURL& origin) const {
+ bool exists;
+ std::string map_id;
+ EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin.spec(),
+ leveldb::ReadOptions(), &exists, &map_id));
+ EXPECT_TRUE(exists);
+ return map_id;
+}
+
+int64 SessionStorageDatabaseTest::GetMapRefCount(
+ const std::string& map_id) const {
+ int64 ref_count;
+ EXPECT_TRUE(db_->GetMapRefCount(map_id, &ref_count));
+ return ref_count;
+}
+
+TEST_F(SessionStorageDatabaseTest, EmptyDatabaseSanityCheck) {
+ // An empty database should be valid.
+ CheckDatabaseConsistency();
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForOneOrigin) {
+ // Keep track on what the values should look like.
+ DOMStorageValuesMap reference;
+ // Write data.
+ {
+ DOMStorageValuesMap changes;
+ changes[kKey1] = kValue1;
+ changes[kKey2] = kValue2;
+ changes[kKey3] = kValue3;
+ reference[kKey1] = kValue1;
+ reference[kKey2] = kValue2;
+ reference[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, reference);
+
+ // Overwrite and delete values.
+ {
+ DOMStorageValuesMap changes;
+ changes[kKey1] = kValue4;
+ changes[kKey3] = kValueNull;
+ reference[kKey1] = kValue4;
+ reference.erase(kKey3);
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, reference);
+
+ // Clear data before writing.
+ {
+ DOMStorageValuesMap changes;
+ changes[kKey2] = kValue2;
+ reference.erase(kKey1);
+ reference[kKey2] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, true, changes));
+ }
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, reference);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForTwoOrigins) {
+ // Write data.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue4;
+ data2[kKey2] = kValue1;
+ data2[kKey3] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+ CheckAreaData(kNamespace1, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteDataForTwoNamespaces) {
+ // Write data.
+ DOMStorageValuesMap data11;
+ data11[kKey1] = kValue1;
+ data11[kKey2] = kValue2;
+ data11[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data11));
+ DOMStorageValuesMap data12;
+ data12[kKey2] = kValue4;
+ data12[kKey3] = kValue3;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data12));
+ DOMStorageValuesMap data21;
+ data21[kKey1] = kValue2;
+ data21[kKey2] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin1, false, data21));
+ DOMStorageValuesMap data22;
+ data22[kKey2] = kValue1;
+ data22[kKey3] = kValue2;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data22));
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, data11);
+ CheckAreaData(kNamespace1, kOrigin2, data12);
+ CheckAreaData(kNamespace2, kOrigin1, data21);
+ CheckAreaData(kNamespace2, kOrigin2, data22);
+}
+
+TEST_F(SessionStorageDatabaseTest, ShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+ // Make a shallow copy.
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ // Now both namespaces should have the same data.
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+ CheckAreaData(kNamespace1, kOrigin2, data2);
+ CheckAreaData(kNamespaceClone, kOrigin1, data1);
+ CheckAreaData(kNamespaceClone, kOrigin2, data2);
+ // Both the namespaces refer to the same maps.
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
+ GetMapForArea(kNamespaceClone, kOrigin1));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
+ GetMapForArea(kNamespaceClone, kOrigin2));
+ EXPECT_EQ(2, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
+ EXPECT_EQ(2, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin2)));
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteIntoShallowCopy) {
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+
+ // Write data into a shallow copy.
+ DOMStorageValuesMap changes;
+ DOMStorageValuesMap reference;
+ changes[kKey1] = kValueNull;
+ changes[kKey2] = kValue4;
+ changes[kKey3] = kValue4;
+ reference[kKey2] = kValue4;
+ reference[kKey3] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespaceClone, kOrigin1, false,
+ changes));
+
+ // Values in the original namespace were not changed.
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+ // But values in the copy were.
+ CheckAreaData(kNamespaceClone, kOrigin1, reference);
+
+ // The namespaces no longer refer to the same map.
+ EXPECT_NE(GetMapForArea(kNamespace1, kOrigin1),
+ GetMapForArea(kNamespaceClone, kOrigin1));
+ EXPECT_EQ(1, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
+ EXPECT_EQ(1, GetMapRefCount(GetMapForArea(kNamespaceClone, kOrigin1)));
+}
+
+TEST_F(SessionStorageDatabaseTest, ManyShallowCopies) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ // Make a two shallow copies.
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ std::string another_clone("another_cloned");
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, another_clone));
+
+ // Make a shallow copy of a shallow copy.
+ std::string clone_of_clone("clone_of_clone");
+ EXPECT_TRUE(db_->CloneNamespace(another_clone, clone_of_clone));
+
+ // Now all namespaces should have the same data.
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+ CheckAreaData(kNamespaceClone, kOrigin1, data1);
+ CheckAreaData(another_clone, kOrigin1, data1);
+ CheckAreaData(clone_of_clone, kOrigin1, data1);
+ CheckAreaData(kNamespace1, kOrigin2, data2);
+ CheckAreaData(kNamespaceClone, kOrigin2, data2);
+ CheckAreaData(another_clone, kOrigin2, data2);
+ CheckAreaData(clone_of_clone, kOrigin2, data2);
+
+ // All namespaces refer to the same maps.
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
+ GetMapForArea(kNamespaceClone, kOrigin1));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
+ GetMapForArea(kNamespaceClone, kOrigin2));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
+ GetMapForArea(another_clone, kOrigin1));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
+ GetMapForArea(another_clone, kOrigin2));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
+ GetMapForArea(clone_of_clone, kOrigin1));
+ EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
+ GetMapForArea(clone_of_clone, kOrigin2));
+
+ // Check the ref counts.
+ EXPECT_EQ(4, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
+ EXPECT_EQ(4, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin2)));
+}
+
+TEST_F(SessionStorageDatabaseTest, DisassociateShallowCopy) {
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+
+ // Disassoaciate the shallow copy.
+ EXPECT_TRUE(db_->DeleteArea(kNamespaceClone, kOrigin1));
+ CheckDatabaseConsistency();
+
+ // Now new data can be written to that map.
+ DOMStorageValuesMap reference;
+ DOMStorageValuesMap changes;
+ changes[kKey1] = kValueNull;
+ changes[kKey2] = kValue4;
+ changes[kKey3] = kValue4;
+ reference[kKey2] = kValue4;
+ reference[kKey3] = kValue4;
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespaceClone, kOrigin1, false,
+ changes));
+
+ // Values in the original map were not changed.
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+
+ // But values in the disassociated map were.
+ CheckAreaData(kNamespaceClone, kOrigin1, reference);
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteNamespace) {
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey2] = kValue4;
+ data2[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+ EXPECT_TRUE(db_->DeleteNamespace(kNamespace1));
+ CheckDatabaseConsistency();
+ CheckEmptyDatabase();
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteNamespaceWithShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ // Make a shallow copy and delete the original namespace.
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ EXPECT_TRUE(db_->DeleteNamespace(kNamespace1));
+
+ // The original namespace has no data.
+ CheckDatabaseConsistency();
+ CheckAreaData(kNamespace1, kOrigin1, DOMStorageValuesMap());
+ CheckAreaData(kNamespace1, kOrigin2, DOMStorageValuesMap());
+ // But the copy persists.
+ CheckAreaData(kNamespaceClone, kOrigin1, data1);
+ CheckAreaData(kNamespaceClone, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteArea) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
+ CheckDatabaseConsistency();
+ // The data for the non-deleted origin persists.
+ CheckAreaData(kNamespace1, kOrigin1, data1);
+ // The data for the deleted origin is gone.
+ CheckAreaData(kNamespace1, kOrigin2, DOMStorageValuesMap());
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteAreaWithShallowCopy) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ data2[kKey3] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ // Make a shallow copy and delete an origin from the original namespace.
+ EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin1));
+ CheckDatabaseConsistency();
+
+ // The original namespace has data for only the non-deleted origin.
+ CheckAreaData(kNamespace1, kOrigin1, DOMStorageValuesMap());
+ CheckAreaData(kNamespace1, kOrigin2, data2);
+ // But the copy persists.
+ CheckAreaData(kNamespaceClone, kOrigin1, data1);
+ CheckAreaData(kNamespaceClone, kOrigin2, data2);
+}
+
+TEST_F(SessionStorageDatabaseTest, WriteRawBytes) {
+ // Write data which is not valid utf8 and contains null bytes.
+ unsigned char raw_data[10] = {255, 0, 0, 0, 1, 2, 3, 4, 5, 0};
+ DOMStorageValuesMap changes;
+ base::string16 string_with_raw_data;
+ string_with_raw_data.assign(reinterpret_cast<char16*>(raw_data), 5);
+ changes[kKey1] = base::NullableString16(string_with_raw_data, false);
+ EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
+ CheckDatabaseConsistency();
+ DOMStorageValuesMap values;
+ db_->ReadAreaValues(kNamespace1, kOrigin1, &values);
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(values[kKey1].string().data());
+ for (int i = 0; i < 10; ++i)
+ EXPECT_EQ(raw_data[i], data[i]);
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteNamespaceConfusion) {
+ // Regression test for a bug where a namespace with id 10 prevented deleting
+ // the namespace with id 1.
+
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges("foobar", kOrigin1, false, data1));
+ ASSERT_TRUE(db_->CommitAreaChanges("foobarbaz", kOrigin1, false, data1));
+
+ // Delete the namespace with ID 1.
+ EXPECT_TRUE(db_->DeleteNamespace("foobar"));
+}
+
+TEST_F(SessionStorageDatabaseTest, ReadNamespaceIds) {
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+ std::set<std::string> expected_namespace_ids;
+
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ expected_namespace_ids.insert(kNamespace1);
+ CheckNamespaceIds(expected_namespace_ids);
+
+ ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ expected_namespace_ids.insert(kNamespaceClone);
+ CheckNamespaceIds(expected_namespace_ids);
+
+ ASSERT_TRUE(db_->DeleteNamespace(kNamespace1));
+ expected_namespace_ids.erase(kNamespace1);
+ CheckNamespaceIds(expected_namespace_ids);
+
+ CheckDatabaseConsistency();
+}
+
+TEST_F(SessionStorageDatabaseTest, ReadNamespaceIdsInEmptyDatabase) {
+ std::set<std::string> expected_namespace_ids;
+ CheckNamespaceIds(expected_namespace_ids);
+}
+
+TEST_F(SessionStorageDatabaseTest, ReadOriginsInNamespace) {
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ data1[kKey2] = kValue2;
+ data1[kKey3] = kValue3;
+
+ std::set<GURL> expected_origins1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data1));
+ expected_origins1.insert(kOrigin1);
+ expected_origins1.insert(kOrigin2);
+ CheckOrigins(kNamespace1, expected_origins1);
+
+ std::set<GURL> expected_origins2;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data1));
+ expected_origins2.insert(kOrigin2);
+ CheckOrigins(kNamespace2, expected_origins2);
+
+ ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
+ CheckOrigins(kNamespaceClone, expected_origins1);
+
+ ASSERT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
+ expected_origins1.erase(kOrigin2);
+ CheckOrigins(kNamespace1, expected_origins1);
+
+ CheckDatabaseConsistency();
+}
+
+TEST_F(SessionStorageDatabaseTest, DeleteAllOrigins) {
+ // Write data for a namespace, for 2 origins.
+ DOMStorageValuesMap data1;
+ data1[kKey1] = kValue1;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
+ DOMStorageValuesMap data2;
+ data2[kKey1] = kValue2;
+ ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
+
+ EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin1));
+ EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
+ // Check that also the namespace start key was deleted.
+ CheckDatabaseConsistency();
+}
+
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/session_storage_namespace_impl.cc b/chromium/content/browser/dom_storage/session_storage_namespace_impl.cc
new file mode 100644
index 00000000000..a1f7f2dd7f7
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_namespace_impl.cc
@@ -0,0 +1,61 @@
+// 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 "content/browser/dom_storage/session_storage_namespace_impl.h"
+
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/dom_storage_session.h"
+
+namespace content {
+
+SessionStorageNamespaceImpl::SessionStorageNamespaceImpl(
+ DOMStorageContextWrapper* context)
+ : session_(new DOMStorageSession(context->context())) {
+}
+
+SessionStorageNamespaceImpl::SessionStorageNamespaceImpl(
+ DOMStorageContextWrapper* context, int64 namepace_id_to_clone)
+ : session_(DOMStorageSession::CloneFrom(context->context(),
+ namepace_id_to_clone)) {
+}
+
+SessionStorageNamespaceImpl::SessionStorageNamespaceImpl(
+ DOMStorageContextWrapper* context, const std::string& persistent_id)
+ : session_(new DOMStorageSession(context->context(), persistent_id)) {
+}
+
+int64 SessionStorageNamespaceImpl::id() const {
+ return session_->namespace_id();
+}
+
+const std::string& SessionStorageNamespaceImpl::persistent_id() const {
+ return session_->persistent_namespace_id();
+}
+
+void SessionStorageNamespaceImpl::SetShouldPersist(bool should_persist) {
+ session_->SetShouldPersist(should_persist);
+}
+
+bool SessionStorageNamespaceImpl::should_persist() const {
+ return session_->should_persist();
+}
+
+SessionStorageNamespaceImpl* SessionStorageNamespaceImpl::Clone() {
+ return new SessionStorageNamespaceImpl(session_->Clone());
+}
+
+bool SessionStorageNamespaceImpl::IsFromContext(
+ DOMStorageContextWrapper* context) {
+ return session_->IsFromContext(context->context());
+}
+
+SessionStorageNamespaceImpl::SessionStorageNamespaceImpl(
+ DOMStorageSession* clone)
+ : session_(clone) {
+}
+
+SessionStorageNamespaceImpl::~SessionStorageNamespaceImpl() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/dom_storage/session_storage_namespace_impl.h b/chromium/content/browser/dom_storage/session_storage_namespace_impl.h
new file mode 100644
index 00000000000..d741848c9e8
--- /dev/null
+++ b/chromium/content/browser/dom_storage/session_storage_namespace_impl.h
@@ -0,0 +1,57 @@
+// 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 CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_NAMESPACE_IMPL_H_
+#define CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_NAMESPACE_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/session_storage_namespace.h"
+
+namespace content {
+
+class DOMStorageContextWrapper;
+class DOMStorageSession;
+
+class SessionStorageNamespaceImpl
+ : NON_EXPORTED_BASE(public SessionStorageNamespace) {
+ public:
+ // Constructs a |SessionStorageNamespaceImpl| and allocates new IDs for it.
+ //
+ // The CONTENT_EXPORT allows TestRenderViewHost to instantiate these.
+ CONTENT_EXPORT explicit SessionStorageNamespaceImpl(
+ DOMStorageContextWrapper* context);
+
+ // Constructs a |SessionStorageNamespaceImpl| by cloning
+ // |namespace_to_clone|. Allocates new IDs for it.
+ SessionStorageNamespaceImpl(DOMStorageContextWrapper* context,
+ int64 namepace_id_to_clone);
+
+ // Constructs a |SessionStorageNamespaceImpl| and assigns |persistent_id|
+ // to it. Allocates a new non-persistent ID.
+ SessionStorageNamespaceImpl(DOMStorageContextWrapper* context,
+ const std::string& persistent_id);
+
+ // SessionStorageNamespace implementation.
+ virtual int64 id() const OVERRIDE;
+ virtual const std::string& persistent_id() const OVERRIDE;
+ virtual void SetShouldPersist(bool should_persist) OVERRIDE;
+ virtual bool should_persist() const OVERRIDE;
+
+ SessionStorageNamespaceImpl* Clone();
+ bool IsFromContext(DOMStorageContextWrapper* context);
+
+ private:
+ explicit SessionStorageNamespaceImpl(DOMStorageSession* clone);
+ virtual ~SessionStorageNamespaceImpl();
+
+ scoped_refptr<DOMStorageSession> session_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStorageNamespaceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOM_STORAGE_SESSION_STORAGE_NAMESPACE_IMPL_H_
diff --git a/chromium/content/browser/download/OWNERS b/chromium/content/browser/download/OWNERS
new file mode 100644
index 00000000000..7b50c05bfba
--- /dev/null
+++ b/chromium/content/browser/download/OWNERS
@@ -0,0 +1,5 @@
+ahendrickson@chromium.org
+asanka@chromium.org
+benjhayden@chromium.org
+phajdan.jr@chromium.org
+rdsmith@chromium.org
diff --git a/chromium/content/browser/download/base_file.cc b/chromium/content/browser/download/base_file.cc
new file mode 100644
index 00000000000..90ab485c361
--- /dev/null
+++ b/chromium/content/browser/download/base_file.cc
@@ -0,0 +1,371 @@
+// 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 "content/browser/download/base_file.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_net_log_parameters.h"
+#include "content/browser/download/download_stats.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "crypto/secure_hash.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+// This will initialize the entire array to zero.
+const unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
+
+BaseFile::BaseFile(const base::FilePath& full_path,
+ const GURL& source_url,
+ const GURL& referrer_url,
+ int64 received_bytes,
+ bool calculate_hash,
+ const std::string& hash_state_bytes,
+ scoped_ptr<net::FileStream> file_stream,
+ const net::BoundNetLog& bound_net_log)
+ : full_path_(full_path),
+ source_url_(source_url),
+ referrer_url_(referrer_url),
+ file_stream_(file_stream.Pass()),
+ bytes_so_far_(received_bytes),
+ start_tick_(base::TimeTicks::Now()),
+ calculate_hash_(calculate_hash),
+ detached_(false),
+ bound_net_log_(bound_net_log) {
+ memcpy(sha256_hash_, kEmptySha256Hash, kSha256HashLen);
+ if (calculate_hash_) {
+ secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ if ((bytes_so_far_ > 0) && // Not starting at the beginning.
+ (!IsEmptyHash(hash_state_bytes))) {
+ Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size());
+ PickleIterator data_iterator(hash_state);
+ secure_hash_->Deserialize(&data_iterator);
+ }
+ }
+}
+
+BaseFile::~BaseFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (detached_)
+ Close();
+ else
+ Cancel(); // Will delete the file.
+}
+
+DownloadInterruptReason BaseFile::Initialize(
+ const base::FilePath& default_directory) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ if (file_stream_) {
+ file_stream_->SetBoundNetLogSource(bound_net_log_);
+ file_stream_->EnableErrorStatistics();
+ }
+
+ if (full_path_.empty()) {
+ base::FilePath initial_directory(default_directory);
+ base::FilePath temp_file;
+ if (initial_directory.empty()) {
+ initial_directory =
+ GetContentClient()->browser()->GetDefaultDownloadDirectory();
+ }
+ // |initial_directory| can still be empty if ContentBrowserClient returned
+ // an empty path for the downloads directory.
+ if ((initial_directory.empty() ||
+ !file_util::CreateTemporaryFileInDir(initial_directory, &temp_file)) &&
+ !file_util::CreateTemporaryFile(&temp_file)) {
+ return LogInterruptReason("Unable to create", 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ }
+ full_path_ = temp_file;
+ }
+
+ return Open();
+}
+
+DownloadInterruptReason BaseFile::AppendDataToFile(const char* data,
+ size_t data_len) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ // NOTE(benwells): The above DCHECK won't be present in release builds,
+ // so we log any occurences to see how common this error is in the wild.
+ if (detached_)
+ RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT);
+
+ if (!file_stream_)
+ return LogInterruptReason("No file stream on append", 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+
+ // TODO(phajdan.jr): get rid of this check.
+ if (data_len == 0)
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ // The Write call below is not guaranteed to write all the data.
+ size_t write_count = 0;
+ size_t len = data_len;
+ const char* current_data = data;
+ while (len > 0) {
+ write_count++;
+ int write_result =
+ file_stream_->WriteSync(current_data, len);
+ DCHECK_NE(0, write_result);
+
+ // Check for errors.
+ if (static_cast<size_t>(write_result) != data_len) {
+ // We should never get ERR_IO_PENDING, as the Write above is synchronous.
+ DCHECK_NE(net::ERR_IO_PENDING, write_result);
+
+ // Report errors on file writes.
+ if (write_result < 0)
+ return LogNetError("Write", static_cast<net::Error>(write_result));
+ }
+
+ // Update status.
+ size_t write_size = static_cast<size_t>(write_result);
+ DCHECK_LE(write_size, len);
+ len -= write_size;
+ current_data += write_size;
+ bytes_so_far_ += write_size;
+ }
+
+ RecordDownloadWriteSize(data_len);
+ RecordDownloadWriteLoopCount(write_count);
+
+ if (calculate_hash_)
+ secure_hash_->Update(data, data_len);
+
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DownloadInterruptReason rename_result = DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ // If the new path is same as the old one, there is no need to perform the
+ // following renaming logic.
+ if (new_path == full_path_)
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ // Save the information whether the download is in progress because
+ // it will be overwritten by closing the file.
+ bool was_in_progress = in_progress();
+
+ bound_net_log_.BeginEvent(
+ net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED,
+ base::Bind(&FileRenamedNetLogCallback, &full_path_, &new_path));
+ Close();
+ file_util::CreateDirectory(new_path.DirName());
+
+ // A simple rename wouldn't work here since we want the file to have
+ // permissions / security descriptors that makes sense in the new directory.
+ rename_result = MoveFileAndAdjustPermissions(new_path);
+
+ if (rename_result == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ full_path_ = new_path;
+ // Re-open the file if we were still using it.
+ if (was_in_progress)
+ rename_result = Open();
+ }
+
+ bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED);
+ return rename_result;
+}
+
+void BaseFile::Detach() {
+ detached_ = true;
+ bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
+}
+
+void BaseFile::Cancel() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
+
+ Close();
+
+ if (!full_path_.empty()) {
+ bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
+
+ base::DeleteFile(full_path_, false);
+ }
+}
+
+void BaseFile::Finish() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (calculate_hash_)
+ secure_hash_->Finish(sha256_hash_, kSha256HashLen);
+
+ Close();
+}
+
+void BaseFile::SetClientGuid(const std::string& guid) {
+ client_guid_ = guid;
+}
+
+// OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
+#if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
+DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+#endif
+
+bool BaseFile::GetHash(std::string* hash) {
+ DCHECK(!detached_);
+ hash->assign(reinterpret_cast<const char*>(sha256_hash_),
+ sizeof(sha256_hash_));
+ return (calculate_hash_ && !in_progress());
+}
+
+std::string BaseFile::GetHashState() {
+ if (!calculate_hash_)
+ return std::string();
+
+ Pickle hash_state;
+ if (!secure_hash_->Serialize(&hash_state))
+ return std::string();
+
+ return std::string(reinterpret_cast<const char*>(hash_state.data()),
+ hash_state.size());
+}
+
+// static
+bool BaseFile::IsEmptyHash(const std::string& hash) {
+ return (hash.size() == kSha256HashLen &&
+ 0 == memcmp(hash.data(), kEmptySha256Hash, sizeof(kSha256HashLen)));
+}
+
+std::string BaseFile::DebugString() const {
+ return base::StringPrintf("{ source_url_ = \"%s\""
+ " full_path_ = \"%" PRFilePath "\""
+ " bytes_so_far_ = %" PRId64
+ " detached_ = %c }",
+ source_url_.spec().c_str(),
+ full_path_.value().c_str(),
+ bytes_so_far_,
+ detached_ ? 'T' : 'F');
+}
+
+void BaseFile::CreateFileStream() {
+ file_stream_.reset(new net::FileStream(bound_net_log_.net_log()));
+ file_stream_->SetBoundNetLogSource(bound_net_log_);
+}
+
+DownloadInterruptReason BaseFile::Open() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+ DCHECK(!full_path_.empty());
+
+ bound_net_log_.BeginEvent(
+ net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
+ base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
+
+ // Create a new file stream if it is not provided.
+ if (!file_stream_) {
+ CreateFileStream();
+ file_stream_->EnableErrorStatistics();
+ int open_result = file_stream_->OpenSync(
+ full_path_,
+ base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE);
+ if (open_result != net::OK) {
+ ClearStream();
+ return LogNetError("Open", static_cast<net::Error>(open_result));
+ }
+
+ // We may be re-opening the file after rename. Always make sure we're
+ // writing at the end of the file.
+ int64 seek_result = file_stream_->SeekSync(net::FROM_END, 0);
+ if (seek_result < 0) {
+ ClearStream();
+ return LogNetError("Seek", static_cast<net::Error>(seek_result));
+ }
+ } else {
+ file_stream_->SetBoundNetLogSource(bound_net_log_);
+ }
+
+ int64 file_size = file_stream_->SeekSync(net::FROM_END, 0);
+ if (file_size > bytes_so_far_) {
+ // The file is larger than we expected.
+ // This is OK, as long as we don't use the extra.
+ // Truncate the file.
+ int64 truncate_result = file_stream_->Truncate(bytes_so_far_);
+ if (truncate_result < 0)
+ return LogNetError("Truncate", static_cast<net::Error>(truncate_result));
+
+ // If if wasn't an error, it should have truncated to the size
+ // specified.
+ DCHECK_EQ(bytes_so_far_, truncate_result);
+ } else if (file_size < bytes_so_far_) {
+ // The file is shorter than we expected. Our hashes won't be valid.
+ return LogInterruptReason("Unable to seek to last written point", 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
+ }
+
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+void BaseFile::Close() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
+
+ if (file_stream_) {
+#if defined(OS_CHROMEOS)
+ // Currently we don't really care about the return value, since if it fails
+ // theres not much we can do. But we might in the future.
+ file_stream_->FlushSync();
+#endif
+ ClearStream();
+ }
+}
+
+void BaseFile::ClearStream() {
+ // This should only be called when we have a stream.
+ DCHECK(file_stream_.get() != NULL);
+ file_stream_.reset();
+ bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
+}
+
+DownloadInterruptReason BaseFile::LogNetError(
+ const char* operation,
+ net::Error error) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
+ base::Bind(&FileErrorNetLogCallback, operation, error));
+ return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
+}
+
+DownloadInterruptReason BaseFile::LogSystemError(
+ const char* operation,
+ int os_error) {
+ // There's no direct conversion from a system error to an interrupt reason.
+ net::Error net_error = net::MapSystemError(os_error);
+ return LogInterruptReason(
+ operation, os_error,
+ ConvertNetErrorToInterruptReason(
+ net_error, DOWNLOAD_INTERRUPT_FROM_DISK));
+}
+
+DownloadInterruptReason BaseFile::LogInterruptReason(
+ const char* operation,
+ int os_error,
+ DownloadInterruptReason reason) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
+ base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
+ return reason;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/base_file.h b/chromium/content/browser/download/base_file.h
new file mode 100644
index 00000000000..f1686c81fb0
--- /dev/null
+++ b/chromium/content/browser/download/base_file.h
@@ -0,0 +1,183 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_BASE_FILE_H_
+#define CONTENT_BROWSER_DOWNLOAD_BASE_FILE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "url/gurl.h"
+
+namespace crypto {
+class SecureHash;
+}
+namespace net {
+class FileStream;
+}
+
+namespace content {
+
+// File being downloaded and saved to disk. This is a base class
+// for DownloadFile and SaveFile, which keep more state information.
+class CONTENT_EXPORT BaseFile {
+ public:
+ // May be constructed on any thread. All other routines (including
+ // destruction) must occur on the FILE thread.
+ BaseFile(const base::FilePath& full_path,
+ const GURL& source_url,
+ const GURL& referrer_url,
+ int64 received_bytes,
+ bool calculate_hash,
+ const std::string& hash_state,
+ scoped_ptr<net::FileStream> file_stream,
+ const net::BoundNetLog& bound_net_log);
+ virtual ~BaseFile();
+
+ // Returns DOWNLOAD_INTERRUPT_REASON_NONE on success, or a
+ // DownloadInterruptReason on failure. |default_directory| specifies the
+ // directory to create the temporary file in if |full_path()| is empty. If
+ // |default_directory| and |full_path()| are empty, then a temporary file will
+ // be created in the default download location as determined by
+ // ContentBrowserClient.
+ DownloadInterruptReason Initialize(const base::FilePath& default_directory);
+
+ // Write a new chunk of data to the file. Returns a DownloadInterruptReason
+ // indicating the result of the operation.
+ DownloadInterruptReason AppendDataToFile(const char* data, size_t data_len);
+
+ // Rename the download file. Returns a DownloadInterruptReason indicating the
+ // result of the operation.
+ virtual DownloadInterruptReason Rename(const base::FilePath& full_path);
+
+ // Detach the file so it is not deleted on destruction.
+ virtual void Detach();
+
+ // Abort the download and automatically close the file.
+ void Cancel();
+
+ // Indicate that the download has finished. No new data will be received.
+ void Finish();
+
+ // Set the client guid which will be used to identify the app to the
+ // system AV scanning function. Should be called before
+ // AnnotateWithSourceInformation() to take effect.
+ void SetClientGuid(const std::string& guid);
+
+ // Informs the OS that this file came from the internet. Returns a
+ // DownloadInterruptReason indicating the result of the operation.
+ // Note: SetClientGuid() should be called before this function on
+ // Windows to ensure the correct app client ID is available.
+ DownloadInterruptReason AnnotateWithSourceInformation();
+
+ base::FilePath full_path() const { return full_path_; }
+ bool in_progress() const { return file_stream_.get() != NULL; }
+ int64 bytes_so_far() const { return bytes_so_far_; }
+
+ // Fills |hash| with the hash digest for the file.
+ // Returns true if digest is successfully calculated.
+ virtual bool GetHash(std::string* hash);
+
+ // Returns the current (intermediate) state of the hash as a byte string.
+ virtual std::string GetHashState();
+
+ // Returns true if the given hash is considered empty. An empty hash is
+ // a string of size kSha256HashLen that contains only zeros (initial value
+ // for the hash).
+ static bool IsEmptyHash(const std::string& hash);
+
+ virtual std::string DebugString() const;
+
+ private:
+ friend class BaseFileTest;
+ FRIEND_TEST_ALL_PREFIXES(BaseFileTest, IsEmptyHash);
+
+ // Re-initializes file_stream_ with a newly allocated net::FileStream().
+ void CreateFileStream();
+
+ // Creates and opens the file_stream_ if it is NULL.
+ DownloadInterruptReason Open();
+
+ // Closes and resets file_stream_.
+ void Close();
+
+ // Resets file_stream_.
+ void ClearStream();
+
+ // Platform specific method that moves a file to a new path and adjusts the
+ // security descriptor / permissions on the file to match the defaults for the
+ // new directory.
+ DownloadInterruptReason MoveFileAndAdjustPermissions(
+ const base::FilePath& new_path);
+
+ // Split out from CurrentSpeed to enable testing.
+ int64 CurrentSpeedAtTime(base::TimeTicks current_time) const;
+
+ // Log a TYPE_DOWNLOAD_FILE_ERROR NetLog event with |error| and passes error
+ // on through, converting to a |DownloadInterruptReason|.
+ DownloadInterruptReason LogNetError(const char* operation, net::Error error);
+
+ // Log the system error in |os_error| and converts it into a
+ // |DownloadInterruptReason|.
+ DownloadInterruptReason LogSystemError(const char* operation, int os_error);
+
+ // Log a TYPE_DOWNLOAD_FILE_ERROR NetLog event with |os_error| and |reason|.
+ // Returns |reason|.
+ DownloadInterruptReason LogInterruptReason(
+ const char* operation, int os_error,
+ DownloadInterruptReason reason);
+
+ static const size_t kSha256HashLen = 32;
+ static const unsigned char kEmptySha256Hash[kSha256HashLen];
+
+ // Full path to the file including the file name.
+ base::FilePath full_path_;
+
+ // Source URL for the file being downloaded.
+ GURL source_url_;
+
+ // The URL where the download was initiated.
+ GURL referrer_url_;
+
+ std::string client_guid_;
+
+ // OS file stream for writing
+ scoped_ptr<net::FileStream> file_stream_;
+
+ // Amount of data received up so far, in bytes.
+ int64 bytes_so_far_;
+
+ // Start time for calculating speed.
+ base::TimeTicks start_tick_;
+
+ // Indicates if hash should be calculated for the file.
+ bool calculate_hash_;
+
+ // Used to calculate hash for the file when calculate_hash_
+ // is set.
+ scoped_ptr<crypto::SecureHash> secure_hash_;
+
+ unsigned char sha256_hash_[kSha256HashLen];
+
+ // Indicates that this class no longer owns the associated file, and so
+ // won't delete it on destruction.
+ bool detached_;
+
+ net::BoundNetLog bound_net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseFile);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_BASE_FILE_H_
diff --git a/chromium/content/browser/download/base_file_linux.cc b/chromium/content/browser/download/base_file_linux.cc
new file mode 100644
index 00000000000..ee8d1fa8587
--- /dev/null
+++ b/chromium/content/browser/download/base_file_linux.cc
@@ -0,0 +1,20 @@
+// 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 "content/browser/download/base_file.h"
+
+#include "content/browser/download/file_metadata_linux.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ AddOriginMetadataToFile(full_path_, source_url_, referrer_url_);
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/base_file_mac.cc b/chromium/content/browser/download/base_file_mac.cc
new file mode 100644
index 00000000000..21e6b556abb
--- /dev/null
+++ b/chromium/content/browser/download/base_file_mac.cc
@@ -0,0 +1,21 @@
+// 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 "content/browser/download/base_file.h"
+
+#include "content/browser/download/file_metadata_mac.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ AddQuarantineMetadataToFile(full_path_, source_url_, referrer_url_);
+ AddOriginMetadataToFile(full_path_, source_url_, referrer_url_);
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/base_file_posix.cc b/chromium/content/browser/download/base_file_posix.cc
new file mode 100644
index 00000000000..dd664afeb59
--- /dev/null
+++ b/chromium/content/browser/download/base_file_posix.cc
@@ -0,0 +1,44 @@
+// 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 "content/browser/download/base_file.h"
+
+#include "base/file_util.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+
+namespace content {
+
+DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
+ const base::FilePath& new_path) {
+ // Similarly, on Unix, we're moving a temp file created with permissions 600
+ // to |new_path|. Here, we try to fix up the destination file with appropriate
+ // permissions.
+ struct stat st;
+ // First check the file existence and create an empty file if it doesn't
+ // exist.
+ if (!base::PathExists(new_path)) {
+ int write_error = file_util::WriteFile(new_path, "", 0);
+ if (write_error < 0)
+ return LogSystemError("WriteFile", errno);
+ }
+ int stat_error = stat(new_path.value().c_str(), &st);
+ bool stat_succeeded = (stat_error == 0);
+ if (!stat_succeeded)
+ LogSystemError("stat", errno);
+
+ // TODO(estade): Move() falls back to copying and deleting when a simple
+ // rename fails. Copying sucks for large downloads. crbug.com/8737
+ if (!base::Move(full_path_, new_path))
+ return LogSystemError("Move", errno);
+
+ if (stat_succeeded) {
+ // On Windows file systems (FAT, NTFS), chmod fails. This is OK.
+ int chmod_error = chmod(new_path.value().c_str(), st.st_mode);
+ if (chmod_error < 0)
+ LogSystemError("chmod", errno);
+ }
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/base_file_unittest.cc b/chromium/content/browser/download/base_file_unittest.cc
new file mode 100644
index 00000000000..826f56b4cf8
--- /dev/null
+++ b/chromium/content/browser/download/base_file_unittest.cc
@@ -0,0 +1,634 @@
+// 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 "content/browser/download/base_file.h"
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/test_file_util.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "crypto/secure_hash.h"
+#include "net/base/file_stream.h"
+#include "net/base/mock_file_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+const char kTestData1[] = "Let's write some data to the file!\n";
+const char kTestData2[] = "Writing more data.\n";
+const char kTestData3[] = "Final line.";
+const char kTestData4[] = "supercalifragilisticexpialidocious";
+const int kTestDataLength1 = arraysize(kTestData1) - 1;
+const int kTestDataLength2 = arraysize(kTestData2) - 1;
+const int kTestDataLength3 = arraysize(kTestData3) - 1;
+const int kTestDataLength4 = arraysize(kTestData4) - 1;
+const int kElapsedTimeSeconds = 5;
+const base::TimeDelta kElapsedTimeDelta = base::TimeDelta::FromSeconds(
+ kElapsedTimeSeconds);
+
+} // namespace
+
+class BaseFileTest : public testing::Test {
+ public:
+ static const size_t kSha256HashLen = 32;
+ static const unsigned char kEmptySha256Hash[kSha256HashLen];
+
+ BaseFileTest()
+ : expect_file_survives_(false),
+ expect_in_progress_(true),
+ expected_error_(DOWNLOAD_INTERRUPT_REASON_NONE),
+ file_thread_(BrowserThread::FILE, &message_loop_) {
+ }
+
+ virtual void SetUp() {
+ ResetHash();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ base_file_.reset(new BaseFile(base::FilePath(),
+ GURL(),
+ GURL(),
+ 0,
+ false,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog()));
+ }
+
+ virtual void TearDown() {
+ EXPECT_FALSE(base_file_->in_progress());
+ if (!expected_error_) {
+ EXPECT_EQ(static_cast<int64>(expected_data_.size()),
+ base_file_->bytes_so_far());
+ }
+
+ base::FilePath full_path = base_file_->full_path();
+
+ if (!expected_data_.empty() && !expected_error_) {
+ // Make sure the data has been properly written to disk.
+ std::string disk_data;
+ EXPECT_TRUE(file_util::ReadFileToString(full_path, &disk_data));
+ EXPECT_EQ(expected_data_, disk_data);
+ }
+
+ // Make sure the mock BrowserThread outlives the BaseFile to satisfy
+ // thread checks inside it.
+ base_file_.reset();
+
+ EXPECT_EQ(expect_file_survives_, base::PathExists(full_path));
+ }
+
+ void ResetHash() {
+ secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ memcpy(sha256_hash_, kEmptySha256Hash, kSha256HashLen);
+ }
+
+ void UpdateHash(const char* data, size_t length) {
+ secure_hash_->Update(data, length);
+ }
+
+ std::string GetFinalHash() {
+ std::string hash;
+ secure_hash_->Finish(sha256_hash_, kSha256HashLen);
+ hash.assign(reinterpret_cast<const char*>(sha256_hash_),
+ sizeof(sha256_hash_));
+ return hash;
+ }
+
+ void MakeFileWithHash() {
+ base_file_.reset(new BaseFile(base::FilePath(),
+ GURL(),
+ GURL(),
+ 0,
+ true,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog()));
+ }
+
+ bool InitializeFile() {
+ DownloadInterruptReason result = base_file_->Initialize(temp_dir_.path());
+ EXPECT_EQ(expected_error_, result);
+ return result == DOWNLOAD_INTERRUPT_REASON_NONE;
+ }
+
+ bool AppendDataToFile(const std::string& data) {
+ EXPECT_EQ(expect_in_progress_, base_file_->in_progress());
+ DownloadInterruptReason result =
+ base_file_->AppendDataToFile(data.data(), data.size());
+ if (result == DOWNLOAD_INTERRUPT_REASON_NONE)
+ EXPECT_TRUE(expect_in_progress_) << " result = " << result;
+
+ EXPECT_EQ(expected_error_, result);
+ if (base_file_->in_progress()) {
+ expected_data_ += data;
+ if (expected_error_ == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ EXPECT_EQ(static_cast<int64>(expected_data_.size()),
+ base_file_->bytes_so_far());
+ }
+ }
+ return result == DOWNLOAD_INTERRUPT_REASON_NONE;
+ }
+
+ void set_expected_data(const std::string& data) { expected_data_ = data; }
+
+ // Helper functions.
+ // Create a file. Returns the complete file path.
+ base::FilePath CreateTestFile() {
+ base::FilePath file_name;
+ BaseFile file(base::FilePath(),
+ GURL(),
+ GURL(),
+ 0,
+ false,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog());
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ file.Initialize(temp_dir_.path()));
+ file_name = file.full_path();
+ EXPECT_NE(base::FilePath::StringType(), file_name.value());
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ file.AppendDataToFile(kTestData4, kTestDataLength4));
+
+ // Keep the file from getting deleted when existing_file_name is deleted.
+ file.Detach();
+
+ return file_name;
+ }
+
+ // Create a file with the specified file name.
+ void CreateFileWithName(const base::FilePath& file_name) {
+ EXPECT_NE(base::FilePath::StringType(), file_name.value());
+ BaseFile duplicate_file(file_name,
+ GURL(),
+ GURL(),
+ 0,
+ false,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ duplicate_file.Initialize(temp_dir_.path()));
+ // Write something into it.
+ duplicate_file.AppendDataToFile(kTestData4, kTestDataLength4);
+ // Detach the file so it isn't deleted on destruction of |duplicate_file|.
+ duplicate_file.Detach();
+ }
+
+ int64 CurrentSpeedAtTime(base::TimeTicks current_time) {
+ EXPECT_TRUE(base_file_.get());
+ return base_file_->CurrentSpeedAtTime(current_time);
+ }
+
+ base::TimeTicks StartTick() {
+ EXPECT_TRUE(base_file_.get());
+ return base_file_->start_tick_;
+ }
+
+ void set_expected_error(DownloadInterruptReason err) {
+ expected_error_ = err;
+ }
+
+ protected:
+ linked_ptr<net::testing::MockFileStream> mock_file_stream_;
+
+ // BaseClass instance we are testing.
+ scoped_ptr<BaseFile> base_file_;
+
+ // Temporary directory for renamed downloads.
+ base::ScopedTempDir temp_dir_;
+
+ // Expect the file to survive deletion of the BaseFile instance.
+ bool expect_file_survives_;
+
+ // Expect the file to be in progress.
+ bool expect_in_progress_;
+
+ // Hash calculator.
+ scoped_ptr<crypto::SecureHash> secure_hash_;
+
+ unsigned char sha256_hash_[kSha256HashLen];
+
+ private:
+ // Keep track of what data should be saved to the disk file.
+ std::string expected_data_;
+ DownloadInterruptReason expected_error_;
+
+ // Mock file thread to satisfy debug checks in BaseFile.
+ base::MessageLoop message_loop_;
+ BrowserThreadImpl file_thread_;
+};
+
+// This will initialize the entire array to zero.
+const unsigned char BaseFileTest::kEmptySha256Hash[] = { 0 };
+
+// Test the most basic scenario: just create the object and do a sanity check
+// on all its accessors. This is actually a case that rarely happens
+// in production, where we would at least Initialize it.
+TEST_F(BaseFileTest, CreateDestroy) {
+ EXPECT_EQ(base::FilePath().value(), base_file_->full_path().value());
+}
+
+// Cancel the download explicitly.
+TEST_F(BaseFileTest, Cancel) {
+ ASSERT_TRUE(InitializeFile());
+ EXPECT_TRUE(base::PathExists(base_file_->full_path()));
+ base_file_->Cancel();
+ EXPECT_FALSE(base::PathExists(base_file_->full_path()));
+ EXPECT_NE(base::FilePath().value(), base_file_->full_path().value());
+}
+
+// Write data to the file and detach it, so it doesn't get deleted
+// automatically when base_file_ is destructed.
+TEST_F(BaseFileTest, WriteAndDetach) {
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ base_file_->Finish();
+ base_file_->Detach();
+ expect_file_survives_ = true;
+}
+
+// Write data to the file and detach it, and calculate its sha256 hash.
+TEST_F(BaseFileTest, WriteWithHashAndDetach) {
+ // Calculate the final hash.
+ ResetHash();
+ UpdateHash(kTestData1, kTestDataLength1);
+ std::string expected_hash = GetFinalHash();
+ std::string expected_hash_hex =
+ base::HexEncode(expected_hash.data(), expected_hash.size());
+
+ MakeFileWithHash();
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ base_file_->Finish();
+
+ std::string hash;
+ base_file_->GetHash(&hash);
+ EXPECT_EQ("0B2D3F3F7943AD64B860DF94D05CB56A8A97C6EC5768B5B70B930C5AA7FA9ADE",
+ expected_hash_hex);
+ EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size()));
+
+ base_file_->Detach();
+ expect_file_survives_ = true;
+}
+
+// Rename the file after writing to it, then detach.
+TEST_F(BaseFileTest, WriteThenRenameAndDetach) {
+ ASSERT_TRUE(InitializeFile());
+
+ base::FilePath initial_path(base_file_->full_path());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ base::FilePath new_path(temp_dir_.path().AppendASCII("NewFile"));
+ EXPECT_FALSE(base::PathExists(new_path));
+
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, base_file_->Rename(new_path));
+ EXPECT_FALSE(base::PathExists(initial_path));
+ EXPECT_TRUE(base::PathExists(new_path));
+
+ base_file_->Finish();
+ base_file_->Detach();
+ expect_file_survives_ = true;
+}
+
+// Write data to the file once.
+TEST_F(BaseFileTest, SingleWrite) {
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ base_file_->Finish();
+}
+
+// Write data to the file multiple times.
+TEST_F(BaseFileTest, MultipleWrites) {
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ ASSERT_TRUE(AppendDataToFile(kTestData2));
+ ASSERT_TRUE(AppendDataToFile(kTestData3));
+ std::string hash;
+ EXPECT_FALSE(base_file_->GetHash(&hash));
+ base_file_->Finish();
+}
+
+// Write data to the file once and calculate its sha256 hash.
+TEST_F(BaseFileTest, SingleWriteWithHash) {
+ // Calculate the final hash.
+ ResetHash();
+ UpdateHash(kTestData1, kTestDataLength1);
+ std::string expected_hash = GetFinalHash();
+ std::string expected_hash_hex =
+ base::HexEncode(expected_hash.data(), expected_hash.size());
+
+ MakeFileWithHash();
+ ASSERT_TRUE(InitializeFile());
+ // Can get partial hash states before Finish() is called.
+ EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str());
+ base_file_->Finish();
+
+ std::string hash;
+ base_file_->GetHash(&hash);
+ EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size()));
+}
+
+// Write data to the file multiple times and calculate its sha256 hash.
+TEST_F(BaseFileTest, MultipleWritesWithHash) {
+ // Calculate the final hash.
+ ResetHash();
+ UpdateHash(kTestData1, kTestDataLength1);
+ UpdateHash(kTestData2, kTestDataLength2);
+ UpdateHash(kTestData3, kTestDataLength3);
+ std::string expected_hash = GetFinalHash();
+ std::string expected_hash_hex =
+ base::HexEncode(expected_hash.data(), expected_hash.size());
+
+ std::string hash;
+ MakeFileWithHash();
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ ASSERT_TRUE(AppendDataToFile(kTestData2));
+ ASSERT_TRUE(AppendDataToFile(kTestData3));
+ // No hash before Finish() is called.
+ EXPECT_FALSE(base_file_->GetHash(&hash));
+ base_file_->Finish();
+
+ EXPECT_TRUE(base_file_->GetHash(&hash));
+ EXPECT_EQ("CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8",
+ expected_hash_hex);
+ EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size()));
+}
+
+// Write data to the file multiple times, interrupt it, and continue using
+// another file. Calculate the resulting combined sha256 hash.
+TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) {
+ // Calculate the final hash.
+ ResetHash();
+ UpdateHash(kTestData1, kTestDataLength1);
+ UpdateHash(kTestData2, kTestDataLength2);
+ UpdateHash(kTestData3, kTestDataLength3);
+ std::string expected_hash = GetFinalHash();
+ std::string expected_hash_hex =
+ base::HexEncode(expected_hash.data(), expected_hash.size());
+
+ MakeFileWithHash();
+ ASSERT_TRUE(InitializeFile());
+ // Write some data
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ ASSERT_TRUE(AppendDataToFile(kTestData2));
+ // Get the hash state and file name.
+ std::string hash_state;
+ hash_state = base_file_->GetHashState();
+ // Finish the file.
+ base_file_->Finish();
+
+ base::FilePath new_file_path(temp_dir_.path().Append(
+ base::FilePath(FILE_PATH_LITERAL("second_file"))));
+
+ ASSERT_TRUE(base::CopyFile(base_file_->full_path(), new_file_path));
+
+ // Create another file
+ BaseFile second_file(new_file_path,
+ GURL(),
+ GURL(),
+ base_file_->bytes_so_far(),
+ true,
+ hash_state,
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog());
+ ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ second_file.Initialize(base::FilePath()));
+ std::string data(kTestData3);
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ second_file.AppendDataToFile(data.data(), data.size()));
+ second_file.Finish();
+
+ std::string hash;
+ EXPECT_TRUE(second_file.GetHash(&hash));
+ // This will fail until getting the hash state is supported in SecureHash.
+ EXPECT_STREQ(expected_hash_hex.c_str(),
+ base::HexEncode(hash.data(), hash.size()).c_str());
+}
+
+// Rename the file after all writes to it.
+TEST_F(BaseFileTest, WriteThenRename) {
+ ASSERT_TRUE(InitializeFile());
+
+ base::FilePath initial_path(base_file_->full_path());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ base::FilePath new_path(temp_dir_.path().AppendASCII("NewFile"));
+ EXPECT_FALSE(base::PathExists(new_path));
+
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base_file_->Rename(new_path));
+ EXPECT_FALSE(base::PathExists(initial_path));
+ EXPECT_TRUE(base::PathExists(new_path));
+
+ base_file_->Finish();
+}
+
+// Rename the file while the download is still in progress.
+TEST_F(BaseFileTest, RenameWhileInProgress) {
+ ASSERT_TRUE(InitializeFile());
+
+ base::FilePath initial_path(base_file_->full_path());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ base::FilePath new_path(temp_dir_.path().AppendASCII("NewFile"));
+ EXPECT_FALSE(base::PathExists(new_path));
+
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+
+ EXPECT_TRUE(base_file_->in_progress());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, base_file_->Rename(new_path));
+ EXPECT_FALSE(base::PathExists(initial_path));
+ EXPECT_TRUE(base::PathExists(new_path));
+
+ ASSERT_TRUE(AppendDataToFile(kTestData2));
+
+ base_file_->Finish();
+}
+
+// Test that a failed rename reports the correct error.
+TEST_F(BaseFileTest, RenameWithError) {
+ ASSERT_TRUE(InitializeFile());
+
+ // TestDir is a subdirectory in |temp_dir_| that we will make read-only so
+ // that the rename will fail.
+ base::FilePath test_dir(temp_dir_.path().AppendASCII("TestDir"));
+ ASSERT_TRUE(file_util::CreateDirectory(test_dir));
+
+ base::FilePath new_path(test_dir.AppendASCII("TestFile"));
+ EXPECT_FALSE(base::PathExists(new_path));
+
+ {
+ file_util::PermissionRestorer restore_permissions_for(test_dir);
+ ASSERT_TRUE(file_util::MakeFileUnwritable(test_dir));
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
+ base_file_->Rename(new_path));
+ }
+
+ base_file_->Finish();
+}
+
+// Write data to the file multiple times.
+TEST_F(BaseFileTest, MultipleWritesWithError) {
+ base::FilePath path;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&path));
+ // Create a new file stream. scoped_ptr takes ownership and passes it to
+ // BaseFile; we use the pointer anyway and rely on the BaseFile not
+ // deleting the MockFileStream until the BaseFile is reset.
+ net::testing::MockFileStream* mock_file_stream(
+ new net::testing::MockFileStream(NULL));
+ scoped_ptr<net::FileStream> mock_file_stream_scoped_ptr(mock_file_stream);
+
+ ASSERT_EQ(0,
+ mock_file_stream->OpenSync(
+ path,
+ base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE));
+
+ // Copy of mock_file_stream; we pass ownership and rely on the BaseFile
+ // not deleting it until it is reset.
+
+ base_file_.reset(new BaseFile(mock_file_stream->get_path(),
+ GURL(),
+ GURL(),
+ 0,
+ false,
+ std::string(),
+ mock_file_stream_scoped_ptr.Pass(),
+ net::BoundNetLog()));
+ ASSERT_TRUE(InitializeFile());
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ ASSERT_TRUE(AppendDataToFile(kTestData2));
+ mock_file_stream->set_forced_error(net::ERR_ACCESS_DENIED);
+ set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED);
+ ASSERT_FALSE(AppendDataToFile(kTestData3));
+ std::string hash;
+ EXPECT_FALSE(base_file_->GetHash(&hash));
+ base_file_->Finish();
+}
+
+// Try to write to uninitialized file.
+TEST_F(BaseFileTest, UninitializedFile) {
+ expect_in_progress_ = false;
+ set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ EXPECT_FALSE(AppendDataToFile(kTestData1));
+}
+
+// Create two |BaseFile|s with the same file, and attempt to write to both.
+// Overwrite base_file_ with another file with the same name and
+// non-zero contents, and make sure the last file to close 'wins'.
+TEST_F(BaseFileTest, DuplicateBaseFile) {
+ ASSERT_TRUE(InitializeFile());
+
+ // Create another |BaseFile| referring to the file that |base_file_| owns.
+ CreateFileWithName(base_file_->full_path());
+
+ ASSERT_TRUE(AppendDataToFile(kTestData1));
+ base_file_->Finish();
+}
+
+// Create a file and append to it.
+TEST_F(BaseFileTest, AppendToBaseFile) {
+ // Create a new file.
+ base::FilePath existing_file_name = CreateTestFile();
+
+ set_expected_data(kTestData4);
+
+ // Use the file we've just created.
+ base_file_.reset(new BaseFile(existing_file_name,
+ GURL(),
+ GURL(),
+ kTestDataLength4,
+ false,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog()));
+
+ ASSERT_TRUE(InitializeFile());
+
+ const base::FilePath file_name = base_file_->full_path();
+ EXPECT_NE(base::FilePath::StringType(), file_name.value());
+
+ // Write into the file.
+ EXPECT_TRUE(AppendDataToFile(kTestData1));
+
+ base_file_->Finish();
+ base_file_->Detach();
+ expect_file_survives_ = true;
+}
+
+// Create a read-only file and attempt to write to it.
+TEST_F(BaseFileTest, ReadonlyBaseFile) {
+ // Create a new file.
+ base::FilePath readonly_file_name = CreateTestFile();
+
+ // Restore permissions to the file when we are done with this test.
+ file_util::PermissionRestorer restore_permissions(readonly_file_name);
+
+ // Make it read-only.
+ EXPECT_TRUE(file_util::MakeFileUnwritable(readonly_file_name));
+
+ // Try to overwrite it.
+ base_file_.reset(new BaseFile(readonly_file_name,
+ GURL(),
+ GURL(),
+ 0,
+ false,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog()));
+
+ expect_in_progress_ = false;
+ set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED);
+ EXPECT_FALSE(InitializeFile());
+
+ const base::FilePath file_name = base_file_->full_path();
+ EXPECT_NE(base::FilePath::StringType(), file_name.value());
+
+ // Write into the file.
+ set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ EXPECT_FALSE(AppendDataToFile(kTestData1));
+
+ base_file_->Finish();
+ base_file_->Detach();
+ expect_file_survives_ = true;
+}
+
+TEST_F(BaseFileTest, IsEmptyHash) {
+ std::string empty(BaseFile::kSha256HashLen, '\x00');
+ EXPECT_TRUE(BaseFile::IsEmptyHash(empty));
+ std::string not_empty(BaseFile::kSha256HashLen, '\x01');
+ EXPECT_FALSE(BaseFile::IsEmptyHash(not_empty));
+ EXPECT_FALSE(BaseFile::IsEmptyHash(std::string()));
+}
+
+// Test that a temporary file is created in the default download directory.
+TEST_F(BaseFileTest, CreatedInDefaultDirectory) {
+ ASSERT_TRUE(base_file_->full_path().empty());
+ ASSERT_TRUE(InitializeFile());
+ EXPECT_FALSE(base_file_->full_path().empty());
+
+ // On Windows, CreateTemporaryFileInDir() will cause a path with short names
+ // to be expanded into a path with long names. Thus temp_dir.path() might not
+ // be a string-wise match to base_file_->full_path().DirName() even though
+ // they are in the same directory.
+ base::FilePath temp_file;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file));
+ ASSERT_FALSE(temp_file.empty());
+ EXPECT_STREQ(temp_file.DirName().value().c_str(),
+ base_file_->full_path().DirName().value().c_str());
+ base_file_->Finish();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/base_file_win.cc b/chromium/content/browser/download/base_file_win.cc
new file mode 100644
index 00000000000..252f0495d7d
--- /dev/null
+++ b/chromium/content/browser/download/base_file_win.cc
@@ -0,0 +1,367 @@
+// 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 "content/browser/download/base_file.h"
+
+#include <windows.h>
+#include <cguid.h>
+#include <objbase.h>
+#include <shellapi.h>
+
+#include "base/file_util.h"
+#include "base/guid.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/safe_util_win.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+namespace {
+
+const int kAllSpecialShFileOperationCodes[] = {
+ // Should be kept in sync with the case statement below.
+ ERROR_ACCESS_DENIED,
+ 0x71,
+ 0x72,
+ 0x73,
+ 0x74,
+ 0x75,
+ 0x76,
+ 0x78,
+ 0x79,
+ 0x7A,
+ 0x7C,
+ 0x7D,
+ 0x7E,
+ 0x80,
+ 0x81,
+ 0x82,
+ 0x83,
+ 0x84,
+ 0x85,
+ 0x86,
+ 0x87,
+ 0x88,
+ 0xB7,
+ 0x402,
+ 0x10000,
+ 0x10074,
+};
+
+// Maps the result of a call to |SHFileOperation()| onto a
+// |DownloadInterruptReason|.
+//
+// These return codes are *old* (as in, DOS era), and specific to
+// |SHFileOperation()|.
+// They do not appear in any windows header.
+//
+// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
+DownloadInterruptReason MapShFileOperationCodes(int code) {
+ DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ // Check these pre-Win32 error codes first, then check for matches
+ // in Winerror.h.
+ // This switch statement should be kept in sync with the list of codes
+ // above.
+ switch (code) {
+ // Not a pre-Win32 error code; here so that this particular
+ // case shows up in our histograms. This is redundant with the
+ // mapping function net::MapSystemError used later.
+ case ERROR_ACCESS_DENIED: // Access is denied.
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The source and destination files are the same file.
+ // DE_SAMEFILE == 0x71
+ case 0x71:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The operation was canceled by the user, or silently canceled if the
+ // appropriate flags were supplied to SHFileOperation.
+ // DE_OPCANCELLED == 0x75
+ case 0x75:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // Security settings denied access to the source.
+ // DE_ACCESSDENIEDSRC == 0x78
+ case 0x78:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The source or destination path exceeded or would exceed MAX_PATH.
+ // DE_PATHTOODEEP == 0x79
+ case 0x79:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
+ break;
+
+ // The path in the source or destination or both was invalid.
+ // DE_INVALIDFILES == 0x7C
+ case 0x7C:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The destination path is an existing file.
+ // DE_FLDDESTISFILE == 0x7E
+ case 0x7E:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The destination path is an existing folder.
+ // DE_FILEDESTISFLD == 0x80
+ case 0x80:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The name of the file exceeds MAX_PATH.
+ // DE_FILENAMETOOLONG == 0x81
+ case 0x81:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
+ break;
+
+ // The destination is a read-only CD-ROM, possibly unformatted.
+ // DE_DEST_IS_CDROM == 0x82
+ case 0x82:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The destination is a read-only DVD, possibly unformatted.
+ // DE_DEST_IS_DVD == 0x83
+ case 0x83:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The destination is a writable CD-ROM, possibly unformatted.
+ // DE_DEST_IS_CDRECORD == 0x84
+ case 0x84:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The file involved in the operation is too large for the destination
+ // media or file system.
+ // DE_FILE_TOO_LARGE == 0x85
+ case 0x85:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
+ break;
+
+ // The source is a read-only CD-ROM, possibly unformatted.
+ // DE_SRC_IS_CDROM == 0x86
+ case 0x86:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The source is a read-only DVD, possibly unformatted.
+ // DE_SRC_IS_DVD == 0x87
+ case 0x87:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // The source is a writable CD-ROM, possibly unformatted.
+ // DE_SRC_IS_CDRECORD == 0x88
+ case 0x88:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+ break;
+
+ // MAX_PATH was exceeded during the operation.
+ // DE_ERROR_MAX == 0xB7
+ case 0xB7:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
+ break;
+
+ // An unspecified error occurred on the destination.
+ // XE_ERRORONDEST == 0x10000
+ case 0x10000:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // Multiple file paths were specified in the source buffer, but only one
+ // destination file path.
+ // DE_MANYSRC1DEST == 0x72
+ case 0x72:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // Rename operation was specified but the destination path is
+ // a different directory. Use the move operation instead.
+ // DE_DIFFDIR == 0x73
+ case 0x73:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The source is a root directory, which cannot be moved or renamed.
+ // DE_ROOTDIR == 0x74
+ case 0x74:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The destination is a subtree of the source.
+ // DE_DESTSUBTREE == 0x76
+ case 0x76:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The operation involved multiple destination paths,
+ // which can fail in the case of a move operation.
+ // DE_MANYDEST == 0x7A
+ case 0x7A:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // The source and destination have the same parent folder.
+ // DE_DESTSAMETREE == 0x7D
+ case 0x7D:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // An unknown error occurred. This is typically due to an invalid path in
+ // the source or destination. This error does not occur on Windows Vista
+ // and later.
+ // DE_UNKNOWN_ERROR == 0x402
+ case 0x402:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+
+ // Destination is a root directory and cannot be renamed.
+ // DE_ROOTDIR | ERRORONDEST == 0x10074
+ case 0x10074:
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ break;
+ }
+
+ // Narrow down on the reason we're getting some catch-all interrupt reasons.
+ if (result == DOWNLOAD_INTERRUPT_REASON_FILE_FAILED) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Download.MapWinShErrorFileFailed", code,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllSpecialShFileOperationCodes,
+ arraysize(kAllSpecialShFileOperationCodes)));
+ }
+
+ if (result == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Download.MapWinShErrorAccessDenied", code,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllSpecialShFileOperationCodes,
+ arraysize(kAllSpecialShFileOperationCodes)));
+ }
+
+ if (result != DOWNLOAD_INTERRUPT_REASON_NONE)
+ return result;
+
+ // If not one of the above codes, it should be a standard Windows error code.
+ return ConvertNetErrorToInterruptReason(
+ net::MapSystemError(code), DOWNLOAD_INTERRUPT_FROM_DISK);
+}
+
+// Maps a return code from ScanAndSaveDownloadedFile() to a
+// DownloadInterruptReason. The return code in |result| is usually from the
+// final IAttachmentExecute::Save() call.
+DownloadInterruptReason MapScanAndSaveErrorCodeToInterruptReason(
+ HRESULT result) {
+ if (SUCCEEDED(result))
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ switch (result) {
+ case INET_E_SECURITY_PROBLEM: // 0x800c000e
+ // This is returned if the download was blocked due to security
+ // restrictions. E.g. if the source URL was in the Restricted Sites zone
+ // and downloads are blocked on that zone, then the download would be
+ // deleted and this error code is returned.
+ return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
+
+ case E_FAIL: // 0x80004005
+ // Returned if an anti-virus product reports an infection in the
+ // downloaded file during IAE::Save().
+ return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
+
+ default:
+ // Any other error that occurs during IAttachmentExecute::Save() likely
+ // indicates a problem with the security check, but not necessarily the
+ // download. See http://crbug.com/153212.
+ return DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
+ }
+}
+
+} // namespace
+
+// Renames a file using the SHFileOperation API to ensure that the target file
+// gets the correct default security descriptor in the new path.
+// Returns a network error, or net::OK for success.
+DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
+ const base::FilePath& new_path) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ // The parameters to SHFileOperation must be terminated with 2 NULL chars.
+ base::FilePath::StringType source = full_path_.value();
+ base::FilePath::StringType target = new_path.value();
+
+ source.append(1, L'\0');
+ target.append(1, L'\0');
+
+ SHFILEOPSTRUCT move_info = {0};
+ move_info.wFunc = FO_MOVE;
+ move_info.pFrom = source.c_str();
+ move_info.pTo = target.c_str();
+ move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI |
+ FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
+
+ int result = SHFileOperation(&move_info);
+ DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ if (result == 0 && move_info.fAnyOperationsAborted)
+ interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ else if (result != 0)
+ interrupt_reason = MapShFileOperationCodes(result);
+
+ if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
+ return LogInterruptReason("SHFileOperation", result, interrupt_reason);
+ return interrupt_reason;
+}
+
+DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!detached_);
+
+ bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
+ DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
+ std::string braces_guid = "{" + client_guid_ + "}";
+ GUID guid = GUID_NULL;
+ if (base::IsValidGUID(client_guid_)) {
+ HRESULT hr = CLSIDFromString(
+ base::UTF8ToUTF16(braces_guid).c_str(), &guid);
+ if (FAILED(hr))
+ guid = GUID_NULL;
+ }
+
+ HRESULT hr = AVScanFile(full_path_, source_url_.spec(), guid);
+
+ // If the download file is missing after the call, then treat this as an
+ // interrupted download.
+ //
+ // If the ScanAndSaveDownloadedFile() call failed, but the downloaded file is
+ // still around, then don't interrupt the download. Attachment Execution
+ // Services deletes the submitted file if the downloaded file is blocked by
+ // policy or if it was found to be infected.
+ //
+ // If the file is still there, then the error could be due to AES not being
+ // available or some other error during the AES invocation. In either case,
+ // we don't surface the error to the user.
+ if (!base::PathExists(full_path_)) {
+ DCHECK(FAILED(hr));
+ result = MapScanAndSaveErrorCodeToInterruptReason(hr);
+ if (result == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ RecordDownloadCount(FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT);
+ result = DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
+ }
+ LogInterruptReason("ScanAndSaveDownloadedFile", hr, result);
+ }
+ bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_browsertest.cc b/chromium/content/browser/download/download_browsertest.cc
new file mode 100644
index 00000000000..516ae18d861
--- /dev/null
+++ b/chromium/content/browser/download/download_browsertest.cc
@@ -0,0 +1,1641 @@
+// 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 file contains download browser tests that are known to be runnable
+// in a pure content context. Over time tests should be migrated here.
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_file_impl.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/webplugininfo.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_file_error_injector.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/shell/shell_browser_context.h"
+#include "content/shell/shell_download_manager_delegate.h"
+#include "content/shell/shell_network_delegate.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "content/test/net/url_request_slow_download_job.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Field;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+namespace content {
+
+namespace {
+
+class MockDownloadItemObserver : public DownloadItem::Observer {
+ public:
+ MockDownloadItemObserver() {}
+ virtual ~MockDownloadItemObserver() {}
+
+ MOCK_METHOD1(OnDownloadUpdated, void(DownloadItem*));
+ MOCK_METHOD1(OnDownloadOpened, void(DownloadItem*));
+ MOCK_METHOD1(OnDownloadRemoved, void(DownloadItem*));
+ MOCK_METHOD1(OnDownloadDestroyed, void(DownloadItem*));
+};
+
+class MockDownloadManagerObserver : public DownloadManager::Observer {
+ public:
+ MockDownloadManagerObserver(DownloadManager* manager) {
+ manager_ = manager;
+ manager->AddObserver(this);
+ }
+ virtual ~MockDownloadManagerObserver() {
+ if (manager_)
+ manager_->RemoveObserver(this);
+ }
+
+ MOCK_METHOD2(OnDownloadCreated, void(DownloadManager*, DownloadItem*));
+ MOCK_METHOD1(ModelChanged, void(DownloadManager*));
+ void ManagerGoingDown(DownloadManager* manager) {
+ DCHECK_EQ(manager_, manager);
+ MockManagerGoingDown(manager);
+
+ manager_->RemoveObserver(this);
+ manager_ = NULL;
+ }
+
+ MOCK_METHOD1(MockManagerGoingDown, void(DownloadManager*));
+ private:
+ DownloadManager* manager_;
+};
+
+class DownloadFileWithDelayFactory;
+
+static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) {
+ // We're in a content_browsertest; we know that the DownloadManager
+ // is a DownloadManagerImpl.
+ return static_cast<DownloadManagerImpl*>(
+ BrowserContext::GetDownloadManager(
+ shell->web_contents()->GetBrowserContext()));
+}
+
+class DownloadFileWithDelay : public DownloadFileImpl {
+ public:
+ DownloadFileWithDelay(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ scoped_ptr<PowerSaveBlocker> power_save_blocker,
+ base::WeakPtr<DownloadDestinationObserver> observer,
+ base::WeakPtr<DownloadFileWithDelayFactory> owner);
+
+ virtual ~DownloadFileWithDelay();
+
+ // Wraps DownloadFileImpl::Rename* and intercepts the return callback,
+ // storing it in the factory that produced this object for later
+ // retrieval.
+ virtual void RenameAndUniquify(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) OVERRIDE;
+ virtual void RenameAndAnnotate(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) OVERRIDE;
+
+ private:
+ static void RenameCallbackWrapper(
+ const base::WeakPtr<DownloadFileWithDelayFactory>& factory,
+ const RenameCompletionCallback& original_callback,
+ DownloadInterruptReason reason,
+ const base::FilePath& path);
+
+ // This variable may only be read on the FILE thread, and may only be
+ // indirected through (e.g. methods on DownloadFileWithDelayFactory called)
+ // on the UI thread. This is because after construction,
+ // DownloadFileWithDelay lives on the file thread, but
+ // DownloadFileWithDelayFactory is purely a UI thread object.
+ base::WeakPtr<DownloadFileWithDelayFactory> owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelay);
+};
+
+// All routines on this class must be called on the UI thread.
+class DownloadFileWithDelayFactory : public DownloadFileFactory {
+ public:
+ DownloadFileWithDelayFactory();
+ virtual ~DownloadFileWithDelayFactory();
+
+ // DownloadFileFactory interface.
+ virtual DownloadFile* CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE;
+
+ void AddRenameCallback(base::Closure callback);
+ void GetAllRenameCallbacks(std::vector<base::Closure>* results);
+
+ // Do not return until GetAllRenameCallbacks() will return a non-empty list.
+ void WaitForSomeCallback();
+
+ private:
+ base::WeakPtrFactory<DownloadFileWithDelayFactory> weak_ptr_factory_;
+ std::vector<base::Closure> rename_callbacks_;
+ bool waiting_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory);
+};
+
+DownloadFileWithDelay::DownloadFileWithDelay(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ scoped_ptr<PowerSaveBlocker> power_save_blocker,
+ base::WeakPtr<DownloadDestinationObserver> observer,
+ base::WeakPtr<DownloadFileWithDelayFactory> owner)
+ : DownloadFileImpl(
+ save_info.Pass(), default_download_directory, url, referrer_url,
+ calculate_hash, stream.Pass(), bound_net_log,
+ power_save_blocker.Pass(), observer),
+ owner_(owner) {}
+
+DownloadFileWithDelay::~DownloadFileWithDelay() {}
+
+void DownloadFileWithDelay::RenameAndUniquify(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DownloadFileImpl::RenameAndUniquify(
+ full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper,
+ owner_, callback));
+}
+
+void DownloadFileWithDelay::RenameAndAnnotate(
+ const base::FilePath& full_path, const RenameCompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DownloadFileImpl::RenameAndAnnotate(
+ full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper,
+ owner_, callback));
+}
+
+// static
+void DownloadFileWithDelay::RenameCallbackWrapper(
+ const base::WeakPtr<DownloadFileWithDelayFactory>& factory,
+ const RenameCompletionCallback& original_callback,
+ DownloadInterruptReason reason,
+ const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!factory)
+ return;
+ factory->AddRenameCallback(base::Bind(original_callback, reason, path));
+}
+
+DownloadFileWithDelayFactory::DownloadFileWithDelayFactory()
+ : weak_ptr_factory_(this),
+ waiting_(false) {}
+DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {}
+
+DownloadFile* DownloadFileWithDelayFactory::CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer) {
+ scoped_ptr<PowerSaveBlocker> psb(
+ PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ "Download in progress"));
+ return new DownloadFileWithDelay(
+ save_info.Pass(), default_download_directory, url, referrer_url,
+ calculate_hash, stream.Pass(), bound_net_log,
+ psb.Pass(), observer, weak_ptr_factory_.GetWeakPtr());
+}
+
+void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ rename_callbacks_.push_back(callback);
+ if (waiting_)
+ base::MessageLoopForUI::current()->Quit();
+}
+
+void DownloadFileWithDelayFactory::GetAllRenameCallbacks(
+ std::vector<base::Closure>* results) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ results->swap(rename_callbacks_);
+}
+
+void DownloadFileWithDelayFactory::WaitForSomeCallback() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (rename_callbacks_.empty()) {
+ waiting_ = true;
+ RunMessageLoop();
+ waiting_ = false;
+ }
+}
+
+class CountingDownloadFile : public DownloadFileImpl {
+ public:
+ CountingDownloadFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_downloads_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ scoped_ptr<PowerSaveBlocker> power_save_blocker,
+ base::WeakPtr<DownloadDestinationObserver> observer)
+ : DownloadFileImpl(save_info.Pass(), default_downloads_directory,
+ url, referrer_url, calculate_hash,
+ stream.Pass(), bound_net_log,
+ power_save_blocker.Pass(), observer) {}
+
+ virtual ~CountingDownloadFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ active_files_--;
+ }
+
+ virtual void Initialize(const InitializeCallback& callback) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ active_files_++;
+ return DownloadFileImpl::Initialize(callback);
+ }
+
+ static void GetNumberActiveFiles(int* result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ *result = active_files_;
+ }
+
+ // Can be called on any thread, and will block (running message loop)
+ // until data is returned.
+ static int GetNumberActiveFilesFromFileThread() {
+ int result = -1;
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&CountingDownloadFile::GetNumberActiveFiles, &result),
+ base::MessageLoop::current()->QuitClosure());
+ base::MessageLoop::current()->Run();
+ DCHECK_NE(-1, result);
+ return result;
+ }
+
+ private:
+ static int active_files_;
+};
+
+int CountingDownloadFile::active_files_ = 0;
+
+class CountingDownloadFileFactory : public DownloadFileFactory {
+ public:
+ CountingDownloadFileFactory() {}
+ virtual ~CountingDownloadFileFactory() {}
+
+ // DownloadFileFactory interface.
+ virtual DownloadFile* CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_downloads_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE {
+ scoped_ptr<PowerSaveBlocker> psb(
+ PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ "Download in progress"));
+ return new CountingDownloadFile(
+ save_info.Pass(), default_downloads_directory, url, referrer_url,
+ calculate_hash, stream.Pass(), bound_net_log,
+ psb.Pass(), observer);
+ }
+};
+
+class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate {
+ public:
+ TestShellDownloadManagerDelegate()
+ : delay_download_open_(false) {}
+
+ virtual bool ShouldOpenDownload(
+ DownloadItem* item,
+ const DownloadOpenDelayedCallback& callback) OVERRIDE {
+ if (delay_download_open_) {
+ delayed_callbacks_.push_back(callback);
+ return false;
+ }
+ return true;
+ }
+
+ void SetDelayedOpen(bool delay) {
+ delay_download_open_ = delay;
+ }
+
+ void GetDelayedCallbacks(
+ std::vector<DownloadOpenDelayedCallback>* callbacks) {
+ callbacks->swap(delayed_callbacks_);
+ }
+ private:
+ virtual ~TestShellDownloadManagerDelegate() {}
+
+ bool delay_download_open_;
+ std::vector<DownloadOpenDelayedCallback> delayed_callbacks_;
+};
+
+// Record all state transitions and byte counts on the observed download.
+class RecordingDownloadObserver : DownloadItem::Observer {
+ public:
+ struct RecordStruct {
+ DownloadItem::DownloadState state;
+ int bytes_received;
+ };
+
+ typedef std::vector<RecordStruct> RecordVector;
+
+ RecordingDownloadObserver(DownloadItem* download)
+ : download_(download) {
+ last_state_.state = download->GetState();
+ last_state_.bytes_received = download->GetReceivedBytes();
+ download_->AddObserver(this);
+ }
+
+ virtual ~RecordingDownloadObserver() {
+ RemoveObserver();
+ }
+
+ void CompareToExpectedRecord(const RecordStruct expected[], size_t size) {
+ EXPECT_EQ(size, record_.size());
+ int min = size > record_.size() ? record_.size() : size;
+ for (int i = 0; i < min; ++i) {
+ EXPECT_EQ(expected[i].state, record_[i].state) << "Iteration " << i;
+ EXPECT_EQ(expected[i].bytes_received, record_[i].bytes_received)
+ << "Iteration " << i;
+ }
+ }
+
+ private:
+ virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE {
+ DCHECK_EQ(download_, download);
+ DownloadItem::DownloadState state = download->GetState();
+ int bytes = download->GetReceivedBytes();
+ if (last_state_.state != state || last_state_.bytes_received > bytes) {
+ last_state_.state = state;
+ last_state_.bytes_received = bytes;
+ record_.push_back(last_state_);
+ }
+ }
+
+ virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE {
+ DCHECK_EQ(download_, download);
+ RemoveObserver();
+ }
+
+ void RemoveObserver() {
+ if (download_) {
+ download_->RemoveObserver(this);
+ download_ = NULL;
+ }
+ }
+
+ DownloadItem* download_;
+ RecordStruct last_state_;
+ RecordVector record_;
+};
+
+// Get the next created download.
+class DownloadCreateObserver : DownloadManager::Observer {
+ public:
+ DownloadCreateObserver(DownloadManager* manager)
+ : manager_(manager),
+ item_(NULL),
+ waiting_(false) {
+ manager_->AddObserver(this);
+ }
+
+ virtual ~DownloadCreateObserver() {
+ if (manager_)
+ manager_->RemoveObserver(this);
+ manager_ = NULL;
+ }
+
+ virtual void ManagerGoingDown(DownloadManager* manager) OVERRIDE {
+ DCHECK_EQ(manager_, manager);
+ manager_->RemoveObserver(this);
+ manager_ = NULL;
+ }
+
+ virtual void OnDownloadCreated(DownloadManager* manager,
+ DownloadItem* download) OVERRIDE {
+ if (!item_)
+ item_ = download;
+
+ if (waiting_)
+ base::MessageLoopForUI::current()->Quit();
+ }
+
+ DownloadItem* WaitForFinished() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!item_) {
+ waiting_ = true;
+ RunMessageLoop();
+ waiting_ = false;
+ }
+ return item_;
+ }
+
+ private:
+ DownloadManager* manager_;
+ DownloadItem* item_;
+ bool waiting_;
+};
+
+
+// Filter for waiting for a certain number of bytes.
+bool DataReceivedFilter(int number_of_bytes, DownloadItem* download) {
+ return download->GetReceivedBytes() >= number_of_bytes;
+}
+
+// Filter for download completion.
+bool DownloadCompleteFilter(DownloadItem* download) {
+ return download->GetState() == DownloadItem::COMPLETE;
+}
+
+// Filter for saving the size of the download when the first IN_PROGRESS
+// is hit.
+bool InitialSizeFilter(int* download_size, DownloadItem* download) {
+ if (download->GetState() != DownloadItem::IN_PROGRESS)
+ return false;
+
+ *download_size = download->GetReceivedBytes();
+ return true;
+}
+
+} // namespace
+
+class DownloadContentTest : public ContentBrowserTest {
+ protected:
+ // An initial send from a website of at least this size will not be
+ // help up by buffering in the underlying downloads ByteStream data
+ // transfer. This is important because on resumption tests we wait
+ // until we've gotten the data we expect before allowing the test server
+ // to send its reset, to get around hard close semantics on the Windows
+ // socket layer implementation.
+ int GetSafeBufferChunk() const {
+ return (DownloadResourceHandler::kDownloadByteStreamSize /
+ ByteStreamWriter::kFractionBufferBeforeSending) + 1;
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+
+ TestShellDownloadManagerDelegate* delegate =
+ new TestShellDownloadManagerDelegate();
+ delegate->SetDownloadBehaviorForTesting(downloads_directory_.path());
+ DownloadManager* manager = DownloadManagerForShell(shell());
+ manager->SetDelegate(delegate);
+ delegate->SetDownloadManager(manager);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestSlowDownloadJob::AddUrlHandler));
+ base::FilePath mock_base(GetTestFilePath("download", ""));
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestMockHTTPJob::AddUrlHandler, mock_base));
+ }
+
+ TestShellDownloadManagerDelegate* GetDownloadManagerDelegate(
+ DownloadManager* manager) {
+ return static_cast<TestShellDownloadManagerDelegate*>(
+ manager->GetDelegate());
+ }
+
+ // Create a DownloadTestObserverTerminal that will wait for the
+ // specified number of downloads to finish.
+ DownloadTestObserver* CreateWaiter(
+ Shell* shell, int num_downloads) {
+ DownloadManager* download_manager = DownloadManagerForShell(shell);
+ return new DownloadTestObserverTerminal(download_manager, num_downloads,
+ DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+ }
+
+ // Create a DownloadTestObserverInProgress that will wait for the
+ // specified number of downloads to start.
+ DownloadCreateObserver* CreateInProgressWaiter(
+ Shell* shell, int num_downloads) {
+ DownloadManager* download_manager = DownloadManagerForShell(shell);
+ return new DownloadCreateObserver(download_manager);
+ }
+
+ DownloadTestObserver* CreateInterruptedWaiter(
+ Shell* shell, int num_downloads) {
+ DownloadManager* download_manager = DownloadManagerForShell(shell);
+ return new DownloadTestObserverInterrupted(download_manager, num_downloads,
+ DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
+ }
+
+ // Note: Cannot be used with other alternative DownloadFileFactorys
+ void SetupEnsureNoPendingDownloads() {
+ DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory>(
+ new CountingDownloadFileFactory()).Pass());
+ }
+
+ bool EnsureNoPendingDownloads() {
+ bool result = true;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&EnsureNoPendingDownloadJobsOnIO, &result));
+ base::MessageLoop::current()->Run();
+ return result &&
+ (CountingDownloadFile::GetNumberActiveFilesFromFileThread() == 0);
+ }
+
+ void DownloadAndWait(Shell* shell, const GURL& url,
+ DownloadItem::DownloadState expected_terminal_state) {
+ scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell, 1));
+ NavigateToURL(shell, url);
+ observer->WaitForFinished();
+ EXPECT_EQ(1u, observer->NumDownloadsSeenInState(expected_terminal_state));
+ }
+
+ // Checks that |path| is has |file_size| bytes, and matches the |value|
+ // string.
+ bool VerifyFile(const base::FilePath& path,
+ const std::string& value,
+ const int64 file_size) {
+ std::string file_contents;
+
+ bool read = file_util::ReadFileToString(path, &file_contents);
+ EXPECT_TRUE(read) << "Failed reading file: " << path.value() << std::endl;
+ if (!read)
+ return false; // Couldn't read the file.
+
+ // Note: we don't handle really large files (more than size_t can hold)
+ // so we will fail in that case.
+ size_t expected_size = static_cast<size_t>(file_size);
+
+ // Check the size.
+ EXPECT_EQ(expected_size, file_contents.size());
+ if (expected_size != file_contents.size())
+ return false;
+
+ // Check the contents.
+ EXPECT_EQ(value, file_contents);
+ if (memcmp(file_contents.c_str(), value.c_str(), expected_size) != 0)
+ return false;
+
+ return true;
+ }
+
+ // Start a download and return the item.
+ DownloadItem* StartDownloadAndReturnItem(GURL url) {
+ scoped_ptr<DownloadCreateObserver> observer(
+ CreateInProgressWaiter(shell(), 1));
+ NavigateToURL(shell(), url);
+ observer->WaitForFinished();
+ std::vector<DownloadItem*> downloads;
+ DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
+ EXPECT_EQ(1u, downloads.size());
+ if (1u != downloads.size())
+ return NULL;
+ return downloads[0];
+ }
+
+ // Wait for data
+ void WaitForData(DownloadItem* download, int size) {
+ DownloadUpdatedObserver data_observer(
+ download, base::Bind(&DataReceivedFilter, size));
+ data_observer.WaitForEvent();
+ ASSERT_EQ(size, download->GetReceivedBytes());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState());
+ }
+
+ // Tell the test server to release a pending RST and confirm
+ // that the interrupt is received properly (for download resumption
+ // testing).
+ void ReleaseRSTAndConfirmInterruptForResume(DownloadItem* download) {
+ scoped_ptr<DownloadTestObserver> rst_observer(
+ CreateInterruptedWaiter(shell(), 1));
+ NavigateToURL(shell(), test_server()->GetURL("download-finish"));
+ rst_observer->WaitForFinished();
+ EXPECT_EQ(DownloadItem::INTERRUPTED, download->GetState());
+ }
+
+ // Confirm file status expected for the given location in a stream
+ // provided by the resume test server.
+ void ConfirmFileStatusForResume(
+ DownloadItem* download, bool file_exists,
+ int received_bytes, int total_bytes,
+ const base::FilePath& expected_filename) {
+ // expected_filename is only known if the file exists.
+ ASSERT_EQ(file_exists, !expected_filename.empty());
+ EXPECT_EQ(received_bytes, download->GetReceivedBytes());
+ EXPECT_EQ(total_bytes, download->GetTotalBytes());
+ EXPECT_EQ(expected_filename.value(),
+ download->GetFullPath().BaseName().value());
+ EXPECT_EQ(file_exists,
+ (!download->GetFullPath().empty() &&
+ base::PathExists(download->GetFullPath())));
+
+ if (file_exists) {
+ std::string file_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(
+ download->GetFullPath(), &file_contents));
+
+ ASSERT_EQ(static_cast<size_t>(received_bytes), file_contents.size());
+ for (int i = 0; i < received_bytes; ++i) {
+ EXPECT_EQ(static_cast<char>((i * 2 + 15) % 256), file_contents[i])
+ << "File contents diverged at position " << i
+ << " for " << expected_filename.value();
+
+ if (static_cast<char>((i * 2 + 15) % 256) != file_contents[i])
+ return;
+ }
+ }
+ }
+
+ private:
+ static void EnsureNoPendingDownloadJobsOnIO(bool* result) {
+ if (URLRequestSlowDownloadJob::NumberOutstandingRequests())
+ *result = false;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ // Location of the downloads directory for these tests
+ base::ScopedTempDir downloads_directory_;
+};
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) {
+ SetupEnsureNoPendingDownloads();
+
+ // Create a download, wait until it's started, and confirm
+ // we're in the expected state.
+ scoped_ptr<DownloadCreateObserver> observer(
+ CreateInProgressWaiter(shell(), 1));
+ NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
+ observer->WaitForFinished();
+
+ std::vector<DownloadItem*> downloads;
+ DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
+ ASSERT_EQ(1u, downloads.size());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState());
+
+ // Cancel the download and wait for download system quiesce.
+ downloads[0]->Cancel(true);
+ scoped_refptr<DownloadTestFlushObserver> flush_observer(
+ new DownloadTestFlushObserver(DownloadManagerForShell(shell())));
+ flush_observer->WaitForFlush();
+
+ // Get the important info from other threads and check it.
+ EXPECT_TRUE(EnsureNoPendingDownloads());
+}
+
+// Check that downloading multiple (in this case, 2) files does not result in
+// corrupted files.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, MultiDownload) {
+ SetupEnsureNoPendingDownloads();
+
+ // Create a download, wait until it's started, and confirm
+ // we're in the expected state.
+ scoped_ptr<DownloadCreateObserver> observer1(
+ CreateInProgressWaiter(shell(), 1));
+ NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
+ observer1->WaitForFinished();
+
+ std::vector<DownloadItem*> downloads;
+ DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
+ ASSERT_EQ(1u, downloads.size());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState());
+ DownloadItem* download1 = downloads[0]; // The only download.
+
+ // Start the second download and wait until it's done.
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(file));
+ // Download the file and wait.
+ DownloadAndWait(shell(), url, DownloadItem::COMPLETE);
+
+ // Should now have 2 items on the manager.
+ downloads.clear();
+ DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
+ ASSERT_EQ(2u, downloads.size());
+ // We don't know the order of the downloads.
+ DownloadItem* download2 = downloads[(download1 == downloads[0]) ? 1 : 0];
+
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, download1->GetState());
+ ASSERT_EQ(DownloadItem::COMPLETE, download2->GetState());
+
+ // Allow the first request to finish.
+ scoped_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1));
+ NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kFinishDownloadUrl));
+ observer2->WaitForFinished(); // Wait for the third request.
+ EXPECT_EQ(1u, observer2->NumDownloadsSeenInState(DownloadItem::COMPLETE));
+
+ // Get the important info from other threads and check it.
+ EXPECT_TRUE(EnsureNoPendingDownloads());
+
+ // The |DownloadItem|s should now be done and have the final file names.
+ // Verify that the files have the expected data and size.
+ // |file1| should be full of '*'s, and |file2| should be the same as the
+ // source file.
+ base::FilePath file1(download1->GetTargetFilePath());
+ size_t file_size1 = URLRequestSlowDownloadJob::kFirstDownloadSize +
+ URLRequestSlowDownloadJob::kSecondDownloadSize;
+ std::string expected_contents(file_size1, '*');
+ ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1));
+
+ base::FilePath file2(download2->GetTargetFilePath());
+ ASSERT_TRUE(base::ContentsEqual(
+ file2, GetTestFilePath("download", "download-test.lib")));
+}
+
+#if defined(ENABLE_PLUGINS)
+// Content served with a MIME type of application/octet-stream should be
+// downloaded even when a plugin can be found that handles the file type.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadOctetStream) {
+ const base::FilePath::CharType kTestFilePath[] =
+ FILE_PATH_LITERAL("octet-stream.abc");
+ const char kTestPluginName[] = "TestPlugin";
+ const char kTestMimeType[] = "application/x-test-mime-type";
+ const char kTestFileType[] = "abc";
+
+ WebPluginInfo plugin_info;
+ plugin_info.name = base::ASCIIToUTF16(kTestPluginName);
+ plugin_info.mime_types.push_back(
+ WebPluginMimeType(kTestMimeType, kTestFileType, ""));
+ PluginServiceImpl::GetInstance()->RegisterInternalPlugin(plugin_info, false);
+
+ // The following is served with a Content-Type of application/octet-stream.
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(base::FilePath(kTestFilePath)));
+ DownloadAndWait(shell(), url, DownloadItem::COMPLETE);
+}
+#endif
+
+// Try to cancel just before we release the download file, by delaying final
+// rename callback.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtFinalRename) {
+ // Setup new factory.
+ DownloadFileWithDelayFactory* file_factory =
+ new DownloadFileWithDelayFactory();
+ DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));
+ download_manager->SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory>(file_factory).Pass());
+
+ // Create a download
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Wait until the first (intermediate file) rename and execute the callback.
+ file_factory->WaitForSomeCallback();
+ std::vector<base::Closure> callbacks;
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Wait until the second (final) rename callback is posted.
+ file_factory->WaitForSomeCallback();
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+
+ // Cancel it.
+ std::vector<DownloadItem*> items;
+ download_manager->GetAllDownloads(&items);
+ ASSERT_EQ(1u, items.size());
+ items[0]->Cancel(true);
+ RunAllPendingInMessageLoop();
+
+ // Check state.
+ EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());
+
+ // Run final rename callback.
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Check state.
+ EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());
+}
+
+// Try to cancel just after we release the download file, by delaying
+// in ShouldOpenDownload.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtRelease) {
+ DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));
+
+ // Mark delegate for delayed open.
+ GetDownloadManagerDelegate(download_manager)->SetDelayedOpen(true);
+
+ // Setup new factory.
+ DownloadFileWithDelayFactory* file_factory =
+ new DownloadFileWithDelayFactory();
+ download_manager->SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory>(file_factory).Pass());
+
+ // Create a download
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Wait until the first (intermediate file) rename and execute the callback.
+ file_factory->WaitForSomeCallback();
+ std::vector<base::Closure> callbacks;
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Wait until the second (final) rename callback is posted.
+ file_factory->WaitForSomeCallback();
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+
+ // Call it.
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Confirm download still IN_PROGRESS (internal state COMPLETING).
+ std::vector<DownloadItem*> items;
+ download_manager->GetAllDownloads(&items);
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+ // Cancel the download; confirm cancel fails.
+ ASSERT_EQ(1u, items.size());
+ items[0]->Cancel(true);
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+ // Need to complete open test.
+ std::vector<DownloadOpenDelayedCallback> delayed_callbacks;
+ GetDownloadManagerDelegate(download_manager)->GetDelayedCallbacks(
+ &delayed_callbacks);
+ ASSERT_EQ(1u, delayed_callbacks.size());
+ delayed_callbacks[0].Run(true);
+
+ // *Now* the download should be complete.
+ EXPECT_EQ(DownloadItem::COMPLETE, items[0]->GetState());
+}
+
+// Try to shutdown with a download in progress to make sure shutdown path
+// works properly.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownInProgress) {
+ // Create a download that won't complete.
+ scoped_ptr<DownloadCreateObserver> observer(
+ CreateInProgressWaiter(shell(), 1));
+ NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
+ observer->WaitForFinished();
+
+ // Get the item.
+ std::vector<DownloadItem*> items;
+ DownloadManagerForShell(shell())->GetAllDownloads(&items);
+ ASSERT_EQ(1u, items.size());
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+ // Shutdown the download manager and make sure we get the right
+ // notifications in the right order.
+ StrictMock<MockDownloadItemObserver> item_observer;
+ items[0]->AddObserver(&item_observer);
+ MockDownloadManagerObserver manager_observer(
+ DownloadManagerForShell(shell()));
+ // Don't care about ModelChanged() events.
+ EXPECT_CALL(manager_observer, ModelChanged(_))
+ .WillRepeatedly(Return());
+ {
+ InSequence notifications;
+
+ EXPECT_CALL(manager_observer, MockManagerGoingDown(
+ DownloadManagerForShell(shell())))
+ .WillOnce(Return());
+ EXPECT_CALL(item_observer, OnDownloadUpdated(
+ AllOf(items[0],
+ Property(&DownloadItem::GetState, DownloadItem::CANCELLED))))
+ .WillOnce(Return());
+ EXPECT_CALL(item_observer, OnDownloadDestroyed(items[0]))
+ .WillOnce(Return());
+ }
+ DownloadManagerForShell(shell())->Shutdown();
+ items.clear();
+}
+
+// Try to shutdown just after we release the download file, by delaying
+// release.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownAtRelease) {
+ DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));
+
+ // Mark delegate for delayed open.
+ GetDownloadManagerDelegate(download_manager)->SetDelayedOpen(true);
+
+ // Setup new factory.
+ DownloadFileWithDelayFactory* file_factory =
+ new DownloadFileWithDelayFactory();
+ download_manager->SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory>(file_factory).Pass());
+
+ // Create a download
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Wait until the first (intermediate file) rename and execute the callback.
+ file_factory->WaitForSomeCallback();
+ std::vector<base::Closure> callbacks;
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Wait until the second (final) rename callback is posted.
+ file_factory->WaitForSomeCallback();
+ file_factory->GetAllRenameCallbacks(&callbacks);
+ ASSERT_EQ(1u, callbacks.size());
+
+ // Call it.
+ callbacks[0].Run();
+ callbacks.clear();
+
+ // Confirm download isn't complete yet.
+ std::vector<DownloadItem*> items;
+ DownloadManagerForShell(shell())->GetAllDownloads(&items);
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+ // Cancel the download; confirm cancel fails anyway.
+ ASSERT_EQ(1u, items.size());
+ items[0]->Cancel(true);
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+ MockDownloadItemObserver observer;
+ items[0]->AddObserver(&observer);
+ EXPECT_CALL(observer, OnDownloadDestroyed(items[0]));
+
+ // Shutdown the download manager. Mostly this is confirming a lack of
+ // crashes.
+ DownloadManagerForShell(shell())->Shutdown();
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownload) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
+
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+ ::testing::Mock::VerifyAndClearExpectations(&dm_observer);
+
+ // Confirm resumption while in progress doesn't do anything.
+ download->Resume();
+ ASSERT_EQ(GetSafeBufferChunk(), download->GetReceivedBytes());
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState());
+
+ // Tell the server to send the RST and confirm the interrupt happens.
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ // Resume, confirming received bytes on resumption is correct.
+ // Make sure no creation calls are included.
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(0);
+ int initial_size = 0;
+ DownloadUpdatedObserver initial_size_observer(
+ download, base::Bind(&InitialSizeFilter, &initial_size));
+ download->Resume();
+ initial_size_observer.WaitForEvent();
+ EXPECT_EQ(GetSafeBufferChunk(), initial_size);
+ ::testing::Mock::VerifyAndClearExpectations(&dm_observer);
+
+ // and wait for expected data.
+ WaitForData(download, GetSafeBufferChunk() * 2);
+
+ // Tell the server to send the RST and confirm the interrupt happens.
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 2, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ // Resume and wait for completion.
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset")));
+
+ // Confirm resumption while complete doesn't do anything.
+ download->Resume();
+ ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes());
+ ASSERT_EQ(DownloadItem::COMPLETE, download->GetState());
+ RunAllPendingInMessageLoop();
+ ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes());
+ ASSERT_EQ(DownloadItem::COMPLETE, download->GetState());
+}
+
+// Confirm restart fallback happens if a range request is bounced.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownloadNoRange) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ // Auto-restart if server doesn't handle ranges.
+ GURL url = test_server()->GetURL(
+ base::StringPrintf(
+ // First download hits an RST, rest don't, no ranges.
+ "rangereset?size=%d&rst_boundary=%d&"
+ "token=NoRange&rst_limit=1&bounce_range",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ // Start the download and wait for first data chunk.
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+
+ RecordingDownloadObserver recorder(download);
+
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset")));
+
+ static const RecordingDownloadObserver::RecordStruct expected_record[] = {
+ // Result of RST
+ {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
+ // Starting continuation
+ {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
+ // Notification of receiving whole file.
+ {DownloadItem::IN_PROGRESS, 0},
+ // Completion.
+ {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
+ };
+
+ recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
+}
+
+// Confirm restart fallback happens if a precondition is failed.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest,
+ ResumeInterruptedDownloadBadPrecondition) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf(
+ // First download hits an RST, rest don't, precondition fail.
+ "rangereset?size=%d&rst_boundary=%d&"
+ "token=NoRange&rst_limit=1&fail_precondition=2",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ // Start the download and wait for first data chunk.
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+
+ RecordingDownloadObserver recorder(download);
+
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset")));
+
+ static const RecordingDownloadObserver::RecordStruct expected_record[] = {
+ // Result of RST
+ {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
+ // Starting continuation
+ {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
+ // Server precondition fail.
+ {DownloadItem::INTERRUPTED, 0},
+ // Notification of successful restart.
+ {DownloadItem::IN_PROGRESS, 0},
+ // Completion.
+ {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
+ };
+
+ recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
+}
+
+// Confirm we don't try to resume if we don't have a verifier.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest,
+ ResumeInterruptedDownloadNoVerifiers) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf(
+ // First download hits an RST, rest don't, no verifiers.
+ "rangereset?size=%d&rst_boundary=%d&"
+ "token=NoRange&rst_limit=1&no_verifiers",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ // Start the download and wait for first data chunk.
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+
+ RecordingDownloadObserver recorder(download);
+
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, false, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath());
+
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset")));
+
+ static const RecordingDownloadObserver::RecordStruct expected_record[] = {
+ // Result of RST
+ {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
+ // Restart for lack of verifiers
+ {DownloadItem::IN_PROGRESS, 0},
+ // Completion.
+ {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
+ };
+
+ recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithDeletedFile) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf(
+ // First download hits an RST, rest don't
+ "rangereset?size=%d&rst_boundary=%d&"
+ "token=NoRange&rst_limit=1",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ // Start the download and wait for first data chunk.
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+
+ RecordingDownloadObserver recorder(download);
+
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ // Delete the intermediate file.
+ base::DeleteFile(download->GetFullPath(), false);
+
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset")));
+
+ static const RecordingDownloadObserver::RecordStruct expected_record[] = {
+ // Result of RST
+ {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
+ // Starting continuation
+ {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
+ // Error because file isn't there.
+ {DownloadItem::INTERRUPTED, 0},
+ // Restart.
+ {DownloadItem::IN_PROGRESS, 0},
+ // Completion.
+ {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
+ };
+
+ recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileInitError) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Setup the error injector.
+ scoped_refptr<TestFileErrorInjector> injector(
+ TestFileErrorInjector::Create(DownloadManagerForShell(shell())));
+
+ TestFileErrorInjector::FileErrorInfo err = {
+ url.spec(),
+ TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
+ 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
+ };
+ injector->AddError(err);
+ injector->InjectErrors();
+
+ // Start and watch for interrupt.
+ scoped_ptr<DownloadTestObserver> int_observer(
+ CreateInterruptedWaiter(shell(), 1));
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ int_observer->WaitForFinished();
+ ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
+ download->GetLastReason());
+ EXPECT_EQ(0, download->GetReceivedBytes());
+ EXPECT_TRUE(download->GetFullPath().empty());
+ EXPECT_TRUE(download->GetTargetFilePath().empty());
+
+ // We need to make sure that any cross-thread downloads communication has
+ // quiesced before clearing and injecting the new errors, as the
+ // InjectErrors() routine alters the currently in use download file
+ // factory, which is a file thread object.
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // Clear the old errors list.
+ injector->ClearErrors();
+ injector->InjectErrors();
+
+ // Resume and watch completion.
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+ EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest,
+ ResumeWithFileIntermediateRenameError) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Setup the error injector.
+ scoped_refptr<TestFileErrorInjector> injector(
+ TestFileErrorInjector::Create(DownloadManagerForShell(shell())));
+
+ TestFileErrorInjector::FileErrorInfo err = {
+ url.spec(),
+ TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY,
+ 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
+ };
+ injector->AddError(err);
+ injector->InjectErrors();
+
+ // Start and watch for interrupt.
+ scoped_ptr<DownloadTestObserver> int_observer(
+ CreateInterruptedWaiter(shell(), 1));
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ int_observer->WaitForFinished();
+ ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
+ download->GetLastReason());
+ EXPECT_TRUE(download->GetFullPath().empty());
+ // Target path will have been set after file name determination. GetFullPath()
+ // being empty is sufficient to signal that filename determination needs to be
+ // redone.
+ EXPECT_FALSE(download->GetTargetFilePath().empty());
+
+ // We need to make sure that any cross-thread downloads communication has
+ // quiesced before clearing and injecting the new errors, as the
+ // InjectErrors() routine alters the currently in use download file
+ // factory, which is a file thread object.
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // Clear the old errors list.
+ injector->ClearErrors();
+ injector->InjectErrors();
+
+ // Resume and watch completion.
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+ EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileFinalRenameError) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(file));
+
+ // Setup the error injector.
+ scoped_refptr<TestFileErrorInjector> injector(
+ TestFileErrorInjector::Create(DownloadManagerForShell(shell())));
+
+ DownloadManagerForShell(shell())->RemoveAllDownloads();
+ TestFileErrorInjector::FileErrorInfo err = {
+ url.spec(),
+ TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE,
+ 0,
+ DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
+ };
+ injector->AddError(err);
+ injector->InjectErrors();
+
+ // Start and watch for interrupt.
+ scoped_ptr<DownloadTestObserver> int_observer(
+ CreateInterruptedWaiter(shell(), 1));
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ int_observer->WaitForFinished();
+ ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
+ download->GetLastReason());
+ EXPECT_TRUE(download->GetFullPath().empty());
+ // Target path should still be intact.
+ EXPECT_FALSE(download->GetTargetFilePath().empty());
+
+ // We need to make sure that any cross-thread downloads communication has
+ // quiesced before clearing and injecting the new errors, as the
+ // InjectErrors() routine alters the currently in use download file
+ // factory, which is a file thread object.
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // Clear the old errors list.
+ injector->ClearErrors();
+ injector->InjectErrors();
+
+ // Resume and watch completion.
+ DownloadUpdatedObserver completion_observer(
+ download, base::Bind(DownloadCompleteFilter));
+ download->Resume();
+ completion_observer.WaitForEvent();
+ EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
+}
+
+// An interrupted download should remove the intermediate file when it is
+// cancelled.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelInterruptedDownload) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url1 = test_server()->GetURL(
+ base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ DownloadItem* download(StartDownloadAndReturnItem(url1));
+ WaitForData(download, GetSafeBufferChunk());
+
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ base::FilePath intermediate_path(download->GetFullPath());
+ ASSERT_FALSE(intermediate_path.empty());
+ EXPECT_TRUE(base::PathExists(intermediate_path));
+
+ download->Cancel(true /* user_cancel */);
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // The intermediate file should now be gone.
+ EXPECT_FALSE(base::PathExists(intermediate_path));
+ EXPECT_TRUE(download->GetFullPath().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveDownload) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ // An interrupted download should remove the intermediate file when it is
+ // removed.
+ {
+ GURL url1 = test_server()->GetURL(
+ base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ DownloadItem* download(StartDownloadAndReturnItem(url1));
+ WaitForData(download, GetSafeBufferChunk());
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ base::FilePath intermediate_path(download->GetFullPath());
+ ASSERT_FALSE(intermediate_path.empty());
+ EXPECT_TRUE(base::PathExists(intermediate_path));
+
+ download->Remove();
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // The intermediate file should now be gone.
+ EXPECT_FALSE(base::PathExists(intermediate_path));
+ }
+
+ // A completed download shouldn't delete the downloaded file when it is
+ // removed.
+ {
+ // Start the second download and wait until it's done.
+ base::FilePath file2(FILE_PATH_LITERAL("download-test.lib"));
+ GURL url2(URLRequestMockHTTPJob::GetMockUrl(file2));
+ scoped_ptr<DownloadTestObserver> completion_observer(
+ CreateWaiter(shell(), 1));
+ DownloadItem* download(StartDownloadAndReturnItem(url2));
+ completion_observer->WaitForFinished();
+
+ // The target path should exist.
+ base::FilePath target_path(download->GetTargetFilePath());
+ EXPECT_TRUE(base::PathExists(target_path));
+ download->Remove();
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+
+ // The file should still exist.
+ EXPECT_TRUE(base::PathExists(target_path));
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveResumingDownload) {
+ SetupEnsureNoPendingDownloads();
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
+
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+ ::testing::Mock::VerifyAndClearExpectations(&dm_observer);
+
+ // Tell the server to send the RST and confirm the interrupt happens.
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ base::FilePath intermediate_path(download->GetFullPath());
+ ASSERT_FALSE(intermediate_path.empty());
+ EXPECT_TRUE(base::PathExists(intermediate_path));
+
+ // Resume and remove download. We expect only a single OnDownloadCreated()
+ // call, and that's for the second download created below.
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
+ download->Resume();
+ download->Remove();
+
+ // The intermediate file should now be gone.
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(base::PathExists(intermediate_path));
+
+ // Start the second download and wait until it's done. The test server is
+ // single threaded. The response to this download request should follow the
+ // response to the previous resumption request.
+ GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x"));
+ DownloadAndWait(shell(), url2, DownloadItem::COMPLETE);
+
+ EXPECT_TRUE(EnsureNoPendingDownloads());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelResumingDownload) {
+ SetupEnsureNoPendingDownloads();
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ ASSERT_TRUE(test_server()->Start());
+
+ GURL url = test_server()->GetURL(
+ base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
+ GetSafeBufferChunk() * 3, GetSafeBufferChunk()));
+
+ MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
+
+ DownloadItem* download(StartDownloadAndReturnItem(url));
+ WaitForData(download, GetSafeBufferChunk());
+ ::testing::Mock::VerifyAndClearExpectations(&dm_observer);
+
+ // Tell the server to send the RST and confirm the interrupt happens.
+ ReleaseRSTAndConfirmInterruptForResume(download);
+ ConfirmFileStatusForResume(
+ download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
+ base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
+
+ base::FilePath intermediate_path(download->GetFullPath());
+ ASSERT_FALSE(intermediate_path.empty());
+ EXPECT_TRUE(base::PathExists(intermediate_path));
+
+ // Resume and cancel download. We expect only a single OnDownloadCreated()
+ // call, and that's for the second download created below.
+ EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
+ download->Resume();
+ download->Cancel(true);
+
+ // The intermediate file should now be gone.
+ RunAllPendingInMessageLoop(BrowserThread::FILE);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(base::PathExists(intermediate_path));
+ EXPECT_TRUE(download->GetFullPath().empty());
+
+ // Start the second download and wait until it's done. The test server is
+ // single threaded. The response to this download request should follow the
+ // response to the previous resumption request.
+ GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x"));
+ DownloadAndWait(shell(), url2, DownloadItem::COMPLETE);
+
+ EXPECT_TRUE(EnsureNoPendingDownloads());
+}
+
+// Check that the cookie policy is correctly updated when downloading a file
+// that redirects cross origin.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CookiePolicy) {
+ ASSERT_TRUE(test_server()->Start());
+ net::HostPortPair host_port = test_server()->host_port_pair();
+ DCHECK_EQ(host_port.host(), std::string("127.0.0.1"));
+
+ // Block third-party cookies.
+ ShellNetworkDelegate::SetAcceptAllCookies(false);
+
+ // |url| redirects to a different origin |download| which tries to set a
+ // cookie.
+ std::string download(base::StringPrintf(
+ "http://localhost:%d/set-cookie?A=B", host_port.port()));
+ GURL url(test_server()->GetURL("server-redirect?" + download));
+
+ // Download the file.
+ SetupEnsureNoPendingDownloads();
+ scoped_ptr<DownloadUrlParameters> dl_params(
+ DownloadUrlParameters::FromWebContents(shell()->web_contents(), url));
+ scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell(), 1));
+ DownloadManagerForShell(shell())->DownloadUrl(dl_params.Pass());
+ observer->WaitForFinished();
+
+ // Get the important info from other threads and check it.
+ EXPECT_TRUE(EnsureNoPendingDownloads());
+
+ std::vector<DownloadItem*> downloads;
+ DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
+ ASSERT_EQ(1u, downloads.size());
+ ASSERT_EQ(DownloadItem::COMPLETE, downloads[0]->GetState());
+
+ // Check that the cookies were correctly set.
+ EXPECT_EQ("A=B",
+ content::GetCookies(shell()->web_contents()->GetBrowserContext(),
+ GURL(download)));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_create_info.cc b/chromium/content/browser/download/download_create_info.cc
new file mode 100644
index 00000000000..8c47cabd038
--- /dev/null
+++ b/chromium/content/browser/download/download_create_info.cc
@@ -0,0 +1,57 @@
+// 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 "content/browser/download/download_create_info.h"
+
+#include <string>
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+
+namespace content {
+
+DownloadCreateInfo::DownloadCreateInfo(
+ const base::Time& start_time,
+ int64 total_bytes,
+ const net::BoundNetLog& bound_net_log,
+ bool has_user_gesture,
+ PageTransition transition_type)
+ : start_time(start_time),
+ total_bytes(total_bytes),
+ download_id(DownloadItem::kInvalidId),
+ has_user_gesture(has_user_gesture),
+ transition_type(transition_type),
+ save_info(new DownloadSaveInfo()),
+ request_bound_net_log(bound_net_log) {
+}
+
+DownloadCreateInfo::DownloadCreateInfo()
+ : total_bytes(0),
+ download_id(DownloadItem::kInvalidId),
+ has_user_gesture(false),
+ transition_type(PAGE_TRANSITION_LINK),
+ save_info(new DownloadSaveInfo()) {
+}
+
+DownloadCreateInfo::~DownloadCreateInfo() {
+}
+
+std::string DownloadCreateInfo::DebugString() const {
+ return base::StringPrintf("{"
+ " download_id = %u"
+ " url = \"%s\""
+ " request_handle = %s"
+ " total_bytes = %" PRId64
+ " }",
+ download_id,
+ url().spec().c_str(),
+ request_handle.DebugString().c_str(),
+ total_bytes);
+}
+
+const GURL& DownloadCreateInfo::url() const {
+ return url_chain.empty() ? GURL::EmptyGURL() : url_chain.back();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_create_info.h b/chromium/content/browser/download/download_create_info.h
new file mode 100644
index 00000000000..4326b2428b0
--- /dev/null
+++ b/chromium/content/browser/download/download_create_info.h
@@ -0,0 +1,100 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_CREATE_INFO_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_CREATE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/time/time.h"
+#include "content/browser/download/download_file.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_save_info.h"
+#include "content/public/common/page_transition_types.h"
+#include "net/base/net_log.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Used for informing the download manager of a new download, since we don't
+// want to pass |DownloadItem|s between threads.
+struct CONTENT_EXPORT DownloadCreateInfo {
+ DownloadCreateInfo(const base::Time& start_time,
+ int64 total_bytes,
+ const net::BoundNetLog& bound_net_log,
+ bool has_user_gesture,
+ PageTransition transition_type);
+ DownloadCreateInfo();
+ ~DownloadCreateInfo();
+
+ std::string DebugString() const;
+
+ // The URL from which we are downloading. This is the final URL after any
+ // redirection by the server for |url_chain|.
+ const GURL& url() const;
+
+ // The chain of redirects that leading up to and including the final URL.
+ std::vector<GURL> url_chain;
+
+ // The URL that referred us.
+ GURL referrer_url;
+
+ // The time when the download started.
+ base::Time start_time;
+
+ // The total download size.
+ int64 total_bytes;
+
+ // The ID of the download.
+ uint32 download_id;
+
+ // True if the download was initiated by user action.
+ bool has_user_gesture;
+
+ PageTransition transition_type;
+
+ // The content-disposition string from the response header.
+ std::string content_disposition;
+
+ // The mime type string from the response header (may be overridden).
+ std::string mime_type;
+
+ // The value of the content type header sent with the downloaded item. It
+ // may be different from |mime_type|, which may be set based on heuristics
+ // which may look at the file extension and first few bytes of the file.
+ std::string original_mime_type;
+
+ // For continuing a download, the modification time of the file.
+ // Storing as a string for exact match to server format on
+ // "If-Unmodified-Since" comparison.
+ std::string last_modified;
+
+ // For continuing a download, the ETAG of the file.
+ std::string etag;
+
+ // The download file save info.
+ scoped_ptr<DownloadSaveInfo> save_info;
+
+ // The remote IP address where the download was fetched from. Copied from
+ // UrlRequest::GetSocketAddress().
+ std::string remote_address;
+
+ // The handle to the URLRequest sourcing this download.
+ DownloadRequestHandle request_handle;
+
+ // The request's |BoundNetLog|, for "source_dependency" linking with the
+ // download item's.
+ const net::BoundNetLog request_bound_net_log;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadCreateInfo);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_CREATE_INFO_H_
diff --git a/chromium/content/browser/download/download_file.h b/chromium/content/browser/download/download_file.h
new file mode 100644
index 00000000000..568e7ee091c
--- /dev/null
+++ b/chromium/content/browser/download/download_file.h
@@ -0,0 +1,93 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+
+namespace content {
+
+class DownloadManager;
+
+// These objects live exclusively on the file thread and handle the writing
+// operations for one download. These objects live only for the duration that
+// the download is 'in progress': once the download has been completed or
+// cancelled, the DownloadFile is destroyed.
+class CONTENT_EXPORT DownloadFile {
+ public:
+ // Callback used with Initialize. On a successful initialize, |reason| will
+ // be DOWNLOAD_INTERRUPT_REASON_NONE; on a failed initialize, it will be
+ // set to the reason for the failure.
+ typedef base::Callback<void(DownloadInterruptReason reason)>
+ InitializeCallback;
+
+ // Callback used with Rename*(). On a successful rename |reason| will be
+ // DOWNLOAD_INTERRUPT_REASON_NONE and |path| the path the rename
+ // was done to. On a failed rename, |reason| will contain the
+ // error.
+ typedef base::Callback<void(DownloadInterruptReason reason,
+ const base::FilePath& path)>
+ RenameCompletionCallback;
+
+ virtual ~DownloadFile() {}
+
+ // Upon completion, |callback| will be called on the UI
+ // thread as per the comment above, passing DOWNLOAD_INTERRUPT_REASON_NONE
+ // on success, or a network download interrupt reason on failure.
+ virtual void Initialize(const InitializeCallback& callback) = 0;
+
+ // Rename the download file to |full_path|. If that file exists
+ // |full_path| will be uniquified by suffixing " (<number>)" to the
+ // file name before the extension.
+ virtual void RenameAndUniquify(const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) = 0;
+
+ // Rename the download file to |full_path| and annotate it with
+ // "Mark of the Web" information about its source. No uniquification
+ // will be performed.
+ virtual void RenameAndAnnotate(const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) = 0;
+
+ // Detach the file so it is not deleted on destruction.
+ virtual void Detach() = 0;
+
+ // Abort the download and automatically close the file.
+ virtual void Cancel() = 0;
+
+ virtual base::FilePath FullPath() const = 0;
+ virtual bool InProgress() const = 0;
+ virtual int64 CurrentSpeed() const = 0;
+
+ // Set |hash| with sha256 digest for the file.
+ // Returns true if digest is successfully calculated.
+ virtual bool GetHash(std::string* hash) = 0;
+
+ // Returns the current (intermediate) state of the hash as a byte string.
+ virtual std::string GetHashState() = 0;
+
+ // Set the application GUID to be used to identify the app to the
+ // system AV function when scanning downloaded files. Should be called
+ // before RenameAndAnnotate() to take effect.
+ virtual void SetClientGuid(const std::string& guid) = 0;
+
+ // For testing. Must be called on FILE thread.
+ // TODO(rdsmith): Replace use of EnsureNoPendingDownloads()
+ // on the DownloadManager with a test-specific DownloadFileFactory
+ // which keeps track of the number of DownloadFiles.
+ static int GetNumberOfDownloadFiles();
+
+ protected:
+ static int number_active_objects_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H_
diff --git a/chromium/content/browser/download/download_file_factory.cc b/chromium/content/browser/download/download_file_factory.cc
new file mode 100644
index 00000000000..a7fe7b834a9
--- /dev/null
+++ b/chromium/content/browser/download/download_file_factory.cc
@@ -0,0 +1,33 @@
+// 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 "content/browser/download/download_file_factory.h"
+
+#include "content/browser/download/download_file_impl.h"
+#include "content/public/browser/power_save_blocker.h"
+
+namespace content {
+
+DownloadFileFactory::~DownloadFileFactory() {}
+
+DownloadFile* DownloadFileFactory::CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_downloads_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer) {
+ scoped_ptr<PowerSaveBlocker> psb(
+ PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ "Download in progress"));
+ return new DownloadFileImpl(
+ save_info.Pass(), default_downloads_directory, url, referrer_url,
+ calculate_hash, stream.Pass(), bound_net_log,
+ psb.Pass(), observer);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_file_factory.h b/chromium/content/browser/download/download_file_factory.h
new file mode 100644
index 00000000000..2600e3f8455
--- /dev/null
+++ b/chromium/content/browser/download/download_file_factory.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace net {
+class BoundNetLog;
+}
+
+namespace content {
+
+class ByteStreamReader;
+class DownloadDestinationObserver;
+class DownloadFile;
+class DownloadManager;
+struct DownloadSaveInfo;
+
+class CONTENT_EXPORT DownloadFileFactory {
+ public:
+ virtual ~DownloadFileFactory();
+
+ virtual DownloadFile* CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_downloads_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
diff --git a/chromium/content/browser/download/download_file_impl.cc b/chromium/content/browser/download/download_file_impl.cc
new file mode 100644
index 00000000000..67bee81b67a
--- /dev/null
+++ b/chromium/content/browser/download/download_file_impl.cc
@@ -0,0 +1,321 @@
+// 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 "content/browser/download/download_file_impl.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_net_log_parameters.h"
+#include "content/browser/download/download_stats.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_destination_observer.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "net/base/io_buffer.h"
+
+namespace content {
+
+const int kUpdatePeriodMs = 500;
+const int kMaxTimeBlockingFileThreadMs = 1000;
+
+int DownloadFile::number_active_objects_ = 0;
+
+DownloadFileImpl::DownloadFileImpl(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ scoped_ptr<PowerSaveBlocker> power_save_blocker,
+ base::WeakPtr<DownloadDestinationObserver> observer)
+ : file_(save_info->file_path,
+ url,
+ referrer_url,
+ save_info->offset,
+ calculate_hash,
+ save_info->hash_state,
+ save_info->file_stream.Pass(),
+ bound_net_log),
+ default_download_directory_(default_download_directory),
+ stream_reader_(stream.Pass()),
+ bytes_seen_(0),
+ bound_net_log_(bound_net_log),
+ observer_(observer),
+ weak_factory_(this),
+ power_save_blocker_(power_save_blocker.Pass()) {
+}
+
+DownloadFileImpl::~DownloadFileImpl() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ --number_active_objects_;
+}
+
+void DownloadFileImpl::Initialize(const InitializeCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>());
+ DownloadInterruptReason result =
+ file_.Initialize(default_download_directory_);
+ if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(callback, result));
+ return;
+ }
+
+ stream_reader_->RegisterCallback(
+ base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr()));
+
+ download_start_ = base::TimeTicks::Now();
+
+ // Primarily to make reset to zero in restart visible to owner.
+ SendUpdate();
+
+ // Initial pull from the straw.
+ StreamActive();
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(
+ callback, DOWNLOAD_INTERRUPT_REASON_NONE));
+
+ ++number_active_objects_;
+}
+
+DownloadInterruptReason DownloadFileImpl::AppendDataToFile(
+ const char* data, size_t data_len) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (!update_timer_->IsRunning()) {
+ update_timer_->Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kUpdatePeriodMs),
+ this, &DownloadFileImpl::SendUpdate);
+ }
+ rate_estimator_.Increment(data_len);
+ return file_.AppendDataToFile(data, data_len);
+}
+
+void DownloadFileImpl::RenameAndUniquify(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ base::FilePath new_path(full_path);
+
+ int uniquifier = file_util::GetUniquePathNumber(
+ new_path, base::FilePath::StringType());
+ if (uniquifier > 0) {
+ new_path = new_path.InsertBeforeExtensionASCII(
+ base::StringPrintf(" (%d)", uniquifier));
+ }
+
+ DownloadInterruptReason reason = file_.Rename(new_path);
+ if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
+ // Make sure our information is updated, since we're about to
+ // error out.
+ SendUpdate();
+
+ // Null out callback so that we don't do any more stream processing.
+ stream_reader_->RegisterCallback(base::Closure());
+
+ new_path.clear();
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, reason, new_path));
+}
+
+void DownloadFileImpl::RenameAndAnnotate(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ base::FilePath new_path(full_path);
+
+ DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
+ // Short circuit null rename.
+ if (full_path != file_.full_path())
+ reason = file_.Rename(new_path);
+
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ // Doing the annotation after the rename rather than before leaves
+ // a very small window during which the file has the final name but
+ // hasn't been marked with the Mark Of The Web. However, it allows
+ // anti-virus scanners on Windows to actually see the data
+ // (http://crbug.com/127999) under the correct name (which is information
+ // it uses).
+ reason = file_.AnnotateWithSourceInformation();
+ }
+
+ if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
+ // Make sure our information is updated, since we're about to
+ // error out.
+ SendUpdate();
+
+ // Null out callback so that we don't do any more stream processing.
+ stream_reader_->RegisterCallback(base::Closure());
+
+ new_path.clear();
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, reason, new_path));
+}
+
+void DownloadFileImpl::Detach() {
+ file_.Detach();
+}
+
+void DownloadFileImpl::Cancel() {
+ file_.Cancel();
+}
+
+base::FilePath DownloadFileImpl::FullPath() const {
+ return file_.full_path();
+}
+
+bool DownloadFileImpl::InProgress() const {
+ return file_.in_progress();
+}
+
+int64 DownloadFileImpl::CurrentSpeed() const {
+ return rate_estimator_.GetCountPerSecond();
+}
+
+bool DownloadFileImpl::GetHash(std::string* hash) {
+ return file_.GetHash(hash);
+}
+
+std::string DownloadFileImpl::GetHashState() {
+ return file_.GetHashState();
+}
+
+void DownloadFileImpl::SetClientGuid(const std::string& guid) {
+ file_.SetClientGuid(guid);
+}
+
+void DownloadFileImpl::StreamActive() {
+ base::TimeTicks start(base::TimeTicks::Now());
+ base::TimeTicks now;
+ scoped_refptr<net::IOBuffer> incoming_data;
+ size_t incoming_data_size = 0;
+ size_t total_incoming_data_size = 0;
+ size_t num_buffers = 0;
+ ByteStreamReader::StreamState state(ByteStreamReader::STREAM_EMPTY);
+ DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
+ base::TimeDelta delta(
+ base::TimeDelta::FromMilliseconds(kMaxTimeBlockingFileThreadMs));
+
+ // Take care of any file local activity required.
+ do {
+ state = stream_reader_->Read(&incoming_data, &incoming_data_size);
+
+ switch (state) {
+ case ByteStreamReader::STREAM_EMPTY:
+ break;
+ case ByteStreamReader::STREAM_HAS_DATA:
+ {
+ ++num_buffers;
+ base::TimeTicks write_start(base::TimeTicks::Now());
+ reason = AppendDataToFile(
+ incoming_data.get()->data(), incoming_data_size);
+ disk_writes_time_ += (base::TimeTicks::Now() - write_start);
+ bytes_seen_ += incoming_data_size;
+ total_incoming_data_size += incoming_data_size;
+ }
+ break;
+ case ByteStreamReader::STREAM_COMPLETE:
+ {
+ reason = static_cast<DownloadInterruptReason>(
+ stream_reader_->GetStatus());
+ SendUpdate();
+ base::TimeTicks close_start(base::TimeTicks::Now());
+ file_.Finish();
+ base::TimeTicks now(base::TimeTicks::Now());
+ disk_writes_time_ += (now - close_start);
+ RecordFileBandwidth(
+ bytes_seen_, disk_writes_time_, now - download_start_);
+ update_timer_.reset();
+ }
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ now = base::TimeTicks::Now();
+ } while (state == ByteStreamReader::STREAM_HAS_DATA &&
+ reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
+ now - start <= delta);
+
+ // If we're stopping to yield the thread, post a task so we come back.
+ if (state == ByteStreamReader::STREAM_HAS_DATA &&
+ now - start > delta) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DownloadFileImpl::StreamActive,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ if (total_incoming_data_size)
+ RecordFileThreadReceiveBuffers(num_buffers);
+
+ RecordContiguousWriteTime(now - start);
+
+ // Take care of communication with our observer.
+ if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
+ // Error case for both upstream source and file write.
+ // Shut down processing and signal an error to our observer.
+ // Our observer will clean us up.
+ stream_reader_->RegisterCallback(base::Closure());
+ weak_factory_.InvalidateWeakPtrs();
+ SendUpdate(); // Make info up to date before error.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DownloadDestinationObserver::DestinationError,
+ observer_, reason));
+ } else if (state == ByteStreamReader::STREAM_COMPLETE) {
+ // Signal successful completion and shut down processing.
+ stream_reader_->RegisterCallback(base::Closure());
+ weak_factory_.InvalidateWeakPtrs();
+ std::string hash;
+ if (!GetHash(&hash) || file_.IsEmptyHash(hash))
+ hash.clear();
+ SendUpdate();
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &DownloadDestinationObserver::DestinationCompleted,
+ observer_, hash));
+ }
+ if (bound_net_log_.IsLoggingAllEvents()) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_STREAM_DRAINED,
+ base::Bind(&FileStreamDrainedNetLogCallback, total_incoming_data_size,
+ num_buffers));
+ }
+}
+
+void DownloadFileImpl::SendUpdate() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DownloadDestinationObserver::DestinationUpdate,
+ observer_, file_.bytes_so_far(), CurrentSpeed(),
+ GetHashState()));
+}
+
+// static
+int DownloadFile::GetNumberOfDownloadFiles() {
+ return number_active_objects_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_file_impl.h b/chromium/content/browser/download/download_file_impl.h
new file mode 100644
index 00000000000..9a50870fb2a
--- /dev/null
+++ b/chromium/content/browser/download/download_file_impl.h
@@ -0,0 +1,116 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_IMPL_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_IMPL_H_
+
+#include "content/browser/download/download_file.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/base_file.h"
+#include "content/browser/download/rate_estimator.h"
+#include "content/public/browser/download_save_info.h"
+#include "net/base/net_log.h"
+
+namespace content {
+class ByteStreamReader;
+class DownloadDestinationObserver;
+class DownloadManager;
+class PowerSaveBlocker;
+struct DownloadCreateInfo;
+
+class CONTENT_EXPORT DownloadFileImpl : virtual public DownloadFile {
+ public:
+ // Takes ownership of the object pointed to by |request_handle|.
+ // |bound_net_log| will be used for logging the download file's events.
+ // May be constructed on any thread. All methods besides the constructor
+ // (including destruction) must occur on the FILE thread.
+ //
+ // Note that the DownloadFileImpl automatically reads from the passed in
+ // stream, and sends updates and status of those reads to the
+ // DownloadDestinationObserver.
+ DownloadFileImpl(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_downloads_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ scoped_ptr<PowerSaveBlocker> power_save_blocker,
+ base::WeakPtr<DownloadDestinationObserver> observer);
+
+ virtual ~DownloadFileImpl();
+
+ // DownloadFile functions.
+ virtual void Initialize(const InitializeCallback& callback) OVERRIDE;
+ virtual void RenameAndUniquify(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) OVERRIDE;
+ virtual void RenameAndAnnotate(
+ const base::FilePath& full_path,
+ const RenameCompletionCallback& callback) OVERRIDE;
+ virtual void Detach() OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual base::FilePath FullPath() const OVERRIDE;
+ virtual bool InProgress() const OVERRIDE;
+ virtual int64 CurrentSpeed() const OVERRIDE;
+ virtual bool GetHash(std::string* hash) OVERRIDE;
+ virtual std::string GetHashState() OVERRIDE;
+ virtual void SetClientGuid(const std::string& guid) OVERRIDE;
+
+ protected:
+ // For test class overrides.
+ virtual DownloadInterruptReason AppendDataToFile(
+ const char* data, size_t data_len);
+
+ private:
+ // Send an update on our progress.
+ void SendUpdate();
+
+ // Called when there's some activity on stream_reader_ that needs to be
+ // handled.
+ void StreamActive();
+
+ // The base file instance.
+ BaseFile file_;
+
+ // The default directory for creating the download file.
+ base::FilePath default_download_directory_;
+
+ // The stream through which data comes.
+ // TODO(rdsmith): Move this into BaseFile; requires using the same
+ // stream semantics in SavePackage. Alternatively, replace SaveFile
+ // with DownloadFile and get rid of BaseFile.
+ scoped_ptr<ByteStreamReader> stream_reader_;
+
+ // Used to trigger progress updates.
+ scoped_ptr<base::RepeatingTimer<DownloadFileImpl> > update_timer_;
+
+ // Statistics
+ size_t bytes_seen_;
+ base::TimeDelta disk_writes_time_;
+ base::TimeTicks download_start_;
+ RateEstimator rate_estimator_;
+
+ net::BoundNetLog bound_net_log_;
+
+ base::WeakPtr<DownloadDestinationObserver> observer_;
+
+ base::WeakPtrFactory<DownloadFileImpl> weak_factory_;
+
+ // RAII handle to keep the system from sleeping while we're downloading.
+ scoped_ptr<PowerSaveBlocker> power_save_blocker_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadFileImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_IMPL_H_
diff --git a/chromium/content/browser/download/download_file_unittest.cc b/chromium/content/browser/download/download_file_unittest.cc
new file mode 100644
index 00000000000..49e418c85bc
--- /dev/null
+++ b/chromium/content/browser/download/download_file_unittest.cc
@@ -0,0 +1,606 @@
+// 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 "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/test_file_util.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_file_impl.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/public/browser/download_destination_observer.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "content/public/test/mock_download_manager.h"
+#include "net/base/file_stream.h"
+#include "net/base/mock_file_stream.h"
+#include "net/base/net_errors.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrictMock;
+
+namespace content {
+namespace {
+
+class MockByteStreamReader : public ByteStreamReader {
+ public:
+ MockByteStreamReader() {}
+ ~MockByteStreamReader() {}
+
+ // ByteStream functions
+ MOCK_METHOD2(Read, ByteStreamReader::StreamState(
+ scoped_refptr<net::IOBuffer>*, size_t*));
+ MOCK_CONST_METHOD0(GetStatus, int());
+ MOCK_METHOD1(RegisterCallback, void(const base::Closure&));
+};
+
+class MockDownloadDestinationObserver : public DownloadDestinationObserver {
+ public:
+ MOCK_METHOD3(DestinationUpdate, void(int64, int64, const std::string&));
+ MOCK_METHOD1(DestinationError, void(DownloadInterruptReason));
+ MOCK_METHOD1(DestinationCompleted, void(const std::string&));
+
+ // Doesn't override any methods in the base class. Used to make sure
+ // that the last DestinationUpdate before a Destination{Completed,Error}
+ // had the right values.
+ MOCK_METHOD3(CurrentUpdateStatus,
+ void(int64, int64, const std::string&));
+};
+
+MATCHER(IsNullCallback, "") { return (arg.is_null()); }
+
+} // namespace
+
+class DownloadFileTest : public testing::Test {
+ public:
+
+ static const char* kTestData1;
+ static const char* kTestData2;
+ static const char* kTestData3;
+ static const char* kDataHash;
+ static const uint32 kDummyDownloadId;
+ static const int kDummyChildId;
+ static const int kDummyRequestId;
+
+ DownloadFileTest() :
+ observer_(new StrictMock<MockDownloadDestinationObserver>),
+ observer_factory_(observer_.get()),
+ input_stream_(NULL),
+ bytes_(-1),
+ bytes_per_sec_(-1),
+ hash_state_("xyzzy"),
+ ui_thread_(BrowserThread::UI, &loop_),
+ file_thread_(BrowserThread::FILE, &loop_) {
+ }
+
+ virtual ~DownloadFileTest() {
+ }
+
+ void SetUpdateDownloadInfo(int64 bytes, int64 bytes_per_sec,
+ const std::string& hash_state) {
+ bytes_ = bytes;
+ bytes_per_sec_ = bytes_per_sec;
+ hash_state_ = hash_state;
+ }
+
+ void ConfirmUpdateDownloadInfo() {
+ observer_->CurrentUpdateStatus(bytes_, bytes_per_sec_, hash_state_);
+ }
+
+ virtual void SetUp() {
+ EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo));
+ }
+
+ // Mock calls to this function are forwarded here.
+ void RegisterCallback(base::Closure sink_callback) {
+ sink_callback_ = sink_callback;
+ }
+
+ void SetInterruptReasonCallback(bool* was_called,
+ DownloadInterruptReason* reason_p,
+ DownloadInterruptReason reason) {
+ *was_called = true;
+ *reason_p = reason;
+ }
+
+ virtual bool CreateDownloadFile(int offset, bool calculate_hash) {
+ // There can be only one.
+ DCHECK(!download_file_.get());
+
+ input_stream_ = new StrictMock<MockByteStreamReader>();
+
+ // TODO: Need to actually create a function that'll set the variables
+ // based on the inputs from the callback.
+ EXPECT_CALL(*input_stream_, RegisterCallback(_))
+ .WillOnce(Invoke(this, &DownloadFileTest::RegisterCallback))
+ .RetiresOnSaturation();
+
+ scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo());
+ download_file_.reset(
+ new DownloadFileImpl(save_info.Pass(),
+ base::FilePath(),
+ GURL(), // Source
+ GURL(), // Referrer
+ calculate_hash,
+ scoped_ptr<ByteStreamReader>(input_stream_),
+ net::BoundNetLog(),
+ scoped_ptr<PowerSaveBlocker>().Pass(),
+ observer_factory_.GetWeakPtr()));
+ download_file_->SetClientGuid(
+ "12345678-ABCD-1234-DCBA-123456789ABC");
+
+ EXPECT_CALL(*input_stream_, Read(_, _))
+ .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+ .RetiresOnSaturation();
+
+ base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this);
+ bool called = false;
+ DownloadInterruptReason result;
+ download_file_->Initialize(base::Bind(
+ &DownloadFileTest::SetInterruptReasonCallback,
+ weak_ptr_factory.GetWeakPtr(), &called, &result));
+ loop_.RunUntilIdle();
+ EXPECT_TRUE(called);
+
+ ::testing::Mock::VerifyAndClearExpectations(input_stream_);
+ return result == DOWNLOAD_INTERRUPT_REASON_NONE;
+ }
+
+ virtual void DestroyDownloadFile(int offset) {
+ EXPECT_FALSE(download_file_->InProgress());
+
+ // Make sure the data has been properly written to disk.
+ std::string disk_data;
+ EXPECT_TRUE(file_util::ReadFileToString(download_file_->FullPath(),
+ &disk_data));
+ EXPECT_EQ(expected_data_, disk_data);
+
+ // Make sure the Browser and File threads outlive the DownloadFile
+ // to satisfy thread checks inside it.
+ download_file_.reset();
+ }
+
+ // Setup the stream to do be a data append; don't actually trigger
+ // the callback or do verifications.
+ void SetupDataAppend(const char **data_chunks, size_t num_chunks,
+ ::testing::Sequence s) {
+ DCHECK(input_stream_);
+ for (size_t i = 0; i < num_chunks; i++) {
+ const char *source_data = data_chunks[i];
+ size_t length = strlen(source_data);
+ scoped_refptr<net::IOBuffer> data = new net::IOBuffer(length);
+ memcpy(data->data(), source_data, length);
+ EXPECT_CALL(*input_stream_, Read(_, _))
+ .InSequence(s)
+ .WillOnce(DoAll(SetArgPointee<0>(data),
+ SetArgPointee<1>(length),
+ Return(ByteStreamReader::STREAM_HAS_DATA)))
+ .RetiresOnSaturation();
+ expected_data_ += source_data;
+ }
+ }
+
+ void VerifyStreamAndSize() {
+ ::testing::Mock::VerifyAndClearExpectations(input_stream_);
+ int64 size;
+ EXPECT_TRUE(file_util::GetFileSize(download_file_->FullPath(), &size));
+ EXPECT_EQ(expected_data_.size(), static_cast<size_t>(size));
+ }
+
+ // TODO(rdsmith): Manage full percentage issues properly.
+ void AppendDataToFile(const char **data_chunks, size_t num_chunks) {
+ ::testing::Sequence s1;
+ SetupDataAppend(data_chunks, num_chunks, s1);
+ EXPECT_CALL(*input_stream_, Read(_, _))
+ .InSequence(s1)
+ .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+ .RetiresOnSaturation();
+ sink_callback_.Run();
+ VerifyStreamAndSize();
+ }
+
+ void SetupFinishStream(DownloadInterruptReason interrupt_reason,
+ ::testing::Sequence s) {
+ EXPECT_CALL(*input_stream_, Read(_, _))
+ .InSequence(s)
+ .WillOnce(Return(ByteStreamReader::STREAM_COMPLETE))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*input_stream_, GetStatus())
+ .InSequence(s)
+ .WillOnce(Return(interrupt_reason))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*input_stream_, RegisterCallback(_))
+ .RetiresOnSaturation();
+ }
+
+ void FinishStream(DownloadInterruptReason interrupt_reason,
+ bool check_observer) {
+ ::testing::Sequence s1;
+ SetupFinishStream(interrupt_reason, s1);
+ sink_callback_.Run();
+ VerifyStreamAndSize();
+ if (check_observer) {
+ EXPECT_CALL(*(observer_.get()), DestinationCompleted(_));
+ loop_.RunUntilIdle();
+ ::testing::Mock::VerifyAndClearExpectations(observer_.get());
+ EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke(this,
+ &DownloadFileTest::SetUpdateDownloadInfo));
+ }
+ }
+
+ DownloadInterruptReason RenameAndUniquify(
+ const base::FilePath& full_path,
+ base::FilePath* result_path_p) {
+ base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this);
+ DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE);
+ bool callback_was_called(false);
+ base::FilePath result_path;
+
+ download_file_->RenameAndUniquify(
+ full_path, base::Bind(&DownloadFileTest::SetRenameResult,
+ weak_ptr_factory.GetWeakPtr(),
+ &callback_was_called,
+ &result_reason, result_path_p));
+ loop_.RunUntilIdle();
+
+ EXPECT_TRUE(callback_was_called);
+ return result_reason;
+ }
+
+ DownloadInterruptReason RenameAndAnnotate(
+ const base::FilePath& full_path,
+ base::FilePath* result_path_p) {
+ base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this);
+ DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE);
+ bool callback_was_called(false);
+ base::FilePath result_path;
+
+ download_file_->RenameAndAnnotate(
+ full_path, base::Bind(&DownloadFileTest::SetRenameResult,
+ weak_ptr_factory.GetWeakPtr(),
+ &callback_was_called,
+ &result_reason, result_path_p));
+ loop_.RunUntilIdle();
+
+ EXPECT_TRUE(callback_was_called);
+ return result_reason;
+ }
+
+ protected:
+ scoped_ptr<StrictMock<MockDownloadDestinationObserver> > observer_;
+ base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_;
+
+ // DownloadFile instance we are testing.
+ scoped_ptr<DownloadFile> download_file_;
+
+ // Stream for sending data into the download file.
+ // Owned by download_file_; will be alive for lifetime of download_file_.
+ StrictMock<MockByteStreamReader>* input_stream_;
+
+ // Sink callback data for stream.
+ base::Closure sink_callback_;
+
+ // Latest update sent to the observer.
+ int64 bytes_;
+ int64 bytes_per_sec_;
+ std::string hash_state_;
+
+ base::MessageLoop loop_;
+
+ private:
+ void SetRenameResult(bool* called_p,
+ DownloadInterruptReason* reason_p,
+ base::FilePath* result_path_p,
+ DownloadInterruptReason reason,
+ const base::FilePath& result_path) {
+ if (called_p)
+ *called_p = true;
+ if (reason_p)
+ *reason_p = reason;
+ if (result_path_p)
+ *result_path_p = result_path;
+ }
+
+ // UI thread.
+ BrowserThreadImpl ui_thread_;
+ // File thread to satisfy debug checks in DownloadFile.
+ BrowserThreadImpl file_thread_;
+
+ // Keep track of what data should be saved to the disk file.
+ std::string expected_data_;
+};
+
+const char* DownloadFileTest::kTestData1 =
+ "Let's write some data to the file!\n";
+const char* DownloadFileTest::kTestData2 = "Writing more data.\n";
+const char* DownloadFileTest::kTestData3 = "Final line.";
+const char* DownloadFileTest::kDataHash =
+ "CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8";
+
+const uint32 DownloadFileTest::kDummyDownloadId = 23;
+const int DownloadFileTest::kDummyChildId = 3;
+const int DownloadFileTest::kDummyRequestId = 67;
+
+// Rename the file before any data is downloaded, after some has, after it all
+// has, and after it's closed.
+TEST_F(DownloadFileTest, RenameFileFinal) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1"));
+ base::FilePath path_2(initial_path.InsertBeforeExtensionASCII("_2"));
+ base::FilePath path_3(initial_path.InsertBeforeExtensionASCII("_3"));
+ base::FilePath path_4(initial_path.InsertBeforeExtensionASCII("_4"));
+ base::FilePath path_5(initial_path.InsertBeforeExtensionASCII("_5"));
+ base::FilePath output_path;
+
+ // Rename the file before downloading any data.
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ RenameAndUniquify(path_1, &output_path));
+ base::FilePath renamed_path = download_file_->FullPath();
+ EXPECT_EQ(path_1, renamed_path);
+ EXPECT_EQ(path_1, output_path);
+
+ // Check the files.
+ EXPECT_FALSE(base::PathExists(initial_path));
+ EXPECT_TRUE(base::PathExists(path_1));
+
+ // Download the data.
+ const char* chunks1[] = { kTestData1, kTestData2 };
+ AppendDataToFile(chunks1, 2);
+
+ // Rename the file after downloading some data.
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ RenameAndUniquify(path_2, &output_path));
+ renamed_path = download_file_->FullPath();
+ EXPECT_EQ(path_2, renamed_path);
+ EXPECT_EQ(path_2, output_path);
+
+ // Check the files.
+ EXPECT_FALSE(base::PathExists(path_1));
+ EXPECT_TRUE(base::PathExists(path_2));
+
+ const char* chunks2[] = { kTestData3 };
+ AppendDataToFile(chunks2, 1);
+
+ // Rename the file after downloading all the data.
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ RenameAndUniquify(path_3, &output_path));
+ renamed_path = download_file_->FullPath();
+ EXPECT_EQ(path_3, renamed_path);
+ EXPECT_EQ(path_3, output_path);
+
+ // Check the files.
+ EXPECT_FALSE(base::PathExists(path_2));
+ EXPECT_TRUE(base::PathExists(path_3));
+
+ // Should not be able to get the hash until the file is closed.
+ std::string hash;
+ EXPECT_FALSE(download_file_->GetHash(&hash));
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true);
+ loop_.RunUntilIdle();
+
+ // Rename the file after downloading all the data and closing the file.
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ RenameAndUniquify(path_4, &output_path));
+ renamed_path = download_file_->FullPath();
+ EXPECT_EQ(path_4, renamed_path);
+ EXPECT_EQ(path_4, output_path);
+
+ // Check the files.
+ EXPECT_FALSE(base::PathExists(path_3));
+ EXPECT_TRUE(base::PathExists(path_4));
+
+ // Check the hash.
+ EXPECT_TRUE(download_file_->GetHash(&hash));
+ EXPECT_EQ(kDataHash, base::HexEncode(hash.data(), hash.size()));
+
+ // Check that a rename with overwrite to an existing file succeeds.
+ std::string file_contents;
+ ASSERT_FALSE(base::PathExists(path_5));
+ static const char file_data[] = "xyzzy";
+ ASSERT_EQ(static_cast<int>(sizeof(file_data) - 1),
+ file_util::WriteFile(path_5, file_data, sizeof(file_data) - 1));
+ ASSERT_TRUE(base::PathExists(path_5));
+ EXPECT_TRUE(file_util::ReadFileToString(path_5, &file_contents));
+ EXPECT_EQ(std::string(file_data), file_contents);
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
+ RenameAndAnnotate(path_5, &output_path));
+ EXPECT_EQ(path_5, output_path);
+
+ file_contents = "";
+ EXPECT_TRUE(file_util::ReadFileToString(path_5, &file_contents));
+ EXPECT_NE(std::string(file_data), file_contents);
+
+ DestroyDownloadFile(0);
+}
+
+// Test to make sure the rename uniquifies if we aren't overwriting
+// and there's a file where we're aiming.
+TEST_F(DownloadFileTest, RenameUniquifies) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1"));
+ base::FilePath path_1_suffixed(path_1.InsertBeforeExtensionASCII(" (1)"));
+
+ ASSERT_FALSE(base::PathExists(path_1));
+ static const char file_data[] = "xyzzy";
+ ASSERT_EQ(static_cast<int>(sizeof(file_data)),
+ file_util::WriteFile(path_1, file_data, sizeof(file_data)));
+ ASSERT_TRUE(base::PathExists(path_1));
+
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndUniquify(path_1, NULL));
+ EXPECT_TRUE(base::PathExists(path_1_suffixed));
+
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true);
+ loop_.RunUntilIdle();
+ DestroyDownloadFile(0);
+}
+
+// Test to make sure we get the proper error on failure.
+TEST_F(DownloadFileTest, RenameError) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+
+ // Create a subdirectory.
+ base::FilePath tempdir(
+ initial_path.DirName().Append(FILE_PATH_LITERAL("tempdir")));
+ ASSERT_TRUE(file_util::CreateDirectory(tempdir));
+ base::FilePath target_path(tempdir.Append(initial_path.BaseName()));
+
+ // Targets
+ base::FilePath target_path_suffixed(
+ target_path.InsertBeforeExtensionASCII(" (1)"));
+ ASSERT_FALSE(base::PathExists(target_path));
+ ASSERT_FALSE(base::PathExists(target_path_suffixed));
+
+ // Make the directory unwritable and try to rename within it.
+ {
+ file_util::PermissionRestorer restorer(tempdir);
+ ASSERT_TRUE(file_util::MakeFileUnwritable(tempdir));
+
+ // Expect nulling out of further processing.
+ EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback()));
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
+ RenameAndAnnotate(target_path, NULL));
+ EXPECT_FALSE(base::PathExists(target_path_suffixed));
+ }
+
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true);
+ loop_.RunUntilIdle();
+ DestroyDownloadFile(0);
+}
+
+// Various tests of the StreamActive method.
+TEST_F(DownloadFileTest, StreamEmptySuccess) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+
+ // Test that calling the sink_callback_ on an empty stream shouldn't
+ // do anything.
+ AppendDataToFile(NULL, 0);
+
+ // Finish the download this way and make sure we see it on the
+ // observer.
+ EXPECT_CALL(*(observer_.get()), DestinationCompleted(_));
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, false);
+ loop_.RunUntilIdle();
+
+ DestroyDownloadFile(0);
+}
+
+TEST_F(DownloadFileTest, StreamEmptyError) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+
+ // Finish the download in error and make sure we see it on the
+ // observer.
+ EXPECT_CALL(*(observer_.get()),
+ DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED))
+ .WillOnce(InvokeWithoutArgs(
+ this, &DownloadFileTest::ConfirmUpdateDownloadInfo));
+
+ // If this next EXPECT_CALL fails flakily, it's probably a real failure.
+ // We'll be getting a stream of UpdateDownload calls from the timer, and
+ // the last one may have the correct information even if the failure
+ // doesn't produce an update, as the timer update may have triggered at the
+ // same time.
+ EXPECT_CALL(*(observer_.get()), CurrentUpdateStatus(0, _, _));
+
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, false);
+
+ loop_.RunUntilIdle();
+
+ DestroyDownloadFile(0);
+}
+
+TEST_F(DownloadFileTest, StreamNonEmptySuccess) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+
+ const char* chunks1[] = { kTestData1, kTestData2 };
+ ::testing::Sequence s1;
+ SetupDataAppend(chunks1, 2, s1);
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, s1);
+ EXPECT_CALL(*(observer_.get()), DestinationCompleted(_));
+ sink_callback_.Run();
+ VerifyStreamAndSize();
+ loop_.RunUntilIdle();
+ DestroyDownloadFile(0);
+}
+
+TEST_F(DownloadFileTest, StreamNonEmptyError) {
+ ASSERT_TRUE(CreateDownloadFile(0, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+
+ const char* chunks1[] = { kTestData1, kTestData2 };
+ ::testing::Sequence s1;
+ SetupDataAppend(chunks1, 2, s1);
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, s1);
+
+ EXPECT_CALL(*(observer_.get()),
+ DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED))
+ .WillOnce(InvokeWithoutArgs(
+ this, &DownloadFileTest::ConfirmUpdateDownloadInfo));
+
+ // If this next EXPECT_CALL fails flakily, it's probably a real failure.
+ // We'll be getting a stream of UpdateDownload calls from the timer, and
+ // the last one may have the correct information even if the failure
+ // doesn't produce an update, as the timer update may have triggered at the
+ // same time.
+ EXPECT_CALL(*(observer_.get()),
+ CurrentUpdateStatus(strlen(kTestData1) + strlen(kTestData2),
+ _, _));
+
+ sink_callback_.Run();
+ loop_.RunUntilIdle();
+ VerifyStreamAndSize();
+ DestroyDownloadFile(0);
+}
+
+// Send some data, wait 3/4s of a second, run the message loop, and
+// confirm the values the observer received are correct.
+TEST_F(DownloadFileTest, ConfirmUpdate) {
+ CreateDownloadFile(0, true);
+
+ const char* chunks1[] = { kTestData1, kTestData2 };
+ AppendDataToFile(chunks1, 2);
+
+ // Run the message loops for 750ms and check for results.
+ loop_.PostDelayedTask(FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromMilliseconds(750));
+ loop_.Run();
+
+ EXPECT_EQ(static_cast<int64>(strlen(kTestData1) + strlen(kTestData2)),
+ bytes_);
+ EXPECT_EQ(download_file_->GetHashState(), hash_state_);
+
+ FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true);
+ DestroyDownloadFile(0);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_interrupt_reasons_impl.cc b/chromium/content/browser/download/download_interrupt_reasons_impl.cc
new file mode 100644
index 00000000000..beb56494e87
--- /dev/null
+++ b/chromium/content/browser/download/download_interrupt_reasons_impl.cc
@@ -0,0 +1,110 @@
+// 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 "content/browser/download/download_interrupt_reasons_impl.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+DownloadInterruptReason ConvertNetErrorToInterruptReason(
+ net::Error net_error, DownloadInterruptSource source) {
+ switch (net_error) {
+ case net::OK:
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+
+ // File errors.
+
+ // The file is too large.
+ case net::ERR_FILE_TOO_BIG:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
+
+ // Permission to access a resource, other than the network, was denied.
+ case net::ERR_ACCESS_DENIED:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+
+ // There were not enough resources to complete the operation.
+ case net::ERR_INSUFFICIENT_RESOURCES:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
+
+ // Memory allocation failed.
+ case net::ERR_OUT_OF_MEMORY:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
+
+ // The path or file name is too long.
+ case net::ERR_FILE_PATH_TOO_LONG:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
+
+ // Not enough room left on the disk.
+ case net::ERR_FILE_NO_SPACE:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE;
+
+ // The file has a virus.
+ case net::ERR_FILE_VIRUS_INFECTED:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
+
+ // The file was blocked by local policy.
+ case net::ERR_BLOCKED_BY_CLIENT:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
+
+ // Network errors.
+
+ // The network operation timed out.
+ case net::ERR_TIMED_OUT:
+ return DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT;
+
+ // The network connection has been lost.
+ case net::ERR_INTERNET_DISCONNECTED:
+ return DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED;
+
+ // The server has gone down.
+ case net::ERR_CONNECTION_FAILED:
+ return DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN;
+
+ // Server responses.
+
+ // The server does not support range requests.
+ case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
+ return DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE;
+
+ default: break;
+ }
+
+ // Handle errors that don't have mappings, depending on the source.
+ switch (source) {
+ case DOWNLOAD_INTERRUPT_FROM_DISK:
+ return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+ case DOWNLOAD_INTERRUPT_FROM_NETWORK:
+ return DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED;
+ case DOWNLOAD_INTERRUPT_FROM_SERVER:
+ return DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED;
+ default:
+ break;
+ }
+
+ NOTREACHED();
+
+ return DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+std::string InterruptReasonDebugString(DownloadInterruptReason error) {
+
+#define INTERRUPT_REASON(name, value) \
+ case DOWNLOAD_INTERRUPT_REASON_##name: return #name;
+
+ switch (error) {
+ INTERRUPT_REASON(NONE, 0)
+
+#include "content/public/browser/download_interrupt_reason_values.h"
+
+ default:
+ break;
+ }
+
+#undef INTERRUPT_REASON
+
+ return "Unknown error";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_interrupt_reasons_impl.h b/chromium/content/browser/download/download_interrupt_reasons_impl.h
new file mode 100644
index 00000000000..137dcb2abc5
--- /dev/null
+++ b/chromium/content/browser/download/download_interrupt_reasons_impl.h
@@ -0,0 +1,27 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_INTERRUPT_REASONS_IMPL_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_INTERRUPT_REASONS_IMPL_H_
+
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+enum DownloadInterruptSource {
+ DOWNLOAD_INTERRUPT_FROM_DISK,
+ DOWNLOAD_INTERRUPT_FROM_NETWORK,
+ DOWNLOAD_INTERRUPT_FROM_SERVER,
+ DOWNLOAD_INTERRUPT_FROM_USER,
+ DOWNLOAD_INTERRUPT_FROM_CRASH
+};
+
+// Safe to call from any thread.
+DownloadInterruptReason CONTENT_EXPORT ConvertNetErrorToInterruptReason(
+ net::Error file_error, DownloadInterruptSource source);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_INTERRUPT_REASONS_IMPL_H_
diff --git a/chromium/content/browser/download/download_item_factory.h b/chromium/content/browser/download/download_item_factory.h
new file mode 100644
index 00000000000..c5e03f5c41d
--- /dev/null
+++ b/chromium/content/browser/download/download_item_factory.h
@@ -0,0 +1,77 @@
+// 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.
+//
+// The DownloadItemFactory is used to produce different DownloadItems.
+// It is separate from the DownloadManager to allow download manager
+// unit tests to control the items produced.
+
+#ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_FACTORY_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_FACTORY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/download_item.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+class BoundNetLog;
+}
+
+namespace content {
+
+class DownloadItem;
+class DownloadItemImpl;
+class DownloadItemImplDelegate;
+class DownloadRequestHandleInterface;
+struct DownloadCreateInfo;
+
+class DownloadItemFactory {
+public:
+ virtual ~DownloadItemFactory() {}
+
+ virtual DownloadItemImpl* CreatePersistedItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log) = 0;
+
+ virtual DownloadItemImpl* CreateActiveItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log) = 0;
+
+ virtual DownloadItemImpl* CreateSavePageItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_FACTORY_H_
diff --git a/chromium/content/browser/download/download_item_impl.cc b/chromium/content/browser/download/download_item_impl.cc
new file mode 100644
index 00000000000..e2e86a34eb6
--- /dev/null
+++ b/chromium/content/browser/download/download_item_impl.cc
@@ -0,0 +1,1711 @@
+// 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.
+
+// File method ordering: Methods in this file are in the same order as
+// in download_item_impl.h, with the following exception: The public
+// interface Start is placed in chronological order with the other
+// (private) routines that together define a DownloadItem's state
+// transitions as the download progresses. See "Download progression
+// cascade" later in this file.
+
+// A regular DownloadItem (created for a download in this session of the
+// browser) normally goes through the following states:
+// * Created (when download starts)
+// * Destination filename determined
+// * Entered into the history database.
+// * Made visible in the download shelf.
+// * All the data is saved. Note that the actual data download occurs
+// in parallel with the above steps, but until those steps are
+// complete, the state of the data save will be ignored.
+// * Download file is renamed to its final name, and possibly
+// auto-opened.
+
+#include "content/browser/download/download_item_impl.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_file.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_item_impl_delegate.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_danger_type.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/referrer.h"
+#include "net/base/net_util.h"
+
+namespace content {
+
+namespace {
+
+void DeleteDownloadedFile(const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ // Make sure we only delete files.
+ if (!base::DirectoryExists(path))
+ base::DeleteFile(path, false);
+}
+
+// Wrapper around DownloadFile::Detach and DownloadFile::Cancel that
+// takes ownership of the DownloadFile and hence implicitly destroys it
+// at the end of the function.
+static base::FilePath DownloadFileDetach(
+ scoped_ptr<DownloadFile> download_file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::FilePath full_path = download_file->FullPath();
+ download_file->Detach();
+ return full_path;
+}
+
+static void DownloadFileCancel(scoped_ptr<DownloadFile> download_file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ download_file->Cancel();
+}
+
+bool IsDownloadResumptionEnabled() {
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableDownloadResumption);
+}
+
+} // namespace
+
+const uint32 DownloadItem::kInvalidId = 0;
+
+const char DownloadItem::kEmptyFileHash[] = "";
+
+// The maximum number of attempts we will make to resume automatically.
+const int DownloadItemImpl::kMaxAutoResumeAttempts = 5;
+
+// Constructor for reading from the history service.
+DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log)
+ : is_save_package_download_(false),
+ download_id_(download_id),
+ current_path_(current_path),
+ target_path_(target_path),
+ target_disposition_(TARGET_DISPOSITION_OVERWRITE),
+ url_chain_(url_chain),
+ referrer_url_(referrer_url),
+ transition_type_(PAGE_TRANSITION_LINK),
+ has_user_gesture_(false),
+ total_bytes_(total_bytes),
+ received_bytes_(received_bytes),
+ bytes_per_sec_(0),
+ last_modified_time_(last_modified),
+ etag_(etag),
+ last_reason_(interrupt_reason),
+ start_tick_(base::TimeTicks()),
+ state_(ExternalToInternalState(state)),
+ danger_type_(danger_type),
+ start_time_(start_time),
+ end_time_(end_time),
+ delegate_(delegate),
+ is_paused_(false),
+ auto_resume_count_(0),
+ open_when_complete_(false),
+ file_externally_removed_(false),
+ auto_opened_(false),
+ is_temporary_(false),
+ all_data_saved_(state == COMPLETE),
+ destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
+ opened_(opened),
+ delegate_delayed_complete_(false),
+ bound_net_log_(bound_net_log),
+ weak_ptr_factory_(this) {
+ delegate_->Attach();
+ DCHECK_NE(IN_PROGRESS_INTERNAL, state_);
+ Init(false /* not actively downloading */, SRC_HISTORY_IMPORT);
+}
+
+// Constructing for a regular download:
+DownloadItemImpl::DownloadItemImpl(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log)
+ : is_save_package_download_(false),
+ download_id_(download_id),
+ target_disposition_(
+ (info.save_info->prompt_for_save_location) ?
+ TARGET_DISPOSITION_PROMPT : TARGET_DISPOSITION_OVERWRITE),
+ url_chain_(info.url_chain),
+ referrer_url_(info.referrer_url),
+ suggested_filename_(UTF16ToUTF8(info.save_info->suggested_name)),
+ forced_file_path_(info.save_info->file_path),
+ transition_type_(info.transition_type),
+ has_user_gesture_(info.has_user_gesture),
+ content_disposition_(info.content_disposition),
+ mime_type_(info.mime_type),
+ original_mime_type_(info.original_mime_type),
+ remote_address_(info.remote_address),
+ total_bytes_(info.total_bytes),
+ received_bytes_(0),
+ bytes_per_sec_(0),
+ last_modified_time_(info.last_modified),
+ etag_(info.etag),
+ last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
+ start_tick_(base::TimeTicks::Now()),
+ state_(IN_PROGRESS_INTERNAL),
+ danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
+ start_time_(info.start_time),
+ delegate_(delegate),
+ is_paused_(false),
+ auto_resume_count_(0),
+ open_when_complete_(false),
+ file_externally_removed_(false),
+ auto_opened_(false),
+ is_temporary_(!info.save_info->file_path.empty()),
+ all_data_saved_(false),
+ destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
+ opened_(false),
+ delegate_delayed_complete_(false),
+ bound_net_log_(bound_net_log),
+ weak_ptr_factory_(this) {
+ delegate_->Attach();
+ Init(true /* actively downloading */, SRC_ACTIVE_DOWNLOAD);
+
+ // Link the event sources.
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_URL_REQUEST,
+ info.request_bound_net_log.source().ToEventParametersCallback());
+
+ info.request_bound_net_log.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_STARTED,
+ bound_net_log_.source().ToEventParametersCallback());
+}
+
+// Constructing for the "Save Page As..." feature:
+DownloadItemImpl::DownloadItemImpl(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log)
+ : is_save_package_download_(true),
+ request_handle_(request_handle.Pass()),
+ download_id_(download_id),
+ current_path_(path),
+ target_path_(path),
+ target_disposition_(TARGET_DISPOSITION_OVERWRITE),
+ url_chain_(1, url),
+ referrer_url_(GURL()),
+ transition_type_(PAGE_TRANSITION_LINK),
+ has_user_gesture_(false),
+ mime_type_(mime_type),
+ original_mime_type_(mime_type),
+ total_bytes_(0),
+ received_bytes_(0),
+ bytes_per_sec_(0),
+ last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
+ start_tick_(base::TimeTicks::Now()),
+ state_(IN_PROGRESS_INTERNAL),
+ danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
+ start_time_(base::Time::Now()),
+ delegate_(delegate),
+ is_paused_(false),
+ auto_resume_count_(0),
+ open_when_complete_(false),
+ file_externally_removed_(false),
+ auto_opened_(false),
+ is_temporary_(false),
+ all_data_saved_(false),
+ destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
+ opened_(false),
+ delegate_delayed_complete_(false),
+ bound_net_log_(bound_net_log),
+ weak_ptr_factory_(this) {
+ delegate_->Attach();
+ Init(true /* actively downloading */, SRC_SAVE_PAGE_AS);
+}
+
+DownloadItemImpl::~DownloadItemImpl() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Should always have been nuked before now, at worst in
+ // DownloadManager shutdown.
+ DCHECK(!download_file_.get());
+
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadDestroyed(this));
+ delegate_->AssertStateConsistent(this);
+ delegate_->Detach();
+}
+
+void DownloadItemImpl::AddObserver(Observer* observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ observers_.AddObserver(observer);
+}
+
+void DownloadItemImpl::RemoveObserver(Observer* observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ observers_.RemoveObserver(observer);
+}
+
+void DownloadItemImpl::UpdateObservers() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this));
+}
+
+void DownloadItemImpl::ValidateDangerousDownload() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!IsDone());
+ DCHECK(IsDangerous());
+
+ VLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
+
+ if (IsDone() || !IsDangerous())
+ return;
+
+ RecordDangerousDownloadAccept(GetDangerType());
+
+ danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
+
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
+ base::Bind(&ItemCheckedNetLogCallback, GetDangerType()));
+
+ UpdateObservers();
+
+ MaybeCompleteDownload();
+}
+
+void DownloadItemImpl::StealDangerousDownload(
+ const AcquireFileCallback& callback) {
+ VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(IsDangerous());
+ if (download_file_) {
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&DownloadFileDetach, base::Passed(&download_file_)),
+ callback);
+ } else {
+ callback.Run(current_path_);
+ }
+ current_path_.clear();
+ Remove();
+ // We have now been deleted.
+}
+
+void DownloadItemImpl::Pause() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Ignore irrelevant states.
+ if (state_ != IN_PROGRESS_INTERNAL || is_paused_)
+ return;
+
+ request_handle_->PauseRequest();
+ is_paused_ = true;
+ UpdateObservers();
+}
+
+void DownloadItemImpl::Resume() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ switch (state_) {
+ case IN_PROGRESS_INTERNAL:
+ if (!is_paused_)
+ return;
+ request_handle_->ResumeRequest();
+ is_paused_ = false;
+ UpdateObservers();
+ return;
+
+ case COMPLETING_INTERNAL:
+ case COMPLETE_INTERNAL:
+ case CANCELLED_INTERNAL:
+ case RESUMING_INTERNAL:
+ return;
+
+ case INTERRUPTED_INTERNAL:
+ auto_resume_count_ = 0; // User input resets the counter.
+ ResumeInterruptedDownload();
+ return;
+
+ case MAX_DOWNLOAD_INTERNAL_STATE:
+ NOTREACHED();
+ }
+}
+
+void DownloadItemImpl::Cancel(bool user_cancel) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
+ if (state_ != IN_PROGRESS_INTERNAL &&
+ state_ != INTERRUPTED_INTERNAL &&
+ state_ != RESUMING_INTERNAL) {
+ // Small downloads might be complete before this method has a chance to run.
+ return;
+ }
+
+ if (IsDangerous()) {
+ RecordDangerousDownloadDiscard(
+ user_cancel ? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION
+ : DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN,
+ GetDangerType());
+ }
+
+ last_reason_ = user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
+ : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN;
+
+ RecordDownloadCount(CANCELLED_COUNT);
+
+ // TODO(rdsmith/benjhayden): Remove condition as part of
+ // |SavePackage| integration.
+ // |download_file_| can be NULL if Interrupt() is called after the
+ // download file has been released.
+ if (!is_save_package_download_ && download_file_)
+ ReleaseDownloadFile(true);
+
+ if (state_ == IN_PROGRESS_INTERNAL) {
+ // Cancel the originating URL request unless it's already been cancelled
+ // by interrupt.
+ request_handle_->CancelRequest();
+ }
+
+ // Remove the intermediate file if we are cancelling an interrupted download.
+ // Continuable interruptions leave the intermediate file around.
+ if ((state_ == INTERRUPTED_INTERNAL || state_ == RESUMING_INTERNAL) &&
+ !current_path_.empty()) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DeleteDownloadedFile, current_path_));
+ current_path_.clear();
+ }
+
+ TransitionTo(CANCELLED_INTERNAL, UPDATE_OBSERVERS);
+}
+
+void DownloadItemImpl::Remove() {
+ VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ delegate_->AssertStateConsistent(this);
+ Cancel(true);
+ delegate_->AssertStateConsistent(this);
+
+ NotifyRemoved();
+ delegate_->DownloadRemoved(this);
+ // We have now been deleted.
+}
+
+void DownloadItemImpl::OpenDownload() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!IsDone()) {
+ // We don't honor the open_when_complete_ flag for temporary
+ // downloads. Don't set it because it shows up in the UI.
+ if (!IsTemporary())
+ open_when_complete_ = !open_when_complete_;
+ return;
+ }
+
+ if (state_ != COMPLETE_INTERNAL || file_externally_removed_)
+ return;
+
+ // Ideally, we want to detect errors in opening and report them, but we
+ // don't generally have the proper interface for that to the external
+ // program that opens the file. So instead we spawn a check to update
+ // the UI if the file has been deleted in parallel with the open.
+ delegate_->CheckForFileRemoval(this);
+ RecordOpen(GetEndTime(), !GetOpened());
+ opened_ = true;
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this));
+ delegate_->OpenDownload(this);
+}
+
+void DownloadItemImpl::ShowDownloadInShell() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ delegate_->ShowDownloadInShell(this);
+}
+
+uint32 DownloadItemImpl::GetId() const {
+ return download_id_;
+}
+
+DownloadItem::DownloadState DownloadItemImpl::GetState() const {
+ return InternalToExternalState(state_);
+}
+
+DownloadInterruptReason DownloadItemImpl::GetLastReason() const {
+ return last_reason_;
+}
+
+bool DownloadItemImpl::IsPaused() const {
+ return is_paused_;
+}
+
+bool DownloadItemImpl::IsTemporary() const {
+ return is_temporary_;
+}
+
+bool DownloadItemImpl::CanResume() const {
+ if ((GetState() == IN_PROGRESS) && IsPaused())
+ return true;
+
+ if (state_ != INTERRUPTED_INTERNAL)
+ return false;
+
+ // Downloads that don't have a WebContents should still be resumable, but this
+ // isn't currently the case. See ResumeInterruptedDownload().
+ if (!GetWebContents())
+ return false;
+
+ ResumeMode resume_mode = GetResumeMode();
+ return IsDownloadResumptionEnabled() &&
+ (resume_mode == RESUME_MODE_USER_RESTART ||
+ resume_mode == RESUME_MODE_USER_CONTINUE);
+}
+
+bool DownloadItemImpl::IsDone() const {
+ switch (state_) {
+ case IN_PROGRESS_INTERNAL:
+ case COMPLETING_INTERNAL:
+ return false;
+
+ case COMPLETE_INTERNAL:
+ case CANCELLED_INTERNAL:
+ return true;
+
+ case INTERRUPTED_INTERNAL:
+ return !CanResume();
+
+ case RESUMING_INTERNAL:
+ return false;
+
+ case MAX_DOWNLOAD_INTERNAL_STATE:
+ break;
+ }
+ NOTREACHED();
+ return true;
+}
+
+const GURL& DownloadItemImpl::GetURL() const {
+ return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.back();
+}
+
+const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const {
+ return url_chain_;
+}
+
+const GURL& DownloadItemImpl::GetOriginalUrl() const {
+ // Be careful about taking the front() of possibly-empty vectors!
+ // http://crbug.com/190096
+ return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.front();
+}
+
+const GURL& DownloadItemImpl::GetReferrerUrl() const {
+ return referrer_url_;
+}
+
+std::string DownloadItemImpl::GetSuggestedFilename() const {
+ return suggested_filename_;
+}
+
+std::string DownloadItemImpl::GetContentDisposition() const {
+ return content_disposition_;
+}
+
+std::string DownloadItemImpl::GetMimeType() const {
+ return mime_type_;
+}
+
+std::string DownloadItemImpl::GetOriginalMimeType() const {
+ return original_mime_type_;
+}
+
+std::string DownloadItemImpl::GetRemoteAddress() const {
+ return remote_address_;
+}
+
+bool DownloadItemImpl::HasUserGesture() const {
+ return has_user_gesture_;
+};
+
+PageTransition DownloadItemImpl::GetTransitionType() const {
+ return transition_type_;
+};
+
+const std::string& DownloadItemImpl::GetLastModifiedTime() const {
+ return last_modified_time_;
+}
+
+const std::string& DownloadItemImpl::GetETag() const {
+ return etag_;
+}
+
+bool DownloadItemImpl::IsSavePackageDownload() const {
+ return is_save_package_download_;
+}
+
+const base::FilePath& DownloadItemImpl::GetFullPath() const {
+ return current_path_;
+}
+
+const base::FilePath& DownloadItemImpl::GetTargetFilePath() const {
+ return target_path_;
+}
+
+const base::FilePath& DownloadItemImpl::GetForcedFilePath() const {
+ // TODO(asanka): Get rid of GetForcedFilePath(). We should instead just
+ // require that clients respect GetTargetFilePath() if it is already set.
+ return forced_file_path_;
+}
+
+base::FilePath DownloadItemImpl::GetFileNameToReportUser() const {
+ if (!display_name_.empty())
+ return display_name_;
+ return target_path_.BaseName();
+}
+
+DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const {
+ return target_disposition_;
+}
+
+const std::string& DownloadItemImpl::GetHash() const {
+ return hash_;
+}
+
+const std::string& DownloadItemImpl::GetHashState() const {
+ return hash_state_;
+}
+
+bool DownloadItemImpl::GetFileExternallyRemoved() const {
+ return file_externally_removed_;
+}
+
+void DownloadItemImpl::DeleteFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if ((GetState() != DownloadItem::COMPLETE) ||
+ file_externally_removed_) {
+ return;
+ }
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DeleteDownloadedFile, current_path_),
+ base::Bind(&DownloadItemImpl::OnDownloadedFileRemoved,
+ weak_ptr_factory_.GetWeakPtr()));
+ current_path_.clear();
+}
+
+bool DownloadItemImpl::IsDangerous() const {
+#if defined(OS_WIN)
+ // TODO(noelutz): At this point only the windows views UI supports
+ // warnings based on dangerous content.
+ return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED);
+#else
+ return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE ||
+ danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL);
+#endif
+}
+
+DownloadDangerType DownloadItemImpl::GetDangerType() const {
+ return danger_type_;
+}
+
+bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const {
+ if (total_bytes_ <= 0)
+ return false; // We never received the content_length for this download.
+
+ int64 speed = CurrentSpeed();
+ if (speed == 0)
+ return false;
+
+ *remaining = base::TimeDelta::FromSeconds(
+ (total_bytes_ - received_bytes_) / speed);
+ return true;
+}
+
+int64 DownloadItemImpl::CurrentSpeed() const {
+ if (is_paused_)
+ return 0;
+ return bytes_per_sec_;
+}
+
+int DownloadItemImpl::PercentComplete() const {
+ // If the delegate is delaying completion of the download, then we have no
+ // idea how long it will take.
+ if (delegate_delayed_complete_ || total_bytes_ <= 0)
+ return -1;
+
+ return static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
+}
+
+bool DownloadItemImpl::AllDataSaved() const {
+ return all_data_saved_;
+}
+
+int64 DownloadItemImpl::GetTotalBytes() const {
+ return total_bytes_;
+}
+
+int64 DownloadItemImpl::GetReceivedBytes() const {
+ return received_bytes_;
+}
+
+base::Time DownloadItemImpl::GetStartTime() const {
+ return start_time_;
+}
+
+base::Time DownloadItemImpl::GetEndTime() const {
+ return end_time_;
+}
+
+bool DownloadItemImpl::CanShowInFolder() {
+ // A download can be shown in the folder if the downloaded file is in a known
+ // location.
+ return CanOpenDownload() && !GetFullPath().empty();
+}
+
+bool DownloadItemImpl::CanOpenDownload() {
+ // We can open the file or mark it for opening on completion if the download
+ // is expected to complete successfully. Exclude temporary downloads, since
+ // they aren't owned by the download system.
+ const bool is_complete = GetState() == DownloadItem::COMPLETE;
+ return (!IsDone() || is_complete) && !IsTemporary() &&
+ !file_externally_removed_;
+}
+
+bool DownloadItemImpl::ShouldOpenFileBasedOnExtension() {
+ return delegate_->ShouldOpenFileBasedOnExtension(GetTargetFilePath());
+}
+
+bool DownloadItemImpl::GetOpenWhenComplete() const {
+ return open_when_complete_;
+}
+
+bool DownloadItemImpl::GetAutoOpened() {
+ return auto_opened_;
+}
+
+bool DownloadItemImpl::GetOpened() const {
+ return opened_;
+}
+
+BrowserContext* DownloadItemImpl::GetBrowserContext() const {
+ return delegate_->GetBrowserContext();
+}
+
+WebContents* DownloadItemImpl::GetWebContents() const {
+ // TODO(rdsmith): Remove null check after removing GetWebContents() from
+ // paths that might be used by DownloadItems created from history import.
+ // Currently such items have null request_handle_s, where other items
+ // (regular and SavePackage downloads) have actual objects off the pointer.
+ if (request_handle_)
+ return request_handle_->GetWebContents();
+ return NULL;
+}
+
+void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(AllDataSaved());
+ VLOG(20) << __FUNCTION__ << " danger_type=" << danger_type
+ << " download=" << DebugString(true);
+ SetDangerType(danger_type);
+ UpdateObservers();
+}
+
+void DownloadItemImpl::SetOpenWhenComplete(bool open) {
+ open_when_complete_ = open;
+}
+
+void DownloadItemImpl::SetIsTemporary(bool temporary) {
+ is_temporary_ = temporary;
+}
+
+void DownloadItemImpl::SetOpened(bool opened) {
+ opened_ = opened;
+}
+
+void DownloadItemImpl::SetDisplayName(const base::FilePath& name) {
+ display_name_ = name;
+}
+
+std::string DownloadItemImpl::DebugString(bool verbose) const {
+ std::string description =
+ base::StringPrintf("{ id = %d"
+ " state = %s",
+ download_id_,
+ DebugDownloadStateString(state_));
+
+ // Construct a string of the URL chain.
+ std::string url_list("<none>");
+ if (!url_chain_.empty()) {
+ std::vector<GURL>::const_iterator iter = url_chain_.begin();
+ std::vector<GURL>::const_iterator last = url_chain_.end();
+ url_list = (*iter).is_valid() ? (*iter).spec() : "<invalid>";
+ ++iter;
+ for ( ; verbose && (iter != last); ++iter) {
+ url_list += " ->\n\t";
+ const GURL& next_url = *iter;
+ url_list += next_url.is_valid() ? next_url.spec() : "<invalid>";
+ }
+ }
+
+ if (verbose) {
+ description += base::StringPrintf(
+ " total = %" PRId64
+ " received = %" PRId64
+ " reason = %s"
+ " paused = %c"
+ " resume_mode = %s"
+ " auto_resume_count = %d"
+ " danger = %d"
+ " all_data_saved = %c"
+ " last_modified = '%s'"
+ " etag = '%s'"
+ " has_download_file = %s"
+ " url_chain = \n\t\"%s\"\n\t"
+ " full_path = \"%" PRFilePath "\"\n\t"
+ " target_path = \"%" PRFilePath "\"",
+ GetTotalBytes(),
+ GetReceivedBytes(),
+ InterruptReasonDebugString(last_reason_).c_str(),
+ IsPaused() ? 'T' : 'F',
+ DebugResumeModeString(GetResumeMode()),
+ auto_resume_count_,
+ GetDangerType(),
+ AllDataSaved() ? 'T' : 'F',
+ GetLastModifiedTime().c_str(),
+ GetETag().c_str(),
+ download_file_.get() ? "true" : "false",
+ url_list.c_str(),
+ GetFullPath().value().c_str(),
+ GetTargetFilePath().value().c_str());
+ } else {
+ description += base::StringPrintf(" url = \"%s\"", url_list.c_str());
+ }
+
+ description += " }";
+
+ return description;
+}
+
+DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // We can't continue without a handle on the intermediate file.
+ // We also can't continue if we don't have some verifier to make sure
+ // we're getting the same file.
+ const bool force_restart =
+ (current_path_.empty() || (etag_.empty() && last_modified_time_.empty()));
+
+ // We won't auto-restart if we've used up our attempts or the
+ // download has been paused by user action.
+ const bool force_user =
+ (auto_resume_count_ >= kMaxAutoResumeAttempts || is_paused_);
+
+ ResumeMode mode = RESUME_MODE_INVALID;
+
+ switch(last_reason_) {
+ case DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
+ case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
+ if (force_restart && force_user)
+ mode = RESUME_MODE_USER_RESTART;
+ else if (force_restart)
+ mode = RESUME_MODE_IMMEDIATE_RESTART;
+ else if (force_user)
+ mode = RESUME_MODE_USER_CONTINUE;
+ else
+ mode = RESUME_MODE_IMMEDIATE_CONTINUE;
+ break;
+
+ case DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION:
+ case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
+ if (force_user)
+ mode = RESUME_MODE_USER_RESTART;
+ else
+ mode = RESUME_MODE_IMMEDIATE_RESTART;
+ break;
+
+ case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
+ case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
+ case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
+ case DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
+ case DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
+ case DOWNLOAD_INTERRUPT_REASON_CRASH:
+ if (force_restart)
+ mode = RESUME_MODE_USER_RESTART;
+ else
+ mode = RESUME_MODE_USER_CONTINUE;
+ break;
+
+ case DOWNLOAD_INTERRUPT_REASON_FILE_FAILED:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
+ mode = RESUME_MODE_USER_RESTART;
+ break;
+
+ case DOWNLOAD_INTERRUPT_REASON_NONE:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
+ case DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
+ case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
+ case DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
+ mode = RESUME_MODE_INVALID;
+ break;
+ }
+
+ return mode;
+}
+
+void DownloadItemImpl::NotifyRemoved() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadRemoved(this));
+}
+
+void DownloadItemImpl::OnDownloadedFileRemoved() {
+ file_externally_removed_ = true;
+ VLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
+ UpdateObservers();
+}
+
+base::WeakPtr<DownloadDestinationObserver>
+DownloadItemImpl::DestinationObserverAsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+const net::BoundNetLog& DownloadItemImpl::GetBoundNetLog() const {
+ return bound_net_log_;
+}
+
+void DownloadItemImpl::SetTotalBytes(int64 total_bytes) {
+ total_bytes_ = total_bytes;
+}
+
+void DownloadItemImpl::OnAllDataSaved(const std::string& final_hash) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ DCHECK_EQ(IN_PROGRESS_INTERNAL, state_);
+ DCHECK(!all_data_saved_);
+ all_data_saved_ = true;
+ VLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
+
+ // Store final hash and null out intermediate serialized hash state.
+ hash_ = final_hash;
+ hash_state_ = "";
+
+ UpdateObservers();
+}
+
+void DownloadItemImpl::MarkAsComplete() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ DCHECK(all_data_saved_);
+ end_time_ = base::Time::Now();
+ TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS);
+}
+
+void DownloadItemImpl::DestinationUpdate(int64 bytes_so_far,
+ int64 bytes_per_sec,
+ const std::string& hash_state) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(20) << __FUNCTION__ << " so_far=" << bytes_so_far
+ << " per_sec=" << bytes_per_sec << " download=" << DebugString(true);
+
+ if (GetState() != IN_PROGRESS) {
+ // Ignore if we're no longer in-progress. This can happen if we race a
+ // Cancel on the UI thread with an update on the FILE thread.
+ //
+ // TODO(rdsmith): Arguably we should let this go through, as this means
+ // the download really did get further than we know before it was
+ // cancelled. But the gain isn't very large, and the code is more
+ // fragile if it has to support in progress updates in a non-in-progress
+ // state. This issue should be readdressed when we revamp performance
+ // reporting.
+ return;
+ }
+ bytes_per_sec_ = bytes_per_sec;
+ hash_state_ = hash_state;
+ received_bytes_ = bytes_so_far;
+
+ // If we've received more data than we were expecting (bad server info?),
+ // revert to 'unknown size mode'.
+ if (received_bytes_ > total_bytes_)
+ total_bytes_ = 0;
+
+ if (bound_net_log_.IsLoggingAllEvents()) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_UPDATED,
+ net::NetLog::Int64Callback("bytes_so_far", received_bytes_));
+ }
+
+ UpdateObservers();
+}
+
+void DownloadItemImpl::DestinationError(DownloadInterruptReason reason) {
+ // Postpone recognition of this error until after file name determination
+ // has completed and the intermediate file has been renamed to simplify
+ // resumption conditions.
+ if (current_path_.empty() || target_path_.empty())
+ destination_error_ = reason;
+ else
+ Interrupt(reason);
+}
+
+void DownloadItemImpl::DestinationCompleted(const std::string& final_hash) {
+ VLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
+ if (GetState() != IN_PROGRESS)
+ return;
+ OnAllDataSaved(final_hash);
+ MaybeCompleteDownload();
+}
+
+// **** Download progression cascade
+
+void DownloadItemImpl::Init(bool active,
+ DownloadType download_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (active)
+ RecordDownloadCount(START_COUNT);
+
+ std::string file_name;
+ if (download_type == SRC_HISTORY_IMPORT) {
+ // target_path_ works for History and Save As versions.
+ file_name = target_path_.AsUTF8Unsafe();
+ } else {
+ // See if it's set programmatically.
+ file_name = forced_file_path_.AsUTF8Unsafe();
+ // Possibly has a 'download' attribute for the anchor.
+ if (file_name.empty())
+ file_name = suggested_filename_;
+ // From the URL file name.
+ if (file_name.empty())
+ file_name = GetURL().ExtractFileName();
+ }
+
+ base::Callback<base::Value*(net::NetLog::LogLevel)> active_data = base::Bind(
+ &ItemActivatedNetLogCallback, this, download_type, &file_name);
+ if (active) {
+ bound_net_log_.BeginEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data);
+ } else {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data);
+ }
+
+ VLOG(20) << __FUNCTION__ << "() " << DebugString(true);
+}
+
+// We're starting the download.
+void DownloadItemImpl::Start(
+ scoped_ptr<DownloadFile> file,
+ scoped_ptr<DownloadRequestHandleInterface> req_handle) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!download_file_.get());
+ DCHECK(file.get());
+ DCHECK(req_handle.get());
+
+ download_file_ = file.Pass();
+ request_handle_ = req_handle.Pass();
+
+ if (GetState() == CANCELLED) {
+ // The download was in the process of resuming when it was cancelled. Don't
+ // proceed.
+ ReleaseDownloadFile(true);
+ request_handle_->CancelRequest();
+ return;
+ }
+
+ TransitionTo(IN_PROGRESS_INTERNAL, UPDATE_OBSERVERS);
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DownloadFile::Initialize,
+ // Safe because we control download file lifetime.
+ base::Unretained(download_file_.get()),
+ base::Bind(&DownloadItemImpl::OnDownloadFileInitialized,
+ weak_ptr_factory_.GetWeakPtr())));
+}
+
+void DownloadItemImpl::OnDownloadFileInitialized(
+ DownloadInterruptReason result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
+ Interrupt(result);
+ // TODO(rdsmith/asanka): Arguably we should show this in the UI, but
+ // it's not at all clear what to show--we haven't done filename
+ // determination, so we don't know what name to display. OTOH,
+ // the failure mode of not showing the DI if the file initialization
+ // fails isn't a good one. Can we hack up a name based on the
+ // URLRequest? We'll need to make sure that initialization happens
+ // properly. Possibly the right thing is to have the UI handle
+ // this case specially.
+ return;
+ }
+
+ delegate_->DetermineDownloadTarget(
+ this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+// Called by delegate_ when the download target path has been
+// determined.
+void DownloadItemImpl::OnDownloadTargetDetermined(
+ const base::FilePath& target_path,
+ TargetDisposition disposition,
+ DownloadDangerType danger_type,
+ const base::FilePath& intermediate_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // If the |target_path| is empty, then we consider this download to be
+ // canceled.
+ if (target_path.empty()) {
+ Cancel(true);
+ return;
+ }
+
+ // TODO(rdsmith,asanka): We are ignoring the possibility that the download
+ // has been interrupted at this point until we finish the intermediate
+ // rename and set the full path. That's dangerous, because we might race
+ // with resumption, either manual (because the interrupt is visible to the
+ // UI) or automatic. If we keep the "ignore an error on download until file
+ // name determination complete" semantics, we need to make sure that the
+ // error is kept completely invisible until that point.
+
+ VLOG(20) << __FUNCTION__ << " " << target_path.value() << " " << disposition
+ << " " << danger_type << " " << DebugString(true);
+
+ target_path_ = target_path;
+ target_disposition_ = disposition;
+ SetDangerType(danger_type);
+
+ // We want the intermediate and target paths to refer to the same directory so
+ // that they are both on the same device and subject to same
+ // space/permission/availability constraints.
+ DCHECK(intermediate_path.DirName() == target_path.DirName());
+
+ // During resumption, we may choose to proceed with the same intermediate
+ // file. No rename is necessary if our intermediate file already has the
+ // correct name.
+ //
+ // The intermediate name may change from its original value during filename
+ // determination on resumption, for example if the reason for the interruption
+ // was the download target running out space, resulting in a user prompt.
+ if (intermediate_path == current_path_) {
+ OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE,
+ intermediate_path);
+ return;
+ }
+
+ // Rename to intermediate name.
+ // TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a
+ // spurious rename when we can just rename to the final
+ // filename. Unnecessary renames may cause bugs like
+ // http://crbug.com/74187.
+ DCHECK(!is_save_package_download_);
+ DCHECK(download_file_.get());
+ DownloadFile::RenameCompletionCallback callback =
+ base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
+ weak_ptr_factory_.GetWeakPtr());
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DownloadFile::RenameAndUniquify,
+ // Safe because we control download file lifetime.
+ base::Unretained(download_file_.get()),
+ intermediate_path, callback));
+}
+
+void DownloadItemImpl::OnDownloadRenamedToIntermediateName(
+ DownloadInterruptReason reason,
+ const base::FilePath& full_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(20) << __FUNCTION__ << " download=" << DebugString(true);
+
+ if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) {
+ // Process destination error. If both |reason| and |destination_error_|
+ // refer to actual errors, we want to use the |destination_error_| as the
+ // argument to the Interrupt() routine, as it happened first.
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NONE)
+ SetFullPath(full_path);
+ Interrupt(destination_error_);
+ destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
+ } else if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
+ Interrupt(reason);
+ // All file errors result in file deletion above; no need to cleanup. The
+ // current_path_ should be empty. Resuming this download will force a
+ // restart and a re-doing of filename determination.
+ DCHECK(current_path_.empty());
+ } else {
+ SetFullPath(full_path);
+ UpdateObservers();
+ MaybeCompleteDownload();
+ }
+}
+
+// When SavePackage downloads MHTML to GData (see
+// SavePackageFilePickerChromeOS), GData calls MaybeCompleteDownload() like it
+// does for non-SavePackage downloads, but SavePackage downloads never satisfy
+// IsDownloadReadyForCompletion(). GDataDownloadObserver manually calls
+// DownloadItem::UpdateObservers() when the upload completes so that SavePackage
+// notices that the upload has completed and runs its normal Finish() pathway.
+// MaybeCompleteDownload() is never the mechanism by which SavePackage completes
+// downloads. SavePackage always uses its own Finish() to mark downloads
+// complete.
+void DownloadItemImpl::MaybeCompleteDownload() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!is_save_package_download_);
+
+ if (!IsDownloadReadyForCompletion(
+ base::Bind(&DownloadItemImpl::MaybeCompleteDownload,
+ weak_ptr_factory_.GetWeakPtr())))
+ return;
+
+ // TODO(rdsmith): DCHECK that we only pass through this point
+ // once per download. The natural way to do this is by a state
+ // transition on the DownloadItem.
+
+ // Confirm we're in the proper set of states to be here;
+ // have all data, have a history handle, (validated or safe).
+ DCHECK_EQ(IN_PROGRESS_INTERNAL, state_);
+ DCHECK(!IsDangerous());
+ DCHECK(all_data_saved_);
+
+ OnDownloadCompleting();
+}
+
+// Called by MaybeCompleteDownload() when it has determined that the download
+// is ready for completion.
+void DownloadItemImpl::OnDownloadCompleting() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (state_ != IN_PROGRESS_INTERNAL)
+ return;
+
+ VLOG(20) << __FUNCTION__ << "()"
+ << " " << DebugString(true);
+ DCHECK(!GetTargetFilePath().empty());
+ DCHECK(!IsDangerous());
+
+ // TODO(rdsmith/benjhayden): Remove as part of SavePackage integration.
+ if (is_save_package_download_) {
+ // Avoid doing anything on the file thread; there's nothing we control
+ // there.
+ // Strictly speaking, this skips giving the embedder a chance to open
+ // the download. But on a save package download, there's no real
+ // concept of opening.
+ Completed();
+ return;
+ }
+
+ DCHECK(download_file_.get());
+ // Unilaterally rename; even if it already has the right name,
+ // we need theannotation.
+ DownloadFile::RenameCompletionCallback callback =
+ base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName,
+ weak_ptr_factory_.GetWeakPtr());
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DownloadFile::RenameAndAnnotate,
+ base::Unretained(download_file_.get()),
+ GetTargetFilePath(), callback));
+}
+
+void DownloadItemImpl::OnDownloadRenamedToFinalName(
+ DownloadInterruptReason reason,
+ const base::FilePath& full_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!is_save_package_download_);
+
+ // If a cancel or interrupt hit, we'll cancel the DownloadFile, which
+ // will result in deleting the file on the file thread. So we don't
+ // care about the name having been changed.
+ if (state_ != IN_PROGRESS_INTERNAL)
+ return;
+
+ VLOG(20) << __FUNCTION__ << "()"
+ << " full_path = \"" << full_path.value() << "\""
+ << " " << DebugString(false);
+
+ if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
+ Interrupt(reason);
+
+ // All file errors should have resulted in in file deletion above. On
+ // resumption we will need to re-do filename determination.
+ DCHECK(current_path_.empty());
+ return;
+ }
+
+ DCHECK(target_path_ == full_path);
+
+ if (full_path != current_path_) {
+ // full_path is now the current and target file path.
+ DCHECK(!full_path.empty());
+ SetFullPath(full_path);
+ }
+
+ // Complete the download and release the DownloadFile.
+ DCHECK(download_file_.get());
+ ReleaseDownloadFile(false);
+
+ // We're not completely done with the download item yet, but at this
+ // point we're committed to complete the download. Cancels (or Interrupts,
+ // though it's not clear how they could happen) after this point will be
+ // ignored.
+ TransitionTo(COMPLETING_INTERNAL, DONT_UPDATE_OBSERVERS);
+
+ if (delegate_->ShouldOpenDownload(
+ this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened,
+ weak_ptr_factory_.GetWeakPtr()))) {
+ Completed();
+ } else {
+ delegate_delayed_complete_ = true;
+ UpdateObservers();
+ }
+}
+
+void DownloadItemImpl::DelayedDownloadOpened(bool auto_opened) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ auto_opened_ = auto_opened;
+ Completed();
+}
+
+void DownloadItemImpl::Completed() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ VLOG(20) << __FUNCTION__ << "() " << DebugString(false);
+
+ DCHECK(all_data_saved_);
+ end_time_ = base::Time::Now();
+ TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS);
+ RecordDownloadCompleted(start_tick_, received_bytes_);
+
+ if (auto_opened_) {
+ // If it was already handled by the delegate, do nothing.
+ } else if (GetOpenWhenComplete() ||
+ ShouldOpenFileBasedOnExtension() ||
+ IsTemporary()) {
+ // If the download is temporary, like in drag-and-drop, do not open it but
+ // we still need to set it auto-opened so that it can be removed from the
+ // download shelf.
+ if (!IsTemporary())
+ OpenDownload();
+
+ auto_opened_ = true;
+ UpdateObservers();
+ }
+}
+
+void DownloadItemImpl::OnResumeRequestStarted(DownloadItem* item,
+ net::Error error) {
+ // If |item| is not NULL, then Start() has been called already, and nothing
+ // more needs to be done here.
+ if (item) {
+ DCHECK_EQ(net::OK, error);
+ DCHECK_EQ(static_cast<DownloadItem*>(this), item);
+ return;
+ }
+ // Otherwise, the request failed without passing through
+ // DownloadResourceHandler::OnResponseStarted.
+ if (error == net::OK)
+ error = net::ERR_FAILED;
+ DownloadInterruptReason reason =
+ ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_NETWORK);
+ DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason);
+ Interrupt(reason);
+}
+
+// **** End of Download progression cascade
+
+// An error occurred somewhere.
+void DownloadItemImpl::Interrupt(DownloadInterruptReason reason) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Somewhat counter-intuitively, it is possible for us to receive an
+ // interrupt after we've already been interrupted. The generation of
+ // interrupts from the file thread Renames and the generation of
+ // interrupts from disk writes go through two different mechanisms (driven
+ // by rename requests from UI thread and by write requests from IO thread,
+ // respectively), and since we choose not to keep state on the File thread,
+ // this is the place where the races collide. It's also possible for
+ // interrupts to race with cancels.
+
+ // Whatever happens, the first one to hit the UI thread wins.
+ if (state_ != IN_PROGRESS_INTERNAL && state_ != RESUMING_INTERNAL)
+ return;
+
+ last_reason_ = reason;
+
+ ResumeMode resume_mode = GetResumeMode();
+
+ if (state_ == IN_PROGRESS_INTERNAL) {
+ // Cancel (delete file) if we're going to restart; no point in leaving
+ // data around we aren't going to use. Also cancel if resumption isn't
+ // enabled for the same reason.
+ ReleaseDownloadFile(resume_mode == RESUME_MODE_IMMEDIATE_RESTART ||
+ resume_mode == RESUME_MODE_USER_RESTART ||
+ !IsDownloadResumptionEnabled());
+
+ // Cancel the originating URL request.
+ request_handle_->CancelRequest();
+ } else {
+ DCHECK(!download_file_.get());
+ }
+
+ // Reset all data saved, as even if we did save all the data we're going
+ // to go through another round of downloading when we resume.
+ // There's a potential problem here in the abstract, as if we did download
+ // all the data and then run into a continuable error, on resumption we
+ // won't download any more data. However, a) there are currently no
+ // continuable errors that can occur after we download all the data, and
+ // b) if there were, that would probably simply result in a null range
+ // request, which would generate a DestinationCompleted() notification
+ // from the DownloadFile, which would behave properly with setting
+ // all_data_saved_ to false here.
+ all_data_saved_ = false;
+
+ TransitionTo(INTERRUPTED_INTERNAL, DONT_UPDATE_OBSERVERS);
+ RecordDownloadInterrupted(reason, received_bytes_, total_bytes_);
+ if (!GetWebContents())
+ RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS);
+
+ AutoResumeIfValid();
+ UpdateObservers();
+}
+
+void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (destroy_file) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ // Will be deleted at end of task execution.
+ base::Bind(&DownloadFileCancel, base::Passed(&download_file_)));
+ // Avoid attempting to reuse the intermediate file by clearing out
+ // current_path_.
+ current_path_.clear();
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&DownloadFileDetach),
+ // Will be deleted at end of task execution.
+ base::Passed(&download_file_)));
+ }
+ // Don't accept any more messages from the DownloadFile, and null
+ // out any previous "all data received". This also breaks links to
+ // other entities we've given out weak pointers to.
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+bool DownloadItemImpl::IsDownloadReadyForCompletion(
+ const base::Closure& state_change_notification) {
+ // If we don't have all the data, the download is not ready for
+ // completion.
+ if (!AllDataSaved())
+ return false;
+
+ // If the download is dangerous, but not yet validated, it's not ready for
+ // completion.
+ if (IsDangerous())
+ return false;
+
+ // If the download isn't active (e.g. has been cancelled) it's not
+ // ready for completion.
+ if (state_ != IN_PROGRESS_INTERNAL)
+ return false;
+
+ // If the target filename hasn't been determined, then it's not ready for
+ // completion. This is checked in ReadyForDownloadCompletionDone().
+ if (GetTargetFilePath().empty())
+ return false;
+
+ // This is checked in NeedsRename(). Without this conditional,
+ // browser_tests:DownloadTest.DownloadMimeType fails the DCHECK.
+ if (target_path_.DirName() != current_path_.DirName())
+ return false;
+
+ // Give the delegate a chance to hold up a stop sign. It'll call
+ // use back through the passed callback if it does and that state changes.
+ if (!delegate_->ShouldCompleteDownload(this, state_change_notification))
+ return false;
+
+ return true;
+}
+
+void DownloadItemImpl::TransitionTo(DownloadInternalState new_state,
+ ShouldUpdateObservers notify_action) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (state_ == new_state)
+ return;
+
+ DownloadInternalState old_state = state_;
+ state_ = new_state;
+
+ switch (state_) {
+ case COMPLETING_INTERNAL:
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_COMPLETING,
+ base::Bind(&ItemCompletingNetLogCallback, received_bytes_, &hash_));
+ break;
+ case COMPLETE_INTERNAL:
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_FINISHED,
+ base::Bind(&ItemFinishedNetLogCallback, auto_opened_));
+ break;
+ case INTERRUPTED_INTERNAL:
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_INTERRUPTED,
+ base::Bind(&ItemInterruptedNetLogCallback, last_reason_,
+ received_bytes_, &hash_state_));
+ break;
+ case IN_PROGRESS_INTERNAL:
+ if (old_state == INTERRUPTED_INTERNAL) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_RESUMED,
+ base::Bind(&ItemResumingNetLogCallback,
+ false, last_reason_, received_bytes_, &hash_state_));
+ }
+ break;
+ case CANCELLED_INTERNAL:
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_CANCELED,
+ base::Bind(&ItemCanceledNetLogCallback, received_bytes_,
+ &hash_state_));
+ break;
+ default:
+ break;
+ }
+
+ VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true)
+ << " " << InternalToExternalState(old_state)
+ << " " << InternalToExternalState(state_);
+
+ bool is_done = (state_ != IN_PROGRESS_INTERNAL &&
+ state_ != COMPLETING_INTERNAL);
+ bool was_done = (old_state != IN_PROGRESS_INTERNAL &&
+ old_state != COMPLETING_INTERNAL);
+ // Termination
+ if (is_done && !was_done)
+ bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE);
+
+ // Resumption
+ if (was_done && !is_done) {
+ std::string file_name(target_path_.BaseName().AsUTF8Unsafe());
+ bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE,
+ base::Bind(&ItemActivatedNetLogCallback,
+ this, SRC_ACTIVE_DOWNLOAD,
+ &file_name));
+ }
+
+ if (notify_action == UPDATE_OBSERVERS)
+ UpdateObservers();
+}
+
+void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) {
+ if (danger_type != danger_type_) {
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
+ base::Bind(&ItemCheckedNetLogCallback, danger_type));
+ }
+ danger_type_ = danger_type;
+}
+
+void DownloadItemImpl::SetFullPath(const base::FilePath& new_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(20) << __FUNCTION__ << "()"
+ << " new_path = \"" << new_path.value() << "\""
+ << " " << DebugString(true);
+ DCHECK(!new_path.empty());
+
+ bound_net_log_.AddEvent(
+ net::NetLog::TYPE_DOWNLOAD_ITEM_RENAMED,
+ base::Bind(&ItemRenamedNetLogCallback, &current_path_, &new_path));
+
+ current_path_ = new_path;
+}
+
+void DownloadItemImpl::AutoResumeIfValid() {
+ DVLOG(20) << __FUNCTION__ << "() " << DebugString(true);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ ResumeMode mode = GetResumeMode();
+
+ if (mode != RESUME_MODE_IMMEDIATE_RESTART &&
+ mode != RESUME_MODE_IMMEDIATE_CONTINUE) {
+ return;
+ }
+
+ auto_resume_count_++;
+
+ ResumeInterruptedDownload();
+}
+
+void DownloadItemImpl::ResumeInterruptedDownload() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // If the flag for downloads resumption isn't enabled, ignore
+ // this request.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (!command_line.HasSwitch(switches::kEnableDownloadResumption))
+ return;
+
+ // If we're not interrupted, ignore the request; our caller is drunk.
+ if (state_ != INTERRUPTED_INTERNAL)
+ return;
+
+ // If we can't get a web contents, we can't resume the download.
+ // TODO(rdsmith): Find some alternative web contents to use--this
+ // means we can't restart a download if it's a download imported
+ // from the history.
+ if (!GetWebContents())
+ return;
+
+ // Reset the appropriate state if restarting.
+ ResumeMode mode = GetResumeMode();
+ if (mode == RESUME_MODE_IMMEDIATE_RESTART ||
+ mode == RESUME_MODE_USER_RESTART) {
+ received_bytes_ = 0;
+ hash_state_ = "";
+ last_modified_time_ = "";
+ etag_ = "";
+ }
+
+ scoped_ptr<DownloadUrlParameters> download_params(
+ DownloadUrlParameters::FromWebContents(GetWebContents(),
+ GetOriginalUrl()));
+
+ download_params->set_file_path(GetFullPath());
+ download_params->set_offset(GetReceivedBytes());
+ download_params->set_hash_state(GetHashState());
+ download_params->set_last_modified(GetLastModifiedTime());
+ download_params->set_etag(GetETag());
+ download_params->set_callback(
+ base::Bind(&DownloadItemImpl::OnResumeRequestStarted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ delegate_->ResumeInterruptedDownload(download_params.Pass(), GetId());
+ // Just in case we were interrupted while paused.
+ is_paused_ = false;
+
+ TransitionTo(RESUMING_INTERNAL, DONT_UPDATE_OBSERVERS);
+}
+
+// static
+DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState(
+ DownloadInternalState internal_state) {
+ switch (internal_state) {
+ case IN_PROGRESS_INTERNAL:
+ return IN_PROGRESS;
+ case COMPLETING_INTERNAL:
+ return IN_PROGRESS;
+ case COMPLETE_INTERNAL:
+ return COMPLETE;
+ case CANCELLED_INTERNAL:
+ return CANCELLED;
+ case INTERRUPTED_INTERNAL:
+ return INTERRUPTED;
+ case RESUMING_INTERNAL:
+ return INTERRUPTED;
+ case MAX_DOWNLOAD_INTERNAL_STATE:
+ break;
+ }
+ NOTREACHED();
+ return MAX_DOWNLOAD_STATE;
+}
+
+// static
+DownloadItemImpl::DownloadInternalState
+DownloadItemImpl::ExternalToInternalState(
+ DownloadState external_state) {
+ switch (external_state) {
+ case IN_PROGRESS:
+ return IN_PROGRESS_INTERNAL;
+ case COMPLETE:
+ return COMPLETE_INTERNAL;
+ case CANCELLED:
+ return CANCELLED_INTERNAL;
+ case INTERRUPTED:
+ return INTERRUPTED_INTERNAL;
+ default:
+ NOTREACHED();
+ }
+ return MAX_DOWNLOAD_INTERNAL_STATE;
+}
+
+const char* DownloadItemImpl::DebugDownloadStateString(
+ DownloadInternalState state) {
+ switch (state) {
+ case IN_PROGRESS_INTERNAL:
+ return "IN_PROGRESS";
+ case COMPLETING_INTERNAL:
+ return "COMPLETING";
+ case COMPLETE_INTERNAL:
+ return "COMPLETE";
+ case CANCELLED_INTERNAL:
+ return "CANCELLED";
+ case INTERRUPTED_INTERNAL:
+ return "INTERRUPTED";
+ case RESUMING_INTERNAL:
+ return "RESUMING";
+ case MAX_DOWNLOAD_INTERNAL_STATE:
+ break;
+ };
+ NOTREACHED() << "Unknown download state " << state;
+ return "unknown";
+}
+
+const char* DownloadItemImpl::DebugResumeModeString(ResumeMode mode) {
+ switch (mode) {
+ case RESUME_MODE_INVALID:
+ return "INVALID";
+ case RESUME_MODE_IMMEDIATE_CONTINUE:
+ return "IMMEDIATE_CONTINUE";
+ case RESUME_MODE_IMMEDIATE_RESTART:
+ return "IMMEDIATE_RESTART";
+ case RESUME_MODE_USER_CONTINUE:
+ return "USER_CONTINUE";
+ case RESUME_MODE_USER_RESTART:
+ return "USER_RESTART";
+ }
+ NOTREACHED() << "Unknown resume mode " << mode;
+ return "unknown";
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_item_impl.h b/chromium/content/browser/download/download_item_impl.h
new file mode 100644
index 00000000000..2086a54a89d
--- /dev/null
+++ b/chromium/content/browser/download/download_item_impl.h
@@ -0,0 +1,546 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/browser/download/download_net_log_parameters.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_destination_observer.h"
+#include "content/public/browser/download_item.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "url/gurl.h"
+
+namespace content {
+class DownloadFile;
+class DownloadItemImplDelegate;
+
+// See download_item.h for usage.
+class CONTENT_EXPORT DownloadItemImpl
+ : public DownloadItem,
+ public DownloadDestinationObserver {
+ public:
+ enum ResumeMode {
+ RESUME_MODE_INVALID = 0,
+ RESUME_MODE_IMMEDIATE_CONTINUE,
+ RESUME_MODE_IMMEDIATE_RESTART,
+ RESUME_MODE_USER_CONTINUE,
+ RESUME_MODE_USER_RESTART
+ };
+
+ // The maximum number of attempts we will make to resume automatically.
+ static const int kMaxAutoResumeAttempts;
+
+ // Note that it is the responsibility of the caller to ensure that a
+ // DownloadItemImplDelegate passed to a DownloadItemImpl constructor
+ // outlives the DownloadItemImpl.
+
+ // Constructing from persistent store:
+ // |bound_net_log| is constructed externally for our use.
+ DownloadItemImpl(DownloadItemImplDelegate* delegate,
+ uint32 id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log);
+
+ // Constructing for a regular download.
+ // |bound_net_log| is constructed externally for our use.
+ DownloadItemImpl(DownloadItemImplDelegate* delegate,
+ uint32 id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log);
+
+ // Constructing for the "Save Page As..." feature:
+ // |bound_net_log| is constructed externally for our use.
+ DownloadItemImpl(DownloadItemImplDelegate* delegate,
+ uint32 id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log);
+
+ virtual ~DownloadItemImpl();
+
+ // DownloadItem
+ virtual void AddObserver(DownloadItem::Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(DownloadItem::Observer* observer) OVERRIDE;
+ virtual void UpdateObservers() OVERRIDE;
+ virtual void ValidateDangerousDownload() OVERRIDE;
+ virtual void StealDangerousDownload(const AcquireFileCallback& callback)
+ OVERRIDE;
+ virtual void Pause() OVERRIDE;
+ virtual void Resume() OVERRIDE;
+ virtual void Cancel(bool user_cancel) OVERRIDE;
+ virtual void Remove() OVERRIDE;
+ virtual void OpenDownload() OVERRIDE;
+ virtual void ShowDownloadInShell() OVERRIDE;
+ virtual uint32 GetId() const OVERRIDE;
+ virtual DownloadState GetState() const OVERRIDE;
+ virtual DownloadInterruptReason GetLastReason() const OVERRIDE;
+ virtual bool IsPaused() const OVERRIDE;
+ virtual bool IsTemporary() const OVERRIDE;
+ virtual bool CanResume() const OVERRIDE;
+ virtual bool IsDone() const OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const std::vector<GURL>& GetUrlChain() const OVERRIDE;
+ virtual const GURL& GetOriginalUrl() const OVERRIDE;
+ virtual const GURL& GetReferrerUrl() const OVERRIDE;
+ virtual std::string GetSuggestedFilename() const OVERRIDE;
+ virtual std::string GetContentDisposition() const OVERRIDE;
+ virtual std::string GetMimeType() const OVERRIDE;
+ virtual std::string GetOriginalMimeType() const OVERRIDE;
+ virtual std::string GetRemoteAddress() const OVERRIDE;
+ virtual bool HasUserGesture() const OVERRIDE;
+ virtual PageTransition GetTransitionType() const OVERRIDE;
+ virtual const std::string& GetLastModifiedTime() const OVERRIDE;
+ virtual const std::string& GetETag() const OVERRIDE;
+ virtual bool IsSavePackageDownload() const OVERRIDE;
+ virtual const base::FilePath& GetFullPath() const OVERRIDE;
+ virtual const base::FilePath& GetTargetFilePath() const OVERRIDE;
+ virtual const base::FilePath& GetForcedFilePath() const OVERRIDE;
+ virtual base::FilePath GetFileNameToReportUser() const OVERRIDE;
+ virtual TargetDisposition GetTargetDisposition() const OVERRIDE;
+ virtual const std::string& GetHash() const OVERRIDE;
+ virtual const std::string& GetHashState() const OVERRIDE;
+ virtual bool GetFileExternallyRemoved() const OVERRIDE;
+ virtual void DeleteFile() OVERRIDE;
+ virtual bool IsDangerous() const OVERRIDE;
+ virtual DownloadDangerType GetDangerType() const OVERRIDE;
+ virtual bool TimeRemaining(base::TimeDelta* remaining) const OVERRIDE;
+ virtual int64 CurrentSpeed() const OVERRIDE;
+ virtual int PercentComplete() const OVERRIDE;
+ virtual bool AllDataSaved() const OVERRIDE;
+ virtual int64 GetTotalBytes() const OVERRIDE;
+ virtual int64 GetReceivedBytes() const OVERRIDE;
+ virtual base::Time GetStartTime() const OVERRIDE;
+ virtual base::Time GetEndTime() const OVERRIDE;
+ virtual bool CanShowInFolder() OVERRIDE;
+ virtual bool CanOpenDownload() OVERRIDE;
+ virtual bool ShouldOpenFileBasedOnExtension() OVERRIDE;
+ virtual bool GetOpenWhenComplete() const OVERRIDE;
+ virtual bool GetAutoOpened() OVERRIDE;
+ virtual bool GetOpened() const OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual void OnContentCheckCompleted(DownloadDangerType danger_type) OVERRIDE;
+ virtual void SetOpenWhenComplete(bool open) OVERRIDE;
+ virtual void SetIsTemporary(bool temporary) OVERRIDE;
+ virtual void SetOpened(bool opened) OVERRIDE;
+ virtual void SetDisplayName(const base::FilePath& name) OVERRIDE;
+ virtual std::string DebugString(bool verbose) const OVERRIDE;
+
+ // All remaining public interfaces virtual to allow for DownloadItemImpl
+ // mocks.
+
+ // Determines the resume mode for an interrupted download. Requires
+ // last_reason_ to be set, but doesn't require the download to be in
+ // INTERRUPTED state.
+ virtual ResumeMode GetResumeMode() const;
+
+ // State transition operations on regular downloads --------------------------
+
+ // Start the download.
+ // |download_file| is the associated file on the storage medium.
+ // |req_handle| is the new request handle associated with the download.
+ virtual void Start(scoped_ptr<DownloadFile> download_file,
+ scoped_ptr<DownloadRequestHandleInterface> req_handle);
+
+ // Needed because of intertwining with DownloadManagerImpl -------------------
+
+ // TODO(rdsmith): Unwind DownloadManagerImpl and DownloadItemImpl,
+ // removing these from the public interface.
+
+ // Notify observers that this item is being removed by the user.
+ virtual void NotifyRemoved();
+
+ virtual void OnDownloadedFileRemoved();
+
+ // Provide a weak pointer reference to a DownloadDestinationObserver
+ // for use by download destinations.
+ virtual base::WeakPtr<DownloadDestinationObserver>
+ DestinationObserverAsWeakPtr();
+
+ // Get the download's BoundNetLog.
+ virtual const net::BoundNetLog& GetBoundNetLog() const;
+
+ // DownloadItemImpl routines only needed by SavePackage ----------------------
+
+ // Called by SavePackage to set the total number of bytes on the item.
+ virtual void SetTotalBytes(int64 total_bytes);
+
+ virtual void OnAllDataSaved(const std::string& final_hash);
+
+ // Called by SavePackage to display progress when the DownloadItem
+ // should be considered complete.
+ virtual void MarkAsComplete();
+
+ // DownloadDestinationObserver
+ virtual void DestinationUpdate(int64 bytes_so_far,
+ int64 bytes_per_sec,
+ const std::string& hash_state) OVERRIDE;
+ virtual void DestinationError(DownloadInterruptReason reason) OVERRIDE;
+ virtual void DestinationCompleted(const std::string& final_hash) OVERRIDE;
+
+ private:
+ // Fine grained states of a download. Note that active downloads are created
+ // in IN_PROGRESS_INTERNAL state. However, downloads creates via history can
+ // be created in COMPLETE_INTERNAL, CANCELLED_INTERNAL and
+ // INTERRUPTED_INTERNAL.
+
+ enum DownloadInternalState {
+ // Includes both before and after file name determination, and paused
+ // downloads.
+ // TODO(rdsmith): Put in state variable for file name determination.
+ // Transitions from:
+ // <Initial creation> Active downloads are created in this state.
+ // RESUMING_INTERNAL
+ // Transitions to:
+ // COMPLETING_INTERNAL On final rename completion.
+ // CANCELLED_INTERNAL On cancel.
+ // INTERRUPTED_INTERNAL On interrupt.
+ // COMPLETE_INTERNAL On SavePackage download completion.
+ IN_PROGRESS_INTERNAL,
+
+ // Between commit point (dispatch of download file release) and completed.
+ // Embedder may be opening the file in this state.
+ // Transitions from:
+ // IN_PROGRESS_INTERNAL
+ // Transitions to:
+ // COMPLETE_INTERNAL On successful completion.
+ COMPLETING_INTERNAL,
+
+ // After embedder has had a chance to auto-open. User may now open
+ // or auto-open based on extension.
+ // Transitions from:
+ // COMPLETING_INTERNAL
+ // IN_PROGRESS_INTERNAL SavePackage only.
+ // <Initial creation> Completed persisted downloads.
+ // Transitions to:
+ // <none> Terminal state.
+ COMPLETE_INTERNAL,
+
+ // User has cancelled the download.
+ // Transitions from:
+ // IN_PROGRESS_INTERNAL
+ // INTERRUPTED_INTERNAL
+ // RESUMING_INTERNAL
+ // <Initial creation> Canceleld persisted downloads.
+ // Transitions to:
+ // <none> Terminal state.
+ CANCELLED_INTERNAL,
+
+ // An error has interrupted the download.
+ // Transitions from:
+ // IN_PROGRESS_INTERNAL
+ // RESUMING_INTERNAL
+ // <Initial creation> Interrupted persisted downloads.
+ // Transitions to:
+ // RESUMING_INTERNAL On resumption.
+ INTERRUPTED_INTERNAL,
+
+ // A request to resume this interrupted download is in progress.
+ // Transitions from:
+ // INTERRUPTED_INTERNAL
+ // Transitions to:
+ // IN_PROGRESS_INTERNAL Once a server response is received from a
+ // resumption.
+ // INTERRUPTED_INTERNAL If the resumption request fails.
+ // CANCELLED_INTERNAL On cancel.
+ RESUMING_INTERNAL,
+
+ MAX_DOWNLOAD_INTERNAL_STATE,
+ };
+
+ // Used with TransitionTo() to indicate whether or not to call
+ // UpdateObservers() after the state transition.
+ enum ShouldUpdateObservers {
+ UPDATE_OBSERVERS,
+ DONT_UPDATE_OBSERVERS
+ };
+
+ // Normal progression of a download ------------------------------------------
+
+ // These are listed in approximately chronological order. There are also
+ // public methods involved in normal download progression; see
+ // the implementation ordering in download_item_impl.cc.
+
+ // Construction common to all constructors. |active| should be true for new
+ // downloads and false for downloads from the history.
+ // |download_type| indicates to the net log system what kind of download
+ // this is.
+ void Init(bool active, DownloadType download_type);
+
+ // Called when the target path has been determined. |target_path| is the
+ // suggested target path. |disposition| indicates how the target path should
+ // be used (see TargetDisposition). |danger_type| is the danger level of
+ // |target_path| as determined by the caller. |intermediate_path| is the path
+ // to use to store the download until OnDownloadCompleting() is called.
+ virtual void OnDownloadTargetDetermined(
+ const base::FilePath& target_path,
+ TargetDisposition disposition,
+ DownloadDangerType danger_type,
+ const base::FilePath& intermediate_path);
+
+ // Callback from file thread when we initialize the DownloadFile.
+ void OnDownloadFileInitialized(DownloadInterruptReason result);
+
+ void OnDownloadRenamedToIntermediateName(
+ DownloadInterruptReason reason, const base::FilePath& full_path);
+
+ // If all pre-requisites have been met, complete download processing, i.e. do
+ // internal cleanup, file rename, and potentially auto-open. (Dangerous
+ // downloads still may block on user acceptance after this point.)
+ void MaybeCompleteDownload();
+
+ // Called when the download is ready to complete.
+ // This may perform final rename if necessary and will eventually call
+ // DownloadItem::Completed().
+ void OnDownloadCompleting();
+
+ void OnDownloadRenamedToFinalName(DownloadInterruptReason reason,
+ const base::FilePath& full_path);
+
+ // Called if the embedder took over opening a download, to indicate that
+ // the download has been opened.
+ void DelayedDownloadOpened(bool auto_opened);
+
+ // Called when the entire download operation (including renaming etc)
+ // is completed.
+ void Completed();
+
+ // Callback invoked when the URLRequest for a download resumption has started.
+ void OnResumeRequestStarted(DownloadItem* item, net::Error error);
+
+ // Helper routines -----------------------------------------------------------
+
+ // Indicate that an error has occurred on the download.
+ void Interrupt(DownloadInterruptReason reason);
+
+ // Destroy the DownloadFile object. If |destroy_file| is true, the file is
+ // destroyed with it. Otherwise, DownloadFile::Detach() is called before
+ // object destruction to prevent file destruction. Destroying the file also
+ // resets |current_path_|.
+ void ReleaseDownloadFile(bool destroy_file);
+
+ // Check if a download is ready for completion. The callback provided
+ // may be called at some point in the future if an external entity
+ // state has change s.t. this routine should be checked again.
+ bool IsDownloadReadyForCompletion(const base::Closure& state_change_notify);
+
+ // Call to transition state; all state transitions should go through this.
+ // |notify_action| specifies whether or not to call UpdateObservers() after
+ // the state transition.
+ void TransitionTo(DownloadInternalState new_state,
+ ShouldUpdateObservers notify_action);
+
+ // Set the |danger_type_| and invoke obserers if necessary.
+ void SetDangerType(DownloadDangerType danger_type);
+
+ void SetFullPath(const base::FilePath& new_path);
+
+ void AutoResumeIfValid();
+
+ void ResumeInterruptedDownload();
+
+ static DownloadState InternalToExternalState(
+ DownloadInternalState internal_state);
+ static DownloadInternalState ExternalToInternalState(
+ DownloadState external_state);
+
+ // Debugging routines --------------------------------------------------------
+ static const char* DebugDownloadStateString(DownloadInternalState state);
+ static const char* DebugResumeModeString(ResumeMode mode);
+
+ // Will be false for save package downloads retrieved from the history.
+ // TODO(rdsmith): Replace with a generalized enum for "download source".
+ const bool is_save_package_download_;
+
+ // The handle to the request information. Used for operations outside the
+ // download system.
+ scoped_ptr<DownloadRequestHandleInterface> request_handle_;
+
+ uint32 download_id_;
+
+ // Display name for the download. If this is empty, then the display name is
+ // considered to be |target_path_.BaseName()|.
+ base::FilePath display_name_;
+
+ // Full path to the downloaded or downloading file. This is the path to the
+ // physical file, if one exists. The final target path is specified by
+ // |target_path_|. |current_path_| can be empty if the in-progress path hasn't
+ // been determined.
+ base::FilePath current_path_;
+
+ // Target path of an in-progress download. We may be downloading to a
+ // temporary or intermediate file (specified by |current_path_|. Once the
+ // download completes, we will rename the file to |target_path_|.
+ base::FilePath target_path_;
+
+ // Whether the target should be overwritten, uniquified or prompted for.
+ TargetDisposition target_disposition_;
+
+ // The chain of redirects that leading up to and including the final URL.
+ std::vector<GURL> url_chain_;
+
+ // The URL of the page that initiated the download.
+ GURL referrer_url_;
+
+ // Filename suggestion from DownloadSaveInfo. It could, among others, be the
+ // suggested filename in 'download' attribute of an anchor. Details:
+ // http://www.whatwg.org/specs/web-apps/current-work/#downloading-hyperlinks
+ std::string suggested_filename_;
+
+ // If non-empty, contains an externally supplied path that should be used as
+ // the target path.
+ base::FilePath forced_file_path_;
+
+ // Page transition that triggerred the download.
+ PageTransition transition_type_;
+
+ // Whether the download was triggered with a user gesture.
+ bool has_user_gesture_;
+
+ // Information from the request.
+ // Content-disposition field from the header.
+ std::string content_disposition_;
+
+ // Mime-type from the header. Subject to change.
+ std::string mime_type_;
+
+ // The value of the content type header sent with the downloaded item. It
+ // may be different from |mime_type_|, which may be set based on heuristics
+ // which may look at the file extension and first few bytes of the file.
+ std::string original_mime_type_;
+
+ // The remote IP address where the download was fetched from. Copied from
+ // DownloadCreateInfo::remote_address.
+ std::string remote_address_;
+
+ // Total bytes expected.
+ int64 total_bytes_;
+
+ // Current received bytes.
+ int64 received_bytes_;
+
+ // Current speed. Calculated by the DownloadFile.
+ int64 bytes_per_sec_;
+
+ // Sha256 hash of the content. This might be empty either because
+ // the download isn't done yet or because the hash isn't needed
+ // (ChromeDownloadManagerDelegate::GenerateFileHash() returned false).
+ std::string hash_;
+
+ // A blob containing the state of the hash algorithm. Only valid while the
+ // download is in progress.
+ std::string hash_state_;
+
+ // Server's time stamp for the file.
+ std::string last_modified_time_;
+
+ // Server's ETAG for the file.
+ std::string etag_;
+
+ // Last reason.
+ DownloadInterruptReason last_reason_;
+
+ // Start time for recording statistics.
+ base::TimeTicks start_tick_;
+
+ // The current state of this download.
+ DownloadInternalState state_;
+
+ // Current danger type for the download.
+ DownloadDangerType danger_type_;
+
+ // The views of this item in the download shelf and download contents.
+ ObserverList<Observer> observers_;
+
+ // Time the download was started.
+ base::Time start_time_;
+
+ // Time the download completed.
+ base::Time end_time_;
+
+ // Our delegate.
+ DownloadItemImplDelegate* delegate_;
+
+ // In progress downloads may be paused by the user, we note it here.
+ bool is_paused_;
+
+ // The number of times this download has been resumed automatically.
+ int auto_resume_count_;
+
+ // A flag for indicating if the download should be opened at completion.
+ bool open_when_complete_;
+
+ // A flag for indicating if the downloaded file is externally removed.
+ bool file_externally_removed_;
+
+ // True if the download was auto-opened. We set this rather than using
+ // an observer as it's frequently possible for the download to be auto opened
+ // before the observer is added.
+ bool auto_opened_;
+
+ // True if the item was downloaded temporarily.
+ bool is_temporary_;
+
+ // True if we've saved all the data for the download.
+ bool all_data_saved_;
+
+ // Error return from DestinationError. Stored separately from
+ // last_reason_ so that we can avoid handling destination errors until
+ // after file name determination has occurred.
+ DownloadInterruptReason destination_error_;
+
+ // Did the user open the item either directly or indirectly (such as by
+ // setting always open files of this type)? The shelf also sets this field
+ // when the user closes the shelf before the item has been opened but should
+ // be treated as though the user opened it.
+ bool opened_;
+
+ // Did the delegate delay calling Complete on this download?
+ bool delegate_delayed_complete_;
+
+ // DownloadFile associated with this download. Note that this
+ // pointer may only be used or destroyed on the FILE thread.
+ // This pointer will be non-null only while the DownloadItem is in
+ // the IN_PROGRESS state.
+ scoped_ptr<DownloadFile> download_file_;
+
+ // Net log to use for this download.
+ const net::BoundNetLog bound_net_log_;
+
+ base::WeakPtrFactory<DownloadItemImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadItemImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_H_
diff --git a/chromium/content/browser/download/download_item_impl_delegate.cc b/chromium/content/browser/download/download_item_impl_delegate.cc
new file mode 100644
index 00000000000..a069fa84e90
--- /dev/null
+++ b/chromium/content/browser/download/download_item_impl_delegate.cc
@@ -0,0 +1,78 @@
+// 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 "content/browser/download/download_item_impl_delegate.h"
+
+#include "base/logging.h"
+#include "content/browser/download/download_item_impl.h"
+
+namespace content {
+
+// Infrastructure in DownloadItemImplDelegate to assert invariant that
+// delegate always outlives all attached DownloadItemImpls.
+DownloadItemImplDelegate::DownloadItemImplDelegate()
+ : count_(0) {}
+
+DownloadItemImplDelegate::~DownloadItemImplDelegate() {
+ DCHECK_EQ(0, count_);
+}
+
+void DownloadItemImplDelegate::Attach() {
+ ++count_;
+}
+
+void DownloadItemImplDelegate::Detach() {
+ DCHECK_LT(0, count_);
+ --count_;
+}
+
+void DownloadItemImplDelegate::DetermineDownloadTarget(
+ DownloadItemImpl* download, const DownloadTargetCallback& callback) {
+ // TODO(rdsmith/asanka): Do something useful if forced file path is null.
+ base::FilePath target_path(download->GetForcedFilePath());
+ callback.Run(target_path,
+ DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+ target_path);
+}
+
+bool DownloadItemImplDelegate::ShouldCompleteDownload(
+ DownloadItemImpl* download,
+ const base::Closure& complete_callback) {
+ return true;
+}
+
+bool DownloadItemImplDelegate::ShouldOpenDownload(
+ DownloadItemImpl* download, const ShouldOpenDownloadCallback& callback) {
+ return false;
+}
+
+bool DownloadItemImplDelegate::ShouldOpenFileBasedOnExtension(
+ const base::FilePath& path) {
+ return false;
+}
+
+void DownloadItemImplDelegate::CheckForFileRemoval(
+ DownloadItemImpl* download_item) {}
+
+void DownloadItemImplDelegate::ResumeInterruptedDownload(
+ scoped_ptr<DownloadUrlParameters> params, uint32 id) {}
+
+BrowserContext* DownloadItemImplDelegate::GetBrowserContext() const {
+ return NULL;
+}
+
+void DownloadItemImplDelegate::UpdatePersistence(DownloadItemImpl* download) {}
+
+void DownloadItemImplDelegate::OpenDownload(DownloadItemImpl* download) {}
+
+void DownloadItemImplDelegate::ShowDownloadInShell(DownloadItemImpl* download) {
+}
+
+void DownloadItemImplDelegate::DownloadRemoved(DownloadItemImpl* download) {}
+
+void DownloadItemImplDelegate::AssertStateConsistent(
+ DownloadItemImpl* download) const {}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_item_impl_delegate.h b/chromium/content/browser/download/download_item_impl_delegate.h
new file mode 100644
index 00000000000..72e079b8963
--- /dev/null
+++ b/chromium/content/browser/download/download_item_impl_delegate.h
@@ -0,0 +1,102 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_DELEGATE_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_DELEGATE_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_danger_type.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_url_parameters.h"
+
+namespace content {
+class DownloadItemImpl;
+class BrowserContext;
+
+// Delegate for operations that a DownloadItemImpl can't do for itself.
+// The base implementation of this class does nothing (returning false
+// on predicates) so interfaces not of interest to a derived class may
+// be left unimplemented.
+class CONTENT_EXPORT DownloadItemImplDelegate {
+ public:
+ typedef base::Callback<void(
+ const base::FilePath&, // Target path
+ DownloadItem::TargetDisposition, // overwrite/uniquify target
+ DownloadDangerType,
+ const base::FilePath& // Intermediate file path
+ )> DownloadTargetCallback;
+
+ // The boolean argument indicates whether or not the download was
+ // actually opened.
+ typedef base::Callback<void(bool)> ShouldOpenDownloadCallback;
+
+ DownloadItemImplDelegate();
+ virtual ~DownloadItemImplDelegate();
+
+ // Used for catching use-after-free errors.
+ void Attach();
+ void Detach();
+
+ // Request determination of the download target from the delegate.
+ virtual void DetermineDownloadTarget(
+ DownloadItemImpl* download, const DownloadTargetCallback& callback);
+
+ // Allows the delegate to delay completion of the download. This function
+ // will either return true (if the download may complete now) or will return
+ // false and call the provided callback at some future point. This function
+ // may be called repeatedly.
+ virtual bool ShouldCompleteDownload(
+ DownloadItemImpl* download,
+ const base::Closure& complete_callback);
+
+ // Allows the delegate to override the opening of a download. If it returns
+ // true then it's reponsible for opening the item.
+ virtual bool ShouldOpenDownload(
+ DownloadItemImpl* download, const ShouldOpenDownloadCallback& callback);
+
+ // Tests if a file type should be opened automatically.
+ virtual bool ShouldOpenFileBasedOnExtension(const base::FilePath& path);
+
+ // Checks whether a downloaded file still exists and updates the
+ // file's state if the file is already removed.
+ // The check may or may not result in a later asynchronous call
+ // to OnDownloadedFileRemoved().
+ virtual void CheckForFileRemoval(DownloadItemImpl* download_item);
+
+ // Called when an interrupted download is resumed.
+ virtual void ResumeInterruptedDownload(
+ scoped_ptr<content::DownloadUrlParameters> params,
+ uint32 id);
+
+ // For contextual issues like language and prefs.
+ virtual BrowserContext* GetBrowserContext() const;
+
+ // Update the persistent store with our information.
+ virtual void UpdatePersistence(DownloadItemImpl* download);
+
+ // Opens the file associated with this download.
+ virtual void OpenDownload(DownloadItemImpl* download);
+
+ // Shows the download via the OS shell.
+ virtual void ShowDownloadInShell(DownloadItemImpl* download);
+
+ // Handle any delegate portions of a state change operation on the
+ // DownloadItem.
+ virtual void DownloadRemoved(DownloadItemImpl* download);
+
+ // Assert consistent state for delgate object at various transitions.
+ virtual void AssertStateConsistent(DownloadItemImpl* download) const;
+
+ private:
+ // For "Outlives attached DownloadItemImpl" invariant assertion.
+ int count_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadItemImplDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_IMPL_DELEGATE_H_
diff --git a/chromium/content/browser/download/download_item_impl_unittest.cc b/chromium/content/browser/download/download_item_impl_unittest.cc
new file mode 100644
index 00000000000..ff5d32602fa
--- /dev/null
+++ b/chromium/content/browser/download/download_item_impl_unittest.cc
@@ -0,0 +1,1276 @@
+// 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 "base/callback.h"
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/threading/thread.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_item_impl_delegate.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/browser/download/mock_download_file.h"
+#include "content/public/browser/download_destination_observer.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/mock_download_item.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::NiceMock;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+
+namespace {
+
+const int kDownloadChunkSize = 1000;
+const int kDownloadSpeed = 1000;
+const int kDummyDBHandle = 10;
+const base::FilePath::CharType kDummyPath[] = FILE_PATH_LITERAL("/testpath");
+
+} // namespace
+
+namespace content {
+
+namespace {
+
+class MockDelegate : public DownloadItemImplDelegate {
+ public:
+ MockDelegate() : DownloadItemImplDelegate() {
+ SetDefaultExpectations();
+ }
+
+ MOCK_METHOD2(DetermineDownloadTarget, void(
+ DownloadItemImpl*, const DownloadTargetCallback&));
+ MOCK_METHOD2(ShouldCompleteDownload,
+ bool(DownloadItemImpl*, const base::Closure&));
+ MOCK_METHOD2(ShouldOpenDownload,
+ bool(DownloadItemImpl*, const ShouldOpenDownloadCallback&));
+ MOCK_METHOD1(ShouldOpenFileBasedOnExtension, bool(const base::FilePath&));
+ MOCK_METHOD1(CheckForFileRemoval, void(DownloadItemImpl*));
+
+ virtual void ResumeInterruptedDownload(
+ scoped_ptr<DownloadUrlParameters> params, uint32 id) OVERRIDE {
+ MockResumeInterruptedDownload(params.get(), id);
+ }
+ MOCK_METHOD2(MockResumeInterruptedDownload,
+ void(DownloadUrlParameters* params, uint32 id));
+
+ MOCK_CONST_METHOD0(GetBrowserContext, BrowserContext*());
+ MOCK_METHOD1(UpdatePersistence, void(DownloadItemImpl*));
+ MOCK_METHOD1(DownloadOpened, void(DownloadItemImpl*));
+ MOCK_METHOD1(DownloadRemoved, void(DownloadItemImpl*));
+ MOCK_CONST_METHOD1(AssertStateConsistent, void(DownloadItemImpl*));
+
+ void VerifyAndClearExpectations() {
+ ::testing::Mock::VerifyAndClearExpectations(this);
+ SetDefaultExpectations();
+ }
+
+ private:
+ void SetDefaultExpectations() {
+ EXPECT_CALL(*this, AssertStateConsistent(_))
+ .WillRepeatedly(Return());
+ EXPECT_CALL(*this, ShouldOpenFileBasedOnExtension(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*this, ShouldOpenDownload(_, _))
+ .WillRepeatedly(Return(true));
+ }
+};
+
+class MockRequestHandle : public DownloadRequestHandleInterface {
+ public:
+ MOCK_CONST_METHOD0(GetWebContents, WebContents*());
+ MOCK_CONST_METHOD0(GetDownloadManager, DownloadManager*());
+ MOCK_CONST_METHOD0(PauseRequest, void());
+ MOCK_CONST_METHOD0(ResumeRequest, void());
+ MOCK_CONST_METHOD0(CancelRequest, void());
+ MOCK_CONST_METHOD0(DebugString, std::string());
+};
+
+// Schedules a task to invoke the RenameCompletionCallback with |new_path| on
+// the UI thread. Should only be used as the action for
+// MockDownloadFile::Rename as follows:
+// EXPECT_CALL(download_file, Rename*(_,_))
+// .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+// new_path));
+ACTION_P2(ScheduleRenameCallback, interrupt_reason, new_path) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(arg1, interrupt_reason, new_path));
+}
+
+} // namespace
+
+class DownloadItemTest : public testing::Test {
+ public:
+ class MockObserver : public DownloadItem::Observer {
+ public:
+ explicit MockObserver(DownloadItem* item)
+ : item_(item),
+ last_state_(item->GetState()),
+ removed_(false),
+ destroyed_(false),
+ updated_(false),
+ interrupt_count_(0),
+ resume_count_(0) {
+ item_->AddObserver(this);
+ }
+
+ virtual ~MockObserver() {
+ if (item_) item_->RemoveObserver(this);
+ }
+
+ virtual void OnDownloadRemoved(DownloadItem* download) OVERRIDE {
+ DVLOG(20) << " " << __FUNCTION__
+ << " download = " << download->DebugString(false);
+ removed_ = true;
+ }
+
+ virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE {
+ DVLOG(20) << " " << __FUNCTION__
+ << " download = " << download->DebugString(false);
+ updated_ = true;
+ DownloadItem::DownloadState new_state = download->GetState();
+ if (last_state_ == DownloadItem::IN_PROGRESS &&
+ new_state == DownloadItem::INTERRUPTED) {
+ interrupt_count_++;
+ }
+ if (last_state_ == DownloadItem::INTERRUPTED &&
+ new_state == DownloadItem::IN_PROGRESS) {
+ resume_count_++;
+ }
+ last_state_ = new_state;
+ }
+
+ virtual void OnDownloadOpened(DownloadItem* download) OVERRIDE {
+ DVLOG(20) << " " << __FUNCTION__
+ << " download = " << download->DebugString(false);
+ }
+
+ virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE {
+ DVLOG(20) << " " << __FUNCTION__
+ << " download = " << download->DebugString(false);
+ destroyed_ = true;
+ item_->RemoveObserver(this);
+ item_ = NULL;
+ }
+
+ bool CheckRemoved() {
+ return removed_;
+ }
+
+ bool CheckDestroyed() {
+ return destroyed_;
+ }
+
+ bool CheckUpdated() {
+ bool was_updated = updated_;
+ updated_ = false;
+ return was_updated;
+ }
+
+ int GetInterruptCount() {
+ return interrupt_count_;
+ }
+
+ int GetResumeCount() {
+ return resume_count_;
+ }
+
+ private:
+ DownloadItem* item_;
+ DownloadItem::DownloadState last_state_;
+ bool removed_;
+ bool destroyed_;
+ bool updated_;
+ int interrupt_count_;
+ int resume_count_;
+ };
+
+ DownloadItemTest()
+ : ui_thread_(BrowserThread::UI, &loop_),
+ file_thread_(BrowserThread::FILE, &loop_),
+ delegate_() {
+ }
+
+ ~DownloadItemTest() {
+ }
+
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ ui_thread_.DeprecatedGetThreadObject()->message_loop()->RunUntilIdle();
+ STLDeleteElements(&allocated_downloads_);
+ allocated_downloads_.clear();
+ }
+
+ // This class keeps ownership of the created download item; it will
+ // be torn down at the end of the test unless DestroyDownloadItem is
+ // called.
+ DownloadItemImpl* CreateDownloadItem() {
+ // Normally, the download system takes ownership of info, and is
+ // responsible for deleting it. In these unit tests, however, we
+ // don't call the function that deletes it, so we do so ourselves.
+ scoped_ptr<DownloadCreateInfo> info_;
+
+ info_.reset(new DownloadCreateInfo());
+ static uint32 next_id = DownloadItem::kInvalidId + 1;
+ info_->save_info = scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo());
+ info_->save_info->prompt_for_save_location = false;
+ info_->url_chain.push_back(GURL());
+ info_->etag = "SomethingToSatisfyResumption";
+
+ DownloadItemImpl* download =
+ new DownloadItemImpl(
+ &delegate_, next_id++, *(info_.get()), net::BoundNetLog());
+ allocated_downloads_.insert(download);
+ return download;
+ }
+
+ // Add DownloadFile to DownloadItem
+ MockDownloadFile* AddDownloadFileToDownloadItem(
+ DownloadItemImpl* item,
+ DownloadItemImplDelegate::DownloadTargetCallback *callback) {
+ MockDownloadFile* mock_download_file(new StrictMock<MockDownloadFile>);
+ scoped_ptr<DownloadFile> download_file(mock_download_file);
+ EXPECT_CALL(*mock_download_file, Initialize(_));
+ if (callback) {
+ // Save the callback.
+ EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
+ .WillOnce(SaveArg<1>(callback));
+ } else {
+ // Drop it on the floor.
+ EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _));
+ }
+
+ scoped_ptr<DownloadRequestHandleInterface> request_handle(
+ new NiceMock<MockRequestHandle>);
+ item->Start(download_file.Pass(), request_handle.Pass());
+ loop_.RunUntilIdle();
+
+ // So that we don't have a function writing to a stack variable
+ // lying around if the above failed.
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_CALL(*mock_delegate(), AssertStateConsistent(_))
+ .WillRepeatedly(Return());
+ EXPECT_CALL(*mock_delegate(), ShouldOpenFileBasedOnExtension(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(_, _))
+ .WillRepeatedly(Return(true));
+
+ return mock_download_file;
+ }
+
+ // Perform the intermediate rename for |item|. The target path for the
+ // download will be set to kDummyPath. Returns the MockDownloadFile* that was
+ // added to the DownloadItem.
+ MockDownloadFile* DoIntermediateRename(DownloadItemImpl* item,
+ DownloadDangerType danger_type) {
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_TRUE(item->GetTargetFilePath().empty());
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ base::FilePath target_path(kDummyPath);
+ base::FilePath intermediate_path(
+ target_path.InsertBeforeExtensionASCII("x"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ intermediate_path));
+ callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ danger_type, intermediate_path);
+ RunAllPendingInMessageLoops();
+ return download_file;
+ }
+
+ // Cleanup a download item (specifically get rid of the DownloadFile on it).
+ // The item must be in the expected state.
+ void CleanupItem(DownloadItemImpl* item,
+ MockDownloadFile* download_file,
+ DownloadItem::DownloadState expected_state) {
+ EXPECT_EQ(expected_state, item->GetState());
+
+ if (expected_state == DownloadItem::IN_PROGRESS) {
+ EXPECT_CALL(*download_file, Cancel());
+ item->Cancel(true);
+ loop_.RunUntilIdle();
+ }
+ }
+
+ // Destroy a previously created download item.
+ void DestroyDownloadItem(DownloadItem* item) {
+ allocated_downloads_.erase(item);
+ delete item;
+ }
+
+ void RunAllPendingInMessageLoops() {
+ loop_.RunUntilIdle();
+ }
+
+ MockDelegate* mock_delegate() {
+ return &delegate_;
+ }
+
+ void OnDownloadFileAcquired(base::FilePath* return_path,
+ const base::FilePath& path) {
+ *return_path = path;
+ }
+
+ private:
+ base::MessageLoopForUI loop_;
+ TestBrowserThread ui_thread_; // UI thread
+ TestBrowserThread file_thread_; // FILE thread
+ StrictMock<MockDelegate> delegate_;
+ std::set<DownloadItem*> allocated_downloads_;
+};
+
+// Tests to ensure calls that change a DownloadItem generate an update to
+// observers.
+// State changing functions not tested:
+// void OpenDownload();
+// void ShowDownloadInShell();
+// void CompleteDelayedDownload();
+// set_* mutators
+
+TEST_F(DownloadItemTest, NotificationAfterUpdate) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+
+ item->DestinationUpdate(kDownloadChunkSize, kDownloadSpeed, std::string());
+ ASSERT_TRUE(observer.CheckUpdated());
+ EXPECT_EQ(kDownloadSpeed, item->CurrentSpeed());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterCancel) {
+ DownloadItemImpl* user_cancel = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(user_cancel, NULL);
+ EXPECT_CALL(*download_file, Cancel());
+ MockObserver observer1(user_cancel);
+
+ user_cancel->Cancel(true);
+ ASSERT_TRUE(observer1.CheckUpdated());
+
+ DownloadItemImpl* system_cancel = CreateDownloadItem();
+ download_file = AddDownloadFileToDownloadItem(system_cancel, NULL);
+ EXPECT_CALL(*download_file, Cancel());
+ MockObserver observer2(system_cancel);
+
+ system_cancel->Cancel(false);
+ ASSERT_TRUE(observer2.CheckUpdated());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterComplete) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+
+ item->OnAllDataSaved(DownloadItem::kEmptyFileHash);
+ ASSERT_TRUE(observer.CheckUpdated());
+
+ item->MarkAsComplete();
+ ASSERT_TRUE(observer.CheckUpdated());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterDownloadedFileRemoved) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+
+ item->OnDownloadedFileRemoved();
+ ASSERT_TRUE(observer.CheckUpdated());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterInterrupted) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+ EXPECT_CALL(*download_file, Cancel());
+ MockObserver observer(item);
+
+ EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_,_))
+ .Times(0);
+
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ ASSERT_TRUE(observer.CheckUpdated());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterDestroyed) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+
+ DestroyDownloadItem(item);
+ ASSERT_TRUE(observer.CheckDestroyed());
+}
+
+TEST_F(DownloadItemTest, ContinueAfterInterrupted) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Interrupt the download, using a continuable interrupt.
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR);
+ ASSERT_TRUE(observer.CheckUpdated());
+ // Should attempt to auto-resume. Because we don't have a mock WebContents,
+ // ResumeInterruptedDownload() will abort early, with another interrupt,
+ // which will be ignored.
+ ASSERT_EQ(1, observer.GetInterruptCount());
+ ASSERT_EQ(0, observer.GetResumeCount());
+ RunAllPendingInMessageLoops();
+
+ CleanupItem(item, download_file, DownloadItem::INTERRUPTED);
+}
+
+// Same as above, but with a non-continuable interrupt.
+TEST_F(DownloadItemTest, RestartAfterInterrupted) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Interrupt the download, using a restartable interrupt.
+ EXPECT_CALL(*download_file, Cancel());
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ ASSERT_TRUE(observer.CheckUpdated());
+ // Should not try to auto-resume.
+ ASSERT_EQ(1, observer.GetInterruptCount());
+ ASSERT_EQ(0, observer.GetResumeCount());
+ RunAllPendingInMessageLoops();
+
+ CleanupItem(item, download_file, DownloadItem::INTERRUPTED);
+}
+
+TEST_F(DownloadItemTest, LimitRestartsAfterInterrupted) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+
+ DownloadItemImpl* item = CreateDownloadItem();
+ base::WeakPtr<DownloadDestinationObserver> as_observer(
+ item->DestinationObserverAsWeakPtr());
+ MockObserver observer(item);
+ MockDownloadFile* mock_download_file(NULL);
+ scoped_ptr<DownloadFile> download_file;
+ MockRequestHandle* mock_request_handle(NULL);
+ scoped_ptr<DownloadRequestHandleInterface> request_handle;
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+
+ EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
+ .WillRepeatedly(SaveArg<1>(&callback));
+ for (int i = 0; i < (DownloadItemImpl::kMaxAutoResumeAttempts + 1); ++i) {
+ DVLOG(20) << "Loop iteration " << i;
+
+ mock_download_file = new NiceMock<MockDownloadFile>;
+ download_file.reset(mock_download_file);
+ mock_request_handle = new NiceMock<MockRequestHandle>;
+ request_handle.reset(mock_request_handle);
+
+ ON_CALL(*mock_download_file, FullPath())
+ .WillByDefault(Return(base::FilePath()));
+
+ // It's too complicated to set up a WebContents instance that would cause
+ // the MockDownloadItemDelegate's ResumeInterruptedDownload() function
+ // to be callled, so we simply verify that GetWebContents() is called.
+ if (i < (DownloadItemImpl::kMaxAutoResumeAttempts - 1)) {
+ EXPECT_CALL(*mock_request_handle, GetWebContents())
+ .WillRepeatedly(Return(static_cast<WebContents*>(NULL)));
+ }
+
+ // Copied key parts of DoIntermediateRename & AddDownloadFileToDownloadItem
+ // to allow for holding onto the request handle.
+ item->Start(download_file.Pass(), request_handle.Pass());
+ RunAllPendingInMessageLoops();
+ if (i == 0) {
+ // Target determination is only done the first time through.
+ base::FilePath target_path(kDummyPath);
+ base::FilePath intermediate_path(
+ target_path.InsertBeforeExtensionASCII("x"));
+ EXPECT_CALL(*mock_download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ intermediate_path));
+ callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ }
+ ASSERT_EQ(i, observer.GetResumeCount());
+
+ // Use a continuable interrupt.
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR);
+
+ ASSERT_EQ(i + 1, observer.GetInterruptCount());
+ ::testing::Mock::VerifyAndClearExpectations(mock_download_file);
+ }
+
+ CleanupItem(item, mock_download_file, DownloadItem::INTERRUPTED);
+}
+
+TEST_F(DownloadItemTest, NotificationAfterRemove) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file = AddDownloadFileToDownloadItem(item, NULL);
+ EXPECT_CALL(*download_file, Cancel());
+ EXPECT_CALL(*mock_delegate(), DownloadRemoved(_));
+ MockObserver observer(item);
+
+ item->Remove();
+ ASSERT_TRUE(observer.CheckUpdated());
+ ASSERT_TRUE(observer.CheckRemoved());
+}
+
+TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) {
+ // Setting to NOT_DANGEROUS does not trigger a notification.
+ DownloadItemImpl* safe_item = CreateDownloadItem();
+ MockObserver safe_observer(safe_item);
+
+ safe_item->OnAllDataSaved(std::string());
+ EXPECT_TRUE(safe_observer.CheckUpdated());
+ safe_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+ EXPECT_TRUE(safe_observer.CheckUpdated());
+
+ // Setting to unsafe url or unsafe file should trigger a notification.
+ DownloadItemImpl* unsafeurl_item =
+ CreateDownloadItem();
+ MockObserver unsafeurl_observer(unsafeurl_item);
+
+ unsafeurl_item->OnAllDataSaved(std::string());
+ EXPECT_TRUE(unsafeurl_observer.CheckUpdated());
+ unsafeurl_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_DANGEROUS_URL);
+ EXPECT_TRUE(unsafeurl_observer.CheckUpdated());
+
+ unsafeurl_item->ValidateDangerousDownload();
+ EXPECT_TRUE(unsafeurl_observer.CheckUpdated());
+
+ DownloadItemImpl* unsafefile_item =
+ CreateDownloadItem();
+ MockObserver unsafefile_observer(unsafefile_item);
+
+ unsafefile_item->OnAllDataSaved(std::string());
+ EXPECT_TRUE(unsafefile_observer.CheckUpdated());
+ unsafefile_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
+ EXPECT_TRUE(unsafefile_observer.CheckUpdated());
+
+ unsafefile_item->ValidateDangerousDownload();
+ EXPECT_TRUE(unsafefile_observer.CheckUpdated());
+}
+
+// DownloadItemImpl::OnDownloadTargetDetermined will schedule a task to run
+// DownloadFile::Rename(). Once the rename
+// completes, DownloadItemImpl receives a notification with the new file
+// name. Check that observers are updated when the new filename is available and
+// not before.
+TEST_F(DownloadItemTest, NotificationAfterOnDownloadTargetDetermined) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ MockObserver observer(item);
+ base::FilePath target_path(kDummyPath);
+ base::FilePath intermediate_path(target_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ target_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ new_intermediate_path));
+
+ // Currently, a notification would be generated if the danger type is anything
+ // other than NOT_DANGEROUS.
+ callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ EXPECT_FALSE(observer.CheckUpdated());
+ RunAllPendingInMessageLoops();
+ EXPECT_TRUE(observer.CheckUpdated());
+ EXPECT_EQ(new_intermediate_path, item->GetFullPath());
+
+ CleanupItem(item, download_file, DownloadItem::IN_PROGRESS);
+}
+
+TEST_F(DownloadItemTest, NotificationAfterTogglePause) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockObserver observer(item);
+ MockDownloadFile* mock_download_file(new MockDownloadFile);
+ scoped_ptr<DownloadFile> download_file(mock_download_file);
+ scoped_ptr<DownloadRequestHandleInterface> request_handle(
+ new NiceMock<MockRequestHandle>);
+
+ EXPECT_CALL(*mock_download_file, Initialize(_));
+ EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _));
+ item->Start(download_file.Pass(), request_handle.Pass());
+
+ item->Pause();
+ ASSERT_TRUE(observer.CheckUpdated());
+
+ ASSERT_TRUE(item->IsPaused());
+
+ item->Resume();
+ ASSERT_TRUE(observer.CheckUpdated());
+
+ RunAllPendingInMessageLoops();
+
+ CleanupItem(item, mock_download_file, DownloadItem::IN_PROGRESS);
+}
+
+TEST_F(DownloadItemTest, DisplayName) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ base::FilePath target_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(target_path.InsertBeforeExtensionASCII("x"));
+ EXPECT_EQ(FILE_PATH_LITERAL(""),
+ item->GetFileNameToReportUser().value());
+ EXPECT_CALL(*download_file, RenameAndUniquify(_, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ intermediate_path));
+ callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(FILE_PATH_LITERAL("foo.bar"),
+ item->GetFileNameToReportUser().value());
+ item->SetDisplayName(base::FilePath(FILE_PATH_LITERAL("new.name")));
+ EXPECT_EQ(FILE_PATH_LITERAL("new.name"),
+ item->GetFileNameToReportUser().value());
+ CleanupItem(item, download_file, DownloadItem::IN_PROGRESS);
+}
+
+// Test to make sure that Start method calls DF initialize properly.
+TEST_F(DownloadItemTest, Start) {
+ MockDownloadFile* mock_download_file(new MockDownloadFile);
+ scoped_ptr<DownloadFile> download_file(mock_download_file);
+ DownloadItemImpl* item = CreateDownloadItem();
+ EXPECT_CALL(*mock_download_file, Initialize(_));
+ scoped_ptr<DownloadRequestHandleInterface> request_handle(
+ new NiceMock<MockRequestHandle>);
+ EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _));
+ item->Start(download_file.Pass(), request_handle.Pass());
+ RunAllPendingInMessageLoops();
+
+ CleanupItem(item, mock_download_file, DownloadItem::IN_PROGRESS);
+}
+
+// Test that the delegate is invoked after the download file is renamed.
+TEST_F(DownloadItemTest, CallbackAfterRename) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ final_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ new_intermediate_path));
+
+ callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ // All the callbacks should have happened by now.
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, RenameAndAnnotate(final_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ final_path));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ RunAllPendingInMessageLoops();
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+}
+
+// Test that the delegate is invoked after the download file is renamed and the
+// download item is in an interrupted state.
+TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ final_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
+ new_intermediate_path));
+ EXPECT_CALL(*download_file, Cancel())
+ .Times(1);
+
+ callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ // All the callbacks should have happened by now.
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+}
+
+TEST_F(DownloadItemTest, Interrupted) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ const DownloadInterruptReason reason(
+ DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED);
+
+ // Confirm interrupt sets state properly.
+ EXPECT_CALL(*download_file, Cancel());
+ item->DestinationObserverAsWeakPtr()->DestinationError(reason);
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ EXPECT_EQ(reason, item->GetLastReason());
+
+ // Cancel should kill it.
+ item->Cancel(true);
+ EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED, item->GetLastReason());
+}
+
+// Destination errors that occur before the intermediate rename shouldn't cause
+// the download to be marked as interrupted until after the intermediate rename.
+TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ final_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ new_intermediate_path));
+ EXPECT_CALL(*download_file, Cancel())
+ .Times(1);
+
+ callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ // All the callbacks should have happened by now.
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ EXPECT_TRUE(item->GetFullPath().empty());
+ EXPECT_EQ(final_path, item->GetTargetFilePath());
+}
+
+// As above. But if the download can be resumed by continuing, then the
+// intermediate path should be retained when the download is interrupted after
+// the intermediate rename succeeds.
+TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ final_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ new_intermediate_path));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath(new_intermediate_path)));
+ EXPECT_CALL(*download_file, Detach());
+
+ callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ // All the callbacks should have happened by now.
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ EXPECT_EQ(new_intermediate_path, item->GetFullPath());
+ EXPECT_EQ(final_path, item->GetTargetFilePath());
+}
+
+// As above. If the intermediate rename fails, then the interrupt reason should
+// be set to the destination error and the intermediate path should be empty.
+TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ DownloadItemImpl* item = CreateDownloadItem();
+ DownloadItemImplDelegate::DownloadTargetCallback callback;
+ MockDownloadFile* download_file =
+ AddDownloadFileToDownloadItem(item, &callback);
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+
+ base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar"));
+ base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x"));
+ base::FilePath new_intermediate_path(
+ final_path.InsertBeforeExtensionASCII("y"));
+ EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
+ new_intermediate_path));
+ EXPECT_CALL(*download_file, Cancel())
+ .Times(1);
+
+ callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+ RunAllPendingInMessageLoops();
+ // All the callbacks should have happened by now.
+ ::testing::Mock::VerifyAndClearExpectations(download_file);
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item->GetLastReason());
+ EXPECT_TRUE(item->GetFullPath().empty());
+ EXPECT_EQ(final_path, item->GetTargetFilePath());
+}
+
+TEST_F(DownloadItemTest, Canceled) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file = AddDownloadFileToDownloadItem(item, NULL);
+
+ // Confirm cancel sets state properly.
+ EXPECT_CALL(*download_file, Cancel());
+ item->Cancel(true);
+ EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
+}
+
+TEST_F(DownloadItemTest, FileRemoved) {
+ DownloadItemImpl* item = CreateDownloadItem();
+
+ EXPECT_FALSE(item->GetFileExternallyRemoved());
+ item->OnDownloadedFileRemoved();
+ EXPECT_TRUE(item->GetFileExternallyRemoved());
+}
+
+TEST_F(DownloadItemTest, DestinationUpdate) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ base::WeakPtr<DownloadDestinationObserver> as_observer(
+ item->DestinationObserverAsWeakPtr());
+ MockObserver observer(item);
+
+ EXPECT_EQ(0l, item->CurrentSpeed());
+ EXPECT_EQ("", item->GetHashState());
+ EXPECT_EQ(0l, item->GetReceivedBytes());
+ EXPECT_EQ(0l, item->GetTotalBytes());
+ EXPECT_FALSE(observer.CheckUpdated());
+ item->SetTotalBytes(100l);
+ EXPECT_EQ(100l, item->GetTotalBytes());
+
+ as_observer->DestinationUpdate(10, 20, "deadbeef");
+ EXPECT_EQ(20l, item->CurrentSpeed());
+ EXPECT_EQ("deadbeef", item->GetHashState());
+ EXPECT_EQ(10l, item->GetReceivedBytes());
+ EXPECT_EQ(100l, item->GetTotalBytes());
+ EXPECT_TRUE(observer.CheckUpdated());
+
+ as_observer->DestinationUpdate(200, 20, "livebeef");
+ EXPECT_EQ(20l, item->CurrentSpeed());
+ EXPECT_EQ("livebeef", item->GetHashState());
+ EXPECT_EQ(200l, item->GetReceivedBytes());
+ EXPECT_EQ(0l, item->GetTotalBytes());
+ EXPECT_TRUE(observer.CheckUpdated());
+}
+
+TEST_F(DownloadItemTest, DestinationError) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+ base::WeakPtr<DownloadDestinationObserver> as_observer(
+ item->DestinationObserverAsWeakPtr());
+ MockObserver observer(item);
+
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, item->GetLastReason());
+ EXPECT_FALSE(observer.CheckUpdated());
+
+ EXPECT_CALL(*download_file, Cancel());
+ as_observer->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED);
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_TRUE(observer.CheckUpdated());
+ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
+ item->GetLastReason());
+}
+
+TEST_F(DownloadItemTest, DestinationCompleted) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ base::WeakPtr<DownloadDestinationObserver> as_observer(
+ item->DestinationObserverAsWeakPtr());
+ MockObserver observer(item);
+
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_EQ("", item->GetHash());
+ EXPECT_EQ("", item->GetHashState());
+ EXPECT_FALSE(item->AllDataSaved());
+ EXPECT_FALSE(observer.CheckUpdated());
+
+ as_observer->DestinationUpdate(10, 20, "deadbeef");
+ EXPECT_TRUE(observer.CheckUpdated());
+ EXPECT_FALSE(observer.CheckUpdated()); // Confirm reset.
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_EQ("", item->GetHash());
+ EXPECT_EQ("deadbeef", item->GetHashState());
+ EXPECT_FALSE(item->AllDataSaved());
+
+ as_observer->DestinationCompleted("livebeef");
+ mock_delegate()->VerifyAndClearExpectations();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_TRUE(observer.CheckUpdated());
+ EXPECT_EQ("livebeef", item->GetHash());
+ EXPECT_EQ("", item->GetHashState());
+ EXPECT_TRUE(item->AllDataSaved());
+}
+
+TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // InProgress
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ ASSERT_FALSE(item->GetTargetFilePath().empty());
+ EXPECT_TRUE(item->CanShowInFolder());
+ EXPECT_TRUE(item->CanOpenDownload());
+
+ // Complete
+ EXPECT_CALL(*download_file, RenameAndAnnotate(_, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ RunAllPendingInMessageLoops();
+
+ ASSERT_EQ(DownloadItem::COMPLETE, item->GetState());
+ EXPECT_TRUE(item->CanShowInFolder());
+ EXPECT_TRUE(item->CanOpenDownload());
+}
+
+TEST_F(DownloadItemTest, EnabledActionsForTemporaryDownload) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+ item->SetIsTemporary(true);
+
+ // InProgress Temporary
+ ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ ASSERT_FALSE(item->GetTargetFilePath().empty());
+ ASSERT_TRUE(item->IsTemporary());
+ EXPECT_FALSE(item->CanShowInFolder());
+ EXPECT_FALSE(item->CanOpenDownload());
+
+ // Complete Temporary
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, RenameAndAnnotate(_, _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ RunAllPendingInMessageLoops();
+
+ ASSERT_EQ(DownloadItem::COMPLETE, item->GetState());
+ EXPECT_FALSE(item->CanShowInFolder());
+ EXPECT_FALSE(item->CanOpenDownload());
+}
+
+TEST_F(DownloadItemTest, EnabledActionsForInterruptedDownload) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ EXPECT_CALL(*download_file, Cancel());
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ RunAllPendingInMessageLoops();
+
+ ASSERT_EQ(DownloadItem::INTERRUPTED, item->GetState());
+ ASSERT_FALSE(item->GetTargetFilePath().empty());
+ EXPECT_FALSE(item->CanShowInFolder());
+ EXPECT_FALSE(item->CanOpenDownload());
+}
+
+TEST_F(DownloadItemTest, EnabledActionsForCancelledDownload) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ EXPECT_CALL(*download_file, Cancel());
+ item->Cancel(true);
+ RunAllPendingInMessageLoops();
+
+ ASSERT_EQ(DownloadItem::CANCELLED, item->GetState());
+ EXPECT_FALSE(item->CanShowInFolder());
+ EXPECT_FALSE(item->CanOpenDownload());
+}
+
+// Test various aspects of the delegate completion blocker.
+
+// Just allowing completion.
+TEST_F(DownloadItemTest, CompleteDelegate_ReturnTrue) {
+ // Test to confirm that if we have a callback that returns true,
+ // we complete immediately.
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Drive the delegate interaction.
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(Return(true));
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_FALSE(item->IsDangerous());
+
+ // Make sure the download can complete.
+ EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::COMPLETE, item->GetState());
+}
+
+// Just delaying completion.
+TEST_F(DownloadItemTest, CompleteDelegate_BlockOnce) {
+ // Test to confirm that if we have a callback that returns true,
+ // we complete immediately.
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Drive the delegate interaction.
+ base::Closure delegate_callback;
+ base::Closure copy_delegate_callback;
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(DoAll(SaveArg<1>(&delegate_callback),
+ Return(false)))
+ .WillOnce(Return(true));
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ ASSERT_FALSE(delegate_callback.is_null());
+ copy_delegate_callback = delegate_callback;
+ delegate_callback.Reset();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ copy_delegate_callback.Run();
+ ASSERT_TRUE(delegate_callback.is_null());
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_FALSE(item->IsDangerous());
+
+ // Make sure the download can complete.
+ EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::COMPLETE, item->GetState());
+}
+
+// Delay and set danger.
+TEST_F(DownloadItemTest, CompleteDelegate_SetDanger) {
+ // Test to confirm that if we have a callback that returns true,
+ // we complete immediately.
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Drive the delegate interaction.
+ base::Closure delegate_callback;
+ base::Closure copy_delegate_callback;
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(DoAll(SaveArg<1>(&delegate_callback),
+ Return(false)))
+ .WillOnce(Return(true));
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ ASSERT_FALSE(delegate_callback.is_null());
+ copy_delegate_callback = delegate_callback;
+ delegate_callback.Reset();
+ EXPECT_FALSE(item->IsDangerous());
+ item->OnContentCheckCompleted(
+ content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ copy_delegate_callback.Run();
+ ASSERT_TRUE(delegate_callback.is_null());
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_TRUE(item->IsDangerous());
+
+ // Make sure the download doesn't complete until we've validated it.
+ EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_TRUE(item->IsDangerous());
+
+ item->ValidateDangerousDownload();
+ EXPECT_EQ(DOWNLOAD_DANGER_TYPE_USER_VALIDATED, item->GetDangerType());
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::COMPLETE, item->GetState());
+}
+
+// Just delaying completion twice.
+TEST_F(DownloadItemTest, CompleteDelegate_BlockTwice) {
+ // Test to confirm that if we have a callback that returns true,
+ // we complete immediately.
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+ // Drive the delegate interaction.
+ base::Closure delegate_callback;
+ base::Closure copy_delegate_callback;
+ EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _))
+ .WillOnce(DoAll(SaveArg<1>(&delegate_callback),
+ Return(false)))
+ .WillOnce(DoAll(SaveArg<1>(&delegate_callback),
+ Return(false)))
+ .WillOnce(Return(true));
+ item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string());
+ ASSERT_FALSE(delegate_callback.is_null());
+ copy_delegate_callback = delegate_callback;
+ delegate_callback.Reset();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ copy_delegate_callback.Run();
+ ASSERT_FALSE(delegate_callback.is_null());
+ copy_delegate_callback = delegate_callback;
+ delegate_callback.Reset();
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ copy_delegate_callback.Run();
+ ASSERT_TRUE(delegate_callback.is_null());
+ EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
+ EXPECT_FALSE(item->IsDangerous());
+
+ // Make sure the download can complete.
+ EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _))
+ .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE,
+ base::FilePath(kDummyPath)));
+ EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(base::FilePath()));
+ EXPECT_CALL(*download_file, Detach());
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(DownloadItem::COMPLETE, item->GetState());
+}
+
+TEST_F(DownloadItemTest, StealDangerousDownload) {
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
+ ASSERT_TRUE(item->IsDangerous());
+ base::FilePath full_path(FILE_PATH_LITERAL("foo.txt"));
+ base::FilePath returned_path;
+
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(full_path));
+ EXPECT_CALL(*download_file, Detach());
+ EXPECT_CALL(*mock_delegate(), DownloadRemoved(_));
+ base::WeakPtrFactory<DownloadItemTest> weak_ptr_factory(this);
+ item->StealDangerousDownload(
+ base::Bind(&DownloadItemTest::OnDownloadFileAcquired,
+ weak_ptr_factory.GetWeakPtr(),
+ base::Unretained(&returned_path)));
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(full_path, returned_path);
+}
+
+TEST_F(DownloadItemTest, StealInterruptedDangerousDownload) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ base::FilePath returned_path;
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
+ base::FilePath full_path = item->GetFullPath();
+ EXPECT_FALSE(full_path.empty());
+ EXPECT_CALL(*download_file, FullPath())
+ .WillOnce(Return(full_path));
+ EXPECT_CALL(*download_file, Detach());
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
+ ASSERT_TRUE(item->IsDangerous());
+
+ EXPECT_CALL(*mock_delegate(), DownloadRemoved(_));
+ base::WeakPtrFactory<DownloadItemTest> weak_ptr_factory(this);
+ item->StealDangerousDownload(
+ base::Bind(&DownloadItemTest::OnDownloadFileAcquired,
+ weak_ptr_factory.GetWeakPtr(),
+ base::Unretained(&returned_path)));
+ RunAllPendingInMessageLoops();
+ EXPECT_EQ(full_path, returned_path);
+}
+
+TEST_F(DownloadItemTest, StealInterruptedNonResumableDangerousDownload) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableDownloadResumption);
+ base::FilePath returned_path;
+ DownloadItemImpl* item = CreateDownloadItem();
+ MockDownloadFile* download_file =
+ DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
+ EXPECT_CALL(*download_file, Cancel());
+ item->DestinationObserverAsWeakPtr()->DestinationError(
+ DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
+ ASSERT_TRUE(item->IsDangerous());
+
+ EXPECT_CALL(*mock_delegate(), DownloadRemoved(_));
+ base::WeakPtrFactory<DownloadItemTest> weak_ptr_factory(this);
+ item->StealDangerousDownload(
+ base::Bind(&DownloadItemTest::OnDownloadFileAcquired,
+ weak_ptr_factory.GetWeakPtr(),
+ base::Unretained(&returned_path)));
+ RunAllPendingInMessageLoops();
+ EXPECT_TRUE(returned_path.empty());
+}
+
+TEST(MockDownloadItem, Compiles) {
+ MockDownloadItem mock_item;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_manager_impl.cc b/chromium/content/browser/download/download_manager_impl.cc
new file mode 100644
index 00000000000..ccfb4cc0a51
--- /dev/null
+++ b/chromium/content/browser/download/download_manager_impl.cc
@@ -0,0 +1,707 @@
+// 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 "content/browser/download/download_manager_impl.h"
+
+#include <iterator>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/debug/alias.h"
+#include "base/i18n/case_conversion.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/supports_user_data.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_item_factory.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/referrer.h"
+#include "net/base/load_flags.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/url_request/url_request_context.h"
+
+namespace content {
+namespace {
+
+void BeginDownload(scoped_ptr<DownloadUrlParameters> params,
+ uint32 download_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // ResourceDispatcherHost{Base} is-not-a URLRequest::Delegate, and
+ // DownloadUrlParameters can-not include resource_dispatcher_host_impl.h, so
+ // we must down cast. RDHI is the only subclass of RDH as of 2012 May 4.
+ scoped_ptr<net::URLRequest> request(
+ params->resource_context()->GetRequestContext()->CreateRequest(
+ params->url(), NULL));
+ request->set_load_flags(request->load_flags() | params->load_flags());
+ request->set_method(params->method());
+ if (!params->post_body().empty()) {
+ const std::string& body = params->post_body();
+ scoped_ptr<net::UploadElementReader> reader(
+ net::UploadOwnedBytesElementReader::CreateWithString(body));
+ request->set_upload(make_scoped_ptr(
+ net::UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ }
+ if (params->post_id() >= 0) {
+ // The POST in this case does not have an actual body, and only works
+ // when retrieving data from cache. This is done because we don't want
+ // to do a re-POST without user consent, and currently don't have a good
+ // plan on how to display the UI for that.
+ DCHECK(params->prefer_cache());
+ DCHECK(params->method() == "POST");
+ ScopedVector<net::UploadElementReader> element_readers;
+ request->set_upload(make_scoped_ptr(
+ new net::UploadDataStream(&element_readers, params->post_id())));
+ }
+
+ // If we're not at the beginning of the file, retrieve only the remaining
+ // portion.
+ bool has_last_modified = !params->last_modified().empty();
+ bool has_etag = !params->etag().empty();
+
+ // If we've asked for a range, we want to make sure that we only
+ // get that range if our current copy of the information is good.
+ // We shouldn't be asked to continue if we don't have a verifier.
+ DCHECK(params->offset() == 0 || has_etag || has_last_modified);
+
+ if (params->offset() > 0) {
+ request->SetExtraRequestHeaderByName(
+ "Range",
+ base::StringPrintf("bytes=%" PRId64 "-", params->offset()),
+ true);
+
+ if (has_last_modified) {
+ request->SetExtraRequestHeaderByName("If-Unmodified-Since",
+ params->last_modified(),
+ true);
+ }
+ if (has_etag) {
+ request->SetExtraRequestHeaderByName("If-Match", params->etag(), true);
+ }
+ }
+
+ for (DownloadUrlParameters::RequestHeadersType::const_iterator iter
+ = params->request_headers_begin();
+ iter != params->request_headers_end();
+ ++iter) {
+ request->SetExtraRequestHeaderByName(
+ iter->first, iter->second, false/*overwrite*/);
+ }
+
+ scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo());
+ save_info->file_path = params->file_path();
+ save_info->suggested_name = params->suggested_name();
+ save_info->offset = params->offset();
+ save_info->hash_state = params->hash_state();
+ save_info->prompt_for_save_location = params->prompt();
+ save_info->file_stream = params->GetFileStream();
+
+ ResourceDispatcherHost::Get()->BeginDownload(
+ request.Pass(),
+ params->referrer(),
+ params->content_initiated(),
+ params->resource_context(),
+ params->render_process_host_id(),
+ params->render_view_host_routing_id(),
+ params->prefer_cache(),
+ save_info.Pass(),
+ download_id,
+ params->callback());
+}
+
+class MapValueIteratorAdapter {
+ public:
+ explicit MapValueIteratorAdapter(
+ base::hash_map<int64, DownloadItem*>::const_iterator iter)
+ : iter_(iter) {
+ }
+ ~MapValueIteratorAdapter() {}
+
+ DownloadItem* operator*() { return iter_->second; }
+
+ MapValueIteratorAdapter& operator++() {
+ ++iter_;
+ return *this;
+ }
+
+ bool operator!=(const MapValueIteratorAdapter& that) const {
+ return iter_ != that.iter_;
+ }
+
+ private:
+ base::hash_map<int64, DownloadItem*>::const_iterator iter_;
+ // Allow copy and assign.
+};
+
+void EnsureNoPendingDownloadJobsOnFile(bool* result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ *result = (DownloadFile::GetNumberOfDownloadFiles() == 0);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+class DownloadItemFactoryImpl : public DownloadItemFactory {
+ public:
+ DownloadItemFactoryImpl() {}
+ virtual ~DownloadItemFactoryImpl() {}
+
+ virtual DownloadItemImpl* CreatePersistedItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log) OVERRIDE {
+ return new DownloadItemImpl(
+ delegate,
+ download_id,
+ current_path,
+ target_path,
+ url_chain,
+ referrer_url,
+ start_time,
+ end_time,
+ etag,
+ last_modified,
+ received_bytes,
+ total_bytes,
+ state,
+ danger_type,
+ interrupt_reason,
+ opened,
+ bound_net_log);
+ }
+
+ virtual DownloadItemImpl* CreateActiveItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log) OVERRIDE {
+ return new DownloadItemImpl(delegate, download_id, info, bound_net_log);
+ }
+
+ virtual DownloadItemImpl* CreateSavePageItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log) OVERRIDE {
+ return new DownloadItemImpl(delegate, download_id, path, url,
+ mime_type, request_handle.Pass(),
+ bound_net_log);
+ }
+};
+
+} // namespace
+
+DownloadManagerImpl::DownloadManagerImpl(
+ net::NetLog* net_log,
+ BrowserContext* browser_context)
+ : item_factory_(new DownloadItemFactoryImpl()),
+ file_factory_(new DownloadFileFactory()),
+ history_size_(0),
+ shutdown_needed_(true),
+ browser_context_(browser_context),
+ delegate_(NULL),
+ net_log_(net_log),
+ weak_factory_(this) {
+ DCHECK(browser_context);
+}
+
+DownloadManagerImpl::~DownloadManagerImpl() {
+ DCHECK(!shutdown_needed_);
+}
+
+DownloadItemImpl* DownloadManagerImpl::CreateActiveItem(
+ uint32 id, const DownloadCreateInfo& info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!ContainsKey(downloads_, id));
+ net::BoundNetLog bound_net_log =
+ net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_DOWNLOAD);
+ DownloadItemImpl* download =
+ item_factory_->CreateActiveItem(this, id, info, bound_net_log);
+ downloads_[id] = download;
+ return download;
+}
+
+void DownloadManagerImpl::GetNextId(const DownloadIdCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (delegate_) {
+ delegate_->GetNextId(callback);
+ return;
+ }
+ static uint32 next_id = content::DownloadItem::kInvalidId + 1;
+ callback.Run(next_id++);
+}
+
+void DownloadManagerImpl::DetermineDownloadTarget(
+ DownloadItemImpl* item, const DownloadTargetCallback& callback) {
+ // Note that this next call relies on
+ // DownloadItemImplDelegate::DownloadTargetCallback and
+ // DownloadManagerDelegate::DownloadTargetCallback having the same
+ // type. If the types ever diverge, gasket code will need to
+ // be written here.
+ if (!delegate_ || !delegate_->DetermineDownloadTarget(item, callback)) {
+ base::FilePath target_path = item->GetForcedFilePath();
+ // TODO(asanka): Determine a useful path if |target_path| is empty.
+ callback.Run(target_path,
+ DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+ target_path);
+ }
+}
+
+bool DownloadManagerImpl::ShouldCompleteDownload(
+ DownloadItemImpl* item, const base::Closure& complete_callback) {
+ if (!delegate_ ||
+ delegate_->ShouldCompleteDownload(item, complete_callback)) {
+ return true;
+ }
+ // Otherwise, the delegate has accepted responsibility to run the
+ // callback when the download is ready for completion.
+ return false;
+}
+
+bool DownloadManagerImpl::ShouldOpenFileBasedOnExtension(
+ const base::FilePath& path) {
+ if (!delegate_)
+ return false;
+
+ return delegate_->ShouldOpenFileBasedOnExtension(path);
+}
+
+bool DownloadManagerImpl::ShouldOpenDownload(
+ DownloadItemImpl* item, const ShouldOpenDownloadCallback& callback) {
+ if (!delegate_)
+ return true;
+
+ // Relies on DownloadItemImplDelegate::ShouldOpenDownloadCallback and
+ // DownloadManagerDelegate::DownloadOpenDelayedCallback "just happening"
+ // to have the same type :-}.
+ return delegate_->ShouldOpenDownload(item, callback);
+}
+
+void DownloadManagerImpl::SetDelegate(DownloadManagerDelegate* delegate) {
+ delegate_ = delegate;
+}
+
+DownloadManagerDelegate* DownloadManagerImpl::GetDelegate() const {
+ return delegate_;
+}
+
+void DownloadManagerImpl::Shutdown() {
+ VLOG(20) << __FUNCTION__ << "()"
+ << " shutdown_needed_ = " << shutdown_needed_;
+ if (!shutdown_needed_)
+ return;
+ shutdown_needed_ = false;
+
+ FOR_EACH_OBSERVER(Observer, observers_, ManagerGoingDown(this));
+ // TODO(benjhayden): Consider clearing observers_.
+
+ // If there are in-progress downloads, cancel them. This also goes for
+ // dangerous downloads which will remain in history if they aren't explicitly
+ // accepted or discarded. Canceling will remove the intermediate download
+ // file.
+ for (DownloadMap::iterator it = downloads_.begin(); it != downloads_.end();
+ ++it) {
+ DownloadItemImpl* download = it->second;
+ if (download->GetState() == DownloadItem::IN_PROGRESS)
+ download->Cancel(false);
+ }
+ STLDeleteValues(&downloads_);
+ downloads_.clear();
+
+ // We'll have nothing more to report to the observers after this point.
+ observers_.Clear();
+
+ if (delegate_)
+ delegate_->Shutdown();
+ delegate_ = NULL;
+}
+
+void DownloadManagerImpl::StartDownload(
+ scoped_ptr<DownloadCreateInfo> info,
+ scoped_ptr<ByteStreamReader> stream,
+ const DownloadUrlParameters::OnStartedCallback& on_started) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(info);
+ uint32 download_id = info->download_id;
+ const bool new_download = (download_id == content::DownloadItem::kInvalidId);
+ base::Callback<void(uint32)> got_id(base::Bind(
+ &DownloadManagerImpl::StartDownloadWithId,
+ weak_factory_.GetWeakPtr(),
+ base::Passed(info.Pass()),
+ base::Passed(stream.Pass()),
+ on_started,
+ new_download));
+ if (new_download) {
+ GetNextId(got_id);
+ } else {
+ got_id.Run(download_id);
+ }
+}
+
+void DownloadManagerImpl::StartDownloadWithId(
+ scoped_ptr<DownloadCreateInfo> info,
+ scoped_ptr<ByteStreamReader> stream,
+ const DownloadUrlParameters::OnStartedCallback& on_started,
+ bool new_download,
+ uint32 id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_NE(content::DownloadItem::kInvalidId, id);
+ DownloadItemImpl* download = NULL;
+ if (new_download) {
+ download = CreateActiveItem(id, *info);
+ } else {
+ DownloadMap::iterator item_iterator = downloads_.find(id);
+ // Trying to resume an interrupted download.
+ if (item_iterator == downloads_.end() ||
+ (item_iterator->second->GetState() == DownloadItem::CANCELLED)) {
+ // If the download is no longer known to the DownloadManager, then it was
+ // removed after it was resumed. Ignore. If the download is cancelled
+ // while resuming, then also ignore the request.
+ info->request_handle.CancelRequest();
+ if (!on_started.is_null())
+ on_started.Run(NULL, net::ERR_ABORTED);
+ return;
+ }
+ download = item_iterator->second;
+ DCHECK_EQ(DownloadItem::INTERRUPTED, download->GetState());
+ }
+
+ base::FilePath default_download_directory;
+ if (delegate_) {
+ base::FilePath website_save_directory; // Unused
+ bool skip_dir_check = false; // Unused
+ delegate_->GetSaveDir(GetBrowserContext(), &website_save_directory,
+ &default_download_directory, &skip_dir_check);
+ }
+
+ // Create the download file and start the download.
+ scoped_ptr<DownloadFile> download_file(
+ file_factory_->CreateFile(
+ info->save_info.Pass(), default_download_directory,
+ info->url(), info->referrer_url,
+ delegate_->GenerateFileHash(),
+ stream.Pass(), download->GetBoundNetLog(),
+ download->DestinationObserverAsWeakPtr()));
+
+ // Attach the client ID identifying the app to the AV system.
+ if (download_file.get() && delegate_) {
+ download_file->SetClientGuid(
+ delegate_->ApplicationClientIdForFileScanning());
+ }
+
+ scoped_ptr<DownloadRequestHandleInterface> req_handle(
+ new DownloadRequestHandle(info->request_handle));
+ download->Start(download_file.Pass(), req_handle.Pass());
+
+ // For interrupted downloads, Start() will transition the state to
+ // IN_PROGRESS and consumers will be notified via OnDownloadUpdated().
+ // For new downloads, we notify here, rather than earlier, so that
+ // the download_file is bound to download and all the usual
+ // setters (e.g. Cancel) work.
+ if (new_download)
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadCreated(this, download));
+
+ if (!on_started.is_null())
+ on_started.Run(download, net::OK);
+}
+
+void DownloadManagerImpl::CheckForHistoryFilesRemoval() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ for (DownloadMap::iterator it = downloads_.begin();
+ it != downloads_.end(); ++it) {
+ DownloadItemImpl* item = it->second;
+ CheckForFileRemoval(item);
+ }
+}
+
+void DownloadManagerImpl::CheckForFileRemoval(DownloadItemImpl* download_item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if ((download_item->GetState() == DownloadItem::COMPLETE) &&
+ !download_item->GetFileExternallyRemoved() &&
+ delegate_) {
+ delegate_->CheckForFileExistence(
+ download_item,
+ base::Bind(&DownloadManagerImpl::OnFileExistenceChecked,
+ weak_factory_.GetWeakPtr(), download_item->GetId()));
+ }
+}
+
+void DownloadManagerImpl::OnFileExistenceChecked(uint32 download_id,
+ bool result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!result) { // File does not exist.
+ if (ContainsKey(downloads_, download_id))
+ downloads_[download_id]->OnDownloadedFileRemoved();
+ }
+}
+
+BrowserContext* DownloadManagerImpl::GetBrowserContext() const {
+ return browser_context_;
+}
+
+void DownloadManagerImpl::CreateSavePackageDownloadItem(
+ const base::FilePath& main_file_path,
+ const GURL& page_url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const DownloadItemImplCreated& item_created) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ GetNextId(base::Bind(
+ &DownloadManagerImpl::CreateSavePackageDownloadItemWithId,
+ weak_factory_.GetWeakPtr(),
+ main_file_path,
+ page_url,
+ mime_type,
+ base::Passed(request_handle.Pass()),
+ item_created));
+}
+
+void DownloadManagerImpl::CreateSavePackageDownloadItemWithId(
+ const base::FilePath& main_file_path,
+ const GURL& page_url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const DownloadItemImplCreated& item_created,
+ uint32 id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_NE(content::DownloadItem::kInvalidId, id);
+ DCHECK(!ContainsKey(downloads_, id));
+ net::BoundNetLog bound_net_log =
+ net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_DOWNLOAD);
+ DownloadItemImpl* download_item = item_factory_->CreateSavePageItem(
+ this,
+ id,
+ main_file_path,
+ page_url,
+ mime_type,
+ request_handle.Pass(),
+ bound_net_log);
+ downloads_[download_item->GetId()] = download_item;
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadCreated(
+ this, download_item));
+ if (!item_created.is_null())
+ item_created.Run(download_item);
+}
+
+void DownloadManagerImpl::OnSavePackageSuccessfullyFinished(
+ DownloadItem* download_item) {
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnSavePackageSuccessfullyFinished(this, download_item));
+}
+
+// Resume a download of a specific URL. We send the request to the
+// ResourceDispatcherHost, and let it send us responses like a regular
+// download.
+void DownloadManagerImpl::ResumeInterruptedDownload(
+ scoped_ptr<content::DownloadUrlParameters> params,
+ uint32 id) {
+ RecordDownloadSource(INITIATED_BY_RESUMPTION);
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&BeginDownload, base::Passed(&params), id));
+}
+
+void DownloadManagerImpl::SetDownloadItemFactoryForTesting(
+ scoped_ptr<DownloadItemFactory> item_factory) {
+ item_factory_ = item_factory.Pass();
+}
+
+void DownloadManagerImpl::SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory> file_factory) {
+ file_factory_ = file_factory.Pass();
+}
+
+DownloadFileFactory* DownloadManagerImpl::GetDownloadFileFactoryForTesting() {
+ return file_factory_.get();
+}
+
+void DownloadManagerImpl::DownloadRemoved(DownloadItemImpl* download) {
+ if (!download)
+ return;
+
+ uint32 download_id = download->GetId();
+ if (downloads_.find(download_id) == downloads_.end())
+ return;
+
+ delete download;
+ downloads_.erase(download_id);
+}
+
+int DownloadManagerImpl::RemoveDownloadsBetween(base::Time remove_begin,
+ base::Time remove_end) {
+ int count = 0;
+ DownloadMap::const_iterator it = downloads_.begin();
+ while (it != downloads_.end()) {
+ DownloadItemImpl* download = it->second;
+
+ // Increment done here to protect against invalidation below.
+ ++it;
+
+ if (download->GetStartTime() >= remove_begin &&
+ (remove_end.is_null() || download->GetStartTime() < remove_end) &&
+ (download->GetState() != DownloadItem::IN_PROGRESS)) {
+ // Erases the download from downloads_.
+ download->Remove();
+ count++;
+ }
+ }
+ return count;
+}
+
+int DownloadManagerImpl::RemoveDownloads(base::Time remove_begin) {
+ return RemoveDownloadsBetween(remove_begin, base::Time());
+}
+
+int DownloadManagerImpl::RemoveAllDownloads() {
+ // The null times make the date range unbounded.
+ int num_deleted = RemoveDownloadsBetween(base::Time(), base::Time());
+ RecordClearAllSize(num_deleted);
+ return num_deleted;
+}
+
+void DownloadManagerImpl::DownloadUrl(
+ scoped_ptr<DownloadUrlParameters> params) {
+ if (params->post_id() >= 0) {
+ // Check this here so that the traceback is more useful.
+ DCHECK(params->prefer_cache());
+ DCHECK(params->method() == "POST");
+ }
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &BeginDownload, base::Passed(&params),
+ content::DownloadItem::kInvalidId));
+}
+
+void DownloadManagerImpl::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DownloadManagerImpl::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+DownloadItem* DownloadManagerImpl::CreateDownloadItem(
+ uint32 id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened) {
+ DCHECK(!ContainsKey(downloads_, id));
+ if (ContainsKey(downloads_, id))
+ return NULL;
+ DownloadItemImpl* item = item_factory_->CreatePersistedItem(
+ this,
+ id,
+ current_path,
+ target_path,
+ url_chain,
+ referrer_url,
+ start_time,
+ end_time,
+ etag,
+ last_modified,
+ received_bytes,
+ total_bytes,
+ state,
+ danger_type,
+ interrupt_reason,
+ opened,
+ net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_DOWNLOAD));
+ downloads_[id] = item;
+ FOR_EACH_OBSERVER(Observer, observers_, OnDownloadCreated(this, item));
+ VLOG(20) << __FUNCTION__ << "() download = " << item->DebugString(true);
+ return item;
+}
+
+int DownloadManagerImpl::InProgressCount() const {
+ int count = 0;
+ for (DownloadMap::const_iterator it = downloads_.begin();
+ it != downloads_.end(); ++it) {
+ if (it->second->GetState() == DownloadItem::IN_PROGRESS)
+ ++count;
+ }
+ return count;
+}
+
+DownloadItem* DownloadManagerImpl::GetDownload(uint32 download_id) {
+ return ContainsKey(downloads_, download_id) ? downloads_[download_id] : NULL;
+}
+
+void DownloadManagerImpl::GetAllDownloads(DownloadVector* downloads) {
+ for (DownloadMap::iterator it = downloads_.begin();
+ it != downloads_.end(); ++it) {
+ downloads->push_back(it->second);
+ }
+}
+
+void DownloadManagerImpl::OpenDownload(DownloadItemImpl* download) {
+ int num_unopened = 0;
+ for (DownloadMap::iterator it = downloads_.begin();
+ it != downloads_.end(); ++it) {
+ DownloadItemImpl* item = it->second;
+ if ((item->GetState() == DownloadItem::COMPLETE) &&
+ !item->GetOpened())
+ ++num_unopened;
+ }
+ RecordOpensOutstanding(num_unopened);
+
+ if (delegate_)
+ delegate_->OpenDownload(download);
+}
+
+void DownloadManagerImpl::ShowDownloadInShell(DownloadItemImpl* download) {
+ if (delegate_)
+ delegate_->ShowDownloadInShell(download);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_manager_impl.h b/chromium/content/browser/download/download_manager_impl.h
new file mode 100644
index 00000000000..a9384c7567f
--- /dev/null
+++ b/chromium/content/browser/download/download_manager_impl.h
@@ -0,0 +1,195 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_IMPL_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_IMPL_H_
+
+#include <map>
+#include <set>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/download/download_item_impl_delegate.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/browser/download_url_parameters.h"
+
+namespace net {
+class BoundNetLog;
+}
+
+namespace content {
+class DownloadFileFactory;
+class DownloadItemFactory;
+class DownloadItemImpl;
+class DownloadRequestHandleInterface;
+
+class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager,
+ private DownloadItemImplDelegate {
+ public:
+ typedef base::Callback<void(DownloadItemImpl*)> DownloadItemImplCreated;
+
+ // Caller guarantees that |net_log| will remain valid
+ // for the lifetime of DownloadManagerImpl (until Shutdown() is called).
+ DownloadManagerImpl(net::NetLog* net_log, BrowserContext* browser_context);
+ virtual ~DownloadManagerImpl();
+
+ // Implementation functions (not part of the DownloadManager interface).
+
+ // Creates a download item for the SavePackage system.
+ // Must be called on the UI thread. Note that the DownloadManager
+ // retains ownership.
+ virtual void CreateSavePackageDownloadItem(
+ const base::FilePath& main_file_path,
+ const GURL& page_url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const DownloadItemImplCreated& item_created);
+
+ // Notifies DownloadManager about a successful completion of |download_item|.
+ void OnSavePackageSuccessfullyFinished(DownloadItem* download_item);
+
+ // DownloadManager functions.
+ virtual void SetDelegate(DownloadManagerDelegate* delegate) OVERRIDE;
+ virtual DownloadManagerDelegate* GetDelegate() const OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+ virtual void GetAllDownloads(DownloadVector* result) OVERRIDE;
+ virtual void StartDownload(
+ scoped_ptr<DownloadCreateInfo> info,
+ scoped_ptr<ByteStreamReader> stream,
+ const DownloadUrlParameters::OnStartedCallback& on_started) OVERRIDE;
+ virtual int RemoveDownloadsBetween(base::Time remove_begin,
+ base::Time remove_end) OVERRIDE;
+ virtual int RemoveDownloads(base::Time remove_begin) OVERRIDE;
+ virtual int RemoveAllDownloads() OVERRIDE;
+ virtual void DownloadUrl(scoped_ptr<DownloadUrlParameters> params) OVERRIDE;
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual content::DownloadItem* CreateDownloadItem(
+ uint32 id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ content::DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened) OVERRIDE;
+ virtual int InProgressCount() const OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual void CheckForHistoryFilesRemoval() OVERRIDE;
+ virtual DownloadItem* GetDownload(uint32 id) OVERRIDE;
+
+ // For testing; specifically, accessed from TestFileErrorInjector.
+ void SetDownloadItemFactoryForTesting(
+ scoped_ptr<DownloadItemFactory> item_factory);
+ void SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory> file_factory);
+ virtual DownloadFileFactory* GetDownloadFileFactoryForTesting();
+
+ private:
+ typedef std::set<DownloadItem*> DownloadSet;
+ typedef base::hash_map<uint32, DownloadItemImpl*> DownloadMap;
+ typedef std::vector<DownloadItemImpl*> DownloadItemImplVector;
+
+ // For testing.
+ friend class DownloadManagerTest;
+ friend class DownloadTest;
+
+ void StartDownloadWithId(
+ scoped_ptr<DownloadCreateInfo> info,
+ scoped_ptr<ByteStreamReader> stream,
+ const DownloadUrlParameters::OnStartedCallback& on_started,
+ bool new_download,
+ uint32 id);
+
+ void CreateSavePackageDownloadItemWithId(
+ const base::FilePath& main_file_path,
+ const GURL& page_url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const DownloadItemImplCreated& on_started,
+ uint32 id);
+
+ // Create a new active item based on the info. Separate from
+ // StartDownload() for testing.
+ DownloadItemImpl* CreateActiveItem(uint32 id,
+ const DownloadCreateInfo& info);
+
+ // Get next download id. |callback| is called on the UI thread and may
+ // be called synchronously.
+ void GetNextId(const DownloadIdCallback& callback);
+
+ // Called with the result of DownloadManagerDelegate::CheckForFileExistence.
+ // Updates the state of the file and then notifies this update to the file's
+ // observer.
+ void OnFileExistenceChecked(uint32 download_id, bool result);
+
+ // Overridden from DownloadItemImplDelegate
+ // (Note that |GetBrowserContext| are present in both interfaces.)
+ virtual void DetermineDownloadTarget(
+ DownloadItemImpl* item, const DownloadTargetCallback& callback) OVERRIDE;
+ virtual bool ShouldCompleteDownload(
+ DownloadItemImpl* item, const base::Closure& complete_callback) OVERRIDE;
+ virtual bool ShouldOpenFileBasedOnExtension(
+ const base::FilePath& path) OVERRIDE;
+ virtual bool ShouldOpenDownload(
+ DownloadItemImpl* item,
+ const ShouldOpenDownloadCallback& callback) OVERRIDE;
+ virtual void CheckForFileRemoval(DownloadItemImpl* download_item) OVERRIDE;
+ virtual void ResumeInterruptedDownload(
+ scoped_ptr<content::DownloadUrlParameters> params,
+ uint32 id) OVERRIDE;
+ virtual void OpenDownload(DownloadItemImpl* download) OVERRIDE;
+ virtual void ShowDownloadInShell(DownloadItemImpl* download) OVERRIDE;
+ virtual void DownloadRemoved(DownloadItemImpl* download) OVERRIDE;
+
+ // Factory for creation of downloads items.
+ scoped_ptr<DownloadItemFactory> item_factory_;
+
+ // Factory for the creation of download files.
+ scoped_ptr<DownloadFileFactory> file_factory_;
+
+ // |downloads_| is the owning set for all downloads known to the
+ // DownloadManager. This includes downloads started by the user in
+ // this session, downloads initialized from the history system, and
+ // "save page as" downloads.
+ DownloadMap downloads_;
+
+ int history_size_;
+
+ // True if the download manager has been initialized and requires a shutdown.
+ bool shutdown_needed_;
+
+ // Observers that want to be notified of changes to the set of downloads.
+ ObserverList<Observer> observers_;
+
+ // The current active browser context.
+ BrowserContext* browser_context_;
+
+ // Allows an embedder to control behavior. Guaranteed to outlive this object.
+ DownloadManagerDelegate* delegate_;
+
+ net::NetLog* net_log_;
+
+ base::WeakPtrFactory<DownloadManagerImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadManagerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_IMPL_H_
diff --git a/chromium/content/browser/download/download_manager_impl_unittest.cc b/chromium/content/browser/download/download_manager_impl_unittest.cc
new file mode 100644
index 00000000000..0865c04b2fa
--- /dev/null
+++ b/chromium/content/browser/download/download_manager_impl_unittest.cc
@@ -0,0 +1,685 @@
+// 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 <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_item_factory.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_item_impl_delegate.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/browser/download/mock_download_file.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/test/mock_download_item.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AllOf;
+using ::testing::DoAll;
+using ::testing::Eq;
+using ::testing::Ref;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SetArgPointee;
+using ::testing::StrictMock;
+using ::testing::_;
+
+ACTION_TEMPLATE(RunCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p0)) {
+ return ::std::tr1::get<k>(args).Run(p0);
+}
+
+namespace content {
+class ByteStreamReader;
+
+namespace {
+
+// Matches a DownloadCreateInfo* that points to the same object as |info| and
+// has a |default_download_directory| that matches |download_directory|.
+MATCHER_P2(DownloadCreateInfoWithDefaultPath, info, download_directory, "") {
+ return arg == info &&
+ arg->default_download_directory == download_directory;
+}
+
+class MockDownloadItemImpl : public DownloadItemImpl {
+ public:
+ // Use history constructor for minimal base object.
+ explicit MockDownloadItemImpl(DownloadItemImplDelegate* delegate)
+ : DownloadItemImpl(
+ delegate,
+ content::DownloadItem::kInvalidId,
+ base::FilePath(),
+ base::FilePath(),
+ std::vector<GURL>(),
+ GURL(),
+ base::Time(),
+ base::Time(),
+ std::string(),
+ std::string(),
+ 0,
+ 0,
+ DownloadItem::COMPLETE,
+ DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+ DOWNLOAD_INTERRUPT_REASON_NONE,
+ false,
+ net::BoundNetLog()) {}
+ virtual ~MockDownloadItemImpl() {}
+
+ MOCK_METHOD4(OnDownloadTargetDetermined,
+ void(const base::FilePath&, TargetDisposition,
+ DownloadDangerType, const base::FilePath&));
+ MOCK_METHOD1(AddObserver, void(DownloadItem::Observer*));
+ MOCK_METHOD1(RemoveObserver, void(DownloadItem::Observer*));
+ MOCK_METHOD0(UpdateObservers, void());
+ MOCK_METHOD0(CanShowInFolder, bool());
+ MOCK_METHOD0(CanOpenDownload, bool());
+ MOCK_METHOD0(ShouldOpenFileBasedOnExtension, bool());
+ MOCK_METHOD0(OpenDownload, void());
+ MOCK_METHOD0(ShowDownloadInShell, void());
+ MOCK_METHOD0(ValidateDangerousDownload, void());
+ MOCK_METHOD1(StealDangerousDownload, void(const AcquireFileCallback&));
+ MOCK_METHOD3(UpdateProgress, void(int64, int64, const std::string&));
+ MOCK_METHOD1(Cancel, void(bool));
+ MOCK_METHOD0(MarkAsComplete, void());
+ MOCK_METHOD1(OnAllDataSaved, void(const std::string&));
+ MOCK_METHOD0(OnDownloadedFileRemoved, void());
+ virtual void Start(
+ scoped_ptr<DownloadFile> download_file,
+ scoped_ptr<DownloadRequestHandleInterface> req_handle) OVERRIDE {
+ MockStart(download_file.get(), req_handle.get());
+ }
+
+ MOCK_METHOD2(MockStart, void(DownloadFile*, DownloadRequestHandleInterface*));
+
+ MOCK_METHOD0(Remove, void());
+ MOCK_CONST_METHOD1(TimeRemaining, bool(base::TimeDelta*));
+ MOCK_CONST_METHOD0(CurrentSpeed, int64());
+ MOCK_CONST_METHOD0(PercentComplete, int());
+ MOCK_CONST_METHOD0(AllDataSaved, bool());
+ MOCK_CONST_METHOD1(MatchesQuery, bool(const string16& query));
+ MOCK_CONST_METHOD0(IsDone, bool());
+ MOCK_CONST_METHOD0(GetFullPath, const base::FilePath&());
+ MOCK_CONST_METHOD0(GetTargetFilePath, const base::FilePath&());
+ MOCK_CONST_METHOD0(GetTargetDisposition, TargetDisposition());
+ MOCK_METHOD1(OnContentCheckCompleted, void(DownloadDangerType));
+ MOCK_CONST_METHOD0(GetState, DownloadState());
+ MOCK_CONST_METHOD0(GetUrlChain, const std::vector<GURL>&());
+ MOCK_METHOD1(SetTotalBytes, void(int64));
+ MOCK_CONST_METHOD0(GetURL, const GURL&());
+ MOCK_CONST_METHOD0(GetOriginalUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetReferrerUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetSuggestedFilename, std::string());
+ MOCK_CONST_METHOD0(GetContentDisposition, std::string());
+ MOCK_CONST_METHOD0(GetMimeType, std::string());
+ MOCK_CONST_METHOD0(GetOriginalMimeType, std::string());
+ MOCK_CONST_METHOD0(GetReferrerCharset, std::string());
+ MOCK_CONST_METHOD0(GetRemoteAddress, std::string());
+ MOCK_CONST_METHOD0(GetTotalBytes, int64());
+ MOCK_CONST_METHOD0(GetReceivedBytes, int64());
+ MOCK_CONST_METHOD0(GetHashState, const std::string&());
+ MOCK_CONST_METHOD0(GetHash, const std::string&());
+ MOCK_CONST_METHOD0(GetId, uint32());
+ MOCK_CONST_METHOD0(GetStartTime, base::Time());
+ MOCK_CONST_METHOD0(GetEndTime, base::Time());
+ MOCK_METHOD0(GetDownloadManager, DownloadManager*());
+ MOCK_CONST_METHOD0(IsPaused, bool());
+ MOCK_CONST_METHOD0(GetOpenWhenComplete, bool());
+ MOCK_METHOD1(SetOpenWhenComplete, void(bool));
+ MOCK_CONST_METHOD0(GetFileExternallyRemoved, bool());
+ MOCK_CONST_METHOD0(GetDangerType, DownloadDangerType());
+ MOCK_CONST_METHOD0(IsDangerous, bool());
+ MOCK_METHOD0(GetAutoOpened, bool());
+ MOCK_CONST_METHOD0(GetForcedFilePath, const base::FilePath&());
+ MOCK_CONST_METHOD0(HasUserGesture, bool());
+ MOCK_CONST_METHOD0(GetTransitionType, PageTransition());
+ MOCK_CONST_METHOD0(IsTemporary, bool());
+ MOCK_METHOD1(SetIsTemporary, void(bool));
+ MOCK_METHOD1(SetOpened, void(bool));
+ MOCK_CONST_METHOD0(GetOpened, bool());
+ MOCK_CONST_METHOD0(GetLastModifiedTime, const std::string&());
+ MOCK_CONST_METHOD0(GetETag, const std::string&());
+ MOCK_CONST_METHOD0(GetLastReason, DownloadInterruptReason());
+ MOCK_CONST_METHOD0(GetBrowserContext, BrowserContext*());
+ MOCK_CONST_METHOD0(GetWebContents, WebContents*());
+ MOCK_CONST_METHOD0(GetFileNameToReportUser, base::FilePath());
+ MOCK_METHOD1(SetDisplayName, void(const base::FilePath&));
+ MOCK_METHOD0(NotifyRemoved, void());
+ // May be called when vlog is on.
+ virtual std::string DebugString(bool verbose) const OVERRIDE {
+ return std::string();
+ }
+};
+
+class MockDownloadManagerDelegate : public DownloadManagerDelegate {
+ public:
+ MockDownloadManagerDelegate();
+ virtual ~MockDownloadManagerDelegate();
+
+ MOCK_METHOD0(Shutdown, void());
+ MOCK_METHOD1(GetNextId, void(const DownloadIdCallback&));
+ MOCK_METHOD2(DetermineDownloadTarget,
+ bool(DownloadItem* item,
+ const DownloadTargetCallback&));
+ MOCK_METHOD1(ShouldOpenFileBasedOnExtension, bool(const base::FilePath&));
+ MOCK_METHOD2(ShouldCompleteDownload,
+ bool(DownloadItem*, const base::Closure&));
+ MOCK_METHOD2(ShouldOpenDownload,
+ bool(DownloadItem*, const DownloadOpenDelayedCallback&));
+ MOCK_METHOD0(GenerateFileHash, bool());
+ MOCK_METHOD4(GetSaveDir, void(BrowserContext*,
+ base::FilePath*, base::FilePath*, bool*));
+ MOCK_METHOD5(ChooseSavePath, void(
+ WebContents*, const base::FilePath&, const base::FilePath::StringType&,
+ bool, const SavePackagePathPickedCallback&));
+ MOCK_CONST_METHOD0(ApplicationClientIdForFileScanning, std::string());
+};
+
+MockDownloadManagerDelegate::MockDownloadManagerDelegate() {}
+
+MockDownloadManagerDelegate::~MockDownloadManagerDelegate() {}
+
+class MockDownloadItemFactory
+ : public DownloadItemFactory,
+ public base::SupportsWeakPtr<MockDownloadItemFactory> {
+ public:
+ MockDownloadItemFactory();
+ virtual ~MockDownloadItemFactory();
+
+ // Access to map of created items.
+ // TODO(rdsmith): Could add type (save page, persisted, etc.)
+ // functionality if it's ever needed by consumers.
+
+ // Returns NULL if no item of that id is present.
+ MockDownloadItemImpl* GetItem(int id);
+
+ // Remove and return an item made by the factory.
+ // Generally used during teardown.
+ MockDownloadItemImpl* PopItem();
+
+ // Should be called when the item of this id is removed so that
+ // we don't keep dangling pointers.
+ void RemoveItem(int id);
+
+ // Overridden methods from DownloadItemFactory.
+ virtual DownloadItemImpl* CreatePersistedItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modofied,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log) OVERRIDE;
+ virtual DownloadItemImpl* CreateActiveItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log) OVERRIDE;
+ virtual DownloadItemImpl* CreateSavePageItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log) OVERRIDE;
+
+ private:
+ std::map<uint32, MockDownloadItemImpl*> items_;
+ DownloadItemImplDelegate item_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockDownloadItemFactory);
+};
+
+MockDownloadItemFactory::MockDownloadItemFactory() {}
+
+MockDownloadItemFactory::~MockDownloadItemFactory() {}
+
+MockDownloadItemImpl* MockDownloadItemFactory::GetItem(int id) {
+ if (items_.find(id) == items_.end())
+ return NULL;
+ return items_[id];
+}
+
+MockDownloadItemImpl* MockDownloadItemFactory::PopItem() {
+ if (items_.empty())
+ return NULL;
+
+ std::map<uint32, MockDownloadItemImpl*>::iterator first_item
+ = items_.begin();
+ MockDownloadItemImpl* result = first_item->second;
+ items_.erase(first_item);
+ return result;
+}
+
+void MockDownloadItemFactory::RemoveItem(int id) {
+ DCHECK(items_.find(id) != items_.end());
+ items_.erase(id);
+}
+
+DownloadItemImpl* MockDownloadItemFactory::CreatePersistedItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64 received_bytes,
+ int64 total_bytes,
+ DownloadItem::DownloadState state,
+ DownloadDangerType danger_type,
+ DownloadInterruptReason interrupt_reason,
+ bool opened,
+ const net::BoundNetLog& bound_net_log) {
+ DCHECK(items_.find(download_id) == items_.end());
+ MockDownloadItemImpl* result =
+ new StrictMock<MockDownloadItemImpl>(&item_delegate_);
+ EXPECT_CALL(*result, GetId())
+ .WillRepeatedly(Return(download_id));
+ items_[download_id] = result;
+ return result;
+}
+
+DownloadItemImpl* MockDownloadItemFactory::CreateActiveItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const DownloadCreateInfo& info,
+ const net::BoundNetLog& bound_net_log) {
+ DCHECK(items_.find(download_id) == items_.end());
+
+ MockDownloadItemImpl* result =
+ new StrictMock<MockDownloadItemImpl>(&item_delegate_);
+ EXPECT_CALL(*result, GetId())
+ .WillRepeatedly(Return(download_id));
+ items_[download_id] = result;
+
+ // Active items are created and then immediately are called to start
+ // the download.
+ EXPECT_CALL(*result, MockStart(_, _));
+
+ return result;
+}
+
+DownloadItemImpl* MockDownloadItemFactory::CreateSavePageItem(
+ DownloadItemImplDelegate* delegate,
+ uint32 download_id,
+ const base::FilePath& path,
+ const GURL& url,
+ const std::string& mime_type,
+ scoped_ptr<DownloadRequestHandleInterface> request_handle,
+ const net::BoundNetLog& bound_net_log) {
+ DCHECK(items_.find(download_id) == items_.end());
+
+ MockDownloadItemImpl* result =
+ new StrictMock<MockDownloadItemImpl>(&item_delegate_);
+ EXPECT_CALL(*result, GetId())
+ .WillRepeatedly(Return(download_id));
+ items_[download_id] = result;
+
+ return result;
+}
+
+class MockDownloadFileFactory
+ : public DownloadFileFactory,
+ public base::SupportsWeakPtr<MockDownloadFileFactory> {
+ public:
+ MockDownloadFileFactory() {}
+ virtual ~MockDownloadFileFactory() {}
+
+ // Overridden method from DownloadFileFactory
+ MOCK_METHOD8(MockCreateFile, MockDownloadFile*(
+ const DownloadSaveInfo&,
+ const base::FilePath&,
+ const GURL&, const GURL&, bool,
+ ByteStreamReader*,
+ const net::BoundNetLog&,
+ base::WeakPtr<DownloadDestinationObserver>));
+
+ virtual DownloadFile* CreateFile(
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const base::FilePath& default_download_directory,
+ const GURL& url,
+ const GURL& referrer_url,
+ bool calculate_hash,
+ scoped_ptr<ByteStreamReader> stream,
+ const net::BoundNetLog& bound_net_log,
+ base::WeakPtr<DownloadDestinationObserver> observer) {
+ return MockCreateFile(*save_info.get(), default_download_directory, url,
+ referrer_url, calculate_hash,
+ stream.get(), bound_net_log, observer);
+ }
+};
+
+class MockBrowserContext : public BrowserContext {
+ public:
+ MockBrowserContext() {}
+ ~MockBrowserContext() {}
+
+ MOCK_CONST_METHOD0(GetPath, base::FilePath());
+ MOCK_CONST_METHOD0(IsOffTheRecord, bool());
+ MOCK_METHOD0(GetRequestContext, net::URLRequestContextGetter*());
+ MOCK_METHOD1(GetRequestContextForRenderProcess,
+ net::URLRequestContextGetter*(int renderer_child_id));
+ MOCK_METHOD0(GetMediaRequestContext,
+ net::URLRequestContextGetter*());
+ MOCK_METHOD1(GetMediaRequestContextForRenderProcess,
+ net::URLRequestContextGetter*(int renderer_child_id));
+ MOCK_METHOD2(GetMediaRequestContextForStoragePartition,
+ net::URLRequestContextGetter*(
+ const base::FilePath& partition_path, bool in_memory));
+ MOCK_METHOD4(RequestMIDISysExPermission,
+ void(int render_process_id,
+ int render_view_id,
+ const GURL& requesting_frame,
+ const MIDISysExPermissionCallback& callback));
+ MOCK_METHOD0(GetResourceContext, ResourceContext*());
+ MOCK_METHOD0(GetDownloadManagerDelegate, DownloadManagerDelegate*());
+ MOCK_METHOD0(GetGeolocationPermissionContext,
+ GeolocationPermissionContext* ());
+ MOCK_METHOD0(GetSpecialStoragePolicy, quota::SpecialStoragePolicy*());
+};
+
+class MockDownloadManagerObserver : public DownloadManager::Observer {
+ public:
+ MockDownloadManagerObserver() {}
+ ~MockDownloadManagerObserver() {}
+ MOCK_METHOD2(OnDownloadCreated, void(
+ DownloadManager*, DownloadItem*));
+ MOCK_METHOD1(ManagerGoingDown, void(DownloadManager*));
+ MOCK_METHOD2(SelectFileDialogDisplayed, void(
+ DownloadManager*, int32));
+};
+
+} // namespace
+
+class DownloadManagerTest : public testing::Test {
+ public:
+ static const char* kTestData;
+ static const size_t kTestDataLen;
+
+ DownloadManagerTest()
+ : callback_called_(false),
+ ui_thread_(BrowserThread::UI, &message_loop_),
+ file_thread_(BrowserThread::FILE, &message_loop_),
+ next_download_id_(0) {
+ }
+
+ // We tear down everything in TearDown().
+ virtual ~DownloadManagerTest() {}
+
+ // Create a MockDownloadItemFactory and MockDownloadManagerDelegate,
+ // then create a DownloadManager that points
+ // at all of those.
+ virtual void SetUp() {
+ DCHECK(!download_manager_);
+
+ mock_download_item_factory_ = (new MockDownloadItemFactory())->AsWeakPtr();
+ mock_download_file_factory_ = (new MockDownloadFileFactory())->AsWeakPtr();
+ mock_download_manager_delegate_.reset(
+ new StrictMock<MockDownloadManagerDelegate>);
+ EXPECT_CALL(*mock_download_manager_delegate_.get(), Shutdown())
+ .WillOnce(Return());
+ mock_browser_context_.reset(new StrictMock<MockBrowserContext>);
+ EXPECT_CALL(*mock_browser_context_.get(), IsOffTheRecord())
+ .WillRepeatedly(Return(false));
+
+ download_manager_.reset(new DownloadManagerImpl(
+ NULL, mock_browser_context_.get()));
+ download_manager_->SetDownloadItemFactoryForTesting(
+ scoped_ptr<DownloadItemFactory>(
+ mock_download_item_factory_.get()).Pass());
+ download_manager_->SetDownloadFileFactoryForTesting(
+ scoped_ptr<DownloadFileFactory>(
+ mock_download_file_factory_.get()).Pass());
+ observer_.reset(new MockDownloadManagerObserver());
+ download_manager_->AddObserver(observer_.get());
+ download_manager_->SetDelegate(mock_download_manager_delegate_.get());
+ }
+
+ virtual void TearDown() {
+ while (MockDownloadItemImpl*
+ item = mock_download_item_factory_->PopItem()) {
+ EXPECT_CALL(*item, GetState())
+ .WillOnce(Return(DownloadItem::CANCELLED));
+ }
+ EXPECT_CALL(GetMockObserver(), ManagerGoingDown(download_manager_.get()))
+ .WillOnce(Return());
+
+ download_manager_->Shutdown();
+ download_manager_.reset();
+ message_loop_.RunUntilIdle();
+ ASSERT_EQ(NULL, mock_download_item_factory_.get());
+ ASSERT_EQ(NULL, mock_download_file_factory_.get());
+ message_loop_.RunUntilIdle();
+ mock_download_manager_delegate_.reset();
+ mock_browser_context_.reset();
+ }
+
+ // Returns download id.
+ MockDownloadItemImpl& AddItemToManager() {
+ DownloadCreateInfo info;
+
+ // Args are ignored except for download id, so everything else can be
+ // null.
+ uint32 id = next_download_id_;
+ ++next_download_id_;
+ info.request_handle = DownloadRequestHandle();
+ download_manager_->CreateActiveItem(id, info);
+ DCHECK(mock_download_item_factory_->GetItem(id));
+ MockDownloadItemImpl& item(*mock_download_item_factory_->GetItem(id));
+ // Satisfy expectation. If the item is created in StartDownload(),
+ // we call Start on it immediately, so we need to set that expectation
+ // in the factory.
+ scoped_ptr<DownloadRequestHandleInterface> req_handle;
+ item.Start(scoped_ptr<DownloadFile>(), req_handle.Pass());
+
+ return item;
+ }
+
+ MockDownloadItemImpl& GetMockDownloadItem(int id) {
+ MockDownloadItemImpl* itemp = mock_download_item_factory_->GetItem(id);
+
+ DCHECK(itemp);
+ return *itemp;
+ }
+
+ void RemoveMockDownloadItem(int id) {
+ // Owned by DownloadManager; should be deleted there.
+ mock_download_item_factory_->RemoveItem(id);
+ }
+
+ MockDownloadManagerDelegate& GetMockDownloadManagerDelegate() {
+ return *mock_download_manager_delegate_;
+ }
+
+ MockDownloadManagerObserver& GetMockObserver() {
+ return *observer_;
+ }
+
+ void DownloadTargetDeterminedCallback(
+ const base::FilePath& target_path,
+ DownloadItem::TargetDisposition disposition,
+ DownloadDangerType danger_type,
+ const base::FilePath& intermediate_path) {
+ callback_called_ = true;
+ target_path_ = target_path;
+ target_disposition_ = disposition;
+ danger_type_ = danger_type;
+ intermediate_path_ = intermediate_path;
+ }
+
+ void DetermineDownloadTarget(DownloadItemImpl* item) {
+ download_manager_->DetermineDownloadTarget(
+ item, base::Bind(
+ &DownloadManagerTest::DownloadTargetDeterminedCallback,
+ base::Unretained(this)));
+ }
+
+ protected:
+ // Key test variable; we'll keep it available to sub-classes.
+ scoped_ptr<DownloadManagerImpl> download_manager_;
+ base::WeakPtr<MockDownloadFileFactory> mock_download_file_factory_;
+
+ // Target detetermined callback.
+ bool callback_called_;
+ base::FilePath target_path_;
+ DownloadItem::TargetDisposition target_disposition_;
+ DownloadDangerType danger_type_;
+ base::FilePath intermediate_path_;
+
+ private:
+ base::MessageLoopForUI message_loop_;
+ TestBrowserThread ui_thread_;
+ TestBrowserThread file_thread_;
+ base::WeakPtr<MockDownloadItemFactory> mock_download_item_factory_;
+ scoped_ptr<MockDownloadManagerDelegate> mock_download_manager_delegate_;
+ scoped_ptr<MockBrowserContext> mock_browser_context_;
+ scoped_ptr<MockDownloadManagerObserver> observer_;
+ uint32 next_download_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadManagerTest);
+};
+
+// Confirm the appropriate invocations occur when you start a download.
+TEST_F(DownloadManagerTest, StartDownload) {
+ scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo);
+ scoped_ptr<ByteStreamReader> stream;
+ uint32 local_id(5); // Random value
+ base::FilePath download_path(FILE_PATH_LITERAL("download/path"));
+
+ EXPECT_FALSE(download_manager_->GetDownload(local_id));
+
+ EXPECT_CALL(GetMockObserver(), OnDownloadCreated(download_manager_.get(), _))
+ .WillOnce(Return());
+ EXPECT_CALL(GetMockDownloadManagerDelegate(), GetNextId(_))
+ .WillOnce(RunCallback<0>(local_id));
+
+ // Doing nothing will set the default download directory to null.
+ EXPECT_CALL(GetMockDownloadManagerDelegate(), GetSaveDir(_, _, _, _));
+ EXPECT_CALL(GetMockDownloadManagerDelegate(), GenerateFileHash())
+ .WillOnce(Return(true));
+ EXPECT_CALL(GetMockDownloadManagerDelegate(),
+ ApplicationClientIdForFileScanning())
+ .WillRepeatedly(Return("client-id"));
+ MockDownloadFile* mock_file = new MockDownloadFile;
+ EXPECT_CALL(*mock_file, SetClientGuid("client-id"));
+ EXPECT_CALL(*mock_download_file_factory_.get(),
+ MockCreateFile(Ref(*info->save_info.get()), _, _, _, true,
+ stream.get(), _, _))
+ .WillOnce(Return(mock_file));
+
+ download_manager_->StartDownload(
+ info.Pass(), stream.Pass(), DownloadUrlParameters::OnStartedCallback());
+ EXPECT_TRUE(download_manager_->GetDownload(local_id));
+}
+
+// Confirm that calling DetermineDownloadTarget behaves properly if the delegate
+// blocks starting.
+TEST_F(DownloadManagerTest, DetermineDownloadTarget_True) {
+ // Put a mock we have a handle to on the download manager.
+ MockDownloadItemImpl& item(AddItemToManager());
+ EXPECT_CALL(item, GetState())
+ .WillRepeatedly(Return(DownloadItem::IN_PROGRESS));
+
+ EXPECT_CALL(GetMockDownloadManagerDelegate(),
+ DetermineDownloadTarget(&item, _))
+ .WillOnce(Return(true));
+ DetermineDownloadTarget(&item);
+}
+
+// Confirm that calling DetermineDownloadTarget behaves properly if the delegate
+// allows starting. This also tests OnDownloadTargetDetermined.
+TEST_F(DownloadManagerTest, DetermineDownloadTarget_False) {
+ // Put a mock we have a handle to on the download manager.
+ MockDownloadItemImpl& item(AddItemToManager());
+
+ base::FilePath path(FILE_PATH_LITERAL("random_filepath.txt"));
+ EXPECT_CALL(GetMockDownloadManagerDelegate(),
+ DetermineDownloadTarget(&item, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(item, GetForcedFilePath())
+ .WillOnce(ReturnRef(path));
+
+ // Confirm that the callback was called with the right values in this case.
+ callback_called_ = false;
+ DetermineDownloadTarget(&item);
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(path, target_path_);
+ EXPECT_EQ(DownloadItem::TARGET_DISPOSITION_OVERWRITE, target_disposition_);
+ EXPECT_EQ(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, danger_type_);
+ EXPECT_EQ(path, intermediate_path_);
+}
+
+// Confirm the DownloadManagerImpl::RemoveAllDownloads() functionality
+TEST_F(DownloadManagerTest, RemoveAllDownloads) {
+ base::Time now(base::Time::Now());
+ for (uint32 i = 0; i < 4; ++i) {
+ MockDownloadItemImpl& item(AddItemToManager());
+ EXPECT_EQ(i, item.GetId());
+ EXPECT_CALL(item, GetStartTime())
+ .WillRepeatedly(Return(now));
+ }
+
+ // Specify states for each.
+ EXPECT_CALL(GetMockDownloadItem(0), GetState())
+ .WillRepeatedly(Return(DownloadItem::COMPLETE));
+ EXPECT_CALL(GetMockDownloadItem(1), GetState())
+ .WillRepeatedly(Return(DownloadItem::CANCELLED));
+ EXPECT_CALL(GetMockDownloadItem(2), GetState())
+ .WillRepeatedly(Return(DownloadItem::INTERRUPTED));
+ EXPECT_CALL(GetMockDownloadItem(3), GetState())
+ .WillRepeatedly(Return(DownloadItem::IN_PROGRESS));
+
+ // Expectations for whether or not they'll actually be removed.
+ EXPECT_CALL(GetMockDownloadItem(0), Remove())
+ .WillOnce(Return());
+ EXPECT_CALL(GetMockDownloadItem(1), Remove())
+ .WillOnce(Return());
+ EXPECT_CALL(GetMockDownloadItem(2), Remove())
+ .WillOnce(Return());
+ EXPECT_CALL(GetMockDownloadItem(3), Remove())
+ .Times(0);
+
+ download_manager_->RemoveAllDownloads();
+ // Because we're mocking the download item, the Remove call doesn't
+ // result in them being removed from the DownloadManager list.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_net_log_parameters.cc b/chromium/content/browser/download/download_net_log_parameters.cc
new file mode 100644
index 00000000000..f464298d73e
--- /dev/null
+++ b/chromium/content/browser/download/download_net_log_parameters.cc
@@ -0,0 +1,208 @@
+// 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 "content/browser/download/download_net_log_parameters.h"
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+static const char* download_type_names[] = {
+ "NEW_DOWNLOAD",
+ "HISTORY_IMPORT",
+ "SAVE_PAGE_AS"
+};
+static const char* download_danger_names[] = {
+ "NOT_DANGEROUS",
+ "DANGEROUS_FILE",
+ "DANGEROUS_URL",
+ "DANGEROUS_CONTENT",
+ "MAYBE_DANGEROUS_CONTENT",
+ "UNCOMMON_CONTENT",
+ "USER_VALIDATED",
+ "DANGEROUS_HOST",
+ "POTENTIALLY_UNWANTED"
+};
+
+COMPILE_ASSERT(ARRAYSIZE_UNSAFE(download_type_names) == SRC_SAVE_PAGE_AS + 1,
+ download_type_enum_has_changed);
+COMPILE_ASSERT(ARRAYSIZE_UNSAFE(download_danger_names) ==
+ DOWNLOAD_DANGER_TYPE_MAX,
+ download_danger_enum_has_changed);
+
+} // namespace
+
+base::Value* ItemActivatedNetLogCallback(
+ const DownloadItem* download_item,
+ DownloadType download_type,
+ const std::string* file_name,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("type", download_type_names[download_type]);
+ dict->SetString("id", base::Int64ToString(download_item->GetId()));
+ dict->SetString("original_url", download_item->GetOriginalUrl().spec());
+ dict->SetString("final_url", download_item->GetURL().spec());
+ dict->SetString("file_name", *file_name);
+ dict->SetString("danger_type",
+ download_danger_names[download_item->GetDangerType()]);
+ dict->SetString("start_offset",
+ base::Int64ToString(download_item->GetReceivedBytes()));
+ dict->SetBoolean("has_user_gesture", download_item->HasUserGesture());
+
+ return dict;
+}
+
+base::Value* ItemCheckedNetLogCallback(
+ DownloadDangerType danger_type,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("danger_type", download_danger_names[danger_type]);
+
+ return dict;
+}
+
+base::Value* ItemRenamedNetLogCallback(const base::FilePath* old_filename,
+ const base::FilePath* new_filename,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("old_filename", old_filename->AsUTF8Unsafe());
+ dict->SetString("new_filename", new_filename->AsUTF8Unsafe());
+
+ return dict;
+}
+
+base::Value* ItemInterruptedNetLogCallback(DownloadInterruptReason reason,
+ int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("interrupt_reason", InterruptReasonDebugString(reason));
+ dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far));
+ dict->SetString("hash_state",
+ base::HexEncode(hash_state->data(), hash_state->size()));
+
+ return dict;
+}
+
+base::Value* ItemResumingNetLogCallback(bool user_initiated,
+ DownloadInterruptReason reason,
+ int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("user_initiated", user_initiated ? "true" : "false");
+ dict->SetString("interrupt_reason", InterruptReasonDebugString(reason));
+ dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far));
+ dict->SetString("hash_state",
+ base::HexEncode(hash_state->data(), hash_state->size()));
+
+ return dict;
+}
+
+base::Value* ItemCompletingNetLogCallback(int64 bytes_so_far,
+ const std::string* final_hash,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far));
+ dict->SetString("final_hash",
+ base::HexEncode(final_hash->data(), final_hash->size()));
+
+ return dict;
+}
+
+base::Value* ItemFinishedNetLogCallback(bool auto_opened,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("auto_opened", auto_opened ? "yes" : "no");
+
+ return dict;
+}
+
+base::Value* ItemCanceledNetLogCallback(int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far));
+ dict->SetString("hash_state",
+ base::HexEncode(hash_state->data(), hash_state->size()));
+
+ return dict;
+}
+
+base::Value* FileOpenedNetLogCallback(const base::FilePath* file_name,
+ int64 start_offset,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("file_name", file_name->AsUTF8Unsafe());
+ dict->SetString("start_offset", base::Int64ToString(start_offset));
+
+ return dict;
+}
+
+base::Value* FileStreamDrainedNetLogCallback(size_t stream_size,
+ size_t num_buffers,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetInteger("stream_size", static_cast<int>(stream_size));
+ dict->SetInteger("num_buffers", static_cast<int>(num_buffers));
+
+ return dict;
+}
+
+base::Value* FileRenamedNetLogCallback(const base::FilePath* old_filename,
+ const base::FilePath* new_filename,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("old_filename", old_filename->AsUTF8Unsafe());
+ dict->SetString("new_filename", new_filename->AsUTF8Unsafe());
+
+ return dict;
+}
+
+base::Value* FileErrorNetLogCallback(const char* operation,
+ net::Error net_error,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("operation", operation);
+ dict->SetInteger("net_error", net_error);
+
+ return dict;
+}
+
+base::Value* FileInterruptedNetLogCallback(const char* operation,
+ int os_error,
+ DownloadInterruptReason reason,
+ net::NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("operation", operation);
+ if (os_error != 0)
+ dict->SetInteger("os_error", os_error);
+ dict->SetString("interrupt_reason", InterruptReasonDebugString(reason));
+
+ return dict;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_net_log_parameters.h b/chromium/content/browser/download/download_net_log_parameters.h
new file mode 100644
index 00000000000..1c12b8d1767
--- /dev/null
+++ b/chromium/content/browser/download/download_net_log_parameters.h
@@ -0,0 +1,100 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_NET_LOG_PARAMETERS_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_NET_LOG_PARAMETERS_H_
+
+#include <string>
+
+#include "content/public/browser/download_item.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+enum DownloadType {
+ SRC_ACTIVE_DOWNLOAD,
+ SRC_HISTORY_IMPORT,
+ SRC_SAVE_PAGE_AS
+};
+
+// Returns NetLog parameters when a DownloadItem is activated.
+base::Value* ItemActivatedNetLogCallback(
+ const DownloadItem* download_item,
+ DownloadType download_type,
+ const std::string* file_name,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is checked for danger.
+base::Value* ItemCheckedNetLogCallback(
+ DownloadDangerType danger_type,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is renamed.
+base::Value* ItemRenamedNetLogCallback(const base::FilePath* old_filename,
+ const base::FilePath* new_filename,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is interrupted.
+base::Value* ItemInterruptedNetLogCallback(DownloadInterruptReason reason,
+ int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is resumed.
+base::Value* ItemResumingNetLogCallback(bool user_initiated,
+ DownloadInterruptReason reason,
+ int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is completing.
+base::Value* ItemCompletingNetLogCallback(int64 bytes_so_far,
+ const std::string* final_hash,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is finished.
+base::Value* ItemFinishedNetLogCallback(bool auto_opened,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadItem is canceled.
+base::Value* ItemCanceledNetLogCallback(int64 bytes_so_far,
+ const std::string* hash_state,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadFile is opened.
+base::Value* FileOpenedNetLogCallback(const base::FilePath* file_name,
+ int64 start_offset,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadFile is opened.
+base::Value* FileStreamDrainedNetLogCallback(size_t stream_size,
+ size_t num_buffers,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a DownloadFile is renamed.
+base::Value* FileRenamedNetLogCallback(const base::FilePath* old_filename,
+ const base::FilePath* new_filename,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters when a File has an error.
+base::Value* FileErrorNetLogCallback(const char* operation,
+ net::Error net_error,
+ net::NetLog::LogLevel log_level);
+
+// Returns NetLog parameters for a download interruption.
+base::Value* FileInterruptedNetLogCallback(const char* operation,
+ int os_error,
+ DownloadInterruptReason reason,
+ net::NetLog::LogLevel log_level);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_NET_LOG_PARAMETERS_H_
diff --git a/chromium/content/browser/download/download_request_handle.cc b/chromium/content/browser/download/download_request_handle.cc
new file mode 100644
index 00000000000..32c5b987799
--- /dev/null
+++ b/chromium/content/browser/download/download_request_handle.cc
@@ -0,0 +1,89 @@
+// 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 "content/browser/download/download_request_handle.h"
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+DownloadRequestHandle::~DownloadRequestHandle() {
+}
+
+DownloadRequestHandle::DownloadRequestHandle()
+ : child_id_(-1),
+ render_view_id_(-1),
+ request_id_(-1) {
+}
+
+DownloadRequestHandle::DownloadRequestHandle(
+ const base::WeakPtr<DownloadResourceHandler>& handler,
+ int child_id,
+ int render_view_id,
+ int request_id)
+ : handler_(handler),
+ child_id_(child_id),
+ render_view_id_(render_view_id),
+ request_id_(request_id) {
+ DCHECK(handler_.get());
+}
+
+WebContents* DownloadRequestHandle::GetWebContents() const {
+ RenderViewHostImpl* render_view_host =
+ RenderViewHostImpl::FromID(child_id_, render_view_id_);
+ if (!render_view_host)
+ return NULL;
+
+ return render_view_host->GetDelegate()->GetAsWebContents();
+}
+
+DownloadManager* DownloadRequestHandle::GetDownloadManager() const {
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(
+ child_id_, render_view_id_);
+ if (rvh == NULL)
+ return NULL;
+ RenderProcessHost* rph = rvh->GetProcess();
+ if (rph == NULL)
+ return NULL;
+ BrowserContext* context = rph->GetBrowserContext();
+ if (context == NULL)
+ return NULL;
+ return BrowserContext::GetDownloadManager(context);
+}
+
+void DownloadRequestHandle::PauseRequest() const {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&DownloadResourceHandler::PauseRequest, handler_));
+}
+
+void DownloadRequestHandle::ResumeRequest() const {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&DownloadResourceHandler::ResumeRequest, handler_));
+}
+
+void DownloadRequestHandle::CancelRequest() const {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&DownloadResourceHandler::CancelRequest, handler_));
+}
+
+std::string DownloadRequestHandle::DebugString() const {
+ return base::StringPrintf("{"
+ " child_id = %d"
+ " render_view_id = %d"
+ " request_id = %d"
+ "}",
+ child_id_,
+ render_view_id_,
+ request_id_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_request_handle.h b/chromium/content/browser/download/download_request_handle.h
new file mode 100644
index 00000000000..de1d8ae83d1
--- /dev/null
+++ b/chromium/content/browser/download/download_request_handle.h
@@ -0,0 +1,88 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_HANDLE_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_HANDLE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/common/content_export.h"
+
+namespace content {
+class DownloadManager;
+class WebContents;
+
+// A handle used by the download system for operations on the URLRequest
+// or objects conditional on it (e.g. WebContentsImpl).
+// This class needs to be copyable, so we can pass it across threads and not
+// worry about lifetime or const-ness.
+//
+// DownloadRequestHandleInterface is defined for mocking purposes.
+class CONTENT_EXPORT DownloadRequestHandleInterface {
+ public:
+ virtual ~DownloadRequestHandleInterface() {}
+
+ // These functions must be called on the UI thread.
+ virtual WebContents* GetWebContents() const = 0;
+ virtual DownloadManager* GetDownloadManager() const = 0;
+
+ // Pauses or resumes the matching URL request.
+ virtual void PauseRequest() const = 0;
+ virtual void ResumeRequest() const = 0;
+
+ // Cancels the request.
+ virtual void CancelRequest() const = 0;
+
+ // Describes the object.
+ virtual std::string DebugString() const = 0;
+};
+
+
+class CONTENT_EXPORT DownloadRequestHandle
+ : public DownloadRequestHandleInterface {
+ public:
+ virtual ~DownloadRequestHandle();
+
+ // Create a null DownloadRequestHandle: getters will return null, and
+ // all actions are no-ops.
+ // TODO(rdsmith): Ideally, actions would be forbidden rather than
+ // no-ops, to confirm that no non-testing code actually uses
+ // a null DownloadRequestHandle. But for now, we need the no-op
+ // behavior for unit tests. Long-term, this should be fixed by
+ // allowing mocking of ResourceDispatcherHost in unit tests.
+ DownloadRequestHandle();
+
+ // Note that |rdh| is required to be non-null.
+ DownloadRequestHandle(const base::WeakPtr<DownloadResourceHandler>& handler,
+ int child_id,
+ int render_view_id,
+ int request_id);
+
+ // Implement DownloadRequestHandleInterface interface.
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual DownloadManager* GetDownloadManager() const OVERRIDE;
+ virtual void PauseRequest() const OVERRIDE;
+ virtual void ResumeRequest() const OVERRIDE;
+ virtual void CancelRequest() const OVERRIDE;
+ virtual std::string DebugString() const OVERRIDE;
+
+ private:
+ base::WeakPtr<DownloadResourceHandler> handler_;
+
+ // The ID of the child process that started the download.
+ int child_id_;
+
+ // The ID of the render view that started the download.
+ int render_view_id_;
+
+ // The ID associated with the request used for the download.
+ int request_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_HANDLE_H_
diff --git a/chromium/content/browser/download/download_resource_handler.cc b/chromium/content/browser/download/download_resource_handler.cc
new file mode 100644
index 00000000000..ed4edff67fe
--- /dev/null
+++ b/chromium/content/browser/download/download_resource_handler.cc
@@ -0,0 +1,493 @@
+// 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 "content/browser/download/download_resource_handler.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/download/download_request_handle.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/common/resource_response.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request_context.h"
+
+namespace content {
+namespace {
+
+void CallStartedCBOnUIThread(
+ const DownloadUrlParameters::OnStartedCallback& started_cb,
+ DownloadItem* item,
+ net::Error error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (started_cb.is_null())
+ return;
+ started_cb.Run(item, error);
+}
+
+// Static function in order to prevent any accidental accesses to
+// DownloadResourceHandler members from the UI thread.
+static void StartOnUIThread(
+ scoped_ptr<DownloadCreateInfo> info,
+ scoped_ptr<ByteStreamReader> stream,
+ const DownloadUrlParameters::OnStartedCallback& started_cb) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ DownloadManager* download_manager = info->request_handle.GetDownloadManager();
+ if (!download_manager) {
+ // NULL in unittests or if the page closed right after starting the
+ // download.
+ if (!started_cb.is_null())
+ started_cb.Run(NULL, net::ERR_ACCESS_DENIED);
+ return;
+ }
+
+ download_manager->StartDownload(info.Pass(), stream.Pass(), started_cb);
+}
+
+} // namespace
+
+const int DownloadResourceHandler::kDownloadByteStreamSize = 100 * 1024;
+
+DownloadResourceHandler::DownloadResourceHandler(
+ uint32 id,
+ net::URLRequest* request,
+ const DownloadUrlParameters::OnStartedCallback& started_cb,
+ scoped_ptr<DownloadSaveInfo> save_info)
+ : download_id_(id),
+ render_view_id_(0), // Actually initialized below.
+ content_length_(0),
+ request_(request),
+ started_cb_(started_cb),
+ save_info_(save_info.Pass()),
+ last_buffer_size_(0),
+ bytes_read_(0),
+ pause_count_(0),
+ was_deferred_(false),
+ on_response_started_called_(false) {
+ ResourceRequestInfoImpl* info(ResourceRequestInfoImpl::ForRequest(request));
+ global_id_ = info->GetGlobalRequestID();
+ render_view_id_ = info->GetRouteID();
+
+ RecordDownloadCount(UNTHROTTLED_COUNT);
+}
+
+bool DownloadResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return true;
+}
+
+bool DownloadResourceHandler::OnRequestRedirected(
+ int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) {
+ // We treat a download as a main frame load, and thus update the policy URL
+ // on redirects.
+ request_->set_first_party_for_cookies(url);
+ return true;
+}
+
+// Send the download creation information to the download thread.
+bool DownloadResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // There can be only one (call)
+ DCHECK(!on_response_started_called_);
+ on_response_started_called_ = true;
+
+ VLOG(20) << __FUNCTION__ << "()" << DebugString()
+ << " request_id = " << request_id;
+ download_start_time_ = base::TimeTicks::Now();
+
+ // If it's a download, we don't want to poison the cache with it.
+ request_->StopCaching();
+
+ // Lower priority as well, so downloads don't contend for resources
+ // with main frames.
+ request_->SetPriority(net::IDLE);
+
+ std::string content_disposition;
+ request_->GetResponseHeaderByName("content-disposition",
+ &content_disposition);
+ SetContentDisposition(content_disposition);
+ SetContentLength(response->head.content_length);
+
+ const ResourceRequestInfoImpl* request_info =
+ ResourceRequestInfoImpl::ForRequest(request_);
+
+ // Deleted in DownloadManager.
+ scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo(
+ base::Time::Now(), content_length_,
+ request_->net_log(), request_info->HasUserGesture(),
+ request_info->GetPageTransition()));
+
+ // Create the ByteStream for sending data to the download sink.
+ scoped_ptr<ByteStreamReader> stream_reader;
+ CreateByteStream(
+ base::MessageLoopProxy::current(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
+ kDownloadByteStreamSize, &stream_writer_, &stream_reader);
+ stream_writer_->RegisterCallback(
+ base::Bind(&DownloadResourceHandler::ResumeRequest, AsWeakPtr()));
+
+ info->download_id = download_id_;
+ info->url_chain = request_->url_chain();
+ info->referrer_url = GURL(request_->referrer());
+ info->start_time = base::Time::Now();
+ info->total_bytes = content_length_;
+ info->has_user_gesture = request_info->HasUserGesture();
+ info->content_disposition = content_disposition_;
+ info->mime_type = response->head.mime_type;
+ info->remote_address = request_->GetSocketAddress().host();
+ RecordDownloadMimeType(info->mime_type);
+ RecordDownloadContentDisposition(info->content_disposition);
+
+ info->request_handle =
+ DownloadRequestHandle(AsWeakPtr(), global_id_.child_id,
+ render_view_id_, global_id_.request_id);
+
+ // Get the last modified time and etag.
+ const net::HttpResponseHeaders* headers = request_->response_headers();
+ if (headers) {
+ std::string last_modified_hdr;
+ if (headers->EnumerateHeader(NULL, "Last-Modified", &last_modified_hdr))
+ info->last_modified = last_modified_hdr;
+ if (headers->EnumerateHeader(NULL, "ETag", &etag_))
+ info->etag = etag_;
+
+ int status = headers->response_code();
+ if (2 == status / 100 && status != net::HTTP_PARTIAL_CONTENT) {
+ // Success & not range response; if we asked for a range, we didn't
+ // get it--reset the file pointers to reflect that.
+ save_info_->offset = 0;
+ save_info_->hash_state = "";
+ }
+ }
+
+ std::string content_type_header;
+ if (!response->head.headers.get() ||
+ !response->head.headers->GetMimeType(&content_type_header))
+ content_type_header = "";
+ info->original_mime_type = content_type_header;
+
+ if (!response->head.headers.get() ||
+ !response->head.headers->EnumerateHeader(
+ NULL, "Accept-Ranges", &accept_ranges_)) {
+ accept_ranges_ = "";
+ }
+
+ info->save_info = save_info_.Pass();
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&StartOnUIThread,
+ base::Passed(&info),
+ base::Passed(&stream_reader),
+ // Pass to StartOnUIThread so that variable
+ // access is always on IO thread but function
+ // is called on UI thread.
+ started_cb_));
+ // Guaranteed to be called in StartOnUIThread
+ started_cb_.Reset();
+
+ return true;
+}
+
+void DownloadResourceHandler::CallStartedCB(
+ DownloadItem* item, net::Error error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (started_cb_.is_null())
+ return;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&CallStartedCBOnUIThread, started_cb_, item, error));
+ started_cb_.Reset();
+}
+
+bool DownloadResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+// Create a new buffer, which will be handed to the download thread for file
+// writing and deletion.
+bool DownloadResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(buf && buf_size);
+ DCHECK(!read_buffer_.get());
+
+ *buf_size = min_size < 0 ? kReadBufSize : min_size;
+ last_buffer_size_ = *buf_size;
+ read_buffer_ = new net::IOBuffer(*buf_size);
+ *buf = read_buffer_.get();
+ return true;
+}
+
+// Pass the buffer to the download file writer.
+bool DownloadResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(read_buffer_.get());
+
+ base::TimeTicks now(base::TimeTicks::Now());
+ if (!last_read_time_.is_null()) {
+ double seconds_since_last_read = (now - last_read_time_).InSecondsF();
+ if (now == last_read_time_)
+ // Use 1/10 ms as a "very small number" so that we avoid
+ // divide-by-zero error and still record a very high potential bandwidth.
+ seconds_since_last_read = 0.00001;
+
+ double actual_bandwidth = (bytes_read)/seconds_since_last_read;
+ double potential_bandwidth = last_buffer_size_/seconds_since_last_read;
+ RecordBandwidth(actual_bandwidth, potential_bandwidth);
+ }
+ last_read_time_ = now;
+
+ if (!bytes_read)
+ return true;
+ bytes_read_ += bytes_read;
+ DCHECK(read_buffer_.get());
+
+ // Take the data ship it down the stream. If the stream is full, pause the
+ // request; the stream callback will resume it.
+ if (!stream_writer_->Write(read_buffer_, bytes_read)) {
+ PauseRequest();
+ *defer = was_deferred_ = true;
+ last_stream_pause_time_ = now;
+ }
+
+ read_buffer_ = NULL; // Drop our reference.
+
+ if (pause_count_ > 0)
+ *defer = was_deferred_ = true;
+
+ return true;
+}
+
+bool DownloadResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ int response_code = status.is_success() ? request_->GetResponseCode() : 0;
+ VLOG(20) << __FUNCTION__ << "()" << DebugString()
+ << " request_id = " << request_id
+ << " status.status() = " << status.status()
+ << " status.error() = " << status.error()
+ << " response_code = " << response_code;
+
+ net::Error error_code = net::OK;
+ if (status.status() == net::URLRequestStatus::FAILED ||
+ // Note cancels as failures too.
+ status.status() == net::URLRequestStatus::CANCELED) {
+ error_code = static_cast<net::Error>(status.error()); // Normal case.
+ // Make sure that at least the fact of failure comes through.
+ if (error_code == net::OK)
+ error_code = net::ERR_FAILED;
+ }
+
+ // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are
+ // allowed since a number of servers in the wild close the connection too
+ // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 -
+ // treat downloads as complete in both cases, so we follow their lead.
+ if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH ||
+ error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) {
+ error_code = net::OK;
+ }
+ DownloadInterruptReason reason =
+ ConvertNetErrorToInterruptReason(
+ error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK);
+
+ if (status.status() == net::URLRequestStatus::CANCELED &&
+ status.error() == net::ERR_ABORTED) {
+ // CANCELED + ERR_ABORTED == something outside of the network
+ // stack cancelled the request. There aren't that many things that
+ // could do this to a download request (whose lifetime is separated from
+ // the tab from which it came). We map this to USER_CANCELLED as the
+ // case we know about (system suspend because of laptop close) corresponds
+ // to a user action.
+ // TODO(ahendrickson) -- Find a better set of codes to use here, as
+ // CANCELED/ERR_ABORTED can occur for reasons other than user cancel.
+ reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED;
+ }
+
+ if (status.is_success() &&
+ reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
+ request_->response_headers()) {
+ // Handle server's response codes.
+ switch(response_code) {
+ case -1: // Non-HTTP request.
+ case net::HTTP_OK:
+ case net::HTTP_CREATED:
+ case net::HTTP_ACCEPTED:
+ case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
+ case net::HTTP_RESET_CONTENT:
+ case net::HTTP_PARTIAL_CONTENT:
+ // Expected successful codes.
+ break;
+ case net::HTTP_NO_CONTENT:
+ case net::HTTP_NOT_FOUND:
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT;
+ break;
+ case net::HTTP_PRECONDITION_FAILED:
+ // Failed our 'If-Unmodified-Since' or 'If-Match'; see
+ // download_manager_impl.cc BeginDownload()
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION;
+ break;
+ case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
+ // Retry by downloading from the start automatically:
+ // If we haven't received data when we get this error, we won't.
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE;
+ break;
+ default: // All other errors.
+ // Redirection and informational codes should have been handled earlier
+ // in the stack.
+ DCHECK_NE(3, response_code / 100);
+ DCHECK_NE(1, response_code / 100);
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED;
+ break;
+ }
+ }
+
+ RecordAcceptsRanges(accept_ranges_, bytes_read_, etag_);
+ RecordNetworkBlockage(
+ base::TimeTicks::Now() - download_start_time_, total_pause_time_);
+
+ CallStartedCB(NULL, error_code);
+
+ // Send the info down the stream. Conditional is in case we get
+ // OnResponseCompleted without OnResponseStarted.
+ if (stream_writer_)
+ stream_writer_->Close(reason);
+
+ // If the error mapped to something unknown, record it so that
+ // we can drill down.
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION("Download.MapErrorNetworkFailed",
+ std::abs(status.error()),
+ net::GetAllErrorCodesForUma());
+ }
+
+ stream_writer_.reset(); // We no longer need the stream.
+ read_buffer_ = NULL;
+
+ return true;
+}
+
+void DownloadResourceHandler::OnDataDownloaded(
+ int request_id,
+ int bytes_downloaded) {
+ NOTREACHED();
+}
+
+// If the content-length header is not present (or contains something other
+// than numbers), the incoming content_length is -1 (unknown size).
+// Set the content length to 0 to indicate unknown size to DownloadManager.
+void DownloadResourceHandler::SetContentLength(const int64& content_length) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ content_length_ = 0;
+ if (content_length > 0)
+ content_length_ = content_length;
+}
+
+void DownloadResourceHandler::SetContentDisposition(
+ const std::string& content_disposition) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ content_disposition_ = content_disposition;
+}
+
+void DownloadResourceHandler::PauseRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ ++pause_count_;
+}
+
+void DownloadResourceHandler::ResumeRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_LT(0, pause_count_);
+
+ --pause_count_;
+
+ if (!was_deferred_)
+ return;
+ if (pause_count_ > 0)
+ return;
+
+ was_deferred_ = false;
+ if (!last_stream_pause_time_.is_null()) {
+ total_pause_time_ += (base::TimeTicks::Now() - last_stream_pause_time_);
+ last_stream_pause_time_ = base::TimeTicks();
+ }
+
+ controller()->Resume();
+}
+
+void DownloadResourceHandler::CancelRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ ResourceDispatcherHostImpl::Get()->CancelRequest(
+ global_id_.child_id,
+ global_id_.request_id,
+ false);
+}
+
+std::string DownloadResourceHandler::DebugString() const {
+ return base::StringPrintf("{"
+ " url_ = " "\"%s\""
+ " global_id_ = {"
+ " child_id = " "%d"
+ " request_id = " "%d"
+ " }"
+ " render_view_id_ = " "%d"
+ " }",
+ request_ ?
+ request_->url().spec().c_str() :
+ "<NULL request>",
+ global_id_.child_id,
+ global_id_.request_id,
+ render_view_id_);
+}
+
+DownloadResourceHandler::~DownloadResourceHandler() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // This won't do anything if the callback was called before.
+ // If it goes through, it will likely be because OnWillStart() returned
+ // false somewhere in the chain of resource handlers.
+ CallStartedCB(NULL, net::ERR_ACCESS_DENIED);
+
+ // Remove output stream callback if a stream exists.
+ if (stream_writer_)
+ stream_writer_->RegisterCallback(base::Closure());
+
+ UMA_HISTOGRAM_TIMES("SB2.DownloadDuration",
+ base::TimeTicks::Now() - download_start_time_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_resource_handler.h b/chromium/content/browser/download/download_resource_handler.h
new file mode 100644
index 00000000000..60fbc944387
--- /dev/null
+++ b/chromium/content/browser/download/download_resource_handler.h
@@ -0,0 +1,144 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_RESOURCE_HANDLER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "content/browser/loader/resource_handler.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_save_info.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/global_request_id.h"
+#include "net/base/net_errors.h"
+
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace content {
+class ByteStreamWriter;
+class ByteStreamReader;
+class DownloadRequestHandle;
+struct DownloadCreateInfo;
+
+// Forwards data to the download thread.
+class CONTENT_EXPORT DownloadResourceHandler
+ : public ResourceHandler,
+ public base::SupportsWeakPtr<DownloadResourceHandler> {
+ public:
+ // Size of the buffer used between the DownloadResourceHandler and the
+ // downstream receiver of its output.
+ static const int kDownloadByteStreamSize;
+
+ // started_cb will be called exactly once on the UI thread.
+ // |id| should be invalid if the id should be automatically assigned.
+ DownloadResourceHandler(
+ uint32 id,
+ net::URLRequest* request,
+ const DownloadUrlParameters::OnStartedCallback& started_cb,
+ scoped_ptr<DownloadSaveInfo> save_info);
+
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+
+ // Send the download creation information to the download thread.
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+
+ // Pass-through implementation.
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+
+ // Create a new buffer, which will be handed to the download thread for file
+ // writing and deletion.
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+
+ virtual bool OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) OVERRIDE;
+
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+
+ // N/A to this flavor of DownloadHandler.
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ void PauseRequest();
+ void ResumeRequest();
+ void CancelRequest();
+
+ std::string DebugString() const;
+
+ private:
+ virtual ~DownloadResourceHandler();
+
+ // Arrange for started_cb_ to be called on the UI thread with the
+ // below values, nulling out started_cb_. Should only be called
+ // on the IO thread.
+ void CallStartedCB(DownloadItem* item, net::Error error);
+
+ // If the content-length header is not present (or contains something other
+ // than numbers), the incoming content_length is -1 (unknown size).
+ // Set the content length to 0 to indicate unknown size to DownloadManager.
+ void SetContentLength(const int64& content_length);
+
+ void SetContentDisposition(const std::string& content_disposition);
+
+ uint32 download_id_;
+ GlobalRequestID global_id_;
+ int render_view_id_;
+ std::string content_disposition_;
+ int64 content_length_;
+ net::URLRequest* request_;
+ // This is read only on the IO thread, but may only
+ // be called on the UI thread.
+ DownloadUrlParameters::OnStartedCallback started_cb_;
+ scoped_ptr<DownloadSaveInfo> save_info_;
+
+ // Data flow
+ scoped_refptr<net::IOBuffer> read_buffer_; // From URLRequest.
+ scoped_ptr<ByteStreamWriter> stream_writer_; // To rest of system.
+
+ // The following are used to collect stats.
+ base::TimeTicks download_start_time_;
+ base::TimeTicks last_read_time_;
+ base::TimeTicks last_stream_pause_time_;
+ base::TimeDelta total_pause_time_;
+ size_t last_buffer_size_;
+ int64 bytes_read_;
+ std::string accept_ranges_;
+ std::string etag_;
+
+ int pause_count_;
+ bool was_deferred_;
+
+ // For DCHECKing
+ bool on_response_started_called_;
+
+ static const int kReadBufSize = 32768; // bytes
+ static const int kThrottleTimeMs = 200; // milliseconds
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/download/download_stats.cc b/chromium/content/browser/download/download_stats.cc
new file mode 100644
index 00000000000..7831a4db32a
--- /dev/null
+++ b/chromium/content/browser/download/download_stats.cc
@@ -0,0 +1,462 @@
+// 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 "content/browser/download/download_stats.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "net/http/http_content_disposition.h"
+
+namespace content {
+
+namespace {
+
+// All possible error codes from the network module. Note that the error codes
+// are all positive (since histograms expect positive sample values).
+const int kAllInterruptReasonCodes[] = {
+#define INTERRUPT_REASON(label, value) (value),
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+};
+
+// These values are based on net::HttpContentDisposition::ParseResult values.
+// Values other than HEADER_PRESENT and IS_VALID are only measured if |IS_VALID|
+// is true.
+enum ContentDispositionCountTypes {
+ // Count of downloads which had a Content-Disposition headers. The total
+ // number of downloads is measured by UNTHROTTLED_COUNT.
+ CONTENT_DISPOSITION_HEADER_PRESENT = 0,
+
+ // At least one of 'name', 'filename' or 'filenae*' attributes were valid and
+ // yielded a non-empty filename.
+ CONTENT_DISPOSITION_IS_VALID,
+
+ // The following enum values correspond to
+ // net::HttpContentDisposition::ParseResult.
+ CONTENT_DISPOSITION_HAS_DISPOSITION_TYPE,
+ CONTENT_DISPOSITION_HAS_UNKNOWN_TYPE,
+ CONTENT_DISPOSITION_HAS_NAME,
+ CONTENT_DISPOSITION_HAS_FILENAME,
+ CONTENT_DISPOSITION_HAS_EXT_FILENAME,
+ CONTENT_DISPOSITION_HAS_NON_ASCII_STRINGS,
+ CONTENT_DISPOSITION_HAS_PERCENT_ENCODED_STRINGS,
+ CONTENT_DISPOSITION_HAS_RFC2047_ENCODED_STRINGS,
+
+ // Only have the 'name' attribute is present.
+ CONTENT_DISPOSITION_HAS_NAME_ONLY,
+
+ CONTENT_DISPOSITION_LAST_ENTRY
+};
+
+void RecordContentDispositionCount(ContentDispositionCountTypes type,
+ bool record) {
+ if (!record)
+ return;
+ UMA_HISTOGRAM_ENUMERATION(
+ "Download.ContentDisposition", type, CONTENT_DISPOSITION_LAST_ENTRY);
+}
+
+void RecordContentDispositionCountFlag(
+ ContentDispositionCountTypes type,
+ int flags_to_test,
+ net::HttpContentDisposition::ParseResultFlags flag) {
+ RecordContentDispositionCount(type, (flags_to_test & flag) == flag);
+}
+
+} // namespace
+
+void RecordDownloadCount(DownloadCountTypes type) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Download.Counts", type, DOWNLOAD_COUNT_TYPES_LAST_ENTRY);
+}
+
+void RecordDownloadSource(DownloadSource source) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Download.Sources", source, DOWNLOAD_SOURCE_LAST_ENTRY);
+}
+
+void RecordDownloadCompleted(const base::TimeTicks& start, int64 download_len) {
+ RecordDownloadCount(COMPLETED_COUNT);
+ UMA_HISTOGRAM_LONG_TIMES("Download.Time", (base::TimeTicks::Now() - start));
+ int64 max = 1024 * 1024 * 1024; // One Terabyte.
+ download_len /= 1024; // In Kilobytes
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.DownloadSize",
+ download_len,
+ 1,
+ max,
+ 256);
+}
+
+void RecordDownloadInterrupted(DownloadInterruptReason reason,
+ int64 received,
+ int64 total) {
+ RecordDownloadCount(INTERRUPTED_COUNT);
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Download.InterruptedReason",
+ reason,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllInterruptReasonCodes, arraysize(kAllInterruptReasonCodes)));
+
+ // The maximum should be 2^kBuckets, to have the logarithmic bucket
+ // boundaries fall on powers of 2.
+ static const int kBuckets = 30;
+ static const int64 kMaxKb = 1 << kBuckets; // One Terabyte, in Kilobytes.
+ int64 delta_bytes = total - received;
+ bool unknown_size = total <= 0;
+ int64 received_kb = received / 1024;
+ int64 total_kb = total / 1024;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedReceivedSizeK",
+ received_kb,
+ 1,
+ kMaxKb,
+ kBuckets);
+ if (!unknown_size) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedTotalSizeK",
+ total_kb,
+ 1,
+ kMaxKb,
+ kBuckets);
+ if (delta_bytes == 0) {
+ RecordDownloadCount(INTERRUPTED_AT_END_COUNT);
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Download.InterruptedAtEndReason",
+ reason,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllInterruptReasonCodes,
+ arraysize(kAllInterruptReasonCodes)));
+ } else if (delta_bytes > 0) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedOverrunBytes",
+ delta_bytes,
+ 1,
+ kMaxKb,
+ kBuckets);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedUnderrunBytes",
+ -delta_bytes,
+ 1,
+ kMaxKb,
+ kBuckets);
+ }
+ }
+
+ UMA_HISTOGRAM_BOOLEAN("Download.InterruptedUnknownSize", unknown_size);
+}
+
+void RecordDangerousDownloadAccept(DownloadDangerType danger_type) {
+ UMA_HISTOGRAM_ENUMERATION("Download.DangerousDownloadValidated",
+ danger_type,
+ DOWNLOAD_DANGER_TYPE_MAX);
+}
+
+void RecordDangerousDownloadDiscard(DownloadDiscardReason reason,
+ DownloadDangerType danger_type) {
+ switch (reason) {
+ case DOWNLOAD_DISCARD_DUE_TO_USER_ACTION:
+ UMA_HISTOGRAM_ENUMERATION(
+ "Download.UserDiscard", danger_type, DOWNLOAD_DANGER_TYPE_MAX);
+ break;
+ case DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN:
+ UMA_HISTOGRAM_ENUMERATION(
+ "Download.Discard", danger_type, DOWNLOAD_DANGER_TYPE_MAX);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void RecordDownloadWriteSize(size_t data_len) {
+ int max = 1024 * 1024; // One Megabyte.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.WriteSize", data_len, 1, max, 256);
+}
+
+void RecordDownloadWriteLoopCount(int count) {
+ UMA_HISTOGRAM_ENUMERATION("Download.WriteLoopCount", count, 20);
+}
+
+void RecordAcceptsRanges(const std::string& accepts_ranges,
+ int64 download_len,
+ const std::string& etag) {
+ int64 max = 1024 * 1024 * 1024; // One Terabyte.
+ download_len /= 1024; // In Kilobytes
+ static const int kBuckets = 50;
+
+ if (LowerCaseEqualsASCII(accepts_ranges, "none")) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesNone.KBytes",
+ download_len,
+ 1,
+ max,
+ kBuckets);
+ } else if (LowerCaseEqualsASCII(accepts_ranges, "bytes")) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesBytes.KBytes",
+ download_len,
+ 1,
+ max,
+ kBuckets);
+ // ETags that start with "W/" are considered weak ETags which don't imply
+ // byte-wise equality.
+ if (!StartsWithASCII(etag, "w/", false))
+ RecordDownloadCount(STRONG_ETAG_AND_ACCEPTS_RANGES);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesMissingOrInvalid.KBytes",
+ download_len,
+ 1,
+ max,
+ kBuckets);
+ }
+}
+
+namespace {
+
+enum DownloadContent {
+ DOWNLOAD_CONTENT_UNRECOGNIZED = 0,
+ DOWNLOAD_CONTENT_TEXT = 1,
+ DOWNLOAD_CONTENT_IMAGE = 2,
+ DOWNLOAD_CONTENT_AUDIO = 3,
+ DOWNLOAD_CONTENT_VIDEO = 4,
+ DOWNLOAD_CONTENT_OCTET_STREAM = 5,
+ DOWNLOAD_CONTENT_PDF = 6,
+ DOWNLOAD_CONTENT_DOC = 7,
+ DOWNLOAD_CONTENT_XLS = 8,
+ DOWNLOAD_CONTENT_PPT = 9,
+ DOWNLOAD_CONTENT_ARCHIVE = 10,
+ DOWNLOAD_CONTENT_EXE = 11,
+ DOWNLOAD_CONTENT_DMG = 12,
+ DOWNLOAD_CONTENT_CRX = 13,
+ DOWNLOAD_CONTENT_MAX = 14,
+};
+
+struct MimeTypeToDownloadContent {
+ const char* mime_type;
+ DownloadContent download_content;
+};
+
+static MimeTypeToDownloadContent kMapMimeTypeToDownloadContent[] = {
+ {"application/octet-stream", DOWNLOAD_CONTENT_OCTET_STREAM},
+ {"binary/octet-stream", DOWNLOAD_CONTENT_OCTET_STREAM},
+ {"application/pdf", DOWNLOAD_CONTENT_PDF},
+ {"application/msword", DOWNLOAD_CONTENT_DOC},
+ {"application/vnd.ms-excel", DOWNLOAD_CONTENT_XLS},
+ {"application/vns.ms-powerpoint", DOWNLOAD_CONTENT_PPT},
+ {"application/zip", DOWNLOAD_CONTENT_ARCHIVE},
+ {"application/x-gzip", DOWNLOAD_CONTENT_ARCHIVE},
+ {"application/x-rar-compressed", DOWNLOAD_CONTENT_ARCHIVE},
+ {"application/x-tar", DOWNLOAD_CONTENT_ARCHIVE},
+ {"application/x-bzip", DOWNLOAD_CONTENT_ARCHIVE},
+ {"application/x-exe", DOWNLOAD_CONTENT_EXE},
+ {"application/x-apple-diskimage", DOWNLOAD_CONTENT_DMG},
+ {"application/x-chrome-extension", DOWNLOAD_CONTENT_CRX},
+};
+
+enum DownloadImage {
+ DOWNLOAD_IMAGE_UNRECOGNIZED = 0,
+ DOWNLOAD_IMAGE_GIF = 1,
+ DOWNLOAD_IMAGE_JPEG = 2,
+ DOWNLOAD_IMAGE_PNG = 3,
+ DOWNLOAD_IMAGE_TIFF = 4,
+ DOWNLOAD_IMAGE_ICON = 5,
+ DOWNLOAD_IMAGE_WEBP = 6,
+ DOWNLOAD_IMAGE_MAX = 7,
+};
+
+struct MimeTypeToDownloadImage {
+ const char* mime_type;
+ DownloadImage download_image;
+};
+
+static MimeTypeToDownloadImage kMapMimeTypeToDownloadImage[] = {
+ {"image/gif", DOWNLOAD_IMAGE_GIF},
+ {"image/jpeg", DOWNLOAD_IMAGE_JPEG},
+ {"image/png", DOWNLOAD_IMAGE_PNG},
+ {"image/tiff", DOWNLOAD_IMAGE_TIFF},
+ {"image/vnd.microsoft.icon", DOWNLOAD_IMAGE_ICON},
+ {"image/webp", DOWNLOAD_IMAGE_WEBP},
+};
+
+void RecordDownloadImageType(const std::string& mime_type_string) {
+ DownloadImage download_image = DOWNLOAD_IMAGE_UNRECOGNIZED;
+
+ // Look up exact matches.
+ for (size_t i = 0; i < arraysize(kMapMimeTypeToDownloadImage); ++i) {
+ const MimeTypeToDownloadImage& entry = kMapMimeTypeToDownloadImage[i];
+ if (mime_type_string == entry.mime_type) {
+ download_image = entry.download_image;
+ break;
+ }
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Download.ContentImageType",
+ download_image,
+ DOWNLOAD_IMAGE_MAX);
+}
+
+} // namespace
+
+void RecordDownloadMimeType(const std::string& mime_type_string) {
+ DownloadContent download_content = DOWNLOAD_CONTENT_UNRECOGNIZED;
+
+ // Look up exact matches.
+ for (size_t i = 0; i < arraysize(kMapMimeTypeToDownloadContent); ++i) {
+ const MimeTypeToDownloadContent& entry = kMapMimeTypeToDownloadContent[i];
+ if (mime_type_string == entry.mime_type) {
+ download_content = entry.download_content;
+ break;
+ }
+ }
+
+ // Do partial matches.
+ if (download_content == DOWNLOAD_CONTENT_UNRECOGNIZED) {
+ if (StartsWithASCII(mime_type_string, "text/", true)) {
+ download_content = DOWNLOAD_CONTENT_TEXT;
+ } else if (StartsWithASCII(mime_type_string, "image/", true)) {
+ download_content = DOWNLOAD_CONTENT_IMAGE;
+ RecordDownloadImageType(mime_type_string);
+ } else if (StartsWithASCII(mime_type_string, "audio/", true)) {
+ download_content = DOWNLOAD_CONTENT_AUDIO;
+ } else if (StartsWithASCII(mime_type_string, "video/", true)) {
+ download_content = DOWNLOAD_CONTENT_VIDEO;
+ }
+ }
+
+ // Record the value.
+ UMA_HISTOGRAM_ENUMERATION("Download.ContentType",
+ download_content,
+ DOWNLOAD_CONTENT_MAX);
+}
+
+void RecordDownloadContentDisposition(
+ const std::string& content_disposition_string) {
+ if (content_disposition_string.empty())
+ return;
+ net::HttpContentDisposition content_disposition(content_disposition_string,
+ std::string());
+ int result = content_disposition.parse_result_flags();
+
+ bool is_valid = !content_disposition.filename().empty();
+ RecordContentDispositionCount(CONTENT_DISPOSITION_HEADER_PRESENT, true);
+ RecordContentDispositionCount(CONTENT_DISPOSITION_IS_VALID, is_valid);
+ if (!is_valid)
+ return;
+
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_DISPOSITION_TYPE, result,
+ net::HttpContentDisposition::HAS_DISPOSITION_TYPE);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_UNKNOWN_TYPE, result,
+ net::HttpContentDisposition::HAS_UNKNOWN_DISPOSITION_TYPE);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_NAME, result,
+ net::HttpContentDisposition::HAS_NAME);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_FILENAME, result,
+ net::HttpContentDisposition::HAS_FILENAME);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_EXT_FILENAME, result,
+ net::HttpContentDisposition::HAS_EXT_FILENAME);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_NON_ASCII_STRINGS, result,
+ net::HttpContentDisposition::HAS_NON_ASCII_STRINGS);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_PERCENT_ENCODED_STRINGS, result,
+ net::HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS);
+ RecordContentDispositionCountFlag(
+ CONTENT_DISPOSITION_HAS_RFC2047_ENCODED_STRINGS, result,
+ net::HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS);
+
+ RecordContentDispositionCount(
+ CONTENT_DISPOSITION_HAS_NAME_ONLY,
+ (result & (net::HttpContentDisposition::HAS_NAME |
+ net::HttpContentDisposition::HAS_FILENAME |
+ net::HttpContentDisposition::HAS_EXT_FILENAME)) ==
+ net::HttpContentDisposition::HAS_NAME);
+}
+
+void RecordFileThreadReceiveBuffers(size_t num_buffers) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Download.FileThreadReceiveBuffers", num_buffers, 1,
+ 100, 100);
+}
+
+void RecordBandwidth(double actual_bandwidth, double potential_bandwidth) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Download.ActualBandwidth", actual_bandwidth, 1, 1000000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Download.PotentialBandwidth", potential_bandwidth, 1, 1000000000, 50);
+ UMA_HISTOGRAM_PERCENTAGE(
+ "Download.BandwidthUsed",
+ (int) ((actual_bandwidth * 100)/ potential_bandwidth));
+}
+
+void RecordOpen(const base::Time& end, bool first) {
+ if (!end.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("Download.OpenTime", (base::Time::Now() - end));
+ if (first) {
+ UMA_HISTOGRAM_LONG_TIMES("Download.FirstOpenTime",
+ (base::Time::Now() - end));
+ }
+ }
+}
+
+void RecordClearAllSize(int size) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.ClearAllSize",
+ size,
+ 0/*min*/,
+ (1 << 10)/*max*/,
+ 32/*num_buckets*/);
+}
+
+void RecordOpensOutstanding(int size) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Download.OpensOutstanding",
+ size,
+ 0/*min*/,
+ (1 << 10)/*max*/,
+ 64/*num_buckets*/);
+}
+
+void RecordContiguousWriteTime(base::TimeDelta time_blocked) {
+ UMA_HISTOGRAM_TIMES("Download.FileThreadBlockedTime", time_blocked);
+}
+
+// Record what percentage of the time we have the network flow controlled.
+void RecordNetworkBlockage(base::TimeDelta resource_handler_lifetime,
+ base::TimeDelta resource_handler_blocked_time) {
+ int percentage = 0;
+ // Avoid division by zero errors.
+ if (resource_handler_blocked_time != base::TimeDelta()) {
+ percentage =
+ resource_handler_blocked_time * 100 / resource_handler_lifetime;
+ }
+
+ UMA_HISTOGRAM_COUNTS_100("Download.ResourceHandlerBlockedPercentage",
+ percentage);
+}
+
+void RecordFileBandwidth(size_t length,
+ base::TimeDelta disk_write_time,
+ base::TimeDelta elapsed_time) {
+ size_t elapsed_time_ms = elapsed_time.InMilliseconds();
+ if (0u == elapsed_time_ms)
+ elapsed_time_ms = 1;
+ size_t disk_write_time_ms = disk_write_time.InMilliseconds();
+ if (0u == disk_write_time_ms)
+ disk_write_time_ms = 1;
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Download.BandwidthOverallBytesPerSecond",
+ (1000 * length / elapsed_time_ms), 1, 50000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Download.BandwidthDiskBytesPerSecond",
+ (1000 * length / disk_write_time_ms), 1, 50000000, 50);
+ UMA_HISTOGRAM_COUNTS_100("Download.DiskBandwidthUsedPercentage",
+ disk_write_time_ms * 100 / elapsed_time_ms);
+}
+
+void RecordSavePackageEvent(SavePackageEvent event) {
+ UMA_HISTOGRAM_ENUMERATION("Download.SavePackage",
+ event,
+ SAVE_PACKAGE_LAST_ENTRY);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/download_stats.h b/chromium/content/browser/download/download_stats.h
new file mode 100644
index 00000000000..ebe643eaaec
--- /dev/null
+++ b/chromium/content/browser/download/download_stats.h
@@ -0,0 +1,211 @@
+// 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.
+//
+// Holds helpers for gathering UMA stats about downloads.
+
+#ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATS_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_danger_type.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+
+namespace base {
+class Time;
+class TimeDelta;
+class TimeTicks;
+}
+
+namespace content {
+
+// We keep a count of how often various events occur in the
+// histogram "Download.Counts".
+enum DownloadCountTypes {
+ // Stale enum values left around so that values passed to UMA don't
+ // change.
+ DOWNLOAD_COUNT_UNUSED_0 = 0,
+ DOWNLOAD_COUNT_UNUSED_1,
+ DOWNLOAD_COUNT_UNUSED_2,
+ DOWNLOAD_COUNT_UNUSED_3,
+ DOWNLOAD_COUNT_UNUSED_4,
+
+ // Downloads that made it to DownloadResourceHandler
+ UNTHROTTLED_COUNT,
+
+ // Downloads that actually complete.
+ COMPLETED_COUNT,
+
+ // Downloads that are cancelled before completion (user action or error).
+ CANCELLED_COUNT,
+
+ // Downloads that are started. Should be equal to UNTHROTTLED_COUNT.
+ START_COUNT,
+
+ // Downloads that were interrupted by the OS.
+ INTERRUPTED_COUNT,
+
+ // (Deprecated) Write sizes for downloads.
+ // This is equal to the number of samples in Download.WriteSize histogram.
+ DOWNLOAD_COUNT_UNUSED_10,
+
+ // (Deprecated) Counts iterations of the BaseFile::AppendDataToFile() loop.
+ // This is equal to the number of samples in Download.WriteLoopCount
+ // histogram.
+ DOWNLOAD_COUNT_UNUSED_11,
+
+ // Counts interruptions that happened at the end of the download.
+ INTERRUPTED_AT_END_COUNT,
+
+ // Counts errors due to writes to BaseFiles that have been detached already.
+ // This can happen when saving web pages as complete packages. It happens
+ // when we get messages to append data to files that have already finished and
+ // been detached, but haven't yet been removed from the list of files in
+ // progress.
+ APPEND_TO_DETACHED_FILE_COUNT,
+
+ // Counts the number of instances where the downloaded file is missing after a
+ // successful invocation of ScanAndSaveDownloadedFile().
+ FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT,
+
+ // Count of downloads that supplies a strong ETag and has a 'Accept-Ranges:
+ // bytes' header. These downloads are candidates for partial resumption.
+ STRONG_ETAG_AND_ACCEPTS_RANGES,
+
+ // Count of downloads that didn't have a valid WebContents at the time it was
+ // interrupted.
+ INTERRUPTED_WITHOUT_WEBCONTENTS,
+
+ DOWNLOAD_COUNT_TYPES_LAST_ENTRY
+};
+
+enum DownloadSource {
+ // The download was initiated when the SavePackage system rejected
+ // a Save Page As ... by returning false from
+ // SavePackage::IsSaveableContents().
+ INITIATED_BY_SAVE_PACKAGE_ON_NON_HTML = 0,
+
+ // The download was initiated by a drag and drop from a drag-and-drop
+ // enabled web application.
+ INITIATED_BY_DRAG_N_DROP,
+
+ // The download was initiated by explicit RPC from the renderer process
+ // (e.g. by Alt-click) through the IPC ViewHostMsg_DownloadUrl.
+ INITIATED_BY_RENDERER,
+
+ // Fomerly INITIATED_BY_PEPPER_SAVE.
+ DOWNLOAD_SOURCE_UNUSED_3,
+
+ // A request that was initiated as a result of resuming an interrupted
+ // download.
+ INITIATED_BY_RESUMPTION,
+
+ DOWNLOAD_SOURCE_LAST_ENTRY
+};
+
+enum DownloadDiscardReason {
+ // The download is being discarded due to a user action.
+ DOWNLOAD_DISCARD_DUE_TO_USER_ACTION,
+
+ // The download is being discarded due to the browser being shut down.
+ DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN
+};
+
+// Increment one of the above counts.
+void RecordDownloadCount(DownloadCountTypes type);
+
+// Record initiation of a download from a specific source.
+void RecordDownloadSource(DownloadSource source);
+
+// Record COMPLETED_COUNT and how long the download took.
+void RecordDownloadCompleted(const base::TimeTicks& start, int64 download_len);
+
+// Record INTERRUPTED_COUNT, |reason|, |received| and |total| bytes.
+void RecordDownloadInterrupted(DownloadInterruptReason reason,
+ int64 received,
+ int64 total);
+
+// Record a dangerous download accept event.
+void RecordDangerousDownloadAccept(DownloadDangerType danger_type);
+
+// Record a dangerous download discard event.
+void RecordDangerousDownloadDiscard(DownloadDiscardReason reason,
+ DownloadDangerType danger_type);
+
+// Records the mime type of the download.
+void RecordDownloadMimeType(const std::string& mime_type);
+
+// Records usage of Content-Disposition header.
+void RecordDownloadContentDisposition(const std::string& content_disposition);
+
+// Record WRITE_SIZE_COUNT and data_len.
+void RecordDownloadWriteSize(size_t data_len);
+
+// Record WRITE_LOOP_COUNT and number of loops.
+void RecordDownloadWriteLoopCount(int count);
+
+// Record the number of buffers piled up by the IO thread
+// before the file thread gets to draining them.
+void RecordFileThreadReceiveBuffers(size_t num_buffers);
+
+// Record the bandwidth seen in DownloadResourceHandler
+// |actual_bandwidth| and |potential_bandwidth| are in bytes/second.
+void RecordBandwidth(double actual_bandwidth, double potential_bandwidth);
+
+// Record the time of both the first open and all subsequent opens since the
+// download completed.
+void RecordOpen(const base::Time& end, bool first);
+
+// Record whether or not the server accepts ranges, and the download size. Also
+// counts if a strong ETag is supplied. The combination of range request support
+// and ETag indicates downloads that are candidates for partial resumption.
+void RecordAcceptsRanges(const std::string& accepts_ranges, int64 download_len,
+ const std::string& etag);
+
+// Record the number of downloads removed by ClearAll.
+void RecordClearAllSize(int size);
+
+// Record the number of completed unopened downloads when a download is opened.
+void RecordOpensOutstanding(int size);
+
+// Record how long we block the file thread at a time.
+void RecordContiguousWriteTime(base::TimeDelta time_blocked);
+
+// Record the percentage of time we had to block the network (i.e.
+// how often, for each download, something other than the network
+// was the bottleneck).
+void RecordNetworkBlockage(base::TimeDelta resource_handler_lifetime,
+ base::TimeDelta resource_handler_blocked_time);
+
+// Record overall bandwidth stats at the file end.
+void RecordFileBandwidth(size_t length,
+ base::TimeDelta disk_write_time,
+ base::TimeDelta elapsed_time);
+
+enum SavePackageEvent {
+ // The user has started to save a page as a package.
+ SAVE_PACKAGE_STARTED,
+
+ // The save package operation was cancelled.
+ SAVE_PACKAGE_CANCELLED,
+
+ // The save package operation finished without being cancelled.
+ SAVE_PACKAGE_FINISHED,
+
+ // The save package tried to write to an already completed file.
+ SAVE_PACKAGE_WRITE_TO_COMPLETED,
+
+ // The save package tried to write to an already failed file.
+ SAVE_PACKAGE_WRITE_TO_FAILED,
+
+ SAVE_PACKAGE_LAST_ENTRY
+};
+
+void RecordSavePackageEvent(SavePackageEvent event);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATS_H_
diff --git a/chromium/content/browser/download/drag_download_file.cc b/chromium/content/browser/download/drag_download_file.cc
new file mode 100644
index 00000000000..d81b04ccf6b
--- /dev/null
+++ b/chromium/content/browser/download/drag_download_file.cc
@@ -0,0 +1,242 @@
+// 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 "content/browser/download/drag_download_file.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_save_info.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "net/base/file_stream.h"
+
+namespace content {
+
+namespace {
+
+typedef base::Callback<void(bool)> OnCompleted;
+
+} // namespace
+
+// On windows, DragDownloadFile runs on a thread other than the UI thread.
+// DownloadItem and DownloadManager may not be accessed on any thread other than
+// the UI thread. DragDownloadFile may run on either the "drag" thread or the UI
+// thread depending on the platform, but DragDownloadFileUI strictly always runs
+// on the UI thread. On platforms where DragDownloadFile runs on the UI thread,
+// none of the PostTasks are necessary, but it simplifies the code to do them
+// anyway.
+class DragDownloadFile::DragDownloadFileUI : public DownloadItem::Observer {
+ public:
+ DragDownloadFileUI(const GURL& url,
+ const Referrer& referrer,
+ const std::string& referrer_encoding,
+ WebContents* web_contents,
+ base::MessageLoop* on_completed_loop,
+ const OnCompleted& on_completed)
+ : on_completed_loop_(on_completed_loop),
+ on_completed_(on_completed),
+ url_(url),
+ referrer_(referrer),
+ referrer_encoding_(referrer_encoding),
+ web_contents_(web_contents),
+ download_item_(NULL),
+ weak_ptr_factory_(this) {
+ DCHECK(on_completed_loop_);
+ DCHECK(!on_completed_.is_null());
+ DCHECK(web_contents_);
+ // May be called on any thread.
+ // Do not call weak_ptr_factory_.GetWeakPtr() outside the UI thread.
+ }
+
+ void InitiateDownload(scoped_ptr<net::FileStream> file_stream,
+ const base::FilePath& file_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DownloadManager* download_manager =
+ BrowserContext::GetDownloadManager(web_contents_->GetBrowserContext());
+
+ RecordDownloadSource(INITIATED_BY_DRAG_N_DROP);
+ scoped_ptr<content::DownloadUrlParameters> params(
+ DownloadUrlParameters::FromWebContents(web_contents_, url_));
+ params->set_referrer(referrer_);
+ params->set_referrer_encoding(referrer_encoding_);
+ params->set_callback(base::Bind(&DragDownloadFileUI::OnDownloadStarted,
+ weak_ptr_factory_.GetWeakPtr()));
+ params->set_file_path(file_path);
+ params->set_file_stream(file_stream.Pass()); // Nulls file_stream.
+ download_manager->DownloadUrl(params.Pass());
+ }
+
+ void Cancel() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (download_item_)
+ download_item_->Cancel(true);
+ }
+
+ void Delete() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ delete this;
+ }
+
+ private:
+ virtual ~DragDownloadFileUI() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (download_item_)
+ download_item_->RemoveObserver(this);
+ }
+
+ void OnDownloadStarted(DownloadItem* item, net::Error error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!item) {
+ DCHECK_NE(net::OK, error);
+ on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false));
+ return;
+ }
+ DCHECK_EQ(net::OK, error);
+ download_item_ = item;
+ download_item_->AddObserver(this);
+ }
+
+ // DownloadItem::Observer:
+ virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(download_item_, item);
+ DownloadItem::DownloadState state = download_item_->GetState();
+ if (state == DownloadItem::COMPLETE ||
+ state == DownloadItem::CANCELLED ||
+ state == DownloadItem::INTERRUPTED) {
+ if (!on_completed_.is_null()) {
+ on_completed_loop_->PostTask(FROM_HERE, base::Bind(
+ on_completed_, state == DownloadItem::COMPLETE));
+ on_completed_.Reset();
+ }
+ download_item_->RemoveObserver(this);
+ download_item_ = NULL;
+ }
+ // Ignore other states.
+ }
+
+ virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(download_item_, item);
+ if (!on_completed_.is_null()) {
+ const bool is_complete =
+ download_item_->GetState() == DownloadItem::COMPLETE;
+ on_completed_loop_->PostTask(FROM_HERE, base::Bind(
+ on_completed_, is_complete));
+ on_completed_.Reset();
+ }
+ download_item_->RemoveObserver(this);
+ download_item_ = NULL;
+ }
+
+ base::MessageLoop* on_completed_loop_;
+ OnCompleted on_completed_;
+ GURL url_;
+ Referrer referrer_;
+ std::string referrer_encoding_;
+ WebContents* web_contents_;
+ DownloadItem* download_item_;
+
+ // Only used in the callback from DownloadManager::DownloadUrl().
+ base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDownloadFileUI);
+};
+
+DragDownloadFile::DragDownloadFile(const base::FilePath& file_path,
+ scoped_ptr<net::FileStream> file_stream,
+ const GURL& url,
+ const Referrer& referrer,
+ const std::string& referrer_encoding,
+ WebContents* web_contents)
+ : file_path_(file_path),
+ file_stream_(file_stream.Pass()),
+ drag_message_loop_(base::MessageLoop::current()),
+ state_(INITIALIZED),
+ drag_ui_(NULL),
+ weak_ptr_factory_(this) {
+ drag_ui_ = new DragDownloadFileUI(
+ url,
+ referrer,
+ referrer_encoding,
+ web_contents,
+ drag_message_loop_,
+ base::Bind(&DragDownloadFile::DownloadCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+ DCHECK(!file_path_.empty());
+}
+
+DragDownloadFile::~DragDownloadFile() {
+ CheckThread();
+
+ // This is the only place that drag_ui_ can be deleted from. Post a message to
+ // the UI thread so that it calls RemoveObserver on the right thread, and so
+ // that this task will run after the InitiateDownload task runs on the UI
+ // thread.
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DragDownloadFileUI::Delete, base::Unretained(drag_ui_)));
+ drag_ui_ = NULL;
+}
+
+void DragDownloadFile::Start(ui::DownloadFileObserver* observer) {
+ CheckThread();
+
+ if (state_ != INITIALIZED)
+ return;
+ state_ = STARTED;
+
+ DCHECK(!observer_.get());
+ observer_ = observer;
+ DCHECK(observer_.get());
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DragDownloadFileUI::InitiateDownload, base::Unretained(drag_ui_),
+ base::Passed(&file_stream_), file_path_));
+}
+
+bool DragDownloadFile::Wait() {
+ CheckThread();
+ if (state_ == STARTED)
+ nested_loop_.Run();
+ return state_ == SUCCESS;
+}
+
+void DragDownloadFile::Stop() {
+ CheckThread();
+ if (drag_ui_) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &DragDownloadFileUI::Cancel, base::Unretained(drag_ui_)));
+ }
+}
+
+void DragDownloadFile::DownloadCompleted(bool is_successful) {
+ CheckThread();
+
+ state_ = is_successful ? SUCCESS : FAILURE;
+
+ if (is_successful)
+ observer_->OnDownloadCompleted(file_path_);
+ else
+ observer_->OnDownloadAborted();
+
+ // Release the observer since we do not need it any more.
+ observer_ = NULL;
+
+ if (nested_loop_.running())
+ nested_loop_.Quit();
+}
+
+void DragDownloadFile::CheckThread() {
+#if defined(OS_WIN)
+ DCHECK(drag_message_loop_ == base::MessageLoop::current());
+#else
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+#endif
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/drag_download_file.h b/chromium/content/browser/download/drag_download_file.h
new file mode 100644
index 00000000000..41fdf6224e2
--- /dev/null
+++ b/chromium/content/browser/download/drag_download_file.h
@@ -0,0 +1,77 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_FILE_H_
+#define CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_FILE_H_
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "content/browser/download/download_file.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/common/referrer.h"
+#include "net/base/file_stream.h"
+#include "ui/base/dragdrop/download_file_interface.h"
+#include "ui/base/ui_export.h"
+#include "url/gurl.h"
+
+namespace net {
+class FileStream;
+}
+
+namespace content {
+
+class DownloadManager;
+class WebContents;
+
+// This class implements downloading a file via dragging virtual files out of
+// the browser:
+// http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2009-August/022118.html
+// http://www.html5rocks.com/en/tutorials/casestudies/box_dnd_download/
+class CONTENT_EXPORT DragDownloadFile : public ui::DownloadFileProvider {
+ public:
+ // On Windows, we need to download into a temporary file. On posix, we need to
+ // download into a file stream that has already been created, so only the UI
+ // thread is involved. |file_stream| must be null on windows but non-null on
+ // posix systems. |file_path| is an absolute path on all systems.
+ DragDownloadFile(const base::FilePath& file_path,
+ scoped_ptr<net::FileStream> file_stream,
+ const GURL& url,
+ const Referrer& referrer,
+ const std::string& referrer_encoding,
+ WebContents* web_contents);
+
+ // DownloadFileProvider methods.
+ virtual void Start(ui::DownloadFileObserver* observer) OVERRIDE;
+ virtual bool Wait() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+
+ private:
+ class DragDownloadFileUI;
+ enum State {INITIALIZED, STARTED, SUCCESS, FAILURE};
+
+ virtual ~DragDownloadFile();
+
+ void DownloadCompleted(bool is_successful);
+ void CheckThread();
+
+ base::FilePath file_path_;
+ scoped_ptr<net::FileStream> file_stream_;
+ base::MessageLoop* drag_message_loop_;
+ State state_;
+ scoped_refptr<ui::DownloadFileObserver> observer_;
+ base::RunLoop nested_loop_;
+ DragDownloadFileUI* drag_ui_;
+ base::WeakPtrFactory<DragDownloadFile> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDownloadFile);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_FILE_H_
diff --git a/chromium/content/browser/download/drag_download_file_browsertest.cc b/chromium/content/browser/download/drag_download_file_browsertest.cc
new file mode 100644
index 00000000000..fc35d6c04e0
--- /dev/null
+++ b/chromium/content/browser/download/drag_download_file_browsertest.cc
@@ -0,0 +1,137 @@
+// 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 "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_file_impl.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/download/drag_download_file.h"
+#include "content/browser/download/drag_download_util.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/shell/shell_browser_context.h"
+#include "content/shell/shell_download_manager_delegate.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "content/test/net/url_request_slow_download_job.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using testing::_;
+using testing::InvokeWithoutArgs;
+
+namespace content {
+
+class MockDownloadFileObserver : public ui::DownloadFileObserver {
+ public:
+ MockDownloadFileObserver() {}
+
+ MOCK_METHOD1(OnDownloadCompleted, void(const base::FilePath& file_path));
+ MOCK_METHOD0(OnDownloadAborted, void());
+
+ private:
+ virtual ~MockDownloadFileObserver() {}
+
+ DISALLOW_COPY_AND_ASSIGN(MockDownloadFileObserver);
+};
+
+class DragDownloadFileTest : public ContentBrowserTest {
+ public:
+ DragDownloadFileTest() {}
+ virtual ~DragDownloadFileTest() {}
+
+ void Succeed() {
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::MessageLoopForUI::current()->QuitClosure());
+ }
+
+ void FailFast() {
+ CHECK(false);
+ }
+
+ protected:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+ ShellDownloadManagerDelegate* delegate =
+ static_cast<ShellDownloadManagerDelegate*>(
+ shell()->web_contents()->GetBrowserContext()
+ ->GetDownloadManagerDelegate());
+ delegate->SetDownloadBehaviorForTesting(downloads_directory());
+ }
+
+ void SetUpServer() {
+ base::FilePath mock_base(GetTestFilePath("download", ""));
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestMockHTTPJob::AddUrlHandler, mock_base));
+ }
+
+ const base::FilePath& downloads_directory() const {
+ return downloads_directory_.path();
+ }
+
+ private:
+ base::ScopedTempDir downloads_directory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDownloadFileTest);
+};
+
+IN_PROC_BROWSER_TEST_F(DragDownloadFileTest, DragDownloadFileTest_NetError) {
+ base::FilePath name(downloads_directory().AppendASCII(
+ "DragDownloadFileTest_NetError.txt"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(base::FilePath(FILE_PATH_LITERAL(
+ "download-test.lib"))));
+ Referrer referrer;
+ std::string referrer_encoding;
+ DragDownloadFile* file = new DragDownloadFile(name,
+ scoped_ptr<net::FileStream>(),
+ url,
+ referrer,
+ referrer_encoding,
+ shell()->web_contents());
+ scoped_refptr<MockDownloadFileObserver> observer(
+ new MockDownloadFileObserver());
+ EXPECT_CALL(*observer.get(), OnDownloadAborted())
+ .WillOnce(InvokeWithoutArgs(this, &DragDownloadFileTest::Succeed));
+ ON_CALL(*observer.get(), OnDownloadCompleted(_))
+ .WillByDefault(InvokeWithoutArgs(this, &DragDownloadFileTest::FailFast));
+ file->Start(observer.get());
+ RunMessageLoop();
+}
+
+IN_PROC_BROWSER_TEST_F(DragDownloadFileTest, DragDownloadFileTest_Complete) {
+ base::FilePath name(downloads_directory().AppendASCII(
+ "DragDownloadFileTest_Complete.txt"));
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(base::FilePath(FILE_PATH_LITERAL(
+ "download-test.lib"))));
+ Referrer referrer;
+ std::string referrer_encoding;
+ net::FileStream* stream = NULL;
+ SetUpServer();
+ DragDownloadFile* file = new DragDownloadFile(
+ name, scoped_ptr<net::FileStream>(stream), url, referrer,
+ referrer_encoding, shell()->web_contents());
+ scoped_refptr<MockDownloadFileObserver> observer(
+ new MockDownloadFileObserver());
+ EXPECT_CALL(*observer.get(), OnDownloadCompleted(_))
+ .WillOnce(InvokeWithoutArgs(this, &DragDownloadFileTest::Succeed));
+ ON_CALL(*observer.get(), OnDownloadAborted())
+ .WillByDefault(InvokeWithoutArgs(this, &DragDownloadFileTest::FailFast));
+ file->Start(observer.get());
+ RunMessageLoop();
+}
+
+// TODO(benjhayden): Test Stop().
+
+} // namespace content
diff --git a/chromium/content/browser/download/drag_download_util.cc b/chromium/content/browser/download/drag_download_util.cc
new file mode 100644
index 00000000000..41bff06dd27
--- /dev/null
+++ b/chromium/content/browser/download/drag_download_util.cc
@@ -0,0 +1,121 @@
+// 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 "content/browser/download/drag_download_util.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+using net::FileStream;
+
+namespace content {
+
+bool ParseDownloadMetadata(const string16& metadata,
+ string16* mime_type,
+ base::FilePath* file_name,
+ GURL* url) {
+ const char16 separator = L':';
+
+ size_t mime_type_end_pos = metadata.find(separator);
+ if (mime_type_end_pos == string16::npos)
+ return false;
+
+ size_t file_name_end_pos = metadata.find(separator, mime_type_end_pos + 1);
+ if (file_name_end_pos == string16::npos)
+ return false;
+
+ GURL parsed_url = GURL(metadata.substr(file_name_end_pos + 1));
+ if (!parsed_url.is_valid())
+ return false;
+
+ if (mime_type)
+ *mime_type = metadata.substr(0, mime_type_end_pos);
+ if (file_name) {
+ string16 file_name_str = metadata.substr(
+ mime_type_end_pos + 1, file_name_end_pos - mime_type_end_pos - 1);
+#if defined(OS_WIN)
+ *file_name = base::FilePath(file_name_str);
+#else
+ *file_name = base::FilePath(UTF16ToUTF8(file_name_str));
+#endif
+ }
+ if (url)
+ *url = parsed_url;
+
+ return true;
+}
+
+FileStream* CreateFileStreamForDrop(base::FilePath* file_path,
+ net::NetLog* net_log) {
+ DCHECK(file_path && !file_path->empty());
+
+ scoped_ptr<FileStream> file_stream(new FileStream(net_log));
+ const int kMaxSeq = 99;
+ for (int seq = 0; seq <= kMaxSeq; seq++) {
+ base::FilePath new_file_path;
+ if (seq == 0) {
+ new_file_path = *file_path;
+ } else {
+#if defined(OS_WIN)
+ string16 suffix = ASCIIToUTF16("-") + base::IntToString16(seq);
+#else
+ std::string suffix = std::string("-") + base::IntToString(seq);
+#endif
+ new_file_path = file_path->InsertBeforeExtension(suffix);
+ }
+
+ // http://crbug.com/110709
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Explicitly (and redundantly check) for file -- despite the fact that our
+ // open won't overwrite -- just to avoid log spew.
+ if (!base::PathExists(new_file_path) &&
+ file_stream->OpenSync(new_file_path, base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_WRITE) == net::OK) {
+ *file_path = new_file_path;
+ return file_stream.release();
+ }
+ }
+
+ return NULL;
+}
+
+PromiseFileFinalizer::PromiseFileFinalizer(
+ DragDownloadFile* drag_file_downloader)
+ : drag_file_downloader_(drag_file_downloader) {
+}
+
+void PromiseFileFinalizer::OnDownloadCompleted(
+ const base::FilePath& file_path) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PromiseFileFinalizer::Cleanup, this));
+}
+
+void PromiseFileFinalizer::OnDownloadAborted() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PromiseFileFinalizer::Cleanup, this));
+}
+
+PromiseFileFinalizer::~PromiseFileFinalizer() {}
+
+void PromiseFileFinalizer::Cleanup() {
+ if (drag_file_downloader_.get())
+ drag_file_downloader_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/drag_download_util.h b/chromium/content/browser/download/drag_download_util.h
new file mode 100644
index 00000000000..8b58f4840e2
--- /dev/null
+++ b/chromium/content/browser/download/drag_download_util.h
@@ -0,0 +1,69 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_UTIL_H_
+#define CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_UTIL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "content/browser/download/drag_download_file.h"
+#include "ui/base/dragdrop/download_file_interface.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace content {
+
+// Parse the download metadata set in DataTransfer.setData. The metadata
+// consists of a set of the following values separated by ":"
+// * MIME type
+// * File name
+// * URL
+// If the file name contains special characters, they need to be escaped
+// appropriately.
+// For example, we can have
+// text/plain:example.txt:http://example.com/example.txt
+bool ParseDownloadMetadata(const string16& metadata,
+ string16* mime_type,
+ base::FilePath* file_name,
+ GURL* url);
+
+// Create a new file at the specified path. If the file already exists, try to
+// insert the sequential unifier to produce a new file, like foo-01.txt.
+// Return a FileStream if successful.
+// |net_log| is a NetLog for the stream.
+CONTENT_EXPORT net::FileStream* CreateFileStreamForDrop(
+ base::FilePath* file_path, net::NetLog* net_log);
+
+// Implementation of DownloadFileObserver to finalize the download process.
+class PromiseFileFinalizer : public ui::DownloadFileObserver {
+ public:
+ explicit PromiseFileFinalizer(DragDownloadFile* drag_file_downloader);
+
+ // DownloadFileObserver methods.
+ virtual void OnDownloadCompleted(const base::FilePath& file_path) OVERRIDE;
+ virtual void OnDownloadAborted() OVERRIDE;
+
+ protected:
+ virtual ~PromiseFileFinalizer();
+
+ private:
+ void Cleanup();
+
+ scoped_refptr<DragDownloadFile> drag_file_downloader_;
+
+ DISALLOW_COPY_AND_ASSIGN(PromiseFileFinalizer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_DRAG_DOWNLOAD_UTIL_H_
diff --git a/chromium/content/browser/download/file_metadata_linux.cc b/chromium/content/browser/download/file_metadata_linux.cc
new file mode 100644
index 00000000000..c06d3f1a90f
--- /dev/null
+++ b/chromium/content/browser/download/file_metadata_linux.cc
@@ -0,0 +1,43 @@
+// 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 "content/browser/download/file_metadata_linux.h"
+
+#include <sys/types.h>
+#include <sys/xattr.h>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "url/gurl.h"
+
+namespace content {
+
+const char kSourceURLAttrName[] = "user.xdg.origin.url";
+const char kReferrerURLAttrName[] = "user.xdg.referrer.url";
+
+static void SetExtendedFileAttribute(const char* path, const char* name,
+ const char* value, size_t value_size,
+ int flags) {
+ int result = setxattr(path, name, value, value_size, flags);
+ if (result) {
+ DPLOG(ERROR)
+ << "Could not set extended attribute " << name << " on file " << path;
+ }
+}
+
+void AddOriginMetadataToFile(const base::FilePath& file, const GURL& source,
+ const GURL& referrer) {
+ DCHECK(base::PathIsWritable(file));
+ if (source.is_valid()) {
+ SetExtendedFileAttribute(file.value().c_str(), kSourceURLAttrName,
+ source.spec().c_str(), source.spec().length(), 0);
+ }
+ if (referrer.is_valid()) {
+ SetExtendedFileAttribute(file.value().c_str(), kReferrerURLAttrName,
+ referrer.spec().c_str(), referrer.spec().length(), 0);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/file_metadata_linux.h b/chromium/content/browser/download/file_metadata_linux.h
new file mode 100644
index 00000000000..c4fc3045c41
--- /dev/null
+++ b/chromium/content/browser/download/file_metadata_linux.h
@@ -0,0 +1,34 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_LINUX_H_
+#define CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_LINUX_H_
+
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+// The source URL attribute is part of the XDG standard.
+// The referrer URL attribute is not part of the XDG standard,
+// but it is used to keep the naming consistent.
+// http://freedesktop.org/wiki/CommonExtendedAttributes
+CONTENT_EXPORT extern const char kSourceURLAttrName[];
+CONTENT_EXPORT extern const char kReferrerURLAttrName[];
+
+// Adds origin metadata to the file.
+// |source| should be the source URL for the download, and |referrer| should be
+// the URL the user initiated the download from.
+CONTENT_EXPORT void AddOriginMetadataToFile(const base::FilePath& file,
+ const GURL& source,
+ const GURL& referrer);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_LINUX_H_
diff --git a/chromium/content/browser/download/file_metadata_mac.h b/chromium/content/browser/download/file_metadata_mac.h
new file mode 100644
index 00000000000..19a3f07ff62
--- /dev/null
+++ b/chromium/content/browser/download/file_metadata_mac.h
@@ -0,0 +1,31 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_MAC_H_
+#define CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_MAC_H_
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+// Adds origin metadata to the file.
+// |source| should be the source URL for the download, and |referrer| should be
+// the URL the user initiated the download from.
+void AddOriginMetadataToFile(const base::FilePath& file, const GURL& source,
+ const GURL& referrer);
+
+// Adds quarantine metadata to the file, assuming it has already been
+// quarantined by the OS.
+// |source| should be the source URL for the download, and |referrer| should be
+// the URL the user initiated the download from.
+void AddQuarantineMetadataToFile(const base::FilePath& file, const GURL& source,
+ const GURL& referrer);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_FILE_METADATA_MAC_H_
diff --git a/chromium/content/browser/download/file_metadata_mac.mm b/chromium/content/browser/download/file_metadata_mac.mm
new file mode 100644
index 00000000000..fc0556e0a3b
--- /dev/null
+++ b/chromium/content/browser/download/file_metadata_mac.mm
@@ -0,0 +1,168 @@
+// 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 "content/browser/download/file_metadata_mac.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Foundation/Foundation.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// As of Mac OS X 10.4 ("Tiger"), files can be tagged with metadata describing
+// various attributes. Metadata is integrated with the system's Spotlight
+// feature and is searchable. Ordinarily, metadata can only be set by
+// Spotlight importers, which requires that the importer own the target file.
+// However, there's an attribute intended to describe the origin of a
+// file, that can store the source URL and referrer of a downloaded file.
+// It's stored as a "com.apple.metadata:kMDItemWhereFroms" extended attribute,
+// structured as a binary1-format plist containing a list of sources. This
+// attribute can only be populated by the downloader, not a Spotlight importer.
+// Safari on 10.4 and later populates this attribute.
+//
+// With this metadata set, you can locate downloads by performing a Spotlight
+// search for their source or referrer URLs, either from within the Spotlight
+// UI or from the command line:
+// mdfind 'kMDItemWhereFroms == "http://releases.mozilla.org/*"'
+//
+// There is no documented API to set metadata on a file directly as of the
+// 10.5 SDK. The MDSetItemAttribute function does exist to perform this task,
+// but it's undocumented.
+void AddOriginMetadataToFile(const base::FilePath& file, const GURL& source,
+ const GURL& referrer) {
+ // There's no declaration for MDItemSetAttribute in any known public SDK.
+ // It exists in the 10.4 and 10.5 runtimes. To play it safe, do the lookup
+ // at runtime instead of declaring it ourselves and linking against what's
+ // provided. This has two benefits:
+ // - If Apple relents and declares the function in a future SDK (it's
+ // happened before), our build won't break.
+ // - If Apple removes or renames the function in a future runtime, the
+ // loader won't refuse to let the application launch. Instead, we'll
+ // silently fail to set any metadata.
+ typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef,
+ CFTypeRef);
+ static MDItemSetAttribute_type md_item_set_attribute_func = NULL;
+
+ static bool did_symbol_lookup = false;
+ if (!did_symbol_lookup) {
+ did_symbol_lookup = true;
+ CFBundleRef metadata_bundle =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata"));
+ if (!metadata_bundle)
+ return;
+
+ md_item_set_attribute_func = (MDItemSetAttribute_type)
+ CFBundleGetFunctionPointerForName(metadata_bundle,
+ CFSTR("MDItemSetAttribute"));
+ }
+ if (!md_item_set_attribute_func)
+ return;
+
+ NSString* file_path =
+ [NSString stringWithUTF8String:file.value().c_str()];
+ if (!file_path)
+ return;
+
+ base::ScopedCFTypeRef<MDItemRef> md_item(
+ MDItemCreate(NULL, base::mac::NSToCFCast(file_path)));
+ if (!md_item)
+ return;
+
+ // We won't put any more than 2 items into the attribute.
+ NSMutableArray* list = [NSMutableArray arrayWithCapacity:2];
+
+ // Follow Safari's lead: the first item in the list is the source URL of
+ // the downloaded file. If the referrer is known, store that, too.
+ NSString* origin_url = [NSString stringWithUTF8String:source.spec().c_str()];
+ if (origin_url)
+ [list addObject:origin_url];
+ NSString* referrer_url =
+ [NSString stringWithUTF8String:referrer.spec().c_str()];
+ if (referrer_url)
+ [list addObject:referrer_url];
+
+ md_item_set_attribute_func(md_item, kMDItemWhereFroms,
+ base::mac::NSToCFCast(list));
+}
+
+// The OS will automatically quarantine files due to the
+// LSFileQuarantineEnabled entry in our Info.plist, but it knows relatively
+// little about the files. We add more information about the download to
+// improve the UI shown by the OS when the users tries to open the file.
+void AddQuarantineMetadataToFile(const base::FilePath& file, const GURL& source,
+ const GURL& referrer) {
+ FSRef file_ref;
+ if (!base::mac::FSRefFromPath(file.value(), &file_ref))
+ return;
+
+ NSMutableDictionary* quarantine_properties = nil;
+ CFTypeRef quarantine_properties_base = NULL;
+ if (LSCopyItemAttribute(&file_ref, kLSRolesAll, kLSItemQuarantineProperties,
+ &quarantine_properties_base) == noErr) {
+ if (CFGetTypeID(quarantine_properties_base) ==
+ CFDictionaryGetTypeID()) {
+ // Quarantine properties will already exist if LSFileQuarantineEnabled
+ // is on and the file doesn't match an exclusion.
+ quarantine_properties =
+ [[(NSDictionary*)quarantine_properties_base mutableCopy] autorelease];
+ } else {
+ LOG(WARNING) << "kLSItemQuarantineProperties is not a dictionary on file "
+ << file.value();
+ }
+ CFRelease(quarantine_properties_base);
+ }
+
+ if (!quarantine_properties) {
+ // If there are no quarantine properties, then the file isn't quarantined
+ // (e.g., because the user has set up exclusions for certain file types).
+ // We don't want to add any metadata, because that will cause the file to
+ // be quarantined against the user's wishes.
+ return;
+ }
+
+ // kLSQuarantineAgentNameKey, kLSQuarantineAgentBundleIdentifierKey, and
+ // kLSQuarantineTimeStampKey are set for us (see LSQuarantine.h), so we only
+ // need to set the values that the OS can't infer.
+
+ if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineTypeKey]) {
+ CFStringRef type = (source.SchemeIs("http") || source.SchemeIs("https"))
+ ? kLSQuarantineTypeWebDownload
+ : kLSQuarantineTypeOtherDownload;
+ [quarantine_properties setValue:(NSString*)type
+ forKey:(NSString*)kLSQuarantineTypeKey];
+ }
+
+ if (![quarantine_properties
+ valueForKey:(NSString*)kLSQuarantineOriginURLKey] &&
+ referrer.is_valid()) {
+ NSString* referrer_url =
+ [NSString stringWithUTF8String:referrer.spec().c_str()];
+ [quarantine_properties setValue:referrer_url
+ forKey:(NSString*)kLSQuarantineOriginURLKey];
+ }
+
+ if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineDataURLKey] &&
+ source.is_valid()) {
+ NSString* origin_url =
+ [NSString stringWithUTF8String:source.spec().c_str()];
+ [quarantine_properties setValue:origin_url
+ forKey:(NSString*)kLSQuarantineDataURLKey];
+ }
+
+ OSStatus os_error = LSSetItemAttribute(&file_ref, kLSRolesAll,
+ kLSItemQuarantineProperties,
+ quarantine_properties);
+ if (os_error != noErr) {
+ OSSTATUS_LOG(WARNING, os_error)
+ << "Unable to set quarantine attributes on file " << file.value();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/file_metadata_unittest_linux.cc b/chromium/content/browser/download/file_metadata_unittest_linux.cc
new file mode 100644
index 00000000000..4a3ebca5a2c
--- /dev/null
+++ b/chromium/content/browser/download/file_metadata_unittest_linux.cc
@@ -0,0 +1,164 @@
+// 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 <errno.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "content/browser/download/file_metadata_linux.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+using std::istringstream;
+using std::string;
+using std::vector;
+
+class FileMetadataLinuxTest : public testing::Test {
+ public:
+ FileMetadataLinuxTest()
+ : source_url_("http://www.source.com"),
+ referrer_url_("http://www.referrer.com"),
+ is_xattr_supported_(false) {}
+
+ const base::FilePath& test_file() const {
+ return test_file_;
+ }
+
+ const GURL& source_url() const {
+ return source_url_;
+ }
+
+ const GURL& referrer_url() const {
+ return referrer_url_;
+ }
+
+ bool is_xattr_supported() const {
+ return is_xattr_supported_;
+ }
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &test_file_));
+ int result = setxattr(test_file_.value().c_str(),
+ "user.test", "test", 4, 0);
+ is_xattr_supported_ = (!result) || (errno != ENOTSUP);
+ if (!is_xattr_supported_) {
+ LOG(INFO) << "Test will be skipped because extended attributes are not "
+ << "supported on this OS/file system.";
+ }
+ }
+
+ void CheckExtendedAttributeValue(const string attr_name,
+ const string expected_value) const {
+ ssize_t len = getxattr(test_file().value().c_str(), attr_name.c_str(),
+ NULL, 0);
+ if (len <= static_cast<ssize_t>(0)) {
+ FAIL() << "Attribute '" << attr_name << "' does not exist";
+ }
+ char* buffer = new char[len];
+ len = getxattr(test_file().value().c_str(), attr_name.c_str(), buffer, len);
+ EXPECT_EQ(expected_value.size(), static_cast<size_t>(len));
+ string real_value(buffer, len);
+ delete[] buffer;
+ EXPECT_EQ(expected_value, real_value);
+ }
+
+ void GetExtendedAttributeNames(vector<string>* attr_names) const {
+ ssize_t len = listxattr(test_file().value().c_str(), NULL, 0);
+ if (len <= static_cast<ssize_t>(0)) return;
+ char* buffer = new char[len];
+ len = listxattr(test_file().value().c_str(), buffer, len);
+ attr_names->clear();
+ base::SplitString(string(buffer, len), '\0', attr_names);
+ delete[] buffer;
+ }
+
+ void VerifyAttributesAreSetCorrectly() const {
+ vector<string> attr_names;
+ GetExtendedAttributeNames(&attr_names);
+
+ // Check if the attributes are set on the file
+ vector<string>::const_iterator pos = find(attr_names.begin(),
+ attr_names.end(), kSourceURLAttrName);
+ EXPECT_NE(pos, attr_names.end());
+ pos = find(attr_names.begin(), attr_names.end(), kReferrerURLAttrName);
+ EXPECT_NE(pos, attr_names.end());
+
+ // Check if the attribute values are set correctly
+ CheckExtendedAttributeValue(kSourceURLAttrName, source_url().spec());
+ CheckExtendedAttributeValue(kReferrerURLAttrName, referrer_url().spec());
+ }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ base::FilePath test_file_;
+ GURL source_url_;
+ GURL referrer_url_;
+ bool is_xattr_supported_;
+};
+
+TEST_F(FileMetadataLinuxTest, CheckMetadataSetCorrectly) {
+ if (!is_xattr_supported()) return;
+ AddOriginMetadataToFile(test_file(), source_url(), referrer_url());
+ VerifyAttributesAreSetCorrectly();
+}
+
+TEST_F(FileMetadataLinuxTest, SetMetadataMultipleTimes) {
+ if (!is_xattr_supported()) return;
+ GURL dummy_url("http://www.dummy.com");
+ AddOriginMetadataToFile(test_file(), dummy_url, dummy_url);
+ AddOriginMetadataToFile(test_file(), source_url(), referrer_url());
+ VerifyAttributesAreSetCorrectly();
+}
+
+TEST_F(FileMetadataLinuxTest, InvalidSourceURLTest) {
+ if (!is_xattr_supported()) return;
+ GURL invalid_url;
+ vector<string> attr_names;
+ AddOriginMetadataToFile(test_file(), invalid_url, referrer_url());
+ GetExtendedAttributeNames(&attr_names);
+ EXPECT_EQ(attr_names.end(), find(attr_names.begin(), attr_names.end(),
+ kSourceURLAttrName));
+ CheckExtendedAttributeValue(kReferrerURLAttrName, referrer_url().spec());
+}
+
+TEST_F(FileMetadataLinuxTest, InvalidReferrerURLTest) {
+ if (!is_xattr_supported()) return;
+ GURL invalid_url;
+ vector<string> attr_names;
+ AddOriginMetadataToFile(test_file(), source_url(), invalid_url);
+ GetExtendedAttributeNames(&attr_names);
+ EXPECT_EQ(attr_names.end(), find(attr_names.begin(), attr_names.end(),
+ kReferrerURLAttrName));
+ CheckExtendedAttributeValue(kSourceURLAttrName, source_url().spec());
+}
+
+TEST_F(FileMetadataLinuxTest, InvalidURLsTest) {
+ if (!is_xattr_supported()) return;
+ GURL invalid_url;
+ vector<string> attr_names;
+ AddOriginMetadataToFile(test_file(), invalid_url, invalid_url);
+ GetExtendedAttributeNames(&attr_names);
+ EXPECT_EQ(attr_names.end(), find(attr_names.begin(), attr_names.end(),
+ kSourceURLAttrName));
+ EXPECT_EQ(attr_names.end(), find(attr_names.begin(), attr_names.end(),
+ kReferrerURLAttrName));
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/download/mhtml_generation_browsertest.cc b/chromium/content/browser/download/mhtml_generation_browsertest.cc
new file mode 100644
index 00000000000..07bffed6b4b
--- /dev/null
+++ b/chromium/content/browser/download/mhtml_generation_browsertest.cc
@@ -0,0 +1,73 @@
+// 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 "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class MHTMLGenerationTest : public ContentBrowserTest {
+ public:
+ MHTMLGenerationTest() : mhtml_generated_(false), file_size_(0) {}
+
+ void MHTMLGenerated(const base::FilePath& path, int64 size) {
+ mhtml_generated_ = true;
+ file_size_ = size;
+ base::MessageLoopForUI::current()->Quit();
+ }
+
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ContentBrowserTest::SetUp();
+ }
+
+ bool mhtml_generated() const { return mhtml_generated_; }
+ int64 file_size() const { return file_size_; }
+
+ base::ScopedTempDir temp_dir_;
+
+ private:
+ bool mhtml_generated_;
+ int64 file_size_;
+};
+
+// Tests that generating a MHTML does create contents.
+// Note that the actual content of the file is not tested, the purpose of this
+// test is to ensure we were successfull in creating the MHTML data from the
+// renderer.
+IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTML) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ base::FilePath path(temp_dir_.path());
+ path = path.Append(FILE_PATH_LITERAL("test.mht"));
+
+ NavigateToURL(shell(), embedded_test_server()->GetURL("/simple_page.html"));
+
+ shell()->web_contents()->GenerateMHTML(
+ path, base::Bind(&MHTMLGenerationTest::MHTMLGenerated, this));
+
+ // Block until the MHTML is generated.
+ RunMessageLoop();
+
+ EXPECT_TRUE(mhtml_generated());
+ EXPECT_GT(file_size(), 0);
+
+ // Make sure the actual generated file has some contents.
+ int64 file_size;
+ ASSERT_TRUE(file_util::GetFileSize(path, &file_size));
+ EXPECT_GT(file_size, 100);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/mhtml_generation_manager.cc b/chromium/content/browser/download/mhtml_generation_manager.cc
new file mode 100644
index 00000000000..c4bc378c148
--- /dev/null
+++ b/chromium/content/browser/download/mhtml_generation_manager.cc
@@ -0,0 +1,170 @@
+// 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 "content/browser/download/mhtml_generation_manager.h"
+
+#include "base/bind.h"
+#include "base/platform_file.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_types.h"
+
+namespace content {
+
+MHTMLGenerationManager::Job::Job()
+ : browser_file(base::kInvalidPlatformFileValue),
+ renderer_file(IPC::InvalidPlatformFileForTransit()),
+ process_id(-1),
+ routing_id(-1) {
+}
+
+MHTMLGenerationManager::Job::~Job() {
+}
+
+MHTMLGenerationManager* MHTMLGenerationManager::GetInstance() {
+ return Singleton<MHTMLGenerationManager>::get();
+}
+
+MHTMLGenerationManager::MHTMLGenerationManager() {
+}
+
+MHTMLGenerationManager::~MHTMLGenerationManager() {
+}
+
+void MHTMLGenerationManager::GenerateMHTML(
+ WebContents* web_contents,
+ const base::FilePath& file,
+ const GenerateMHTMLCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ static int id_counter = 0;
+
+ int job_id = id_counter++;
+ Job job;
+ job.file_path = file;
+ job.process_id = web_contents->GetRenderProcessHost()->GetID();
+ job.routing_id = web_contents->GetRenderViewHost()->GetRoutingID();
+ job.callback = callback;
+ id_to_job_[job_id] = job;
+ if (!registrar_.IsRegistered(
+ this,
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ Source<RenderProcessHost>(web_contents->GetRenderProcessHost()))) {
+ registrar_.Add(
+ this,
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ Source<RenderProcessHost>(web_contents->GetRenderProcessHost()));
+ }
+
+ base::ProcessHandle renderer_process =
+ web_contents->GetRenderProcessHost()->GetHandle();
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&MHTMLGenerationManager::CreateFile, base::Unretained(this),
+ job_id, file, renderer_process));
+}
+
+void MHTMLGenerationManager::MHTMLGenerated(int job_id, int64 mhtml_data_size) {
+ JobFinished(job_id, mhtml_data_size);
+}
+
+void MHTMLGenerationManager::CreateFile(
+ int job_id, const base::FilePath& file_path,
+ base::ProcessHandle renderer_process) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::PlatformFile browser_file = base::CreatePlatformFile(file_path,
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
+ NULL, NULL);
+ if (browser_file == base::kInvalidPlatformFileValue) {
+ LOG(ERROR) << "Failed to create file to save MHTML at: " <<
+ file_path.value();
+ }
+
+ IPC::PlatformFileForTransit renderer_file =
+ IPC::GetFileHandleForProcess(browser_file, renderer_process, false);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&MHTMLGenerationManager::FileCreated, base::Unretained(this),
+ job_id, browser_file, renderer_file));
+}
+
+void MHTMLGenerationManager::FileCreated(int job_id,
+ base::PlatformFile browser_file,
+ IPC::PlatformFileForTransit renderer_file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (browser_file == base::kInvalidPlatformFileValue) {
+ LOG(ERROR) << "Failed to create file";
+ JobFinished(job_id, -1);
+ return;
+ }
+
+ IDToJobMap::iterator iter = id_to_job_.find(job_id);
+ if (iter == id_to_job_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ Job& job = iter->second;
+ job.browser_file = browser_file;
+ job.renderer_file = renderer_file;
+
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(
+ job.process_id, job.routing_id);
+ if (!rvh) {
+ // The contents went away.
+ JobFinished(job_id, -1);
+ return;
+ }
+
+ rvh->Send(new ViewMsg_SavePageAsMHTML(rvh->GetRoutingID(), job_id,
+ renderer_file));
+}
+
+void MHTMLGenerationManager::JobFinished(int job_id, int64 file_size) {
+ IDToJobMap::iterator iter = id_to_job_.find(job_id);
+ if (iter == id_to_job_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ Job& job = iter->second;
+ job.callback.Run(job.file_path, file_size);
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&MHTMLGenerationManager::CloseFile, base::Unretained(this),
+ job.browser_file));
+
+ id_to_job_.erase(job_id);
+}
+
+void MHTMLGenerationManager::CloseFile(base::PlatformFile file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::ClosePlatformFile(file);
+}
+
+void MHTMLGenerationManager::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ RenderProcessHost* host = Source<RenderProcessHost>(source).ptr();
+ registrar_.Remove(
+ this,
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ source);
+ std::set<int> job_to_delete;
+ for (IDToJobMap::iterator it = id_to_job_.begin(); it != id_to_job_.end();
+ ++it) {
+ if (it->second.process_id == host->GetID())
+ job_to_delete.insert(it->first);
+ }
+ for (std::set<int>::iterator it = job_to_delete.begin();
+ it != job_to_delete.end();
+ ++it) {
+ JobFinished(*it, -1);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/mhtml_generation_manager.h b/chromium/content/browser/download/mhtml_generation_manager.h
new file mode 100644
index 00000000000..0c9ec619557
--- /dev/null
+++ b/chromium/content/browser/download/mhtml_generation_manager.h
@@ -0,0 +1,102 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_MHTML_GENERATION_MANAGER_H_
+#define CONTENT_BROWSER_DOWNLOAD_MHTML_GENERATION_MANAGER_H_
+
+#include <map>
+
+#include "base/memory/singleton.h"
+#include "base/platform_file.h"
+#include "base/process/process.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ipc/ipc_platform_file.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+class WebContents;
+
+class MHTMLGenerationManager : public NotificationObserver {
+ public:
+ static MHTMLGenerationManager* GetInstance();
+
+ typedef base::Callback<void(const base::FilePath& /* path to the MHTML file */,
+ int64 /* size of the file */)> GenerateMHTMLCallback;
+
+ // Instructs the render view to generate a MHTML representation of the current
+ // page for |web_contents|.
+ void GenerateMHTML(WebContents* web_contents,
+ const base::FilePath& file,
+ const GenerateMHTMLCallback& callback);
+
+ // Notification from the renderer that the MHTML generation finished.
+ // |mhtml_data_size| contains the size in bytes of the generated MHTML data,
+ // or -1 in case of failure.
+ void MHTMLGenerated(int job_id, int64 mhtml_data_size);
+
+ private:
+ friend struct DefaultSingletonTraits<MHTMLGenerationManager>;
+
+ struct Job{
+ Job();
+ ~Job();
+
+ base::FilePath file_path;
+
+ // The handles to file the MHTML is saved to, for the browser and renderer
+ // processes.
+ base::PlatformFile browser_file;
+ IPC::PlatformFileForTransit renderer_file;
+
+ // The IDs mapping to a specific contents.
+ int process_id;
+ int routing_id;
+
+ // The callback to call once generation is complete.
+ GenerateMHTMLCallback callback;
+ };
+
+ MHTMLGenerationManager();
+ virtual ~MHTMLGenerationManager();
+
+ // Called on the file thread to create |file|.
+ void CreateFile(int job_id,
+ const base::FilePath& file,
+ base::ProcessHandle renderer_process);
+
+ // Called on the UI thread when the file that should hold the MHTML data has
+ // been created. This returns a handle to that file for the browser process
+ // and one for the renderer process. These handles are
+ // kInvalidPlatformFileValue if the file could not be opened.
+ void FileCreated(int job_id,
+ base::PlatformFile browser_file,
+ IPC::PlatformFileForTransit renderer_file);
+
+ // Called on the file thread to close the file the MHTML was saved to.
+ void CloseFile(base::PlatformFile file);
+
+ // Called on the UI thread when a job has been processed (successfully or
+ // not). Closes the file and removes the job from the job map.
+ // |mhtml_data_size| is -1 if the MHTML generation failed.
+ void JobFinished(int job_id, int64 mhtml_data_size);
+
+ // Implementation of NotificationObserver.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ typedef std::map<int, Job> IDToJobMap;
+ IDToJobMap id_to_job_;
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(MHTMLGenerationManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_MHTML_GENERATION_MANAGER_H_
diff --git a/chromium/content/browser/download/mock_download_file.cc b/chromium/content/browser/download/mock_download_file.cc
new file mode 100644
index 00000000000..9d883c7a7eb
--- /dev/null
+++ b/chromium/content/browser/download/mock_download_file.cc
@@ -0,0 +1,30 @@
+// 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 "content/browser/download/mock_download_file.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace content {
+namespace {
+
+void SuccessRun(const DownloadFile::InitializeCallback& callback) {
+ callback.Run(DOWNLOAD_INTERRUPT_REASON_NONE);
+}
+
+} // namespace
+
+MockDownloadFile::MockDownloadFile() {
+ // This is here because |Initialize()| is normally called right after
+ // construction.
+ ON_CALL(*this, Initialize(_))
+ .WillByDefault(::testing::Invoke(SuccessRun));
+}
+
+MockDownloadFile::~MockDownloadFile() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/mock_download_file.h b/chromium/content/browser/download/mock_download_file.h
new file mode 100644
index 00000000000..5d4b270086c
--- /dev/null
+++ b/chromium/content/browser/download/mock_download_file.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_MOCK_DOWNLOAD_FILE_H_
+#define CONTENT_BROWSER_DOWNLOAD_MOCK_DOWNLOAD_FILE_H_
+
+#include <map>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/download/download_file.h"
+#include "content/public/browser/download_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+struct DownloadCreateInfo;
+
+class MockDownloadFile : virtual public DownloadFile {
+ public:
+ MockDownloadFile();
+ virtual ~MockDownloadFile();
+
+ // DownloadFile functions.
+ MOCK_METHOD1(Initialize, void(const InitializeCallback&));
+ MOCK_METHOD2(AppendDataToFile, DownloadInterruptReason(
+ const char* data, size_t data_len));
+ MOCK_METHOD1(Rename, DownloadInterruptReason(
+ const base::FilePath& full_path));
+ MOCK_METHOD2(RenameAndUniquify,
+ void(const base::FilePath& full_path,
+ const RenameCompletionCallback& callback));
+ MOCK_METHOD2(RenameAndAnnotate,
+ void(const base::FilePath& full_path,
+ const RenameCompletionCallback& callback));
+ MOCK_METHOD0(Detach, void());
+ MOCK_METHOD0(Cancel, void());
+ MOCK_METHOD0(Finish, void());
+ MOCK_CONST_METHOD0(FullPath, base::FilePath());
+ MOCK_CONST_METHOD0(InProgress, bool());
+ MOCK_CONST_METHOD0(BytesSoFar, int64());
+ MOCK_CONST_METHOD0(CurrentSpeed, int64());
+ MOCK_METHOD1(GetHash, bool(std::string* hash));
+ MOCK_METHOD0(GetHashState, std::string());
+ MOCK_METHOD0(SendUpdate, void());
+ MOCK_CONST_METHOD0(Id, int());
+ MOCK_METHOD0(GetDownloadManager, DownloadManager*());
+ MOCK_CONST_METHOD0(DebugString, std::string());
+ MOCK_METHOD1(SetClientGuid, void(const std::string&));
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_MOCK_DOWNLOAD_FILE_H_
diff --git a/chromium/content/browser/download/rate_estimator.cc b/chromium/content/browser/download/rate_estimator.cc
new file mode 100644
index 00000000000..dcdd71af41d
--- /dev/null
+++ b/chromium/content/browser/download/rate_estimator.cc
@@ -0,0 +1,122 @@
+// 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 "content/browser/download/rate_estimator.h"
+
+#include "base/logging.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace content {
+
+namespace {
+
+static const int kDefaultBucketTimeSeconds = 1;
+static const size_t kDefaultNumBuckets = 10;
+
+} // namespace
+
+RateEstimator::RateEstimator()
+ : history_(kDefaultNumBuckets),
+ bucket_time_(TimeDelta::FromSeconds(kDefaultBucketTimeSeconds)),
+ oldest_index_(0),
+ bucket_count_(1) {
+ ResetBuckets(TimeTicks::Now());
+}
+
+RateEstimator::RateEstimator(TimeDelta bucket_time,
+ size_t num_buckets,
+ TimeTicks now)
+ : history_(num_buckets),
+ bucket_time_(bucket_time),
+ oldest_index_(0),
+ bucket_count_(1) {
+ DCHECK(bucket_time_.InSeconds() > 0);
+ ResetBuckets(now);
+}
+
+RateEstimator::~RateEstimator() {
+}
+
+void RateEstimator::Increment(uint32 count) {
+ Increment(count, TimeTicks::Now());
+}
+
+void RateEstimator::Increment(uint32 count, TimeTicks now) {
+ ClearOldBuckets(now);
+ int64 seconds_since_oldest = (now - oldest_time_).InSeconds();
+ DCHECK(seconds_since_oldest >= 0);
+ int64 delta_buckets = seconds_since_oldest / bucket_time_.InSeconds();
+ DCHECK(delta_buckets >= 0);
+ size_t index_offset = static_cast<size_t>(delta_buckets);
+ DCHECK(index_offset <= history_.size());
+ int current_index = (oldest_index_ + delta_buckets) % history_.size();
+ history_[current_index] += count;
+}
+
+uint64 RateEstimator::GetCountPerSecond() const {
+ return GetCountPerSecond(TimeTicks::Now());
+}
+
+uint64 RateEstimator::GetCountPerSecond(TimeTicks now) const {
+ const_cast<RateEstimator*>(this)->ClearOldBuckets(now);
+ // TODO(cbentzel): Support fractional seconds for active bucket?
+ // We explicitly don't check for overflow here. If it happens, unsigned
+ // arithmetic at least guarantees behavior by wrapping around. The estimate
+ // will be off, but the code will still be valid.
+ uint64 total_count = 0;
+ for (size_t i = 0; i < bucket_count_; ++i) {
+ size_t index = (oldest_index_ + i) % history_.size();
+ total_count += history_[index];
+ }
+ return total_count / (bucket_count_ * bucket_time_.InSeconds());
+}
+
+void RateEstimator::ClearOldBuckets(TimeTicks now) {
+ int64 seconds_since_oldest = (now - oldest_time_).InSeconds();
+
+ int64 delta_buckets = seconds_since_oldest / bucket_time_.InSeconds();
+
+ // It's possible (although unlikely) for there to be rollover with TimeTicks.
+ // If that's the case, just reset the history.
+ if (delta_buckets < 0) {
+ ResetBuckets(now);
+ return;
+ }
+ size_t delta_index = static_cast<size_t>(delta_buckets);
+
+ // If we are within the current window, keep the existing data.
+ if (delta_index < history_.size()) {
+ bucket_count_ = delta_index + 1;
+ return;
+ }
+
+ // If it's been long enough that all history data is too stale, just
+ // clear all the buckets.
+ size_t extra_buckets = delta_index - history_.size() + 1;
+ if (extra_buckets > history_.size()) {
+ ResetBuckets(now);
+ return;
+ }
+
+ // Clear out stale buckets in the history.
+ bucket_count_ = history_.size();
+ for (size_t i = 0; i < extra_buckets; ++i) {
+ history_[oldest_index_] = 0;
+ oldest_index_ = (oldest_index_ + 1) % history_.size();
+ oldest_time_ = oldest_time_ + bucket_time_;
+ }
+}
+
+void RateEstimator::ResetBuckets(TimeTicks now) {
+ for (size_t i = 0; i < history_.size(); ++i) {
+ history_[i] = 0;
+ }
+ oldest_index_ = 0;
+ bucket_count_ = 1;
+ oldest_time_ = now;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/rate_estimator.h b/chromium/content/browser/download/rate_estimator.h
new file mode 100644
index 00000000000..111e8eb8d8b
--- /dev/null
+++ b/chromium/content/browser/download/rate_estimator.h
@@ -0,0 +1,52 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_RATE_ESTIMATOR_H_
+#define CONTENT_BROWSER_DOWNLOAD_RATE_ESTIMATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// RateEstimator generates rate estimates based on recent activity.
+//
+// Internally it uses a fixed-size ring buffer, and develops estimates
+// based on a small sliding window of activity.
+class CONTENT_EXPORT RateEstimator {
+ public:
+ RateEstimator();
+ RateEstimator(base::TimeDelta bucket_time,
+ size_t num_buckets,
+ base::TimeTicks now);
+ ~RateEstimator();
+
+ // Increment the counter by |count|. The first variant uses the current time,
+ // the second variant provides the time that |count| is observed.
+ void Increment(uint32 count);
+ void Increment(uint32 count, base::TimeTicks now);
+
+ // Get a rate estimate, in terms of counts/second. The first variant uses the
+ // current time, the second variant provides the time.
+ uint64 GetCountPerSecond() const;
+ uint64 GetCountPerSecond(base::TimeTicks now) const;
+
+ private:
+ void ClearOldBuckets(base::TimeTicks now);
+ void ResetBuckets(base::TimeTicks now);
+
+ std::vector<uint32> history_;
+ base::TimeDelta bucket_time_;
+ size_t oldest_index_;
+ size_t bucket_count_;
+ base::TimeTicks oldest_time_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_RATE_ESTIMATOR_H_
diff --git a/chromium/content/browser/download/rate_estimator_unittest.cc b/chromium/content/browser/download/rate_estimator_unittest.cc
new file mode 100644
index 00000000000..cb3a14fd5dd
--- /dev/null
+++ b/chromium/content/browser/download/rate_estimator_unittest.cc
@@ -0,0 +1,58 @@
+// 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 "content/browser/download/rate_estimator.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace content {
+
+TEST(RateEstimatorTest, RateEstimator) {
+ base::TimeTicks now;
+ RateEstimator estimator(TimeDelta::FromSeconds(1), 10u, now);
+ EXPECT_EQ(0u, estimator.GetCountPerSecond(now));
+
+ estimator.Increment(50u, now);
+ EXPECT_EQ(50u, estimator.GetCountPerSecond(now));
+
+ now += TimeDelta::FromMilliseconds(800);
+ estimator.Increment(50, now);
+ EXPECT_EQ(100u, estimator.GetCountPerSecond(now));
+
+ // Advance time.
+ now += TimeDelta::FromSeconds(3);
+ EXPECT_EQ(25u, estimator.GetCountPerSecond(now));
+ estimator.Increment(60, now);
+ EXPECT_EQ(40u, estimator.GetCountPerSecond(now));
+
+ // Advance time again.
+ now += TimeDelta::FromSeconds(4);
+ EXPECT_EQ(20u, estimator.GetCountPerSecond(now));
+
+ // Advance time to the end.
+ now += TimeDelta::FromSeconds(2);
+ EXPECT_EQ(16u, estimator.GetCountPerSecond(now));
+ estimator.Increment(100, now);
+ EXPECT_EQ(26u, estimator.GetCountPerSecond(now));
+
+ // Now wrap around to the start.
+ now += TimeDelta::FromSeconds(1);
+ EXPECT_EQ(16u, estimator.GetCountPerSecond(now));
+ estimator.Increment(100, now);
+ EXPECT_EQ(26u, estimator.GetCountPerSecond(now));
+
+ // Advance far into the future.
+ now += TimeDelta::FromSeconds(40);
+ EXPECT_EQ(0u, estimator.GetCountPerSecond(now));
+ estimator.Increment(100, now);
+ EXPECT_EQ(100u, estimator.GetCountPerSecond(now));
+
+ // Pretend that there is timeticks wrap around.
+ now = base::TimeTicks();
+ EXPECT_EQ(0u, estimator.GetCountPerSecond(now));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_file.cc b/chromium/content/browser/download/save_file.cc
new file mode 100644
index 00000000000..e8885e4e363
--- /dev/null
+++ b/chromium/content/browser/download/save_file.cc
@@ -0,0 +1,88 @@
+// 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 "content/browser/download/save_file.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/file_stream.h"
+
+namespace content {
+
+// TODO(asanka): SaveFile should use the target directory of the save package as
+// the default download directory when initializing |file_|.
+// Unfortunately, as it is, constructors of SaveFile don't always
+// have access to the SavePackage at this point.
+SaveFile::SaveFile(const SaveFileCreateInfo* info, bool calculate_hash)
+ : file_(base::FilePath(),
+ info->url,
+ GURL(),
+ 0,
+ calculate_hash,
+ std::string(),
+ scoped_ptr<net::FileStream>(),
+ net::BoundNetLog()),
+ info_(info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ DCHECK(info);
+ DCHECK(info->path.empty());
+}
+
+SaveFile::~SaveFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+}
+
+DownloadInterruptReason SaveFile::Initialize() {
+ return file_.Initialize(base::FilePath());
+}
+
+DownloadInterruptReason SaveFile::AppendDataToFile(const char* data,
+ size_t data_len) {
+ return file_.AppendDataToFile(data, data_len);
+}
+
+DownloadInterruptReason SaveFile::Rename(const base::FilePath& full_path) {
+ return file_.Rename(full_path);
+}
+
+void SaveFile::Detach() {
+ file_.Detach();
+}
+
+void SaveFile::Cancel() {
+ file_.Cancel();
+}
+
+void SaveFile::Finish() {
+ file_.Finish();
+}
+
+void SaveFile::AnnotateWithSourceInformation() {
+ // TODO(gbillock): If this method is called, it should set the
+ // file_.SetClientGuid() method first.
+ file_.AnnotateWithSourceInformation();
+}
+
+base::FilePath SaveFile::FullPath() const {
+ return file_.full_path();
+}
+
+bool SaveFile::InProgress() const {
+ return file_.in_progress();
+}
+
+int64 SaveFile::BytesSoFar() const {
+ return file_.bytes_so_far();
+}
+
+bool SaveFile::GetHash(std::string* hash) {
+ return file_.GetHash(hash);
+}
+
+std::string SaveFile::DebugString() const {
+ return file_.DebugString();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_file.h b/chromium/content/browser/download/save_file.h
new file mode 100644
index 00000000000..bafa253b5c4
--- /dev/null
+++ b/chromium/content/browser/download/save_file.h
@@ -0,0 +1,59 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_H_
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/download/base_file.h"
+#include "content/browser/download/save_types.h"
+
+namespace content {
+// SaveFile ----------------------------------------------------------------
+
+// These objects live exclusively on the file thread and handle the writing
+// operations for one save item. These objects live only for the duration that
+// the saving job is 'in progress': once the saving job has been completed or
+// canceled, the SaveFile is destroyed. One SaveFile object represents one item
+// in a save session.
+class SaveFile {
+ public:
+ explicit SaveFile(const SaveFileCreateInfo* info, bool calculate_hash);
+ virtual ~SaveFile();
+
+ // BaseFile delegated functions.
+ DownloadInterruptReason Initialize();
+ DownloadInterruptReason AppendDataToFile(const char* data, size_t data_len);
+ DownloadInterruptReason Rename(const base::FilePath& full_path);
+ void Detach();
+ void Cancel();
+ void Finish();
+ void AnnotateWithSourceInformation();
+ base::FilePath FullPath() const;
+ bool InProgress() const;
+ int64 BytesSoFar() const;
+ bool GetHash(std::string* hash);
+ std::string DebugString() const;
+
+ // Accessors.
+ int save_id() const { return info_->save_id; }
+ int render_process_id() const { return info_->render_process_id; }
+ int render_view_id() const { return info_->render_view_id; }
+ int request_id() const { return info_->request_id; }
+ SaveFileCreateInfo::SaveFileSource save_source() const {
+ return info_->save_source;
+ }
+
+ private:
+ BaseFile file_;
+ scoped_ptr<const SaveFileCreateInfo> info_;
+
+ DISALLOW_COPY_AND_ASSIGN(SaveFile);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_H_
diff --git a/chromium/content/browser/download/save_file_manager.cc b/chromium/content/browser/download/save_file_manager.cc
new file mode 100644
index 00000000000..442c28e1024
--- /dev/null
+++ b/chromium/content/browser/download/save_file_manager.cc
@@ -0,0 +1,534 @@
+// 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 "build/build_config.h"
+
+#include "content/browser/download/save_file_manager.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "content/browser/download/save_file.h"
+#include "content/browser/download/save_package.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "url/gurl.h"
+
+namespace content {
+
+SaveFileManager::SaveFileManager()
+ : next_id_(0) {
+}
+
+SaveFileManager::~SaveFileManager() {
+ // Check for clean shutdown.
+ DCHECK(save_file_map_.empty());
+}
+
+// Called during the browser shutdown process to clean up any state (open files,
+// timers) that live on the saving thread (file thread).
+void SaveFileManager::Shutdown() {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::OnShutdown, this));
+}
+
+// Stop file thread operations.
+void SaveFileManager::OnShutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ STLDeleteValues(&save_file_map_);
+}
+
+SaveFile* SaveFileManager::LookupSaveFile(int save_id) {
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ return it == save_file_map_.end() ? NULL : it->second;
+}
+
+// Called on the IO thread when
+// a) The ResourceDispatcherHostImpl has decided that a request is savable.
+// b) The resource does not come from the network, but we still need a
+// save ID for for managing the status of the saving operation. So we
+// file a request from the file thread to the IO thread to generate a
+// unique save ID.
+int SaveFileManager::GetNextId() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return next_id_++;
+}
+
+void SaveFileManager::RegisterStartingRequest(const GURL& save_url,
+ SavePackage* save_package) {
+ // Make sure it runs in the UI thread.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ int contents_id = save_package->contents_id();
+
+ // Register this starting request.
+ StartingRequestsMap& starting_requests =
+ contents_starting_requests_[contents_id];
+ bool never_present = starting_requests.insert(
+ StartingRequestsMap::value_type(save_url.spec(), save_package)).second;
+ DCHECK(never_present);
+}
+
+SavePackage* SaveFileManager::UnregisterStartingRequest(
+ const GURL& save_url, int contents_id) {
+ // Make sure it runs in UI thread.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ ContentsToStartingRequestsMap::iterator it =
+ contents_starting_requests_.find(contents_id);
+ if (it != contents_starting_requests_.end()) {
+ StartingRequestsMap& requests = it->second;
+ StartingRequestsMap::iterator sit = requests.find(save_url.spec());
+ if (sit == requests.end())
+ return NULL;
+
+ // Found, erase it from starting list and return SavePackage.
+ SavePackage* save_package = sit->second;
+ requests.erase(sit);
+ // If there is no element in requests, remove it
+ if (requests.empty())
+ contents_starting_requests_.erase(it);
+ return save_package;
+ }
+
+ return NULL;
+}
+
+// Look up a SavePackage according to a save id.
+SavePackage* SaveFileManager::LookupPackage(int save_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SavePackageMap::iterator it = packages_.find(save_id);
+ if (it != packages_.end())
+ return it->second;
+ return NULL;
+}
+
+// Call from SavePackage for starting a saving job
+void SaveFileManager::SaveURL(
+ const GURL& url,
+ const Referrer& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ SaveFileCreateInfo::SaveFileSource save_source,
+ const base::FilePath& file_full_path,
+ ResourceContext* context,
+ SavePackage* save_package) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Register a saving job.
+ RegisterStartingRequest(url, save_package);
+ if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
+ DCHECK(url.is_valid());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SaveFileManager::OnSaveURL, this, url, referrer,
+ render_process_host_id, render_view_id, context));
+ } else {
+ // We manually start the save job.
+ SaveFileCreateInfo* info = new SaveFileCreateInfo(file_full_path,
+ url,
+ save_source,
+ -1);
+ info->render_process_id = render_process_host_id;
+ info->render_view_id = render_view_id;
+
+ // Since the data will come from render process, so we need to start
+ // this kind of save job by ourself.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SaveFileManager::OnRequireSaveJobFromOtherSource,
+ this, info));
+ }
+}
+
+// Utility function for look up table maintenance, called on the UI thread.
+// A manager may have multiple save page job (SavePackage) in progress,
+// so we just look up the save id and remove it from the tracking table.
+// If the save id is -1, it means we just send a request to save, but the
+// saving action has still not happened, need to call UnregisterStartingRequest
+// to remove it from the tracking map.
+void SaveFileManager::RemoveSaveFile(int save_id, const GURL& save_url,
+ SavePackage* package) {
+ DCHECK(package);
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // A save page job (SavePackage) can only have one manager,
+ // so remove it if it exists.
+ if (save_id == -1) {
+ SavePackage* old_package =
+ UnregisterStartingRequest(save_url, package->contents_id());
+ DCHECK_EQ(old_package, package);
+ } else {
+ SavePackageMap::iterator it = packages_.find(save_id);
+ if (it != packages_.end())
+ packages_.erase(it);
+ }
+}
+
+// Static
+SavePackage* SaveFileManager::GetSavePackageFromRenderIds(
+ int render_process_id, int render_view_id) {
+ RenderViewHostImpl* render_view_host =
+ RenderViewHostImpl::FromID(render_process_id, render_view_id);
+ if (!render_view_host)
+ return NULL;
+
+ WebContentsImpl* contents = static_cast<WebContentsImpl*>(
+ render_view_host->GetDelegate()->GetAsWebContents());
+ if (!contents)
+ return NULL;
+
+ return contents->save_package();
+}
+
+void SaveFileManager::DeleteDirectoryOrFile(const base::FilePath& full_path,
+ bool is_dir) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::OnDeleteDirectoryOrFile,
+ this, full_path, is_dir));
+}
+
+void SaveFileManager::SendCancelRequest(int save_id) {
+ // Cancel the request which has specific save id.
+ DCHECK_GT(save_id, -1);
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::CancelSave, this, save_id));
+}
+
+// Notifications sent from the IO thread and run on the file thread:
+
+// The IO thread created |info|, but the file thread (this method) uses it
+// to create a SaveFile which will hold and finally destroy |info|. It will
+// then passes |info| to the UI thread for reporting saving status.
+void SaveFileManager::StartSave(SaveFileCreateInfo* info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(info);
+ // No need to calculate hash.
+ SaveFile* save_file = new SaveFile(info, false);
+
+ // TODO(phajdan.jr): We should check the return value and handle errors here.
+ save_file->Initialize();
+
+ DCHECK(!LookupSaveFile(info->save_id));
+ save_file_map_[info->save_id] = save_file;
+ info->path = save_file->FullPath();
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SaveFileManager::OnStartSave, this, info));
+}
+
+// We do forward an update to the UI thread here, since we do not use timer to
+// update the UI. If the user has canceled the saving action (in the UI
+// thread). We may receive a few more updates before the IO thread gets the
+// cancel message. We just delete the data since the SaveFile has been deleted.
+void SaveFileManager::UpdateSaveProgress(int save_id,
+ net::IOBuffer* data,
+ int data_len) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ SaveFile* save_file = LookupSaveFile(save_id);
+ if (save_file) {
+ DCHECK(save_file->InProgress());
+
+ DownloadInterruptReason reason =
+ save_file->AppendDataToFile(data->data(), data_len);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SaveFileManager::OnUpdateSaveProgress,
+ this,
+ save_file->save_id(),
+ save_file->BytesSoFar(),
+ reason == DOWNLOAD_INTERRUPT_REASON_NONE));
+ }
+}
+
+// The IO thread will call this when saving is completed or it got error when
+// fetching data. In the former case, we forward the message to OnSaveFinished
+// in UI thread. In the latter case, the save ID will be -1, which means the
+// saving action did not even start, so we need to call OnErrorFinished in UI
+// thread, which will use the save URL to find corresponding request record and
+// delete it.
+void SaveFileManager::SaveFinished(int save_id,
+ const GURL& save_url,
+ int render_process_id,
+ bool is_success) {
+ VLOG(20) << " " << __FUNCTION__ << "()"
+ << " save_id = " << save_id
+ << " save_url = \"" << save_url.spec() << "\""
+ << " is_success = " << is_success;
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ // This routine may be called twice for the same from from
+ // SaveePackage::OnReceivedSerializedHtmlData, once for the file
+ // itself, and once when all frames have been serialized.
+ // So we can't assert that the file is InProgress() here.
+ // TODO(rdsmith): Fix this logic and put the DCHECK below back in.
+ // DCHECK(save_file->InProgress());
+
+ VLOG(20) << " " << __FUNCTION__ << "()"
+ << " save_file = " << save_file->DebugString();
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SaveFileManager::OnSaveFinished, this, save_id,
+ save_file->BytesSoFar(), is_success));
+
+ save_file->Finish();
+ save_file->Detach();
+ } else if (save_id == -1) {
+ // Before saving started, we got error. We still call finish process.
+ DCHECK(!save_url.is_empty());
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SaveFileManager::OnErrorFinished, this, save_url,
+ render_process_id));
+ }
+}
+
+// Notifications sent from the file thread and run on the UI thread.
+
+void SaveFileManager::OnStartSave(const SaveFileCreateInfo* info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SavePackage* save_package =
+ GetSavePackageFromRenderIds(info->render_process_id,
+ info->render_view_id);
+ if (!save_package) {
+ // Cancel this request.
+ SendCancelRequest(info->save_id);
+ return;
+ }
+
+ // Insert started saving job to tracking list.
+ SavePackageMap::iterator sit = packages_.find(info->save_id);
+ if (sit == packages_.end()) {
+ // Find the registered request. If we can not find, it means we have
+ // canceled the job before.
+ SavePackage* old_save_package = UnregisterStartingRequest(info->url,
+ info->render_process_id);
+ if (!old_save_package) {
+ // Cancel this request.
+ SendCancelRequest(info->save_id);
+ return;
+ }
+ DCHECK_EQ(old_save_package, save_package);
+ packages_[info->save_id] = save_package;
+ } else {
+ NOTREACHED();
+ }
+
+ // Forward this message to SavePackage.
+ save_package->StartSave(info);
+}
+
+void SaveFileManager::OnUpdateSaveProgress(int save_id, int64 bytes_so_far,
+ bool write_success) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SavePackage* package = LookupPackage(save_id);
+ if (package)
+ package->UpdateSaveProgress(save_id, bytes_so_far, write_success);
+ else
+ SendCancelRequest(save_id);
+}
+
+void SaveFileManager::OnSaveFinished(int save_id,
+ int64 bytes_so_far,
+ bool is_success) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SavePackage* package = LookupPackage(save_id);
+ if (package)
+ package->SaveFinished(save_id, bytes_so_far, is_success);
+}
+
+void SaveFileManager::OnErrorFinished(const GURL& save_url, int contents_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SavePackage* save_package = UnregisterStartingRequest(save_url, contents_id);
+ if (save_package)
+ save_package->SaveFailed(save_url);
+}
+
+// Notifications sent from the UI thread and run on the IO thread.
+
+void SaveFileManager::OnSaveURL(
+ const GURL& url,
+ const Referrer& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ ResourceContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ResourceDispatcherHostImpl::Get()->BeginSaveFile(url,
+ referrer,
+ render_process_host_id,
+ render_view_id,
+ context);
+}
+
+void SaveFileManager::OnRequireSaveJobFromOtherSource(
+ SaveFileCreateInfo* info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(info->save_id, -1);
+ // Generate a unique save id.
+ info->save_id = GetNextId();
+ // Start real saving action.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::StartSave, this, info));
+}
+
+void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id,
+ int request_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ResourceDispatcherHostImpl::Get()->CancelRequest(render_process_id,
+ request_id,
+ false);
+}
+
+// Notifications sent from the UI thread and run on the file thread.
+
+// This method will be sent via a user action, or shutdown on the UI thread,
+// and run on the file thread. We don't post a message back for cancels,
+// but we do forward the cancel to the IO thread. Since this message has been
+// sent from the UI thread, the saving job may have already completed and
+// won't exist in our map.
+void SaveFileManager::CancelSave(int save_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+
+ if (!save_file->InProgress()) {
+ // We've won a race with the UI thread--we finished the file before
+ // the UI thread cancelled it on us. Unfortunately, in this situation
+ // the cancel wins, so we need to delete the now detached file.
+ base::DeleteFile(save_file->FullPath(), false);
+ } else if (save_file->save_source() ==
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
+ // If the data comes from the net IO thread and hasn't completed
+ // yet, then forward the cancel message to IO thread & cancel the
+ // save locally. If the data doesn't come from the IO thread,
+ // we can ignore the message.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SaveFileManager::ExecuteCancelSaveRequest, this,
+ save_file->render_process_id(), save_file->request_id()));
+ }
+
+ // Whatever the save file is complete or not, just delete it. This
+ // will delete the underlying file if InProgress() is true.
+ save_file_map_.erase(it);
+ delete save_file;
+ }
+}
+
+// It is possible that SaveItem which has specified save_id has been canceled
+// before this function runs. So if we can not find corresponding SaveFile by
+// using specified save_id, just return.
+void SaveFileManager::SaveLocalFile(const GURL& original_file_url,
+ int save_id,
+ int render_process_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ SaveFile* save_file = LookupSaveFile(save_id);
+ if (!save_file)
+ return;
+ // If it has finished, just return.
+ if (!save_file->InProgress())
+ return;
+
+ // Close the save file before the copy operation.
+ save_file->Finish();
+ save_file->Detach();
+
+ DCHECK(original_file_url.SchemeIsFile());
+ base::FilePath file_path;
+ net::FileURLToFilePath(original_file_url, &file_path);
+ // If we can not get valid file path from original URL, treat it as
+ // disk error.
+ if (file_path.empty())
+ SaveFinished(save_id, original_file_url, render_process_id, false);
+
+ // Copy the local file to the temporary file. It will be renamed to its
+ // final name later.
+ bool success = base::CopyFile(file_path, save_file->FullPath());
+ if (!success)
+ base::DeleteFile(save_file->FullPath(), false);
+ SaveFinished(save_id, original_file_url, render_process_id, success);
+}
+
+void SaveFileManager::OnDeleteDirectoryOrFile(const base::FilePath& full_path,
+ bool is_dir) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!full_path.empty());
+
+ base::DeleteFile(full_path, is_dir);
+}
+
+void SaveFileManager::RenameAllFiles(
+ const FinalNameList& final_names,
+ const base::FilePath& resource_dir,
+ int render_process_id,
+ int render_view_id,
+ int save_package_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (!resource_dir.empty() && !base::PathExists(resource_dir))
+ file_util::CreateDirectory(resource_dir);
+
+ for (FinalNameList::const_iterator i = final_names.begin();
+ i != final_names.end(); ++i) {
+ SaveFileMap::iterator it = save_file_map_.find(i->first);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ DCHECK(!save_file->InProgress());
+ save_file->Rename(i->second);
+ delete save_file;
+ save_file_map_.erase(it);
+ }
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SaveFileManager::OnFinishSavePageJob, this,
+ render_process_id, render_view_id, save_package_id));
+}
+
+void SaveFileManager::OnFinishSavePageJob(int render_process_id,
+ int render_view_id,
+ int save_package_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ SavePackage* save_package =
+ GetSavePackageFromRenderIds(render_process_id, render_view_id);
+
+ if (save_package && save_package->id() == save_package_id)
+ save_package->Finish();
+}
+
+void SaveFileManager::RemoveSavedFileFromFileMap(
+ const SaveIDList& save_ids) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ for (SaveIDList::const_iterator i = save_ids.begin();
+ i != save_ids.end(); ++i) {
+ SaveFileMap::iterator it = save_file_map_.find(*i);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ DCHECK(!save_file->InProgress());
+ base::DeleteFile(save_file->FullPath(), false);
+ delete save_file;
+ save_file_map_.erase(it);
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_file_manager.h b/chromium/content/browser/download/save_file_manager.h
new file mode 100644
index 00000000000..0a49b6c8dcc
--- /dev/null
+++ b/chromium/content/browser/download/save_file_manager.h
@@ -0,0 +1,252 @@
+// 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.
+//
+// Objects that handle file operations for saving files, on the file thread.
+//
+// The SaveFileManager owns a set of SaveFile objects, each of which connects
+// with a SaveItem object which belongs to one SavePackage and runs on the file
+// thread for saving data in order to avoid disk activity on either network IO
+// thread or the UI thread. It coordinates the notifications from the network
+// and UI.
+//
+// The SaveFileManager itself is a singleton object owned by the
+// ResourceDispatcherHostImpl.
+//
+// The data sent to SaveFileManager have 2 sources, one is from
+// ResourceDispatcherHostImpl, run in network IO thread, the all sub-resources
+// and save-only-HTML pages will be got from network IO. The second is from
+// render process, those html pages which are serialized from DOM will be
+// composed in render process and encoded to its original encoding, then sent
+// to UI loop in browser process, then UI loop will dispatch the data to
+// SaveFileManager on the file thread. SaveFileManager will directly
+// call SaveFile's method to persist data.
+//
+// A typical saving job operation involves multiple threads:
+//
+// Updating an in progress save file
+// io_thread
+// |----> data from net ---->|
+// |
+// |
+// |----> data from ---->| |
+// | render process | |
+// ui_thread | |
+// file_thread (writes to disk)
+// |----> stats ---->|
+// ui_thread (feedback for user)
+//
+//
+// Cancel operations perform the inverse order when triggered by a user action:
+// ui_thread (user click)
+// |----> cancel command ---->|
+// | | file_thread (close file)
+// | |---------------------> cancel command ---->|
+// | io_thread (stops net IO
+// ui_thread (user close contents) for saving)
+// |----> cancel command ---->|
+// Render process(stop serializing DOM and sending
+// data)
+//
+//
+// The SaveFileManager tracks saving requests, mapping from a save ID (unique
+// integer created in the IO thread) to the SavePackage for the contents where
+// the saving job was initiated. In the event of a contents closure during
+// saving, the SavePackage will notify the SaveFileManage to cancel all SaveFile
+// jobs.
+
+#ifndef CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/download/save_types.h"
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace content {
+class ResourceContext;
+class SaveFile;
+class SavePackage;
+struct Referrer;
+
+class SaveFileManager : public base::RefCountedThreadSafe<SaveFileManager> {
+ public:
+ SaveFileManager();
+
+ // Lifetime management.
+ CONTENT_EXPORT void Shutdown();
+
+ // Called on the IO thread. This generates unique IDs for
+ // SaveFileResourceHandler objects (there's one per file in a SavePackage).
+ // Note that this is different from the SavePackage's id.
+ int GetNextId();
+
+ // Save the specified URL. Called on the UI thread and forwarded to the
+ // ResourceDispatcherHostImpl on the IO thread.
+ void SaveURL(const GURL& url,
+ const Referrer& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ SaveFileCreateInfo::SaveFileSource save_source,
+ const base::FilePath& file_full_path,
+ ResourceContext* context,
+ SavePackage* save_package);
+
+ // Notifications sent from the IO thread and run on the file thread:
+ void StartSave(SaveFileCreateInfo* info);
+ void UpdateSaveProgress(int save_id, net::IOBuffer* data, int size);
+ void SaveFinished(int save_id,
+ const GURL& save_url,
+ int render_process_id,
+ bool is_success);
+
+ // Notifications sent from the UI thread and run on the file thread.
+ // Cancel a SaveFile instance which has specified save id.
+ void CancelSave(int save_id);
+
+ // Called on the UI thread to remove a save package from SaveFileManager's
+ // tracking map.
+ void RemoveSaveFile(int save_id, const GURL& save_url,
+ SavePackage* package);
+
+ // Helper function for deleting specified file.
+ void DeleteDirectoryOrFile(const base::FilePath& full_path, bool is_dir);
+
+ // Runs on file thread to save a file by copying from file system when
+ // original url is using file scheme.
+ void SaveLocalFile(const GURL& original_file_url,
+ int save_id,
+ int render_process_id);
+
+ // Renames all the successfully saved files.
+ // |final_names| points to a vector which contains pairs of save ids and
+ // final names of successfully saved files.
+ void RenameAllFiles(
+ const FinalNameList& final_names,
+ const base::FilePath& resource_dir,
+ int render_process_id,
+ int render_view_id,
+ int save_package_id);
+
+ // When the user cancels the saving, we need to remove all remaining saved
+ // files of this page saving job from save_file_map_.
+ void RemoveSavedFileFromFileMap(const SaveIDList & save_ids);
+
+ private:
+ friend class base::RefCountedThreadSafe<SaveFileManager>;
+
+ ~SaveFileManager();
+
+ // A cleanup helper that runs on the file thread.
+ void OnShutdown();
+
+ // Called only on UI thread to get the SavePackage for a contents's browser
+ // context.
+ static SavePackage* GetSavePackageFromRenderIds(int render_process_id,
+ int review_view_id);
+
+ // Register a starting request. Associate the save URL with a
+ // SavePackage for further matching.
+ void RegisterStartingRequest(const GURL& save_url,
+ SavePackage* save_package);
+ // Unregister a start request according save URL, disassociate
+ // the save URL and SavePackage.
+ SavePackage* UnregisterStartingRequest(const GURL& save_url,
+ int contents_id);
+
+ // Look up the SavePackage according to save id.
+ SavePackage* LookupPackage(int save_id);
+
+ // Called only on the file thread.
+ // Look up one in-progress saving item according to save id.
+ SaveFile* LookupSaveFile(int save_id);
+
+ // Help function for sending notification of canceling specific request.
+ void SendCancelRequest(int save_id);
+
+ // Notifications sent from the file thread and run on the UI thread.
+
+ // Lookup the SaveManager for this WebContents' saving browser context and
+ // inform it the saving job has been started.
+ void OnStartSave(const SaveFileCreateInfo* info);
+ // Update the SavePackage with the current state of a started saving job.
+ // If the SavePackage for this saving job is gone, cancel the request.
+ void OnUpdateSaveProgress(int save_id,
+ int64 bytes_so_far,
+ bool write_success);
+ // Update the SavePackage with the finish state, and remove the request
+ // tracking entries.
+ void OnSaveFinished(int save_id, int64 bytes_so_far, bool is_success);
+ // For those requests that do not have valid save id, use
+ // map:(url, SavePackage) to find the request and remove it.
+ void OnErrorFinished(const GURL& save_url, int contents_id);
+ // Notifies SavePackage that the whole page saving job is finished.
+ void OnFinishSavePageJob(int render_process_id,
+ int render_view_id,
+ int save_package_id);
+
+ // Notifications sent from the UI thread and run on the file thread.
+
+ // Deletes a specified file on the file thread.
+ void OnDeleteDirectoryOrFile(const base::FilePath& full_path, bool is_dir);
+
+ // Notifications sent from the UI thread and run on the IO thread
+
+ // Initiates a request for URL to be saved.
+ void OnSaveURL(const GURL& url,
+ const Referrer& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ ResourceContext* context);
+ // Handler for a notification sent to the IO thread for generating save id.
+ void OnRequireSaveJobFromOtherSource(SaveFileCreateInfo* info);
+ // Call ResourceDispatcherHostImpl's CancelRequest method to execute cancel
+ // action in the IO thread.
+ void ExecuteCancelSaveRequest(int render_process_id, int request_id);
+
+ // Unique ID for the next SaveFile object.
+ int next_id_;
+
+ // A map of all saving jobs by using save id.
+ typedef base::hash_map<int, SaveFile*> SaveFileMap;
+ SaveFileMap save_file_map_;
+
+ // Tracks which SavePackage to send data to, called only on UI thread.
+ // SavePackageMap maps save IDs to their SavePackage.
+ typedef base::hash_map<int, SavePackage*> SavePackageMap;
+ SavePackageMap packages_;
+
+ // There is a gap between after calling SaveURL() and before calling
+ // StartSave(). In this gap, each request does not have save id for tracking.
+ // But sometimes users might want to stop saving job or ResourceDispatcherHost
+ // calls SaveFinished with save id -1 for network error. We name the requests
+ // as starting requests. For tracking those starting requests, we need to
+ // have some data structure.
+ // First we use a hashmap to map the request URL to SavePackage, then we use a
+ // hashmap to map the contents id (we actually use render_process_id) to the
+ // hashmap since it is possible to save the same URL in different contents at
+ // same time.
+ typedef base::hash_map<std::string, SavePackage*> StartingRequestsMap;
+ typedef base::hash_map<int, StartingRequestsMap>
+ ContentsToStartingRequestsMap;
+ ContentsToStartingRequestsMap contents_starting_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(SaveFileManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H_
diff --git a/chromium/content/browser/download/save_file_resource_handler.cc b/chromium/content/browser/download/save_file_resource_handler.cc
new file mode 100644
index 00000000000..ee72e0b0f79
--- /dev/null
+++ b/chromium/content/browser/download/save_file_resource_handler.cc
@@ -0,0 +1,123 @@
+// 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 "content/browser/download/save_file_resource_handler.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/download/save_file_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+
+SaveFileResourceHandler::SaveFileResourceHandler(int render_process_host_id,
+ int render_view_id,
+ const GURL& url,
+ SaveFileManager* manager)
+ : save_id_(-1),
+ render_process_id_(render_process_host_id),
+ render_view_id_(render_view_id),
+ url_(url),
+ content_length_(0),
+ save_manager_(manager) {
+}
+
+SaveFileResourceHandler::~SaveFileResourceHandler() {
+}
+
+bool SaveFileResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return true;
+}
+
+bool SaveFileResourceHandler::OnRequestRedirected(
+ int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) {
+ final_url_ = url;
+ return true;
+}
+
+bool SaveFileResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ save_id_ = save_manager_->GetNextId();
+ // |save_manager_| consumes (deletes):
+ SaveFileCreateInfo* info = new SaveFileCreateInfo;
+ info->url = url_;
+ info->final_url = final_url_;
+ info->total_bytes = content_length_;
+ info->save_id = save_id_;
+ info->render_process_id = render_process_id_;
+ info->render_view_id = render_view_id_;
+ info->request_id = request_id;
+ info->content_disposition = content_disposition_;
+ info->save_source = SaveFileCreateInfo::SAVE_FILE_FROM_NET;
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::StartSave, save_manager_, info));
+ return true;
+}
+
+bool SaveFileResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+bool SaveFileResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ DCHECK(buf && buf_size);
+ if (!read_buffer_.get()) {
+ *buf_size = min_size < 0 ? kReadBufSize : min_size;
+ read_buffer_ = new net::IOBuffer(*buf_size);
+ }
+ *buf = read_buffer_.get();
+ return true;
+}
+
+bool SaveFileResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ DCHECK(read_buffer_.get());
+ // We are passing ownership of this buffer to the save file manager.
+ scoped_refptr<net::IOBuffer> buffer;
+ read_buffer_.swap(buffer);
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::UpdateSaveProgress,
+ save_manager_, save_id_, buffer, bytes_read));
+ return true;
+}
+
+bool SaveFileResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::SaveFinished, save_manager_, save_id_, url_,
+ render_process_id_, status.is_success() && !status.is_io_pending()));
+ read_buffer_ = NULL;
+ return true;
+}
+
+void SaveFileResourceHandler::OnDataDownloaded(
+ int request_id,
+ int bytes_downloaded) {
+ NOTREACHED();
+}
+
+void SaveFileResourceHandler::set_content_length(
+ const std::string& content_length) {
+ base::StringToInt64(content_length, &content_length_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_file_resource_handler.h b/chromium/content/browser/download/save_file_resource_handler.h
new file mode 100644
index 00000000000..8dad4f890a1
--- /dev/null
+++ b/chromium/content/browser/download/save_file_resource_handler.h
@@ -0,0 +1,93 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_RESOURCE_HANDLER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/loader/resource_handler.h"
+#include "url/gurl.h"
+
+namespace content {
+class SaveFileManager;
+
+// Forwards data to the save thread.
+class SaveFileResourceHandler : public ResourceHandler {
+ public:
+ SaveFileResourceHandler(int render_process_host_id,
+ int render_view_id,
+ const GURL& url,
+ SaveFileManager* manager);
+ virtual ~SaveFileResourceHandler();
+
+ // ResourceHandler Implementation:
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+
+ // Saves the redirected URL to final_url_, we need to use the original
+ // URL to match original request.
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+
+ // Sends the download creation information to the download thread.
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+
+ // Pass-through implementation.
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+
+ // Creates a new buffer, which will be handed to the download thread for file
+ // writing and deletion.
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+
+ // Passes the buffer to the download file writer.
+ virtual bool OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) OVERRIDE;
+
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+
+ // N/A to this flavor of SaveFileResourceHandler.
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ // If the content-length header is not present (or contains something other
+ // than numbers), StringToInt64 returns 0, which indicates 'unknown size' and
+ // is handled correctly by the SaveManager.
+ void set_content_length(const std::string& content_length);
+
+ void set_content_disposition(const std::string& content_disposition) {
+ content_disposition_ = content_disposition;
+ }
+
+ private:
+ int save_id_;
+ int render_process_id_;
+ int render_view_id_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ std::string content_disposition_;
+ GURL url_;
+ GURL final_url_;
+ int64 content_length_;
+ SaveFileManager* save_manager_;
+
+ static const int kReadBufSize = 32768; // bytes
+
+ DISALLOW_COPY_AND_ASSIGN(SaveFileResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_FILE_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/download/save_item.cc b/chromium/content/browser/download/save_item.cc
new file mode 100644
index 00000000000..9e516204202
--- /dev/null
+++ b/chromium/content/browser/download/save_item.cc
@@ -0,0 +1,133 @@
+// 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 "content/browser/download/save_item.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "content/browser/download/save_file.h"
+#include "content/browser/download/save_file_manager.h"
+#include "content/browser/download/save_package.h"
+
+namespace content {
+
+// Constructor for SaveItem when creating each saving job.
+SaveItem::SaveItem(const GURL& url,
+ const Referrer& referrer,
+ SavePackage* package,
+ SaveFileCreateInfo::SaveFileSource save_source)
+ : save_id_(-1),
+ url_(url),
+ referrer_(referrer),
+ total_bytes_(0),
+ received_bytes_(0),
+ state_(WAIT_START),
+ has_final_name_(false),
+ is_success_(false),
+ save_source_(save_source),
+ package_(package) {
+ DCHECK(package);
+}
+
+SaveItem::~SaveItem() {
+}
+
+// Set start state for save item.
+void SaveItem::Start() {
+ DCHECK(state_ == WAIT_START);
+ state_ = IN_PROGRESS;
+}
+
+// If we've received more data than we were expecting (bad server info?),
+// revert to 'unknown size mode'.
+void SaveItem::UpdateSize(int64 bytes_so_far) {
+ received_bytes_ = bytes_so_far;
+ if (received_bytes_ >= total_bytes_)
+ total_bytes_ = 0;
+}
+
+// Updates from the file thread may have been posted while this saving job
+// was being canceled in the UI thread, so we'll accept them unless we're
+// complete.
+void SaveItem::Update(int64 bytes_so_far) {
+ if (state_ != IN_PROGRESS) {
+ NOTREACHED();
+ return;
+ }
+ UpdateSize(bytes_so_far);
+}
+
+// Cancel this saving item job. If the job is not in progress, ignore
+// this command. The SavePackage will each in-progress SaveItem's cancel
+// when canceling whole saving page job.
+void SaveItem::Cancel() {
+ // If item is in WAIT_START mode, which means no request has been sent.
+ // So we need not to cancel it.
+ if (state_ != IN_PROGRESS) {
+ // Small downloads might be complete before method has a chance to run.
+ return;
+ }
+ state_ = CANCELED;
+ is_success_ = false;
+ Finish(received_bytes_, false);
+ package_->SaveCanceled(this);
+}
+
+// Set finish state for a save item
+void SaveItem::Finish(int64 size, bool is_success) {
+ // When this function is called, the SaveItem should be one of following
+ // three situations.
+ // a) The data of this SaveItem is finished saving. So it should have
+ // generated final name.
+ // b) Error happened before the start of saving process. So no |save_id_| is
+ // generated for this SaveItem and the |is_success_| should be false.
+ // c) Error happened in the start of saving process, the SaveItem has a save
+ // id, |is_success_| should be false, and the |size| should be 0.
+ DCHECK(has_final_name() || (save_id_ == -1 && !is_success_) ||
+ (save_id_ != -1 && !is_success_ && !size));
+ state_ = COMPLETE;
+ is_success_ = is_success;
+ UpdateSize(size);
+}
+
+// Calculate the percentage of the save item
+int SaveItem::PercentComplete() const {
+ switch (state_) {
+ case COMPLETE:
+ case CANCELED:
+ return 100;
+ case WAIT_START:
+ return 0;
+ case IN_PROGRESS: {
+ int percent = 0;
+ if (total_bytes_ > 0)
+ percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
+ return percent;
+ }
+ default: {
+ NOTREACHED();
+ return -1;
+ }
+ }
+}
+
+// Rename the save item with new path.
+void SaveItem::Rename(const base::FilePath& full_path) {
+ DCHECK(!full_path.empty() && !has_final_name());
+ full_path_ = full_path;
+ file_name_ = full_path_.BaseName();
+ has_final_name_ = true;
+}
+
+void SaveItem::SetSaveId(int32 save_id) {
+ DCHECK_EQ(-1, save_id_);
+ save_id_ = save_id;
+}
+
+void SaveItem::SetTotalBytes(int64 total_bytes) {
+ DCHECK_EQ(0, total_bytes_);
+ total_bytes_ = total_bytes;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_item.h b/chromium/content/browser/download/save_item.h
new file mode 100644
index 00000000000..d7cbb4f0329
--- /dev/null
+++ b/chromium/content/browser/download/save_item.h
@@ -0,0 +1,115 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_SAVE_ITEM_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_ITEM_H_
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "content/browser/download/save_types.h"
+#include "content/public/common/referrer.h"
+#include "url/gurl.h"
+
+namespace content {
+class SavePackage;
+
+// One SaveItem per save file. This is the model class that stores all the
+// state for one save file.
+class SaveItem {
+ public:
+ enum SaveState {
+ WAIT_START,
+ IN_PROGRESS,
+ COMPLETE,
+ CANCELED
+ };
+
+ SaveItem(const GURL& url,
+ const Referrer& referrer,
+ SavePackage* package,
+ SaveFileCreateInfo::SaveFileSource save_source);
+
+ ~SaveItem();
+
+ void Start();
+
+ // Received a new chunk of data.
+ void Update(int64 bytes_so_far);
+
+ // Cancel saving item.
+ void Cancel();
+
+ // Saving operation completed.
+ void Finish(int64 size, bool is_success);
+
+ // Rough percent complete, -1 means we don't know (since we didn't receive a
+ // total size).
+ int PercentComplete() const;
+
+ // Update path for SaveItem, the actual file is renamed on the file thread.
+ void Rename(const base::FilePath& full_path);
+
+ void SetSaveId(int32 save_id);
+
+ void SetTotalBytes(int64 total_bytes);
+
+ // Accessors.
+ SaveState state() const { return state_; }
+ const base::FilePath& full_path() const { return full_path_; }
+ const base::FilePath& file_name() const { return file_name_; }
+ const GURL& url() const { return url_; }
+ const Referrer& referrer() const { return referrer_; }
+ int64 total_bytes() const { return total_bytes_; }
+ int64 received_bytes() const { return received_bytes_; }
+ int32 save_id() const { return save_id_; }
+ bool has_final_name() const { return has_final_name_; }
+ bool success() const { return is_success_; }
+ SaveFileCreateInfo::SaveFileSource save_source() const {
+ return save_source_;
+ }
+ SavePackage* package() const { return package_; }
+
+ private:
+ // Internal helper for maintaining consistent received and total sizes.
+ void UpdateSize(int64 size);
+
+ // Request ID assigned by the ResourceDispatcherHost.
+ int32 save_id_;
+
+ // Full path to the save item file.
+ base::FilePath full_path_;
+
+ // Short display version of the file.
+ base::FilePath file_name_;
+
+ // The URL for this save item.
+ GURL url_;
+ Referrer referrer_;
+
+ // Total bytes expected.
+ int64 total_bytes_;
+
+ // Current received bytes.
+ int64 received_bytes_;
+
+ // The current state of this save item.
+ SaveState state_;
+
+ // Specifies if this name is a final or not.
+ bool has_final_name_;
+
+ // Flag indicates whether SaveItem has error while in saving process.
+ bool is_success_;
+
+ SaveFileCreateInfo::SaveFileSource save_source_;
+
+ // Our owning object.
+ SavePackage* package_;
+
+ DISALLOW_COPY_AND_ASSIGN(SaveItem);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_ITEM_H_
diff --git a/chromium/content/browser/download/save_package.cc b/chromium/content/browser/download/save_package.cc
new file mode 100644
index 00000000000..8a9781d82b0
--- /dev/null
+++ b/chromium/content/browser/download/save_package.cc
@@ -0,0 +1,1455 @@
+// 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 "content/browser/download/save_package.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/i18n/file_util_icu.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "content/browser/download/download_item_impl.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/download/save_file.h"
+#include "content/browser/download/save_file_manager.h"
+#include "content/browser/download/save_item.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_context.h"
+#include "third_party/WebKit/public/web/WebPageSerializerClient.h"
+
+using base::Time;
+using WebKit::WebPageSerializerClient;
+
+namespace content {
+namespace {
+
+// A counter for uniquely identifying each save package.
+int g_save_package_id = 0;
+
+// Default name which will be used when we can not get proper name from
+// resource URL.
+const char kDefaultSaveName[] = "saved_resource";
+
+// Maximum number of file ordinal number. I think it's big enough for resolving
+// name-conflict files which has same base file name.
+const int32 kMaxFileOrdinalNumber = 9999;
+
+// Maximum length for file path. Since Windows have MAX_PATH limitation for
+// file path, we need to make sure length of file path of every saved file
+// is less than MAX_PATH
+#if defined(OS_WIN)
+const uint32 kMaxFilePathLength = MAX_PATH - 1;
+#elif defined(OS_POSIX)
+const uint32 kMaxFilePathLength = PATH_MAX - 1;
+#endif
+
+// Maximum length for file ordinal number part. Since we only support the
+// maximum 9999 for ordinal number, which means maximum file ordinal number part
+// should be "(9998)", so the value is 6.
+const uint32 kMaxFileOrdinalNumberPartLength = 6;
+
+// Strip current ordinal number, if any. Should only be used on pure
+// file names, i.e. those stripped of their extensions.
+// TODO(estade): improve this to not choke on alternate encodings.
+base::FilePath::StringType StripOrdinalNumber(
+ const base::FilePath::StringType& pure_file_name) {
+ base::FilePath::StringType::size_type r_paren_index =
+ pure_file_name.rfind(FILE_PATH_LITERAL(')'));
+ base::FilePath::StringType::size_type l_paren_index =
+ pure_file_name.rfind(FILE_PATH_LITERAL('('));
+ if (l_paren_index >= r_paren_index)
+ return pure_file_name;
+
+ for (base::FilePath::StringType::size_type i = l_paren_index + 1;
+ i != r_paren_index; ++i) {
+ if (!IsAsciiDigit(pure_file_name[i]))
+ return pure_file_name;
+ }
+
+ return pure_file_name.substr(0, l_paren_index);
+}
+
+// Check whether we can save page as complete-HTML for the contents which
+// have specified a MIME type. Now only contents which have the MIME type
+// "text/html" can be saved as complete-HTML.
+bool CanSaveAsComplete(const std::string& contents_mime_type) {
+ return contents_mime_type == "text/html" ||
+ contents_mime_type == "application/xhtml+xml";
+}
+
+// Request handle for SavePackage downloads. Currently doesn't support
+// pause/resume/cancel, but returns a WebContents.
+class SavePackageRequestHandle : public DownloadRequestHandleInterface {
+ public:
+ SavePackageRequestHandle(base::WeakPtr<SavePackage> save_package)
+ : save_package_(save_package) {}
+
+ // DownloadRequestHandleInterface
+ virtual WebContents* GetWebContents() const OVERRIDE {
+ return save_package_.get() ? save_package_->web_contents() : NULL;
+ }
+ virtual DownloadManager* GetDownloadManager() const OVERRIDE {
+ return NULL;
+ }
+ virtual void PauseRequest() const OVERRIDE {}
+ virtual void ResumeRequest() const OVERRIDE {}
+ virtual void CancelRequest() const OVERRIDE {}
+ virtual std::string DebugString() const OVERRIDE {
+ return "SavePackage DownloadRequestHandle";
+ }
+
+ private:
+ base::WeakPtr<SavePackage> save_package_;
+};
+
+} // namespace
+
+const base::FilePath::CharType SavePackage::kDefaultHtmlExtension[] =
+#if defined(OS_WIN)
+ FILE_PATH_LITERAL("htm");
+#else
+ FILE_PATH_LITERAL("html");
+#endif
+
+SavePackage::SavePackage(WebContents* web_contents,
+ SavePageType save_type,
+ const base::FilePath& file_full_path,
+ const base::FilePath& directory_full_path)
+ : WebContentsObserver(web_contents),
+ file_manager_(NULL),
+ download_manager_(NULL),
+ download_(NULL),
+ page_url_(GetUrlToBeSaved()),
+ saved_main_file_path_(file_full_path),
+ saved_main_directory_path_(directory_full_path),
+ title_(web_contents->GetTitle()),
+ start_tick_(base::TimeTicks::Now()),
+ finished_(false),
+ mhtml_finishing_(false),
+ user_canceled_(false),
+ disk_error_occurred_(false),
+ save_type_(save_type),
+ all_save_items_count_(0),
+ file_name_set_(&base::FilePath::CompareLessIgnoreCase),
+ wait_state_(INITIALIZE),
+ contents_id_(web_contents->GetRenderProcessHost()->GetID()),
+ unique_id_(g_save_package_id++),
+ wrote_to_completed_file_(false),
+ wrote_to_failed_file_(false) {
+ DCHECK(page_url_.is_valid());
+ DCHECK((save_type_ == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
+ (save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ||
+ (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML));
+ DCHECK(!saved_main_file_path_.empty() &&
+ saved_main_file_path_.value().length() <= kMaxFilePathLength);
+ DCHECK(!saved_main_directory_path_.empty() &&
+ saved_main_directory_path_.value().length() < kMaxFilePathLength);
+ InternalInit();
+}
+
+SavePackage::SavePackage(WebContents* web_contents)
+ : WebContentsObserver(web_contents),
+ file_manager_(NULL),
+ download_manager_(NULL),
+ download_(NULL),
+ page_url_(GetUrlToBeSaved()),
+ title_(web_contents->GetTitle()),
+ start_tick_(base::TimeTicks::Now()),
+ finished_(false),
+ mhtml_finishing_(false),
+ user_canceled_(false),
+ disk_error_occurred_(false),
+ save_type_(SAVE_PAGE_TYPE_UNKNOWN),
+ all_save_items_count_(0),
+ file_name_set_(&base::FilePath::CompareLessIgnoreCase),
+ wait_state_(INITIALIZE),
+ contents_id_(web_contents->GetRenderProcessHost()->GetID()),
+ unique_id_(g_save_package_id++),
+ wrote_to_completed_file_(false),
+ wrote_to_failed_file_(false) {
+ DCHECK(page_url_.is_valid());
+ InternalInit();
+}
+
+// This is for testing use. Set |finished_| as true because we don't want
+// method Cancel to be be called in destructor in test mode.
+// We also don't call InternalInit().
+SavePackage::SavePackage(WebContents* web_contents,
+ const base::FilePath& file_full_path,
+ const base::FilePath& directory_full_path)
+ : WebContentsObserver(web_contents),
+ file_manager_(NULL),
+ download_manager_(NULL),
+ download_(NULL),
+ saved_main_file_path_(file_full_path),
+ saved_main_directory_path_(directory_full_path),
+ start_tick_(base::TimeTicks::Now()),
+ finished_(true),
+ mhtml_finishing_(false),
+ user_canceled_(false),
+ disk_error_occurred_(false),
+ save_type_(SAVE_PAGE_TYPE_UNKNOWN),
+ all_save_items_count_(0),
+ file_name_set_(&base::FilePath::CompareLessIgnoreCase),
+ wait_state_(INITIALIZE),
+ contents_id_(0),
+ unique_id_(g_save_package_id++),
+ wrote_to_completed_file_(false),
+ wrote_to_failed_file_(false) {
+}
+
+SavePackage::~SavePackage() {
+ // Stop receiving saving job's updates
+ if (!finished_ && !canceled()) {
+ // Unexpected quit.
+ Cancel(true);
+ }
+
+ // We should no longer be observing the DownloadItem at this point.
+ CHECK(!download_);
+
+ DCHECK(all_save_items_count_ == (waiting_item_queue_.size() +
+ completed_count() +
+ in_process_count()));
+ // Free all SaveItems.
+ while (!waiting_item_queue_.empty()) {
+ // We still have some items which are waiting for start to save.
+ SaveItem* save_item = waiting_item_queue_.front();
+ waiting_item_queue_.pop();
+ delete save_item;
+ }
+
+ STLDeleteValues(&saved_success_items_);
+ STLDeleteValues(&in_progress_items_);
+ STLDeleteValues(&saved_failed_items_);
+
+ file_manager_ = NULL;
+}
+
+GURL SavePackage::GetUrlToBeSaved() {
+ // Instead of using web_contents_.GetURL here, we use url() (which is the
+ // "real" url of the page) from the NavigationEntry because it reflects its
+ // origin rather than the displayed one (returned by GetURL) which may be
+ // different (like having "view-source:" on the front).
+ NavigationEntry* active_entry =
+ web_contents()->GetController().GetActiveEntry();
+ return active_entry->GetURL();
+}
+
+void SavePackage::Cancel(bool user_action) {
+ if (!canceled()) {
+ if (user_action)
+ user_canceled_ = true;
+ else
+ disk_error_occurred_ = true;
+ Stop();
+ }
+ RecordSavePackageEvent(SAVE_PACKAGE_CANCELLED);
+}
+
+// Init() can be called directly, or indirectly via GetSaveInfo(). In both
+// cases, we need file_manager_ to be initialized, so we do this first.
+void SavePackage::InternalInit() {
+ ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
+ if (!rdh) {
+ NOTREACHED();
+ return;
+ }
+
+ file_manager_ = rdh->save_file_manager();
+ DCHECK(file_manager_);
+
+ download_manager_ = static_cast<DownloadManagerImpl*>(
+ BrowserContext::GetDownloadManager(
+ web_contents()->GetBrowserContext()));
+ DCHECK(download_manager_);
+
+ RecordSavePackageEvent(SAVE_PACKAGE_STARTED);
+}
+
+bool SavePackage::Init(
+ const SavePackageDownloadCreatedCallback& download_created_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Set proper running state.
+ if (wait_state_ != INITIALIZE)
+ return false;
+
+ wait_state_ = START_PROCESS;
+
+ // Initialize the request context and resource dispatcher.
+ BrowserContext* browser_context = web_contents()->GetBrowserContext();
+ if (!browser_context) {
+ NOTREACHED();
+ return false;
+ }
+
+ scoped_ptr<DownloadRequestHandleInterface> request_handle(
+ new SavePackageRequestHandle(AsWeakPtr()));
+ // The download manager keeps ownership but adds us as an observer.
+ download_manager_->CreateSavePackageDownloadItem(
+ saved_main_file_path_,
+ page_url_,
+ ((save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ?
+ "multipart/related" : "text/html"),
+ request_handle.Pass(),
+ base::Bind(&SavePackage::InitWithDownloadItem, AsWeakPtr(),
+ download_created_callback));
+ return true;
+}
+
+void SavePackage::InitWithDownloadItem(
+ const SavePackageDownloadCreatedCallback& download_created_callback,
+ DownloadItemImpl* item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(item);
+ download_ = item;
+ download_->AddObserver(this);
+ // Confirm above didn't delete the tab out from under us.
+ if (!download_created_callback.is_null())
+ download_created_callback.Run(download_);
+
+ // Check save type and process the save page job.
+ if (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML) {
+ // Get directory
+ DCHECK(!saved_main_directory_path_.empty());
+ GetAllSavableResourceLinksForCurrentPage();
+ } else if (save_type_ == SAVE_PAGE_TYPE_AS_MHTML) {
+ web_contents()->GenerateMHTML(saved_main_file_path_, base::Bind(
+ &SavePackage::OnMHTMLGenerated, this));
+ } else {
+ DCHECK_EQ(SAVE_PAGE_TYPE_AS_ONLY_HTML, save_type_) << save_type_;
+ wait_state_ = NET_FILES;
+ SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ?
+ SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET;
+ SaveItem* save_item = new SaveItem(page_url_,
+ Referrer(),
+ this,
+ save_source);
+ // Add this item to waiting list.
+ waiting_item_queue_.push(save_item);
+ all_save_items_count_ = 1;
+ download_->SetTotalBytes(1);
+
+ DoSavingProcess();
+ }
+}
+
+void SavePackage::OnMHTMLGenerated(const base::FilePath& path, int64 size) {
+ if (size <= 0) {
+ Cancel(false);
+ return;
+ }
+ wrote_to_completed_file_ = true;
+
+ // Hack to avoid touching download_ after user cancel.
+ // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem
+ // with SavePackage flow.
+ if (download_->GetState() == DownloadItem::IN_PROGRESS) {
+ download_->SetTotalBytes(size);
+ download_->DestinationUpdate(size, 0, std::string());
+ // Must call OnAllDataSaved here in order for
+ // GDataDownloadObserver::ShouldUpload() to return true.
+ // ShouldCompleteDownload() may depend on the gdata uploader to finish.
+ download_->OnAllDataSaved(DownloadItem::kEmptyFileHash);
+ }
+
+ if (!download_manager_->GetDelegate()) {
+ Finish();
+ return;
+ }
+
+ if (download_manager_->GetDelegate()->ShouldCompleteDownload(
+ download_, base::Bind(&SavePackage::Finish, this))) {
+ Finish();
+ }
+}
+
+// On POSIX, the length of |pure_file_name| + |file_name_ext| is further
+// restricted by NAME_MAX. The maximum allowed path looks like:
+// '/path/to/save_dir' + '/' + NAME_MAX.
+uint32 SavePackage::GetMaxPathLengthForDirectory(
+ const base::FilePath& base_dir) {
+#if defined(OS_POSIX)
+ return std::min(kMaxFilePathLength,
+ static_cast<uint32>(base_dir.value().length()) +
+ NAME_MAX + 1);
+#else
+ return kMaxFilePathLength;
+#endif
+}
+
+// File name is considered being consist of pure file name, dot and file
+// extension name. File name might has no dot and file extension, or has
+// multiple dot inside file name. The dot, which separates the pure file
+// name and file extension name, is last dot in the whole file name.
+// This function is for making sure the length of specified file path is not
+// great than the specified maximum length of file path and getting safe pure
+// file name part if the input pure file name is too long.
+// The parameter |dir_path| specifies directory part of the specified
+// file path. The parameter |file_name_ext| specifies file extension
+// name part of the specified file path (including start dot). The parameter
+// |max_file_path_len| specifies maximum length of the specified file path.
+// The parameter |pure_file_name| input pure file name part of the specified
+// file path. If the length of specified file path is great than
+// |max_file_path_len|, the |pure_file_name| will output new pure file name
+// part for making sure the length of specified file path is less than
+// specified maximum length of file path. Return false if the function can
+// not get a safe pure file name, otherwise it returns true.
+bool SavePackage::GetSafePureFileName(
+ const base::FilePath& dir_path,
+ const base::FilePath::StringType& file_name_ext,
+ uint32 max_file_path_len,
+ base::FilePath::StringType* pure_file_name) {
+ DCHECK(!pure_file_name->empty());
+ int available_length = static_cast<int>(max_file_path_len -
+ dir_path.value().length() -
+ file_name_ext.length());
+ // Need an extra space for the separator.
+ if (!dir_path.EndsWithSeparator())
+ --available_length;
+
+ // Plenty of room.
+ if (static_cast<int>(pure_file_name->length()) <= available_length)
+ return true;
+
+ // Limited room. Truncate |pure_file_name| to fit.
+ if (available_length > 0) {
+ *pure_file_name = pure_file_name->substr(0, available_length);
+ return true;
+ }
+
+ // Not enough room to even use a shortened |pure_file_name|.
+ pure_file_name->clear();
+ return false;
+}
+
+// Generate name for saving resource.
+bool SavePackage::GenerateFileName(const std::string& disposition,
+ const GURL& url,
+ bool need_html_ext,
+ base::FilePath::StringType* generated_name) {
+ // TODO(jungshik): Figure out the referrer charset when having one
+ // makes sense and pass it to GenerateFileName.
+ base::FilePath file_path = net::GenerateFileName(url,
+ disposition,
+ std::string(),
+ std::string(),
+ std::string(),
+ kDefaultSaveName);
+
+ DCHECK(!file_path.empty());
+ base::FilePath::StringType pure_file_name =
+ file_path.RemoveExtension().BaseName().value();
+ base::FilePath::StringType file_name_ext = file_path.Extension();
+
+ // If it is HTML resource, use ".htm{l,}" as its extension.
+ if (need_html_ext) {
+ file_name_ext = FILE_PATH_LITERAL(".");
+ file_name_ext.append(kDefaultHtmlExtension);
+ }
+
+ // Need to make sure the suggested file name is not too long.
+ uint32 max_path = GetMaxPathLengthForDirectory(saved_main_directory_path_);
+
+ // Get safe pure file name.
+ if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
+ max_path, &pure_file_name))
+ return false;
+
+ base::FilePath::StringType file_name = pure_file_name + file_name_ext;
+
+ // Check whether we already have same name in a case insensitive manner.
+ FileNameSet::const_iterator iter = file_name_set_.find(file_name);
+ if (iter == file_name_set_.end()) {
+ file_name_set_.insert(file_name);
+ } else {
+ // Found same name, increase the ordinal number for the file name.
+ pure_file_name =
+ base::FilePath(*iter).RemoveExtension().BaseName().value();
+ base::FilePath::StringType base_file_name =
+ StripOrdinalNumber(pure_file_name);
+
+ // We need to make sure the length of base file name plus maximum ordinal
+ // number path will be less than or equal to kMaxFilePathLength.
+ if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
+ max_path - kMaxFileOrdinalNumberPartLength, &base_file_name))
+ return false;
+
+ // Prepare the new ordinal number.
+ uint32 ordinal_number;
+ FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name);
+ if (it == file_name_count_map_.end()) {
+ // First base-name-conflict resolving, use 1 as initial ordinal number.
+ file_name_count_map_[base_file_name] = 1;
+ ordinal_number = 1;
+ } else {
+ // We have met same base-name conflict, use latest ordinal number.
+ ordinal_number = it->second;
+ }
+
+ if (ordinal_number > (kMaxFileOrdinalNumber - 1)) {
+ // Use a random file from temporary file.
+ base::FilePath temp_file;
+ file_util::CreateTemporaryFile(&temp_file);
+ file_name = temp_file.RemoveExtension().BaseName().value();
+ // Get safe pure file name.
+ if (!GetSafePureFileName(saved_main_directory_path_,
+ base::FilePath::StringType(),
+ max_path, &file_name))
+ return false;
+ } else {
+ for (int i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) {
+ base::FilePath::StringType new_name = base_file_name +
+ base::StringPrintf(FILE_PATH_LITERAL("(%d)"), i) + file_name_ext;
+ if (file_name_set_.find(new_name) == file_name_set_.end()) {
+ // Resolved name conflict.
+ file_name = new_name;
+ file_name_count_map_[base_file_name] = ++i;
+ break;
+ }
+ }
+ }
+
+ file_name_set_.insert(file_name);
+ }
+
+ DCHECK(!file_name.empty());
+ generated_name->assign(file_name);
+
+ return true;
+}
+
+// We have received a message from SaveFileManager about a new saving job. We
+// create a SaveItem and store it in our in_progress list.
+void SavePackage::StartSave(const SaveFileCreateInfo* info) {
+ DCHECK(info && !info->url.is_empty());
+
+ SaveUrlItemMap::iterator it = in_progress_items_.find(info->url.spec());
+ if (it == in_progress_items_.end()) {
+ // If not found, we must have cancel action.
+ DCHECK(canceled());
+ return;
+ }
+ SaveItem* save_item = it->second;
+
+ DCHECK(!saved_main_file_path_.empty());
+
+ save_item->SetSaveId(info->save_id);
+ save_item->SetTotalBytes(info->total_bytes);
+
+ // Determine the proper path for a saving job, by choosing either the default
+ // save directory, or prompting the user.
+ DCHECK(!save_item->has_final_name());
+ if (info->url != page_url_) {
+ base::FilePath::StringType generated_name;
+ // For HTML resource file, make sure it will have .htm as extension name,
+ // otherwise, when you open the saved page in Chrome again, download
+ // file manager will treat it as downloadable resource, and download it
+ // instead of opening it as HTML.
+ bool need_html_ext =
+ info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM;
+ if (!GenerateFileName(info->content_disposition,
+ GURL(info->url),
+ need_html_ext,
+ &generated_name)) {
+ // We can not generate file name for this SaveItem, so we cancel the
+ // saving page job if the save source is from serialized DOM data.
+ // Otherwise, it means this SaveItem is sub-resource type, we treat it
+ // as an error happened on saving. We can ignore this type error for
+ // sub-resource links which will be resolved as absolute links instead
+ // of local links in final saved contents.
+ if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)
+ Cancel(true);
+ else
+ SaveFinished(save_item->save_id(), 0, false);
+ return;
+ }
+
+ // When saving page as only-HTML, we only have a SaveItem whose url
+ // must be page_url_.
+ DCHECK(save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML);
+ DCHECK(!saved_main_directory_path_.empty());
+
+ // Now we get final name retrieved from GenerateFileName, we will use it
+ // rename the SaveItem.
+ base::FilePath final_name =
+ saved_main_directory_path_.Append(generated_name);
+ save_item->Rename(final_name);
+ } else {
+ // It is the main HTML file, use the name chosen by the user.
+ save_item->Rename(saved_main_file_path_);
+ }
+
+ // If the save source is from file system, inform SaveFileManager to copy
+ // corresponding file to the file path which this SaveItem specifies.
+ if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::SaveLocalFile,
+ file_manager_,
+ save_item->url(),
+ save_item->save_id(),
+ contents_id()));
+ return;
+ }
+
+ // Check whether we begin to require serialized HTML data.
+ if (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML &&
+ wait_state_ == HTML_DATA) {
+ // Inform backend to serialize the all frames' DOM and send serialized
+ // HTML data back.
+ GetSerializedHtmlDataForCurrentPageWithLocalLinks();
+ }
+}
+
+SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) {
+ if (in_process_count()) {
+ for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ it != in_progress_items_.end(); ++it) {
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
+ if (save_item->save_id() == save_id)
+ return save_item;
+ }
+ }
+ return NULL;
+}
+
+void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) {
+ SaveUrlItemMap::iterator it = in_progress_items_.find(
+ save_item->url().spec());
+ DCHECK(it != in_progress_items_.end());
+ DCHECK(save_item == it->second);
+ in_progress_items_.erase(it);
+
+ if (save_item->success()) {
+ // Add it to saved_success_items_.
+ DCHECK(saved_success_items_.find(save_item->save_id()) ==
+ saved_success_items_.end());
+ saved_success_items_[save_item->save_id()] = save_item;
+ } else {
+ // Add it to saved_failed_items_.
+ DCHECK(saved_failed_items_.find(save_item->url().spec()) ==
+ saved_failed_items_.end());
+ saved_failed_items_[save_item->url().spec()] = save_item;
+ }
+}
+
+// Called for updating saving state.
+bool SavePackage::UpdateSaveProgress(int32 save_id,
+ int64 size,
+ bool write_success) {
+ // Because we might have canceled this saving job before,
+ // so we might not find corresponding SaveItem.
+ SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
+ if (!save_item)
+ return false;
+
+ save_item->Update(size);
+
+ // If we got disk error, cancel whole save page job.
+ if (!write_success) {
+ // Cancel job with reason of disk error.
+ Cancel(false);
+ }
+ return true;
+}
+
+// Stop all page saving jobs that are in progress and instruct the file thread
+// to delete all saved files.
+void SavePackage::Stop() {
+ // If we haven't moved out of the initial state, there's nothing to cancel and
+ // there won't be valid pointers for file_manager_ or download_.
+ if (wait_state_ == INITIALIZE)
+ return;
+
+ // When stopping, if it still has some items in in_progress, cancel them.
+ DCHECK(canceled());
+ if (in_process_count()) {
+ SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ for (; it != in_progress_items_.end(); ++it) {
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
+ save_item->Cancel();
+ }
+ // Remove all in progress item to saved map. For failed items, they will
+ // be put into saved_failed_items_, for successful item, they will be put
+ // into saved_success_items_.
+ while (in_process_count())
+ PutInProgressItemToSavedMap(in_progress_items_.begin()->second);
+ }
+
+ // This vector contains the save ids of the save files which SaveFileManager
+ // needs to remove from its save_file_map_.
+ SaveIDList save_ids;
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it)
+ save_ids.push_back(it->first);
+ for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
+ it != saved_failed_items_.end(); ++it)
+ save_ids.push_back(it->second->save_id());
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::RemoveSavedFileFromFileMap,
+ file_manager_,
+ save_ids));
+
+ finished_ = true;
+ wait_state_ = FAILED;
+
+ // Inform the DownloadItem we have canceled whole save page job.
+ if (download_) {
+ download_->Cancel(false);
+ FinalizeDownloadEntry();
+ }
+}
+
+void SavePackage::CheckFinish() {
+ if (in_process_count() || finished_)
+ return;
+
+ base::FilePath dir = (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML &&
+ saved_success_items_.size() > 1) ?
+ saved_main_directory_path_ : base::FilePath();
+
+ // This vector contains the final names of all the successfully saved files
+ // along with their save ids. It will be passed to SaveFileManager to do the
+ // renaming job.
+ FinalNameList final_names;
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it)
+ final_names.push_back(std::make_pair(it->first,
+ it->second->full_path()));
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::RenameAllFiles,
+ file_manager_,
+ final_names,
+ dir,
+ web_contents()->GetRenderProcessHost()->GetID(),
+ web_contents()->GetRenderViewHost()->GetRoutingID(),
+ id()));
+}
+
+// Successfully finished all items of this SavePackage.
+void SavePackage::Finish() {
+ // User may cancel the job when we're moving files to the final directory.
+ if (canceled())
+ return;
+
+ wait_state_ = SUCCESSFUL;
+ finished_ = true;
+
+ // Record finish.
+ RecordSavePackageEvent(SAVE_PACKAGE_FINISHED);
+
+ // Record any errors that occurred.
+ if (wrote_to_completed_file_) {
+ RecordSavePackageEvent(SAVE_PACKAGE_WRITE_TO_COMPLETED);
+ }
+
+ if (wrote_to_failed_file_) {
+ RecordSavePackageEvent(SAVE_PACKAGE_WRITE_TO_FAILED);
+ }
+
+ // This vector contains the save ids of the save files which SaveFileManager
+ // needs to remove from its save_file_map_.
+ SaveIDList save_ids;
+ for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
+ it != saved_failed_items_.end(); ++it)
+ save_ids.push_back(it->second->save_id());
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::RemoveSavedFileFromFileMap,
+ file_manager_,
+ save_ids));
+
+ if (download_) {
+ // Hack to avoid touching download_ after user cancel.
+ // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem
+ // with SavePackage flow.
+ if (download_->GetState() == DownloadItem::IN_PROGRESS) {
+ if (save_type_ != SAVE_PAGE_TYPE_AS_MHTML) {
+ download_->DestinationUpdate(
+ all_save_items_count_, CurrentSpeed(), std::string());
+ download_->OnAllDataSaved(DownloadItem::kEmptyFileHash);
+ }
+ download_->MarkAsComplete();
+ }
+ FinalizeDownloadEntry();
+ }
+}
+
+// Called for updating end state.
+void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) {
+ // Because we might have canceled this saving job before,
+ // so we might not find corresponding SaveItem. Just ignore it.
+ SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
+ if (!save_item)
+ return;
+
+ // Let SaveItem set end state.
+ save_item->Finish(size, is_success);
+ // Remove the associated save id and SavePackage.
+ file_manager_->RemoveSaveFile(save_id, save_item->url(), this);
+
+ PutInProgressItemToSavedMap(save_item);
+
+ // Inform the DownloadItem to update UI.
+ // We use the received bytes as number of saved files.
+ // Hack to avoid touching download_ after user cancel.
+ // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem
+ // with SavePackage flow.
+ if (download_ && (download_->GetState() == DownloadItem::IN_PROGRESS)) {
+ download_->DestinationUpdate(
+ completed_count(), CurrentSpeed(), std::string());
+ }
+
+ if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM &&
+ save_item->url() == page_url_ && !save_item->received_bytes()) {
+ // If size of main HTML page is 0, treat it as disk error.
+ Cancel(false);
+ return;
+ }
+
+ if (canceled()) {
+ DCHECK(finished_);
+ return;
+ }
+
+ // Continue processing the save page job.
+ DoSavingProcess();
+
+ // Check whether we can successfully finish whole job.
+ CheckFinish();
+}
+
+// Sometimes, the net io will only call SaveFileManager::SaveFinished with
+// save id -1 when it encounters error. Since in this case, save id will be
+// -1, so we can only use URL to find which SaveItem is associated with
+// this error.
+// Saving an item failed. If it's a sub-resource, ignore it. If the error comes
+// from serializing HTML data, then cancel saving page.
+void SavePackage::SaveFailed(const GURL& save_url) {
+ SaveUrlItemMap::iterator it = in_progress_items_.find(save_url.spec());
+ if (it == in_progress_items_.end()) {
+ NOTREACHED(); // Should not exist!
+ return;
+ }
+ SaveItem* save_item = it->second;
+
+ save_item->Finish(0, false);
+
+ PutInProgressItemToSavedMap(save_item);
+
+ // Inform the DownloadItem to update UI.
+ // We use the received bytes as number of saved files.
+ // Hack to avoid touching download_ after user cancel.
+ // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem
+ // with SavePackage flow.
+ if (download_ && (download_->GetState() == DownloadItem::IN_PROGRESS)) {
+ download_->DestinationUpdate(
+ completed_count(), CurrentSpeed(), std::string());
+ }
+
+ if ((save_type_ == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
+ (save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ||
+ (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)) {
+ // We got error when saving page. Treat it as disk error.
+ Cancel(true);
+ }
+
+ if (canceled()) {
+ DCHECK(finished_);
+ return;
+ }
+
+ // Continue processing the save page job.
+ DoSavingProcess();
+
+ CheckFinish();
+}
+
+void SavePackage::SaveCanceled(SaveItem* save_item) {
+ // Call the RemoveSaveFile in UI thread.
+ file_manager_->RemoveSaveFile(save_item->save_id(),
+ save_item->url(),
+ this);
+ if (save_item->save_id() != -1)
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::CancelSave,
+ file_manager_,
+ save_item->save_id()));
+}
+
+// Initiate a saving job of a specific URL. We send the request to
+// SaveFileManager, which will dispatch it to different approach according to
+// the save source. Parameter process_all_remaining_items indicates whether
+// we need to save all remaining items.
+void SavePackage::SaveNextFile(bool process_all_remaining_items) {
+ DCHECK(web_contents());
+ DCHECK(waiting_item_queue_.size());
+
+ do {
+ // Pop SaveItem from waiting list.
+ SaveItem* save_item = waiting_item_queue_.front();
+ waiting_item_queue_.pop();
+
+ // Add the item to in_progress_items_.
+ SaveUrlItemMap::iterator it = in_progress_items_.find(
+ save_item->url().spec());
+ DCHECK(it == in_progress_items_.end());
+ in_progress_items_[save_item->url().spec()] = save_item;
+ save_item->Start();
+ file_manager_->SaveURL(save_item->url(),
+ save_item->referrer(),
+ web_contents()->GetRenderProcessHost()->GetID(),
+ routing_id(),
+ save_item->save_source(),
+ save_item->full_path(),
+ web_contents()->
+ GetBrowserContext()->GetResourceContext(),
+ this);
+ } while (process_all_remaining_items && waiting_item_queue_.size());
+}
+
+// Calculate the percentage of whole save page job.
+int SavePackage::PercentComplete() {
+ if (!all_save_items_count_)
+ return 0;
+ else if (!in_process_count())
+ return 100;
+ else
+ return completed_count() / all_save_items_count_;
+}
+
+int64 SavePackage::CurrentSpeed() const {
+ base::TimeDelta diff = base::TimeTicks::Now() - start_tick_;
+ int64 diff_ms = diff.InMilliseconds();
+ return diff_ms == 0 ? 0 : completed_count() * 1000 / diff_ms;
+}
+
+// Continue processing the save page job after one SaveItem has been
+// finished.
+void SavePackage::DoSavingProcess() {
+ if (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML) {
+ // We guarantee that images and JavaScripts must be downloaded first.
+ // So when finishing all those sub-resources, we will know which
+ // sub-resource's link can be replaced with local file path, which
+ // sub-resource's link need to be replaced with absolute URL which
+ // point to its internet address because it got error when saving its data.
+
+ // Start a new SaveItem job if we still have job in waiting queue.
+ if (waiting_item_queue_.size()) {
+ DCHECK(wait_state_ == NET_FILES);
+ SaveItem* save_item = waiting_item_queue_.front();
+ if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
+ SaveNextFile(false);
+ } else if (!in_process_count()) {
+ // If there is no in-process SaveItem, it means all sub-resources
+ // have been processed. Now we need to start serializing HTML DOM
+ // for the current page to get the generated HTML data.
+ wait_state_ = HTML_DATA;
+ // All non-HTML resources have been finished, start all remaining
+ // HTML files.
+ SaveNextFile(true);
+ }
+ } else if (in_process_count()) {
+ // Continue asking for HTML data.
+ DCHECK(wait_state_ == HTML_DATA);
+ }
+ } else {
+ // Save as HTML only or MHTML.
+ DCHECK(wait_state_ == NET_FILES);
+ DCHECK((save_type_ == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
+ (save_type_ == SAVE_PAGE_TYPE_AS_MHTML));
+ if (waiting_item_queue_.size()) {
+ DCHECK(all_save_items_count_ == waiting_item_queue_.size());
+ SaveNextFile(false);
+ }
+ }
+}
+
+bool SavePackage::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(SavePackage, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SendCurrentPageAllSavableResourceLinks,
+ OnReceivedSavableResourceLinksForCurrentPage)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SendSerializedHtmlData,
+ OnReceivedSerializedHtmlData)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+// After finishing all SaveItems which need to get data from net.
+// We collect all URLs which have local storage and send the
+// map:(originalURL:currentLocalPath) to render process (backend).
+// Then render process will serialize DOM and send data to us.
+void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() {
+ if (wait_state_ != HTML_DATA)
+ return;
+ std::vector<GURL> saved_links;
+ std::vector<base::FilePath> saved_file_paths;
+ int successful_started_items_count = 0;
+
+ // Collect all saved items which have local storage.
+ // First collect the status of all the resource files and check whether they
+ // have created local files although they have not been completely saved.
+ // If yes, the file can be saved. Otherwise, there is a disk error, so we
+ // need to cancel the page saving job.
+ for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ it != in_progress_items_.end(); ++it) {
+ DCHECK(it->second->save_source() ==
+ SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+ if (it->second->has_final_name())
+ successful_started_items_count++;
+ saved_links.push_back(it->second->url());
+ saved_file_paths.push_back(it->second->file_name());
+ }
+
+ // If not all file of HTML resource have been started, then wait.
+ if (successful_started_items_count != in_process_count())
+ return;
+
+ // Collect all saved success items.
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it) {
+ DCHECK(it->second->has_final_name());
+ saved_links.push_back(it->second->url());
+ saved_file_paths.push_back(it->second->file_name());
+ }
+
+ // Get the relative directory name.
+ base::FilePath relative_dir_name = saved_main_directory_path_.BaseName();
+
+ Send(new ViewMsg_GetSerializedHtmlDataForCurrentPageWithLocalLinks(
+ routing_id(), saved_links, saved_file_paths, relative_dir_name));
+}
+
+// Process the serialized HTML content data of a specified web page
+// retrieved from render process.
+void SavePackage::OnReceivedSerializedHtmlData(const GURL& frame_url,
+ const std::string& data,
+ int32 status) {
+ WebPageSerializerClient::PageSerializationStatus flag =
+ static_cast<WebPageSerializerClient::PageSerializationStatus>(status);
+ // Check current state.
+ if (wait_state_ != HTML_DATA)
+ return;
+
+ int id = contents_id();
+ // If the all frames are finished saving, we need to close the
+ // remaining SaveItems.
+ if (flag == WebPageSerializerClient::AllFramesAreFinished) {
+ for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ it != in_progress_items_.end(); ++it) {
+ VLOG(20) << " " << __FUNCTION__ << "()"
+ << " save_id = " << it->second->save_id()
+ << " url = \"" << it->second->url().spec() << "\"";
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::SaveFinished,
+ file_manager_,
+ it->second->save_id(),
+ it->second->url(),
+ id,
+ true));
+ }
+ return;
+ }
+
+ SaveUrlItemMap::iterator it = in_progress_items_.find(frame_url.spec());
+ if (it == in_progress_items_.end()) {
+ for (SavedItemMap::iterator saved_it = saved_success_items_.begin();
+ saved_it != saved_success_items_.end(); ++saved_it) {
+ if (saved_it->second->url() == frame_url) {
+ wrote_to_completed_file_ = true;
+ break;
+ }
+ }
+
+ it = saved_failed_items_.find(frame_url.spec());
+ if (it != saved_failed_items_.end())
+ wrote_to_failed_file_ = true;
+
+ return;
+ }
+
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+
+ if (!data.empty()) {
+ // Prepare buffer for saving HTML data.
+ scoped_refptr<net::IOBuffer> new_data(new net::IOBuffer(data.size()));
+ memcpy(new_data->data(), data.data(), data.size());
+
+ // Call write file functionality in file thread.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::UpdateSaveProgress,
+ file_manager_,
+ save_item->save_id(),
+ new_data,
+ static_cast<int>(data.size())));
+ }
+
+ // Current frame is completed saving, call finish in file thread.
+ if (flag == WebPageSerializerClient::CurrentFrameIsFinished) {
+ VLOG(20) << " " << __FUNCTION__ << "()"
+ << " save_id = " << save_item->save_id()
+ << " url = \"" << save_item->url().spec() << "\"";
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SaveFileManager::SaveFinished,
+ file_manager_,
+ save_item->save_id(),
+ save_item->url(),
+ id,
+ true));
+ }
+}
+
+// Ask for all savable resource links from backend, include main frame and
+// sub-frame.
+void SavePackage::GetAllSavableResourceLinksForCurrentPage() {
+ if (wait_state_ != START_PROCESS)
+ return;
+
+ wait_state_ = RESOURCES_LIST;
+ Send(new ViewMsg_GetAllSavableResourceLinksForCurrentPage(routing_id(),
+ page_url_));
+}
+
+// Give backend the lists which contain all resource links that have local
+// storage, after which, render process will serialize DOM for generating
+// HTML data.
+void SavePackage::OnReceivedSavableResourceLinksForCurrentPage(
+ const std::vector<GURL>& resources_list,
+ const std::vector<Referrer>& referrers_list,
+ const std::vector<GURL>& frames_list) {
+ if (wait_state_ != RESOURCES_LIST)
+ return;
+
+ if (resources_list.size() != referrers_list.size())
+ return;
+
+ all_save_items_count_ = static_cast<int>(resources_list.size()) +
+ static_cast<int>(frames_list.size());
+
+ // We use total bytes as the total number of files we want to save.
+ // Hack to avoid touching download_ after user cancel.
+ // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem
+ // with SavePackage flow.
+ if (download_ && (download_->GetState() == DownloadItem::IN_PROGRESS))
+ download_->SetTotalBytes(all_save_items_count_);
+
+ if (all_save_items_count_) {
+ // Put all sub-resources to wait list.
+ for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) {
+ const GURL& u = resources_list[i];
+ DCHECK(u.is_valid());
+ SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
+ SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET;
+ SaveItem* save_item = new SaveItem(u, referrers_list[i],
+ this, save_source);
+ waiting_item_queue_.push(save_item);
+ }
+ // Put all HTML resources to wait list.
+ for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) {
+ const GURL& u = frames_list[i];
+ DCHECK(u.is_valid());
+ SaveItem* save_item = new SaveItem(
+ u, Referrer(), this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+ waiting_item_queue_.push(save_item);
+ }
+ wait_state_ = NET_FILES;
+ DoSavingProcess();
+ } else {
+ // No resource files need to be saved, treat it as user cancel.
+ Cancel(true);
+ }
+}
+
+base::FilePath SavePackage::GetSuggestedNameForSaveAs(
+ bool can_save_as_complete,
+ const std::string& contents_mime_type,
+ const std::string& accept_langs) {
+ base::FilePath name_with_proper_ext =
+ base::FilePath::FromWStringHack(UTF16ToWideHack(title_));
+
+ // If the page's title matches its URL, use the URL. Try to use the last path
+ // component or if there is none, the domain as the file name.
+ // Normally we want to base the filename on the page title, or if it doesn't
+ // exist, on the URL. It's not easy to tell if the page has no title, because
+ // if the page has no title, WebContents::GetTitle() will return the page's
+ // URL (adjusted for display purposes). Therefore, we convert the "title"
+ // back to a URL, and if it matches the original page URL, we know the page
+ // had no title (or had a title equal to its URL, which is fine to treat
+ // similarly).
+ if (title_ == net::FormatUrl(page_url_, accept_langs)) {
+ std::string url_path;
+ if (!page_url_.SchemeIs(chrome::kDataScheme)) {
+ std::vector<std::string> url_parts;
+ base::SplitString(page_url_.path(), '/', &url_parts);
+ if (!url_parts.empty()) {
+ for (int i = static_cast<int>(url_parts.size()) - 1; i >= 0; --i) {
+ url_path = url_parts[i];
+ if (!url_path.empty())
+ break;
+ }
+ }
+ if (url_path.empty())
+ url_path = page_url_.host();
+ } else {
+ url_path = "dataurl";
+ }
+ name_with_proper_ext =
+ base::FilePath::FromWStringHack(UTF8ToWide(url_path));
+ }
+
+ // Ask user for getting final saving name.
+ name_with_proper_ext = EnsureMimeExtension(name_with_proper_ext,
+ contents_mime_type);
+ // Adjust extension for complete types.
+ if (can_save_as_complete)
+ name_with_proper_ext = EnsureHtmlExtension(name_with_proper_ext);
+
+ base::FilePath::StringType file_name = name_with_proper_ext.value();
+ file_util::ReplaceIllegalCharactersInPath(&file_name, ' ');
+ return base::FilePath(file_name);
+}
+
+base::FilePath SavePackage::EnsureHtmlExtension(const base::FilePath& name) {
+ // If the file name doesn't have an extension suitable for HTML files,
+ // append one.
+ base::FilePath::StringType ext = name.Extension();
+ if (!ext.empty())
+ ext.erase(ext.begin()); // Erase preceding '.'.
+ std::string mime_type;
+ if (!net::GetMimeTypeFromExtension(ext, &mime_type) ||
+ !CanSaveAsComplete(mime_type)) {
+ return base::FilePath(name.value() + FILE_PATH_LITERAL(".") +
+ kDefaultHtmlExtension);
+ }
+ return name;
+}
+
+base::FilePath SavePackage::EnsureMimeExtension(const base::FilePath& name,
+ const std::string& contents_mime_type) {
+ // Start extension at 1 to skip over period if non-empty.
+ base::FilePath::StringType ext = name.Extension().length() ?
+ name.Extension().substr(1) : name.Extension();
+ base::FilePath::StringType suggested_extension =
+ ExtensionForMimeType(contents_mime_type);
+ std::string mime_type;
+ if (!suggested_extension.empty() &&
+ !net::GetMimeTypeFromExtension(ext, &mime_type)) {
+ // Extension is absent or needs to be updated.
+ return base::FilePath(name.value() + FILE_PATH_LITERAL(".") +
+ suggested_extension);
+ }
+ return name;
+}
+
+const base::FilePath::CharType* SavePackage::ExtensionForMimeType(
+ const std::string& contents_mime_type) {
+ static const struct {
+ const base::FilePath::CharType *mime_type;
+ const base::FilePath::CharType *suggested_extension;
+ } extensions[] = {
+ { FILE_PATH_LITERAL("text/html"), kDefaultHtmlExtension },
+ { FILE_PATH_LITERAL("text/xml"), FILE_PATH_LITERAL("xml") },
+ { FILE_PATH_LITERAL("application/xhtml+xml"), FILE_PATH_LITERAL("xhtml") },
+ { FILE_PATH_LITERAL("text/plain"), FILE_PATH_LITERAL("txt") },
+ { FILE_PATH_LITERAL("text/css"), FILE_PATH_LITERAL("css") },
+ };
+#if defined(OS_POSIX)
+ base::FilePath::StringType mime_type(contents_mime_type);
+#elif defined(OS_WIN)
+ base::FilePath::StringType mime_type(UTF8ToWide(contents_mime_type));
+#endif // OS_WIN
+ for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(extensions); ++i) {
+ if (mime_type == extensions[i].mime_type)
+ return extensions[i].suggested_extension;
+ }
+ return FILE_PATH_LITERAL("");
+}
+
+WebContents* SavePackage::web_contents() const {
+ return WebContentsObserver::web_contents();
+}
+
+void SavePackage::GetSaveInfo() {
+ // Can't use web_contents_ in the file thread, so get the data that we need
+ // before calling to it.
+ base::FilePath website_save_dir, download_save_dir;
+ bool skip_dir_check = false;
+ DCHECK(download_manager_);
+ if (download_manager_->GetDelegate()) {
+ download_manager_->GetDelegate()->GetSaveDir(
+ web_contents()->GetBrowserContext(), &website_save_dir,
+ &download_save_dir, &skip_dir_check);
+ }
+ std::string mime_type = web_contents()->GetContentsMimeType();
+ std::string accept_languages =
+ GetContentClient()->browser()->GetAcceptLangs(
+ web_contents()->GetBrowserContext());
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SavePackage::CreateDirectoryOnFileThread, this,
+ website_save_dir, download_save_dir, skip_dir_check,
+ mime_type, accept_languages));
+}
+
+void SavePackage::CreateDirectoryOnFileThread(
+ const base::FilePath& website_save_dir,
+ const base::FilePath& download_save_dir,
+ bool skip_dir_check,
+ const std::string& mime_type,
+ const std::string& accept_langs) {
+ base::FilePath save_dir;
+ // If the default html/websites save folder doesn't exist...
+ // We skip the directory check for gdata directories on ChromeOS.
+ if (!skip_dir_check && !base::DirectoryExists(website_save_dir)) {
+ // If the default download dir doesn't exist, create it.
+ if (!base::DirectoryExists(download_save_dir)) {
+ bool res = file_util::CreateDirectory(download_save_dir);
+ DCHECK(res);
+ }
+ save_dir = download_save_dir;
+ } else {
+ // If it does exist, use the default save dir param.
+ save_dir = website_save_dir;
+ }
+
+ bool can_save_as_complete = CanSaveAsComplete(mime_type);
+ base::FilePath suggested_filename = GetSuggestedNameForSaveAs(
+ can_save_as_complete, mime_type, accept_langs);
+ base::FilePath::StringType pure_file_name =
+ suggested_filename.RemoveExtension().BaseName().value();
+ base::FilePath::StringType file_name_ext = suggested_filename.Extension();
+
+ // Need to make sure the suggested file name is not too long.
+ uint32 max_path = GetMaxPathLengthForDirectory(save_dir);
+
+ if (GetSafePureFileName(save_dir, file_name_ext, max_path, &pure_file_name)) {
+ save_dir = save_dir.Append(pure_file_name + file_name_ext);
+ } else {
+ // Cannot create a shorter filename. This will cause the save as operation
+ // to fail unless the user pick a shorter name. Continuing even though it
+ // will fail because returning means no save as popup for the user, which
+ // is even more confusing. This case should be rare though.
+ save_dir = save_dir.Append(suggested_filename);
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SavePackage::ContinueGetSaveInfo, this, save_dir,
+ can_save_as_complete));
+}
+
+void SavePackage::ContinueGetSaveInfo(const base::FilePath& suggested_path,
+ bool can_save_as_complete) {
+
+ // The WebContents which owns this SavePackage may have disappeared during
+ // the UI->FILE->UI thread hop of
+ // GetSaveInfo->CreateDirectoryOnFileThread->ContinueGetSaveInfo.
+ if (!web_contents() || !download_manager_->GetDelegate())
+ return;
+
+ base::FilePath::StringType default_extension;
+ if (can_save_as_complete)
+ default_extension = kDefaultHtmlExtension;
+
+ download_manager_->GetDelegate()->ChooseSavePath(
+ web_contents(),
+ suggested_path,
+ default_extension,
+ can_save_as_complete,
+ base::Bind(&SavePackage::OnPathPicked, AsWeakPtr()));
+}
+
+void SavePackage::OnPathPicked(
+ const base::FilePath& final_name,
+ SavePageType type,
+ const SavePackageDownloadCreatedCallback& download_created_callback) {
+ DCHECK((type == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
+ (type == SAVE_PAGE_TYPE_AS_MHTML) ||
+ (type == SAVE_PAGE_TYPE_AS_COMPLETE_HTML)) << type;
+ // Ensure the filename is safe.
+ saved_main_file_path_ = final_name;
+ // TODO(asanka): This call may block on IO and shouldn't be made
+ // from the UI thread. See http://crbug.com/61827.
+ net::GenerateSafeFileName(web_contents()->GetContentsMimeType(), false,
+ &saved_main_file_path_);
+
+ saved_main_directory_path_ = saved_main_file_path_.DirName();
+ save_type_ = type;
+ if (save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML) {
+ // Make new directory for saving complete file.
+ saved_main_directory_path_ = saved_main_directory_path_.Append(
+ saved_main_file_path_.RemoveExtension().BaseName().value() +
+ FILE_PATH_LITERAL("_files"));
+ }
+
+ Init(download_created_callback);
+}
+
+void SavePackage::StopObservation() {
+ DCHECK(download_);
+ DCHECK(download_manager_);
+
+ download_->RemoveObserver(this);
+ download_ = NULL;
+ download_manager_ = NULL;
+}
+
+void SavePackage::OnDownloadDestroyed(DownloadItem* download) {
+ StopObservation();
+}
+
+void SavePackage::FinalizeDownloadEntry() {
+ DCHECK(download_);
+ DCHECK(download_manager_);
+
+ download_manager_->OnSavePackageSuccessfullyFinished(download_);
+ StopObservation();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_package.h b/chromium/content/browser/download/save_package.h
new file mode 100644
index 00000000000..b53280d7adf
--- /dev/null
+++ b/chromium/content/browser/download/save_package.h
@@ -0,0 +1,340 @@
+// 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 CONTENT_BROWSER_DOWNLOAD_SAVE_PACKAGE_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_PACKAGE_H_
+
+#include <queue>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager_delegate.h"
+#include "content/public/browser/save_page_type.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/referrer.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+class GURL;
+
+namespace content {
+class DownloadItemImpl;
+class DownloadManagerImpl;
+class WebContents;
+class SaveFileManager;
+class SaveItem;
+class SavePackage;
+struct SaveFileCreateInfo;
+
+// The SavePackage object manages the process of saving a page as only-html or
+// complete-html or MHTML and providing the information for displaying saving
+// status. Saving page as only-html means means that we save web page to a
+// single HTML file regardless internal sub resources and sub frames. Saving
+// page as complete-html page means we save not only the main html file the user
+// told it to save but also a directory for the auxiliary files such as all
+// sub-frame html files, image files, css files and js files. Saving page as
+// MHTML means the same thing as complete-html, but it uses the MHTML format to
+// contain the html and all auxiliary files in a single text file.
+//
+// Each page saving job may include one or multiple files which need to be
+// saved. Each file is represented by a SaveItem, and all SaveItems are owned
+// by the SavePackage. SaveItems are created when a user initiates a page
+// saving job, and exist for the duration of one contents's life time.
+class CONTENT_EXPORT SavePackage
+ : public base::RefCountedThreadSafe<SavePackage>,
+ public WebContentsObserver,
+ public DownloadItem::Observer,
+ public base::SupportsWeakPtr<SavePackage> {
+ public:
+ enum WaitState {
+ // State when created but not initialized.
+ INITIALIZE = 0,
+ // State when after initializing, but not yet saving.
+ START_PROCESS,
+ // Waiting on a list of savable resources from the backend.
+ RESOURCES_LIST,
+ // Waiting for data sent from net IO or from file system.
+ NET_FILES,
+ // Waiting for html DOM data sent from render process.
+ HTML_DATA,
+ // Saving page finished successfully.
+ SUCCESSFUL,
+ // Failed to save page.
+ FAILED
+ };
+
+ static const base::FilePath::CharType kDefaultHtmlExtension[];
+
+ // Constructor for user initiated page saving. This constructor results in a
+ // SavePackage that will generate and sanitize a suggested name for the user
+ // in the "Save As" dialog box.
+ explicit SavePackage(WebContents* web_contents);
+
+ // This contructor is used only for testing. We can bypass the file and
+ // directory name generation / sanitization by providing well known paths
+ // better suited for tests.
+ SavePackage(WebContents* web_contents,
+ SavePageType save_type,
+ const base::FilePath& file_full_path,
+ const base::FilePath& directory_full_path);
+
+ // Initialize the SavePackage. Returns true if it initializes properly. Need
+ // to make sure that this method must be called in the UI thread because using
+ // g_browser_process on a non-UI thread can cause crashes during shutdown.
+ // |cb| will be called when the DownloadItem is created, before data is
+ // written to disk.
+ bool Init(const SavePackageDownloadCreatedCallback& cb);
+
+ // Cancel all in progress request, might be called by user or internal error.
+ void Cancel(bool user_action);
+
+ void Finish();
+
+ // Notifications sent from the file thread to the UI thread.
+ void StartSave(const SaveFileCreateInfo* info);
+ bool UpdateSaveProgress(int32 save_id, int64 size, bool write_success);
+ void SaveFinished(int32 save_id, int64 size, bool is_success);
+ void SaveFailed(const GURL& save_url);
+ void SaveCanceled(SaveItem* save_item);
+
+ // Rough percent complete, -1 means we don't know (since we didn't receive a
+ // total size).
+ int PercentComplete();
+
+ bool canceled() const { return user_canceled_ || disk_error_occurred_; }
+ bool finished() const { return finished_; }
+ SavePageType save_type() const { return save_type_; }
+ int contents_id() const { return contents_id_; }
+ int id() const { return unique_id_; }
+ WebContents* web_contents() const;
+
+ void GetSaveInfo();
+
+ private:
+ friend class base::RefCountedThreadSafe<SavePackage>;
+
+ void InitWithDownloadItem(
+ const SavePackageDownloadCreatedCallback& download_created_callback,
+ DownloadItemImpl* item);
+
+ // Callback for WebContents::GenerateMHTML().
+ void OnMHTMLGenerated(const base::FilePath& path, int64 size);
+
+ // For testing only.
+ SavePackage(WebContents* web_contents,
+ const base::FilePath& file_full_path,
+ const base::FilePath& directory_full_path);
+
+ virtual ~SavePackage();
+
+ // Notes from Init() above applies here as well.
+ void InternalInit();
+
+ void Stop();
+ void CheckFinish();
+ void SaveNextFile(bool process_all_remainder_items);
+ void DoSavingProcess();
+
+ // WebContentsObserver implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // DownloadItem::Observer implementation.
+ virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE;
+
+ // Update the download history of this item upon completion.
+ void FinalizeDownloadEntry();
+
+ // Detach from DownloadManager.
+ void StopObservation();
+
+ // Return max length of a path for a specific base directory.
+ // This is needed on POSIX, which restrict the length of file names in
+ // addition to the restriction on the length of path names.
+ // |base_dir| is assumed to be a directory name with no trailing slash.
+ static uint32 GetMaxPathLengthForDirectory(const base::FilePath& base_dir);
+
+ static bool GetSafePureFileName(
+ const base::FilePath& dir_path,
+ const base::FilePath::StringType& file_name_ext,
+ uint32 max_file_path_len,
+ base::FilePath::StringType* pure_file_name);
+
+ // Create a file name based on the response from the server.
+ bool GenerateFileName(const std::string& disposition,
+ const GURL& url,
+ bool need_html_ext,
+ base::FilePath::StringType* generated_name);
+
+ // Get all savable resource links from current web page, include main
+ // frame and sub-frame.
+ void GetAllSavableResourceLinksForCurrentPage();
+ // Get html data by serializing all frames of current page with lists
+ // which contain all resource links that have local copy.
+ void GetSerializedHtmlDataForCurrentPageWithLocalLinks();
+
+ // Look up SaveItem by save id from in progress map.
+ SaveItem* LookupItemInProcessBySaveId(int32 save_id);
+
+ // Remove SaveItem from in progress map and put it to saved map.
+ void PutInProgressItemToSavedMap(SaveItem* save_item);
+
+ // Retrieves the URL to be saved from the WebContents.
+ GURL GetUrlToBeSaved();
+
+ void CreateDirectoryOnFileThread(const base::FilePath& website_save_dir,
+ const base::FilePath& download_save_dir,
+ bool skip_dir_check,
+ const std::string& mime_type,
+ const std::string& accept_langs);
+ void ContinueGetSaveInfo(const base::FilePath& suggested_path,
+ bool can_save_as_complete);
+ void OnPathPicked(
+ const base::FilePath& final_name,
+ SavePageType type,
+ const SavePackageDownloadCreatedCallback& cb);
+ void OnReceivedSavableResourceLinksForCurrentPage(
+ const std::vector<GURL>& resources_list,
+ const std::vector<Referrer>& referrers_list,
+ const std::vector<GURL>& frames_list);
+
+ void OnReceivedSerializedHtmlData(const GURL& frame_url,
+ const std::string& data,
+ int32 status);
+
+ typedef base::hash_map<std::string, SaveItem*> SaveUrlItemMap;
+ // in_progress_items_ is map of all saving job in in-progress state.
+ SaveUrlItemMap in_progress_items_;
+ // saved_failed_items_ is map of all saving job which are failed.
+ SaveUrlItemMap saved_failed_items_;
+
+ // The number of in process SaveItems.
+ int in_process_count() const {
+ return static_cast<int>(in_progress_items_.size());
+ }
+
+ // The number of all SaveItems which have completed, including success items
+ // and failed items.
+ int completed_count() const {
+ return static_cast<int>(saved_success_items_.size() +
+ saved_failed_items_.size());
+ }
+
+ // The current speed in files per second. This is used to update the
+ // DownloadItem associated to this SavePackage. The files per second is
+ // presented by the DownloadItem to the UI as bytes per second, which is
+ // not correct but matches the way the total and received number of files is
+ // presented as the total and received bytes.
+ int64 CurrentSpeed() const;
+
+ // Helper function for preparing suggested name for the SaveAs Dialog. The
+ // suggested name is determined by the web document's title.
+ base::FilePath GetSuggestedNameForSaveAs(
+ bool can_save_as_complete,
+ const std::string& contents_mime_type,
+ const std::string& accept_langs);
+
+ // Ensures that the file name has a proper extension for HTML by adding ".htm"
+ // if necessary.
+ static base::FilePath EnsureHtmlExtension(const base::FilePath& name);
+
+ // Ensures that the file name has a proper extension for supported formats
+ // if necessary.
+ static base::FilePath EnsureMimeExtension(const base::FilePath& name,
+ const std::string& contents_mime_type);
+
+ // Returns extension for supported MIME types (for example, for "text/plain"
+ // it returns "txt").
+ static const base::FilePath::CharType* ExtensionForMimeType(
+ const std::string& contents_mime_type);
+
+ typedef std::queue<SaveItem*> SaveItemQueue;
+ // A queue for items we are about to start saving.
+ SaveItemQueue waiting_item_queue_;
+
+ typedef base::hash_map<int32, SaveItem*> SavedItemMap;
+ // saved_success_items_ is map of all saving job which are successfully saved.
+ SavedItemMap saved_success_items_;
+
+ // Non-owning pointer for handling file writing on the file thread.
+ SaveFileManager* file_manager_;
+
+ // DownloadManager owns the DownloadItem and handles history and UI.
+ DownloadManagerImpl* download_manager_;
+ DownloadItemImpl* download_;
+
+ // The URL of the page the user wants to save.
+ GURL page_url_;
+ base::FilePath saved_main_file_path_;
+ base::FilePath saved_main_directory_path_;
+
+ // The title of the page the user wants to save.
+ string16 title_;
+
+ // Used to calculate package download speed (in files per second).
+ base::TimeTicks start_tick_;
+
+ // Indicates whether the actual saving job is finishing or not.
+ bool finished_;
+
+ // Indicates whether a call to Finish() has been scheduled.
+ bool mhtml_finishing_;
+
+ // Indicates whether user canceled the saving job.
+ bool user_canceled_;
+
+ // Indicates whether user get disk error.
+ bool disk_error_occurred_;
+
+ // Type about saving page as only-html or complete-html.
+ SavePageType save_type_;
+
+ // Number of all need to be saved resources.
+ size_t all_save_items_count_;
+
+ typedef std::set<base::FilePath::StringType,
+ bool (*)(const base::FilePath::StringType&,
+ const base::FilePath::StringType&)> FileNameSet;
+ // This set is used to eliminate duplicated file names in saving directory.
+ FileNameSet file_name_set_;
+
+ typedef base::hash_map<base::FilePath::StringType, uint32> FileNameCountMap;
+ // This map is used to track serial number for specified filename.
+ FileNameCountMap file_name_count_map_;
+
+ // Indicates current waiting state when SavePackage try to get something
+ // from outside.
+ WaitState wait_state_;
+
+ // Since for one contents, it can only have one SavePackage in same time.
+ // Now we actually use render_process_id as the contents's unique id.
+ const int contents_id_;
+
+ // Unique ID for this SavePackage.
+ const int unique_id_;
+
+ // Variables to record errors that happened so we can record them via
+ // UMA statistics.
+ bool wrote_to_completed_file_;
+ bool wrote_to_failed_file_;
+
+ friend class SavePackageTest;
+ FRIEND_TEST_ALL_PREFIXES(SavePackageTest, TestSuggestedSaveNames);
+ FRIEND_TEST_ALL_PREFIXES(SavePackageTest, TestLongSafePureFilename);
+
+ DISALLOW_COPY_AND_ASSIGN(SavePackage);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_PACKAGE_H_
diff --git a/chromium/content/browser/download/save_package_browsertest.cc b/chromium/content/browser/download/save_package_browsertest.cc
new file mode 100644
index 00000000000..cfa63edefb2
--- /dev/null
+++ b/chromium/content/browser/download/save_package_browsertest.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 The Chromium 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/files/scoped_temp_dir.h"
+#include "content/browser/download/save_package.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+const char kTestFile[] = "files/simple_page.html";
+
+class SavePackageBrowserTest : public ContentBrowserTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(save_dir_.CreateUniqueTempDir());
+ ContentBrowserTest::SetUp();
+ }
+
+ // Returns full paths of destination file and directory.
+ void GetDestinationPaths(const std::string& prefix,
+ base::FilePath* full_file_name,
+ base::FilePath* dir) {
+ *full_file_name = save_dir_.path().AppendASCII(prefix + ".htm");
+ *dir = save_dir_.path().AppendASCII(prefix + "_files");
+ }
+
+ // Temporary directory we will save pages to.
+ base::ScopedTempDir save_dir_;
+};
+
+// Create a SavePackage and delete it without calling Init.
+// SavePackage dtor has various asserts/checks that should not fire.
+IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, ImplicitCancel) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(kTestFile);
+ NavigateToURL(shell(), url);
+ base::FilePath full_file_name, dir;
+ GetDestinationPaths("a", &full_file_name, &dir);
+ scoped_refptr<SavePackage> save_package(new SavePackage(
+ shell()->web_contents(), SAVE_PAGE_TYPE_AS_ONLY_HTML, full_file_name,
+ dir));
+ ASSERT_TRUE(test_server()->Stop());
+}
+
+// Create a SavePackage, call Cancel, then delete it.
+// SavePackage dtor has various asserts/checks that should not fire.
+IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, ExplicitCancel) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(kTestFile);
+ NavigateToURL(shell(), url);
+ base::FilePath full_file_name, dir;
+ GetDestinationPaths("a", &full_file_name, &dir);
+ scoped_refptr<SavePackage> save_package(new SavePackage(
+ shell()->web_contents(), SAVE_PAGE_TYPE_AS_ONLY_HTML, full_file_name,
+ dir));
+ save_package->Cancel(true);
+ ASSERT_TRUE(test_server()->Stop());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_package_unittest.cc b/chromium/content/browser/download/save_package_unittest.cc
new file mode 100644
index 00000000000..c0aa3f8862b
--- /dev/null
+++ b/chromium/content/browser/download/save_package_unittest.cc
@@ -0,0 +1,440 @@
+// 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 <string>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/download/save_package.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+#define FPL FILE_PATH_LITERAL
+#if defined(OS_WIN)
+#define HTML_EXTENSION ".htm"
+// This second define is needed because MSVC is broken.
+#define FPL_HTML_EXTENSION L".htm"
+#else
+#define HTML_EXTENSION ".html"
+#define FPL_HTML_EXTENSION ".html"
+#endif
+
+namespace {
+
+// This constant copied from save_package.cc.
+#if defined(OS_WIN)
+const uint32 kMaxFilePathLength = MAX_PATH - 1;
+const uint32 kMaxFileNameLength = MAX_PATH - 1;
+#elif defined(OS_POSIX)
+const uint32 kMaxFilePathLength = PATH_MAX - 1;
+const uint32 kMaxFileNameLength = NAME_MAX;
+#endif
+
+// Used to make long filenames.
+std::string long_file_name(
+ "EFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ "89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ "6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ "456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a");
+
+bool HasOrdinalNumber(const base::FilePath::StringType& filename) {
+ base::FilePath::StringType::size_type r_paren_index =
+ filename.rfind(FPL(')'));
+ base::FilePath::StringType::size_type l_paren_index =
+ filename.rfind(FPL('('));
+ if (l_paren_index >= r_paren_index)
+ return false;
+
+ for (base::FilePath::StringType::size_type i = l_paren_index + 1;
+ i != r_paren_index; ++i) {
+ if (!IsAsciiDigit(filename[i]))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+class SavePackageTest : public RenderViewHostImplTestHarness {
+ public:
+ bool GetGeneratedFilename(bool need_success_generate_filename,
+ const std::string& disposition,
+ const std::string& url,
+ bool need_htm_ext,
+ base::FilePath::StringType* generated_name) {
+ SavePackage* save_package;
+ if (need_success_generate_filename)
+ save_package = save_package_success_.get();
+ else
+ save_package = save_package_fail_.get();
+ return save_package->GenerateFileName(disposition, GURL(url), need_htm_ext,
+ generated_name);
+ }
+
+ base::FilePath EnsureHtmlExtension(const base::FilePath& name) {
+ return SavePackage::EnsureHtmlExtension(name);
+ }
+
+ base::FilePath EnsureMimeExtension(const base::FilePath& name,
+ const std::string& content_mime_type) {
+ return SavePackage::EnsureMimeExtension(name, content_mime_type);
+ }
+
+ GURL GetUrlToBeSaved() {
+ return save_package_success_->GetUrlToBeSaved();
+ }
+
+ protected:
+ virtual void SetUp() {
+ RenderViewHostImplTestHarness::SetUp();
+
+ // Do the initialization in SetUp so contents() is initialized by
+ // RenderViewHostImplTestHarness::SetUp.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ save_package_success_ = new SavePackage(contents(),
+ temp_dir_.path().AppendASCII("testfile" HTML_EXTENSION),
+ temp_dir_.path().AppendASCII("testfile_files"));
+
+ // We need to construct a path that is *almost* kMaxFilePathLength long
+ long_file_name.reserve(kMaxFilePathLength + long_file_name.length());
+ while (long_file_name.length() < kMaxFilePathLength)
+ long_file_name += long_file_name;
+ long_file_name.resize(
+ kMaxFilePathLength - 9 - temp_dir_.path().value().length());
+
+ save_package_fail_ = new SavePackage(contents(),
+ temp_dir_.path().AppendASCII(long_file_name + HTML_EXTENSION),
+ temp_dir_.path().AppendASCII(long_file_name + "_files"));
+ }
+
+ private:
+ // SavePackage for successfully generating file name.
+ scoped_refptr<SavePackage> save_package_success_;
+ // SavePackage for failed generating file name.
+ scoped_refptr<SavePackage> save_package_fail_;
+
+ base::ScopedTempDir temp_dir_;
+};
+
+static const struct {
+ const char* disposition;
+ const char* url;
+ const base::FilePath::CharType* expected_name;
+ bool need_htm_ext;
+} kGeneratedFiles[] = {
+ // We mainly focus on testing duplicated names here, since retrieving file
+ // name from disposition and url has been tested in DownloadManagerTest.
+
+ // No useful information in disposition or URL, use default.
+ {"1.html", "http://www.savepage.com/",
+ FPL("saved_resource") FPL_HTML_EXTENSION, true},
+
+ // No duplicate occurs.
+ {"filename=1.css", "http://www.savepage.com", FPL("1.css"), false},
+
+ // No duplicate occurs.
+ {"filename=1.js", "http://www.savepage.com", FPL("1.js"), false},
+
+ // Append numbers for duplicated names.
+ {"filename=1.css", "http://www.savepage.com", FPL("1(1).css"), false},
+
+ // No duplicate occurs.
+ {"filename=1(1).js", "http://www.savepage.com", FPL("1(1).js"), false},
+
+ // Append numbers for duplicated names.
+ {"filename=1.css", "http://www.savepage.com", FPL("1(2).css"), false},
+
+ // Change number for duplicated names.
+ {"filename=1(1).css", "http://www.savepage.com", FPL("1(3).css"), false},
+
+ // No duplicate occurs.
+ {"filename=1(11).css", "http://www.savepage.com", FPL("1(11).css"), false},
+
+ // Test for case-insensitive file names.
+ {"filename=readme.txt", "http://www.savepage.com",
+ FPL("readme.txt"), false},
+
+ {"filename=readme.TXT", "http://www.savepage.com",
+ FPL("readme(1).TXT"), false},
+
+ {"filename=READme.txt", "http://www.savepage.com",
+ FPL("readme(2).txt"), false},
+
+ {"filename=Readme(1).txt", "http://www.savepage.com",
+ FPL("readme(3).txt"), false},
+};
+
+TEST_F(SavePackageTest, TestSuccessfullyGenerateSavePackageFilename) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGeneratedFiles); ++i) {
+ base::FilePath::StringType file_name;
+ bool ok = GetGeneratedFilename(true,
+ kGeneratedFiles[i].disposition,
+ kGeneratedFiles[i].url,
+ kGeneratedFiles[i].need_htm_ext,
+ &file_name);
+ ASSERT_TRUE(ok);
+ EXPECT_EQ(kGeneratedFiles[i].expected_name, file_name);
+ }
+}
+
+TEST_F(SavePackageTest, TestUnSuccessfullyGenerateSavePackageFilename) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGeneratedFiles); ++i) {
+ base::FilePath::StringType file_name;
+ bool ok = GetGeneratedFilename(false,
+ kGeneratedFiles[i].disposition,
+ kGeneratedFiles[i].url,
+ kGeneratedFiles[i].need_htm_ext,
+ &file_name);
+ ASSERT_FALSE(ok);
+ }
+}
+
+// Crashing on Windows, see http://crbug.com/79365
+#if defined(OS_WIN)
+#define MAYBE_TestLongSavePackageFilename DISABLED_TestLongSavePackageFilename
+#else
+#define MAYBE_TestLongSavePackageFilename TestLongSavePackageFilename
+#endif
+TEST_F(SavePackageTest, MAYBE_TestLongSavePackageFilename) {
+ const std::string base_url("http://www.google.com/");
+ const std::string long_file = long_file_name + ".css";
+ const std::string url = base_url + long_file;
+
+ base::FilePath::StringType filename;
+ // Test that the filename is successfully shortened to fit.
+ ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
+ EXPECT_TRUE(filename.length() < long_file.length());
+ EXPECT_FALSE(HasOrdinalNumber(filename));
+
+ // Test that the filename is successfully shortened to fit, and gets an
+ // an ordinal appended.
+ ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
+ EXPECT_TRUE(filename.length() < long_file.length());
+ EXPECT_TRUE(HasOrdinalNumber(filename));
+
+ // Test that the filename is successfully shortened to fit, and gets a
+ // different ordinal appended.
+ base::FilePath::StringType filename2;
+ ASSERT_TRUE(
+ GetGeneratedFilename(true, std::string(), url, false, &filename2));
+ EXPECT_TRUE(filename2.length() < long_file.length());
+ EXPECT_TRUE(HasOrdinalNumber(filename2));
+ EXPECT_NE(filename, filename2);
+}
+
+// Crashing on Windows, see http://crbug.com/79365
+#if defined(OS_WIN)
+#define MAYBE_TestLongSafePureFilename DISABLED_TestLongSafePureFilename
+#else
+#define MAYBE_TestLongSafePureFilename TestLongSafePureFilename
+#endif
+TEST_F(SavePackageTest, MAYBE_TestLongSafePureFilename) {
+ const base::FilePath save_dir(FPL("test_dir"));
+ const base::FilePath::StringType ext(FPL_HTML_EXTENSION);
+ base::FilePath::StringType filename =
+#if defined(OS_WIN)
+ ASCIIToWide(long_file_name);
+#else
+ long_file_name;
+#endif
+
+ // Test that the filename + extension doesn't exceed kMaxFileNameLength
+ uint32 max_path = SavePackage::GetMaxPathLengthForDirectory(save_dir);
+ ASSERT_TRUE(SavePackage::GetSafePureFileName(save_dir, ext, max_path,
+ &filename));
+ EXPECT_TRUE(filename.length() <= kMaxFileNameLength-ext.length());
+}
+
+static const struct {
+ const base::FilePath::CharType* page_title;
+ const base::FilePath::CharType* expected_name;
+} kExtensionTestCases[] = {
+ // Extension is preserved if it is already proper for HTML.
+ {FPL("filename.html"), FPL("filename.html")},
+ {FPL("filename.HTML"), FPL("filename.HTML")},
+ {FPL("filename.XHTML"), FPL("filename.XHTML")},
+ {FPL("filename.xhtml"), FPL("filename.xhtml")},
+ {FPL("filename.htm"), FPL("filename.htm")},
+ // ".htm" is added if the extension is improper for HTML.
+ {FPL("hello.world"), FPL("hello.world") FPL_HTML_EXTENSION},
+ {FPL("hello.txt"), FPL("hello.txt") FPL_HTML_EXTENSION},
+ {FPL("is.html.good"), FPL("is.html.good") FPL_HTML_EXTENSION},
+ // ".htm" is added if the name doesn't have an extension.
+ {FPL("helloworld"), FPL("helloworld") FPL_HTML_EXTENSION},
+ {FPL("helloworld."), FPL("helloworld.") FPL_HTML_EXTENSION},
+};
+
+// Crashing on Windows, see http://crbug.com/79365
+#if defined(OS_WIN)
+#define MAYBE_TestEnsureHtmlExtension DISABLED_TestEnsureHtmlExtension
+#else
+#define MAYBE_TestEnsureHtmlExtension TestEnsureHtmlExtension
+#endif
+TEST_F(SavePackageTest, MAYBE_TestEnsureHtmlExtension) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExtensionTestCases); ++i) {
+ base::FilePath original = base::FilePath(kExtensionTestCases[i].page_title);
+ base::FilePath expected =
+ base::FilePath(kExtensionTestCases[i].expected_name);
+ base::FilePath actual = EnsureHtmlExtension(original);
+ EXPECT_EQ(expected.value(), actual.value()) << "Failed for page title: " <<
+ kExtensionTestCases[i].page_title;
+ }
+}
+
+// Crashing on Windows, see http://crbug.com/79365
+#if defined(OS_WIN)
+#define MAYBE_TestEnsureMimeExtension DISABLED_TestEnsureMimeExtension
+#else
+#define MAYBE_TestEnsureMimeExtension TestEnsureMimeExtension
+#endif
+TEST_F(SavePackageTest, MAYBE_TestEnsureMimeExtension) {
+ static const struct {
+ const base::FilePath::CharType* page_title;
+ const base::FilePath::CharType* expected_name;
+ const char* contents_mime_type;
+ } kExtensionTests[] = {
+ { FPL("filename.html"), FPL("filename.html"), "text/html" },
+ { FPL("filename.htm"), FPL("filename.htm"), "text/html" },
+ { FPL("filename.xhtml"), FPL("filename.xhtml"), "text/html" },
+#if defined(OS_WIN)
+ { FPL("filename"), FPL("filename.htm"), "text/html" },
+#else // defined(OS_WIN)
+ { FPL("filename"), FPL("filename.html"), "text/html" },
+#endif // defined(OS_WIN)
+ { FPL("filename.html"), FPL("filename.html"), "text/xml" },
+ { FPL("filename.xml"), FPL("filename.xml"), "text/xml" },
+ { FPL("filename"), FPL("filename.xml"), "text/xml" },
+ { FPL("filename.xhtml"), FPL("filename.xhtml"),
+ "application/xhtml+xml" },
+ { FPL("filename.html"), FPL("filename.html"),
+ "application/xhtml+xml" },
+ { FPL("filename"), FPL("filename.xhtml"), "application/xhtml+xml" },
+ { FPL("filename.txt"), FPL("filename.txt"), "text/plain" },
+ { FPL("filename"), FPL("filename.txt"), "text/plain" },
+ { FPL("filename.css"), FPL("filename.css"), "text/css" },
+ { FPL("filename"), FPL("filename.css"), "text/css" },
+ { FPL("filename.abc"), FPL("filename.abc"), "unknown/unknown" },
+ { FPL("filename"), FPL("filename"), "unknown/unknown" },
+ };
+ for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(kExtensionTests); ++i) {
+ base::FilePath original = base::FilePath(kExtensionTests[i].page_title);
+ base::FilePath expected = base::FilePath(kExtensionTests[i].expected_name);
+ std::string mime_type(kExtensionTests[i].contents_mime_type);
+ base::FilePath actual = EnsureMimeExtension(original, mime_type);
+ EXPECT_EQ(expected.value(), actual.value()) << "Failed for page title: " <<
+ kExtensionTests[i].page_title << " MIME:" << mime_type;
+ }
+}
+
+// Test that the suggested names generated by SavePackage are reasonable:
+// If the name is a URL, retrieve only the path component since the path name
+// generation code will turn the entire URL into the file name leading to bad
+// extension names. For example, a page with no title and a URL:
+// http://www.foo.com/a/path/name.txt will turn into file:
+// "http www.foo.com a path name.txt", when we want to save it as "name.txt".
+
+static const struct SuggestedSaveNameTestCase {
+ const char* page_url;
+ const string16 page_title;
+ const base::FilePath::CharType* expected_name;
+ bool ensure_html_extension;
+} kSuggestedSaveNames[] = {
+ // Title overrides the URL.
+ { "http://foo.com",
+ ASCIIToUTF16("A page title"),
+ FPL("A page title") FPL_HTML_EXTENSION,
+ true
+ },
+ // Extension is preserved.
+ { "http://foo.com",
+ ASCIIToUTF16("A page title with.ext"),
+ FPL("A page title with.ext"),
+ false
+ },
+ // If the title matches the URL, use the last component of the URL.
+ { "http://foo.com/bar",
+ ASCIIToUTF16("foo.com/bar"),
+ FPL("bar"),
+ false
+ },
+ // If the title matches the URL, but there is no "filename" component,
+ // use the domain.
+ { "http://foo.com",
+ ASCIIToUTF16("foo.com"),
+ FPL("foo.com"),
+ false
+ },
+ // Make sure fuzzy matching works.
+ { "http://foo.com/bar",
+ ASCIIToUTF16("foo.com/bar"),
+ FPL("bar"),
+ false
+ },
+ // A URL-like title that does not match the title is respected in full.
+ { "http://foo.com",
+ ASCIIToUTF16("http://www.foo.com/path/title.txt"),
+ FPL("http www.foo.com path title.txt"),
+ false
+ },
+};
+
+// Crashing on Windows, see http://crbug.com/79365
+#if defined(OS_WIN)
+#define MAYBE_TestSuggestedSaveNames DISABLED_TestSuggestedSaveNames
+#else
+#define MAYBE_TestSuggestedSaveNames TestSuggestedSaveNames
+#endif
+TEST_F(SavePackageTest, MAYBE_TestSuggestedSaveNames) {
+ for (size_t i = 0; i < arraysize(kSuggestedSaveNames); ++i) {
+ scoped_refptr<SavePackage> save_package(
+ new SavePackage(contents(), base::FilePath(), base::FilePath()));
+ save_package->page_url_ = GURL(kSuggestedSaveNames[i].page_url);
+ save_package->title_ = kSuggestedSaveNames[i].page_title;
+
+ base::FilePath save_name = save_package->GetSuggestedNameForSaveAs(
+ kSuggestedSaveNames[i].ensure_html_extension,
+ std::string(), std::string());
+ EXPECT_EQ(kSuggestedSaveNames[i].expected_name, save_name.value()) <<
+ "Test case " << i;
+ }
+}
+
+static const base::FilePath::CharType* kTestDir =
+ FILE_PATH_LITERAL("save_page");
+
+// GetUrlToBeSaved method should return correct url to be saved.
+TEST_F(SavePackageTest, TestGetUrlToBeSaved) {
+ base::FilePath file_name(FILE_PATH_LITERAL("a.htm"));
+ GURL url = URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath(kTestDir).Append(file_name));
+ NavigateAndCommit(url);
+ EXPECT_EQ(url, GetUrlToBeSaved());
+}
+
+// GetUrlToBeSaved method sould return actual url to be saved,
+// instead of the displayed url used to view source of a page.
+// Ex:GetUrlToBeSaved method should return http://www.google.com
+// when user types view-source:http://www.google.com
+TEST_F(SavePackageTest, TestGetUrlToBeSavedViewSource) {
+ base::FilePath file_name(FILE_PATH_LITERAL("a.htm"));
+ GURL view_source_url = URLRequestMockHTTPJob::GetMockViewSourceUrl(
+ base::FilePath(kTestDir).Append(file_name));
+ GURL actual_url = URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath(kTestDir).Append(file_name));
+ NavigateAndCommit(view_source_url);
+ EXPECT_EQ(actual_url, GetUrlToBeSaved());
+ EXPECT_EQ(view_source_url, contents()->GetLastCommittedURL());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_types.cc b/chromium/content/browser/download/save_types.cc
new file mode 100644
index 00000000000..b72c01b6f8b
--- /dev/null
+++ b/chromium/content/browser/download/save_types.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2010 The Chromium 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 "content/browser/download/save_types.h"
+
+namespace content {
+
+SaveFileCreateInfo::SaveFileCreateInfo(const base::FilePath& path,
+ const GURL& url,
+ SaveFileSource save_source,
+ int32 save_id)
+ : path(path),
+ url(url),
+ save_id(save_id),
+ render_process_id(-1),
+ render_view_id(-1),
+ request_id(-1),
+ total_bytes(0),
+ save_source(save_source) {
+}
+
+SaveFileCreateInfo::SaveFileCreateInfo()
+ : save_id(-1),
+ render_process_id(-1),
+ render_view_id(-1),
+ request_id(-1),
+ total_bytes(0),
+ save_source(SAVE_FILE_FROM_UNKNOWN) {
+}
+
+SaveFileCreateInfo::~SaveFileCreateInfo() {}
+
+} // namespace content
diff --git a/chromium/content/browser/download/save_types.h b/chromium/content/browser/download/save_types.h
new file mode 100644
index 00000000000..dcdebb4964a
--- /dev/null
+++ b/chromium/content/browser/download/save_types.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_DOWNLOAD_SAVE_TYPES_H_
+#define CONTENT_BROWSER_DOWNLOAD_SAVE_TYPES_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "url/gurl.h"
+
+namespace content {
+typedef std::vector<std::pair<int, base::FilePath> > FinalNameList;
+typedef std::vector<int> SaveIDList;
+
+// This structure is used to handle and deliver some info
+// when processing each save item job.
+struct SaveFileCreateInfo {
+ enum SaveFileSource {
+ // This type indicates the source is not set.
+ SAVE_FILE_FROM_UNKNOWN = -1,
+ // This type indicates the save item needs to be retrieved from the network.
+ SAVE_FILE_FROM_NET = 0,
+ // This type indicates the save item needs to be retrieved from serializing
+ // DOM.
+ SAVE_FILE_FROM_DOM,
+ // This type indicates the save item needs to be retrieved from local file
+ // system.
+ SAVE_FILE_FROM_FILE
+ };
+
+ SaveFileCreateInfo(const base::FilePath& path,
+ const GURL& url,
+ SaveFileSource save_source,
+ int32 save_id);
+
+ SaveFileCreateInfo();
+
+ ~SaveFileCreateInfo();
+
+ // SaveItem fields.
+ // The local file path of saved file.
+ base::FilePath path;
+ // Original URL of the saved resource.
+ GURL url;
+ // Final URL of the saved resource since some URL might be redirected.
+ GURL final_url;
+ // The unique identifier for saving job, assigned at creation by
+ // the SaveFileManager for its internal record keeping.
+ int save_id;
+ // IDs for looking up the contents we are associated with.
+ int render_process_id;
+ int render_view_id;
+ // Handle for informing the ResourceDispatcherHost of a UI based cancel.
+ int request_id;
+ // Disposition info from HTTP response.
+ std::string content_disposition;
+ // Total bytes of saved file.
+ int64 total_bytes;
+ // Source type of saved file.
+ SaveFileSource save_source;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_DOWNLOAD_SAVE_TYPES_H_
diff --git a/chromium/content/browser/fileapi/OWNERS b/chromium/content/browser/fileapi/OWNERS
new file mode 100644
index 00000000000..f7cc33cc7c2
--- /dev/null
+++ b/chromium/content/browser/fileapi/OWNERS
@@ -0,0 +1,4 @@
+ericu@chromium.org
+kinuko@chromium.org
+michaeln@chromium.org
+jianli@chromium.org
diff --git a/chromium/content/browser/fileapi/browser_file_system_helper.cc b/chromium/content/browser/fileapi/browser_file_system_helper.cc
new file mode 100644
index 00000000000..6df6f1f381c
--- /dev/null
+++ b/chromium/content/browser/fileapi/browser_file_system_helper.cc
@@ -0,0 +1,151 @@
+// 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 "content/browser/fileapi/browser_file_system_helper.h"
+
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_options.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+namespace content {
+
+namespace {
+
+using fileapi::FileSystemOptions;
+
+FileSystemOptions CreateBrowserFileSystemOptions(bool is_incognito) {
+ FileSystemOptions::ProfileMode profile_mode =
+ is_incognito ? FileSystemOptions::PROFILE_MODE_INCOGNITO
+ : FileSystemOptions::PROFILE_MODE_NORMAL;
+ std::vector<std::string> additional_allowed_schemes;
+ GetContentClient()->browser()->GetAdditionalAllowedSchemesForFileSystem(
+ &additional_allowed_schemes);
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowFileAccessFromFiles)) {
+ additional_allowed_schemes.push_back(chrome::kFileScheme);
+ }
+ return FileSystemOptions(profile_mode, additional_allowed_schemes);
+}
+
+} // namespace
+
+scoped_refptr<fileapi::FileSystemContext> CreateFileSystemContext(
+ BrowserContext* browser_context,
+ const base::FilePath& profile_path,
+ bool is_incognito,
+ quota::QuotaManagerProxy* quota_manager_proxy) {
+
+ base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner =
+ pool->GetSequencedTaskRunnerWithShutdownBehavior(
+ pool->GetNamedSequenceToken("FileAPI"),
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+
+ // Setting up additional filesystem backends.
+ ScopedVector<fileapi::FileSystemBackend> additional_backends;
+ GetContentClient()->browser()->GetAdditionalFileSystemBackends(
+ browser_context,
+ profile_path,
+ &additional_backends);
+
+ scoped_refptr<fileapi::FileSystemContext> file_system_context =
+ new fileapi::FileSystemContext(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get(),
+ file_task_runner.get(),
+ BrowserContext::GetMountPoints(browser_context),
+ browser_context->GetSpecialStoragePolicy(),
+ quota_manager_proxy,
+ additional_backends.Pass(),
+ profile_path,
+ CreateBrowserFileSystemOptions(is_incognito));
+
+ std::vector<fileapi::FileSystemType> types;
+ file_system_context->GetFileSystemTypes(&types);
+ for (size_t i = 0; i < types.size(); ++i) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->
+ RegisterFileSystemPermissionPolicy(
+ types[i],
+ fileapi::FileSystemContext::GetPermissionPolicy(types[i]));
+ }
+
+ return file_system_context;
+}
+
+bool FileSystemURLIsValid(
+ fileapi::FileSystemContext* context,
+ const fileapi::FileSystemURL& url) {
+ if (!url.is_valid())
+ return false;
+
+ return context->GetFileSystemBackend(url.type()) != NULL;
+}
+
+bool CheckFileSystemPermissionsForProcess(
+ fileapi::FileSystemContext* context, int process_id,
+ const fileapi::FileSystemURL& url, int permissions,
+ base::PlatformFileError* error) {
+ DCHECK(error);
+
+ if (!FileSystemURLIsValid(context, url)) {
+ *error = base::PLATFORM_FILE_ERROR_INVALID_URL;
+ return false;
+ }
+
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ HasPermissionsForFileSystemFile(process_id, url, permissions)) {
+ *error = base::PLATFORM_FILE_ERROR_SECURITY;
+ return false;
+ }
+
+ *error = base::PLATFORM_FILE_OK;
+ return true;
+}
+
+void SyncGetPlatformPath(fileapi::FileSystemContext* context,
+ int process_id,
+ const GURL& path,
+ base::FilePath* platform_path) {
+ DCHECK(context->default_file_task_runner()->
+ RunsTasksOnCurrentThread());
+ DCHECK(platform_path);
+ *platform_path = base::FilePath();
+ fileapi::FileSystemURL url(context->CrackURL(path));
+ if (!FileSystemURLIsValid(context, url))
+ return;
+
+ // Make sure if this file is ok to be read (in the current architecture
+ // which means roughly same as the renderer is allowed to get the platform
+ // path to the file).
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanReadFileSystemFile(process_id, url))
+ return;
+
+ context->operation_runner()->SyncGetPlatformPath(url, platform_path);
+
+ // The path is to be attached to URLLoader so we grant read permission
+ // for the file. (We need to check first because a parent directory may
+ // already have the permissions and we don't need to grant it to the file.)
+ if (!policy->CanReadFile(process_id, *platform_path))
+ policy->GrantReadFile(process_id, *platform_path);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/fileapi/browser_file_system_helper.h b/chromium/content/browser/fileapi/browser_file_system_helper.h
new file mode 100644
index 00000000000..ba1e0943628
--- /dev/null
+++ b/chromium/content/browser/fileapi/browser_file_system_helper.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_FILEAPI_BROWSER_FILE_SYSTEM_HELPER_H_
+#define CONTENT_BROWSER_FILEAPI_BROWSER_FILE_SYSTEM_HELPER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+
+namespace fileapi {
+class ExternalMountPoints;
+class FileSystemContext;
+class FileSystemURL;
+}
+
+namespace content {
+
+class BrowserContext;
+
+// Helper method that returns FileSystemContext constructed for
+// the browser process.
+CONTENT_EXPORT scoped_refptr<fileapi::FileSystemContext>
+CreateFileSystemContext(
+ BrowserContext* browser_context,
+ const base::FilePath& profile_path,
+ bool is_incognito,
+ quota::QuotaManagerProxy* quota_manager_proxy);
+
+// Verifies that |url| is valid and has a registered backend in |context|.
+CONTENT_EXPORT bool FileSystemURLIsValid(fileapi::FileSystemContext* context,
+ const fileapi::FileSystemURL& url);
+
+// Check whether a process has permission to access the file system URL.
+CONTENT_EXPORT bool CheckFileSystemPermissionsForProcess(
+ fileapi::FileSystemContext* context,
+ int process_id,
+ const fileapi::FileSystemURL& url,
+ int permissions,
+ base::PlatformFileError* error);
+
+// Get the platform path from a file system URL. This needs to be called
+// on the FILE thread.
+CONTENT_EXPORT void SyncGetPlatformPath(fileapi::FileSystemContext* context,
+ int process_id,
+ const GURL& path,
+ base::FilePath* platform_path);
+} // namespace content
+
+#endif // CONTENT_BROWSER_FILEAPI_BROWSER_FILE_SYSTEM_HELPER_H_
diff --git a/chromium/content/browser/fileapi/chrome_blob_storage_context.cc b/chromium/content/browser/fileapi/chrome_blob_storage_context.cc
new file mode 100644
index 00000000000..728b0d58e25
--- /dev/null
+++ b/chromium/content/browser/fileapi/chrome_blob_storage_context.cc
@@ -0,0 +1,57 @@
+// 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 "content/browser/fileapi/chrome_blob_storage_context.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+
+using base::UserDataAdapter;
+using webkit_blob::BlobStorageController;
+
+namespace content {
+
+static const char* kBlobStorageContextKeyName = "content_blob_storage_context";
+
+ChromeBlobStorageContext::ChromeBlobStorageContext() {}
+
+ChromeBlobStorageContext* ChromeBlobStorageContext::GetFor(
+ BrowserContext* context) {
+ if (!context->GetUserData(kBlobStorageContextKeyName)) {
+ scoped_refptr<ChromeBlobStorageContext> blob =
+ new ChromeBlobStorageContext();
+ context->SetUserData(
+ kBlobStorageContextKeyName,
+ new UserDataAdapter<ChromeBlobStorageContext>(blob.get()));
+ // Check first to avoid memory leak in unittests.
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ChromeBlobStorageContext::InitializeOnIOThread, blob));
+ }
+ }
+
+ return UserDataAdapter<ChromeBlobStorageContext>::Get(
+ context, kBlobStorageContextKeyName);
+}
+
+void ChromeBlobStorageContext::InitializeOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ controller_.reset(new BlobStorageController());
+}
+
+ChromeBlobStorageContext::~ChromeBlobStorageContext() {}
+
+void ChromeBlobStorageContext::DeleteOnCorrectThread() const {
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO) &&
+ !BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, this);
+ return;
+ }
+ delete this;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/fileapi/chrome_blob_storage_context.h b/chromium/content/browser/fileapi/chrome_blob_storage_context.h
new file mode 100644
index 00000000000..3992e0e4198
--- /dev/null
+++ b/chromium/content/browser/fileapi/chrome_blob_storage_context.h
@@ -0,0 +1,65 @@
+// 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 CONTENT_BROWSER_FILEAPI_CHROME_BLOB_STORAGE_CONTEXT_H_
+#define CONTENT_BROWSER_FILEAPI_CHROME_BLOB_STORAGE_CONTEXT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/content_export.h"
+
+namespace webkit_blob {
+class BlobStorageController;
+}
+
+namespace content {
+class BrowserContext;
+struct ChromeBlobStorageContextDeleter;
+
+// A context class that keeps track of BlobStorageController used by the chrome.
+// There is an instance associated with each BrowserContext. There could be
+// multiple URLRequestContexts in the same browser context that refers to the
+// same instance.
+//
+// All methods, except the ctor, are expected to be called on
+// the IO thread (unless specifically called out in doc comments).
+class CONTENT_EXPORT ChromeBlobStorageContext
+ : public base::RefCountedThreadSafe<ChromeBlobStorageContext,
+ ChromeBlobStorageContextDeleter> {
+ public:
+ ChromeBlobStorageContext();
+
+ static ChromeBlobStorageContext* GetFor(
+ BrowserContext* browser_context);
+
+ void InitializeOnIOThread();
+
+ webkit_blob::BlobStorageController* controller() const {
+ return controller_.get();
+ }
+
+ protected:
+ virtual ~ChromeBlobStorageContext();
+
+ private:
+ friend class base::DeleteHelper<ChromeBlobStorageContext>;
+ friend class base::RefCountedThreadSafe<ChromeBlobStorageContext,
+ ChromeBlobStorageContextDeleter>;
+ friend struct ChromeBlobStorageContextDeleter;
+
+ void DeleteOnCorrectThread() const;
+
+ scoped_ptr<webkit_blob::BlobStorageController> controller_;
+};
+
+struct ChromeBlobStorageContextDeleter {
+ static void Destruct(const ChromeBlobStorageContext* context) {
+ context->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FILEAPI_CHROME_BLOB_STORAGE_CONTEXT_H_
diff --git a/chromium/content/browser/fileapi/file_system_browsertest.cc b/chromium/content/browser/fileapi/file_system_browsertest.cc
new file mode 100644
index 00000000000..6a79a409bd9
--- /dev/null
+++ b/chromium/content/browser/fileapi/file_system_browsertest.cc
@@ -0,0 +1,93 @@
+// 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 "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/thread_test_helper.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+using quota::QuotaManager;
+
+namespace content {
+
+// This browser test is aimed towards exercising the FileAPI bindings and
+// the actual implementation that lives in the browser side.
+class FileSystemBrowserTest : public ContentBrowserTest {
+ public:
+ FileSystemBrowserTest() {}
+
+ void SimpleTest(const GURL& test_url, bool incognito = false) {
+ // The test page will perform tests on FileAPI, then navigate to either
+ // a #pass or #fail ref.
+ Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell();
+
+ LOG(INFO) << "Navigating to URL and blocking.";
+ NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2);
+ LOG(INFO) << "Navigation done.";
+ std::string result =
+ the_browser->web_contents()->GetLastCommittedURL().ref();
+ if (result != "pass") {
+ std::string js_result;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ the_browser->web_contents(),
+ "window.domAutomationController.send(getLog())",
+ &js_result));
+ FAIL() << "Failed: " << js_result;
+ }
+ }
+};
+
+class FileSystemBrowserTestWithLowQuota : public FileSystemBrowserTest {
+ public:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ const int kInitialQuotaKilobytes = 5000;
+ const int kTemporaryStorageQuotaMaxSize =
+ kInitialQuotaKilobytes * 1024 * QuotaManager::kPerHostTemporaryPortion;
+ SetTempQuota(
+ kTemporaryStorageQuotaMaxSize,
+ BrowserContext::GetDefaultStoragePartition(
+ shell()->web_contents()->GetBrowserContext())->GetQuotaManager());
+ }
+
+ static void SetTempQuota(int64 bytes, scoped_refptr<QuotaManager> qm) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&FileSystemBrowserTestWithLowQuota::SetTempQuota, bytes,
+ qm));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ qm->SetTemporaryGlobalOverrideQuota(bytes, quota::QuotaCallback());
+ // Don't return until the quota has been set.
+ scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB).get()));
+ ASSERT_TRUE(helper->Run());
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(FileSystemBrowserTest, RequestTest) {
+ SimpleTest(GetTestUrl("fileapi", "request_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemBrowserTest, CreateTest) {
+ SimpleTest(GetTestUrl("fileapi", "create_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemBrowserTestWithLowQuota, QuotaTest) {
+ SimpleTest(GetTestUrl("fileapi", "quota_test.html"));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/fileapi/fileapi_message_filter.cc b/chromium/content/browser/fileapi/fileapi_message_filter.cc
new file mode 100644
index 00000000000..d5e903b3e9a
--- /dev/null
+++ b/chromium/content/browser/fileapi/fileapi_message_filter.cc
@@ -0,0 +1,863 @@
+// 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 "content/browser/fileapi/fileapi_message_filter.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/common/fileapi/file_system_messages.h"
+#include "content/common/fileapi/webblob_messages.h"
+#include "content/public/browser/user_metrics.h"
+#include "ipc/ipc_platform_file.h"
+#include "net/base/mime_util.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/blob/blob_data.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/directory_entry.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using fileapi::FileSystemFileUtil;
+using fileapi::FileSystemBackend;
+using fileapi::FileSystemOperation;
+using fileapi::FileSystemURL;
+using fileapi::FileUpdateObserver;
+using fileapi::UpdateObserverList;
+using webkit_blob::BlobData;
+using webkit_blob::BlobStorageController;
+
+namespace content {
+
+namespace {
+
+void RevokeFilePermission(int child_id, const base::FilePath& path) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->RevokeAllPermissionsForFile(
+ child_id, path);
+}
+
+} // namespace
+
+FileAPIMessageFilter::FileAPIMessageFilter(
+ int process_id,
+ net::URLRequestContextGetter* request_context_getter,
+ fileapi::FileSystemContext* file_system_context,
+ ChromeBlobStorageContext* blob_storage_context,
+ StreamContext* stream_context)
+ : process_id_(process_id),
+ context_(file_system_context),
+ request_context_getter_(request_context_getter),
+ request_context_(NULL),
+ blob_storage_context_(blob_storage_context),
+ stream_context_(stream_context) {
+ DCHECK(context_);
+ DCHECK(request_context_getter_.get());
+ DCHECK(blob_storage_context);
+ DCHECK(stream_context);
+}
+
+FileAPIMessageFilter::FileAPIMessageFilter(
+ int process_id,
+ net::URLRequestContext* request_context,
+ fileapi::FileSystemContext* file_system_context,
+ ChromeBlobStorageContext* blob_storage_context,
+ StreamContext* stream_context)
+ : process_id_(process_id),
+ context_(file_system_context),
+ request_context_(request_context),
+ blob_storage_context_(blob_storage_context),
+ stream_context_(stream_context) {
+ DCHECK(context_);
+ DCHECK(request_context_);
+ DCHECK(blob_storage_context);
+ DCHECK(stream_context);
+}
+
+void FileAPIMessageFilter::OnChannelConnected(int32 peer_pid) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserMessageFilter::OnChannelConnected(peer_pid);
+
+ if (request_context_getter_.get()) {
+ DCHECK(!request_context_);
+ request_context_ = request_context_getter_->GetURLRequestContext();
+ request_context_getter_ = NULL;
+ DCHECK(request_context_);
+ }
+
+ operation_runner_ = context_->CreateFileSystemOperationRunner();
+}
+
+void FileAPIMessageFilter::OnChannelClosing() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Unregister all the blob and stream URLs that are previously registered in
+ // this process.
+ for (base::hash_set<std::string>::const_iterator iter = blob_urls_.begin();
+ iter != blob_urls_.end(); ++iter) {
+ blob_storage_context_->controller()->RemoveBlob(GURL(*iter));
+ }
+ for (base::hash_set<std::string>::const_iterator iter = stream_urls_.begin();
+ iter != stream_urls_.end(); ++iter) {
+ stream_context_->registry()->UnregisterStream(GURL(*iter));
+ }
+
+ in_transit_snapshot_files_.clear();
+
+ // Close all files that are previously OpenFile()'ed in this process.
+ if (!on_close_callbacks_.IsEmpty()) {
+ DLOG(INFO)
+ << "File API: Renderer process shut down before NotifyCloseFile"
+ << " for " << on_close_callbacks_.size() << " files opened in PPAPI";
+ }
+
+ for (OnCloseCallbackMap::iterator itr(&on_close_callbacks_);
+ !itr.IsAtEnd(); itr.Advance()) {
+ const base::Closure* callback = itr.GetCurrentValue();
+ DCHECK(callback);
+ if (!callback->is_null())
+ callback->Run();
+ }
+
+ on_close_callbacks_.Clear();
+ operation_runner_.reset();
+ operations_.clear();
+}
+
+base::TaskRunner* FileAPIMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ if (message.type() == FileSystemHostMsg_SyncGetPlatformPath::ID)
+ return context_->default_file_task_runner();
+ return NULL;
+}
+
+bool FileAPIMessageFilter::OnMessageReceived(
+ const IPC::Message& message, bool* message_was_ok) {
+ *message_was_ok = true;
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(FileAPIMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Open, OnOpen)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_DeleteFileSystem, OnDeleteFileSystem)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Move, OnMove)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Copy, OnCopy)
+ IPC_MESSAGE_HANDLER(FileSystemMsg_Remove, OnRemove)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadMetadata, OnReadMetadata)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Create, OnCreate)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Exists, OnExists)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadDirectory, OnReadDirectory)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Write, OnWrite)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_Truncate, OnTruncate)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_TouchFile, OnTouchFile)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_CancelWrite, OnCancel)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_OpenFile, OnOpenFile)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_NotifyCloseFile, OnNotifyCloseFile)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_CreateSnapshotFile,
+ OnCreateSnapshotFile)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidReceiveSnapshotFile,
+ OnDidReceiveSnapshotFile)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_WillUpdate, OnWillUpdate)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidUpdate, OnDidUpdate)
+ IPC_MESSAGE_HANDLER(FileSystemHostMsg_SyncGetPlatformPath,
+ OnSyncGetPlatformPath)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_StartBuilding, OnStartBuildingBlob)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_AppendBlobDataItem,
+ OnAppendBlobDataItemToBlob)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_SyncAppendSharedMemory,
+ OnAppendSharedMemoryToBlob)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_FinishBuilding, OnFinishBuildingBlob)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_Clone, OnCloneBlob)
+ IPC_MESSAGE_HANDLER(BlobHostMsg_Remove, OnRemoveBlob)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_StartBuilding, OnStartBuildingStream)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_AppendBlobDataItem,
+ OnAppendBlobDataItemToStream)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_SyncAppendSharedMemory,
+ OnAppendSharedMemoryToStream)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_FinishBuilding, OnFinishBuildingStream)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_Clone, OnCloneStream)
+ IPC_MESSAGE_HANDLER(StreamHostMsg_Remove, OnRemoveStream)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+FileAPIMessageFilter::~FileAPIMessageFilter() {}
+
+void FileAPIMessageFilter::BadMessageReceived() {
+ RecordAction(UserMetricsAction("BadMessageTerminate_FAMF"));
+ BrowserMessageFilter::BadMessageReceived();
+}
+
+void FileAPIMessageFilter::OnOpen(
+ int request_id, const GURL& origin_url, fileapi::FileSystemType type,
+ int64 requested_size, bool create) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (type == fileapi::kFileSystemTypeTemporary) {
+ RecordAction(UserMetricsAction("OpenFileSystemTemporary"));
+ } else if (type == fileapi::kFileSystemTypePersistent) {
+ RecordAction(UserMetricsAction("OpenFileSystemPersistent"));
+ }
+ // TODO(kinuko): Use this mode for IPC too.
+ fileapi::OpenFileSystemMode mode =
+ create ? fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT
+ : fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT;
+ context_->OpenFileSystem(origin_url, type, mode, base::Bind(
+ &FileAPIMessageFilter::DidOpenFileSystem, this, request_id));
+}
+
+void FileAPIMessageFilter::OnDeleteFileSystem(
+ int request_id,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ context_->DeleteFileSystem(origin_url, type, base::Bind(
+ &FileAPIMessageFilter::DidDeleteFileSystem, this, request_id));
+}
+
+void FileAPIMessageFilter::OnMove(
+ int request_id, const GURL& src_path, const GURL& dest_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL src_url(context_->CrackURL(src_path));
+ FileSystemURL dest_url(context_->CrackURL(dest_path));
+ const int src_permissions =
+ fileapi::kReadFilePermissions | fileapi::kWriteFilePermissions;
+ if (!HasPermissionsForFile(src_url, src_permissions, &error) ||
+ !HasPermissionsForFile(
+ dest_url, fileapi::kCreateFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->Move(
+ src_url, dest_url,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+}
+
+void FileAPIMessageFilter::OnCopy(
+ int request_id, const GURL& src_path, const GURL& dest_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL src_url(context_->CrackURL(src_path));
+ FileSystemURL dest_url(context_->CrackURL(dest_path));
+ if (!HasPermissionsForFile(src_url, fileapi::kReadFilePermissions, &error) ||
+ !HasPermissionsForFile(
+ dest_url, fileapi::kCreateFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->Copy(
+ src_url, dest_url,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+}
+
+void FileAPIMessageFilter::OnRemove(
+ int request_id, const GURL& path, bool recursive) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kWriteFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->Remove(
+ url, recursive,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+}
+
+void FileAPIMessageFilter::OnReadMetadata(
+ int request_id, const GURL& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kReadFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->GetMetadata(
+ url, base::Bind(&FileAPIMessageFilter::DidGetMetadata, this, request_id));
+}
+
+void FileAPIMessageFilter::OnCreate(
+ int request_id, const GURL& path, bool exclusive,
+ bool is_directory, bool recursive) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kCreateFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ if (is_directory) {
+ operations_[request_id] = operation_runner()->CreateDirectory(
+ url, exclusive, recursive,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+ } else {
+ operations_[request_id] = operation_runner()->CreateFile(
+ url, exclusive,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+ }
+}
+
+void FileAPIMessageFilter::OnExists(
+ int request_id, const GURL& path, bool is_directory) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kReadFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ if (is_directory) {
+ operations_[request_id] = operation_runner()->DirectoryExists(
+ url,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+ } else {
+ operations_[request_id] = operation_runner()->FileExists(
+ url,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+ }
+}
+
+void FileAPIMessageFilter::OnReadDirectory(
+ int request_id, const GURL& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kReadFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->ReadDirectory(
+ url, base::Bind(&FileAPIMessageFilter::DidReadDirectory,
+ this, request_id));
+}
+
+void FileAPIMessageFilter::OnWrite(
+ int request_id,
+ const GURL& path,
+ const GURL& blob_url,
+ int64 offset) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!request_context_) {
+ // We can't write w/o a request context, trying to do so will crash.
+ NOTREACHED();
+ return;
+ }
+
+ FileSystemURL url(context_->CrackURL(path));
+ base::PlatformFileError error;
+ if (!HasPermissionsForFile(url, fileapi::kWriteFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->Write(
+ request_context_, url, blob_url, offset,
+ base::Bind(&FileAPIMessageFilter::DidWrite, this, request_id));
+}
+
+void FileAPIMessageFilter::OnTruncate(
+ int request_id,
+ const GURL& path,
+ int64 length) {
+ base::PlatformFileError error;
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, fileapi::kWriteFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->Truncate(
+ url, length,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+}
+
+void FileAPIMessageFilter::OnTouchFile(
+ int request_id,
+ const GURL& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ FileSystemURL url(context_->CrackURL(path));
+ base::PlatformFileError error;
+ if (!HasPermissionsForFile(url, fileapi::kCreateFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->TouchFile(
+ url, last_access_time, last_modified_time,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+}
+
+void FileAPIMessageFilter::OnCancel(
+ int request_id,
+ int request_id_to_cancel) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ OperationsMap::iterator found = operations_.find(request_id_to_cancel);
+ if (found != operations_.end()) {
+ // The cancel will eventually send both the write failure and the cancel
+ // success.
+ operation_runner()->Cancel(
+ found->second,
+ base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
+ } else {
+ // The write already finished; report that we failed to stop it.
+ Send(new FileSystemMsg_DidFail(
+ request_id, base::PLATFORM_FILE_ERROR_INVALID_OPERATION));
+ }
+}
+
+void FileAPIMessageFilter::OnOpenFile(
+ int request_id, const GURL& path, int file_flags) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::PlatformFileError error;
+ const int open_permissions = base::PLATFORM_FILE_OPEN |
+ (file_flags & fileapi::kOpenFilePermissions);
+ FileSystemURL url(context_->CrackURL(path));
+ if (!HasPermissionsForFile(url, open_permissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ quota::QuotaLimitType quota_policy = quota::kQuotaLimitTypeUnknown;
+ quota::QuotaManagerProxy* quota_manager_proxy =
+ context_->quota_manager_proxy();
+ CHECK(quota_manager_proxy);
+ CHECK(quota_manager_proxy->quota_manager());
+
+ if (quota_manager_proxy->quota_manager()->IsStorageUnlimited(
+ url.origin(), FileSystemTypeToQuotaStorageType(url.type()))) {
+ quota_policy = quota::kQuotaLimitTypeUnlimited;
+ } else {
+ quota_policy = quota::kQuotaLimitTypeLimited;
+ }
+
+ operations_[request_id] = operation_runner()->OpenFile(
+ url, file_flags, PeerHandle(),
+ base::Bind(&FileAPIMessageFilter::DidOpenFile, this, request_id,
+ quota_policy));
+}
+
+void FileAPIMessageFilter::OnNotifyCloseFile(int file_open_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Remove |file_open_id| from the map of |on_close_callback|s.
+ // It must only be called for a ID that is successfully opened and enrolled in
+ // DidOpenFile.
+ base::Closure* on_close_callback = on_close_callbacks_.Lookup(file_open_id);
+ if (on_close_callback && !on_close_callback->is_null()) {
+ on_close_callback->Run();
+ on_close_callbacks_.Remove(file_open_id);
+ }
+}
+
+void FileAPIMessageFilter::OnWillUpdate(const GURL& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ FileSystemURL url(context_->CrackURL(path));
+ if (!url.is_valid())
+ return;
+ const UpdateObserverList* observers =
+ context_->GetUpdateObservers(url.type());
+ if (!observers)
+ return;
+ observers->Notify(&FileUpdateObserver::OnStartUpdate, MakeTuple(url));
+}
+
+void FileAPIMessageFilter::OnDidUpdate(const GURL& path, int64 delta) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ FileSystemURL url(context_->CrackURL(path));
+ if (!url.is_valid())
+ return;
+ const UpdateObserverList* observers =
+ context_->GetUpdateObservers(url.type());
+ if (!observers)
+ return;
+ observers->Notify(&FileUpdateObserver::OnUpdate, MakeTuple(url, delta));
+ observers->Notify(&FileUpdateObserver::OnEndUpdate, MakeTuple(url));
+}
+
+void FileAPIMessageFilter::OnSyncGetPlatformPath(
+ const GURL& path, base::FilePath* platform_path) {
+ SyncGetPlatformPath(context_, process_id_, path, platform_path);
+}
+
+void FileAPIMessageFilter::OnCreateSnapshotFile(
+ int request_id, const GURL& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ FileSystemURL url(context_->CrackURL(path));
+
+ // Make sure if this file can be read by the renderer as this is
+ // called when the renderer is about to create a new File object
+ // (for reading the file).
+ base::PlatformFileError error;
+ if (!HasPermissionsForFile(url, fileapi::kReadFilePermissions, &error)) {
+ Send(new FileSystemMsg_DidFail(request_id, error));
+ return;
+ }
+
+ operations_[request_id] = operation_runner()->CreateSnapshotFile(
+ url,
+ base::Bind(&FileAPIMessageFilter::DidCreateSnapshot,
+ this, request_id, url));
+}
+
+void FileAPIMessageFilter::OnDidReceiveSnapshotFile(int request_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ in_transit_snapshot_files_.erase(request_id);
+}
+
+void FileAPIMessageFilter::OnStartBuildingBlob(const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ blob_storage_context_->controller()->StartBuildingBlob(url);
+ blob_urls_.insert(url.spec());
+}
+
+void FileAPIMessageFilter::OnAppendBlobDataItemToBlob(
+ const GURL& url, const BlobData::Item& item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (item.type() == BlobData::Item::TYPE_FILE_FILESYSTEM) {
+ base::PlatformFileError error;
+ FileSystemURL filesystem_url(context_->CrackURL(item.url()));
+ if (!HasPermissionsForFile(filesystem_url,
+ fileapi::kReadFilePermissions, &error)) {
+ OnRemoveBlob(url);
+ return;
+ }
+ }
+ if (item.type() == BlobData::Item::TYPE_FILE &&
+ !ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
+ process_id_, item.path())) {
+ OnRemoveBlob(url);
+ return;
+ }
+ if (item.length() == 0) {
+ BadMessageReceived();
+ return;
+ }
+ blob_storage_context_->controller()->AppendBlobDataItem(url, item);
+}
+
+void FileAPIMessageFilter::OnAppendSharedMemoryToBlob(
+ const GURL& url, base::SharedMemoryHandle handle, size_t buffer_size) {
+ DCHECK(base::SharedMemory::IsHandleValid(handle));
+ if (!buffer_size) {
+ BadMessageReceived();
+ return;
+ }
+#if defined(OS_WIN)
+ base::SharedMemory shared_memory(handle, true, PeerHandle());
+#else
+ base::SharedMemory shared_memory(handle, true);
+#endif
+ if (!shared_memory.Map(buffer_size)) {
+ OnRemoveBlob(url);
+ return;
+ }
+
+ BlobData::Item item;
+ item.SetToSharedBytes(static_cast<char*>(shared_memory.memory()),
+ buffer_size);
+ blob_storage_context_->controller()->AppendBlobDataItem(url, item);
+}
+
+void FileAPIMessageFilter::OnFinishBuildingBlob(
+ const GURL& url, const std::string& content_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ blob_storage_context_->controller()->FinishBuildingBlob(url, content_type);
+}
+
+void FileAPIMessageFilter::OnCloneBlob(
+ const GURL& url, const GURL& src_url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ blob_storage_context_->controller()->CloneBlob(url, src_url);
+ blob_urls_.insert(url.spec());
+}
+
+void FileAPIMessageFilter::OnRemoveBlob(const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ blob_storage_context_->controller()->RemoveBlob(url);
+ blob_urls_.erase(url.spec());
+}
+
+void FileAPIMessageFilter::OnStartBuildingStream(
+ const GURL& url, const std::string& content_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Only an internal Blob URL is expected here. See the BlobURL of the Blink.
+ if (!StartsWithASCII(
+ url.path(), "blobinternal%3A///", true /* case_sensitive */)) {
+ NOTREACHED() << "Malformed Stream URL: " << url.spec();
+ BadMessageReceived();
+ return;
+ }
+ // Use an empty security origin for now. Stream accepts a security origin
+ // but how it's handled is not fixed yet.
+ new Stream(stream_context_->registry(),
+ NULL /* write_observer */,
+ url);
+ stream_urls_.insert(url.spec());
+}
+
+void FileAPIMessageFilter::OnAppendBlobDataItemToStream(
+ const GURL& url, const BlobData::Item& item) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ scoped_refptr<Stream> stream(GetStreamForURL(url));
+ if (!stream.get()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Data for stream is delivered as TYPE_BYTES item.
+ if (item.type() != BlobData::Item::TYPE_BYTES) {
+ BadMessageReceived();
+ return;
+ }
+ stream->AddData(item.bytes(), item.length());
+}
+
+void FileAPIMessageFilter::OnAppendSharedMemoryToStream(
+ const GURL& url, base::SharedMemoryHandle handle, size_t buffer_size) {
+ DCHECK(base::SharedMemory::IsHandleValid(handle));
+ if (!buffer_size) {
+ BadMessageReceived();
+ return;
+ }
+#if defined(OS_WIN)
+ base::SharedMemory shared_memory(handle, true, PeerHandle());
+#else
+ base::SharedMemory shared_memory(handle, true);
+#endif
+ if (!shared_memory.Map(buffer_size)) {
+ OnRemoveStream(url);
+ return;
+ }
+
+ scoped_refptr<Stream> stream(GetStreamForURL(url));
+ if (!stream.get()) {
+ NOTREACHED();
+ return;
+ }
+
+ stream->AddData(static_cast<char*>(shared_memory.memory()), buffer_size);
+}
+
+void FileAPIMessageFilter::OnFinishBuildingStream(const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ scoped_refptr<Stream> stream(GetStreamForURL(url));
+ if (stream.get())
+ stream->Finalize();
+ else
+ NOTREACHED();
+}
+
+void FileAPIMessageFilter::OnCloneStream(
+ const GURL& url, const GURL& src_url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!GetStreamForURL(src_url)) {
+ NOTREACHED();
+ return;
+ }
+
+ stream_context_->registry()->CloneStream(url, src_url);
+ stream_urls_.insert(url.spec());
+}
+
+void FileAPIMessageFilter::OnRemoveStream(const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!GetStreamForURL(url).get()) {
+ NOTREACHED();
+ return;
+ }
+
+ stream_context_->registry()->UnregisterStream(url);
+ stream_urls_.erase(url.spec());
+}
+
+void FileAPIMessageFilter::DidFinish(int request_id,
+ base::PlatformFileError result) {
+ if (result == base::PLATFORM_FILE_OK)
+ Send(new FileSystemMsg_DidSucceed(request_id));
+ else
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ operations_.erase(request_id);
+}
+
+void FileAPIMessageFilter::DidGetMetadata(
+ int request_id,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& info) {
+ if (result == base::PLATFORM_FILE_OK)
+ Send(new FileSystemMsg_DidReadMetadata(request_id, info));
+ else
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ operations_.erase(request_id);
+}
+
+void FileAPIMessageFilter::DidReadDirectory(
+ int request_id,
+ base::PlatformFileError result,
+ const std::vector<fileapi::DirectoryEntry>& entries,
+ bool has_more) {
+ if (result == base::PLATFORM_FILE_OK)
+ Send(new FileSystemMsg_DidReadDirectory(request_id, entries, has_more));
+ else
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ operations_.erase(request_id);
+}
+
+void FileAPIMessageFilter::DidOpenFile(int request_id,
+ quota::QuotaLimitType quota_policy,
+ base::PlatformFileError result,
+ base::PlatformFile file,
+ const base::Closure& on_close_callback,
+ base::ProcessHandle peer_handle) {
+ if (result == base::PLATFORM_FILE_OK) {
+ IPC::PlatformFileForTransit file_for_transit =
+ file != base::kInvalidPlatformFileValue ?
+ IPC::GetFileHandleForProcess(file, peer_handle, true) :
+ IPC::InvalidPlatformFileForTransit();
+ int file_open_id = on_close_callbacks_.Add(
+ new base::Closure(on_close_callback));
+
+ Send(new FileSystemMsg_DidOpenFile(request_id,
+ file_for_transit,
+ file_open_id,
+ quota_policy));
+ } else {
+ Send(new FileSystemMsg_DidFail(request_id,
+ result));
+ }
+ operations_.erase(request_id);
+}
+
+void FileAPIMessageFilter::DidWrite(int request_id,
+ base::PlatformFileError result,
+ int64 bytes,
+ bool complete) {
+ if (result == base::PLATFORM_FILE_OK) {
+ Send(new FileSystemMsg_DidWrite(request_id, bytes, complete));
+ if (complete)
+ operations_.erase(request_id);
+ } else {
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ operations_.erase(request_id);
+ }
+}
+
+void FileAPIMessageFilter::DidOpenFileSystem(int request_id,
+ base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (result == base::PLATFORM_FILE_OK) {
+ DCHECK(root.is_valid());
+ Send(new FileSystemMsg_DidOpenFileSystem(request_id, name, root));
+ } else {
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ }
+ // For OpenFileSystem we do not create a new operation, so no unregister here.
+}
+
+void FileAPIMessageFilter::DidDeleteFileSystem(
+ int request_id,
+ base::PlatformFileError result) {
+ if (result == base::PLATFORM_FILE_OK)
+ Send(new FileSystemMsg_DidSucceed(request_id));
+ else
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ // For DeleteFileSystem we do not create a new operation,
+ // so no unregister here.
+}
+
+void FileAPIMessageFilter::DidCreateSnapshot(
+ int request_id,
+ const fileapi::FileSystemURL& url,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& snapshot_file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ operations_.erase(request_id);
+
+ if (result != base::PLATFORM_FILE_OK) {
+ Send(new FileSystemMsg_DidFail(request_id, result));
+ return;
+ }
+
+ scoped_refptr<webkit_blob::ShareableFileReference> file_ref = snapshot_file;
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
+ process_id_, platform_path)) {
+ // Give per-file read permission to the snapshot file if it hasn't it yet.
+ // In order for the renderer to be able to read the file via File object,
+ // it must be granted per-file read permission for the file's platform
+ // path. By now, it has already been verified that the renderer has
+ // sufficient permissions to read the file, so giving per-file permission
+ // here must be safe.
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ process_id_, platform_path);
+
+ // Revoke all permissions for the file when the last ref of the file
+ // is dropped.
+ if (!file_ref.get()) {
+ // Create a reference for temporary permission handling.
+ file_ref = webkit_blob::ShareableFileReference::GetOrCreate(
+ platform_path,
+ webkit_blob::ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE,
+ context_->default_file_task_runner());
+ }
+ file_ref->AddFinalReleaseCallback(
+ base::Bind(&RevokeFilePermission, process_id_));
+ }
+
+ if (file_ref.get()) {
+ // This ref is held until OnDidReceiveSnapshotFile is called.
+ in_transit_snapshot_files_[request_id] = file_ref;
+ }
+
+ // Return the file info and platform_path.
+ Send(new FileSystemMsg_DidCreateSnapshotFile(
+ request_id, info, platform_path));
+}
+
+bool FileAPIMessageFilter::HasPermissionsForFile(
+ const FileSystemURL& url, int permissions, base::PlatformFileError* error) {
+ return CheckFileSystemPermissionsForProcess(context_, process_id_, url,
+ permissions, error);
+}
+
+scoped_refptr<Stream> FileAPIMessageFilter::GetStreamForURL(const GURL& url) {
+ return stream_context_->registry()->GetStream(url);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/fileapi/fileapi_message_filter.h b/chromium/content/browser/fileapi/fileapi_message_filter.h
new file mode 100644
index 00000000000..8680f24a998
--- /dev/null
+++ b/chromium/content/browser/fileapi/fileapi_message_filter.h
@@ -0,0 +1,248 @@
+// 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 CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_util_proxy.h"
+#include "base/id_map.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "base/platform_file.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/common/blob/blob_data.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/quota/quota_types.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace fileapi {
+class FileSystemURL;
+class FileSystemContext;
+class FileSystemOperationRunner;
+struct DirectoryEntry;
+}
+
+namespace net {
+class URLRequestContext;
+class URLRequestContextGetter;
+} // namespace net
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+namespace content {
+class ChromeBlobStorageContext;
+
+// TODO(tyoshino): Factor out code except for IPC gluing from
+// FileAPIMessageFilter into separate classes. See crbug.com/263741.
+class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter {
+ public:
+ // Used by the renderer process host on the UI thread.
+ FileAPIMessageFilter(
+ int process_id,
+ net::URLRequestContextGetter* request_context_getter,
+ fileapi::FileSystemContext* file_system_context,
+ ChromeBlobStorageContext* blob_storage_context,
+ StreamContext* stream_context);
+ // Used by the worker process host on the IO thread.
+ FileAPIMessageFilter(
+ int process_id,
+ net::URLRequestContext* request_context,
+ fileapi::FileSystemContext* file_system_context,
+ ChromeBlobStorageContext* blob_storage_context,
+ StreamContext* stream_context);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ protected:
+ virtual ~FileAPIMessageFilter();
+
+ virtual void BadMessageReceived() OVERRIDE;
+
+ private:
+ typedef fileapi::FileSystemOperationRunner::OperationID OperationID;
+
+ void OnOpen(int request_id,
+ const GURL& origin_url,
+ fileapi::FileSystemType type,
+ int64 requested_size,
+ bool create);
+ void OnDeleteFileSystem(int request_id,
+ const GURL& origin_url,
+ fileapi::FileSystemType type);
+ void OnMove(int request_id,
+ const GURL& src_path,
+ const GURL& dest_path);
+ void OnCopy(int request_id,
+ const GURL& src_path,
+ const GURL& dest_path);
+ void OnRemove(int request_id, const GURL& path, bool recursive);
+ void OnReadMetadata(int request_id, const GURL& path);
+ void OnCreate(int request_id,
+ const GURL& path,
+ bool exclusive,
+ bool is_directory,
+ bool recursive);
+ void OnExists(int request_id, const GURL& path, bool is_directory);
+ void OnReadDirectory(int request_id, const GURL& path);
+ void OnWrite(int request_id,
+ const GURL& path,
+ const GURL& blob_url,
+ int64 offset);
+ void OnTruncate(int request_id, const GURL& path, int64 length);
+ void OnTouchFile(int request_id,
+ const GURL& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time);
+ void OnCancel(int request_id, int request_to_cancel);
+ void OnOpenFile(int request_id, const GURL& path, int file_flags);
+ void OnNotifyCloseFile(int file_open_id);
+ void OnWillUpdate(const GURL& path);
+ void OnDidUpdate(const GURL& path, int64 delta);
+ void OnSyncGetPlatformPath(const GURL& path,
+ base::FilePath* platform_path);
+ void OnCreateSnapshotFile(int request_id,
+ const GURL& path);
+ void OnDidReceiveSnapshotFile(int request_id);
+
+ // Handlers for BlobHostMsg_ family messages.
+
+ void OnStartBuildingBlob(const GURL& url);
+ void OnAppendBlobDataItemToBlob(
+ const GURL& url, const webkit_blob::BlobData::Item& item);
+ void OnAppendSharedMemoryToBlob(
+ const GURL& url, base::SharedMemoryHandle handle, size_t buffer_size);
+ void OnFinishBuildingBlob(const GURL& url, const std::string& content_type);
+ void OnCloneBlob(const GURL& url, const GURL& src_url);
+ void OnRemoveBlob(const GURL& url);
+
+ // Handlers for StreamHostMsg_ family messages.
+ //
+ // TODO(tyoshino): Consider renaming BlobData to more generic one as it's now
+ // used for Stream.
+
+ // Currently |content_type| is ignored.
+ //
+ // TODO(tyoshino): Set |content_type| to the stream.
+ void OnStartBuildingStream(const GURL& url, const std::string& content_type);
+ void OnAppendBlobDataItemToStream(
+ const GURL& url, const webkit_blob::BlobData::Item& item);
+ void OnAppendSharedMemoryToStream(
+ const GURL& url, base::SharedMemoryHandle handle, size_t buffer_size);
+ void OnFinishBuildingStream(const GURL& url);
+ void OnCloneStream(const GURL& url, const GURL& src_url);
+ void OnRemoveStream(const GURL& url);
+
+ // Callback functions to be used when each file operation is finished.
+ void DidFinish(int request_id, base::PlatformFileError result);
+ void DidCancel(int request_id, base::PlatformFileError result);
+ void DidGetMetadata(int request_id,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& info);
+ void DidReadDirectory(int request_id,
+ base::PlatformFileError result,
+ const std::vector<fileapi::DirectoryEntry>& entries,
+ bool has_more);
+ void DidOpenFile(int request_id,
+ quota::QuotaLimitType quota_policy,
+ base::PlatformFileError result,
+ base::PlatformFile file,
+ const base::Closure& on_close_callback,
+ base::ProcessHandle peer_handle);
+ void DidWrite(int request_id,
+ base::PlatformFileError result,
+ int64 bytes,
+ bool complete);
+ void DidOpenFileSystem(int request_id,
+ base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root);
+ void DidDeleteFileSystem(int request_id,
+ base::PlatformFileError result);
+ void DidCreateSnapshot(
+ int request_id,
+ const fileapi::FileSystemURL& url,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+
+ // Checks renderer's access permissions for single file.
+ bool HasPermissionsForFile(const fileapi::FileSystemURL& url,
+ int permissions,
+ base::PlatformFileError* error);
+
+ // Retrieves the Stream object for |url| from |stream_context_|.
+ scoped_refptr<Stream> GetStreamForURL(const GURL& url);
+
+ fileapi::FileSystemOperationRunner* operation_runner() {
+ return operation_runner_.get();
+ }
+
+ int process_id_;
+
+ fileapi::FileSystemContext* context_;
+
+ // Keeps map from request_id to OperationID for ongoing operations.
+ // (Primarily for Cancel operation)
+ typedef std::map<int, OperationID> OperationsMap;
+ OperationsMap operations_;
+
+ // The getter holds the context until Init() can be called from the
+ // IO thread, which will extract the net::URLRequestContext from it.
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ net::URLRequestContext* request_context_;
+
+ scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
+ scoped_refptr<StreamContext> stream_context_;
+
+ scoped_ptr<fileapi::FileSystemOperationRunner> operation_runner_;
+
+ // Keep track of blob URLs registered in this process. Need to unregister
+ // all of them when the renderer process dies.
+ base::hash_set<std::string> blob_urls_;
+
+ // Keep track of stream URLs registered in this process. Need to unregister
+ // all of them when the renderer process dies.
+ base::hash_set<std::string> stream_urls_;
+
+ // Used to keep snapshot files alive while a DidCreateSnapshot
+ // is being sent to the renderer.
+ std::map<int, scoped_refptr<webkit_blob::ShareableFileReference> >
+ in_transit_snapshot_files_;
+
+ // Keep track of file system file opened by OpenFile() in this process.
+ // Need to close all of them when the renderer process dies.
+ typedef IDMap<base::Closure, IDMapOwnPointer> OnCloseCallbackMap;
+ OnCloseCallbackMap on_close_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileAPIMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc b/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc
new file mode 100644
index 00000000000..06b112ba64d
--- /dev/null
+++ b/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc
@@ -0,0 +1,357 @@
+// 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 "content/browser/fileapi/fileapi_message_filter.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/common/fileapi/file_system_messages.h"
+#include "content/common/fileapi/webblob_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/common_param_traits.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace content {
+
+namespace {
+
+const char kFakeBlobInternalUrlSpec[] =
+ "blob:blobinternal%3A///dc83ede4-9bbd-453b-be2e-60fd623fcc93";
+const char kFakeBlobInternalUrlSpec2[] =
+ "blob:blobinternal%3A///d28ae2e7-d233-4dda-9598-d135fe5d403e";
+
+const char kFakeContentType[] = "fake/type";
+
+} // namespace
+
+class FileAPIMessageFilterTest : public testing::Test {
+ public:
+ FileAPIMessageFilterTest()
+ : io_browser_thread_(BrowserThread::IO, &message_loop_) {
+ }
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ file_system_context_ =
+ fileapi::CreateFileSystemContextForTesting(NULL, base::FilePath());
+
+ std::vector<fileapi::FileSystemType> types;
+ file_system_context_->GetFileSystemTypes(&types);
+ for (size_t i = 0; i < types.size(); ++i) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->
+ RegisterFileSystemPermissionPolicy(
+ types[i],
+ fileapi::FileSystemContext::GetPermissionPolicy(types[i]));
+ }
+
+ stream_context_ = StreamContext::GetFor(&browser_context_);
+ blob_storage_context_ = ChromeBlobStorageContext::GetFor(&browser_context_);
+
+ filter_ = new FileAPIMessageFilter(
+ 0 /* process_id */,
+ browser_context_.GetRequestContext(),
+ file_system_context_.get(),
+ blob_storage_context_,
+ stream_context_);
+
+ // Complete initialization.
+ message_loop_.RunUntilIdle();
+ }
+
+ // Tests via OnMessageReceived(const IPC::Message&). The channel proxy calls
+ // this method. Since OnMessageReceived is hidden on FileAPIMessageFilter,
+ // we need to cast it.
+ bool InvokeOnMessageReceived(const IPC::Message& message) {
+ IPC::ChannelProxy::MessageFilter* casted_filter =
+ static_cast<IPC::ChannelProxy::MessageFilter*>(filter_.get());
+ return casted_filter->OnMessageReceived(message);
+ }
+
+ base::MessageLoop message_loop_;
+ TestBrowserThread io_browser_thread_;
+
+ TestBrowserContext browser_context_;
+ scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+ StreamContext* stream_context_;
+ ChromeBlobStorageContext* blob_storage_context_;
+
+ scoped_refptr<FileAPIMessageFilter> filter_;
+};
+
+TEST_F(FileAPIMessageFilterTest, BuildEmptyBlob) {
+ webkit_blob::BlobStorageController* controller =
+ blob_storage_context_->controller();
+
+ const GURL kUrl("blob:foobar");
+ const GURL kDifferentUrl("blob:barfoo");
+
+ EXPECT_EQ(NULL, controller->GetBlobDataFromUrl(kUrl));
+
+ BlobHostMsg_StartBuilding start_message(kUrl);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ // Blob is still being built. Nothing should be returned.
+ EXPECT_EQ(NULL, controller->GetBlobDataFromUrl(kUrl));
+
+ BlobHostMsg_FinishBuilding finish_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(finish_message));
+
+ // Now, Blob is built.
+ webkit_blob::BlobData* blob_data = controller->GetBlobDataFromUrl(kUrl);
+ ASSERT_FALSE(blob_data == NULL);
+ EXPECT_EQ(0U, blob_data->items().size());
+ EXPECT_EQ(kFakeContentType, blob_data->content_type());
+
+ // Nothing should be returned for a URL we didn't use.
+ EXPECT_TRUE(controller->GetBlobDataFromUrl(kDifferentUrl) == NULL);
+}
+
+TEST_F(FileAPIMessageFilterTest, CloseChannelWithInflightRequest) {
+ scoped_refptr<FileAPIMessageFilter> filter(
+ new FileAPIMessageFilter(
+ 0 /* process_id */,
+ browser_context_.GetRequestContext(),
+ file_system_context_.get(),
+ ChromeBlobStorageContext::GetFor(&browser_context_),
+ StreamContext::GetFor(&browser_context_)));
+ filter->OnChannelConnected(0);
+
+ // Complete initialization.
+ message_loop_.RunUntilIdle();
+
+ IPC::ChannelProxy::MessageFilter* casted_filter =
+ static_cast<IPC::ChannelProxy::MessageFilter*>(filter.get());
+
+ int request_id = 0;
+ const GURL kUrl("filesystem:http://example.com/temporary/foo");
+ FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl);
+ EXPECT_TRUE(casted_filter->OnMessageReceived(read_metadata));
+
+ // Close the filter while it has inflight request.
+ filter->OnChannelClosing();
+
+ // This shouldn't cause DCHECK failure.
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(FileAPIMessageFilterTest, MultipleFilters) {
+ scoped_refptr<FileAPIMessageFilter> filter1(
+ new FileAPIMessageFilter(
+ 0 /* process_id */,
+ browser_context_.GetRequestContext(),
+ file_system_context_.get(),
+ ChromeBlobStorageContext::GetFor(&browser_context_),
+ StreamContext::GetFor(&browser_context_)));
+ scoped_refptr<FileAPIMessageFilter> filter2(
+ new FileAPIMessageFilter(
+ 1 /* process_id */,
+ browser_context_.GetRequestContext(),
+ file_system_context_.get(),
+ ChromeBlobStorageContext::GetFor(&browser_context_),
+ StreamContext::GetFor(&browser_context_)));
+ filter1->OnChannelConnected(0);
+ filter2->OnChannelConnected(1);
+
+ // Complete initialization.
+ message_loop_.RunUntilIdle();
+
+ IPC::ChannelProxy::MessageFilter* casted_filter =
+ static_cast<IPC::ChannelProxy::MessageFilter*>(filter1.get());
+
+ int request_id = 0;
+ const GURL kUrl("filesystem:http://example.com/temporary/foo");
+ FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl);
+ EXPECT_TRUE(casted_filter->OnMessageReceived(read_metadata));
+
+ // Close the other filter before the request for filter1 is processed.
+ filter2->OnChannelClosing();
+
+ // This shouldn't cause DCHECK failure.
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(FileAPIMessageFilterTest, BuildEmptyStream) {
+ StreamRegistry* stream_registry = stream_context_->registry();
+
+ webkit_blob::BlobStorageController* blob_controller =
+ blob_storage_context_->controller();
+
+ const GURL kUrl(kFakeBlobInternalUrlSpec);
+ const GURL kDifferentUrl("blob:barfoo");
+
+ EXPECT_EQ(NULL, stream_registry->GetStream(kUrl).get());
+ EXPECT_EQ(NULL, blob_controller->GetBlobDataFromUrl(kUrl));
+
+ StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ const int kBufferSize = 10;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+ int bytes_read = 0;
+
+ scoped_refptr<Stream> stream = stream_registry->GetStream(kUrl);
+ // Stream becomes available for read right after registration.
+ ASSERT_FALSE(stream.get() == NULL);
+ EXPECT_EQ(Stream::STREAM_EMPTY,
+ stream->ReadRawData(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+ stream = NULL;
+
+ StreamHostMsg_FinishBuilding finish_message(kUrl);
+ EXPECT_TRUE(InvokeOnMessageReceived(finish_message));
+
+ // Blob controller shouldn't be affected.
+ EXPECT_EQ(NULL, blob_controller->GetBlobDataFromUrl(kUrl));
+
+ stream = stream_registry->GetStream(kUrl);
+ ASSERT_FALSE(stream.get() == NULL);
+ EXPECT_EQ(Stream::STREAM_EMPTY,
+ stream->ReadRawData(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+
+ // Run loop to finish transfer.
+ message_loop_.RunUntilIdle();
+
+ EXPECT_EQ(Stream::STREAM_COMPLETE,
+ stream->ReadRawData(buffer.get(), kBufferSize, &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+
+ // Nothing should be returned for a URL we didn't use.
+ EXPECT_TRUE(stream_registry->GetStream(kDifferentUrl).get() == NULL);
+}
+
+TEST_F(FileAPIMessageFilterTest, BuildNonEmptyStream) {
+ StreamRegistry* stream_registry = stream_context_->registry();
+
+ const GURL kUrl(kFakeBlobInternalUrlSpec);
+
+ EXPECT_EQ(NULL, stream_registry->GetStream(kUrl).get());
+
+ StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ webkit_blob::BlobData::Item item;
+ const std::string kFakeData = "foobarbaz";
+ item.SetToBytes(kFakeData.data(), kFakeData.size());
+ StreamHostMsg_AppendBlobDataItem append_message(kUrl, item);
+ EXPECT_TRUE(InvokeOnMessageReceived(append_message));
+
+ StreamHostMsg_FinishBuilding finish_message(kUrl);
+ EXPECT_TRUE(InvokeOnMessageReceived(finish_message));
+
+ // Run loop to finish transfer and commit finalize command.
+ message_loop_.RunUntilIdle();
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kFakeData.size()));
+ int bytes_read = 0;
+
+ scoped_refptr<Stream> stream = stream_registry->GetStream(kUrl);
+ ASSERT_FALSE(stream.get() == NULL);
+
+ EXPECT_EQ(Stream::STREAM_HAS_DATA,
+ stream->ReadRawData(buffer.get(), kFakeData.size(), &bytes_read));
+ EXPECT_EQ(kFakeData.size(), static_cast<size_t>(bytes_read));
+ EXPECT_EQ(kFakeData, std::string(buffer->data(), bytes_read));
+
+ EXPECT_EQ(Stream::STREAM_COMPLETE,
+ stream->ReadRawData(buffer.get(), kFakeData.size(), &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+}
+
+TEST_F(FileAPIMessageFilterTest, BuildStreamWithSharedMemory) {
+ StreamRegistry* stream_registry = stream_context_->registry();
+
+ const GURL kUrl(kFakeBlobInternalUrlSpec);
+
+ EXPECT_EQ(NULL, stream_registry->GetStream(kUrl).get());
+
+ // For win, we need to set valid PID to the filter.
+ // OnAppendSharedMemoryToStream passes the peer process's handle to
+ // SharedMemory's constructor. If it's incorrect, DuplicateHandle won't work
+ // correctly.
+ static_cast<IPC::ChannelProxy::MessageFilter*>(
+ filter_.get())->OnChannelConnected(base::Process::Current().pid());
+
+ StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ const std::string kFakeData = "foobarbaz";
+
+ scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory);
+ ASSERT_TRUE(shared_memory->CreateAndMapAnonymous(kFakeData.size()));
+ memcpy(shared_memory->memory(), kFakeData.data(), kFakeData.size());
+ StreamHostMsg_SyncAppendSharedMemory append_message(
+ kUrl, shared_memory->handle(), kFakeData.size());
+ EXPECT_TRUE(InvokeOnMessageReceived(append_message));
+
+ StreamHostMsg_FinishBuilding finish_message(kUrl);
+ EXPECT_TRUE(InvokeOnMessageReceived(finish_message));
+
+ // Run loop to finish transfer and commit finalize command.
+ message_loop_.RunUntilIdle();
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kFakeData.size()));
+ int bytes_read = 0;
+
+ scoped_refptr<Stream> stream = stream_registry->GetStream(kUrl);
+ ASSERT_FALSE(stream.get() == NULL);
+
+ EXPECT_EQ(Stream::STREAM_HAS_DATA,
+ stream->ReadRawData(buffer.get(), kFakeData.size(), &bytes_read));
+ EXPECT_EQ(kFakeData.size(), static_cast<size_t>(bytes_read));
+ EXPECT_EQ(kFakeData, std::string(buffer->data(), bytes_read));
+
+ EXPECT_EQ(Stream::STREAM_COMPLETE,
+ stream->ReadRawData(buffer.get(), kFakeData.size(), &bytes_read));
+ EXPECT_EQ(0, bytes_read);
+}
+
+TEST_F(FileAPIMessageFilterTest, BuildStreamAndCallOnChannelClosing) {
+ StreamRegistry* stream_registry = stream_context_->registry();
+
+ const GURL kUrl(kFakeBlobInternalUrlSpec);
+
+ StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ ASSERT_FALSE(stream_registry->GetStream(kUrl).get() == NULL);
+
+ filter_->OnChannelClosing();
+
+ ASSERT_EQ(NULL, stream_registry->GetStream(kUrl).get());
+}
+
+TEST_F(FileAPIMessageFilterTest, CloneStream) {
+ StreamRegistry* stream_registry = stream_context_->registry();
+
+ const GURL kUrl(kFakeBlobInternalUrlSpec);
+ const GURL kDestUrl(kFakeBlobInternalUrlSpec2);
+
+ StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType);
+ EXPECT_TRUE(InvokeOnMessageReceived(start_message));
+
+ StreamHostMsg_Clone clone_message(kDestUrl, kUrl);
+ EXPECT_TRUE(InvokeOnMessageReceived(clone_message));
+
+ ASSERT_FALSE(stream_registry->GetStream(kUrl).get() == NULL);
+ ASSERT_FALSE(stream_registry->GetStream(kDestUrl).get() == NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/font_list_async.cc b/chromium/content/browser/font_list_async.cc
new file mode 100644
index 00000000000..422531947a4
--- /dev/null
+++ b/chromium/content/browser/font_list_async.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/font_list_async.h"
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "content/common/font_list.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+namespace {
+
+// Just executes the given callback with the parameter.
+void ReturnFontListToOriginalThread(
+ const base::Callback<void(scoped_ptr<base::ListValue>)>& callback,
+ scoped_ptr<base::ListValue> result) {
+ callback.Run(result.Pass());
+}
+
+void GetFontListInBlockingPool(
+ BrowserThread::ID calling_thread_id,
+ const base::Callback<void(scoped_ptr<base::ListValue>)>& callback) {
+ scoped_ptr<base::ListValue> result(GetFontList_SlowBlocking());
+ BrowserThread::PostTask(calling_thread_id, FROM_HERE,
+ base::Bind(&ReturnFontListToOriginalThread, callback,
+ base::Passed(&result)));
+}
+
+} // namespace
+
+void GetFontListAsync(
+ const base::Callback<void(scoped_ptr<base::ListValue>)>& callback) {
+ BrowserThread::ID id;
+ bool well_known_thread = BrowserThread::GetCurrentThreadIdentifier(&id);
+ DCHECK(well_known_thread)
+ << "Can only call GetFontList from a well-known thread.";
+
+ BrowserThread::PostBlockingPoolSequencedTask(
+ kFontListSequenceToken,
+ FROM_HERE,
+ base::Bind(&GetFontListInBlockingPool, id, callback));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/OWNERS b/chromium/content/browser/gamepad/OWNERS
new file mode 100644
index 00000000000..3f809e82b19
--- /dev/null
+++ b/chromium/content/browser/gamepad/OWNERS
@@ -0,0 +1 @@
+scottmg@chromium.org
diff --git a/chromium/content/browser/gamepad/gamepad_data_fetcher.h b/chromium/content/browser/gamepad/gamepad_data_fetcher.h
new file mode 100644
index 00000000000..e5e009dc42b
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_data_fetcher.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_
+
+namespace WebKit {
+class WebGamepads;
+}
+
+namespace content {
+
+// Abstract interface for imlementing platform- (and test-) specific behaviro
+// for getting the gamepad data.
+class GamepadDataFetcher {
+ public:
+ virtual ~GamepadDataFetcher() {}
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) = 0;
+ virtual void PauseHint(bool paused) {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc
new file mode 100644
index 00000000000..b4499d6fd41
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc
@@ -0,0 +1,19 @@
+// 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 "content/browser/gamepad/gamepad_platform_data_fetcher.h"
+
+#include "third_party/WebKit/public/platform/WebGamepads.h"
+
+namespace content {
+
+GamepadDataFetcherEmpty::GamepadDataFetcherEmpty() {
+}
+
+void GamepadDataFetcherEmpty::GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) {
+ pads->length = 0;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h
new file mode 100644
index 00000000000..f073a538d6f
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h
@@ -0,0 +1,55 @@
+// 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.
+
+// Define the default data fetcher that GamepadProvider will use if none is
+// supplied. (GamepadPlatformDataFetcher).
+
+#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+
+#if defined(OS_WIN)
+#include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
+#elif defined(OS_MACOSX)
+#include "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h"
+#elif defined(OS_LINUX)
+#include "content/browser/gamepad/gamepad_platform_data_fetcher_linux.h"
+#endif
+
+namespace content {
+
+#if defined(OS_WIN)
+
+typedef GamepadPlatformDataFetcherWin GamepadPlatformDataFetcher;
+
+#elif defined(OS_MACOSX)
+
+typedef GamepadPlatformDataFetcherMac GamepadPlatformDataFetcher;
+
+#elif defined(OS_LINUX)
+
+typedef GamepadPlatformDataFetcherLinux GamepadPlatformDataFetcher;
+
+#else
+
+class GamepadDataFetcherEmpty : public GamepadDataFetcher {
+ public:
+ GamepadDataFetcherEmpty();
+
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GamepadDataFetcherEmpty);
+};
+typedef GamepadDataFetcherEmpty GamepadPlatformDataFetcher;
+
+#endif
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc
new file mode 100644
index 00000000000..c82997e22a1
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc
@@ -0,0 +1,257 @@
+// 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 "content/browser/gamepad/gamepad_platform_data_fetcher_linux.h"
+
+#include <fcntl.h>
+#include <libudev.h>
+#include <linux/joystick.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/udev_linux.h"
+
+namespace {
+
+const char kInputSubsystem[] = "input";
+const char kUsbSubsystem[] = "usb";
+const char kUsbDeviceType[] = "usb_device";
+const float kMaxLinuxAxisValue = 32767.0;
+
+void CloseFileDescriptorIfValid(int fd) {
+ if (fd >= 0)
+ close(fd);
+}
+
+bool IsGamepad(udev_device* dev, int* index, std::string* path) {
+ if (!udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"))
+ return false;
+
+ const char* node_path = udev_device_get_devnode(dev);
+ if (!node_path)
+ return false;
+
+ static const char kJoystickRoot[] = "/dev/input/js";
+ bool is_gamepad = StartsWithASCII(node_path, kJoystickRoot, true);
+ if (!is_gamepad)
+ return false;
+
+ int tmp_idx = -1;
+ const int base_len = sizeof(kJoystickRoot) - 1;
+ base::StringPiece str(&node_path[base_len], strlen(node_path) - base_len);
+ if (!base::StringToInt(str, &tmp_idx))
+ return false;
+ if (tmp_idx < 0 ||
+ tmp_idx >= static_cast<int>(WebKit::WebGamepads::itemsLengthCap)) {
+ return false;
+ }
+ *index = tmp_idx;
+ *path = node_path;
+ return true;
+}
+
+} // namespace
+
+namespace content {
+
+using WebKit::WebGamepad;
+using WebKit::WebGamepads;
+
+GamepadPlatformDataFetcherLinux::GamepadPlatformDataFetcherLinux() {
+ for (size_t i = 0; i < arraysize(device_fds_); ++i)
+ device_fds_[i] = -1;
+ memset(mappers_, 0, sizeof(mappers_));
+
+ std::vector<UdevLinux::UdevMonitorFilter> filters;
+ filters.push_back(UdevLinux::UdevMonitorFilter(kInputSubsystem, NULL));
+ udev_.reset(
+ new UdevLinux(filters,
+ base::Bind(&GamepadPlatformDataFetcherLinux::RefreshDevice,
+ base::Unretained(this))));
+
+ EnumerateDevices();
+}
+
+GamepadPlatformDataFetcherLinux::~GamepadPlatformDataFetcherLinux() {
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
+ CloseFileDescriptorIfValid(device_fds_[i]);
+}
+
+void GamepadPlatformDataFetcherLinux::GetGamepadData(WebGamepads* pads, bool) {
+ TRACE_EVENT0("GAMEPAD", "GetGamepadData");
+
+ data_.length = WebGamepads::itemsLengthCap;
+
+ // Update our internal state.
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (device_fds_[i] >= 0) {
+ ReadDeviceData(i);
+ }
+ }
+
+ // Copy to the current state to the output buffer, using the mapping
+ // function, if there is one available.
+ pads->length = data_.length;
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (mappers_[i])
+ mappers_[i](data_.items[i], &pads->items[i]);
+ else
+ pads->items[i] = data_.items[i];
+ }
+}
+
+// Used during enumeration, and monitor notifications.
+void GamepadPlatformDataFetcherLinux::RefreshDevice(udev_device* dev) {
+ int index;
+ std::string node_path;
+ if (IsGamepad(dev, &index, &node_path)) {
+ int& device_fd = device_fds_[index];
+ WebGamepad& pad = data_.items[index];
+ GamepadStandardMappingFunction& mapper = mappers_[index];
+
+ CloseFileDescriptorIfValid(device_fd);
+
+ // The device pointed to by dev contains information about the logical
+ // joystick device. In order to get the information about the physical
+ // hardware, get the parent device that is also in the "input" subsystem.
+ // This function should just walk up the tree one level.
+ dev = udev_device_get_parent_with_subsystem_devtype(
+ dev,
+ kInputSubsystem,
+ NULL);
+ if (!dev) {
+ // Unable to get device information, don't use this device.
+ device_fd = -1;
+ pad.connected = false;
+ return;
+ }
+
+ device_fd = HANDLE_EINTR(open(node_path.c_str(), O_RDONLY | O_NONBLOCK));
+ if (device_fd < 0) {
+ // Unable to open device, don't use.
+ pad.connected = false;
+ return;
+ }
+
+ const char* vendor_id = udev_device_get_sysattr_value(dev, "id/vendor");
+ const char* product_id = udev_device_get_sysattr_value(dev, "id/product");
+ mapper = GetGamepadStandardMappingFunction(vendor_id, product_id);
+
+ // Driver returns utf-8 strings here, so combine in utf-8 first and
+ // convert to WebUChar later once we've picked an id string.
+ const char* name = udev_device_get_sysattr_value(dev, "name");
+ std::string name_string = base::StringPrintf("%s", name);
+
+ // In many cases the information the input subsystem contains isn't
+ // as good as the information that the device bus has, walk up further
+ // to the subsystem/device type "usb"/"usb_device" and if this device
+ // has the same vendor/product id, prefer the description from that.
+ struct udev_device *usb_dev = udev_device_get_parent_with_subsystem_devtype(
+ dev,
+ kUsbSubsystem,
+ kUsbDeviceType);
+ if (usb_dev) {
+ const char* usb_vendor_id =
+ udev_device_get_sysattr_value(usb_dev, "idVendor");
+ const char* usb_product_id =
+ udev_device_get_sysattr_value(usb_dev, "idProduct");
+
+ if (strcmp(vendor_id, usb_vendor_id) == 0 &&
+ strcmp(product_id, usb_product_id) == 0) {
+ const char* manufacturer =
+ udev_device_get_sysattr_value(usb_dev, "manufacturer");
+ const char* product = udev_device_get_sysattr_value(usb_dev, "product");
+
+ // Replace the previous name string with one containing the better
+ // information, again driver returns utf-8 strings here so combine
+ // in utf-8 for conversion to WebUChar below.
+ name_string = base::StringPrintf("%s %s", manufacturer, product);
+ }
+ }
+
+ // Append the vendor and product information then convert the utf-8
+ // id string to WebUChar.
+ std::string id = name_string + base::StringPrintf(
+ " (%sVendor: %s Product: %s)",
+ mapper ? "STANDARD GAMEPAD " : "",
+ vendor_id,
+ product_id);
+ TruncateUTF8ToByteSize(id, WebGamepad::idLengthCap - 1, &id);
+ string16 tmp16 = UTF8ToUTF16(id);
+ memset(pad.id, 0, sizeof(pad.id));
+ tmp16.copy(pad.id, arraysize(pad.id) - 1);
+
+ pad.connected = true;
+ }
+}
+
+void GamepadPlatformDataFetcherLinux::EnumerateDevices() {
+ udev_enumerate* enumerate = udev_enumerate_new(udev_->udev_handle());
+ if (!enumerate)
+ return;
+ int ret = udev_enumerate_add_match_subsystem(enumerate, kInputSubsystem);
+ if (ret != 0)
+ return;
+ ret = udev_enumerate_scan_devices(enumerate);
+ if (ret != 0)
+ return;
+
+ udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate);
+ for (udev_list_entry* dev_list_entry = devices;
+ dev_list_entry != NULL;
+ dev_list_entry = udev_list_entry_get_next(dev_list_entry)) {
+ // Get the filename of the /sys entry for the device and create a
+ // udev_device object (dev) representing it
+ const char* path = udev_list_entry_get_name(dev_list_entry);
+ udev_device* dev = udev_device_new_from_syspath(udev_->udev_handle(), path);
+ if (!dev)
+ continue;
+ RefreshDevice(dev);
+ udev_device_unref(dev);
+ }
+ // Free the enumerator object
+ udev_enumerate_unref(enumerate);
+}
+
+void GamepadPlatformDataFetcherLinux::ReadDeviceData(size_t index) {
+ // Linker does not like CHECK_LT(index, WebGamepads::itemsLengthCap). =/
+ if (index >= WebGamepads::itemsLengthCap) {
+ CHECK(false);
+ return;
+ }
+
+ const int& fd = device_fds_[index];
+ WebGamepad& pad = data_.items[index];
+ DCHECK_GE(fd, 0);
+
+ js_event event;
+ while (HANDLE_EINTR(read(fd, &event, sizeof(struct js_event))) > 0) {
+ size_t item = event.number;
+ if (event.type & JS_EVENT_AXIS) {
+ if (item >= WebGamepad::axesLengthCap)
+ continue;
+ pad.axes[item] = event.value / kMaxLinuxAxisValue;
+ if (item >= pad.axesLength)
+ pad.axesLength = item + 1;
+ } else if (event.type & JS_EVENT_BUTTON) {
+ if (item >= WebGamepad::buttonsLengthCap)
+ continue;
+ pad.buttons[item] = event.value ? 1.0 : 0.0;
+ if (item >= pad.buttonsLength)
+ pad.buttonsLength = item + 1;
+ }
+ pad.timestamp = event.time;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h
new file mode 100644
index 00000000000..916eda311f4
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_standard_mappings.h"
+#include "third_party/WebKit/public/platform/WebGamepads.h"
+
+extern "C" {
+struct udev_device;
+}
+
+namespace content {
+
+class UdevLinux;
+
+class GamepadPlatformDataFetcherLinux : public GamepadDataFetcher {
+ public:
+ GamepadPlatformDataFetcherLinux();
+ virtual ~GamepadPlatformDataFetcherLinux();
+
+ // GamepadDataFetcher implementation.
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) OVERRIDE;
+
+ private:
+ void RefreshDevice(udev_device* dev);
+ void EnumerateDevices();
+ void ReadDeviceData(size_t index);
+
+ // File descriptors for the /dev/input/js* devices. -1 if not in use.
+ int device_fds_[WebKit::WebGamepads::itemsLengthCap];
+
+ // Functions to map from device data to standard layout, if available. May
+ // be null if no mapping is available.
+ GamepadStandardMappingFunction mappers_[WebKit::WebGamepads::itemsLengthCap];
+
+ // Data that's returned to the consumer.
+ WebKit::WebGamepads data_;
+
+ scoped_ptr<UdevLinux> udev_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherLinux);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h
new file mode 100644
index 00000000000..3b22b69350c
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h
@@ -0,0 +1,106 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_standard_mappings.h"
+#include "content/browser/gamepad/xbox_data_fetcher_mac.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "third_party/WebKit/public/platform/WebGamepads.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#if defined(__OBJC__)
+@class NSArray;
+#else
+class NSArray;
+#endif
+
+namespace content {
+
+class GamepadPlatformDataFetcherMac : public GamepadDataFetcher,
+ public XboxDataFetcher::Delegate {
+ public:
+ GamepadPlatformDataFetcherMac();
+ virtual ~GamepadPlatformDataFetcherMac();
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) OVERRIDE;
+ virtual void PauseHint(bool paused) OVERRIDE;
+
+ private:
+ bool enabled_;
+ base::ScopedCFTypeRef<IOHIDManagerRef> hid_manager_ref_;
+
+ static GamepadPlatformDataFetcherMac* InstanceFromContext(void* context);
+ static void DeviceAddCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDDeviceRef ref);
+ static void DeviceRemoveCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDDeviceRef ref);
+ static void ValueChangedCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef ref);
+
+ size_t GetEmptySlot();
+ size_t GetSlotForDevice(IOHIDDeviceRef device);
+ size_t GetSlotForXboxDevice(XboxController* device);
+
+ void DeviceAdd(IOHIDDeviceRef device);
+ void AddButtonsAndAxes(NSArray* elements, size_t slot);
+ void DeviceRemove(IOHIDDeviceRef device);
+ void ValueChanged(IOHIDValueRef value);
+
+ virtual void XboxDeviceAdd(XboxController* device) OVERRIDE;
+ virtual void XboxDeviceRemove(XboxController* device) OVERRIDE;
+ virtual void XboxValueChanged(XboxController* device,
+ const XboxController::Data& data) OVERRIDE;
+
+ void RegisterForNotifications();
+ void UnregisterFromNotifications();
+
+ scoped_ptr<XboxDataFetcher> xbox_fetcher_;
+
+ WebKit::WebGamepads data_;
+
+ // Side-band data that's not passed to the consumer, but we need to maintain
+ // to update data_.
+ struct AssociatedData {
+ bool is_xbox;
+ union {
+ struct {
+ IOHIDDeviceRef device_ref;
+ IOHIDElementRef button_elements[WebKit::WebGamepad::buttonsLengthCap];
+ IOHIDElementRef axis_elements[WebKit::WebGamepad::buttonsLengthCap];
+ CFIndex axis_minimums[WebKit::WebGamepad::axesLengthCap];
+ CFIndex axis_maximums[WebKit::WebGamepad::axesLengthCap];
+ // Function to map from device data to standard layout, if available.
+ // May be null if no mapping is available.
+ GamepadStandardMappingFunction mapper;
+ } hid;
+ struct {
+ XboxController* device;
+ UInt32 location_id;
+ } xbox;
+ };
+ };
+ AssociatedData associated_[WebKit::WebGamepads::itemsLengthCap];
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm
new file mode 100644
index 00000000000..51524d7f523
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm
@@ -0,0 +1,441 @@
+// 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 "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h"
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+
+#import <Foundation/Foundation.h>
+#include <IOKit/hid/IOHIDKeys.h>
+
+using WebKit::WebGamepad;
+using WebKit::WebGamepads;
+
+namespace content {
+
+namespace {
+
+NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:usage_page],
+ base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)),
+ [NSNumber numberWithUnsignedInt:usage],
+ base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)),
+ nil];
+}
+
+float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
+ return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
+}
+
+// http://www.usb.org/developers/hidpage
+const uint32_t kGenericDesktopUsagePage = 0x01;
+const uint32_t kButtonUsagePage = 0x09;
+const uint32_t kJoystickUsageNumber = 0x04;
+const uint32_t kGameUsageNumber = 0x05;
+const uint32_t kMultiAxisUsageNumber = 0x08;
+const uint32_t kAxisMinimumUsageNumber = 0x30;
+const uint32_t kAxisMaximumUsageNumber = 0x35;
+
+} // namespace
+
+GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac()
+ : enabled_(true) {
+ memset(associated_, 0, sizeof(associated_));
+
+ xbox_fetcher_.reset(new XboxDataFetcher(this));
+ if (!xbox_fetcher_->RegisterForNotifications())
+ xbox_fetcher_.reset();
+
+ hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
+ kIOHIDOptionsTypeNone));
+ if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
+ enabled_ = false;
+ return;
+ }
+
+ base::scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects:
+ DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber),
+ DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber),
+ DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
+ nil]);
+ IOHIDManagerSetDeviceMatchingMultiple(
+ hid_manager_ref_,
+ base::mac::NSToCFCast(criteria));
+
+ RegisterForNotifications();
+}
+
+void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
+ // Register for plug/unplug notifications.
+ IOHIDManagerRegisterDeviceMatchingCallback(
+ hid_manager_ref_,
+ &DeviceAddCallback,
+ this);
+ IOHIDManagerRegisterDeviceRemovalCallback(
+ hid_manager_ref_,
+ DeviceRemoveCallback,
+ this);
+
+ // Register for value change notifications.
+ IOHIDManagerRegisterInputValueCallback(
+ hid_manager_ref_,
+ ValueChangedCallback,
+ this);
+
+ IOHIDManagerScheduleWithRunLoop(
+ hid_manager_ref_,
+ CFRunLoopGetMain(),
+ kCFRunLoopDefaultMode);
+
+ enabled_ = IOHIDManagerOpen(hid_manager_ref_,
+ kIOHIDOptionsTypeNone) == kIOReturnSuccess;
+
+ if (xbox_fetcher_)
+ xbox_fetcher_->RegisterForNotifications();
+}
+
+void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
+ IOHIDManagerUnscheduleFromRunLoop(
+ hid_manager_ref_,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
+ if (xbox_fetcher_)
+ xbox_fetcher_->UnregisterFromNotifications();
+}
+
+void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
+ if (pause)
+ UnregisterFromNotifications();
+ else
+ RegisterForNotifications();
+}
+
+GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
+ UnregisterFromNotifications();
+}
+
+GamepadPlatformDataFetcherMac*
+GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
+ return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
+}
+
+void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDDeviceRef ref) {
+ InstanceFromContext(context)->DeviceAdd(ref);
+}
+
+void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDDeviceRef ref) {
+ InstanceFromContext(context)->DeviceRemove(ref);
+}
+
+void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef ref) {
+ InstanceFromContext(context)->ValueChanged(ref);
+}
+
+void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
+ size_t slot) {
+ WebGamepad& pad = data_.items[slot];
+ AssociatedData& associated = associated_[slot];
+ CHECK(!associated.is_xbox);
+
+ pad.axesLength = 0;
+ pad.buttonsLength = 0;
+ pad.timestamp = 0;
+ memset(pad.axes, 0, sizeof(pad.axes));
+ memset(pad.buttons, 0, sizeof(pad.buttons));
+
+ for (id elem in elements) {
+ IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
+ uint32_t usagePage = IOHIDElementGetUsagePage(element);
+ uint32_t usage = IOHIDElementGetUsage(element);
+ if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
+ usagePage == kButtonUsagePage) {
+ uint32_t button_index = usage - 1;
+ if (button_index < WebGamepad::buttonsLengthCap) {
+ associated.hid.button_elements[button_index] = element;
+ pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
+ }
+ }
+ else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
+ uint32_t axis_index = usage - kAxisMinimumUsageNumber;
+ if (axis_index < WebGamepad::axesLengthCap) {
+ associated.hid.axis_minimums[axis_index] =
+ IOHIDElementGetLogicalMin(element);
+ associated.hid.axis_maximums[axis_index] =
+ IOHIDElementGetLogicalMax(element);
+ associated.hid.axis_elements[axis_index] = element;
+ pad.axesLength = std::max(pad.axesLength, axis_index + 1);
+ }
+ }
+ }
+}
+
+size_t GamepadPlatformDataFetcherMac::GetEmptySlot() {
+ // Find a free slot for this device.
+ for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
+ if (!data_.items[slot].connected)
+ return slot;
+ }
+ return WebGamepads::itemsLengthCap;
+}
+
+size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) {
+ for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
+ // If we already have this device, and it's already connected, don't do
+ // anything now.
+ if (data_.items[slot].connected &&
+ !associated_[slot].is_xbox &&
+ associated_[slot].hid.device_ref == device)
+ return WebGamepads::itemsLengthCap;
+ }
+ return GetEmptySlot();
+}
+
+size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice(
+ XboxController* device) {
+ for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
+ if (associated_[slot].is_xbox &&
+ associated_[slot].xbox.location_id == device->location_id()) {
+ if (data_.items[slot].connected) {
+ // The device is already connected. No idea why we got a second "device
+ // added" call, but let's not add it twice.
+ DCHECK_EQ(associated_[slot].xbox.device, device);
+ return WebGamepads::itemsLengthCap;
+ } else {
+ // A device with the same location ID was previously connected, so put
+ // it in the same slot.
+ return slot;
+ }
+ }
+ }
+ return GetEmptySlot();
+}
+
+void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
+ using base::mac::CFToNSCast;
+ using base::mac::CFCastStrict;
+
+ if (!enabled_)
+ return;
+
+ // Find an index for this device.
+ size_t slot = GetSlotForDevice(device);
+
+ // We can't handle this many connected devices.
+ if (slot == WebGamepads::itemsLengthCap)
+ return;
+
+ NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
+ IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
+ NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
+ IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
+ NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
+ IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
+ int vendor_int = [vendor_id intValue];
+ int product_int = [product_id intValue];
+
+ char vendor_as_str[5], product_as_str[5];
+ snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
+ snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
+ associated_[slot].hid.mapper =
+ GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
+
+ NSString* ident = [NSString stringWithFormat:
+ @"%@ (%sVendor: %04x Product: %04x)",
+ product,
+ associated_[slot].hid.mapper ? "STANDARD GAMEPAD " : "",
+ vendor_int,
+ product_int];
+ NSData* as16 = [ident dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
+
+ const size_t kOutputLengthBytes = sizeof(data_.items[slot].id);
+ memset(&data_.items[slot].id, 0, kOutputLengthBytes);
+ [as16 getBytes:data_.items[slot].id
+ length:kOutputLengthBytes - sizeof(WebKit::WebUChar)];
+
+ base::ScopedCFTypeRef<CFArrayRef> elements(
+ IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
+ AddButtonsAndAxes(CFToNSCast(elements), slot);
+
+ associated_[slot].hid.device_ref = device;
+ data_.items[slot].connected = true;
+ if (slot >= data_.length)
+ data_.length = slot + 1;
+}
+
+void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
+ if (!enabled_)
+ return;
+
+ // Find the index for this device.
+ size_t slot;
+ for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
+ if (data_.items[slot].connected &&
+ !associated_[slot].is_xbox &&
+ associated_[slot].hid.device_ref == device)
+ break;
+ }
+ DCHECK(slot < WebGamepads::itemsLengthCap);
+ // Leave associated device_ref so that it will be reconnected in the same
+ // location. Simply mark it as disconnected.
+ data_.items[slot].connected = false;
+}
+
+void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
+ if (!enabled_)
+ return;
+
+ IOHIDElementRef element = IOHIDValueGetElement(value);
+ IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
+ // Find device slot.
+ size_t slot;
+ for (slot = 0; slot < data_.length; ++slot) {
+ if (data_.items[slot].connected &&
+ !associated_[slot].is_xbox &&
+ associated_[slot].hid.device_ref == device)
+ break;
+ }
+ if (slot == data_.length)
+ return;
+
+ WebGamepad& pad = data_.items[slot];
+ AssociatedData& associated = associated_[slot];
+
+ // Find and fill in the associated button event, if any.
+ for (size_t i = 0; i < pad.buttonsLength; ++i) {
+ if (associated.hid.button_elements[i] == element) {
+ pad.buttons[i] = IOHIDValueGetIntegerValue(value) ? 1.f : 0.f;
+ pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
+ return;
+ }
+ }
+
+ // Find and fill in the associated axis event, if any.
+ for (size_t i = 0; i < pad.axesLength; ++i) {
+ if (associated.hid.axis_elements[i] == element) {
+ pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value),
+ associated.hid.axis_minimums[i],
+ associated.hid.axis_maximums[i]);
+ pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
+ return;
+ }
+ }
+}
+
+void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) {
+ if (!enabled_)
+ return;
+
+ size_t slot = GetSlotForXboxDevice(device);
+
+ // We can't handle this many connected devices.
+ if (slot == WebGamepads::itemsLengthCap)
+ return;
+
+ device->SetLEDPattern(
+ (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot));
+
+ NSString* ident =
+ [NSString stringWithFormat:
+ @"Xbox 360 Controller (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
+ device->GetProductId(), device->GetVendorId()];
+ NSData* as16 = [ident dataUsingEncoding:NSUTF16StringEncoding];
+ const size_t kOutputLengthBytes = sizeof(data_.items[slot].id);
+ memset(&data_.items[slot].id, 0, kOutputLengthBytes);
+ [as16 getBytes:data_.items[slot].id
+ length:kOutputLengthBytes - sizeof(WebKit::WebUChar)];
+
+ associated_[slot].is_xbox = true;
+ associated_[slot].xbox.device = device;
+ associated_[slot].xbox.location_id = device->location_id();
+ data_.items[slot].connected = true;
+ data_.items[slot].axesLength = 4;
+ data_.items[slot].buttonsLength = 17;
+ data_.items[slot].timestamp = 0;
+ if (slot >= data_.length)
+ data_.length = slot + 1;
+}
+
+void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) {
+ if (!enabled_)
+ return;
+
+ // Find the index for this device.
+ size_t slot;
+ for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
+ if (data_.items[slot].connected &&
+ associated_[slot].is_xbox &&
+ associated_[slot].xbox.device == device)
+ break;
+ }
+ DCHECK(slot < WebGamepads::itemsLengthCap);
+ // Leave associated location id so that the controller will be reconnected in
+ // the same slot if it is plugged in again. Simply mark it as disconnected.
+ data_.items[slot].connected = false;
+}
+
+void GamepadPlatformDataFetcherMac::XboxValueChanged(
+ XboxController* device, const XboxController::Data& data) {
+ // Find device slot.
+ size_t slot;
+ for (slot = 0; slot < data_.length; ++slot) {
+ if (data_.items[slot].connected &&
+ associated_[slot].is_xbox &&
+ associated_[slot].xbox.device == device)
+ break;
+ }
+ if (slot == data_.length)
+ return;
+
+ WebGamepad& pad = data_.items[slot];
+
+ for (size_t i = 0; i < 6; i++) {
+ pad.buttons[i] = data.buttons[i] ? 1.0f : 0.0f;
+ }
+ pad.buttons[6] = data.triggers[0];
+ pad.buttons[7] = data.triggers[1];
+ for (size_t i = 8; i < 17; i++) {
+ pad.buttons[i] = data.buttons[i - 2] ? 1.0f : 0.0f;
+ }
+ for (size_t i = 0; i < arraysize(data.axes); i++) {
+ pad.axes[i] = data.axes[i];
+ }
+
+ pad.timestamp = base::TimeTicks::Now().ToInternalValue();
+}
+
+void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) {
+ if (!enabled_ && !xbox_fetcher_) {
+ pads->length = 0;
+ return;
+ }
+
+ // Copy to the current state to the output buffer, using the mapping
+ // function, if there is one available.
+ pads->length = WebGamepads::itemsLengthCap;
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (!associated_[i].is_xbox && associated_[i].hid.mapper)
+ associated_[i].hid.mapper(data_.items[i], &pads->items[i]);
+ else
+ pads->items[i] = data_.items[i];
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc
new file mode 100644
index 00000000000..d59ac180aa8
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc
@@ -0,0 +1,498 @@
+// 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 "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
+
+#include <dinput.h>
+#include <dinputd.h>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/stringprintf.h"
+#include "base/win/windows_version.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "content/common/gamepad_messages.h"
+
+// This was removed from the Windows 8 SDK for some reason.
+// We need it so we can get state for axes without worrying if they
+// exist.
+#ifndef DIDFT_OPTIONAL
+#define DIDFT_OPTIONAL 0x80000000
+#endif
+
+namespace content {
+
+using namespace WebKit;
+
+namespace {
+
+// See http://goo.gl/5VSJR. These are not available in all versions of the
+// header, but they can be returned from the driver, so we define our own
+// versions here.
+static const BYTE kDeviceSubTypeGamepad = 1;
+static const BYTE kDeviceSubTypeWheel = 2;
+static const BYTE kDeviceSubTypeArcadeStick = 3;
+static const BYTE kDeviceSubTypeFlightStick = 4;
+static const BYTE kDeviceSubTypeDancePad = 5;
+static const BYTE kDeviceSubTypeGuitar = 6;
+static const BYTE kDeviceSubTypeGuitarAlternate = 7;
+static const BYTE kDeviceSubTypeDrumKit = 8;
+static const BYTE kDeviceSubTypeGuitarBass = 11;
+static const BYTE kDeviceSubTypeArcadePad = 19;
+
+float NormalizeXInputAxis(SHORT value) {
+ return ((value + 32768.f) / 32767.5f) - 1.f;
+}
+
+const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
+ switch (sub_type) {
+ case kDeviceSubTypeGamepad: return L"GAMEPAD";
+ case kDeviceSubTypeWheel: return L"WHEEL";
+ case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
+ case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
+ case kDeviceSubTypeDancePad: return L"DANCE_PAD";
+ case kDeviceSubTypeGuitar: return L"GUITAR";
+ case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
+ case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
+ case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
+ case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
+ default: return L"<UNKNOWN>";
+ }
+}
+
+bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad,
+ std::string* vendor,
+ std::string* product) {
+ DIPROPDWORD prop;
+ prop.diph.dwSize = sizeof(DIPROPDWORD);
+ prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
+ prop.diph.dwObj = 0;
+ prop.diph.dwHow = DIPH_DEVICE;
+
+ if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph)))
+ return false;
+ *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData));
+ *product = base::StringPrintf("%04x", HIWORD(prop.dwData));
+ return true;
+}
+
+// Sets the deadzone value for all axes of a gamepad.
+// deadzone values range from 0 (no deadzone) to 10,000 (entire range
+// is dead).
+bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad,
+ int deadzone) {
+ DIPROPDWORD prop;
+ prop.diph.dwSize = sizeof(DIPROPDWORD);
+ prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
+ prop.diph.dwObj = 0;
+ prop.diph.dwHow = DIPH_DEVICE;
+ prop.dwData = deadzone;
+ return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph));
+}
+
+struct InternalDirectInputDevice {
+ IDirectInputDevice8* gamepad;
+ GamepadStandardMappingFunction mapper;
+ wchar_t id[WebGamepad::idLengthCap];
+ GUID guid;
+};
+
+struct EnumDevicesContext {
+ IDirectInput8* directinput_interface;
+ std::vector<InternalDirectInputDevice>* directinput_devices;
+};
+
+// We define our own data format structure to attempt to get as many
+// axes as possible.
+struct JoyData {
+ long axes[10];
+ char buttons[24];
+ DWORD pov; // Often used for D-pads.
+};
+
+BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance,
+ void* context) {
+ EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context);
+ IDirectInputDevice8* gamepad;
+
+ if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance,
+ &gamepad,
+ NULL)))
+ return DIENUM_CONTINUE;
+
+ gamepad->Acquire();
+
+#define MAKE_AXIS(i) \
+ {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \
+ DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
+#define MAKE_BUTTON(i) \
+ {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \
+ DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
+#define MAKE_POV() \
+ {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0}
+ DIOBJECTDATAFORMAT rgodf[] = {
+ MAKE_AXIS(0),
+ MAKE_AXIS(1),
+ MAKE_AXIS(2),
+ MAKE_AXIS(3),
+ MAKE_AXIS(4),
+ MAKE_AXIS(5),
+ MAKE_AXIS(6),
+ MAKE_AXIS(7),
+ MAKE_AXIS(8),
+ MAKE_AXIS(9),
+ MAKE_BUTTON(0),
+ MAKE_BUTTON(1),
+ MAKE_BUTTON(2),
+ MAKE_BUTTON(3),
+ MAKE_BUTTON(4),
+ MAKE_BUTTON(5),
+ MAKE_BUTTON(6),
+ MAKE_BUTTON(7),
+ MAKE_BUTTON(8),
+ MAKE_BUTTON(9),
+ MAKE_BUTTON(10),
+ MAKE_BUTTON(11),
+ MAKE_BUTTON(12),
+ MAKE_BUTTON(13),
+ MAKE_BUTTON(14),
+ MAKE_BUTTON(15),
+ MAKE_BUTTON(16),
+ MAKE_POV(),
+ };
+#undef MAKE_AXIS
+#undef MAKE_BUTTON
+#undef MAKE_POV
+
+ DIDATAFORMAT df = {
+ sizeof (DIDATAFORMAT),
+ sizeof (DIOBJECTDATAFORMAT),
+ DIDF_ABSAXIS,
+ sizeof (JoyData),
+ sizeof (rgodf) / sizeof (rgodf[0]),
+ rgodf
+ };
+
+ // If we can't set the data format on the device, don't add it to our
+ // list, since we won't know how to read data from it.
+ if (FAILED(gamepad->SetDataFormat(&df))) {
+ gamepad->Release();
+ return DIENUM_CONTINUE;
+ }
+
+ InternalDirectInputDevice device;
+ device.guid = instance->guidInstance;
+ device.gamepad = gamepad;
+ std::string vendor;
+ std::string product;
+ if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) {
+ gamepad->Release();
+ return DIENUM_CONTINUE;
+ }
+
+ // Set the dead zone to 10% of the axis length for all axes. This
+ // gives us a larger space for what's "neutral" so the controls don't
+ // slowly drift.
+ SetDirectInputDeadZone(gamepad, 1000);
+ device.mapper = GetGamepadStandardMappingFunction(vendor, product);
+ if (device.mapper) {
+ base::swprintf(device.id,
+ WebGamepad::idLengthCap,
+ L"STANDARD GAMEPAD (%ls)",
+ instance->tszProductName);
+ ctxt->directinput_devices->push_back(device);
+ } else {
+ gamepad->Release();
+ }
+ return DIENUM_CONTINUE;
+}
+
+} // namespace
+
+GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin()
+ : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))),
+ xinput_available_(GetXInputDllFunctions()) {
+ // TODO(teravest): http://crbug.com/260187
+ if (base::win::GetVersion() > base::win::VERSION_XP) {
+ directinput_available_ = SUCCEEDED(DirectInput8Create(
+ GetModuleHandle(NULL),
+ DIRECTINPUT_VERSION,
+ IID_IDirectInput8,
+ reinterpret_cast<void**>(&directinput_interface_),
+ NULL));
+ } else {
+ directinput_available_ = false;
+ }
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
+ pad_state_[i].status = DISCONNECTED;
+}
+
+GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() {
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
+ pad_state_[i].directinput_gamepad->Release();
+ }
+}
+
+int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (pad_state_[i].status == DISCONNECTED)
+ return i;
+ }
+ return -1;
+}
+
+bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const {
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (pad_state_[i].status == XINPUT_CONNECTED &&
+ pad_state_[i].xinput_index == index)
+ return true;
+ }
+ return false;
+}
+
+bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad(
+ const GUID& guid) const {
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (pad_state_[i].status == DIRECTINPUT_CONNECTED &&
+ pad_state_[i].guid == guid)
+ return true;
+ }
+ return false;
+}
+
+void GamepadPlatformDataFetcherWin::EnumerateDevices(
+ WebGamepads* pads) {
+ TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
+
+ // Mark all disconnected pads DISCONNECTED.
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ if (!pads->items[i].connected)
+ pad_state_[i].status = DISCONNECTED;
+ }
+
+ for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
+ if (HasXInputGamepad(i))
+ continue;
+ int pad_index = FirstAvailableGamepadId();
+ if (pad_index == -1)
+ return; // We can't add any more gamepads.
+ WebGamepad& pad = pads->items[pad_index];
+ if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) {
+ pad_state_[pad_index].status = XINPUT_CONNECTED;
+ pad_state_[pad_index].xinput_index = i;
+ }
+ }
+
+ if (directinput_available_) {
+ struct EnumDevicesContext context;
+ std::vector<InternalDirectInputDevice> directinput_gamepads;
+ context.directinput_interface = directinput_interface_;
+ context.directinput_devices = &directinput_gamepads;
+
+ directinput_interface_->EnumDevices(
+ DI8DEVCLASS_GAMECTRL,
+ &DirectInputEnumDevicesCallback,
+ &context,
+ DIEDFL_ATTACHEDONLY);
+ for (size_t i = 0; i < directinput_gamepads.size(); ++i) {
+ if (HasDirectInputGamepad(directinput_gamepads[i].guid)) {
+ directinput_gamepads[i].gamepad->Release();
+ continue;
+ }
+ int pad_index = FirstAvailableGamepadId();
+ if (pad_index == -1)
+ return;
+ WebGamepad& pad = pads->items[pad_index];
+ pad.connected = true;
+ wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id);
+ PadState& state = pad_state_[pad_index];
+ state.status = DIRECTINPUT_CONNECTED;
+ state.guid = directinput_gamepads[i].guid;
+ state.directinput_gamepad = directinput_gamepads[i].gamepad;
+ state.mapper = directinput_gamepads[i].mapper;
+ }
+ }
+}
+
+
+void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
+ bool devices_changed_hint) {
+ TRACE_EVENT0("GAMEPAD", "GetGamepadData");
+
+ if (!xinput_available_ && !directinput_available_) {
+ pads->length = 0;
+ return;
+ }
+
+ // A note on XInput devices:
+ // If we got notification that system devices have been updated, then
+ // run GetCapabilities to update the connected status and the device
+ // identifier. It can be slow to do to both GetCapabilities and
+ // GetState on unconnected devices, so we want to avoid a 2-5ms pause
+ // here by only doing this when the devices are updated (despite
+ // documentation claiming it's OK to call it any time).
+ if (devices_changed_hint)
+ EnumerateDevices(pads);
+
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
+ WebGamepad& pad = pads->items[i];
+ if (pad_state_[i].status == XINPUT_CONNECTED)
+ GetXInputPadData(i, &pad);
+ else if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
+ GetDirectInputPadData(i, &pad);
+ }
+ pads->length = WebGamepads::itemsLengthCap;
+}
+
+bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
+ int i,
+ WebGamepad* pad) const {
+ DCHECK(pad);
+ TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i);
+ XINPUT_CAPABILITIES caps;
+ DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps);
+ if (res == ERROR_DEVICE_NOT_CONNECTED) {
+ pad->connected = false;
+ return false;
+ } else {
+ pad->connected = true;
+ base::swprintf(pad->id,
+ WebGamepad::idLengthCap,
+ L"Xbox 360 Controller (XInput STANDARD %ls)",
+ GamepadSubTypeName(caps.SubType));
+ return true;
+ }
+}
+
+void GamepadPlatformDataFetcherWin::GetXInputPadData(
+ int i,
+ WebGamepad* pad) {
+ // We rely on device_changed and GetCapabilities to tell us that
+ // something's been connected, but we will mark as disconnected if
+ // GetState returns that we've lost the pad.
+ if (!pad->connected)
+ return;
+
+ XINPUT_STATE state;
+ memset(&state, 0, sizeof(XINPUT_STATE));
+ TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
+ DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state);
+ TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
+
+ if (dwResult == ERROR_SUCCESS) {
+ pad->timestamp = state.dwPacketNumber;
+ pad->buttonsLength = 0;
+#define ADD(b) pad->buttons[pad->buttonsLength++] = \
+ ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0);
+ ADD(XINPUT_GAMEPAD_A);
+ ADD(XINPUT_GAMEPAD_B);
+ ADD(XINPUT_GAMEPAD_X);
+ ADD(XINPUT_GAMEPAD_Y);
+ ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
+ ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
+ pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0;
+ pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0;
+ ADD(XINPUT_GAMEPAD_BACK);
+ ADD(XINPUT_GAMEPAD_START);
+ ADD(XINPUT_GAMEPAD_LEFT_THUMB);
+ ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
+ ADD(XINPUT_GAMEPAD_DPAD_UP);
+ ADD(XINPUT_GAMEPAD_DPAD_DOWN);
+ ADD(XINPUT_GAMEPAD_DPAD_LEFT);
+ ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
+#undef ADD
+ pad->axesLength = 0;
+ // XInput are +up/+right, -down/-left, we want -up/-left.
+ pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX);
+ pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY);
+ pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX);
+ pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY);
+ } else {
+ pad->connected = false;
+ }
+}
+
+void GamepadPlatformDataFetcherWin::GetDirectInputPadData(
+ int index,
+ WebGamepad* pad) {
+ if (!pad->connected)
+ return;
+
+ IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad;
+ if (FAILED(gamepad->Poll())) {
+ // Polling didn't work, try acquiring the gamepad.
+ if (FAILED(gamepad->Acquire())) {
+ pad->buttonsLength = 0;
+ pad->axesLength = 0;
+ return;
+ }
+ // Try polling again.
+ if (FAILED(gamepad->Poll())) {
+ pad->buttonsLength = 0;
+ pad->axesLength = 0;
+ return;
+ }
+ }
+ JoyData state;
+ if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) {
+ pad->connected = false;
+ return;
+ }
+
+ WebGamepad raw;
+ raw.connected = true;
+ for (int i = 0; i < 16; i++)
+ raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0;
+
+ // We map the POV (often a D-pad) into the buttons 16-19.
+ // DirectInput gives pov measurements in hundredths of degrees,
+ // clockwise from "North".
+ // We use 22.5 degree slices so we can handle diagonal D-raw presses.
+ static const int arc_segment = 2250; // 22.5 degrees = 1/16 circle
+ if (state.pov > arc_segment && state.pov < 7 * arc_segment)
+ raw.buttons[19] = 1.0;
+ else
+ raw.buttons[19] = 0.0;
+
+ if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment)
+ raw.buttons[17] = 1.0;
+ else
+ raw.buttons[17] = 0.0;
+
+ if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment)
+ raw.buttons[18] = 1.0;
+ else
+ raw.buttons[18] = 0.0;
+
+ if (state.pov < 3 * arc_segment ||
+ (state.pov > 13 * arc_segment && state.pov < 36000))
+ raw.buttons[16] = 1.0;
+ else
+ raw.buttons[16] = 0.0;
+
+ for (int i = 0; i < 10; i++)
+ raw.axes[i] = state.axes[i];
+ pad_state_[index].mapper(raw, pad);
+}
+
+bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() {
+ xinput_get_capabilities_ = NULL;
+ xinput_get_state_ = NULL;
+ xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
+ xinput_dll_.GetFunctionPointer("XInputEnable"));
+ if (!xinput_enable_)
+ return false;
+ xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
+ xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
+ if (!xinput_get_capabilities_)
+ return false;
+ xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
+ xinput_dll_.GetFunctionPointer("XInputGetState"));
+ if (!xinput_get_state_)
+ return false;
+ xinput_enable_(true);
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h
new file mode 100644
index 00000000000..7551ee91c4c
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h
@@ -0,0 +1,100 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_
+
+#include "build/build_config.h"
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#define DIRECTINPUT_VERSION 0x0800
+#include <dinput.h>
+#include <stdlib.h>
+#include <Unknwn.h>
+#include <WinDef.h>
+#include <windows.h>
+#include <XInput.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/scoped_native_library.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_standard_mappings.h"
+#include "third_party/WebKit/public/platform/WebGamepads.h"
+
+namespace content {
+
+class GamepadPlatformDataFetcherWin : public GamepadDataFetcher {
+ public:
+ GamepadPlatformDataFetcherWin();
+ virtual ~GamepadPlatformDataFetcherWin();
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) OVERRIDE;
+ private:
+ // XInput-specific implementation for GetGamepadData.
+ bool GetXInputGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint);
+ bool GetDirectInputGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint);
+
+ // The three function types we use from xinput1_3.dll.
+ typedef void (WINAPI *XInputEnableFunc)(BOOL enable);
+ typedef DWORD (WINAPI *XInputGetCapabilitiesFunc)(
+ DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES* pCapabilities);
+ typedef DWORD (WINAPI *XInputGetStateFunc)(
+ DWORD dwUserIndex, XINPUT_STATE* pState);
+
+ // Get functions from dynamically loaded xinput1_3.dll. We don't use
+ // DELAYLOAD because the import library for Win8 SDK pulls xinput1_4 which
+ // isn't redistributable. Returns true if loading was successful. We include
+ // xinput1_3.dll with Chrome.
+ bool GetXInputDllFunctions();
+
+ // Scan for connected XInput and DirectInput gamepads.
+ void EnumerateDevices(WebKit::WebGamepads* pads);
+ bool GetXInputPadConnectivity(int i, WebKit::WebGamepad* pad) const;
+
+ void GetXInputPadData(int i, WebKit::WebGamepad* pad);
+ void GetDirectInputPadData(int i, WebKit::WebGamepad* pad);
+
+ int FirstAvailableGamepadId() const;
+ bool HasXInputGamepad(int index) const;
+ bool HasDirectInputGamepad(const GUID &guid) const;
+
+ base::ScopedNativeLibrary xinput_dll_;
+ bool xinput_available_;
+ bool directinput_available_;
+
+ // Function pointers to XInput functionality, retrieved in
+ // |GetXinputDllFunctions|.
+ XInputEnableFunc xinput_enable_;
+ XInputGetCapabilitiesFunc xinput_get_capabilities_;
+ XInputGetStateFunc xinput_get_state_;
+
+ IDirectInput8* directinput_interface_;
+
+ enum PadConnectionStatus {
+ DISCONNECTED,
+ XINPUT_CONNECTED,
+ DIRECTINPUT_CONNECTED
+ };
+
+ struct PadState {
+ PadConnectionStatus status;
+ int xinput_index; // XInput-only.
+ // Fields below are for DirectInput devices only.
+ GUID guid;
+ IDirectInputDevice8* directinput_gamepad;
+ GamepadStandardMappingFunction mapper;
+ };
+ PadState pad_state_[WebKit::WebGamepads::itemsLengthCap];
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_
diff --git a/chromium/content/browser/gamepad/gamepad_provider.cc b/chromium/content/browser/gamepad/gamepad_provider.cc
new file mode 100644
index 00000000000..7e8e6dee132
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_provider.cc
@@ -0,0 +1,220 @@
+// 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 <cmath>
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_platform_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_provider.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "content/common/gamepad_messages.h"
+#include "content/common/gamepad_user_gesture.h"
+
+namespace content {
+
+GamepadProvider::ClosureAndThread::ClosureAndThread(
+ const base::Closure& c,
+ const scoped_refptr<base::MessageLoopProxy>& m)
+ : closure(c),
+ message_loop(m) {
+}
+
+GamepadProvider::ClosureAndThread::~ClosureAndThread() {
+}
+
+GamepadProvider::GamepadProvider()
+ : is_paused_(true),
+ have_scheduled_do_poll_(false),
+ devices_changed_(true) {
+ Initialize(scoped_ptr<GamepadDataFetcher>());
+}
+
+GamepadProvider::GamepadProvider(scoped_ptr<GamepadDataFetcher> fetcher)
+ : is_paused_(true),
+ have_scheduled_do_poll_(false),
+ devices_changed_(true) {
+ Initialize(fetcher.Pass());
+}
+
+GamepadProvider::~GamepadProvider() {
+ base::SystemMonitor* monitor = base::SystemMonitor::Get();
+ if (monitor)
+ monitor->RemoveDevicesChangedObserver(this);
+
+ // Use Stop() to join the polling thread, as there may be pending callbacks
+ // which dereference |polling_thread_|.
+ polling_thread_->Stop();
+ data_fetcher_.reset();
+}
+
+base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess(
+ base::ProcessHandle process) {
+ base::SharedMemoryHandle renderer_handle;
+ gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
+ return renderer_handle;
+}
+
+void GamepadProvider::Pause() {
+ {
+ base::AutoLock lock(is_paused_lock_);
+ is_paused_ = true;
+ }
+ base::MessageLoop* polling_loop = polling_thread_->message_loop();
+ polling_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true));
+}
+
+void GamepadProvider::Resume() {
+ {
+ base::AutoLock lock(is_paused_lock_);
+ if (!is_paused_)
+ return;
+ is_paused_ = false;
+ }
+
+ base::MessageLoop* polling_loop = polling_thread_->message_loop();
+ polling_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false));
+ polling_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this)));
+}
+
+void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) {
+ base::AutoLock lock(user_gesture_lock_);
+ user_gesture_observers_.push_back(ClosureAndThread(
+ closure, base::MessageLoop::current()->message_loop_proxy()));
+}
+
+void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) {
+ base::AutoLock lock(devices_changed_lock_);
+ devices_changed_ = true;
+}
+
+void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) {
+ size_t data_size = sizeof(GamepadHardwareBuffer);
+ base::SystemMonitor* monitor = base::SystemMonitor::Get();
+ if (monitor)
+ monitor->AddDevicesChangedObserver(this);
+ bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size);
+ CHECK(res);
+ GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
+ memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
+
+ polling_thread_.reset(new base::Thread("Gamepad polling thread"));
+#if defined(OS_MACOSX)
+ // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the
+ // message loop needs to be a UI-type loop.
+ const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI;
+#else
+ // On Linux, the data fetcher needs to watch file descriptors, so the message
+ // loop needs to be a libevent loop. On Windows it doesn't matter what the
+ // loop is.
+ const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO;
+#endif
+ polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0));
+
+ polling_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&GamepadProvider::DoInitializePollingThread,
+ base::Unretained(this),
+ base::Passed(&fetcher)));
+}
+
+void GamepadProvider::DoInitializePollingThread(
+ scoped_ptr<GamepadDataFetcher> fetcher) {
+ DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
+ DCHECK(!data_fetcher_.get()); // Should only initialize once.
+
+ if (!fetcher)
+ fetcher.reset(new GamepadPlatformDataFetcher);
+ data_fetcher_ = fetcher.Pass();
+}
+
+void GamepadProvider::SendPauseHint(bool paused) {
+ DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
+ if (data_fetcher_)
+ data_fetcher_->PauseHint(paused);
+}
+
+void GamepadProvider::DoPoll() {
+ DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
+ DCHECK(have_scheduled_do_poll_);
+ have_scheduled_do_poll_ = false;
+
+ bool changed;
+ GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
+
+ ANNOTATE_BENIGN_RACE_SIZED(
+ &hwbuf->buffer,
+ sizeof(WebKit::WebGamepads),
+ "Racey reads are discarded");
+
+ {
+ base::AutoLock lock(devices_changed_lock_);
+ changed = devices_changed_;
+ devices_changed_ = false;
+ }
+
+ // Acquire the SeqLock. There is only ever one writer to this data.
+ // See gamepad_hardware_buffer.h.
+ hwbuf->sequence.WriteBegin();
+ data_fetcher_->GetGamepadData(&hwbuf->buffer, changed);
+ hwbuf->sequence.WriteEnd();
+
+ CheckForUserGesture();
+
+ // Schedule our next interval of polling.
+ ScheduleDoPoll();
+}
+
+void GamepadProvider::ScheduleDoPoll() {
+ DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
+ if (have_scheduled_do_poll_)
+ return;
+
+ {
+ base::AutoLock lock(is_paused_lock_);
+ if (is_paused_)
+ return;
+ }
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&GamepadProvider::DoPoll, Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs));
+ have_scheduled_do_poll_ = true;
+}
+
+GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
+ void* mem = gamepad_shared_memory_.memory();
+ CHECK(mem);
+ return static_cast<GamepadHardwareBuffer*>(mem);
+}
+
+void GamepadProvider::CheckForUserGesture() {
+ base::AutoLock lock(user_gesture_lock_);
+ if (user_gesture_observers_.empty())
+ return; // Don't need to check if nobody is listening.
+
+ if (GamepadsHaveUserGesture(SharedMemoryAsHardwareBuffer()->buffer)) {
+ for (size_t i = 0; i < user_gesture_observers_.size(); i++) {
+ user_gesture_observers_[i].message_loop->PostTask(FROM_HERE,
+ user_gesture_observers_[i].closure);
+ }
+ user_gesture_observers_.clear();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_provider.h b/chromium/content/browser/gamepad/gamepad_provider.h
new file mode 100644
index 00000000000..5f10f137a14
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_provider.h
@@ -0,0 +1,130 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "base/system_monitor/system_monitor.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class MessageLoopProxy;
+class Thread;
+}
+
+namespace content {
+
+class GamepadDataFetcher;
+struct GamepadHardwareBuffer;
+
+class CONTENT_EXPORT GamepadProvider :
+ public base::SystemMonitor::DevicesChangedObserver {
+ public:
+ GamepadProvider();
+
+ // Manually specifies the data fetcher. Used for testing.
+ explicit GamepadProvider(scoped_ptr<GamepadDataFetcher> fetcher);
+
+ virtual ~GamepadProvider();
+
+ // Returns the shared memory handle of the gamepad data duplicated into the
+ // given process.
+ base::SharedMemoryHandle GetSharedMemoryHandleForProcess(
+ base::ProcessHandle renderer_process);
+
+ // Pause and resume the background polling thread. Can be called from any
+ // thread.
+ void Pause();
+ void Resume();
+
+ // Registers the given closure for calling when the user has interacted with
+ // the device. This callback will only be issued once.
+ void RegisterForUserGesture(const base::Closure& closure);
+
+ // base::SystemMonitor::DevicesChangedObserver implementation.
+ virtual void OnDevicesChanged(base::SystemMonitor::DeviceType type) OVERRIDE;
+
+ private:
+ void Initialize(scoped_ptr<GamepadDataFetcher> fetcher);
+
+ // Method for setting up the platform-specific data fetcher. Takes ownership
+ // of |fetcher|.
+ void DoInitializePollingThread(scoped_ptr<GamepadDataFetcher> fetcher);
+
+ // Method for sending pause hints to the low-level data fetcher. Runs on
+ // polling_thread_.
+ void SendPauseHint(bool paused);
+
+ // Method for polling a GamepadDataFetcher. Runs on the polling_thread_.
+ void DoPoll();
+ void ScheduleDoPoll();
+
+ GamepadHardwareBuffer* SharedMemoryAsHardwareBuffer();
+
+ // Checks the gamepad state to see if the user has interacted with it.
+ void CheckForUserGesture();
+
+ enum { kDesiredSamplingIntervalMs = 16 };
+
+ // Keeps track of when the background thread is paused. Access to is_paused_
+ // must be guarded by is_paused_lock_.
+ base::Lock is_paused_lock_;
+ bool is_paused_;
+
+ // Keep track of when a polling task is schedlued, so as to prevent us from
+ // accidentally scheduling more than one at any time, when rapidly toggling
+ // |is_paused_|.
+ bool have_scheduled_do_poll_;
+
+ // Lists all observers registered for user gestures, and the thread which
+ // to issue the callbacks on. Since we always issue the callback on the
+ // thread which the registration happened, and this class lives on the I/O
+ // thread, the message loop proxies will normally just be the I/O thread.
+ // However, this will be the main thread for unit testing.
+ base::Lock user_gesture_lock_;
+ struct ClosureAndThread {
+ ClosureAndThread(const base::Closure& c,
+ const scoped_refptr<base::MessageLoopProxy>& m);
+ ~ClosureAndThread();
+
+ base::Closure closure;
+ scoped_refptr<base::MessageLoopProxy> message_loop;
+ };
+ typedef std::vector<ClosureAndThread> UserGestureObserverVector;
+ UserGestureObserverVector user_gesture_observers_;
+
+ // Updated based on notification from SystemMonitor when the system devices
+ // have been updated, and this notification is passed on to the data fetcher
+ // to enable it to avoid redundant (and possibly expensive) is-connected
+ // tests. Access to devices_changed_ must be guarded by
+ // devices_changed_lock_.
+ base::Lock devices_changed_lock_;
+ bool devices_changed_;
+
+ // When polling_thread_ is running, members below are only to be used
+ // from that thread.
+ scoped_ptr<GamepadDataFetcher> data_fetcher_;
+ base::SharedMemory gamepad_shared_memory_;
+
+ // Polling is done on this background thread.
+ scoped_ptr<base::Thread> polling_thread_;
+
+ static GamepadProvider* instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadProvider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_
diff --git a/chromium/content/browser/gamepad/gamepad_provider_unittest.cc b/chromium/content/browser/gamepad/gamepad_provider_unittest.cc
new file mode 100644
index 00000000000..0e5785cc598
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_provider_unittest.cc
@@ -0,0 +1,158 @@
+// 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 "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_provider.h"
+#include "content/browser/gamepad/gamepad_test_helpers.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "content/common/gamepad_messages.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+using WebKit::WebGamepads;
+
+// Helper class to generate and record user gesture callbacks.
+class UserGestureListener {
+ public:
+ UserGestureListener()
+ : weak_factory_(this),
+ has_user_gesture_(false) {
+ }
+
+ base::Closure GetClosure() {
+ return base::Bind(&UserGestureListener::GotUserGesture,
+ weak_factory_.GetWeakPtr());
+ }
+
+ bool has_user_gesture() const { return has_user_gesture_; }
+
+ private:
+ void GotUserGesture() {
+ has_user_gesture_ = true;
+ }
+
+ base::WeakPtrFactory<UserGestureListener> weak_factory_;
+ bool has_user_gesture_;
+};
+
+// Main test fixture
+class GamepadProviderTest : public testing::Test, public GamepadTestHelper {
+ public:
+ GamepadProvider* CreateProvider(const WebGamepads& test_data) {
+ mock_data_fetcher_ = new MockGamepadDataFetcher(test_data);
+ provider_.reset(new GamepadProvider(
+ scoped_ptr<GamepadDataFetcher>(mock_data_fetcher_)));
+ return provider_.get();
+ }
+
+ protected:
+ GamepadProviderTest() {
+ }
+
+ scoped_ptr<GamepadProvider> provider_;
+
+ // Pointer owned by the provider.
+ MockGamepadDataFetcher* mock_data_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadProviderTest);
+};
+
+// Crashes. http://crbug.com/106163
+TEST_F(GamepadProviderTest, PollingAccess) {
+ WebGamepads test_data;
+ test_data.length = 1;
+ test_data.items[0].connected = true;
+ test_data.items[0].timestamp = 0;
+ test_data.items[0].buttonsLength = 1;
+ test_data.items[0].axesLength = 2;
+ test_data.items[0].buttons[0] = 1.f;
+ test_data.items[0].axes[0] = -1.f;
+ test_data.items[0].axes[1] = .5f;
+
+ GamepadProvider* provider = CreateProvider(test_data);
+ provider->Resume();
+
+ message_loop().RunUntilIdle();
+
+ mock_data_fetcher_->WaitForDataRead();
+
+ // Renderer-side, pull data out of poll buffer.
+ base::SharedMemoryHandle handle = provider->GetSharedMemoryHandleForProcess(
+ base::GetCurrentProcessHandle());
+ scoped_ptr<base::SharedMemory> shared_memory(
+ new base::SharedMemory(handle, true));
+ EXPECT_TRUE(shared_memory->Map(sizeof(GamepadHardwareBuffer)));
+ void* mem = shared_memory->memory();
+
+ GamepadHardwareBuffer* hwbuf = static_cast<GamepadHardwareBuffer*>(mem);
+ // See gamepad_hardware_buffer.h for details on the read discipline.
+ WebGamepads output;
+
+ base::subtle::Atomic32 version;
+ do {
+ version = hwbuf->sequence.ReadBegin();
+ memcpy(&output, &hwbuf->buffer, sizeof(output));
+ } while (hwbuf->sequence.ReadRetry(version));
+
+ EXPECT_EQ(1u, output.length);
+ EXPECT_EQ(1u, output.items[0].buttonsLength);
+ EXPECT_EQ(1.f, output.items[0].buttons[0]);
+ EXPECT_EQ(2u, output.items[0].axesLength);
+ EXPECT_EQ(-1.f, output.items[0].axes[0]);
+ EXPECT_EQ(0.5f, output.items[0].axes[1]);
+}
+
+// Tests that waiting for a user gesture works properly.
+TEST_F(GamepadProviderTest, UserGesture) {
+ WebGamepads no_button_data;
+ no_button_data.length = 1;
+ no_button_data.items[0].connected = true;
+ no_button_data.items[0].timestamp = 0;
+ no_button_data.items[0].buttonsLength = 1;
+ no_button_data.items[0].axesLength = 2;
+ no_button_data.items[0].buttons[0] = 0.f;
+ no_button_data.items[0].axes[0] = -1.f;
+ no_button_data.items[0].axes[1] = .5f;
+
+ WebGamepads button_down_data = no_button_data;
+ button_down_data.items[0].buttons[0] = 1.f;
+
+ UserGestureListener listener;
+ GamepadProvider* provider = CreateProvider(no_button_data);
+ provider->Resume();
+
+ // Register for a user gesture and make sure the provider reads it twice
+ // see below for why).
+ provider->RegisterForUserGesture(listener.GetClosure());
+ mock_data_fetcher_->WaitForDataRead();
+ mock_data_fetcher_->WaitForDataRead();
+
+ // It should not have issued our callback.
+ message_loop().RunUntilIdle();
+ EXPECT_FALSE(listener.has_user_gesture());
+
+ // Set a button down and wait for it to be read twice.
+ //
+ // We wait for two reads before calling RunAllPending because the provider
+ // will read the data on the background thread (setting the event) and *then*
+ // will issue the callback on our thread. Waiting for it to read twice
+ // ensures that it was able to issue callbacks for the first read (if it
+ // issued one) before we try to check for it.
+ mock_data_fetcher_->SetTestData(button_down_data);
+ mock_data_fetcher_->WaitForDataRead();
+ mock_data_fetcher_->WaitForDataRead();
+
+ // It should have issued our callback.
+ message_loop().RunUntilIdle();
+ EXPECT_TRUE(listener.has_user_gesture());
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_service.cc b/chromium/content/browser/gamepad/gamepad_service.cc
new file mode 100644
index 00000000000..11a6964b3ab
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_service.cc
@@ -0,0 +1,69 @@
+// 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 "content/browser/gamepad/gamepad_service.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "content/browser/gamepad/gamepad_provider.h"
+#include "content/public/browser/render_process_host.h"
+
+namespace content {
+
+GamepadService::GamepadService() : num_readers_(0) {
+}
+
+GamepadService::GamepadService(scoped_ptr<GamepadDataFetcher> fetcher)
+ : num_readers_(0),
+ provider_(new GamepadProvider(fetcher.Pass())) {
+ thread_checker_.DetachFromThread();
+}
+
+GamepadService::~GamepadService() {
+}
+
+GamepadService* GamepadService::GetInstance() {
+ return Singleton<GamepadService,
+ LeakySingletonTraits<GamepadService> >::get();
+}
+
+void GamepadService::AddConsumer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ num_readers_++;
+ DCHECK(num_readers_ > 0);
+ if (!provider_)
+ provider_.reset(new GamepadProvider);
+ provider_->Resume();
+}
+
+void GamepadService::RemoveConsumer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ --num_readers_;
+ DCHECK(num_readers_ >= 0);
+
+ if (num_readers_ == 0)
+ provider_->Pause();
+}
+
+void GamepadService::RegisterForUserGesture(const base::Closure& closure) {
+ DCHECK(num_readers_ > 0);
+ DCHECK(thread_checker_.CalledOnValidThread());
+ provider_->RegisterForUserGesture(closure);
+}
+
+void GamepadService::Terminate() {
+ provider_.reset();
+}
+
+base::SharedMemoryHandle GamepadService::GetSharedMemoryHandleForProcess(
+ base::ProcessHandle handle) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return provider_->GetSharedMemoryHandleForProcess(handle);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_service.h b/chromium/content/browser/gamepad/gamepad_service.h
new file mode 100644
index 00000000000..94620b34b60
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_service.h
@@ -0,0 +1,77 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/singleton.h"
+#include "base/threading/thread_checker.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class GamepadDataFetcher;
+class GamepadProvider;
+class GamepadServiceTestConstructor;
+class RenderProcessHost;
+
+// Owns the GamepadProvider (the background polling thread) and keeps track of
+// the number of consumers currently using the data (and pausing the provider
+// when not in use).
+class CONTENT_EXPORT GamepadService {
+ public:
+ // Returns the GamepadService singleton.
+ static GamepadService* GetInstance();
+
+ // Increments the number of users of the provider. The Provider is running
+ // when there's > 0 users, and is paused when the count drops to 0.
+ //
+ // Must be called on the I/O thread.
+ void AddConsumer();
+
+ // Removes a consumer. Should be matched with an AddConsumer call.
+ //
+ // Must be called on the I/O thread.
+ void RemoveConsumer();
+
+ // Registers the given closure for calling when the user has interacted with
+ // the device. This callback will only be issued once. Should only be called
+ // while a consumer is active.
+ void RegisterForUserGesture(const base::Closure& closure);
+
+ // Returns the shared memory handle of the gamepad data duplicated into the
+ // given process.
+ base::SharedMemoryHandle GetSharedMemoryHandleForProcess(
+ base::ProcessHandle handle);
+
+ // Stop/join with the background thread in GamepadProvider |provider_|.
+ void Terminate();
+
+ private:
+ friend struct DefaultSingletonTraits<GamepadService>;
+ friend class GamepadServiceTestConstructor;
+
+ GamepadService();
+
+ // Constructor for testing. This specifies the data fetcher to use for a
+ // provider, bypassing the default platform one.
+ GamepadService(scoped_ptr<GamepadDataFetcher> fetcher);
+
+ virtual ~GamepadService();
+
+ int num_readers_;
+ scoped_ptr<GamepadProvider> provider_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadService);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H_
diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings.h b/chromium/content/browser/gamepad/gamepad_standard_mappings.h
new file mode 100644
index 00000000000..6f5d72fc0a7
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_standard_mappings.h
@@ -0,0 +1,61 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_
+
+#include "base/strings/string_piece.h"
+
+namespace WebKit {
+class WebGamepad;
+}
+
+namespace content {
+
+typedef void (*GamepadStandardMappingFunction)(
+ const WebKit::WebGamepad& original,
+ WebKit::WebGamepad* mapped);
+
+GamepadStandardMappingFunction GetGamepadStandardMappingFunction(
+ const base::StringPiece& vendor_id,
+ const base::StringPiece& product_id);
+
+// This defines our canonical mapping order for gamepad-like devices. If these
+// items cannot all be satisfied, it is a case-by-case judgement as to whether
+// it is better to leave the device unmapped, or to partially map it. In
+// general, err towards leaving it *unmapped* so that content can handle
+// appropriately.
+
+enum CanonicalButtonIndex {
+ kButtonPrimary,
+ kButtonSecondary,
+ kButtonTertiary,
+ kButtonQuaternary,
+ kButtonLeftShoulder,
+ kButtonRightShoulder,
+ kButtonLeftTrigger,
+ kButtonRightTrigger,
+ kButtonBackSelect,
+ kButtonStart,
+ kButtonLeftThumbstick,
+ kButtonRightThumbstick,
+ kButtonDpadUp,
+ kButtonDpadDown,
+ kButtonDpadLeft,
+ kButtonDpadRight,
+ kButtonMeta,
+ kNumButtons
+};
+
+enum CanonicalAxisIndex {
+ kAxisLeftStickX,
+ kAxisLeftStickY,
+ kAxisRightStickX,
+ kAxisRightStickY,
+ kNumAxes
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_
diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc b/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc
new file mode 100644
index 00000000000..e9e7094c532
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc
@@ -0,0 +1,164 @@
+// 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 "content/browser/gamepad/gamepad_standard_mappings.h"
+
+#include "content/common/gamepad_hardware_buffer.h"
+
+namespace content {
+
+namespace {
+
+float AxisToButton(float input) {
+ return (input + 1.f) / 2.f;
+}
+
+float AxisNegativeAsButton(float input) {
+ return (input < -0.5f) ? 1.f : 0.f;
+}
+
+float AxisPositiveAsButton(float input) {
+ return (input > 0.5f) ? 1.f : 0.f;
+}
+
+void MapperXInputStyleGamepad(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[2]);
+ mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[5]);
+ mapped->buttons[kButtonBackSelect] = input.buttons[6];
+ mapped->buttons[kButtonStart] = input.buttons[7];
+ mapped->buttons[kButtonLeftThumbstick] = input.buttons[9];
+ mapped->buttons[kButtonRightThumbstick] = input.buttons[10];
+ mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[7]);
+ mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[7]);
+ mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[6]);
+ mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[6]);
+ mapped->buttons[kButtonMeta] = input.buttons[8];
+ mapped->axes[kAxisRightStickX] = input.axes[3];
+ mapped->axes[kAxisRightStickY] = input.axes[4];
+ mapped->buttonsLength = kNumButtons;
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperLakeviewResearch(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[2];
+ mapped->buttons[kButtonTertiary] = input.buttons[3];
+ mapped->buttons[kButtonQuaternary] = input.buttons[0];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[6];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[7];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[4];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[5];
+ mapped->buttons[kButtonBackSelect] = input.buttons[9];
+ mapped->buttons[kButtonStart] = input.buttons[8];
+ mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[5]);
+ mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[5]);
+ mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[4]);
+ mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[4]);
+ mapped->buttonsLength = kNumButtons - 1; // no Meta on this device
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperPlaystationSixAxis(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[14];
+ mapped->buttons[kButtonSecondary] = input.buttons[13];
+ mapped->buttons[kButtonTertiary] = input.buttons[15];
+ mapped->buttons[kButtonQuaternary] = input.buttons[12];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[10];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[11];
+ mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[12]);
+ mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[13]);
+ mapped->buttons[kButtonBackSelect] = input.buttons[0];
+ mapped->buttons[kButtonStart] = input.buttons[3];
+ mapped->buttons[kButtonLeftThumbstick] = input.buttons[1];
+ mapped->buttons[kButtonRightThumbstick] = input.buttons[2];
+ mapped->buttons[kButtonDpadUp] = AxisToButton(input.axes[8]);
+ mapped->buttons[kButtonDpadDown] = AxisToButton(input.axes[10]);
+ mapped->buttons[kButtonDpadLeft] = input.buttons[7];
+ mapped->buttons[kButtonDpadRight] = AxisToButton(input.axes[9]);
+ mapped->buttons[kButtonMeta] = input.buttons[16];
+
+ mapped->buttonsLength = kNumButtons;
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperXGEAR(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[2];
+ mapped->buttons[kButtonSecondary] = input.buttons[1];
+ mapped->buttons[kButtonTertiary] = input.buttons[3];
+ mapped->buttons[kButtonQuaternary] = input.buttons[0];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[6];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[7];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[4];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[5];
+ mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[5]);
+ mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[5]);
+ mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[4]);
+ mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[4]);
+ mapped->axes[kAxisRightStickX] = input.axes[3];
+ mapped->axes[kAxisRightStickY] = input.axes[2];
+ mapped->buttonsLength = kNumButtons - 1; // no Meta on this device
+ mapped->axesLength = kNumAxes;
+}
+
+
+void MapperDragonRiseGeneric(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[6]);
+ mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[6]);
+ mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[5]);
+ mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[5]);
+ mapped->axes[kAxisLeftStickX] = input.axes[0];
+ mapped->axes[kAxisLeftStickY] = input.axes[1];
+ mapped->axes[kAxisRightStickX] = input.axes[3];
+ mapped->axes[kAxisRightStickY] = input.axes[4];
+ mapped->buttonsLength = kNumButtons - 1; // no Meta on this device
+ mapped->axesLength = kNumAxes;
+}
+
+
+struct MappingData {
+ const char* const vendor_id;
+ const char* const product_id;
+ GamepadStandardMappingFunction function;
+} AvailableMappings[] = {
+ // http://www.linux-usb.org/usb.ids
+ { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB
+ { "045e", "028e", MapperXInputStyleGamepad }, // Xbox 360 Controller
+ { "045e", "028f", MapperXInputStyleGamepad }, // Xbox 360 Wireless Controller
+ { "046d", "c21d", MapperXInputStyleGamepad }, // Logitech F310
+ { "046d", "c21e", MapperXInputStyleGamepad }, // Logitech F510
+ { "046d", "c21f", MapperXInputStyleGamepad }, // Logitech F710
+ { "054c", "0268", MapperPlaystationSixAxis }, // Playstation SIXAXIS
+ { "0925", "0005", MapperLakeviewResearch }, // SmartJoy PLUS Adapter
+ { "0925", "8866", MapperLakeviewResearch }, // WiseGroup MP-8866
+ { "0e8f", "0003", MapperXGEAR }, // XFXforce XGEAR PS2 Controller
+};
+
+} // namespace
+
+GamepadStandardMappingFunction GetGamepadStandardMappingFunction(
+ const base::StringPiece& vendor_id,
+ const base::StringPiece& product_id) {
+ for (size_t i = 0; i < arraysize(AvailableMappings); ++i) {
+ MappingData& item = AvailableMappings[i];
+ if (vendor_id == item.vendor_id && product_id == item.product_id)
+ return item.function;
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm b/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm
new file mode 100644
index 00000000000..0ffb35f8fec
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm
@@ -0,0 +1,219 @@
+// 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 "content/browser/gamepad/gamepad_standard_mappings.h"
+
+#include "content/common/gamepad_hardware_buffer.h"
+
+namespace content {
+
+namespace {
+
+float AxisToButton(float input) {
+ return (input + 1.f) / 2.f;
+}
+
+void DpadFromAxis(WebKit::WebGamepad* mapped, float dir) {
+ // Dpad is mapped as a direction on one axis, where -1 is up and it
+ // increases clockwise to 1, which is up + left. It's set to a large (> 1.f)
+ // number when nothing is depressed, except on start up, sometimes it's 0.0
+ // for no data, rather than the large number.
+ if (dir == 0.0f) {
+ mapped->buttons[kButtonDpadUp] = 0.f;
+ mapped->buttons[kButtonDpadDown] = 0.f;
+ mapped->buttons[kButtonDpadLeft] = 0.f;
+ mapped->buttons[kButtonDpadRight] = 0.f;
+ } else {
+ mapped->buttons[kButtonDpadUp] = (dir >= -1.f && dir < -0.7f) ||
+ (dir >= .95f && dir <= 1.f);
+ mapped->buttons[kButtonDpadRight] = dir >= -.75f && dir < -.1f;
+ mapped->buttons[kButtonDpadDown] = dir >= -.2f && dir < .45f;
+ mapped->buttons[kButtonDpadLeft] = dir >= .4f && dir <= 1.f;
+ }
+}
+
+void MapperXbox360Gamepad(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[2]);
+ mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[5]);
+ mapped->buttons[kButtonBackSelect] = input.buttons[9];
+ mapped->buttons[kButtonStart] = input.buttons[8];
+ mapped->buttons[kButtonLeftThumbstick] = input.buttons[6];
+ mapped->buttons[kButtonRightThumbstick] = input.buttons[7];
+ mapped->buttons[kButtonDpadUp] = input.buttons[11];
+ mapped->buttons[kButtonDpadDown] = input.buttons[12];
+ mapped->buttons[kButtonDpadLeft] = input.buttons[13];
+ mapped->buttons[kButtonDpadRight] = input.buttons[14];
+ mapped->buttons[kButtonMeta] = input.buttons[10];
+ mapped->axes[kAxisRightStickX] = input.axes[3];
+ mapped->axes[kAxisRightStickY] = input.axes[4];
+ mapped->buttonsLength = kNumButtons;
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperPlaystationSixAxis(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[14];
+ mapped->buttons[kButtonSecondary] = input.buttons[13];
+ mapped->buttons[kButtonTertiary] = input.buttons[15];
+ mapped->buttons[kButtonQuaternary] = input.buttons[12];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[10];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[11];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[8];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[9];
+ mapped->buttons[kButtonBackSelect] = input.buttons[0];
+ mapped->buttons[kButtonStart] = input.buttons[3];
+ mapped->buttons[kButtonLeftThumbstick] = input.buttons[1];
+ mapped->buttons[kButtonRightThumbstick] = input.buttons[2];
+ mapped->buttons[kButtonDpadUp] = input.buttons[4];
+ mapped->buttons[kButtonDpadDown] = input.buttons[6];
+ mapped->buttons[kButtonDpadLeft] = input.buttons[7];
+ mapped->buttons[kButtonDpadRight] = input.buttons[5];
+ mapped->buttons[kButtonMeta] = input.buttons[16];
+ mapped->axes[kAxisRightStickY] = input.axes[5];
+
+ mapped->buttonsLength = kNumButtons;
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperDirectInputStyle(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[1];
+ mapped->buttons[kButtonSecondary] = input.buttons[2];
+ mapped->buttons[kButtonTertiary] = input.buttons[0];
+ mapped->axes[kAxisRightStickY] = input.axes[5];
+ DpadFromAxis(mapped, input.axes[9]);
+ mapped->buttonsLength = kNumButtons - 1; /* no meta */
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperMacallyIShock(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ enum IShockButtons {
+ kButtonC = kNumButtons,
+ kButtonD,
+ kButtonE,
+ kNumIShockButtons
+ };
+
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[6];
+ mapped->buttons[kButtonSecondary] = input.buttons[5];
+ mapped->buttons[kButtonTertiary] = input.buttons[7];
+ mapped->buttons[kButtonQuaternary] = input.buttons[4];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[14];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[12];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[15];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[13];
+ mapped->buttons[kButtonBackSelect] = input.buttons[9];
+ mapped->buttons[kButtonStart] = input.buttons[10];
+ mapped->buttons[kButtonLeftThumbstick] = input.buttons[16];
+ mapped->buttons[kButtonRightThumbstick] = input.buttons[17];
+ mapped->buttons[kButtonDpadUp] = input.buttons[0];
+ mapped->buttons[kButtonDpadDown] = input.buttons[1];
+ mapped->buttons[kButtonDpadLeft] = input.buttons[2];
+ mapped->buttons[kButtonDpadRight] = input.buttons[3];
+ mapped->buttons[kButtonMeta] = input.buttons[11];
+ mapped->buttons[kButtonC] = input.buttons[8];
+ mapped->buttons[kButtonD] = input.buttons[18];
+ mapped->buttons[kButtonE] = input.buttons[19];
+ mapped->axes[kAxisLeftStickX] = input.axes[0];
+ mapped->axes[kAxisLeftStickY] = input.axes[1];
+ mapped->axes[kAxisRightStickX] = -input.axes[5];
+ mapped->axes[kAxisRightStickY] = input.axes[6];
+
+ mapped->buttonsLength = kNumIShockButtons;
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperXGEAR(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[2];
+ mapped->buttons[kButtonTertiary] = input.buttons[3];
+ mapped->buttons[kButtonQuaternary] = input.buttons[0];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[6];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[7];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[4];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[5];
+ DpadFromAxis(mapped, input.axes[9]);
+ mapped->axes[kAxisRightStickX] = input.axes[5];
+ mapped->axes[kAxisRightStickY] = input.axes[2];
+ mapped->buttonsLength = kNumButtons - 1; /* no meta */
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperSmartJoyPLUS(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonPrimary] = input.buttons[2];
+ mapped->buttons[kButtonTertiary] = input.buttons[3];
+ mapped->buttons[kButtonQuaternary] = input.buttons[0];
+ mapped->buttons[kButtonStart] = input.buttons[8];
+ mapped->buttons[kButtonBackSelect] = input.buttons[9];
+ mapped->buttons[kButtonLeftShoulder] = input.buttons[6];
+ mapped->buttons[kButtonRightShoulder] = input.buttons[7];
+ mapped->buttons[kButtonLeftTrigger] = input.buttons[4];
+ mapped->buttons[kButtonRightTrigger] = input.buttons[5];
+ DpadFromAxis(mapped, input.axes[9]);
+ mapped->axes[kAxisRightStickY] = input.axes[5];
+ mapped->buttonsLength = kNumButtons - 1; /* no meta */
+ mapped->axesLength = kNumAxes;
+}
+
+void MapperDragonRiseGeneric(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ DpadFromAxis(mapped, input.axes[9]);
+ mapped->axes[kAxisLeftStickX] = input.axes[0];
+ mapped->axes[kAxisLeftStickY] = input.axes[1];
+ mapped->axes[kAxisRightStickX] = input.axes[2];
+ mapped->axes[kAxisRightStickY] = input.axes[5];
+ mapped->buttonsLength = kNumButtons - 1; /* no meta */
+ mapped->axesLength = kNumAxes;
+}
+
+struct MappingData {
+ const char* const vendor_id;
+ const char* const product_id;
+ GamepadStandardMappingFunction function;
+} AvailableMappings[] = {
+ // http://www.linux-usb.org/usb.ids
+ { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB
+ { "045e", "028e", MapperXbox360Gamepad }, // Xbox 360 Controller
+ { "045e", "028f", MapperXbox360Gamepad }, // Xbox 360 Wireless Controller
+ { "046d", "c216", MapperDirectInputStyle }, // Logitech F310, D mode
+ { "046d", "c218", MapperDirectInputStyle }, // Logitech F510, D mode
+ { "046d", "c219", MapperDirectInputStyle }, // Logitech F710, D mode
+ { "054c", "0268", MapperPlaystationSixAxis }, // Playstation SIXAXIS
+ { "0925", "0005", MapperSmartJoyPLUS }, // SmartJoy PLUS Adapter
+ { "0e8f", "0003", MapperXGEAR }, // XFXforce XGEAR PS2 Controller
+ { "2222", "0060", MapperDirectInputStyle }, // Macally iShockX, analog mode
+ { "2222", "4010", MapperMacallyIShock }, // Macally iShock
+};
+
+} // namespace
+
+GamepadStandardMappingFunction GetGamepadStandardMappingFunction(
+ const base::StringPiece& vendor_id,
+ const base::StringPiece& product_id) {
+ for (size_t i = 0; i < arraysize(AvailableMappings); ++i) {
+ MappingData& item = AvailableMappings[i];
+ if (vendor_id == item.vendor_id && product_id == item.product_id)
+ return item.function;
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc b/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc
new file mode 100644
index 00000000000..fa57c032c77
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc
@@ -0,0 +1,124 @@
+// 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 "content/browser/gamepad/gamepad_standard_mappings.h"
+
+#include "content/common/gamepad_hardware_buffer.h"
+
+namespace content {
+
+namespace {
+
+// Maps 0..65535 to -1..1.
+float NormalizeDirectInputAxis(long value) {
+ return (value * 1.f / 32767.5f) - 1.f;
+}
+
+float AxisNegativeAsButton(long value) {
+ return (value < 32767) ? 1.f : 0.f;
+}
+
+float AxisPositiveAsButton(long value) {
+ return (value > 32767) ? 1.f : 0.f;
+}
+
+void MapperDragonRiseGeneric(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[0] = input.buttons[1];
+ mapped->buttons[1] = input.buttons[2];
+ mapped->buttons[2] = input.buttons[0];
+ mapped->buttons[12] = input.buttons[16];
+ mapped->buttons[13] = input.buttons[17];
+ mapped->buttons[14] = input.buttons[18];
+ mapped->buttons[15] = input.buttons[19];
+ mapped->buttonsLength = 16;
+ mapped->axes[0] = NormalizeDirectInputAxis(input.axes[0]);
+ mapped->axes[1] = NormalizeDirectInputAxis(input.axes[1]);
+ mapped->axes[2] = NormalizeDirectInputAxis(input.axes[2]);
+ mapped->axes[3] = NormalizeDirectInputAxis(input.axes[5]);
+ mapped->axesLength = 4;
+}
+
+void MapperLogitechDualAction(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[0] = input.buttons[1];
+ mapped->buttons[1] = input.buttons[2];
+ mapped->buttons[2] = input.buttons[0];
+ mapped->buttons[12] = input.buttons[16];
+ mapped->buttons[13] = input.buttons[17];
+ mapped->buttons[14] = input.buttons[18];
+ mapped->buttons[15] = input.buttons[19];
+ mapped->buttonsLength = 16;
+ mapped->axes[0] = NormalizeDirectInputAxis(input.axes[0]);
+ mapped->axes[1] = NormalizeDirectInputAxis(input.axes[1]);
+ mapped->axes[2] = NormalizeDirectInputAxis(input.axes[2]);
+ mapped->axes[3] = NormalizeDirectInputAxis(input.axes[5]);
+ mapped->axesLength = 4;
+}
+
+void MapperLogitechPrecision(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[0] = input.buttons[1];
+ mapped->buttons[1] = input.buttons[2];
+ mapped->buttons[2] = input.buttons[0];
+ mapped->buttons[kButtonLeftThumbstick] = 0; // Not present
+ mapped->buttons[kButtonRightThumbstick] = 0; // Not present
+ mapped->buttons[12] = AxisNegativeAsButton(input.axes[1]);
+ mapped->buttons[13] = AxisPositiveAsButton(input.axes[1]);
+ mapped->buttons[14] = AxisNegativeAsButton(input.axes[0]);
+ mapped->buttons[15] = AxisPositiveAsButton(input.axes[0]);
+ mapped->buttonsLength = 16;
+ mapped->axesLength = 0;
+}
+
+void Mapper2Axes8Keys(
+ const WebKit::WebGamepad& input,
+ WebKit::WebGamepad* mapped) {
+ *mapped = input;
+ mapped->buttons[kButtonLeftTrigger] = 0; // Not present
+ mapped->buttons[kButtonRightTrigger] = 0; // Not present
+ mapped->buttons[8] = input.buttons[6];
+ mapped->buttons[9] = input.buttons[7];
+ mapped->buttons[kButtonLeftThumbstick] = 0; // Not present
+ mapped->buttons[kButtonRightThumbstick] = 0; // Not present
+ mapped->buttons[12] = AxisNegativeAsButton(input.axes[1]);
+ mapped->buttons[13] = AxisPositiveAsButton(input.axes[1]);
+ mapped->buttons[14] = AxisNegativeAsButton(input.axes[0]);
+ mapped->buttons[15] = AxisPositiveAsButton(input.axes[0]);
+ mapped->buttonsLength = 16;
+ mapped->axesLength = 0;
+}
+
+struct MappingData {
+ const char* const vendor_id;
+ const char* const product_id;
+ GamepadStandardMappingFunction function;
+} AvailableMappings[] = {
+ // http://www.linux-usb.org/usb.ids
+ { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB
+ { "046d", "c216", MapperLogitechDualAction }, // Logitech DualAction
+ { "046d", "c21a", MapperLogitechPrecision }, // Logitech Precision
+ { "12bd", "d012", Mapper2Axes8Keys }, // 2Axes 8Keys Game Pad
+};
+
+} // namespace
+
+GamepadStandardMappingFunction GetGamepadStandardMappingFunction(
+ const base::StringPiece& vendor_id,
+ const base::StringPiece& product_id) {
+ for (size_t i = 0; i < arraysize(AvailableMappings); ++i) {
+ MappingData& item = AvailableMappings[i];
+ if (vendor_id == item.vendor_id && product_id == item.product_id)
+ return item.function;
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_test_helpers.cc b/chromium/content/browser/gamepad/gamepad_test_helpers.cc
new file mode 100644
index 00000000000..e4db14ce080
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_test_helpers.cc
@@ -0,0 +1,55 @@
+// 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 "content/browser/gamepad/gamepad_test_helpers.h"
+
+#include "content/browser/gamepad/gamepad_service.h"
+
+namespace content {
+
+MockGamepadDataFetcher::MockGamepadDataFetcher(
+ const WebKit::WebGamepads& test_data)
+ : test_data_(test_data),
+ read_data_(false, false) {
+}
+
+MockGamepadDataFetcher::~MockGamepadDataFetcher() {
+}
+
+void MockGamepadDataFetcher::GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) {
+ {
+ base::AutoLock lock(lock_);
+ *pads = test_data_;
+ }
+ read_data_.Signal();
+}
+
+void MockGamepadDataFetcher::WaitForDataRead() {
+ return read_data_.Wait();
+}
+
+void MockGamepadDataFetcher::SetTestData(const WebKit::WebGamepads& new_data) {
+ base::AutoLock lock(lock_);
+ test_data_ = new_data;
+}
+
+GamepadTestHelper::GamepadTestHelper() {
+}
+
+GamepadTestHelper::~GamepadTestHelper() {
+}
+
+GamepadServiceTestConstructor::GamepadServiceTestConstructor(
+ const WebKit::WebGamepads& test_data) {
+ data_fetcher_ = new MockGamepadDataFetcher(test_data);
+ gamepad_service_ =
+ new GamepadService(scoped_ptr<GamepadDataFetcher>(data_fetcher_));
+}
+
+GamepadServiceTestConstructor::~GamepadServiceTestConstructor() {
+ delete gamepad_service_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gamepad/gamepad_test_helpers.h b/chromium/content/browser/gamepad/gamepad_test_helpers.h
new file mode 100644
index 00000000000..3b43f7787ff
--- /dev/null
+++ b/chromium/content/browser/gamepad/gamepad_test_helpers.h
@@ -0,0 +1,84 @@
+// 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 CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_
+#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/browser/gamepad/gamepad_data_fetcher.h"
+#include "third_party/WebKit/public/platform/WebGamepads.h"
+
+namespace content {
+
+class GamepadService;
+
+// Data fetcher that returns canned data for the gamepad provider.
+class MockGamepadDataFetcher : public GamepadDataFetcher {
+ public:
+ // Initializes the fetcher with the given gamepad data, which will be
+ // returned when the provider queries us.
+ explicit MockGamepadDataFetcher(const WebKit::WebGamepads& test_data);
+
+ virtual ~MockGamepadDataFetcher();
+
+ // GamepadDataFetcher.
+ virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ bool devices_changed_hint) OVERRIDE;
+
+ // Blocks the current thread until the GamepadProvider reads from this
+ // fetcher on the background thread.
+ void WaitForDataRead();
+
+ // Updates the test data.
+ void SetTestData(const WebKit::WebGamepads& new_data);
+
+ private:
+ base::Lock lock_;
+ WebKit::WebGamepads test_data_;
+ base::WaitableEvent read_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockGamepadDataFetcher);
+};
+
+// Base class for the other test helpers. This just sets up the system monitor.
+class GamepadTestHelper {
+ public:
+ GamepadTestHelper();
+ virtual ~GamepadTestHelper();
+
+ base::MessageLoop& message_loop() { return message_loop_; }
+
+ private:
+ // This must be constructed before the system monitor.
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadTestHelper);
+};
+
+// Constructs a GamepadService with a mock data source. This bypasses the
+// global singleton for the gamepad service.
+class GamepadServiceTestConstructor : public GamepadTestHelper {
+ public:
+ explicit GamepadServiceTestConstructor(const WebKit::WebGamepads& test_data);
+ virtual ~GamepadServiceTestConstructor();
+
+ GamepadService* gamepad_service() { return gamepad_service_; }
+ MockGamepadDataFetcher* data_fetcher() { return data_fetcher_; }
+
+ private:
+ // Owning pointer (can't be a scoped_ptr due to private destructor).
+ GamepadService* gamepad_service_;
+
+ // Pointer owned by the provider (which is owned by the gamepad service).
+ MockGamepadDataFetcher* data_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadServiceTestConstructor);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_
diff --git a/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc
new file mode 100644
index 00000000000..6619b9b48f7
--- /dev/null
+++ b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc
@@ -0,0 +1,625 @@
+// 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 "content/browser/gamepad/xbox_data_fetcher_mac.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/usb/IOUSBLib.h>
+#include <IOKit/usb/USB.h>
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+
+namespace {
+const int kVendorMicrosoft = 0x045e;
+const int kProduct360Controller = 0x028e;
+
+const int kReadEndpoint = 1;
+const int kControlEndpoint = 2;
+
+enum {
+ STATUS_MESSAGE_BUTTONS = 0,
+ STATUS_MESSAGE_LED = 1,
+
+ // Apparently this message tells you if the rumble pack is disabled in the
+ // controller. If the rumble pack is disabled, vibration control messages
+ // have no effect.
+ STATUS_MESSAGE_RUMBLE = 3,
+};
+
+enum {
+ CONTROL_MESSAGE_SET_RUMBLE = 0,
+ CONTROL_MESSAGE_SET_LED = 1,
+};
+
+#pragma pack(push, 1)
+struct ButtonData {
+ bool dpad_up : 1;
+ bool dpad_down : 1;
+ bool dpad_left : 1;
+ bool dpad_right : 1;
+
+ bool start : 1;
+ bool back : 1;
+ bool stick_left_click : 1;
+ bool stick_right_click : 1;
+
+ bool bumper_left : 1;
+ bool bumper_right : 1;
+ bool guide : 1;
+ bool dummy1 : 1; // Always 0.
+
+ bool a : 1;
+ bool b : 1;
+ bool x : 1;
+ bool y : 1;
+
+ uint8 trigger_left;
+ uint8 trigger_right;
+
+ int16 stick_left_x;
+ int16 stick_left_y;
+ int16 stick_right_x;
+ int16 stick_right_y;
+
+ // Always 0.
+ uint32 dummy2;
+ uint16 dummy3;
+};
+#pragma pack(pop)
+
+COMPILE_ASSERT(sizeof(ButtonData) == 0x12, xbox_button_data_wrong_size);
+
+// From MSDN:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx#dead_zone
+const int16 kLeftThumbDeadzone = 7849;
+const int16 kRightThumbDeadzone = 8689;
+const uint8 kTriggerDeadzone = 30;
+
+void NormalizeAxis(int16 x,
+ int16 y,
+ int16 deadzone,
+ float* x_out,
+ float* y_out) {
+ float x_val = x;
+ float y_val = y;
+
+ // Determine how far the stick is pushed.
+ float real_magnitude = std::sqrt(x_val * x_val + y_val * y_val);
+
+ // Check if the controller is outside a circular dead zone.
+ if (real_magnitude > deadzone) {
+ // Clip the magnitude at its expected maximum value.
+ float magnitude = std::min(32767.0f, real_magnitude);
+
+ // Adjust magnitude relative to the end of the dead zone.
+ magnitude -= deadzone;
+
+ // Normalize the magnitude with respect to its expected range giving a
+ // magnitude value of 0.0 to 1.0
+ float ratio = (magnitude / (32767 - deadzone)) / real_magnitude;
+
+ // Y is negated because xbox controllers have an opposite sign from
+ // the 'standard controller' recommendations.
+ *x_out = x_val * ratio;
+ *y_out = -y_val * ratio;
+ } else {
+ // If the controller is in the deadzone zero out the magnitude.
+ *x_out = *y_out = 0.0f;
+ }
+}
+
+float NormalizeTrigger(uint8 value) {
+ return value < kTriggerDeadzone ? 0 :
+ static_cast<float>(value - kTriggerDeadzone) /
+ (std::numeric_limits<uint8>::max() - kTriggerDeadzone);
+}
+
+void NormalizeButtonData(const ButtonData& data,
+ XboxController::Data* normalized_data) {
+ normalized_data->buttons[0] = data.a;
+ normalized_data->buttons[1] = data.b;
+ normalized_data->buttons[2] = data.x;
+ normalized_data->buttons[3] = data.y;
+ normalized_data->buttons[4] = data.bumper_left;
+ normalized_data->buttons[5] = data.bumper_right;
+ normalized_data->buttons[6] = data.back;
+ normalized_data->buttons[7] = data.start;
+ normalized_data->buttons[8] = data.stick_left_click;
+ normalized_data->buttons[9] = data.stick_right_click;
+ normalized_data->buttons[10] = data.dpad_up;
+ normalized_data->buttons[11] = data.dpad_down;
+ normalized_data->buttons[12] = data.dpad_left;
+ normalized_data->buttons[13] = data.dpad_right;
+ normalized_data->buttons[14] = data.guide;
+ normalized_data->triggers[0] = NormalizeTrigger(data.trigger_left);
+ normalized_data->triggers[1] = NormalizeTrigger(data.trigger_right);
+ NormalizeAxis(data.stick_left_x,
+ data.stick_left_y,
+ kLeftThumbDeadzone,
+ &normalized_data->axes[0],
+ &normalized_data->axes[1]);
+ NormalizeAxis(data.stick_right_x,
+ data.stick_right_y,
+ kRightThumbDeadzone,
+ &normalized_data->axes[2],
+ &normalized_data->axes[3]);
+}
+
+} // namespace
+
+XboxController::XboxController(Delegate* delegate)
+ : device_(NULL),
+ interface_(NULL),
+ device_is_open_(false),
+ interface_is_open_(false),
+ read_buffer_size_(0),
+ led_pattern_(LED_NUM_PATTERNS),
+ location_id_(0),
+ delegate_(delegate) {
+}
+
+XboxController::~XboxController() {
+ if (source_)
+ CFRunLoopSourceInvalidate(source_);
+ if (interface_ && interface_is_open_)
+ (*interface_)->USBInterfaceClose(interface_);
+ if (device_ && device_is_open_)
+ (*device_)->USBDeviceClose(device_);
+}
+
+bool XboxController::OpenDevice(io_service_t service) {
+ IOCFPlugInInterface **plugin;
+ SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService.
+ kern_return_t kr =
+ IOCreatePlugInInterfaceForService(service,
+ kIOUSBDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugin,
+ &score);
+ if (kr != KERN_SUCCESS)
+ return false;
+ base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin);
+
+ HRESULT res =
+ (*plugin)->QueryInterface(plugin,
+ CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID320),
+ (LPVOID *)&device_);
+ if (!SUCCEEDED(res) || !device_)
+ return false;
+
+ UInt16 vendor_id;
+ kr = (*device_)->GetDeviceVendor(device_, &vendor_id);
+ if (kr != KERN_SUCCESS)
+ return false;
+ UInt16 product_id;
+ kr = (*device_)->GetDeviceProduct(device_, &product_id);
+ if (kr != KERN_SUCCESS)
+ return false;
+ if (vendor_id != kVendorMicrosoft || product_id != kProduct360Controller)
+ return false;
+
+ // Open the device and configure it.
+ kr = (*device_)->USBDeviceOpen(device_);
+ if (kr != KERN_SUCCESS)
+ return false;
+ device_is_open_ = true;
+
+ // Xbox controllers have one configuration option which has configuration
+ // value 1. Try to set it and fail if it couldn't be configured.
+ IOUSBConfigurationDescriptorPtr config_desc;
+ kr = (*device_)->GetConfigurationDescriptorPtr(device_, 0, &config_desc);
+ if (kr != KERN_SUCCESS)
+ return false;
+ kr = (*device_)->SetConfiguration(device_, config_desc->bConfigurationValue);
+ if (kr != KERN_SUCCESS)
+ return false;
+
+ // The device has 4 interfaces. They are as follows:
+ // Protocol 1:
+ // - Endpoint 1 (in) : Controller events, including button presses.
+ // - Endpoint 2 (out): Rumble pack and LED control
+ // Protocol 2 has a single endpoint to read from a connected ChatPad device.
+ // Protocol 3 is used by a connected headset device.
+ // The device also has an interface on subclass 253, protocol 10 with no
+ // endpoints. It is unused.
+ //
+ // We don't currently support the ChatPad or headset, so protocol 1 is the
+ // only protocol we care about.
+ //
+ // For more detail, see
+ // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL
+ IOUSBFindInterfaceRequest request;
+ request.bInterfaceClass = 255;
+ request.bInterfaceSubClass = 93;
+ request.bInterfaceProtocol = 1;
+ request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+ io_iterator_t iter;
+ kr = (*device_)->CreateInterfaceIterator(device_, &request, &iter);
+ if (kr != KERN_SUCCESS)
+ return false;
+ base::mac::ScopedIOObject<io_iterator_t> iter_ref(iter);
+
+ // There should be exactly one USB interface which matches the requested
+ // settings.
+ io_service_t usb_interface = IOIteratorNext(iter);
+ if (!usb_interface)
+ return false;
+
+ // We need to make an InterfaceInterface to communicate with the device
+ // endpoint. This is the same process as earlier: first make a
+ // PluginInterface from the io_service then make the InterfaceInterface from
+ // that.
+ IOCFPlugInInterface **plugin_interface;
+ kr = IOCreatePlugInInterfaceForService(usb_interface,
+ kIOUSBInterfaceUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugin_interface,
+ &score);
+ if (kr != KERN_SUCCESS || !plugin_interface)
+ return false;
+ base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> interface_ref(
+ plugin_interface);
+
+ // Release the USB interface, and any subsequent interfaces returned by the
+ // iterator. (There shouldn't be any, but in case a future device does
+ // contain more interfaces, this will serve to avoid memory leaks.)
+ do {
+ IOObjectRelease(usb_interface);
+ } while ((usb_interface = IOIteratorNext(iter)));
+
+ // Actually create the interface.
+ res = (*plugin_interface)->QueryInterface(
+ plugin_interface,
+ CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
+ (LPVOID *)&interface_);
+
+ if (!SUCCEEDED(res) || !interface_)
+ return false;
+
+ // Actually open the interface.
+ kr = (*interface_)->USBInterfaceOpen(interface_);
+ if (kr != KERN_SUCCESS)
+ return false;
+ interface_is_open_ = true;
+
+ CFRunLoopSourceRef source_ref;
+ kr = (*interface_)->CreateInterfaceAsyncEventSource(interface_, &source_ref);
+ if (kr != KERN_SUCCESS || !source_ref)
+ return false;
+ source_.reset(source_ref);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopDefaultMode);
+
+ // The interface should have two pipes. Pipe 1 with direction kUSBIn and pipe
+ // 2 with direction kUSBOut. Both pipes should have type kUSBInterrupt.
+ uint8 num_endpoints;
+ kr = (*interface_)->GetNumEndpoints(interface_, &num_endpoints);
+ if (kr != KERN_SUCCESS || num_endpoints < 2)
+ return false;
+
+ for (int i = 1; i <= 2; i++) {
+ uint8 direction;
+ uint8 number;
+ uint8 transfer_type;
+ uint16 max_packet_size;
+ uint8 interval;
+
+ kr = (*interface_)->GetPipeProperties(interface_,
+ i,
+ &direction,
+ &number,
+ &transfer_type,
+ &max_packet_size,
+ &interval);
+ if (kr != KERN_SUCCESS || transfer_type != kUSBInterrupt)
+ return false;
+ if (i == kReadEndpoint) {
+ if (direction != kUSBIn)
+ return false;
+ if (max_packet_size > 32)
+ return false;
+ read_buffer_.reset(new uint8[max_packet_size]);
+ read_buffer_size_ = max_packet_size;
+ QueueRead();
+ } else if (i == kControlEndpoint) {
+ if (direction != kUSBOut)
+ return false;
+ }
+ }
+
+ // The location ID is unique per controller, and can be used to track
+ // controllers through reconnections (though if a controller is detached from
+ // one USB hub and attached to another, the location ID will change).
+ kr = (*device_)->GetLocationID(device_, &location_id_);
+ if (kr != KERN_SUCCESS)
+ return false;
+
+ return true;
+}
+
+void XboxController::SetLEDPattern(LEDPattern pattern) {
+ led_pattern_ = pattern;
+ const UInt8 length = 3;
+
+ // This buffer will be released in WriteComplete when WritePipeAsync
+ // finishes.
+ UInt8* buffer = new UInt8[length];
+ buffer[0] = static_cast<UInt8>(CONTROL_MESSAGE_SET_LED);
+ buffer[1] = length;
+ buffer[2] = static_cast<UInt8>(pattern);
+ kern_return_t kr = (*interface_)->WritePipeAsync(interface_,
+ kControlEndpoint,
+ buffer,
+ (UInt32)length,
+ WriteComplete,
+ buffer);
+ if (kr != KERN_SUCCESS) {
+ delete[] buffer;
+ IOError();
+ return;
+ }
+}
+
+int XboxController::GetVendorId() const {
+ return kVendorMicrosoft;
+}
+
+int XboxController::GetProductId() const {
+ return kProduct360Controller;
+}
+
+void XboxController::WriteComplete(void* context, IOReturn result, void* arg0) {
+ UInt8* buffer = static_cast<UInt8*>(context);
+ delete[] buffer;
+
+ // Ignoring any errors sending data, because they will usually only occur
+ // when the device is disconnected, in which case it really doesn't matter if
+ // the data got to the controller or not.
+ if (result != kIOReturnSuccess)
+ return;
+}
+
+void XboxController::GotData(void* context, IOReturn result, void* arg0) {
+ size_t bytes_read = reinterpret_cast<size_t>(arg0);
+ XboxController* controller = static_cast<XboxController*>(context);
+
+ if (result != kIOReturnSuccess) {
+ // This will happen if the device was disconnected. The gamepad has
+ // probably been destroyed by a meteorite.
+ controller->IOError();
+ return;
+ }
+
+ controller->ProcessPacket(bytes_read);
+
+ // Queue up another read.
+ controller->QueueRead();
+}
+
+void XboxController::ProcessPacket(size_t length) {
+ if (length < 2) return;
+ DCHECK(length <= read_buffer_size_);
+ if (length > read_buffer_size_) {
+ IOError();
+ return;
+ }
+ uint8* buffer = read_buffer_.get();
+
+ if (buffer[1] != length)
+ // Length in packet doesn't match length reported by USB.
+ return;
+
+ uint8 type = buffer[0];
+ buffer += 2;
+ length -= 2;
+ switch (type) {
+ case STATUS_MESSAGE_BUTTONS: {
+ if (length != sizeof(ButtonData))
+ return;
+ ButtonData* data = reinterpret_cast<ButtonData*>(buffer);
+ Data normalized_data;
+ NormalizeButtonData(*data, &normalized_data);
+ delegate_->XboxControllerGotData(this, normalized_data);
+ break;
+ }
+ case STATUS_MESSAGE_LED:
+ if (length != 3)
+ return;
+ // The controller sends one of these messages every time the LED pattern
+ // is set, as well as once when it is plugged in.
+ if (led_pattern_ == LED_NUM_PATTERNS && buffer[0] < LED_NUM_PATTERNS)
+ led_pattern_ = static_cast<LEDPattern>(buffer[0]);
+ break;
+ default:
+ // Unknown packet: ignore!
+ break;
+ }
+}
+
+void XboxController::QueueRead() {
+ kern_return_t kr = (*interface_)->ReadPipeAsync(interface_,
+ kReadEndpoint,
+ read_buffer_.get(),
+ read_buffer_size_,
+ GotData,
+ this);
+ if (kr != KERN_SUCCESS)
+ IOError();
+}
+
+void XboxController::IOError() {
+ delegate_->XboxControllerError(this);
+}
+
+//-----------------------------------------------------------------------------
+
+XboxDataFetcher::XboxDataFetcher(Delegate* delegate)
+ : delegate_(delegate),
+ listening_(false),
+ source_(NULL),
+ port_(NULL) {
+}
+
+XboxDataFetcher::~XboxDataFetcher() {
+ while (!controllers_.empty()) {
+ RemoveController(*controllers_.begin());
+ }
+ UnregisterFromNotifications();
+}
+
+void XboxDataFetcher::DeviceAdded(void* context, io_iterator_t iterator) {
+ DCHECK(context);
+ XboxDataFetcher* fetcher = static_cast<XboxDataFetcher*>(context);
+ io_service_t ref;
+ while ((ref = IOIteratorNext(iterator))) {
+ base::mac::ScopedIOObject<io_service_t> scoped_ref(ref);
+ XboxController* controller = new XboxController(fetcher);
+ if (controller->OpenDevice(ref)) {
+ fetcher->AddController(controller);
+ } else {
+ delete controller;
+ }
+ }
+}
+
+void XboxDataFetcher::DeviceRemoved(void* context, io_iterator_t iterator) {
+ DCHECK(context);
+ XboxDataFetcher* fetcher = static_cast<XboxDataFetcher*>(context);
+ io_service_t ref;
+ while ((ref = IOIteratorNext(iterator))) {
+ base::mac::ScopedIOObject<io_service_t> scoped_ref(ref);
+ base::ScopedCFTypeRef<CFNumberRef> number(
+ base::mac::CFCastStrict<CFNumberRef>(
+ IORegistryEntryCreateCFProperty(ref,
+ CFSTR(kUSBDevicePropertyLocationID),
+ kCFAllocatorDefault,
+ kNilOptions)));
+ UInt32 location_id = 0;
+ CFNumberGetValue(number, kCFNumberSInt32Type, &location_id);
+ fetcher->RemoveControllerByLocationID(location_id);
+ }
+}
+
+bool XboxDataFetcher::RegisterForNotifications() {
+ if (listening_)
+ return true;
+ base::ScopedCFTypeRef<CFNumberRef> vendor_cf(CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt32Type, &kVendorMicrosoft));
+ base::ScopedCFTypeRef<CFNumberRef> product_cf(CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt32Type, &kProduct360Controller));
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict(
+ IOServiceMatching(kIOUSBDeviceClassName));
+ if (!matching_dict)
+ return false;
+ CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID), vendor_cf);
+ CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID), product_cf);
+ port_ = IONotificationPortCreate(kIOMasterPortDefault);
+ if (!port_)
+ return false;
+ source_ = IONotificationPortGetRunLoopSource(port_);
+ if (!source_)
+ return false;
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopDefaultMode);
+
+ listening_ = true;
+
+ // IOServiceAddMatchingNotification() releases the dictionary when it's done.
+ // Retain it before each call to IOServiceAddMatchingNotification to keep
+ // things balanced.
+ CFRetain(matching_dict);
+ io_iterator_t device_added_iter;
+ IOReturn ret;
+ ret = IOServiceAddMatchingNotification(port_,
+ kIOFirstMatchNotification,
+ matching_dict,
+ DeviceAdded,
+ this,
+ &device_added_iter);
+ device_added_iter_.reset(device_added_iter);
+ if (ret != kIOReturnSuccess) {
+ LOG(ERROR) << "Error listening for Xbox controller add events: " << ret;
+ return false;
+ }
+ DeviceAdded(this, device_added_iter_.get());
+
+ CFRetain(matching_dict);
+ io_iterator_t device_removed_iter;
+ ret = IOServiceAddMatchingNotification(port_,
+ kIOTerminatedNotification,
+ matching_dict,
+ DeviceRemoved,
+ this,
+ &device_removed_iter);
+ device_removed_iter_.reset(device_removed_iter);
+ if (ret != kIOReturnSuccess) {
+ LOG(ERROR) << "Error listening for Xbox controller remove events: " << ret;
+ return false;
+ }
+ DeviceRemoved(this, device_removed_iter_.get());
+ return true;
+}
+
+void XboxDataFetcher::UnregisterFromNotifications() {
+ if (!listening_)
+ return;
+ listening_ = false;
+ if (source_)
+ CFRunLoopSourceInvalidate(source_);
+ if (port_)
+ IONotificationPortDestroy(port_);
+ port_ = NULL;
+}
+
+XboxController* XboxDataFetcher::ControllerForLocation(UInt32 location_id) {
+ for (std::set<XboxController*>::iterator i = controllers_.begin();
+ i != controllers_.end();
+ ++i) {
+ if ((*i)->location_id() == location_id)
+ return *i;
+ }
+ return NULL;
+}
+
+void XboxDataFetcher::AddController(XboxController* controller) {
+ DCHECK(!ControllerForLocation(controller->location_id()))
+ << "Controller with location ID " << controller->location_id()
+ << " already exists in the set of controllers.";
+ controllers_.insert(controller);
+ delegate_->XboxDeviceAdd(controller);
+}
+
+void XboxDataFetcher::RemoveController(XboxController* controller) {
+ delegate_->XboxDeviceRemove(controller);
+ controllers_.erase(controller);
+ delete controller;
+}
+
+void XboxDataFetcher::RemoveControllerByLocationID(uint32 location_id) {
+ XboxController* controller = NULL;
+ for (std::set<XboxController*>::iterator i = controllers_.begin();
+ i != controllers_.end();
+ ++i) {
+ if ((*i)->location_id() == location_id) {
+ controller = *i;
+ break;
+ }
+ }
+ if (controller)
+ RemoveController(controller);
+}
+
+void XboxDataFetcher::XboxControllerGotData(XboxController* controller,
+ const XboxController::Data& data) {
+ delegate_->XboxValueChanged(controller, data);
+}
+
+void XboxDataFetcher::XboxControllerError(XboxController* controller) {
+ RemoveController(controller);
+}
diff --git a/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h
new file mode 100644
index 00000000000..ca8f7fcd88c
--- /dev/null
+++ b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h
@@ -0,0 +1,167 @@
+// 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 CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_
+#define CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+#include "base/mac/scoped_ioplugininterface.h"
+#include "base/memory/scoped_ptr.h"
+
+class XboxController {
+ public:
+ enum LEDPattern {
+ LED_OFF = 0,
+
+ // 2 quick flashes, then a series of slow flashes (about 1 per second).
+ LED_FLASH = 1,
+
+ // Flash three times then hold the LED on. This is the standard way to tell
+ // the player which player number they are.
+ LED_FLASH_TOP_LEFT = 2,
+ LED_FLASH_TOP_RIGHT = 3,
+ LED_FLASH_BOTTOM_LEFT = 4,
+ LED_FLASH_BOTTOM_RIGHT = 5,
+
+ // Simply turn on the specified LED and turn all other LEDs off.
+ LED_HOLD_TOP_LEFT = 6,
+ LED_HOLD_TOP_RIGHT = 7,
+ LED_HOLD_BOTTOM_LEFT = 8,
+ LED_HOLD_BOTTOM_RIGHT = 9,
+
+ LED_ROTATE = 10,
+
+ LED_FLASH_FAST = 11,
+ LED_FLASH_SLOW = 12, // Flash about once per 3 seconds
+
+ // Flash alternating LEDs for a few seconds, then flash all LEDs about once
+ // per second
+ LED_ALTERNATE_PATTERN = 13,
+
+ // 14 is just another boring flashing speed.
+
+ // Flash all LEDs once then go black.
+ LED_FLASH_ONCE = 15,
+
+ LED_NUM_PATTERNS
+ };
+
+ struct Data {
+ bool buttons[15];
+ float triggers[2];
+ float axes[4];
+ };
+
+ class Delegate {
+ public:
+ virtual void XboxControllerGotData(XboxController* controller,
+ const Data& data) = 0;
+ virtual void XboxControllerError(XboxController* controller) = 0;
+ };
+
+ explicit XboxController(Delegate* delegate_);
+ virtual ~XboxController();
+
+ bool OpenDevice(io_service_t service);
+
+ void SetLEDPattern(LEDPattern pattern);
+
+ UInt32 location_id() { return location_id_; }
+ int GetVendorId() const;
+ int GetProductId() const;
+
+ private:
+ static void WriteComplete(void* context, IOReturn result, void* arg0);
+ static void GotData(void* context, IOReturn result, void* arg0);
+
+ void ProcessPacket(size_t length);
+ void QueueRead();
+
+ void IOError();
+
+ // Handle for the USB device. IOUSBDeviceStruct320 is the latest version of
+ // the device API that is supported on Mac OS 10.6.
+ base::mac::ScopedIOPluginInterface<struct IOUSBDeviceStruct320> device_;
+
+ // Handle for the interface on the device which sends button and analog data.
+ // The other interfaces (for the ChatPad and headset) are ignored.
+ base::mac::ScopedIOPluginInterface<struct IOUSBInterfaceStruct300> interface_;
+
+ bool device_is_open_;
+ bool interface_is_open_;
+
+ base::ScopedCFTypeRef<CFRunLoopSourceRef> source_;
+
+ // This will be set to the max packet size reported by the interface, which
+ // is 32 bytes. I would have expected USB to do message framing itself, but
+ // somehow we still sometimes (rarely!) get packets off the interface which
+ // aren't correctly framed. The 360 controller frames its packets with a 2
+ // byte header (type, total length) so we can reframe the packet data
+ // ourselves.
+ uint16 read_buffer_size_;
+ scoped_ptr<uint8[]> read_buffer_;
+
+ // The pattern that the LEDs on the device are currently displaying, or
+ // LED_NUM_PATTERNS if unknown.
+ LEDPattern led_pattern_;
+
+ UInt32 location_id_;
+
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(XboxController);
+};
+
+class XboxDataFetcher : public XboxController::Delegate {
+ public:
+ class Delegate {
+ public:
+ virtual void XboxDeviceAdd(XboxController* device) = 0;
+ virtual void XboxDeviceRemove(XboxController* device) = 0;
+ virtual void XboxValueChanged(XboxController* device,
+ const XboxController::Data& data) = 0;
+ };
+
+ explicit XboxDataFetcher(Delegate* delegate);
+ virtual ~XboxDataFetcher();
+
+ bool RegisterForNotifications();
+ void UnregisterFromNotifications();
+
+ XboxController* ControllerForLocation(UInt32 location_id);
+
+ private:
+ static void DeviceAdded(void* context, io_iterator_t iterator);
+ static void DeviceRemoved(void* context, io_iterator_t iterator);
+ void AddController(XboxController* controller);
+ void RemoveController(XboxController* controller);
+ void RemoveControllerByLocationID(uint32 id);
+ virtual void XboxControllerGotData(XboxController* controller,
+ const XboxController::Data& data) OVERRIDE;
+ virtual void XboxControllerError(XboxController* controller) OVERRIDE;
+
+ Delegate* delegate_;
+
+ std::set<XboxController*> controllers_;
+
+ bool listening_;
+
+ // port_ owns source_, so this doesn't need to be a ScopedCFTypeRef, but we
+ // do need to maintain a reference to it so we can invalidate it.
+ CFRunLoopSourceRef source_;
+ IONotificationPortRef port_;
+ base::mac::ScopedIOObject<io_iterator_t> device_added_iter_;
+ base::mac::ScopedIOObject<io_iterator_t> device_removed_iter_;
+
+ DISALLOW_COPY_AND_ASSIGN(XboxDataFetcher);
+};
+
+#endif // CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_
diff --git a/chromium/content/browser/geolocation/DEPS b/chromium/content/browser/geolocation/DEPS
new file mode 100644
index 00000000000..9f09a7077c5
--- /dev/null
+++ b/chromium/content/browser/geolocation/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+google_apis", # Exception to general rule, see content/DEPS for details.
+]
+
diff --git a/chromium/content/browser/geolocation/OWNERS b/chromium/content/browser/geolocation/OWNERS
new file mode 100644
index 00000000000..c2252ab07e2
--- /dev/null
+++ b/chromium/content/browser/geolocation/OWNERS
@@ -0,0 +1,6 @@
+# Reviews:
+# Not yet owner - mvanouwerkerk@chromium.org
+bulach@chromium.org
+
+# Just owners:
+joth@chromium.org
diff --git a/chromium/content/browser/geolocation/device_data_provider.cc b/chromium/content/browser/geolocation/device_data_provider.cc
new file mode 100644
index 00000000000..00c585bd11d
--- /dev/null
+++ b/chromium/content/browser/geolocation/device_data_provider.cc
@@ -0,0 +1,54 @@
+// 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 "content/browser/geolocation/device_data_provider.h"
+
+namespace content {
+
+// statics
+template<> DeviceDataProvider<WifiData>*
+ DeviceDataProvider<WifiData>::instance_ = NULL;
+template<> DeviceDataProvider<WifiData>::ImplFactoryFunction
+ DeviceDataProvider<WifiData>::factory_function_ = DefaultFactoryFunction;
+
+AccessPointData::AccessPointData()
+ : radio_signal_strength(kint32min),
+ channel(kint32min),
+ signal_to_noise(kint32min) {
+}
+
+AccessPointData::~AccessPointData() {}
+
+WifiData::WifiData() {}
+
+WifiData::~WifiData() {}
+
+bool WifiData::DiffersSignificantly(const WifiData& other) const {
+ // More than 4 or 50% of access points added or removed is significant.
+ static const size_t kMinChangedAccessPoints = 4;
+ const size_t min_ap_count =
+ std::min(access_point_data.size(), other.access_point_data.size());
+ const size_t max_ap_count =
+ std::max(access_point_data.size(), other.access_point_data.size());
+ const size_t difference_threadhold = std::min(kMinChangedAccessPoints,
+ min_ap_count / 2);
+ if (max_ap_count > min_ap_count + difference_threadhold)
+ return true;
+ // Compute size of interesction of old and new sets.
+ size_t num_common = 0;
+ for (AccessPointDataSet::const_iterator iter = access_point_data.begin();
+ iter != access_point_data.end();
+ iter++) {
+ if (other.access_point_data.find(*iter) !=
+ other.access_point_data.end()) {
+ ++num_common;
+ }
+ }
+ DCHECK(num_common <= min_ap_count);
+
+ // Test how many have changed.
+ return max_ap_count > num_common + difference_threadhold;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/device_data_provider.h b/chromium/content/browser/geolocation/device_data_provider.h
new file mode 100644
index 00000000000..d8907354a23
--- /dev/null
+++ b/chromium/content/browser/geolocation/device_data_provider.h
@@ -0,0 +1,305 @@
+// 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.
+
+// A device data provider provides data from the device that is used by a
+// NetworkLocationProvider to obtain a position fix. This data may be either
+// cell radio data or wifi data. For a given type of data, we use a singleton
+// instance of the device data provider, which is used by multiple
+// NetworkLocationProvider objects.
+//
+// This file providers DeviceDataProvider, which provides static methods to
+// access the singleton instance. The singleton instance uses a private
+// implementation to abstract across platforms and also to allow mock providers
+// to be used for testing.
+//
+// This file also provides DeviceDataProviderImplBase, a base class which
+// provides commom functionality for the private implementations.
+//
+// This file also declares the data structures used to represent cell radio data
+// and wifi data.
+
+#ifndef CONTENT_BROWSER_GEOLOCATION_DEVICE_DATA_PROVIDER_H_
+#define CONTENT_BROWSER_GEOLOCATION_DEVICE_DATA_PROVIDER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Wifi data relating to a single access point.
+struct CONTENT_EXPORT AccessPointData {
+ AccessPointData();
+ ~AccessPointData();
+
+ // MAC address, formatted as per MacAddressAsString16.
+ string16 mac_address;
+ int radio_signal_strength; // Measured in dBm
+ int channel;
+ int signal_to_noise; // Ratio in dB
+ string16 ssid; // Network identifier
+};
+
+// This is to allow AccessPointData to be used in std::set. We order
+// lexicographically by MAC address.
+struct AccessPointDataLess {
+ bool operator()(const AccessPointData& data1,
+ const AccessPointData& data2) const {
+ return data1.mac_address < data2.mac_address;
+ }
+};
+
+// All data for wifi.
+struct CONTENT_EXPORT WifiData {
+ WifiData();
+ ~WifiData();
+
+ // Determines whether a new set of WiFi data differs significantly from this.
+ bool DiffersSignificantly(const WifiData& other) const;
+
+ // Store access points as a set, sorted by MAC address. This allows quick
+ // comparison of sets for detecting changes and for caching.
+ typedef std::set<AccessPointData, AccessPointDataLess> AccessPointDataSet;
+ AccessPointDataSet access_point_data;
+};
+
+template<typename DataType>
+class DeviceDataProvider;
+
+// This class just exists to work-around MSVC2005 not being able to have a
+// template class implement RefCountedThreadSafe
+class CONTENT_EXPORT DeviceDataProviderImplBaseHack
+ : public base::RefCountedThreadSafe<DeviceDataProviderImplBaseHack> {
+ protected:
+ friend class base::RefCountedThreadSafe<DeviceDataProviderImplBaseHack>;
+ virtual ~DeviceDataProviderImplBaseHack() {}
+};
+
+// See class DeviceDataProvider for the public client API.
+// DeviceDataProvider uses containment to hide platform-specific implementation
+// details from common code. This class provides common functionality for these
+// contained implementation classes. This is a modified pimpl pattern: this
+// class needs to be in the public header due to use of templating.
+template<typename DataType>
+class DeviceDataProviderImplBase : public DeviceDataProviderImplBaseHack {
+ public:
+ DeviceDataProviderImplBase()
+ : container_(NULL), client_loop_(base::MessageLoop::current()) {
+ DCHECK(client_loop_);
+ }
+
+ virtual bool StartDataProvider() = 0;
+ virtual void StopDataProvider() = 0;
+ virtual bool GetData(DataType* data) = 0;
+
+ // Sets the container of this class, which is of type DeviceDataProvider.
+ // This is required to pass as a parameter when making the callback to
+ // listeners.
+ void SetContainer(DeviceDataProvider<DataType>* container) {
+ DCHECK(CalledOnClientThread());
+ container_ = container;
+ }
+
+ typedef typename DeviceDataProvider<DataType>::ListenerInterface
+ ListenerInterface;
+ void AddListener(ListenerInterface* listener) {
+ DCHECK(CalledOnClientThread());
+ listeners_.insert(listener);
+ }
+ bool RemoveListener(ListenerInterface* listener) {
+ DCHECK(CalledOnClientThread());
+ return listeners_.erase(listener) == 1;
+ }
+
+ bool has_listeners() const {
+ DCHECK(CalledOnClientThread());
+ return !listeners_.empty();
+ }
+
+ protected:
+ virtual ~DeviceDataProviderImplBase() {}
+
+ // Calls DeviceDataUpdateAvailable() on all registered listeners.
+ typedef std::set<ListenerInterface*> ListenersSet;
+ void NotifyListeners() {
+ // Always make the notify callback via a posted task, so we can unwind
+ // callstack here and make callback without causing client re-entrancy.
+ client_loop_->PostTask(FROM_HERE, base::Bind(
+ &DeviceDataProviderImplBase<DataType>::NotifyListenersInClientLoop,
+ this));
+ }
+
+ bool CalledOnClientThread() const {
+ return base::MessageLoop::current() == this->client_loop_;
+ }
+
+ base::MessageLoop* client_loop() const { return client_loop_; }
+
+ private:
+ void NotifyListenersInClientLoop() {
+ DCHECK(CalledOnClientThread());
+ // It's possible that all the listeners (and the container) went away
+ // whilst this task was pending. This is fine; the loop will be a no-op.
+ typename ListenersSet::const_iterator iter = listeners_.begin();
+ while (iter != listeners_.end()) {
+ ListenerInterface* listener = *iter;
+ ++iter; // Advance iter before callback, in case listener unregisters.
+ listener->DeviceDataUpdateAvailable(container_);
+ }
+ }
+
+ DeviceDataProvider<DataType>* container_;
+
+ // Reference to the client's message loop, all callbacks and access to
+ // the listeners_ member should happen in this context.
+ base::MessageLoop* client_loop_;
+
+ ListenersSet listeners_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceDataProviderImplBase);
+};
+
+typedef DeviceDataProviderImplBase<WifiData> WifiDataProviderImplBase;
+
+// A device data provider
+//
+// We use a singleton instance of this class which is shared by multiple network
+// location providers. These location providers access the instance through the
+// Register and Unregister methods.
+template<typename DataType>
+class DeviceDataProvider : public base::NonThreadSafe {
+ public:
+ // Interface to be implemented by listeners to a device data provider.
+ class ListenerInterface {
+ public:
+ // Will be called in the context of the thread that called Register().
+ virtual void DeviceDataUpdateAvailable(
+ DeviceDataProvider<DataType>* provider) = 0;
+ virtual ~ListenerInterface() {}
+ };
+
+ // Sets the factory function which will be used by Register to create the
+ // implementation used by the singleton instance. This factory approach is
+ // used to abastract accross both platform-specific implementation and to
+ // inject mock implementations for testing.
+ typedef DeviceDataProviderImplBase<DataType>* (*ImplFactoryFunction)(void);
+ static void SetFactory(ImplFactoryFunction factory_function_in) {
+ factory_function_ = factory_function_in;
+ }
+
+ static void ResetFactory() {
+ factory_function_ = DefaultFactoryFunction;
+ }
+
+ // Adds a listener, which will be called back with DeviceDataUpdateAvailable
+ // whenever new data is available. Returns the singleton instance.
+ static DeviceDataProvider* Register(ListenerInterface* listener) {
+ bool need_to_start_thread = false;
+ if (!instance_) {
+ instance_ = new DeviceDataProvider();
+ need_to_start_thread = true;
+ }
+ DCHECK(instance_);
+ DCHECK(instance_->CalledOnValidThread());
+ instance_->AddListener(listener);
+ // Start the provider after adding the listener, to avoid any race in
+ // it receiving an early callback.
+ if (need_to_start_thread) {
+ bool started = instance_->StartDataProvider();
+ DCHECK(started);
+ }
+ return instance_;
+ }
+
+ // Removes a listener. If this is the last listener, deletes the singleton
+ // instance. Return value indicates success.
+ static bool Unregister(ListenerInterface* listener) {
+ DCHECK(instance_);
+ DCHECK(instance_->CalledOnValidThread());
+ DCHECK(instance_->has_listeners());
+ if (!instance_->RemoveListener(listener)) {
+ return false;
+ }
+ if (!instance_->has_listeners()) {
+ // Must stop the provider (and any implementation threads) before
+ // destroying to avoid any race conditions in access to the provider in
+ // the destructor chain.
+ instance_->StopDataProvider();
+ delete instance_;
+ instance_ = NULL;
+ }
+ return true;
+ }
+
+ // Provides whatever data the provider has, which may be nothing. Return
+ // value indicates whether this is all the data the provider could ever
+ // obtain.
+ bool GetData(DataType* data) {
+ DCHECK(this->CalledOnValidThread());
+ return impl_->GetData(data);
+ }
+
+ private:
+ // Private constructor and destructor, callers access singleton through
+ // Register and Unregister.
+ DeviceDataProvider() {
+ DCHECK(factory_function_);
+ impl_ = (*factory_function_)();
+ DCHECK(impl_.get());
+ impl_->SetContainer(this);
+ }
+ virtual ~DeviceDataProvider() {
+ DCHECK(impl_.get());
+ impl_->SetContainer(NULL);
+ }
+
+ void AddListener(ListenerInterface* listener) {
+ impl_->AddListener(listener);
+ }
+
+ bool RemoveListener(ListenerInterface* listener) {
+ return impl_->RemoveListener(listener);
+ }
+
+ bool has_listeners() const {
+ return impl_->has_listeners();
+ }
+
+ bool StartDataProvider() {
+ return impl_->StartDataProvider();
+ }
+
+ void StopDataProvider() {
+ impl_->StopDataProvider();
+ }
+
+ CONTENT_EXPORT static DeviceDataProviderImplBase<DataType>*
+ DefaultFactoryFunction();
+
+ // The singleton-like instance of this class. (Not 'true' singleton, as it
+ // may go through multiple create/destroy/create cycles per process instance,
+ // e.g. when under test).
+ CONTENT_EXPORT static DeviceDataProvider* instance_;
+
+ // The factory function used to create the singleton instance.
+ CONTENT_EXPORT static ImplFactoryFunction factory_function_;
+
+ // The internal implementation.
+ scoped_refptr<DeviceDataProviderImplBase<DataType> > impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceDataProvider);
+};
+
+typedef DeviceDataProvider<WifiData> WifiDataProvider;
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_DEVICE_DATA_PROVIDER_H_
diff --git a/chromium/content/browser/geolocation/device_data_provider_unittest.cc b/chromium/content/browser/geolocation/device_data_provider_unittest.cc
new file mode 100644
index 00000000000..42a678c8d5e
--- /dev/null
+++ b/chromium/content/browser/geolocation/device_data_provider_unittest.cc
@@ -0,0 +1,41 @@
+// 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 "base/threading/platform_thread.h"
+#include "content/browser/geolocation/device_data_provider.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class NullWifiDataListenerInterface
+ : public WifiDataProviderCommon::ListenerInterface {
+ public:
+ // ListenerInterface
+ virtual void DeviceDataUpdateAvailable(
+ DeviceDataProvider<WifiData>* provider) OVERRIDE {}
+};
+
+TEST(GeolocationDeviceDataProviderWifiData, CreateDestroy) {
+ // See http://crbug.com/59913 . The main_message_loop is not required to be
+ // run for correct behaviour, but we run it in this test to help smoke out
+ // any race conditions between processing in the main loop and the setup /
+ // tear down of the DeviceDataProvider thread.
+ base::MessageLoopForUI main_message_loop;
+ NullWifiDataListenerInterface listener;
+ for (int i = 0; i < 10; i++) {
+ DeviceDataProvider<WifiData>::Register(&listener);
+ for (int j = 0; j < 10; j++) {
+ base::PlatformThread::Sleep(base::TimeDelta());
+ main_message_loop.RunUntilIdle(); // See comment above
+ }
+ DeviceDataProvider<WifiData>::Unregister(&listener);
+ for (int j = 0; j < 10; j++) {
+ base::PlatformThread::Sleep(base::TimeDelta());
+ main_message_loop.RunUntilIdle(); // See comment above
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/empty_device_data_provider.cc b/chromium/content/browser/geolocation/empty_device_data_provider.cc
new file mode 100644
index 00000000000..57e25b0a504
--- /dev/null
+++ b/chromium/content/browser/geolocation/empty_device_data_provider.cc
@@ -0,0 +1,18 @@
+// 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 "content/browser/geolocation/empty_device_data_provider.h"
+
+namespace content {
+
+// Only define for platforms that lack a real wifi data provider.
+#if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
+// static
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new EmptyDeviceDataProvider<WifiData>();
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/empty_device_data_provider.h b/chromium/content/browser/geolocation/empty_device_data_provider.h
new file mode 100644
index 00000000000..76653f55cfe
--- /dev/null
+++ b/chromium/content/browser/geolocation/empty_device_data_provider.h
@@ -0,0 +1,36 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_EMPTY_DEVICE_DATA_PROVIDER_H_
+#define CONTENT_BROWSER_GEOLOCATION_EMPTY_DEVICE_DATA_PROVIDER_H_
+
+#include "content/browser/geolocation/device_data_provider.h"
+
+namespace content {
+
+// An implementation of DeviceDataProviderImplBase that does not provide any
+// data. Used on platforms where a given data type is not available.
+
+template<typename DataType>
+class EmptyDeviceDataProvider : public DeviceDataProviderImplBase<DataType> {
+ public:
+ EmptyDeviceDataProvider() {}
+ virtual ~EmptyDeviceDataProvider() {}
+
+ // DeviceDataProviderImplBase implementation
+ virtual bool StartDataProvider() { return true; }
+ virtual void StopDataProvider() { }
+ virtual bool GetData(DataType *data) {
+ DCHECK(data);
+ // This is all the data we can get - nothing.
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyDeviceDataProvider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_EMPTY_DEVICE_DATA_PROVIDER_H_
diff --git a/chromium/content/browser/geolocation/fake_access_token_store.cc b/chromium/content/browser/geolocation/fake_access_token_store.cc
new file mode 100644
index 00000000000..9f9f61ba7f7
--- /dev/null
+++ b/chromium/content/browser/geolocation/fake_access_token_store.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/geolocation/fake_access_token_store.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+
+using base::MessageLoopProxy;
+using testing::_;
+using testing::Invoke;
+
+namespace content {
+
+FakeAccessTokenStore::FakeAccessTokenStore()
+ : originating_message_loop_(NULL) {
+ ON_CALL(*this, LoadAccessTokens(_))
+ .WillByDefault(Invoke(this,
+ &FakeAccessTokenStore::DefaultLoadAccessTokens));
+ ON_CALL(*this, SaveAccessToken(_, _))
+ .WillByDefault(Invoke(this,
+ &FakeAccessTokenStore::DefaultSaveAccessToken));
+}
+
+void FakeAccessTokenStore::NotifyDelegateTokensLoaded() {
+ DCHECK(originating_message_loop_);
+ if (!originating_message_loop_->BelongsToCurrentThread()) {
+ originating_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeAccessTokenStore::NotifyDelegateTokensLoaded, this));
+ return;
+ }
+
+ net::URLRequestContextGetter* context_getter = NULL;
+ callback_.Run(access_token_set_, context_getter);
+}
+
+void FakeAccessTokenStore::DefaultLoadAccessTokens(
+ const LoadAccessTokensCallbackType& callback) {
+ originating_message_loop_ = MessageLoopProxy::current().get();
+ callback_ = callback;
+}
+
+void FakeAccessTokenStore::DefaultSaveAccessToken(
+ const GURL& server_url, const string16& access_token) {
+ DCHECK(server_url.is_valid());
+ access_token_set_[server_url] = access_token;
+}
+
+FakeAccessTokenStore::~FakeAccessTokenStore() {}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/fake_access_token_store.h b/chromium/content/browser/geolocation/fake_access_token_store.h
new file mode 100644
index 00000000000..4d0867bf0be
--- /dev/null
+++ b/chromium/content/browser/geolocation/fake_access_token_store.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_FAKE_ACCESS_TOKEN_STORE_H_
+#define CONTENT_BROWSER_GEOLOCATION_FAKE_ACCESS_TOKEN_STORE_H_
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/public/browser/access_token_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+// A fake (non-persisted) access token store instance useful for testing.
+class FakeAccessTokenStore : public AccessTokenStore {
+ public:
+ FakeAccessTokenStore();
+
+ void NotifyDelegateTokensLoaded();
+
+ // AccessTokenStore
+ MOCK_METHOD1(LoadAccessTokens,
+ void(const LoadAccessTokensCallbackType& callback));
+ MOCK_METHOD2(SaveAccessToken,
+ void(const GURL& server_url, const string16& access_token));
+
+ void DefaultLoadAccessTokens(const LoadAccessTokensCallbackType& callback);
+
+ void DefaultSaveAccessToken(const GURL& server_url,
+ const string16& access_token);
+
+ AccessTokenSet access_token_set_;
+ LoadAccessTokensCallbackType callback_;
+
+ protected:
+ // Protected instead of private so we can have NiceMocks.
+ virtual ~FakeAccessTokenStore();
+
+ private:
+ // In some tests, NotifyDelegateTokensLoaded() is called on a thread
+ // other than the originating thread, in which case we must post
+ // back to it.
+ base::MessageLoopProxy* originating_message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeAccessTokenStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_FAKE_ACCESS_TOKEN_STORE_H_
diff --git a/chromium/content/browser/geolocation/geolocation_dispatcher_host.cc b/chromium/content/browser/geolocation/geolocation_dispatcher_host.cc
new file mode 100644
index 00000000000..53f8e4feafb
--- /dev/null
+++ b/chromium/content/browser/geolocation/geolocation_dispatcher_host.cc
@@ -0,0 +1,246 @@
+// 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 "content/browser/geolocation/geolocation_dispatcher_host.h"
+
+#include <map>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "content/browser/geolocation/geolocation_provider_impl.h"
+#include "content/browser/renderer_host/render_message_filter.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/geolocation_permission_context.h"
+#include "content/public/common/geoposition.h"
+#include "content/common/geolocation_messages.h"
+
+namespace content {
+namespace {
+
+void NotifyArbitratorPermissionGranted() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GeolocationProviderImpl::GetInstance()->UserDidOptIntoLocationServices();
+}
+
+void SendGeolocationPermissionResponse(int render_process_id,
+ int render_view_id,
+ int bridge_id,
+ bool allowed) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderViewHostImpl* r =
+ RenderViewHostImpl::FromID(render_process_id, render_view_id);
+ if (!r)
+ return;
+ r->Send(new GeolocationMsg_PermissionSet(render_view_id, bridge_id, allowed));
+
+ if (allowed) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&NotifyArbitratorPermissionGranted));
+ }
+}
+
+class GeolocationDispatcherHostImpl : public GeolocationDispatcherHost {
+ public:
+ GeolocationDispatcherHostImpl(
+ int render_process_id,
+ GeolocationPermissionContext* geolocation_permission_context);
+
+ // GeolocationDispatcherHost
+ virtual bool OnMessageReceived(const IPC::Message& msg,
+ bool* msg_was_ok) OVERRIDE;
+
+ private:
+ virtual ~GeolocationDispatcherHostImpl();
+
+ void OnRequestPermission(int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame);
+ void OnCancelPermissionRequest(int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame);
+ void OnStartUpdating(int render_view_id,
+ const GURL& requesting_frame,
+ bool enable_high_accuracy);
+ void OnStopUpdating(int render_view_id);
+
+ // Updates the |location_arbitrator_| with the currently required update
+ // options, based on |renderer_high_accuracy_|.
+ void RefreshHighAccuracy();
+
+ void OnLocationUpdate(const Geoposition& position);
+
+ int render_process_id_;
+ scoped_refptr<GeolocationPermissionContext> geolocation_permission_context_;
+
+ // Iterated when sending location updates to renderer processes. The fan out
+ // to individual bridge IDs happens renderer side, in order to minimize
+ // context switches.
+ // Only used on the IO thread.
+ std::set<int> geolocation_renderer_ids_;
+ // Maps renderer_id to whether high accuracy is requestd for this particular
+ // bridge.
+ std::map<int, bool> renderer_high_accuracy_;
+ // Only set whilst we are registered with the arbitrator.
+ GeolocationProviderImpl* location_provider_;
+
+ GeolocationProviderImpl::LocationUpdateCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(GeolocationDispatcherHostImpl);
+};
+
+GeolocationDispatcherHostImpl::GeolocationDispatcherHostImpl(
+ int render_process_id,
+ GeolocationPermissionContext* geolocation_permission_context)
+ : render_process_id_(render_process_id),
+ geolocation_permission_context_(geolocation_permission_context),
+ location_provider_(NULL) {
+ callback_ = base::Bind(
+ &GeolocationDispatcherHostImpl::OnLocationUpdate, base::Unretained(this));
+ // This is initialized by ResourceMessageFilter. Do not add any non-trivial
+ // initialization here, defer to OnRegisterBridge which is triggered whenever
+ // a javascript geolocation object is actually initialized.
+}
+
+GeolocationDispatcherHostImpl::~GeolocationDispatcherHostImpl() {
+ if (location_provider_)
+ location_provider_->RemoveLocationUpdateCallback(callback_);
+}
+
+bool GeolocationDispatcherHostImpl::OnMessageReceived(
+ const IPC::Message& msg, bool* msg_was_ok) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ *msg_was_ok = true;
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(GeolocationDispatcherHostImpl, msg, *msg_was_ok)
+ IPC_MESSAGE_HANDLER(GeolocationHostMsg_CancelPermissionRequest,
+ OnCancelPermissionRequest)
+ IPC_MESSAGE_HANDLER(GeolocationHostMsg_RequestPermission,
+ OnRequestPermission)
+ IPC_MESSAGE_HANDLER(GeolocationHostMsg_StartUpdating, OnStartUpdating)
+ IPC_MESSAGE_HANDLER(GeolocationHostMsg_StopUpdating, OnStopUpdating)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void GeolocationDispatcherHostImpl::OnLocationUpdate(
+ const Geoposition& geoposition) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ for (std::set<int>::iterator it = geolocation_renderer_ids_.begin();
+ it != geolocation_renderer_ids_.end(); ++it) {
+ Send(new GeolocationMsg_PositionUpdated(*it, geoposition));
+ }
+}
+
+void GeolocationDispatcherHostImpl::OnRequestPermission(
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
+ << render_view_id << ":" << bridge_id;
+ if (geolocation_permission_context_.get()) {
+ geolocation_permission_context_->RequestGeolocationPermission(
+ render_process_id_,
+ render_view_id,
+ bridge_id,
+ requesting_frame,
+ base::Bind(&SendGeolocationPermissionResponse,
+ render_process_id_,
+ render_view_id,
+ bridge_id));
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SendGeolocationPermissionResponse, render_process_id_,
+ render_view_id, bridge_id, true));
+ }
+}
+
+void GeolocationDispatcherHostImpl::OnCancelPermissionRequest(
+ int render_view_id,
+ int bridge_id,
+ const GURL& requesting_frame) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
+ << render_view_id << ":" << bridge_id;
+ if (geolocation_permission_context_.get()) {
+ geolocation_permission_context_->CancelGeolocationPermissionRequest(
+ render_process_id_, render_view_id, bridge_id, requesting_frame);
+ }
+}
+
+void GeolocationDispatcherHostImpl::OnStartUpdating(
+ int render_view_id,
+ const GURL& requesting_frame,
+ bool enable_high_accuracy) {
+ // StartUpdating() can be invoked as a result of high-accuracy mode
+ // being enabled / disabled. No need to record the dispatcher again.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
+ << render_view_id;
+ if (!geolocation_renderer_ids_.count(render_view_id))
+ geolocation_renderer_ids_.insert(render_view_id);
+
+ renderer_high_accuracy_[render_view_id] = enable_high_accuracy;
+ RefreshHighAccuracy();
+}
+
+void GeolocationDispatcherHostImpl::OnStopUpdating(int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
+ << render_view_id;
+ if (renderer_high_accuracy_.erase(render_view_id))
+ RefreshHighAccuracy();
+
+ DCHECK_EQ(1U, geolocation_renderer_ids_.count(render_view_id));
+ geolocation_renderer_ids_.erase(render_view_id);
+}
+
+void GeolocationDispatcherHostImpl::RefreshHighAccuracy() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (renderer_high_accuracy_.empty()) {
+ if (location_provider_) {
+ location_provider_->RemoveLocationUpdateCallback(callback_);
+ location_provider_ = NULL;
+ }
+ } else {
+ if (!location_provider_)
+ location_provider_ = GeolocationProviderImpl::GetInstance();
+ // Re-add to re-establish our options, in case they changed.
+ bool use_high_accuracy = false;
+ std::map<int, bool>::iterator i = renderer_high_accuracy_.begin();
+ for (; i != renderer_high_accuracy_.end(); ++i) {
+ if (i->second) {
+ use_high_accuracy = true;
+ break;
+ }
+ }
+ location_provider_->AddLocationUpdateCallback(callback_, use_high_accuracy);
+ }
+}
+} // namespace
+
+
+// GeolocationDispatcherHost --------------------------------------------------
+
+// static
+GeolocationDispatcherHost* GeolocationDispatcherHost::New(
+ int render_process_id,
+ GeolocationPermissionContext* geolocation_permission_context) {
+ return new GeolocationDispatcherHostImpl(
+ render_process_id,
+ geolocation_permission_context);
+}
+
+GeolocationDispatcherHost::GeolocationDispatcherHost() {
+}
+
+GeolocationDispatcherHost::~GeolocationDispatcherHost() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/geolocation_dispatcher_host.h b/chromium/content/browser/geolocation/geolocation_dispatcher_host.h
new file mode 100644
index 00000000000..0631c8718f2
--- /dev/null
+++ b/chromium/content/browser/geolocation/geolocation_dispatcher_host.h
@@ -0,0 +1,31 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_DISPATCHER_HOST_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class GeolocationPermissionContext;
+
+// GeolocationDispatcherHost is a browser filter for Geolocation messages.
+// It's the complement of GeolocationDispatcher (owned by RenderView).
+class GeolocationDispatcherHost : public BrowserMessageFilter {
+ public:
+ static GeolocationDispatcherHost* New(
+ int render_process_id,
+ GeolocationPermissionContext* geolocation_permission_context);
+
+ protected:
+ GeolocationDispatcherHost();
+ virtual ~GeolocationDispatcherHost();
+
+ DISALLOW_COPY_AND_ASSIGN(GeolocationDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/geolocation/geolocation_provider_impl.cc b/chromium/content/browser/geolocation/geolocation_provider_impl.cc
new file mode 100644
index 00000000000..f24cb2dcb7a
--- /dev/null
+++ b/chromium/content/browser/geolocation/geolocation_provider_impl.cc
@@ -0,0 +1,236 @@
+// 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 "content/browser/geolocation/geolocation_provider_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/geolocation/location_arbitrator_impl.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+namespace {
+void OverrideLocationForTestingOnIOThread(
+ const Geoposition& position,
+ const base::Closure& completion_callback,
+ scoped_refptr<base::MessageLoopProxy> callback_loop) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GeolocationProviderImpl::GetInstance()->OverrideLocationForTesting(position);
+ callback_loop->PostTask(FROM_HERE, completion_callback);
+}
+} // namespace
+
+GeolocationProvider* GeolocationProvider::GetInstance() {
+ return GeolocationProviderImpl::GetInstance();
+}
+
+void GeolocationProvider::OverrideLocationForTesting(
+ const Geoposition& position,
+ const base::Closure& completion_callback) {
+ base::Closure closure = base::Bind(&OverrideLocationForTestingOnIOThread,
+ position,
+ completion_callback,
+ base::MessageLoopProxy::current());
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO))
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, closure);
+ else
+ closure.Run();
+}
+
+void GeolocationProviderImpl::AddLocationUpdateCallback(
+ const LocationUpdateCallback& callback, bool use_high_accuracy) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ bool found = false;
+ CallbackList::iterator i = callbacks_.begin();
+ for (; i != callbacks_.end(); ++i) {
+ if (i->first.Equals(callback)) {
+ i->second = use_high_accuracy;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ callbacks_.push_back(std::make_pair(callback, use_high_accuracy));
+
+ OnClientsChanged();
+ if (position_.Validate() ||
+ position_.error_code != Geoposition::ERROR_CODE_NONE) {
+ callback.Run(position_);
+ }
+}
+
+bool GeolocationProviderImpl::RemoveLocationUpdateCallback(
+ const LocationUpdateCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ bool removed = false;
+ CallbackList::iterator i = callbacks_.begin();
+ for (; i != callbacks_.end(); ++i) {
+ if (i->first.Equals(callback)) {
+ callbacks_.erase(i);
+ removed = true;
+ break;
+ }
+ }
+ if (removed)
+ OnClientsChanged();
+ return removed;
+}
+
+void GeolocationProviderImpl::UserDidOptIntoLocationServices() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ bool was_permission_granted = user_did_opt_into_location_services_;
+ user_did_opt_into_location_services_ = true;
+ if (IsRunning() && !was_permission_granted)
+ InformProvidersPermissionGranted();
+}
+
+bool GeolocationProviderImpl::LocationServicesOptedIn() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return user_did_opt_into_location_services_;
+}
+
+void GeolocationProviderImpl::OnLocationUpdate(const Geoposition& position) {
+ DCHECK(OnGeolocationThread());
+ // Will be true only in testing.
+ if (ignore_location_updates_)
+ return;
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&GeolocationProviderImpl::NotifyClients,
+ base::Unretained(this), position));
+}
+
+void GeolocationProviderImpl::OverrideLocationForTesting(
+ const Geoposition& position) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ position_ = position;
+ ignore_location_updates_ = true;
+ NotifyClients(position);
+}
+
+GeolocationProviderImpl* GeolocationProviderImpl::GetInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return Singleton<GeolocationProviderImpl>::get();
+}
+
+GeolocationProviderImpl::GeolocationProviderImpl()
+ : base::Thread("Geolocation"),
+ user_did_opt_into_location_services_(false),
+ ignore_location_updates_(false),
+ arbitrator_(NULL) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+GeolocationProviderImpl::~GeolocationProviderImpl() {
+ // All callbacks should have unregistered before this singleton is destructed.
+ DCHECK(callbacks_.empty());
+ Stop();
+ DCHECK(!arbitrator_);
+}
+
+bool GeolocationProviderImpl::OnGeolocationThread() const {
+ return base::MessageLoop::current() == message_loop();
+}
+
+void GeolocationProviderImpl::OnClientsChanged() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ base::Closure task;
+ if (callbacks_.empty()) {
+ DCHECK(IsRunning());
+ // We have no more observers, so we clear the cached geoposition so that
+ // when the next observer is added we will not provide a stale position.
+ position_ = Geoposition();
+ task = base::Bind(&GeolocationProviderImpl::StopProviders,
+ base::Unretained(this));
+ } else {
+ if (!IsRunning()) {
+ Start();
+ if (LocationServicesOptedIn())
+ InformProvidersPermissionGranted();
+ }
+ // Determine a set of options that satisfies all clients.
+ bool use_high_accuracy = false;
+ CallbackList::iterator i = callbacks_.begin();
+ for (; i != callbacks_.end(); ++i) {
+ if (i->second) {
+ use_high_accuracy = true;
+ break;
+ }
+ }
+
+ // Send the current options to the providers as they may have changed.
+ task = base::Bind(&GeolocationProviderImpl::StartProviders,
+ base::Unretained(this),
+ use_high_accuracy);
+ }
+
+ message_loop()->PostTask(FROM_HERE, task);
+}
+
+void GeolocationProviderImpl::StopProviders() {
+ DCHECK(OnGeolocationThread());
+ DCHECK(arbitrator_);
+ arbitrator_->StopProviders();
+}
+
+void GeolocationProviderImpl::StartProviders(bool use_high_accuracy) {
+ DCHECK(OnGeolocationThread());
+ DCHECK(arbitrator_);
+ arbitrator_->StartProviders(use_high_accuracy);
+}
+
+void GeolocationProviderImpl::InformProvidersPermissionGranted() {
+ DCHECK(IsRunning());
+ if (!OnGeolocationThread()) {
+ message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&GeolocationProviderImpl::InformProvidersPermissionGranted,
+ base::Unretained(this)));
+ return;
+ }
+ DCHECK(OnGeolocationThread());
+ DCHECK(arbitrator_);
+ arbitrator_->OnPermissionGranted();
+}
+
+void GeolocationProviderImpl::NotifyClients(const Geoposition& position) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(position.Validate() ||
+ position.error_code != Geoposition::ERROR_CODE_NONE);
+ position_ = position;
+ CallbackList::const_iterator it = callbacks_.begin();
+ while (it != callbacks_.end()) {
+ // Advance iterator before calling the observer to guard against synchronous
+ // unregister.
+ LocationUpdateCallback callback = it->first;
+ ++it;
+ callback.Run(position_);
+ }
+}
+
+void GeolocationProviderImpl::Init() {
+ DCHECK(OnGeolocationThread());
+ DCHECK(!arbitrator_);
+ arbitrator_ = CreateArbitrator();
+}
+
+void GeolocationProviderImpl::CleanUp() {
+ DCHECK(OnGeolocationThread());
+ delete arbitrator_;
+ arbitrator_ = NULL;
+}
+
+GeolocationArbitrator* GeolocationProviderImpl::CreateArbitrator() {
+ GeolocationArbitratorImpl::LocationUpdateCallback callback = base::Bind(
+ &GeolocationProviderImpl::OnLocationUpdate, base::Unretained(this));
+ return new GeolocationArbitratorImpl(callback);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/geolocation_provider_impl.h b/chromium/content/browser/geolocation/geolocation_provider_impl.h
new file mode 100644
index 00000000000..68e83a87968
--- /dev/null
+++ b/chromium/content/browser/geolocation/geolocation_provider_impl.h
@@ -0,0 +1,114 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_PROVIDER_IMPL_H_
+#define CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_PROVIDER_IMPL_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/threading/thread.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/geolocation_provider.h"
+#include "content/public/common/geoposition.h"
+
+template<typename Type> struct DefaultSingletonTraits;
+
+namespace content {
+class GeolocationArbitrator;
+class GeolocationProviderTest;
+
+// This is the main API to the geolocation subsystem. The application will hold
+// a single instance of this class and can register multiple clients to be
+// notified of location changes:
+// * Callbacks are registered by AddLocationUpdateCallback() and will keep
+// receiving updates until unregistered by RemoveLocationUpdateCallback().
+// The application must instantiate the GeolocationProvider on the IO thread and
+// must communicate with it on the same thread.
+// The underlying location arbitrator will only be enabled whilst there is at
+// least one registered observer or pending callback. The arbitrator and the
+// location providers it uses run on a separate Geolocation thread.
+class CONTENT_EXPORT GeolocationProviderImpl
+ : public NON_EXPORTED_BASE(GeolocationProvider),
+ public base::Thread {
+ public:
+ // GeolocationProvider implementation:
+ virtual void AddLocationUpdateCallback(const LocationUpdateCallback& callback,
+ bool use_high_accuracy) OVERRIDE;
+ virtual bool RemoveLocationUpdateCallback(
+ const LocationUpdateCallback& callback) OVERRIDE;
+ virtual void UserDidOptIntoLocationServices() OVERRIDE;
+
+ bool LocationServicesOptedIn() const;
+
+ // Overrides the location for automation/testing. Suppresses any further
+ // updates from the actual providers and sends an update with the overridden
+ // position to all registered clients.
+ void OverrideLocationForTesting(const Geoposition& override_position);
+
+ // Callback from the GeolocationArbitrator. Public for testing.
+ void OnLocationUpdate(const Geoposition& position);
+
+ // Gets a pointer to the singleton instance of the location relayer, which
+ // is in turn bound to the browser's global context objects. This must only be
+ // called on the IO thread so that the GeolocationProviderImpl is always
+ // instantiated on the same thread. Ownership is NOT returned.
+ static GeolocationProviderImpl* GetInstance();
+
+ protected:
+ friend struct DefaultSingletonTraits<GeolocationProviderImpl>;
+ GeolocationProviderImpl();
+ virtual ~GeolocationProviderImpl();
+
+ // Useful for injecting mock geolocation arbitrator in tests.
+ virtual GeolocationArbitrator* CreateArbitrator();
+
+ private:
+ typedef std::pair<LocationUpdateCallback, bool> LocationUpdateInfo;
+ typedef std::list<LocationUpdateInfo> CallbackList;
+
+ bool OnGeolocationThread() const;
+
+ // Start and stop providers as needed when clients are added or removed.
+ void OnClientsChanged();
+
+ // Stops the providers when there are no more registered clients. Note that
+ // once the Geolocation thread is started, it will stay alive (but sitting
+ // idle without any pending messages).
+ void StopProviders();
+
+ // Starts the geolocation providers or updates their options (delegates to
+ // arbitrator).
+ void StartProviders(bool use_high_accuracy);
+
+ // Updates the providers on the geolocation thread, which must be running.
+ void InformProvidersPermissionGranted();
+
+ // Notifies all registered clients that a position update is available.
+ void NotifyClients(const Geoposition& position);
+
+ // Thread
+ virtual void Init() OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ // Only used on the IO thread
+ CallbackList callbacks_;
+ bool user_did_opt_into_location_services_;
+ Geoposition position_;
+
+ // True only in testing, where we want to use a custom position.
+ bool ignore_location_updates_;
+
+ // Only to be used on the geolocation thread.
+ GeolocationArbitrator* arbitrator_;
+
+ DISALLOW_COPY_AND_ASSIGN(GeolocationProviderImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_GEOLOCATION_PROVIDER_IMPL_H_
diff --git a/chromium/content/browser/geolocation/geolocation_provider_unittest.cc b/chromium/content/browser/geolocation/geolocation_provider_unittest.cc
new file mode 100644
index 00000000000..47b397a9a90
--- /dev/null
+++ b/chromium/content/browser/geolocation/geolocation_provider_unittest.cc
@@ -0,0 +1,249 @@
+// 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 "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "content/browser/geolocation/geolocation_provider_impl.h"
+#include "content/browser/geolocation/mock_location_arbitrator.h"
+#include "content/public/browser/access_token_store.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::MakeMatcher;
+using testing::Matcher;
+using testing::MatcherInterface;
+using testing::MatchResultListener;
+
+namespace content {
+
+class LocationProviderForTestArbitrator : public GeolocationProviderImpl {
+ public:
+ LocationProviderForTestArbitrator() : mock_arbitrator_(NULL) {}
+ virtual ~LocationProviderForTestArbitrator() {}
+
+ // Only valid for use on the geolocation thread.
+ MockGeolocationArbitrator* mock_arbitrator() const {
+ return mock_arbitrator_;
+ }
+
+ protected:
+ // GeolocationProviderImpl implementation:
+ virtual GeolocationArbitrator* CreateArbitrator() OVERRIDE;
+
+ private:
+ MockGeolocationArbitrator* mock_arbitrator_;
+};
+
+GeolocationArbitrator* LocationProviderForTestArbitrator::CreateArbitrator() {
+ DCHECK(mock_arbitrator_ == NULL);
+ mock_arbitrator_ = new MockGeolocationArbitrator;
+ return mock_arbitrator_;
+}
+
+class GeolocationObserver {
+ public:
+ virtual ~GeolocationObserver() {}
+ virtual void OnLocationUpdate(const Geoposition& position) = 0;
+};
+
+class MockGeolocationObserver : public GeolocationObserver {
+ public:
+ MOCK_METHOD1(OnLocationUpdate, void(const Geoposition& position));
+};
+
+class AsyncMockGeolocationObserver : public MockGeolocationObserver {
+ public:
+ virtual void OnLocationUpdate(const Geoposition& position) OVERRIDE {
+ MockGeolocationObserver::OnLocationUpdate(position);
+ base::MessageLoop::current()->Quit();
+ }
+};
+
+class MockGeolocationCallbackWrapper {
+ public:
+ MOCK_METHOD1(Callback, void(const Geoposition& position));
+};
+
+class GeopositionEqMatcher
+ : public MatcherInterface<const Geoposition&> {
+ public:
+ explicit GeopositionEqMatcher(const Geoposition& expected)
+ : expected_(expected) {}
+
+ virtual bool MatchAndExplain(const Geoposition& actual,
+ MatchResultListener* listener) const OVERRIDE {
+ return actual.latitude == expected_.latitude &&
+ actual.longitude == expected_.longitude &&
+ actual.altitude == expected_.altitude &&
+ actual.accuracy == expected_.accuracy &&
+ actual.altitude_accuracy == expected_.altitude_accuracy &&
+ actual.heading == expected_.heading &&
+ actual.speed == expected_.speed &&
+ actual.timestamp == expected_.timestamp &&
+ actual.error_code == expected_.error_code &&
+ actual.error_message == expected_.error_message;
+ }
+
+ virtual void DescribeTo(::std::ostream* os) const OVERRIDE {
+ *os << "which matches the expected position";
+ }
+
+ virtual void DescribeNegationTo(::std::ostream* os) const OVERRIDE {
+ *os << "which does not match the expected position";
+ }
+
+ private:
+ Geoposition expected_;
+
+ DISALLOW_COPY_AND_ASSIGN(GeopositionEqMatcher);
+};
+
+Matcher<const Geoposition&> GeopositionEq(const Geoposition& expected) {
+ return MakeMatcher(new GeopositionEqMatcher(expected));
+}
+
+class GeolocationProviderTest : public testing::Test {
+ protected:
+ GeolocationProviderTest()
+ : message_loop_(),
+ io_thread_(BrowserThread::IO, &message_loop_),
+ provider_(new LocationProviderForTestArbitrator) {
+ }
+
+ virtual ~GeolocationProviderTest() {}
+
+ LocationProviderForTestArbitrator* provider() { return provider_.get(); }
+
+ // Called on test thread.
+ bool ProvidersStarted();
+ void SendMockLocation(const Geoposition& position);
+
+ private:
+ // Called on provider thread.
+ void GetProvidersStarted(bool* started);
+
+ base::MessageLoop message_loop_;
+ TestBrowserThread io_thread_;
+ scoped_ptr<LocationProviderForTestArbitrator> provider_;
+};
+
+
+bool GeolocationProviderTest::ProvidersStarted() {
+ DCHECK(provider_->IsRunning());
+ DCHECK(base::MessageLoop::current() == &message_loop_);
+ bool started;
+ provider_->message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GeolocationProviderTest::GetProvidersStarted,
+ base::Unretained(this),
+ &started),
+ base::MessageLoop::QuitClosure());
+ message_loop_.Run();
+ return started;
+}
+
+void GeolocationProviderTest::GetProvidersStarted(bool* started) {
+ DCHECK(base::MessageLoop::current() == provider_->message_loop());
+ *started = provider_->mock_arbitrator()->providers_started();
+}
+
+void GeolocationProviderTest::SendMockLocation(const Geoposition& position) {
+ DCHECK(provider_->IsRunning());
+ DCHECK(base::MessageLoop::current() == &message_loop_);
+ provider_->message_loop()
+ ->PostTask(FROM_HERE,
+ base::Bind(&GeolocationProviderImpl::OnLocationUpdate,
+ base::Unretained(provider_.get()),
+ position));
+}
+
+// Regression test for http://crbug.com/59377
+TEST_F(GeolocationProviderTest, OnPermissionGrantedWithoutObservers) {
+ EXPECT_FALSE(provider()->LocationServicesOptedIn());
+ provider()->UserDidOptIntoLocationServices();
+ EXPECT_TRUE(provider()->LocationServicesOptedIn());
+}
+
+TEST_F(GeolocationProviderTest, StartStop) {
+ EXPECT_FALSE(provider()->IsRunning());
+ GeolocationProviderImpl::LocationUpdateCallback null_callback;
+ provider()->AddLocationUpdateCallback(null_callback, false);
+ EXPECT_TRUE(provider()->IsRunning());
+ EXPECT_TRUE(ProvidersStarted());
+
+ provider()->RemoveLocationUpdateCallback(null_callback);
+ EXPECT_FALSE(ProvidersStarted());
+ EXPECT_TRUE(provider()->IsRunning());
+}
+
+TEST_F(GeolocationProviderTest, StalePositionNotSent) {
+ Geoposition first_position;
+ first_position.latitude = 12;
+ first_position.longitude = 34;
+ first_position.accuracy = 56;
+ first_position.timestamp = base::Time::Now();
+
+ AsyncMockGeolocationObserver first_observer;
+ GeolocationProviderImpl::LocationUpdateCallback first_callback = base::Bind(
+ &MockGeolocationObserver::OnLocationUpdate,
+ base::Unretained(&first_observer));
+ EXPECT_CALL(first_observer, OnLocationUpdate(GeopositionEq(first_position)));
+ provider()->AddLocationUpdateCallback(first_callback, false);
+ SendMockLocation(first_position);
+ base::MessageLoop::current()->Run();
+
+ provider()->RemoveLocationUpdateCallback(first_callback);
+
+ Geoposition second_position;
+ second_position.latitude = 13;
+ second_position.longitude = 34;
+ second_position.accuracy = 56;
+ second_position.timestamp = base::Time::Now();
+
+ AsyncMockGeolocationObserver second_observer;
+
+ // After adding a second observer, check that no unexpected position update
+ // is sent.
+ EXPECT_CALL(second_observer, OnLocationUpdate(testing::_)).Times(0);
+ GeolocationProviderImpl::LocationUpdateCallback second_callback = base::Bind(
+ &MockGeolocationObserver::OnLocationUpdate,
+ base::Unretained(&second_observer));
+ provider()->AddLocationUpdateCallback(second_callback, false);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The second observer should receive the new position now.
+ EXPECT_CALL(second_observer,
+ OnLocationUpdate(GeopositionEq(second_position)));
+ SendMockLocation(second_position);
+ base::MessageLoop::current()->Run();
+
+ provider()->RemoveLocationUpdateCallback(second_callback);
+ EXPECT_FALSE(ProvidersStarted());
+}
+
+TEST_F(GeolocationProviderTest, OverrideLocationForTesting) {
+ Geoposition position;
+ position.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ provider()->OverrideLocationForTesting(position);
+ // Adding an observer when the location is overridden should synchronously
+ // update the observer with our overridden position.
+ MockGeolocationObserver mock_observer;
+ EXPECT_CALL(mock_observer, OnLocationUpdate(GeopositionEq(position)));
+ GeolocationProviderImpl::LocationUpdateCallback callback = base::Bind(
+ &MockGeolocationObserver::OnLocationUpdate,
+ base::Unretained(&mock_observer));
+ provider()->AddLocationUpdateCallback(callback, false);
+ provider()->RemoveLocationUpdateCallback(callback);
+ // Wait for the providers to be stopped now that all clients are gone.
+ EXPECT_FALSE(ProvidersStarted());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/gps_location_provider_linux.cc b/chromium/content/browser/geolocation/gps_location_provider_linux.cc
new file mode 100644
index 00000000000..685c3c35aac
--- /dev/null
+++ b/chromium/content/browser/geolocation/gps_location_provider_linux.cc
@@ -0,0 +1,302 @@
+// 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 "content/browser/geolocation/gps_location_provider_linux.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+namespace {
+
+const int kGpsdReconnectRetryIntervalMillis = 10 * 1000;
+
+// As per http://gpsd.berlios.de/performance.html#id374524, poll twice per sec.
+const int kPollPeriodMovingMillis = 500;
+
+// Poll less frequently whilst stationary.
+const int kPollPeriodStationaryMillis = kPollPeriodMovingMillis * 3;
+
+// GPS reading must differ by more than this amount to be considered movement.
+const int kMovementThresholdMeters = 20;
+
+// This algorithm is reused from the corresponding code in the Gears project.
+// The arbitrary delta is decreased (Gears used 100 meters); if we need to
+// decrease it any further we'll likely want to do some smarter filtering to
+// remove GPS location jitter noise.
+bool PositionsDifferSiginificantly(const Geoposition& position_1,
+ const Geoposition& position_2) {
+ const bool pos_1_valid = position_1.Validate();
+ if (pos_1_valid != position_2.Validate())
+ return true;
+ if (!pos_1_valid) {
+ DCHECK(!position_2.Validate());
+ return false;
+ }
+ double delta = std::sqrt(
+ std::pow(std::fabs(position_1.latitude - position_2.latitude), 2) +
+ std::pow(std::fabs(position_1.longitude - position_2.longitude), 2));
+ // Convert to meters. 1 minute of arc of latitude (or longitude at the
+ // equator) is 1 nautical mile or 1852m.
+ delta *= 60 * 1852;
+ return delta > kMovementThresholdMeters;
+}
+
+} // namespace
+
+#if defined(USE_LIBGPS)
+
+// See http://crbug.com/103751.
+COMPILE_ASSERT(GPSD_API_MAJOR_VERSION == 5, GPSD_API_version_is_not_5);
+
+namespace {
+
+const char kLibGpsName[] = "libgps.so.20";
+
+} // namespace
+
+LibGps::LibGps()
+ : gps_data_(new gps_data_t),
+ is_open_(false) {
+}
+
+LibGps::~LibGps() {
+ Stop();
+}
+
+LibGps* LibGps::New() {
+ scoped_ptr<LibGps> libgps(new LibGps);
+ if (!libgps->libgps_loader_.Load(kLibGpsName))
+ return NULL;
+
+ return libgps.release();
+}
+
+bool LibGps::Start() {
+ if (is_open_)
+ return true;
+
+ errno = 0;
+ if (libgps_loader_.gps_open(GPSD_SHARED_MEMORY, 0, gps_data_.get()) != 0) {
+ // See gps.h NL_NOxxx for definition of gps_open() error numbers.
+ DLOG(WARNING) << "gps_open() failed " << errno;
+ return false;
+ }
+
+ is_open_ = true;
+ return true;
+}
+
+void LibGps::Stop() {
+ if (is_open_)
+ libgps_loader_.gps_close(gps_data_.get());
+ is_open_ = false;
+}
+
+bool LibGps::Read(Geoposition* position) {
+ DCHECK(position);
+ position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ if (!is_open_) {
+ DLOG(WARNING) << "No gpsd connection";
+ position->error_message = "No gpsd connection";
+ return false;
+ }
+
+ if (libgps_loader_.gps_read(gps_data_.get()) < 0) {
+ DLOG(WARNING) << "gps_read() fails";
+ position->error_message = "gps_read() fails";
+ return false;
+ }
+
+ if (!GetPositionIfFixed(position)) {
+ DLOG(WARNING) << "No fixed position";
+ position->error_message = "No fixed position";
+ return false;
+ }
+
+ position->error_code = Geoposition::ERROR_CODE_NONE;
+ position->timestamp = base::Time::Now();
+ if (!position->Validate()) {
+ // GetPositionIfFixed returned true, yet we've not got a valid fix.
+ // This shouldn't happen; something went wrong in the conversion.
+ NOTREACHED() << "Invalid position from GetPositionIfFixed: lat,long "
+ << position->latitude << "," << position->longitude
+ << " accuracy " << position->accuracy << " time "
+ << position->timestamp.ToDoubleT();
+ position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ position->error_message = "Bad fix from gps";
+ return false;
+ }
+ return true;
+}
+
+bool LibGps::GetPositionIfFixed(Geoposition* position) {
+ DCHECK(position);
+ if (gps_data_->status == STATUS_NO_FIX) {
+ DVLOG(2) << "Status_NO_FIX";
+ return false;
+ }
+
+ if (isnan(gps_data_->fix.latitude) || isnan(gps_data_->fix.longitude)) {
+ DVLOG(2) << "No valid lat/lon value";
+ return false;
+ }
+
+ position->latitude = gps_data_->fix.latitude;
+ position->longitude = gps_data_->fix.longitude;
+
+ if (!isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
+ position->accuracy = std::max(gps_data_->fix.epx, gps_data_->fix.epy);
+ } else if (isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
+ position->accuracy = gps_data_->fix.epy;
+ } else if (!isnan(gps_data_->fix.epx) && isnan(gps_data_->fix.epy)) {
+ position->accuracy = gps_data_->fix.epx;
+ } else {
+ // TODO(joth): Fixme. This is a workaround for http://crbug.com/99326
+ DVLOG(2) << "libgps reported accuracy NaN, forcing to zero";
+ position->accuracy = 0;
+ }
+
+ if (gps_data_->fix.mode == MODE_3D && !isnan(gps_data_->fix.altitude)) {
+ position->altitude = gps_data_->fix.altitude;
+ if (!isnan(gps_data_->fix.epv))
+ position->altitude_accuracy = gps_data_->fix.epv;
+ }
+
+ if (!isnan(gps_data_->fix.track))
+ position->heading = gps_data_->fix.track;
+ if (!isnan(gps_data_->fix.speed))
+ position->speed = gps_data_->fix.speed;
+ return true;
+}
+
+#else // !defined(USE_LIBGPS)
+
+// Stub implementation of LibGps.
+LibGps::LibGps() {
+}
+
+LibGps::~LibGps() {
+}
+
+LibGps* LibGps::New() {
+ return NULL;
+}
+
+bool LibGps::Start() {
+ return false;
+}
+
+void LibGps::Stop() {
+}
+
+bool LibGps::Read(Geoposition* position) {
+ return false;
+}
+
+bool LibGps::GetPositionIfFixed(Geoposition* position) {
+ return false;
+}
+
+#endif // !defined(USE_LIBGPS)
+
+GpsLocationProviderLinux::GpsLocationProviderLinux(LibGpsFactory libgps_factory)
+ : gpsd_reconnect_interval_millis_(kGpsdReconnectRetryIntervalMillis),
+ poll_period_moving_millis_(kPollPeriodMovingMillis),
+ poll_period_stationary_millis_(kPollPeriodStationaryMillis),
+ libgps_factory_(libgps_factory),
+ weak_factory_(this) {
+ DCHECK(libgps_factory_);
+}
+
+GpsLocationProviderLinux::~GpsLocationProviderLinux() {
+}
+
+bool GpsLocationProviderLinux::StartProvider(bool high_accuracy) {
+ if (!high_accuracy) {
+ StopProvider();
+ return true; // Not an error condition, so still return true.
+ }
+ if (gps_ != NULL) {
+ DCHECK(weak_factory_.HasWeakPtrs());
+ return true;
+ }
+ position_.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ gps_.reset(libgps_factory_());
+ if (gps_ == NULL) {
+ DLOG(WARNING) << "libgps could not be loaded";
+ return false;
+ }
+ ScheduleNextGpsPoll(0);
+ return true;
+}
+
+void GpsLocationProviderLinux::StopProvider() {
+ weak_factory_.InvalidateWeakPtrs();
+ gps_.reset();
+}
+
+void GpsLocationProviderLinux::GetPosition(Geoposition* position) {
+ DCHECK(position);
+ *position = position_;
+ DCHECK(position->Validate() ||
+ position->error_code != Geoposition::ERROR_CODE_NONE);
+}
+
+void GpsLocationProviderLinux::RequestRefresh() {
+ ScheduleNextGpsPoll(0);
+}
+
+void GpsLocationProviderLinux::OnPermissionGranted() {
+}
+
+void GpsLocationProviderLinux::DoGpsPollTask() {
+ if (!gps_->Start()) {
+ DLOG(WARNING) << "Couldn't start GPS provider.";
+ ScheduleNextGpsPoll(gpsd_reconnect_interval_millis_);
+ return;
+ }
+
+ Geoposition new_position;
+ if (!gps_->Read(&new_position)) {
+ ScheduleNextGpsPoll(poll_period_stationary_millis_);
+ return;
+ }
+
+ DCHECK(new_position.Validate() ||
+ new_position.error_code != Geoposition::ERROR_CODE_NONE);
+ const bool differ = PositionsDifferSiginificantly(position_, new_position);
+ ScheduleNextGpsPoll(differ ? poll_period_moving_millis_ :
+ poll_period_stationary_millis_);
+ if (differ || new_position.error_code != Geoposition::ERROR_CODE_NONE) {
+ // Update if the new location is interesting or we have an error to report.
+ position_ = new_position;
+ NotifyCallback(position_);
+ }
+}
+
+void GpsLocationProviderLinux::ScheduleNextGpsPoll(int interval) {
+ weak_factory_.InvalidateWeakPtrs();
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&GpsLocationProviderLinux::DoGpsPollTask,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(interval));
+}
+
+LocationProvider* NewSystemLocationProvider() {
+ return new GpsLocationProviderLinux(LibGps::New);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/gps_location_provider_linux.h b/chromium/content/browser/geolocation/gps_location_provider_linux.h
new file mode 100644
index 00000000000..76589379641
--- /dev/null
+++ b/chromium/content/browser/geolocation/gps_location_provider_linux.h
@@ -0,0 +1,105 @@
+// 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 file declares GPS providers that run on linux. Currently, just uses
+// the libgps (gpsd) API. Public for testing only - for normal usage this
+// header should not be required, as location_provider.h declares the needed
+// factory function.
+
+#ifndef CONTENT_BROWSER_GEOLOCATION_GPS_LOCATION_PROVIDER_LINUX_H_
+#define CONTENT_BROWSER_GEOLOCATION_GPS_LOCATION_PROVIDER_LINUX_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/geolocation/location_provider_base.h"
+#include "content/common/content_export.h"
+#include "content/public/common/geoposition.h"
+
+#if defined(USE_LIBGPS)
+#include "library_loaders/libgps.h"
+#endif
+
+struct gps_data_t;
+
+namespace content {
+
+// Defines a wrapper around the C libgps API (gps.h). Similar to the libgpsmm.h
+// API provided by that package.
+class CONTENT_EXPORT LibGps {
+ public:
+ virtual ~LibGps();
+ // Attempts to dynamically load the libgps.so library and returns NULL on
+ // failure.
+ static LibGps* New();
+
+ bool Start();
+ void Stop();
+ bool Read(content::Geoposition* position);
+
+ protected:
+ LibGps();
+
+ // Returns false if there is no fix available.
+ virtual bool GetPositionIfFixed(content::Geoposition* position);
+
+#if defined(USE_LIBGPS)
+ LibGpsLoader libgps_loader_;
+
+ scoped_ptr<gps_data_t> gps_data_;
+ bool is_open_;
+#endif // defined(USE_LIBGPS)
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LibGps);
+};
+
+// Location provider for Linux, that uses libgps/gpsd to obtain position fixes.
+class CONTENT_EXPORT GpsLocationProviderLinux : public LocationProviderBase {
+ public:
+ typedef LibGps* (*LibGpsFactory)();
+ // |factory| will be used to create the gpsd client library wrapper. (Note
+ // NewSystemLocationProvider() will use the default factory).
+ explicit GpsLocationProviderLinux(LibGpsFactory libgps_factory);
+ virtual ~GpsLocationProviderLinux();
+
+ void SetGpsdReconnectIntervalMillis(int value) {
+ gpsd_reconnect_interval_millis_ = value;
+ }
+ void SetPollPeriodMovingMillis(int value) {
+ poll_period_moving_millis_ = value;
+ }
+ void SetPollPeriodStationaryMillis(int value) {
+ poll_period_stationary_millis_ = value;
+ }
+
+ // LocationProvider
+ virtual bool StartProvider(bool high_accuracy) OVERRIDE;
+ virtual void StopProvider() OVERRIDE;
+ virtual void GetPosition(Geoposition* position) OVERRIDE;
+ virtual void RequestRefresh() OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+
+ private:
+ // Task which run in the child thread.
+ void DoGpsPollTask();
+
+ // Will schedule a poll; i.e. enqueue DoGpsPollTask deferred task.
+ void ScheduleNextGpsPoll(int interval);
+
+ int gpsd_reconnect_interval_millis_;
+ int poll_period_moving_millis_;
+ int poll_period_stationary_millis_;
+
+ const LibGpsFactory libgps_factory_;
+ scoped_ptr<LibGps> gps_;
+ Geoposition position_;
+
+ // Holder for the tasks which run on the thread; takes care of cleanup.
+ base::WeakPtrFactory<GpsLocationProviderLinux> weak_factory_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_GPS_LOCATION_PROVIDER_LINUX_H_
diff --git a/chromium/content/browser/geolocation/gps_location_provider_unittest_linux.cc b/chromium/content/browser/geolocation/gps_location_provider_unittest_linux.cc
new file mode 100644
index 00000000000..41967a45f96
--- /dev/null
+++ b/chromium/content/browser/geolocation/gps_location_provider_unittest_linux.cc
@@ -0,0 +1,208 @@
+// 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 "content/browser/browser_thread_impl.h"
+#include "content/browser/geolocation/gps_location_provider_linux.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/bind.h"
+
+namespace content {
+
+class MockLibGps : public LibGps {
+ public:
+ MockLibGps();
+ virtual ~MockLibGps();
+
+ virtual bool GetPositionIfFixed(Geoposition* position) OVERRIDE {
+ CHECK(position);
+ ++get_position_calls_;
+ *position = get_position_;
+ return get_position_ret_;
+ }
+
+ static int gps_open_stub(const char*, const char*, struct gps_data_t*) {
+ CHECK(g_instance_);
+ g_instance_->gps_open_calls_++;
+ return g_instance_->gps_open_ret_;
+ }
+
+ static int gps_close_stub(struct gps_data_t*) {
+ return 0;
+ }
+
+ static int gps_read_stub(struct gps_data_t*) {
+ CHECK(g_instance_);
+ g_instance_->gps_read_calls_++;
+ return g_instance_->gps_read_ret_;
+ }
+
+ int get_position_calls_;
+ bool get_position_ret_;
+ int gps_open_calls_;
+ int gps_open_ret_;
+ int gps_read_calls_;
+ int gps_read_ret_;
+ Geoposition get_position_;
+ static MockLibGps* g_instance_;
+};
+
+class GeolocationGpsProviderLinuxTests : public testing::Test {
+ public:
+ GeolocationGpsProviderLinuxTests();
+ virtual ~GeolocationGpsProviderLinuxTests();
+
+ static LibGps* NewMockLibGps() {
+ return new MockLibGps();
+ }
+ static LibGps* NoLibGpsFactory() {
+ return NULL;
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ BrowserThreadImpl ui_thread_;
+ scoped_ptr<LocationProvider> provider_;
+};
+
+void CheckValidPosition(const Geoposition& expected,
+ const Geoposition& actual) {
+ EXPECT_TRUE(actual.Validate());
+ EXPECT_DOUBLE_EQ(expected.latitude, actual.latitude);
+ EXPECT_DOUBLE_EQ(expected.longitude, actual.longitude);
+ EXPECT_DOUBLE_EQ(expected.accuracy, actual.accuracy);
+}
+
+void QuitMessageLoopAfterUpdate(const LocationProvider* provider,
+ const Geoposition& position) {
+ base::MessageLoop::current()->Quit();
+}
+
+MockLibGps* MockLibGps::g_instance_ = NULL;
+
+MockLibGps::MockLibGps()
+ : get_position_calls_(0),
+ get_position_ret_(true),
+ gps_open_calls_(0),
+ gps_open_ret_(0),
+ gps_read_calls_(0),
+ gps_read_ret_(0) {
+ get_position_.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ EXPECT_FALSE(g_instance_);
+ g_instance_ = this;
+#if defined(USE_LIBGPS)
+ libgps_loader_.gps_open = gps_open_stub;
+ libgps_loader_.gps_close = gps_close_stub;
+ libgps_loader_.gps_read = gps_read_stub;
+#endif // defined(USE_LIBGPS)
+}
+
+MockLibGps::~MockLibGps() {
+ EXPECT_EQ(this, g_instance_);
+ g_instance_ = NULL;
+}
+
+GeolocationGpsProviderLinuxTests::GeolocationGpsProviderLinuxTests()
+ : ui_thread_(BrowserThread::IO, &message_loop_),
+ provider_(new GpsLocationProviderLinux(NewMockLibGps)) {
+ provider_->SetUpdateCallback(base::Bind(&QuitMessageLoopAfterUpdate));
+}
+
+GeolocationGpsProviderLinuxTests::~GeolocationGpsProviderLinuxTests() {
+}
+
+TEST_F(GeolocationGpsProviderLinuxTests, NoLibGpsInstalled) {
+ provider_.reset(new GpsLocationProviderLinux(NoLibGpsFactory));
+ ASSERT_TRUE(provider_.get());
+ const bool ok = provider_->StartProvider(true);
+ EXPECT_FALSE(ok);
+ Geoposition position;
+ provider_->GetPosition(&position);
+ EXPECT_FALSE(position.Validate());
+ EXPECT_EQ(Geoposition::ERROR_CODE_POSITION_UNAVAILABLE, position.error_code);
+}
+
+#if defined(OS_CHROMEOS)
+
+TEST_F(GeolocationGpsProviderLinuxTests, GetPosition) {
+ ASSERT_TRUE(provider_.get());
+ const bool ok = provider_->StartProvider(true);
+ EXPECT_TRUE(ok);
+ ASSERT_TRUE(MockLibGps::g_instance_);
+ EXPECT_EQ(0, MockLibGps::g_instance_->get_position_calls_);
+ EXPECT_EQ(0, MockLibGps::g_instance_->gps_open_calls_);
+ EXPECT_EQ(0, MockLibGps::g_instance_->gps_read_calls_);
+ Geoposition position;
+ provider_->GetPosition(&position);
+ EXPECT_FALSE(position.Validate());
+ EXPECT_EQ(Geoposition::ERROR_CODE_POSITION_UNAVAILABLE, position.error_code);
+ MockLibGps::g_instance_->get_position_.error_code =
+ Geoposition::ERROR_CODE_NONE;
+ MockLibGps::g_instance_->get_position_.latitude = 4.5;
+ MockLibGps::g_instance_->get_position_.longitude = -34.1;
+ MockLibGps::g_instance_->get_position_.accuracy = 345;
+ MockLibGps::g_instance_->get_position_.timestamp =
+ base::Time::FromDoubleT(200);
+ EXPECT_TRUE(MockLibGps::g_instance_->get_position_.Validate());
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(1, MockLibGps::g_instance_->get_position_calls_);
+ EXPECT_EQ(1, MockLibGps::g_instance_->gps_open_calls_);
+ EXPECT_EQ(1, MockLibGps::g_instance_->gps_read_calls_);
+ provider_->GetPosition(&position);
+ CheckValidPosition(MockLibGps::g_instance_->get_position_, position);
+
+ // Movement. This will block for up to half a second.
+ MockLibGps::g_instance_->get_position_.latitude += 0.01;
+ base::MessageLoop::current()->Run();
+ provider_->GetPosition(&position);
+ EXPECT_EQ(2, MockLibGps::g_instance_->get_position_calls_);
+ EXPECT_EQ(1, MockLibGps::g_instance_->gps_open_calls_);
+ EXPECT_EQ(2, MockLibGps::g_instance_->gps_read_calls_);
+ CheckValidPosition(MockLibGps::g_instance_->get_position_, position);
+}
+
+void EnableGpsOpenCallback() {
+ CHECK(MockLibGps::g_instance_);
+ MockLibGps::g_instance_->gps_open_ret_ = 0;
+}
+
+TEST_F(GeolocationGpsProviderLinuxTests, LibGpsReconnect) {
+ // Setup gpsd reconnect interval to be 1000ms to speed up test.
+ GpsLocationProviderLinux* gps_provider =
+ static_cast<GpsLocationProviderLinux*>(provider_.get());
+ gps_provider->SetGpsdReconnectIntervalMillis(1000);
+ gps_provider->SetPollPeriodMovingMillis(200);
+ const bool ok = provider_->StartProvider(true);
+ EXPECT_TRUE(ok);
+ ASSERT_TRUE(MockLibGps::g_instance_);
+ // Let gps_open() fails, and so will LibGps::Start().
+ // Reconnect will happen in 1000ms.
+ MockLibGps::g_instance_->gps_open_ret_ = 1;
+ Geoposition position;
+ MockLibGps::g_instance_->get_position_.error_code =
+ Geoposition::ERROR_CODE_NONE;
+ MockLibGps::g_instance_->get_position_.latitude = 4.5;
+ MockLibGps::g_instance_->get_position_.longitude = -34.1;
+ MockLibGps::g_instance_->get_position_.accuracy = 345;
+ MockLibGps::g_instance_->get_position_.timestamp =
+ base::Time::FromDoubleT(200);
+ EXPECT_TRUE(MockLibGps::g_instance_->get_position_.Validate());
+ // This task makes gps_open() and LibGps::Start() to succeed after
+ // 1500ms.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&EnableGpsOpenCallback),
+ base::TimeDelta::FromMilliseconds(1500));
+ base::MessageLoop::current()->Run();
+ provider_->GetPosition(&position);
+ EXPECT_TRUE(position.Validate());
+ // 3 gps_open() calls are expected (2 failures and 1 success)
+ EXPECT_EQ(1, MockLibGps::g_instance_->get_position_calls_);
+ EXPECT_EQ(3, MockLibGps::g_instance_->gps_open_calls_);
+ EXPECT_EQ(1, MockLibGps::g_instance_->gps_read_calls_);
+}
+
+#endif // #if defined(OS_CHROMEOS)
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_api_adapter_android.cc b/chromium/content/browser/geolocation/location_api_adapter_android.cc
new file mode 100644
index 00000000000..3ab693b33d1
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_api_adapter_android.cc
@@ -0,0 +1,163 @@
+// 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 "content/browser/geolocation/location_api_adapter_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "content/browser/geolocation/location_provider_android.h"
+#include "jni/LocationProvider_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::CheckException;
+using base::android::ClearException;
+using content::AndroidLocationApiAdapter;
+
+static void NewLocationAvailable(JNIEnv* env, jclass,
+ jdouble latitude,
+ jdouble longitude,
+ jdouble time_stamp,
+ jboolean has_altitude, jdouble altitude,
+ jboolean has_accuracy, jdouble accuracy,
+ jboolean has_heading, jdouble heading,
+ jboolean has_speed, jdouble speed) {
+ AndroidLocationApiAdapter::OnNewLocationAvailable(latitude, longitude,
+ time_stamp, has_altitude, altitude, has_accuracy, accuracy,
+ has_heading, heading, has_speed, speed);
+}
+
+static void NewErrorAvailable(JNIEnv* env, jclass, jstring message) {
+ AndroidLocationApiAdapter::OnNewErrorAvailable(env, message);
+}
+
+namespace content {
+
+AndroidLocationApiAdapter::AndroidLocationApiAdapter()
+ : location_provider_(NULL) {
+}
+
+AndroidLocationApiAdapter::~AndroidLocationApiAdapter() {
+ CHECK(!location_provider_);
+ CHECK(!message_loop_.get());
+ CHECK(java_location_provider_android_object_.is_null());
+}
+
+bool AndroidLocationApiAdapter::Start(
+ LocationProviderAndroid* location_provider, bool high_accuracy) {
+ JNIEnv* env = AttachCurrentThread();
+ if (!location_provider_) {
+ location_provider_ = location_provider;
+ CHECK(java_location_provider_android_object_.is_null());
+ CreateJavaObject(env);
+ {
+ base::AutoLock lock(lock_);
+ CHECK(!message_loop_.get());
+ message_loop_ = base::MessageLoopProxy::current();
+ }
+ }
+ // At this point we should have all our pre-conditions ready, and they'd only
+ // change in Stop() which must be called on the same thread as here.
+ CHECK(location_provider_);
+ CHECK(message_loop_.get());
+ CHECK(!java_location_provider_android_object_.is_null());
+ // We'll start receiving notifications from java in the main thread looper
+ // until Stop() is called.
+ return Java_LocationProvider_start(env,
+ java_location_provider_android_object_.obj(), high_accuracy);
+}
+
+void AndroidLocationApiAdapter::Stop() {
+ if (!location_provider_) {
+ CHECK(!message_loop_.get());
+ CHECK(java_location_provider_android_object_.is_null());
+ return;
+ }
+
+ {
+ base::AutoLock lock(lock_);
+ message_loop_ = NULL;
+ }
+
+ location_provider_ = NULL;
+
+ JNIEnv* env = AttachCurrentThread();
+ Java_LocationProvider_stop(env, java_location_provider_android_object_.obj());
+ java_location_provider_android_object_.Reset();
+}
+
+// static
+void AndroidLocationApiAdapter::NotifyProviderNewGeoposition(
+ const Geoposition& geoposition) {
+ // Called on the geolocation thread, safe to access location_provider_ here.
+ if (GetInstance()->location_provider_) {
+ CHECK(GetInstance()->message_loop_->BelongsToCurrentThread());
+ GetInstance()->location_provider_->NotifyNewGeoposition(geoposition);
+ }
+}
+
+// static
+void AndroidLocationApiAdapter::OnNewLocationAvailable(
+ double latitude, double longitude, double time_stamp,
+ bool has_altitude, double altitude,
+ bool has_accuracy, double accuracy,
+ bool has_heading, double heading,
+ bool has_speed, double speed) {
+ Geoposition position;
+ position.latitude = latitude;
+ position.longitude = longitude;
+ position.timestamp = base::Time::FromDoubleT(time_stamp);
+ if (has_altitude)
+ position.altitude = altitude;
+ if (has_accuracy)
+ position.accuracy = accuracy;
+ if (has_heading)
+ position.heading = heading;
+ if (has_speed)
+ position.speed = speed;
+ GetInstance()->OnNewGeopositionInternal(position);
+}
+
+// static
+void AndroidLocationApiAdapter::OnNewErrorAvailable(JNIEnv* env,
+ jstring message) {
+ Geoposition position_error;
+ position_error.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ position_error.error_message =
+ base::android::ConvertJavaStringToUTF8(env, message);
+ GetInstance()->OnNewGeopositionInternal(position_error);
+}
+
+// static
+AndroidLocationApiAdapter* AndroidLocationApiAdapter::GetInstance() {
+ return Singleton<AndroidLocationApiAdapter>::get();
+}
+
+// static
+bool AndroidLocationApiAdapter::RegisterGeolocationService(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void AndroidLocationApiAdapter::CreateJavaObject(JNIEnv* env) {
+ // Create the Java AndroidLocationProvider object.
+ java_location_provider_android_object_.Reset(
+ Java_LocationProvider_create(env,
+ base::android::GetApplicationContext()));
+ CHECK(!java_location_provider_android_object_.is_null());
+}
+
+void AndroidLocationApiAdapter::OnNewGeopositionInternal(
+ const Geoposition& geoposition) {
+ base::AutoLock lock(lock_);
+ if (!message_loop_.get())
+ return;
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &AndroidLocationApiAdapter::NotifyProviderNewGeoposition,
+ geoposition));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_api_adapter_android.h b/chromium/content/browser/geolocation/location_api_adapter_android.h
new file mode 100644
index 00000000000..f81ac93826b
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_api_adapter_android.h
@@ -0,0 +1,81 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_LOCATION_API_ADAPTER_ANDROID_H_
+#define CONTENT_BROWSER_GEOLOCATION_LOCATION_API_ADAPTER_ANDROID_H_
+
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+
+namespace content {
+class LocationProviderAndroid;
+struct Geoposition;
+
+// Interacts with JNI and reports back to AndroidLocationProvider.
+// This class creates a LocationProvider java object and listens for
+// updates.
+// The simplified flow is:
+// GeolocationProvider runs in a Geolocation Thread and fetches geolocation data
+// from a LocationProvider.
+// AndroidLocationProvider access a singleton AndroidLocationApiAdapter
+// AndroidLocationApiAdapter calls via JNI and uses the main thread Looper
+// in the java side to listen for location updates. We then bounce these updates
+// to the Geolocation thread.
+// Note that AndroidLocationApiAdapter is a singleton and there's at most only
+// one AndroidLocationProvider that has called Start().
+class AndroidLocationApiAdapter {
+ public:
+ // Starts the underlying location provider, returns true if successful.
+ // Called on the Geolocation thread.
+ bool Start(LocationProviderAndroid* location_provider, bool high_accuracy);
+ // Stops the underlying location provider.
+ // Called on the Geolocation thread.
+ void Stop();
+
+ // Returns our singleton.
+ static AndroidLocationApiAdapter* GetInstance();
+
+ // Called when initializing chrome_view to obtain a pointer to the java class.
+ static bool RegisterGeolocationService(JNIEnv* env);
+
+ // Called by JNI on main thread looper.
+ static void OnNewLocationAvailable(double latitude,
+ double longitude,
+ double time_stamp,
+ bool has_altitude, double altitude,
+ bool has_accuracy, double accuracy,
+ bool has_heading, double heading,
+ bool has_speed, double speed);
+ static void OnNewErrorAvailable(JNIEnv* env, jstring message);
+
+ private:
+ friend struct DefaultSingletonTraits<AndroidLocationApiAdapter>;
+ AndroidLocationApiAdapter();
+ ~AndroidLocationApiAdapter();
+
+ void CreateJavaObject(JNIEnv* env);
+
+ // Called on the JNI main thread looper.
+ void OnNewGeopositionInternal(const Geoposition& geoposition);
+
+ /// Called on the Geolocation thread.
+ static void NotifyProviderNewGeoposition(const Geoposition& geoposition);
+
+ base::android::ScopedJavaGlobalRef<jobject>
+ java_location_provider_android_object_;
+ LocationProviderAndroid* location_provider_;
+
+ // Guards against the following member which is accessed on Geolocation
+ // thread and the JNI main thread looper.
+ base::Lock lock_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_LOCATION_API_ADAPTER_ANDROID_H_
diff --git a/chromium/content/browser/geolocation/location_arbitrator.h b/chromium/content/browser/geolocation/location_arbitrator.h
new file mode 100644
index 00000000000..4bf82b03ac5
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_arbitrator.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_H_
+#define CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_H_
+
+#include "content/common/content_export.h"
+
+namespace content {
+
+// This class is responsible for handling updates from multiple underlying
+// providers and resolving them to a single 'best' location fix at any given
+// moment.
+class CONTENT_EXPORT GeolocationArbitrator {
+public:
+ virtual ~GeolocationArbitrator() {};
+
+ // See more details in geolocation_provider.
+ virtual void StartProviders(bool use_high_accuracy) = 0;
+ virtual void StopProviders() = 0;
+
+ // Called everytime permission is granted to a page for using geolocation.
+ // This may either be through explicit user action (e.g. responding to the
+ // infobar prompt) or inferred from a persisted site permission.
+ // The arbitrator will inform all providers of this, which may in turn use
+ // this information to modify their internal policy.
+ virtual void OnPermissionGranted() = 0;
+
+ // Returns true if this arbitrator has received at least one call to
+ // OnPermissionGranted().
+ virtual bool HasPermissionBeenGranted() const = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_IMPL_H_
diff --git a/chromium/content/browser/geolocation/location_arbitrator_impl.cc b/chromium/content/browser/geolocation/location_arbitrator_impl.cc
new file mode 100644
index 00000000000..4befee2f4f8
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_arbitrator_impl.cc
@@ -0,0 +1,204 @@
+// 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 "content/browser/geolocation/location_arbitrator_impl.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "content/browser/geolocation/network_location_provider.h"
+#include "content/public/browser/access_token_store.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+const char* kDefaultNetworkProviderUrl =
+ "https://www.googleapis.com/geolocation/v1/geolocate";
+} // namespace
+
+// To avoid oscillations, set this to twice the expected update interval of a
+// a GPS-type location provider (in case it misses a beat) plus a little.
+const int64 GeolocationArbitratorImpl::kFixStaleTimeoutMilliseconds =
+ 11 * base::Time::kMillisecondsPerSecond;
+
+GeolocationArbitratorImpl::GeolocationArbitratorImpl(
+ const LocationUpdateCallback& callback)
+ : callback_(callback),
+ provider_callback_(
+ base::Bind(&GeolocationArbitratorImpl::LocationUpdateAvailable,
+ base::Unretained(this))),
+ position_provider_(NULL),
+ is_permission_granted_(false),
+ is_running_(false) {
+}
+
+GeolocationArbitratorImpl::~GeolocationArbitratorImpl() {
+}
+
+GURL GeolocationArbitratorImpl::DefaultNetworkProviderURL() {
+ return GURL(kDefaultNetworkProviderUrl);
+}
+
+void GeolocationArbitratorImpl::OnPermissionGranted() {
+ is_permission_granted_ = true;
+ for (ScopedVector<LocationProvider>::iterator i = providers_.begin();
+ i != providers_.end(); ++i) {
+ (*i)->OnPermissionGranted();
+ }
+}
+
+void GeolocationArbitratorImpl::StartProviders(bool use_high_accuracy) {
+ // Stash options as OnAccessTokenStoresLoaded has not yet been called.
+ is_running_ = true;
+ use_high_accuracy_ = use_high_accuracy;
+ if (providers_.empty()) {
+ DCHECK(DefaultNetworkProviderURL().is_valid());
+ GetAccessTokenStore()->LoadAccessTokens(
+ base::Bind(&GeolocationArbitratorImpl::OnAccessTokenStoresLoaded,
+ base::Unretained(this)));
+ } else {
+ DoStartProviders();
+ }
+}
+
+void GeolocationArbitratorImpl::DoStartProviders() {
+ for (ScopedVector<LocationProvider>::iterator i = providers_.begin();
+ i != providers_.end(); ++i) {
+ (*i)->StartProvider(use_high_accuracy_);
+ }
+}
+
+void GeolocationArbitratorImpl::StopProviders() {
+ // Reset the reference location state (provider+position)
+ // so that future starts use fresh locations from
+ // the newly constructed providers.
+ position_provider_ = NULL;
+ position_ = Geoposition();
+
+ providers_.clear();
+ is_running_ = false;
+}
+
+void GeolocationArbitratorImpl::OnAccessTokenStoresLoaded(
+ AccessTokenStore::AccessTokenSet access_token_set,
+ net::URLRequestContextGetter* context_getter) {
+ if (!is_running_ || !providers_.empty()) {
+ // A second StartProviders() call may have arrived before the first
+ // completed.
+ return;
+ }
+ // If there are no access tokens, boot strap it with the default server URL.
+ if (access_token_set.empty())
+ access_token_set[DefaultNetworkProviderURL()];
+ for (AccessTokenStore::AccessTokenSet::iterator i =
+ access_token_set.begin();
+ i != access_token_set.end(); ++i) {
+ RegisterProvider(
+ NewNetworkLocationProvider(
+ GetAccessTokenStore(), context_getter,
+ i->first, i->second));
+ }
+
+ LocationProvider* provider =
+ GetContentClient()->browser()->OverrideSystemLocationProvider();
+ if (!provider)
+ provider = NewSystemLocationProvider();
+ RegisterProvider(provider);
+ DoStartProviders();
+}
+
+void GeolocationArbitratorImpl::RegisterProvider(
+ LocationProvider* provider) {
+ if (!provider)
+ return;
+ provider->SetUpdateCallback(provider_callback_);
+ if (is_permission_granted_)
+ provider->OnPermissionGranted();
+ providers_.push_back(provider);
+}
+
+void GeolocationArbitratorImpl::LocationUpdateAvailable(
+ const LocationProvider* provider,
+ const Geoposition& new_position) {
+ DCHECK(new_position.Validate() ||
+ new_position.error_code != Geoposition::ERROR_CODE_NONE);
+ if (!IsNewPositionBetter(position_, new_position,
+ provider == position_provider_))
+ return;
+ position_provider_ = provider;
+ position_ = new_position;
+ callback_.Run(position_);
+}
+
+AccessTokenStore* GeolocationArbitratorImpl::NewAccessTokenStore() {
+ return GetContentClient()->browser()->CreateAccessTokenStore();
+}
+
+AccessTokenStore* GeolocationArbitratorImpl::GetAccessTokenStore() {
+ if (!access_token_store_.get())
+ access_token_store_ = NewAccessTokenStore();
+ return access_token_store_.get();
+}
+
+LocationProvider* GeolocationArbitratorImpl::NewNetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token) {
+#if defined(OS_ANDROID)
+ // Android uses its own SystemLocationProvider.
+ return NULL;
+#else
+ return new NetworkLocationProvider(access_token_store, context, url,
+ access_token);
+#endif
+}
+
+LocationProvider* GeolocationArbitratorImpl::NewSystemLocationProvider() {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ return NULL;
+#else
+ return content::NewSystemLocationProvider();
+#endif
+}
+
+base::Time GeolocationArbitratorImpl::GetTimeNow() const {
+ return base::Time::Now();
+}
+
+bool GeolocationArbitratorImpl::IsNewPositionBetter(
+ const Geoposition& old_position, const Geoposition& new_position,
+ bool from_same_provider) const {
+ // Updates location_info if it's better than what we currently have,
+ // or if it's a newer update from the same provider.
+ if (!old_position.Validate()) {
+ // Older location wasn't locked.
+ return true;
+ }
+ if (new_position.Validate()) {
+ // New location is locked, let's check if it's any better.
+ if (old_position.accuracy >= new_position.accuracy) {
+ // Accuracy is better.
+ return true;
+ } else if (from_same_provider) {
+ // Same provider, fresher location.
+ return true;
+ } else if ((GetTimeNow() - old_position.timestamp).InMilliseconds() >
+ kFixStaleTimeoutMilliseconds) {
+ // Existing fix is stale.
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GeolocationArbitratorImpl::HasPermissionBeenGranted() const {
+ return is_permission_granted_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_arbitrator_impl.h b/chromium/content/browser/geolocation/location_arbitrator_impl.h
new file mode 100644
index 00000000000..91ffa0c356f
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_arbitrator_impl.h
@@ -0,0 +1,108 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_IMPL_H_
+#define CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_IMPL_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "content/browser/geolocation/location_arbitrator.h"
+#include "content/common/content_export.h"
+#include "content/port/browser/location_provider.h"
+#include "content/public/browser/access_token_store.h"
+#include "content/public/common/geoposition.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+class AccessTokenStore;
+class LocationProvider;
+
+// This class is responsible for handling updates from multiple underlying
+// providers and resolving them to a single 'best' location fix at any given
+// moment.
+class CONTENT_EXPORT GeolocationArbitratorImpl
+ : public GeolocationArbitrator {
+ public:
+ // Number of milliseconds newer a location provider has to be that it's worth
+ // switching to this location provider on the basis of it being fresher
+ // (regardles of relative accuracy). Public for tests.
+ static const int64 kFixStaleTimeoutMilliseconds;
+
+ typedef base::Callback<void(const Geoposition&)> LocationUpdateCallback;
+
+ explicit GeolocationArbitratorImpl(const LocationUpdateCallback& callback);
+ virtual ~GeolocationArbitratorImpl();
+
+ static GURL DefaultNetworkProviderURL();
+
+ // GeolocationArbitrator
+ virtual void StartProviders(bool use_high_accuracy) OVERRIDE;
+ virtual void StopProviders() OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+ virtual bool HasPermissionBeenGranted() const OVERRIDE;
+
+ protected:
+ AccessTokenStore* GetAccessTokenStore();
+
+ // These functions are useful for injection of dependencies in derived
+ // testing classes.
+ virtual AccessTokenStore* NewAccessTokenStore();
+ virtual LocationProvider* NewNetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token);
+ virtual LocationProvider* NewSystemLocationProvider();
+ virtual base::Time GetTimeNow() const;
+
+ private:
+ // Takes ownership of |provider| on entry; it will either be added to
+ // |providers_| or deleted on error (e.g. it fails to start).
+ void RegisterProvider(LocationProvider* provider);
+ void OnAccessTokenStoresLoaded(
+ AccessTokenStore::AccessTokenSet access_token_store,
+ net::URLRequestContextGetter* context_getter);
+ void DoStartProviders();
+
+ // The providers call this function when a new position is available.
+ void LocationUpdateAvailable(const LocationProvider* provider,
+ const Geoposition& new_position);
+
+ // Returns true if |new_position| is an improvement over |old_position|.
+ // Set |from_same_provider| to true if both the positions came from the same
+ // provider.
+ bool IsNewPositionBetter(const Geoposition& old_position,
+ const Geoposition& new_position,
+ bool from_same_provider) const;
+
+ scoped_refptr<AccessTokenStore> access_token_store_;
+ LocationUpdateCallback callback_;
+ LocationProvider::LocationProviderUpdateCallback provider_callback_;
+ ScopedVector<LocationProvider> providers_;
+ bool use_high_accuracy_;
+ // The provider which supplied the current |position_|
+ const LocationProvider* position_provider_;
+ bool is_permission_granted_;
+ // The current best estimate of our position.
+ Geoposition position_;
+
+ // Tracks whether providers should be running.
+ bool is_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(GeolocationArbitratorImpl);
+};
+
+// Factory functions for the various types of location provider to abstract
+// over the platform-dependent implementations.
+LocationProvider* NewSystemLocationProvider();
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_LOCATION_ARBITRATOR_IMPL_H_
diff --git a/chromium/content/browser/geolocation/location_arbitrator_impl_unittest.cc b/chromium/content/browser/geolocation/location_arbitrator_impl_unittest.cc
new file mode 100644
index 00000000000..ce2d1e27ad0
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_arbitrator_impl_unittest.cc
@@ -0,0 +1,330 @@
+// 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 "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/geolocation/fake_access_token_store.h"
+#include "content/browser/geolocation/location_arbitrator_impl.h"
+#include "content/browser/geolocation/mock_location_provider.h"
+#include "content/public/common/geoposition.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::NiceMock;
+
+namespace content {
+
+class MockLocationObserver {
+ public:
+ // Need a vtable for GMock.
+ virtual ~MockLocationObserver() {}
+ void InvalidateLastPosition() {
+ last_position_.latitude = 100;
+ last_position_.error_code = Geoposition::ERROR_CODE_NONE;
+ ASSERT_FALSE(last_position_.Validate());
+ }
+ // Delegate
+ void OnLocationUpdate(const Geoposition& position) {
+ last_position_ = position;
+ }
+
+ Geoposition last_position_;
+};
+
+double g_fake_time_now_secs = 1;
+
+base::Time GetTimeNowForTest() {
+ return base::Time::FromDoubleT(g_fake_time_now_secs);
+}
+
+void AdvanceTimeNow(const base::TimeDelta& delta) {
+ g_fake_time_now_secs += delta.InSecondsF();
+}
+
+void SetPositionFix(MockLocationProvider* provider,
+ double latitude,
+ double longitude,
+ double accuracy) {
+ Geoposition position;
+ position.error_code = Geoposition::ERROR_CODE_NONE;
+ position.latitude = latitude;
+ position.longitude = longitude;
+ position.accuracy = accuracy;
+ position.timestamp = GetTimeNowForTest();
+ ASSERT_TRUE(position.Validate());
+ provider->HandlePositionChanged(position);
+}
+
+void SetReferencePosition(MockLocationProvider* provider) {
+ SetPositionFix(provider, 51.0, -0.1, 400);
+}
+
+namespace {
+
+class TestingGeolocationArbitrator : public GeolocationArbitratorImpl {
+ public:
+ TestingGeolocationArbitrator(
+ const GeolocationArbitratorImpl::LocationUpdateCallback& callback,
+ AccessTokenStore* access_token_store)
+ : GeolocationArbitratorImpl(callback),
+ cell_(NULL),
+ gps_(NULL),
+ access_token_store_(access_token_store) {
+ }
+
+ virtual base::Time GetTimeNow() const OVERRIDE {
+ return GetTimeNowForTest();
+ }
+
+ virtual AccessTokenStore* NewAccessTokenStore() OVERRIDE {
+ return access_token_store_.get();
+ }
+
+ virtual LocationProvider* NewNetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token) OVERRIDE {
+ return new MockLocationProvider(&cell_);
+ }
+
+ virtual LocationProvider* NewSystemLocationProvider() OVERRIDE {
+ return new MockLocationProvider(&gps_);
+ }
+
+ // Two location providers, with nice short names to make the tests more
+ // readable. Note |gps_| will only be set when there is a high accuracy
+ // observer registered (and |cell_| when there's at least one observer of any
+ // type).
+ MockLocationProvider* cell_;
+ MockLocationProvider* gps_;
+ scoped_refptr<AccessTokenStore> access_token_store_;
+};
+
+} // namespace
+
+class GeolocationLocationArbitratorTest : public testing::Test {
+ protected:
+ // testing::Test
+ virtual void SetUp() {
+ access_token_store_ = new NiceMock<FakeAccessTokenStore>;
+ observer_.reset(new MockLocationObserver);
+ GeolocationArbitratorImpl::LocationUpdateCallback callback =
+ base::Bind(&MockLocationObserver::OnLocationUpdate,
+ base::Unretained(observer_.get()));
+ arbitrator_.reset(new TestingGeolocationArbitrator(
+ callback, access_token_store_.get()));
+ }
+
+ // testing::Test
+ virtual void TearDown() {
+ }
+
+ void CheckLastPositionInfo(double latitude,
+ double longitude,
+ double accuracy) {
+ Geoposition geoposition = observer_->last_position_;
+ EXPECT_TRUE(geoposition.Validate());
+ EXPECT_DOUBLE_EQ(latitude, geoposition.latitude);
+ EXPECT_DOUBLE_EQ(longitude, geoposition.longitude);
+ EXPECT_DOUBLE_EQ(accuracy, geoposition.accuracy);
+ }
+
+ base::TimeDelta SwitchOnFreshnessCliff() {
+ // Add 1, to ensure it meets any greater-than test.
+ return base::TimeDelta::FromMilliseconds(
+ GeolocationArbitratorImpl::kFixStaleTimeoutMilliseconds + 1);
+ }
+
+ MockLocationProvider* cell() {
+ return arbitrator_->cell_;
+ }
+
+ MockLocationProvider* gps() {
+ return arbitrator_->gps_;
+ }
+
+ scoped_refptr<FakeAccessTokenStore> access_token_store_;
+ scoped_ptr<MockLocationObserver> observer_;
+ scoped_ptr<TestingGeolocationArbitrator> arbitrator_;
+ base::MessageLoop loop_;
+};
+
+TEST_F(GeolocationLocationArbitratorTest, CreateDestroy) {
+ EXPECT_TRUE(access_token_store_.get());
+ EXPECT_TRUE(arbitrator_ != NULL);
+ arbitrator_.reset();
+ SUCCEED();
+}
+
+TEST_F(GeolocationLocationArbitratorTest, OnPermissionGranted) {
+ EXPECT_FALSE(arbitrator_->HasPermissionBeenGranted());
+ arbitrator_->OnPermissionGranted();
+ EXPECT_TRUE(arbitrator_->HasPermissionBeenGranted());
+ // Can't check the provider has been notified without going through the
+ // motions to create the provider (see next test).
+ EXPECT_FALSE(cell());
+ EXPECT_FALSE(gps());
+}
+
+TEST_F(GeolocationLocationArbitratorTest, NormalUsage) {
+ ASSERT_TRUE(access_token_store_.get());
+ ASSERT_TRUE(arbitrator_ != NULL);
+
+ EXPECT_FALSE(cell());
+ EXPECT_FALSE(gps());
+ arbitrator_->StartProviders(false);
+
+ EXPECT_TRUE(access_token_store_->access_token_set_.empty());
+ EXPECT_TRUE(access_token_store_->access_token_set_.empty());
+
+ access_token_store_->NotifyDelegateTokensLoaded();
+ ASSERT_TRUE(cell());
+ EXPECT_TRUE(gps());
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, cell()->state_);
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, gps()->state_);
+ EXPECT_FALSE(observer_->last_position_.Validate());
+ EXPECT_EQ(Geoposition::ERROR_CODE_NONE,
+ observer_->last_position_.error_code);
+
+ SetReferencePosition(cell());
+
+ EXPECT_TRUE(observer_->last_position_.Validate() ||
+ observer_->last_position_.error_code !=
+ Geoposition::ERROR_CODE_NONE);
+ EXPECT_EQ(cell()->position_.latitude,
+ observer_->last_position_.latitude);
+
+ EXPECT_FALSE(cell()->is_permission_granted_);
+ EXPECT_FALSE(arbitrator_->HasPermissionBeenGranted());
+ arbitrator_->OnPermissionGranted();
+ EXPECT_TRUE(arbitrator_->HasPermissionBeenGranted());
+ EXPECT_TRUE(cell()->is_permission_granted_);
+}
+
+TEST_F(GeolocationLocationArbitratorTest, SetObserverOptions) {
+ arbitrator_->StartProviders(false);
+ access_token_store_->NotifyDelegateTokensLoaded();
+ ASSERT_TRUE(cell());
+ ASSERT_TRUE(gps());
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, cell()->state_);
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, gps()->state_);
+ SetReferencePosition(cell());
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, cell()->state_);
+ EXPECT_EQ(MockLocationProvider::LOW_ACCURACY, gps()->state_);
+ arbitrator_->StartProviders(true);
+ EXPECT_EQ(MockLocationProvider::HIGH_ACCURACY, cell()->state_);
+ EXPECT_EQ(MockLocationProvider::HIGH_ACCURACY, gps()->state_);
+}
+
+TEST_F(GeolocationLocationArbitratorTest, Arbitration) {
+ arbitrator_->StartProviders(false);
+ access_token_store_->NotifyDelegateTokensLoaded();
+ ASSERT_TRUE(cell());
+ ASSERT_TRUE(gps());
+
+ SetPositionFix(cell(), 1, 2, 150);
+
+ // First position available
+ EXPECT_TRUE(observer_->last_position_.Validate());
+ CheckLastPositionInfo(1, 2, 150);
+
+ SetPositionFix(gps(), 3, 4, 50);
+
+ // More accurate fix available
+ CheckLastPositionInfo(3, 4, 50);
+
+ SetPositionFix(cell(), 5, 6, 150);
+
+ // New fix is available but it's less accurate, older fix should be kept.
+ CheckLastPositionInfo(3, 4, 50);
+
+ // Advance time, and notify once again
+ AdvanceTimeNow(SwitchOnFreshnessCliff());
+ cell()->HandlePositionChanged(cell()->position_);
+
+ // New fix is available, less accurate but fresher
+ CheckLastPositionInfo(5, 6, 150);
+
+ // Advance time, and set a low accuracy position
+ AdvanceTimeNow(SwitchOnFreshnessCliff());
+ SetPositionFix(cell(), 5.676731, 139.629385, 1000);
+ CheckLastPositionInfo(5.676731, 139.629385, 1000);
+
+ // 15 secs later, step outside. Switches to gps signal.
+ AdvanceTimeNow(base::TimeDelta::FromSeconds(15));
+ SetPositionFix(gps(), 3.5676457, 139.629198, 50);
+ CheckLastPositionInfo(3.5676457, 139.629198, 50);
+
+ // 5 mins later switch cells while walking. Stay on gps.
+ AdvanceTimeNow(base::TimeDelta::FromMinutes(5));
+ SetPositionFix(cell(), 3.567832, 139.634648, 300);
+ SetPositionFix(gps(), 3.5677675, 139.632314, 50);
+ CheckLastPositionInfo(3.5677675, 139.632314, 50);
+
+ // Ride train and gps signal degrades slightly. Stay on fresher gps
+ AdvanceTimeNow(base::TimeDelta::FromMinutes(5));
+ SetPositionFix(gps(), 3.5679026, 139.634777, 300);
+ CheckLastPositionInfo(3.5679026, 139.634777, 300);
+
+ // 14 minutes later
+ AdvanceTimeNow(base::TimeDelta::FromMinutes(14));
+
+ // GPS reading misses a beat, but don't switch to cell yet to avoid
+ // oscillating.
+ SetPositionFix(gps(), 3.5659005, 139.682579, 300);
+
+ AdvanceTimeNow(base::TimeDelta::FromSeconds(7));
+ SetPositionFix(cell(), 3.5689579, 139.691420, 1000);
+ CheckLastPositionInfo(3.5659005, 139.682579, 300);
+
+ // 1 minute later
+ AdvanceTimeNow(base::TimeDelta::FromMinutes(1));
+
+ // Enter tunnel. Stay on fresher gps for a moment.
+ SetPositionFix(cell(), 3.5657078, 139.68922, 300);
+ SetPositionFix(gps(), 3.5657104, 139.690341, 300);
+ CheckLastPositionInfo(3.5657104, 139.690341, 300);
+
+ // 2 minutes later
+ AdvanceTimeNow(base::TimeDelta::FromMinutes(2));
+ // Arrive in station. Cell moves but GPS is stale. Switch to fresher cell.
+ SetPositionFix(cell(), 3.5658700, 139.069979, 1000);
+ CheckLastPositionInfo(3.5658700, 139.069979, 1000);
+}
+
+TEST_F(GeolocationLocationArbitratorTest, TwoOneShotsIsNewPositionBetter) {
+ arbitrator_->StartProviders(false);
+ access_token_store_->NotifyDelegateTokensLoaded();
+ ASSERT_TRUE(cell());
+ ASSERT_TRUE(gps());
+
+ // Set the initial position.
+ SetPositionFix(cell(), 3, 139, 100);
+ CheckLastPositionInfo(3, 139, 100);
+
+ // Restart providers to simulate a one-shot request.
+ arbitrator_->StopProviders();
+
+ // To test 240956, perform a throwaway alloc.
+ // This convinces the allocator to put the providers in a new memory location.
+ MockLocationProvider* fakeMockProvider = NULL;
+ LocationProvider* fakeProvider =
+ new MockLocationProvider(&fakeMockProvider);
+
+ arbitrator_->StartProviders(false);
+ access_token_store_->NotifyDelegateTokensLoaded();
+
+ // Advance the time a short while to simulate successive calls.
+ AdvanceTimeNow(base::TimeDelta::FromMilliseconds(5));
+
+ // Update with a less accurate position to verify 240956.
+ SetPositionFix(cell(), 3, 139, 150);
+ CheckLastPositionInfo(3, 139, 150);
+
+ // No delete required for fakeMockProvider. It points to fakeProvider.
+ delete fakeProvider;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_provider_android.cc b/chromium/content/browser/geolocation/location_provider_android.cc
new file mode 100644
index 00000000000..20e672f5e3d
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_provider_android.cc
@@ -0,0 +1,51 @@
+// 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 "content/browser/geolocation/location_provider_android.h"
+
+#include "base/time/time.h"
+#include "content/browser/geolocation/location_api_adapter_android.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+
+// LocationProviderAndroid
+LocationProviderAndroid::LocationProviderAndroid() {
+}
+
+LocationProviderAndroid::~LocationProviderAndroid() {
+ StopProvider();
+}
+
+void LocationProviderAndroid::NotifyNewGeoposition(
+ const Geoposition& position) {
+ last_position_ = position;
+ NotifyCallback(last_position_);
+}
+
+bool LocationProviderAndroid::StartProvider(bool high_accuracy) {
+ return AndroidLocationApiAdapter::GetInstance()->Start(this, high_accuracy);
+}
+
+void LocationProviderAndroid::StopProvider() {
+ AndroidLocationApiAdapter::GetInstance()->Stop();
+}
+
+void LocationProviderAndroid::GetPosition(Geoposition* position) {
+ *position = last_position_;
+}
+
+void LocationProviderAndroid::RequestRefresh() {
+ // Nothing to do here, android framework will call us back on new position.
+}
+
+void LocationProviderAndroid::OnPermissionGranted() {
+ // Nothing to do here.
+}
+
+LocationProvider* NewSystemLocationProvider() {
+ return new LocationProviderAndroid;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_provider_android.h b/chromium/content/browser/geolocation/location_provider_android.h
new file mode 100644
index 00000000000..b4f5886ceba
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_provider_android.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_ANDROID_H_
+#define CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_ANDROID_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/geolocation/location_provider_base.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+class AndroidLocationApiAdapter;
+struct Geoposition;
+
+// Location provider for Android using the platform provider over JNI.
+class LocationProviderAndroid : public LocationProviderBase {
+ public:
+ LocationProviderAndroid();
+ virtual ~LocationProviderAndroid();
+
+ // Called by the AndroidLocationApiAdapter.
+ void NotifyNewGeoposition(const Geoposition& position);
+
+ // LocationProvider.
+ virtual bool StartProvider(bool high_accuracy) OVERRIDE;
+ virtual void StopProvider() OVERRIDE;
+ virtual void GetPosition(Geoposition* position) OVERRIDE;
+ virtual void RequestRefresh() OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+
+ private:
+ Geoposition last_position_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_ANDROID_H_
diff --git a/chromium/content/browser/geolocation/location_provider_base.cc b/chromium/content/browser/geolocation/location_provider_base.cc
new file mode 100644
index 00000000000..2a2d087dd82
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_provider_base.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 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 "content/browser/geolocation/location_provider_base.h"
+
+namespace content {
+
+LocationProviderBase::LocationProviderBase() {
+}
+
+LocationProviderBase::~LocationProviderBase() {
+}
+
+void LocationProviderBase::NotifyCallback(const Geoposition& position) {
+ if (!callback_.is_null())
+ callback_.Run(this, position);
+}
+
+void LocationProviderBase::SetUpdateCallback(
+ const LocationProviderUpdateCallback& callback) {
+ callback_ = callback;
+}
+
+void LocationProviderBase::RequestRefresh() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/location_provider_base.h b/chromium/content/browser/geolocation/location_provider_base.h
new file mode 100644
index 00000000000..dbc42402d5e
--- /dev/null
+++ b/chromium/content/browser/geolocation/location_provider_base.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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 CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_H_
+#define CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_H_
+
+#include "content/common/content_export.h"
+#include "content/port/browser/location_provider.h"
+
+namespace content {
+
+class CONTENT_EXPORT LocationProviderBase
+ : NON_EXPORTED_BASE(public LocationProvider) {
+ public:
+ LocationProviderBase();
+ virtual ~LocationProviderBase();
+
+ protected:
+ void NotifyCallback(const Geoposition& position);
+
+ // Overridden from LocationProvider:
+ virtual void SetUpdateCallback(
+ const LocationProviderUpdateCallback& callback) OVERRIDE;
+ virtual void RequestRefresh() OVERRIDE;
+
+ private:
+ LocationProviderUpdateCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocationProviderBase);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_LOCATION_PROVIDER_H_
diff --git a/chromium/content/browser/geolocation/mock_location_arbitrator.cc b/chromium/content/browser/geolocation/mock_location_arbitrator.cc
new file mode 100644
index 00000000000..e0508e11ecf
--- /dev/null
+++ b/chromium/content/browser/geolocation/mock_location_arbitrator.cc
@@ -0,0 +1,33 @@
+// 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 "content/browser/geolocation/mock_location_arbitrator.h"
+
+#include "base/message_loop/message_loop.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+
+MockGeolocationArbitrator::MockGeolocationArbitrator()
+ : permission_granted_(false),
+ providers_started_(false) {
+}
+
+void MockGeolocationArbitrator::StartProviders(bool use_high_accuracy) {
+ providers_started_ = true;;
+}
+
+void MockGeolocationArbitrator::StopProviders() {
+ providers_started_ = false;
+}
+
+void MockGeolocationArbitrator::OnPermissionGranted() {
+ permission_granted_ = true;
+}
+
+bool MockGeolocationArbitrator::HasPermissionBeenGranted() const {
+ return permission_granted_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/mock_location_arbitrator.h b/chromium/content/browser/geolocation/mock_location_arbitrator.h
new file mode 100644
index 00000000000..20ef672842a
--- /dev/null
+++ b/chromium/content/browser/geolocation/mock_location_arbitrator.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_ARBITRATOR_H_
+#define CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_ARBITRATOR_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/geolocation/location_arbitrator.h"
+
+namespace content {
+
+struct Geoposition;
+
+class MockGeolocationArbitrator : public GeolocationArbitrator {
+ public:
+ MockGeolocationArbitrator();
+
+ bool providers_started() const { return providers_started_; }
+
+ // GeolocationArbitrator:
+ virtual void StartProviders(bool use_high_accuracy)
+ OVERRIDE;
+ virtual void StopProviders() OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+ virtual bool HasPermissionBeenGranted() const OVERRIDE;
+
+ private:
+ bool permission_granted_;
+ bool providers_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockGeolocationArbitrator);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_ARBITRATOR_H_
diff --git a/chromium/content/browser/geolocation/mock_location_provider.cc b/chromium/content/browser/geolocation/mock_location_provider.cc
new file mode 100644
index 00000000000..22980b1de73
--- /dev/null
+++ b/chromium/content/browser/geolocation/mock_location_provider.cc
@@ -0,0 +1,136 @@
+// 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 file implements a mock location provider and the factory functions for
+// various ways of creating it.
+
+#include "content/browser/geolocation/mock_location_provider.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+
+namespace content {
+MockLocationProvider* MockLocationProvider::instance_ = NULL;
+
+MockLocationProvider::MockLocationProvider(MockLocationProvider** self_ref)
+ : state_(STOPPED),
+ is_permission_granted_(false),
+ self_ref_(self_ref),
+ provider_loop_(base::MessageLoopProxy::current()) {
+ CHECK(self_ref_);
+ CHECK(*self_ref_ == NULL);
+ *self_ref_ = this;
+}
+
+MockLocationProvider::~MockLocationProvider() {
+ CHECK(*self_ref_ == this);
+ *self_ref_ = NULL;
+}
+
+void MockLocationProvider::HandlePositionChanged(const Geoposition& position) {
+ if (provider_loop_->BelongsToCurrentThread()) {
+ // The location arbitrator unit tests rely on this method running
+ // synchronously.
+ position_ = position;
+ NotifyCallback(position_);
+ } else {
+ provider_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&MockLocationProvider::HandlePositionChanged,
+ base::Unretained(this), position));
+ }
+}
+
+bool MockLocationProvider::StartProvider(bool high_accuracy) {
+ state_ = high_accuracy ? HIGH_ACCURACY : LOW_ACCURACY;
+ return true;
+}
+
+void MockLocationProvider::StopProvider() {
+ state_ = STOPPED;
+}
+
+void MockLocationProvider::GetPosition(Geoposition* position) {
+ *position = position_;
+}
+
+void MockLocationProvider::OnPermissionGranted() {
+ is_permission_granted_ = true;
+}
+
+// Mock location provider that automatically calls back its client at most
+// once, when StartProvider or OnPermissionGranted is called. Use
+// |requires_permission_to_start| to select which event triggers the callback.
+class AutoMockLocationProvider : public MockLocationProvider {
+ public:
+ AutoMockLocationProvider(bool has_valid_location,
+ bool requires_permission_to_start)
+ : MockLocationProvider(&instance_),
+ weak_factory_(this),
+ requires_permission_to_start_(requires_permission_to_start),
+ listeners_updated_(false) {
+ if (has_valid_location) {
+ position_.accuracy = 3;
+ position_.latitude = 4.3;
+ position_.longitude = -7.8;
+ // Webkit compares the timestamp to wall clock time, so we need it to be
+ // contemporary.
+ position_.timestamp = base::Time::Now();
+ } else {
+ position_.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ }
+ }
+ virtual bool StartProvider(bool high_accuracy) OVERRIDE {
+ MockLocationProvider::StartProvider(high_accuracy);
+ if (!requires_permission_to_start_) {
+ UpdateListenersIfNeeded();
+ }
+ return true;
+ }
+
+ virtual void OnPermissionGranted() OVERRIDE {
+ MockLocationProvider::OnPermissionGranted();
+ if (requires_permission_to_start_) {
+ UpdateListenersIfNeeded();
+ }
+ }
+
+ void UpdateListenersIfNeeded() {
+ if (!listeners_updated_) {
+ listeners_updated_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockLocationProvider::HandlePositionChanged,
+ weak_factory_.GetWeakPtr(),
+ position_));
+ }
+ }
+
+ base::WeakPtrFactory<MockLocationProvider> weak_factory_;
+ const bool requires_permission_to_start_;
+ bool listeners_updated_;
+};
+
+LocationProvider* NewMockLocationProvider() {
+ return new MockLocationProvider(&MockLocationProvider::instance_);
+}
+
+LocationProvider* NewAutoSuccessMockLocationProvider() {
+ return new AutoMockLocationProvider(true, false);
+}
+
+LocationProvider* NewAutoFailMockLocationProvider() {
+ return new AutoMockLocationProvider(false, false);
+}
+
+LocationProvider* NewAutoSuccessMockNetworkLocationProvider() {
+ return new AutoMockLocationProvider(true, true);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/mock_location_provider.h b/chromium/content/browser/geolocation/mock_location_provider.h
new file mode 100644
index 00000000000..342effcac5d
--- /dev/null
+++ b/chromium/content/browser/geolocation/mock_location_provider.h
@@ -0,0 +1,67 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_PROVIDER_H_
+#define CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_PROVIDER_H_
+
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "content/browser/geolocation/location_provider_base.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+
+// Mock implementation of a location provider for testing.
+class MockLocationProvider : public LocationProviderBase {
+ public:
+ // Will update |*self_ref| to point to |this| on construction, and to NULL
+ // on destruction.
+ explicit MockLocationProvider(MockLocationProvider** self_ref);
+ virtual ~MockLocationProvider();
+
+ // Updates listeners with the new position.
+ void HandlePositionChanged(const Geoposition& position);
+
+ // LocationProvider implementation.
+ virtual bool StartProvider(bool high_accuracy) OVERRIDE;
+ virtual void StopProvider() OVERRIDE;
+ virtual void GetPosition(Geoposition* position) OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+
+ Geoposition position_;
+ enum State { STOPPED, LOW_ACCURACY, HIGH_ACCURACY } state_;
+ bool is_permission_granted_;
+ MockLocationProvider** self_ref_;
+
+ scoped_refptr<base::MessageLoopProxy> provider_loop_;
+
+ // Set when an instance of the mock is created via a factory function.
+ static MockLocationProvider* instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockLocationProvider);
+};
+
+// Factory functions for the various sorts of mock location providers,
+// for use with GeolocationArbitrator::SetProviderFactoryForTest (i.e.
+// not intended for test code to use to get access to the mock, you can use
+// MockLocationProvider::instance_ for this, or make a custom factory method).
+
+// Creates a mock location provider with no default behavior.
+LocationProvider* NewMockLocationProvider();
+// Creates a mock location provider that automatically notifies its
+// listeners with a valid location when StartProvider is called.
+LocationProvider* NewAutoSuccessMockLocationProvider();
+// Creates a mock location provider that automatically notifies its
+// listeners with an error when StartProvider is called.
+LocationProvider* NewAutoFailMockLocationProvider();
+// Similar to NewAutoSuccessMockLocationProvider but mimicks the behavior of
+// the Network Location provider, in deferring making location updates until
+// a permission request has been confirmed.
+LocationProvider* NewAutoSuccessMockNetworkLocationProvider();
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_MOCK_LOCATION_PROVIDER_H_
diff --git a/chromium/content/browser/geolocation/network_location_provider.cc b/chromium/content/browser/geolocation/network_location_provider.cc
new file mode 100644
index 00000000000..82171a062b9
--- /dev/null
+++ b/chromium/content/browser/geolocation/network_location_provider.cc
@@ -0,0 +1,271 @@
+// 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 "content/browser/geolocation/network_location_provider.h"
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/public/browser/access_token_store.h"
+
+namespace content {
+namespace {
+// The maximum period of time we'll wait for a complete set of device data
+// before sending the request.
+const int kDataCompleteWaitSeconds = 2;
+} // namespace
+
+// static
+const size_t NetworkLocationProvider::PositionCache::kMaximumSize = 10;
+
+NetworkLocationProvider::PositionCache::PositionCache() {}
+
+NetworkLocationProvider::PositionCache::~PositionCache() {}
+
+bool NetworkLocationProvider::PositionCache::CachePosition(
+ const WifiData& wifi_data,
+ const Geoposition& position) {
+ // Check that we can generate a valid key for the device data.
+ string16 key;
+ if (!MakeKey(wifi_data, &key)) {
+ return false;
+ }
+ // If the cache is full, remove the oldest entry.
+ if (cache_.size() == kMaximumSize) {
+ DCHECK(cache_age_list_.size() == kMaximumSize);
+ CacheAgeList::iterator oldest_entry = cache_age_list_.begin();
+ DCHECK(oldest_entry != cache_age_list_.end());
+ cache_.erase(*oldest_entry);
+ cache_age_list_.erase(oldest_entry);
+ }
+ DCHECK_LT(cache_.size(), kMaximumSize);
+ // Insert the position into the cache.
+ std::pair<CacheMap::iterator, bool> result =
+ cache_.insert(std::make_pair(key, position));
+ if (!result.second) {
+ NOTREACHED(); // We never try to add the same key twice.
+ CHECK_EQ(cache_.size(), cache_age_list_.size());
+ return false;
+ }
+ cache_age_list_.push_back(result.first);
+ DCHECK_EQ(cache_.size(), cache_age_list_.size());
+ return true;
+}
+
+// Searches for a cached position response for the current set of cell ID and
+// WiFi data. Returns the cached position if available, NULL otherwise.
+const Geoposition* NetworkLocationProvider::PositionCache::FindPosition(
+ const WifiData& wifi_data) {
+ string16 key;
+ if (!MakeKey(wifi_data, &key)) {
+ return NULL;
+ }
+ CacheMap::const_iterator iter = cache_.find(key);
+ return iter == cache_.end() ? NULL : &iter->second;
+}
+
+// Makes the key for the map of cached positions, using a set of
+// device data. Returns true if a good key was generated, false otherwise.
+//
+// static
+bool NetworkLocationProvider::PositionCache::MakeKey(
+ const WifiData& wifi_data,
+ string16* key) {
+ // Currently we use only the WiFi data, and base the key only on
+ // the MAC addresses.
+ DCHECK(key);
+ key->clear();
+ const size_t kCharsPerMacAddress = 6 * 3 + 1; // e.g. "11:22:33:44:55:66|"
+ key->reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress);
+ const string16 separator(ASCIIToUTF16("|"));
+ for (WifiData::AccessPointDataSet::const_iterator iter =
+ wifi_data.access_point_data.begin();
+ iter != wifi_data.access_point_data.end();
+ iter++) {
+ *key += separator;
+ *key += iter->mac_address;
+ *key += separator;
+ }
+ // If the key is the empty string, return false, as we don't want to cache a
+ // position for such a set of device data.
+ return !key->empty();
+}
+
+// NetworkLocationProvider factory function
+LocationProviderBase* NewNetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token) {
+ return new NetworkLocationProvider(
+ access_token_store, context, url, access_token);
+}
+
+// NetworkLocationProvider
+NetworkLocationProvider::NetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* url_context_getter,
+ const GURL& url,
+ const string16& access_token)
+ : access_token_store_(access_token_store),
+ wifi_data_provider_(NULL),
+ is_wifi_data_complete_(false),
+ access_token_(access_token),
+ is_permission_granted_(false),
+ is_new_data_available_(false),
+ weak_factory_(this) {
+ // Create the position cache.
+ position_cache_.reset(new PositionCache());
+
+ request_.reset(new NetworkLocationRequest(url_context_getter, url, this));
+}
+
+NetworkLocationProvider::~NetworkLocationProvider() {
+ StopProvider();
+}
+
+// LocationProvider implementation
+void NetworkLocationProvider::GetPosition(Geoposition *position) {
+ DCHECK(position);
+ *position = position_;
+}
+
+void NetworkLocationProvider::RequestRefresh() {
+ // TODO(joth): When called via the public (base class) interface, this should
+ // poke each data provider to get them to expedite their next scan.
+ // Whilst in the delayed start, only send request if all data is ready.
+ if (!weak_factory_.HasWeakPtrs() || is_wifi_data_complete_) {
+ RequestPosition();
+ }
+}
+
+void NetworkLocationProvider::OnPermissionGranted() {
+ const bool was_permission_granted = is_permission_granted_;
+ is_permission_granted_ = true;
+ if (!was_permission_granted && IsStarted()) {
+ RequestRefresh();
+ }
+}
+
+// DeviceDataProviderInterface::ListenerInterface implementation.
+void NetworkLocationProvider::DeviceDataUpdateAvailable(
+ WifiDataProvider* provider) {
+ DCHECK(provider == wifi_data_provider_);
+ is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_);
+ OnDeviceDataUpdated();
+}
+
+// NetworkLocationRequest::ListenerInterface implementation.
+void NetworkLocationProvider::LocationResponseAvailable(
+ const Geoposition& position,
+ bool server_error,
+ const string16& access_token,
+ const WifiData& wifi_data) {
+ DCHECK(CalledOnValidThread());
+ // Record the position and update our cache.
+ position_ = position;
+ if (position.Validate()) {
+ position_cache_->CachePosition(wifi_data, position);
+ }
+
+ // Record access_token if it's set.
+ if (!access_token.empty() && access_token_ != access_token) {
+ access_token_ = access_token;
+ access_token_store_->SaveAccessToken(request_->url(), access_token);
+ }
+
+ // Let listeners know that we now have a position available.
+ NotifyCallback(position_);
+}
+
+bool NetworkLocationProvider::StartProvider(bool high_accuracy) {
+ DCHECK(CalledOnValidThread());
+ if (IsStarted())
+ return true;
+ DCHECK(wifi_data_provider_ == NULL);
+ if (!request_->url().is_valid()) {
+ LOG(WARNING) << "StartProvider() : Failed, Bad URL: "
+ << request_->url().possibly_invalid_spec();
+ return false;
+ }
+
+ // Get the device data providers. The first call to Register will create the
+ // provider and it will be deleted by ref counting.
+ wifi_data_provider_ = WifiDataProvider::Register(this);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NetworkLocationProvider::RequestPosition,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kDataCompleteWaitSeconds));
+ // Get the device data.
+ is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_);
+ if (is_wifi_data_complete_)
+ OnDeviceDataUpdated();
+ return true;
+}
+
+void NetworkLocationProvider::StopProvider() {
+ DCHECK(CalledOnValidThread());
+ if (IsStarted()) {
+ wifi_data_provider_->Unregister(this);
+ }
+ wifi_data_provider_ = NULL;
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+// Other methods
+void NetworkLocationProvider::RequestPosition() {
+ DCHECK(CalledOnValidThread());
+ if (!is_new_data_available_)
+ return;
+
+ const Geoposition* cached_position =
+ position_cache_->FindPosition(wifi_data_);
+ DCHECK(!device_data_updated_timestamp_.is_null()) <<
+ "Timestamp must be set before looking up position";
+ if (cached_position) {
+ DCHECK(cached_position->Validate());
+ // Record the position and update its timestamp.
+ position_ = *cached_position;
+ // The timestamp of a position fix is determined by the timestamp
+ // of the source data update. (The value of position_.timestamp from
+ // the cache could be from weeks ago!)
+ position_.timestamp = device_data_updated_timestamp_;
+ is_new_data_available_ = false;
+ // Let listeners know that we now have a position available.
+ NotifyCallback(position_);
+ return;
+ }
+ // Don't send network requests until authorized. http://crbug.com/39171
+ if (!is_permission_granted_)
+ return;
+
+ weak_factory_.InvalidateWeakPtrs();
+ is_new_data_available_ = false;
+
+ // TODO(joth): Rather than cancel pending requests, we should create a new
+ // NetworkLocationRequest for each and hold a set of pending requests.
+ if (request_->is_request_pending()) {
+ DVLOG(1) << "NetworkLocationProvider - pre-empting pending network request "
+ "with new data. Wifi APs: "
+ << wifi_data_.access_point_data.size();
+ }
+ request_->MakeRequest(access_token_, wifi_data_,
+ device_data_updated_timestamp_);
+}
+
+void NetworkLocationProvider::OnDeviceDataUpdated() {
+ DCHECK(CalledOnValidThread());
+ device_data_updated_timestamp_ = base::Time::Now();
+
+ is_new_data_available_ = is_wifi_data_complete_;
+ RequestRefresh();
+}
+
+bool NetworkLocationProvider::IsStarted() const {
+ return wifi_data_provider_ != NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/network_location_provider.h b/chromium/content/browser/geolocation/network_location_provider.h
new file mode 100644
index 00000000000..a1ba5b38673
--- /dev/null
+++ b/chromium/content/browser/geolocation/network_location_provider.h
@@ -0,0 +1,146 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_PROVIDER_H_
+#define CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_PROVIDER_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/thread.h"
+#include "content/browser/geolocation/device_data_provider.h"
+#include "content/browser/geolocation/location_provider_base.h"
+#include "content/browser/geolocation/network_location_request.h"
+#include "content/common/content_export.h"
+#include "content/public/common/geoposition.h"
+
+namespace content {
+class AccessTokenStore;
+
+
+class NetworkLocationProvider
+ : public base::NonThreadSafe,
+ public LocationProviderBase,
+ public WifiDataProvider::ListenerInterface,
+ public NetworkLocationRequest::ListenerInterface {
+ public:
+ // Cache of recently resolved locations. Public for tests.
+ class CONTENT_EXPORT PositionCache {
+ public:
+ // The maximum size of the cache of positions for previously requested
+ // device data.
+ static const size_t kMaximumSize;
+
+ PositionCache();
+ ~PositionCache();
+
+ // Caches the current position response for the current set of cell ID and
+ // WiFi data. In the case of the cache exceeding kMaximumSize this will
+ // evict old entries in FIFO orderer of being added.
+ // Returns true on success, false otherwise.
+ bool CachePosition(const WifiData& wifi_data,
+ const Geoposition& position);
+
+ // Searches for a cached position response for the current set of device
+ // data. Returns NULL if the position is not in the cache, or the cached
+ // position if available. Ownership remains with the cache.
+ const Geoposition* FindPosition(const WifiData& wifi_data);
+
+ private:
+ // Makes the key for the map of cached positions, using a set of
+ // device data. Returns true if a good key was generated, false otherwise.
+ static bool MakeKey(const WifiData& wifi_data,
+ string16* key);
+
+ // The cache of positions. This is stored as a map keyed on a string that
+ // represents a set of device data, and a list to provide
+ // least-recently-added eviction.
+ typedef std::map<string16, Geoposition> CacheMap;
+ CacheMap cache_;
+ typedef std::list<CacheMap::iterator> CacheAgeList;
+ CacheAgeList cache_age_list_; // Oldest first.
+ };
+
+ NetworkLocationProvider(AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token);
+ virtual ~NetworkLocationProvider();
+
+ // LocationProvider implementation
+ virtual bool StartProvider(bool high_accuracy) OVERRIDE;
+ virtual void StopProvider() OVERRIDE;
+ virtual void GetPosition(Geoposition *position) OVERRIDE;
+ virtual void RequestRefresh() OVERRIDE;
+ virtual void OnPermissionGranted() OVERRIDE;
+
+ private:
+ // Satisfies a position request from cache or network.
+ void RequestPosition();
+
+ // Internal helper used by DeviceDataUpdateAvailable
+ void OnDeviceDataUpdated();
+
+ bool IsStarted() const;
+
+ // DeviceDataProvider::ListenerInterface implementation.
+ virtual void DeviceDataUpdateAvailable(WifiDataProvider* provider) OVERRIDE;
+
+ // NetworkLocationRequest::ListenerInterface implementation.
+ virtual void LocationResponseAvailable(const Geoposition& position,
+ bool server_error,
+ const string16& access_token,
+ const WifiData& wifi_data) OVERRIDE;
+
+ scoped_refptr<AccessTokenStore> access_token_store_;
+
+ // The wifi data provider, acquired via global factories.
+ WifiDataProvider* wifi_data_provider_;
+
+ // The wifi data, flags to indicate if the data set is complete.
+ WifiData wifi_data_;
+ bool is_wifi_data_complete_;
+
+ // The timestamp for the latest device data update.
+ base::Time device_data_updated_timestamp_;
+
+ // Cached value loaded from the token store or set by a previous server
+ // response, and sent in each subsequent network request.
+ string16 access_token_;
+
+ // The current best position estimate.
+ Geoposition position_;
+
+ // Whether permission has been granted for the provider to operate.
+ bool is_permission_granted_;
+
+ bool is_new_data_available_;
+
+ // The network location request object, and the url it uses.
+ scoped_ptr<NetworkLocationRequest> request_;
+
+ base::WeakPtrFactory<NetworkLocationProvider> weak_factory_;
+ // The cache of positions.
+ scoped_ptr<PositionCache> position_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkLocationProvider);
+};
+
+// Factory functions for the various types of location provider to abstract
+// over the platform-dependent implementations.
+CONTENT_EXPORT LocationProviderBase* NewNetworkLocationProvider(
+ AccessTokenStore* access_token_store,
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ const string16& access_token);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_PROVIDER_H_
diff --git a/chromium/content/browser/geolocation/network_location_provider_unittest.cc b/chromium/content/browser/geolocation/network_location_provider_unittest.cc
new file mode 100644
index 00000000000..edfada7766a
--- /dev/null
+++ b/chromium/content/browser/geolocation/network_location_provider_unittest.cc
@@ -0,0 +1,569 @@
+// 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 "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/geolocation/fake_access_token_store.h"
+#include "content/browser/geolocation/location_arbitrator_impl.h"
+#include "content/browser/geolocation/network_location_provider.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+// Constants used in multiple tests.
+const char kTestServerUrl[] = "https://www.geolocation.test/service";
+const char kAccessTokenString[] = "accessToken";
+
+// Using #define so we can easily paste this into various other strings.
+#define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe"
+
+// Stops the specified (nested) message loop when the listener is called back.
+class MessageLoopQuitListener {
+ public:
+ MessageLoopQuitListener()
+ : client_message_loop_(base::MessageLoop::current()),
+ updated_provider_(NULL) {
+ CHECK(client_message_loop_);
+ }
+
+ void LocationUpdateAvailable(const LocationProvider* provider,
+ const Geoposition& position) {
+ EXPECT_EQ(client_message_loop_, base::MessageLoop::current());
+ updated_provider_ = provider;
+ client_message_loop_->Quit();
+ }
+
+ base::MessageLoop* client_message_loop_;
+ const LocationProvider* updated_provider_;
+};
+
+// A mock implementation of DeviceDataProviderImplBase for testing. Adapted from
+// http://gears.googlecode.com/svn/trunk/gears/geolocation/geolocation_test.cc
+template<typename DataType>
+class MockDeviceDataProviderImpl
+ : public DeviceDataProviderImplBase<DataType> {
+ public:
+ // Factory method for use with DeviceDataProvider::SetFactory.
+ static DeviceDataProviderImplBase<DataType>* GetInstance() {
+ CHECK(instance_);
+ return instance_;
+ }
+
+ static MockDeviceDataProviderImpl<DataType>* CreateInstance() {
+ CHECK(!instance_);
+ instance_ = new MockDeviceDataProviderImpl<DataType>;
+ return instance_;
+ }
+
+ MockDeviceDataProviderImpl()
+ : start_calls_(0),
+ stop_calls_(0),
+ got_data_(true) {
+ }
+
+ virtual ~MockDeviceDataProviderImpl() {
+ CHECK(this == instance_);
+ instance_ = NULL;
+ }
+
+ // DeviceDataProviderImplBase implementation.
+ virtual bool StartDataProvider() {
+ ++start_calls_;
+ return true;
+ }
+ virtual void StopDataProvider() {
+ ++stop_calls_;
+ }
+ virtual bool GetData(DataType* data_out) {
+ CHECK(data_out);
+ *data_out = data_;
+ return got_data_;
+ }
+
+ void SetData(const DataType& new_data) {
+ got_data_ = true;
+ const bool differs = data_.DiffersSignificantly(new_data);
+ data_ = new_data;
+ if (differs)
+ this->NotifyListeners();
+ }
+
+ void set_got_data(bool got_data) { got_data_ = got_data; }
+ int start_calls_;
+ int stop_calls_;
+
+ private:
+ static MockDeviceDataProviderImpl<DataType>* instance_;
+
+ DataType data_;
+ bool got_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockDeviceDataProviderImpl);
+};
+
+template<typename DataType>
+MockDeviceDataProviderImpl<DataType>*
+MockDeviceDataProviderImpl<DataType>::instance_ = NULL;
+
+// Main test fixture
+class GeolocationNetworkProviderTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ test_server_url_ = GURL(kTestServerUrl);
+ access_token_store_ = new FakeAccessTokenStore;
+ wifi_data_provider_ =
+ MockDeviceDataProviderImpl<WifiData>::CreateInstance();
+ }
+
+ virtual void TearDown() {
+ WifiDataProvider::ResetFactory();
+ }
+
+ LocationProvider* CreateProvider(bool set_permission_granted) {
+ LocationProvider* provider = NewNetworkLocationProvider(
+ access_token_store_.get(),
+ NULL, // No URLContextGetter needed, as using test urlfecther factory.
+ test_server_url_,
+ access_token_store_->access_token_set_[test_server_url_]);
+ if (set_permission_granted)
+ provider->OnPermissionGranted();
+ return provider;
+ }
+
+ protected:
+ GeolocationNetworkProviderTest() {
+ // TODO(joth): Really these should be in SetUp, not here, but they take no
+ // effect on Mac OS Release builds if done there. I kid not. Figure out why.
+ WifiDataProvider::SetFactory(
+ MockDeviceDataProviderImpl<WifiData>::GetInstance);
+ }
+
+ // Returns the current url fetcher (if any) and advances the id ready for the
+ // next test step.
+ net::TestURLFetcher* get_url_fetcher_and_advance_id() {
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(
+ NetworkLocationRequest::url_fetcher_id_for_tests);
+ if (fetcher)
+ ++NetworkLocationRequest::url_fetcher_id_for_tests;
+ return fetcher;
+ }
+
+ static int IndexToChannel(int index) { return index + 4; }
+
+ // Creates wifi data containing the specified number of access points, with
+ // some differentiating charactistics in each.
+ static WifiData CreateReferenceWifiScanData(int ap_count) {
+ WifiData data;
+ for (int i = 0; i < ap_count; ++i) {
+ AccessPointData ap;
+ ap.mac_address =
+ ASCIIToUTF16(base::StringPrintf("%02d-34-56-78-54-32", i));
+ ap.radio_signal_strength = ap_count - i;
+ ap.channel = IndexToChannel(i);
+ ap.signal_to_noise = i + 42;
+ ap.ssid = ASCIIToUTF16("Some nice+network|name\\");
+ data.access_point_data.insert(ap);
+ }
+ return data;
+ }
+
+ static void CreateReferenceWifiScanDataJson(
+ int ap_count, int start_index, base::ListValue* wifi_access_point_list) {
+ std::vector<std::string> wifi_data;
+ for (int i = 0; i < ap_count; ++i) {
+ base::DictionaryValue* ap = new base::DictionaryValue();
+ ap->SetString("macAddress", base::StringPrintf("%02d-34-56-78-54-32", i));
+ ap->SetInteger("signalStrength", start_index + ap_count - i);
+ ap->SetInteger("age", 0);
+ ap->SetInteger("channel", IndexToChannel(i));
+ ap->SetInteger("signalToNoiseRatio", i + 42);
+ wifi_access_point_list->Append(ap);
+ }
+ }
+
+ static Geoposition CreateReferencePosition(int id) {
+ Geoposition pos;
+ pos.latitude = id;
+ pos.longitude = -(id + 1);
+ pos.altitude = 2 * id;
+ pos.timestamp = base::Time::Now();
+ return pos;
+ }
+
+ static std::string PrettyJson(const base::Value& value) {
+ std::string pretty;
+ base::JSONWriter::WriteWithOptions(
+ &value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty);
+ return pretty;
+ }
+
+ static testing::AssertionResult JsonGetList(
+ const std::string& field,
+ const base::DictionaryValue& dict,
+ const base::ListValue** output_list) {
+ if (!dict.GetList(field, output_list))
+ return testing::AssertionFailure() << "Dictionary " << PrettyJson(dict)
+ << " is missing list field " << field;
+ return testing::AssertionSuccess();
+ }
+
+ static testing::AssertionResult JsonFieldEquals(
+ const std::string& field,
+ const base::DictionaryValue& expected,
+ const base::DictionaryValue& actual) {
+ const base::Value* expected_value;
+ const base::Value* actual_value;
+ if (!expected.Get(field, &expected_value))
+ return testing::AssertionFailure()
+ << "Expected dictionary " << PrettyJson(expected)
+ << " is missing field " << field;
+ if (!expected.Get(field, &actual_value))
+ return testing::AssertionFailure()
+ << "Actual dictionary " << PrettyJson(actual)
+ << " is missing field " << field;
+ if (!expected_value->Equals(actual_value))
+ return testing::AssertionFailure()
+ << "Field " << field << " mismatch: " << PrettyJson(*expected_value)
+ << " != " << PrettyJson(*actual_value);
+ return testing::AssertionSuccess();
+ }
+
+ static GURL UrlWithoutQuery(const GURL& url) {
+ url_canon::Replacements<char> replacements;
+ replacements.ClearQuery();
+ return url.ReplaceComponents(replacements);
+ }
+
+ testing::AssertionResult IsTestServerUrl(const GURL& request_url) {
+ const GURL a(UrlWithoutQuery(test_server_url_));
+ const GURL b(UrlWithoutQuery(request_url));
+ if (a == b)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure() << a << " != " << b;
+ }
+
+ void CheckRequestIsValid(const net::TestURLFetcher& request,
+ int expected_routers,
+ int expected_wifi_aps,
+ int wifi_start_index,
+ const std::string& expected_access_token) {
+ const GURL& request_url = request.GetOriginalURL();
+
+ EXPECT_TRUE(IsTestServerUrl(request_url));
+
+ // Check to see that the api key is being appended for the default
+ // network provider url.
+ bool is_default_url = UrlWithoutQuery(request_url) ==
+ UrlWithoutQuery(GeolocationArbitratorImpl::DefaultNetworkProviderURL());
+ EXPECT_EQ(is_default_url, !request_url.query().empty());
+
+ const std::string& upload_data = request.upload_data();
+ ASSERT_FALSE(upload_data.empty());
+ std::string json_parse_error_msg;
+ scoped_ptr<base::Value> parsed_json(
+ base::JSONReader::ReadAndReturnError(
+ upload_data,
+ base::JSON_PARSE_RFC,
+ NULL,
+ &json_parse_error_msg));
+ EXPECT_TRUE(json_parse_error_msg.empty());
+ ASSERT_TRUE(parsed_json.get() != NULL);
+
+ const base::DictionaryValue* request_json;
+ ASSERT_TRUE(parsed_json->GetAsDictionary(&request_json));
+
+ if (!is_default_url) {
+ if (expected_access_token.empty())
+ ASSERT_FALSE(request_json->HasKey(kAccessTokenString));
+ else {
+ std::string access_token;
+ EXPECT_TRUE(request_json->GetString(kAccessTokenString, &access_token));
+ EXPECT_EQ(expected_access_token, access_token);
+ }
+ }
+
+ if (expected_wifi_aps) {
+ base::ListValue expected_wifi_aps_json;
+ CreateReferenceWifiScanDataJson(
+ expected_wifi_aps,
+ wifi_start_index,
+ &expected_wifi_aps_json);
+ EXPECT_EQ(size_t(expected_wifi_aps), expected_wifi_aps_json.GetSize());
+
+ const base::ListValue* wifi_aps_json;
+ ASSERT_TRUE(JsonGetList("wifiAccessPoints", *request_json,
+ &wifi_aps_json));
+ for (size_t i = 0; i < expected_wifi_aps_json.GetSize(); ++i ) {
+ const base::DictionaryValue* expected_json;
+ ASSERT_TRUE(expected_wifi_aps_json.GetDictionary(i, &expected_json));
+ const base::DictionaryValue* actual_json;
+ ASSERT_TRUE(wifi_aps_json->GetDictionary(i, &actual_json));
+ ASSERT_TRUE(JsonFieldEquals("macAddress", *expected_json,
+ *actual_json));
+ ASSERT_TRUE(JsonFieldEquals("signalStrength", *expected_json,
+ *actual_json));
+ ASSERT_TRUE(JsonFieldEquals("channel", *expected_json, *actual_json));
+ ASSERT_TRUE(JsonFieldEquals("signalToNoiseRatio", *expected_json,
+ *actual_json));
+ }
+ } else {
+ ASSERT_FALSE(request_json->HasKey("wifiAccessPoints"));
+ }
+ EXPECT_TRUE(request_url.is_valid());
+ }
+
+ GURL test_server_url_;
+ base::MessageLoop main_message_loop_;
+ scoped_refptr<FakeAccessTokenStore> access_token_store_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ scoped_refptr<MockDeviceDataProviderImpl<WifiData> > wifi_data_provider_;
+};
+
+TEST_F(GeolocationNetworkProviderTest, CreateDestroy) {
+ // Test fixture members were SetUp correctly.
+ EXPECT_EQ(&main_message_loop_, base::MessageLoop::current());
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(NULL != provider.get());
+ provider.reset();
+ SUCCEED();
+}
+
+TEST_F(GeolocationNetworkProviderTest, StartProvider) {
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+ CheckRequestIsValid(*fetcher, 0, 0, 0, std::string());
+}
+
+TEST_F(GeolocationNetworkProviderTest, StartProviderDefaultUrl) {
+ test_server_url_ = GeolocationArbitratorImpl::DefaultNetworkProviderURL();
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+ CheckRequestIsValid(*fetcher, 0, 0, 0, std::string());
+}
+
+TEST_F(GeolocationNetworkProviderTest, StartProviderLongRequest) {
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+ const int kFirstScanAps = 20;
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
+ main_message_loop_.RunUntilIdle();
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+ // The request url should have been shortened to less than 2048 characters
+ // in length by not including access points with the lowest signal strength
+ // in the request.
+ EXPECT_LT(fetcher->GetOriginalURL().spec().size(), size_t(2048));
+ CheckRequestIsValid(*fetcher, 0, 16, 4, std::string());
+}
+
+TEST_F(GeolocationNetworkProviderTest, MultipleWifiScansComplete) {
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+ EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
+
+ // Complete the network request with bad position fix.
+ const char* kNoFixNetworkResponse =
+ "{"
+ " \"status\": \"ZERO_RESULTS\""
+ "}";
+ fetcher->set_url(test_server_url_);
+ fetcher->set_status(net::URLRequestStatus());
+ fetcher->set_response_code(200); // OK
+ fetcher->SetResponseString(kNoFixNetworkResponse);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ Geoposition position;
+ provider->GetPosition(&position);
+ EXPECT_FALSE(position.Validate());
+
+ // Now wifi data arrives -- SetData will notify listeners.
+ const int kFirstScanAps = 6;
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
+ main_message_loop_.RunUntilIdle();
+ fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+ // The request should have the wifi data.
+ CheckRequestIsValid(*fetcher, 0, kFirstScanAps, 0, std::string());
+
+ // Send a reply with good position fix.
+ const char* kReferenceNetworkResponse =
+ "{"
+ " \"accessToken\": \"" REFERENCE_ACCESS_TOKEN "\","
+ " \"accuracy\": 1200.4,"
+ " \"location\": {"
+ " \"lat\": 51.0,"
+ " \"lng\": -0.1"
+ " }"
+ "}";
+ fetcher->set_url(test_server_url_);
+ fetcher->set_status(net::URLRequestStatus());
+ fetcher->set_response_code(200); // OK
+ fetcher->SetResponseString(kReferenceNetworkResponse);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ provider->GetPosition(&position);
+ EXPECT_EQ(51.0, position.latitude);
+ EXPECT_EQ(-0.1, position.longitude);
+ EXPECT_EQ(1200.4, position.accuracy);
+ EXPECT_FALSE(position.timestamp.is_null());
+ EXPECT_TRUE(position.Validate());
+
+ // Token should be in the store.
+ EXPECT_EQ(UTF8ToUTF16(REFERENCE_ACCESS_TOKEN),
+ access_token_store_->access_token_set_[test_server_url_]);
+
+ // Wifi updated again, with one less AP. This is 'close enough' to the
+ // previous scan, so no new request made.
+ const int kSecondScanAps = kFirstScanAps - 1;
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kSecondScanAps));
+ main_message_loop_.RunUntilIdle();
+ fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_FALSE(fetcher);
+
+ provider->GetPosition(&position);
+ EXPECT_EQ(51.0, position.latitude);
+ EXPECT_EQ(-0.1, position.longitude);
+ EXPECT_TRUE(position.Validate());
+
+ // Now a third scan with more than twice the original amount -> new request.
+ const int kThirdScanAps = kFirstScanAps * 2 + 1;
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kThirdScanAps));
+ main_message_loop_.RunUntilIdle();
+ fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_TRUE(fetcher);
+ CheckRequestIsValid(*fetcher, 0, kThirdScanAps, 0, REFERENCE_ACCESS_TOKEN);
+ // ...reply with a network error.
+
+ fetcher->set_url(test_server_url_);
+ fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED, -1));
+ fetcher->set_response_code(200); // should be ignored
+ fetcher->SetResponseString(std::string());
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ // Error means we now no longer have a fix.
+ provider->GetPosition(&position);
+ EXPECT_FALSE(position.Validate());
+
+ // Wifi scan returns to original set: should be serviced from cache.
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
+ main_message_loop_.RunUntilIdle();
+ EXPECT_FALSE(get_url_fetcher_and_advance_id()); // No new request created.
+
+ provider->GetPosition(&position);
+ EXPECT_EQ(51.0, position.latitude);
+ EXPECT_EQ(-0.1, position.longitude);
+ EXPECT_TRUE(position.Validate());
+}
+
+TEST_F(GeolocationNetworkProviderTest, NoRequestOnStartupUntilWifiData) {
+ MessageLoopQuitListener listener;
+ wifi_data_provider_->set_got_data(false);
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+
+ provider->SetUpdateCallback(
+ base::Bind(&MessageLoopQuitListener::LocationUpdateAvailable,
+ base::Unretained(&listener)));
+
+ main_message_loop_.RunUntilIdle();
+ EXPECT_FALSE(get_url_fetcher_and_advance_id())
+ << "Network request should not be created right away on startup when "
+ "wifi data has not yet arrived";
+
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(1));
+ main_message_loop_.RunUntilIdle();
+ EXPECT_TRUE(get_url_fetcher_and_advance_id());
+}
+
+TEST_F(GeolocationNetworkProviderTest, NewDataReplacesExistingNetworkRequest) {
+ // Send initial request with empty device data
+ scoped_ptr<LocationProvider> provider(CreateProvider(true));
+ EXPECT_TRUE(provider->StartProvider(false));
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_TRUE(fetcher);
+
+ // Now wifi data arrives; new request should be sent.
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(4));
+ main_message_loop_.RunUntilIdle();
+ fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_TRUE(fetcher);
+}
+
+TEST_F(GeolocationNetworkProviderTest, NetworkRequestDeferredForPermission) {
+ scoped_ptr<LocationProvider> provider(CreateProvider(false));
+ EXPECT_TRUE(provider->StartProvider(false));
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_FALSE(fetcher);
+ provider->OnPermissionGranted();
+
+ fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+
+ EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
+}
+
+TEST_F(GeolocationNetworkProviderTest,
+ NetworkRequestWithWifiDataDeferredForPermission) {
+ access_token_store_->access_token_set_[test_server_url_] =
+ UTF8ToUTF16(REFERENCE_ACCESS_TOKEN);
+ scoped_ptr<LocationProvider> provider(CreateProvider(false));
+ EXPECT_TRUE(provider->StartProvider(false));
+ net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_FALSE(fetcher);
+
+ static const int kScanCount = 4;
+ wifi_data_provider_->SetData(CreateReferenceWifiScanData(kScanCount));
+ main_message_loop_.RunUntilIdle();
+
+ fetcher = get_url_fetcher_and_advance_id();
+ EXPECT_FALSE(fetcher);
+
+ provider->OnPermissionGranted();
+
+ fetcher = get_url_fetcher_and_advance_id();
+ ASSERT_TRUE(fetcher != NULL);
+
+ CheckRequestIsValid(*fetcher, 0, kScanCount, 0, REFERENCE_ACCESS_TOKEN);
+}
+
+TEST_F(GeolocationNetworkProviderTest, NetworkPositionCache) {
+ NetworkLocationProvider::PositionCache cache;
+
+ const int kCacheSize = NetworkLocationProvider::PositionCache::kMaximumSize;
+ for (int i = 1; i < kCacheSize * 2 + 1; ++i) {
+ Geoposition pos = CreateReferencePosition(i);
+ bool ret = cache.CachePosition(CreateReferenceWifiScanData(i), pos);
+ EXPECT_TRUE(ret) << i;
+ const Geoposition* item =
+ cache.FindPosition(CreateReferenceWifiScanData(i));
+ ASSERT_TRUE(item) << i;
+ EXPECT_EQ(pos.latitude, item->latitude) << i;
+ EXPECT_EQ(pos.longitude, item->longitude) << i;
+ if (i <= kCacheSize) {
+ // Nothing should have spilled yet; check oldest item is still there.
+ EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(1)));
+ } else {
+ const int evicted = i - kCacheSize;
+ EXPECT_FALSE(cache.FindPosition(CreateReferenceWifiScanData(evicted)));
+ EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(evicted + 1)));
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/network_location_request.cc b/chromium/content/browser/geolocation/network_location_request.cc
new file mode 100644
index 00000000000..74a77e1f459
--- /dev/null
+++ b/chromium/content/browser/geolocation/network_location_request.cc
@@ -0,0 +1,392 @@
+// 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 "content/browser/geolocation/network_location_request.h"
+
+#include <set>
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/geolocation/location_arbitrator_impl.h"
+#include "content/public/common/geoposition.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+namespace {
+
+const size_t kMaxRequestLength = 2048;
+
+const char kAccessTokenString[] = "accessToken";
+const char kLocationString[] = "location";
+const char kLatitudeString[] = "lat";
+const char kLongitudeString[] = "lng";
+const char kAccuracyString[] = "accuracy";
+const char kStatusString[] = "status";
+const char kStatusOKString[] = "OK";
+
+// Local functions
+// Creates the request url to send to the server.
+GURL FormRequestURL(const GURL& url);
+
+void FormUploadData(const WifiData& wifi_data,
+ const base::Time& timestamp,
+ const string16& access_token,
+ std::string* upload_data);
+
+// Parsers the server response.
+void GetLocationFromResponse(bool http_post_result,
+ int status_code,
+ const std::string& response_body,
+ const base::Time& timestamp,
+ const GURL& server_url,
+ Geoposition* position,
+ string16* access_token);
+
+// Parses the server response body. Returns true if parsing was successful.
+// Sets |*position| to the parsed location if a valid fix was received,
+// otherwise leaves it unchanged.
+bool ParseServerResponse(const std::string& response_body,
+ const base::Time& timestamp,
+ Geoposition* position,
+ string16* access_token);
+void AddWifiData(const WifiData& wifi_data,
+ int age_milliseconds,
+ base::DictionaryValue* request);
+} // namespace
+
+int NetworkLocationRequest::url_fetcher_id_for_tests = 0;
+
+NetworkLocationRequest::NetworkLocationRequest(
+ net::URLRequestContextGetter* context,
+ const GURL& url,
+ ListenerInterface* listener)
+ : url_context_(context), listener_(listener),
+ url_(url) {
+ DCHECK(listener);
+}
+
+NetworkLocationRequest::~NetworkLocationRequest() {
+}
+
+bool NetworkLocationRequest::MakeRequest(const string16& access_token,
+ const WifiData& wifi_data,
+ const base::Time& timestamp) {
+ if (url_fetcher_ != NULL) {
+ DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
+ url_fetcher_.reset();
+ }
+ wifi_data_ = wifi_data;
+ timestamp_ = timestamp;
+
+ GURL request_url = FormRequestURL(url_);
+ url_fetcher_.reset(net::URLFetcher::Create(
+ url_fetcher_id_for_tests, request_url, net::URLFetcher::POST, this));
+ url_fetcher_->SetRequestContext(url_context_.get());
+ std::string upload_data;
+ FormUploadData(wifi_data, timestamp, access_token, &upload_data);
+ url_fetcher_->SetUploadData("application/json", upload_data);
+ url_fetcher_->SetLoadFlags(
+ net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
+ net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+
+ start_time_ = base::TimeTicks::Now();
+ url_fetcher_->Start();
+ return true;
+}
+
+void NetworkLocationRequest::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ DCHECK_EQ(url_fetcher_.get(), source);
+
+ net::URLRequestStatus status = source->GetStatus();
+ int response_code = source->GetResponseCode();
+
+ Geoposition position;
+ string16 access_token;
+ std::string data;
+ source->GetResponseAsString(&data);
+ GetLocationFromResponse(status.is_success(),
+ response_code,
+ data,
+ timestamp_,
+ source->GetURL(),
+ &position,
+ &access_token);
+ const bool server_error =
+ !status.is_success() || (response_code >= 500 && response_code < 600);
+ url_fetcher_.reset();
+
+ if (!server_error) {
+ const base::TimeDelta request_time = base::TimeTicks::Now() - start_time_;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Wifi.LbsLatency",
+ request_time,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromSeconds(10),
+ 100);
+ }
+
+ DCHECK(listener_);
+ DVLOG(1) << "NetworkLocationRequest::Run() : Calling listener with position.";
+ listener_->LocationResponseAvailable(position, server_error, access_token,
+ wifi_data_);
+}
+
+// Local functions.
+namespace {
+
+struct AccessPointLess {
+ bool operator()(const AccessPointData* ap1,
+ const AccessPointData* ap2) const {
+ return ap2->radio_signal_strength < ap1->radio_signal_strength;
+ }
+};
+
+GURL FormRequestURL(const GURL& url) {
+ if (url == GeolocationArbitratorImpl::DefaultNetworkProviderURL()) {
+ std::string api_key = google_apis::GetAPIKey();
+ if (!api_key.empty()) {
+ std::string query(url.query());
+ if (!query.empty())
+ query += "&";
+ query += "key=" + net::EscapeQueryParamValue(api_key, true);
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ return url.ReplaceComponents(replacements);
+ }
+ }
+ return url;
+}
+
+void FormUploadData(const WifiData& wifi_data,
+ const base::Time& timestamp,
+ const string16& access_token,
+ std::string* upload_data) {
+ int age = kint32min; // Invalid so AddInteger() will ignore.
+ if (!timestamp.is_null()) {
+ // Convert absolute timestamps into a relative age.
+ int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds();
+ if (delta_ms >= 0 && delta_ms < kint32max)
+ age = static_cast<int>(delta_ms);
+ }
+
+ base::DictionaryValue request;
+ AddWifiData(wifi_data, age, &request);
+ if (!access_token.empty())
+ request.SetString(kAccessTokenString, access_token);
+ base::JSONWriter::Write(&request, upload_data);
+}
+
+void AddString(const std::string& property_name, const std::string& value,
+ base::DictionaryValue* dict) {
+ DCHECK(dict);
+ if (!value.empty())
+ dict->SetString(property_name, value);
+}
+
+void AddInteger(const std::string& property_name, int value,
+ base::DictionaryValue* dict) {
+ DCHECK(dict);
+ if (value != kint32min)
+ dict->SetInteger(property_name, value);
+}
+
+void AddWifiData(const WifiData& wifi_data,
+ int age_milliseconds,
+ base::DictionaryValue* request) {
+ DCHECK(request);
+
+ if (wifi_data.access_point_data.empty())
+ return;
+
+ typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet;
+ AccessPointSet access_points_by_signal_strength;
+
+ for (WifiData::AccessPointDataSet::const_iterator iter =
+ wifi_data.access_point_data.begin();
+ iter != wifi_data.access_point_data.end();
+ ++iter) {
+ access_points_by_signal_strength.insert(&(*iter));
+ }
+
+ base::ListValue* wifi_access_point_list = new base::ListValue();
+ for (AccessPointSet::iterator iter =
+ access_points_by_signal_strength.begin();
+ iter != access_points_by_signal_strength.end();
+ ++iter) {
+ base::DictionaryValue* wifi_dict = new base::DictionaryValue();
+ AddString("macAddress", UTF16ToUTF8((*iter)->mac_address), wifi_dict);
+ AddInteger("signalStrength", (*iter)->radio_signal_strength, wifi_dict);
+ AddInteger("age", age_milliseconds, wifi_dict);
+ AddInteger("channel", (*iter)->channel, wifi_dict);
+ AddInteger("signalToNoiseRatio", (*iter)->signal_to_noise, wifi_dict);
+ wifi_access_point_list->Append(wifi_dict);
+ }
+ request->Set("wifiAccessPoints", wifi_access_point_list);
+}
+
+void FormatPositionError(const GURL& server_url,
+ const std::string& message,
+ Geoposition* position) {
+ position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
+ position->error_message = "Network location provider at '";
+ position->error_message += server_url.GetOrigin().spec();
+ position->error_message += "' : ";
+ position->error_message += message;
+ position->error_message += ".";
+ VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : "
+ << position->error_message;
+}
+
+void GetLocationFromResponse(bool http_post_result,
+ int status_code,
+ const std::string& response_body,
+ const base::Time& timestamp,
+ const GURL& server_url,
+ Geoposition* position,
+ string16* access_token) {
+ DCHECK(position);
+ DCHECK(access_token);
+
+ // HttpPost can fail for a number of reasons. Most likely this is because
+ // we're offline, or there was no response.
+ if (!http_post_result) {
+ FormatPositionError(server_url, "No response received", position);
+ return;
+ }
+ if (status_code != 200) { // HTTP OK.
+ std::string message = "Returned error code ";
+ message += base::IntToString(status_code);
+ FormatPositionError(server_url, message, position);
+ return;
+ }
+ // We use the timestamp from the device data that was used to generate
+ // this position fix.
+ if (!ParseServerResponse(response_body, timestamp, position, access_token)) {
+ // We failed to parse the repsonse.
+ FormatPositionError(server_url, "Response was malformed", position);
+ return;
+ }
+ // The response was successfully parsed, but it may not be a valid
+ // position fix.
+ if (!position->Validate()) {
+ FormatPositionError(server_url,
+ "Did not provide a good position fix", position);
+ return;
+ }
+}
+
+// Numeric values without a decimal point have type integer and IsDouble() will
+// return false. This is convenience function for detecting integer or floating
+// point numeric values. Note that isIntegral() includes boolean values, which
+// is not what we want.
+bool GetAsDouble(const base::DictionaryValue& object,
+ const std::string& property_name,
+ double* out) {
+ DCHECK(out);
+ const base::Value* value = NULL;
+ if (!object.Get(property_name, &value))
+ return false;
+ int value_as_int;
+ DCHECK(value);
+ if (value->GetAsInteger(&value_as_int)) {
+ *out = value_as_int;
+ return true;
+ }
+ return value->GetAsDouble(out);
+}
+
+bool ParseServerResponse(const std::string& response_body,
+ const base::Time& timestamp,
+ Geoposition* position,
+ string16* access_token) {
+ DCHECK(position);
+ DCHECK(!position->Validate());
+ DCHECK(position->error_code == Geoposition::ERROR_CODE_NONE);
+ DCHECK(access_token);
+ DCHECK(!timestamp.is_null());
+
+ if (response_body.empty()) {
+ LOG(WARNING) << "ParseServerResponse() : Response was empty.";
+ return false;
+ }
+ DVLOG(1) << "ParseServerResponse() : Parsing response " << response_body;
+
+ // Parse the response, ignoring comments.
+ std::string error_msg;
+ scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
+ response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
+ if (response_value == NULL) {
+ LOG(WARNING) << "ParseServerResponse() : JSONReader failed : "
+ << error_msg;
+ return false;
+ }
+
+ if (!response_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ VLOG(1) << "ParseServerResponse() : Unexpected response type "
+ << response_value->GetType();
+ return false;
+ }
+ const base::DictionaryValue* response_object =
+ static_cast<base::DictionaryValue*>(response_value.get());
+
+ // Get the access token, if any.
+ response_object->GetString(kAccessTokenString, access_token);
+
+ // Get the location
+ const base::Value* location_value = NULL;
+ if (!response_object->Get(kLocationString, &location_value)) {
+ VLOG(1) << "ParseServerResponse() : Missing location attribute.";
+ // GLS returns a response with no location property to represent
+ // no fix available; return true to indicate successful parse.
+ return true;
+ }
+ DCHECK(location_value);
+
+ if (!location_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ if (!location_value->IsType(base::Value::TYPE_NULL)) {
+ VLOG(1) << "ParseServerResponse() : Unexpected location type "
+ << location_value->GetType();
+ // If the network provider was unable to provide a position fix, it should
+ // return a HTTP 200, with "location" : null. Otherwise it's an error.
+ return false;
+ }
+ return true; // Successfully parsed response containing no fix.
+ }
+ const base::DictionaryValue* location_object =
+ static_cast<const base::DictionaryValue*>(location_value);
+
+ // latitude and longitude fields are always required.
+ double latitude, longitude;
+ if (!GetAsDouble(*location_object, kLatitudeString, &latitude) ||
+ !GetAsDouble(*location_object, kLongitudeString, &longitude)) {
+ VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
+ return false;
+ }
+ // All error paths covered: now start actually modifying postion.
+ position->latitude = latitude;
+ position->longitude = longitude;
+ position->timestamp = timestamp;
+
+ // Other fields are optional.
+ GetAsDouble(*response_object, kAccuracyString, &position->accuracy);
+
+ return true;
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/network_location_request.h b/chromium/content/browser/geolocation/network_location_request.h
new file mode 100644
index 00000000000..36e843b4351
--- /dev/null
+++ b/chromium/content/browser/geolocation/network_location_request.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_REQUEST_H_
+#define CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_REQUEST_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/geolocation/device_data_provider.h"
+#include "content/common/content_export.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+namespace content {
+struct Geoposition;
+
+// Takes a set of device data and sends it to a server to get a position fix.
+// It performs formatting of the request and interpretation of the response.
+class NetworkLocationRequest : private net::URLFetcherDelegate {
+ public:
+ // ID passed to URLFetcher::Create(). Used for testing.
+ CONTENT_EXPORT static int url_fetcher_id_for_tests;
+ // Interface for receiving callbacks from a NetworkLocationRequest object.
+ class ListenerInterface {
+ public:
+ // Updates the listener with a new position. server_error indicates whether
+ // was a server or network error - either no response or a 500 error code.
+ virtual void LocationResponseAvailable(
+ const Geoposition& position,
+ bool server_error,
+ const string16& access_token,
+ const WifiData& wifi_data) = 0;
+
+ protected:
+ virtual ~ListenerInterface() {}
+ };
+
+ // |url| is the server address to which the request wil be sent.
+ NetworkLocationRequest(net::URLRequestContextGetter* context,
+ const GURL& url,
+ ListenerInterface* listener);
+ virtual ~NetworkLocationRequest();
+
+ // Makes a new request. Returns true if the new request was successfully
+ // started. In all cases, any currently pending request will be canceled.
+ bool MakeRequest(const string16& access_token,
+ const WifiData& wifi_data,
+ const base::Time& timestamp);
+
+ bool is_request_pending() const { return url_fetcher_ != NULL; }
+ const GURL& url() const { return url_; }
+
+ private:
+ // net::URLFetcherDelegate
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ scoped_refptr<net::URLRequestContextGetter> url_context_;
+ ListenerInterface* listener_;
+ const GURL url_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ // Keep a copy of the data sent in the request, so we can refer back to it
+ // when the response arrives.
+ WifiData wifi_data_;
+ base::Time timestamp_; // Timestamp of the above data, not of the request.
+
+ // The start time for the request.
+ base::TimeTicks start_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkLocationRequest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_NETWORK_LOCATION_REQUEST_H_
diff --git a/chromium/content/browser/geolocation/osx_wifi.h b/chromium/content/browser/geolocation/osx_wifi.h
new file mode 100644
index 00000000000..cef998b91d9
--- /dev/null
+++ b/chromium/content/browser/geolocation/osx_wifi.h
@@ -0,0 +1,102 @@
+// 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.
+
+// The remainder of this file is copied from the Gears project:
+// http://code.google.com/p/gears/source/browse/trunk/gears/geolocation/osx_wifi.h
+
+// The contents of this file are taken from Apple80211.h from the iStumbler
+// project (http://www.istumbler.net). This project is released under the BSD
+// license with the following restrictions.
+//
+// Copyright (c) 02006, Alf Watt (alf@istumbler.net). All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// * Neither the name of iStumbler nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This is the reverse engineered header for the Apple80211 private framework.
+// The framework can be found at
+// /System/Library/PrivateFrameworks/Apple80211.framework.
+
+#ifndef CONTENT_BROWSER_GEOLOCATION_OSX_WIFI_H_
+#define CONTENT_BROWSER_GEOLOCATION_OSX_WIFI_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+extern "C" {
+
+typedef SInt32 WIErr;
+
+// A WirelessContext should be created using WirelessAttach
+// before any other Wireless functions are called. WirelessDetach
+// is used to dispose of a WirelessContext.
+struct WirelessContext;
+
+// WirelessAttach
+//
+// This should be called before all other wireless functions.
+typedef WIErr (*WirelessAttachFunction)(WirelessContext** outContext,
+ const UInt32);
+
+// WirelessDetach
+//
+// This should be called after all other wireless functions.
+typedef WIErr (*WirelessDetachFunction)(WirelessContext* inContext);
+
+typedef UInt16 WINetworkInfoFlags;
+
+struct WirelessNetworkInfo
+{
+ UInt16 channel; // Channel for the network.
+ SInt16 noise; // Noise for the network. 0 for Adhoc.
+ SInt16 signal; // Signal strength of the network. 0 for Adhoc.
+ UInt8 macAddress[6]; // MAC address of the wireless access point.
+ UInt16 beaconInterval; // Beacon interval in milliseconds
+ WINetworkInfoFlags flags; // Flags for the network
+ UInt16 nameLen;
+ SInt8 name[32];
+};
+
+// WirelessScanSplit
+//
+// WirelessScanSplit scans for available wireless networks. It will allocate 2
+// CFArrays to store a list of managed and adhoc networks. The arrays hold
+// CFData objects which contain WirelessNetworkInfo structures.
+//
+// Note: An adhoc network created on the computer the scan is running on will
+// not be found. WirelessGetInfo can be used to find info about a local adhoc
+// network.
+//
+// If stripDups != 0 only one bases tation for each SSID will be returned.
+typedef WIErr (*WirelessScanSplitFunction)(WirelessContext* inContext,
+ CFArrayRef* apList,
+ CFArrayRef* adhocList,
+ const UInt32 stripDups);
+
+} // extern "C"
+
+#endif // CONTENT_BROWSER_GEOLOCATION_OSX_WIFI_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_chromeos.cc b/chromium/content/browser/geolocation/wifi_data_provider_chromeos.cc
new file mode 100644
index 00000000000..b4222d7a01b
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_chromeos.cc
@@ -0,0 +1,176 @@
+// 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.
+
+// Provides wifi scan API binding for chromeos, using proprietary APIs.
+
+#include "content/browser/geolocation/wifi_data_provider_chromeos.h"
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/geolocation_handler.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+namespace {
+
+// The time periods between successive polls of the wifi data.
+const int kDefaultPollingIntervalMilliseconds = 10 * 1000; // 10s
+const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000; // 2 mins
+const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000; // 10 mins
+const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
+
+} // namespace
+
+WifiDataProviderChromeOs::WifiDataProviderChromeOs() : started_(false) {
+}
+
+WifiDataProviderChromeOs::~WifiDataProviderChromeOs() {
+}
+
+bool WifiDataProviderChromeOs::StartDataProvider() {
+ DCHECK(CalledOnClientThread());
+
+ DCHECK(polling_policy_ == NULL);
+ polling_policy_.reset(
+ new GenericPollingPolicy<kDefaultPollingIntervalMilliseconds,
+ kNoChangePollingIntervalMilliseconds,
+ kTwoNoChangePollingIntervalMilliseconds,
+ kNoWifiPollingIntervalMilliseconds>);
+
+ ScheduleStart();
+ return true;
+}
+
+void WifiDataProviderChromeOs::StopDataProvider() {
+ DCHECK(CalledOnClientThread());
+
+ polling_policy_.reset();
+ ScheduleStop();
+}
+
+bool WifiDataProviderChromeOs::GetData(WifiData* data) {
+ DCHECK(CalledOnClientThread());
+ DCHECK(data);
+ *data = wifi_data_;
+ return is_first_scan_complete_;
+}
+
+void WifiDataProviderChromeOs::DoStartTaskOnUIThread() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DoWifiScanTaskOnUIThread();
+}
+
+void WifiDataProviderChromeOs::DoWifiScanTaskOnUIThread() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // This method could be scheduled after a ScheduleStop.
+ if (!started_)
+ return;
+
+ WifiData new_data;
+
+ if (!GetAccessPointData(&new_data.access_point_data)) {
+ client_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&WifiDataProviderChromeOs::DidWifiScanTaskNoResults, this));
+ } else {
+ client_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&WifiDataProviderChromeOs::DidWifiScanTask, this, new_data));
+ }
+}
+
+void WifiDataProviderChromeOs::DidWifiScanTaskNoResults() {
+ DCHECK(CalledOnClientThread());
+ // Schedule next scan if started (StopDataProvider could have been called
+ // in between DoWifiScanTaskOnUIThread and this method).
+ if (started_)
+ ScheduleNextScan(polling_policy_->NoWifiInterval());
+ MaybeNotifyListeners(false);
+}
+
+void WifiDataProviderChromeOs::DidWifiScanTask(const WifiData& new_data) {
+ DCHECK(CalledOnClientThread());
+ bool update_available = wifi_data_.DiffersSignificantly(new_data);
+ wifi_data_ = new_data;
+ // Schedule next scan if started (StopDataProvider could have been called
+ // in between DoWifiScanTaskOnUIThread and this method).
+ if (started_) {
+ polling_policy_->UpdatePollingInterval(update_available);
+ ScheduleNextScan(polling_policy_->PollingInterval());
+ }
+ MaybeNotifyListeners(update_available);
+}
+
+void WifiDataProviderChromeOs::MaybeNotifyListeners(bool update_available) {
+ if (update_available || !is_first_scan_complete_) {
+ is_first_scan_complete_ = true;
+ NotifyListeners();
+ }
+}
+
+void WifiDataProviderChromeOs::ScheduleNextScan(int interval) {
+ DCHECK(CalledOnClientThread());
+ DCHECK(started_);
+ BrowserThread::PostDelayedTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WifiDataProviderChromeOs::DoWifiScanTaskOnUIThread, this),
+ base::TimeDelta::FromMilliseconds(interval));
+}
+
+void WifiDataProviderChromeOs::ScheduleStop() {
+ DCHECK(CalledOnClientThread());
+ DCHECK(started_);
+ started_ = false;
+}
+
+void WifiDataProviderChromeOs::ScheduleStart() {
+ DCHECK(CalledOnClientThread());
+ DCHECK(!started_);
+ started_ = true;
+ // Perform first scan ASAP regardless of the polling policy. If this scan
+ // fails we'll retry at a rate in line with the polling policy.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WifiDataProviderChromeOs::DoStartTaskOnUIThread, this));
+}
+
+bool WifiDataProviderChromeOs::GetAccessPointData(
+ WifiData::AccessPointDataSet* result) {
+ chromeos::WifiAccessPointVector access_points;
+ if (!chromeos::NetworkHandler::Get()->geolocation_handler()->wifi_enabled())
+ return false;
+ int64 age_ms = 0;
+ if (!chromeos::NetworkHandler::Get()->geolocation_handler()->
+ GetWifiAccessPoints(&access_points, &age_ms)) {
+ return false;
+ }
+ for (chromeos::WifiAccessPointVector::const_iterator i
+ = access_points.begin();
+ i != access_points.end(); ++i) {
+ AccessPointData ap_data;
+ ap_data.mac_address = ASCIIToUTF16(i->mac_address);
+ ap_data.radio_signal_strength = i->signal_strength;
+ ap_data.channel = i->channel;
+ ap_data.signal_to_noise = i->signal_to_noise;
+ ap_data.ssid = UTF8ToUTF16(i->ssid);
+ result->insert(ap_data);
+ }
+ // If the age is significantly longer than our long polling time, assume the
+ // data is stale and return false which will trigger a faster update.
+ if (age_ms > kTwoNoChangePollingIntervalMilliseconds * 2)
+ return false;
+ return true;
+}
+
+// static
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new WifiDataProviderChromeOs();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_chromeos.h b/chromium/content/browser/geolocation/wifi_data_provider_chromeos.h
new file mode 100644
index 00000000000..ac8902c9874
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_chromeos.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_CHROMEOS_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_CHROMEOS_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+
+namespace content {
+
+class CONTENT_EXPORT WifiDataProviderChromeOs
+ : public WifiDataProviderImplBase {
+ public:
+ WifiDataProviderChromeOs();
+
+ // WifiDataProviderImplBase
+ virtual bool StartDataProvider() OVERRIDE;
+ virtual void StopDataProvider() OVERRIDE;
+ virtual bool GetData(WifiData* data) OVERRIDE;
+
+ private:
+ friend class GeolocationChromeOsWifiDataProviderTest;
+ virtual ~WifiDataProviderChromeOs();
+
+ // UI thread
+ void DoWifiScanTaskOnUIThread(); // The polling task
+ void DoStartTaskOnUIThread();
+
+ // Client thread
+ void DidWifiScanTaskNoResults();
+ void DidWifiScanTask(const WifiData& new_data);
+ void MaybeNotifyListeners(bool update_available);
+
+ // Will schedule a scan; i.e. enqueue DoWifiScanTask deferred task.
+ void ScheduleNextScan(int interval);
+
+ // Will schedule starting of the scanning process.
+ void ScheduleStart();
+
+ // Will schedule stopping of the scanning process.
+ void ScheduleStop();
+
+ // Get access point data from chromeos.
+ bool GetAccessPointData(WifiData::AccessPointDataSet* data);
+
+ // Underlying OS wifi API. (UI thread)
+ scoped_ptr<WifiDataProviderCommon::WlanApiInterface> wlan_api_;
+
+ // Controls the polling update interval. (client thread)
+ scoped_ptr<PollingPolicyInterface> polling_policy_;
+
+ // The latest wifi data. (client thread)
+ WifiData wifi_data_;
+
+ // Whether we have strated the data provider. (client thread)
+ bool started_;
+
+ // Whether we've successfully completed a scan for WiFi data. (client thread)
+ bool is_first_scan_complete_;
+
+ DISALLOW_COPY_AND_ASSIGN(WifiDataProviderChromeOs);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_CHROMEOS_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_chromeos_unittest.cc b/chromium/content/browser/geolocation/wifi_data_provider_chromeos_unittest.cc
new file mode 100644
index 00000000000..541b28422b2
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_chromeos_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 The Chromium 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/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill_manager_client.h"
+#include "chromeos/network/geolocation_handler.h"
+#include "content/browser/geolocation/wifi_data_provider_chromeos.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace content {
+
+class GeolocationChromeOsWifiDataProviderTest : public testing::Test {
+ protected:
+ GeolocationChromeOsWifiDataProviderTest() {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ chromeos::DBusThreadManager::InitializeWithStub();
+ chromeos::NetworkHandler::Initialize();
+ manager_client_ =
+ chromeos::DBusThreadManager::Get()->GetShillManagerClient();
+ manager_test_ = manager_client_->GetTestInterface();
+ provider_ = new WifiDataProviderChromeOs();
+ message_loop_.RunUntilIdle();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ provider_ = NULL;
+ chromeos::NetworkHandler::Shutdown();
+ chromeos::DBusThreadManager::Shutdown();
+ }
+
+ bool GetAccessPointData() {
+ return provider_->GetAccessPointData(&ap_data_);
+ }
+
+ void AddAccessPoints(int ssids, int aps_per_ssid) {
+ for (int i = 0; i < ssids; ++i) {
+ for (int j = 0; j < aps_per_ssid; ++j) {
+ base::DictionaryValue properties;
+ std::string mac_address =
+ base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X",
+ i, j, 3, 4, 5, 6);
+ std::string channel = base::StringPrintf("%d", i * 10 + j);
+ std::string strength = base::StringPrintf("%d", i * 100 + j);
+ properties.SetStringWithoutPathExpansion(
+ shill::kGeoMacAddressProperty, mac_address);
+ properties.SetStringWithoutPathExpansion(
+ shill::kGeoChannelProperty, channel);
+ properties.SetStringWithoutPathExpansion(
+ shill::kGeoSignalStrengthProperty, strength);
+ manager_test_->AddGeoNetwork(flimflam::kTypeWifi, properties);
+ }
+ }
+ message_loop_.RunUntilIdle();
+ }
+
+ base::MessageLoopForUI message_loop_;
+ scoped_refptr<WifiDataProviderChromeOs> provider_;
+ chromeos::ShillManagerClient* manager_client_;
+ chromeos::ShillManagerClient::TestInterface* manager_test_;
+ WifiData::AccessPointDataSet ap_data_;
+};
+
+TEST_F(GeolocationChromeOsWifiDataProviderTest, NoAccessPoints) {
+ message_loop_.RunUntilIdle();
+ // Initial call to GetAccessPointData requests data and will return false.
+ EXPECT_FALSE(GetAccessPointData());
+ message_loop_.RunUntilIdle();
+ // Additional call to GetAccessPointData also returns false with no devices.
+ EXPECT_FALSE(GetAccessPointData());
+ EXPECT_EQ(0u, ap_data_.size());
+}
+
+TEST_F(GeolocationChromeOsWifiDataProviderTest, GetOneAccessPoint) {
+ message_loop_.RunUntilIdle();
+ EXPECT_FALSE(GetAccessPointData());
+
+ AddAccessPoints(1, 1);
+ EXPECT_TRUE(GetAccessPointData());
+ ASSERT_EQ(1u, ap_data_.size());
+ EXPECT_EQ("00:00:03:04:05:06", UTF16ToUTF8(ap_data_.begin()->mac_address));
+}
+
+TEST_F(GeolocationChromeOsWifiDataProviderTest, GetManyAccessPoints) {
+ message_loop_.RunUntilIdle();
+ EXPECT_FALSE(GetAccessPointData());
+
+ AddAccessPoints(3, 4);
+ EXPECT_TRUE(GetAccessPointData());
+ ASSERT_EQ(12u, ap_data_.size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_common.cc b/chromium/content/browser/geolocation/wifi_data_provider_common.cc
new file mode 100644
index 00000000000..2642176358c
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_common.cc
@@ -0,0 +1,113 @@
+// 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 "content/browser/geolocation/wifi_data_provider_common.h"
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace content {
+
+string16 MacAddressAsString16(const uint8 mac_as_int[6]) {
+ // mac_as_int is big-endian. Write in byte chunks.
+ // Format is XX-XX-XX-XX-XX-XX.
+ static const char* const kMacFormatString =
+ "%02x-%02x-%02x-%02x-%02x-%02x";
+ return ASCIIToUTF16(base::StringPrintf(kMacFormatString,
+ mac_as_int[0],
+ mac_as_int[1],
+ mac_as_int[2],
+ mac_as_int[3],
+ mac_as_int[4],
+ mac_as_int[5]));
+}
+
+WifiDataProviderCommon::WifiDataProviderCommon()
+ : Thread("Geolocation_wifi_provider"),
+ is_first_scan_complete_(false),
+ weak_factory_(this) {
+}
+
+WifiDataProviderCommon::~WifiDataProviderCommon() {
+ // Thread must be stopped before entering destructor chain to avoid race
+ // conditions; see comment in DeviceDataProvider::Unregister.
+ DCHECK(!IsRunning()); // Must call StopDataProvider before destroying me.
+}
+
+bool WifiDataProviderCommon::StartDataProvider() {
+ DCHECK(CalledOnClientThread());
+ DCHECK(!IsRunning()); // StartDataProvider must only be called once.
+ return Start();
+}
+
+void WifiDataProviderCommon::StopDataProvider() {
+ DCHECK(CalledOnClientThread());
+ Stop();
+}
+
+bool WifiDataProviderCommon::GetData(WifiData* data) {
+ DCHECK(CalledOnClientThread());
+ DCHECK(data);
+ base::AutoLock lock(data_mutex_);
+ *data = wifi_data_;
+ // If we've successfully completed a scan, indicate that we have all of the
+ // data we can get.
+ return is_first_scan_complete_;
+}
+
+// Thread implementation
+void WifiDataProviderCommon::Init() {
+ DCHECK(wlan_api_ == NULL);
+ wlan_api_.reset(NewWlanApi());
+ if (wlan_api_ == NULL) {
+ // Error! Can't do scans, so don't try and schedule one.
+ is_first_scan_complete_ = true;
+ return;
+ }
+
+ DCHECK(polling_policy_ == NULL);
+ polling_policy_.reset(NewPollingPolicy());
+ DCHECK(polling_policy_ != NULL);
+
+ // Perform first scan ASAP regardless of the polling policy. If this scan
+ // fails we'll retry at a rate in line with the polling policy.
+ ScheduleNextScan(0);
+}
+
+void WifiDataProviderCommon::CleanUp() {
+ // Destroy these instances in the thread on which they were created.
+ wlan_api_.reset();
+ polling_policy_.reset();
+}
+
+void WifiDataProviderCommon::DoWifiScanTask() {
+ bool update_available = false;
+ WifiData new_data;
+ if (!wlan_api_->GetAccessPointData(&new_data.access_point_data)) {
+ ScheduleNextScan(polling_policy_->NoWifiInterval());
+ } else {
+ {
+ base::AutoLock lock(data_mutex_);
+ update_available = wifi_data_.DiffersSignificantly(new_data);
+ wifi_data_ = new_data;
+ }
+ polling_policy_->UpdatePollingInterval(update_available);
+ ScheduleNextScan(polling_policy_->PollingInterval());
+ }
+ if (update_available || !is_first_scan_complete_) {
+ is_first_scan_complete_ = true;
+ NotifyListeners();
+ }
+}
+
+void WifiDataProviderCommon::ScheduleNextScan(int interval) {
+ message_loop()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&WifiDataProviderCommon::DoWifiScanTask,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(interval));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_common.h b/chromium/content/browser/geolocation/wifi_data_provider_common.h
new file mode 100644
index 00000000000..e4bed573201
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_common.h
@@ -0,0 +1,130 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_H_
+
+#include <assert.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread.h"
+#include "content/browser/geolocation/device_data_provider.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Converts a MAC address stored as an array of uint8 to a string.
+string16 MacAddressAsString16(const uint8 mac_as_int[6]);
+
+// Allows sharing and mocking of the update polling policy function.
+class PollingPolicyInterface {
+ public:
+ virtual ~PollingPolicyInterface() {}
+ // Calculates the new polling interval for wiFi scans, given the previous
+ // interval and whether the last scan produced new results.
+ virtual void UpdatePollingInterval(bool scan_results_differ) = 0;
+ virtual int PollingInterval() = 0;
+ virtual int NoWifiInterval() = 0;
+};
+
+// Generic polling policy, constants are compile-time parameterized to allow
+// tuning on a per-platform basis.
+template<int DEFAULT_INTERVAL,
+ int NO_CHANGE_INTERVAL,
+ int TWO_NO_CHANGE_INTERVAL,
+ int NO_WIFI_INTERVAL>
+class GenericPollingPolicy : public PollingPolicyInterface {
+ public:
+ GenericPollingPolicy() : polling_interval_(DEFAULT_INTERVAL) {}
+ // PollingPolicyInterface
+ virtual void UpdatePollingInterval(bool scan_results_differ) {
+ if (scan_results_differ) {
+ polling_interval_ = DEFAULT_INTERVAL;
+ } else if (polling_interval_ == DEFAULT_INTERVAL) {
+ polling_interval_ = NO_CHANGE_INTERVAL;
+ } else {
+ DCHECK(polling_interval_ == NO_CHANGE_INTERVAL ||
+ polling_interval_ == TWO_NO_CHANGE_INTERVAL);
+ polling_interval_ = TWO_NO_CHANGE_INTERVAL;
+ }
+ }
+ virtual int PollingInterval() { return polling_interval_; }
+ virtual int NoWifiInterval() { return NO_WIFI_INTERVAL; }
+
+ private:
+ int polling_interval_;
+};
+
+// Base class to promote code sharing between platform specific wifi data
+// providers. It's optional for specific platforms to derive this, but if they
+// do threading and polling is taken care of by this base class, and all the
+// platform need do is provide the underlying WLAN access API and policy policy,
+// both of which will be create & accessed in the worker thread (only).
+// Also designed this way to promotes ease of testing the cross-platform
+// behavior w.r.t. polling & threading.
+class CONTENT_EXPORT WifiDataProviderCommon
+ : public WifiDataProviderImplBase,
+ private base::Thread {
+ public:
+ // Interface to abstract the low level data OS library call, and to allow
+ // mocking (hence public).
+ class WlanApiInterface {
+ public:
+ virtual ~WlanApiInterface() {}
+ // Gets wifi data for all visible access points.
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) = 0;
+ };
+
+ WifiDataProviderCommon();
+
+ // WifiDataProviderImplBase implementation
+ virtual bool StartDataProvider() OVERRIDE;
+ virtual void StopDataProvider() OVERRIDE;
+ virtual bool GetData(WifiData* data) OVERRIDE;
+
+ protected:
+ virtual ~WifiDataProviderCommon();
+
+ // Returns ownership. Will be called from the worker thread.
+ virtual WlanApiInterface* NewWlanApi() = 0;
+
+ // Returns ownership. Will be called from the worker thread.
+ virtual PollingPolicyInterface* NewPollingPolicy() = 0;
+
+ private:
+ // Thread implementation
+ virtual void Init() OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ // Task which run in the child thread.
+ void DoWifiScanTask();
+
+ // Will schedule a scan; i.e. enqueue DoWifiScanTask deferred task.
+ void ScheduleNextScan(int interval);
+
+ WifiData wifi_data_;
+ base::Lock data_mutex_;
+
+ // Whether we've successfully completed a scan for WiFi data (or the polling
+ // thread has terminated early).
+ bool is_first_scan_complete_;
+
+ // Underlying OS wifi API.
+ scoped_ptr<WlanApiInterface> wlan_api_;
+
+ // Controls the polling update interval.
+ scoped_ptr<PollingPolicyInterface> polling_policy_;
+
+ // Holder for the tasks which run on the thread; takes care of cleanup.
+ base::WeakPtrFactory<WifiDataProviderCommon> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WifiDataProviderCommon);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_common_unittest.cc b/chromium/content/browser/geolocation/wifi_data_provider_common_unittest.cc
new file mode 100644
index 00000000000..9d1b9fbcc7b
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_common_unittest.cc
@@ -0,0 +1,233 @@
+// 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 <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AtLeast;
+using testing::DoDefault;
+using testing::Invoke;
+using testing::Return;
+
+namespace content {
+
+class MockWlanApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ MockWlanApi() : calls_(0), bool_return_(true) {
+ ANNOTATE_BENIGN_RACE(&calls_, "This is a test-only data race on a counter");
+ ON_CALL(*this, GetAccessPointData(_))
+ .WillByDefault(Invoke(this, &MockWlanApi::GetAccessPointDataInternal));
+ }
+
+ MOCK_METHOD1(GetAccessPointData, bool(WifiData::AccessPointDataSet* data));
+
+ int calls_;
+ bool bool_return_;
+ WifiData::AccessPointDataSet data_out_;
+
+ private:
+ bool GetAccessPointDataInternal(WifiData::AccessPointDataSet* data) {
+ ++calls_;
+ *data = data_out_;
+ return bool_return_;
+ }
+};
+
+class MockPollingPolicy :public PollingPolicyInterface {
+ public:
+ MockPollingPolicy() {
+ ON_CALL(*this,PollingInterval())
+ .WillByDefault(Return(1));
+ ON_CALL(*this,NoWifiInterval())
+ .WillByDefault(Return(1));
+ }
+
+ MOCK_METHOD0(PollingInterval, int());
+ MOCK_METHOD0(NoWifiInterval, int());
+
+ virtual void UpdatePollingInterval(bool) {}
+};
+
+// Stops the specified (nested) message loop when the listener is called back.
+class MessageLoopQuitListener
+ : public WifiDataProviderCommon::ListenerInterface {
+ public:
+ explicit MessageLoopQuitListener(base::MessageLoop* message_loop)
+ : message_loop_to_quit_(message_loop) {
+ CHECK(message_loop_to_quit_ != NULL);
+ }
+ // ListenerInterface
+ virtual void DeviceDataUpdateAvailable(
+ DeviceDataProvider<WifiData>* provider) OVERRIDE {
+ // Provider should call back on client's thread.
+ EXPECT_EQ(base::MessageLoop::current(), message_loop_to_quit_);
+ provider_ = provider;
+ message_loop_to_quit_->QuitNow();
+ }
+ base::MessageLoop* message_loop_to_quit_;
+ DeviceDataProvider<WifiData>* provider_;
+};
+
+
+class WifiDataProviderCommonWithMock : public WifiDataProviderCommon {
+ public:
+ WifiDataProviderCommonWithMock()
+ : new_wlan_api_(new MockWlanApi),
+ new_polling_policy_(new MockPollingPolicy) {}
+
+ // WifiDataProviderCommon
+ virtual WlanApiInterface* NewWlanApi() OVERRIDE {
+ CHECK(new_wlan_api_ != NULL);
+ return new_wlan_api_.release();
+ }
+ virtual PollingPolicyInterface* NewPollingPolicy() OVERRIDE {
+ CHECK(new_polling_policy_ != NULL);
+ return new_polling_policy_.release();
+ }
+
+ scoped_ptr<MockWlanApi> new_wlan_api_;
+ scoped_ptr<MockPollingPolicy> new_polling_policy_;
+
+ private:
+ virtual ~WifiDataProviderCommonWithMock() {}
+
+ DISALLOW_COPY_AND_ASSIGN(WifiDataProviderCommonWithMock);
+};
+
+WifiDataProviderImplBase* CreateWifiDataProviderCommonWithMock() {
+ return new WifiDataProviderCommonWithMock;
+}
+
+// Main test fixture
+class GeolocationWifiDataProviderCommonTest : public testing::Test {
+ public:
+ GeolocationWifiDataProviderCommonTest()
+ : quit_listener_(&main_message_loop_) {
+ }
+
+ virtual void SetUp() {
+ provider_ = new WifiDataProviderCommonWithMock;
+ wlan_api_ = provider_->new_wlan_api_.get();
+ polling_policy_ = provider_->new_polling_policy_.get();
+ provider_->AddListener(&quit_listener_);
+ }
+ virtual void TearDown() {
+ provider_->RemoveListener(&quit_listener_);
+ provider_->StopDataProvider();
+ provider_ = NULL;
+ }
+
+ protected:
+ base::MessageLoop main_message_loop_;
+ MessageLoopQuitListener quit_listener_;
+ scoped_refptr<WifiDataProviderCommonWithMock> provider_;
+ MockWlanApi* wlan_api_;
+ MockPollingPolicy* polling_policy_;
+};
+
+TEST_F(GeolocationWifiDataProviderCommonTest, CreateDestroy) {
+ // Test fixture members were SetUp correctly.
+ EXPECT_EQ(&main_message_loop_, base::MessageLoop::current());
+ EXPECT_TRUE(NULL != provider_.get());
+ EXPECT_TRUE(NULL != wlan_api_);
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest, StartThread) {
+ EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*polling_policy_, PollingInterval())
+ .Times(AtLeast(1));
+ EXPECT_TRUE(provider_->StartDataProvider());
+ main_message_loop_.Run();
+ SUCCEED();
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest, NoWifi){
+ EXPECT_CALL(*polling_policy_, NoWifiInterval())
+ .Times(AtLeast(1));
+ EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+ .WillRepeatedly(Return(false));
+ provider_->StartDataProvider();
+ main_message_loop_.Run();
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest, IntermittentWifi){
+ EXPECT_CALL(*polling_policy_, PollingInterval())
+ .Times(AtLeast(1));
+ EXPECT_CALL(*polling_policy_, NoWifiInterval())
+ .Times(1);
+ EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false))
+ .WillRepeatedly(DoDefault());
+
+ AccessPointData single_access_point;
+ single_access_point.channel = 2;
+ single_access_point.mac_address = 3;
+ single_access_point.radio_signal_strength = 4;
+ single_access_point.signal_to_noise = 5;
+ single_access_point.ssid = ASCIIToUTF16("foossid");
+ wlan_api_->data_out_.insert(single_access_point);
+
+ provider_->StartDataProvider();
+ main_message_loop_.Run();
+ main_message_loop_.Run();
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest, DoAnEmptyScan) {
+ EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*polling_policy_, PollingInterval())
+ .Times(AtLeast(1));
+ EXPECT_TRUE(provider_->StartDataProvider());
+ main_message_loop_.Run();
+ // Check we had at least one call. The worker thread may have raced ahead
+ // and made multiple calls.
+ EXPECT_GT(wlan_api_->calls_, 0);
+ WifiData data;
+ EXPECT_TRUE(provider_->GetData(&data));
+ EXPECT_EQ(0, static_cast<int>(data.access_point_data.size()));
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest, DoScanWithResults) {
+ EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*polling_policy_, PollingInterval())
+ .Times(AtLeast(1));
+ AccessPointData single_access_point;
+ single_access_point.channel = 2;
+ single_access_point.mac_address = 3;
+ single_access_point.radio_signal_strength = 4;
+ single_access_point.signal_to_noise = 5;
+ single_access_point.ssid = ASCIIToUTF16("foossid");
+ wlan_api_->data_out_.insert(single_access_point);
+
+ EXPECT_TRUE(provider_->StartDataProvider());
+ main_message_loop_.Run();
+ EXPECT_GT(wlan_api_->calls_, 0);
+ WifiData data;
+ EXPECT_TRUE(provider_->GetData(&data));
+ EXPECT_EQ(1, static_cast<int>(data.access_point_data.size()));
+ EXPECT_EQ(single_access_point.ssid, data.access_point_data.begin()->ssid);
+}
+
+TEST_F(GeolocationWifiDataProviderCommonTest,
+ StartThreadViaDeviceDataProvider) {
+ MessageLoopQuitListener quit_listener(&main_message_loop_);
+ WifiDataProvider::SetFactory(CreateWifiDataProviderCommonWithMock);
+ DeviceDataProvider<WifiData>::Register(&quit_listener);
+ main_message_loop_.Run();
+ DeviceDataProvider<WifiData>::Unregister(&quit_listener);
+ DeviceDataProvider<WifiData>::ResetFactory();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_common_win.cc b/chromium/content/browser/geolocation/wifi_data_provider_common_win.cc
new file mode 100644
index 00000000000..9ac7848835d
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_common_win.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2010 The Chromium 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 "content/browser/geolocation/wifi_data_provider_common_win.h"
+
+#include <assert.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/geolocation/device_data_provider.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+
+namespace content {
+
+bool ConvertToAccessPointData(const NDIS_WLAN_BSSID& data,
+ AccessPointData *access_point_data) {
+ // Currently we get only MAC address, signal strength and SSID.
+ // TODO(steveblock): Work out how to get age, channel and signal-to-noise.
+ DCHECK(access_point_data);
+ access_point_data->mac_address = MacAddressAsString16(data.MacAddress);
+ access_point_data->radio_signal_strength = data.Rssi;
+ // Note that _NDIS_802_11_SSID::Ssid::Ssid is not null-terminated.
+ UTF8ToUTF16(reinterpret_cast<const char*>(data.Ssid.Ssid),
+ data.Ssid.SsidLength,
+ &access_point_data->ssid);
+ return true;
+}
+
+int GetDataFromBssIdList(const NDIS_802_11_BSSID_LIST& bss_id_list,
+ int list_size,
+ WifiData::AccessPointDataSet* data) {
+ // Walk through the BSS IDs.
+ int found = 0;
+ const uint8 *iterator = reinterpret_cast<const uint8*>(&bss_id_list.Bssid[0]);
+ const uint8 *end_of_buffer =
+ reinterpret_cast<const uint8*>(&bss_id_list) + list_size;
+ for (int i = 0; i < static_cast<int>(bss_id_list.NumberOfItems); ++i) {
+ const NDIS_WLAN_BSSID *bss_id =
+ reinterpret_cast<const NDIS_WLAN_BSSID*>(iterator);
+ // Check that the length of this BSS ID is reasonable.
+ if (bss_id->Length < sizeof(NDIS_WLAN_BSSID) ||
+ iterator + bss_id->Length > end_of_buffer) {
+ break;
+ }
+ AccessPointData access_point_data;
+ if (ConvertToAccessPointData(*bss_id, &access_point_data)) {
+ data->insert(access_point_data);
+ ++found;
+ }
+ // Move to the next BSS ID.
+ iterator += bss_id->Length;
+ }
+ return found;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_common_win.h b/chromium/content/browser/geolocation/wifi_data_provider_common_win.h
new file mode 100644
index 00000000000..f58e0c6d2b2
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_common_win.h
@@ -0,0 +1,24 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_WIN_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_WIN_H_
+
+#include <windows.h>
+#include <ntddndis.h>
+
+#include "content/browser/geolocation/device_data_provider.h"
+
+namespace content {
+
+// Extracts access point data from the NDIS_802_11_BSSID_LIST structure and
+// appends it to the data vector. Returns the number of access points for which
+// data was extracted.
+int GetDataFromBssIdList(const NDIS_802_11_BSSID_LIST& bss_id_list,
+ int list_size,
+ WifiData::AccessPointDataSet* data);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_COMMON_WIN_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_corewlan_mac.mm b/chromium/content/browser/geolocation/wifi_data_provider_corewlan_mac.mm
new file mode 100644
index 00000000000..81930e08f36
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_corewlan_mac.mm
@@ -0,0 +1,187 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Implements a WLAN API binding for CoreWLAN, as available on OSX 10.6
+
+#include "content/browser/geolocation/wifi_data_provider_mac.h"
+
+#include <dlfcn.h>
+#import <Foundation/Foundation.h>
+
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+
+// Define a subset of the CoreWLAN interfaces we require. We can't depend on
+// CoreWLAN.h existing as we need to build on 10.5 SDKs. We can't just send
+// messages to an untyped id due to the build treating warnings as errors,
+// hence the reason we need class definitions.
+// TODO(joth): When we build all 10.6 code exclusively 10.6 SDK (or later)
+// tidy this up to use the framework directly. See http://crbug.com/37703
+
+@interface CWInterface : NSObject
++ (CWInterface*)interface;
++ (CWInterface*)interfaceWithName:(NSString*)name;
++ (NSArray*)supportedInterfaces;
+- (NSArray*)scanForNetworksWithParameters:(NSDictionary*)parameters
+ error:(NSError**)error;
+@end
+
+@interface CWNetwork : NSObject <NSCopying, NSCoding>
+@property (nonatomic, readonly) NSString* ssid;
+@property (nonatomic, readonly) NSString* bssid;
+@property (nonatomic, readonly) NSData* bssidData;
+@property (nonatomic, readonly) NSNumber* securityMode;
+@property (nonatomic, readonly) NSNumber* phyMode;
+@property (nonatomic, readonly) NSNumber* channel;
+@property (nonatomic, readonly) NSNumber* rssi;
+@property (nonatomic, readonly) NSNumber* noise;
+@property (nonatomic, readonly) NSData* ieData;
+@property (nonatomic, readonly) BOOL isIBSS;
+- (BOOL)isEqualToNetwork:(CWNetwork*)network;
+@end
+
+namespace content {
+
+class CoreWlanApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ CoreWlanApi() {}
+
+ // Must be called before any other interface method. Will return false if the
+ // CoreWLAN framework cannot be initialized (e.g. running on pre-10.6 OSX),
+ // in which case no other method may be called.
+ bool Init();
+
+ // WlanApiInterface
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE;
+
+ private:
+ base::scoped_nsobject<NSBundle> bundle_;
+ base::scoped_nsobject<NSString> merge_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(CoreWlanApi);
+};
+
+bool CoreWlanApi::Init() {
+ // As the WLAN api binding runs on its own thread, we need to provide our own
+ // auto release pool. It's simplest to do this as an automatic variable in
+ // each method that needs it, to ensure the scoping is correct and does not
+ // interfere with any other code using autorelease pools on the thread.
+ base::mac::ScopedNSAutoreleasePool auto_pool;
+ bundle_.reset([[NSBundle alloc]
+ initWithPath:@"/System/Library/Frameworks/CoreWLAN.framework"]);
+ if (!bundle_) {
+ DVLOG(1) << "Failed to load the CoreWLAN framework bundle";
+ return false;
+ }
+
+ // Dynamically look up the value of the kCWScanKeyMerge (i.e. without build
+ // time dependency on the 10.6 specific library).
+ void* dl_handle = dlopen([[bundle_ executablePath] fileSystemRepresentation],
+ RTLD_LAZY | RTLD_LOCAL);
+ if (dl_handle) {
+ NSString* key = *reinterpret_cast<NSString**>(dlsym(dl_handle,
+ "kCWScanKeyMerge"));
+ if (key)
+ merge_key_.reset([key copy]);
+ }
+ // "Leak" dl_handle rather than dlclose it, to ensure |merge_key_|
+ // remains valid.
+ if (!merge_key_) {
+ // Fall back to a known-working value should the lookup fail (if
+ // this value is itself wrong it's not the end of the world, we might just
+ // get very slightly lower quality location fixes due to SSID merges).
+ DLOG(WARNING) << "Could not dynamically load the CoreWLAN merge key";
+ merge_key_.reset([@"SCAN_MERGE" retain]);
+ }
+
+ return true;
+}
+
+bool CoreWlanApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
+ base::mac::ScopedNSAutoreleasePool auto_pool;
+ // Initialize the scan parameters with scan key merging disabled, so we get
+ // every AP listed in the scan without any SSID de-duping logic.
+ NSDictionary* params =
+ [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
+ forKey:merge_key_.get()];
+
+ Class cw_interface_class = [bundle_ classNamed:@"CWInterface"];
+ NSArray* supported_interfaces = [cw_interface_class supportedInterfaces];
+ uint interface_error_count = 0;
+ for (NSString* interface_name in supported_interfaces) {
+ CWInterface* corewlan_interface =
+ [cw_interface_class interfaceWithName:interface_name];
+ if (!corewlan_interface) {
+ DLOG(WARNING) << interface_name << ": initWithName failed";
+ ++interface_error_count;
+ continue;
+ }
+
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ NSError* err = nil;
+ NSArray* scan = [corewlan_interface scanForNetworksWithParameters:params
+ error:&err];
+ const int error_code = [err code];
+ const int count = [scan count];
+ // We could get an error code but count != 0 if the scan was interrupted,
+ // for example. For our purposes this is not fatal, so process as normal.
+ if (error_code && count == 0) {
+ DLOG(WARNING) << interface_name << ": CoreWLAN scan failed with error "
+ << error_code;
+ ++interface_error_count;
+ continue;
+ }
+
+ const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Wifi.ScanLatency",
+ duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+
+ DVLOG(1) << interface_name << ": found " << count << " wifi APs";
+
+ for (CWNetwork* network in scan) {
+ DCHECK(network);
+ AccessPointData access_point_data;
+ NSData* mac = [network bssidData];
+ DCHECK([mac length] == 6);
+ access_point_data.mac_address = MacAddressAsString16(
+ static_cast<const uint8*>([mac bytes]));
+ access_point_data.radio_signal_strength = [[network rssi] intValue];
+ access_point_data.channel = [[network channel] intValue];
+ access_point_data.signal_to_noise =
+ access_point_data.radio_signal_strength - [[network noise] intValue];
+ access_point_data.ssid = base::SysNSStringToUTF16([network ssid]);
+ data->insert(access_point_data);
+ }
+ }
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.Wifi.InterfaceCount",
+ [supported_interfaces count] - interface_error_count,
+ 1,
+ 5,
+ 5);
+
+ // Return true even if some interfaces failed to scan, so long as at least
+ // one interface did not fail.
+ return interface_error_count == 0 ||
+ [supported_interfaces count] > interface_error_count;
+}
+
+WifiDataProviderCommon::WlanApiInterface* NewCoreWlanApi() {
+ scoped_ptr<CoreWlanApi> self(new CoreWlanApi);
+ if (self->Init())
+ return self.release();
+
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_linux.cc b/chromium/content/browser/geolocation/wifi_data_provider_linux.cc
new file mode 100644
index 00000000000..edece1d0211
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_linux.cc
@@ -0,0 +1,381 @@
+// 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.
+
+// Provides wifi scan API binding for suitable for typical linux distributions.
+// Currently, only the NetworkManager API is used, accessed via D-Bus (in turn
+// accessed via the GLib wrapper).
+
+#include "content/browser/geolocation/wifi_data_provider_linux.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+
+namespace content {
+namespace {
+// The time periods between successive polls of the wifi data.
+const int kDefaultPollingIntervalMilliseconds = 10 * 1000; // 10s
+const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000; // 2 mins
+const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000; // 10 mins
+const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
+
+const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
+const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
+const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
+
+// From http://projects.gnome.org/NetworkManager/developers/spec.html
+enum { NM_DEVICE_TYPE_WIFI = 2 };
+
+// Wifi API binding to NetworkManager, to allow reuse of the polling behavior
+// defined in WifiDataProviderCommon.
+// TODO(joth): NetworkManager also allows for notification based handling,
+// however this will require reworking of the threading code to run a GLib
+// event loop (GMainLoop).
+class NetworkManagerWlanApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ NetworkManagerWlanApi();
+ virtual ~NetworkManagerWlanApi();
+
+ // Must be called before any other interface method. Will return false if the
+ // NetworkManager session cannot be created (e.g. not present on this distro),
+ // in which case no other method may be called.
+ bool Init();
+
+ // Similar to Init() but can inject the bus object. Used for testing.
+ bool InitWithBus(dbus::Bus* bus);
+
+ // WifiDataProviderCommon::WlanApiInterface
+ //
+ // This function makes blocking D-Bus calls, but it's totally fine as
+ // the code runs in "Geolocation" thread, not the browser's UI thread.
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE;
+
+ private:
+ // Enumerates the list of available network adapter devices known to
+ // NetworkManager. Return true on success.
+ bool GetAdapterDeviceList(std::vector<dbus::ObjectPath>* device_paths);
+
+ // Given the NetworkManager path to a wireless adapater, dumps the wifi scan
+ // results and appends them to |data|. Returns false if a fatal error is
+ // encountered such that the data set could not be populated.
+ bool GetAccessPointsForAdapter(const dbus::ObjectPath& adapter_path,
+ WifiData::AccessPointDataSet* data);
+
+ // Internal method used by |GetAccessPointsForAdapter|, given a wifi access
+ // point proxy retrieves the named property and returns it. Returns NULL in
+ // a scoped_ptr if the property could not be read.
+ scoped_ptr<dbus::Response> GetAccessPointProperty(
+ dbus::ObjectProxy* proxy,
+ const std::string& property_name);
+
+ scoped_refptr<dbus::Bus> system_bus_;
+ dbus::ObjectProxy* network_manager_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi);
+};
+
+// Convert a wifi frequency to the corresponding channel. Adapted from
+// geolocaiton/wifilib.cc in googleclient (internal to google).
+int frquency_in_khz_to_channel(int frequency_khz) {
+ if (frequency_khz >= 2412000 && frequency_khz <= 2472000) // Channels 1-13.
+ return (frequency_khz - 2407000) / 5000;
+ if (frequency_khz == 2484000)
+ return 14;
+ if (frequency_khz > 5000000 && frequency_khz < 6000000) // .11a bands.
+ return (frequency_khz - 5000000) / 5000;
+ // Ignore everything else.
+ return AccessPointData().channel; // invalid channel
+}
+
+NetworkManagerWlanApi::NetworkManagerWlanApi()
+ : network_manager_proxy_(NULL) {
+}
+
+NetworkManagerWlanApi::~NetworkManagerWlanApi() {
+ // Close the connection.
+ system_bus_->ShutdownAndBlock();
+}
+
+bool NetworkManagerWlanApi::Init() {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ options.connection_type = dbus::Bus::PRIVATE;
+ return InitWithBus(new dbus::Bus(options));
+}
+
+bool NetworkManagerWlanApi::InitWithBus(dbus::Bus* bus) {
+ system_bus_ = bus;
+ // system_bus_ will own all object proxies created from the bus.
+ network_manager_proxy_ =
+ system_bus_->GetObjectProxy(kNetworkManagerServiceName,
+ dbus::ObjectPath(kNetworkManagerPath));
+ // Validate the proxy object by checking we can enumerate devices.
+ std::vector<dbus::ObjectPath> adapter_paths;
+ const bool success = GetAdapterDeviceList(&adapter_paths);
+ VLOG(1) << "Init() result: " << success;
+ return success;
+}
+
+bool NetworkManagerWlanApi::GetAccessPointData(
+ WifiData::AccessPointDataSet* data) {
+ std::vector<dbus::ObjectPath> device_paths;
+ if (!GetAdapterDeviceList(&device_paths)) {
+ LOG(WARNING) << "Could not enumerate access points";
+ return false;
+ }
+ int success_count = 0;
+ int fail_count = 0;
+
+ // Iterate the devices, getting APs for each wireless adapter found
+ for (size_t i = 0; i < device_paths.size(); ++i) {
+ const dbus::ObjectPath& device_path = device_paths[i];
+ VLOG(1) << "Checking device: " << device_path.value();
+
+ dbus::ObjectProxy* device_proxy =
+ system_bus_->GetObjectProxy(kNetworkManagerServiceName,
+ device_path);
+
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get");
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString("org.freedesktop.NetworkManager.Device");
+ builder.AppendString("DeviceType");
+ scoped_ptr<dbus::Response> response(
+ device_proxy->CallMethodAndBlock(
+ &method_call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (!response) {
+ LOG(WARNING) << "Failed to get the device type for "
+ << device_path.value();
+ continue; // Check the next device.
+ }
+ dbus::MessageReader reader(response.get());
+ uint32 device_type = 0;
+ if (!reader.PopVariantOfUint32(&device_type)) {
+ LOG(WARNING) << "Unexpected response for " << device_type << ": "
+ << response->ToString();
+ continue; // Check the next device.
+ }
+ VLOG(1) << "Device type: " << device_type;
+
+ if (device_type == NM_DEVICE_TYPE_WIFI) { // Found a wlan adapter
+ if (GetAccessPointsForAdapter(device_path, data))
+ ++success_count;
+ else
+ ++fail_count;
+ }
+ }
+ // At least one successfull scan overrides any other adapter reporting error.
+ return success_count || fail_count == 0;
+}
+
+bool NetworkManagerWlanApi::GetAdapterDeviceList(
+ std::vector<dbus::ObjectPath>* device_paths) {
+ dbus::MethodCall method_call(kNetworkManagerInterface, "GetDevices");
+ scoped_ptr<dbus::Response> response(
+ network_manager_proxy_->CallMethodAndBlock(
+ &method_call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (!response) {
+ LOG(WARNING) << "Failed to get the device list";
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ if (!reader.PopArrayOfObjectPaths(device_paths)) {
+ LOG(WARNING) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+ return true;
+}
+
+
+bool NetworkManagerWlanApi::GetAccessPointsForAdapter(
+ const dbus::ObjectPath& adapter_path, WifiData::AccessPointDataSet* data) {
+ // Create a proxy object for this wifi adapter, and ask it to do a scan
+ // (or at least, dump its scan results).
+ dbus::ObjectProxy* device_proxy =
+ system_bus_->GetObjectProxy(kNetworkManagerServiceName,
+ adapter_path);
+ dbus::MethodCall method_call(
+ "org.freedesktop.NetworkManager.Device.Wireless",
+ "GetAccessPoints");
+ scoped_ptr<dbus::Response> response(
+ device_proxy->CallMethodAndBlock(
+ &method_call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (!response) {
+ LOG(WARNING) << "Failed to get access points data for "
+ << adapter_path.value();
+ return false;
+ }
+ dbus::MessageReader reader(response.get());
+ std::vector<dbus::ObjectPath> access_point_paths;
+ if (!reader.PopArrayOfObjectPaths(&access_point_paths)) {
+ LOG(WARNING) << "Unexpected response for " << adapter_path.value() << ": "
+ << response->ToString();
+ return false;
+ }
+
+ VLOG(1) << "Wireless adapter " << adapter_path.value() << " found "
+ << access_point_paths.size() << " access points.";
+
+ for (size_t i = 0; i < access_point_paths.size(); ++i) {
+ const dbus::ObjectPath& access_point_path = access_point_paths[i];
+ VLOG(1) << "Checking access point: " << access_point_path.value();
+
+ dbus::ObjectProxy* access_point_proxy =
+ system_bus_->GetObjectProxy(kNetworkManagerServiceName,
+ access_point_path);
+
+ AccessPointData access_point_data;
+ {
+ scoped_ptr<dbus::Response> response(
+ GetAccessPointProperty(access_point_proxy, "Ssid"));
+ if (!response)
+ continue;
+ // The response should contain a variant that contains an array of bytes.
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(response.get());
+ if (!reader.PopVariant(&variant_reader)) {
+ LOG(WARNING) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ continue;
+ }
+ uint8* ssid_bytes = NULL;
+ size_t ssid_length = 0;
+ if (!variant_reader.PopArrayOfBytes(&ssid_bytes, &ssid_length)) {
+ LOG(WARNING) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ continue;
+ }
+ std::string ssid(ssid_bytes, ssid_bytes + ssid_length);
+ access_point_data.ssid = UTF8ToUTF16(ssid);
+ }
+
+ { // Read the mac address
+ scoped_ptr<dbus::Response> response(
+ GetAccessPointProperty(access_point_proxy, "HwAddress"));
+ if (!response)
+ continue;
+ dbus::MessageReader reader(response.get());
+ std::string mac;
+ if (!reader.PopVariantOfString(&mac)) {
+ LOG(WARNING) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ continue;
+ }
+
+ ReplaceSubstringsAfterOffset(&mac, 0U, ":", std::string());
+ std::vector<uint8> mac_bytes;
+ if (!base::HexStringToBytes(mac, &mac_bytes) || mac_bytes.size() != 6) {
+ LOG(WARNING) << "Can't parse mac address (found " << mac_bytes.size()
+ << " bytes) so using raw string: " << mac;
+ access_point_data.mac_address = UTF8ToUTF16(mac);
+ } else {
+ access_point_data.mac_address = MacAddressAsString16(&mac_bytes[0]);
+ }
+ }
+
+ { // Read signal strength.
+ scoped_ptr<dbus::Response> response(
+ GetAccessPointProperty(access_point_proxy, "Strength"));
+ if (!response)
+ continue;
+ dbus::MessageReader reader(response.get());
+ uint8 strength = 0;
+ if (!reader.PopVariantOfByte(&strength)) {
+ LOG(WARNING) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ continue;
+ }
+ // Convert strength as a percentage into dBs.
+ access_point_data.radio_signal_strength = -100 + strength / 2;
+ }
+
+ { // Read the channel
+ scoped_ptr<dbus::Response> response(
+ GetAccessPointProperty(access_point_proxy, "Frequency"));
+ if (!response)
+ continue;
+ dbus::MessageReader reader(response.get());
+ uint32 frequency = 0;
+ if (!reader.PopVariantOfUint32(&frequency)) {
+ LOG(WARNING) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ continue;
+ }
+
+ // NetworkManager returns frequency in MHz.
+ access_point_data.channel =
+ frquency_in_khz_to_channel(frequency * 1000);
+ }
+ VLOG(1) << "Access point data of " << access_point_path.value() << ": "
+ << "SSID: " << access_point_data.ssid << ", "
+ << "MAC: " << access_point_data.mac_address << ", "
+ << "Strength: " << access_point_data.radio_signal_strength << ", "
+ << "Channel: " << access_point_data.channel;
+
+ data->insert(access_point_data);
+ }
+ return true;
+}
+
+scoped_ptr<dbus::Response> NetworkManagerWlanApi::GetAccessPointProperty(
+ dbus::ObjectProxy* access_point_proxy,
+ const std::string& property_name) {
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get");
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString("org.freedesktop.NetworkManager.AccessPoint");
+ builder.AppendString(property_name);
+ scoped_ptr<dbus::Response> response = access_point_proxy->CallMethodAndBlock(
+ &method_call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+ if (!response) {
+ LOG(WARNING) << "Failed to get property for " << property_name;
+ }
+ return response.Pass();
+}
+
+} // namespace
+
+// static
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new WifiDataProviderLinux();
+}
+
+WifiDataProviderLinux::WifiDataProviderLinux() {
+}
+
+WifiDataProviderLinux::~WifiDataProviderLinux() {
+}
+
+WifiDataProviderCommon::WlanApiInterface*
+WifiDataProviderLinux::NewWlanApi() {
+ scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
+ if (wlan_api->Init())
+ return wlan_api.release();
+ return NULL;
+}
+
+PollingPolicyInterface* WifiDataProviderLinux::NewPollingPolicy() {
+ return new GenericPollingPolicy<kDefaultPollingIntervalMilliseconds,
+ kNoChangePollingIntervalMilliseconds,
+ kTwoNoChangePollingIntervalMilliseconds,
+ kNoWifiPollingIntervalMilliseconds>;
+}
+
+WifiDataProviderCommon::WlanApiInterface*
+WifiDataProviderLinux::NewWlanApiForTesting(dbus::Bus* bus) {
+ scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
+ if (wlan_api->InitWithBus(bus))
+ return wlan_api.release();
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_linux.h b/chromium/content/browser/geolocation/wifi_data_provider_linux.h
new file mode 100644
index 00000000000..526f61074af
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_linux.h
@@ -0,0 +1,39 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_LINUX_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_LINUX_H_
+
+#include "base/compiler_specific.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+#include "content/common/content_export.h"
+
+namespace dbus {
+class Bus;
+};
+
+namespace content {
+
+class CONTENT_EXPORT WifiDataProviderLinux : public WifiDataProviderCommon {
+ public:
+ WifiDataProviderLinux();
+
+ private:
+ friend class GeolocationWifiDataProviderLinuxTest;
+
+ virtual ~WifiDataProviderLinux();
+
+ // WifiDataProviderCommon
+ virtual WlanApiInterface* NewWlanApi() OVERRIDE;
+ virtual PollingPolicyInterface* NewPollingPolicy() OVERRIDE;
+
+ // For testing.
+ WlanApiInterface* NewWlanApiForTesting(dbus::Bus* bus);
+
+ DISALLOW_COPY_AND_ASSIGN(WifiDataProviderLinux);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_LINUX_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_linux_unittest.cc b/chromium/content/browser/geolocation/wifi_data_provider_linux_unittest.cc
new file mode 100644
index 00000000000..2e724706078
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_linux_unittest.cc
@@ -0,0 +1,231 @@
+// 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 "content/browser/geolocation/wifi_data_provider_linux.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "dbus/message.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::Unused;
+
+namespace content {
+
+class GeolocationWifiDataProviderLinuxTest : public testing::Test {
+ virtual void SetUp() {
+ // Create a mock bus.
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ mock_bus_ = new dbus::MockBus(options);
+
+ // Create a mock proxy that behaves as NetworkManager.
+ mock_network_manager_proxy_ =
+ new dbus::MockObjectProxy(
+ mock_bus_.get(),
+ "org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager"));
+ // Set an expectation so mock_network_manager_proxy_'s
+ // CallMethodAndBlock() will use CreateNetworkManagerProxyResponse()
+ // to return responses.
+ EXPECT_CALL(*mock_network_manager_proxy_.get(),
+ MockCallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(this,
+ &GeolocationWifiDataProviderLinuxTest::
+ CreateNetworkManagerProxyResponse));
+
+ // Create a mock proxy that behaves as NetworkManager/Devices/0.
+ mock_device_proxy_ =
+ new dbus::MockObjectProxy(
+ mock_bus_.get(),
+ "org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/Devices/0"));
+ EXPECT_CALL(*mock_device_proxy_.get(), MockCallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(
+ this,
+ &GeolocationWifiDataProviderLinuxTest::CreateDeviceProxyResponse));
+
+ // Create a mock proxy that behaves as NetworkManager/AccessPoint/0.
+ mock_access_point_proxy_ =
+ new dbus::MockObjectProxy(
+ mock_bus_.get(),
+ "org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/AccessPoint/0"));
+ EXPECT_CALL(*mock_access_point_proxy_.get(), MockCallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(this,
+ &GeolocationWifiDataProviderLinuxTest::
+ CreateAccessPointProxyResponse));
+
+ // Set an expectation so mock_bus_'s GetObjectProxy() for the given
+ // service name and the object path will return
+ // mock_network_manager_proxy_.
+ EXPECT_CALL(
+ *mock_bus_.get(),
+ GetObjectProxy("org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager")))
+ .WillOnce(Return(mock_network_manager_proxy_.get()));
+ // Likewise, set an expectation for mock_device_proxy_.
+ EXPECT_CALL(
+ *mock_bus_.get(),
+ GetObjectProxy(
+ "org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/Devices/0")))
+ .WillOnce(Return(mock_device_proxy_.get()))
+ .WillOnce(Return(mock_device_proxy_.get()));
+ // Likewise, set an expectation for mock_access_point_proxy_.
+ EXPECT_CALL(
+ *mock_bus_.get(),
+ GetObjectProxy(
+ "org.freedesktop.NetworkManager",
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/AccessPoint/0")))
+ .WillOnce(Return(mock_access_point_proxy_.get()));
+
+ // ShutdownAndBlock() should be called.
+ EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());
+
+ // Create the wlan API with the mock bus object injected.
+ wifi_provider_linux_ = new WifiDataProviderLinux;
+ wlan_api_.reset(
+ wifi_provider_linux_->NewWlanApiForTesting(mock_bus_.get()));
+ ASSERT_TRUE(wlan_api_.get());
+ }
+
+ protected:
+ // DeviceDataProviderImplBase, a super class of WifiDataProviderLinux,
+ // requires a message loop to be present. message_loop_ is defined here,
+ // as it should outlive wifi_provider_linux_.
+ base::MessageLoop message_loop_;
+ scoped_refptr<dbus::MockBus> mock_bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_network_manager_proxy_;
+ scoped_refptr<dbus::MockObjectProxy> mock_access_point_proxy_;
+ scoped_refptr<dbus::MockObjectProxy> mock_device_proxy_;
+ scoped_refptr<WifiDataProviderLinux> wifi_provider_linux_;
+ scoped_ptr<WifiDataProviderCommon::WlanApiInterface> wlan_api_;
+
+ private:
+ // Creates a response for |mock_network_manager_proxy_|.
+ dbus::Response* CreateNetworkManagerProxyResponse(
+ dbus::MethodCall* method_call,
+ Unused) {
+ if (method_call->GetInterface() == "org.freedesktop.NetworkManager" &&
+ method_call->GetMember() == "GetDevices") {
+ // The list of devices is asked. Return the object path.
+ std::vector<dbus::ObjectPath> object_paths;
+ object_paths.push_back(
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/Devices/0"));
+
+ scoped_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response.get());
+ writer.AppendArrayOfObjectPaths(object_paths);
+ return response.release();
+ }
+
+ LOG(ERROR) << "Unexpected method call: " << method_call->ToString();
+ return NULL;
+ }
+
+ // Creates a response for |mock_device_proxy_|.
+ dbus::Response* CreateDeviceProxyResponse(dbus::MethodCall* method_call,
+ Unused) {
+ if (method_call->GetInterface() == DBUS_INTERFACE_PROPERTIES &&
+ method_call->GetMember() == "Get") {
+ dbus::MessageReader reader(method_call);
+ std::string interface_name;
+ std::string property_name;
+ if (reader.PopString(&interface_name) &&
+ reader.PopString(&property_name)) {
+ // The device type is asked. Respond that the device type is wifi.
+ scoped_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response.get());
+ // This matches NM_DEVICE_TYPE_WIFI in wifi_data_provider_linux.cc.
+ const int kDeviceTypeWifi = 2;
+ writer.AppendVariantOfUint32(kDeviceTypeWifi);
+ return response.release();
+ }
+ } else if (method_call->GetInterface() ==
+ "org.freedesktop.NetworkManager.Device.Wireless" &&
+ method_call->GetMember() == "GetAccessPoints") {
+ // The list of access points is asked. Return the object path.
+ scoped_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response.get());
+ std::vector<dbus::ObjectPath> object_paths;
+ object_paths.push_back(
+ dbus::ObjectPath("/org/freedesktop/NetworkManager/AccessPoint/0"));
+ writer.AppendArrayOfObjectPaths(object_paths);
+ return response.release();
+ }
+
+ LOG(ERROR) << "Unexpected method call: " << method_call->ToString();
+ return NULL;
+ }
+
+
+ // Creates a response for |mock_access_point_proxy_|.
+ dbus::Response* CreateAccessPointProxyResponse(dbus::MethodCall* method_call,
+ Unused) {
+ if (method_call->GetInterface() == DBUS_INTERFACE_PROPERTIES &&
+ method_call->GetMember() == "Get") {
+ dbus::MessageReader reader(method_call);
+
+ std::string interface_name;
+ std::string property_name;
+ if (reader.PopString(&interface_name) &&
+ reader.PopString(&property_name)) {
+ scoped_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response.get());
+
+ if (property_name == "Ssid") {
+ const uint8 kSsid[] = {0x74, 0x65, 0x73, 0x74}; // "test"
+ dbus::MessageWriter variant_writer(response.get());
+ writer.OpenVariant("ay", &variant_writer);
+ variant_writer.AppendArrayOfBytes(kSsid, arraysize(kSsid));
+ writer.CloseContainer(&variant_writer);
+ } else if (property_name == "HwAddress") {
+ // This will be converted to "00-11-22-33-44-55".
+ const std::string kMacAddress = "00:11:22:33:44:55";
+ writer.AppendVariantOfString(kMacAddress);
+ } else if (property_name == "Strength") {
+ // This will be converted to -50.
+ const uint8 kStrength = 100;
+ writer.AppendVariantOfByte(kStrength);
+ } else if (property_name == "Frequency") {
+ // This will be converted to channel 4.
+ const uint32 kFrequency = 2427;
+ writer.AppendVariantOfUint32(kFrequency);
+ }
+ return response.release();
+ }
+ }
+
+ LOG(ERROR) << "Unexpected method call: " << method_call->ToString();
+ return NULL;
+ }
+};
+
+TEST_F(GeolocationWifiDataProviderLinuxTest, GetAccessPointData) {
+ WifiData::AccessPointDataSet access_point_data_set;
+ ASSERT_TRUE(wlan_api_->GetAccessPointData(&access_point_data_set));
+
+ ASSERT_EQ(1U, access_point_data_set.size());
+ AccessPointData access_point_data = *access_point_data_set.begin();
+
+ // Check the contents of the access point data.
+ // The expected values come from CreateAccessPointProxyResponse() above.
+ EXPECT_EQ("test", UTF16ToUTF8(access_point_data.ssid));
+ EXPECT_EQ("00-11-22-33-44-55", UTF16ToUTF8(access_point_data.mac_address));
+ EXPECT_EQ(-50, access_point_data.radio_signal_strength);
+ EXPECT_EQ(4, access_point_data.channel);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_mac.cc b/chromium/content/browser/geolocation/wifi_data_provider_mac.cc
new file mode 100644
index 00000000000..8f81305ac3a
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_mac.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// For OSX 10.5 we use the system API function WirelessScanSplit. This function
+// is not documented or included in the SDK, so we use a reverse-engineered
+// header, osx_wifi_.h. This file is taken from the iStumbler project
+// (http://www.istumbler.net).
+
+#include "content/browser/geolocation/wifi_data_provider_mac.h"
+
+#include <dlfcn.h>
+#include <stdio.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/geolocation/osx_wifi.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+
+namespace content {
+namespace {
+// The time periods, in milliseconds, between successive polls of the wifi data.
+const int kDefaultPollingInterval = 120000; // 2 mins
+const int kNoChangePollingInterval = 300000; // 5 mins
+const int kTwoNoChangePollingInterval = 600000; // 10 mins
+const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
+
+// Provides the wifi API binding for use when running on OSX 10.5 machines using
+// the Apple80211 framework.
+class Apple80211Api : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ Apple80211Api();
+ virtual ~Apple80211Api();
+
+ // Must be called before any other interface method. Will return false if the
+ // Apple80211 framework cannot be initialized (e.g. running on post-10.5 OSX),
+ // in which case no other method may be called.
+ bool Init();
+
+ // WlanApiInterface
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE;
+
+ private:
+ // Handle, context and function pointers for Apple80211 library.
+ void* apple_80211_library_;
+ WirelessContext* wifi_context_;
+ WirelessAttachFunction WirelessAttach_function_;
+ WirelessScanSplitFunction WirelessScanSplit_function_;
+ WirelessDetachFunction WirelessDetach_function_;
+
+ WifiData wifi_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(Apple80211Api);
+};
+
+Apple80211Api::Apple80211Api()
+ : apple_80211_library_(NULL), wifi_context_(NULL),
+ WirelessAttach_function_(NULL), WirelessScanSplit_function_(NULL),
+ WirelessDetach_function_(NULL) {
+}
+
+Apple80211Api::~Apple80211Api() {
+ if (WirelessDetach_function_)
+ (*WirelessDetach_function_)(wifi_context_);
+ dlclose(apple_80211_library_);
+}
+
+bool Apple80211Api::Init() {
+ DVLOG(1) << "Apple80211Api::Init";
+ apple_80211_library_ = dlopen(
+ "/System/Library/PrivateFrameworks/Apple80211.framework/Apple80211",
+ RTLD_LAZY);
+ if (!apple_80211_library_) {
+ DLOG(WARNING) << "Could not open Apple80211 library";
+ return false;
+ }
+ WirelessAttach_function_ = reinterpret_cast<WirelessAttachFunction>(
+ dlsym(apple_80211_library_, "WirelessAttach"));
+ WirelessScanSplit_function_ = reinterpret_cast<WirelessScanSplitFunction>(
+ dlsym(apple_80211_library_, "WirelessScanSplit"));
+ WirelessDetach_function_ = reinterpret_cast<WirelessDetachFunction>(
+ dlsym(apple_80211_library_, "WirelessDetach"));
+ DCHECK(WirelessAttach_function_);
+ DCHECK(WirelessScanSplit_function_);
+ DCHECK(WirelessDetach_function_);
+
+ if (!WirelessAttach_function_ || !WirelessScanSplit_function_ ||
+ !WirelessDetach_function_) {
+ DLOG(WARNING) << "Symbol error. Attach: " << !!WirelessAttach_function_
+ << " Split: " << !!WirelessScanSplit_function_
+ << " Detach: " << !!WirelessDetach_function_;
+ return false;
+ }
+
+ WIErr err = (*WirelessAttach_function_)(&wifi_context_, 0);
+ if (err != noErr) {
+ DLOG(WARNING) << "Error attaching: " << err;
+ return false;
+ }
+ return true;
+}
+
+bool Apple80211Api::GetAccessPointData(WifiData::AccessPointDataSet* data) {
+ DVLOG(1) << "Apple80211Api::GetAccessPointData";
+ DCHECK(data);
+ DCHECK(WirelessScanSplit_function_);
+ CFArrayRef managed_access_points = NULL;
+ CFArrayRef adhoc_access_points = NULL;
+ // Arrays returned here are owned by the caller.
+ WIErr err = (*WirelessScanSplit_function_)(wifi_context_,
+ &managed_access_points,
+ &adhoc_access_points,
+ 0);
+ if (err != noErr) {
+ DLOG(WARNING) << "Error spliting scan: " << err;
+ return false;
+ }
+
+ if (managed_access_points == NULL) {
+ DLOG(WARNING) << "managed_access_points == NULL";
+ return false;
+ }
+
+ int num_access_points = CFArrayGetCount(managed_access_points);
+ DVLOG(1) << "Found " << num_access_points << " managed access points";
+ for (int i = 0; i < num_access_points; ++i) {
+ const WirelessNetworkInfo* access_point_info =
+ reinterpret_cast<const WirelessNetworkInfo*>(
+ CFDataGetBytePtr(
+ reinterpret_cast<const CFDataRef>(
+ CFArrayGetValueAtIndex(managed_access_points, i))));
+
+ // Currently we get only MAC address, signal strength, channel
+ // signal-to-noise and SSID
+ AccessPointData access_point_data;
+ access_point_data.mac_address =
+ MacAddressAsString16(access_point_info->macAddress);
+ // WirelessNetworkInfo::signal appears to be signal strength in dBm.
+ access_point_data.radio_signal_strength = access_point_info->signal;
+ access_point_data.channel = access_point_info->channel;
+ // WirelessNetworkInfo::noise appears to be noise floor in dBm.
+ access_point_data.signal_to_noise = access_point_info->signal -
+ access_point_info->noise;
+ if (!UTF8ToUTF16(reinterpret_cast<const char*>(access_point_info->name),
+ access_point_info->nameLen,
+ &access_point_data.ssid)) {
+ access_point_data.ssid.clear();
+ }
+ data->insert(access_point_data);
+ }
+
+ if (managed_access_points)
+ CFRelease(managed_access_points);
+ if (adhoc_access_points)
+ CFRelease(adhoc_access_points);
+
+ return true;
+}
+} // namespace
+
+// static
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new MacWifiDataProvider();
+}
+
+MacWifiDataProvider::MacWifiDataProvider() {
+}
+
+MacWifiDataProvider::~MacWifiDataProvider() {
+}
+
+MacWifiDataProvider::WlanApiInterface* MacWifiDataProvider::NewWlanApi() {
+ // Try and find a API binding that works: first try the officially supported
+ // CoreWLAN API, and if this fails (e.g. on OSX 10.5) fall back to the reverse
+ // engineered Apple80211 API.
+ MacWifiDataProvider::WlanApiInterface* core_wlan_api = NewCoreWlanApi();
+ if (core_wlan_api)
+ return core_wlan_api;
+
+ scoped_ptr<Apple80211Api> wlan_api(new Apple80211Api);
+ if (wlan_api->Init())
+ return wlan_api.release();
+
+ DVLOG(1) << "MacWifiDataProvider : failed to initialize any wlan api";
+ return NULL;
+}
+
+PollingPolicyInterface* MacWifiDataProvider::NewPollingPolicy() {
+ return new GenericPollingPolicy<kDefaultPollingInterval,
+ kNoChangePollingInterval,
+ kTwoNoChangePollingInterval,
+ kNoWifiPollingIntervalMilliseconds>;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_mac.h b/chromium/content/browser/geolocation/wifi_data_provider_mac.h
new file mode 100644
index 00000000000..e36bd8ca584
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_mac.h
@@ -0,0 +1,35 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_MAC_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_MAC_H_
+
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+
+namespace content {
+
+// Implementation of the wifi data provider for Mac OSX. Uses different API
+// bindings depending on APIs detected available at runtime in order to access
+// wifi scan data: Apple80211.h on OSX 10.5, CoreWLAN framework on OSX 10.6.
+class MacWifiDataProvider : public WifiDataProviderCommon {
+ public:
+ MacWifiDataProvider();
+
+ private:
+ virtual ~MacWifiDataProvider();
+
+ // WifiDataProviderCommon
+ virtual WlanApiInterface* NewWlanApi() OVERRIDE;
+ virtual PollingPolicyInterface* NewPollingPolicy() OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(MacWifiDataProvider);
+};
+
+// Creates and returns a new API binding for the CoreWLAN API, or NULL if the
+// API can not be initialized.
+WifiDataProviderCommon::WlanApiInterface* NewCoreWlanApi();
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_MAC_H_
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_unittest_win.cc b/chromium/content/browser/geolocation/wifi_data_provider_unittest_win.cc
new file mode 100644
index 00000000000..c74f42c5739
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_unittest_win.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Most logic for the platform wifi provider is now factored into
+// WifiDataProviderCommon and covered by it's unit tests.
+
+#include "content/browser/geolocation/wifi_data_provider_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(GeolocationWin32WifiDataProviderTest, CreateDestroy) {
+ // WifiDataProviderCommon requires the client to have a message loop.
+ base::MessageLoop dummy_loop;
+ scoped_refptr<Win32WifiDataProvider> instance(new Win32WifiDataProvider);
+ instance = NULL;
+ SUCCEED();
+ // Can't actually call start provider on the Win32WifiDataProvider without
+ // it accessing hardware and so risking making the test flaky.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_win.cc b/chromium/content/browser/geolocation/wifi_data_provider_win.cc
new file mode 100644
index 00000000000..a35e6e948f6
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_win.cc
@@ -0,0 +1,640 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See
+// http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP
+// Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix)
+// also support a limited version of the WLAN API. See
+// http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses
+// wlanapi.h, which is not part of the SDK used by Gears, so is replicated
+// locally using data from the MSDN.
+//
+// Windows XP from Service Pack 2 onwards supports the Wireless Zero
+// Configuration (WZC) programming interface. See
+// http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx.
+//
+// The MSDN recommends that one use the WLAN API where available, and WZC
+// otherwise.
+//
+// However, it seems that WZC fails for some wireless cards. Also, WLAN seems
+// not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly
+// otherwise.
+
+#include "content/browser/geolocation/wifi_data_provider_win.h"
+
+#include <windows.h>
+#include <winioctl.h>
+#include <wlanapi.h>
+
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+#include "content/browser/geolocation/wifi_data_provider_common_win.h"
+
+// Taken from ndis.h for WinCE.
+#define NDIS_STATUS_INVALID_LENGTH ((NDIS_STATUS)0xC0010014L)
+#define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L)
+
+namespace content {
+namespace {
+// The limits on the size of the buffer used for the OID query.
+const int kInitialBufferSize = 2 << 12; // Good for about 50 APs.
+const int kMaximumBufferSize = 2 << 20; // 2MB
+
+// Length for generic string buffers passed to Win32 APIs.
+const int kStringLength = 512;
+
+// The time periods, in milliseconds, between successive polls of the wifi data.
+const int kDefaultPollingInterval = 10000; // 10s
+const int kNoChangePollingInterval = 120000; // 2 mins
+const int kTwoNoChangePollingInterval = 600000; // 10 mins
+const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
+
+// WlanOpenHandle
+typedef DWORD (WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
+ PVOID pReserved,
+ PDWORD pdwNegotiatedVersion,
+ PHANDLE phClientHandle);
+
+// WlanEnumInterfaces
+typedef DWORD (WINAPI* WlanEnumInterfacesFunction)(
+ HANDLE hClientHandle,
+ PVOID pReserved,
+ PWLAN_INTERFACE_INFO_LIST* ppInterfaceList);
+
+// WlanGetNetworkBssList
+typedef DWORD (WINAPI* WlanGetNetworkBssListFunction)(
+ HANDLE hClientHandle,
+ const GUID* pInterfaceGuid,
+ const PDOT11_SSID pDot11Ssid,
+ DOT11_BSS_TYPE dot11BssType,
+ BOOL bSecurityEnabled,
+ PVOID pReserved,
+ PWLAN_BSS_LIST* ppWlanBssList
+);
+
+// WlanFreeMemory
+typedef VOID (WINAPI* WlanFreeMemoryFunction)(PVOID pMemory);
+
+// WlanCloseHandle
+typedef DWORD (WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle,
+ PVOID pReserved);
+
+
+// Local classes and functions
+class WindowsWlanApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ virtual ~WindowsWlanApi();
+ // Factory function. Will return NULL if this API is unavailable.
+ static WindowsWlanApi* Create();
+
+ // WlanApiInterface
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
+
+ private:
+ // Takes ownership of the library handle.
+ explicit WindowsWlanApi(HINSTANCE library);
+
+ // Loads the required functions from the DLL.
+ void GetWLANFunctions(HINSTANCE wlan_library);
+ int GetInterfaceDataWLAN(HANDLE wlan_handle,
+ const GUID& interface_id,
+ WifiData::AccessPointDataSet* data);
+
+ // Logs number of detected wlan interfaces.
+ static void LogWlanInterfaceCount(int count);
+
+ // Handle to the wlanapi.dll library.
+ HINSTANCE library_;
+
+ // Function pointers for WLAN
+ WlanOpenHandleFunction WlanOpenHandle_function_;
+ WlanEnumInterfacesFunction WlanEnumInterfaces_function_;
+ WlanGetNetworkBssListFunction WlanGetNetworkBssList_function_;
+ WlanFreeMemoryFunction WlanFreeMemory_function_;
+ WlanCloseHandleFunction WlanCloseHandle_function_;
+};
+
+class WindowsNdisApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ virtual ~WindowsNdisApi();
+ static WindowsNdisApi* Create();
+
+ // WlanApiInterface
+ virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
+
+ private:
+ static bool GetInterfacesNDIS(
+ std::vector<string16>* interface_service_names_out);
+
+ // Swaps in content of the vector passed
+ explicit WindowsNdisApi(std::vector<string16>* interface_service_names);
+
+ bool GetInterfaceDataNDIS(HANDLE adapter_handle,
+ WifiData::AccessPointDataSet* data);
+ // NDIS variables.
+ std::vector<string16> interface_service_names_;
+
+ // Remembers scan result buffer size across calls.
+ int oid_buffer_size_;
+};
+
+// Extracts data for an access point and converts to Gears format.
+bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
+ AccessPointData* access_point_data);
+bool UndefineDosDevice(const string16& device_name);
+bool DefineDosDeviceIfNotExists(const string16& device_name);
+HANDLE GetFileHandle(const string16& device_name);
+// Makes the OID query and returns a Win32 error code.
+int PerformQuery(HANDLE adapter_handle,
+ BYTE* buffer,
+ DWORD buffer_size,
+ DWORD* bytes_out);
+bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer);
+// Gets the system directory and appends a trailing slash if not already
+// present.
+bool GetSystemDirectory(string16* path);
+} // namespace
+
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new Win32WifiDataProvider();
+}
+
+Win32WifiDataProvider::Win32WifiDataProvider() {
+}
+
+Win32WifiDataProvider::~Win32WifiDataProvider() {
+}
+
+WifiDataProviderCommon::WlanApiInterface* Win32WifiDataProvider::NewWlanApi() {
+ // Use the WLAN interface if we're on Vista and if it's available. Otherwise,
+ // use NDIS.
+ WlanApiInterface* api = WindowsWlanApi::Create();
+ if (api) {
+ return api;
+ }
+ return WindowsNdisApi::Create();
+}
+
+PollingPolicyInterface* Win32WifiDataProvider::NewPollingPolicy() {
+ return new GenericPollingPolicy<kDefaultPollingInterval,
+ kNoChangePollingInterval,
+ kTwoNoChangePollingInterval,
+ kNoWifiPollingIntervalMilliseconds>;
+}
+
+// Local classes and functions
+namespace {
+
+// WindowsWlanApi
+WindowsWlanApi::WindowsWlanApi(HINSTANCE library)
+ : library_(library) {
+ GetWLANFunctions(library_);
+}
+
+WindowsWlanApi::~WindowsWlanApi() {
+ FreeLibrary(library_);
+}
+
+WindowsWlanApi* WindowsWlanApi::Create() {
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return NULL;
+ // We use an absolute path to load the DLL to avoid DLL preloading attacks.
+ string16 system_directory;
+ if (!GetSystemDirectory(&system_directory)) {
+ return NULL;
+ }
+ DCHECK(!system_directory.empty());
+ string16 dll_path = system_directory + L"wlanapi.dll";
+ HINSTANCE library = LoadLibraryEx(dll_path.c_str(),
+ NULL,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (!library) {
+ return NULL;
+ }
+ return new WindowsWlanApi(library);
+}
+
+void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) {
+ DCHECK(wlan_library);
+ WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
+ GetProcAddress(wlan_library, "WlanOpenHandle"));
+ WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
+ GetProcAddress(wlan_library, "WlanEnumInterfaces"));
+ WlanGetNetworkBssList_function_ =
+ reinterpret_cast<WlanGetNetworkBssListFunction>(
+ GetProcAddress(wlan_library, "WlanGetNetworkBssList"));
+ WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
+ GetProcAddress(wlan_library, "WlanFreeMemory"));
+ WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
+ GetProcAddress(wlan_library, "WlanCloseHandle"));
+ DCHECK(WlanOpenHandle_function_ &&
+ WlanEnumInterfaces_function_ &&
+ WlanGetNetworkBssList_function_ &&
+ WlanFreeMemory_function_ &&
+ WlanCloseHandle_function_);
+}
+
+void WindowsWlanApi::LogWlanInterfaceCount(int count) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.Wifi.InterfaceCount",
+ count,
+ 1,
+ 5,
+ 5);
+}
+
+bool WindowsWlanApi::GetAccessPointData(
+ WifiData::AccessPointDataSet* data) {
+ DCHECK(data);
+
+ // Get the handle to the WLAN API.
+ DWORD negotiated_version;
+ HANDLE wlan_handle = NULL;
+ // We could be executing on either Windows XP or Windows Vista, so use the
+ // lower version of the client WLAN API. It seems that the negotiated version
+ // is the Vista version irrespective of what we pass!
+ static const int kXpWlanClientVersion = 1;
+ if ((*WlanOpenHandle_function_)(kXpWlanClientVersion,
+ NULL,
+ &negotiated_version,
+ &wlan_handle) != ERROR_SUCCESS) {
+ LogWlanInterfaceCount(0);
+ return false;
+ }
+ DCHECK(wlan_handle);
+
+ // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
+ WLAN_INTERFACE_INFO_LIST* interface_list = NULL;
+ if ((*WlanEnumInterfaces_function_)(wlan_handle, NULL, &interface_list) !=
+ ERROR_SUCCESS) {
+ LogWlanInterfaceCount(0);
+ return false;
+ }
+ DCHECK(interface_list);
+
+ LogWlanInterfaceCount(interface_list->dwNumberOfItems);
+
+ // Go through the list of interfaces and get the data for each.
+ for (int i = 0; i < static_cast<int>(interface_list->dwNumberOfItems); ++i) {
+ // Skip any interface that is midway through association; the
+ // WlanGetNetworkBssList function call is known to hang indefinitely
+ // when it's in this state. http://crbug.com/39300
+ if (interface_list->InterfaceInfo[i].isState ==
+ wlan_interface_state_associating) {
+ LOG(WARNING) << "Skipping wifi scan on adapter " << i << " ("
+ << interface_list->InterfaceInfo[i].strInterfaceDescription
+ << ") in 'associating' state. Repeated occurrences "
+ "indicates a non-responding adapter.";
+ continue;
+ }
+ GetInterfaceDataWLAN(wlan_handle,
+ interface_list->InterfaceInfo[i].InterfaceGuid,
+ data);
+ }
+
+ // Free interface_list.
+ (*WlanFreeMemory_function_)(interface_list);
+
+ // Close the handle.
+ if ((*WlanCloseHandle_function_)(wlan_handle, NULL) != ERROR_SUCCESS) {
+ return false;
+ }
+
+ return true;
+}
+
+// Appends the data for a single interface to the data vector. Returns the
+// number of access points found, or -1 on error.
+int WindowsWlanApi::GetInterfaceDataWLAN(
+ const HANDLE wlan_handle,
+ const GUID& interface_id,
+ WifiData::AccessPointDataSet* data) {
+ DCHECK(data);
+
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ // WlanGetNetworkBssList allocates bss_list.
+ WLAN_BSS_LIST* bss_list = NULL;
+ if ((*WlanGetNetworkBssList_function_)(wlan_handle,
+ &interface_id,
+ NULL, // Use all SSIDs.
+ dot11_BSS_type_any,
+ false, // bSecurityEnabled - unused
+ NULL, // reserved
+ &bss_list) != ERROR_SUCCESS) {
+ return -1;
+ }
+ // According to http://www.attnetclient.com/kb/questions.php?questionid=75
+ // WlanGetNetworkBssList can sometimes return success, but leave the bss
+ // list as NULL.
+ if (!bss_list)
+ return -1;
+
+ const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Wifi.ScanLatency",
+ duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+
+
+ int found = 0;
+ for (int i = 0; i < static_cast<int>(bss_list->dwNumberOfItems); ++i) {
+ AccessPointData access_point_data;
+ if (GetNetworkData(bss_list->wlanBssEntries[i], &access_point_data)) {
+ ++found;
+ data->insert(access_point_data);
+ }
+ }
+
+ (*WlanFreeMemory_function_)(bss_list);
+
+ return found;
+}
+
+// WindowsNdisApi
+WindowsNdisApi::WindowsNdisApi(
+ std::vector<string16>* interface_service_names)
+ : oid_buffer_size_(kInitialBufferSize) {
+ DCHECK(!interface_service_names->empty());
+ interface_service_names_.swap(*interface_service_names);
+}
+
+WindowsNdisApi::~WindowsNdisApi() {
+}
+
+WindowsNdisApi* WindowsNdisApi::Create() {
+ std::vector<string16> interface_service_names;
+ if (GetInterfacesNDIS(&interface_service_names)) {
+ return new WindowsNdisApi(&interface_service_names);
+ }
+ return NULL;
+}
+
+bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
+ DCHECK(data);
+ int interfaces_failed = 0;
+ int interfaces_succeeded = 0;
+
+ for (int i = 0; i < static_cast<int>(interface_service_names_.size()); ++i) {
+ // First, check that we have a DOS device for this adapter.
+ if (!DefineDosDeviceIfNotExists(interface_service_names_[i])) {
+ continue;
+ }
+
+ // Get the handle to the device. This will fail if the named device is not
+ // valid.
+ HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]);
+ if (adapter_handle == INVALID_HANDLE_VALUE) {
+ continue;
+ }
+
+ // Get the data.
+ if (GetInterfaceDataNDIS(adapter_handle, data)) {
+ ++interfaces_succeeded;
+ } else {
+ ++interfaces_failed;
+ }
+
+ // Clean up.
+ CloseHandle(adapter_handle);
+ UndefineDosDevice(interface_service_names_[i]);
+ }
+
+ // Return true if at least one interface succeeded, or at the very least none
+ // failed.
+ return interfaces_succeeded > 0 || interfaces_failed == 0;
+}
+
+bool WindowsNdisApi::GetInterfacesNDIS(
+ std::vector<string16>* interface_service_names_out) {
+ HKEY network_cards_key = NULL;
+ if (RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards",
+ 0,
+ KEY_READ,
+ &network_cards_key) != ERROR_SUCCESS) {
+ return false;
+ }
+ DCHECK(network_cards_key);
+
+ for (int i = 0; ; ++i) {
+ TCHAR name[kStringLength];
+ DWORD name_size = kStringLength;
+ FILETIME time;
+ if (RegEnumKeyEx(network_cards_key,
+ i,
+ name,
+ &name_size,
+ NULL,
+ NULL,
+ NULL,
+ &time) != ERROR_SUCCESS) {
+ break;
+ }
+ HKEY hardware_key = NULL;
+ if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) !=
+ ERROR_SUCCESS) {
+ break;
+ }
+ DCHECK(hardware_key);
+
+ TCHAR service_name[kStringLength];
+ DWORD service_name_size = kStringLength;
+ DWORD type = 0;
+ if (RegQueryValueEx(hardware_key,
+ L"ServiceName",
+ NULL,
+ &type,
+ reinterpret_cast<LPBYTE>(service_name),
+ &service_name_size) == ERROR_SUCCESS) {
+ interface_service_names_out->push_back(service_name);
+ }
+ RegCloseKey(hardware_key);
+ }
+
+ RegCloseKey(network_cards_key);
+ return true;
+}
+
+
+bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle,
+ WifiData::AccessPointDataSet* data) {
+ DCHECK(data);
+
+ scoped_ptr_malloc<BYTE> buffer(
+ reinterpret_cast<BYTE*>(malloc(oid_buffer_size_)));
+ if (buffer == NULL) {
+ return false;
+ }
+
+ DWORD bytes_out;
+ int result;
+
+ while (true) {
+ bytes_out = 0;
+ result = PerformQuery(adapter_handle, buffer.get(),
+ oid_buffer_size_, &bytes_out);
+ if (result == ERROR_GEN_FAILURE || // Returned by some Intel cards.
+ result == ERROR_INSUFFICIENT_BUFFER ||
+ result == ERROR_MORE_DATA ||
+ result == NDIS_STATUS_INVALID_LENGTH ||
+ result == NDIS_STATUS_BUFFER_TOO_SHORT) {
+ // The buffer we supplied is too small, so increase it. bytes_out should
+ // provide the required buffer size, but this is not always the case.
+ if (bytes_out > static_cast<DWORD>(oid_buffer_size_)) {
+ oid_buffer_size_ = bytes_out;
+ } else {
+ oid_buffer_size_ *= 2;
+ }
+ if (!ResizeBuffer(oid_buffer_size_, &buffer)) {
+ oid_buffer_size_ = kInitialBufferSize; // Reset for next time.
+ return false;
+ }
+ } else {
+ // The buffer is not too small.
+ break;
+ }
+ }
+ DCHECK(buffer.get());
+
+ if (result == ERROR_SUCCESS) {
+ NDIS_802_11_BSSID_LIST* bssid_list =
+ reinterpret_cast<NDIS_802_11_BSSID_LIST*>(buffer.get());
+ GetDataFromBssIdList(*bssid_list, oid_buffer_size_, data);
+ }
+
+ return true;
+}
+
+bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
+ AccessPointData* access_point_data) {
+ // Currently we get only MAC address, signal strength and SSID.
+ DCHECK(access_point_data);
+ access_point_data->mac_address = MacAddressAsString16(bss_entry.dot11Bssid);
+ access_point_data->radio_signal_strength = bss_entry.lRssi;
+ // bss_entry.dot11Ssid.ucSSID is not null-terminated.
+ UTF8ToUTF16(reinterpret_cast<const char*>(bss_entry.dot11Ssid.ucSSID),
+ static_cast<ULONG>(bss_entry.dot11Ssid.uSSIDLength),
+ &access_point_data->ssid);
+ // TODO(steveblock): Is it possible to get the following?
+ // access_point_data->signal_to_noise
+ // access_point_data->age
+ // access_point_data->channel
+ return true;
+}
+
+bool UndefineDosDevice(const string16& device_name) {
+ // We remove only the mapping we use, that is \Device\<device_name>.
+ string16 target_path = L"\\Device\\" + device_name;
+ return DefineDosDevice(
+ DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
+ device_name.c_str(),
+ target_path.c_str()) == TRUE;
+}
+
+bool DefineDosDeviceIfNotExists(const string16& device_name) {
+ // We create a DOS device name for the device at \Device\<device_name>.
+ string16 target_path = L"\\Device\\" + device_name;
+
+ TCHAR target[kStringLength];
+ if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
+ target_path.compare(target) == 0) {
+ // Device already exists.
+ return true;
+ }
+
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ return false;
+ }
+
+ if (!DefineDosDevice(DDD_RAW_TARGET_PATH,
+ device_name.c_str(),
+ target_path.c_str())) {
+ return false;
+ }
+
+ // Check that the device is really there.
+ return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
+ target_path.compare(target) == 0;
+}
+
+HANDLE GetFileHandle(const string16& device_name) {
+ // We access a device with DOS path \Device\<device_name> at
+ // \\.\<device_name>.
+ string16 formatted_device_name = L"\\\\.\\" + device_name;
+
+ return CreateFile(formatted_device_name.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
+ 0, // security attributes
+ OPEN_EXISTING,
+ 0, // flags and attributes
+ INVALID_HANDLE_VALUE);
+}
+
+int PerformQuery(HANDLE adapter_handle,
+ BYTE* buffer,
+ DWORD buffer_size,
+ DWORD* bytes_out) {
+ DWORD oid = OID_802_11_BSSID_LIST;
+ if (!DeviceIoControl(adapter_handle,
+ IOCTL_NDIS_QUERY_GLOBAL_STATS,
+ &oid,
+ sizeof(oid),
+ buffer,
+ buffer_size,
+ bytes_out,
+ NULL)) {
+ return GetLastError();
+ }
+ return ERROR_SUCCESS;
+}
+
+bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer) {
+ DCHECK_GT(requested_size, 0);
+ DCHECK(buffer);
+ if (requested_size > kMaximumBufferSize) {
+ buffer->reset();
+ return false;
+ }
+
+ buffer->reset(reinterpret_cast<BYTE*>(
+ realloc(buffer->release(), requested_size)));
+ return buffer != NULL;
+}
+
+bool GetSystemDirectory(string16* path) {
+ DCHECK(path);
+ // Return value includes terminating NULL.
+ int buffer_size = ::GetSystemDirectory(NULL, 0);
+ if (buffer_size == 0) {
+ return false;
+ }
+ scoped_ptr<char16[]> buffer(new char16[buffer_size]);
+
+ // Return value excludes terminating NULL.
+ int characters_written = ::GetSystemDirectory(buffer.get(), buffer_size);
+ if (characters_written == 0) {
+ return false;
+ }
+ DCHECK_EQ(buffer_size - 1, characters_written);
+
+ path->assign(buffer.get(), characters_written);
+
+ if (*path->rbegin() != L'\\') {
+ path->append(L"\\");
+ }
+ DCHECK_EQ(L'\\', *path->rbegin());
+ return true;
+}
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/geolocation/wifi_data_provider_win.h b/chromium/content/browser/geolocation/wifi_data_provider_win.h
new file mode 100644
index 00000000000..3fbb9cec99d
--- /dev/null
+++ b/chromium/content/browser/geolocation/wifi_data_provider_win.h
@@ -0,0 +1,30 @@
+// 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 CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_WIN_H_
+#define CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_WIN_H_
+
+#include "content/browser/geolocation/wifi_data_provider_common.h"
+#include "content/common/content_export.h"
+
+namespace content {
+class PollingPolicyInterface;
+
+class CONTENT_EXPORT Win32WifiDataProvider : public WifiDataProviderCommon {
+ public:
+ Win32WifiDataProvider();
+
+ private:
+ virtual ~Win32WifiDataProvider();
+
+ // WifiDataProviderCommon
+ virtual WlanApiInterface* NewWlanApi();
+ virtual PollingPolicyInterface* NewPollingPolicy();
+
+ DISALLOW_COPY_AND_ASSIGN(Win32WifiDataProvider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GEOLOCATION_WIFI_DATA_PROVIDER_WIN_H_
diff --git a/chromium/content/browser/gpu.sb b/chromium/content/browser/gpu.sb
new file mode 100644
index 00000000000..b3d17d5182b
--- /dev/null
+++ b/chromium/content/browser/gpu.sb
@@ -0,0 +1,24 @@
+;;
+;; Copyright (c) 2011 The Chromium Authors. All rights reserved.
+;; Use of this source code is governed by a BSD-style license that can be
+;; found in the LICENSE file.
+;;
+
+; *** The contents of content/common/common.sb are implicitly included here. ***
+
+; Allow communication between the GPU process and the UI server.
+(allow mach-lookup (global-name "com.apple.tsm.uiserver"))
+
+(allow file-read-metadata (literal "/"))
+
+; Needed for WebGL on OS X 10.7 - crbug.com/75343
+;10.7_OR_ABOVE (allow iokit-open
+;10.7_OR_ABOVE (iokit-connection "IOAccelerator")
+;10.7_OR_ABOVE (iokit-user-client-class "IOAccelerationUserClient")
+;10.7_OR_ABOVE (iokit-user-client-class "IOFramebufferSharedUserClient")
+;10.7_OR_ABOVE (iokit-user-client-class "AppleGraphicsControlClient")
+;10.7_OR_ABOVE (iokit-user-client-class "AGPMClient")
+;10.7_OR_ABOVE (iokit-user-client-class "IOHIDParamUserClient")
+;10.7_OR_ABOVE (iokit-user-client-class "RootDomainUserClient")
+;10.7_OR_ABOVE (iokit-user-client-class "IOSurfaceRootUserClient")
+;10.7_OR_ABOVE (iokit-user-client-class "IOSurfaceSendRight")) \ No newline at end of file
diff --git a/chromium/content/browser/gpu/OWNERS b/chromium/content/browser/gpu/OWNERS
new file mode 100644
index 00000000000..92ac85812fe
--- /dev/null
+++ b/chromium/content/browser/gpu/OWNERS
@@ -0,0 +1,4 @@
+apatrick@chromium.org
+kbr@chromium.org
+piman@chromium.org
+zmo@chromium.org
diff --git a/chromium/content/browser/gpu/browser_gpu_channel_host_factory.cc b/chromium/content/browser/gpu/browser_gpu_channel_host_factory.cc
new file mode 100644
index 00000000000..7bc6fca71e6
--- /dev/null
+++ b/chromium/content/browser/gpu/browser_gpu_channel_host_factory.cc
@@ -0,0 +1,321 @@
+// 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 "content/browser/gpu/browser_gpu_channel_host_factory.h"
+
+#include "base/bind.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_client.h"
+#include "ipc/ipc_forwarding_message_filter.h"
+
+namespace content {
+
+BrowserGpuChannelHostFactory* BrowserGpuChannelHostFactory::instance_ = NULL;
+
+BrowserGpuChannelHostFactory::CreateRequest::CreateRequest()
+ : event(false, false),
+ gpu_host_id(0),
+ route_id(MSG_ROUTING_NONE) {
+}
+
+BrowserGpuChannelHostFactory::CreateRequest::~CreateRequest() {
+}
+
+BrowserGpuChannelHostFactory::EstablishRequest::EstablishRequest(
+ CauseForGpuLaunch cause)
+ : event(false, false),
+ cause_for_gpu_launch(cause),
+ gpu_host_id(0),
+ reused_gpu_process(true) {
+}
+
+BrowserGpuChannelHostFactory::EstablishRequest::~EstablishRequest() {
+}
+
+void BrowserGpuChannelHostFactory::Initialize() {
+ instance_ = new BrowserGpuChannelHostFactory();
+}
+
+void BrowserGpuChannelHostFactory::Terminate() {
+ delete instance_;
+ instance_ = NULL;
+}
+
+BrowserGpuChannelHostFactory::BrowserGpuChannelHostFactory()
+ : gpu_client_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()),
+ shutdown_event_(new base::WaitableEvent(true, false)),
+ gpu_host_id_(0) {
+}
+
+BrowserGpuChannelHostFactory::~BrowserGpuChannelHostFactory() {
+ shutdown_event_->Signal();
+}
+
+bool BrowserGpuChannelHostFactory::IsMainThread() {
+ return BrowserThread::CurrentlyOn(BrowserThread::UI);
+}
+
+base::MessageLoop* BrowserGpuChannelHostFactory::GetMainLoop() {
+ return BrowserThread::UnsafeGetMessageLoopForThread(BrowserThread::UI);
+}
+
+scoped_refptr<base::MessageLoopProxy>
+BrowserGpuChannelHostFactory::GetIOLoopProxy() {
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
+}
+
+base::WaitableEvent* BrowserGpuChannelHostFactory::GetShutDownEvent() {
+ return shutdown_event_.get();
+}
+
+scoped_ptr<base::SharedMemory>
+BrowserGpuChannelHostFactory::AllocateSharedMemory(size_t size) {
+ scoped_ptr<base::SharedMemory> shm(new base::SharedMemory());
+ if (!shm->CreateAnonymous(size))
+ return scoped_ptr<base::SharedMemory>();
+ return shm.Pass();
+}
+
+void BrowserGpuChannelHostFactory::CreateViewCommandBufferOnIO(
+ CreateRequest* request,
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params) {
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_host_id_);
+ if (!host) {
+ request->event.Signal();
+ return;
+ }
+
+ gfx::GLSurfaceHandle surface =
+ GpuSurfaceTracker::Get()->GetSurfaceHandle(surface_id);
+
+ host->CreateViewCommandBuffer(
+ surface,
+ surface_id,
+ gpu_client_id_,
+ init_params,
+ base::Bind(&BrowserGpuChannelHostFactory::CommandBufferCreatedOnIO,
+ request));
+}
+
+// static
+void BrowserGpuChannelHostFactory::CommandBufferCreatedOnIO(
+ CreateRequest* request, int32 route_id) {
+ request->route_id = route_id;
+ request->event.Signal();
+}
+
+int32 BrowserGpuChannelHostFactory::CreateViewCommandBuffer(
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params) {
+ CreateRequest request;
+ GetIOLoopProxy()->PostTask(FROM_HERE, base::Bind(
+ &BrowserGpuChannelHostFactory::CreateViewCommandBufferOnIO,
+ base::Unretained(this),
+ &request,
+ surface_id,
+ init_params));
+ // We're blocking the UI thread, which is generally undesirable.
+ // In this case we need to wait for this before we can show any UI /anyway/,
+ // so it won't cause additional jank.
+ // TODO(piman): Make this asynchronous (http://crbug.com/125248).
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ request.event.Wait();
+ return request.route_id;
+}
+
+void BrowserGpuChannelHostFactory::CreateImageOnIO(
+ gfx::PluginWindowHandle window,
+ int32 image_id,
+ const CreateImageCallback& callback) {
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_host_id_);
+ if (!host) {
+ ImageCreatedOnIO(callback, gfx::Size());
+ return;
+ }
+
+ host->CreateImage(
+ window,
+ gpu_client_id_,
+ image_id,
+ base::Bind(&BrowserGpuChannelHostFactory::ImageCreatedOnIO, callback));
+}
+
+// static
+void BrowserGpuChannelHostFactory::ImageCreatedOnIO(
+ const CreateImageCallback& callback, const gfx::Size size) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&BrowserGpuChannelHostFactory::OnImageCreated,
+ callback, size));
+}
+
+// static
+void BrowserGpuChannelHostFactory::OnImageCreated(
+ const CreateImageCallback& callback, const gfx::Size size) {
+ callback.Run(size);
+}
+
+void BrowserGpuChannelHostFactory::CreateImage(
+ gfx::PluginWindowHandle window,
+ int32 image_id,
+ const CreateImageCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ GetIOLoopProxy()->PostTask(FROM_HERE, base::Bind(
+ &BrowserGpuChannelHostFactory::CreateImageOnIO,
+ base::Unretained(this),
+ window,
+ image_id,
+ callback));
+}
+
+void BrowserGpuChannelHostFactory::DeleteImageOnIO(
+ int32 image_id, int32 sync_point) {
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_host_id_);
+ if (!host) {
+ return;
+ }
+
+ host->DeleteImage(gpu_client_id_, image_id, sync_point);
+}
+
+void BrowserGpuChannelHostFactory::DeleteImage(
+ int32 image_id, int32 sync_point) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ GetIOLoopProxy()->PostTask(FROM_HERE, base::Bind(
+ &BrowserGpuChannelHostFactory::DeleteImageOnIO,
+ base::Unretained(this),
+ image_id,
+ sync_point));
+}
+
+void BrowserGpuChannelHostFactory::EstablishGpuChannelOnIO(
+ EstablishRequest* request) {
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_host_id_);
+ if (!host) {
+ host = GpuProcessHost::Get(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ request->cause_for_gpu_launch);
+ if (!host) {
+ request->event.Signal();
+ return;
+ }
+ gpu_host_id_ = host->host_id();
+ request->reused_gpu_process = false;
+ } else {
+ if (host->host_id() == request->gpu_host_id) {
+ // We come here if we retried to establish the channel because of a
+ // failure in GpuChannelEstablishedOnIO, but we ended up with the same
+ // process ID, meaning the failure was not because of a channel error, but
+ // another reason. So fail now.
+ request->event.Signal();
+ return;
+ }
+ request->reused_gpu_process = true;
+ }
+ request->gpu_host_id = gpu_host_id_;
+
+ host->EstablishGpuChannel(
+ gpu_client_id_,
+ true,
+ base::Bind(&BrowserGpuChannelHostFactory::GpuChannelEstablishedOnIO,
+ base::Unretained(this),
+ request));
+}
+
+void BrowserGpuChannelHostFactory::GpuChannelEstablishedOnIO(
+ EstablishRequest* request,
+ const IPC::ChannelHandle& channel_handle,
+ const gpu::GPUInfo& gpu_info) {
+ if (channel_handle.name.empty() && request->reused_gpu_process) {
+ // We failed after re-using the GPU process, but it may have died in the
+ // mean time. Retry to have a chance to create a fresh GPU process.
+ EstablishGpuChannelOnIO(request);
+ } else {
+ request->channel_handle = channel_handle;
+ request->gpu_info = gpu_info;
+ request->event.Signal();
+ }
+}
+
+GpuChannelHost* BrowserGpuChannelHostFactory::EstablishGpuChannelSync(
+ CauseForGpuLaunch cause_for_gpu_launch) {
+ if (gpu_channel_.get()) {
+ // Recreate the channel if it has been lost.
+ if (gpu_channel_->IsLost())
+ gpu_channel_ = NULL;
+ else
+ return gpu_channel_.get();
+ }
+ // Ensure initialization on the main thread.
+ GpuDataManagerImpl::GetInstance();
+
+ EstablishRequest request(cause_for_gpu_launch);
+ GetIOLoopProxy()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &BrowserGpuChannelHostFactory::EstablishGpuChannelOnIO,
+ base::Unretained(this),
+ &request));
+
+ {
+ // We're blocking the UI thread, which is generally undesirable.
+ // In this case we need to wait for this before we can show any UI /anyway/,
+ // so it won't cause additional jank.
+ // TODO(piman): Make this asynchronous (http://crbug.com/125248).
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ request.event.Wait();
+ }
+
+ if (request.channel_handle.name.empty())
+ return NULL;
+
+ GetContentClient()->SetGpuInfo(request.gpu_info);
+ gpu_channel_ = GpuChannelHost::Create(
+ this, request.gpu_host_id, gpu_client_id_,
+ request.gpu_info, request.channel_handle);
+ return gpu_channel_.get();
+}
+
+// static
+void BrowserGpuChannelHostFactory::AddFilterOnIO(
+ int host_id,
+ scoped_refptr<IPC::ChannelProxy::MessageFilter> filter) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ GpuProcessHost* host = GpuProcessHost::FromID(host_id);
+ if (host)
+ host->AddFilter(filter.get());
+}
+
+void BrowserGpuChannelHostFactory::SetHandlerForControlMessages(
+ const uint32* message_ids,
+ size_t num_messages,
+ const base::Callback<void(const IPC::Message&)>& handler,
+ base::TaskRunner* target_task_runner) {
+ DCHECK(gpu_host_id_)
+ << "Do not call"
+ << " BrowserGpuChannelHostFactory::SetHandlerForControlMessages()"
+ << " until the GpuProcessHost has been set up.";
+
+ scoped_refptr<IPC::ForwardingMessageFilter> filter =
+ new IPC::ForwardingMessageFilter(message_ids,
+ num_messages,
+ target_task_runner);
+ filter->AddRoute(MSG_ROUTING_CONTROL, handler);
+
+ GetIOLoopProxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&BrowserGpuChannelHostFactory::AddFilterOnIO,
+ gpu_host_id_,
+ filter));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/browser_gpu_channel_host_factory.h b/chromium/content/browser/gpu/browser_gpu_channel_host_factory.h
new file mode 100644
index 00000000000..ca311839d9c
--- /dev/null
+++ b/chromium/content/browser/gpu/browser_gpu_channel_host_factory.h
@@ -0,0 +1,110 @@
+// 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 CONTENT_BROWSER_GPU_BROWSER_GPU_CHANNEL_HOST_FACTORY_H_
+#define CONTENT_BROWSER_GPU_BROWSER_GPU_CHANNEL_HOST_FACTORY_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/common/gpu/client/gpu_channel_host.h"
+#include "ipc/ipc_channel_handle.h"
+
+namespace content {
+
+class CONTENT_EXPORT BrowserGpuChannelHostFactory
+ : public GpuChannelHostFactory {
+ public:
+ static void Initialize();
+ static void Terminate();
+ static BrowserGpuChannelHostFactory* instance() { return instance_; }
+
+ // GpuChannelHostFactory implementation.
+ virtual bool IsMainThread() OVERRIDE;
+ virtual base::MessageLoop* GetMainLoop() OVERRIDE;
+ virtual scoped_refptr<base::MessageLoopProxy> GetIOLoopProxy() OVERRIDE;
+ virtual base::WaitableEvent* GetShutDownEvent() OVERRIDE;
+ virtual scoped_ptr<base::SharedMemory> AllocateSharedMemory(
+ size_t size) OVERRIDE;
+ virtual int32 CreateViewCommandBuffer(
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params) OVERRIDE;
+ virtual void CreateImage(
+ gfx::PluginWindowHandle window,
+ int32 image_id,
+ const CreateImageCallback& callback) OVERRIDE;
+ virtual void DeleteImage(int32 image_idu, int32 sync_point) OVERRIDE;
+ virtual GpuChannelHost* EstablishGpuChannelSync(
+ CauseForGpuLaunch cause_for_gpu_launch) OVERRIDE;
+
+ // Specify a task runner and callback to be used for a set of messages. The
+ // callback will be set up on the current GpuProcessHost, identified by
+ // GpuProcessHostId().
+ virtual void SetHandlerForControlMessages(
+ const uint32* message_ids,
+ size_t num_messages,
+ const base::Callback<void(const IPC::Message&)>& handler,
+ base::TaskRunner* target_task_runner);
+ int GpuProcessHostId() { return gpu_host_id_; }
+
+ private:
+ struct CreateRequest {
+ CreateRequest();
+ ~CreateRequest();
+ base::WaitableEvent event;
+ int gpu_host_id;
+ int32 route_id;
+ };
+
+ struct EstablishRequest {
+ explicit EstablishRequest(CauseForGpuLaunch);
+ ~EstablishRequest();
+ base::WaitableEvent event;
+ CauseForGpuLaunch cause_for_gpu_launch;
+ int gpu_host_id;
+ bool reused_gpu_process;
+ IPC::ChannelHandle channel_handle;
+ gpu::GPUInfo gpu_info;
+ };
+
+ BrowserGpuChannelHostFactory();
+ virtual ~BrowserGpuChannelHostFactory();
+
+ void CreateViewCommandBufferOnIO(
+ CreateRequest* request,
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params);
+ static void CommandBufferCreatedOnIO(CreateRequest* request, int32 route_id);
+ void CreateImageOnIO(
+ gfx::PluginWindowHandle window,
+ int32 image_id,
+ const CreateImageCallback& callback);
+ static void ImageCreatedOnIO(
+ const CreateImageCallback& callback, const gfx::Size size);
+ static void OnImageCreated(
+ const CreateImageCallback& callback, const gfx::Size size);
+ void DeleteImageOnIO(int32 image_id, int32 sync_point);
+ void EstablishGpuChannelOnIO(EstablishRequest* request);
+ void GpuChannelEstablishedOnIO(
+ EstablishRequest* request,
+ const IPC::ChannelHandle& channel_handle,
+ const gpu::GPUInfo& gpu_info);
+ static void AddFilterOnIO(
+ int gpu_host_id,
+ scoped_refptr<IPC::ChannelProxy::MessageFilter> filter);
+
+ int gpu_client_id_;
+ scoped_ptr<base::WaitableEvent> shutdown_event_;
+ scoped_refptr<GpuChannelHost> gpu_channel_;
+ int gpu_host_id_;
+
+ static BrowserGpuChannelHostFactory* instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserGpuChannelHostFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_BROWSER_GPU_CHANNEL_HOST_FACTORY_H_
diff --git a/chromium/content/browser/gpu/compositor_util.cc b/chromium/content/browser/gpu/compositor_util.cc
new file mode 100644
index 00000000000..a0cf1e21a2b
--- /dev/null
+++ b/chromium/content/browser/gpu/compositor_util.cc
@@ -0,0 +1,117 @@
+// 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 "content/public/browser/compositor_util.h"
+
+#include "base/command_line.h"
+#include "base/metrics/field_trial.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "gpu/config/gpu_feature_type.h"
+
+namespace content {
+
+namespace {
+
+bool CanDoAcceleratedCompositing() {
+ const GpuDataManager* manager = GpuDataManager::GetInstance();
+
+ // Don't run the field trial if gpu access has been blocked or
+ // accelerated compositing is blacklisted.
+ if (!manager->GpuAccessAllowed(NULL) ||
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING))
+ return false;
+
+ // Check for SwiftShader.
+ if (manager->ShouldUseSwiftShader())
+ return false;
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kDisableAcceleratedCompositing))
+ return false;
+
+ return true;
+}
+
+bool IsForceCompositingModeBlacklisted() {
+ return GpuDataManager::GetInstance()->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_FORCE_COMPOSITING_MODE);
+}
+
+} // namespace
+
+bool IsThreadedCompositingEnabled() {
+#if defined(OS_WIN) && defined(USE_AURA)
+ // We always want compositing on Aura Windows.
+ return true;
+#endif
+
+ if (!CanDoAcceleratedCompositing())
+ return false;
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+
+ // Command line switches take precedence over blacklist and field trials.
+ if (command_line.HasSwitch(switches::kDisableForceCompositingMode) ||
+ command_line.HasSwitch(switches::kDisableThreadedCompositing))
+ return false;
+
+#if defined(OS_CHROMEOS)
+ // We always want threaded compositing on ChromeOS unless it's explicitly
+ // disabled above.
+ return true;
+#endif
+
+ if (command_line.HasSwitch(switches::kEnableThreadedCompositing))
+ return true;
+
+ if (IsForceCompositingModeBlacklisted())
+ return false;
+
+ base::FieldTrial* trial =
+ base::FieldTrialList::Find(kGpuCompositingFieldTrialName);
+ return trial &&
+ trial->group_name() == kGpuCompositingFieldTrialThreadEnabledName;
+}
+
+bool IsForceCompositingModeEnabled() {
+#if defined(OS_WIN) && defined(USE_AURA)
+ // We always want compositing on Aura Windows.
+ return true;
+#endif
+
+ if (!CanDoAcceleratedCompositing())
+ return false;
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+
+ // Command line switches take precedence over blacklisting and field trials.
+ if (command_line.HasSwitch(switches::kDisableForceCompositingMode))
+ return false;
+
+#if defined(OS_CHROMEOS)
+ // We always want compositing ChromeOS unless it's explicitly disabled above.
+ return true;
+#endif
+
+ if (command_line.HasSwitch(switches::kForceCompositingMode))
+ return true;
+
+ if (IsForceCompositingModeBlacklisted())
+ return false;
+
+ base::FieldTrial* trial =
+ base::FieldTrialList::Find(kGpuCompositingFieldTrialName);
+
+ // Force compositing is enabled in both the force compositing
+ // and threaded compositing mode field trials.
+ return trial &&
+ (trial->group_name() ==
+ kGpuCompositingFieldTrialForceCompositingEnabledName ||
+ trial->group_name() == kGpuCompositingFieldTrialThreadEnabledName);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/generate_webgl_conformance_test_list.py b/chromium/content/browser/gpu/generate_webgl_conformance_test_list.py
new file mode 100755
index 00000000000..8d5854ab52d
--- /dev/null
+++ b/chromium/content/browser/gpu/generate_webgl_conformance_test_list.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# 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.
+
+"""Auto-generates the WebGL conformance test list header file.
+
+Parses the WebGL conformance test *.txt file, which contains a list of URLs
+for individual conformance tests (each on a new line). It recursively parses
+*.txt files. For each test URL, the matching gtest call is created and
+sent to the C++ header file.
+"""
+
+import getopt
+import os
+import re
+import sys
+
+COPYRIGHT = """\
+// 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.
+
+"""
+WARNING = """\
+// DO NOT EDIT! This file is auto-generated by
+// generate_webgl_conformance_test_list.py
+// It is included by webgl_conformance_test.cc
+
+"""
+HEADER_GUARD = """\
+#ifndef CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+#define CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+
+"""
+HEADER_GUARD_END = """
+#endif // CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+
+"""
+
+# Assume this script is run from the src/content/test/gpu directory.
+INPUT_DIR = "../../../third_party/webgl_conformance"
+INPUT_FILE = "00_test_list.txt"
+OUTPUT_FILE = "webgl_conformance_test_list_autogen.h"
+
+def main(argv):
+ """Main function for the WebGL conformance test list generator.
+ """
+ if not os.path.exists(os.path.join(INPUT_DIR, INPUT_FILE)):
+ print >> sys.stderr, "ERROR: WebGL conformance tests do not exist."
+ print >> sys.stderr, "Run the script from the directory containing it."
+ return 1
+
+ output = open(OUTPUT_FILE, "w")
+ output.write(COPYRIGHT)
+ output.write(WARNING)
+ output.write(HEADER_GUARD)
+
+ test_prefix = {}
+
+ unparsed_files = [INPUT_FILE]
+ while unparsed_files:
+ filename = unparsed_files.pop(0)
+ try:
+ input = open(os.path.join(INPUT_DIR, filename))
+ except IOError:
+ print >> sys.stderr, "WARNING: %s does not exist (skipped)." % filename
+ continue
+
+ for url in input:
+ url = re.sub("//.*", "", url)
+ url = re.sub("#.*", "", url)
+ url = url.strip()
+ # Some filename has options before them, for example,
+ # --min-version 1.0.2 testname.html
+ pos = url.rfind(" ")
+ if pos != -1:
+ url = url[pos+1:]
+
+ if not url:
+ continue
+
+ # Cannot use os.path.join() because Windows with use "\\" but this path
+ # is sent through javascript.
+ if os.path.dirname(filename):
+ url = "%s/%s" % (os.path.dirname(filename), url)
+
+ # Queue all text files for parsing, because test list URLs are nested
+ # through .txt files.
+ if re.match(".+\.txt\s*$", url):
+ unparsed_files.append(url)
+
+ # Convert the filename to a valid test name and output the gtest code.
+ else:
+ name = os.path.splitext(url)[0]
+ name = re.sub("\W+", "_", name)
+ if os.path.exists(os.path.join(INPUT_DIR, url)):
+ output.write('CONFORMANCE_TEST(%s,\n "%s");\n' % (name, url))
+ else:
+ print >> sys.stderr, "WARNING: %s does not exist (skipped)." % url
+ input.close()
+
+ output.write(HEADER_GUARD_END)
+ output.close()
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
diff --git a/chromium/content/browser/gpu/gpu_crash_browsertest.cc b/chromium/content/browser/gpu/gpu_crash_browsertest.cc
new file mode 100644
index 00000000000..eafbc689cd0
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_crash_browsertest.cc
@@ -0,0 +1,76 @@
+// 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 "base/path_service.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+class GpuCrashTest : public ContentBrowserTest {
+ protected:
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ base::FilePath test_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir));
+ gpu_test_dir_ = test_dir.AppendASCII("gpu");
+ }
+ base::FilePath gpu_test_dir_;
+};
+
+#if defined(OS_LINUX) && !defined(NDEBUG)
+// http://crbug.com/254724
+#define IF_NOT_DEBUG_LINUX(x) DISABLED_ ## x
+#else
+#define IF_NOT_DEBUG_LINUX(x) x
+#endif
+
+IN_PROC_BROWSER_TEST_F(GpuCrashTest, IF_NOT_DEBUG_LINUX(MANUAL_Kill)) {
+ DOMMessageQueue message_queue;
+
+ content::GpuDataManagerImpl::GetInstance()->
+ DisableDomainBlockingFor3DAPIsForTesting();
+
+ // Load page and wait for it to load.
+ content::WindowedNotificationObserver observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::NotificationService::AllSources());
+ NavigateToURL(
+ shell(),
+ GetFileUrlWithQuery(
+ gpu_test_dir_.AppendASCII("webgl.html"), "query=kill"));
+ observer.Wait();
+
+ GpuProcessHostUIShim* host =
+ GpuProcessHostUIShim::GetOneInstance();
+ ASSERT_TRUE(host);
+ host->SimulateCrash();
+
+ std::string m;
+ ASSERT_TRUE(message_queue.WaitForMessage(&m));
+ EXPECT_EQ("\"SUCCESS\"", m);
+}
+
+IN_PROC_BROWSER_TEST_F(GpuCrashTest,
+ IF_NOT_DEBUG_LINUX(MANUAL_WebkitLoseContext)) {
+ DOMMessageQueue message_queue;
+
+ NavigateToURL(
+ shell(),
+ GetFileUrlWithQuery(
+ gpu_test_dir_.AppendASCII("webgl.html"),
+ "query=WEBGL_lose_context"));
+
+ std::string m;
+ ASSERT_TRUE(message_queue.WaitForMessage(&m));
+ EXPECT_EQ("\"SUCCESS\"", m);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_data_manager_impl.cc b/chromium/content/browser/gpu/gpu_data_manager_impl.cc
new file mode 100644
index 00000000000..747503cedfe
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_data_manager_impl.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 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 "content/browser/gpu/gpu_data_manager_impl.h"
+
+#include "content/browser/gpu/gpu_data_manager_impl_private.h"
+
+namespace content {
+
+// static
+GpuDataManager* GpuDataManager::GetInstance() {
+ return GpuDataManagerImpl::GetInstance();
+}
+
+// static
+GpuDataManagerImpl* GpuDataManagerImpl::GetInstance() {
+ return Singleton<GpuDataManagerImpl>::get();
+}
+
+void GpuDataManagerImpl::InitializeForTesting(
+ const std::string& gpu_blacklist_json, const gpu::GPUInfo& gpu_info) {
+ base::AutoLock auto_lock(lock_);
+ private_->InitializeForTesting(gpu_blacklist_json, gpu_info);
+}
+
+bool GpuDataManagerImpl::IsFeatureBlacklisted(int feature) const {
+ base::AutoLock auto_lock(lock_);
+ return private_->IsFeatureBlacklisted(feature);
+}
+
+gpu::GPUInfo GpuDataManagerImpl::GetGPUInfo() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetGPUInfo();
+}
+
+void GpuDataManagerImpl::GetGpuProcessHandles(
+ const GetGpuProcessHandlesCallback& callback) const {
+ base::AutoLock auto_lock(lock_);
+ private_->GetGpuProcessHandles(callback);
+}
+
+bool GpuDataManagerImpl::GpuAccessAllowed(std::string* reason) const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GpuAccessAllowed(reason);
+}
+
+void GpuDataManagerImpl::RequestCompleteGpuInfoIfNeeded() {
+ base::AutoLock auto_lock(lock_);
+ private_->RequestCompleteGpuInfoIfNeeded();
+}
+
+bool GpuDataManagerImpl::IsCompleteGpuInfoAvailable() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->IsCompleteGpuInfoAvailable();
+}
+
+void GpuDataManagerImpl::RequestVideoMemoryUsageStatsUpdate() const {
+ base::AutoLock auto_lock(lock_);
+ private_->RequestVideoMemoryUsageStatsUpdate();
+}
+
+bool GpuDataManagerImpl::ShouldUseSwiftShader() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->ShouldUseSwiftShader();
+}
+
+void GpuDataManagerImpl::RegisterSwiftShaderPath(
+ const base::FilePath& path) {
+ base::AutoLock auto_lock(lock_);
+ private_->RegisterSwiftShaderPath(path);
+}
+
+void GpuDataManagerImpl::AddObserver(
+ GpuDataManagerObserver* observer) {
+ base::AutoLock auto_lock(lock_);
+ private_->AddObserver(observer);
+}
+
+void GpuDataManagerImpl::RemoveObserver(
+ GpuDataManagerObserver* observer) {
+ base::AutoLock auto_lock(lock_);
+ private_->RemoveObserver(observer);
+}
+
+void GpuDataManagerImpl::UnblockDomainFrom3DAPIs(const GURL& url) {
+ base::AutoLock auto_lock(lock_);
+ private_->UnblockDomainFrom3DAPIs(url);
+}
+
+void GpuDataManagerImpl::DisableGpuWatchdog() {
+ base::AutoLock auto_lock(lock_);
+ private_->DisableGpuWatchdog();
+}
+
+void GpuDataManagerImpl::SetGLStrings(const std::string& gl_vendor,
+ const std::string& gl_renderer,
+ const std::string& gl_version) {
+ base::AutoLock auto_lock(lock_);
+ private_->SetGLStrings(gl_vendor, gl_renderer, gl_version);
+}
+
+void GpuDataManagerImpl::GetGLStrings(std::string* gl_vendor,
+ std::string* gl_renderer,
+ std::string* gl_version) {
+ base::AutoLock auto_lock(lock_);
+ private_->GetGLStrings(gl_vendor, gl_renderer, gl_version);
+}
+
+void GpuDataManagerImpl::DisableHardwareAcceleration() {
+ base::AutoLock auto_lock(lock_);
+ private_->DisableHardwareAcceleration();
+}
+
+void GpuDataManagerImpl::Initialize() {
+ base::AutoLock auto_lock(lock_);
+ private_->Initialize();
+}
+
+void GpuDataManagerImpl::UpdateGpuInfo(const gpu::GPUInfo& gpu_info) {
+ base::AutoLock auto_lock(lock_);
+ private_->UpdateGpuInfo(gpu_info);
+}
+
+void GpuDataManagerImpl::UpdateVideoMemoryUsageStats(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats) {
+ base::AutoLock auto_lock(lock_);
+ private_->UpdateVideoMemoryUsageStats(video_memory_usage_stats);
+}
+
+void GpuDataManagerImpl::AppendRendererCommandLine(
+ CommandLine* command_line) const {
+ base::AutoLock auto_lock(lock_);
+ private_->AppendRendererCommandLine(command_line);
+}
+
+void GpuDataManagerImpl::AppendGpuCommandLine(
+ CommandLine* command_line) const {
+ base::AutoLock auto_lock(lock_);
+ private_->AppendGpuCommandLine(command_line);
+}
+
+void GpuDataManagerImpl::AppendPluginCommandLine(
+ CommandLine* command_line) const {
+ base::AutoLock auto_lock(lock_);
+ private_->AppendPluginCommandLine(command_line);
+}
+
+void GpuDataManagerImpl::UpdateRendererWebPrefs(
+ WebPreferences* prefs) const {
+ base::AutoLock auto_lock(lock_);
+ private_->UpdateRendererWebPrefs(prefs);
+}
+
+gpu::GpuSwitchingOption GpuDataManagerImpl::GetGpuSwitchingOption() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetGpuSwitchingOption();
+}
+
+std::string GpuDataManagerImpl::GetBlacklistVersion() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetBlacklistVersion();
+}
+
+std::string GpuDataManagerImpl::GetDriverBugListVersion() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetDriverBugListVersion();
+}
+
+void GpuDataManagerImpl::GetBlacklistReasons(base::ListValue* reasons) const {
+ base::AutoLock auto_lock(lock_);
+ private_->GetBlacklistReasons(reasons);
+}
+
+void GpuDataManagerImpl::GetDriverBugWorkarounds(
+ base::ListValue* workarounds) const {
+ base::AutoLock auto_lock(lock_);
+ private_->GetDriverBugWorkarounds(workarounds);
+}
+
+void GpuDataManagerImpl::AddLogMessage(int level,
+ const std::string& header,
+ const std::string& message) {
+ base::AutoLock auto_lock(lock_);
+ private_->AddLogMessage(level, header, message);
+}
+
+void GpuDataManagerImpl::ProcessCrashed(
+ base::TerminationStatus exit_code) {
+ base::AutoLock auto_lock(lock_);
+ private_->ProcessCrashed(exit_code);
+}
+
+base::ListValue* GpuDataManagerImpl::GetLogMessages() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetLogMessages();
+}
+
+void GpuDataManagerImpl::HandleGpuSwitch() {
+ base::AutoLock auto_lock(lock_);
+ private_->HandleGpuSwitch();
+}
+
+#if defined(OS_WIN)
+bool GpuDataManagerImpl::IsUsingAcceleratedSurface() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->IsUsingAcceleratedSurface();
+}
+#endif
+
+bool GpuDataManagerImpl::CanUseGpuBrowserCompositor() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->CanUseGpuBrowserCompositor();
+}
+
+void GpuDataManagerImpl::BlockDomainFrom3DAPIs(
+ const GURL& url, DomainGuilt guilt) {
+ base::AutoLock auto_lock(lock_);
+ private_->BlockDomainFrom3DAPIs(url, guilt);
+}
+
+bool GpuDataManagerImpl::Are3DAPIsBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester) {
+ base::AutoLock auto_lock(lock_);
+ return private_->Are3DAPIsBlocked(
+ url, render_process_id, render_view_id, requester);
+}
+
+void GpuDataManagerImpl::DisableDomainBlockingFor3DAPIsForTesting() {
+ base::AutoLock auto_lock(lock_);
+ private_->DisableDomainBlockingFor3DAPIsForTesting();
+}
+
+size_t GpuDataManagerImpl::GetBlacklistedFeatureCount() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetBlacklistedFeatureCount();
+}
+
+void GpuDataManagerImpl::SetDisplayCount(unsigned int display_count) {
+ base::AutoLock auto_lock(lock_);
+ private_->SetDisplayCount(display_count);
+}
+
+unsigned int GpuDataManagerImpl::GetDisplayCount() const {
+ base::AutoLock auto_lock(lock_);
+ return private_->GetDisplayCount();
+}
+
+void GpuDataManagerImpl::Notify3DAPIBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester) {
+ base::AutoLock auto_lock(lock_);
+ private_->Notify3DAPIBlocked(
+ url, render_process_id, render_view_id, requester);
+}
+
+void GpuDataManagerImpl::OnGpuProcessInitFailure() {
+ base::AutoLock auto_lock(lock_);
+ private_->OnGpuProcessInitFailure();
+}
+
+GpuDataManagerImpl::GpuDataManagerImpl()
+ : private_(GpuDataManagerImplPrivate::Create(this)) {
+}
+
+GpuDataManagerImpl::~GpuDataManagerImpl() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_data_manager_impl.h b/chromium/content/browser/gpu/gpu_data_manager_impl.h
new file mode 100644
index 00000000000..497d3bdf40b
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_data_manager_impl.h
@@ -0,0 +1,220 @@
+// Copyright (c) 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 CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_H_
+#define CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/process/kill.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/common/gpu_memory_stats.h"
+#include "content/public/common/three_d_api_types.h"
+#include "gpu/config/gpu_info.h"
+#include "gpu/config/gpu_switching_option.h"
+
+class CommandLine;
+class GURL;
+struct WebPreferences;
+
+namespace content {
+
+class GpuDataManagerImplPrivate;
+
+class CONTENT_EXPORT GpuDataManagerImpl
+ : public NON_EXPORTED_BASE(GpuDataManager) {
+ public:
+ // Indicates the guilt level of a domain which caused a GPU reset.
+ // If a domain is 100% known to be guilty of resetting the GPU, then
+ // it will generally not cause other domains' use of 3D APIs to be
+ // blocked, unless system stability would be compromised.
+ enum DomainGuilt {
+ DOMAIN_GUILT_KNOWN,
+ DOMAIN_GUILT_UNKNOWN
+ };
+
+ // Indicates the reason that access to a given client API (like
+ // WebGL or Pepper 3D) was blocked or not. This state is distinct
+ // from blacklisting of an entire feature.
+ enum DomainBlockStatus {
+ DOMAIN_BLOCK_STATUS_BLOCKED,
+ DOMAIN_BLOCK_STATUS_ALL_DOMAINS_BLOCKED,
+ DOMAIN_BLOCK_STATUS_NOT_BLOCKED
+ };
+
+ // Getter for the singleton. This will return NULL on failure.
+ static GpuDataManagerImpl* GetInstance();
+
+ // GpuDataManager implementation.
+ virtual void InitializeForTesting(
+ const std::string& gpu_blacklist_json,
+ const gpu::GPUInfo& gpu_info) OVERRIDE;
+ virtual bool IsFeatureBlacklisted(int feature) const OVERRIDE;
+ virtual gpu::GPUInfo GetGPUInfo() const OVERRIDE;
+ virtual void GetGpuProcessHandles(
+ const GetGpuProcessHandlesCallback& callback) const OVERRIDE;
+ virtual bool GpuAccessAllowed(std::string* reason) const OVERRIDE;
+ virtual void RequestCompleteGpuInfoIfNeeded() OVERRIDE;
+ virtual bool IsCompleteGpuInfoAvailable() const OVERRIDE;
+ virtual void RequestVideoMemoryUsageStatsUpdate() const OVERRIDE;
+ virtual bool ShouldUseSwiftShader() const OVERRIDE;
+ virtual void RegisterSwiftShaderPath(const base::FilePath& path) OVERRIDE;
+ virtual void AddObserver(GpuDataManagerObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(GpuDataManagerObserver* observer) OVERRIDE;
+ virtual void UnblockDomainFrom3DAPIs(const GURL& url) OVERRIDE;
+ virtual void DisableGpuWatchdog() OVERRIDE;
+ virtual void SetGLStrings(const std::string& gl_vendor,
+ const std::string& gl_renderer,
+ const std::string& gl_version) OVERRIDE;
+ virtual void GetGLStrings(std::string* gl_vendor,
+ std::string* gl_renderer,
+ std::string* gl_version) OVERRIDE;
+ virtual void DisableHardwareAcceleration() OVERRIDE;
+
+ // This collects preliminary GPU info, load GpuBlacklist, and compute the
+ // preliminary blacklisted features; it should only be called at browser
+ // startup time in UI thread before the IO restriction is turned on.
+ void Initialize();
+
+ // Only update if the current GPUInfo is not finalized. If blacklist is
+ // loaded, run through blacklist and update blacklisted features.
+ void UpdateGpuInfo(const gpu::GPUInfo& gpu_info);
+
+ void UpdateVideoMemoryUsageStats(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats);
+
+ // Insert disable-feature switches corresponding to preliminary gpu feature
+ // flags into the renderer process command line.
+ void AppendRendererCommandLine(CommandLine* command_line) const;
+
+ // Insert switches into gpu process command line: kUseGL,
+ // kDisableGLMultisampling.
+ void AppendGpuCommandLine(CommandLine* command_line) const;
+
+ // Insert switches into plugin process command line:
+ // kDisableCoreAnimationPlugins.
+ void AppendPluginCommandLine(CommandLine* command_line) const;
+
+ // Update WebPreferences for renderer based on blacklisting decisions.
+ void UpdateRendererWebPrefs(WebPreferences* prefs) const;
+
+ gpu::GpuSwitchingOption GetGpuSwitchingOption() const;
+
+ std::string GetBlacklistVersion() const;
+ std::string GetDriverBugListVersion() const;
+
+ // Returns the reasons for the latest run of blacklisting decisions.
+ // For the structure of returned value, see documentation for
+ // GpuBlacklist::GetBlacklistedReasons().
+ void GetBlacklistReasons(base::ListValue* reasons) const;
+
+ // Returns the workarounds that are applied to the current system as
+ // a list of strings.
+ void GetDriverBugWorkarounds(base::ListValue* workarounds) const;
+
+ void AddLogMessage(int level,
+ const std::string& header,
+ const std::string& message);
+
+ void ProcessCrashed(base::TerminationStatus exit_code);
+
+ // Returns a new copy of the ListValue. Caller is responsible to release
+ // the returned value.
+ base::ListValue* GetLogMessages() const;
+
+ // Called when switching gpu.
+ void HandleGpuSwitch();
+
+#if defined(OS_WIN)
+ // Is the GPU process using the accelerated surface to present, instead of
+ // presenting by itself.
+ bool IsUsingAcceleratedSurface() const;
+#endif
+
+ bool CanUseGpuBrowserCompositor() const;
+
+ // Maintenance of domains requiring explicit user permission before
+ // using client-facing 3D APIs (WebGL, Pepper 3D), either because
+ // the domain has caused the GPU to reset, or because too many GPU
+ // resets have been observed globally recently, and system stability
+ // might be compromised.
+ //
+ // The given URL may be a partial URL (including at least the host)
+ // or a full URL to a page.
+ //
+ // Note that the unblocking API must be part of the content API
+ // because it is called from Chrome side code.
+ void BlockDomainFrom3DAPIs(const GURL& url, DomainGuilt guilt);
+ bool Are3DAPIsBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester);
+
+ // Disables domain blocking for 3D APIs. For use only in tests.
+ void DisableDomainBlockingFor3DAPIsForTesting();
+
+ void Notify3DAPIBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester);
+
+ // Get number of features being blacklisted.
+ size_t GetBlacklistedFeatureCount() const;
+
+ void SetDisplayCount(unsigned int display_count);
+ unsigned int GetDisplayCount() const;
+
+ // Called when GPU process initialization failed.
+ void OnGpuProcessInitFailure();
+
+ private:
+ friend class GpuDataManagerImplPrivate;
+ friend class GpuDataManagerImplPrivateTest;
+ friend struct DefaultSingletonTraits<GpuDataManagerImpl>;
+
+ // It's similar to AutoUnlock, but we want to make it a no-op
+ // if the owner GpuDataManagerImpl is null.
+ // This should only be used by GpuDataManagerImplPrivate where
+ // callbacks are called, during which re-entering
+ // GpuDataManagerImpl is possible.
+ class UnlockedSession {
+ public:
+ explicit UnlockedSession(GpuDataManagerImpl* owner)
+ : owner_(owner) {
+ DCHECK(owner_);
+ owner_->lock_.AssertAcquired();
+ owner_->lock_.Release();
+ }
+
+ ~UnlockedSession() {
+ DCHECK(owner_);
+ owner_->lock_.Acquire();
+ }
+
+ private:
+ GpuDataManagerImpl* owner_;
+ DISALLOW_COPY_AND_ASSIGN(UnlockedSession);
+ };
+
+ GpuDataManagerImpl();
+ virtual ~GpuDataManagerImpl();
+
+ mutable base::Lock lock_;
+ scoped_ptr<GpuDataManagerImplPrivate> private_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuDataManagerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_H_
diff --git a/chromium/content/browser/gpu/gpu_data_manager_impl_private.cc b/chromium/content/browser/gpu/gpu_data_manager_impl_private.cc
new file mode 100644
index 00000000000..8491508667d
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_data_manager_impl_private.cc
@@ -0,0 +1,1254 @@
+// Copyright (c) 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 "content/browser/gpu/gpu_data_manager_impl_private.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/version.h"
+#include "cc/base/switches.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "gpu/config/gpu_control_list_jsons.h"
+#include "gpu/config/gpu_driver_bug_workaround_type.h"
+#include "gpu/config/gpu_feature_type.h"
+#include "gpu/config/gpu_info_collector.h"
+#include "gpu/config/gpu_util.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gl/gl_implementation.h"
+#include "ui/gl/gl_switches.h"
+#include "ui/gl/gpu_switching_manager.h"
+#include "webkit/common/webpreferences.h"
+
+#if defined(OS_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif // OS_MACOSX
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif // OS_WIN
+#if defined(OS_ANDROID)
+#include "ui/gfx/android/device_display_info.h"
+#endif // OS_ANDROID
+
+namespace content {
+
+namespace {
+
+enum GpuFeatureStatus {
+ kGpuFeatureEnabled = 0,
+ kGpuFeatureBlacklisted = 1,
+ kGpuFeatureDisabled = 2, // disabled by user but not blacklisted
+ kGpuFeatureNumStatus
+};
+
+#if defined(OS_WIN)
+
+enum WinSubVersion {
+ kWinOthers = 0,
+ kWinXP,
+ kWinVista,
+ kWin7,
+ kWin8,
+ kNumWinSubVersions
+};
+
+int GetGpuBlacklistHistogramValueWin(GpuFeatureStatus status) {
+ static WinSubVersion sub_version = kNumWinSubVersions;
+ if (sub_version == kNumWinSubVersions) {
+ sub_version = kWinOthers;
+ std::string version_str = base::SysInfo::OperatingSystemVersion();
+ size_t pos = version_str.find_first_not_of("0123456789.");
+ if (pos != std::string::npos)
+ version_str = version_str.substr(0, pos);
+ Version os_version(version_str);
+ if (os_version.IsValid() && os_version.components().size() >= 2) {
+ const std::vector<uint16>& version_numbers = os_version.components();
+ if (version_numbers[0] == 5)
+ sub_version = kWinXP;
+ else if (version_numbers[0] == 6 && version_numbers[1] == 0)
+ sub_version = kWinVista;
+ else if (version_numbers[0] == 6 && version_numbers[1] == 1)
+ sub_version = kWin7;
+ else if (version_numbers[0] == 6 && version_numbers[1] == 2)
+ sub_version = kWin8;
+ }
+ }
+ int entry_index = static_cast<int>(sub_version) * kGpuFeatureNumStatus;
+ switch (status) {
+ case kGpuFeatureEnabled:
+ break;
+ case kGpuFeatureBlacklisted:
+ entry_index++;
+ break;
+ case kGpuFeatureDisabled:
+ entry_index += 2;
+ break;
+ }
+ return entry_index;
+}
+#endif // OS_WIN
+
+// Send UMA histograms about the enabled features and GPU properties.
+void UpdateStats(const gpu::GPUInfo& gpu_info,
+ const gpu::GpuBlacklist* blacklist,
+ const std::set<int>& blacklisted_features) {
+ uint32 max_entry_id = blacklist->max_entry_id();
+ if (max_entry_id == 0) {
+ // GPU Blacklist was not loaded. No need to go further.
+ return;
+ }
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ bool disabled = false;
+
+ // Use entry 0 to capture the total number of times that data
+ // was recorded in this histogram in order to have a convenient
+ // denominator to compute blacklist percentages for the rest of the
+ // entries.
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlacklistTestResultsPerEntry",
+ 0, max_entry_id + 1);
+
+ if (blacklisted_features.size() != 0) {
+ std::vector<uint32> flag_entries;
+ blacklist->GetDecisionEntries(&flag_entries, disabled);
+ DCHECK_GT(flag_entries.size(), 0u);
+ for (size_t i = 0; i < flag_entries.size(); ++i) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlacklistTestResultsPerEntry",
+ flag_entries[i], max_entry_id + 1);
+ }
+ }
+
+ // This counts how many users are affected by a disabled entry - this allows
+ // us to understand the impact of an entry before enable it.
+ std::vector<uint32> flag_disabled_entries;
+ disabled = true;
+ blacklist->GetDecisionEntries(&flag_disabled_entries, disabled);
+ for (size_t i = 0; i < flag_disabled_entries.size(); ++i) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlacklistTestResultsPerDisabledEntry",
+ flag_disabled_entries[i], max_entry_id + 1);
+ }
+
+ const gpu::GpuFeatureType kGpuFeatures[] = {
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS,
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING,
+ gpu::GPU_FEATURE_TYPE_WEBGL,
+ gpu::GPU_FEATURE_TYPE_TEXTURE_SHARING
+ };
+ const std::string kGpuBlacklistFeatureHistogramNames[] = {
+ "GPU.BlacklistFeatureTestResults.Accelerated2dCanvas",
+ "GPU.BlacklistFeatureTestResults.AcceleratedCompositing",
+ "GPU.BlacklistFeatureTestResults.Webgl",
+ "GPU.BlacklistFeatureTestResults.TextureSharing"
+ };
+ const bool kGpuFeatureUserFlags[] = {
+ command_line.HasSwitch(switches::kDisableAccelerated2dCanvas),
+ command_line.HasSwitch(switches::kDisableAcceleratedCompositing),
+ command_line.HasSwitch(switches::kDisableExperimentalWebGL),
+ command_line.HasSwitch(switches::kDisableImageTransportSurface)
+ };
+#if defined(OS_WIN)
+ const std::string kGpuBlacklistFeatureHistogramNamesWin[] = {
+ "GPU.BlacklistFeatureTestResultsWindows.Accelerated2dCanvas",
+ "GPU.BlacklistFeatureTestResultsWindows.AcceleratedCompositing",
+ "GPU.BlacklistFeatureTestResultsWindows.Webgl",
+ "GPU.BlacklistFeatureTestResultsWindows.TextureSharing"
+ };
+#endif
+ const size_t kNumFeatures =
+ sizeof(kGpuFeatures) / sizeof(gpu::GpuFeatureType);
+ for (size_t i = 0; i < kNumFeatures; ++i) {
+ // We can't use UMA_HISTOGRAM_ENUMERATION here because the same name is
+ // expected if the macro is used within a loop.
+ GpuFeatureStatus value = kGpuFeatureEnabled;
+ if (blacklisted_features.count(kGpuFeatures[i]))
+ value = kGpuFeatureBlacklisted;
+ else if (kGpuFeatureUserFlags[i])
+ value = kGpuFeatureDisabled;
+ base::HistogramBase* histogram_pointer = base::LinearHistogram::FactoryGet(
+ kGpuBlacklistFeatureHistogramNames[i],
+ 1, kGpuFeatureNumStatus, kGpuFeatureNumStatus + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ histogram_pointer->Add(value);
+#if defined(OS_WIN)
+ histogram_pointer = base::LinearHistogram::FactoryGet(
+ kGpuBlacklistFeatureHistogramNamesWin[i],
+ 1, kNumWinSubVersions * kGpuFeatureNumStatus,
+ kNumWinSubVersions * kGpuFeatureNumStatus + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ histogram_pointer->Add(GetGpuBlacklistHistogramValueWin(value));
+#endif
+ }
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("GPU.GLResetNotificationStrategy",
+ gpu_info.gl_reset_notification_strategy);
+}
+
+// Strip out the non-digital info; if after that, we get an empty string,
+// return "0".
+std::string ProcessVersionString(const std::string& raw_string) {
+ const std::string valid_set = "0123456789.";
+ size_t start_pos = raw_string.find_first_of(valid_set);
+ if (start_pos == std::string::npos)
+ return "0";
+ size_t end_pos = raw_string.find_first_not_of(raw_string, start_pos);
+ std::string version_string = raw_string.substr(
+ start_pos, end_pos - start_pos);
+ if (version_string.empty())
+ return "0";
+ return version_string;
+}
+
+// Combine the integers into a string, seperated by ','.
+std::string IntSetToString(const std::set<int>& list) {
+ std::string rt;
+ for (std::set<int>::const_iterator it = list.begin();
+ it != list.end(); ++it) {
+ if (!rt.empty())
+ rt += ",";
+ rt += base::IntToString(*it);
+ }
+ return rt;
+}
+
+#if defined(OS_MACOSX)
+void DisplayReconfigCallback(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void* gpu_data_manager) {
+ if(flags == kCGDisplayBeginConfigurationFlag)
+ return; // This call contains no information about the display change
+
+ GpuDataManagerImpl* manager =
+ reinterpret_cast<GpuDataManagerImpl*>(gpu_data_manager);
+ DCHECK(manager);
+
+ uint32_t displayCount;
+ CGGetActiveDisplayList(0, NULL, &displayCount);
+
+ bool fireGpuSwitch = flags & kCGDisplayAddFlag;
+
+ if (displayCount != manager->GetDisplayCount()) {
+ manager->SetDisplayCount(displayCount);
+ fireGpuSwitch = true;
+ }
+
+ if (fireGpuSwitch)
+ manager->HandleGpuSwitch();
+}
+#endif // OS_MACOSX
+
+#if defined(OS_ANDROID)
+void ApplyAndroidWorkarounds(const gpu::GPUInfo& gpu_info,
+ CommandLine* command_line) {
+ std::string vendor(StringToLowerASCII(gpu_info.gl_vendor));
+ std::string renderer(StringToLowerASCII(gpu_info.gl_renderer));
+ bool is_img =
+ gpu_info.gl_vendor.find("Imagination") != std::string::npos;
+ bool is_arm =
+ gpu_info.gl_vendor.find("ARM") != std::string::npos;
+ bool is_qualcomm =
+ gpu_info.gl_vendor.find("Qualcomm") != std::string::npos;
+ bool is_broadcom =
+ gpu_info.gl_vendor.find("Broadcom") != std::string::npos;
+ bool is_mali_t604 = is_arm &&
+ gpu_info.gl_renderer.find("Mali-T604") != std::string::npos;
+ bool is_nvidia =
+ gpu_info.gl_vendor.find("NVIDIA") != std::string::npos;
+
+ bool is_vivante =
+ gpu_info.gl_extensions.find("GL_VIV_shader_binary") !=
+ std::string::npos;
+
+ bool is_nexus7 =
+ gpu_info.machine_model.find("Nexus 7") != std::string::npos;
+ bool is_nexus10 =
+ gpu_info.machine_model.find("Nexus 10") != std::string::npos;
+
+ // IMG: avoid context switching perf problems, crashes with share groups
+ // Mali-T604: http://crbug.com/154715
+ // QualComm, NVIDIA: Crashes with share groups
+ if (is_vivante || is_img || is_mali_t604 || is_nvidia || is_qualcomm ||
+ is_broadcom)
+ command_line->AppendSwitch(switches::kEnableVirtualGLContexts);
+
+ gfx::DeviceDisplayInfo info;
+ int default_tile_size = 256;
+
+ // For very high resolution displays (eg. Nexus 10), set the default
+ // tile size to be 512. This should be removed in favour of a generic
+ // hueristic that works across all platforms and devices, once that
+ // exists: http://crbug.com/159524. This switches to 512 for screens
+ // containing 40 or more 256x256 tiles, such that 1080p devices do
+ // not use 512x512 tiles (eg. 1920x1280 requires 37.5 tiles)
+ int numTiles = (info.GetDisplayWidth() *
+ info.GetDisplayHeight()) / (256 * 256);
+ if (numTiles >= 40)
+ default_tile_size = 512;
+
+ // IMG: Fast async texture uploads only work with non-power-of-two,
+ // but still multiple-of-eight sizes.
+ // http://crbug.com/168099
+ if (is_img)
+ default_tile_size -= 8;
+
+ // If we are using the MapImage API double the tile size to reduce
+ // the number of zero-copy buffers being used.
+ if (command_line->HasSwitch(cc::switches::kUseMapImage))
+ default_tile_size *= 2;
+
+ // Set the command line if it isn't already set and we changed
+ // the default tile size.
+ if (default_tile_size != 256 &&
+ !command_line->HasSwitch(switches::kDefaultTileWidth) &&
+ !command_line->HasSwitch(switches::kDefaultTileHeight)) {
+ std::stringstream size;
+ size << default_tile_size;
+ command_line->AppendSwitchASCII(
+ switches::kDefaultTileWidth, size.str());
+ command_line->AppendSwitchASCII(
+ switches::kDefaultTileHeight, size.str());
+ }
+
+ // Increase the resolution of low resolution tiles for Nexus tablets.
+ if ((is_nexus7 || is_nexus10) &&
+ !command_line->HasSwitch(
+ cc::switches::kLowResolutionContentsScaleFactor)) {
+ command_line->AppendSwitchASCII(
+ cc::switches::kLowResolutionContentsScaleFactor, "0.25");
+ }
+}
+#endif // OS_ANDROID
+
+// Block all domains' use of 3D APIs for this many milliseconds if
+// approaching a threshold where system stability might be compromised.
+const int64 kBlockAllDomainsMs = 10000;
+const int kNumResetsWithinDuration = 1;
+
+// Enums for UMA histograms.
+enum BlockStatusHistogram {
+ BLOCK_STATUS_NOT_BLOCKED,
+ BLOCK_STATUS_SPECIFIC_DOMAIN_BLOCKED,
+ BLOCK_STATUS_ALL_DOMAINS_BLOCKED,
+ BLOCK_STATUS_MAX
+};
+
+} // namespace anonymous
+
+void GpuDataManagerImplPrivate::InitializeForTesting(
+ const std::string& gpu_blacklist_json,
+ const gpu::GPUInfo& gpu_info) {
+ // This function is for testing only, so disable histograms.
+ update_histograms_ = false;
+
+ InitializeImpl(gpu_blacklist_json, std::string(), std::string(), gpu_info);
+}
+
+bool GpuDataManagerImplPrivate::IsFeatureBlacklisted(int feature) const {
+ if (use_swiftshader_) {
+ // Skia's software rendering is probably more efficient than going through
+ // software emulation of the GPU, so use that.
+ if (feature == gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS)
+ return true;
+ return false;
+ }
+
+ return (blacklisted_features_.count(feature) == 1);
+}
+
+size_t GpuDataManagerImplPrivate::GetBlacklistedFeatureCount() const {
+ if (use_swiftshader_)
+ return 1;
+ return blacklisted_features_.size();
+}
+
+void GpuDataManagerImplPrivate::SetDisplayCount(unsigned int display_count) {
+ display_count_ = display_count;
+}
+
+unsigned int GpuDataManagerImplPrivate::GetDisplayCount() const {
+ return display_count_;
+}
+
+gpu::GPUInfo GpuDataManagerImplPrivate::GetGPUInfo() const {
+ return gpu_info_;
+}
+
+void GpuDataManagerImplPrivate::GetGpuProcessHandles(
+ const GpuDataManager::GetGpuProcessHandlesCallback& callback) const {
+ GpuProcessHost::GetProcessHandles(callback);
+}
+
+bool GpuDataManagerImplPrivate::GpuAccessAllowed(
+ std::string* reason) const {
+ if (use_swiftshader_)
+ return true;
+
+ if (!gpu_process_accessible_) {
+ if (reason) {
+ *reason = "GPU process launch failed.";
+ }
+ return false;
+ }
+
+ if (card_blacklisted_) {
+ if (reason) {
+ *reason = "GPU access is disabled ";
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kDisableGpu))
+ *reason += "through commandline switch --disable-gpu.";
+ else
+ *reason += "in chrome://settings.";
+ }
+ return false;
+ }
+
+ // We only need to block GPU process if more features are disallowed other
+ // than those in the preliminary gpu feature flags because the latter work
+ // through renderer commandline switches.
+ std::set<int> features = preliminary_blacklisted_features_;
+ gpu::MergeFeatureSets(&features, blacklisted_features_);
+ if (features.size() > preliminary_blacklisted_features_.size()) {
+ if (reason) {
+ *reason = "Features are disabled upon full but not preliminary GPU info.";
+ }
+ return false;
+ }
+
+ if (blacklisted_features_.size() == gpu::NUMBER_OF_GPU_FEATURE_TYPES) {
+ // On Linux, we use cached GL strings to make blacklist decsions at browser
+ // startup time. We need to launch the GPU process to validate these
+ // strings even if all features are blacklisted. If all GPU features are
+ // disabled, the GPU process will only initialize GL bindings, create a GL
+ // context, and collect full GPU info.
+#if !defined(OS_LINUX)
+ if (reason) {
+ *reason = "All GPU features are blacklisted.";
+ }
+ return false;
+#endif
+ }
+
+ return true;
+}
+
+void GpuDataManagerImplPrivate::RequestCompleteGpuInfoIfNeeded() {
+ if (complete_gpu_info_already_requested_ || gpu_info_.finalized)
+ return;
+ complete_gpu_info_already_requested_ = true;
+
+ GpuProcessHost::SendOnIO(
+#if defined(OS_WIN)
+ GpuProcessHost::GPU_PROCESS_KIND_UNSANDBOXED,
+#else
+ GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+#endif
+ CAUSE_FOR_GPU_LAUNCH_GPUDATAMANAGER_REQUESTCOMPLETEGPUINFOIFNEEDED,
+ new GpuMsg_CollectGraphicsInfo());
+}
+
+bool GpuDataManagerImplPrivate::IsCompleteGpuInfoAvailable() const {
+ return gpu_info_.finalized;
+}
+
+void GpuDataManagerImplPrivate::RequestVideoMemoryUsageStatsUpdate() const {
+ GpuProcessHost::SendOnIO(
+ GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH,
+ new GpuMsg_GetVideoMemoryUsageStats());
+}
+
+bool GpuDataManagerImplPrivate::ShouldUseSwiftShader() const {
+ return use_swiftshader_;
+}
+
+void GpuDataManagerImplPrivate::RegisterSwiftShaderPath(
+ const base::FilePath& path) {
+ swiftshader_path_ = path;
+ EnableSwiftShaderIfNecessary();
+}
+
+void GpuDataManagerImplPrivate::AddObserver(GpuDataManagerObserver* observer) {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->AddObserver(observer);
+}
+
+void GpuDataManagerImplPrivate::RemoveObserver(
+ GpuDataManagerObserver* observer) {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->RemoveObserver(observer);
+}
+
+void GpuDataManagerImplPrivate::UnblockDomainFrom3DAPIs(const GURL& url) {
+ // This method must do two things:
+ //
+ // 1. If the specific domain is blocked, then unblock it.
+ //
+ // 2. Reset our notion of how many GPU resets have occurred recently.
+ // This is necessary even if the specific domain was blocked.
+ // Otherwise, if we call Are3DAPIsBlocked with the same domain right
+ // after unblocking it, it will probably still be blocked because of
+ // the recent GPU reset caused by that domain.
+ //
+ // These policies could be refined, but at a certain point the behavior
+ // will become difficult to explain.
+ std::string domain = GetDomainFromURL(url);
+
+ blocked_domains_.erase(domain);
+ timestamps_of_gpu_resets_.clear();
+}
+
+void GpuDataManagerImplPrivate::DisableGpuWatchdog() {
+ GpuProcessHost::SendOnIO(
+ GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH,
+ new GpuMsg_DisableWatchdog);
+}
+
+void GpuDataManagerImplPrivate::SetGLStrings(const std::string& gl_vendor,
+ const std::string& gl_renderer,
+ const std::string& gl_version) {
+ if (gl_vendor.empty() && gl_renderer.empty() && gl_version.empty())
+ return;
+
+ // If GPUInfo already got GL strings, do nothing. This is for the rare
+ // situation where GPU process collected GL strings before this call.
+ if (!gpu_info_.gl_vendor.empty() ||
+ !gpu_info_.gl_renderer.empty() ||
+ !gpu_info_.gl_version_string.empty())
+ return;
+
+ gpu::GPUInfo gpu_info = gpu_info_;
+
+ gpu_info.gl_vendor = gl_vendor;
+ gpu_info.gl_renderer = gl_renderer;
+ gpu_info.gl_version_string = gl_version;
+
+ gpu::CollectDriverInfoGL(&gpu_info);
+
+ UpdateGpuInfo(gpu_info);
+ UpdateGpuSwitchingManager(gpu_info);
+ UpdatePreliminaryBlacklistedFeatures();
+}
+
+void GpuDataManagerImplPrivate::GetGLStrings(std::string* gl_vendor,
+ std::string* gl_renderer,
+ std::string* gl_version) {
+ DCHECK(gl_vendor && gl_renderer && gl_version);
+
+ *gl_vendor = gpu_info_.gl_vendor;
+ *gl_renderer = gpu_info_.gl_renderer;
+ *gl_version = gpu_info_.gl_version_string;
+}
+
+void GpuDataManagerImplPrivate::Initialize() {
+ TRACE_EVENT0("startup", "GpuDataManagerImpl::Initialize");
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kSkipGpuDataLoading) &&
+ !command_line->HasSwitch(switches::kUseGpuInTests))
+ return;
+
+ gpu::GPUInfo gpu_info;
+ {
+ TRACE_EVENT0("startup",
+ "GpuDataManagerImpl::Initialize:CollectBasicGraphicsInfo");
+ gpu::CollectBasicGraphicsInfo(&gpu_info);
+ }
+#if defined(ARCH_CPU_X86_FAMILY)
+ if (!gpu_info.gpu.vendor_id || !gpu_info.gpu.device_id)
+ gpu_info.finalized = true;
+#endif
+
+ std::string gpu_blacklist_string;
+ std::string gpu_switching_list_string;
+ std::string gpu_driver_bug_list_string;
+ if (!command_line->HasSwitch(switches::kIgnoreGpuBlacklist) &&
+ !command_line->HasSwitch(switches::kUseGpuInTests)) {
+ gpu_blacklist_string = gpu::kSoftwareRenderingListJson;
+ gpu_switching_list_string = gpu::kGpuSwitchingListJson;
+ }
+ if (!command_line->HasSwitch(switches::kDisableGpuDriverBugWorkarounds)) {
+ gpu_driver_bug_list_string = gpu::kGpuDriverBugListJson;
+ }
+ InitializeImpl(gpu_blacklist_string,
+ gpu_switching_list_string,
+ gpu_driver_bug_list_string,
+ gpu_info);
+}
+
+void GpuDataManagerImplPrivate::UpdateGpuInfo(const gpu::GPUInfo& gpu_info) {
+ // No further update of gpu_info if falling back to SwiftShader.
+ if (use_swiftshader_)
+ return;
+
+ gpu::MergeGPUInfo(&gpu_info_, gpu_info);
+ complete_gpu_info_already_requested_ =
+ complete_gpu_info_already_requested_ || gpu_info_.finalized;
+
+ GetContentClient()->SetGpuInfo(gpu_info_);
+
+ if (gpu_blacklist_) {
+ std::set<int> features = gpu_blacklist_->MakeDecision(
+ gpu::GpuControlList::kOsAny, std::string(), gpu_info_);
+ if (update_histograms_)
+ UpdateStats(gpu_info_, gpu_blacklist_.get(), features);
+
+ UpdateBlacklistedFeatures(features);
+ }
+ if (gpu_switching_list_) {
+ std::set<int> option = gpu_switching_list_->MakeDecision(
+ gpu::GpuControlList::kOsAny, std::string(), gpu_info_);
+ if (option.size() == 1) {
+ // Blacklist decision should not overwrite commandline switch from users.
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (!command_line->HasSwitch(switches::kGpuSwitching)) {
+ gpu_switching_ = static_cast<gpu::GpuSwitchingOption>(
+ *(option.begin()));
+ }
+ }
+ }
+ if (gpu_driver_bug_list_) {
+ gpu_driver_bugs_ = gpu_driver_bug_list_->MakeDecision(
+ gpu::GpuControlList::kOsAny, std::string(), gpu_info_);
+ }
+
+ // We have to update GpuFeatureType before notify all the observers.
+ NotifyGpuInfoUpdate();
+}
+
+void GpuDataManagerImplPrivate::UpdateVideoMemoryUsageStats(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats) {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->Notify(&GpuDataManagerObserver::OnVideoMemoryUsageStatsUpdate,
+ video_memory_usage_stats);
+}
+
+void GpuDataManagerImplPrivate::AppendRendererCommandLine(
+ CommandLine* command_line) const {
+ DCHECK(command_line);
+
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL)) {
+ if (!command_line->HasSwitch(switches::kDisableExperimentalWebGL))
+ command_line->AppendSwitch(switches::kDisableExperimentalWebGL);
+ if (!command_line->HasSwitch(switches::kDisablePepper3d))
+ command_line->AppendSwitch(switches::kDisablePepper3d);
+ }
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_MULTISAMPLING) &&
+ !command_line->HasSwitch(switches::kDisableGLMultisampling))
+ command_line->AppendSwitch(switches::kDisableGLMultisampling);
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) &&
+ !command_line->HasSwitch(switches::kDisableAcceleratedCompositing))
+ command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS) &&
+ !command_line->HasSwitch(switches::kDisableAccelerated2dCanvas))
+ command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE) &&
+ !command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode))
+ command_line->AppendSwitch(switches::kDisableAcceleratedVideoDecode);
+
+ if (use_software_compositor_ &&
+ !command_line->HasSwitch(switches::kEnableSoftwareCompositing))
+ command_line->AppendSwitch(switches::kEnableSoftwareCompositing);
+
+#if defined(USE_AURA)
+ if (!CanUseGpuBrowserCompositor()) {
+ command_line->AppendSwitch(switches::kDisableGpuCompositing);
+ command_line->AppendSwitch(switches::kDisablePepper3d);
+ }
+#endif
+}
+
+void GpuDataManagerImplPrivate::AppendGpuCommandLine(
+ CommandLine* command_line) const {
+ DCHECK(command_line);
+
+ bool reduce_sandbox = false;
+
+ std::string use_gl =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switches::kUseGL);
+ base::FilePath swiftshader_path =
+ CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kSwiftShaderPath);
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_MULTISAMPLING) &&
+ !command_line->HasSwitch(switches::kDisableGLMultisampling)) {
+ command_line->AppendSwitch(switches::kDisableGLMultisampling);
+ }
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_TEXTURE_SHARING)) {
+ command_line->AppendSwitch(switches::kDisableImageTransportSurface);
+ reduce_sandbox = true;
+ }
+ if (gpu_driver_bugs_.find(gpu::DISABLE_D3D11) != gpu_driver_bugs_.end())
+ command_line->AppendSwitch(switches::kDisableD3D11);
+ if (use_swiftshader_) {
+ command_line->AppendSwitchASCII(switches::kUseGL, "swiftshader");
+ if (swiftshader_path.empty())
+ swiftshader_path = swiftshader_path_;
+ } else if ((IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL) ||
+ IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) ||
+ IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS)) &&
+ (use_gl == "any")) {
+ command_line->AppendSwitchASCII(
+ switches::kUseGL, gfx::kGLImplementationOSMesaName);
+ } else if (!use_gl.empty()) {
+ command_line->AppendSwitchASCII(switches::kUseGL, use_gl);
+ }
+ if (ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus()) {
+ command_line->AppendSwitchASCII(switches::kSupportsDualGpus, "true");
+ switch (gpu_switching_) {
+ case gpu::GPU_SWITCHING_OPTION_FORCE_DISCRETE:
+ command_line->AppendSwitchASCII(switches::kGpuSwitching,
+ switches::kGpuSwitchingOptionNameForceDiscrete);
+ break;
+ case gpu::GPU_SWITCHING_OPTION_FORCE_INTEGRATED:
+ command_line->AppendSwitchASCII(switches::kGpuSwitching,
+ switches::kGpuSwitchingOptionNameForceIntegrated);
+ break;
+ case gpu::GPU_SWITCHING_OPTION_AUTOMATIC:
+ case gpu::GPU_SWITCHING_OPTION_UNKNOWN:
+ break;
+ }
+ } else {
+ command_line->AppendSwitchASCII(switches::kSupportsDualGpus, "false");
+ }
+
+ if (!swiftshader_path.empty()) {
+ command_line->AppendSwitchPath(switches::kSwiftShaderPath,
+ swiftshader_path);
+ }
+
+ if (!gpu_driver_bugs_.empty()) {
+ command_line->AppendSwitchASCII(switches::kGpuDriverBugWorkarounds,
+ IntSetToString(gpu_driver_bugs_));
+ }
+
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE) &&
+ !command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode)) {
+ command_line->AppendSwitch(switches::kDisableAcceleratedVideoDecode);
+ }
+
+#if defined(OS_WIN)
+ // DisplayLink 7.1 and earlier can cause the GPU process to crash on startup.
+ // http://crbug.com/177611
+ // Thinkpad USB Port Replicator driver causes GPU process to crash when the
+ // sandbox is enabled. http://crbug.com/181665.
+ if ((gpu_info_.display_link_version.IsValid()
+ && gpu_info_.display_link_version.IsOlderThan("7.2")) ||
+ gpu_info_.lenovo_dcute) {
+ reduce_sandbox = true;
+ }
+#endif
+
+ if (gpu_info_.optimus)
+ reduce_sandbox = true;
+
+ if (reduce_sandbox)
+ command_line->AppendSwitch(switches::kReduceGpuSandbox);
+
+ // Pass GPU and driver information to GPU process. We try to avoid full GPU
+ // info collection at GPU process startup, but we need gpu vendor_id,
+ // device_id, driver_vendor, driver_version for deciding whether we need to
+ // collect full info (on Linux) and for crash reporting purpose.
+ command_line->AppendSwitchASCII(switches::kGpuVendorID,
+ base::StringPrintf("0x%04x", gpu_info_.gpu.vendor_id));
+ command_line->AppendSwitchASCII(switches::kGpuDeviceID,
+ base::StringPrintf("0x%04x", gpu_info_.gpu.device_id));
+ command_line->AppendSwitchASCII(switches::kGpuDriverVendor,
+ gpu_info_.driver_vendor);
+ command_line->AppendSwitchASCII(switches::kGpuDriverVersion,
+ gpu_info_.driver_version);
+}
+
+void GpuDataManagerImplPrivate::AppendPluginCommandLine(
+ CommandLine* command_line) const {
+ DCHECK(command_line);
+
+#if defined(OS_MACOSX)
+ // TODO(jbauman): Add proper blacklist support for core animation plugins so
+ // special-casing this video card won't be necessary. See
+ // http://crbug.com/134015
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAcceleratedCompositing)) {
+ if (!command_line->HasSwitch(
+ switches::kDisableCoreAnimationPlugins))
+ command_line->AppendSwitch(
+ switches::kDisableCoreAnimationPlugins);
+ }
+#endif
+}
+
+void GpuDataManagerImplPrivate::UpdateRendererWebPrefs(
+ WebPreferences* prefs) const {
+ DCHECK(prefs);
+
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING))
+ prefs->accelerated_compositing_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL))
+ prefs->experimental_webgl_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH3D))
+ prefs->flash_3d_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D)) {
+ prefs->flash_stage3d_enabled = false;
+ prefs->flash_stage3d_baseline_enabled = false;
+ }
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D_BASELINE))
+ prefs->flash_stage3d_baseline_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS))
+ prefs->accelerated_2d_canvas_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_MULTISAMPLING)
+ || display_count_ > 1)
+ prefs->gl_multisampling_enabled = false;
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_3D_CSS)) {
+ prefs->accelerated_compositing_for_3d_transforms_enabled = false;
+ prefs->accelerated_compositing_for_animation_enabled = false;
+ }
+ if (IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO))
+ prefs->accelerated_compositing_for_video_enabled = false;
+
+ // Accelerated video and animation are slower than regular when using
+ // SwiftShader. 3D CSS may also be too slow to be worthwhile.
+ if (ShouldUseSwiftShader()) {
+ prefs->accelerated_compositing_for_video_enabled = false;
+ prefs->accelerated_compositing_for_animation_enabled = false;
+ prefs->accelerated_compositing_for_3d_transforms_enabled = false;
+ prefs->accelerated_compositing_for_plugins_enabled = false;
+ }
+
+ if (use_software_compositor_) {
+ prefs->force_compositing_mode = true;
+ prefs->accelerated_compositing_enabled = true;
+ prefs->accelerated_compositing_for_3d_transforms_enabled = true;
+ prefs->accelerated_compositing_for_plugins_enabled = true;
+ }
+
+#if defined(USE_AURA)
+ if (!CanUseGpuBrowserCompositor())
+ prefs->accelerated_2d_canvas_enabled = false;
+#endif
+}
+
+gpu::GpuSwitchingOption
+GpuDataManagerImplPrivate::GetGpuSwitchingOption() const {
+ if (!ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus())
+ return gpu::GPU_SWITCHING_OPTION_UNKNOWN;
+ return gpu_switching_;
+}
+
+void GpuDataManagerImplPrivate::DisableHardwareAcceleration() {
+ card_blacklisted_ = true;
+
+ for (int i = 0; i < gpu::NUMBER_OF_GPU_FEATURE_TYPES; ++i)
+ blacklisted_features_.insert(i);
+
+ EnableSwiftShaderIfNecessary();
+ NotifyGpuInfoUpdate();
+}
+
+std::string GpuDataManagerImplPrivate::GetBlacklistVersion() const {
+ if (gpu_blacklist_)
+ return gpu_blacklist_->version();
+ return "0";
+}
+
+std::string GpuDataManagerImplPrivate::GetDriverBugListVersion() const {
+ if (gpu_driver_bug_list_)
+ return gpu_driver_bug_list_->version();
+ return "0";
+}
+
+void GpuDataManagerImplPrivate::GetBlacklistReasons(
+ base::ListValue* reasons) const {
+ if (gpu_blacklist_)
+ gpu_blacklist_->GetReasons(reasons);
+}
+
+void GpuDataManagerImplPrivate::GetDriverBugWorkarounds(
+ base::ListValue* workarounds) const {
+ for (std::set<int>::const_iterator it = gpu_driver_bugs_.begin();
+ it != gpu_driver_bugs_.end(); ++it) {
+ workarounds->AppendString(
+ gpu::GpuDriverBugWorkaroundTypeToString(
+ static_cast<gpu::GpuDriverBugWorkaroundType>(*it)));
+ }
+}
+
+void GpuDataManagerImplPrivate::AddLogMessage(
+ int level, const std::string& header, const std::string& message) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("level", level);
+ dict->SetString("header", header);
+ dict->SetString("message", message);
+ log_messages_.Append(dict);
+}
+
+void GpuDataManagerImplPrivate::ProcessCrashed(
+ base::TerminationStatus exit_code) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ // Unretained is ok, because it's posted to UI thread, the thread
+ // where the singleton GpuDataManagerImpl lives until the end.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&GpuDataManagerImpl::ProcessCrashed,
+ base::Unretained(owner_),
+ exit_code));
+ return;
+ }
+ {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->Notify(
+ &GpuDataManagerObserver::OnGpuProcessCrashed, exit_code);
+ }
+}
+
+base::ListValue* GpuDataManagerImplPrivate::GetLogMessages() const {
+ base::ListValue* value;
+ value = log_messages_.DeepCopy();
+ return value;
+}
+
+void GpuDataManagerImplPrivate::HandleGpuSwitch() {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->Notify(&GpuDataManagerObserver::OnGpuSwitching);
+}
+
+#if defined(OS_WIN)
+bool GpuDataManagerImplPrivate::IsUsingAcceleratedSurface() const {
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return false;
+
+ if (use_swiftshader_)
+ return false;
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kDisableImageTransportSurface))
+ return false;
+ return !IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_TEXTURE_SHARING);
+}
+#endif
+
+bool GpuDataManagerImplPrivate::CanUseGpuBrowserCompositor() const {
+ return !ShouldUseSwiftShader() &&
+ !IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) &&
+ !IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FORCE_COMPOSITING_MODE);
+}
+
+void GpuDataManagerImplPrivate::BlockDomainFrom3DAPIs(
+ const GURL& url, GpuDataManagerImpl::DomainGuilt guilt) {
+ BlockDomainFrom3DAPIsAtTime(url, guilt, base::Time::Now());
+}
+
+bool GpuDataManagerImplPrivate::Are3DAPIsBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester) {
+ bool blocked = Are3DAPIsBlockedAtTime(url, base::Time::Now()) !=
+ GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED;
+ if (blocked) {
+ // Unretained is ok, because it's posted to UI thread, the thread
+ // where the singleton GpuDataManagerImpl lives until the end.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&GpuDataManagerImpl::Notify3DAPIBlocked,
+ base::Unretained(owner_), url, render_process_id,
+ render_view_id, requester));
+ }
+
+ return blocked;
+}
+
+void GpuDataManagerImplPrivate::DisableDomainBlockingFor3DAPIsForTesting() {
+ domain_blocking_enabled_ = false;
+}
+
+// static
+GpuDataManagerImplPrivate* GpuDataManagerImplPrivate::Create(
+ GpuDataManagerImpl* owner) {
+ return new GpuDataManagerImplPrivate(owner);
+}
+
+GpuDataManagerImplPrivate::GpuDataManagerImplPrivate(
+ GpuDataManagerImpl* owner)
+ : complete_gpu_info_already_requested_(false),
+ gpu_switching_(gpu::GPU_SWITCHING_OPTION_AUTOMATIC),
+ observer_list_(new GpuDataManagerObserverList),
+ use_swiftshader_(false),
+ card_blacklisted_(false),
+ update_histograms_(true),
+ window_count_(0),
+ domain_blocking_enabled_(true),
+ owner_(owner),
+ display_count_(0),
+ gpu_process_accessible_(true),
+ use_software_compositor_(false) {
+ DCHECK(owner_);
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kDisableAcceleratedCompositing)) {
+ command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
+ command_line->AppendSwitch(switches::kDisableAcceleratedLayers);
+ }
+ if (command_line->HasSwitch(switches::kDisableGpu))
+ DisableHardwareAcceleration();
+ if (command_line->HasSwitch(switches::kEnableSoftwareCompositing))
+ use_software_compositor_ = true;
+ //TODO(jbauman): enable for Chrome OS and Linux
+#if defined(USE_AURA) && defined(OS_WIN)
+ use_software_compositor_ = true;
+#endif
+ if (command_line->HasSwitch(switches::kGpuSwitching)) {
+ std::string option_string = command_line->GetSwitchValueASCII(
+ switches::kGpuSwitching);
+ gpu::GpuSwitchingOption option =
+ gpu::StringToGpuSwitchingOption(option_string);
+ if (option != gpu::GPU_SWITCHING_OPTION_UNKNOWN)
+ gpu_switching_ = option;
+ }
+
+#if defined(OS_MACOSX)
+ CGGetActiveDisplayList (0, NULL, &display_count_);
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigCallback, owner_);
+#endif // OS_MACOSX
+}
+
+GpuDataManagerImplPrivate::~GpuDataManagerImplPrivate() {
+#if defined(OS_MACOSX)
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigCallback, owner_);
+#endif
+}
+
+void GpuDataManagerImplPrivate::InitializeImpl(
+ const std::string& gpu_blacklist_json,
+ const std::string& gpu_switching_list_json,
+ const std::string& gpu_driver_bug_list_json,
+ const gpu::GPUInfo& gpu_info) {
+ std::string browser_version_string = ProcessVersionString(
+ GetContentClient()->GetProduct());
+ CHECK(!browser_version_string.empty());
+
+ if (!gpu_blacklist_json.empty()) {
+ gpu_blacklist_.reset(gpu::GpuBlacklist::Create());
+ gpu_blacklist_->LoadList(
+ browser_version_string, gpu_blacklist_json,
+ gpu::GpuControlList::kCurrentOsOnly);
+ }
+ if (!gpu_switching_list_json.empty()) {
+ gpu_switching_list_.reset(gpu::GpuSwitchingList::Create());
+ gpu_switching_list_->LoadList(
+ browser_version_string, gpu_switching_list_json,
+ gpu::GpuControlList::kCurrentOsOnly);
+ }
+ if (!gpu_driver_bug_list_json.empty()) {
+ gpu_driver_bug_list_.reset(gpu::GpuDriverBugList::Create());
+ gpu_driver_bug_list_->LoadList(
+ browser_version_string, gpu_driver_bug_list_json,
+ gpu::GpuControlList::kCurrentOsOnly);
+ }
+
+ gpu_info_ = gpu_info;
+ UpdateGpuInfo(gpu_info);
+ UpdateGpuSwitchingManager(gpu_info);
+ UpdatePreliminaryBlacklistedFeatures();
+
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ // We pass down the list to GPU command buffer through commandline
+ // switches at GPU process launch. However, in situations where we don't
+ // have a GPU process, we append the browser process commandline.
+ if (command_line->HasSwitch(switches::kSingleProcess) ||
+ command_line->HasSwitch(switches::kInProcessGPU)) {
+ if (!gpu_driver_bugs_.empty()) {
+ command_line->AppendSwitchASCII(switches::kGpuDriverBugWorkarounds,
+ IntSetToString(gpu_driver_bugs_));
+ }
+ }
+#if defined(OS_ANDROID)
+ ApplyAndroidWorkarounds(gpu_info, command_line);
+#endif // OS_ANDROID
+}
+
+void GpuDataManagerImplPrivate::UpdateBlacklistedFeatures(
+ const std::set<int>& features) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ blacklisted_features_ = features;
+
+ // Force disable using the GPU for these features, even if they would
+ // otherwise be allowed.
+ if (card_blacklisted_ ||
+ command_line->HasSwitch(switches::kBlacklistAcceleratedCompositing)) {
+ blacklisted_features_.insert(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING);
+ }
+ if (card_blacklisted_ ||
+ command_line->HasSwitch(switches::kBlacklistWebGL)) {
+ blacklisted_features_.insert(gpu::GPU_FEATURE_TYPE_WEBGL);
+ }
+
+ EnableSwiftShaderIfNecessary();
+}
+
+void GpuDataManagerImplPrivate::UpdatePreliminaryBlacklistedFeatures() {
+ preliminary_blacklisted_features_ = blacklisted_features_;
+}
+
+void GpuDataManagerImplPrivate::UpdateGpuSwitchingManager(
+ const gpu::GPUInfo& gpu_info) {
+ ui::GpuSwitchingManager::GetInstance()->SetGpuCount(
+ gpu_info.secondary_gpus.size() + 1);
+
+ if (ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus()) {
+ switch (gpu_switching_) {
+ case gpu::GPU_SWITCHING_OPTION_FORCE_DISCRETE:
+ ui::GpuSwitchingManager::GetInstance()->ForceUseOfDiscreteGpu();
+ break;
+ case gpu::GPU_SWITCHING_OPTION_FORCE_INTEGRATED:
+ ui::GpuSwitchingManager::GetInstance()->ForceUseOfIntegratedGpu();
+ break;
+ case gpu::GPU_SWITCHING_OPTION_AUTOMATIC:
+ case gpu::GPU_SWITCHING_OPTION_UNKNOWN:
+ break;
+ }
+ }
+}
+
+void GpuDataManagerImplPrivate::NotifyGpuInfoUpdate() {
+ observer_list_->Notify(&GpuDataManagerObserver::OnGpuInfoUpdate);
+}
+
+void GpuDataManagerImplPrivate::EnableSwiftShaderIfNecessary() {
+ if (!GpuAccessAllowed(NULL) ||
+ blacklisted_features_.count(gpu::GPU_FEATURE_TYPE_WEBGL)) {
+ if (!swiftshader_path_.empty() &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableSoftwareRasterizer))
+ use_swiftshader_ = true;
+ }
+}
+
+std::string GpuDataManagerImplPrivate::GetDomainFromURL(
+ const GURL& url) const {
+ // For the moment, we just use the host, or its IP address, as the
+ // entry in the set, rather than trying to figure out the top-level
+ // domain. This does mean that a.foo.com and b.foo.com will be
+ // treated independently in the blocking of a given domain, but it
+ // would require a third-party library to reliably figure out the
+ // top-level domain from a URL.
+ if (!url.has_host()) {
+ return std::string();
+ }
+
+ return url.host();
+}
+
+void GpuDataManagerImplPrivate::BlockDomainFrom3DAPIsAtTime(
+ const GURL& url,
+ GpuDataManagerImpl::DomainGuilt guilt,
+ base::Time at_time) {
+ if (!domain_blocking_enabled_)
+ return;
+
+ std::string domain = GetDomainFromURL(url);
+
+ DomainBlockEntry& entry = blocked_domains_[domain];
+ entry.last_guilt = guilt;
+ timestamps_of_gpu_resets_.push_back(at_time);
+}
+
+GpuDataManagerImpl::DomainBlockStatus
+GpuDataManagerImplPrivate::Are3DAPIsBlockedAtTime(
+ const GURL& url, base::Time at_time) const {
+ if (!domain_blocking_enabled_)
+ return GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED;
+
+ // Note: adjusting the policies in this code will almost certainly
+ // require adjusting the associated unit tests.
+ std::string domain = GetDomainFromURL(url);
+
+ DomainBlockMap::const_iterator iter = blocked_domains_.find(domain);
+ if (iter != blocked_domains_.end()) {
+ // Err on the side of caution, and assume that if a particular
+ // domain shows up in the block map, it's there for a good
+ // reason and don't let its presence there automatically expire.
+
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlockStatusForClient3DAPIs",
+ BLOCK_STATUS_SPECIFIC_DOMAIN_BLOCKED,
+ BLOCK_STATUS_MAX);
+
+ return GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_BLOCKED;
+ }
+
+ // Look at the timestamps of the recent GPU resets to see if there are
+ // enough within the threshold which would cause us to blacklist all
+ // domains. This doesn't need to be overly precise -- if time goes
+ // backward due to a system clock adjustment, that's fine.
+ //
+ // TODO(kbr): make this pay attention to the TDR thresholds in the
+ // Windows registry, but make sure it continues to be testable.
+ {
+ std::list<base::Time>::iterator iter = timestamps_of_gpu_resets_.begin();
+ int num_resets_within_timeframe = 0;
+ while (iter != timestamps_of_gpu_resets_.end()) {
+ base::Time time = *iter;
+ base::TimeDelta delta_t = at_time - time;
+
+ // If this entry has "expired", just remove it.
+ if (delta_t.InMilliseconds() > kBlockAllDomainsMs) {
+ iter = timestamps_of_gpu_resets_.erase(iter);
+ continue;
+ }
+
+ ++num_resets_within_timeframe;
+ ++iter;
+ }
+
+ if (num_resets_within_timeframe >= kNumResetsWithinDuration) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlockStatusForClient3DAPIs",
+ BLOCK_STATUS_ALL_DOMAINS_BLOCKED,
+ BLOCK_STATUS_MAX);
+
+ return GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_ALL_DOMAINS_BLOCKED;
+ }
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("GPU.BlockStatusForClient3DAPIs",
+ BLOCK_STATUS_NOT_BLOCKED,
+ BLOCK_STATUS_MAX);
+
+ return GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED;
+}
+
+int64 GpuDataManagerImplPrivate::GetBlockAllDomainsDurationInMs() const {
+ return kBlockAllDomainsMs;
+}
+
+void GpuDataManagerImplPrivate::Notify3DAPIBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester) {
+ GpuDataManagerImpl::UnlockedSession session(owner_);
+ observer_list_->Notify(&GpuDataManagerObserver::DidBlock3DAPIs,
+ url, render_process_id, render_view_id, requester);
+}
+
+void GpuDataManagerImplPrivate::OnGpuProcessInitFailure() {
+ gpu_process_accessible_ = false;
+ gpu_info_.finalized = true;
+ complete_gpu_info_already_requested_ = true;
+ // Some observers might be waiting.
+ NotifyGpuInfoUpdate();
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/gpu/gpu_data_manager_impl_private.h b/chromium/content/browser/gpu/gpu_data_manager_impl_private.h
new file mode 100644
index 00000000000..eb226e863dc
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_data_manager_impl_private.h
@@ -0,0 +1,255 @@
+// Copyright (c) 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 CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_PRIVATE_H_
+#define CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_PRIVATE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list_threadsafe.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "gpu/config/gpu_blacklist.h"
+#include "gpu/config/gpu_driver_bug_list.h"
+#include "gpu/config/gpu_switching_list.h"
+
+namespace content {
+
+class CONTENT_EXPORT GpuDataManagerImplPrivate {
+ public:
+ static GpuDataManagerImplPrivate* Create(GpuDataManagerImpl* owner);
+
+ void InitializeForTesting(
+ const std::string& gpu_blacklist_json,
+ const gpu::GPUInfo& gpu_info);
+ bool IsFeatureBlacklisted(int feature) const;
+ gpu::GPUInfo GetGPUInfo() const;
+ void GetGpuProcessHandles(
+ const GpuDataManager::GetGpuProcessHandlesCallback& callback) const;
+ bool GpuAccessAllowed(std::string* reason) const;
+ void RequestCompleteGpuInfoIfNeeded();
+ bool IsCompleteGpuInfoAvailable() const;
+ void RequestVideoMemoryUsageStatsUpdate() const;
+ bool ShouldUseSwiftShader() const;
+ void RegisterSwiftShaderPath(const base::FilePath& path);
+ void AddObserver(GpuDataManagerObserver* observer);
+ void RemoveObserver(GpuDataManagerObserver* observer);
+ void UnblockDomainFrom3DAPIs(const GURL& url);
+ void DisableGpuWatchdog();
+ void SetGLStrings(const std::string& gl_vendor,
+ const std::string& gl_renderer,
+ const std::string& gl_version);
+ void GetGLStrings(std::string* gl_vendor,
+ std::string* gl_renderer,
+ std::string* gl_version);
+ void DisableHardwareAcceleration();
+
+ void Initialize();
+
+ void UpdateGpuInfo(const gpu::GPUInfo& gpu_info);
+
+ void UpdateVideoMemoryUsageStats(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats);
+
+ void AppendRendererCommandLine(CommandLine* command_line) const;
+
+ void AppendGpuCommandLine(CommandLine* command_line) const;
+
+ void AppendPluginCommandLine(CommandLine* command_line) const;
+
+ void UpdateRendererWebPrefs(WebPreferences* prefs) const;
+
+ gpu::GpuSwitchingOption GetGpuSwitchingOption() const;
+
+ std::string GetBlacklistVersion() const;
+ std::string GetDriverBugListVersion() const;
+
+ void GetBlacklistReasons(base::ListValue* reasons) const;
+
+ void GetDriverBugWorkarounds(base::ListValue* workarounds) const;
+
+ void AddLogMessage(int level,
+ const std::string& header,
+ const std::string& message);
+
+ void ProcessCrashed(base::TerminationStatus exit_code);
+
+ base::ListValue* GetLogMessages() const;
+
+ void HandleGpuSwitch();
+
+#if defined(OS_WIN)
+ // Is the GPU process using the accelerated surface to present, instead of
+ // presenting by itself.
+ bool IsUsingAcceleratedSurface() const;
+#endif
+
+ bool CanUseGpuBrowserCompositor() const;
+
+ void BlockDomainFrom3DAPIs(
+ const GURL& url, GpuDataManagerImpl::DomainGuilt guilt);
+ bool Are3DAPIsBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester);
+
+ void DisableDomainBlockingFor3DAPIsForTesting();
+
+ void Notify3DAPIBlocked(const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ ThreeDAPIType requester);
+
+ size_t GetBlacklistedFeatureCount() const;
+
+ void SetDisplayCount(unsigned int display_count);
+ unsigned int GetDisplayCount() const;
+
+ void OnGpuProcessInitFailure();
+
+ virtual ~GpuDataManagerImplPrivate();
+
+ private:
+ friend class GpuDataManagerImplPrivateTest;
+
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GpuSideBlacklisting);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GpuSideExceptions);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ DisableHardwareAcceleration);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ SwiftShaderRendering);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ SwiftShaderRendering2);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GpuInfoUpdate);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ NoGpuInfoUpdateWithSwiftShader);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GPUVideoMemoryUsageStatsUpdate);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ BlockAllDomainsFrom3DAPIs);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ UnblockGuiltyDomainFrom3DAPIs);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ UnblockDomainOfUnknownGuiltFrom3DAPIs);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ UnblockOtherDomainFrom3DAPIs);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ UnblockThisDomainFrom3DAPIs);
+#if defined(OS_LINUX)
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ SetGLStrings);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ SetGLStringsNoEffects);
+#endif
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GpuDriverBugListSingle);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ GpuDriverBugListMultiple);
+ FRIEND_TEST_ALL_PREFIXES(GpuDataManagerImplPrivateTest,
+ BlacklistAllFeatures);
+
+ struct DomainBlockEntry {
+ GpuDataManagerImpl::DomainGuilt last_guilt;
+ };
+
+ typedef std::map<std::string, DomainBlockEntry> DomainBlockMap;
+
+ typedef ObserverListThreadSafe<GpuDataManagerObserver>
+ GpuDataManagerObserverList;
+
+ explicit GpuDataManagerImplPrivate(GpuDataManagerImpl* owner);
+
+ void InitializeImpl(const std::string& gpu_blacklist_json,
+ const std::string& gpu_switching_list_json,
+ const std::string& gpu_driver_bug_list_json,
+ const gpu::GPUInfo& gpu_info);
+
+ void UpdateBlacklistedFeatures(const std::set<int>& features);
+
+ // This should only be called once at initialization time, when preliminary
+ // gpu info is collected.
+ void UpdatePreliminaryBlacklistedFeatures();
+
+ // Update the GPU switching status.
+ // This should only be called once at initialization time.
+ void UpdateGpuSwitchingManager(const gpu::GPUInfo& gpu_info);
+
+ // Notify all observers whenever there is a GPU info update.
+ void NotifyGpuInfoUpdate();
+
+ // Try to switch to SwiftShader rendering, if possible and necessary.
+ void EnableSwiftShaderIfNecessary();
+
+ // Helper to extract the domain from a given URL.
+ std::string GetDomainFromURL(const GURL& url) const;
+
+ // Implementation functions for blocking of 3D graphics APIs, used
+ // for unit testing.
+ void BlockDomainFrom3DAPIsAtTime(const GURL& url,
+ GpuDataManagerImpl::DomainGuilt guilt,
+ base::Time at_time);
+ GpuDataManagerImpl::DomainBlockStatus Are3DAPIsBlockedAtTime(
+ const GURL& url, base::Time at_time) const;
+ int64 GetBlockAllDomainsDurationInMs() const;
+
+ bool complete_gpu_info_already_requested_;
+
+ std::set<int> blacklisted_features_;
+ std::set<int> preliminary_blacklisted_features_;
+
+ gpu::GpuSwitchingOption gpu_switching_;
+
+ std::set<int> gpu_driver_bugs_;
+
+ gpu::GPUInfo gpu_info_;
+
+ scoped_ptr<gpu::GpuBlacklist> gpu_blacklist_;
+ scoped_ptr<gpu::GpuSwitchingList> gpu_switching_list_;
+ scoped_ptr<gpu::GpuDriverBugList> gpu_driver_bug_list_;
+
+ const scoped_refptr<GpuDataManagerObserverList> observer_list_;
+
+ base::ListValue log_messages_;
+
+ bool use_swiftshader_;
+
+ base::FilePath swiftshader_path_;
+
+ // Current card force-blacklisted due to GPU crashes, or disabled through
+ // the --disable-gpu commandline switch.
+ bool card_blacklisted_;
+
+ // We disable histogram stuff in testing, especially in unit tests because
+ // they cause random failures.
+ bool update_histograms_;
+
+ // Number of currently open windows, to be used in gpu memory allocation.
+ int window_count_;
+
+ DomainBlockMap blocked_domains_;
+ mutable std::list<base::Time> timestamps_of_gpu_resets_;
+ bool domain_blocking_enabled_;
+
+ GpuDataManagerImpl* owner_;
+
+ unsigned int display_count_;
+
+ bool gpu_process_accessible_;
+
+ bool use_software_compositor_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuDataManagerImplPrivate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_PRIVATE_H_
+
diff --git a/chromium/content/browser/gpu/gpu_data_manager_impl_private_unittest.cc b/chromium/content/browser/gpu/gpu_data_manager_impl_private_unittest.cc
new file mode 100644
index 00000000000..2d70b734c4e
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_data_manager_impl_private_unittest.cc
@@ -0,0 +1,662 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "content/browser/gpu/gpu_data_manager_impl_private.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/common/gpu_feature_type.h"
+#include "content/public/common/gpu_info.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+#define LONG_STRING_CONST(...) #__VA_ARGS__
+
+namespace content {
+namespace {
+
+class TestObserver : public GpuDataManagerObserver {
+ public:
+ TestObserver()
+ : gpu_info_updated_(false),
+ video_memory_usage_stats_updated_(false) {
+ }
+ virtual ~TestObserver() { }
+
+ bool gpu_info_updated() const { return gpu_info_updated_; }
+ bool video_memory_usage_stats_updated() const {
+ return video_memory_usage_stats_updated_;
+ }
+
+ virtual void OnGpuInfoUpdate() OVERRIDE {
+ gpu_info_updated_ = true;
+ }
+
+ virtual void OnVideoMemoryUsageStatsUpdate(
+ const GPUVideoMemoryUsageStats& stats) OVERRIDE {
+ video_memory_usage_stats_updated_ = true;
+ }
+
+ private:
+ bool gpu_info_updated_;
+ bool video_memory_usage_stats_updated_;
+};
+
+static base::Time GetTimeForTesting() {
+ return base::Time::FromDoubleT(1000);
+}
+
+static GURL GetDomain1ForTesting() {
+ return GURL("http://foo.com/");
+}
+
+static GURL GetDomain2ForTesting() {
+ return GURL("http://bar.com/");
+}
+
+} // namespace anonymous
+
+class GpuDataManagerImplPrivateTest : public testing::Test {
+ public:
+ GpuDataManagerImplPrivateTest() { }
+
+ virtual ~GpuDataManagerImplPrivateTest() { }
+
+ protected:
+ // scoped_ptr doesn't work with GpuDataManagerImpl because its
+ // destructor is private. GpuDataManagerImplPrivateTest is however a friend
+ // so we can make a little helper class here.
+ class ScopedGpuDataManagerImpl {
+ public:
+ ScopedGpuDataManagerImpl() : impl_(new GpuDataManagerImpl()) {
+ EXPECT_TRUE(impl_);
+ EXPECT_TRUE(impl_->private_.get());
+ }
+ ~ScopedGpuDataManagerImpl() { delete impl_; }
+
+ GpuDataManagerImpl* get() const { return impl_; }
+
+ GpuDataManagerImpl* operator->() const { return impl_; }
+
+ private:
+ GpuDataManagerImpl* impl_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedGpuDataManagerImpl);
+ };
+
+ // We want to test the code path where GpuDataManagerImplPrivate is created
+ // in the GpuDataManagerImpl constructor.
+ class ScopedGpuDataManagerImplPrivate {
+ public:
+ ScopedGpuDataManagerImplPrivate() : impl_(new GpuDataManagerImpl()) {
+ EXPECT_TRUE(impl_);
+ EXPECT_TRUE(impl_->private_.get());
+ }
+ ~ScopedGpuDataManagerImplPrivate() { delete impl_; }
+
+ GpuDataManagerImplPrivate* get() const {
+ return impl_->private_.get();
+ }
+
+ GpuDataManagerImplPrivate* operator->() const {
+ return impl_->private_.get();
+ }
+
+ private:
+ GpuDataManagerImpl* impl_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedGpuDataManagerImplPrivate);
+ };
+
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+
+ base::Time JustBeforeExpiration(const GpuDataManagerImplPrivate* manager);
+ base::Time JustAfterExpiration(const GpuDataManagerImplPrivate* manager);
+ void TestBlockingDomainFrom3DAPIs(
+ GpuDataManagerImpl::DomainGuilt guilt_level);
+ void TestUnblockingDomainFrom3DAPIs(
+ GpuDataManagerImpl::DomainGuilt guilt_level);
+
+ base::MessageLoop message_loop_;
+};
+
+// We use new method instead of GetInstance() method because we want
+// each test to be independent of each other.
+
+TEST_F(GpuDataManagerImplPrivateTest, GpuSideBlacklisting) {
+ // If a feature is allowed in preliminary step (browser side), but
+ // disabled when GPU process launches and collects full GPU info,
+ // it's too late to let renderer know, so we basically block all GPU
+ // access, to be on the safe side.
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ std::string reason;
+ EXPECT_TRUE(manager->GpuAccessAllowed(&reason));
+ EXPECT_TRUE(reason.empty());
+
+ const std::string blacklist_json = LONG_STRING_CONST(
+ {
+ "name": "gpu blacklist",
+ "version": "0.1",
+ "entries": [
+ {
+ "id": 1,
+ "features": [
+ "webgl"
+ ]
+ },
+ {
+ "id": 2,
+ "gl_renderer": {
+ "op": "contains",
+ "value": "GeForce"
+ },
+ "features": [
+ "accelerated_2d_canvas"
+ ]
+ }
+ ]
+ }
+ );
+
+ GPUInfo gpu_info;
+ gpu_info.gpu.vendor_id = 0x10de;
+ gpu_info.gpu.device_id = 0x0640;
+ manager->InitializeForTesting(blacklist_json, gpu_info);
+
+ EXPECT_TRUE(manager->GpuAccessAllowed(&reason));
+ EXPECT_TRUE(reason.empty());
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_WEBGL));
+
+ gpu_info.gl_vendor = "NVIDIA";
+ gpu_info.gl_renderer = "NVIDIA GeForce GT 120";
+ manager->UpdateGpuInfo(gpu_info);
+ EXPECT_FALSE(manager->GpuAccessAllowed(&reason));
+ EXPECT_FALSE(reason.empty());
+ EXPECT_EQ(2u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_WEBGL));
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(
+ GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, GpuSideExceptions) {
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+
+ const std::string blacklist_json = LONG_STRING_CONST(
+ {
+ "name": "gpu blacklist",
+ "version": "0.1",
+ "entries": [
+ {
+ "id": 1,
+ "exceptions": [
+ {
+ "gl_renderer": {
+ "op": "contains",
+ "value": "GeForce"
+ }
+ }
+ ],
+ "features": [
+ "webgl"
+ ]
+ }
+ ]
+ }
+ );
+ GPUInfo gpu_info;
+ gpu_info.gpu.vendor_id = 0x10de;
+ gpu_info.gpu.device_id = 0x0640;
+ manager->InitializeForTesting(blacklist_json, gpu_info);
+
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+
+ // Now assume gpu process launches and full GPU info is collected.
+ gpu_info.gl_renderer = "NVIDIA GeForce GT 120";
+ manager->UpdateGpuInfo(gpu_info);
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, DisableHardwareAcceleration) {
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ std::string reason;
+ EXPECT_TRUE(manager->GpuAccessAllowed(&reason));
+ EXPECT_TRUE(reason.empty());
+
+ manager->DisableHardwareAcceleration();
+ EXPECT_FALSE(manager->GpuAccessAllowed(&reason));
+ EXPECT_FALSE(reason.empty());
+ EXPECT_EQ(static_cast<size_t>(NUMBER_OF_GPU_FEATURE_TYPES),
+ manager->GetBlacklistedFeatureCount());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, SwiftShaderRendering) {
+ // Blacklist, then register SwiftShader.
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_FALSE(manager->ShouldUseSwiftShader());
+
+ manager->DisableHardwareAcceleration();
+ EXPECT_FALSE(manager->GpuAccessAllowed(NULL));
+ EXPECT_FALSE(manager->ShouldUseSwiftShader());
+
+ // If SwiftShader is enabled, even if we blacklist GPU,
+ // GPU process is still allowed.
+ const base::FilePath test_path(FILE_PATH_LITERAL("AnyPath"));
+ manager->RegisterSwiftShaderPath(test_path);
+ EXPECT_TRUE(manager->ShouldUseSwiftShader());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(
+ manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, SwiftShaderRendering2) {
+ // Register SwiftShader, then blacklist.
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_FALSE(manager->ShouldUseSwiftShader());
+
+ const base::FilePath test_path(FILE_PATH_LITERAL("AnyPath"));
+ manager->RegisterSwiftShaderPath(test_path);
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_FALSE(manager->ShouldUseSwiftShader());
+
+ manager->DisableHardwareAcceleration();
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_TRUE(manager->ShouldUseSwiftShader());
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(
+ manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, GpuInfoUpdate) {
+ ScopedGpuDataManagerImpl manager;
+
+ TestObserver observer;
+ manager->AddObserver(&observer);
+
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_FALSE(observer.gpu_info_updated());
+
+ GPUInfo gpu_info;
+ manager->UpdateGpuInfo(gpu_info);
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_TRUE(observer.gpu_info_updated());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, NoGpuInfoUpdateWithSwiftShader) {
+ ScopedGpuDataManagerImpl manager;
+
+ manager->DisableHardwareAcceleration();
+ const base::FilePath test_path(FILE_PATH_LITERAL("AnyPath"));
+ manager->RegisterSwiftShaderPath(test_path);
+ EXPECT_TRUE(manager->ShouldUseSwiftShader());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ TestObserver observer;
+ manager->AddObserver(&observer);
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_FALSE(observer.gpu_info_updated());
+
+ GPUInfo gpu_info;
+ manager->UpdateGpuInfo(gpu_info);
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_FALSE(observer.gpu_info_updated());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, GPUVideoMemoryUsageStatsUpdate) {
+ ScopedGpuDataManagerImpl manager;
+
+ TestObserver observer;
+ manager->AddObserver(&observer);
+
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_FALSE(observer.video_memory_usage_stats_updated());
+
+ GPUVideoMemoryUsageStats vram_stats;
+ manager->UpdateVideoMemoryUsageStats(vram_stats);
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_TRUE(observer.video_memory_usage_stats_updated());
+}
+
+base::Time GpuDataManagerImplPrivateTest::JustBeforeExpiration(
+ const GpuDataManagerImplPrivate* manager) {
+ return GetTimeForTesting() + base::TimeDelta::FromMilliseconds(
+ manager->GetBlockAllDomainsDurationInMs()) -
+ base::TimeDelta::FromMilliseconds(3);
+}
+
+base::Time GpuDataManagerImplPrivateTest::JustAfterExpiration(
+ const GpuDataManagerImplPrivate* manager) {
+ return GetTimeForTesting() + base::TimeDelta::FromMilliseconds(
+ manager->GetBlockAllDomainsDurationInMs()) +
+ base::TimeDelta::FromMilliseconds(3);
+}
+
+void GpuDataManagerImplPrivateTest::TestBlockingDomainFrom3DAPIs(
+ GpuDataManagerImpl::DomainGuilt guilt_level) {
+ ScopedGpuDataManagerImplPrivate manager;
+
+ manager->BlockDomainFrom3DAPIsAtTime(GetDomain1ForTesting(),
+ guilt_level,
+ GetTimeForTesting());
+
+ // This domain should be blocked no matter what.
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(GetDomain1ForTesting(),
+ GetTimeForTesting()));
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain1ForTesting(), JustBeforeExpiration(manager.get())));
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain1ForTesting(), JustAfterExpiration(manager.get())));
+}
+
+void GpuDataManagerImplPrivateTest::TestUnblockingDomainFrom3DAPIs(
+ GpuDataManagerImpl::DomainGuilt guilt_level) {
+ ScopedGpuDataManagerImplPrivate manager;
+
+ manager->BlockDomainFrom3DAPIsAtTime(GetDomain1ForTesting(),
+ guilt_level,
+ GetTimeForTesting());
+
+ // Unblocking the domain should work.
+ manager->UnblockDomainFrom3DAPIs(GetDomain1ForTesting());
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(GetDomain1ForTesting(),
+ GetTimeForTesting()));
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain1ForTesting(), JustBeforeExpiration(manager.get())));
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain1ForTesting(), JustAfterExpiration(manager.get())));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, BlockGuiltyDomainFrom3DAPIs) {
+ TestBlockingDomainFrom3DAPIs(GpuDataManagerImpl::DOMAIN_GUILT_KNOWN);
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, BlockDomainOfUnknownGuiltFrom3DAPIs) {
+ TestBlockingDomainFrom3DAPIs(GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN);
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, BlockAllDomainsFrom3DAPIs) {
+ ScopedGpuDataManagerImplPrivate manager;
+
+ manager->BlockDomainFrom3DAPIsAtTime(GetDomain1ForTesting(),
+ GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN,
+ GetTimeForTesting());
+
+ // Blocking of other domains should expire.
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_ALL_DOMAINS_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain2ForTesting(), JustBeforeExpiration(manager.get())));
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain2ForTesting(), JustAfterExpiration(manager.get())));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, UnblockGuiltyDomainFrom3DAPIs) {
+ TestUnblockingDomainFrom3DAPIs(GpuDataManagerImpl::DOMAIN_GUILT_KNOWN);
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, UnblockDomainOfUnknownGuiltFrom3DAPIs) {
+ TestUnblockingDomainFrom3DAPIs(GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN);
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, UnblockOtherDomainFrom3DAPIs) {
+ ScopedGpuDataManagerImplPrivate manager;
+
+ manager->BlockDomainFrom3DAPIsAtTime(GetDomain1ForTesting(),
+ GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN,
+ GetTimeForTesting());
+
+ manager->UnblockDomainFrom3DAPIs(GetDomain2ForTesting());
+
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain2ForTesting(), JustBeforeExpiration(manager.get())));
+
+ // The original domain should still be blocked.
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain1ForTesting(), JustBeforeExpiration(manager.get())));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, UnblockThisDomainFrom3DAPIs) {
+ ScopedGpuDataManagerImplPrivate manager;
+
+ manager->BlockDomainFrom3DAPIsAtTime(GetDomain1ForTesting(),
+ GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN,
+ GetTimeForTesting());
+
+ manager->UnblockDomainFrom3DAPIs(GetDomain1ForTesting());
+
+ // This behavior is debatable. Perhaps the GPU reset caused by
+ // domain 1 should still cause other domains to be blocked.
+ EXPECT_EQ(GpuDataManagerImpl::DOMAIN_BLOCK_STATUS_NOT_BLOCKED,
+ manager->Are3DAPIsBlockedAtTime(
+ GetDomain2ForTesting(), JustBeforeExpiration(manager.get())));
+}
+
+#if defined(OS_LINUX)
+TEST_F(GpuDataManagerImplPrivateTest, SetGLStrings) {
+ const char* kGLVendorMesa = "Tungsten Graphics, Inc";
+ const char* kGLRendererMesa = "Mesa DRI Intel(R) G41";
+ const char* kGLVersionMesa801 = "2.1 Mesa 8.0.1-DEVEL";
+
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+
+ const std::string blacklist_json = LONG_STRING_CONST(
+ {
+ "name": "gpu blacklist",
+ "version": "0.1",
+ "entries": [
+ {
+ "id": 1,
+ "vendor_id": "0x8086",
+ "exceptions": [
+ {
+ "device_id": ["0x0042"],
+ "driver_version": {
+ "op": ">=",
+ "number": "8.0.2"
+ }
+ }
+ ],
+ "features": [
+ "webgl"
+ ]
+ }
+ ]
+ }
+ );
+ GPUInfo gpu_info;
+ gpu_info.gpu.vendor_id = 0x8086;
+ gpu_info.gpu.device_id = 0x0042;
+ manager->InitializeForTesting(blacklist_json, gpu_info);
+
+ // Not enough GPUInfo.
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+
+ // Now assume browser gets GL strings from local state.
+ // The entry applies, blacklist more features than from the preliminary step.
+ // However, GPU process is not blocked because this is all browser side and
+ // happens before renderer launching.
+ manager->SetGLStrings(kGLVendorMesa, kGLRendererMesa, kGLVersionMesa801);
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_WEBGL));
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, SetGLStringsNoEffects) {
+ const char* kGLVendorMesa = "Tungsten Graphics, Inc";
+ const char* kGLRendererMesa = "Mesa DRI Intel(R) G41";
+ const char* kGLVersionMesa801 = "2.1 Mesa 8.0.1-DEVEL";
+ const char* kGLVersionMesa802 = "2.1 Mesa 8.0.2-DEVEL";
+
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+
+ const std::string blacklist_json = LONG_STRING_CONST(
+ {
+ "name": "gpu blacklist",
+ "version": "0.1",
+ "entries": [
+ {
+ "id": 1,
+ "vendor_id": "0x8086",
+ "exceptions": [
+ {
+ "device_id": ["0x0042"],
+ "driver_version": {
+ "op": ">=",
+ "number": "8.0.2"
+ }
+ }
+ ],
+ "features": [
+ "webgl"
+ ]
+ }
+ ]
+ }
+ );
+ GPUInfo gpu_info;
+ gpu_info.gpu.vendor_id = 0x8086;
+ gpu_info.gpu.device_id = 0x0042;
+ gpu_info.gl_vendor = kGLVendorMesa;
+ gpu_info.gl_renderer = kGLRendererMesa;
+ gpu_info.gl_version = kGLVersionMesa801;
+ gpu_info.driver_vendor = "Mesa";
+ gpu_info.driver_version = "8.0.1";
+ manager->InitializeForTesting(blacklist_json, gpu_info);
+
+ // Full GPUInfo, the entry applies.
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_WEBGL));
+
+ // Now assume browser gets GL strings from local state.
+ // SetGLStrings() has no effects because GPUInfo already got these strings.
+ // (Otherwise the entry should not apply.)
+ manager->SetGLStrings(kGLVendorMesa, kGLRendererMesa, kGLVersionMesa802);
+ EXPECT_TRUE(manager->GpuAccessAllowed(NULL));
+ EXPECT_EQ(1u, manager->GetBlacklistedFeatureCount());
+ EXPECT_TRUE(manager->IsFeatureBlacklisted(GPU_FEATURE_TYPE_WEBGL));
+}
+#endif // OS_LINUX
+
+TEST_F(GpuDataManagerImplPrivateTest, GpuDriverBugListSingle) {
+ ScopedGpuDataManagerImplPrivate manager;
+ manager->gpu_driver_bugs_.insert(5);
+
+ CommandLine command_line(0, NULL);
+ manager->AppendGpuCommandLine(&command_line);
+
+ EXPECT_TRUE(command_line.HasSwitch(switches::kGpuDriverBugWorkarounds));
+ std::string args = command_line.GetSwitchValueASCII(
+ switches::kGpuDriverBugWorkarounds);
+ EXPECT_STREQ("5", args.c_str());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, GpuDriverBugListMultiple) {
+ ScopedGpuDataManagerImplPrivate manager;
+ manager->gpu_driver_bugs_.insert(5);
+ manager->gpu_driver_bugs_.insert(7);
+
+ CommandLine command_line(0, NULL);
+ manager->AppendGpuCommandLine(&command_line);
+
+ EXPECT_TRUE(command_line.HasSwitch(switches::kGpuDriverBugWorkarounds));
+ std::string args = command_line.GetSwitchValueASCII(
+ switches::kGpuDriverBugWorkarounds);
+ EXPECT_STREQ("5,7", args.c_str());
+}
+
+TEST_F(GpuDataManagerImplPrivateTest, BlacklistAllFeatures) {
+ ScopedGpuDataManagerImplPrivate manager;
+ EXPECT_EQ(0u, manager->GetBlacklistedFeatureCount());
+ std::string reason;
+ EXPECT_TRUE(manager->GpuAccessAllowed(&reason));
+ EXPECT_TRUE(reason.empty());
+
+ const std::string blacklist_json = LONG_STRING_CONST(
+ {
+ "name": "gpu blacklist",
+ "version": "0.1",
+ "entries": [
+ {
+ "id": 1,
+ "features": [
+ "all"
+ ]
+ }
+ ]
+ }
+ );
+
+ GPUInfo gpu_info;
+ gpu_info.gpu.vendor_id = 0x10de;
+ gpu_info.gpu.device_id = 0x0640;
+ manager->InitializeForTesting(blacklist_json, gpu_info);
+
+ EXPECT_EQ(static_cast<size_t>(NUMBER_OF_GPU_FEATURE_TYPES),
+ manager->GetBlacklistedFeatureCount());
+ // TODO(zmo): remove the Linux specific behavior once we fix
+ // crbug.com/238466.
+#if defined(OS_LINUX)
+ EXPECT_TRUE(manager->GpuAccessAllowed(&reason));
+ EXPECT_TRUE(reason.empty());
+#else
+ EXPECT_FALSE(manager->GpuAccessAllowed(&reason));
+ EXPECT_FALSE(reason.empty());
+#endif
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_functional_browsertest.cc b/chromium/content/browser/gpu/gpu_functional_browsertest.cc
new file mode 100644
index 00000000000..b3c2a57319f
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_functional_browsertest.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/path_service.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+namespace {
+ void VerifyGPUProcessLaunch(bool* result) {
+ GpuProcessHost* host =
+ GpuProcessHost::Get(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ content::CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH);
+ *result = !!host;
+ }
+}
+
+class GpuFunctionalTest : public ContentBrowserTest {
+ protected:
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ base::FilePath test_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir));
+ gpu_test_dir_ = test_dir.AppendASCII("gpu");
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(switches::kDisableGpuProcessPrelaunch);
+ }
+
+ void VerifyHardwareAccelerated(const std::string& feature) {
+ NavigateToURL(shell(),
+ GURL(std::string(chrome::kChromeUIScheme).
+ append("://").
+ append(kChromeUIGpuHost)));
+
+ {
+ // Verify that the given feature is hardware accelerated..
+ std::string javascript =
+ "function VerifyHardwareAccelerated(feature) {"
+ " var list = document.querySelector(\".feature-status-list\");"
+ " for (var i=0; i < list.childElementCount; i++) {"
+ " var span_list = list.children[i].getElementsByTagName('span');"
+ " var feature_str = span_list[0].textContent;"
+ " var value_str = span_list[1].textContent;"
+ " if ((feature_str == feature) &&"
+ " (value_str == 'Hardware accelerated')) {"
+ " domAutomationController.send(\"success\");"
+ " }"
+ " }"
+ "};";
+ javascript.append("VerifyHardwareAccelerated(\"");
+ javascript.append(feature);
+ javascript.append("\");");
+ std::string result;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(shell()->web_contents(),
+ javascript,
+ &result));
+ EXPECT_EQ(result, "success");
+ }
+ }
+
+ void VerifyGPUProcessOnPage(std::string filename, bool wait) {
+ Shell::Initialize();
+ ASSERT_TRUE(test_server()->Start());
+ DOMMessageQueue message_queue;
+
+ std::string url("files/gpu/");
+ GURL full_url = test_server()->GetURL(url.append(filename));
+ NavigateToURL(shell(), full_url);
+
+ if (wait) {
+ std::string result_string;
+ ASSERT_TRUE(message_queue.WaitForMessage(&result_string));
+ }
+
+ bool result = false;
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VerifyGPUProcessLaunch, &result),
+ base::MessageLoop::QuitClosure());
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(result);
+ }
+
+ base::FilePath gpu_test_dir_;
+};
+
+#if defined(OS_LINUX) && !defined(NDEBUG)
+// http://crbug.com/254724
+#define IF_NOT_DEBUG_LINUX(x) DISABLED_ ## x
+#else
+#define IF_NOT_DEBUG_LINUX(x) x
+#endif
+
+IN_PROC_BROWSER_TEST_F(
+ GpuFunctionalTest,
+ IF_NOT_DEBUG_LINUX(MANUAL_TestFeatureHardwareAccelerated)) {
+ VerifyHardwareAccelerated("WebGL: ");
+ VerifyHardwareAccelerated("Canvas: ");
+ VerifyHardwareAccelerated("3D CSS: ");
+}
+
+// Verify that gpu process is spawned in webgl example.
+IN_PROC_BROWSER_TEST_F(GpuFunctionalTest,
+ IF_NOT_DEBUG_LINUX(MANUAL_TestWebGL)) {
+ VerifyGPUProcessOnPage("functional_webgl.html", false);
+}
+
+// Verify that gpu process is spawned when viewing a 2D canvas.
+IN_PROC_BROWSER_TEST_F(GpuFunctionalTest,
+ IF_NOT_DEBUG_LINUX(MANUAL_Test2dCanvas)) {
+ VerifyGPUProcessOnPage("functional_canvas_demo.html", false);
+}
+
+// Verify that gpu process is spawned when viewing a 3D CSS page.
+IN_PROC_BROWSER_TEST_F(GpuFunctionalTest,
+ IF_NOT_DEBUG_LINUX(MANUAL_Test3dCss)) {
+ VerifyGPUProcessOnPage("functional_3d_css.html", false);
+}
+
+#if defined(OS_LINUX)
+// crbug.com/257109
+#define MANUAL_TestGpuWithVideo DISABLED_TestGpuWithVideo
+#endif
+
+// Verify that gpu process is started when viewing video.
+IN_PROC_BROWSER_TEST_F(GpuFunctionalTest,
+ MANUAL_TestGpuWithVideo) {
+ VerifyGPUProcessOnPage("functional_video.html", true);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_info_browsertest.cc b/chromium/content/browser/gpu/gpu_info_browsertest.cc
new file mode 100644
index 00000000000..d555d1d05f0
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_info_browsertest.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/test/content_browser_test.h"
+
+namespace content {
+
+namespace {
+
+class TestObserver : public GpuDataManagerObserver {
+ public:
+ explicit TestObserver(base::MessageLoop* message_loop)
+ : message_loop_(message_loop) {
+ }
+
+ virtual ~TestObserver() { }
+
+ virtual void OnGpuInfoUpdate() OVERRIDE {
+ // Display GPU/Driver information.
+ gpu::GPUInfo gpu_info =
+ GpuDataManagerImpl::GetInstance()->GetGPUInfo();
+ std::string vendor_id = base::StringPrintf(
+ "0x%04x", gpu_info.gpu.vendor_id);
+ std::string device_id = base::StringPrintf(
+ "0x%04x", gpu_info.gpu.device_id);
+ LOG(INFO) << "GPU[0]: vendor_id = " << vendor_id
+ << ", device_id = " << device_id;
+ for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) {
+ gpu::GPUInfo::GPUDevice gpu = gpu_info.secondary_gpus[i];
+ vendor_id = base::StringPrintf("0x%04x", gpu.vendor_id);
+ device_id = base::StringPrintf("0x%04x", gpu.device_id);
+ LOG(INFO) << "GPU[" << (i + 1)
+ << "]: vendor_id = " << vendor_id
+ << ", device_od = " << device_id;
+ }
+ LOG(INFO) << "GPU Driver: vendor = " << gpu_info.driver_vendor
+ << ", version = " << gpu_info.driver_version
+ << ", date = " << gpu_info.driver_date;
+
+ // Display GL information.
+ LOG(INFO) << "GL: vendor = " << gpu_info.gl_vendor
+ << ", renderer = " << gpu_info.gl_renderer;
+
+ // Display GL window system binding information.
+ LOG(INFO) << "GL Window System: vendor = " << gpu_info.gl_ws_vendor
+ << ", version = " << gpu_info.gl_ws_version;
+
+ // Display OS information.
+ LOG(INFO) << "OS = " << base::SysInfo::OperatingSystemName()
+ << " " << base::SysInfo::OperatingSystemVersion();
+
+ message_loop_->Quit();
+ }
+
+ private:
+ base::MessageLoop* message_loop_;
+};
+
+} // namespace anonymous
+
+class GpuInfoBrowserTest : public ContentBrowserTest {
+ public:
+ GpuInfoBrowserTest()
+ : message_loop_(base::MessageLoop::TYPE_UI) {
+ }
+
+ virtual void SetUp() {
+ // We expect real pixel output for these tests.
+ UseRealGLContexts();
+
+ ContentBrowserTest::SetUp();
+ }
+
+ base::MessageLoop* GetMessageLoop() { return &message_loop_; }
+
+ private:
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuInfoBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(GpuInfoBrowserTest, MANUAL_DisplayGpuInfo) {
+ // crbug.com/262287
+#if defined(OS_MACOSX)
+ // TODO(zmo): crashing on Mac, and also we don't have the full info
+ // collected.
+ return;
+#endif
+#if defined(OS_LINUX) && !defined(NDEBUG)
+ // TODO(zmo): crashing on Linux Debug.
+ return;
+#endif
+ TestObserver observer(GetMessageLoop());
+ GpuDataManagerImpl::GetInstance()->AddObserver(&observer);
+ GpuDataManagerImpl::GetInstance()->RequestCompleteGpuInfoIfNeeded();
+
+ GetMessageLoop()->Run();
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/gpu/gpu_internals_ui.cc b/chromium/content/browser/gpu/gpu_internals_ui.cc
new file mode 100644
index 00000000000..0e778c277b9
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_internals_ui.cc
@@ -0,0 +1,661 @@
+// 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 "content/browser/gpu/gpu_internals_ui.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/values.h"
+#include "cc/base/switches.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/compositor_util.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/browser/web_ui_message_handler.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "gpu/config/gpu_feature_type.h"
+#include "gpu/config/gpu_info.h"
+#include "grit/content_resources.h"
+#include "third_party/angle_dx11/src/common/version.h"
+
+namespace content {
+namespace {
+
+struct GpuFeatureInfo {
+ std::string name;
+ uint32 blocked;
+ bool disabled;
+ std::string disabled_description;
+ bool fallback_to_software;
+};
+
+WebUIDataSource* CreateGpuHTMLSource() {
+ WebUIDataSource* source = WebUIDataSource::Create(kChromeUIGpuHost);
+
+ source->SetJsonPath("strings.js");
+ source->AddResourcePath("gpu_internals.js", IDR_GPU_INTERNALS_JS);
+ source->SetDefaultResource(IDR_GPU_INTERNALS_HTML);
+ return source;
+}
+
+base::DictionaryValue* NewDescriptionValuePair(const std::string& desc,
+ const std::string& value) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("description", desc);
+ dict->SetString("value", value);
+ return dict;
+}
+
+base::DictionaryValue* NewDescriptionValuePair(const std::string& desc,
+ base::Value* value) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("description", desc);
+ dict->Set("value", value);
+ return dict;
+}
+
+base::Value* NewStatusValue(const char* name, const char* status) {
+ base::DictionaryValue* value = new base::DictionaryValue();
+ value->SetString("name", name);
+ value->SetString("status", status);
+ return value;
+}
+
+#if defined(OS_WIN)
+// Output DxDiagNode tree as nested array of {description,value} pairs
+base::ListValue* DxDiagNodeToList(const gpu::DxDiagNode& node) {
+ base::ListValue* list = new base::ListValue();
+ for (std::map<std::string, std::string>::const_iterator it =
+ node.values.begin();
+ it != node.values.end();
+ ++it) {
+ list->Append(NewDescriptionValuePair(it->first, it->second));
+ }
+
+ for (std::map<std::string, gpu::DxDiagNode>::const_iterator it =
+ node.children.begin();
+ it != node.children.end();
+ ++it) {
+ base::ListValue* sublist = DxDiagNodeToList(it->second);
+ list->Append(NewDescriptionValuePair(it->first, sublist));
+ }
+ return list;
+}
+#endif
+
+std::string GPUDeviceToString(const gpu::GPUInfo::GPUDevice& gpu) {
+ std::string vendor = base::StringPrintf("0x%04x", gpu.vendor_id);
+ if (!gpu.vendor_string.empty())
+ vendor += " [" + gpu.vendor_string + "]";
+ std::string device = base::StringPrintf("0x%04x", gpu.device_id);
+ if (!gpu.device_string.empty())
+ device += " [" + gpu.device_string + "]";
+ return base::StringPrintf(
+ "VENDOR = %s, DEVICE= %s", vendor.c_str(), device.c_str());
+}
+
+base::DictionaryValue* GpuInfoAsDictionaryValue() {
+ gpu::GPUInfo gpu_info = GpuDataManagerImpl::GetInstance()->GetGPUInfo();
+ base::ListValue* basic_info = new base::ListValue();
+ basic_info->Append(NewDescriptionValuePair(
+ "Initialization time",
+ base::Int64ToString(gpu_info.initialization_time.InMilliseconds())));
+ basic_info->Append(NewDescriptionValuePair(
+ "Sandboxed", new base::FundamentalValue(gpu_info.sandboxed)));
+ basic_info->Append(NewDescriptionValuePair(
+ "GPU0", GPUDeviceToString(gpu_info.gpu)));
+ for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) {
+ basic_info->Append(NewDescriptionValuePair(
+ base::StringPrintf("GPU%d", static_cast<int>(i + 1)),
+ GPUDeviceToString(gpu_info.secondary_gpus[i])));
+ }
+ basic_info->Append(NewDescriptionValuePair(
+ "Optimus", new base::FundamentalValue(gpu_info.optimus)));
+ basic_info->Append(NewDescriptionValuePair(
+ "AMD switchable", new base::FundamentalValue(gpu_info.amd_switchable)));
+ if (gpu_info.lenovo_dcute) {
+ basic_info->Append(NewDescriptionValuePair(
+ "Lenovo dCute", new base::FundamentalValue(true)));
+ }
+ if (gpu_info.display_link_version.IsValid()) {
+ basic_info->Append(NewDescriptionValuePair(
+ "DisplayLink Version", gpu_info.display_link_version.GetString()));
+ }
+ basic_info->Append(NewDescriptionValuePair("Driver vendor",
+ gpu_info.driver_vendor));
+ basic_info->Append(NewDescriptionValuePair("Driver version",
+ gpu_info.driver_version));
+ basic_info->Append(NewDescriptionValuePair("Driver date",
+ gpu_info.driver_date));
+ basic_info->Append(NewDescriptionValuePair("Pixel shader version",
+ gpu_info.pixel_shader_version));
+ basic_info->Append(NewDescriptionValuePair("Vertex shader version",
+ gpu_info.vertex_shader_version));
+ basic_info->Append(NewDescriptionValuePair("Machine model",
+ gpu_info.machine_model));
+ basic_info->Append(NewDescriptionValuePair("GL version",
+ gpu_info.gl_version));
+ basic_info->Append(NewDescriptionValuePair("GL_VENDOR",
+ gpu_info.gl_vendor));
+ basic_info->Append(NewDescriptionValuePair("GL_RENDERER",
+ gpu_info.gl_renderer));
+ basic_info->Append(NewDescriptionValuePair("GL_VERSION",
+ gpu_info.gl_version_string));
+ basic_info->Append(NewDescriptionValuePair("GL_EXTENSIONS",
+ gpu_info.gl_extensions));
+ basic_info->Append(NewDescriptionValuePair("Window system binding vendor",
+ gpu_info.gl_ws_vendor));
+ basic_info->Append(NewDescriptionValuePair("Window system binding version",
+ gpu_info.gl_ws_version));
+ basic_info->Append(NewDescriptionValuePair("Window system binding extensions",
+ gpu_info.gl_ws_extensions));
+ std::string reset_strategy =
+ base::StringPrintf("0x%04x", gpu_info.gl_reset_notification_strategy);
+ basic_info->Append(NewDescriptionValuePair(
+ "Reset notification strategy", reset_strategy));
+
+ base::DictionaryValue* info = new base::DictionaryValue();
+ info->Set("basic_info", basic_info);
+
+#if defined(OS_WIN)
+ base::ListValue* perf_info = new base::ListValue();
+ perf_info->Append(NewDescriptionValuePair(
+ "Graphics",
+ base::StringPrintf("%.1f", gpu_info.performance_stats.graphics)));
+ perf_info->Append(NewDescriptionValuePair(
+ "Gaming",
+ base::StringPrintf("%.1f", gpu_info.performance_stats.gaming)));
+ perf_info->Append(NewDescriptionValuePair(
+ "Overall",
+ base::StringPrintf("%.1f", gpu_info.performance_stats.overall)));
+ info->Set("performance_info", perf_info);
+
+ base::Value* dx_info = gpu_info.dx_diagnostics.children.size() ?
+ DxDiagNodeToList(gpu_info.dx_diagnostics) :
+ base::Value::CreateNullValue();
+ info->Set("diagnostics", dx_info);
+#endif
+
+ return info;
+}
+
+// Determine if accelerated-2d-canvas is supported, which depends on whether
+// lose_context could happen.
+bool SupportsAccelerated2dCanvas() {
+ if (GpuDataManagerImpl::GetInstance()->GetGPUInfo().can_lose_context)
+ return false;
+ return true;
+}
+
+base::Value* GetFeatureStatus() {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ GpuDataManagerImpl* manager = GpuDataManagerImpl::GetInstance();
+ std::string gpu_access_blocked_reason;
+ bool gpu_access_blocked =
+ !manager->GpuAccessAllowed(&gpu_access_blocked_reason);
+
+ base::DictionaryValue* status = new base::DictionaryValue();
+
+ const GpuFeatureInfo kGpuFeatureInfo[] = {
+ {
+ "2d_canvas",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS),
+ command_line.HasSwitch(switches::kDisableAccelerated2dCanvas) ||
+ !SupportsAccelerated2dCanvas(),
+ "Accelerated 2D canvas is unavailable: either disabled at the command"
+ " line or not supported by the current system.",
+ true
+ },
+ {
+ "compositing",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING),
+ command_line.HasSwitch(switches::kDisableAcceleratedCompositing),
+ "Accelerated compositing has been disabled, either via about:flags or"
+ " command line. This adversely affects performance of all hardware"
+ " accelerated features.",
+ true
+ },
+ {
+ "3d_css",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) ||
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_3D_CSS),
+ command_line.HasSwitch(switches::kDisableAcceleratedLayers),
+ "Accelerated layers have been disabled at the command line.",
+ false
+ },
+ {
+ "css_animation",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) ||
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_3D_CSS),
+ command_line.HasSwitch(cc::switches::kDisableThreadedAnimation) ||
+ command_line.HasSwitch(switches::kDisableAcceleratedCompositing) ||
+ command_line.HasSwitch(switches::kDisableAcceleratedLayers),
+ "Accelerated CSS animation has been disabled at the command line.",
+ true
+ },
+ {
+ "webgl",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL),
+ command_line.HasSwitch(switches::kDisableExperimentalWebGL),
+ "WebGL has been disabled, either via about:flags or command line.",
+ false
+ },
+ {
+ "multisampling",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_MULTISAMPLING),
+ command_line.HasSwitch(switches::kDisableGLMultisampling),
+ "Multisampling has been disabled, either via about:flags or command"
+ " line.",
+ false
+ },
+ {
+ "flash_3d",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH3D),
+ command_line.HasSwitch(switches::kDisableFlash3d),
+ "Using 3d in flash has been disabled, either via about:flags or"
+ " command line.",
+ false
+ },
+ {
+ "flash_stage3d",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D),
+ command_line.HasSwitch(switches::kDisableFlashStage3d),
+ "Using Stage3d in Flash has been disabled, either via about:flags or"
+ " command line.",
+ false
+ },
+ {
+ "flash_stage3d_baseline",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D_BASELINE) ||
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D),
+ command_line.HasSwitch(switches::kDisableFlashStage3d),
+ "Using Stage3d Baseline profile in Flash has been disabled, either"
+ " via about:flags or command line.",
+ false
+ },
+ {
+ "texture_sharing",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_TEXTURE_SHARING),
+ command_line.HasSwitch(switches::kDisableImageTransportSurface),
+ "Sharing textures between processes has been disabled, either via"
+ " about:flags or command line.",
+ false
+ },
+ {
+ "video_decode",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE),
+ command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode),
+ "Accelerated video decode has been disabled, either via about:flags"
+ " or command line.",
+ true
+ },
+ {
+ "video",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO),
+ command_line.HasSwitch(switches::kDisableAcceleratedVideo) ||
+ command_line.HasSwitch(switches::kDisableAcceleratedCompositing),
+ "Accelerated video presentation has been disabled, either via"
+ " about:flags or command line.",
+ true
+ },
+#if defined(OS_CHROMEOS)
+ {
+ "panel_fitting",
+ manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_PANEL_FITTING),
+ command_line.HasSwitch(switches::kDisablePanelFitting),
+ "Panel fitting has been disabled, either via about:flags or command"
+ " line.",
+ false
+ },
+#endif
+ {
+ "force_compositing_mode",
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_FORCE_COMPOSITING_MODE) &&
+ !IsForceCompositingModeEnabled(),
+ !IsForceCompositingModeEnabled() &&
+ !manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_FORCE_COMPOSITING_MODE),
+ "Force compositing mode is off, either disabled at the command"
+ " line or not supported by the current system.",
+ false
+ },
+ };
+ const size_t kNumFeatures = sizeof(kGpuFeatureInfo) / sizeof(GpuFeatureInfo);
+
+ // Build the feature_status field.
+ {
+ base::ListValue* feature_status_list = new base::ListValue();
+
+ for (size_t i = 0; i < kNumFeatures; ++i) {
+ // force_compositing_mode status is part of the compositing status.
+ if (kGpuFeatureInfo[i].name == "force_compositing_mode")
+ continue;
+
+ std::string status;
+ if (kGpuFeatureInfo[i].disabled) {
+ status = "disabled";
+ if (kGpuFeatureInfo[i].name == "css_animation") {
+ status += "_software_animated";
+ } else if (kGpuFeatureInfo[i].name == "raster") {
+ if (cc::switches::IsImplSidePaintingEnabled())
+ status += "_software_multithreaded";
+ else
+ status += "_software";
+ } else {
+ if (kGpuFeatureInfo[i].fallback_to_software)
+ status += "_software";
+ else
+ status += "_off";
+ }
+ } else if (GpuDataManagerImpl::GetInstance()->ShouldUseSwiftShader()) {
+ status = "unavailable_software";
+ } else if (kGpuFeatureInfo[i].blocked ||
+ gpu_access_blocked) {
+ status = "unavailable";
+ if (kGpuFeatureInfo[i].fallback_to_software)
+ status += "_software";
+ else
+ status += "_off";
+ } else {
+ status = "enabled";
+ if (kGpuFeatureInfo[i].name == "webgl" &&
+ (command_line.HasSwitch(switches::kDisableAcceleratedCompositing) ||
+ manager->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING)))
+ status += "_readback";
+ bool has_thread = IsThreadedCompositingEnabled();
+ if (kGpuFeatureInfo[i].name == "compositing") {
+ bool force_compositing = IsForceCompositingModeEnabled();
+ if (force_compositing)
+ status += "_force";
+ if (has_thread)
+ status += "_threaded";
+ }
+ if (kGpuFeatureInfo[i].name == "css_animation") {
+ if (has_thread)
+ status = "accelerated_threaded";
+ else
+ status = "accelerated";
+ }
+ }
+ // TODO(reveman): Remove this when crbug.com/223286 has been fixed.
+ if (kGpuFeatureInfo[i].name == "raster" &&
+ cc::switches::IsImplSidePaintingEnabled()) {
+ status = "disabled_software_multithreaded";
+ }
+ feature_status_list->Append(
+ NewStatusValue(kGpuFeatureInfo[i].name.c_str(), status.c_str()));
+ }
+ gpu::GpuSwitchingOption gpu_switching_option =
+ GpuDataManagerImpl::GetInstance()->GetGpuSwitchingOption();
+ if (gpu_switching_option != gpu::GPU_SWITCHING_OPTION_UNKNOWN) {
+ std::string gpu_switching;
+ switch (gpu_switching_option) {
+ case gpu::GPU_SWITCHING_OPTION_AUTOMATIC:
+ gpu_switching = "gpu_switching_automatic";
+ break;
+ case gpu::GPU_SWITCHING_OPTION_FORCE_DISCRETE:
+ gpu_switching = "gpu_switching_force_discrete";
+ break;
+ case gpu::GPU_SWITCHING_OPTION_FORCE_INTEGRATED:
+ gpu_switching = "gpu_switching_force_integrated";
+ break;
+ default:
+ break;
+ }
+ feature_status_list->Append(
+ NewStatusValue("gpu_switching", gpu_switching.c_str()));
+ }
+ status->Set("featureStatus", feature_status_list);
+ }
+
+ // Build the problems list.
+ {
+ base::ListValue* problem_list = new base::ListValue();
+ GpuDataManagerImpl::GetInstance()->GetBlacklistReasons(problem_list);
+
+ if (gpu_access_blocked) {
+ base::DictionaryValue* problem = new base::DictionaryValue();
+ problem->SetString("description",
+ "GPU process was unable to boot: " + gpu_access_blocked_reason);
+ problem->Set("crBugs", new base::ListValue());
+ problem->Set("webkitBugs", new base::ListValue());
+ problem_list->Insert(0, problem);
+ }
+
+ for (size_t i = 0; i < kNumFeatures; ++i) {
+ if (kGpuFeatureInfo[i].disabled) {
+ base::DictionaryValue* problem = new base::DictionaryValue();
+ problem->SetString(
+ "description", kGpuFeatureInfo[i].disabled_description);
+ problem->Set("crBugs", new base::ListValue());
+ problem->Set("webkitBugs", new base::ListValue());
+ problem_list->Append(problem);
+ }
+ }
+
+ status->Set("problems", problem_list);
+ }
+
+ // Build driver bug workaround list.
+ {
+ base::ListValue* workaround_list = new base::ListValue();
+ GpuDataManagerImpl::GetInstance()->GetDriverBugWorkarounds(workaround_list);
+ status->Set("workarounds", workaround_list);
+ }
+
+ return status;
+}
+
+// This class receives javascript messages from the renderer.
+// Note that the WebUI infrastructure runs on the UI thread, therefore all of
+// this class's methods are expected to run on the UI thread.
+class GpuMessageHandler
+ : public WebUIMessageHandler,
+ public base::SupportsWeakPtr<GpuMessageHandler>,
+ public GpuDataManagerObserver {
+ public:
+ GpuMessageHandler();
+ virtual ~GpuMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // GpuDataManagerObserver implementation.
+ virtual void OnGpuInfoUpdate() OVERRIDE;
+ virtual void OnGpuSwitching() OVERRIDE;
+
+ // Messages
+ void OnBrowserBridgeInitialized(const base::ListValue* list);
+ void OnCallAsync(const base::ListValue* list);
+
+ // Submessages dispatched from OnCallAsync
+ base::Value* OnRequestClientInfo(const base::ListValue* list);
+ base::Value* OnRequestLogMessages(const base::ListValue* list);
+
+ private:
+ // True if observing the GpuDataManager (re-attaching as observer would
+ // DCHECK).
+ bool observing_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuMessageHandler);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GpuMessageHandler
+//
+////////////////////////////////////////////////////////////////////////////////
+
+GpuMessageHandler::GpuMessageHandler()
+ : observing_(false) {
+}
+
+GpuMessageHandler::~GpuMessageHandler() {
+ GpuDataManagerImpl::GetInstance()->RemoveObserver(this);
+}
+
+/* BrowserBridge.callAsync prepends a requestID to these messages. */
+void GpuMessageHandler::RegisterMessages() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ web_ui()->RegisterMessageCallback("browserBridgeInitialized",
+ base::Bind(&GpuMessageHandler::OnBrowserBridgeInitialized,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("callAsync",
+ base::Bind(&GpuMessageHandler::OnCallAsync,
+ base::Unretained(this)));
+}
+
+void GpuMessageHandler::OnCallAsync(const base::ListValue* args) {
+ DCHECK_GE(args->GetSize(), static_cast<size_t>(2));
+ // unpack args into requestId, submessage and submessageArgs
+ bool ok;
+ const base::Value* requestId;
+ ok = args->Get(0, &requestId);
+ DCHECK(ok);
+
+ std::string submessage;
+ ok = args->GetString(1, &submessage);
+ DCHECK(ok);
+
+ base::ListValue* submessageArgs = new base::ListValue();
+ for (size_t i = 2; i < args->GetSize(); ++i) {
+ const base::Value* arg;
+ ok = args->Get(i, &arg);
+ DCHECK(ok);
+
+ base::Value* argCopy = arg->DeepCopy();
+ submessageArgs->Append(argCopy);
+ }
+
+ // call the submessage handler
+ base::Value* ret = NULL;
+ if (submessage == "requestClientInfo") {
+ ret = OnRequestClientInfo(submessageArgs);
+ } else if (submessage == "requestLogMessages") {
+ ret = OnRequestLogMessages(submessageArgs);
+ } else { // unrecognized submessage
+ NOTREACHED();
+ delete submessageArgs;
+ return;
+ }
+ delete submessageArgs;
+
+ // call BrowserBridge.onCallAsyncReply with result
+ if (ret) {
+ web_ui()->CallJavascriptFunction("browserBridge.onCallAsyncReply",
+ *requestId,
+ *ret);
+ delete ret;
+ } else {
+ web_ui()->CallJavascriptFunction("browserBridge.onCallAsyncReply",
+ *requestId);
+ }
+}
+
+void GpuMessageHandler::OnBrowserBridgeInitialized(
+ const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Watch for changes in GPUInfo
+ if (!observing_)
+ GpuDataManagerImpl::GetInstance()->AddObserver(this);
+ observing_ = true;
+
+ // Tell GpuDataManager it should have full GpuInfo. If the
+ // Gpu process has not run yet, this will trigger its launch.
+ GpuDataManagerImpl::GetInstance()->RequestCompleteGpuInfoIfNeeded();
+
+ // Run callback immediately in case the info is ready and no update in the
+ // future.
+ OnGpuInfoUpdate();
+}
+
+base::Value* GpuMessageHandler::OnRequestClientInfo(
+ const base::ListValue* list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("version", GetContentClient()->GetProduct());
+ dict->SetString("command_line",
+ CommandLine::ForCurrentProcess()->GetCommandLineString());
+ dict->SetString("operating_system",
+ base::SysInfo::OperatingSystemName() + " " +
+ base::SysInfo::OperatingSystemVersion());
+ dict->SetString("angle_revision", base::UintToString(BUILD_REVISION));
+ dict->SetString("graphics_backend", "Skia");
+ dict->SetString("blacklist_version",
+ GpuDataManagerImpl::GetInstance()->GetBlacklistVersion());
+ dict->SetString("driver_bug_list_version",
+ GpuDataManagerImpl::GetInstance()->GetDriverBugListVersion());
+
+ return dict;
+}
+
+base::Value* GpuMessageHandler::OnRequestLogMessages(const base::ListValue*) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ return GpuDataManagerImpl::GetInstance()->GetLogMessages();
+}
+
+void GpuMessageHandler::OnGpuInfoUpdate() {
+ // Get GPU Info.
+ scoped_ptr<base::DictionaryValue> gpu_info_val(GpuInfoAsDictionaryValue());
+
+ // Add in blacklisting features
+ base::Value* feature_status = GetFeatureStatus();
+ if (feature_status)
+ gpu_info_val->Set("featureStatus", feature_status);
+
+ // Send GPU Info to javascript.
+ web_ui()->CallJavascriptFunction("browserBridge.onGpuInfoUpdate",
+ *(gpu_info_val.get()));
+}
+
+void GpuMessageHandler::OnGpuSwitching() {
+ GpuDataManagerImpl::GetInstance()->RequestCompleteGpuInfoIfNeeded();
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GpuInternalsUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+GpuInternalsUI::GpuInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new GpuMessageHandler());
+
+ // Set up the chrome://gpu/ source.
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateGpuHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_internals_ui.h b/chromium/content/browser/gpu/gpu_internals_ui.h
new file mode 100644
index 00000000000..45912e30703
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_internals_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 The Chromium 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 CHROME_BROWSER_UI_WEBUI_GPU_INTERNALS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_GPU_INTERNALS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+class GpuInternalsUI : public WebUIController {
+ public:
+ explicit GpuInternalsUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GpuInternalsUI);
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_UI_WEBUI_GPU_INTERNALS_UI_H_
+
diff --git a/chromium/content/browser/gpu/gpu_ipc_browsertests.cc b/chromium/content/browser/gpu/gpu_ipc_browsertests.cc
new file mode 100644
index 00000000000..0ee93dba806
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_ipc_browsertests.cc
@@ -0,0 +1,43 @@
+// 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 "base/command_line.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/public/common/content_switches.h"
+#include "content/test/content_browser_test.h"
+#include "ui/gl/gl_switches.h"
+#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h"
+
+namespace {
+
+class ContextTestBase : public content::ContentBrowserTest {
+ public:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ CHECK(content::BrowserGpuChannelHostFactory::instance());
+ context_.reset(
+ content::WebGraphicsContext3DCommandBufferImpl::CreateOffscreenContext(
+ content::BrowserGpuChannelHostFactory::instance(),
+ WebKit::WebGraphicsContext3D::Attributes(),
+ GURL()));
+ CHECK(context_.get());
+ context_->makeContextCurrent();
+ ContentBrowserTest::SetUpOnMainThread();
+ }
+
+ virtual void TearDownOnMainThread() OVERRIDE {
+ // Must delete the context first.
+ context_.reset(NULL);
+ ContentBrowserTest::TearDownOnMainThread();
+ }
+
+ protected:
+ scoped_ptr<WebKit::WebGraphicsContext3D> context_;
+};
+
+} // namespace
+
+// Include the actual tests.
+#define CONTEXT_TEST_F IN_PROC_BROWSER_TEST_F
+#include "content/common/gpu/client/gpu_context_tests.h"
diff --git a/chromium/content/browser/gpu/gpu_memory_test.cc b/chromium/content/browser/gpu/gpu_memory_test.cc
new file mode 100644
index 00000000000..cb74a629299
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_memory_test.cc
@@ -0,0 +1,241 @@
+// 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 "base/callback.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "gpu/config/gpu_test_config.h"
+#include "net/base/net_util.h"
+
+namespace content {
+
+// Run the tests with a memory limit of 256MB, and give
+// and extra 4MB of wiggle-room for over-allocation.
+const char* kMemoryLimitMBSwitch = "256";
+const size_t kMemoryLimitMB = 256;
+const size_t kSingleTabLimitMB = 128;
+const size_t kWiggleRoomMB = 4;
+
+// Observer to report GPU memory usage when requested.
+class GpuMemoryBytesAllocatedObserver : public GpuDataManagerObserver {
+ public:
+ GpuMemoryBytesAllocatedObserver()
+ : bytes_allocated_(0) {
+ }
+
+ virtual ~GpuMemoryBytesAllocatedObserver() {
+ }
+
+ virtual void OnVideoMemoryUsageStatsUpdate(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats) OVERRIDE {
+ bytes_allocated_ = video_memory_usage_stats.bytes_allocated;
+ message_loop_runner_->Quit();
+ }
+
+ size_t GetBytesAllocated() {
+ message_loop_runner_ = new MessageLoopRunner;
+ GpuDataManager::GetInstance()->AddObserver(this);
+ GpuDataManager::GetInstance()->RequestVideoMemoryUsageStatsUpdate();
+ message_loop_runner_->Run();
+ GpuDataManager::GetInstance()->RemoveObserver(this);
+ message_loop_runner_ = NULL;
+ return bytes_allocated_;
+ }
+
+ private:
+ size_t bytes_allocated_;
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+};
+
+class GpuMemoryTest : public ContentBrowserTest {
+ public:
+ GpuMemoryTest()
+ : allow_tests_to_run_(false),
+ has_used_first_shell_(false) {
+ }
+ virtual ~GpuMemoryTest() {
+ }
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ base::FilePath test_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir));
+ gpu_test_dir_ = test_dir.AppendASCII("gpu");
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(switches::kEnableLogging);
+ command_line->AppendSwitch(switches::kForceCompositingMode);
+ command_line->AppendSwitchASCII(switches::kForceGpuMemAvailableMb,
+ kMemoryLimitMBSwitch);
+ // Only run this on GPU bots for now. These tests should work with
+ // any GPU process, but may be slow.
+ if (command_line->HasSwitch(switches::kUseGpuInTests)) {
+ allow_tests_to_run_ = true;
+ }
+ // Don't enable these tests on Android just yet (they use lots of memory and
+ // may not be stable).
+#if defined(OS_ANDROID)
+ allow_tests_to_run_ = false;
+#endif
+ }
+
+ enum PageType {
+ PAGE_CSS3D,
+ PAGE_WEBGL,
+ };
+
+ // Load a page and consume a specified amount of GPU memory.
+ void LoadPage(Shell* tab_to_load,
+ PageType page_type,
+ size_t mb_to_use) {
+ base::FilePath url;
+ switch (page_type) {
+ case PAGE_CSS3D:
+ url = gpu_test_dir_.AppendASCII("mem_css3d.html");
+ break;
+ case PAGE_WEBGL:
+ url = gpu_test_dir_.AppendASCII("mem_webgl.html");
+ break;
+ }
+
+ NavigateToURL(tab_to_load, net::FilePathToFileURL(url));
+ std::ostringstream js_call;
+ js_call << "useGpuMemory(";
+ js_call << mb_to_use;
+ js_call << ");";
+ std::string message;
+ ASSERT_TRUE(ExecuteScriptInFrameAndExtractString(
+ tab_to_load->web_contents(), std::string(), js_call.str(), &message));
+ EXPECT_EQ("DONE_USE_GPU_MEMORY", message);
+ }
+
+ // Create a new tab.
+ Shell* CreateNewTab() {
+ // The ContentBrowserTest will create one shell by default, use that one
+ // first so that we don't confuse the memory manager into thinking there
+ // are more windows than there are.
+ Shell* new_tab = has_used_first_shell_ ? CreateBrowser() : shell();
+ has_used_first_shell_ = true;
+ tabs_.insert(new_tab);
+ visible_tabs_.insert(new_tab);
+ return new_tab;
+ }
+
+ void SetTabBackgrounded(Shell* tab_to_background) {
+ ASSERT_TRUE(
+ visible_tabs_.find(tab_to_background) != visible_tabs_.end());
+ visible_tabs_.erase(tab_to_background);
+ tab_to_background->web_contents()->WasHidden();
+ }
+
+ bool MemoryUsageInRange(size_t low, size_t high) {
+ FinishGpuMemoryChanges();
+ size_t memory_usage_bytes = GetMemoryUsageMbytes();
+
+ // If it's not immediately the case that low <= usage <= high, then
+ // allow
+ // Because we haven't implemented the full delay in FinishGpuMemoryChanges,
+ // keep re-reading the GPU memory usage for 2 seconds before declaring
+ // failure.
+ base::Time start_time = base::Time::Now();
+ while (low > memory_usage_bytes || memory_usage_bytes > high) {
+ memory_usage_bytes = GetMemoryUsageMbytes();
+ base::TimeDelta delta = base::Time::Now() - start_time;
+ if (delta.InMilliseconds() >= 2000)
+ break;
+ }
+
+ return (low <= memory_usage_bytes && memory_usage_bytes <= high);
+ }
+
+ bool AllowTestsToRun() const {
+ return allow_tests_to_run_;
+ }
+
+ private:
+ void FinishGpuMemoryChanges() {
+ // This should wait until all effects of memory management complete.
+ // We will need to wait until all
+ // 1. pending commits from the main thread to the impl thread in the
+ // compositor complete (for visible compositors).
+ // 2. allocations that the renderer's impl thread will make due to the
+ // compositor and WebGL are completed.
+ // 3. pending GpuMemoryManager::Manage() calls to manage are made.
+ // 4. renderers' OnMemoryAllocationChanged callbacks in response to
+ // manager are made.
+ // Each step in this sequence can cause trigger the next (as a 1-2-3-4-1
+ // cycle), so we will need to pump this cycle until it stabilizes.
+
+ // Pump the cycle 8 times (in principle it could take an infinite number
+ // of iterations to settle).
+ for (size_t pump_it = 0; pump_it < 8; ++pump_it) {
+ // Wait for a RequestAnimationFrame to complete from all visible tabs
+ // for stage 1 of the cycle.
+ for (std::set<Shell*>::iterator it = visible_tabs_.begin();
+ it != visible_tabs_.end();
+ ++it) {
+ std::string js_call(
+ "window.webkitRequestAnimationFrame(function() {"
+ " domAutomationController.setAutomationId(1);"
+ " domAutomationController.send(\"DONE_RAF\");"
+ "})");
+ std::string message;
+ ASSERT_TRUE(ExecuteScriptInFrameAndExtractString(
+ (*it)->web_contents(), std::string(), js_call, &message));
+ EXPECT_EQ("DONE_RAF", message);
+ }
+ // TODO(ccameron): send an IPC from Browser -> Renderer (delay it until
+ // painting finishes) -> GPU process (delay it until any pending manages
+ // happen) -> All Renderers -> Browser to flush parts 2, 3, and 4.
+ }
+ }
+
+ size_t GetMemoryUsageMbytes() {
+ GpuMemoryBytesAllocatedObserver observer;
+ observer.GetBytesAllocated();
+ return observer.GetBytesAllocated() / 1048576;
+ }
+
+ bool allow_tests_to_run_;
+ std::set<Shell*> tabs_;
+ std::set<Shell*> visible_tabs_;
+ bool has_used_first_shell_;
+ base::FilePath gpu_test_dir_;
+};
+
+#if defined(OS_LINUX) && !defined(NDEBUG)
+// http://crbug.com/254724
+#define IF_NOT_DEBUG_LINUX(x) DISABLED_ ## x
+#else
+#define IF_NOT_DEBUG_LINUX(x) x
+#endif
+
+// When trying to load something that doesn't fit into our total GPU memory
+// limit, we shouldn't exceed that limit.
+IN_PROC_BROWSER_TEST_F(GpuMemoryTest,
+ IF_NOT_DEBUG_LINUX(SingleWindowDoesNotExceedLimit)) {
+ if (!AllowTestsToRun())
+ return;
+
+ Shell* tab = CreateNewTab();
+ LoadPage(tab, PAGE_CSS3D, kMemoryLimitMB);
+ // Make sure that the CSS3D page maxes out a single tab's budget (otherwise
+ // the test doesn't test anything) but still stays under the limit.
+ EXPECT_TRUE(MemoryUsageInRange(
+ kSingleTabLimitMB - kWiggleRoomMB,
+ kMemoryLimitMB + kWiggleRoomMB));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_pixel_browsertest.cc b/chromium/content/browser/gpu/gpu_pixel_browsertest.cc
new file mode 100644
index 00000000000..5909321377f
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_pixel_browsertest.cc
@@ -0,0 +1,568 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "gpu/config/gpu_test_config.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/size.h"
+#include "ui/gl/gl_switches.h"
+#include "ui/snapshot/snapshot.h"
+
+namespace {
+
+enum ReferenceImageOption {
+ kReferenceImageLocal,
+ kReferenceImageCheckedIn,
+ kReferenceImageNone // Only check a few key pixels.
+};
+
+struct ReferencePixel {
+ int x, y;
+ unsigned char r, g, b;
+};
+
+// Command line flag for overriding the default location for putting generated
+// test images that do not match references.
+const char kGeneratedDir[] = "generated-dir";
+// Command line flag for overriding the default location for reference images.
+const char kReferenceDir[] = "reference-dir";
+// Command line flag for Chromium build revision.
+const char kBuildRevision[] = "build-revision";
+
+// Reads and decodes a PNG image to a bitmap. Returns true on success. The PNG
+// should have been encoded using |gfx::PNGCodec::Encode|.
+bool ReadPNGFile(const base::FilePath& file_path, SkBitmap* bitmap) {
+ DCHECK(bitmap);
+ base::FilePath abs_path(base::MakeAbsoluteFilePath(file_path));
+ if (abs_path.empty())
+ return false;
+
+ std::string png_data;
+ return file_util::ReadFileToString(abs_path, &png_data) &&
+ gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&png_data[0]),
+ png_data.length(),
+ bitmap);
+}
+
+// Encodes a bitmap into a PNG and write to disk. Returns true on success. The
+// parent directory does not have to exist.
+bool WritePNGFile(const SkBitmap& bitmap, const base::FilePath& file_path) {
+ std::vector<unsigned char> png_data;
+ if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &png_data) &&
+ file_util::CreateDirectory(file_path.DirName())) {
+ int bytes_written = file_util::WriteFile(
+ file_path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
+ if (bytes_written == static_cast<int>(png_data.size()))
+ return true;
+ }
+ return false;
+}
+
+// Write an empty file, whose name indicates the chrome revision when the ref
+// image was generated.
+bool WriteREVFile(const base::FilePath& file_path) {
+ if (file_util::CreateDirectory(file_path.DirName())) {
+ char one_byte = 0;
+ int bytes_written = file_util::WriteFile(file_path, &one_byte, 1);
+ if (bytes_written == 1)
+ return true;
+ }
+ return false;
+}
+
+} // namespace anonymous
+
+namespace content {
+
+// Test fixture for GPU image comparison tests.
+// TODO(kkania): Document how to add to/modify these tests.
+class GpuPixelBrowserTest : public ContentBrowserTest {
+ public:
+ GpuPixelBrowserTest()
+ : ref_img_revision_(0),
+ ref_img_revision_no_older_than_(0),
+ ref_img_option_(kReferenceImageNone) {
+ }
+
+ virtual void SetUp() {
+ // We expect real pixel output for these tests.
+ UseRealGLContexts();
+
+ ContentBrowserTest::SetUp();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitchASCII(switches::kTestGLLib,
+ "libllvmpipe.so");
+ }
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ContentBrowserTest::SetUpInProcessBrowserTestFixture();
+
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kUseGpuInTests))
+ ref_img_option_ = kReferenceImageLocal;
+
+ if (command_line->HasSwitch(kBuildRevision))
+ build_revision_ = command_line->GetSwitchValueASCII(kBuildRevision);
+
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_data_dir_));
+ test_data_dir_ = test_data_dir_.AppendASCII("gpu");
+
+ if (command_line->HasSwitch(kGeneratedDir))
+ generated_img_dir_ = command_line->GetSwitchValuePath(kGeneratedDir);
+ else
+ generated_img_dir_ = test_data_dir_.AppendASCII("generated");
+
+ switch (ref_img_option_) {
+ case kReferenceImageLocal:
+ if (command_line->HasSwitch(kReferenceDir))
+ ref_img_dir_ = command_line->GetSwitchValuePath(kReferenceDir);
+ else
+ ref_img_dir_ = test_data_dir_.AppendASCII("gpu_reference");
+ break;
+ case kReferenceImageCheckedIn:
+ ref_img_dir_ = test_data_dir_.AppendASCII("llvmpipe_reference");
+ break;
+ default:
+ break;
+ }
+
+ test_name_ = testing::UnitTest::GetInstance()->current_test_info()->name();
+ const char* test_status_prefixes[] = {
+ "DISABLED_", "FLAKY_", "FAILS_", "MANUAL_"};
+ for (size_t i = 0; i < arraysize(test_status_prefixes); ++i) {
+ ReplaceFirstSubstringAfterOffset(
+ &test_name_, 0, test_status_prefixes[i], std::string());
+ }
+ }
+
+ // If the existing ref image was saved from an revision older than the
+ // ref_img_update_revision, refresh the ref image.
+ void RunPixelTest(const gfx::Size& tab_container_size,
+ const base::FilePath& url,
+ int64 ref_img_update_revision,
+ const ReferencePixel* ref_pixels,
+ size_t ref_pixel_count) {
+ if (ref_img_option_ == kReferenceImageLocal) {
+ ref_img_revision_no_older_than_ = ref_img_update_revision;
+ ObtainLocalRefImageRevision();
+ }
+
+ DOMMessageQueue message_queue;
+ NavigateToURL(shell(), net::FilePathToFileURL(url));
+
+ std::string message;
+ // Wait for notification that page is loaded.
+ ASSERT_TRUE(message_queue.WaitForMessage(&message));
+ EXPECT_STREQ("\"SUCCESS\"", message.c_str()) << message;
+
+ SkBitmap bitmap;
+ ASSERT_TRUE(TabSnapShotToImage(&bitmap, tab_container_size));
+ bool same_pixels = true;
+ if (ref_img_option_ == kReferenceImageNone && ref_pixels && ref_pixel_count)
+ same_pixels = ComparePixels(bitmap, ref_pixels, ref_pixel_count);
+ else
+ same_pixels = CompareImages(bitmap);
+ EXPECT_TRUE(same_pixels);
+ }
+
+ const base::FilePath& test_data_dir() const {
+ return test_data_dir_;
+ }
+
+ private:
+ base::FilePath test_data_dir_;
+ base::FilePath generated_img_dir_;
+ base::FilePath ref_img_dir_;
+ int64 ref_img_revision_;
+ std::string build_revision_;
+ // The name of the test, with any special prefixes dropped.
+ std::string test_name_;
+
+ // Any local ref image generated from older revision is ignored.
+ int64 ref_img_revision_no_older_than_;
+
+ // Whether use locally generated ref images, or checked in ref images, or
+ // simply check a few key pixels.
+ ReferenceImageOption ref_img_option_;
+
+ // Compares the generated bitmap with the appropriate reference image on disk.
+ // Returns true iff the images were the same.
+ //
+ // If no valid reference image exists, save the generated bitmap to the disk.
+ // The image format is:
+ // <test_name>_<revision>.png
+ // E.g.,
+ // WebGLTeapot_19762.png
+ // The number is the chromium revision that generated the image.
+ //
+ // On failure or on ref image generation, the image and diff image will be
+ // written to disk. The formats are:
+ // FAIL_<ref_image_name>, DIFF_<ref_image_name>
+ // E.g.,
+ // FAIL_WebGLTeapot_19762.png, DIFF_WebGLTeapot_19762.png
+ bool CompareImages(const SkBitmap& gen_bmp) {
+ SkBitmap ref_bmp_on_disk;
+
+ base::FilePath img_path = ref_img_dir_.AppendASCII(test_name_ + ".png");
+ bool found_ref_img = ReadPNGFile(img_path, &ref_bmp_on_disk);
+
+ if (!found_ref_img && ref_img_option_ == kReferenceImageCheckedIn) {
+ LOG(ERROR) << "Couldn't find reference image: "
+ << img_path.value();
+ // No image to compare to, exit early.
+ return false;
+ }
+
+ const SkBitmap* ref_bmp;
+ bool save_gen = false;
+ bool save_diff = true;
+ bool rt = true;
+
+ if ((ref_img_revision_ <= 0 && ref_img_option_ == kReferenceImageLocal) ||
+ !found_ref_img) {
+ base::FilePath rev_path = ref_img_dir_.AppendASCII(
+ test_name_ + "_" + build_revision_ + ".rev");
+ if (!WritePNGFile(gen_bmp, img_path)) {
+ LOG(ERROR) << "Can't save generated image to: "
+ << img_path.value()
+ << " as future reference.";
+ rt = false;
+ } else {
+ LOG(INFO) << "Saved reference image to: "
+ << img_path.value();
+ }
+ if (rt) {
+ if (!WriteREVFile(rev_path)) {
+ LOG(ERROR) << "Can't save revision file to: "
+ << rev_path.value();
+ rt = false;
+ base::DeleteFile(img_path, false);
+ } else {
+ LOG(INFO) << "Saved revision file to: "
+ << rev_path.value();
+ }
+ }
+ if (ref_img_revision_ > 0) {
+ LOG(ERROR) << "Can't read the local ref image: "
+ << img_path.value()
+ << ", reset it.";
+ rt = false;
+ }
+ // If we re-generate the ref image, we save the gen and diff images so
+ // the ref image can be uploaded to the server and be viewed later.
+ save_gen = true;
+ save_diff = true;
+ ref_bmp = &gen_bmp;
+ } else {
+ ref_bmp = &ref_bmp_on_disk;
+ }
+
+ SkBitmap diff_bmp;
+ if (ref_bmp->width() != gen_bmp.width() ||
+ ref_bmp->height() != gen_bmp.height()) {
+ LOG(ERROR)
+ << "Dimensions do not match (Expected) vs (Actual):"
+ << "(" << ref_bmp->width() << "x" << ref_bmp->height()
+ << ") vs. "
+ << "(" << gen_bmp.width() << "x" << gen_bmp.height() << ")";
+ if (ref_img_option_ == kReferenceImageLocal)
+ save_gen = true;
+ rt = false;
+ } else {
+ // Compare pixels and create a simple diff image.
+ int diff_pixels_count = 0;
+ diff_bmp.setConfig(SkBitmap::kARGB_8888_Config,
+ gen_bmp.width(), gen_bmp.height());
+ diff_bmp.allocPixels();
+ diff_bmp.eraseColor(SK_ColorWHITE);
+ SkAutoLockPixels lock_bmp(gen_bmp);
+ SkAutoLockPixels lock_ref_bmp(*ref_bmp);
+ SkAutoLockPixels lock_diff_bmp(diff_bmp);
+ // The reference images were saved with no alpha channel. Use the mask to
+ // set alpha to 0.
+ uint32_t kAlphaMask = 0x00FFFFFF;
+ for (int x = 0; x < gen_bmp.width(); ++x) {
+ for (int y = 0; y < gen_bmp.height(); ++y) {
+ if ((*gen_bmp.getAddr32(x, y) & kAlphaMask) !=
+ (*ref_bmp->getAddr32(x, y) & kAlphaMask)) {
+ ++diff_pixels_count;
+ *diff_bmp.getAddr32(x, y) = 192 << 16; // red
+ }
+ }
+ }
+ if (diff_pixels_count > 0) {
+ LOG(ERROR) << diff_pixels_count
+ << " pixels do not match.";
+ if (ref_img_option_ == kReferenceImageLocal) {
+ save_gen = true;
+ save_diff = true;
+ }
+ rt = false;
+ }
+ }
+
+ std::string ref_img_filename = img_path.BaseName().MaybeAsASCII();
+ if (save_gen) {
+ base::FilePath img_fail_path = generated_img_dir_.AppendASCII(
+ "FAIL_" + ref_img_filename);
+ if (!WritePNGFile(gen_bmp, img_fail_path)) {
+ LOG(ERROR) << "Can't save generated image to: "
+ << img_fail_path.value();
+ } else {
+ LOG(INFO) << "Saved generated image to: "
+ << img_fail_path.value();
+ }
+ }
+ if (save_diff) {
+ base::FilePath img_diff_path = generated_img_dir_.AppendASCII(
+ "DIFF_" + ref_img_filename);
+ if (!WritePNGFile(diff_bmp, img_diff_path)) {
+ LOG(ERROR) << "Can't save generated diff image to: "
+ << img_diff_path.value();
+ } else {
+ LOG(INFO) << "Saved difference image to: "
+ << img_diff_path.value();
+ }
+ }
+ return rt;
+ }
+
+ bool ComparePixels(const SkBitmap& gen_bmp,
+ const ReferencePixel* ref_pixels,
+ size_t ref_pixel_count) {
+ SkAutoLockPixels lock_bmp(gen_bmp);
+
+ for (size_t i = 0; i < ref_pixel_count; ++i) {
+ int x = ref_pixels[i].x;
+ int y = ref_pixels[i].y;
+ unsigned char r = ref_pixels[i].r;
+ unsigned char g = ref_pixels[i].g;
+ unsigned char b = ref_pixels[i].b;
+
+ DCHECK(x >= 0 && x < gen_bmp.width() && y >= 0 && y < gen_bmp.height());
+
+ unsigned char* rgba = reinterpret_cast<unsigned char*>(
+ gen_bmp.getAddr32(x, y));
+ DCHECK(rgba);
+ if (rgba[0] != b || rgba[1] != g || rgba[2] != r) {
+ std::string error_message = base::StringPrintf(
+ "pixel(%d,%d) expects [%u,%u,%u], but gets [%u,%u,%u] instead",
+ x, y, r, g, b, rgba[0], rgba[1], rgba[2]);
+ LOG(ERROR) << error_message.c_str();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Take snapshot of the tab, encode it as PNG, and save to a SkBitmap.
+ bool TabSnapShotToImage(SkBitmap* bitmap, const gfx::Size& size) {
+ CHECK(bitmap);
+ std::vector<unsigned char> png;
+
+ gfx::Rect snapshot_bounds(size);
+ RenderViewHost* view_host = shell()->web_contents()->GetRenderViewHost();
+ if (!ui::GrabViewSnapshot(view_host->GetView()->GetNativeView(),
+ &png, snapshot_bounds)) {
+ LOG(ERROR) << "ui::GrabViewSnapShot() failed";
+ return false;
+ }
+
+ if (!gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&*png.begin()),
+ png.size(), bitmap)) {
+ LOG(ERROR) << "Decode PNG to a SkBitmap failed";
+ return false;
+ }
+ return true;
+ }
+
+ // If no valid local revision file is located, the ref_img_revision_ is 0.
+ void ObtainLocalRefImageRevision() {
+ base::FilePath filter;
+ filter = filter.AppendASCII(test_name_ + "_*.rev");
+ base::FileEnumerator locator(ref_img_dir_,
+ false, // non recursive
+ base::FileEnumerator::FILES,
+ filter.value());
+ int64 max_revision = 0;
+ std::vector<base::FilePath> outdated_revs;
+ for (base::FilePath full_path = locator.Next();
+ !full_path.empty();
+ full_path = locator.Next()) {
+ std::string filename =
+ full_path.BaseName().RemoveExtension().MaybeAsASCII();
+ std::string revision_string =
+ filename.substr(test_name_.length() + 1);
+ int64 revision = 0;
+ bool converted = base::StringToInt64(revision_string, &revision);
+ if (!converted)
+ continue;
+ if (revision < ref_img_revision_no_older_than_ ||
+ revision < max_revision) {
+ outdated_revs.push_back(full_path);
+ continue;
+ }
+ max_revision = revision;
+ }
+ ref_img_revision_ = max_revision;
+ for (size_t i = 0; i < outdated_revs.size(); ++i)
+ base::DeleteFile(outdated_revs[i], false);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(GpuPixelBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_WebGLGreenTriangle) {
+ // If test baseline needs to be updated after a given revision, update the
+ // following number. If no revision requirement, then 0.
+ const int64 ref_img_revision_update = 123489;
+
+ const ReferencePixel ref_pixels[] = {
+ // x, y, r, g, b
+ {50, 100, 0, 0, 0},
+ {100, 100, 0, 255, 0},
+ {150, 100, 0, 0, 0},
+ {50, 150, 0, 255, 0},
+ {100, 150, 0, 255, 0},
+ {150, 150, 0, 255, 0}
+ };
+ const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
+
+ gfx::Size container_size(400, 300);
+ base::FilePath url =
+ test_data_dir().AppendASCII("pixel_webgl.html");
+ RunPixelTest(container_size, url, ref_img_revision_update,
+ ref_pixels, ref_pixel_count);
+}
+
+IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_CSS3DBlueBox) {
+ // If test baseline needs to be updated after a given revision, update the
+ // following number. If no revision requirement, then 0.
+ const int64 ref_img_revision_update = 209827;
+
+ const ReferencePixel ref_pixels[] = {
+ // x, y, r, g, b
+ {70, 50, 0, 0, 255},
+ {150, 50, 0, 0, 0},
+ {70, 90, 0, 0, 255},
+ {150, 90, 0, 0, 255},
+ {70, 125, 0, 0, 255},
+ {150, 125, 0, 0, 0}
+ };
+ const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
+
+ gfx::Size container_size(400, 300);
+ base::FilePath url =
+ test_data_dir().AppendASCII("pixel_css3d.html");
+ RunPixelTest(container_size, url, ref_img_revision_update,
+ ref_pixels, ref_pixel_count);
+}
+
+IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_Canvas2DRedBoxHD) {
+ // If test baseline needs to be updated after a given revision, update the
+ // following number. If no revision requirement, then 0.
+ const int64 ref_img_revision_update = 123489;
+
+ const ReferencePixel ref_pixels[] = {
+ // x, y, r, g, b
+ {40, 100, 0, 0, 0},
+ {60, 100, 127, 0, 0},
+ {140, 100, 127, 0, 0},
+ {160, 100, 0, 0, 0}
+ };
+ const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
+
+ gfx::Size container_size(400, 300);
+ base::FilePath url =
+ test_data_dir().AppendASCII("pixel_canvas2d.html");
+ RunPixelTest(container_size, url, ref_img_revision_update,
+ ref_pixels, ref_pixel_count);
+}
+
+class GpuPixelTestCanvas2DSD : public GpuPixelBrowserTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ GpuPixelBrowserTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(GpuPixelTestCanvas2DSD, MANUAL_Canvas2DRedBoxSD) {
+ // If test baseline needs to be updated after a given revision, update the
+ // following number. If no revision requirement, then 0.
+ const int64 ref_img_revision_update = 123489;
+
+ const ReferencePixel ref_pixels[] = {
+ // x, y, r, g, b
+ {40, 100, 0, 0, 0},
+ {60, 100, 127, 0, 0},
+ {140, 100, 127, 0, 0},
+ {160, 100, 0, 0, 0}
+ };
+ const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
+
+ gfx::Size container_size(400, 300);
+ base::FilePath url =
+ test_data_dir().AppendASCII("pixel_canvas2d.html");
+ RunPixelTest(container_size, url, ref_img_revision_update,
+ ref_pixels, ref_pixel_count);
+}
+
+class GpuPixelTestBrowserPlugin : public GpuPixelBrowserTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ GpuPixelBrowserTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kEnableBrowserPluginForAllViewTypes);
+ }
+};
+
+// TODO(fsamuel): re-enable as MANUAL_BrowserPluginBlueBox: crbug.com/166165
+IN_PROC_BROWSER_TEST_F(GpuPixelTestBrowserPlugin,
+ DISABLED_BrowserPluginBlueBox) {
+ // If test baseline needs to be updated after a given revision, update the
+ // following number. If no revision requirement, then 0.
+ const int64 ref_img_revision_update = 209445;
+
+ const ReferencePixel ref_pixels[] = {
+ // x, y, r, g, b
+ {70, 50, 0, 0, 255},
+ {150, 50, 0, 0, 0},
+ {70, 90, 0, 0, 255},
+ {150, 90, 0, 0, 255},
+ {70, 125, 0, 0, 255},
+ {150, 125, 0, 0, 0}
+ };
+ const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
+
+ gfx::Size container_size(400, 300);
+ base::FilePath url =
+ test_data_dir().AppendASCII("pixel_browser_plugin.html");
+ RunPixelTest(container_size, url, ref_img_revision_update,
+ ref_pixels, ref_pixel_count);
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/gpu/gpu_process_host.cc b/chromium/content/browser/gpu/gpu_process_host.cc
new file mode 100644
index 00000000000..8cbb7aa1d82
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_process_host.cc
@@ -0,0 +1,1285 @@
+// 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 "content/browser/gpu/gpu_process_host.h"
+
+#include "base/base64.h"
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/sha1.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/gpu/shader_disk_cache.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/view_messages.h"
+#include "content/gpu/gpu_child_thread.h"
+#include "content/gpu/gpu_process.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_switches.h"
+#include "ui/base/latency_info.h"
+#include "ui/gl/gl_switches.h"
+
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "content/common/sandbox_win.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#include "sandbox/win/src/sandbox_policy.h"
+#include "ui/surface/accelerated_surface_win.h"
+#endif
+
+namespace content {
+
+bool GpuProcessHost::gpu_enabled_ = true;
+bool GpuProcessHost::hardware_gpu_enabled_ = true;
+
+namespace {
+
+enum GPUProcessLifetimeEvent {
+ LAUNCHED,
+ DIED_FIRST_TIME,
+ DIED_SECOND_TIME,
+ DIED_THIRD_TIME,
+ DIED_FOURTH_TIME,
+ GPU_PROCESS_LIFETIME_EVENT_MAX = 100
+};
+
+// Indexed by GpuProcessKind. There is one of each kind maximum. This array may
+// only be accessed from the IO thread.
+GpuProcessHost* g_gpu_process_hosts[GpuProcessHost::GPU_PROCESS_KIND_COUNT];
+
+
+void SendGpuProcessMessage(GpuProcessHost::GpuProcessKind kind,
+ CauseForGpuLaunch cause,
+ IPC::Message* message) {
+ GpuProcessHost* host = GpuProcessHost::Get(kind, cause);
+ if (host) {
+ host->Send(message);
+ } else {
+ delete message;
+ }
+}
+
+void AcceleratedSurfaceBuffersSwappedCompletedForGPU(int host_id,
+ int route_id,
+ bool alive) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AcceleratedSurfaceBuffersSwappedCompletedForGPU,
+ host_id,
+ route_id,
+ alive));
+ return;
+ }
+
+ GpuProcessHost* host = GpuProcessHost::FromID(host_id);
+ if (host) {
+ if (alive) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ host->Send(
+ new AcceleratedSurfaceMsg_BufferPresented(route_id, ack_params));
+ } else {
+ host->ForceShutdown();
+ }
+ }
+}
+
+#if defined(OS_WIN)
+// This sends a ViewMsg_SwapBuffers_ACK directly to the renderer process
+// (RenderWidget).
+void AcceleratedSurfaceBuffersSwappedCompletedForRenderer(
+ int surface_id,
+ base::TimeTicks timebase,
+ base::TimeDelta interval,
+ const ui::LatencyInfo& latency_info) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&AcceleratedSurfaceBuffersSwappedCompletedForRenderer,
+ surface_id, timebase, interval, latency_info));
+ return;
+ }
+
+ int render_process_id = 0;
+ int render_widget_id = 0;
+ if (!GpuSurfaceTracker::Get()->GetRenderWidgetIDForSurface(
+ surface_id, &render_process_id, &render_widget_id)) {
+ RenderWidgetHostImpl::CompositorFrameDrawn(latency_info);
+ return;
+ }
+ RenderWidgetHost* rwh =
+ RenderWidgetHost::FromID(render_process_id, render_widget_id);
+ if (!rwh)
+ return;
+ RenderWidgetHostImpl::From(rwh)->AcknowledgeSwapBuffersToRenderer();
+ if (interval != base::TimeDelta())
+ RenderWidgetHostImpl::From(rwh)->UpdateVSyncParameters(timebase, interval);
+ RenderWidgetHostImpl::From(rwh)->FrameSwapped(latency_info);
+ RenderWidgetHostImpl::From(rwh)->DidReceiveRendererFrame();
+}
+
+void AcceleratedSurfaceBuffersSwappedCompleted(
+ int host_id,
+ int route_id,
+ int surface_id,
+ bool alive,
+ base::TimeTicks timebase,
+ base::TimeDelta interval,
+ const ui::LatencyInfo& latency_info) {
+ AcceleratedSurfaceBuffersSwappedCompletedForGPU(host_id, route_id,
+ alive);
+ AcceleratedSurfaceBuffersSwappedCompletedForRenderer(surface_id, timebase,
+ interval, latency_info);
+}
+
+// NOTE: changes to this class need to be reviewed by the security team.
+class GpuSandboxedProcessLauncherDelegate
+ : public SandboxedProcessLauncherDelegate {
+ public:
+ explicit GpuSandboxedProcessLauncherDelegate(CommandLine* cmd_line)
+ : cmd_line_(cmd_line) {}
+ virtual ~GpuSandboxedProcessLauncherDelegate() {}
+
+ virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE {
+ if (cmd_line_->HasSwitch(switches::kDisableGpuSandbox)) {
+ *in_sandbox = false;
+ DVLOG(1) << "GPU sandbox is disabled";
+ }
+ }
+
+ virtual void PreSandbox(bool* disable_default_policy,
+ base::FilePath* exposed_dir) OVERRIDE {
+ *disable_default_policy = true;
+ }
+
+ // For the GPU process we gotten as far as USER_LIMITED. The next level
+ // which is USER_RESTRICTED breaks both the DirectX backend and the OpenGL
+ // backend. Note that the GPU process is connected to the interactive
+ // desktop.
+ virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
+ bool* success) {
+ if (base::win::GetVersion() > base::win::VERSION_XP) {
+ if (cmd_line_->GetSwitchValueASCII(switches::kUseGL) ==
+ gfx::kGLImplementationDesktopName) {
+ // Open GL path.
+ policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
+ sandbox::USER_LIMITED);
+ SetJobLevel(*cmd_line_, sandbox::JOB_UNPROTECTED, 0, policy);
+ policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
+ } else {
+ if (cmd_line_->GetSwitchValueASCII(switches::kUseGL) ==
+ gfx::kGLImplementationSwiftShaderName ||
+ cmd_line_->HasSwitch(switches::kReduceGpuSandbox) ||
+ cmd_line_->HasSwitch(switches::kDisableImageTransportSurface)) {
+ // Swiftshader path.
+ policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
+ sandbox::USER_LIMITED);
+ } else {
+ // Angle + DirectX path.
+ policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
+ sandbox::USER_RESTRICTED);
+ // This is a trick to keep the GPU out of low-integrity processes. It
+ // starts at low-integrity for UIPI to work, then drops below
+ // low-integrity after warm-up.
+ policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED);
+ }
+
+ // UI restrictions break when we access Windows from outside our job.
+ // However, we don't want a proxy window in this process because it can
+ // introduce deadlocks where the renderer blocks on the gpu, which in
+ // turn blocks on the browser UI thread. So, instead we forgo a window
+ // message pump entirely and just add job restrictions to prevent child
+ // processes.
+ SetJobLevel(*cmd_line_,
+ sandbox::JOB_LIMITED_USER,
+ JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS |
+ JOB_OBJECT_UILIMIT_DESKTOP |
+ JOB_OBJECT_UILIMIT_EXITWINDOWS |
+ JOB_OBJECT_UILIMIT_DISPLAYSETTINGS,
+ policy);
+
+ policy->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
+ }
+ } else {
+ SetJobLevel(*cmd_line_, sandbox::JOB_UNPROTECTED, 0, policy);
+ policy->SetTokenLevel(sandbox::USER_UNPROTECTED,
+ sandbox::USER_LIMITED);
+ }
+
+ // Allow the server side of GPU sockets, which are pipes that have
+ // the "chrome.gpu" namespace and an arbitrary suffix.
+ sandbox::ResultCode result = policy->AddRule(
+ sandbox::TargetPolicy::SUBSYS_NAMED_PIPES,
+ sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY,
+ L"\\\\.\\pipe\\chrome.gpu.*");
+ if (result != sandbox::SBOX_ALL_OK) {
+ *success = false;
+ return;
+ }
+
+ // Block this DLL even if it is not loaded by the browser process.
+ policy->AddDllToUnload(L"cmsetac.dll");
+
+#ifdef USE_AURA
+ // GPU also needs to add sections to the browser for aura
+ // TODO(jschuh): refactor the GPU channel to remove this. crbug.com/128786
+ result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES,
+ sandbox::TargetPolicy::HANDLES_DUP_BROKER,
+ L"Section");
+ if (result != sandbox::SBOX_ALL_OK) {
+ *success = false;
+ return;
+ }
+#endif
+
+ if (cmd_line_->HasSwitch(switches::kEnableLogging)) {
+ string16 log_file_path = logging::GetLogFileFullPath();
+ if (!log_file_path.empty()) {
+ result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
+ sandbox::TargetPolicy::FILES_ALLOW_ANY,
+ log_file_path.c_str());
+ if (result != sandbox::SBOX_ALL_OK) {
+ *success = false;
+ return;
+ }
+ }
+ }
+ }
+
+ private:
+ CommandLine* cmd_line_;
+};
+#endif // defined(OS_WIN)
+
+} // anonymous namespace
+
+// Single process not supported in multiple dll mode currently.
+#if !defined(CHROME_MULTIPLE_DLL)
+// This class creates a GPU thread (instead of a GPU process), when running
+// with --in-process-gpu or --single-process.
+class GpuMainThread : public base::Thread {
+ public:
+ explicit GpuMainThread(const std::string& channel_id)
+ : base::Thread("Chrome_InProcGpuThread"),
+ channel_id_(channel_id),
+ gpu_process_(NULL) {
+ }
+
+ virtual ~GpuMainThread() {
+ Stop();
+ }
+
+ protected:
+ virtual void Init() OVERRIDE {
+ gpu_process_ = new GpuProcess();
+ // The process object takes ownership of the thread object, so do not
+ // save and delete the pointer.
+ gpu_process_->set_main_thread(new GpuChildThread(channel_id_));
+ }
+
+ virtual void CleanUp() OVERRIDE {
+ delete gpu_process_;
+ }
+
+ private:
+ std::string channel_id_;
+ // Deleted in CleanUp() on the gpu thread, so don't use smart pointers.
+ GpuProcess* gpu_process_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuMainThread);
+};
+#endif // !CHROME_MULTIPLE_DLL
+
+// static
+bool GpuProcessHost::ValidateHost(GpuProcessHost* host) {
+ if (!host)
+ return false;
+
+ // The Gpu process is invalid if it's not using SwiftShader, the card is
+ // blacklisted, and we can kill it and start over.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessGPU) ||
+ (host->valid_ &&
+ (host->swiftshader_rendering_ ||
+ !GpuDataManagerImpl::GetInstance()->ShouldUseSwiftShader()))) {
+ return true;
+ }
+
+ host->ForceShutdown();
+ return false;
+}
+
+// static
+GpuProcessHost* GpuProcessHost::Get(GpuProcessKind kind,
+ CauseForGpuLaunch cause) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Don't grant further access to GPU if it is not allowed.
+ GpuDataManagerImpl* gpu_data_manager = GpuDataManagerImpl::GetInstance();
+ DCHECK(gpu_data_manager);
+ if (!gpu_data_manager->GpuAccessAllowed(NULL))
+ return NULL;
+
+ if (g_gpu_process_hosts[kind] && ValidateHost(g_gpu_process_hosts[kind]))
+ return g_gpu_process_hosts[kind];
+
+ if (cause == CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH)
+ return NULL;
+
+ static int last_host_id = 0;
+ int host_id;
+ host_id = ++last_host_id;
+
+ UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessLaunchCause",
+ cause,
+ CAUSE_FOR_GPU_LAUNCH_MAX_ENUM);
+
+ GpuProcessHost* host = new GpuProcessHost(host_id, kind);
+ if (host->Init())
+ return host;
+
+ delete host;
+ return NULL;
+}
+
+// static
+void GpuProcessHost::GetProcessHandles(
+ const GpuDataManager::GetGpuProcessHandlesCallback& callback) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&GpuProcessHost::GetProcessHandles, callback));
+ return;
+ }
+ std::list<base::ProcessHandle> handles;
+ for (size_t i = 0; i < arraysize(g_gpu_process_hosts); ++i) {
+ GpuProcessHost* host = g_gpu_process_hosts[i];
+ if (host && ValidateHost(host))
+ handles.push_back(host->process_->GetHandle());
+ }
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(callback, handles));
+}
+
+// static
+void GpuProcessHost::SendOnIO(GpuProcessKind kind,
+ CauseForGpuLaunch cause,
+ IPC::Message* message) {
+ if (!BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &SendGpuProcessMessage, kind, cause, message))) {
+ delete message;
+ }
+}
+
+// static
+GpuProcessHost* GpuProcessHost::FromID(int host_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (int i = 0; i < GPU_PROCESS_KIND_COUNT; ++i) {
+ GpuProcessHost* host = g_gpu_process_hosts[i];
+ if (host && host->host_id_ == host_id && ValidateHost(host))
+ return host;
+ }
+
+ return NULL;
+}
+
+GpuProcessHost::GpuProcessHost(int host_id, GpuProcessKind kind)
+ : host_id_(host_id),
+ valid_(true),
+ in_process_(false),
+ swiftshader_rendering_(false),
+ kind_(kind),
+ process_launched_(false),
+ initialized_(false),
+ uma_memory_stats_received_(false) {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessGPU)) {
+ in_process_ = true;
+ }
+
+ // If the 'single GPU process' policy ever changes, we still want to maintain
+ // it for 'gpu thread' mode and only create one instance of host and thread.
+ DCHECK(!in_process_ || g_gpu_process_hosts[kind] == NULL);
+
+ g_gpu_process_hosts[kind] = this;
+
+ // Post a task to create the corresponding GpuProcessHostUIShim. The
+ // GpuProcessHostUIShim will be destroyed if either the browser exits,
+ // in which case it calls GpuProcessHostUIShim::DestroyAll, or the
+ // GpuProcessHost is destroyed, which happens when the corresponding GPU
+ // process terminates or fails to launch.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&GpuProcessHostUIShim::Create), host_id));
+
+ process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_GPU, this));
+}
+
+GpuProcessHost::~GpuProcessHost() {
+ DCHECK(CalledOnValidThread());
+
+ SendOutstandingReplies();
+
+ // Maximum number of times the gpu process is allowed to crash in a session.
+ // Once this limit is reached, any request to launch the gpu process will
+ // fail.
+ const int kGpuMaxCrashCount = 3;
+
+ // Number of times the gpu process has crashed in the current browser session.
+ static int gpu_crash_count = 0;
+ static int gpu_recent_crash_count = 0;
+ static base::Time last_gpu_crash_time;
+ static bool crashed_before = false;
+ static int swiftshader_crash_count = 0;
+
+ // Ending only acts as a failure if the GPU process was actually started and
+ // was intended for actual rendering (and not just checking caps or other
+ // options).
+ if (process_launched_ && kind_ == GPU_PROCESS_KIND_SANDBOXED) {
+ if (swiftshader_rendering_) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.SwiftShaderLifetimeEvents",
+ DIED_FIRST_TIME + swiftshader_crash_count,
+ GPU_PROCESS_LIFETIME_EVENT_MAX);
+
+ if (++swiftshader_crash_count >= kGpuMaxCrashCount) {
+ // SwiftShader is too unstable to use. Disable it for current session.
+ gpu_enabled_ = false;
+ }
+ } else {
+ ++gpu_crash_count;
+ UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessLifetimeEvents",
+ std::min(DIED_FIRST_TIME + gpu_crash_count,
+ GPU_PROCESS_LIFETIME_EVENT_MAX - 1),
+ GPU_PROCESS_LIFETIME_EVENT_MAX);
+
+ // Allow about 1 GPU crash per hour to be removed from the crash count,
+ // so very occasional crashes won't eventually add up and prevent the
+ // GPU process from launching.
+ ++gpu_recent_crash_count;
+ base::Time current_time = base::Time::Now();
+ if (crashed_before) {
+ int hours_different = (current_time - last_gpu_crash_time).InHours();
+ gpu_recent_crash_count =
+ std::max(0, gpu_recent_crash_count - hours_different);
+ }
+
+ crashed_before = true;
+ last_gpu_crash_time = current_time;
+
+ if (gpu_recent_crash_count >= kGpuMaxCrashCount ||
+ !initialized_) {
+#if !defined(OS_CHROMEOS)
+ // The gpu process is too unstable to use. Disable it for current
+ // session.
+ hardware_gpu_enabled_ = false;
+ GpuDataManagerImpl::GetInstance()->DisableHardwareAcceleration();
+#endif
+ }
+ }
+ }
+
+ // In case we never started, clean up.
+ while (!queued_messages_.empty()) {
+ delete queued_messages_.front();
+ queued_messages_.pop();
+ }
+
+ // This is only called on the IO thread so no race against the constructor
+ // for another GpuProcessHost.
+ if (g_gpu_process_hosts[kind_] == this)
+ g_gpu_process_hosts[kind_] = NULL;
+
+ // If there are any remaining offscreen contexts at the point the
+ // GPU process exits, assume something went wrong, and block their
+ // URLs from accessing client 3D APIs without prompting.
+ BlockLiveOffscreenContexts();
+
+ UMA_HISTOGRAM_COUNTS_100("GPU.AtExitSurfaceCount",
+ GpuSurfaceTracker::Get()->GetSurfaceCount());
+ UMA_HISTOGRAM_BOOLEAN("GPU.AtExitReceivedMemoryStats",
+ uma_memory_stats_received_);
+
+ if (uma_memory_stats_received_) {
+ UMA_HISTOGRAM_COUNTS_100("GPU.AtExitManagedMemoryClientCount",
+ uma_memory_stats_.client_count);
+ UMA_HISTOGRAM_COUNTS_100("GPU.AtExitContextGroupCount",
+ uma_memory_stats_.context_group_count);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "GPU.AtExitMBytesAllocated",
+ uma_memory_stats_.bytes_allocated_current / 1024 / 1024, 1, 2000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "GPU.AtExitMBytesAllocatedMax",
+ uma_memory_stats_.bytes_allocated_max / 1024 / 1024, 1, 2000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "GPU.AtExitMBytesLimit",
+ uma_memory_stats_.bytes_limit / 1024 / 1024, 1, 2000, 50);
+ }
+
+ std::string message;
+ if (!in_process_) {
+ int exit_code;
+ base::TerminationStatus status = process_->GetTerminationStatus(&exit_code);
+ UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationStatus",
+ status,
+ base::TERMINATION_STATUS_MAX_ENUM);
+
+ if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION ||
+ status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION) {
+ UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessExitCode",
+ exit_code,
+ RESULT_CODE_LAST_CODE);
+ }
+
+ switch (status) {
+ case base::TERMINATION_STATUS_NORMAL_TERMINATION:
+ message = "The GPU process exited normally. Everything is okay.";
+ break;
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+ message = base::StringPrintf(
+ "The GPU process exited with code %d.",
+ exit_code);
+ break;
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+ message = "You killed the GPU process! Why?";
+ break;
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ message = "The GPU process crashed!";
+ break;
+ default:
+ break;
+ }
+ }
+
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&GpuProcessHostUIShim::Destroy,
+ host_id_,
+ message));
+}
+
+bool GpuProcessHost::Init() {
+ init_start_time_ = base::TimeTicks::Now();
+
+ TRACE_EVENT_INSTANT0("gpu", "LaunchGpuProcess", TRACE_EVENT_SCOPE_THREAD);
+
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+ // Single process not supported in multiple dll mode currently.
+#if !defined(CHROME_MULTIPLE_DLL)
+ if (in_process_) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kDisableGpuWatchdog);
+
+ in_process_gpu_thread_.reset(new GpuMainThread(channel_id));
+ in_process_gpu_thread_->Start();
+
+ OnProcessLaunched(); // Fake a callback that the process is ready.
+ } else
+#endif // !CHROME_MULTIPLE_DLL
+ if (!LaunchGpuProcess(channel_id)) {
+ return false;
+ }
+
+ if (!Send(new GpuMsg_Initialize()))
+ return false;
+
+ return true;
+}
+
+void GpuProcessHost::RouteOnUIThread(const IPC::Message& message) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&RouteToGpuProcessHostUIShimTask, host_id_, message));
+}
+
+bool GpuProcessHost::Send(IPC::Message* msg) {
+ DCHECK(CalledOnValidThread());
+ if (process_->GetHost()->IsChannelOpening()) {
+ queued_messages_.push(msg);
+ return true;
+ }
+
+ bool result = process_->Send(msg);
+ if (!result) {
+ // Channel is hosed, but we may not get destroyed for a while. Send
+ // outstanding channel creation failures now so that the caller can restart
+ // with a new process/channel without waiting.
+ SendOutstandingReplies();
+ }
+ return result;
+}
+
+void GpuProcessHost::AddFilter(IPC::ChannelProxy::MessageFilter* filter) {
+ DCHECK(CalledOnValidThread());
+ process_->GetHost()->AddFilter(filter);
+}
+
+bool GpuProcessHost::OnMessageReceived(const IPC::Message& message) {
+ DCHECK(CalledOnValidThread());
+ IPC_BEGIN_MESSAGE_MAP(GpuProcessHost, message)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_Initialized, OnInitialized)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_ChannelEstablished, OnChannelEstablished)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_CommandBufferCreated, OnCommandBufferCreated)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_DestroyCommandBuffer, OnDestroyCommandBuffer)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_ImageCreated, OnImageCreated)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_DidCreateOffscreenContext,
+ OnDidCreateOffscreenContext)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_DidLoseContext, OnDidLoseContext)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_DidDestroyOffscreenContext,
+ OnDidDestroyOffscreenContext)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_GpuMemoryUmaStats,
+ OnGpuMemoryUmaStatsReceived)
+#if defined(OS_MACOSX)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceBuffersSwapped,
+ OnAcceleratedSurfaceBuffersSwapped)
+#endif
+#if defined(OS_WIN)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceBuffersSwapped,
+ OnAcceleratedSurfaceBuffersSwapped)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfacePostSubBuffer,
+ OnAcceleratedSurfacePostSubBuffer)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceSuspend,
+ OnAcceleratedSurfaceSuspend)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceRelease,
+ OnAcceleratedSurfaceRelease)
+#endif
+ IPC_MESSAGE_HANDLER(GpuHostMsg_DestroyChannel,
+ OnDestroyChannel)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_CacheShader,
+ OnCacheShader)
+
+ IPC_MESSAGE_UNHANDLED(RouteOnUIThread(message))
+ IPC_END_MESSAGE_MAP()
+
+ return true;
+}
+
+void GpuProcessHost::OnChannelConnected(int32 peer_pid) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnChannelConnected");
+
+ while (!queued_messages_.empty()) {
+ Send(queued_messages_.front());
+ queued_messages_.pop();
+ }
+}
+
+void GpuProcessHost::EstablishGpuChannel(
+ int client_id,
+ bool share_context,
+ const EstablishChannelCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ TRACE_EVENT0("gpu", "GpuProcessHost::EstablishGpuChannel");
+
+ // If GPU features are already blacklisted, no need to establish the channel.
+ if (!GpuDataManagerImpl::GetInstance()->GpuAccessAllowed(NULL)) {
+ callback.Run(IPC::ChannelHandle(), gpu::GPUInfo());
+ return;
+ }
+
+ if (Send(new GpuMsg_EstablishChannel(client_id, share_context))) {
+ channel_requests_.push(callback);
+ } else {
+ callback.Run(IPC::ChannelHandle(), gpu::GPUInfo());
+ }
+
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableGpuShaderDiskCache)) {
+ CreateChannelCache(client_id);
+ }
+}
+
+void GpuProcessHost::CreateViewCommandBuffer(
+ const gfx::GLSurfaceHandle& compositing_surface,
+ int surface_id,
+ int client_id,
+ const GPUCreateCommandBufferConfig& init_params,
+ const CreateCommandBufferCallback& callback) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::CreateViewCommandBuffer");
+
+ DCHECK(CalledOnValidThread());
+
+ if (!compositing_surface.is_null() &&
+ Send(new GpuMsg_CreateViewCommandBuffer(
+ compositing_surface, surface_id, client_id, init_params))) {
+ create_command_buffer_requests_.push(callback);
+ surface_refs_.insert(std::make_pair(surface_id,
+ GpuSurfaceTracker::GetInstance()->GetSurfaceRefForSurface(surface_id)));
+ } else {
+ callback.Run(MSG_ROUTING_NONE);
+ }
+}
+
+void GpuProcessHost::CreateImage(gfx::PluginWindowHandle window,
+ int client_id,
+ int image_id,
+ const CreateImageCallback& callback) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::CreateImage");
+
+ DCHECK(CalledOnValidThread());
+
+ if (Send(new GpuMsg_CreateImage(window, client_id, image_id))) {
+ create_image_requests_.push(callback);
+ } else {
+ callback.Run(gfx::Size());
+ }
+}
+
+void GpuProcessHost::DeleteImage(int client_id,
+ int image_id,
+ int sync_point) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::DeleteImage");
+
+ DCHECK(CalledOnValidThread());
+
+ Send(new GpuMsg_DeleteImage(client_id, image_id, sync_point));
+}
+
+void GpuProcessHost::OnInitialized(bool result, const gpu::GPUInfo& gpu_info) {
+ UMA_HISTOGRAM_BOOLEAN("GPU.GPUProcessInitialized", result);
+ initialized_ = result;
+
+#if defined(OS_WIN)
+ if (kind_ == GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED)
+ AcceleratedPresenter::SetAdapterLUID(gpu_info.adapter_luid);
+#endif
+
+ if (!initialized_)
+ GpuDataManagerImpl::GetInstance()->OnGpuProcessInitFailure();
+}
+
+void GpuProcessHost::OnChannelEstablished(
+ const IPC::ChannelHandle& channel_handle) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnChannelEstablished");
+
+ if (channel_requests_.empty()) {
+ // This happens when GPU process is compromised.
+ RouteOnUIThread(GpuHostMsg_OnLogMessage(
+ logging::LOG_WARNING,
+ "WARNING",
+ "Received a ChannelEstablished message but no requests in queue."));
+ return;
+ }
+ EstablishChannelCallback callback = channel_requests_.front();
+ channel_requests_.pop();
+
+ // Currently if any of the GPU features are blacklisted, we don't establish a
+ // GPU channel.
+ if (!channel_handle.name.empty() &&
+ !GpuDataManagerImpl::GetInstance()->GpuAccessAllowed(NULL)) {
+ Send(new GpuMsg_CloseChannel(channel_handle));
+ callback.Run(IPC::ChannelHandle(), gpu::GPUInfo());
+ RouteOnUIThread(GpuHostMsg_OnLogMessage(
+ logging::LOG_WARNING,
+ "WARNING",
+ "Hardware acceleration is unavailable."));
+ return;
+ }
+
+ callback.Run(channel_handle,
+ GpuDataManagerImpl::GetInstance()->GetGPUInfo());
+}
+
+void GpuProcessHost::OnCommandBufferCreated(const int32 route_id) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnCommandBufferCreated");
+
+ if (create_command_buffer_requests_.empty())
+ return;
+
+ CreateCommandBufferCallback callback =
+ create_command_buffer_requests_.front();
+ create_command_buffer_requests_.pop();
+ callback.Run(route_id);
+}
+
+void GpuProcessHost::OnDestroyCommandBuffer(int32 surface_id) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnDestroyCommandBuffer");
+ SurfaceRefMap::iterator it = surface_refs_.find(surface_id);
+ if (it != surface_refs_.end()) {
+ surface_refs_.erase(it);
+ }
+}
+
+void GpuProcessHost::OnImageCreated(const gfx::Size size) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnImageCreated");
+
+ if (create_image_requests_.empty())
+ return;
+
+ CreateImageCallback callback = create_image_requests_.front();
+ create_image_requests_.pop();
+ callback.Run(size);
+}
+
+void GpuProcessHost::OnDidCreateOffscreenContext(const GURL& url) {
+ urls_with_live_offscreen_contexts_.insert(url);
+}
+
+void GpuProcessHost::OnDidLoseContext(bool offscreen,
+ gpu::error::ContextLostReason reason,
+ const GURL& url) {
+ // TODO(kbr): would be nice to see the "offscreen" flag too.
+ TRACE_EVENT2("gpu", "GpuProcessHost::OnDidLoseContext",
+ "reason", reason,
+ "url",
+ url.possibly_invalid_spec());
+
+ if (!offscreen || url.is_empty()) {
+ // Assume that the loss of the compositor's or accelerated canvas'
+ // context is a serious event and blame the loss on all live
+ // offscreen contexts. This more robustly handles situations where
+ // the GPU process may not actually detect the context loss in the
+ // offscreen context.
+ BlockLiveOffscreenContexts();
+ return;
+ }
+
+ GpuDataManagerImpl::DomainGuilt guilt;
+ switch (reason) {
+ case gpu::error::kGuilty:
+ guilt = GpuDataManagerImpl::DOMAIN_GUILT_KNOWN;
+ break;
+ case gpu::error::kUnknown:
+ guilt = GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN;
+ break;
+ case gpu::error::kInnocent:
+ return;
+ default:
+ NOTREACHED();
+ return;
+ }
+
+ GpuDataManagerImpl::GetInstance()->BlockDomainFrom3DAPIs(url, guilt);
+}
+
+void GpuProcessHost::OnDidDestroyOffscreenContext(const GURL& url) {
+ urls_with_live_offscreen_contexts_.erase(url);
+}
+
+void GpuProcessHost::OnGpuMemoryUmaStatsReceived(
+ const GPUMemoryUmaStats& stats) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnGpuMemoryUmaStatsReceived");
+ uma_memory_stats_received_ = true;
+ uma_memory_stats_ = stats;
+}
+
+#if defined(OS_MACOSX)
+void GpuProcessHost::OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnAcceleratedSurfaceBuffersSwapped");
+
+ gfx::GLSurfaceHandle surface_handle =
+ GpuSurfaceTracker::Get()->GetSurfaceHandle(params.surface_id);
+ // Compositor window is always gfx::kNullPluginWindow.
+ // TODO(jbates) http://crbug.com/105344 This will be removed when there are no
+ // plugin windows.
+ if (surface_handle.handle != gfx::kNullPluginWindow ||
+ surface_handle.transport_type == gfx::TEXTURE_TRANSPORT) {
+ RouteOnUIThread(GpuHostMsg_AcceleratedSurfaceBuffersSwapped(params));
+ return;
+ }
+
+ base::ScopedClosureRunner scoped_completion_runner(
+ base::Bind(&AcceleratedSurfaceBuffersSwappedCompletedForGPU,
+ host_id_, params.route_id,
+ true /* alive */));
+
+ int render_process_id = 0;
+ int render_widget_id = 0;
+ if (!GpuSurfaceTracker::Get()->GetRenderWidgetIDForSurface(
+ params.surface_id, &render_process_id, &render_widget_id)) {
+ return;
+ }
+ RenderWidgetHelper* helper =
+ RenderWidgetHelper::FromProcessHostID(render_process_id);
+ if (!helper)
+ return;
+
+ // Pass the SwapBuffers on to the RenderWidgetHelper to wake up the UI thread
+ // if the browser is waiting for a new frame. Otherwise the RenderWidgetHelper
+ // will forward to the RenderWidgetHostView via RenderProcessHostImpl and
+ // RenderWidgetHostImpl.
+ scoped_completion_runner.Release();
+
+ ViewHostMsg_CompositorSurfaceBuffersSwapped_Params view_params;
+ view_params.surface_id = params.surface_id;
+ view_params.surface_handle = params.surface_handle;
+ view_params.route_id = params.route_id;
+ view_params.size = params.size;
+ view_params.scale_factor = params.scale_factor;
+ view_params.gpu_process_host_id = host_id_;
+ view_params.latency_info = params.latency_info;
+ helper->DidReceiveBackingStoreMsg(ViewHostMsg_CompositorSurfaceBuffersSwapped(
+ render_widget_id,
+ view_params));
+}
+#endif // OS_MACOSX
+
+#if defined(OS_WIN)
+void GpuProcessHost::OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnAcceleratedSurfaceBuffersSwapped");
+
+ base::ScopedClosureRunner scoped_completion_runner(
+ base::Bind(&AcceleratedSurfaceBuffersSwappedCompleted,
+ host_id_, params.route_id, params.surface_id,
+ true, base::TimeTicks(), base::TimeDelta(), ui::LatencyInfo()));
+
+ gfx::GLSurfaceHandle handle =
+ GpuSurfaceTracker::Get()->GetSurfaceHandle(params.surface_id);
+
+ if (handle.is_null())
+ return;
+
+ if (handle.transport_type == gfx::TEXTURE_TRANSPORT) {
+ TRACE_EVENT1("gpu", "SurfaceIDNotFound_RoutingToUI",
+ "surface_id", params.surface_id);
+ // This is a content area swap, send it on to the UI thread.
+ scoped_completion_runner.Release();
+ RouteOnUIThread(GpuHostMsg_AcceleratedSurfaceBuffersSwapped(params));
+ return;
+ }
+
+ // Otherwise it's the UI swap.
+
+ scoped_refptr<AcceleratedPresenter> presenter(
+ AcceleratedPresenter::GetForWindow(handle.handle));
+ if (!presenter) {
+ TRACE_EVENT1("gpu",
+ "EarlyOut_NativeWindowNotFound",
+ "handle",
+ handle.handle);
+ scoped_completion_runner.Release();
+ AcceleratedSurfaceBuffersSwappedCompleted(host_id_,
+ params.route_id,
+ params.surface_id,
+ true,
+ base::TimeTicks(),
+ base::TimeDelta(),
+ params.latency_info);
+ return;
+ }
+
+ scoped_completion_runner.Release();
+ presenter->AsyncPresentAndAcknowledge(
+ params.size,
+ params.surface_handle,
+ params.latency_info,
+ base::Bind(&AcceleratedSurfaceBuffersSwappedCompleted,
+ host_id_,
+ params.route_id,
+ params.surface_id));
+
+ FrameSubscriberMap::iterator it = frame_subscribers_.find(params.surface_id);
+ if (it != frame_subscribers_.end() && it->second) {
+ const base::Time present_time = base::Time::Now();
+ scoped_refptr<media::VideoFrame> target_frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback copy_callback;
+ if (it->second->ShouldCaptureFrame(present_time,
+ &target_frame, &copy_callback)) {
+ // It is a potential improvement to do the copy in present, but we use a
+ // simpler approach for now.
+ presenter->AsyncCopyToVideoFrame(
+ gfx::Rect(params.size), target_frame,
+ base::Bind(copy_callback, present_time));
+ }
+ }
+}
+
+void GpuProcessHost::OnAcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnAcceleratedSurfacePostSubBuffer");
+
+ NOTIMPLEMENTED();
+}
+
+void GpuProcessHost::OnAcceleratedSurfaceSuspend(int32 surface_id) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnAcceleratedSurfaceSuspend");
+
+ gfx::PluginWindowHandle handle =
+ GpuSurfaceTracker::Get()->GetSurfaceHandle(surface_id).handle;
+
+ if (!handle) {
+#if defined(USE_AURA)
+ RouteOnUIThread(GpuHostMsg_AcceleratedSurfaceSuspend(surface_id));
+#endif
+ return;
+ }
+
+ scoped_refptr<AcceleratedPresenter> presenter(
+ AcceleratedPresenter::GetForWindow(handle));
+ if (!presenter)
+ return;
+
+ presenter->Suspend();
+}
+
+void GpuProcessHost::OnAcceleratedSurfaceRelease(
+ const GpuHostMsg_AcceleratedSurfaceRelease_Params& params) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnAcceleratedSurfaceRelease");
+
+ gfx::PluginWindowHandle handle =
+ GpuSurfaceTracker::Get()->GetSurfaceHandle(params.surface_id).handle;
+ if (!handle) {
+#if defined(USE_AURA)
+ RouteOnUIThread(GpuHostMsg_AcceleratedSurfaceRelease(params));
+ return;
+#endif
+ }
+
+ scoped_refptr<AcceleratedPresenter> presenter(
+ AcceleratedPresenter::GetForWindow(handle));
+ if (!presenter)
+ return;
+
+ presenter->ReleaseSurface();
+}
+
+#endif // OS_WIN
+
+void GpuProcessHost::OnProcessLaunched() {
+ UMA_HISTOGRAM_TIMES("GPU.GPUProcessLaunchTime",
+ base::TimeTicks::Now() - init_start_time_);
+}
+
+void GpuProcessHost::OnProcessCrashed(int exit_code) {
+ SendOutstandingReplies();
+ GpuDataManagerImpl::GetInstance()->ProcessCrashed(
+ process_->GetTerminationStatus(NULL));
+}
+
+GpuProcessHost::GpuProcessKind GpuProcessHost::kind() {
+ return kind_;
+}
+
+void GpuProcessHost::ForceShutdown() {
+ // This is only called on the IO thread so no race against the constructor
+ // for another GpuProcessHost.
+ if (g_gpu_process_hosts[kind_] == this)
+ g_gpu_process_hosts[kind_] = NULL;
+
+ process_->ForceShutdown();
+}
+
+void GpuProcessHost::BeginFrameSubscription(
+ int surface_id,
+ base::WeakPtr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ frame_subscribers_[surface_id] = subscriber;
+}
+
+void GpuProcessHost::EndFrameSubscription(int surface_id) {
+ frame_subscribers_.erase(surface_id);
+}
+
+bool GpuProcessHost::LaunchGpuProcess(const std::string& channel_id) {
+ if (!(gpu_enabled_ &&
+ GpuDataManagerImpl::GetInstance()->ShouldUseSwiftShader()) &&
+ !hardware_gpu_enabled_) {
+ SendOutstandingReplies();
+ return false;
+ }
+
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+
+ CommandLine::StringType gpu_launcher =
+ browser_command_line.GetSwitchValueNative(switches::kGpuLauncher);
+
+#if defined(OS_LINUX)
+ int child_flags = gpu_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
+ ChildProcessHost::CHILD_NORMAL;
+#else
+ int child_flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags);
+ if (exe_path.empty())
+ return false;
+
+ CommandLine* cmd_line = new CommandLine(exe_path);
+ cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+
+ if (kind_ == GPU_PROCESS_KIND_UNSANDBOXED)
+ cmd_line->AppendSwitch(switches::kDisableGpuSandbox);
+
+ // Propagate relevant command line switches.
+ static const char* const kSwitchNames[] = {
+ switches::kDisableAcceleratedVideoDecode,
+ switches::kDisableBreakpad,
+ switches::kDisableGLMultisampling,
+ switches::kDisableGpuSandbox,
+ switches::kDisableGpuWatchdog,
+ switches::kDisableImageTransportSurface,
+ switches::kDisableLogging,
+ switches::kDisableSeccompFilterSandbox,
+ switches::kEnableLogging,
+ switches::kEnableShareGroupAsyncTextureUpload,
+ switches::kEnableVirtualGLContexts,
+ switches::kGpuStartupDialog,
+ switches::kGpuSandboxAllowSysVShm,
+ switches::kLoggingLevel,
+ switches::kNoSandbox,
+ switches::kReduceGpuSandbox,
+ switches::kTestGLLib,
+ switches::kTraceStartup,
+ switches::kV,
+ switches::kVModule,
+#if defined(OS_MACOSX)
+ switches::kEnableSandboxLogging,
+#endif
+#if defined(USE_AURA)
+ switches::kUIPrioritizeInGpuProcess,
+#endif
+ };
+ cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
+ arraysize(kSwitchNames));
+ cmd_line->CopySwitchesFrom(
+ browser_command_line, switches::kGpuSwitches, switches::kNumGpuSwitches);
+ cmd_line->CopySwitchesFrom(
+ browser_command_line, switches::kGLSwitchesCopiedFromGpuProcessHost,
+ switches::kGLSwitchesCopiedFromGpuProcessHostNumSwitches);
+
+ GetContentClient()->browser()->AppendExtraCommandLineSwitches(
+ cmd_line, process_->GetData().id);
+
+ GpuDataManagerImpl::GetInstance()->AppendGpuCommandLine(cmd_line);
+
+ if (cmd_line->HasSwitch(switches::kUseGL)) {
+ swiftshader_rendering_ =
+ (cmd_line->GetSwitchValueASCII(switches::kUseGL) == "swiftshader");
+ }
+
+ UMA_HISTOGRAM_BOOLEAN("GPU.GPU.GPUProcessSoftwareRendering",
+ swiftshader_rendering_);
+
+ // If specified, prepend a launcher program to the command line.
+ if (!gpu_launcher.empty())
+ cmd_line->PrependWrapper(gpu_launcher);
+
+ process_->Launch(
+#if defined(OS_WIN)
+ new GpuSandboxedProcessLauncherDelegate(cmd_line),
+#elif defined(OS_POSIX)
+ false,
+ base::EnvironmentVector(),
+#endif
+ cmd_line);
+ process_launched_ = true;
+
+ UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessLifetimeEvents",
+ LAUNCHED, GPU_PROCESS_LIFETIME_EVENT_MAX);
+ return true;
+}
+
+void GpuProcessHost::SendOutstandingReplies() {
+ valid_ = false;
+ // First send empty channel handles for all EstablishChannel requests.
+ while (!channel_requests_.empty()) {
+ EstablishChannelCallback callback = channel_requests_.front();
+ channel_requests_.pop();
+ callback.Run(IPC::ChannelHandle(), gpu::GPUInfo());
+ }
+
+ while (!create_command_buffer_requests_.empty()) {
+ CreateCommandBufferCallback callback =
+ create_command_buffer_requests_.front();
+ create_command_buffer_requests_.pop();
+ callback.Run(MSG_ROUTING_NONE);
+ }
+}
+
+void GpuProcessHost::BlockLiveOffscreenContexts() {
+ for (std::multiset<GURL>::iterator iter =
+ urls_with_live_offscreen_contexts_.begin();
+ iter != urls_with_live_offscreen_contexts_.end(); ++iter) {
+ GpuDataManagerImpl::GetInstance()->BlockDomainFrom3DAPIs(
+ *iter, GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN);
+ }
+}
+
+std::string GpuProcessHost::GetShaderPrefixKey() {
+ if (shader_prefix_key_.empty()) {
+ gpu::GPUInfo info = GpuDataManagerImpl::GetInstance()->GetGPUInfo();
+
+ std::string in_str = GetContentClient()->GetProduct() + "-" +
+ info.gl_vendor + "-" + info.gl_renderer + "-" +
+ info.driver_version + "-" + info.driver_vendor;
+
+ base::Base64Encode(base::SHA1HashString(in_str), &shader_prefix_key_);
+ }
+
+ return shader_prefix_key_;
+}
+
+void GpuProcessHost::LoadedShader(const std::string& key,
+ const std::string& data) {
+ std::string prefix = GetShaderPrefixKey();
+ if (!key.compare(0, prefix.length(), prefix))
+ Send(new GpuMsg_LoadedShader(data));
+}
+
+void GpuProcessHost::CreateChannelCache(int32 client_id) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::CreateChannelCache");
+
+ scoped_refptr<ShaderDiskCache> cache =
+ ShaderCacheFactory::GetInstance()->Get(client_id);
+ if (!cache.get())
+ return;
+
+ cache->set_host_id(host_id_);
+
+ client_id_to_shader_cache_[client_id] = cache;
+}
+
+void GpuProcessHost::OnDestroyChannel(int32 client_id) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnDestroyChannel");
+ client_id_to_shader_cache_.erase(client_id);
+}
+
+void GpuProcessHost::OnCacheShader(int32 client_id,
+ const std::string& key,
+ const std::string& shader) {
+ TRACE_EVENT0("gpu", "GpuProcessHost::OnCacheShader");
+ ClientIdToShaderCacheMap::iterator iter =
+ client_id_to_shader_cache_.find(client_id);
+ // If the cache doesn't exist then this is an off the record profile.
+ if (iter == client_id_to_shader_cache_.end())
+ return;
+ iter->second->Cache(GetShaderPrefixKey() + ":" + key, shader);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_process_host.h b/chromium/content/browser/gpu/gpu_process_host.h
new file mode 100644
index 00000000000..8908a1035db
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_process_host.h
@@ -0,0 +1,275 @@
+// 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 CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_H_
+#define CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_H_
+
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/common/content_export.h"
+#include "content/common/gpu/gpu_memory_uma_stats.h"
+#include "content/common/gpu/gpu_process_launch_causes.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "gpu/command_buffer/common/constants.h"
+#include "gpu/config/gpu_info.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_sender.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+#include "url/gurl.h"
+
+struct GPUCreateCommandBufferConfig;
+struct GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params;
+struct GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params;
+struct GpuHostMsg_AcceleratedSurfaceRelease_Params;
+
+namespace IPC {
+struct ChannelHandle;
+}
+
+namespace content {
+class BrowserChildProcessHostImpl;
+class GpuMainThread;
+class RenderWidgetHostViewFrameSubscriber;
+class ShaderDiskCache;
+
+class GpuProcessHost : public BrowserChildProcessHostDelegate,
+ public IPC::Sender,
+ public base::NonThreadSafe {
+ public:
+ enum GpuProcessKind {
+ GPU_PROCESS_KIND_UNSANDBOXED,
+ GPU_PROCESS_KIND_SANDBOXED,
+ GPU_PROCESS_KIND_COUNT
+ };
+
+ typedef base::Callback<void(const IPC::ChannelHandle&, const gpu::GPUInfo&)>
+ EstablishChannelCallback;
+
+ typedef base::Callback<void(int32)> CreateCommandBufferCallback;
+
+ typedef base::Callback<void(const gfx::Size)> CreateImageCallback;
+
+ static bool gpu_enabled() { return gpu_enabled_; }
+
+ // Creates a new GpuProcessHost or gets an existing one, resulting in the
+ // launching of a GPU process if required. Returns null on failure. It
+ // is not safe to store the pointer once control has returned to the message
+ // loop as it can be destroyed. Instead store the associated GPU host ID.
+ // This could return NULL if GPU access is not allowed (blacklisted).
+ CONTENT_EXPORT static GpuProcessHost* Get(GpuProcessKind kind,
+ CauseForGpuLaunch cause);
+
+ // Retrieves a list of process handles for all gpu processes.
+ static void GetProcessHandles(
+ const GpuDataManager::GetGpuProcessHandlesCallback& callback);
+
+ // Helper function to send the given message to the GPU process on the IO
+ // thread. Calls Get and if a host is returned, sends it. Can be called from
+ // any thread. Deletes the message if it cannot be sent.
+ CONTENT_EXPORT static void SendOnIO(GpuProcessKind kind,
+ CauseForGpuLaunch cause,
+ IPC::Message* message);
+
+ // Get the GPU process host for the GPU process with the given ID. Returns
+ // null if the process no longer exists.
+ static GpuProcessHost* FromID(int host_id);
+ int host_id() const { return host_id_; }
+
+ // IPC::Sender implementation.
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // Adds a message filter to the GpuProcessHost's channel.
+ void AddFilter(IPC::ChannelProxy::MessageFilter* filter);
+
+ // Tells the GPU process to create a new channel for communication with a
+ // client. Once the GPU process responds asynchronously with the IPC handle
+ // and GPUInfo, we call the callback.
+ void EstablishGpuChannel(int client_id,
+ bool share_context,
+ const EstablishChannelCallback& callback);
+
+ // Tells the GPU process to create a new command buffer that draws into the
+ // given surface.
+ void CreateViewCommandBuffer(
+ const gfx::GLSurfaceHandle& compositing_surface,
+ int surface_id,
+ int client_id,
+ const GPUCreateCommandBufferConfig& init_params,
+ const CreateCommandBufferCallback& callback);
+
+ // Tells the GPU process to create a new image using the given window.
+ void CreateImage(
+ gfx::PluginWindowHandle window,
+ int client_id,
+ int image_id,
+ const CreateImageCallback& callback);
+
+ // Tells the GPU process to delete image.
+ void DeleteImage(int client_id, int image_id, int sync_point);
+
+ // What kind of GPU process, e.g. sandboxed or unsandboxed.
+ GpuProcessKind kind();
+
+ void ForceShutdown();
+
+ void BeginFrameSubscription(
+ int surface_id,
+ base::WeakPtr<RenderWidgetHostViewFrameSubscriber> subscriber);
+ void EndFrameSubscription(int surface_id);
+ void LoadedShader(const std::string& key, const std::string& data);
+
+ private:
+ static bool ValidateHost(GpuProcessHost* host);
+
+ GpuProcessHost(int host_id, GpuProcessKind kind);
+ virtual ~GpuProcessHost();
+
+ bool Init();
+
+ // Post an IPC message to the UI shim's message handler on the UI thread.
+ void RouteOnUIThread(const IPC::Message& message);
+
+ // BrowserChildProcessHostDelegate implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnProcessLaunched() OVERRIDE;
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+
+ // Message handlers.
+ void OnInitialized(bool result, const gpu::GPUInfo& gpu_info);
+ void OnChannelEstablished(const IPC::ChannelHandle& channel_handle);
+ void OnCommandBufferCreated(const int32 route_id);
+ void OnDestroyCommandBuffer(int32 surface_id);
+ void OnImageCreated(const gfx::Size size);
+ void OnDidCreateOffscreenContext(const GURL& url);
+ void OnDidLoseContext(bool offscreen,
+ gpu::error::ContextLostReason reason,
+ const GURL& url);
+ void OnDidDestroyOffscreenContext(const GURL& url);
+ void OnGpuMemoryUmaStatsReceived(const GPUMemoryUmaStats& stats);
+#if defined(OS_MACOSX)
+ void OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params);
+#endif
+ // Note: Different implementations depending on USE_AURA.
+#if defined(OS_WIN)
+ void OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params);
+ void OnAcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params);
+ void OnAcceleratedSurfaceSuspend(int32 surface_id);
+ void OnAcceleratedSurfaceRelease(
+ const GpuHostMsg_AcceleratedSurfaceRelease_Params& params);
+#endif
+
+ void CreateChannelCache(int32 client_id);
+ void OnDestroyChannel(int32 client_id);
+ void OnCacheShader(int32 client_id, const std::string& key,
+ const std::string& shader);
+
+ bool LaunchGpuProcess(const std::string& channel_id);
+
+ void SendOutstandingReplies();
+
+ void BlockLiveOffscreenContexts();
+
+ std::string GetShaderPrefixKey();
+
+ // The serial number of the GpuProcessHost / GpuProcessHostUIShim pair.
+ int host_id_;
+
+ // These are the channel requests that we have already sent to
+ // the GPU process, but haven't heard back about yet.
+ std::queue<EstablishChannelCallback> channel_requests_;
+
+ // The pending create command buffer requests we need to reply to.
+ std::queue<CreateCommandBufferCallback> create_command_buffer_requests_;
+
+ // The pending create image requests we need to reply to.
+ std::queue<CreateImageCallback> create_image_requests_;
+
+
+ // Qeueud messages to send when the process launches.
+ std::queue<IPC::Message*> queued_messages_;
+
+ // Whether the GPU process is valid, set to false after Send() failed.
+ bool valid_;
+
+ // Whether we are running a GPU thread inside the browser process instead
+ // of a separate GPU process.
+ bool in_process_;
+
+ bool swiftshader_rendering_;
+ GpuProcessKind kind_;
+
+#if !defined(CHROME_MULTIPLE_DLL)
+ scoped_ptr<GpuMainThread> in_process_gpu_thread_;
+#endif
+
+ // Whether we actually launched a GPU process.
+ bool process_launched_;
+
+ // Whether the GPU process successfully initialized.
+ bool initialized_;
+
+ // Time Init started. Used to log total GPU process startup time to UMA.
+ base::TimeTicks init_start_time_;
+
+ // Master switch for enabling/disabling GPU acceleration for the current
+ // browser session. It does not change the acceleration settings for
+ // existing tabs, just the future ones.
+ static bool gpu_enabled_;
+
+ static bool hardware_gpu_enabled_;
+
+ scoped_ptr<BrowserChildProcessHostImpl> process_;
+
+ // Track the URLs of the pages which have live offscreen contexts,
+ // assumed to be associated with untrusted content such as WebGL.
+ // For best robustness, when any context lost notification is
+ // received, assume all of these URLs are guilty, and block
+ // automatic execution of 3D content from those domains.
+ std::multiset<GURL> urls_with_live_offscreen_contexts_;
+
+ // Statics kept around to send to UMA histograms on GPU process lost.
+ bool uma_memory_stats_received_;
+ GPUMemoryUmaStats uma_memory_stats_;
+
+ // This map of frame subscribers are listening for frame presentation events.
+ // The key is the surface id and value is the subscriber.
+ typedef base::hash_map<int,
+ base::WeakPtr<RenderWidgetHostViewFrameSubscriber> >
+ FrameSubscriberMap;
+ FrameSubscriberMap frame_subscribers_;
+
+ typedef std::map<int32, scoped_refptr<ShaderDiskCache> >
+ ClientIdToShaderCacheMap;
+ ClientIdToShaderCacheMap client_id_to_shader_cache_;
+
+ std::string shader_prefix_key_;
+
+ // Keep an extra reference to the SurfaceRef stored in the GpuSurfaceTracker
+ // in this map so that we don't destroy it whilst the GPU process is
+ // drawing to it.
+ typedef std::multimap<int, scoped_refptr<GpuSurfaceTracker::SurfaceRef> >
+ SurfaceRefMap;
+ SurfaceRefMap surface_refs_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuProcessHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_H_
diff --git a/chromium/content/browser/gpu/gpu_process_host_ui_shim.cc b/chromium/content/browser/gpu/gpu_process_host_ui_shim.cc
new file mode 100644
index 00000000000..50810427388
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_process_host_ui_shim.cc
@@ -0,0 +1,401 @@
+// 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 "content/browser/gpu/gpu_process_host_ui_shim.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/id_map.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/gl/gl_switches.h"
+
+#if defined(TOOLKIT_GTK)
+// These two #includes need to come after gpu_messages.h.
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/size.h"
+#include <gdk/gdk.h> // NOLINT
+#include <gdk/gdkx.h> // NOLINT
+#endif
+
+// From gl2/gl2ext.h.
+#ifndef GL_MAILBOX_SIZE_CHROMIUM
+#define GL_MAILBOX_SIZE_CHROMIUM 64
+#endif
+
+namespace content {
+
+namespace {
+
+// One of the linux specific headers defines this as a macro.
+#ifdef DestroyAll
+#undef DestroyAll
+#endif
+
+base::LazyInstance<IDMap<GpuProcessHostUIShim> > g_hosts_by_id =
+ LAZY_INSTANCE_INITIALIZER;
+
+void SendOnIOThreadTask(int host_id, IPC::Message* msg) {
+ GpuProcessHost* host = GpuProcessHost::FromID(host_id);
+ if (host)
+ host->Send(msg);
+ else
+ delete msg;
+}
+
+class ScopedSendOnIOThread {
+ public:
+ ScopedSendOnIOThread(int host_id, IPC::Message* msg)
+ : host_id_(host_id),
+ msg_(msg),
+ cancelled_(false) {
+ }
+
+ ~ScopedSendOnIOThread() {
+ if (!cancelled_) {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SendOnIOThreadTask,
+ host_id_,
+ msg_.release()));
+ }
+ }
+
+ void Cancel() { cancelled_ = true; }
+
+ private:
+ int host_id_;
+ scoped_ptr<IPC::Message> msg_;
+ bool cancelled_;
+};
+
+RenderWidgetHostViewPort* GetRenderWidgetHostViewFromSurfaceID(
+ int surface_id) {
+ int render_process_id = 0;
+ int render_widget_id = 0;
+ if (!GpuSurfaceTracker::Get()->GetRenderWidgetIDForSurface(
+ surface_id, &render_process_id, &render_widget_id))
+ return NULL;
+
+ RenderWidgetHost* host =
+ RenderWidgetHost::FromID(render_process_id, render_widget_id);
+ return host ? RenderWidgetHostViewPort::FromRWHV(host->GetView()) : NULL;
+}
+
+} // namespace
+
+void RouteToGpuProcessHostUIShimTask(int host_id, const IPC::Message& msg) {
+ GpuProcessHostUIShim* ui_shim = GpuProcessHostUIShim::FromID(host_id);
+ if (ui_shim)
+ ui_shim->OnMessageReceived(msg);
+}
+
+GpuProcessHostUIShim::GpuProcessHostUIShim(int host_id)
+ : host_id_(host_id) {
+ g_hosts_by_id.Pointer()->AddWithID(this, host_id_);
+}
+
+// static
+GpuProcessHostUIShim* GpuProcessHostUIShim::Create(int host_id) {
+ DCHECK(!FromID(host_id));
+ return new GpuProcessHostUIShim(host_id);
+}
+
+// static
+void GpuProcessHostUIShim::Destroy(int host_id, const std::string& message) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ GpuDataManagerImpl::GetInstance()->AddLogMessage(
+ logging::LOG_ERROR, "GpuProcessHostUIShim",
+ message);
+
+ delete FromID(host_id);
+}
+
+// static
+void GpuProcessHostUIShim::DestroyAll() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ while (!g_hosts_by_id.Pointer()->IsEmpty()) {
+ IDMap<GpuProcessHostUIShim>::iterator it(g_hosts_by_id.Pointer());
+ delete it.GetCurrentValue();
+ }
+}
+
+// static
+GpuProcessHostUIShim* GpuProcessHostUIShim::FromID(int host_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return g_hosts_by_id.Pointer()->Lookup(host_id);
+}
+
+// static
+GpuProcessHostUIShim* GpuProcessHostUIShim::GetOneInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (g_hosts_by_id.Pointer()->IsEmpty())
+ return NULL;
+ IDMap<GpuProcessHostUIShim>::iterator it(g_hosts_by_id.Pointer());
+ return it.GetCurrentValue();
+}
+
+bool GpuProcessHostUIShim::Send(IPC::Message* msg) {
+ DCHECK(CalledOnValidThread());
+ return BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SendOnIOThreadTask,
+ host_id_,
+ msg));
+}
+
+bool GpuProcessHostUIShim::OnMessageReceived(const IPC::Message& message) {
+ DCHECK(CalledOnValidThread());
+
+ if (message.routing_id() != MSG_ROUTING_CONTROL)
+ return false;
+
+ return OnControlMessageReceived(message);
+}
+
+void GpuProcessHostUIShim::SimulateRemoveAllContext() {
+ Send(new GpuMsg_Clean());
+}
+
+void GpuProcessHostUIShim::SimulateCrash() {
+ Send(new GpuMsg_Crash());
+}
+
+void GpuProcessHostUIShim::SimulateHang() {
+ Send(new GpuMsg_Hang());
+}
+
+GpuProcessHostUIShim::~GpuProcessHostUIShim() {
+ DCHECK(CalledOnValidThread());
+ g_hosts_by_id.Pointer()->Remove(host_id_);
+}
+
+bool GpuProcessHostUIShim::OnControlMessageReceived(
+ const IPC::Message& message) {
+ DCHECK(CalledOnValidThread());
+
+ IPC_BEGIN_MESSAGE_MAP(GpuProcessHostUIShim, message)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_OnLogMessage,
+ OnLogMessage)
+
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceBuffersSwapped,
+ OnAcceleratedSurfaceBuffersSwapped)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfacePostSubBuffer,
+ OnAcceleratedSurfacePostSubBuffer)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceSuspend,
+ OnAcceleratedSurfaceSuspend)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_GraphicsInfoCollected,
+ OnGraphicsInfoCollected)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceRelease,
+ OnAcceleratedSurfaceRelease)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_VideoMemoryUsageStats,
+ OnVideoMemoryUsageStatsReceived);
+ IPC_MESSAGE_HANDLER(GpuHostMsg_UpdateVSyncParameters,
+ OnUpdateVSyncParameters)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_FrameDrawn, OnFrameDrawn)
+
+#if defined(TOOLKIT_GTK) || defined(OS_WIN)
+ IPC_MESSAGE_HANDLER(GpuHostMsg_ResizeView, OnResizeView)
+#endif
+
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP()
+
+ return true;
+}
+
+void GpuProcessHostUIShim::OnUpdateVSyncParameters(int surface_id,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) {
+
+ int render_process_id = 0;
+ int render_widget_id = 0;
+ if (!GpuSurfaceTracker::Get()->GetRenderWidgetIDForSurface(
+ surface_id, &render_process_id, &render_widget_id)) {
+ return;
+ }
+ RenderWidgetHost* rwh =
+ RenderWidgetHost::FromID(render_process_id, render_widget_id);
+ if (!rwh)
+ return;
+ RenderWidgetHostImpl::From(rwh)->UpdateVSyncParameters(timebase, interval);
+}
+
+void GpuProcessHostUIShim::OnLogMessage(
+ int level,
+ const std::string& header,
+ const std::string& message) {
+ GpuDataManagerImpl::GetInstance()->AddLogMessage(
+ level, header, message);
+}
+
+void GpuProcessHostUIShim::OnGraphicsInfoCollected(
+ const gpu::GPUInfo& gpu_info) {
+ // OnGraphicsInfoCollected is sent back after the GPU process successfully
+ // initializes GL.
+ TRACE_EVENT0("test_gpu", "OnGraphicsInfoCollected");
+
+ GpuDataManagerImpl::GetInstance()->UpdateGpuInfo(gpu_info);
+}
+
+#if defined(TOOLKIT_GTK) || defined(OS_WIN)
+
+void GpuProcessHostUIShim::OnResizeView(int32 surface_id,
+ int32 route_id,
+ gfx::Size size) {
+ // Always respond even if the window no longer exists. The GPU process cannot
+ // make progress on the resizing command buffer until it receives the
+ // response.
+ ScopedSendOnIOThread delayed_send(
+ host_id_,
+ new AcceleratedSurfaceMsg_ResizeViewACK(route_id));
+
+ RenderWidgetHostViewPort* view =
+ GetRenderWidgetHostViewFromSurfaceID(surface_id);
+ if (!view)
+ return;
+
+ gfx::GLSurfaceHandle surface = view->GetCompositingSurface();
+
+ // Resize the window synchronously. The GPU process must not issue GL
+ // calls on the command buffer until the window is the size it expects it
+ // to be.
+#if defined(TOOLKIT_GTK)
+ GdkWindow* window = reinterpret_cast<GdkWindow*>(
+ gdk_xid_table_lookup(surface.handle));
+ if (window) {
+ Display* display = GDK_WINDOW_XDISPLAY(window);
+ gdk_window_resize(window, size.width(), size.height());
+ XSync(display, False);
+ }
+#elif defined(OS_WIN)
+ // Ensure window does not have zero area because D3D cannot create a zero
+ // area swap chain.
+ SetWindowPos(surface.handle,
+ NULL,
+ 0, 0,
+ std::max(1, size.width()),
+ std::max(1, size.height()),
+ SWP_NOSENDCHANGING | SWP_NOCOPYBITS | SWP_NOZORDER |
+ SWP_NOACTIVATE | SWP_DEFERERASE | SWP_NOMOVE);
+#endif
+}
+
+#endif
+
+static base::TimeDelta GetSwapDelay() {
+ CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ int delay = 0;
+ if (cmd_line->HasSwitch(switches::kGpuSwapDelay)) {
+ base::StringToInt(cmd_line->GetSwitchValueNative(
+ switches::kGpuSwapDelay).c_str(), &delay);
+ }
+ return base::TimeDelta::FromMilliseconds(delay);
+}
+
+void GpuProcessHostUIShim::OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params) {
+ TRACE_EVENT0("renderer",
+ "GpuProcessHostUIShim::OnAcceleratedSurfaceBuffersSwapped");
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.mailbox_name = params.mailbox_name;
+ ack_params.sync_point = 0;
+ ScopedSendOnIOThread delayed_send(
+ host_id_,
+ new AcceleratedSurfaceMsg_BufferPresented(params.route_id,
+ ack_params));
+
+ if (!params.mailbox_name.empty() &&
+ params.mailbox_name.length() != GL_MAILBOX_SIZE_CHROMIUM)
+ return;
+
+ RenderWidgetHostViewPort* view = GetRenderWidgetHostViewFromSurfaceID(
+ params.surface_id);
+ if (!view)
+ return;
+
+ delayed_send.Cancel();
+
+ static const base::TimeDelta swap_delay = GetSwapDelay();
+ if (swap_delay.ToInternalValue())
+ base::PlatformThread::Sleep(swap_delay);
+
+ // View must send ACK message after next composite.
+ view->AcceleratedSurfaceBuffersSwapped(params, host_id_);
+ view->DidReceiveRendererFrame();
+}
+
+void GpuProcessHostUIShim::OnFrameDrawn(const ui::LatencyInfo& latency_info) {
+ RenderWidgetHostImpl::CompositorFrameDrawn(latency_info);
+}
+
+void GpuProcessHostUIShim::OnAcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params) {
+ TRACE_EVENT0("renderer",
+ "GpuProcessHostUIShim::OnAcceleratedSurfacePostSubBuffer");
+
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.mailbox_name = params.mailbox_name;
+ ack_params.sync_point = 0;
+ ScopedSendOnIOThread delayed_send(
+ host_id_,
+ new AcceleratedSurfaceMsg_BufferPresented(params.route_id,
+ ack_params));
+
+ if (!params.mailbox_name.empty() &&
+ params.mailbox_name.length() != GL_MAILBOX_SIZE_CHROMIUM)
+ return;
+
+ RenderWidgetHostViewPort* view =
+ GetRenderWidgetHostViewFromSurfaceID(params.surface_id);
+ if (!view)
+ return;
+
+ delayed_send.Cancel();
+
+ // View must send ACK message after next composite.
+ view->AcceleratedSurfacePostSubBuffer(params, host_id_);
+ view->DidReceiveRendererFrame();
+}
+
+void GpuProcessHostUIShim::OnAcceleratedSurfaceSuspend(int32 surface_id) {
+ TRACE_EVENT0("renderer",
+ "GpuProcessHostUIShim::OnAcceleratedSurfaceSuspend");
+
+ RenderWidgetHostViewPort* view =
+ GetRenderWidgetHostViewFromSurfaceID(surface_id);
+ if (!view)
+ return;
+
+ view->AcceleratedSurfaceSuspend();
+}
+
+void GpuProcessHostUIShim::OnAcceleratedSurfaceRelease(
+ const GpuHostMsg_AcceleratedSurfaceRelease_Params& params) {
+ RenderWidgetHostViewPort* view = GetRenderWidgetHostViewFromSurfaceID(
+ params.surface_id);
+ if (!view)
+ return;
+ view->AcceleratedSurfaceRelease();
+}
+
+void GpuProcessHostUIShim::OnVideoMemoryUsageStatsReceived(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats) {
+ GpuDataManagerImpl::GetInstance()->UpdateVideoMemoryUsageStats(
+ video_memory_usage_stats);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_process_host_ui_shim.h b/chromium/content/browser/gpu/gpu_process_host_ui_shim.h
new file mode 100644
index 00000000000..9f4329a10a5
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_process_host_ui_shim.h
@@ -0,0 +1,118 @@
+// 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 CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_UI_SHIM_H_
+#define CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_UI_SHIM_H_
+
+// This class lives on the UI thread and supports classes like the
+// BackingStoreProxy, which must live on the UI thread. The IO thread
+// portion of this class, the GpuProcessHost, is responsible for
+// shuttling messages between the browser and GPU processes.
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+#include "content/common/message_router.h"
+#include "content/public/common/gpu_memory_stats.h"
+#include "gpu/config/gpu_info.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_sender.h"
+
+struct GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params;
+struct GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params;
+struct GpuHostMsg_AcceleratedSurfaceRelease_Params;
+
+namespace ui {
+struct LatencyInfo;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+void RouteToGpuProcessHostUIShimTask(int host_id, const IPC::Message& msg);
+
+class GpuProcessHostUIShim : public IPC::Listener,
+ public IPC::Sender,
+ public base::NonThreadSafe {
+ public:
+ // Create a GpuProcessHostUIShim with the given ID. The object can be found
+ // using FromID with the same id.
+ static GpuProcessHostUIShim* Create(int host_id);
+
+ // Destroy the GpuProcessHostUIShim with the given host ID. This can only
+ // be called on the UI thread. Only the GpuProcessHost should destroy the
+ // UI shim.
+ static void Destroy(int host_id, const std::string& message);
+
+ // Destroy all remaining GpuProcessHostUIShims.
+ CONTENT_EXPORT static void DestroyAll();
+
+ CONTENT_EXPORT static GpuProcessHostUIShim* FromID(int host_id);
+
+ // Get a GpuProcessHostUIShim instance; it doesn't matter which one.
+ // Return NULL if none has been created.
+ CONTENT_EXPORT static GpuProcessHostUIShim* GetOneInstance();
+
+ // IPC::Sender implementation.
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // IPC::Listener implementation.
+ // The GpuProcessHost causes this to be called on the UI thread to
+ // dispatch the incoming messages from the GPU process, which are
+ // actually received on the IO thread.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ CONTENT_EXPORT void SimulateRemoveAllContext();
+ CONTENT_EXPORT void SimulateCrash();
+ CONTENT_EXPORT void SimulateHang();
+
+ private:
+ explicit GpuProcessHostUIShim(int host_id);
+ virtual ~GpuProcessHostUIShim();
+
+ // Message handlers.
+ bool OnControlMessageReceived(const IPC::Message& message);
+
+ void OnLogMessage(int level, const std::string& header,
+ const std::string& message);
+#if defined(TOOLKIT_GTK) || defined(OS_WIN)
+ void OnResizeView(int32 surface_id,
+ int32 route_id,
+ gfx::Size size);
+#endif
+
+ void OnGraphicsInfoCollected(const gpu::GPUInfo& gpu_info);
+
+ void OnAcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params);
+ void OnAcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params);
+ void OnAcceleratedSurfaceSuspend(int32 surface_id);
+ void OnAcceleratedSurfaceRelease(
+ const GpuHostMsg_AcceleratedSurfaceRelease_Params& params);
+ void OnVideoMemoryUsageStatsReceived(
+ const GPUVideoMemoryUsageStats& video_memory_usage_stats);
+ void OnUpdateVSyncParameters(int surface_id,
+ base::TimeTicks timebase,
+ base::TimeDelta interval);
+ void OnFrameDrawn(const ui::LatencyInfo& latency_info);
+
+ // The serial number of the GpuProcessHost / GpuProcessHostUIShim pair.
+ int host_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_GPU_PROCESS_HOST_UI_SHIM_H_
diff --git a/chromium/content/browser/gpu/gpu_surface_tracker.cc b/chromium/content/browser/gpu/gpu_surface_tracker.cc
new file mode 100644
index 00000000000..350e6f3831c
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_surface_tracker.cc
@@ -0,0 +1,200 @@
+// 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 "content/browser/gpu/gpu_surface_tracker.h"
+
+#if defined(OS_ANDROID)
+#include <android/native_window_jni.h>
+#endif // defined(OS_ANDROID)
+
+#include "base/logging.h"
+
+#if defined(TOOLKIT_GTK)
+#include "base/bind.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/gfx/gtk_native_view_id_manager.h"
+#endif // defined(TOOLKIT_GTK)
+
+namespace content {
+
+namespace {
+#if defined(TOOLKIT_GTK)
+
+void ReleasePermanentXIDDispatcher(
+ const gfx::PluginWindowHandle& surface) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ manager->ReleasePermanentXID(surface);
+}
+
+// Implementation of SurfaceRef that allows GTK to ref and unref the
+// surface with the GtkNativeViewManager.
+class SurfaceRefPluginWindow : public GpuSurfaceTracker::SurfaceRef {
+ public:
+ SurfaceRefPluginWindow(const gfx::PluginWindowHandle& surface_ref);
+ private:
+ virtual ~SurfaceRefPluginWindow();
+ gfx::PluginWindowHandle surface_;
+};
+
+SurfaceRefPluginWindow::SurfaceRefPluginWindow(
+ const gfx::PluginWindowHandle& surface)
+ : surface_(surface) {
+ if (surface_ != gfx::kNullPluginWindow) {
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ if (!manager->AddRefPermanentXID(surface_)) {
+ LOG(ERROR) << "Surface " << surface << " cannot be referenced.";
+ }
+ }
+}
+
+SurfaceRefPluginWindow::~SurfaceRefPluginWindow() {
+ if (surface_ != gfx::kNullPluginWindow) {
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ReleasePermanentXIDDispatcher,
+ surface_));
+ }
+}
+#endif // defined(TOOLKIT_GTK)
+} // anonymous
+
+GpuSurfaceTracker::GpuSurfaceTracker()
+ : next_surface_id_(1) {
+ GpuSurfaceLookup::InitInstance(this);
+}
+
+GpuSurfaceTracker::~GpuSurfaceTracker() {
+ GpuSurfaceLookup::InitInstance(NULL);
+}
+
+GpuSurfaceTracker* GpuSurfaceTracker::GetInstance() {
+ return Singleton<GpuSurfaceTracker>::get();
+}
+
+int GpuSurfaceTracker::AddSurfaceForRenderer(int renderer_id,
+ int render_widget_id) {
+ base::AutoLock lock(lock_);
+ int surface_id = next_surface_id_++;
+ surface_map_[surface_id] =
+ SurfaceInfo(renderer_id, render_widget_id, gfx::kNullAcceleratedWidget,
+ gfx::GLSurfaceHandle(), NULL);
+ return surface_id;
+}
+
+int GpuSurfaceTracker::LookupSurfaceForRenderer(int renderer_id,
+ int render_widget_id) {
+ base::AutoLock lock(lock_);
+ for (SurfaceMap::iterator it = surface_map_.begin(); it != surface_map_.end();
+ ++it) {
+ const SurfaceInfo& info = it->second;
+ if (info.renderer_id == renderer_id &&
+ info.render_widget_id == render_widget_id) {
+ return it->first;
+ }
+ }
+ return 0;
+}
+
+int GpuSurfaceTracker::AddSurfaceForNativeWidget(
+ gfx::AcceleratedWidget widget) {
+ base::AutoLock lock(lock_);
+ int surface_id = next_surface_id_++;
+ surface_map_[surface_id] =
+ SurfaceInfo(0, 0, widget, gfx::GLSurfaceHandle(), NULL);
+ return surface_id;
+}
+
+void GpuSurfaceTracker::RemoveSurface(int surface_id) {
+ base::AutoLock lock(lock_);
+ DCHECK(surface_map_.find(surface_id) != surface_map_.end());
+ surface_map_.erase(surface_id);
+}
+
+bool GpuSurfaceTracker::GetRenderWidgetIDForSurface(int surface_id,
+ int* renderer_id,
+ int* render_widget_id) {
+ base::AutoLock lock(lock_);
+ SurfaceMap::iterator it = surface_map_.find(surface_id);
+ if (it == surface_map_.end())
+ return false;
+ const SurfaceInfo& info = it->second;
+ if (!info.handle.is_transport())
+ return false;
+ *renderer_id = info.renderer_id;
+ *render_widget_id = info.render_widget_id;
+ return true;
+}
+
+void GpuSurfaceTracker::SetSurfaceHandle(int surface_id,
+ const gfx::GLSurfaceHandle& handle) {
+ base::AutoLock lock(lock_);
+ DCHECK(surface_map_.find(surface_id) != surface_map_.end());
+ SurfaceInfo& info = surface_map_[surface_id];
+ info.handle = handle;
+#if defined(TOOLKIT_GTK)
+ info.surface_ref = new SurfaceRefPluginWindow(handle.handle);
+#endif // defined(TOOLKIT_GTK)
+}
+
+gfx::GLSurfaceHandle GpuSurfaceTracker::GetSurfaceHandle(int surface_id) {
+ base::AutoLock lock(lock_);
+ SurfaceMap::iterator it = surface_map_.find(surface_id);
+ if (it == surface_map_.end())
+ return gfx::GLSurfaceHandle();
+ return it->second.handle;
+}
+
+gfx::AcceleratedWidget GpuSurfaceTracker::AcquireNativeWidget(int surface_id) {
+ base::AutoLock lock(lock_);
+ SurfaceMap::iterator it = surface_map_.find(surface_id);
+ if (it == surface_map_.end())
+ return gfx::kNullAcceleratedWidget;
+
+#if defined(OS_ANDROID)
+ if (it->second.native_widget != gfx::kNullAcceleratedWidget)
+ ANativeWindow_acquire(it->second.native_widget);
+#endif // defined(OS_ANDROID)
+
+ return it->second.native_widget;
+}
+
+void GpuSurfaceTracker::SetNativeWidget(
+ int surface_id, gfx::AcceleratedWidget widget,
+ SurfaceRef* surface_ref) {
+ base::AutoLock lock(lock_);
+ SurfaceMap::iterator it = surface_map_.find(surface_id);
+ DCHECK(it != surface_map_.end());
+ SurfaceInfo& info = it->second;
+ info.native_widget = widget;
+ info.surface_ref = surface_ref;
+}
+
+std::size_t GpuSurfaceTracker::GetSurfaceCount() {
+ base::AutoLock lock(lock_);
+ return surface_map_.size();
+}
+
+GpuSurfaceTracker::SurfaceInfo::SurfaceInfo()
+ : renderer_id(0),
+ render_widget_id(0),
+ native_widget(gfx::kNullAcceleratedWidget) { }
+
+GpuSurfaceTracker::SurfaceInfo::SurfaceInfo(
+ int renderer_id,
+ int render_widget_id,
+ const gfx::AcceleratedWidget& native_widget,
+ const gfx::GLSurfaceHandle& handle,
+ const scoped_refptr<SurfaceRef>& surface_ref)
+ : renderer_id(renderer_id),
+ render_widget_id(render_widget_id),
+ native_widget(native_widget),
+ handle(handle),
+ surface_ref(surface_ref) { }
+
+GpuSurfaceTracker::SurfaceInfo::~SurfaceInfo() { }
+
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/gpu_surface_tracker.h b/chromium/content/browser/gpu/gpu_surface_tracker.h
new file mode 100644
index 00000000000..de9d666dbe8
--- /dev/null
+++ b/chromium/content/browser/gpu/gpu_surface_tracker.h
@@ -0,0 +1,138 @@
+// 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 CONTENT_BROWSER_GPU_GPU_SURFACE_TRACKER_H_
+#define CONTENT_BROWSER_GPU_GPU_SURFACE_TRACKER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "content/common/gpu/gpu_surface_lookup.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+// This class is responsible for managing rendering surfaces exposed to the
+// GPU process. Every surface gets registered to this class, and gets an ID.
+// All calls to and from the GPU process, with the exception of
+// CreateViewCommandBuffer, refer to the rendering surface by its ID.
+// This class is thread safe.
+//
+// Note: The ID can exist before the actual native handle for the surface is
+// created, for example to allow giving a reference to it to a renderer, so that
+// it is unamibiguously identified.
+class GpuSurfaceTracker : public GpuSurfaceLookup {
+ public:
+ // Base class for reference counting surfaces. We store a
+ // reference to an instance of this class in the surface_map_
+ // and GpuProcessHost (if the GPU process is drawing to
+ // the surface with a Command Buffer). The reference count ensures that
+ // we don't destroy the object until it's released from both places.
+ //
+ // This is especially important on Android and GTK where the surface must
+ // not be destroyed when the WebContents is closed if the GPU is still
+ // drawing to it. Those platforms extend this class with the functionality
+ // they need to implement on tear down (see SurfaceRefPluginWindow for GTK and
+ // SurfaceRefAndroid for Android).
+ class SurfaceRef : public base::RefCountedThreadSafe<SurfaceRef> {
+ protected:
+ SurfaceRef() { }
+ virtual ~SurfaceRef() { }
+
+ private:
+ friend class base::RefCountedThreadSafe<SurfaceRef>;
+ DISALLOW_COPY_AND_ASSIGN(SurfaceRef);
+ };
+
+ // GpuSurfaceLookup implementation:
+ // Returns the native widget associated with a given surface_id.
+ virtual gfx::AcceleratedWidget AcquireNativeWidget(int surface_id) OVERRIDE;
+
+ // Gets the global instance of the surface tracker.
+ static GpuSurfaceTracker* Get() { return GetInstance(); }
+
+ // Adds a surface for a given RenderWidgetHost. |renderer_id| is the renderer
+ // process ID, |render_widget_id| is the RenderWidgetHost route id within that
+ // renderer. Returns the surface ID.
+ int AddSurfaceForRenderer(int renderer_id, int render_widget_id);
+
+ // Looks up a surface for a given RenderWidgetHost. Returns the surface
+ // ID, or 0 if not found.
+ // Note: This is an O(N) lookup.
+ int LookupSurfaceForRenderer(int renderer_id, int render_widget_id);
+
+ // Adds a surface for a native widget. Returns the surface ID.
+ int AddSurfaceForNativeWidget(gfx::AcceleratedWidget widget);
+
+ // Removes a given existing surface.
+ void RemoveSurface(int surface_id);
+
+ // Gets the renderer process ID and RenderWidgetHost route id for a given
+ // surface, returning true if the surface is found (and corresponds to a
+ // RenderWidgetHost), or false if not.
+ bool GetRenderWidgetIDForSurface(int surface_id,
+ int* renderer_id,
+ int* render_widget_id);
+
+ // Sets the native handle for the given surface.
+ // Note: This is an O(log N) lookup.
+ void SetSurfaceHandle(int surface_id, const gfx::GLSurfaceHandle& handle);
+
+ // Sets the native widget associated with the surface_id.
+ void SetNativeWidget(
+ int surface_id,
+ gfx::AcceleratedWidget widget,
+ SurfaceRef* surface_ref);
+
+ // Gets the native handle for the given surface.
+ // Note: This is an O(log N) lookup.
+ gfx::GLSurfaceHandle GetSurfaceHandle(int surface_id);
+
+ // Returns the number of surfaces currently registered with the tracker.
+ std::size_t GetSurfaceCount();
+
+ // Gets the global instance of the surface tracker. Identical to Get(), but
+ // named that way for the implementation of Singleton.
+ static GpuSurfaceTracker* GetInstance();
+
+ scoped_refptr<SurfaceRef> GetSurfaceRefForSurface(int surface_id) {
+ return surface_map_[surface_id].surface_ref;
+ }
+
+ private:
+ struct SurfaceInfo {
+ SurfaceInfo();
+ SurfaceInfo(int renderer_id,
+ int render_widget_id,
+ const gfx::AcceleratedWidget& native_widget,
+ const gfx::GLSurfaceHandle& handle,
+ const scoped_refptr<SurfaceRef>& surface_ref);
+ ~SurfaceInfo();
+ int renderer_id;
+ int render_widget_id;
+ gfx::AcceleratedWidget native_widget;
+ gfx::GLSurfaceHandle handle;
+ scoped_refptr<SurfaceRef> surface_ref;
+ };
+ typedef std::map<int, SurfaceInfo> SurfaceMap;
+
+ friend struct DefaultSingletonTraits<GpuSurfaceTracker>;
+
+ GpuSurfaceTracker();
+ virtual ~GpuSurfaceTracker();
+
+ base::Lock lock_;
+ SurfaceMap surface_map_;
+ int next_surface_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuSurfaceTracker);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_GPU_SURFACE_TRACKER_H_
diff --git a/chromium/content/browser/gpu/shader_disk_cache.cc b/chromium/content/browser/gpu/shader_disk_cache.cc
new file mode 100644
index 00000000000..fc578bc4bf0
--- /dev/null
+++ b/chromium/content/browser/gpu/shader_disk_cache.cc
@@ -0,0 +1,617 @@
+// Copyright (c) 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 "content/browser/gpu/shader_disk_cache.h"
+
+#include "base/threading/thread_checker.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "gpu/command_buffer/common/constants.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+namespace {
+
+static const base::FilePath::CharType kGpuCachePath[] =
+ FILE_PATH_LITERAL("GPUCache");
+
+void EntryCloser(disk_cache::Entry* entry) {
+ entry->Close();
+}
+
+} // namespace
+
+// ShaderDiskCacheEntry handles the work of caching/updating the cached
+// shaders.
+class ShaderDiskCacheEntry
+ : public base::ThreadChecker,
+ public base::RefCounted<ShaderDiskCacheEntry> {
+ public:
+ ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
+ const std::string& key,
+ const std::string& shader);
+ void Cache();
+
+ private:
+ friend class base::RefCounted<ShaderDiskCacheEntry>;
+
+ enum OpType {
+ TERMINATE,
+ OPEN_ENTRY,
+ WRITE_DATA,
+ CREATE_ENTRY,
+ };
+
+ ~ShaderDiskCacheEntry();
+
+ void OnOpComplete(int rv);
+
+ int OpenCallback(int rv);
+ int WriteCallback(int rv);
+ int IOComplete(int rv);
+
+ base::WeakPtr<ShaderDiskCache> cache_;
+ OpType op_type_;
+ std::string key_;
+ std::string shader_;
+ disk_cache::Entry* entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry);
+};
+
+// ShaderDiskReadHelper is used to load all of the cached shaders from the
+// disk cache and send to the memory cache.
+class ShaderDiskReadHelper
+ : public base::ThreadChecker,
+ public base::RefCounted<ShaderDiskReadHelper> {
+ public:
+ ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache, int host_id);
+ void LoadCache();
+
+ private:
+ friend class base::RefCounted<ShaderDiskReadHelper>;
+
+ enum OpType {
+ TERMINATE,
+ OPEN_NEXT,
+ OPEN_NEXT_COMPLETE,
+ READ_COMPLETE,
+ ITERATION_FINISHED
+ };
+
+
+ ~ShaderDiskReadHelper();
+
+ void OnOpComplete(int rv);
+
+ int OpenNextEntry();
+ int OpenNextEntryComplete(int rv);
+ int ReadComplete(int rv);
+ int IterationComplete(int rv);
+
+ base::WeakPtr<ShaderDiskCache> cache_;
+ OpType op_type_;
+ void* iter_;
+ scoped_refptr<net::IOBufferWithSize> buf_;
+ int host_id_;
+ disk_cache::Entry* entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper);
+};
+
+class ShaderClearHelper
+ : public base::RefCounted<ShaderClearHelper>,
+ public base::SupportsWeakPtr<ShaderClearHelper> {
+ public:
+ ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
+ const base::FilePath& path,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const base::Closure& callback);
+ void Clear();
+
+ private:
+ friend class base::RefCounted<ShaderClearHelper>;
+
+ enum OpType {
+ TERMINATE,
+ VERIFY_CACHE_SETUP,
+ DELETE_CACHE
+ };
+
+ ~ShaderClearHelper();
+
+ void DoClearShaderCache(int rv);
+
+ scoped_refptr<ShaderDiskCache> cache_;
+ OpType op_type_;
+ base::FilePath path_;
+ base::Time delete_begin_;
+ base::Time delete_end_;
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper);
+};
+
+ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
+ const std::string& key,
+ const std::string& shader)
+ : cache_(cache),
+ op_type_(OPEN_ENTRY),
+ key_(key),
+ shader_(shader),
+ entry_(NULL) {
+}
+
+ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
+ if (entry_)
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&EntryCloser, entry_));
+}
+
+void ShaderDiskCacheEntry::Cache() {
+ DCHECK(CalledOnValidThread());
+ if (!cache_.get())
+ return;
+
+ int rv = cache_->backend()->OpenEntry(
+ key_,
+ &entry_,
+ base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
+ if (rv != net::ERR_IO_PENDING)
+ OnOpComplete(rv);
+}
+
+void ShaderDiskCacheEntry::OnOpComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ if (!cache_.get())
+ return;
+
+ do {
+ switch (op_type_) {
+ case OPEN_ENTRY:
+ rv = OpenCallback(rv);
+ break;
+ case CREATE_ENTRY:
+ rv = WriteCallback(rv);
+ break;
+ case WRITE_DATA:
+ rv = IOComplete(rv);
+ break;
+ case TERMINATE:
+ rv = net::ERR_IO_PENDING; // break the loop.
+ break;
+ default:
+ NOTREACHED(); // Invalid op_type_ provided.
+ break;
+ }
+ } while (rv != net::ERR_IO_PENDING);
+}
+
+int ShaderDiskCacheEntry::OpenCallback(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ if (rv == net::OK) {
+ cache_->backend()->OnExternalCacheHit(key_);
+ cache_->EntryComplete(this);
+ op_type_ = TERMINATE;
+ return rv;
+ }
+
+ op_type_ = CREATE_ENTRY;
+ return cache_->backend()->CreateEntry(
+ key_,
+ &entry_,
+ base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
+}
+
+int ShaderDiskCacheEntry::WriteCallback(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ if (rv != net::OK) {
+ LOG(ERROR) << "Failed to create shader cache entry: " << rv;
+ cache_->EntryComplete(this);
+ op_type_ = TERMINATE;
+ return rv;
+ }
+
+ op_type_ = WRITE_DATA;
+ scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_);
+ return entry_->WriteData(
+ 1,
+ 0,
+ io_buf.get(),
+ shader_.length(),
+ base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this),
+ false);
+}
+
+int ShaderDiskCacheEntry::IOComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ cache_->EntryComplete(this);
+ op_type_ = TERMINATE;
+ return rv;
+}
+
+ShaderDiskReadHelper::ShaderDiskReadHelper(
+ base::WeakPtr<ShaderDiskCache> cache,
+ int host_id)
+ : cache_(cache),
+ op_type_(OPEN_NEXT),
+ iter_(NULL),
+ buf_(NULL),
+ host_id_(host_id),
+ entry_(NULL) {
+}
+
+void ShaderDiskReadHelper::LoadCache() {
+ DCHECK(CalledOnValidThread());
+ if (!cache_.get())
+ return;
+ OnOpComplete(net::OK);
+}
+
+void ShaderDiskReadHelper::OnOpComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ if (!cache_.get())
+ return;
+
+ do {
+ switch (op_type_) {
+ case OPEN_NEXT:
+ rv = OpenNextEntry();
+ break;
+ case OPEN_NEXT_COMPLETE:
+ rv = OpenNextEntryComplete(rv);
+ break;
+ case READ_COMPLETE:
+ rv = ReadComplete(rv);
+ break;
+ case ITERATION_FINISHED:
+ rv = IterationComplete(rv);
+ break;
+ case TERMINATE:
+ cache_->ReadComplete();
+ rv = net::ERR_IO_PENDING; // break the loop
+ break;
+ default:
+ NOTREACHED(); // Invalid state for read helper
+ rv = net::ERR_FAILED;
+ break;
+ }
+ } while (rv != net::ERR_IO_PENDING);
+}
+
+int ShaderDiskReadHelper::OpenNextEntry() {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ op_type_ = OPEN_NEXT_COMPLETE;
+ return cache_->backend()->OpenNextEntry(
+ &iter_,
+ &entry_,
+ base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
+}
+
+int ShaderDiskReadHelper::OpenNextEntryComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ if (rv == net::ERR_FAILED) {
+ op_type_ = ITERATION_FINISHED;
+ return net::OK;
+ }
+
+ if (rv < 0)
+ return rv;
+
+ op_type_ = READ_COMPLETE;
+ buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1));
+ return entry_->ReadData(
+ 1,
+ 0,
+ buf_.get(),
+ buf_->size(),
+ base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
+}
+
+int ShaderDiskReadHelper::ReadComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ if (rv && rv == buf_->size()) {
+ GpuProcessHost* host = GpuProcessHost::FromID(host_id_);
+ if (host)
+ host->LoadedShader(entry_->GetKey(), std::string(buf_->data(),
+ buf_->size()));
+ }
+
+ buf_ = NULL;
+ entry_->Close();
+ entry_ = NULL;
+
+ op_type_ = OPEN_NEXT;
+ return net::OK;
+}
+
+int ShaderDiskReadHelper::IterationComplete(int rv) {
+ DCHECK(CalledOnValidThread());
+ // Called through OnOpComplete, so we know |cache_| is valid.
+ cache_->backend()->EndEnumeration(&iter_);
+ iter_ = NULL;
+ op_type_ = TERMINATE;
+ return net::OK;
+}
+
+ShaderDiskReadHelper::~ShaderDiskReadHelper() {
+ if (entry_)
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&EntryCloser, entry_));
+}
+
+ShaderClearHelper::ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
+ const base::FilePath& path,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const base::Closure& callback)
+ : cache_(cache),
+ op_type_(VERIFY_CACHE_SETUP),
+ path_(path),
+ delete_begin_(delete_begin),
+ delete_end_(delete_end),
+ callback_(callback) {
+}
+
+ShaderClearHelper::~ShaderClearHelper() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+void ShaderClearHelper::Clear() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DoClearShaderCache(net::OK);
+}
+
+void ShaderClearHelper::DoClearShaderCache(int rv) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Hold a ref to ourselves so when we do the CacheCleared call we don't get
+ // auto-deleted when our ref count drops to zero.
+ scoped_refptr<ShaderClearHelper> helper = this;
+
+ while (rv != net::ERR_IO_PENDING) {
+ switch (op_type_) {
+ case VERIFY_CACHE_SETUP:
+ rv = cache_->SetAvailableCallback(
+ base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
+ op_type_ = DELETE_CACHE;
+ break;
+ case DELETE_CACHE:
+ rv = cache_->Clear(
+ delete_begin_, delete_end_,
+ base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
+ op_type_ = TERMINATE;
+ break;
+ case TERMINATE:
+ ShaderCacheFactory::GetInstance()->CacheCleared(path_);
+ callback_.Run();
+ rv = net::ERR_IO_PENDING; // Break the loop.
+ break;
+ default:
+ NOTREACHED(); // Invalid state provided.
+ op_type_ = TERMINATE;
+ break;
+ }
+ }
+}
+
+// static
+ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
+ return Singleton<ShaderCacheFactory,
+ LeakySingletonTraits<ShaderCacheFactory> >::get();
+}
+
+ShaderCacheFactory::ShaderCacheFactory() {
+}
+
+ShaderCacheFactory::~ShaderCacheFactory() {
+}
+
+void ShaderCacheFactory::SetCacheInfo(int32 client_id,
+ const base::FilePath& path) {
+ client_id_to_path_map_[client_id] = path;
+}
+
+void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
+ client_id_to_path_map_.erase(client_id);
+}
+
+scoped_refptr<ShaderDiskCache> ShaderCacheFactory::Get(int32 client_id) {
+ ClientIdToPathMap::iterator iter =
+ client_id_to_path_map_.find(client_id);
+ if (iter == client_id_to_path_map_.end())
+ return NULL;
+ return ShaderCacheFactory::GetByPath(iter->second);
+}
+
+scoped_refptr<ShaderDiskCache> ShaderCacheFactory::GetByPath(
+ const base::FilePath& path) {
+ ShaderCacheMap::iterator iter = shader_cache_map_.find(path);
+ if (iter != shader_cache_map_.end())
+ return iter->second;
+
+ ShaderDiskCache* cache = new ShaderDiskCache(path);
+ cache->Init();
+ return cache;
+}
+
+void ShaderCacheFactory::AddToCache(const base::FilePath& key,
+ ShaderDiskCache* cache) {
+ shader_cache_map_[key] = cache;
+}
+
+void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
+ shader_cache_map_.erase(key);
+}
+
+void ShaderCacheFactory::ClearByPath(const base::FilePath& path,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!callback.is_null());
+
+ scoped_refptr<ShaderClearHelper> helper = new ShaderClearHelper(
+ GetByPath(path), path, delete_begin, delete_end, callback);
+
+ // We could receive requests to clear the same path with different
+ // begin/end times. So, we keep a list of requests. If we haven't seen this
+ // path before we kick off the clear and add it to the list. If we have see it
+ // already, then we already have a clear running. We add this clear to the
+ // list and wait for any previous clears to finish.
+ ShaderClearMap::iterator iter = shader_clear_map_.find(path);
+ if (iter != shader_clear_map_.end()) {
+ iter->second.push(helper);
+ return;
+ }
+
+ shader_clear_map_.insert(
+ std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue()));
+ shader_clear_map_[path].push(helper);
+ helper->Clear();
+}
+
+void ShaderCacheFactory::CacheCleared(const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ ShaderClearMap::iterator iter = shader_clear_map_.find(path);
+ if (iter == shader_clear_map_.end()) {
+ LOG(ERROR) << "Completed clear but missing clear helper.";
+ return;
+ }
+
+ iter->second.pop();
+
+ // If there are remaining items in the list we trigger the Clear on the
+ // next one.
+ if (!iter->second.empty()) {
+ iter->second.front()->Clear();
+ return;
+ }
+
+ shader_clear_map_.erase(path);
+}
+
+ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path)
+ : cache_available_(false),
+ host_id_(0),
+ cache_path_(cache_path),
+ is_initialized_(false) {
+ ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this);
+}
+
+ShaderDiskCache::~ShaderDiskCache() {
+ ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
+}
+
+void ShaderDiskCache::Init() {
+ if (is_initialized_) {
+ NOTREACHED(); // can't initialize disk cache twice.
+ return;
+ }
+ is_initialized_ = true;
+
+ int rv = disk_cache::CreateCacheBackend(
+ net::SHADER_CACHE,
+ net::CACHE_BACKEND_BLOCKFILE,
+ cache_path_.Append(kGpuCachePath),
+ gpu::kDefaultMaxProgramCacheMemoryBytes,
+ true,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
+ NULL,
+ &backend_,
+ base::Bind(&ShaderDiskCache::CacheCreatedCallback, this));
+
+ if (rv == net::OK)
+ cache_available_ = true;
+}
+
+void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) {
+ if (!cache_available_)
+ return;
+
+ ShaderDiskCacheEntry* shim =
+ new ShaderDiskCacheEntry(AsWeakPtr(), key, shader);
+ shim->Cache();
+
+ entry_map_[shim] = shim;
+}
+
+int ShaderDiskCache::Clear(
+ const base::Time begin_time, const base::Time end_time,
+ const net::CompletionCallback& completion_callback) {
+ int rv;
+ if (begin_time.is_null()) {
+ rv = backend_->DoomAllEntries(completion_callback);
+ } else {
+ rv = backend_->DoomEntriesBetween(begin_time, end_time,
+ completion_callback);
+ }
+ return rv;
+}
+
+int32 ShaderDiskCache::Size() {
+ if (!cache_available_)
+ return -1;
+ return backend_->GetEntryCount();
+}
+
+int ShaderDiskCache::SetAvailableCallback(
+ const net::CompletionCallback& callback) {
+ if (cache_available_)
+ return net::OK;
+ available_callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+void ShaderDiskCache::CacheCreatedCallback(int rv) {
+ if (rv != net::OK) {
+ LOG(ERROR) << "Shader Cache Creation failed: " << rv;
+ return;
+ }
+ helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_);
+ helper_->LoadCache();
+}
+
+void ShaderDiskCache::EntryComplete(void* entry) {
+ entry_map_.erase(entry);
+
+ if (entry_map_.empty() && !cache_complete_callback_.is_null())
+ cache_complete_callback_.Run(net::OK);
+}
+
+void ShaderDiskCache::ReadComplete() {
+ helper_ = NULL;
+
+ // The cache is considered available after we have finished reading any
+ // of the old cache values off disk. This prevents a potential race where we
+ // are reading from disk and execute a cache clear at the same time.
+ cache_available_ = true;
+ if (!available_callback_.is_null()) {
+ available_callback_.Run(net::OK);
+ available_callback_.Reset();
+ }
+}
+
+int ShaderDiskCache::SetCacheCompleteCallback(
+ const net::CompletionCallback& callback) {
+ if (entry_map_.empty()) {
+ return net::OK;
+ }
+ cache_complete_callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/gpu/shader_disk_cache.h b/chromium/content/browser/gpu/shader_disk_cache.h
new file mode 100644
index 00000000000..051be5876ec
--- /dev/null
+++ b/chromium/content/browser/gpu/shader_disk_cache.h
@@ -0,0 +1,154 @@
+// Copyright (c) 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 CONTENT_BROWSER_GPU_SHADER_DISK_CACHE_H_
+#define CONTENT_BROWSER_GPU_SHADER_DISK_CACHE_H_
+
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "content/common/content_export.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace content {
+
+class ShaderDiskCacheEntry;
+class ShaderDiskReadHelper;
+class ShaderClearHelper;
+
+// ShaderDiskCache is the interface to the on disk cache for
+// GL shaders.
+//
+// While this class is both RefCounted and SupportsWeakPtr
+// when using this class you should work with the RefCounting.
+// The WeakPtr is needed interally.
+class CONTENT_EXPORT ShaderDiskCache
+ : public base::RefCounted<ShaderDiskCache>,
+ public base::SupportsWeakPtr<ShaderDiskCache> {
+ public:
+ void Init();
+
+ void set_host_id(int host_id) { host_id_ = host_id; }
+
+ // Store the |shader| into the cache under |key|.
+ void Cache(const std::string& key, const std::string& shader);
+
+ // Clear a range of entries. This supports unbounded deletes in either
+ // direction by using null Time values for either |begin_time| or |end_time|.
+ // The return value is a net error code. If this method returns
+ // ERR_IO_PENDING, the |completion_callback| will be invoked when the
+ // operation completes.
+ int Clear(
+ const base::Time begin_time,
+ const base::Time end_time,
+ const net::CompletionCallback& completion_callback);
+
+ // Sets a callback for when the cache is available. If the cache is
+ // already available the callback will not be called and net::OK is returned.
+ // If the callback is set net::ERR_IO_PENDING is returned and the callback
+ // will be executed when the cache is available.
+ int SetAvailableCallback(const net::CompletionCallback& callback);
+
+ // Returns the number of elements currently in the cache.
+ int32 Size();
+
+ // Set a callback notification for when all current entries have been
+ // written to the cache.
+ // The return value is a net error code. If this method returns
+ // ERR_IO_PENDING, the |callback| will be invoked when all entries have
+ // been written to the cache.
+ int SetCacheCompleteCallback(const net::CompletionCallback& callback);
+
+ private:
+ friend class base::RefCounted<ShaderDiskCache>;
+ friend class ShaderDiskCacheEntry;
+ friend class ShaderDiskReadHelper;
+ friend class ShaderCacheFactory;
+
+ explicit ShaderDiskCache(const base::FilePath& cache_path);
+ ~ShaderDiskCache();
+
+ void CacheCreatedCallback(int rv);
+
+ disk_cache::Backend* backend() { return backend_.get(); }
+
+ void EntryComplete(void* entry);
+ void ReadComplete();
+
+ bool cache_available_;
+ int host_id_;
+ base::FilePath cache_path_;
+ bool is_initialized_;
+ net::CompletionCallback available_callback_;
+ net::CompletionCallback cache_complete_callback_;
+
+ scoped_ptr<disk_cache::Backend> backend_;
+
+ scoped_refptr<ShaderDiskReadHelper> helper_;
+ std::map<void*, scoped_refptr<ShaderDiskCacheEntry> > entry_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderDiskCache);
+};
+
+// ShaderCacheFactory maintains a cache of ShaderDiskCache objects
+// so we only create one per profile directory.
+class CONTENT_EXPORT ShaderCacheFactory {
+ public:
+ static ShaderCacheFactory* GetInstance();
+
+ // Clear the shader disk cache for the given |path|. This supports unbounded
+ // deletes in either direction by using null Time values for either
+ // |begin_time| or |end_time|. The |callback| will be executed when the
+ // clear is complete.
+ void ClearByPath(const base::FilePath& path,
+ const base::Time& begin_time,
+ const base::Time& end_time,
+ const base::Closure& callback);
+
+ // Retrieve the shader disk cache for the provided |client_id|.
+ scoped_refptr<ShaderDiskCache> Get(int32 client_id);
+
+ // Set the |path| to be used for the disk cache for |client_id|.
+ void SetCacheInfo(int32 client_id, const base::FilePath& path);
+
+ // Remove the path mapping for |client_id|.
+ void RemoveCacheInfo(int32 client_id);
+
+ // Set the provided |cache| into the cache map for the given |path|.
+ void AddToCache(const base::FilePath& path, ShaderDiskCache* cache);
+
+ // Remove the provided |path| from our cache map.
+ void RemoveFromCache(const base::FilePath& path);
+
+ private:
+ friend struct DefaultSingletonTraits<ShaderCacheFactory>;
+ friend class ShaderClearHelper;
+
+ ShaderCacheFactory();
+ ~ShaderCacheFactory();
+
+ scoped_refptr<ShaderDiskCache> GetByPath(const base::FilePath& path);
+ void CacheCleared(const base::FilePath& path);
+
+ typedef std::map<base::FilePath, ShaderDiskCache*> ShaderCacheMap;
+ ShaderCacheMap shader_cache_map_;
+
+ typedef std::map<int32, base::FilePath> ClientIdToPathMap;
+ ClientIdToPathMap client_id_to_path_map_;
+
+ typedef std::queue<scoped_refptr<ShaderClearHelper> > ShaderClearQueue;
+ typedef std::map<base::FilePath, ShaderClearQueue> ShaderClearMap;
+ ShaderClearMap shader_clear_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderCacheFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_GPU_SHADER_DISK_CACHE_H_
+
diff --git a/chromium/content/browser/gpu/shader_disk_cache_unittest.cc b/chromium/content/browser/gpu/shader_disk_cache_unittest.cc
new file mode 100644
index 00000000000..9c7061b62bb
--- /dev/null
+++ b/chromium/content/browser/gpu/shader_disk_cache_unittest.cc
@@ -0,0 +1,75 @@
+// 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 "base/files/scoped_temp_dir.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/gpu/shader_disk_cache.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+const int kDefaultClientId = 42;
+const char kCacheKey[] = "key";
+const char kCacheValue[] = "cached value";
+
+} // namespace
+
+class ShaderDiskCacheTest : public testing::Test {
+ public:
+ ShaderDiskCacheTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+ }
+
+ virtual ~ShaderDiskCacheTest() {}
+
+ const base::FilePath& cache_path() { return temp_dir_.path(); }
+
+ void InitCache() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ShaderCacheFactory::GetInstance()->SetCacheInfo(kDefaultClientId,
+ cache_path());
+ }
+
+ private:
+ virtual void TearDown() OVERRIDE {
+ ShaderCacheFactory::GetInstance()->RemoveCacheInfo(kDefaultClientId);
+ }
+
+ base::ScopedTempDir temp_dir_;
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheTest);
+};
+
+TEST_F(ShaderDiskCacheTest, ClearsCache) {
+ InitCache();
+
+ scoped_refptr<ShaderDiskCache> cache =
+ ShaderCacheFactory::GetInstance()->Get(kDefaultClientId);
+ ASSERT_TRUE(cache.get() != NULL);
+
+ net::TestCompletionCallback available_cb;
+ int rv = cache->SetAvailableCallback(available_cb.callback());
+ ASSERT_EQ(net::OK, available_cb.GetResult(rv));
+ EXPECT_EQ(0, cache->Size());
+
+ cache->Cache(kCacheKey, kCacheValue);
+
+ net::TestCompletionCallback complete_cb;
+ rv = cache->SetCacheCompleteCallback(complete_cb.callback());
+ ASSERT_EQ(net::OK, complete_cb.GetResult(rv));
+ EXPECT_EQ(1, cache->Size());
+
+ base::Time time;
+ net::TestCompletionCallback clear_cb;
+ rv = cache->Clear(time, time, clear_cb.callback());
+ ASSERT_EQ(net::OK, clear_cb.GetResult(rv));
+ EXPECT_EQ(0, cache->Size());
+};
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/test_support_gpu.gypi b/chromium/content/browser/gpu/test_support_gpu.gypi
new file mode 100644
index 00000000000..4892e9a38e9
--- /dev/null
+++ b/chromium/content/browser/gpu/test_support_gpu.gypi
@@ -0,0 +1,70 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This file is meant to be included into targets which run gpu tests.
+{
+ 'variables': {
+ 'test_list_out_dir': '<(SHARED_INTERMEDIATE_DIR)/content/test/gpu',
+ 'src_dir': '../../..',
+ },
+ 'defines': [
+ 'HAS_OUT_OF_PROC_TEST_RUNNER',
+ ],
+ 'include_dirs': [
+ '<(src_dir)',
+ '<(test_list_out_dir)',
+ ],
+ # hard_dependency is necessary for this target because it has actions
+ # that generate a header file included by dependent targets. The header
+ # file must be generated before the dependents are compiled. The usual
+ # semantics are to allow the two targets to build concurrently.
+ 'hard_dependency': 1,
+ 'conditions': [
+ ['OS=="win"', {
+ 'include_dirs': [
+ '<(DEPTH)/third_party/wtl/include',
+ ],
+ 'sources': [
+ '<(SHARED_INTERMEDIATE_DIR)/content/content_resources.rc',
+ '<(SHARED_INTERMEDIATE_DIR)/net/net_resources.rc',
+ '<(SHARED_INTERMEDIATE_DIR)/webkit/blink_resources.rc',
+ ],
+ 'conditions': [
+ ['win_use_allocator_shim==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }],
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'LinkIncremental': '<(msvs_large_module_debug_link_mode)',
+ },
+ },
+ },
+ },
+ }],
+ ['OS=="mac"', {
+ # See comments about "xcode_settings" elsewhere in this file.
+ 'xcode_settings': {'OTHER_LDFLAGS': ['-Wl,-ObjC']},
+ }],
+ ['toolkit_uses_gtk == 1', {
+ 'dependencies': [
+ '<(src_dir)/build/linux/system.gyp:gtk',
+ ],
+ }],
+ ['toolkit_uses_gtk == 1 or chromeos==1 or (OS=="linux" and use_aura==1)', {
+ 'dependencies': [
+ '<(src_dir)/build/linux/system.gyp:ssl',
+ ],
+ }],
+ ['toolkit_views==1', {
+ 'dependencies': [
+ '<(src_dir)/ui/views/views.gyp:views',
+ ],
+ }],
+ ],
+}
diff --git a/chromium/content/browser/gpu/webgl_conformance_test.cc b/chromium/content/browser/gpu/webgl_conformance_test.cc
new file mode 100644
index 00000000000..79f30782830
--- /dev/null
+++ b/chromium/content/browser/gpu/webgl_conformance_test.cc
@@ -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.
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "gpu/config/gpu_test_config.h"
+#include "gpu/config/gpu_test_expectations_parser.h"
+#include "net/base/net_util.h"
+
+namespace content {
+
+class WebGLConformanceTest : public ContentBrowserTest {
+ public:
+ WebGLConformanceTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // Allow privileged WebGL extensions.
+ command_line->AppendSwitch(switches::kEnablePrivilegedWebGLExtensions);
+#if defined(OS_ANDROID)
+ command_line->AppendSwitch(
+ switches::kDisableGestureRequirementForMediaPlayback);
+#endif
+ }
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ base::FilePath webgl_conformance_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &webgl_conformance_path);
+ webgl_conformance_path = webgl_conformance_path.Append(
+ FILE_PATH_LITERAL("third_party"));
+ webgl_conformance_path = webgl_conformance_path.Append(
+ FILE_PATH_LITERAL("webgl_conformance"));
+ ASSERT_TRUE(base::DirectoryExists(webgl_conformance_path))
+ << "Missing conformance tests: " << webgl_conformance_path.value();
+
+ PathService::Get(DIR_TEST_DATA, &test_path_);
+ test_path_ = test_path_.Append(FILE_PATH_LITERAL("gpu"));
+ test_path_ = test_path_.Append(FILE_PATH_LITERAL("webgl_conformance.html"));
+
+ ASSERT_TRUE(bot_config_.LoadCurrentConfig(NULL))
+ << "Fail to load bot configuration";
+ ASSERT_TRUE(bot_config_.IsValid())
+ << "Invalid bot configuration";
+
+ base::FilePath path;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &path));
+ path = path.Append(FILE_PATH_LITERAL("gpu"))
+ .Append(FILE_PATH_LITERAL("webgl_conformance_test_expectations.txt"));
+ ASSERT_TRUE(base::PathExists(path));
+ ASSERT_TRUE(test_expectations_.LoadTestExpectations(path));
+ }
+
+ void RunTest(std::string url, std::string test_name) {
+ int32 expectation =
+ test_expectations_.GetTestExpectation(test_name, bot_config_);
+ if (expectation != gpu::GPUTestExpectationsParser::kGpuTestPass) {
+ LOG(WARNING) << "Test " << test_name << " is bypassed";
+ return;
+ }
+
+ DOMMessageQueue message_queue;
+ NavigateToURL(shell(), net::FilePathToFileURL(test_path_));
+
+ std::string message;
+ NavigateToURL(shell(), GURL("javascript:start('" + url + "');"));
+ ASSERT_TRUE(message_queue.WaitForMessage(&message));
+
+ EXPECT_STREQ("\"SUCCESS\"", message.c_str()) << message;
+ }
+
+ private:
+ base::FilePath test_path_;
+ gpu::GPUTestBotConfig bot_config_;
+ gpu::GPUTestExpectationsParser test_expectations_;
+};
+
+#define CONFORMANCE_TEST(name, url) \
+IN_PROC_BROWSER_TEST_F(WebGLConformanceTest, MANUAL_##name) { \
+ RunTest(url, #name); \
+}
+
+// The test declarations are located in webgl_conformance_test_list_autogen.h,
+// because the list is automatically generated by a script.
+// See: generate_webgl_conformance_test_list.py
+#include "webgl_conformance_test_list_autogen.h"
+
+} // namespace content
diff --git a/chromium/content/browser/gpu/webgl_conformance_test_list_autogen.h b/chromium/content/browser/gpu/webgl_conformance_test_list_autogen.h
new file mode 100644
index 00000000000..d9a30c243c2
--- /dev/null
+++ b/chromium/content/browser/gpu/webgl_conformance_test_list_autogen.h
@@ -0,0 +1,1080 @@
+// 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.
+
+// DO NOT EDIT! This file is auto-generated by
+// generate_webgl_conformance_test_list.py
+// It is included by webgl_conformance_test.cc
+
+#ifndef CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+#define CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+
+CONFORMANCE_TEST(conformance_more_conformance_constants,
+ "conformance/more/conformance/constants.html");
+CONFORMANCE_TEST(conformance_more_conformance_getContext,
+ "conformance/more/conformance/getContext.html");
+CONFORMANCE_TEST(conformance_more_conformance_methods,
+ "conformance/more/conformance/methods.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_A,
+ "conformance/more/conformance/quickCheckAPI-A.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_B1,
+ "conformance/more/conformance/quickCheckAPI-B1.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_B2,
+ "conformance/more/conformance/quickCheckAPI-B2.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_B3,
+ "conformance/more/conformance/quickCheckAPI-B3.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_B4,
+ "conformance/more/conformance/quickCheckAPI-B4.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_C,
+ "conformance/more/conformance/quickCheckAPI-C.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_D_G,
+ "conformance/more/conformance/quickCheckAPI-D_G.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_G_I,
+ "conformance/more/conformance/quickCheckAPI-G_I.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_L_S,
+ "conformance/more/conformance/quickCheckAPI-L_S.html");
+CONFORMANCE_TEST(conformance_more_conformance_quickCheckAPI_S_V,
+ "conformance/more/conformance/quickCheckAPI-S_V.html");
+CONFORMANCE_TEST(conformance_more_conformance_webGLArrays,
+ "conformance/more/conformance/webGLArrays.html");
+CONFORMANCE_TEST(conformance_more_functions_bindBuffer,
+ "conformance/more/functions/bindBuffer.html");
+CONFORMANCE_TEST(conformance_more_functions_bindBufferBadArgs,
+ "conformance/more/functions/bindBufferBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_bindFramebufferLeaveNonZero,
+ "conformance/more/functions/bindFramebufferLeaveNonZero.html");
+CONFORMANCE_TEST(conformance_more_functions_bufferData,
+ "conformance/more/functions/bufferData.html");
+CONFORMANCE_TEST(conformance_more_functions_bufferDataBadArgs,
+ "conformance/more/functions/bufferDataBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_bufferSubData,
+ "conformance/more/functions/bufferSubData.html");
+CONFORMANCE_TEST(conformance_more_functions_bufferSubDataBadArgs,
+ "conformance/more/functions/bufferSubDataBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_copyTexImage2D,
+ "conformance/more/functions/copyTexImage2D.html");
+CONFORMANCE_TEST(conformance_more_functions_copyTexImage2DBadArgs,
+ "conformance/more/functions/copyTexImage2DBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_copyTexSubImage2D,
+ "conformance/more/functions/copyTexSubImage2D.html");
+CONFORMANCE_TEST(conformance_more_functions_copyTexSubImage2DBadArgs,
+ "conformance/more/functions/copyTexSubImage2DBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_deleteBufferBadArgs,
+ "conformance/more/functions/deleteBufferBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_drawArrays,
+ "conformance/more/functions/drawArrays.html");
+CONFORMANCE_TEST(conformance_more_functions_drawArraysOutOfBounds,
+ "conformance/more/functions/drawArraysOutOfBounds.html");
+CONFORMANCE_TEST(conformance_more_functions_drawElements,
+ "conformance/more/functions/drawElements.html");
+CONFORMANCE_TEST(conformance_more_functions_drawElementsBadArgs,
+ "conformance/more/functions/drawElementsBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_isTests,
+ "conformance/more/functions/isTests.html");
+CONFORMANCE_TEST(conformance_more_functions_isTestsBadArgs,
+ "conformance/more/functions/isTestsBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_readPixels,
+ "conformance/more/functions/readPixels.html");
+CONFORMANCE_TEST(conformance_more_functions_readPixelsBadArgs,
+ "conformance/more/functions/readPixelsBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_texImage2D,
+ "conformance/more/functions/texImage2D.html");
+CONFORMANCE_TEST(conformance_more_functions_texImage2DBadArgs,
+ "conformance/more/functions/texImage2DBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_texImage2DHTML,
+ "conformance/more/functions/texImage2DHTML.html");
+CONFORMANCE_TEST(conformance_more_functions_texImage2DHTMLBadArgs,
+ "conformance/more/functions/texImage2DHTMLBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_texSubImage2D,
+ "conformance/more/functions/texSubImage2D.html");
+CONFORMANCE_TEST(conformance_more_functions_texSubImage2DBadArgs,
+ "conformance/more/functions/texSubImage2DBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_texSubImage2DHTML,
+ "conformance/more/functions/texSubImage2DHTML.html");
+CONFORMANCE_TEST(conformance_more_functions_texSubImage2DHTMLBadArgs,
+ "conformance/more/functions/texSubImage2DHTMLBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformf,
+ "conformance/more/functions/uniformf.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformfBadArgs,
+ "conformance/more/functions/uniformfBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformfArrayLen1,
+ "conformance/more/functions/uniformfArrayLen1.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformi,
+ "conformance/more/functions/uniformi.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformiBadArgs,
+ "conformance/more/functions/uniformiBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformMatrix,
+ "conformance/more/functions/uniformMatrix.html");
+CONFORMANCE_TEST(conformance_more_functions_uniformMatrixBadArgs,
+ "conformance/more/functions/uniformMatrixBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_vertexAttrib,
+ "conformance/more/functions/vertexAttrib.html");
+CONFORMANCE_TEST(conformance_more_functions_vertexAttribBadArgs,
+ "conformance/more/functions/vertexAttribBadArgs.html");
+CONFORMANCE_TEST(conformance_more_functions_vertexAttribPointer,
+ "conformance/more/functions/vertexAttribPointer.html");
+CONFORMANCE_TEST(conformance_more_functions_vertexAttribPointerBadArgs,
+ "conformance/more/functions/vertexAttribPointerBadArgs.html");
+CONFORMANCE_TEST(conformance_more_glsl_arrayOutOfBounds,
+ "conformance/more/glsl/arrayOutOfBounds.html");
+CONFORMANCE_TEST(conformance_more_glsl_uniformOutOfBounds,
+ "conformance/more/glsl/uniformOutOfBounds.html");
+CONFORMANCE_TEST(conformance_attribs_gl_enable_vertex_attrib,
+ "conformance/attribs/gl-enable-vertex-attrib.html");
+CONFORMANCE_TEST(conformance_attribs_gl_vertex_attrib_render,
+ "conformance/attribs/gl-vertex-attrib-render.html");
+CONFORMANCE_TEST(conformance_attribs_gl_disabled_vertex_attrib,
+ "conformance/attribs/gl-disabled-vertex-attrib.html");
+CONFORMANCE_TEST(conformance_attribs_gl_vertex_attrib_zero_issues,
+ "conformance/attribs/gl-vertex-attrib-zero-issues.html");
+CONFORMANCE_TEST(conformance_attribs_gl_vertex_attrib,
+ "conformance/attribs/gl-vertex-attrib.html");
+CONFORMANCE_TEST(conformance_attribs_gl_vertexattribpointer_offsets,
+ "conformance/attribs/gl-vertexattribpointer-offsets.html");
+CONFORMANCE_TEST(conformance_attribs_gl_vertexattribpointer,
+ "conformance/attribs/gl-vertexattribpointer.html");
+CONFORMANCE_TEST(conformance_buffers_buffer_bind_test,
+ "conformance/buffers/buffer-bind-test.html");
+CONFORMANCE_TEST(conformance_buffers_buffer_data_array_buffer,
+ "conformance/buffers/buffer-data-array-buffer.html");
+CONFORMANCE_TEST(conformance_buffers_index_validation_copies_indices,
+ "conformance/buffers/index-validation-copies-indices.html");
+CONFORMANCE_TEST(conformance_buffers_index_validation_crash_with_buffer_sub_data,
+ "conformance/buffers/index-validation-crash-with-buffer-sub-data.html");
+CONFORMANCE_TEST(conformance_buffers_index_validation_verifies_too_many_indices,
+ "conformance/buffers/index-validation-verifies-too-many-indices.html");
+CONFORMANCE_TEST(conformance_buffers_index_validation_with_resized_buffer,
+ "conformance/buffers/index-validation-with-resized-buffer.html");
+CONFORMANCE_TEST(conformance_buffers_index_validation,
+ "conformance/buffers/index-validation.html");
+CONFORMANCE_TEST(conformance_canvas_buffer_offscreen_test,
+ "conformance/canvas/buffer-offscreen-test.html");
+CONFORMANCE_TEST(conformance_canvas_buffer_preserve_test,
+ "conformance/canvas/buffer-preserve-test.html");
+CONFORMANCE_TEST(conformance_canvas_canvas_test,
+ "conformance/canvas/canvas-test.html");
+CONFORMANCE_TEST(conformance_canvas_canvas_zero_size,
+ "conformance/canvas/canvas-zero-size.html");
+CONFORMANCE_TEST(conformance_canvas_drawingbuffer_static_canvas_test,
+ "conformance/canvas/drawingbuffer-static-canvas-test.html");
+CONFORMANCE_TEST(conformance_canvas_drawingbuffer_test,
+ "conformance/canvas/drawingbuffer-test.html");
+CONFORMANCE_TEST(conformance_canvas_framebuffer_bindings_unaffected_on_resize,
+ "conformance/canvas/framebuffer-bindings-unaffected-on-resize.html");
+CONFORMANCE_TEST(conformance_canvas_texture_bindings_unaffected_on_resize,
+ "conformance/canvas/texture-bindings-unaffected-on-resize.html");
+CONFORMANCE_TEST(conformance_canvas_viewport_unchanged_upon_resize,
+ "conformance/canvas/viewport-unchanged-upon-resize.html");
+CONFORMANCE_TEST(conformance_context_constants,
+ "conformance/context/constants.html");
+CONFORMANCE_TEST(conformance_context_context_attribute_preserve_drawing_buffer,
+ "conformance/context/context-attribute-preserve-drawing-buffer.html");
+CONFORMANCE_TEST(conformance_context_context_attributes_alpha_depth_stencil_antialias,
+ "conformance/context/context-attributes-alpha-depth-stencil-antialias.html");
+CONFORMANCE_TEST(conformance_context_context_creation_and_destruction,
+ "conformance/context/context-creation-and-destruction.html");
+CONFORMANCE_TEST(conformance_context_context_lost_restored,
+ "conformance/context/context-lost-restored.html");
+CONFORMANCE_TEST(conformance_context_context_lost,
+ "conformance/context/context-lost.html");
+CONFORMANCE_TEST(conformance_context_context_type_test,
+ "conformance/context/context-type-test.html");
+CONFORMANCE_TEST(conformance_context_incorrect_context_object_behaviour,
+ "conformance/context/incorrect-context-object-behaviour.html");
+CONFORMANCE_TEST(conformance_context_methods,
+ "conformance/context/methods.html");
+CONFORMANCE_TEST(conformance_context_premultiplyalpha_test,
+ "conformance/context/premultiplyalpha-test.html");
+CONFORMANCE_TEST(conformance_context_resource_sharing_test,
+ "conformance/context/resource-sharing-test.html");
+CONFORMANCE_TEST(conformance_extensions_get_extension,
+ "conformance/extensions/get-extension.html");
+CONFORMANCE_TEST(conformance_extensions_oes_standard_derivatives,
+ "conformance/extensions/oes-standard-derivatives.html");
+CONFORMANCE_TEST(conformance_extensions_oes_texture_float_with_canvas,
+ "conformance/extensions/oes-texture-float-with-canvas.html");
+CONFORMANCE_TEST(conformance_extensions_oes_texture_float_with_image_data,
+ "conformance/extensions/oes-texture-float-with-image-data.html");
+CONFORMANCE_TEST(conformance_extensions_oes_texture_float_with_image,
+ "conformance/extensions/oes-texture-float-with-image.html");
+CONFORMANCE_TEST(conformance_extensions_oes_texture_float_with_video,
+ "conformance/extensions/oes-texture-float-with-video.html");
+CONFORMANCE_TEST(conformance_extensions_oes_texture_float,
+ "conformance/extensions/oes-texture-float.html");
+CONFORMANCE_TEST(conformance_extensions_oes_vertex_array_object,
+ "conformance/extensions/oes-vertex-array-object.html");
+CONFORMANCE_TEST(conformance_extensions_webgl_debug_renderer_info,
+ "conformance/extensions/webgl-debug-renderer-info.html");
+CONFORMANCE_TEST(conformance_extensions_webgl_debug_shaders,
+ "conformance/extensions/webgl-debug-shaders.html");
+CONFORMANCE_TEST(conformance_extensions_webgl_compressed_texture_s3tc,
+ "conformance/extensions/webgl-compressed-texture-s3tc.html");
+CONFORMANCE_TEST(conformance_extensions_ext_texture_filter_anisotropic,
+ "conformance/extensions/ext-texture-filter-anisotropic.html");
+CONFORMANCE_TEST(conformance_limits_gl_min_attribs,
+ "conformance/limits/gl-min-attribs.html");
+CONFORMANCE_TEST(conformance_limits_gl_max_texture_dimensions,
+ "conformance/limits/gl-max-texture-dimensions.html");
+CONFORMANCE_TEST(conformance_limits_gl_min_textures,
+ "conformance/limits/gl-min-textures.html");
+CONFORMANCE_TEST(conformance_limits_gl_min_uniforms,
+ "conformance/limits/gl-min-uniforms.html");
+CONFORMANCE_TEST(conformance_misc_bad_arguments_test,
+ "conformance/misc/bad-arguments-test.html");
+CONFORMANCE_TEST(conformance_misc_delayed_drawing,
+ "conformance/misc/delayed-drawing.html");
+CONFORMANCE_TEST(conformance_misc_error_reporting,
+ "conformance/misc/error-reporting.html");
+CONFORMANCE_TEST(conformance_misc_instanceof_test,
+ "conformance/misc/instanceof-test.html");
+CONFORMANCE_TEST(conformance_misc_invalid_passed_params,
+ "conformance/misc/invalid-passed-params.html");
+CONFORMANCE_TEST(conformance_misc_is_object,
+ "conformance/misc/is-object.html");
+CONFORMANCE_TEST(conformance_misc_null_object_behaviour,
+ "conformance/misc/null-object-behaviour.html");
+CONFORMANCE_TEST(conformance_misc_functions_returning_strings,
+ "conformance/misc/functions-returning-strings.html");
+CONFORMANCE_TEST(conformance_misc_object_deletion_behaviour,
+ "conformance/misc/object-deletion-behaviour.html");
+CONFORMANCE_TEST(conformance_misc_shader_precision_format,
+ "conformance/misc/shader-precision-format.html");
+CONFORMANCE_TEST(conformance_misc_type_conversion_test,
+ "conformance/misc/type-conversion-test.html");
+CONFORMANCE_TEST(conformance_misc_uninitialized_test,
+ "conformance/misc/uninitialized-test.html");
+CONFORMANCE_TEST(conformance_misc_webgl_specific,
+ "conformance/misc/webgl-specific.html");
+CONFORMANCE_TEST(conformance_programs_get_active_test,
+ "conformance/programs/get-active-test.html");
+CONFORMANCE_TEST(conformance_programs_gl_bind_attrib_location_test,
+ "conformance/programs/gl-bind-attrib-location-test.html");
+CONFORMANCE_TEST(conformance_programs_gl_bind_attrib_location_long_names_test,
+ "conformance/programs/gl-bind-attrib-location-long-names-test.html");
+CONFORMANCE_TEST(conformance_programs_gl_get_active_attribute,
+ "conformance/programs/gl-get-active-attribute.html");
+CONFORMANCE_TEST(conformance_programs_gl_get_active_uniform,
+ "conformance/programs/gl-get-active-uniform.html");
+CONFORMANCE_TEST(conformance_programs_gl_getshadersource,
+ "conformance/programs/gl-getshadersource.html");
+CONFORMANCE_TEST(conformance_programs_gl_shader_test,
+ "conformance/programs/gl-shader-test.html");
+CONFORMANCE_TEST(conformance_programs_invalid_UTF_16,
+ "conformance/programs/invalid-UTF-16.html");
+CONFORMANCE_TEST(conformance_programs_program_test,
+ "conformance/programs/program-test.html");
+CONFORMANCE_TEST(conformance_programs_use_program_crash_with_discard_in_fragment_shader,
+ "conformance/programs/use-program-crash-with-discard-in-fragment-shader.html");
+CONFORMANCE_TEST(conformance_reading_read_pixels_pack_alignment,
+ "conformance/reading/read-pixels-pack-alignment.html");
+CONFORMANCE_TEST(conformance_reading_read_pixels_test,
+ "conformance/reading/read-pixels-test.html");
+CONFORMANCE_TEST(conformance_renderbuffers_framebuffer_object_attachment,
+ "conformance/renderbuffers/framebuffer-object-attachment.html");
+CONFORMANCE_TEST(conformance_renderbuffers_framebuffer_state_restoration,
+ "conformance/renderbuffers/framebuffer-state-restoration.html");
+CONFORMANCE_TEST(conformance_renderbuffers_framebuffer_test,
+ "conformance/renderbuffers/framebuffer-test.html");
+CONFORMANCE_TEST(conformance_renderbuffers_renderbuffer_initialization,
+ "conformance/renderbuffers/renderbuffer-initialization.html");
+CONFORMANCE_TEST(conformance_rendering_culling,
+ "conformance/rendering/culling.html");
+CONFORMANCE_TEST(conformance_rendering_draw_arrays_out_of_bounds,
+ "conformance/rendering/draw-arrays-out-of-bounds.html");
+CONFORMANCE_TEST(conformance_rendering_draw_elements_out_of_bounds,
+ "conformance/rendering/draw-elements-out-of-bounds.html");
+CONFORMANCE_TEST(conformance_rendering_gl_clear,
+ "conformance/rendering/gl-clear.html");
+CONFORMANCE_TEST(conformance_rendering_gl_drawelements,
+ "conformance/rendering/gl-drawelements.html");
+CONFORMANCE_TEST(conformance_rendering_gl_scissor_test,
+ "conformance/rendering/gl-scissor-test.html");
+CONFORMANCE_TEST(conformance_rendering_more_than_65536_indices,
+ "conformance/rendering/more-than-65536-indices.html");
+CONFORMANCE_TEST(conformance_rendering_point_size,
+ "conformance/rendering/point-size.html");
+CONFORMANCE_TEST(conformance_rendering_triangle,
+ "conformance/rendering/triangle.html");
+CONFORMANCE_TEST(conformance_rendering_line_loop_tri_fan,
+ "conformance/rendering/line-loop-tri-fan.html");
+CONFORMANCE_TEST(conformance_state_gl_enable_enum_test,
+ "conformance/state/gl-enable-enum-test.html");
+CONFORMANCE_TEST(conformance_state_gl_enum_tests,
+ "conformance/state/gl-enum-tests.html");
+CONFORMANCE_TEST(conformance_state_gl_get_calls,
+ "conformance/state/gl-get-calls.html");
+CONFORMANCE_TEST(conformance_state_gl_geterror,
+ "conformance/state/gl-geterror.html");
+CONFORMANCE_TEST(conformance_state_gl_getstring,
+ "conformance/state/gl-getstring.html");
+CONFORMANCE_TEST(conformance_state_gl_object_get_calls,
+ "conformance/state/gl-object-get-calls.html");
+CONFORMANCE_TEST(conformance_textures_compressed_tex_image,
+ "conformance/textures/compressed-tex-image.html");
+CONFORMANCE_TEST(conformance_textures_copy_tex_image_and_sub_image_2d,
+ "conformance/textures/copy-tex-image-and-sub-image-2d.html");
+CONFORMANCE_TEST(conformance_textures_gl_pixelstorei,
+ "conformance/textures/gl-pixelstorei.html");
+CONFORMANCE_TEST(conformance_textures_gl_teximage,
+ "conformance/textures/gl-teximage.html");
+CONFORMANCE_TEST(conformance_textures_origin_clean_conformance,
+ "conformance/textures/origin-clean-conformance.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_array_buffer_view,
+ "conformance/textures/tex-image-and-sub-image-2d-with-array-buffer-view.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_canvas,
+ "conformance/textures/tex-image-and-sub-image-2d-with-canvas.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_canvas_rgb565,
+ "conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgb565.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_canvas_rgba4444,
+ "conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgba4444.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_canvas_rgba5551,
+ "conformance/textures/tex-image-and-sub-image-2d-with-canvas-rgba5551.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_data,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-data.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_data_rgb565,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgb565.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_data_rgba4444,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgba4444.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_data_rgba5551,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-data-rgba5551.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_rgb565,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-rgb565.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_rgba4444,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-rgba4444.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_image_rgba5551,
+ "conformance/textures/tex-image-and-sub-image-2d-with-image-rgba5551.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_video,
+ "conformance/textures/tex-image-and-sub-image-2d-with-video.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_video_rgb565,
+ "conformance/textures/tex-image-and-sub-image-2d-with-video-rgb565.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_video_rgba4444,
+ "conformance/textures/tex-image-and-sub-image-2d-with-video-rgba4444.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_sub_image_2d_with_video_rgba5551,
+ "conformance/textures/tex-image-and-sub-image-2d-with-video-rgba5551.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_and_uniform_binding_bugs,
+ "conformance/textures/tex-image-and-uniform-binding-bugs.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_with_format_and_type,
+ "conformance/textures/tex-image-with-format-and-type.html");
+CONFORMANCE_TEST(conformance_textures_tex_image_with_invalid_data,
+ "conformance/textures/tex-image-with-invalid-data.html");
+CONFORMANCE_TEST(conformance_textures_tex_input_validation,
+ "conformance/textures/tex-input-validation.html");
+CONFORMANCE_TEST(conformance_textures_tex_sub_image_2d_bad_args,
+ "conformance/textures/tex-sub-image-2d-bad-args.html");
+CONFORMANCE_TEST(conformance_textures_tex_sub_image_2d,
+ "conformance/textures/tex-sub-image-2d.html");
+CONFORMANCE_TEST(conformance_textures_texparameter_test,
+ "conformance/textures/texparameter-test.html");
+CONFORMANCE_TEST(conformance_textures_texture_active_bind_2,
+ "conformance/textures/texture-active-bind-2.html");
+CONFORMANCE_TEST(conformance_textures_texture_active_bind,
+ "conformance/textures/texture-active-bind.html");
+CONFORMANCE_TEST(conformance_textures_texture_attachment_formats,
+ "conformance/textures/texture-attachment-formats.html");
+CONFORMANCE_TEST(conformance_textures_texture_clear,
+ "conformance/textures/texture-clear.html");
+CONFORMANCE_TEST(conformance_textures_texture_complete,
+ "conformance/textures/texture-complete.html");
+CONFORMANCE_TEST(conformance_textures_texture_formats_test,
+ "conformance/textures/texture-formats-test.html");
+CONFORMANCE_TEST(conformance_textures_texture_mips,
+ "conformance/textures/texture-mips.html");
+CONFORMANCE_TEST(conformance_textures_texture_npot_video,
+ "conformance/textures/texture-npot-video.html");
+CONFORMANCE_TEST(conformance_textures_texture_npot,
+ "conformance/textures/texture-npot.html");
+CONFORMANCE_TEST(conformance_textures_texture_size,
+ "conformance/textures/texture-size.html");
+CONFORMANCE_TEST(conformance_textures_texture_size_cube_maps,
+ "conformance/textures/texture-size-cube-maps.html");
+CONFORMANCE_TEST(conformance_textures_texture_transparent_pixels_initialized,
+ "conformance/textures/texture-transparent-pixels-initialized.html");
+CONFORMANCE_TEST(conformance_textures_texture_upload_cube_maps,
+ "conformance/textures/texture-upload-cube-maps.html");
+CONFORMANCE_TEST(conformance_typedarrays_array_buffer_crash,
+ "conformance/typedarrays/array-buffer-crash.html");
+CONFORMANCE_TEST(conformance_typedarrays_array_buffer_view_crash,
+ "conformance/typedarrays/array-buffer-view-crash.html");
+CONFORMANCE_TEST(conformance_typedarrays_array_unit_tests,
+ "conformance/typedarrays/array-unit-tests.html");
+CONFORMANCE_TEST(conformance_typedarrays_data_view_crash,
+ "conformance/typedarrays/data-view-crash.html");
+CONFORMANCE_TEST(conformance_typedarrays_data_view_test,
+ "conformance/typedarrays/data-view-test.html");
+CONFORMANCE_TEST(conformance_uniforms_gl_uniform_arrays,
+ "conformance/uniforms/gl-uniform-arrays.html");
+CONFORMANCE_TEST(conformance_uniforms_gl_uniform_bool,
+ "conformance/uniforms/gl-uniform-bool.html");
+CONFORMANCE_TEST(conformance_uniforms_gl_uniformmatrix4fv,
+ "conformance/uniforms/gl-uniformmatrix4fv.html");
+CONFORMANCE_TEST(conformance_uniforms_gl_unknown_uniform,
+ "conformance/uniforms/gl-unknown-uniform.html");
+CONFORMANCE_TEST(conformance_uniforms_null_uniform_location,
+ "conformance/uniforms/null-uniform-location.html");
+CONFORMANCE_TEST(conformance_uniforms_uniform_default_values,
+ "conformance/uniforms/uniform-default-values.html");
+CONFORMANCE_TEST(conformance_uniforms_uniform_location,
+ "conformance/uniforms/uniform-location.html");
+CONFORMANCE_TEST(conformance_uniforms_uniform_samplers_test,
+ "conformance/uniforms/uniform-samplers-test.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function,
+ "conformance/glsl/functions/glsl-function.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_abs,
+ "conformance/glsl/functions/glsl-function-abs.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_acos,
+ "conformance/glsl/functions/glsl-function-acos.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_asin,
+ "conformance/glsl/functions/glsl-function-asin.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_atan,
+ "conformance/glsl/functions/glsl-function-atan.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_atan_xy,
+ "conformance/glsl/functions/glsl-function-atan-xy.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_ceil,
+ "conformance/glsl/functions/glsl-function-ceil.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_clamp_float,
+ "conformance/glsl/functions/glsl-function-clamp-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_clamp_gentype,
+ "conformance/glsl/functions/glsl-function-clamp-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_cos,
+ "conformance/glsl/functions/glsl-function-cos.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_cross,
+ "conformance/glsl/functions/glsl-function-cross.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_distance,
+ "conformance/glsl/functions/glsl-function-distance.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_dot,
+ "conformance/glsl/functions/glsl-function-dot.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_faceforward,
+ "conformance/glsl/functions/glsl-function-faceforward.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_floor,
+ "conformance/glsl/functions/glsl-function-floor.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_fract,
+ "conformance/glsl/functions/glsl-function-fract.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_length,
+ "conformance/glsl/functions/glsl-function-length.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_max_float,
+ "conformance/glsl/functions/glsl-function-max-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_max_gentype,
+ "conformance/glsl/functions/glsl-function-max-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_min_float,
+ "conformance/glsl/functions/glsl-function-min-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_min_gentype,
+ "conformance/glsl/functions/glsl-function-min-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_mix_float,
+ "conformance/glsl/functions/glsl-function-mix-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_mix_gentype,
+ "conformance/glsl/functions/glsl-function-mix-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_mod_float,
+ "conformance/glsl/functions/glsl-function-mod-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_mod_gentype,
+ "conformance/glsl/functions/glsl-function-mod-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_normalize,
+ "conformance/glsl/functions/glsl-function-normalize.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_reflect,
+ "conformance/glsl/functions/glsl-function-reflect.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_sign,
+ "conformance/glsl/functions/glsl-function-sign.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_sin,
+ "conformance/glsl/functions/glsl-function-sin.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_step_float,
+ "conformance/glsl/functions/glsl-function-step-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_step_gentype,
+ "conformance/glsl/functions/glsl-function-step-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_smoothstep_float,
+ "conformance/glsl/functions/glsl-function-smoothstep-float.html");
+CONFORMANCE_TEST(conformance_glsl_functions_glsl_function_smoothstep_gentype,
+ "conformance/glsl/functions/glsl-function-smoothstep-gentype.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_float_vert,
+ "conformance/glsl/implicit/add_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_mat2_vert,
+ "conformance/glsl/implicit/add_int_mat2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_mat3_vert,
+ "conformance/glsl/implicit/add_int_mat3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_mat4_vert,
+ "conformance/glsl/implicit/add_int_mat4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_vec2_vert,
+ "conformance/glsl/implicit/add_int_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_vec3_vert,
+ "conformance/glsl/implicit/add_int_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_int_vec4_vert,
+ "conformance/glsl/implicit/add_int_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_ivec2_vec2_vert,
+ "conformance/glsl/implicit/add_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_ivec3_vec3_vert,
+ "conformance/glsl/implicit/add_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_add_ivec4_vec4_vert,
+ "conformance/glsl/implicit/add_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_assign_int_to_float_vert,
+ "conformance/glsl/implicit/assign_int_to_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_assign_ivec2_to_vec2_vert,
+ "conformance/glsl/implicit/assign_ivec2_to_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_assign_ivec3_to_vec3_vert,
+ "conformance/glsl/implicit/assign_ivec3_to_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_assign_ivec4_to_vec4_vert,
+ "conformance/glsl/implicit/assign_ivec4_to_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_construct_struct_vert,
+ "conformance/glsl/implicit/construct_struct.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_float_vert,
+ "conformance/glsl/implicit/divide_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_mat2_vert,
+ "conformance/glsl/implicit/divide_int_mat2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_mat3_vert,
+ "conformance/glsl/implicit/divide_int_mat3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_mat4_vert,
+ "conformance/glsl/implicit/divide_int_mat4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_vec2_vert,
+ "conformance/glsl/implicit/divide_int_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_vec3_vert,
+ "conformance/glsl/implicit/divide_int_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_int_vec4_vert,
+ "conformance/glsl/implicit/divide_int_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_ivec2_vec2_vert,
+ "conformance/glsl/implicit/divide_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_ivec3_vec3_vert,
+ "conformance/glsl/implicit/divide_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_divide_ivec4_vec4_vert,
+ "conformance/glsl/implicit/divide_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_equal_int_float_vert,
+ "conformance/glsl/implicit/equal_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_equal_ivec2_vec2_vert,
+ "conformance/glsl/implicit/equal_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_equal_ivec3_vec3_vert,
+ "conformance/glsl/implicit/equal_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_equal_ivec4_vec4_vert,
+ "conformance/glsl/implicit/equal_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_function_int_float_vert,
+ "conformance/glsl/implicit/function_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_function_ivec2_vec2_vert,
+ "conformance/glsl/implicit/function_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_function_ivec3_vec3_vert,
+ "conformance/glsl/implicit/function_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_function_ivec4_vec4_vert,
+ "conformance/glsl/implicit/function_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_greater_than_vert,
+ "conformance/glsl/implicit/greater_than.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_greater_than_equal_vert,
+ "conformance/glsl/implicit/greater_than_equal.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_less_than_vert,
+ "conformance/glsl/implicit/less_than.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_less_than_equal_vert,
+ "conformance/glsl/implicit/less_than_equal.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_float_vert,
+ "conformance/glsl/implicit/multiply_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_mat2_vert,
+ "conformance/glsl/implicit/multiply_int_mat2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_mat3_vert,
+ "conformance/glsl/implicit/multiply_int_mat3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_mat4_vert,
+ "conformance/glsl/implicit/multiply_int_mat4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_vec2_vert,
+ "conformance/glsl/implicit/multiply_int_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_vec3_vert,
+ "conformance/glsl/implicit/multiply_int_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_int_vec4_vert,
+ "conformance/glsl/implicit/multiply_int_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_ivec2_vec2_vert,
+ "conformance/glsl/implicit/multiply_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_ivec3_vec3_vert,
+ "conformance/glsl/implicit/multiply_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_multiply_ivec4_vec4_vert,
+ "conformance/glsl/implicit/multiply_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_not_equal_int_float_vert,
+ "conformance/glsl/implicit/not_equal_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_not_equal_ivec2_vec2_vert,
+ "conformance/glsl/implicit/not_equal_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_not_equal_ivec3_vec3_vert,
+ "conformance/glsl/implicit/not_equal_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_not_equal_ivec4_vec4_vert,
+ "conformance/glsl/implicit/not_equal_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_float_vert,
+ "conformance/glsl/implicit/subtract_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_mat2_vert,
+ "conformance/glsl/implicit/subtract_int_mat2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_mat3_vert,
+ "conformance/glsl/implicit/subtract_int_mat3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_mat4_vert,
+ "conformance/glsl/implicit/subtract_int_mat4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_vec2_vert,
+ "conformance/glsl/implicit/subtract_int_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_vec3_vert,
+ "conformance/glsl/implicit/subtract_int_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_int_vec4_vert,
+ "conformance/glsl/implicit/subtract_int_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_ivec2_vec2_vert,
+ "conformance/glsl/implicit/subtract_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_ivec3_vec3_vert,
+ "conformance/glsl/implicit/subtract_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_subtract_ivec4_vec4_vert,
+ "conformance/glsl/implicit/subtract_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_ternary_int_float_vert,
+ "conformance/glsl/implicit/ternary_int_float.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_ternary_ivec2_vec2_vert,
+ "conformance/glsl/implicit/ternary_ivec2_vec2.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_ternary_ivec3_vec3_vert,
+ "conformance/glsl/implicit/ternary_ivec3_vec3.vert.html");
+CONFORMANCE_TEST(conformance_glsl_implicit_ternary_ivec4_vec4_vert,
+ "conformance/glsl/implicit/ternary_ivec4_vec4.vert.html");
+CONFORMANCE_TEST(conformance_glsl_matrices_glsl_mat4_to_mat3,
+ "conformance/glsl/matrices/glsl-mat4-to-mat3.html");
+CONFORMANCE_TEST(conformance_glsl_misc_attrib_location_length_limits,
+ "conformance/glsl/misc/attrib-location-length-limits.html");
+CONFORMANCE_TEST(conformance_glsl_misc_embedded_struct_definitions_forbidden,
+ "conformance/glsl/misc/embedded-struct-definitions-forbidden.html");
+CONFORMANCE_TEST(conformance_glsl_misc_glsl_function_nodes,
+ "conformance/glsl/misc/glsl-function-nodes.html");
+CONFORMANCE_TEST(conformance_glsl_misc_glsl_vertex_branch,
+ "conformance/glsl/misc/glsl-vertex-branch.html");
+CONFORMANCE_TEST(conformance_glsl_misc_glsl_long_variable_names,
+ "conformance/glsl/misc/glsl-long-variable-names.html");
+CONFORMANCE_TEST(conformance_glsl_misc_non_ascii_comments_vert,
+ "conformance/glsl/misc/non-ascii-comments.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_non_ascii_vert,
+ "conformance/glsl/misc/non-ascii.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_re_compile_re_link,
+ "conformance/glsl/misc/re-compile-re-link.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_256_character_define,
+ "conformance/glsl/misc/shader-with-256-character-define.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_256_character_identifier_frag,
+ "conformance/glsl/misc/shader-with-256-character-identifier.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_257_character_define,
+ "conformance/glsl/misc/shader-with-257-character-define.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_257_character_identifier_frag,
+ "conformance/glsl/misc/shader-with-257-character-identifier.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with__webgl_identifier_vert,
+ "conformance/glsl/misc/shader-with-_webgl-identifier.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_arbitrary_indexing_frag,
+ "conformance/glsl/misc/shader-with-arbitrary-indexing.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_arbitrary_indexing_vert,
+ "conformance/glsl/misc/shader-with-arbitrary-indexing.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_attrib_array_vert,
+ "conformance/glsl/misc/shader-with-attrib-array.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_attrib_struct_vert,
+ "conformance/glsl/misc/shader-with-attrib-struct.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_clipvertex_vert,
+ "conformance/glsl/misc/shader-with-clipvertex.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_conditional_scoping,
+ "conformance/glsl/misc/shader-with-conditional-scoping.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_conditional_scoping_negative,
+ "conformance/glsl/misc/shader-with-conditional-scoping-negative.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_default_precision_frag,
+ "conformance/glsl/misc/shader-with-default-precision.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_default_precision_vert,
+ "conformance/glsl/misc/shader-with-default-precision.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_define_line_continuation_frag,
+ "conformance/glsl/misc/shader-with-define-line-continuation.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_dfdx_no_ext_frag,
+ "conformance/glsl/misc/shader-with-dfdx-no-ext.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_dfdx_frag,
+ "conformance/glsl/misc/shader-with-dfdx.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_do_loop,
+ "conformance/glsl/misc/shader-with-do-loop.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_error_directive,
+ "conformance/glsl/misc/shader-with-error-directive.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_explicit_int_cast_vert,
+ "conformance/glsl/misc/shader-with-explicit-int-cast.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_float_return_value_frag,
+ "conformance/glsl/misc/shader-with-float-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_for_scoping,
+ "conformance/glsl/misc/shader-with-for-scoping.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_for_loop,
+ "conformance/glsl/misc/shader-with-for-loop.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_frag_depth_frag,
+ "conformance/glsl/misc/shader-with-frag-depth.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_function_recursion_frag,
+ "conformance/glsl/misc/shader-with-function-recursion.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_function_scoped_struct,
+ "conformance/glsl/misc/shader-with-function-scoped-struct.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_functional_scoping,
+ "conformance/glsl/misc/shader-with-functional-scoping.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_comma_assignment,
+ "conformance/glsl/misc/shader-with-comma-assignment.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_comma_conditional_assignment,
+ "conformance/glsl/misc/shader-with-comma-conditional-assignment.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_glcolor_vert,
+ "conformance/glsl/misc/shader-with-glcolor.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_gles_1_frag,
+ "conformance/glsl/misc/shader-with-gles-1.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_gles_symbol_frag,
+ "conformance/glsl/misc/shader-with-gles-symbol.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_glprojectionmatrix_vert,
+ "conformance/glsl/misc/shader-with-glprojectionmatrix.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_implicit_vec3_to_vec4_cast_vert,
+ "conformance/glsl/misc/shader-with-implicit-vec3-to-vec4-cast.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_include_vert,
+ "conformance/glsl/misc/shader-with-include.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_int_return_value_frag,
+ "conformance/glsl/misc/shader-with-int-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_invalid_identifier_frag,
+ "conformance/glsl/misc/shader-with-invalid-identifier.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_ivec2_return_value_frag,
+ "conformance/glsl/misc/shader-with-ivec2-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_ivec3_return_value_frag,
+ "conformance/glsl/misc/shader-with-ivec3-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_ivec4_return_value_frag,
+ "conformance/glsl/misc/shader-with-ivec4-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_limited_indexing_frag,
+ "conformance/glsl/misc/shader-with-limited-indexing.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_hex_int_constant_macro,
+ "conformance/glsl/misc/shader-with-hex-int-constant-macro.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_long_line,
+ "conformance/glsl/misc/shader-with-long-line.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_non_ascii_error_frag,
+ "conformance/glsl/misc/shader-with-non-ascii-error.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_non_reserved_words,
+ "conformance/glsl/misc/shader-with-non-reserved-words.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_precision_frag,
+ "conformance/glsl/misc/shader-with-precision.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_quoted_error_frag,
+ "conformance/glsl/misc/shader-with-quoted-error.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_reserved_words,
+ "conformance/glsl/misc/shader-with-reserved-words.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_undefined_preprocessor_symbol_frag,
+ "conformance/glsl/misc/shader-with-undefined-preprocessor-symbol.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_uniform_in_loop_condition_vert,
+ "conformance/glsl/misc/shader-with-uniform-in-loop-condition.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_vec2_return_value_frag,
+ "conformance/glsl/misc/shader-with-vec2-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_vec3_return_value_frag,
+ "conformance/glsl/misc/shader-with-vec3-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_vec4_return_value_frag,
+ "conformance/glsl/misc/shader-with-vec4-return-value.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_vec4_vec3_vec4_conditional,
+ "conformance/glsl/misc/shader-with-vec4-vec3-vec4-conditional.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_version_100_frag,
+ "conformance/glsl/misc/shader-with-version-100.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_version_100_vert,
+ "conformance/glsl/misc/shader-with-version-100.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_version_120_vert,
+ "conformance/glsl/misc/shader-with-version-120.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_version_130_vert,
+ "conformance/glsl/misc/shader-with-version-130.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_webgl_identifier_vert,
+ "conformance/glsl/misc/shader-with-webgl-identifier.vert.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_while_loop,
+ "conformance/glsl/misc/shader-with-while-loop.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_without_precision_frag,
+ "conformance/glsl/misc/shader-without-precision.frag.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shared,
+ "conformance/glsl/misc/shared.html");
+CONFORMANCE_TEST(conformance_glsl_misc_struct_nesting_exceeds_maximum,
+ "conformance/glsl/misc/struct-nesting-exceeds-maximum.html");
+CONFORMANCE_TEST(conformance_glsl_misc_struct_nesting_under_maximum,
+ "conformance/glsl/misc/struct-nesting-under-maximum.html");
+CONFORMANCE_TEST(conformance_glsl_misc_uniform_location_length_limits,
+ "conformance/glsl/misc/uniform-location-length-limits.html");
+CONFORMANCE_TEST(conformance_glsl_misc_shader_with_short_circuiting_operators,
+ "conformance/glsl/misc/shader-with-short-circuiting-operators.html");
+CONFORMANCE_TEST(conformance_glsl_reserved__webgl_field_vert,
+ "conformance/glsl/reserved/_webgl_field.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved__webgl_function_vert,
+ "conformance/glsl/reserved/_webgl_function.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved__webgl_struct_vert,
+ "conformance/glsl/reserved/_webgl_struct.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved__webgl_variable_vert,
+ "conformance/glsl/reserved/_webgl_variable.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved_webgl_field_vert,
+ "conformance/glsl/reserved/webgl_field.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved_webgl_function_vert,
+ "conformance/glsl/reserved/webgl_function.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved_webgl_struct_vert,
+ "conformance/glsl/reserved/webgl_struct.vert.html");
+CONFORMANCE_TEST(conformance_glsl_reserved_webgl_variable_vert,
+ "conformance/glsl/reserved/webgl_variable.vert.html");
+CONFORMANCE_TEST(conformance_glsl_samplers_glsl_function_texture2d_bias,
+ "conformance/glsl/samplers/glsl-function-texture2d-bias.html");
+CONFORMANCE_TEST(conformance_glsl_samplers_glsl_function_texture2dlod,
+ "conformance/glsl/samplers/glsl-function-texture2dlod.html");
+CONFORMANCE_TEST(conformance_glsl_samplers_glsl_function_texture2dproj,
+ "conformance/glsl/samplers/glsl-function-texture2dproj.html");
+CONFORMANCE_TEST(conformance_glsl_variables_gl_fragcoord,
+ "conformance/glsl/variables/gl-fragcoord.html");
+CONFORMANCE_TEST(conformance_glsl_variables_gl_frontfacing,
+ "conformance/glsl/variables/gl-frontfacing.html");
+CONFORMANCE_TEST(conformance_glsl_variables_gl_pointcoord,
+ "conformance/glsl/variables/gl-pointcoord.html");
+CONFORMANCE_TEST(conformance_ogles_GL_abs_abs_001_to_006,
+ "conformance/ogles/GL/abs/abs_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_acos_acos_001_to_006,
+ "conformance/ogles/GL/acos/acos_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_all_all_001_to_004,
+ "conformance/ogles/GL/all/all_001_to_004.html");
+CONFORMANCE_TEST(conformance_ogles_GL_any_any_001_to_004,
+ "conformance/ogles/GL/any/any_001_to_004.html");
+CONFORMANCE_TEST(conformance_ogles_GL_array_array_001_to_006,
+ "conformance/ogles/GL/array/array_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_asin_asin_001_to_006,
+ "conformance/ogles/GL/asin/asin_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_atan_atan_001_to_008,
+ "conformance/ogles/GL/atan/atan_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_atan_atan_009_to_012,
+ "conformance/ogles/GL/atan/atan_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_biConstants_biConstants_001_to_008,
+ "conformance/ogles/GL/biConstants/biConstants_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_biConstants_biConstants_009_to_016,
+ "conformance/ogles/GL/biConstants/biConstants_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_biuDepthRange_biuDepthRange_001_to_002,
+ "conformance/ogles/GL/biuDepthRange/biuDepthRange_001_to_002.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_001_to_008,
+ "conformance/ogles/GL/build/build_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_009_to_016,
+ "conformance/ogles/GL/build/build_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_017_to_024,
+ "conformance/ogles/GL/build/build_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_025_to_032,
+ "conformance/ogles/GL/build/build_025_to_032.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_033_to_040,
+ "conformance/ogles/GL/build/build_033_to_040.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_041_to_048,
+ "conformance/ogles/GL/build/build_041_to_048.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_049_to_056,
+ "conformance/ogles/GL/build/build_049_to_056.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_057_to_064,
+ "conformance/ogles/GL/build/build_057_to_064.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_065_to_072,
+ "conformance/ogles/GL/build/build_065_to_072.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_073_to_080,
+ "conformance/ogles/GL/build/build_073_to_080.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_081_to_088,
+ "conformance/ogles/GL/build/build_081_to_088.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_089_to_096,
+ "conformance/ogles/GL/build/build_089_to_096.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_097_to_104,
+ "conformance/ogles/GL/build/build_097_to_104.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_105_to_112,
+ "conformance/ogles/GL/build/build_105_to_112.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_113_to_120,
+ "conformance/ogles/GL/build/build_113_to_120.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_121_to_128,
+ "conformance/ogles/GL/build/build_121_to_128.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_129_to_136,
+ "conformance/ogles/GL/build/build_129_to_136.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_137_to_144,
+ "conformance/ogles/GL/build/build_137_to_144.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_145_to_152,
+ "conformance/ogles/GL/build/build_145_to_152.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_153_to_160,
+ "conformance/ogles/GL/build/build_153_to_160.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_161_to_168,
+ "conformance/ogles/GL/build/build_161_to_168.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_169_to_176,
+ "conformance/ogles/GL/build/build_169_to_176.html");
+CONFORMANCE_TEST(conformance_ogles_GL_build_build_177_to_178,
+ "conformance/ogles/GL/build/build_177_to_178.html");
+CONFORMANCE_TEST(conformance_ogles_GL_built_in_varying_array_out_of_bounds_built_in_varying_array_out_of_bounds_001_to_001,
+ "conformance/ogles/GL/built_in_varying_array_out_of_bounds/built_in_varying_array_out_of_bounds_001_to_001.html");
+CONFORMANCE_TEST(conformance_ogles_GL_ceil_ceil_001_to_006,
+ "conformance/ogles/GL/ceil/ceil_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_clamp_clamp_001_to_006,
+ "conformance/ogles/GL/clamp/clamp_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_control_flow_control_flow_001_to_008,
+ "conformance/ogles/GL/control_flow/control_flow_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_control_flow_control_flow_009_to_010,
+ "conformance/ogles/GL/control_flow/control_flow_009_to_010.html");
+CONFORMANCE_TEST(conformance_ogles_GL_cos_cos_001_to_006,
+ "conformance/ogles/GL/cos/cos_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_cross_cross_001_to_002,
+ "conformance/ogles/GL/cross/cross_001_to_002.html");
+CONFORMANCE_TEST(conformance_ogles_GL_default_default_001_to_001,
+ "conformance/ogles/GL/default/default_001_to_001.html");
+CONFORMANCE_TEST(conformance_ogles_GL_degrees_degrees_001_to_006,
+ "conformance/ogles/GL/degrees/degrees_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_discard_discard_001_to_002,
+ "conformance/ogles/GL/discard/discard_001_to_002.html");
+CONFORMANCE_TEST(conformance_ogles_GL_distance_distance_001_to_006,
+ "conformance/ogles/GL/distance/distance_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_dot_dot_001_to_006,
+ "conformance/ogles/GL/dot/dot_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_equal_equal_001_to_008,
+ "conformance/ogles/GL/equal/equal_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_equal_equal_009_to_012,
+ "conformance/ogles/GL/equal/equal_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_exp_exp_001_to_008,
+ "conformance/ogles/GL/exp/exp_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_exp_exp_009_to_012,
+ "conformance/ogles/GL/exp/exp_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_exp2_exp2_001_to_008,
+ "conformance/ogles/GL/exp2/exp2_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_exp2_exp2_009_to_012,
+ "conformance/ogles/GL/exp2/exp2_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_faceforward_faceforward_001_to_006,
+ "conformance/ogles/GL/faceforward/faceforward_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_floor_floor_001_to_006,
+ "conformance/ogles/GL/floor/floor_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_fract_fract_001_to_006,
+ "conformance/ogles/GL/fract/fract_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_001_to_008,
+ "conformance/ogles/GL/functions/functions_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_009_to_016,
+ "conformance/ogles/GL/functions/functions_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_017_to_024,
+ "conformance/ogles/GL/functions/functions_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_025_to_032,
+ "conformance/ogles/GL/functions/functions_025_to_032.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_033_to_040,
+ "conformance/ogles/GL/functions/functions_033_to_040.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_041_to_048,
+ "conformance/ogles/GL/functions/functions_041_to_048.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_049_to_056,
+ "conformance/ogles/GL/functions/functions_049_to_056.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_057_to_064,
+ "conformance/ogles/GL/functions/functions_057_to_064.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_065_to_072,
+ "conformance/ogles/GL/functions/functions_065_to_072.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_073_to_080,
+ "conformance/ogles/GL/functions/functions_073_to_080.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_081_to_088,
+ "conformance/ogles/GL/functions/functions_081_to_088.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_089_to_096,
+ "conformance/ogles/GL/functions/functions_089_to_096.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_097_to_104,
+ "conformance/ogles/GL/functions/functions_097_to_104.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_105_to_112,
+ "conformance/ogles/GL/functions/functions_105_to_112.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_113_to_120,
+ "conformance/ogles/GL/functions/functions_113_to_120.html");
+CONFORMANCE_TEST(conformance_ogles_GL_functions_functions_121_to_126,
+ "conformance/ogles/GL/functions/functions_121_to_126.html");
+CONFORMANCE_TEST(conformance_ogles_GL_gl_FragCoord_gl_FragCoord_001_to_003,
+ "conformance/ogles/GL/gl_FragCoord/gl_FragCoord_001_to_003.html");
+CONFORMANCE_TEST(conformance_ogles_GL_gl_FrontFacing_gl_FrontFacing_001_to_001,
+ "conformance/ogles/GL/gl_FrontFacing/gl_FrontFacing_001_to_001.html");
+CONFORMANCE_TEST(conformance_ogles_GL_greaterThan_greaterThan_001_to_008,
+ "conformance/ogles/GL/greaterThan/greaterThan_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_greaterThanEqual_greaterThanEqual_001_to_008,
+ "conformance/ogles/GL/greaterThanEqual/greaterThanEqual_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_inversesqrt_inversesqrt_001_to_006,
+ "conformance/ogles/GL/inversesqrt/inversesqrt_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_length_length_001_to_006,
+ "conformance/ogles/GL/length/length_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_lessThan_lessThan_001_to_008,
+ "conformance/ogles/GL/lessThan/lessThan_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_lessThanEqual_lessThanEqual_001_to_008,
+ "conformance/ogles/GL/lessThanEqual/lessThanEqual_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_log_log_001_to_008,
+ "conformance/ogles/GL/log/log_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_log_log_009_to_012,
+ "conformance/ogles/GL/log/log_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_log2_log2_001_to_008,
+ "conformance/ogles/GL/log2/log2_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_log2_log2_009_to_012,
+ "conformance/ogles/GL/log2/log2_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_001_to_008,
+ "conformance/ogles/GL/mat/mat_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_009_to_016,
+ "conformance/ogles/GL/mat/mat_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_017_to_024,
+ "conformance/ogles/GL/mat/mat_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_025_to_032,
+ "conformance/ogles/GL/mat/mat_025_to_032.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_033_to_040,
+ "conformance/ogles/GL/mat/mat_033_to_040.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat_mat_041_to_046,
+ "conformance/ogles/GL/mat/mat_041_to_046.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mat3_mat3_001_to_006,
+ "conformance/ogles/GL/mat3/mat3_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_matrixCompMult_matrixCompMult_001_to_004,
+ "conformance/ogles/GL/matrixCompMult/matrixCompMult_001_to_004.html");
+CONFORMANCE_TEST(conformance_ogles_GL_max_max_001_to_006,
+ "conformance/ogles/GL/max/max_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_min_min_001_to_006,
+ "conformance/ogles/GL/min/min_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mix_mix_001_to_006,
+ "conformance/ogles/GL/mix/mix_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_mod_mod_001_to_008,
+ "conformance/ogles/GL/mod/mod_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_normalize_normalize_001_to_006,
+ "conformance/ogles/GL/normalize/normalize_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_not_not_001_to_004,
+ "conformance/ogles/GL/not/not_001_to_004.html");
+CONFORMANCE_TEST(conformance_ogles_GL_notEqual_notEqual_001_to_008,
+ "conformance/ogles/GL/notEqual/notEqual_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_notEqual_notEqual_009_to_012,
+ "conformance/ogles/GL/notEqual/notEqual_009_to_012.html");
+CONFORMANCE_TEST(conformance_ogles_GL_operators_operators_001_to_008,
+ "conformance/ogles/GL/operators/operators_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_operators_operators_009_to_016,
+ "conformance/ogles/GL/operators/operators_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_operators_operators_017_to_024,
+ "conformance/ogles/GL/operators/operators_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_operators_operators_025_to_026,
+ "conformance/ogles/GL/operators/operators_025_to_026.html");
+CONFORMANCE_TEST(conformance_ogles_GL_pow_pow_001_to_008,
+ "conformance/ogles/GL/pow/pow_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_pow_pow_009_to_016,
+ "conformance/ogles/GL/pow/pow_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_pow_pow_017_to_024,
+ "conformance/ogles/GL/pow/pow_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_radians_radians_001_to_006,
+ "conformance/ogles/GL/radians/radians_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_reflect_reflect_001_to_006,
+ "conformance/ogles/GL/reflect/reflect_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_refract_refract_001_to_006,
+ "conformance/ogles/GL/refract/refract_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_sign_sign_001_to_006,
+ "conformance/ogles/GL/sign/sign_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_sin_sin_001_to_006,
+ "conformance/ogles/GL/sin/sin_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_smoothstep_smoothstep_001_to_006,
+ "conformance/ogles/GL/smoothstep/smoothstep_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_sqrt_sqrt_001_to_006,
+ "conformance/ogles/GL/sqrt/sqrt_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_step_step_001_to_006,
+ "conformance/ogles/GL/step/step_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_001_to_008,
+ "conformance/ogles/GL/struct/struct_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_009_to_016,
+ "conformance/ogles/GL/struct/struct_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_017_to_024,
+ "conformance/ogles/GL/struct/struct_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_025_to_032,
+ "conformance/ogles/GL/struct/struct_025_to_032.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_033_to_040,
+ "conformance/ogles/GL/struct/struct_033_to_040.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_041_to_048,
+ "conformance/ogles/GL/struct/struct_041_to_048.html");
+CONFORMANCE_TEST(conformance_ogles_GL_struct_struct_049_to_056,
+ "conformance/ogles/GL/struct/struct_049_to_056.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_001_to_008,
+ "conformance/ogles/GL/swizzlers/swizzlers_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_009_to_016,
+ "conformance/ogles/GL/swizzlers/swizzlers_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_017_to_024,
+ "conformance/ogles/GL/swizzlers/swizzlers_017_to_024.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_025_to_032,
+ "conformance/ogles/GL/swizzlers/swizzlers_025_to_032.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_033_to_040,
+ "conformance/ogles/GL/swizzlers/swizzlers_033_to_040.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_041_to_048,
+ "conformance/ogles/GL/swizzlers/swizzlers_041_to_048.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_049_to_056,
+ "conformance/ogles/GL/swizzlers/swizzlers_049_to_056.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_057_to_064,
+ "conformance/ogles/GL/swizzlers/swizzlers_057_to_064.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_065_to_072,
+ "conformance/ogles/GL/swizzlers/swizzlers_065_to_072.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_073_to_080,
+ "conformance/ogles/GL/swizzlers/swizzlers_073_to_080.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_081_to_088,
+ "conformance/ogles/GL/swizzlers/swizzlers_081_to_088.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_089_to_096,
+ "conformance/ogles/GL/swizzlers/swizzlers_089_to_096.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_097_to_104,
+ "conformance/ogles/GL/swizzlers/swizzlers_097_to_104.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_105_to_112,
+ "conformance/ogles/GL/swizzlers/swizzlers_105_to_112.html");
+CONFORMANCE_TEST(conformance_ogles_GL_swizzlers_swizzlers_113_to_120,
+ "conformance/ogles/GL/swizzlers/swizzlers_113_to_120.html");
+CONFORMANCE_TEST(conformance_ogles_GL_tan_tan_001_to_006,
+ "conformance/ogles/GL/tan/tan_001_to_006.html");
+CONFORMANCE_TEST(conformance_ogles_GL_vec_vec_001_to_008,
+ "conformance/ogles/GL/vec/vec_001_to_008.html");
+CONFORMANCE_TEST(conformance_ogles_GL_vec_vec_009_to_016,
+ "conformance/ogles/GL/vec/vec_009_to_016.html");
+CONFORMANCE_TEST(conformance_ogles_GL_vec_vec_017_to_018,
+ "conformance/ogles/GL/vec/vec_017_to_018.html");
+CONFORMANCE_TEST(conformance_ogles_GL_vec3_vec3_001_to_008,
+ "conformance/ogles/GL/vec3/vec3_001_to_008.html");
+
+#endif // CONTENT_TEST_GPU_WEBGL_CONFORMANCE_TEST_LIST_AUTOGEN_H_
+
diff --git a/chromium/content/browser/histogram_controller.cc b/chromium/content/browser/histogram_controller.cc
new file mode 100644
index 00000000000..240de2556b9
--- /dev/null
+++ b/chromium/content/browser/histogram_controller.cc
@@ -0,0 +1,121 @@
+// 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 "content/browser/histogram_controller.h"
+
+#include "base/bind.h"
+#include "base/metrics/histogram.h"
+#include "content/browser/histogram_subscriber.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/process_type.h"
+
+namespace content {
+
+HistogramController* HistogramController::GetInstance() {
+ return Singleton<HistogramController>::get();
+}
+
+HistogramController::HistogramController() : subscriber_(NULL) {
+}
+
+HistogramController::~HistogramController() {
+}
+
+void HistogramController::OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber_)
+ subscriber_->OnPendingProcesses(sequence_number, pending_processes, end);
+}
+
+void HistogramController::OnHistogramDataCollected(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&HistogramController::OnHistogramDataCollected,
+ base::Unretained(this),
+ sequence_number,
+ pickled_histograms));
+ return;
+ }
+
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber_) {
+ subscriber_->OnHistogramDataCollected(sequence_number,
+ pickled_histograms);
+ }
+}
+
+void HistogramController::Register(HistogramSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!subscriber_);
+ subscriber_ = subscriber;
+}
+
+void HistogramController::Unregister(
+ const HistogramSubscriber* subscriber) {
+ DCHECK_EQ(subscriber_, subscriber);
+ subscriber_ = NULL;
+}
+
+void HistogramController::GetHistogramDataFromChildProcesses(
+ int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ int pending_processes = 0;
+ for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
+ int type = iter.GetData().process_type;
+ if (type != PROCESS_TYPE_PLUGIN &&
+ type != PROCESS_TYPE_GPU &&
+ type != PROCESS_TYPE_PPAPI_PLUGIN &&
+ type != PROCESS_TYPE_PPAPI_BROKER) {
+ continue;
+ }
+
+ ++pending_processes;
+ if (!iter.Send(new ChildProcessMsg_GetChildHistogramData(sequence_number)))
+ --pending_processes;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &HistogramController::OnPendingProcesses,
+ base::Unretained(this),
+ sequence_number,
+ pending_processes,
+ true));
+}
+
+void HistogramController::GetHistogramData(int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ int pending_processes = 0;
+ for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ ++pending_processes;
+ if (!it.GetCurrentValue()->Send(
+ new ChildProcessMsg_GetChildHistogramData(sequence_number))) {
+ --pending_processes;
+ }
+ }
+ OnPendingProcesses(sequence_number, pending_processes, false);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&HistogramController::GetHistogramDataFromChildProcesses,
+ base::Unretained(this),
+ sequence_number));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/histogram_controller.h b/chromium/content/browser/histogram_controller.h
new file mode 100644
index 00000000000..0fc46221d4a
--- /dev/null
+++ b/chromium/content/browser/histogram_controller.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_HISTOGRAM_CONTROLLER_H_
+#define CONTENT_BROWSER_HISTOGRAM_CONTROLLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/singleton.h"
+
+namespace content {
+
+class HistogramSubscriber;
+
+// HistogramController is used on the browser process to collect histogram data.
+// Only the browser UI thread is allowed to interact with the
+// HistogramController object.
+class HistogramController {
+ public:
+ // Returns the HistogramController object for the current process, or NULL if
+ // none.
+ static HistogramController* GetInstance();
+
+ // Normally instantiated when the child process is launched. Only one instance
+ // should be created per process.
+ HistogramController();
+ virtual ~HistogramController();
+
+ // Register the subscriber so that it will be called when for example
+ // OnHistogramDataCollected is returning histogram data from a child process.
+ // This is called on UI thread.
+ void Register(HistogramSubscriber* subscriber);
+
+ // Unregister the subscriber so that it will not be called when for example
+ // OnHistogramDataCollected is returning histogram data from a child process.
+ // Safe to call even if caller is not the current subscriber.
+ void Unregister(const HistogramSubscriber* subscriber);
+
+ // Contact all processes and get their histogram data.
+ void GetHistogramData(int sequence_number);
+
+ // Notify the |subscriber_| that it should expect at least |pending_processes|
+ // additional calls to OnHistogramDataCollected(). OnPendingProcess() may be
+ // called repeatedly; the last call will have |end| set to true, indicating
+ // that there is no longer a possibility for the count of pending processes to
+ // increase. This is called on the UI thread.
+ void OnPendingProcesses(int sequence_number, int pending_processes, bool end);
+
+ // Send the |histogram| back to the |subscriber_|.
+ // This can be called from any thread.
+ void OnHistogramDataCollected(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms);
+
+ private:
+ friend struct DefaultSingletonTraits<HistogramController>;
+
+ // Contact PLUGIN and GPU child processes and get their histogram data.
+ // TODO(rtenneti): Enable getting histogram data for other processes like
+ // PPAPI and NACL.
+ void GetHistogramDataFromChildProcesses(int sequence_number);
+
+ HistogramSubscriber* subscriber_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HISTOGRAM_CONTROLLER_H_
diff --git a/chromium/content/browser/histogram_internals_request_job.cc b/chromium/content/browser/histogram_internals_request_job.cc
new file mode 100644
index 00000000000..dc387bf3732
--- /dev/null
+++ b/chromium/content/browser/histogram_internals_request_job.cc
@@ -0,0 +1,71 @@
+// 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 "content/browser/histogram_internals_request_job.h"
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "content/browser/histogram_synchronizer.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+
+namespace content {
+
+HistogramInternalsRequestJob::HistogramInternalsRequestJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate)
+ : net::URLRequestSimpleJob(request, network_delegate) {
+ const std::string& spec = request->url().possibly_invalid_spec();
+ const url_parse::Parsed& parsed =
+ request->url().parsed_for_possibly_invalid_spec();
+ // + 1 to skip the slash at the beginning of the path.
+ int offset = parsed.CountCharactersBefore(url_parse::Parsed::PATH, false) + 1;
+
+ if (offset < static_cast<int>(spec.size()))
+ path_.assign(spec.substr(offset));
+}
+
+void AboutHistogram(std::string* data, const std::string& path) {
+ HistogramSynchronizer::FetchHistograms();
+
+ std::string unescaped_query;
+ std::string unescaped_title("About Histograms");
+ if (!path.empty()) {
+ unescaped_query = net::UnescapeURLComponent(path,
+ net::UnescapeRule::NORMAL);
+ unescaped_title += " - " + unescaped_query;
+ }
+
+ data->append("<!DOCTYPE html>\n<html>\n<head>\n");
+ data->append(
+ "<meta http-equiv=\"Content-Security-Policy\" "
+ "content=\"object-src 'none'; script-src 'none'\">");
+ data->append("<title>");
+ data->append(net::EscapeForHTML(unescaped_title));
+ data->append("</title>\n");
+ data->append("</head><body>");
+
+ // Display any stats for which we sent off requests the last time.
+ data->append("<p>Stats as of last page load;");
+ data->append("reload to get stats as of this page load.</p>\n");
+ data->append("<table width=\"100%\">\n");
+
+ base::StatisticsRecorder::WriteHTMLGraph(unescaped_query, data);
+}
+
+int HistogramInternalsRequestJob::GetData(
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const {
+ mime_type->assign("text/html");
+ charset->assign("UTF8");
+
+ data->clear();
+ AboutHistogram(data, path_);
+ return net::OK;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/histogram_internals_request_job.h b/chromium/content/browser/histogram_internals_request_job.h
new file mode 100644
index 00000000000..4be774be87c
--- /dev/null
+++ b/chromium/content/browser/histogram_internals_request_job.h
@@ -0,0 +1,36 @@
+// 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 CONTENT_BROWSER_HISTOGRAM_INTERNALS_REQUEST_JOB_H_
+#define CONTENT_BROWSER_HISTOGRAM_INTERNALS_REQUEST_JOB_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/url_request/url_request_simple_job.h"
+
+namespace content {
+
+class HistogramInternalsRequestJob : public net::URLRequestSimpleJob {
+ public:
+ HistogramInternalsRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate);
+
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE;
+
+ private:
+ virtual ~HistogramInternalsRequestJob() {}
+
+ // The string to select histograms which have |path_| as a substring.
+ std::string path_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(HistogramInternalsRequestJob);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HISTOGRAM_INTERNALS_REQUEST_JOB_H_
diff --git a/chromium/content/browser/histogram_message_filter.cc b/chromium/content/browser/histogram_message_filter.cc
new file mode 100644
index 00000000000..d46b96bc400
--- /dev/null
+++ b/chromium/content/browser/histogram_message_filter.cc
@@ -0,0 +1,68 @@
+// 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 "content/browser/histogram_message_filter.h"
+
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "content/browser/histogram_controller.h"
+#include "content/browser/tcmalloc_internals_request_job.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/common/content_switches.h"
+
+namespace content {
+
+HistogramMessageFilter::HistogramMessageFilter() {}
+
+void HistogramMessageFilter::OnChannelConnected(int32 peer_pid) {
+ BrowserMessageFilter::OnChannelConnected(peer_pid);
+}
+
+bool HistogramMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(HistogramMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_ChildHistogramData,
+ OnChildHistogramData)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_GetBrowserHistogram,
+ OnGetBrowserHistogram)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+HistogramMessageFilter::~HistogramMessageFilter() {}
+
+void HistogramMessageFilter::OnChildHistogramData(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms) {
+ HistogramController::GetInstance()->OnHistogramDataCollected(
+ sequence_number, pickled_histograms);
+}
+
+void HistogramMessageFilter::OnGetBrowserHistogram(
+ const std::string& name,
+ std::string* histogram_json) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ // Security: Only allow access to browser histograms when running in the
+ // context of a test.
+ bool using_stats_collection_controller =
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kStatsCollectionController);
+ if (!using_stats_collection_controller) {
+ LOG(ERROR) << "Attempt at reading browser histogram without specifying "
+ << "--" << switches::kStatsCollectionController << " switch.";
+ return;
+ }
+ base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(name);
+ if (!histogram) {
+ *histogram_json = "{}";
+ } else {
+ histogram->WriteJSON(histogram_json);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/histogram_message_filter.h b/chromium/content/browser/histogram_message_filter.h
new file mode 100644
index 00000000000..2118d4a97a2
--- /dev/null
+++ b/chromium/content/browser/histogram_message_filter.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_BROWSER_HISTOGRAM_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_HISTOGRAM_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/common/process_type.h"
+
+namespace content {
+
+// This class sends and receives histogram messages in the browser process.
+class HistogramMessageFilter : public BrowserMessageFilter {
+ public:
+ HistogramMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~HistogramMessageFilter();
+
+ // Message handlers.
+ void OnChildHistogramData(int sequence_number,
+ const std::vector<std::string>& pickled_histograms);
+ void OnGetBrowserHistogram(const std::string& name,
+ std::string* histogram_json);
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HISTOGRAM_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/histogram_subscriber.h b/chromium/content/browser/histogram_subscriber.h
new file mode 100644
index 00000000000..993e4c97c3b
--- /dev/null
+++ b/chromium/content/browser/histogram_subscriber.h
@@ -0,0 +1,33 @@
+// 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 CONTENT_BROWSER_HISTOGRAM_SUBSCRIBER_H_
+#define CONTENT_BROWSER_HISTOGRAM_SUBSCRIBER_H_
+
+#include <string>
+#include <vector>
+
+namespace content {
+
+// Objects interested in receiving histograms derive from HistogramSubscriber.
+class HistogramSubscriber {
+ public:
+ virtual ~HistogramSubscriber() {}
+
+ // Send number of pending processes to subscriber. |end| is set to true if it
+ // is the last time. This is called on the UI thread.
+ virtual void OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) = 0;
+
+ // Send |histogram| back to subscriber.
+ // This is called on the UI thread.
+ virtual void OnHistogramDataCollected(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HISTOGRAM_SUBSCRIBER_H_
diff --git a/chromium/content/browser/histogram_synchronizer.cc b/chromium/content/browser/histogram_synchronizer.cc
new file mode 100644
index 00000000000..5a1e6f9a413
--- /dev/null
+++ b/chromium/content/browser/histogram_synchronizer.cc
@@ -0,0 +1,348 @@
+// 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 "content/browser/histogram_synchronizer.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/histogram_controller.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/histogram_fetcher.h"
+#include "content/public/common/content_constants.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+// Negative numbers are never used as sequence numbers. We explicitly pick a
+// negative number that is "so negative" that even when we add one (as is done
+// when we generated the next sequence number) that it will still be negative.
+// We have code that handles wrapping around on an overflow into negative
+// territory.
+static const int kNeverUsableSequenceNumber = -2;
+
+} // anonymous namespace
+
+namespace content {
+
+// The "RequestContext" structure describes an individual request received from
+// the UI. All methods are accessible on UI thread.
+class HistogramSynchronizer::RequestContext {
+ public:
+ // A map from sequence_number_ to the actual RequestContexts.
+ typedef std::map<int, RequestContext*> RequestContextMap;
+
+ RequestContext(const base::Closure& callback, int sequence_number)
+ : callback_(callback),
+ sequence_number_(sequence_number),
+ received_process_group_count_(0),
+ processes_pending_(0) {
+ }
+ ~RequestContext() {}
+
+ void SetReceivedProcessGroupCount(bool done) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ received_process_group_count_ = done;
+ }
+
+ // Methods for book keeping of processes_pending_.
+ void AddProcessesPending(int processes_pending) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ processes_pending_ += processes_pending;
+ }
+
+ void DecrementProcessesPending() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ --processes_pending_;
+ }
+
+ // Records that we are waiting for one less histogram data from a process for
+ // the given sequence number. If |received_process_group_count_| and
+ // |processes_pending_| are zero, then delete the current object by calling
+ // Unregister.
+ void DeleteIfAllDone() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (processes_pending_ <= 0 && received_process_group_count_)
+ RequestContext::Unregister(sequence_number_);
+ }
+
+ // Register |callback| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static void Register(const base::Closure& callback, int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RequestContext* request = new RequestContext(callback, sequence_number);
+ outstanding_requests_.Get()[sequence_number] = request;
+ }
+
+ // Find the |RequestContext| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static RequestContext* GetRequestContext(int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return NULL;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ return request;
+ }
+
+ // Delete the entry for the given |sequence_number| from
+ // |outstanding_requests_| map. This method is called when all changes have
+ // been acquired, or when the wait time expires (whichever is sooner).
+ static void Unregister(int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ bool received_process_group_count = request->received_process_group_count_;
+ int unresponsive_processes = request->processes_pending_;
+
+ request->callback_.Run();
+
+ delete request;
+ outstanding_requests_.Get().erase(it);
+
+ UMA_HISTOGRAM_BOOLEAN("Histogram.ReceivedProcessGroupCount",
+ received_process_group_count);
+ UMA_HISTOGRAM_COUNTS("Histogram.PendingProcessNotResponding",
+ unresponsive_processes);
+ }
+
+ // Delete all the entries in |outstanding_requests_| map.
+ static void OnShutdown() {
+ // Just in case we have any pending tasks, clear them out.
+ while (!outstanding_requests_.Get().empty()) {
+ RequestContextMap::iterator it = outstanding_requests_.Get().begin();
+ delete it->second;
+ outstanding_requests_.Get().erase(it);
+ }
+ }
+
+ // Requests are made to asynchronously send data to the |callback_|.
+ base::Closure callback_;
+
+ // The sequence number used by the most recent update request to contact all
+ // processes.
+ int sequence_number_;
+
+ // Indicates if we have received all pending processes count.
+ bool received_process_group_count_;
+
+ // The number of pending processes (all renderer processes and browser child
+ // processes) that have not yet responded to requests.
+ int processes_pending_;
+
+ // Map of all outstanding RequestContexts, from sequence_number_ to
+ // RequestContext.
+ static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_;
+};
+
+// static
+base::LazyInstance
+ <HistogramSynchronizer::RequestContext::RequestContextMap>::Leaky
+ HistogramSynchronizer::RequestContext::outstanding_requests_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+HistogramSynchronizer::HistogramSynchronizer()
+ : lock_(),
+ callback_thread_(NULL),
+ last_used_sequence_number_(kNeverUsableSequenceNumber),
+ async_sequence_number_(kNeverUsableSequenceNumber) {
+ HistogramController::GetInstance()->Register(this);
+}
+
+HistogramSynchronizer::~HistogramSynchronizer() {
+ RequestContext::OnShutdown();
+
+ // Just in case we have any pending tasks, clear them out.
+ SetCallbackTaskAndThread(NULL, base::Closure());
+}
+
+HistogramSynchronizer* HistogramSynchronizer::GetInstance() {
+ return Singleton<HistogramSynchronizer,
+ LeakySingletonTraits<HistogramSynchronizer> >::get();
+}
+
+// static
+void HistogramSynchronizer::FetchHistograms() {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&HistogramSynchronizer::FetchHistograms));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ HistogramSynchronizer* current_synchronizer =
+ HistogramSynchronizer::GetInstance();
+ if (current_synchronizer == NULL)
+ return;
+
+ current_synchronizer->RegisterAndNotifyAllProcesses(
+ HistogramSynchronizer::UNKNOWN,
+ base::TimeDelta::FromMinutes(1));
+}
+
+void FetchHistogramsAsynchronously(base::MessageLoop* callback_thread,
+ const base::Closure& callback,
+ base::TimeDelta wait_time) {
+ HistogramSynchronizer::FetchHistogramsAsynchronously(
+ callback_thread, callback, wait_time);
+}
+
+// static
+void HistogramSynchronizer::FetchHistogramsAsynchronously(
+ base::MessageLoop* callback_thread,
+ const base::Closure& callback,
+ base::TimeDelta wait_time) {
+ DCHECK(callback_thread != NULL);
+ DCHECK(!callback.is_null());
+
+ HistogramSynchronizer* current_synchronizer =
+ HistogramSynchronizer::GetInstance();
+ current_synchronizer->SetCallbackTaskAndThread(
+ callback_thread, callback);
+
+ current_synchronizer->RegisterAndNotifyAllProcesses(
+ HistogramSynchronizer::ASYNC_HISTOGRAMS, wait_time);
+}
+
+void HistogramSynchronizer::RegisterAndNotifyAllProcesses(
+ ProcessHistogramRequester requester,
+ base::TimeDelta wait_time) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ int sequence_number = GetNextAvailableSequenceNumber(requester);
+
+ base::Closure callback = base::Bind(
+ &HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback,
+ base::Unretained(this),
+ sequence_number);
+
+ RequestContext::Register(callback, sequence_number);
+
+ // Get histogram data from renderer and browser child processes.
+ HistogramController::GetInstance()->GetHistogramData(sequence_number);
+
+ // Post a task that would be called after waiting for wait_time. This acts
+ // as a watchdog, to cancel the requests for non-responsive processes.
+ BrowserThread::PostDelayedTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&RequestContext::Unregister, sequence_number),
+ wait_time);
+}
+
+void HistogramSynchronizer::OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+ if (!request)
+ return;
+ request->AddProcessesPending(pending_processes);
+ request->SetReceivedProcessGroupCount(end);
+ request->DeleteIfAllDone();
+}
+
+void HistogramSynchronizer::OnHistogramDataCollected(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+
+ for (std::vector<std::string>::const_iterator it = pickled_histograms.begin();
+ it < pickled_histograms.end();
+ ++it) {
+ Pickle pickle(it->data(), it->size());
+ PickleIterator iter(pickle);
+ base::DeserializeHistogramAndAddSamples(&iter);
+ }
+
+ if (!request)
+ return;
+
+ // Delete request if we have heard back from all child processes.
+ request->DecrementProcessesPending();
+ request->DeleteIfAllDone();
+}
+
+void HistogramSynchronizer::SetCallbackTaskAndThread(
+ base::MessageLoop* callback_thread,
+ const base::Closure& callback) {
+ base::Closure old_callback;
+ base::MessageLoop* old_thread = NULL;
+ {
+ base::AutoLock auto_lock(lock_);
+ old_callback = callback_;
+ callback_ = callback;
+ old_thread = callback_thread_;
+ callback_thread_ = callback_thread;
+ // Prevent premature calling of our new callbacks.
+ async_sequence_number_ = kNeverUsableSequenceNumber;
+ }
+ // Just in case there was a task pending....
+ InternalPostTask(old_thread, old_callback);
+}
+
+void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback(
+ int sequence_number) {
+ base::Closure callback;
+ base::MessageLoop* thread = NULL;
+ {
+ base::AutoLock lock(lock_);
+ if (sequence_number != async_sequence_number_)
+ return;
+ callback = callback_;
+ thread = callback_thread_;
+ callback_.Reset();
+ callback_thread_ = NULL;
+ }
+ InternalPostTask(thread, callback);
+}
+
+void HistogramSynchronizer::InternalPostTask(base::MessageLoop* thread,
+ const base::Closure& callback) {
+ if (callback.is_null() || !thread)
+ return;
+ thread->PostTask(FROM_HERE, callback);
+}
+
+int HistogramSynchronizer::GetNextAvailableSequenceNumber(
+ ProcessHistogramRequester requester) {
+ base::AutoLock auto_lock(lock_);
+ ++last_used_sequence_number_;
+ // Watch out for wrapping to a negative number.
+ if (last_used_sequence_number_ < 0) {
+ // Bypass the reserved number, which is used when a renderer spontaneously
+ // decides to send some histogram data.
+ last_used_sequence_number_ =
+ kHistogramSynchronizerReservedSequenceNumber + 1;
+ }
+ DCHECK_NE(last_used_sequence_number_,
+ kHistogramSynchronizerReservedSequenceNumber);
+ if (requester == ASYNC_HISTOGRAMS)
+ async_sequence_number_ = last_used_sequence_number_;
+ return last_used_sequence_number_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/histogram_synchronizer.h b/chromium/content/browser/histogram_synchronizer.h
new file mode 100644
index 00000000000..9ff69d5cfeb
--- /dev/null
+++ b/chromium/content/browser/histogram_synchronizer.h
@@ -0,0 +1,154 @@
+// 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 CONTENT_BROWSER_HISTOGRAM_SYNCHRONIZER_H_
+#define CONTENT_BROWSER_HISTOGRAM_SYNCHRONIZER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "content/browser/histogram_subscriber.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace content {
+
+// This class maintains state that is used to upload histogram data from the
+// various child processes, into the browser process. Such transactions are
+// usually instigated by the browser. In general, a child process will respond
+// by gathering snapshots of all internal histograms, calculating what has
+// changed since its last upload, and transmitting a pickled collection of
+// deltas.
+//
+// There are actually two modes of update request. One is synchronous (and
+// blocks the UI thread, waiting to populate an about:histograms tab) and the
+// other is asynchronous, and used by the metrics services in preparation for a
+// log upload.
+//
+// To assure that all the processes have responded, a counter is maintained to
+// indicate the number of pending (not yet responsive) processes. To avoid
+// confusion about a response (i.e., is the process responding to a current
+// request for an update, or to an old request for an update) we tag each group
+// of requests with a sequence number. When an update arrives we can ignore it
+// (relative to the counter) if it does not relate to a current outstanding
+// sequence number.
+//
+// There is one final mode of use, where a renderer spontaneously decides to
+// transmit a collection of histogram data. This is designed for use when the
+// renderer is terminating. Unfortunately, renders may be terminated without
+// warning, and the best we can do is periodically acquire data from a tab, such
+// as when a page load has completed. In this mode, the renderer uses a
+// reserved sequence number, different from any sequence number that might be
+// specified by a browser request. Since this sequence number can't match an
+// outstanding sequence number, the pickled data is accepted into the browser,
+// but there is no impact on the counters.
+
+class HistogramSynchronizer : public HistogramSubscriber {
+ public:
+ enum ProcessHistogramRequester {
+ UNKNOWN,
+ ASYNC_HISTOGRAMS,
+ };
+
+ // Return pointer to the singleton instance for the current process, or NULL
+ // if none.
+ static HistogramSynchronizer* GetInstance();
+
+ // Contact all processes, and get them to upload to the browser any/all
+ // changes to histograms. This method is called from about:histograms.
+ static void FetchHistograms();
+
+ // Contact all child processes, and get them to upload to the browser any/all
+ // changes to histograms. When all changes have been acquired, or when the
+ // wait time expires (whichever is sooner), post the callback to the
+ // specified message loop. Note the callback is posted exactly once.
+ static void FetchHistogramsAsynchronously(base::MessageLoop* callback_thread,
+ const base::Closure& callback,
+ base::TimeDelta wait_time);
+
+ private:
+ friend struct DefaultSingletonTraits<HistogramSynchronizer>;
+
+ class RequestContext;
+
+ HistogramSynchronizer();
+ virtual ~HistogramSynchronizer();
+
+ // Establish a new sequence number, and use it to notify all processes
+ // (renderers, plugins, GPU, etc) of the need to supply, to the browser,
+ // any/all changes to their histograms. |wait_time| specifies the amount of
+ // time to wait before cancelling the requests for non-responsive processes.
+ void RegisterAndNotifyAllProcesses(ProcessHistogramRequester requester,
+ base::TimeDelta wait_time);
+
+ // -------------------------------------------------------
+ // HistogramSubscriber methods for browser child processes
+ // -------------------------------------------------------
+
+ // Update the number of pending processes for the given |sequence_number|.
+ // This is called on UI thread.
+ virtual void OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) OVERRIDE;
+
+ // Send histogram_data back to caller and also record that we are waiting
+ // for one less histogram data from child process for the given sequence
+ // number. This method is accessible on UI thread.
+ virtual void OnHistogramDataCollected(
+ int sequence_number,
+ const std::vector<std::string>& pickled_histograms) OVERRIDE;
+
+ // Set the callback_thread_ and callback_ members. If these members already
+ // had values, then as a side effect, post the old callback_ to the old
+ // callaback_thread_. This side effect should not generally happen, but is in
+ // place to assure correctness (that any tasks that were set, are eventually
+ // called, and never merely discarded).
+ void SetCallbackTaskAndThread(base::MessageLoop* callback_thread,
+ const base::Closure& callback);
+
+ void ForceHistogramSynchronizationDoneCallback(int sequence_number);
+
+ // Internal helper function, to post task, and record callback stats.
+ void InternalPostTask(base::MessageLoop* thread,
+ const base::Closure& callback);
+
+ // Gets a new sequence number to be sent to processes from browser process.
+ int GetNextAvailableSequenceNumber(ProcessHistogramRequester requester);
+
+ // This lock_ protects access to all members.
+ base::Lock lock_;
+
+ // When a request is made to asynchronously update the histograms, we store
+ // the task and thread we use to post a completion notification in
+ // callback_ and callback_thread_.
+ base::Closure callback_;
+ base::MessageLoop* callback_thread_;
+
+ // We don't track the actual processes that are contacted for an update, only
+ // the count of the number of processes, and we can sometimes time-out and
+ // give up on a "slow to respond" process. We use a sequence_number to be
+ // sure a response from a process is associated with the current round of
+ // requests (and not merely a VERY belated prior response).
+ // All sequence numbers used are non-negative.
+ // last_used_sequence_number_ is the most recently used number (used to avoid
+ // reuse for a long time).
+ int last_used_sequence_number_;
+
+ // The sequence number used by the most recent asynchronous update request to
+ // contact all processes.
+ int async_sequence_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramSynchronizer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HISTOGRAM_SYNCHRONIZER_H_
diff --git a/chromium/content/browser/host_zoom_map_impl.cc b/chromium/content/browser/host_zoom_map_impl.cc
new file mode 100644
index 00000000000..667db376efd
--- /dev/null
+++ b/chromium/content/browser/host_zoom_map_impl.cc
@@ -0,0 +1,249 @@
+// 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 "content/browser/host_zoom_map_impl.h"
+
+#include <cmath>
+
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/common/page_zoom.h"
+#include "net/base/net_util.h"
+
+static const char* kHostZoomMapKeyName = "content_host_zoom_map";
+
+namespace content {
+
+HostZoomMap* HostZoomMap::GetForBrowserContext(BrowserContext* context) {
+ HostZoomMapImpl* rv = static_cast<HostZoomMapImpl*>(
+ context->GetUserData(kHostZoomMapKeyName));
+ if (!rv) {
+ rv = new HostZoomMapImpl();
+ context->SetUserData(kHostZoomMapKeyName, rv);
+ }
+ return rv;
+}
+
+HostZoomMapImpl::HostZoomMapImpl()
+ : default_zoom_level_(0.0) {
+ registrar_.Add(
+ this, NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW,
+ NotificationService::AllSources());
+}
+
+void HostZoomMapImpl::CopyFrom(HostZoomMap* copy_interface) {
+ // This can only be called on the UI thread to avoid deadlocks, otherwise
+ // UI: a.CopyFrom(b);
+ // IO: b.CopyFrom(a);
+ // can deadlock.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ HostZoomMapImpl* copy = static_cast<HostZoomMapImpl*>(copy_interface);
+ base::AutoLock auto_lock(lock_);
+ base::AutoLock copy_auto_lock(copy->lock_);
+ host_zoom_levels_.
+ insert(copy->host_zoom_levels_.begin(), copy->host_zoom_levels_.end());
+ for (SchemeHostZoomLevels::const_iterator i(copy->
+ scheme_host_zoom_levels_.begin());
+ i != copy->scheme_host_zoom_levels_.end(); ++i) {
+ scheme_host_zoom_levels_[i->first] = HostZoomLevels();
+ scheme_host_zoom_levels_[i->first].
+ insert(i->second.begin(), i->second.end());
+ }
+ default_zoom_level_ = copy->default_zoom_level_;
+}
+
+double HostZoomMapImpl::GetZoomLevelForHost(const std::string& host) const {
+ base::AutoLock auto_lock(lock_);
+ HostZoomLevels::const_iterator i(host_zoom_levels_.find(host));
+ return (i == host_zoom_levels_.end()) ? default_zoom_level_ : i->second;
+}
+
+double HostZoomMapImpl::GetZoomLevelForHostAndScheme(
+ const std::string& scheme,
+ const std::string& host) const {
+ {
+ base::AutoLock auto_lock(lock_);
+ SchemeHostZoomLevels::const_iterator scheme_iterator(
+ scheme_host_zoom_levels_.find(scheme));
+ if (scheme_iterator != scheme_host_zoom_levels_.end()) {
+ HostZoomLevels::const_iterator i(scheme_iterator->second.find(host));
+ if (i != scheme_iterator->second.end())
+ return i->second;
+ }
+ }
+ return GetZoomLevelForHost(host);
+}
+
+void HostZoomMapImpl::SetZoomLevelForHost(const std::string& host,
+ double level) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ if (ZoomValuesEqual(level, default_zoom_level_))
+ host_zoom_levels_.erase(host);
+ else
+ host_zoom_levels_[host] = level;
+ }
+
+ // Notify renderers from this browser context.
+ for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ RenderProcessHost* render_process_host = i.GetCurrentValue();
+ if (HostZoomMap::GetForBrowserContext(
+ render_process_host->GetBrowserContext()) == this) {
+ render_process_host->Send(
+ new ViewMsg_SetZoomLevelForCurrentURL(std::string(), host, level));
+ }
+ }
+ HostZoomMap::ZoomLevelChange change;
+ change.mode = HostZoomMap::ZOOM_CHANGED_FOR_HOST;
+ change.host = host;
+ change.zoom_level = level;
+
+ for (size_t i = 0; i < zoom_level_changed_callbacks_.size(); i++)
+ zoom_level_changed_callbacks_[i].Run(change);
+}
+
+void HostZoomMapImpl::SetZoomLevelForHostAndScheme(const std::string& scheme,
+ const std::string& host,
+ double level) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ {
+ base::AutoLock auto_lock(lock_);
+ scheme_host_zoom_levels_[scheme][host] = level;
+ }
+
+ // Notify renderers from this browser context.
+ for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ RenderProcessHost* render_process_host = i.GetCurrentValue();
+ if (HostZoomMap::GetForBrowserContext(
+ render_process_host->GetBrowserContext()) == this) {
+ render_process_host->Send(
+ new ViewMsg_SetZoomLevelForCurrentURL(scheme, host, level));
+ }
+ }
+
+ HostZoomMap::ZoomLevelChange change;
+ change.mode = HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST;
+ change.host = host;
+ change.scheme = scheme;
+ change.zoom_level = level;
+
+ for (size_t i = 0; i < zoom_level_changed_callbacks_.size(); i++)
+ zoom_level_changed_callbacks_[i].Run(change);
+}
+
+double HostZoomMapImpl::GetDefaultZoomLevel() const {
+ return default_zoom_level_;
+}
+
+void HostZoomMapImpl::SetDefaultZoomLevel(double level) {
+ default_zoom_level_ = level;
+}
+
+void HostZoomMapImpl::AddZoomLevelChangedCallback(
+ const ZoomLevelChangedCallback& callback) {
+ zoom_level_changed_callbacks_.push_back(callback);
+}
+
+void HostZoomMapImpl::RemoveZoomLevelChangedCallback(
+ const ZoomLevelChangedCallback& callback) {
+ for (size_t i = 0; i < zoom_level_changed_callbacks_.size(); i++) {
+ if (zoom_level_changed_callbacks_[i].Equals(callback)) {
+ zoom_level_changed_callbacks_.erase(
+ zoom_level_changed_callbacks_.begin() + i);
+ return;
+ }
+ }
+}
+
+double HostZoomMapImpl::GetTemporaryZoomLevel(int render_process_id,
+ int render_view_id) const {
+ base::AutoLock auto_lock(lock_);
+ for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) {
+ if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
+ temporary_zoom_levels_[i].render_view_id == render_view_id) {
+ return temporary_zoom_levels_[i].zoom_level;
+ }
+ }
+ return 0;
+}
+
+void HostZoomMapImpl::SetTemporaryZoomLevel(int render_process_id,
+ int render_view_id,
+ double level) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ {
+ base::AutoLock auto_lock(lock_);
+ size_t i;
+ for (i = 0; i < temporary_zoom_levels_.size(); ++i) {
+ if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
+ temporary_zoom_levels_[i].render_view_id == render_view_id) {
+ if (level) {
+ temporary_zoom_levels_[i].zoom_level = level;
+ } else {
+ temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i);
+ }
+ break;
+ }
+ }
+
+ if (level && i == temporary_zoom_levels_.size()) {
+ TemporaryZoomLevel temp;
+ temp.render_process_id = render_process_id;
+ temp.render_view_id = render_view_id;
+ temp.zoom_level = level;
+ temporary_zoom_levels_.push_back(temp);
+ }
+ }
+
+ HostZoomMap::ZoomLevelChange change;
+ change.mode = HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM;
+ change.zoom_level = level;
+
+ for (size_t i = 0; i < zoom_level_changed_callbacks_.size(); i++)
+ zoom_level_changed_callbacks_[i].Run(change);
+}
+
+void HostZoomMapImpl::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: {
+ base::AutoLock auto_lock(lock_);
+ int render_view_id = Source<RenderViewHost>(source)->GetRoutingID();
+ int render_process_id =
+ Source<RenderViewHost>(source)->GetProcess()->GetID();
+
+ for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) {
+ if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
+ temporary_zoom_levels_[i].render_view_id == render_view_id) {
+ temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected preference observed.";
+ }
+}
+
+HostZoomMapImpl::~HostZoomMapImpl() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/host_zoom_map_impl.h b/chromium/content/browser/host_zoom_map_impl.h
new file mode 100644
index 00000000000..216c83ebc17
--- /dev/null
+++ b/chromium/content/browser/host_zoom_map_impl.h
@@ -0,0 +1,106 @@
+// 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 CONTENT_BROWSER_HOST_ZOOM_MAP_IMPL_H_
+#define CONTENT_BROWSER_HOST_ZOOM_MAP_IMPL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/supports_user_data.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/host_zoom_map.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace content {
+
+// HostZoomMap needs to be deleted on the UI thread because it listens
+// to notifications on there (and holds a NotificationRegistrar).
+class CONTENT_EXPORT HostZoomMapImpl : public NON_EXPORTED_BASE(HostZoomMap),
+ public NotificationObserver,
+ public base::SupportsUserData::Data {
+ public:
+ HostZoomMapImpl();
+ virtual ~HostZoomMapImpl();
+
+ // HostZoomMap implementation:
+ virtual void CopyFrom(HostZoomMap* copy) OVERRIDE;
+ virtual double GetZoomLevelForHostAndScheme(
+ const std::string& scheme,
+ const std::string& host) const OVERRIDE;
+ virtual void SetZoomLevelForHost(
+ const std::string& host,
+ double level) OVERRIDE;
+ virtual void SetZoomLevelForHostAndScheme(
+ const std::string& scheme,
+ const std::string& host,
+ double level) OVERRIDE;
+ virtual double GetDefaultZoomLevel() const OVERRIDE;
+ virtual void SetDefaultZoomLevel(double level) OVERRIDE;
+ virtual void AddZoomLevelChangedCallback(
+ const ZoomLevelChangedCallback& callback) OVERRIDE;
+ virtual void RemoveZoomLevelChangedCallback(
+ const ZoomLevelChangedCallback& callback) OVERRIDE;
+
+ // Returns the temporary zoom level that's only valid for the lifetime of
+ // the given WebContents (i.e. isn't saved and doesn't affect other
+ // WebContentses) if it exists, the default zoom level otherwise.
+ //
+ // This may be called on any thread.
+ double GetTemporaryZoomLevel(int render_process_id,
+ int render_view_id) const;
+
+ // Sets the temporary zoom level that's only valid for the lifetime of this
+ // WebContents.
+ //
+ // This should only be called on the UI thread.
+ void SetTemporaryZoomLevel(int render_process_id,
+ int render_view_id,
+ double level);
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ private:
+ double GetZoomLevelForHost(const std::string& host) const;
+
+ typedef std::map<std::string, double> HostZoomLevels;
+ typedef std::map<std::string, HostZoomLevels> SchemeHostZoomLevels;
+
+ // Callbacks called when zoom level changes.
+ std::vector<ZoomLevelChangedCallback> zoom_level_changed_callbacks_;
+
+ // Copy of the pref data, so that we can read it on the IO thread.
+ HostZoomLevels host_zoom_levels_;
+ SchemeHostZoomLevels scheme_host_zoom_levels_;
+ double default_zoom_level_;
+
+ struct TemporaryZoomLevel {
+ int render_process_id;
+ int render_view_id;
+ double zoom_level;
+ };
+
+ // Don't expect more than a couple of tabs that are using a temporary zoom
+ // level, so vector is fine for now.
+ std::vector<TemporaryZoomLevel> temporary_zoom_levels_;
+
+ // Used around accesses to |host_zoom_levels_|, |default_zoom_level_| and
+ // |temporary_zoom_levels_| to guarantee thread safety.
+ mutable base::Lock lock_;
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostZoomMapImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_HOST_ZOOM_MAP_IMPL_H_
diff --git a/chromium/content/browser/host_zoom_map_impl_unittest.cc b/chromium/content/browser/host_zoom_map_impl_unittest.cc
new file mode 100644
index 00000000000..df1b762e144
--- /dev/null
+++ b/chromium/content/browser/host_zoom_map_impl_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/host_zoom_map_impl.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class HostZoomMapTest : public testing::Test {
+ public:
+ HostZoomMapTest() : ui_thread_(BrowserThread::UI, &message_loop_) {
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ TestBrowserThread ui_thread_;
+};
+
+TEST_F(HostZoomMapTest, GetSetZoomLevel) {
+ HostZoomMapImpl host_zoom_map;
+
+ double zoomed = 2.5;
+ host_zoom_map.SetZoomLevelForHost("zoomed.com", zoomed);
+
+ EXPECT_DOUBLE_EQ(0,
+ host_zoom_map.GetZoomLevelForHostAndScheme("http", "normal.com"));
+ EXPECT_DOUBLE_EQ(zoomed,
+ host_zoom_map.GetZoomLevelForHostAndScheme("http", "zoomed.com"));
+}
+
+TEST_F(HostZoomMapTest, GetSetZoomLevelWithScheme) {
+ HostZoomMapImpl host_zoom_map;
+
+ double zoomed = 2.5;
+ double default_zoom = 1.5;
+
+ host_zoom_map.SetZoomLevelForHostAndScheme("chrome", "login", 0);
+
+ host_zoom_map.SetDefaultZoomLevel(default_zoom);
+
+ EXPECT_DOUBLE_EQ(0,
+ host_zoom_map.GetZoomLevelForHostAndScheme("chrome", "login"));
+ EXPECT_DOUBLE_EQ(default_zoom,
+ host_zoom_map.GetZoomLevelForHostAndScheme("http", "login"));
+
+ host_zoom_map.SetZoomLevelForHost("login", zoomed);
+
+ EXPECT_DOUBLE_EQ(0,
+ host_zoom_map.GetZoomLevelForHostAndScheme("chrome", "login"));
+ EXPECT_DOUBLE_EQ(zoomed,
+ host_zoom_map.GetZoomLevelForHostAndScheme("http", "login"));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/DEPS b/chromium/content/browser/indexed_db/DEPS
new file mode 100644
index 00000000000..743a2f3baec
--- /dev/null
+++ b/chromium/content/browser/indexed_db/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/leveldatabase",
+]
diff --git a/chromium/content/browser/indexed_db/OWNERS b/chromium/content/browser/indexed_db/OWNERS
new file mode 100644
index 00000000000..b106dad853f
--- /dev/null
+++ b/chromium/content/browser/indexed_db/OWNERS
@@ -0,0 +1,4 @@
+dgrogan@chromium.org
+michaeln@chromium.org
+jsbell@chromium.org
+alecflett@chromium.org
diff --git a/chromium/content/browser/indexed_db/indexed_db.h b/chromium/content/browser/indexed_db/indexed_db.h
new file mode 100644
index 00000000000..3714d40af33
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
+
+namespace content {
+
+namespace indexed_db {
+
+enum TransactionMode {
+ TRANSACTION_READ_ONLY = 0,
+ TRANSACTION_READ_WRITE = 1,
+ TRANSACTION_VERSION_CHANGE = 2
+};
+
+enum CursorDirection {
+ CURSOR_NEXT = 0,
+ CURSOR_NEXT_NO_DUPLICATE = 1,
+ CURSOR_PREV = 2,
+ CURSOR_PREV_NO_DUPLICATE = 3,
+};
+
+enum CursorType {
+ CURSOR_KEY_AND_VALUE = 0,
+ CURSOR_KEY_ONLY
+};
+
+} // namespace indexed_db
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_backing_store.cc b/chromium/content/browser/indexed_db/indexed_db_backing_store.cc
new file mode 100644
index 00000000000..58369110014
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_backing_store.cc
@@ -0,0 +1,2640 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_backing_store.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+#include "third_party/WebKit/public/platform/WebIDBTypes.h"
+#include "third_party/leveldatabase/env_chromium.h"
+
+using base::StringPiece;
+
+// TODO(jsbell): Make blink push the version during the open() call.
+static const uint32 kWireVersion = 2;
+
+namespace content {
+
+static const int64 kKeyGeneratorInitialNumber =
+ 1; // From the IndexedDB specification.
+
+enum IndexedDBBackingStoreErrorSource {
+ // 0 - 2 are no longer used.
+ FIND_KEY_IN_INDEX = 3,
+ GET_IDBDATABASE_METADATA,
+ GET_INDEXES,
+ GET_KEY_GENERATOR_CURRENT_NUMBER,
+ GET_OBJECT_STORES,
+ GET_RECORD,
+ KEY_EXISTS_IN_OBJECT_STORE,
+ LOAD_CURRENT_ROW,
+ SET_UP_METADATA,
+ GET_PRIMARY_KEY_VIA_INDEX,
+ KEY_EXISTS_IN_INDEX,
+ VERSION_EXISTS,
+ DELETE_OBJECT_STORE,
+ SET_MAX_OBJECT_STORE_ID,
+ SET_MAX_INDEX_ID,
+ GET_NEW_DATABASE_ID,
+ GET_NEW_VERSION_NUMBER,
+ CREATE_IDBDATABASE_METADATA,
+ DELETE_DATABASE,
+ TRANSACTION_COMMIT_METHOD, // TRANSACTION_COMMIT is a WinNT.h macro
+ GET_DATABASE_NAMES,
+ INTERNAL_ERROR_MAX,
+};
+
+static void RecordInternalError(const char* type,
+ IndexedDBBackingStoreErrorSource location) {
+ std::string name;
+ name.append("WebCore.IndexedDB.BackingStore.").append(type).append("Error");
+ base::Histogram::FactoryGet(name,
+ 1,
+ INTERNAL_ERROR_MAX,
+ INTERNAL_ERROR_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(location);
+}
+
+// Use to signal conditions that usually indicate developer error, but
+// could be caused by data corruption. A macro is used instead of an
+// inline function so that the assert and log report the line number.
+#define REPORT_ERROR(type, location) \
+ do { \
+ LOG(ERROR) << "IndexedDB " type " Error: " #location; \
+ NOTREACHED(); \
+ RecordInternalError(type, location); \
+ } while (0)
+
+#define INTERNAL_READ_ERROR(location) REPORT_ERROR("Read", location)
+#define INTERNAL_CONSISTENCY_ERROR(location) \
+ REPORT_ERROR("Consistency", location)
+#define INTERNAL_WRITE_ERROR(location) REPORT_ERROR("Write", location)
+
+static void PutBool(LevelDBTransaction* transaction,
+ const StringPiece& key,
+ bool value) {
+ std::string buffer;
+ EncodeBool(value, &buffer);
+ transaction->Put(key, &buffer);
+}
+
+template <typename DBOrTransaction>
+static bool GetInt(DBOrTransaction* db,
+ const StringPiece& key,
+ int64* found_int,
+ bool* found) {
+ std::string result;
+ bool ok = db->Get(key, &result, found);
+ if (!ok)
+ return false;
+ if (!*found)
+ return true;
+ StringPiece slice(result);
+ return DecodeInt(&slice, found_int) && slice.empty();
+}
+
+static void PutInt(LevelDBTransaction* transaction,
+ const StringPiece& key,
+ int64 value) {
+ DCHECK_GE(value, 0);
+ std::string buffer;
+ EncodeInt(value, &buffer);
+ transaction->Put(key, &buffer);
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetVarInt(DBOrTransaction* db,
+ const StringPiece& key,
+ int64* found_int,
+ bool* found) {
+ std::string result;
+ bool ok = db->Get(key, &result, found);
+ if (!ok)
+ return false;
+ if (!*found)
+ return true;
+ StringPiece slice(result);
+ return DecodeVarInt(&slice, found_int) && slice.empty();
+}
+
+static void PutVarInt(LevelDBTransaction* transaction,
+ const StringPiece& key,
+ int64 value) {
+ std::string buffer;
+ EncodeVarInt(value, &buffer);
+ transaction->Put(key, &buffer);
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetString(DBOrTransaction* db,
+ const StringPiece& key,
+ string16* found_string,
+ bool* found) {
+ std::string result;
+ *found = false;
+ bool ok = db->Get(key, &result, found);
+ if (!ok)
+ return false;
+ if (!*found)
+ return true;
+ StringPiece slice(result);
+ return DecodeString(&slice, found_string) && slice.empty();
+}
+
+static void PutString(LevelDBTransaction* transaction,
+ const StringPiece& key,
+ const string16& value) {
+ std::string buffer;
+ EncodeString(value, &buffer);
+ transaction->Put(key, &buffer);
+}
+
+static void PutIDBKeyPath(LevelDBTransaction* transaction,
+ const StringPiece& key,
+ const IndexedDBKeyPath& value) {
+ std::string buffer;
+ EncodeIDBKeyPath(value, &buffer);
+ transaction->Put(key, &buffer);
+}
+
+static int CompareKeys(const StringPiece& a, const StringPiece& b) {
+ return Compare(a, b, false /*index_keys*/);
+}
+
+static int CompareIndexKeys(const StringPiece& a, const StringPiece& b) {
+ return Compare(a, b, true /*index_keys*/);
+}
+
+class Comparator : public LevelDBComparator {
+ public:
+ virtual int Compare(const StringPiece& a, const StringPiece& b) const
+ OVERRIDE {
+ return content::Compare(a, b, false /*index_keys*/);
+ }
+ virtual const char* Name() const OVERRIDE { return "idb_cmp1"; }
+};
+
+// 0 - Initial version.
+// 1 - Adds UserIntVersion to DatabaseMetaData.
+// 2 - Adds DataVersion to to global metadata.
+static const int64 kLatestKnownSchemaVersion = 2;
+WARN_UNUSED_RESULT static bool IsSchemaKnown(LevelDBDatabase* db, bool* known) {
+ int64 db_schema_version = 0;
+ bool found = false;
+ bool ok = GetInt(db, SchemaVersionKey::Encode(), &db_schema_version, &found);
+ if (!ok)
+ return false;
+ if (!found) {
+ *known = true;
+ return true;
+ }
+ if (db_schema_version > kLatestKnownSchemaVersion) {
+ *known = false;
+ return true;
+ }
+
+ const uint32 latest_known_data_version = kWireVersion;
+ int64 db_data_version = 0;
+ ok = GetInt(db, DataVersionKey::Encode(), &db_data_version, &found);
+ if (!ok)
+ return false;
+ if (!found) {
+ *known = true;
+ return true;
+ }
+
+ if (db_data_version > latest_known_data_version) {
+ *known = false;
+ return true;
+ }
+
+ *known = true;
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetUpMetadata(
+ LevelDBDatabase* db,
+ const std::string& origin_identifier) {
+ const uint32 latest_known_data_version = kWireVersion;
+ const std::string schema_version_key = SchemaVersionKey::Encode();
+ const std::string data_version_key = DataVersionKey::Encode();
+
+ scoped_refptr<LevelDBTransaction> transaction = new LevelDBTransaction(db);
+
+ int64 db_schema_version = 0;
+ int64 db_data_version = 0;
+ bool found = false;
+ bool ok =
+ GetInt(transaction.get(), schema_version_key, &db_schema_version, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ // Initialize new backing store.
+ db_schema_version = kLatestKnownSchemaVersion;
+ PutInt(transaction.get(), schema_version_key, db_schema_version);
+ db_data_version = latest_known_data_version;
+ PutInt(transaction.get(), data_version_key, db_data_version);
+ } else {
+ // Upgrade old backing store.
+ DCHECK_LE(db_schema_version, kLatestKnownSchemaVersion);
+ if (db_schema_version < 1) {
+ db_schema_version = 1;
+ PutInt(transaction.get(), schema_version_key, db_schema_version);
+ const std::string start_key =
+ DatabaseNameKey::EncodeMinKeyForOrigin(origin_identifier);
+ const std::string stop_key =
+ DatabaseNameKey::EncodeStopKeyForOrigin(origin_identifier);
+ scoped_ptr<LevelDBIterator> it = db->CreateIterator();
+ for (it->Seek(start_key);
+ it->IsValid() && CompareKeys(it->Key(), stop_key) < 0;
+ it->Next()) {
+ int64 database_id = 0;
+ found = false;
+ bool ok = GetInt(transaction.get(), it->Key(), &database_id, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ std::string int_version_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::USER_INT_VERSION);
+ PutVarInt(transaction.get(),
+ int_version_key,
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+ }
+ }
+ if (db_schema_version < 2) {
+ db_schema_version = 2;
+ PutInt(transaction.get(), schema_version_key, db_schema_version);
+ db_data_version = kWireVersion;
+ PutInt(transaction.get(), data_version_key, db_data_version);
+ }
+ }
+
+ // All new values will be written using this serialization version.
+ found = false;
+ ok = GetInt(transaction.get(), data_version_key, &db_data_version, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ if (db_data_version < latest_known_data_version) {
+ db_data_version = latest_known_data_version;
+ PutInt(transaction.get(), data_version_key, db_data_version);
+ }
+
+ DCHECK_EQ(db_schema_version, kLatestKnownSchemaVersion);
+ DCHECK_EQ(db_data_version, latest_known_data_version);
+
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(SET_UP_METADATA);
+ return false;
+ }
+ return true;
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetMaxObjectStoreId(DBOrTransaction* db,
+ int64 database_id,
+ int64* max_object_store_id) {
+ const std::string max_object_store_id_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID);
+ bool ok =
+ GetMaxObjectStoreId(db, max_object_store_id_key, max_object_store_id);
+ return ok;
+}
+
+template <typename DBOrTransaction>
+WARN_UNUSED_RESULT static bool GetMaxObjectStoreId(
+ DBOrTransaction* db,
+ const std::string& max_object_store_id_key,
+ int64* max_object_store_id) {
+ *max_object_store_id = -1;
+ bool found = false;
+ bool ok = GetInt(db, max_object_store_id_key, max_object_store_id, &found);
+ if (!ok)
+ return false;
+ if (!found)
+ *max_object_store_id = 0;
+
+ DCHECK_GE(*max_object_store_id, 0);
+ return true;
+}
+
+class DefaultLevelDBFactory : public LevelDBFactory {
+ public:
+ virtual leveldb::Status OpenLevelDB(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* db,
+ bool* is_disk_full) OVERRIDE {
+ return LevelDBDatabase::Open(file_name, comparator, db, is_disk_full);
+ }
+ virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE {
+ return LevelDBDatabase::Destroy(file_name);
+ }
+};
+
+IndexedDBBackingStore::IndexedDBBackingStore(
+ const std::string& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator)
+ : identifier_(identifier),
+ db_(db.Pass()),
+ comparator_(comparator.Pass()),
+ weak_factory_(this) {}
+
+IndexedDBBackingStore::~IndexedDBBackingStore() {
+ // db_'s destructor uses comparator_. The order of destruction is important.
+ db_.reset();
+ comparator_.reset();
+}
+
+IndexedDBBackingStore::RecordIdentifier::RecordIdentifier(
+ const std::string& primary_key,
+ int64 version)
+ : primary_key_(primary_key), version_(version) {
+ DCHECK(!primary_key.empty());
+}
+IndexedDBBackingStore::RecordIdentifier::RecordIdentifier()
+ : primary_key_(), version_(-1) {}
+IndexedDBBackingStore::RecordIdentifier::~RecordIdentifier() {}
+
+IndexedDBBackingStore::Cursor::CursorOptions::CursorOptions() {}
+IndexedDBBackingStore::Cursor::CursorOptions::~CursorOptions() {}
+
+enum IndexedDBLevelDBBackingStoreOpenResult {
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_DISK_FULL,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ORIGIN_TOO_LONG,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_NO_RECOVERY,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+};
+
+bool RecoveryCouldBeFruitful(leveldb::Status status) {
+ leveldb_env::MethodID method;
+ int error = -1;
+ leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError(
+ status.ToString().c_str(), &method, &error);
+ switch (result) {
+ case leveldb_env::NONE:
+ return true;
+ case leveldb_env::METHOD_AND_PFE: {
+ base::PlatformFileError pfe = static_cast<base::PlatformFileError>(error);
+ switch (pfe) {
+ case base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED:
+ case base::PLATFORM_FILE_ERROR_NO_MEMORY:
+ case base::PLATFORM_FILE_ERROR_NO_SPACE:
+ return false;
+ default:
+ return true;
+ }
+ }
+ case leveldb_env::METHOD_AND_ERRNO: {
+ switch (error) {
+ case EMFILE:
+ case ENOMEM:
+ case ENOSPC:
+ return false;
+ default:
+ return true;
+ }
+ }
+ default:
+ return true;
+ }
+ return true;
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
+ const std::string& origin_identifier,
+ const base::FilePath& path_base,
+ const std::string& file_identifier,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss) {
+ *data_loss = WebKit::WebIDBCallbacks::DataLossNone;
+ DefaultLevelDBFactory leveldb_factory;
+ return IndexedDBBackingStore::Open(origin_identifier,
+ path_base,
+ file_identifier,
+ data_loss,
+ &leveldb_factory);
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
+ const std::string& origin_identifier,
+ const base::FilePath& path_base,
+ const std::string& file_identifier,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss,
+ LevelDBFactory* leveldb_factory) {
+ IDB_TRACE("IndexedDBBackingStore::Open");
+ DCHECK(!path_base.empty());
+ *data_loss = WebKit::WebIDBCallbacks::DataLossNone;
+
+ scoped_ptr<LevelDBComparator> comparator(new Comparator());
+
+ if (!IsStringASCII(path_base.AsUTF8Unsafe())) {
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII);
+ }
+ if (!file_util::CreateDirectory(path_base)) {
+ LOG(ERROR) << "Unable to create IndexedDB database path "
+ << path_base.AsUTF8Unsafe();
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ base::FilePath identifier_path =
+ base::FilePath().AppendASCII(origin_identifier)
+ .AddExtension(FILE_PATH_LITERAL(".indexeddb.leveldb"));
+
+ int limit = file_util::GetMaximumPathComponentLength(path_base);
+ if (limit == -1) {
+ DLOG(WARNING) << "GetMaximumPathComponentLength returned -1";
+ // In limited testing, ChromeOS returns 143, other OSes 255.
+#if defined(OS_CHROMEOS)
+ limit = 143;
+#else
+ limit = 255;
+#endif
+ }
+ if (identifier_path.value().length() > static_cast<uint32_t>(limit)) {
+ DLOG(WARNING) << "Path component length ("
+ << identifier_path.value().length() << ") exceeds maximum ("
+ << limit << ") allowed by this filesystem.";
+ const int min = 140;
+ const int max = 300;
+ const int num_buckets = 12;
+ // TODO(dgrogan): Remove WebCore from these histogram names.
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "WebCore.IndexedDB.BackingStore.OverlyLargeOriginLength",
+ identifier_path.value().length(),
+ min,
+ max,
+ num_buckets);
+ // TODO(dgrogan): Translate the FactoryGet calls to
+ // UMA_HISTOGRAM_ENUMERATION. FactoryGet was the most direct translation
+ // from the WebCore code.
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_ORIGIN_TOO_LONG);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ base::FilePath file_path = path_base.Append(identifier_path);
+
+ bool is_disk_full = false;
+ scoped_ptr<LevelDBDatabase> db;
+ leveldb::Status status = leveldb_factory->OpenLevelDB(
+ file_path, comparator.get(), &db, &is_disk_full);
+
+ if (db) {
+ bool known = false;
+ bool ok = IsSchemaKnown(db.get(), &known);
+ if (!ok) {
+ LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as "
+ "failure to open";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA);
+ db.reset();
+ } else if (!known) {
+ LOG(ERROR) << "IndexedDB backing store had unknown schema, treating it "
+ "as failure to open";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA);
+ db.reset();
+ }
+ }
+
+ if (db) {
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_SUCCESS);
+ } else if (is_disk_full) {
+ LOG(ERROR) << "Unable to open backing store - disk is full.";
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_DISK_FULL);
+ return scoped_refptr<IndexedDBBackingStore>();
+ } else if (!RecoveryCouldBeFruitful(status)) {
+ LOG(ERROR) << "Unable to open backing store, not trying to recover - "
+ << status.ToString();
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_NO_RECOVERY);
+ return scoped_refptr<IndexedDBBackingStore>();
+ } else {
+ LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup";
+ *data_loss = WebKit::WebIDBCallbacks::DataLossTotal;
+ bool success = leveldb_factory->DestroyLevelDB(file_path);
+ if (!success) {
+ LOG(ERROR) << "IndexedDB backing store cleanup failed";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening";
+ leveldb_factory->OpenLevelDB(file_path, comparator.get(), &db, NULL);
+ if (!db) {
+ LOG(ERROR) << "IndexedDB backing store reopen after recovery failed";
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS);
+ }
+
+ if (!db) {
+ NOTREACHED();
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_ERR);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+
+ return Create(file_identifier, db.Pass(), comparator.Pass());
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
+ const std::string& file_identifier) {
+ DefaultLevelDBFactory leveldb_factory;
+ return IndexedDBBackingStore::OpenInMemory(file_identifier, &leveldb_factory);
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
+ const std::string& file_identifier,
+ LevelDBFactory* leveldb_factory) {
+ IDB_TRACE("IndexedDBBackingStore::OpenInMemory");
+
+ scoped_ptr<LevelDBComparator> comparator(new Comparator());
+ scoped_ptr<LevelDBDatabase> db =
+ LevelDBDatabase::OpenInMemory(comparator.get());
+ if (!db) {
+ LOG(ERROR) << "LevelDBDatabase::OpenInMemory failed.";
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_FAILED);
+ return scoped_refptr<IndexedDBBackingStore>();
+ }
+ base::Histogram::FactoryGet("WebCore.IndexedDB.BackingStore.OpenStatus",
+ 1,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX,
+ INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(INDEXED_DB_LEVEL_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS);
+
+ return Create(file_identifier, db.Pass(), comparator.Pass());
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create(
+ const std::string& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator) {
+ // TODO(jsbell): Handle comparator name changes.
+ scoped_refptr<IndexedDBBackingStore> backing_store(
+ new IndexedDBBackingStore(identifier, db.Pass(), comparator.Pass()));
+
+ if (!SetUpMetadata(backing_store->db_.get(), identifier))
+ return scoped_refptr<IndexedDBBackingStore>();
+
+ return backing_store;
+}
+
+std::vector<string16> IndexedDBBackingStore::GetDatabaseNames() {
+ std::vector<string16> found_names;
+ const std::string start_key =
+ DatabaseNameKey::EncodeMinKeyForOrigin(identifier_);
+ const std::string stop_key =
+ DatabaseNameKey::EncodeStopKeyForOrigin(identifier_);
+
+ DCHECK(found_names.empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ for (it->Seek(start_key);
+ it->IsValid() && CompareKeys(it->Key(), stop_key) < 0;
+ it->Next()) {
+ StringPiece slice(it->Key());
+ DatabaseNameKey database_name_key;
+ if (!DatabaseNameKey::Decode(&slice, &database_name_key)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_DATABASE_NAMES);
+ continue;
+ }
+ found_names.push_back(database_name_key.database_name());
+ }
+ return found_names;
+}
+
+bool IndexedDBBackingStore::GetIDBDatabaseMetaData(
+ const string16& name,
+ IndexedDBDatabaseMetadata* metadata,
+ bool* found) {
+ const std::string key = DatabaseNameKey::Encode(identifier_, name);
+ *found = false;
+
+ bool ok = GetInt(db_.get(), key, &metadata->id, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!*found)
+ return true;
+
+ ok = GetString(db_.get(),
+ DatabaseMetaDataKey::Encode(metadata->id,
+ DatabaseMetaDataKey::USER_VERSION),
+ &metadata->version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!*found) {
+ INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ ok = GetVarInt(db_.get(),
+ DatabaseMetaDataKey::Encode(
+ metadata->id, DatabaseMetaDataKey::USER_INT_VERSION),
+ &metadata->int_version,
+ found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+ if (!*found) {
+ INTERNAL_CONSISTENCY_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ if (metadata->int_version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION)
+ metadata->int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION;
+
+ ok = GetMaxObjectStoreId(
+ db_.get(), metadata->id, &metadata->max_object_store_id);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_IDBDATABASE_METADATA);
+ return false;
+ }
+
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool GetNewDatabaseId(LevelDBDatabase* db,
+ int64* new_id) {
+ scoped_refptr<LevelDBTransaction> transaction = new LevelDBTransaction(db);
+
+ *new_id = -1;
+ int64 max_database_id = -1;
+ bool found = false;
+ bool ok = GetInt(
+ transaction.get(), MaxDatabaseIdKey::Encode(), &max_database_id, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_NEW_DATABASE_ID);
+ return false;
+ }
+ if (!found)
+ max_database_id = 0;
+
+ DCHECK_GE(max_database_id, 0);
+
+ int64 database_id = max_database_id + 1;
+ PutInt(transaction.get(), MaxDatabaseIdKey::Encode(), database_id);
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(GET_NEW_DATABASE_ID);
+ return false;
+ }
+ *new_id = database_id;
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateIDBDatabaseMetaData(const string16& name,
+ const string16& version,
+ int64 int_version,
+ int64* row_id) {
+ bool ok = GetNewDatabaseId(db_.get(), row_id);
+ if (!ok)
+ return false;
+ DCHECK_GE(*row_id, 0);
+
+ if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION)
+ int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION;
+
+ scoped_refptr<LevelDBTransaction> transaction =
+ new LevelDBTransaction(db_.get());
+ PutInt(
+ transaction.get(), DatabaseNameKey::Encode(identifier_, name), *row_id);
+ PutString(
+ transaction.get(),
+ DatabaseMetaDataKey::Encode(*row_id, DatabaseMetaDataKey::USER_VERSION),
+ version);
+ PutVarInt(transaction.get(),
+ DatabaseMetaDataKey::Encode(*row_id,
+ DatabaseMetaDataKey::USER_INT_VERSION),
+ int_version);
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(CREATE_IDBDATABASE_METADATA);
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBBackingStore::UpdateIDBDatabaseIntVersion(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ int64 int_version) {
+ if (int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION)
+ int_version = IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION;
+ DCHECK_GE(int_version, 0) << "int_version was " << int_version;
+ PutVarInt(Transaction::LevelDBTransactionFrom(transaction),
+ DatabaseMetaDataKey::Encode(row_id,
+ DatabaseMetaDataKey::USER_INT_VERSION),
+ int_version);
+ return true;
+}
+
+bool IndexedDBBackingStore::UpdateIDBDatabaseMetaData(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ const string16& version) {
+ PutString(
+ Transaction::LevelDBTransactionFrom(transaction),
+ DatabaseMetaDataKey::Encode(row_id, DatabaseMetaDataKey::USER_VERSION),
+ version);
+ return true;
+}
+
+static void DeleteRange(LevelDBTransaction* transaction,
+ const std::string& begin,
+ const std::string& end) {
+ scoped_ptr<LevelDBIterator> it = transaction->CreateIterator();
+ for (it->Seek(begin); it->IsValid() && CompareKeys(it->Key(), end) < 0;
+ it->Next())
+ transaction->Remove(it->Key());
+}
+
+bool IndexedDBBackingStore::DeleteDatabase(const string16& name) {
+ IDB_TRACE("IndexedDBBackingStore::DeleteDatabase");
+ scoped_ptr<LevelDBWriteOnlyTransaction> transaction =
+ LevelDBWriteOnlyTransaction::Create(db_.get());
+
+ IndexedDBDatabaseMetadata metadata;
+ bool success = false;
+ bool ok = GetIDBDatabaseMetaData(name, &metadata, &success);
+ if (!ok)
+ return false;
+ if (!success)
+ return true;
+
+ const std::string start_key = DatabaseMetaDataKey::Encode(
+ metadata.id, DatabaseMetaDataKey::ORIGIN_NAME);
+ const std::string stop_key = DatabaseMetaDataKey::Encode(
+ metadata.id + 1, DatabaseMetaDataKey::ORIGIN_NAME);
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ for (it->Seek(start_key);
+ it->IsValid() && CompareKeys(it->Key(), stop_key) < 0;
+ it->Next())
+ transaction->Remove(it->Key());
+
+ const std::string key = DatabaseNameKey::Encode(identifier_, name);
+ transaction->Remove(key);
+
+ if (!transaction->Commit()) {
+ INTERNAL_WRITE_ERROR(DELETE_DATABASE);
+ return false;
+ }
+ return true;
+}
+
+static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it,
+ const std::string& stop_key,
+ int64 object_store_id,
+ int64 meta_data_type) {
+ if (!it->IsValid() || CompareKeys(it->Key(), stop_key) >= 0)
+ return false;
+
+ StringPiece slice(it->Key());
+ ObjectStoreMetaDataKey meta_data_key;
+ bool ok = ObjectStoreMetaDataKey::Decode(&slice, &meta_data_key);
+ DCHECK(ok);
+ if (meta_data_key.ObjectStoreId() != object_store_id)
+ return false;
+ if (meta_data_key.MetaDataType() != meta_data_type)
+ return false;
+ return true;
+}
+
+// TODO(jsbell): This should do some error handling rather than
+// plowing ahead when bad data is encountered.
+bool IndexedDBBackingStore::GetObjectStores(
+ int64 database_id,
+ IndexedDBDatabaseMetadata::ObjectStoreMap* object_stores) {
+ IDB_TRACE("IndexedDBBackingStore::GetObjectStores");
+ if (!KeyPrefix::IsValidDatabaseId(database_id))
+ return false;
+ const std::string start_key =
+ ObjectStoreMetaDataKey::Encode(database_id, 1, 0);
+ const std::string stop_key =
+ ObjectStoreMetaDataKey::EncodeMaxKey(database_id);
+
+ DCHECK(object_stores->empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ it->Seek(start_key);
+ while (it->IsValid() && CompareKeys(it->Key(), stop_key) < 0) {
+ StringPiece slice(it->Key());
+ ObjectStoreMetaDataKey meta_data_key;
+ bool ok = ObjectStoreMetaDataKey::Decode(&slice, &meta_data_key);
+ DCHECK(ok);
+ if (meta_data_key.MetaDataType() != ObjectStoreMetaDataKey::NAME) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ // Possible stale metadata, but don't fail the load.
+ it->Next();
+ continue;
+ }
+
+ int64 object_store_id = meta_data_key.ObjectStoreId();
+
+ // TODO(jsbell): Do this by direct key lookup rather than iteration, to
+ // simplify.
+ string16 object_store_name;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeString(&slice, &object_store_name) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ }
+
+ it->Next();
+ if (!CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_PATH)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ IndexedDBKeyPath key_path;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ }
+
+ it->Next();
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::AUTO_INCREMENT)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ bool auto_increment;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeBool(&slice, &auto_increment) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ }
+
+ it->Next(); // Is evicatble.
+ if (!CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::EVICTABLE)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+
+ it->Next(); // Last version.
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::LAST_VERSION)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+
+ it->Next(); // Maximum index id allocated.
+ if (!CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::MAX_INDEX_ID)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ int64 max_index_id;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeInt(&slice, &max_index_id) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ }
+
+ it->Next(); // [optional] has key path (is not null)
+ if (CheckObjectStoreAndMetaDataType(it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::HAS_KEY_PATH)) {
+ bool has_key_path;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeBool(&slice, &has_key_path))
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ }
+ // This check accounts for two layers of legacy coding:
+ // (1) Initially, has_key_path was added to distinguish null vs. string.
+ // (2) Later, null vs. string vs. array was stored in the key_path itself.
+ // So this check is only relevant for string-type key_paths.
+ if (!has_key_path &&
+ (key_path.type() == WebKit::WebIDBKeyPathTypeString &&
+ !key_path.string().empty())) {
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+ break;
+ }
+ if (!has_key_path)
+ key_path = IndexedDBKeyPath();
+ it->Next();
+ }
+
+ int64 key_generator_current_number = -1;
+ if (CheckObjectStoreAndMetaDataType(
+ it.get(),
+ stop_key,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER)) {
+ StringPiece slice(it->Value());
+ if (!DecodeInt(&slice, &key_generator_current_number) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_OBJECT_STORES);
+
+ // TODO(jsbell): Return key_generator_current_number, cache in
+ // object store, and write lazily to backing store. For now,
+ // just assert that if it was written it was valid.
+ DCHECK_GE(key_generator_current_number, kKeyGeneratorInitialNumber);
+ it->Next();
+ }
+
+ IndexedDBObjectStoreMetadata metadata(object_store_name,
+ object_store_id,
+ key_path,
+ auto_increment,
+ max_index_id);
+ if (!GetIndexes(database_id, object_store_id, &metadata.indexes))
+ return false;
+ (*object_stores)[object_store_id] = metadata;
+ }
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetMaxObjectStoreId(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ const std::string max_object_store_id_key = DatabaseMetaDataKey::Encode(
+ database_id, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID);
+ int64 max_object_store_id = -1;
+ bool ok = GetMaxObjectStoreId(
+ transaction, max_object_store_id_key, &max_object_store_id);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_MAX_OBJECT_STORE_ID);
+ return false;
+ }
+
+ if (object_store_id <= max_object_store_id) {
+ INTERNAL_CONSISTENCY_ERROR(SET_MAX_OBJECT_STORE_ID);
+ return false;
+ }
+ PutInt(transaction, max_object_store_id_key, object_store_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment) {
+ IDB_TRACE("IndexedDBBackingStore::CreateObjectStore");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ if (!SetMaxObjectStoreId(leveldb_transaction, database_id, object_store_id))
+ return false;
+
+ const std::string name_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::NAME);
+ const std::string key_path_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::KEY_PATH);
+ const std::string auto_increment_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::AUTO_INCREMENT);
+ const std::string evictable_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::EVICTABLE);
+ const std::string last_version_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION);
+ const std::string max_index_id_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID);
+ const std::string has_key_path_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::HAS_KEY_PATH);
+ const std::string key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+ const std::string names_key = ObjectStoreNamesKey::Encode(database_id, name);
+
+ PutString(leveldb_transaction, name_key, name);
+ PutIDBKeyPath(leveldb_transaction, key_path_key, key_path);
+ PutInt(leveldb_transaction, auto_increment_key, auto_increment);
+ PutInt(leveldb_transaction, evictable_key, false);
+ PutInt(leveldb_transaction, last_version_key, 1);
+ PutInt(leveldb_transaction, max_index_id_key, kMinimumIndexId);
+ PutBool(leveldb_transaction, has_key_path_key, !key_path.IsNull());
+ PutInt(leveldb_transaction,
+ key_generator_current_number_key,
+ kKeyGeneratorInitialNumber);
+ PutInt(leveldb_transaction, names_key, object_store_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ IDB_TRACE("IndexedDBBackingStore::DeleteObjectStore");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ string16 object_store_name;
+ bool found = false;
+ bool ok = GetString(
+ leveldb_transaction,
+ ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::NAME),
+ &object_store_name,
+ &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(DELETE_OBJECT_STORE);
+ return false;
+ }
+ if (!found) {
+ INTERNAL_CONSISTENCY_ERROR(DELETE_OBJECT_STORE);
+ return false;
+ }
+
+ DeleteRange(
+ leveldb_transaction,
+ ObjectStoreMetaDataKey::Encode(database_id, object_store_id, 0),
+ ObjectStoreMetaDataKey::EncodeMaxKey(database_id, object_store_id));
+
+ leveldb_transaction->Remove(
+ ObjectStoreNamesKey::Encode(database_id, object_store_name));
+
+ DeleteRange(leveldb_transaction,
+ IndexFreeListKey::Encode(database_id, object_store_id, 0),
+ IndexFreeListKey::EncodeMaxKey(database_id, object_store_id));
+ DeleteRange(leveldb_transaction,
+ IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0),
+ IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id));
+
+ return ClearObjectStore(transaction, database_id, object_store_id);
+}
+
+bool IndexedDBBackingStore::GetRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ std::string* record) {
+ IDB_TRACE("IndexedDBBackingStore::GetRecord");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::string leveldb_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+ std::string data;
+
+ record->clear();
+
+ bool found = false;
+ bool ok = leveldb_transaction->Get(leveldb_key, &data, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+ if (!found)
+ return true;
+ if (data.empty()) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+
+ int64 version;
+ StringPiece slice(data);
+ if (!DecodeVarInt(&slice, &version)) {
+ INTERNAL_READ_ERROR(GET_RECORD);
+ return false;
+ }
+
+ *record = slice.as_string();
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool GetNewVersionNumber(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64* new_version_number) {
+ const std::string last_version_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::LAST_VERSION);
+
+ *new_version_number = -1;
+ int64 last_version = -1;
+ bool found = false;
+ bool ok = GetInt(transaction, last_version_key, &last_version, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_NEW_VERSION_NUMBER);
+ return false;
+ }
+ if (!found)
+ last_version = 0;
+
+ DCHECK_GE(last_version, 0);
+
+ int64 version = last_version + 1;
+ PutInt(transaction, last_version_key, version);
+
+ // TODO(jsbell): Think about how we want to handle the overflow scenario.
+ DCHECK(version > last_version);
+
+ *new_version_number = version;
+ return true;
+}
+
+bool IndexedDBBackingStore::PutRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ const std::string& value,
+ RecordIdentifier* record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::PutRecord");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ DCHECK(key.IsValid());
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ int64 version = -1;
+ bool ok = GetNewVersionNumber(
+ leveldb_transaction, database_id, object_store_id, &version);
+ if (!ok)
+ return false;
+ DCHECK_GE(version, 0);
+ const std::string object_storedata_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+
+ std::string v;
+ EncodeVarInt(version, &v);
+ v.append(value);
+
+ leveldb_transaction->Put(object_storedata_key, &v);
+
+ const std::string exists_entry_key =
+ ExistsEntryKey::Encode(database_id, object_store_id, key);
+ std::string version_encoded;
+ EncodeInt(version, &version_encoded);
+ leveldb_transaction->Put(exists_entry_key, &version_encoded);
+
+ std::string key_encoded;
+ EncodeIDBKey(key, &key_encoded);
+ record_identifier->Reset(key_encoded, version);
+ return true;
+}
+
+bool IndexedDBBackingStore::ClearObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ IDB_TRACE("IndexedDBBackingStore::ClearObjectStore");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::string start_key =
+ KeyPrefix(database_id, object_store_id).Encode();
+ const std::string stop_key =
+ KeyPrefix(database_id, object_store_id + 1).Encode();
+
+ DeleteRange(leveldb_transaction, start_key, stop_key);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const RecordIdentifier& record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::DeleteRecord");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::string object_store_data_key = ObjectStoreDataKey::Encode(
+ database_id, object_store_id, record_identifier.primary_key());
+ leveldb_transaction->Remove(object_store_data_key);
+
+ const std::string exists_entry_key = ExistsEntryKey::Encode(
+ database_id, object_store_id, record_identifier.primary_key());
+ leveldb_transaction->Remove(exists_entry_key);
+ return true;
+}
+
+bool IndexedDBBackingStore::GetKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64* key_generator_current_number) {
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::string key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+
+ *key_generator_current_number = -1;
+ std::string data;
+
+ bool found = false;
+ bool ok =
+ leveldb_transaction->Get(key_generator_current_number_key, &data, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER);
+ return false;
+ }
+ if (found && !data.empty()) {
+ StringPiece slice(data);
+ if (!DecodeInt(&slice, key_generator_current_number) || !slice.empty()) {
+ INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER);
+ return false;
+ }
+ return true;
+ }
+
+ // Previously, the key generator state was not stored explicitly
+ // but derived from the maximum numeric key present in existing
+ // data. This violates the spec as the data may be cleared but the
+ // key generator state must be preserved.
+ // TODO(jsbell): Fix this for all stores on database open?
+ const std::string start_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey());
+ const std::string stop_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey());
+
+ scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator();
+ int64 max_numeric_key = 0;
+
+ for (it->Seek(start_key);
+ it->IsValid() && CompareKeys(it->Key(), stop_key) < 0;
+ it->Next()) {
+ StringPiece slice(it->Key());
+ ObjectStoreDataKey data_key;
+ if (!ObjectStoreDataKey::Decode(&slice, &data_key)) {
+ INTERNAL_READ_ERROR(GET_KEY_GENERATOR_CURRENT_NUMBER);
+ return false;
+ }
+ scoped_ptr<IndexedDBKey> user_key = data_key.user_key();
+ if (user_key->type() == WebKit::WebIDBKeyTypeNumber) {
+ int64 n = static_cast<int64>(user_key->number());
+ if (n > max_numeric_key)
+ max_numeric_key = n;
+ }
+ }
+
+ *key_generator_current_number = max_numeric_key + 1;
+ return true;
+}
+
+bool IndexedDBBackingStore::MaybeUpdateKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 new_number,
+ bool check_current) {
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ if (check_current) {
+ int64 current_number;
+ bool ok = GetKeyGeneratorCurrentNumber(
+ transaction, database_id, object_store_id, &current_number);
+ if (!ok)
+ return false;
+ if (new_number <= current_number)
+ return true;
+ }
+
+ const std::string key_generator_current_number_key =
+ ObjectStoreMetaDataKey::Encode(
+ database_id,
+ object_store_id,
+ ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER);
+ PutInt(leveldb_transaction, key_generator_current_number_key, new_number);
+ return true;
+}
+
+bool IndexedDBBackingStore::KeyExistsInObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ RecordIdentifier* found_record_identifier,
+ bool* found) {
+ IDB_TRACE("IndexedDBBackingStore::KeyExistsInObjectStore");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ *found = false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::string leveldb_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, key);
+ std::string data;
+
+ bool ok = leveldb_transaction->Get(leveldb_key, &data, found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE);
+ return false;
+ }
+ if (!*found)
+ return true;
+ if (!data.size()) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_OBJECT_STORE);
+ return false;
+ }
+
+ int64 version;
+ StringPiece slice(data);
+ if (!DecodeVarInt(&slice, &version))
+ return false;
+
+ std::string encoded_key;
+ EncodeIDBKey(key, &encoded_key);
+ found_record_identifier->Reset(encoded_key, version);
+ return true;
+}
+
+static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it,
+ const std::string& stop_key,
+ int64 index_id,
+ unsigned char meta_data_type) {
+ if (!it->IsValid() || CompareKeys(it->Key(), stop_key) >= 0)
+ return false;
+
+ StringPiece slice(it->Key());
+ IndexMetaDataKey meta_data_key;
+ bool ok = IndexMetaDataKey::Decode(&slice, &meta_data_key);
+ DCHECK(ok);
+ if (meta_data_key.IndexId() != index_id)
+ return false;
+ if (meta_data_key.meta_data_type() != meta_data_type)
+ return false;
+ return true;
+}
+
+// TODO(jsbell): This should do some error handling rather than plowing ahead
+// when bad data is encountered.
+bool IndexedDBBackingStore::GetIndexes(
+ int64 database_id,
+ int64 object_store_id,
+ IndexedDBObjectStoreMetadata::IndexMap* indexes) {
+ IDB_TRACE("IndexedDBBackingStore::GetIndexes");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id))
+ return false;
+ const std::string start_key =
+ IndexMetaDataKey::Encode(database_id, object_store_id, 0, 0);
+ const std::string stop_key =
+ IndexMetaDataKey::Encode(database_id, object_store_id + 1, 0, 0);
+
+ DCHECK(indexes->empty());
+
+ scoped_ptr<LevelDBIterator> it = db_->CreateIterator();
+ it->Seek(start_key);
+ while (it->IsValid() && CompareKeys(it->Key(), stop_key) < 0) {
+ StringPiece slice(it->Key());
+ IndexMetaDataKey meta_data_key;
+ bool ok = IndexMetaDataKey::Decode(&slice, &meta_data_key);
+ DCHECK(ok);
+ if (meta_data_key.meta_data_type() != IndexMetaDataKey::NAME) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ // Possible stale metadata due to http://webkit.org/b/85557 but don't fail
+ // the load.
+ it->Next();
+ continue;
+ }
+
+ // TODO(jsbell): Do this by direct key lookup rather than iteration, to
+ // simplify.
+ int64 index_id = meta_data_key.IndexId();
+ string16 index_name;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeString(&slice, &index_name) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ }
+
+ it->Next(); // unique flag
+ if (!CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::UNIQUE)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ break;
+ }
+ bool index_unique;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeBool(&slice, &index_unique) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ }
+
+ it->Next(); // key_path
+ if (!CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::KEY_PATH)) {
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ break;
+ }
+ IndexedDBKeyPath key_path;
+ {
+ StringPiece slice(it->Value());
+ if (!DecodeIDBKeyPath(&slice, &key_path) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+ }
+
+ it->Next(); // [optional] multi_entry flag
+ bool index_multi_entry = false;
+ if (CheckIndexAndMetaDataKey(
+ it.get(), stop_key, index_id, IndexMetaDataKey::MULTI_ENTRY)) {
+ StringPiece slice(it->Value());
+ if (!DecodeBool(&slice, &index_multi_entry) || !slice.empty())
+ INTERNAL_CONSISTENCY_ERROR(GET_INDEXES);
+
+ it->Next();
+ }
+
+ (*indexes)[index_id] = IndexedDBIndexMetadata(
+ index_name, index_id, key_path, index_unique, index_multi_entry);
+ }
+ return true;
+}
+
+WARN_UNUSED_RESULT static bool SetMaxIndexId(LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ int64 max_index_id = -1;
+ const std::string max_index_id_key = ObjectStoreMetaDataKey::Encode(
+ database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID);
+ bool found = false;
+ bool ok = GetInt(transaction, max_index_id_key, &max_index_id, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(SET_MAX_INDEX_ID);
+ return false;
+ }
+ if (!found)
+ max_index_id = kMinimumIndexId;
+
+ if (index_id <= max_index_id) {
+ INTERNAL_CONSISTENCY_ERROR(SET_MAX_INDEX_ID);
+ return false;
+ }
+
+ PutInt(transaction, max_index_id_key, index_id);
+ return true;
+}
+
+bool IndexedDBBackingStore::CreateIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool is_unique,
+ bool is_multi_entry) {
+ IDB_TRACE("IndexedDBBackingStore::CreateIndex");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ if (!SetMaxIndexId(
+ leveldb_transaction, database_id, object_store_id, index_id))
+ return false;
+
+ const std::string name_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::NAME);
+ const std::string unique_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::UNIQUE);
+ const std::string key_path_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::KEY_PATH);
+ const std::string multi_entry_key = IndexMetaDataKey::Encode(
+ database_id, object_store_id, index_id, IndexMetaDataKey::MULTI_ENTRY);
+
+ PutString(leveldb_transaction, name_key, name);
+ PutBool(leveldb_transaction, unique_key, is_unique);
+ PutIDBKeyPath(leveldb_transaction, key_path_key, key_path);
+ PutBool(leveldb_transaction, multi_entry_key, is_multi_entry);
+ return true;
+}
+
+bool IndexedDBBackingStore::DeleteIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ IDB_TRACE("IndexedDBBackingStore::DeleteIndex");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ const std::string index_meta_data_start =
+ IndexMetaDataKey::Encode(database_id, object_store_id, index_id, 0);
+ const std::string index_meta_data_end =
+ IndexMetaDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ DeleteRange(leveldb_transaction, index_meta_data_start, index_meta_data_end);
+
+ const std::string index_data_start =
+ IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id);
+ const std::string index_data_end =
+ IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ DeleteRange(leveldb_transaction, index_data_start, index_data_end);
+ return true;
+}
+
+bool IndexedDBBackingStore::PutIndexDataForRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ const RecordIdentifier& record_identifier) {
+ IDB_TRACE("IndexedDBBackingStore::PutIndexDataForRecord");
+ DCHECK(key.IsValid());
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+
+ std::string encoded_key;
+ EncodeIDBKey(key, &encoded_key);
+
+ const std::string index_data_key =
+ IndexDataKey::Encode(database_id,
+ object_store_id,
+ index_id,
+ encoded_key,
+ record_identifier.primary_key(),
+ 0);
+
+ std::string data;
+ EncodeVarInt(record_identifier.version(), &data);
+ data.append(record_identifier.primary_key());
+
+ leveldb_transaction->Put(index_data_key, &data);
+ return true;
+}
+
+static bool FindGreatestKeyLessThanOrEqual(LevelDBTransaction* transaction,
+ const std::string& target,
+ std::string* found_key) {
+ scoped_ptr<LevelDBIterator> it = transaction->CreateIterator();
+ it->Seek(target);
+
+ if (!it->IsValid()) {
+ it->SeekToLast();
+ if (!it->IsValid())
+ return false;
+ }
+
+ while (CompareIndexKeys(it->Key(), target) > 0) {
+ it->Prev();
+ if (!it->IsValid())
+ return false;
+ }
+
+ do {
+ *found_key = it->Key().as_string();
+
+ // There can be several index keys that compare equal. We want the last one.
+ it->Next();
+ } while (it->IsValid() && !CompareIndexKeys(it->Key(), target));
+
+ return true;
+}
+
+static bool VersionExists(LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 version,
+ const std::string& encoded_primary_key,
+ bool* exists) {
+ const std::string key =
+ ExistsEntryKey::Encode(database_id, object_store_id, encoded_primary_key);
+ std::string data;
+
+ bool ok = transaction->Get(key, &data, exists);
+ if (!ok) {
+ INTERNAL_READ_ERROR(VERSION_EXISTS);
+ return false;
+ }
+ if (!*exists)
+ return true;
+
+ StringPiece slice(data);
+ int64 decoded;
+ if (!DecodeInt(&slice, &decoded) || !slice.empty())
+ return false;
+ *exists = (decoded == version);
+ return true;
+}
+
+bool IndexedDBBackingStore::FindKeyInIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ std::string* found_encoded_primary_key,
+ bool* found) {
+ IDB_TRACE("IndexedDBBackingStore::FindKeyInIndex");
+ DCHECK(KeyPrefix::ValidIds(database_id, object_store_id, index_id));
+
+ DCHECK(found_encoded_primary_key->empty());
+ *found = false;
+
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ const std::string leveldb_key =
+ IndexDataKey::Encode(database_id, object_store_id, index_id, key);
+ scoped_ptr<LevelDBIterator> it = leveldb_transaction->CreateIterator();
+ it->Seek(leveldb_key);
+
+ for (;;) {
+ if (!it->IsValid())
+ return true;
+ if (CompareIndexKeys(it->Key(), leveldb_key) > 0)
+ return true;
+
+ StringPiece slice(it->Value());
+
+ int64 version;
+ if (!DecodeVarInt(&slice, &version)) {
+ INTERNAL_READ_ERROR(FIND_KEY_IN_INDEX);
+ return false;
+ }
+ *found_encoded_primary_key = slice.as_string();
+
+ bool exists = false;
+ bool ok = VersionExists(leveldb_transaction,
+ database_id,
+ object_store_id,
+ version,
+ *found_encoded_primary_key,
+ &exists);
+ if (!ok)
+ return false;
+ if (!exists) {
+ // Delete stale index data entry and continue.
+ leveldb_transaction->Remove(it->Key());
+ it->Next();
+ continue;
+ }
+ *found = true;
+ return true;
+ }
+}
+
+bool IndexedDBBackingStore::GetPrimaryKeyViaIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ scoped_ptr<IndexedDBKey>* primary_key) {
+ IDB_TRACE("IndexedDBBackingStore::GetPrimaryKeyViaIndex");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ bool found = false;
+ std::string found_encoded_primary_key;
+ bool ok = FindKeyInIndex(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ key,
+ &found_encoded_primary_key,
+ &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX);
+ return false;
+ }
+ if (!found)
+ return true;
+ if (!found_encoded_primary_key.size()) {
+ INTERNAL_READ_ERROR(GET_PRIMARY_KEY_VIA_INDEX);
+ return false;
+ }
+
+ StringPiece slice(found_encoded_primary_key);
+ return DecodeIDBKey(&slice, primary_key) && slice.empty();
+}
+
+bool IndexedDBBackingStore::KeyExistsInIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& index_key,
+ scoped_ptr<IndexedDBKey>* found_primary_key,
+ bool* exists) {
+ IDB_TRACE("IndexedDBBackingStore::KeyExistsInIndex");
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ *exists = false;
+ std::string found_encoded_primary_key;
+ bool ok = FindKeyInIndex(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ index_key,
+ &found_encoded_primary_key,
+ exists);
+ if (!ok) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX);
+ return false;
+ }
+ if (!*exists)
+ return true;
+ if (found_encoded_primary_key.empty()) {
+ INTERNAL_READ_ERROR(KEY_EXISTS_IN_INDEX);
+ return false;
+ }
+
+ StringPiece slice(found_encoded_primary_key);
+ return DecodeIDBKey(&slice, found_primary_key) && slice.empty();
+}
+
+IndexedDBBackingStore::Cursor::Cursor(
+ const IndexedDBBackingStore::Cursor* other)
+ : transaction_(other->transaction_),
+ cursor_options_(other->cursor_options_),
+ current_key_(new IndexedDBKey(*other->current_key_)) {
+ if (other->iterator_) {
+ iterator_ = transaction_->CreateIterator();
+
+ if (other->iterator_->IsValid()) {
+ iterator_->Seek(other->iterator_->Key());
+ DCHECK(iterator_->IsValid());
+ }
+ }
+}
+
+IndexedDBBackingStore::Cursor::Cursor(LevelDBTransaction* transaction,
+ const CursorOptions& cursor_options)
+ : transaction_(transaction), cursor_options_(cursor_options) {}
+IndexedDBBackingStore::Cursor::~Cursor() {}
+
+bool IndexedDBBackingStore::Cursor::FirstSeek() {
+ iterator_ = transaction_->CreateIterator();
+ if (cursor_options_.forward)
+ iterator_->Seek(cursor_options_.low_key);
+ else
+ iterator_->Seek(cursor_options_.high_key);
+
+ return Continue(0, READY);
+}
+
+bool IndexedDBBackingStore::Cursor::Advance(uint32 count) {
+ while (count--) {
+ if (!Continue())
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBBackingStore::Cursor::Continue(const IndexedDBKey* key,
+ IteratorState next_state) {
+ // TODO(alecflett): avoid a copy here?
+ IndexedDBKey previous_key = current_key_ ? *current_key_ : IndexedDBKey();
+
+ bool first_iteration = true;
+
+ // When iterating with PrevNoDuplicate, spec requires that the
+ // value we yield for each key is the first duplicate in forwards
+ // order.
+ IndexedDBKey last_duplicate_key;
+
+ bool forward = cursor_options_.forward;
+
+ for (;;) {
+ if (next_state == SEEK) {
+ // TODO(jsbell): Optimize seeking for reverse cursors as well.
+ if (first_iteration && key && key->IsValid() && forward) {
+ iterator_->Seek(EncodeKey(*key));
+ first_iteration = false;
+ } else if (forward) {
+ iterator_->Next();
+ } else {
+ iterator_->Prev();
+ }
+ } else {
+ next_state = SEEK; // for subsequent iterations
+ }
+
+ if (!iterator_->IsValid()) {
+ if (!forward && last_duplicate_key.IsValid()) {
+ // We need to walk forward because we hit the end of
+ // the data.
+ forward = true;
+ continue;
+ }
+
+ return false;
+ }
+
+ if (IsPastBounds()) {
+ if (!forward && last_duplicate_key.IsValid()) {
+ // We need to walk forward because now we're beyond the
+ // bounds defined by the cursor.
+ forward = true;
+ continue;
+ }
+
+ return false;
+ }
+
+ if (!HaveEnteredRange())
+ continue;
+
+ // The row may not load because there's a stale entry in the
+ // index. This is not fatal.
+ if (!LoadCurrentRow())
+ continue;
+
+ if (key && key->IsValid()) {
+ if (forward) {
+ if (current_key_->IsLessThan(*key))
+ continue;
+ } else {
+ if (key->IsLessThan(*current_key_))
+ continue;
+ }
+ }
+
+ if (cursor_options_.unique) {
+ if (previous_key.IsValid() && current_key_->IsEqual(previous_key)) {
+ // We should never be able to walk forward all the way
+ // to the previous key.
+ DCHECK(!last_duplicate_key.IsValid());
+ continue;
+ }
+
+ if (!forward) {
+ if (!last_duplicate_key.IsValid()) {
+ last_duplicate_key = *current_key_;
+ continue;
+ }
+
+ // We need to walk forward because we hit the boundary
+ // between key ranges.
+ if (!last_duplicate_key.IsEqual(*current_key_)) {
+ forward = true;
+ continue;
+ }
+
+ continue;
+ }
+ }
+ break;
+ }
+
+ DCHECK(!last_duplicate_key.IsValid() ||
+ (forward && last_duplicate_key.IsEqual(*current_key_)));
+ return true;
+}
+
+bool IndexedDBBackingStore::Cursor::HaveEnteredRange() const {
+ if (cursor_options_.forward) {
+ int compare = CompareIndexKeys(iterator_->Key(), cursor_options_.low_key);
+ if (cursor_options_.low_open) {
+ return compare > 0;
+ }
+ return compare >= 0;
+ }
+ int compare = CompareIndexKeys(iterator_->Key(), cursor_options_.high_key);
+ if (cursor_options_.high_open) {
+ return compare < 0;
+ }
+ return compare <= 0;
+}
+
+bool IndexedDBBackingStore::Cursor::IsPastBounds() const {
+ if (cursor_options_.forward) {
+ int compare = CompareIndexKeys(iterator_->Key(), cursor_options_.high_key);
+ if (cursor_options_.high_open) {
+ return compare >= 0;
+ }
+ return compare > 0;
+ }
+ int compare = CompareIndexKeys(iterator_->Key(), cursor_options_.low_key);
+ if (cursor_options_.low_open) {
+ return compare <= 0;
+ }
+ return compare < 0;
+}
+
+const IndexedDBKey& IndexedDBBackingStore::Cursor::primary_key() const {
+ return *current_key_;
+}
+
+const IndexedDBBackingStore::RecordIdentifier&
+IndexedDBBackingStore::Cursor::record_identifier() const {
+ return record_identifier_;
+}
+
+class ObjectStoreKeyCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ ObjectStoreKeyCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE {
+ return new ObjectStoreKeyCursorImpl(this);
+ }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::string* Value() OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::string EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return ObjectStoreDataKey::Encode(
+ cursor_options_.database_id, cursor_options_.object_store_id, key);
+ }
+
+ private:
+ explicit ObjectStoreKeyCursorImpl(const ObjectStoreKeyCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other) {}
+};
+
+bool ObjectStoreKeyCursorImpl::LoadCurrentRow() {
+ StringPiece slice(iterator_->Key());
+ ObjectStoreDataKey object_store_data_key;
+ if (!ObjectStoreDataKey::Decode(&slice, &object_store_data_key)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = object_store_data_key.user_key();
+
+ int64 version;
+ slice = StringPiece(iterator_->Value());
+ if (!DecodeVarInt(&slice, &version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ // TODO(jsbell): This re-encodes what was just decoded; try and optimize.
+ std::string encoded_key;
+ EncodeIDBKey(*current_key_, &encoded_key);
+ record_identifier_.Reset(encoded_key, version);
+
+ return true;
+}
+
+class ObjectStoreCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ ObjectStoreCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new ObjectStoreCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::string* Value() OVERRIDE { return &current_value_; }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::string EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return ObjectStoreDataKey::Encode(
+ cursor_options_.database_id, cursor_options_.object_store_id, key);
+ }
+
+ private:
+ explicit ObjectStoreCursorImpl(const ObjectStoreCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ current_value_(other->current_value_) {}
+
+ std::string current_value_;
+};
+
+bool ObjectStoreCursorImpl::LoadCurrentRow() {
+ StringPiece slice(iterator_->Key());
+ ObjectStoreDataKey object_store_data_key;
+ if (!ObjectStoreDataKey::Decode(&slice, &object_store_data_key)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = object_store_data_key.user_key();
+
+ int64 version;
+ slice = StringPiece(iterator_->Value());
+ if (!DecodeVarInt(&slice, &version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ // TODO(jsbell): This re-encodes what was just decoded; try and optimize.
+ std::string encoded_key;
+ EncodeIDBKey(*current_key_, &encoded_key);
+ record_identifier_.Reset(encoded_key, version);
+
+ current_value_ = slice.as_string();
+ return true;
+}
+
+class IndexKeyCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ IndexKeyCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new IndexKeyCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::string* Value() OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual const IndexedDBKey& primary_key() const OVERRIDE {
+ return *primary_key_;
+ }
+ virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier()
+ const {
+ NOTREACHED();
+ return record_identifier_;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::string EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return IndexDataKey::Encode(cursor_options_.database_id,
+ cursor_options_.object_store_id,
+ cursor_options_.index_id,
+ key);
+ }
+
+ private:
+ explicit IndexKeyCursorImpl(const IndexKeyCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ primary_key_(new IndexedDBKey(*other->primary_key_)) {}
+
+ scoped_ptr<IndexedDBKey> primary_key_;
+};
+
+bool IndexKeyCursorImpl::LoadCurrentRow() {
+ StringPiece slice(iterator_->Key());
+ IndexDataKey index_data_key;
+ if (!IndexDataKey::Decode(&slice, &index_data_key)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = index_data_key.user_key();
+ DCHECK(current_key_);
+
+ slice = StringPiece(iterator_->Value());
+ int64 index_data_version;
+ if (!DecodeVarInt(&slice, &index_data_version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ if (!DecodeIDBKey(&slice, &primary_key_) || !slice.empty()) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ std::string primary_leveldb_key =
+ ObjectStoreDataKey::Encode(index_data_key.DatabaseId(),
+ index_data_key.ObjectStoreId(),
+ *primary_key_);
+
+ std::string result;
+ bool found = false;
+ bool ok = transaction_->Get(primary_leveldb_key, &result, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ if (!found) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+ if (!result.size()) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ int64 object_store_data_version;
+ slice = StringPiece(result);
+ if (!DecodeVarInt(&slice, &object_store_data_version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ if (object_store_data_version != index_data_version) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+
+ return true;
+}
+
+class IndexCursorImpl : public IndexedDBBackingStore::Cursor {
+ public:
+ IndexCursorImpl(
+ LevelDBTransaction* transaction,
+ const IndexedDBBackingStore::Cursor::CursorOptions& cursor_options)
+ : IndexedDBBackingStore::Cursor(transaction, cursor_options) {}
+
+ virtual Cursor* Clone() OVERRIDE { return new IndexCursorImpl(this); }
+
+ // IndexedDBBackingStore::Cursor
+ virtual std::string* Value() OVERRIDE { return &current_value_; }
+ virtual const IndexedDBKey& primary_key() const OVERRIDE {
+ return *primary_key_;
+ }
+ virtual const IndexedDBBackingStore::RecordIdentifier& RecordIdentifier()
+ const {
+ NOTREACHED();
+ return record_identifier_;
+ }
+ virtual bool LoadCurrentRow() OVERRIDE;
+
+ protected:
+ virtual std::string EncodeKey(const IndexedDBKey& key) OVERRIDE {
+ return IndexDataKey::Encode(cursor_options_.database_id,
+ cursor_options_.object_store_id,
+ cursor_options_.index_id,
+ key);
+ }
+
+ private:
+ explicit IndexCursorImpl(const IndexCursorImpl* other)
+ : IndexedDBBackingStore::Cursor(other),
+ primary_key_(new IndexedDBKey(*other->primary_key_)),
+ current_value_(other->current_value_),
+ primary_leveldb_key_(other->primary_leveldb_key_) {}
+
+ scoped_ptr<IndexedDBKey> primary_key_;
+ std::string current_value_;
+ std::string primary_leveldb_key_;
+};
+
+bool IndexCursorImpl::LoadCurrentRow() {
+ StringPiece slice(iterator_->Key());
+ IndexDataKey index_data_key;
+ if (!IndexDataKey::Decode(&slice, &index_data_key)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ current_key_ = index_data_key.user_key();
+ DCHECK(current_key_);
+
+ slice = StringPiece(iterator_->Value());
+ int64 index_data_version;
+ if (!DecodeVarInt(&slice, &index_data_version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ if (!DecodeIDBKey(&slice, &primary_key_)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ primary_leveldb_key_ =
+ ObjectStoreDataKey::Encode(index_data_key.DatabaseId(),
+ index_data_key.ObjectStoreId(),
+ *primary_key_);
+
+ std::string result;
+ bool found = false;
+ bool ok = transaction_->Get(primary_leveldb_key_, &result, &found);
+ if (!ok) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+ if (!found) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+ if (!result.size()) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ int64 object_store_data_version;
+ slice = StringPiece(result);
+ if (!DecodeVarInt(&slice, &object_store_data_version)) {
+ INTERNAL_READ_ERROR(LOAD_CURRENT_ROW);
+ return false;
+ }
+
+ if (object_store_data_version != index_data_version) {
+ transaction_->Remove(iterator_->Key());
+ return false;
+ }
+
+ current_value_ = slice.as_string();
+ return true;
+}
+
+bool ObjectStoreCursorOptions(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction,
+ IndexedDBBackingStore::Cursor::CursorOptions* cursor_options) {
+ cursor_options->database_id = database_id;
+ cursor_options->object_store_id = object_store_id;
+
+ bool lower_bound = range.lower().IsValid();
+ bool upper_bound = range.upper().IsValid();
+ cursor_options->forward =
+ (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_NEXT);
+ cursor_options->unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_PREV_NO_DUPLICATE);
+
+ if (!lower_bound) {
+ cursor_options->low_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MinIDBKey());
+ cursor_options->low_open = true; // Not included.
+ } else {
+ cursor_options->low_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, range.lower());
+ cursor_options->low_open = range.lowerOpen();
+ }
+
+ if (!upper_bound) {
+ cursor_options->high_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, MaxIDBKey());
+
+ if (cursor_options->forward) {
+ cursor_options->high_open = true; // Not included.
+ } else {
+ // We need a key that exists.
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options->high_key, &cursor_options->high_key))
+ return false;
+ cursor_options->high_open = false;
+ }
+ } else {
+ cursor_options->high_key =
+ ObjectStoreDataKey::Encode(database_id, object_store_id, range.upper());
+ cursor_options->high_open = range.upperOpen();
+
+ if (!cursor_options->forward) {
+ // For reverse cursors, we need a key that exists.
+ std::string found_high_key;
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options->high_key, &found_high_key))
+ return false;
+
+ // If the target key should not be included, but we end up with a smaller
+ // key, we should include that.
+ if (cursor_options->high_open &&
+ CompareIndexKeys(found_high_key, cursor_options->high_key) < 0)
+ cursor_options->high_open = false;
+
+ cursor_options->high_key = found_high_key;
+ }
+ }
+
+ return true;
+}
+
+bool IndexCursorOptions(
+ LevelDBTransaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction,
+ IndexedDBBackingStore::Cursor::CursorOptions* cursor_options) {
+ DCHECK(transaction);
+ if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
+ return false;
+
+ cursor_options->database_id = database_id;
+ cursor_options->object_store_id = object_store_id;
+ cursor_options->index_id = index_id;
+
+ bool lower_bound = range.lower().IsValid();
+ bool upper_bound = range.upper().IsValid();
+ cursor_options->forward =
+ (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_NEXT);
+ cursor_options->unique = (direction == indexed_db::CURSOR_NEXT_NO_DUPLICATE ||
+ direction == indexed_db::CURSOR_PREV_NO_DUPLICATE);
+
+ if (!lower_bound) {
+ cursor_options->low_key =
+ IndexDataKey::EncodeMinKey(database_id, object_store_id, index_id);
+ cursor_options->low_open = false; // Included.
+ } else {
+ cursor_options->low_key = IndexDataKey::Encode(
+ database_id, object_store_id, index_id, range.lower());
+ cursor_options->low_open = range.lowerOpen();
+ }
+
+ if (!upper_bound) {
+ cursor_options->high_key =
+ IndexDataKey::EncodeMaxKey(database_id, object_store_id, index_id);
+ cursor_options->high_open = false; // Included.
+
+ if (!cursor_options->forward) { // We need a key that exists.
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options->high_key, &cursor_options->high_key))
+ return false;
+ cursor_options->high_open = false;
+ }
+ } else {
+ cursor_options->high_key = IndexDataKey::Encode(
+ database_id, object_store_id, index_id, range.upper());
+ cursor_options->high_open = range.upperOpen();
+
+ std::string found_high_key;
+ // Seek to the *last* key in the set of non-unique keys
+ if (!FindGreatestKeyLessThanOrEqual(
+ transaction, cursor_options->high_key, &found_high_key))
+ return false;
+
+ // If the target key should not be included, but we end up with a smaller
+ // key, we should include that.
+ if (cursor_options->high_open &&
+ CompareIndexKeys(found_high_key, cursor_options->high_key) < 0)
+ cursor_options->high_open = false;
+
+ cursor_options->high_key = found_high_key;
+ }
+
+ return true;
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenObjectStoreCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::OpenObjectStoreCursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!ObjectStoreCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ range,
+ direction,
+ &cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<ObjectStoreCursorImpl> cursor(
+ new ObjectStoreCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenObjectStoreKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::OpenObjectStoreKeyCursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!ObjectStoreCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ range,
+ direction,
+ &cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<ObjectStoreKeyCursorImpl> cursor(
+ new ObjectStoreKeyCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenIndexKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::OpenIndexKeyCursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!IndexCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ range,
+ direction,
+ &cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<IndexKeyCursorImpl> cursor(
+ new IndexKeyCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBBackingStore::OpenIndexCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& range,
+ indexed_db::CursorDirection direction) {
+ IDB_TRACE("IndexedDBBackingStore::OpenIndexCursor");
+ LevelDBTransaction* leveldb_transaction =
+ IndexedDBBackingStore::Transaction::LevelDBTransactionFrom(transaction);
+ IndexedDBBackingStore::Cursor::CursorOptions cursor_options;
+ if (!IndexCursorOptions(leveldb_transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ range,
+ direction,
+ &cursor_options))
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+ scoped_ptr<IndexCursorImpl> cursor(
+ new IndexCursorImpl(leveldb_transaction, cursor_options));
+ if (!cursor->FirstSeek())
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+
+ return cursor.PassAs<IndexedDBBackingStore::Cursor>();
+}
+
+IndexedDBBackingStore::Transaction::Transaction(
+ IndexedDBBackingStore* backing_store)
+ : backing_store_(backing_store) {}
+
+IndexedDBBackingStore::Transaction::~Transaction() {}
+
+void IndexedDBBackingStore::Transaction::Begin() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::Begin");
+ DCHECK(!transaction_.get());
+ transaction_ = new LevelDBTransaction(backing_store_->db_.get());
+}
+
+bool IndexedDBBackingStore::Transaction::Commit() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::Commit");
+ DCHECK(transaction_.get());
+ bool result = transaction_->Commit();
+ transaction_ = NULL;
+ if (!result)
+ INTERNAL_WRITE_ERROR(TRANSACTION_COMMIT_METHOD);
+ return result;
+}
+
+void IndexedDBBackingStore::Transaction::Rollback() {
+ IDB_TRACE("IndexedDBBackingStore::Transaction::Rollback");
+ DCHECK(transaction_.get());
+ transaction_->Rollback();
+ transaction_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_backing_store.h b/chromium/content/browser/indexed_db/indexed_db_backing_store.h
new file mode 100644
index 00000000000..4498b0875a4
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_backing_store.h
@@ -0,0 +1,328 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/indexed_db/indexed_db.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+#include "content/common/content_export.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+#include "third_party/WebKit/public/platform/WebIDBCallbacks.h"
+
+namespace content {
+
+class LevelDBComparator;
+class LevelDBDatabase;
+
+class LevelDBFactory {
+ public:
+ virtual ~LevelDBFactory() {}
+ virtual leveldb::Status OpenLevelDB(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* db,
+ bool* is_disk_full) = 0;
+ virtual bool DestroyLevelDB(const base::FilePath& file_name) = 0;
+};
+
+class CONTENT_EXPORT IndexedDBBackingStore
+ : public base::RefCounted<IndexedDBBackingStore> {
+ public:
+ class CONTENT_EXPORT Transaction;
+
+ static scoped_refptr<IndexedDBBackingStore> Open(
+ const std::string& origin_identifier,
+ const base::FilePath& path_base,
+ const std::string& file_identifier,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss);
+
+ static scoped_refptr<IndexedDBBackingStore> Open(
+ const std::string& origin_identifier,
+ const base::FilePath& path_base,
+ const std::string& file_identifier,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss,
+ LevelDBFactory* factory);
+ static scoped_refptr<IndexedDBBackingStore> OpenInMemory(
+ const std::string& file_identifier);
+ static scoped_refptr<IndexedDBBackingStore> OpenInMemory(
+ const std::string& file_identifier,
+ LevelDBFactory* factory);
+ base::WeakPtr<IndexedDBBackingStore> GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+ }
+
+ virtual std::vector<string16> GetDatabaseNames();
+ virtual bool GetIDBDatabaseMetaData(const string16& name,
+ IndexedDBDatabaseMetadata* metadata,
+ bool* success) WARN_UNUSED_RESULT;
+ virtual bool CreateIDBDatabaseMetaData(const string16& name,
+ const string16& version,
+ int64 int_version,
+ int64* row_id);
+ virtual bool UpdateIDBDatabaseMetaData(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ const string16& version);
+ virtual bool UpdateIDBDatabaseIntVersion(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 row_id,
+ int64 int_version);
+ virtual bool DeleteDatabase(const string16& name);
+
+ bool GetObjectStores(int64 database_id,
+ IndexedDBDatabaseMetadata::ObjectStoreMap* map)
+ WARN_UNUSED_RESULT;
+ virtual bool CreateObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment);
+ virtual bool DeleteObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) WARN_UNUSED_RESULT;
+
+ class CONTENT_EXPORT RecordIdentifier {
+ public:
+ RecordIdentifier(const std::string& primary_key, int64 version);
+ RecordIdentifier();
+ ~RecordIdentifier();
+
+ const std::string& primary_key() const { return primary_key_; }
+ int64 version() const { return version_; }
+ void Reset(const std::string& primary_key, int64 version) {
+ primary_key_ = primary_key;
+ version_ = version;
+ }
+
+ private:
+ // TODO(jsbell): Make it more clear that this is the *encoded* version of
+ // the key.
+ std::string primary_key_;
+ int64 version_;
+ DISALLOW_COPY_AND_ASSIGN(RecordIdentifier);
+ };
+
+ virtual bool GetRecord(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ std::string* record) WARN_UNUSED_RESULT;
+ virtual bool PutRecord(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ const std::string& value,
+ RecordIdentifier* record) WARN_UNUSED_RESULT;
+ virtual bool ClearObjectStore(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) WARN_UNUSED_RESULT;
+ virtual bool DeleteRecord(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const RecordIdentifier& record) WARN_UNUSED_RESULT;
+ virtual bool GetKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64* current_number) WARN_UNUSED_RESULT;
+ virtual bool MaybeUpdateKeyGeneratorCurrentNumber(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 new_state,
+ bool check_current) WARN_UNUSED_RESULT;
+ virtual bool KeyExistsInObjectStore(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& key,
+ RecordIdentifier* found_record_identifier,
+ bool* found) WARN_UNUSED_RESULT;
+
+ virtual bool CreateIndex(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool is_unique,
+ bool is_multi_entry) WARN_UNUSED_RESULT;
+ virtual bool DeleteIndex(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) WARN_UNUSED_RESULT;
+ virtual bool PutIndexDataForRecord(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ const RecordIdentifier& record) WARN_UNUSED_RESULT;
+ virtual bool GetPrimaryKeyViaIndex(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ scoped_ptr<IndexedDBKey>* primary_key) WARN_UNUSED_RESULT;
+ virtual bool KeyExistsInIndex(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ scoped_ptr<IndexedDBKey>* found_primary_key,
+ bool* exists) WARN_UNUSED_RESULT;
+
+ class Cursor {
+ public:
+ virtual ~Cursor();
+
+ enum IteratorState {
+ READY = 0,
+ SEEK
+ };
+
+ struct CursorOptions {
+ CursorOptions();
+ ~CursorOptions();
+ int64 database_id;
+ int64 object_store_id;
+ int64 index_id;
+ std::string low_key;
+ bool low_open;
+ std::string high_key;
+ bool high_open;
+ bool forward;
+ bool unique;
+ };
+
+ const IndexedDBKey& key() const { return *current_key_; }
+ bool Continue() { return Continue(NULL, SEEK); }
+ bool Continue(const IndexedDBKey* key, IteratorState state);
+ bool Advance(uint32 count);
+ bool FirstSeek();
+
+ virtual Cursor* Clone() = 0;
+ virtual const IndexedDBKey& primary_key() const;
+ virtual std::string* Value() = 0;
+ virtual const RecordIdentifier& record_identifier() const;
+ virtual bool LoadCurrentRow() = 0;
+
+ protected:
+ Cursor(LevelDBTransaction* transaction,
+ const CursorOptions& cursor_options);
+ explicit Cursor(const IndexedDBBackingStore::Cursor* other);
+
+ virtual std::string EncodeKey(const IndexedDBKey& key) = 0;
+
+ bool IsPastBounds() const;
+ bool HaveEnteredRange() const;
+
+ LevelDBTransaction* transaction_;
+ const CursorOptions cursor_options_;
+ scoped_ptr<LevelDBIterator> iterator_;
+ scoped_ptr<IndexedDBKey> current_key_;
+ IndexedDBBackingStore::RecordIdentifier record_identifier_;
+ };
+
+ virtual scoped_ptr<Cursor> OpenObjectStoreKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection);
+ virtual scoped_ptr<Cursor> OpenObjectStoreCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection);
+ virtual scoped_ptr<Cursor> OpenIndexKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection);
+ virtual scoped_ptr<Cursor> OpenIndexCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection);
+
+ class Transaction {
+ public:
+ explicit Transaction(IndexedDBBackingStore* backing_store);
+ ~Transaction();
+ void Begin();
+ bool Commit();
+ void Rollback();
+ void Reset() {
+ backing_store_ = NULL;
+ transaction_ = NULL;
+ }
+
+ static LevelDBTransaction* LevelDBTransactionFrom(
+ Transaction* transaction) {
+ return transaction->transaction_;
+ }
+
+ private:
+ IndexedDBBackingStore* backing_store_;
+ scoped_refptr<LevelDBTransaction> transaction_;
+ };
+
+ protected:
+ IndexedDBBackingStore(const std::string& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator);
+ virtual ~IndexedDBBackingStore();
+ friend class base::RefCounted<IndexedDBBackingStore>;
+
+ private:
+ static scoped_refptr<IndexedDBBackingStore> Create(
+ const std::string& identifier,
+ scoped_ptr<LevelDBDatabase> db,
+ scoped_ptr<LevelDBComparator> comparator);
+
+ bool FindKeyInIndex(IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& key,
+ std::string* found_encoded_primary_key,
+ bool* found);
+ bool GetIndexes(int64 database_id,
+ int64 object_store_id,
+ IndexedDBObjectStoreMetadata::IndexMap* map)
+ WARN_UNUSED_RESULT;
+
+ std::string identifier_;
+
+ scoped_ptr<LevelDBDatabase> db_;
+ scoped_ptr<LevelDBComparator> comparator_;
+ base::WeakPtrFactory<IndexedDBBackingStore> weak_factory_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_BACKING_STORE_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_backing_store_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
new file mode 100644
index 00000000000..cc6ea1f2a64
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
@@ -0,0 +1,418 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_backing_store.h"
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebIDBTypes.h"
+#include "url/gurl.h"
+#include "webkit/common/database/database_identifier.h"
+
+namespace content {
+
+namespace {
+
+class IndexedDBBackingStoreTest : public testing::Test {
+ public:
+ IndexedDBBackingStoreTest() {}
+ virtual void SetUp() {
+ std::string file_identifier;
+ backing_store_ = IndexedDBBackingStore::OpenInMemory(file_identifier);
+
+ // useful keys and values during tests
+ m_value1 = "value1";
+ m_value2 = "value2";
+ m_value3 = "value3";
+ m_key1 = IndexedDBKey(99, WebKit::WebIDBKeyTypeNumber);
+ m_key2 = IndexedDBKey(ASCIIToUTF16("key2"));
+ m_key3 = IndexedDBKey(ASCIIToUTF16("key3"));
+ }
+
+ protected:
+ scoped_refptr<IndexedDBBackingStore> backing_store_;
+
+ // Sample keys and values that are consistent.
+ IndexedDBKey m_key1;
+ IndexedDBKey m_key2;
+ IndexedDBKey m_key3;
+ std::string m_value1;
+ std::string m_value2;
+ std::string m_value3;
+};
+
+TEST_F(IndexedDBBackingStoreTest, PutGetConsistency) {
+ {
+ IndexedDBBackingStore::Transaction transaction1(backing_store_);
+ transaction1.Begin();
+ IndexedDBBackingStore::RecordIdentifier record;
+ bool ok = backing_store_->PutRecord(
+ &transaction1, 1, 1, m_key1, m_value1, &record);
+ EXPECT_TRUE(ok);
+ transaction1.Commit();
+ }
+
+ {
+ IndexedDBBackingStore::Transaction transaction2(backing_store_);
+ transaction2.Begin();
+ std::string result_value;
+ bool ok =
+ backing_store_->GetRecord(&transaction2, 1, 1, m_key1, &result_value);
+ transaction2.Commit();
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(m_value1, result_value);
+ }
+}
+
+// Make sure that using very high ( more than 32 bit ) values for database_id
+// and object_store_id still work.
+TEST_F(IndexedDBBackingStoreTest, HighIds) {
+ const int64 high_database_id = 1ULL << 35;
+ const int64 high_object_store_id = 1ULL << 39;
+ // index_ids are capped at 32 bits for storage purposes.
+ const int64 high_index_id = 1ULL << 29;
+
+ const int64 invalid_high_index_id = 1ULL << 37;
+
+ const IndexedDBKey& index_key = m_key2;
+ std::string index_key_raw;
+ EncodeIDBKey(index_key, &index_key_raw);
+ {
+ IndexedDBBackingStore::Transaction transaction1(backing_store_);
+ transaction1.Begin();
+ IndexedDBBackingStore::RecordIdentifier record;
+ bool ok = backing_store_->PutRecord(&transaction1,
+ high_database_id,
+ high_object_store_id,
+ m_key1,
+ m_value1,
+ &record);
+ EXPECT_TRUE(ok);
+
+ ok = backing_store_->PutIndexDataForRecord(&transaction1,
+ high_database_id,
+ high_object_store_id,
+ invalid_high_index_id,
+ index_key,
+ record);
+ EXPECT_FALSE(ok);
+
+ ok = backing_store_->PutIndexDataForRecord(&transaction1,
+ high_database_id,
+ high_object_store_id,
+ high_index_id,
+ index_key,
+ record);
+ EXPECT_TRUE(ok);
+
+ ok = transaction1.Commit();
+ EXPECT_TRUE(ok);
+ }
+
+ {
+ IndexedDBBackingStore::Transaction transaction2(backing_store_);
+ transaction2.Begin();
+ std::string result_value;
+ bool ok = backing_store_->GetRecord(&transaction2,
+ high_database_id,
+ high_object_store_id,
+ m_key1,
+ &result_value);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(m_value1, result_value);
+
+ scoped_ptr<IndexedDBKey> new_primary_key;
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction2,
+ high_database_id,
+ high_object_store_id,
+ invalid_high_index_id,
+ index_key,
+ &new_primary_key);
+ EXPECT_FALSE(ok);
+
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction2,
+ high_database_id,
+ high_object_store_id,
+ high_index_id,
+ index_key,
+ &new_primary_key);
+ EXPECT_TRUE(ok);
+ EXPECT_TRUE(new_primary_key->IsEqual(m_key1));
+
+ ok = transaction2.Commit();
+ EXPECT_TRUE(ok);
+ }
+}
+
+// Make sure that other invalid ids do not crash.
+TEST_F(IndexedDBBackingStoreTest, InvalidIds) {
+ // valid ids for use when testing invalid ids
+ const int64 database_id = 1;
+ const int64 object_store_id = 1;
+ const int64 index_id = kMinimumIndexId;
+ const int64 invalid_low_index_id = 19; // index_ids must be > kMinimumIndexId
+
+ std::string result_value;
+
+ IndexedDBBackingStore::Transaction transaction1(backing_store_);
+ transaction1.Begin();
+
+ IndexedDBBackingStore::RecordIdentifier record;
+ bool ok = backing_store_->PutRecord(&transaction1,
+ database_id,
+ KeyPrefix::kInvalidId,
+ m_key1,
+ m_value1,
+ &record);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->PutRecord(
+ &transaction1, database_id, 0, m_key1, m_value1, &record);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->PutRecord(&transaction1,
+ KeyPrefix::kInvalidId,
+ object_store_id,
+ m_key1,
+ m_value1,
+ &record);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->PutRecord(
+ &transaction1, 0, object_store_id, m_key1, m_value1, &record);
+ EXPECT_FALSE(ok);
+
+ ok = backing_store_->GetRecord(
+ &transaction1, database_id, KeyPrefix::kInvalidId, m_key1, &result_value);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetRecord(
+ &transaction1, database_id, 0, m_key1, &result_value);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetRecord(&transaction1,
+ KeyPrefix::kInvalidId,
+ object_store_id,
+ m_key1,
+ &result_value);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetRecord(
+ &transaction1, 0, object_store_id, m_key1, &result_value);
+ EXPECT_FALSE(ok);
+
+ scoped_ptr<IndexedDBKey> new_primary_key;
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
+ database_id,
+ object_store_id,
+ KeyPrefix::kInvalidId,
+ m_key1,
+ &new_primary_key);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
+ database_id,
+ object_store_id,
+ invalid_low_index_id,
+ m_key1,
+ &new_primary_key);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetPrimaryKeyViaIndex(
+ &transaction1, database_id, object_store_id, 0, m_key1, &new_primary_key);
+ EXPECT_FALSE(ok);
+
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
+ KeyPrefix::kInvalidId,
+ object_store_id,
+ index_id,
+ m_key1,
+ &new_primary_key);
+ EXPECT_FALSE(ok);
+ ok = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
+ database_id,
+ KeyPrefix::kInvalidId,
+ index_id,
+ m_key1,
+ &new_primary_key);
+ EXPECT_FALSE(ok);
+}
+
+TEST_F(IndexedDBBackingStoreTest, CreateDatabase) {
+ const string16 database_name(ASCIIToUTF16("db1"));
+ int64 database_id;
+ const string16 version(ASCIIToUTF16("old_string_version"));
+ const int64 int_version = 9;
+
+ const int64 object_store_id = 99;
+ const string16 object_store_name(ASCIIToUTF16("object_store1"));
+ const bool auto_increment = true;
+ const IndexedDBKeyPath object_store_key_path(
+ ASCIIToUTF16("object_store_key"));
+
+ const int64 index_id = 999;
+ const string16 index_name(ASCIIToUTF16("index1"));
+ const bool unique = true;
+ const bool multi_entry = true;
+ const IndexedDBKeyPath index_key_path(ASCIIToUTF16("index_key"));
+
+ {
+ bool ok = backing_store_->CreateIDBDatabaseMetaData(
+ database_name, version, int_version, &database_id);
+ EXPECT_TRUE(ok);
+ EXPECT_GT(database_id, 0);
+
+ IndexedDBBackingStore::Transaction transaction(backing_store_);
+ transaction.Begin();
+
+ ok = backing_store_->CreateObjectStore(&transaction,
+ database_id,
+ object_store_id,
+ object_store_name,
+ object_store_key_path,
+ auto_increment);
+ EXPECT_TRUE(ok);
+
+ ok = backing_store_->CreateIndex(&transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ index_name,
+ index_key_path,
+ unique,
+ multi_entry);
+ EXPECT_TRUE(ok);
+
+ ok = transaction.Commit();
+ EXPECT_TRUE(ok);
+ }
+
+ {
+ IndexedDBDatabaseMetadata database;
+ bool found;
+ bool ok = backing_store_->GetIDBDatabaseMetaData(
+ database_name, &database, &found);
+ EXPECT_TRUE(ok);
+ EXPECT_TRUE(found);
+
+ // database.name is not filled in by the implementation.
+ EXPECT_EQ(version, database.version);
+ EXPECT_EQ(int_version, database.int_version);
+ EXPECT_EQ(database_id, database.id);
+
+ ok = backing_store_->GetObjectStores(database.id, &database.object_stores);
+ EXPECT_TRUE(ok);
+
+ EXPECT_EQ(1UL, database.object_stores.size());
+ IndexedDBObjectStoreMetadata object_store =
+ database.object_stores[object_store_id];
+ EXPECT_EQ(object_store_name, object_store.name);
+ EXPECT_EQ(object_store_key_path, object_store.key_path);
+ EXPECT_EQ(auto_increment, object_store.auto_increment);
+
+ EXPECT_EQ(1UL, object_store.indexes.size());
+ IndexedDBIndexMetadata index = object_store.indexes[index_id];
+ EXPECT_EQ(index_name, index.name);
+ EXPECT_EQ(index_key_path, index.key_path);
+ EXPECT_EQ(unique, index.unique);
+ EXPECT_EQ(multi_entry, index.multi_entry);
+ }
+}
+
+class MockIDBFactory : public IndexedDBFactory {
+ public:
+ scoped_refptr<IndexedDBBackingStore> TestOpenBackingStore(
+ const GURL& origin,
+ const base::FilePath& data_directory) {
+ WebKit::WebIDBCallbacks::DataLoss data_loss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ OpenBackingStore(webkit_database::GetIdentifierFromOrigin(origin),
+ data_directory,
+ &data_loss);
+ EXPECT_EQ(WebKit::WebIDBCallbacks::DataLossNone, data_loss);
+ return backing_store;
+ }
+
+ private:
+ virtual ~MockIDBFactory() {}
+};
+
+TEST(IndexedDBFactoryTest, BackingStoreLifetime) {
+ GURL origin1("http://localhost:81");
+ GURL origin2("http://localhost:82");
+
+ scoped_refptr<MockIDBFactory> factory = new MockIDBFactory();
+
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+ scoped_refptr<IndexedDBBackingStore> disk_store1 =
+ factory->TestOpenBackingStore(origin1, temp_directory.path());
+ EXPECT_TRUE(disk_store1->HasOneRef());
+
+ scoped_refptr<IndexedDBBackingStore> disk_store2 =
+ factory->TestOpenBackingStore(origin1, temp_directory.path());
+ EXPECT_EQ(disk_store1.get(), disk_store2.get());
+ EXPECT_FALSE(disk_store2->HasOneRef());
+
+ scoped_refptr<IndexedDBBackingStore> disk_store3 =
+ factory->TestOpenBackingStore(origin2, temp_directory.path());
+ EXPECT_TRUE(disk_store3->HasOneRef());
+ EXPECT_FALSE(disk_store1->HasOneRef());
+
+ disk_store2 = NULL;
+ EXPECT_TRUE(disk_store1->HasOneRef());
+}
+
+TEST(IndexedDBFactoryTest, MemoryBackingStoreLifetime) {
+ GURL origin1("http://localhost:81");
+ GURL origin2("http://localhost:82");
+
+ scoped_refptr<MockIDBFactory> factory = new MockIDBFactory();
+ scoped_refptr<IndexedDBBackingStore> mem_store1 =
+ factory->TestOpenBackingStore(origin1, base::FilePath());
+ EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1 and factory
+
+ scoped_refptr<IndexedDBBackingStore> mem_store2 =
+ factory->TestOpenBackingStore(origin1, base::FilePath());
+ EXPECT_EQ(mem_store1.get(), mem_store2.get());
+ EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1, 2 and factory
+ EXPECT_FALSE(mem_store2->HasOneRef()); // mem_store1, 2 and factory
+
+ scoped_refptr<IndexedDBBackingStore> mem_store3 =
+ factory->TestOpenBackingStore(origin2, base::FilePath());
+ EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1, 2 and factory
+ EXPECT_FALSE(mem_store3->HasOneRef()); // mem_store3 and factory
+
+ factory = NULL;
+ EXPECT_FALSE(mem_store1->HasOneRef()); // mem_store1 and 2
+ EXPECT_FALSE(mem_store2->HasOneRef()); // mem_store1 and 2
+ EXPECT_TRUE(mem_store3->HasOneRef());
+
+ mem_store2 = NULL;
+ EXPECT_TRUE(mem_store1->HasOneRef());
+}
+
+TEST(IndexedDBFactoryTest, RejectLongOrigins) {
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+ const base::FilePath base_path = temp_directory.path();
+ scoped_refptr<MockIDBFactory> factory = new MockIDBFactory();
+
+ int limit = file_util::GetMaximumPathComponentLength(base_path);
+ EXPECT_GT(limit, 0);
+
+ std::string origin(limit + 1, 'x');
+ GURL too_long_origin("http://" + origin + ":81/");
+ scoped_refptr<IndexedDBBackingStore> diskStore1 =
+ factory->TestOpenBackingStore(too_long_origin, base_path);
+ EXPECT_FALSE(diskStore1);
+
+ GURL ok_origin("http://someorigin.com:82/");
+ scoped_refptr<IndexedDBBackingStore> diskStore2 =
+ factory->TestOpenBackingStore(ok_origin, base_path);
+ EXPECT_TRUE(diskStore2);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_browsertest.cc b/chromium/content/browser/indexed_db/indexed_db_browsertest.cc
new file mode 100644
index 00000000000..ac541e761d3
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_browsertest.cc
@@ -0,0 +1,434 @@
+// 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 "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/thread_test_helper.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "webkit/browser/database/database_util.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+using quota::QuotaManager;
+using webkit_database::DatabaseUtil;
+
+namespace content {
+
+// This browser test is aimed towards exercising the IndexedDB bindings and
+// the actual implementation that lives in the browser side.
+class IndexedDBBrowserTest : public ContentBrowserTest {
+ public:
+ IndexedDBBrowserTest() : disk_usage_(-1) {}
+
+ void SimpleTest(const GURL& test_url, bool incognito = false) {
+ // The test page will perform tests on IndexedDB, then navigate to either
+ // a #pass or #fail ref.
+ Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell();
+
+ LOG(INFO) << "Navigating to URL and blocking.";
+ NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2);
+ LOG(INFO) << "Navigation done.";
+ std::string result =
+ the_browser->web_contents()->GetLastCommittedURL().ref();
+ if (result != "pass") {
+ std::string js_result;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ the_browser->web_contents(),
+ "window.domAutomationController.send(getLog())",
+ &js_result));
+ FAIL() << "Failed: " << js_result;
+ }
+ }
+
+ void NavigateAndWaitForTitle(Shell* shell,
+ const char* filename,
+ const char* hash,
+ const char* expected_string) {
+ GURL url = GetTestUrl("indexeddb", filename);
+ if (hash)
+ url = GURL(url.spec() + hash);
+
+ string16 expected_title16(ASCIIToUTF16(expected_string));
+ TitleWatcher title_watcher(shell->web_contents(), expected_title16);
+ NavigateToURL(shell, url);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+
+ IndexedDBContextImpl* GetContext() {
+ StoragePartition* partition =
+ BrowserContext::GetDefaultStoragePartition(
+ shell()->web_contents()->GetBrowserContext());
+ return static_cast<IndexedDBContextImpl*>(partition->GetIndexedDBContext());
+ }
+
+ void SetQuota(int quotaKilobytes) {
+ const int kTemporaryStorageQuotaSize = quotaKilobytes
+ * 1024 * QuotaManager::kPerHostTemporaryPortion;
+ SetTempQuota(kTemporaryStorageQuotaSize,
+ BrowserContext::GetDefaultStoragePartition(
+ shell()->web_contents()->GetBrowserContext())->GetQuotaManager());
+ }
+
+ static void SetTempQuota(int64 bytes, scoped_refptr<QuotaManager> qm) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&IndexedDBBrowserTest::SetTempQuota, bytes, qm));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ qm->SetTemporaryGlobalOverrideQuota(bytes, quota::QuotaCallback());
+ // Don't return until the quota has been set.
+ scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ ASSERT_TRUE(helper->Run());
+ }
+
+ virtual int64 RequestDiskUsage() {
+ PostTaskAndReplyWithResult(
+ GetContext()->TaskRunner(),
+ FROM_HERE,
+ base::Bind(&IndexedDBContext::GetOriginDiskUsage,
+ GetContext(),
+ GURL("file:///")),
+ base::Bind(&IndexedDBBrowserTest::DidGetDiskUsage, this));
+ scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
+ BrowserMainLoop::GetInstance()->indexed_db_thread()->
+ message_loop_proxy()));
+ EXPECT_TRUE(helper->Run());
+ // Wait for DidGetDiskUsage to be called.
+ base::MessageLoop::current()->RunUntilIdle();
+ return disk_usage_;
+ }
+ private:
+ virtual void DidGetDiskUsage(int64 bytes) {
+ EXPECT_GT(bytes, 0);
+ disk_usage_ = bytes;
+ }
+
+ int64 disk_usage_;
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTest) {
+ SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTestIncognito) {
+ SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"),
+ true /* incognito */);
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorPrefetch) {
+ SimpleTest(GetTestUrl("indexeddb", "cursor_prefetch.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, IndexTest) {
+ SimpleTest(GetTestUrl("indexeddb", "index_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyPathTest) {
+ SimpleTest(GetTestUrl("indexeddb", "key_path_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionGetTest) {
+ SimpleTest(GetTestUrl("indexeddb", "transaction_get_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyTypesTest) {
+ SimpleTest(GetTestUrl("indexeddb", "key_types_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ObjectStoreTest) {
+ SimpleTest(GetTestUrl("indexeddb", "object_store_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DatabaseTest) {
+ SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionTest) {
+ SimpleTest(GetTestUrl("indexeddb", "transaction_test.html"));
+}
+
+// http://crbug.com/239366
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DISABLED_ValueSizeTest) {
+ SimpleTest(GetTestUrl("indexeddb", "value_size_test.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CallbackAccounting) {
+ SimpleTest(GetTestUrl("indexeddb", "callback_accounting.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DoesntHangTest) {
+ SimpleTest(GetTestUrl("indexeddb", "transaction_run_forever.html"));
+ CrashTab(shell()->web_contents());
+ SimpleTest(GetTestUrl("indexeddb", "transaction_not_blocked.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug84933Test) {
+ const GURL url = GetTestUrl("indexeddb", "bug_84933.html");
+
+ // Just navigate to the URL. Test will crash if it fails.
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug106883Test) {
+ const GURL url = GetTestUrl("indexeddb", "bug_106883.html");
+
+ // Just navigate to the URL. Test will crash if it fails.
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug109187Test) {
+ const GURL url = GetTestUrl("indexeddb", "bug_109187.html");
+
+ // Just navigate to the URL. Test will crash if it fails.
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
+}
+
+class IndexedDBBrowserTestWithLowQuota : public IndexedDBBrowserTest {
+ public:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ const int kInitialQuotaKilobytes = 5000;
+ SetQuota(kInitialQuotaKilobytes);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithLowQuota, QuotaTest) {
+ SimpleTest(GetTestUrl("indexeddb", "quota_test.html"));
+}
+
+class IndexedDBBrowserTestWithGCExposed : public IndexedDBBrowserTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed,
+ DatabaseCallbacksTest) {
+ SimpleTest(GetTestUrl("indexeddb", "database_callbacks_first.html"));
+}
+
+static void CopyLevelDBToProfile(Shell* shell,
+ scoped_refptr<IndexedDBContextImpl> context,
+ const std::string& test_directory) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+ base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
+ base::FilePath test_data_dir =
+ GetTestFilePath("indexeddb", test_directory.c_str()).Append(leveldb_dir);
+ base::FilePath dest = context->data_path().Append(leveldb_dir);
+ // If we don't create the destination directory first, the contents of the
+ // leveldb directory are copied directly into profile/IndexedDB instead of
+ // profile/IndexedDB/file__0.xxx/
+ ASSERT_TRUE(file_util::CreateDirectory(dest));
+ const bool kRecursive = true;
+ ASSERT_TRUE(base::CopyDirectory(test_data_dir,
+ context->data_path(),
+ kRecursive));
+}
+
+class IndexedDBBrowserTestWithPreexistingLevelDB : public IndexedDBBrowserTest {
+ public:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ scoped_refptr<IndexedDBContextImpl> context = GetContext();
+ context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &CopyLevelDBToProfile, shell(), context, EnclosingLevelDBDir()));
+ scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
+ BrowserMainLoop::GetInstance()->indexed_db_thread()->
+ message_loop_proxy()));
+ ASSERT_TRUE(helper->Run());
+ }
+
+ virtual std::string EnclosingLevelDBDir() = 0;
+
+};
+
+class IndexedDBBrowserTestWithVersion0Schema : public
+ IndexedDBBrowserTestWithPreexistingLevelDB {
+ virtual std::string EnclosingLevelDBDir() OVERRIDE {
+ return "migration_from_0";
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion0Schema, MigrationTest) {
+ SimpleTest(GetTestUrl("indexeddb", "migration_test.html"));
+}
+
+class IndexedDBBrowserTestWithVersion123456Schema : public
+ IndexedDBBrowserTestWithPreexistingLevelDB {
+ virtual std::string EnclosingLevelDBDir() OVERRIDE {
+ return "schema_version_123456";
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion123456Schema,
+ DestroyTest) {
+ int64 original_size = RequestDiskUsage();
+ EXPECT_GT(original_size, 0);
+ SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
+ int64 new_size = RequestDiskUsage();
+ EXPECT_NE(original_size, new_size);
+}
+
+class IndexedDBBrowserTestWithVersion987654SSVData : public
+ IndexedDBBrowserTestWithPreexistingLevelDB {
+ virtual std::string EnclosingLevelDBDir() OVERRIDE {
+ return "ssv_version_987654";
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion987654SSVData,
+ DestroyTest) {
+ int64 original_size = RequestDiskUsage();
+ EXPECT_GT(original_size, 0);
+ SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
+ int64 new_size = RequestDiskUsage();
+ EXPECT_NE(original_size, new_size);
+}
+
+class IndexedDBBrowserTestWithCorruptLevelDB : public
+ IndexedDBBrowserTestWithPreexistingLevelDB {
+ virtual std::string EnclosingLevelDBDir() OVERRIDE {
+ return "corrupt_leveldb";
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCorruptLevelDB,
+ DestroyTest) {
+ int64 original_size = RequestDiskUsage();
+ EXPECT_GT(original_size, 0);
+ SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
+ int64 new_size = RequestDiskUsage();
+ EXPECT_NE(original_size, new_size);
+}
+
+class IndexedDBBrowserTestWithMissingSSTFile : public
+ IndexedDBBrowserTestWithPreexistingLevelDB {
+ virtual std::string EnclosingLevelDBDir() OVERRIDE {
+ return "missing_sst";
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile,
+ DestroyTest) {
+ int64 original_size = RequestDiskUsage();
+ EXPECT_GT(original_size, 0);
+ SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
+ int64 new_size = RequestDiskUsage();
+ EXPECT_NE(original_size, new_size);
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) {
+ // Any page that opens an IndexedDB will work here.
+ SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
+ base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
+ base::FilePath log_file(FILE_PATH_LITERAL("LOG"));
+ base::FilePath log_file_path =
+ GetContext()->data_path().Append(leveldb_dir).Append(log_file);
+ int64 size;
+ EXPECT_TRUE(file_util::GetFileSize(log_file_path, &size));
+ EXPECT_GT(size, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CanDeleteWhenOverQuotaTest) {
+ SimpleTest(GetTestUrl("indexeddb", "fill_up_5k.html"));
+ int64 size = RequestDiskUsage();
+ const int kQuotaKilobytes = 2;
+ EXPECT_GT(size, kQuotaKilobytes * 1024);
+ SetQuota(kQuotaKilobytes);
+ SimpleTest(GetTestUrl("indexeddb", "delete_over_quota.html"));
+}
+
+// Complex multi-step (converted from pyauto) tests begin here.
+
+// Verify null key path persists after restarting browser.
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_NullKeyPathPersistence) {
+ NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part1",
+ "pass - first run");
+}
+
+// Verify null key path persists after restarting browser.
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, NullKeyPathPersistence) {
+ NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part2",
+ "pass - second run");
+}
+
+// Verify that a VERSION_CHANGE transaction is rolled back after a
+// renderer/browser crash
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest,
+ PRE_PRE_VersionChangeCrashResilience) {
+ NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part1",
+ "pass - part1 - complete");
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_VersionChangeCrashResilience) {
+ NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part2",
+ "pass - part2 - crash me");
+ NavigateToURL(shell(), GURL(kChromeUIBrowserCrashHost));
+}
+
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, VersionChangeCrashResilience) {
+ NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part3",
+ "pass - part3 - rolled back");
+}
+
+// Verify that open DB connections are closed when a tab is destroyed.
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ConnectionsClosedOnTabClose) {
+ NavigateAndWaitForTitle(shell(), "version_change_blocked.html", "#tab1",
+ "setVersion(2) complete");
+
+ // Start on a different URL to force a new renderer process.
+ Shell* new_shell = CreateBrowser();
+ NavigateToURL(new_shell, GURL(kAboutBlankURL));
+ NavigateAndWaitForTitle(new_shell, "version_change_blocked.html", "#tab2",
+ "setVersion(3) blocked");
+
+ string16 expected_title16(ASCIIToUTF16("setVersion(3) complete"));
+ TitleWatcher title_watcher(new_shell->web_contents(), expected_title16);
+
+ base::KillProcess(
+ shell()->web_contents()->GetRenderProcessHost()->GetHandle(), 0, true);
+ shell()->Close();
+
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+}
+
+// Verify that a "close" event is fired at database connections when
+// the backing store is deleted.
+IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ForceCloseEventTest) {
+ NavigateAndWaitForTitle(shell(), "force_close_event.html", NULL,
+ "connection ready");
+
+ GetContext()->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBContextImpl::DeleteForOrigin,
+ GetContext(),
+ GURL("file:///")));
+
+ string16 expected_title16(ASCIIToUTF16("connection closed"));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_callbacks.cc b/chromium/content/browser/indexed_db/indexed_db_callbacks.cc
new file mode 100644
index 00000000000..46270db7531
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_callbacks.cc
@@ -0,0 +1,336 @@
+// 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 "content/browser/indexed_db/indexed_db_callbacks.h"
+
+#include <algorithm>
+
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_database_error.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/common/indexed_db/indexed_db_messages.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+using WebKit::WebIDBCallbacks;
+
+namespace content {
+
+namespace {
+const int32 kNoCursor = -1;
+const int32 kNoDatabase = -1;
+const int32 kNoDatabaseCallbacks = -1;
+const int64 kNoTransaction = -1;
+}
+
+IndexedDBCallbacks::IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id)
+ : dispatcher_host_(dispatcher_host),
+ ipc_callbacks_id_(ipc_callbacks_id),
+ ipc_thread_id_(ipc_thread_id),
+ ipc_cursor_id_(kNoCursor),
+ host_transaction_id_(kNoTransaction),
+ ipc_database_id_(kNoDatabase),
+ ipc_database_callbacks_id_(kNoDatabaseCallbacks) {}
+
+IndexedDBCallbacks::IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_cursor_id)
+ : dispatcher_host_(dispatcher_host),
+ ipc_callbacks_id_(ipc_callbacks_id),
+ ipc_thread_id_(ipc_thread_id),
+ ipc_cursor_id_(ipc_cursor_id),
+ host_transaction_id_(kNoTransaction),
+ ipc_database_id_(kNoDatabase),
+ ipc_database_callbacks_id_(kNoDatabaseCallbacks) {}
+
+IndexedDBCallbacks::IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_database_callbacks_id,
+ int64 host_transaction_id,
+ const GURL& origin_url)
+ : dispatcher_host_(dispatcher_host),
+ ipc_callbacks_id_(ipc_callbacks_id),
+ ipc_thread_id_(ipc_thread_id),
+ ipc_cursor_id_(kNoCursor),
+ host_transaction_id_(host_transaction_id),
+ origin_url_(origin_url),
+ ipc_database_id_(kNoDatabase),
+ ipc_database_callbacks_id_(ipc_database_callbacks_id) {}
+
+IndexedDBCallbacks::~IndexedDBCallbacks() {}
+
+void IndexedDBCallbacks::OnError(const IndexedDBDatabaseError& error) {
+ DCHECK(dispatcher_host_.get());
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksError(
+ ipc_thread_id_, ipc_callbacks_id_, error.code(), error.message()));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(const std::vector<string16>& value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ std::vector<string16> list;
+ for (unsigned i = 0; i < value.size(); ++i)
+ list.push_back(value[i]);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessStringList(
+ ipc_thread_id_, ipc_callbacks_id_, list));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnBlocked(int64 existing_version) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ // No transaction/db callbacks for DeleteDatabase.
+ DCHECK_EQ(kNoTransaction == host_transaction_id_,
+ kNoDatabaseCallbacks == ipc_database_callbacks_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksIntBlocked(
+ ipc_thread_id_, ipc_callbacks_id_, existing_version));
+}
+
+void IndexedDBCallbacks::OnUpgradeNeeded(
+ int64 old_version,
+ scoped_ptr<IndexedDBConnection> connection,
+ const IndexedDBDatabaseMetadata& metadata,
+ WebIDBCallbacks::DataLoss data_loss) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_NE(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_NE(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ dispatcher_host_->RegisterTransactionId(host_transaction_id_, origin_url_);
+ int32 ipc_database_id =
+ dispatcher_host_->Add(connection.release(), ipc_thread_id_, origin_url_);
+ if (ipc_database_id < 0)
+ return;
+ ipc_database_id_ = ipc_database_id;
+ IndexedDBMsg_CallbacksUpgradeNeeded_Params params;
+ params.ipc_thread_id = ipc_thread_id_;
+ params.ipc_callbacks_id = ipc_callbacks_id_;
+ params.ipc_database_id = ipc_database_id;
+ params.ipc_database_callbacks_id = ipc_database_callbacks_id_;
+ params.old_version = old_version;
+ params.idb_metadata = IndexedDBDispatcherHost::ConvertMetadata(metadata);
+ params.data_loss = data_loss;
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksUpgradeNeeded(params));
+}
+
+void IndexedDBCallbacks::OnSuccess(scoped_ptr<IndexedDBConnection> connection,
+ const IndexedDBDatabaseMetadata& metadata) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_NE(kNoTransaction, host_transaction_id_);
+ DCHECK_NE(ipc_database_id_ == kNoDatabase, !connection);
+ DCHECK_NE(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ scoped_refptr<IndexedDBCallbacks> self(this);
+
+ int32 ipc_object_id = ipc_database_id_;
+ if (ipc_object_id == kNoDatabase) {
+ ipc_object_id = dispatcher_host_->Add(
+ connection.release(), ipc_thread_id_, origin_url_);
+ }
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessIDBDatabase(
+ ipc_thread_id_,
+ ipc_callbacks_id_,
+ ipc_database_callbacks_id_,
+ ipc_object_id,
+ IndexedDBDispatcherHost::ConvertMetadata(metadata)));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(scoped_refptr<IndexedDBCursor> cursor,
+ const IndexedDBKey& key,
+ const IndexedDBKey& primary_key,
+ std::string* value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ int32 ipc_object_id = dispatcher_host_->Add(cursor.get());
+ IndexedDBMsg_CallbacksSuccessIDBCursor_Params params;
+ params.ipc_thread_id = ipc_thread_id_;
+ params.ipc_callbacks_id = ipc_callbacks_id_;
+ params.ipc_cursor_id = ipc_object_id;
+ params.key = key;
+ params.primary_key = primary_key;
+ if (value && !value->empty())
+ std::swap(params.value, *value);
+ // TODO(alecflett): Avoid a copy here: the whole params object is
+ // being copied into the message.
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessIDBCursor(params));
+
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(const IndexedDBKey& key,
+ const IndexedDBKey& primary_key,
+ std::string* value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_NE(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ IndexedDBCursor* idb_cursor =
+ dispatcher_host_->GetCursorFromId(ipc_cursor_id_);
+
+ DCHECK(idb_cursor);
+ if (!idb_cursor)
+ return;
+ IndexedDBMsg_CallbacksSuccessCursorContinue_Params params;
+ params.ipc_thread_id = ipc_thread_id_;
+ params.ipc_callbacks_id = ipc_callbacks_id_;
+ params.ipc_cursor_id = ipc_cursor_id_;
+ params.key = key;
+ params.primary_key = primary_key;
+ if (value && !value->empty())
+ std::swap(params.value, *value);
+ // TODO(alecflett): Avoid a copy here: the whole params object is
+ // being copied into the message.
+ dispatcher_host_->Send(
+ new IndexedDBMsg_CallbacksSuccessCursorContinue(params));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccessWithPrefetch(
+ const std::vector<IndexedDBKey>& keys,
+ const std::vector<IndexedDBKey>& primary_keys,
+ const std::vector<std::string>& values) {
+ DCHECK_EQ(keys.size(), primary_keys.size());
+ DCHECK_EQ(keys.size(), values.size());
+
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_NE(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ std::vector<IndexedDBKey> msgKeys;
+ std::vector<IndexedDBKey> msgPrimaryKeys;
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ msgKeys.push_back(keys[i]);
+ msgPrimaryKeys.push_back(primary_keys[i]);
+ }
+
+ IndexedDBMsg_CallbacksSuccessCursorPrefetch_Params params;
+ params.ipc_thread_id = ipc_thread_id_;
+ params.ipc_callbacks_id = ipc_callbacks_id_;
+ params.ipc_cursor_id = ipc_cursor_id_;
+ params.keys = msgKeys;
+ params.primary_keys = msgPrimaryKeys;
+ params.values = values;
+ dispatcher_host_->Send(
+ new IndexedDBMsg_CallbacksSuccessCursorPrefetch(params));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(std::string* value,
+ const IndexedDBKey& key,
+ const IndexedDBKeyPath& key_path) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ std::string value_copy;
+ if (value && !value->empty())
+ std::swap(value_copy, *value);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessValueWithKey(
+ ipc_thread_id_,
+ ipc_callbacks_id_,
+ // TODO(alecflett): Avoid a copy here.
+ value_copy,
+ key,
+ key_path));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(std::string* value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK(kNoCursor == ipc_cursor_id_ || value == NULL);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ std::string value_copy;
+ if (value && !value->empty())
+ std::swap(value_copy, *value);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessValue(
+ ipc_thread_id_,
+ ipc_callbacks_id_,
+ // TODO(alecflett): avoid a copy here.
+ value_copy));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(const IndexedDBKey& value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessIndexedDBKey(
+ ipc_thread_id_, ipc_callbacks_id_, value));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess(int64 value) {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessInteger(
+ ipc_thread_id_, ipc_callbacks_id_, value));
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBCallbacks::OnSuccess() {
+ DCHECK(dispatcher_host_.get());
+
+ DCHECK_EQ(kNoCursor, ipc_cursor_id_);
+ DCHECK_EQ(kNoTransaction, host_transaction_id_);
+ DCHECK_EQ(kNoDatabase, ipc_database_id_);
+ DCHECK_EQ(kNoDatabaseCallbacks, ipc_database_callbacks_id_);
+
+ dispatcher_host_->Send(new IndexedDBMsg_CallbacksSuccessUndefined(
+ ipc_thread_id_, ipc_callbacks_id_));
+ dispatcher_host_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_callbacks.h b/chromium/content/browser/indexed_db/indexed_db_callbacks.h
new file mode 100644
index 00000000000..8ab8ae40aaf
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_callbacks.h
@@ -0,0 +1,127 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "content/browser/indexed_db/indexed_db_database_error.h"
+#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "third_party/WebKit/public/platform/WebIDBCallbacks.h"
+#include "url/gurl.h"
+
+namespace content {
+class IndexedDBConnection;
+class IndexedDBCursor;
+class IndexedDBDatabase;
+class IndexedDBDatabaseCallbacks;
+struct IndexedDBDatabaseMetadata;
+
+class CONTENT_EXPORT IndexedDBCallbacks
+ : public base::RefCounted<IndexedDBCallbacks> {
+ public:
+ // Simple payload responses
+ IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id);
+
+ // IndexedDBCursor responses
+ IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_cursor_id);
+
+ // IndexedDBDatabase responses
+ IndexedDBCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_database_callbacks_id,
+ int64 host_transaction_id,
+ const GURL& origin_url);
+
+ virtual void OnError(const IndexedDBDatabaseError& error);
+
+ // IndexedDBFactory::GetDatabaseNames
+ virtual void OnSuccess(const std::vector<string16>& string);
+
+ // IndexedDBFactory::Open / DeleteDatabase
+ virtual void OnBlocked(int64 existing_version);
+
+ // IndexedDBFactory::Open
+ virtual void OnUpgradeNeeded(
+ int64 old_version,
+ scoped_ptr<IndexedDBConnection> connection,
+ const content::IndexedDBDatabaseMetadata& metadata,
+ WebKit::WebIDBCallbacks::DataLoss data_loss);
+ virtual void OnSuccess(scoped_ptr<IndexedDBConnection> connection,
+ const content::IndexedDBDatabaseMetadata& metadata);
+
+ // IndexedDBDatabase::OpenCursor
+ virtual void OnSuccess(scoped_refptr<IndexedDBCursor> cursor,
+ const IndexedDBKey& key,
+ const IndexedDBKey& primary_key,
+ std::string* value);
+
+ // IndexedDBCursor::Continue / Advance
+ virtual void OnSuccess(const IndexedDBKey& key,
+ const IndexedDBKey& primary_key,
+ std::string* value);
+
+ // IndexedDBCursor::PrefetchContinue
+ virtual void OnSuccessWithPrefetch(
+ const std::vector<IndexedDBKey>& keys,
+ const std::vector<IndexedDBKey>& primary_keys,
+ const std::vector<std::string>& values);
+
+ // IndexedDBDatabase::Get (with key injection)
+ virtual void OnSuccess(std::string* data,
+ const IndexedDBKey& key,
+ const IndexedDBKeyPath& key_path);
+
+ // IndexedDBDatabase::Get
+ virtual void OnSuccess(std::string* value);
+
+ // IndexedDBDatabase::Put / IndexedDBCursor::Update
+ virtual void OnSuccess(const IndexedDBKey& value);
+
+ // IndexedDBDatabase::Count
+ virtual void OnSuccess(int64 value);
+
+ // IndexedDBDatabase::Delete
+ // IndexedDBCursor::Continue / Advance (when complete)
+ virtual void OnSuccess();
+
+ protected:
+ virtual ~IndexedDBCallbacks();
+
+ private:
+ friend class base::RefCounted<IndexedDBCallbacks>;
+
+ // Originally from IndexedDBCallbacks:
+ scoped_refptr<IndexedDBDispatcherHost> dispatcher_host_;
+ int32 ipc_callbacks_id_;
+ int32 ipc_thread_id_;
+
+ // IndexedDBCursor callbacks ------------------------
+ int32 ipc_cursor_id_;
+
+ // IndexedDBDatabase callbacks ------------------------
+ int64 host_transaction_id_;
+ GURL origin_url_;
+ int32 ipc_database_id_;
+ int32 ipc_database_callbacks_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CALLBACKS_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
new file mode 100644
index 00000000000..e05ceb91f57
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 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 <cerrno>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/env_chromium.h"
+
+using base::StringPiece;
+using content::IndexedDBBackingStore;
+using content::LevelDBComparator;
+using content::LevelDBDatabase;
+using content::LevelDBFactory;
+using content::LevelDBSnapshot;
+
+namespace {
+
+class BustedLevelDBDatabase : public LevelDBDatabase {
+ public:
+ static scoped_ptr<LevelDBDatabase> Open(
+ const base::FilePath& file_name,
+ const LevelDBComparator* /*comparator*/) {
+ return scoped_ptr<LevelDBDatabase>(new BustedLevelDBDatabase);
+ }
+ virtual bool Get(const base::StringPiece& key,
+ std::string* value,
+ bool* found,
+ const LevelDBSnapshot* = 0) OVERRIDE {
+ // false means IO error.
+ return false;
+ }
+};
+
+class MockLevelDBFactory : public LevelDBFactory {
+ public:
+ MockLevelDBFactory() : destroy_called_(false) {}
+ virtual leveldb::Status OpenLevelDB(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* db,
+ bool* is_disk_full = 0) OVERRIDE {
+ *db = BustedLevelDBDatabase::Open(file_name, comparator);
+ return leveldb::Status::OK();
+ }
+ virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE {
+ EXPECT_FALSE(destroy_called_);
+ destroy_called_ = true;
+ return false;
+ }
+ virtual ~MockLevelDBFactory() { EXPECT_TRUE(destroy_called_); }
+
+ private:
+ bool destroy_called_;
+};
+
+TEST(IndexedDBIOErrorTest, CleanUpTest) {
+ std::string origin_identifier("http_localhost_81");
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+ const base::FilePath path = temp_directory.path();
+ std::string dummy_file_identifier;
+ MockLevelDBFactory mock_leveldb_factory;
+ WebKit::WebIDBCallbacks::DataLoss data_loss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ IndexedDBBackingStore::Open(origin_identifier,
+ path,
+ dummy_file_identifier,
+ &data_loss,
+ &mock_leveldb_factory);
+}
+
+template <class T>
+class MockErrorLevelDBFactory : public LevelDBFactory {
+ public:
+ MockErrorLevelDBFactory(T error, bool expect_destroy)
+ : error_(error),
+ expect_destroy_(expect_destroy),
+ destroy_called_(false) {}
+ virtual leveldb::Status OpenLevelDB(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* db,
+ bool* is_disk_full = 0) OVERRIDE {
+ return MakeIOError(
+ "some filename", "some message", leveldb_env::kNewLogger, error_);
+ }
+ virtual bool DestroyLevelDB(const base::FilePath& file_name) OVERRIDE {
+ EXPECT_FALSE(destroy_called_);
+ destroy_called_ = true;
+ return false;
+ }
+ virtual ~MockErrorLevelDBFactory() {
+ EXPECT_EQ(expect_destroy_, destroy_called_);
+ }
+
+ private:
+ T error_;
+ bool expect_destroy_;
+ bool destroy_called_;
+};
+
+TEST(IndexedDBNonRecoverableIOErrorTest, NuancedCleanupTest) {
+ std::string origin_identifier("http_localhost_81");
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+ const base::FilePath path = temp_directory.path();
+ std::string dummy_file_identifier;
+ WebKit::WebIDBCallbacks::DataLoss data_loss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+
+ MockErrorLevelDBFactory<int> mock_leveldb_factory(ENOSPC, false);
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ IndexedDBBackingStore::Open(origin_identifier,
+ path,
+ dummy_file_identifier,
+ &data_loss,
+ &mock_leveldb_factory);
+
+ MockErrorLevelDBFactory<base::PlatformFileError> mock_leveldb_factory2(
+ base::PLATFORM_FILE_ERROR_NO_MEMORY, false);
+ scoped_refptr<IndexedDBBackingStore> backing_store2 =
+ IndexedDBBackingStore::Open(origin_identifier,
+ path,
+ dummy_file_identifier,
+ &data_loss,
+ &mock_leveldb_factory2);
+
+ MockErrorLevelDBFactory<int> mock_leveldb_factory3(EIO, true);
+ scoped_refptr<IndexedDBBackingStore> backing_store3 =
+ IndexedDBBackingStore::Open(origin_identifier,
+ path,
+ dummy_file_identifier,
+ &data_loss,
+ &mock_leveldb_factory3);
+
+ MockErrorLevelDBFactory<base::PlatformFileError> mock_leveldb_factory4(
+ base::PLATFORM_FILE_ERROR_FAILED, true);
+ scoped_refptr<IndexedDBBackingStore> backing_store4 =
+ IndexedDBBackingStore::Open(origin_identifier,
+ path,
+ dummy_file_identifier,
+ &data_loss,
+ &mock_leveldb_factory4);
+}
+
+} // namespace
diff --git a/chromium/content/browser/indexed_db/indexed_db_connection.cc b/chromium/content/browser/indexed_db/indexed_db_connection.cc
new file mode 100644
index 00000000000..fc19c0c75a8
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_connection.cc
@@ -0,0 +1,31 @@
+// 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 "content/browser/indexed_db/indexed_db_connection.h"
+
+namespace content {
+
+IndexedDBConnection::IndexedDBConnection(
+ scoped_refptr<IndexedDBDatabase> database,
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks)
+ : database_(database), callbacks_(callbacks) {}
+
+IndexedDBConnection::~IndexedDBConnection() {}
+
+void IndexedDBConnection::Close() {
+ if (!callbacks_)
+ return;
+ database_->Close(this);
+ callbacks_ = NULL;
+}
+
+void IndexedDBConnection::ForceClose() {
+ if (!callbacks_)
+ return;
+ database_->Close(this);
+ callbacks_->OnForcedClose();
+ callbacks_ = NULL;
+}
+
+} // namespace WebKit
diff --git a/chromium/content/browser/indexed_db/indexed_db_connection.h b/chromium/content/browser/indexed_db/indexed_db_connection.h
new file mode 100644
index 00000000000..f6d01f6122a
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_connection.h
@@ -0,0 +1,39 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONNECTION_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONNECTION_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+
+namespace content {
+class IndexedDBCallbacks;
+class IndexedDBDatabaseError;
+
+class CONTENT_EXPORT IndexedDBConnection {
+ public:
+ IndexedDBConnection(scoped_refptr<IndexedDBDatabase> db,
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks);
+ virtual ~IndexedDBConnection();
+
+ virtual void ForceClose();
+ virtual void Close();
+
+ IndexedDBDatabase* database() { return database_; }
+ IndexedDBDatabaseCallbacks* callbacks() { return callbacks_; }
+
+ private:
+ // Only NULL in unit tests.
+ scoped_refptr<IndexedDBDatabase> database_;
+
+ // The callbacks_ member is cleared when the connection is closed.
+ // May be NULL in unit tests.
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONNECTION_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_context_impl.cc b/chromium/content/browser/indexed_db/indexed_db_context_impl.cc
new file mode 100644
index 00000000000..38decc286f4
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -0,0 +1,539 @@
+// 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 "content/browser/indexed_db/indexed_db_context_impl.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/indexed_db_quota_client.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/indexed_db_info.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/text/bytes_formatting.h"
+#include "webkit/browser/database/database_util.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+#include "webkit/common/database/database_identifier.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using webkit_database::DatabaseUtil;
+
+namespace content {
+const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
+ FILE_PATH_LITERAL("IndexedDB");
+
+static const base::FilePath::CharType kIndexedDBExtension[] =
+ FILE_PATH_LITERAL(".indexeddb");
+
+static const base::FilePath::CharType kLevelDBExtension[] =
+ FILE_PATH_LITERAL(".leveldb");
+
+namespace {
+
+// This may be called after the IndexedDBContext is destroyed.
+void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path,
+ std::vector<GURL>* origins,
+ std::vector<base::FilePath>* file_paths) {
+ // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
+ // if a global handle to it is ever available.
+ if (indexeddb_path.empty())
+ return;
+ base::FileEnumerator file_enumerator(
+ indexeddb_path, false, base::FileEnumerator::DIRECTORIES);
+ for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
+ file_path = file_enumerator.Next()) {
+ if (file_path.Extension() == kLevelDBExtension &&
+ file_path.RemoveExtension().Extension() == kIndexedDBExtension) {
+ std::string origin_id = file_path.BaseName().RemoveExtension()
+ .RemoveExtension().MaybeAsASCII();
+ origins->push_back(webkit_database::GetOriginFromIdentifier(origin_id));
+ if (file_paths)
+ file_paths->push_back(file_path);
+ }
+ }
+}
+
+// This will be called after the IndexedDBContext is destroyed.
+void ClearSessionOnlyOrigins(
+ const base::FilePath& indexeddb_path,
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
+ // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
+ // if a global handle to it is ever available.
+ std::vector<GURL> origins;
+ std::vector<base::FilePath> file_paths;
+ GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths);
+ DCHECK_EQ(origins.size(), file_paths.size());
+ std::vector<base::FilePath>::const_iterator file_path_iter =
+ file_paths.begin();
+ for (std::vector<GURL>::const_iterator iter = origins.begin();
+ iter != origins.end();
+ ++iter, ++file_path_iter) {
+ if (!special_storage_policy->IsStorageSessionOnly(*iter))
+ continue;
+ if (special_storage_policy->IsStorageProtected(*iter))
+ continue;
+ base::DeleteFile(*file_path_iter, true);
+ }
+}
+
+} // namespace
+
+IndexedDBContextImpl::IndexedDBContextImpl(
+ const base::FilePath& data_path,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* task_runner)
+ : force_keep_session_state_(false),
+ special_storage_policy_(special_storage_policy),
+ quota_manager_proxy_(quota_manager_proxy),
+ task_runner_(task_runner) {
+ if (!data_path.empty())
+ data_path_ = data_path.Append(kIndexedDBDirectory);
+ if (quota_manager_proxy) {
+ quota_manager_proxy->RegisterClient(new IndexedDBQuotaClient(this));
+ }
+}
+
+IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (!factory_) {
+ // Prime our cache of origins with existing databases so we can
+ // detect when dbs are newly created.
+ GetOriginSet();
+ factory_ = new IndexedDBFactory();
+ }
+ return factory_;
+}
+
+std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ std::vector<GURL> origins;
+ std::set<GURL>* origins_set = GetOriginSet();
+ for (std::set<GURL>::const_iterator iter = origins_set->begin();
+ iter != origins_set->end();
+ ++iter) {
+ origins.push_back(*iter);
+ }
+ return origins;
+}
+
+std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ std::vector<GURL> origins = GetAllOrigins();
+ std::vector<IndexedDBInfo> result;
+ for (std::vector<GURL>::const_iterator iter = origins.begin();
+ iter != origins.end();
+ ++iter) {
+ const GURL& origin_url = *iter;
+
+ base::FilePath idb_directory = GetFilePath(origin_url);
+ size_t connection_count = GetConnectionCount(origin_url);
+ result.push_back(IndexedDBInfo(origin_url,
+ GetOriginDiskUsage(origin_url),
+ GetOriginLastModified(origin_url),
+ idb_directory,
+ connection_count));
+ }
+ return result;
+}
+
+static bool HostNameComparator(const GURL& i, const GURL& j) {
+ return i.host() < j.host();
+}
+
+ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ std::vector<GURL> origins = GetAllOrigins();
+
+ std::sort(origins.begin(), origins.end(), HostNameComparator);
+
+ scoped_ptr<ListValue> list(new ListValue());
+ for (std::vector<GURL>::const_iterator iter = origins.begin();
+ iter != origins.end();
+ ++iter) {
+ const GURL& origin_url = *iter;
+
+ scoped_ptr<DictionaryValue> info(new DictionaryValue());
+ info->SetString("url", origin_url.spec());
+ info->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url)));
+ info->SetDouble("last_modified",
+ GetOriginLastModified(origin_url).ToJsTime());
+ info->SetString("path", GetFilePath(origin_url).value());
+ info->SetDouble("connection_count", GetConnectionCount(origin_url));
+
+ // This ends up being O(n^2) since we iterate over all open databases
+ // to extract just those in the origin, and we're iterating over all
+ // origins in the outer loop.
+
+ if (factory_) {
+ std::vector<IndexedDBDatabase*> databases =
+ factory_->GetOpenDatabasesForOrigin(
+ webkit_database::GetIdentifierFromOrigin(origin_url));
+ // TODO(jsbell): Sort by name?
+ scoped_ptr<ListValue> database_list(new ListValue());
+
+ for (std::vector<IndexedDBDatabase*>::iterator it = databases.begin();
+ it != databases.end();
+ ++it) {
+
+ const IndexedDBDatabase* db = *it;
+ scoped_ptr<DictionaryValue> db_info(new DictionaryValue());
+
+ db_info->SetString("name", db->name());
+ db_info->SetDouble("pending_opens", db->PendingOpenCount());
+ db_info->SetDouble("pending_upgrades", db->PendingUpgradeCount());
+ db_info->SetDouble("running_upgrades", db->RunningUpgradeCount());
+ db_info->SetDouble("pending_deletes", db->PendingDeleteCount());
+ db_info->SetDouble("connection_count",
+ db->ConnectionCount() - db->PendingUpgradeCount() -
+ db->RunningUpgradeCount());
+
+ scoped_ptr<ListValue> transaction_list(new ListValue());
+ std::vector<const IndexedDBTransaction*> transactions =
+ db->transaction_coordinator().GetTransactions();
+ for (std::vector<const IndexedDBTransaction*>::iterator trans_it =
+ transactions.begin();
+ trans_it != transactions.end();
+ ++trans_it) {
+
+ const IndexedDBTransaction* transaction = *trans_it;
+ scoped_ptr<DictionaryValue> transaction_info(new DictionaryValue());
+
+ const char* kModes[] = { "readonly", "readwrite", "versionchange" };
+ transaction_info->SetString("mode", kModes[transaction->mode()]);
+ transaction_info->SetBoolean("running", transaction->IsRunning());
+
+ scoped_ptr<ListValue> scope(new ListValue());
+ for (std::set<int64>::const_iterator scope_it =
+ transaction->scope().begin();
+ scope_it != transaction->scope().end();
+ ++scope_it) {
+ IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it =
+ db->metadata().object_stores.find(*scope_it);
+ if (it != db->metadata().object_stores.end())
+ scope->AppendString(it->second.name);
+ }
+
+ transaction_info->Set("scope", scope.release());
+ transaction_list->Append(transaction_info.release());
+ }
+ db_info->Set("transactions", transaction_list.release());
+
+ database_list->Append(db_info.release());
+ }
+ info->Set("databases", database_list.release());
+ }
+
+ list->Append(info.release());
+ }
+ return list.release();
+}
+
+int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (data_path_.empty() || !IsInOriginSet(origin_url))
+ return 0;
+ EnsureDiskUsageCacheInitialized(origin_url);
+ return origin_size_map_[origin_url];
+}
+
+base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (data_path_.empty() || !IsInOriginSet(origin_url))
+ return base::Time();
+ base::FilePath idb_directory = GetFilePath(origin_url);
+ base::PlatformFileInfo file_info;
+ if (!file_util::GetFileInfo(idb_directory, &file_info))
+ return base::Time();
+ return file_info.last_modified;
+}
+
+void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ ForceClose(origin_url);
+ if (data_path_.empty() || !IsInOriginSet(origin_url))
+ return;
+
+ base::FilePath idb_directory = GetFilePath(origin_url);
+ EnsureDiskUsageCacheInitialized(origin_url);
+ const bool recursive = true;
+ bool deleted = base::DeleteFile(idb_directory, recursive);
+
+ QueryDiskAndUpdateQuotaUsage(origin_url);
+ if (deleted) {
+ RemoveFromOriginSet(origin_url);
+ origin_size_map_.erase(origin_url);
+ space_available_map_.erase(origin_url);
+ }
+}
+
+void IndexedDBContextImpl::ForceClose(const GURL& origin_url) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (data_path_.empty() || !IsInOriginSet(origin_url))
+ return;
+
+ if (connections_.find(origin_url) != connections_.end()) {
+ ConnectionSet& connections = connections_[origin_url];
+ ConnectionSet::iterator it = connections.begin();
+ while (it != connections.end()) {
+ // Remove before closing so callbacks don't double-erase
+ IndexedDBConnection* connection = *it;
+ connections.erase(it++);
+ connection->ForceClose();
+ }
+ DCHECK_EQ(connections_[origin_url].size(), 0UL);
+ connections_.erase(origin_url);
+ }
+}
+
+size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (data_path_.empty() || !IsInOriginSet(origin_url))
+ return 0;
+
+ if (connections_.find(origin_url) == connections_.end())
+ return 0;
+
+ return connections_[origin_url].size();
+}
+
+base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) {
+ std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
+ return GetIndexedDBFilePath(origin_id);
+}
+
+base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
+ const std::string& origin_id) const {
+ return GetIndexedDBFilePath(origin_id);
+}
+
+void IndexedDBContextImpl::SetTaskRunnerForTesting(
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(!task_runner_);
+ task_runner_ = task_runner;
+}
+
+void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
+ IndexedDBConnection* connection) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ DCHECK_EQ(connections_[origin_url].count(connection), 0UL);
+ if (quota_manager_proxy()) {
+ quota_manager_proxy()->NotifyStorageAccessed(
+ quota::QuotaClient::kIndexedDatabase,
+ origin_url,
+ quota::kStorageTypeTemporary);
+ }
+ connections_[origin_url].insert(connection);
+ if (AddToOriginSet(origin_url)) {
+ // A newly created db, notify the quota system.
+ QueryDiskAndUpdateQuotaUsage(origin_url);
+ } else {
+ EnsureDiskUsageCacheInitialized(origin_url);
+ }
+ QueryAvailableQuota(origin_url);
+}
+
+void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
+ IndexedDBConnection* connection) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ // May not be in the map if connection was forced to close
+ if (connections_.find(origin_url) == connections_.end() ||
+ connections_[origin_url].count(connection) != 1)
+ return;
+ if (quota_manager_proxy()) {
+ quota_manager_proxy()->NotifyStorageAccessed(
+ quota::QuotaClient::kIndexedDatabase,
+ origin_url,
+ quota::kStorageTypeTemporary);
+ }
+ connections_[origin_url].erase(connection);
+ if (connections_[origin_url].size() == 0) {
+ QueryDiskAndUpdateQuotaUsage(origin_url);
+ connections_.erase(origin_url);
+ }
+}
+
+void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
+ DCHECK(connections_.find(origin_url) != connections_.end() &&
+ connections_[origin_url].size() > 0);
+ QueryDiskAndUpdateQuotaUsage(origin_url);
+ QueryAvailableQuota(origin_url);
+}
+
+bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
+ int64 additional_bytes) {
+ if (space_available_map_.find(origin_url) == space_available_map_.end()) {
+ // We haven't heard back from the QuotaManager yet, just let it through.
+ return false;
+ }
+ bool over_quota = additional_bytes > space_available_map_[origin_url];
+ return over_quota;
+}
+
+bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
+ const int kOneAdditionalByte = 1;
+ return WouldBeOverQuota(origin_url, kOneAdditionalByte);
+}
+
+quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
+ return quota_manager_proxy_;
+}
+
+IndexedDBContextImpl::~IndexedDBContextImpl() {
+ if (factory_) {
+ IndexedDBFactory* factory = factory_;
+ factory->AddRef();
+ factory_ = NULL;
+ if (!task_runner_->ReleaseSoon(FROM_HERE, factory)) {
+ factory->Release();
+ }
+ }
+
+ if (data_path_.empty())
+ return;
+
+ if (force_keep_session_state_)
+ return;
+
+ bool has_session_only_databases =
+ special_storage_policy_ &&
+ special_storage_policy_->HasSessionOnlyOrigins();
+
+ // Clearning only session-only databases, and there are none.
+ if (!has_session_only_databases)
+ return;
+
+ TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
+}
+
+base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
+ const std::string& origin_id) const {
+ DCHECK(!data_path_.empty());
+ return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension)
+ .AddExtension(kLevelDBExtension);
+}
+
+int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
+ if (data_path_.empty())
+ return 0;
+ std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
+ base::FilePath file_path = GetIndexedDBFilePath(origin_id);
+ return base::ComputeDirectorySize(file_path);
+}
+
+void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
+ const GURL& origin_url) {
+ if (origin_size_map_.find(origin_url) == origin_size_map_.end())
+ origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
+}
+
+void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
+ const GURL& origin_url) {
+ int64 former_disk_usage = origin_size_map_[origin_url];
+ int64 current_disk_usage = ReadUsageFromDisk(origin_url);
+ int64 difference = current_disk_usage - former_disk_usage;
+ if (difference) {
+ origin_size_map_[origin_url] = current_disk_usage;
+ // quota_manager_proxy() is NULL in unit tests.
+ if (quota_manager_proxy()) {
+ quota_manager_proxy()->NotifyStorageModified(
+ quota::QuotaClient::kIndexedDatabase,
+ origin_url,
+ quota::kStorageTypeTemporary,
+ difference);
+ }
+ }
+}
+
+void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
+ quota::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort)
+ << "status was " << status;
+ if (status == quota::kQuotaErrorAbort) {
+ // We seem to no longer care to wait around for the answer.
+ return;
+ }
+ TaskRunner()->PostTask(FROM_HERE,
+ base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
+ this,
+ origin_url,
+ usage,
+ quota));
+}
+
+void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
+ int64 usage,
+ int64 quota) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ space_available_map_[origin_url] = quota - usage;
+}
+
+void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
+ if (quota_manager_proxy()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
+ }
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
+ return;
+ quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
+ origin_url,
+ quota::kStorageTypeTemporary,
+ base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
+}
+
+std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
+ if (!origin_set_) {
+ origin_set_.reset(new std::set<GURL>);
+ std::vector<GURL> origins;
+ GetAllOriginsAndPaths(data_path_, &origins, NULL);
+ for (std::vector<GURL>::const_iterator iter = origins.begin();
+ iter != origins.end();
+ ++iter) {
+ origin_set_->insert(*iter);
+ }
+ }
+ return origin_set_.get();
+}
+
+void IndexedDBContextImpl::ResetCaches() {
+ origin_set_.reset();
+ origin_size_map_.clear();
+ space_available_map_.clear();
+}
+
+base::TaskRunner* IndexedDBContextImpl::TaskRunner() const {
+ return task_runner_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_context_impl.h b/chromium/content/browser/indexed_db/indexed_db_context_impl.h
new file mode 100644
index 00000000000..3c76465e6bf
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_context_impl.h
@@ -0,0 +1,145 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/public/browser/indexed_db_context.h"
+#include "url/gurl.h"
+#include "webkit/common/quota/quota_types.h"
+
+class GURL;
+
+namespace base {
+class ListValue;
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace content {
+
+class IndexedDBConnection;
+
+class CONTENT_EXPORT IndexedDBContextImpl
+ : NON_EXPORTED_BASE(public IndexedDBContext) {
+ public:
+ // If |data_path| is empty, nothing will be saved to disk.
+ IndexedDBContextImpl(const base::FilePath& data_path,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* task_runner);
+
+ IndexedDBFactory* GetIDBFactory();
+
+ // The indexed db directory.
+ static const base::FilePath::CharType kIndexedDBDirectory[];
+
+ // Disables the exit-time deletion of session-only data.
+ void SetForceKeepSessionState() { force_keep_session_state_ = true; }
+
+ // IndexedDBContext implementation:
+ virtual base::TaskRunner* TaskRunner() const OVERRIDE;
+ virtual std::vector<GURL> GetAllOrigins() OVERRIDE;
+ virtual std::vector<IndexedDBInfo> GetAllOriginsInfo() OVERRIDE;
+ virtual int64 GetOriginDiskUsage(const GURL& origin_url) OVERRIDE;
+ virtual base::Time GetOriginLastModified(const GURL& origin_url) OVERRIDE;
+ virtual void DeleteForOrigin(const GURL& origin_url) OVERRIDE;
+ virtual base::FilePath GetFilePathForTesting(
+ const std::string& origin_id) const OVERRIDE;
+ virtual void SetTaskRunnerForTesting(base::SequencedTaskRunner* task_runner)
+ OVERRIDE;
+
+ // Methods called by IndexedDBDispatcherHost for quota support.
+ void ConnectionOpened(const GURL& origin_url, IndexedDBConnection* db);
+ void ConnectionClosed(const GURL& origin_url, IndexedDBConnection* db);
+ void TransactionComplete(const GURL& origin_url);
+ bool WouldBeOverQuota(const GURL& origin_url, int64 additional_bytes);
+ bool IsOverQuota(const GURL& origin_url);
+
+ quota::QuotaManagerProxy* quota_manager_proxy();
+
+ base::ListValue* GetAllOriginsDetails();
+ void ForceClose(const GURL& origin_url);
+ base::FilePath GetFilePath(const GURL& origin_url);
+ base::FilePath data_path() const { return data_path_; }
+ bool IsInOriginSet(const GURL& origin_url) {
+ std::set<GURL>* set = GetOriginSet();
+ return set->find(origin_url) != set->end();
+ }
+ size_t GetConnectionCount(const GURL& origin_url);
+
+ // For unit tests allow to override the |data_path_|.
+ void set_data_path_for_testing(const base::FilePath& data_path) {
+ data_path_ = data_path;
+ }
+
+ protected:
+ virtual ~IndexedDBContextImpl();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, ClearLocalState);
+ FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, ClearSessionOnlyDatabases);
+ FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, SetForceKeepSessionState);
+ FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, ForceCloseOpenDatabasesOnDelete);
+ friend class IndexedDBQuotaClientTest;
+
+ typedef std::map<GURL, int64> OriginToSizeMap;
+ class IndexedDBGetUsageAndQuotaCallback;
+
+ base::FilePath GetIndexedDBFilePath(const std::string& origin_id) const;
+ int64 ReadUsageFromDisk(const GURL& origin_url) const;
+ void EnsureDiskUsageCacheInitialized(const GURL& origin_url);
+ void QueryDiskAndUpdateQuotaUsage(const GURL& origin_url);
+ void GotUsageAndQuota(const GURL& origin_url,
+ quota::QuotaStatusCode,
+ int64 usage,
+ int64 quota);
+ void GotUpdatedQuota(const GURL& origin_url, int64 usage, int64 quota);
+ void QueryAvailableQuota(const GURL& origin_url);
+
+ std::set<GURL>* GetOriginSet();
+ bool AddToOriginSet(const GURL& origin_url) {
+ return GetOriginSet()->insert(origin_url).second;
+ }
+ void RemoveFromOriginSet(const GURL& origin_url) {
+ GetOriginSet()->erase(origin_url);
+ }
+
+ // Only for testing.
+ void ResetCaches();
+
+ scoped_refptr<IndexedDBFactory> factory_;
+ base::FilePath data_path_;
+ // If true, nothing (not even session-only data) should be deleted on exit.
+ bool force_keep_session_state_;
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_;
+ base::SequencedTaskRunner* task_runner_;
+ scoped_ptr<std::set<GURL> > origin_set_;
+ OriginToSizeMap origin_size_map_;
+ OriginToSizeMap space_available_map_;
+ typedef std::set<IndexedDBConnection*> ConnectionSet;
+ std::map<GURL, ConnectionSet> connections_;
+
+ DISALLOW_COPY_AND_ASSIGN(IndexedDBContextImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_cursor.cc b/chromium/content/browser/indexed_db/indexed_db_cursor.cc
new file mode 100644
index 00000000000..1ee13e7c0b7
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_cursor.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_cursor.h"
+
+#include "base/logging.h"
+#include "content/browser/indexed_db/indexed_db_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_database_error.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+
+namespace content {
+
+class IndexedDBCursor::CursorIterationOperation
+ : public IndexedDBTransaction::Operation {
+ public:
+ CursorIterationOperation(scoped_refptr<IndexedDBCursor> cursor,
+ scoped_ptr<IndexedDBKey> key,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : cursor_(cursor), key_(key.Pass()), callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBCursor> cursor_;
+ scoped_ptr<IndexedDBKey> key_;
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class IndexedDBCursor::CursorAdvanceOperation
+ : public IndexedDBTransaction::Operation {
+ public:
+ CursorAdvanceOperation(scoped_refptr<IndexedDBCursor> cursor,
+ uint32 count,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : cursor_(cursor), count_(count), callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBCursor> cursor_;
+ uint32 count_;
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class IndexedDBCursor::CursorPrefetchIterationOperation
+ : public IndexedDBTransaction::Operation {
+ public:
+ CursorPrefetchIterationOperation(scoped_refptr<IndexedDBCursor> cursor,
+ int number_to_fetch,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : cursor_(cursor),
+ number_to_fetch_(number_to_fetch),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBCursor> cursor_;
+ int number_to_fetch_;
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+IndexedDBCursor::IndexedDBCursor(
+ scoped_ptr<IndexedDBBackingStore::Cursor> cursor,
+ indexed_db::CursorType cursor_type,
+ IndexedDBDatabase::TaskType task_type,
+ IndexedDBTransaction* transaction)
+ : task_type_(task_type),
+ cursor_type_(cursor_type),
+ transaction_(transaction),
+ cursor_(cursor.Pass()),
+ closed_(false) {
+ transaction_->RegisterOpenCursor(this);
+}
+
+IndexedDBCursor::~IndexedDBCursor() {
+ transaction_->UnregisterOpenCursor(this);
+}
+
+void IndexedDBCursor::Continue(scoped_ptr<IndexedDBKey> key,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBCursor::Continue");
+
+ transaction_->ScheduleTask(
+ task_type_, new CursorIterationOperation(this, key.Pass(), callbacks));
+}
+
+void IndexedDBCursor::Advance(uint32 count,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBCursor::Advance");
+
+ transaction_->ScheduleTask(
+ new CursorAdvanceOperation(this, count, callbacks));
+}
+
+void IndexedDBCursor::CursorAdvanceOperation::Perform(
+ IndexedDBTransaction* /*transaction*/) {
+ IDB_TRACE("CursorAdvanceOperation");
+ if (!cursor_->cursor_ || !cursor_->cursor_->Advance(count_)) {
+ cursor_->cursor_.reset();
+ callbacks_->OnSuccess(static_cast<std::string*>(NULL));
+ return;
+ }
+
+ callbacks_->OnSuccess(
+ cursor_->key(), cursor_->primary_key(), cursor_->Value());
+}
+
+void IndexedDBCursor::CursorIterationOperation::Perform(
+ IndexedDBTransaction* /*transaction*/) {
+ IDB_TRACE("CursorIterationOperation");
+ if (!cursor_->cursor_ ||
+ !cursor_->cursor_->Continue(key_.get(),
+ IndexedDBBackingStore::Cursor::SEEK)) {
+ cursor_->cursor_.reset();
+ callbacks_->OnSuccess(static_cast<std::string*>(NULL));
+ return;
+ }
+
+ callbacks_->OnSuccess(
+ cursor_->key(), cursor_->primary_key(), cursor_->Value());
+}
+
+void IndexedDBCursor::PrefetchContinue(
+ int number_to_fetch,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBCursor::PrefetchContinue");
+
+ transaction_->ScheduleTask(
+ task_type_,
+ new CursorPrefetchIterationOperation(this, number_to_fetch, callbacks));
+}
+
+void IndexedDBCursor::CursorPrefetchIterationOperation::Perform(
+ IndexedDBTransaction* /*transaction*/) {
+ IDB_TRACE("CursorPrefetchIterationOperation");
+
+ std::vector<IndexedDBKey> found_keys;
+ std::vector<IndexedDBKey> found_primary_keys;
+ std::vector<std::string> found_values;
+
+ if (cursor_->cursor_)
+ cursor_->saved_cursor_.reset(cursor_->cursor_->Clone());
+ const size_t max_size_estimate = 10 * 1024 * 1024;
+ size_t size_estimate = 0;
+
+ for (int i = 0; i < number_to_fetch_; ++i) {
+ if (!cursor_->cursor_ || !cursor_->cursor_->Continue()) {
+ cursor_->cursor_.reset();
+ break;
+ }
+
+ found_keys.push_back(cursor_->cursor_->key());
+ found_primary_keys.push_back(cursor_->cursor_->primary_key());
+
+ switch (cursor_->cursor_type_) {
+ case indexed_db::CURSOR_KEY_ONLY:
+ found_values.push_back(std::string());
+ break;
+ case indexed_db::CURSOR_KEY_AND_VALUE: {
+ std::string value;
+ value.swap(*cursor_->cursor_->Value());
+ size_estimate += value.size();
+ found_values.push_back(value);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ size_estimate += cursor_->cursor_->key().size_estimate();
+ size_estimate += cursor_->cursor_->primary_key().size_estimate();
+
+ if (size_estimate > max_size_estimate)
+ break;
+ }
+
+ if (!found_keys.size()) {
+ callbacks_->OnSuccess(static_cast<std::string*>(NULL));
+ return;
+ }
+
+ callbacks_->OnSuccessWithPrefetch(
+ found_keys, found_primary_keys, found_values);
+}
+
+void IndexedDBCursor::PrefetchReset(int used_prefetches, int) {
+ IDB_TRACE("IndexedDBCursor::PrefetchReset");
+ cursor_.swap(saved_cursor_);
+ saved_cursor_.reset();
+
+ if (closed_)
+ return;
+ if (cursor_) {
+ for (int i = 0; i < used_prefetches; ++i) {
+ bool ok = cursor_->Continue();
+ DCHECK(ok);
+ }
+ }
+}
+
+void IndexedDBCursor::Close() {
+ IDB_TRACE("IndexedDBCursor::Close");
+ closed_ = true;
+ cursor_.reset();
+ saved_cursor_.reset();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_cursor.h b/chromium/content/browser/indexed_db/indexed_db_cursor.h
new file mode 100644
index 00000000000..2cf9d4f3ecc
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_cursor.h
@@ -0,0 +1,67 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+
+namespace content {
+
+class IndexedDBTransaction;
+
+class CONTENT_EXPORT IndexedDBCursor
+ : NON_EXPORTED_BASE(public base::RefCounted<IndexedDBCursor>) {
+ public:
+ IndexedDBCursor(scoped_ptr<IndexedDBBackingStore::Cursor> cursor,
+ indexed_db::CursorType cursor_type,
+ IndexedDBDatabase::TaskType task_type,
+ IndexedDBTransaction* transaction);
+
+ void Advance(uint32 count, scoped_refptr<IndexedDBCallbacks> callbacks);
+ void Continue(scoped_ptr<IndexedDBKey> key,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void PrefetchContinue(int number_to_fetch,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void PrefetchReset(int used_prefetches, int unused_prefetches);
+
+ const IndexedDBKey& key() const { return cursor_->key(); }
+ const IndexedDBKey& primary_key() const { return cursor_->primary_key(); }
+ std::string* Value() const {
+ return (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) ? NULL
+ : cursor_->Value();
+ }
+ void Close();
+
+ private:
+ friend class base::RefCounted<IndexedDBCursor>;
+
+ ~IndexedDBCursor();
+
+ class CursorIterationOperation;
+ class CursorAdvanceOperation;
+ class CursorPrefetchIterationOperation;
+
+ IndexedDBDatabase::TaskType task_type_;
+ indexed_db::CursorType cursor_type_;
+ const scoped_refptr<IndexedDBTransaction> transaction_;
+
+ // Must be destroyed before transaction_.
+ scoped_ptr<IndexedDBBackingStore::Cursor> cursor_;
+ // Must be destroyed before transaction_.
+ scoped_ptr<IndexedDBBackingStore::Cursor> saved_cursor_;
+
+ bool closed_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CURSOR_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_database.cc b/chromium/content/browser/indexed_db/indexed_db_database.cc
new file mode 100644
index 00000000000..9eb577448df
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database.cc
@@ -0,0 +1,1951 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_database.h"
+
+#include <math.h>
+#include <set>
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/indexed_db_index_writer.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
+
+using base::Int64ToString16;
+using WebKit::WebIDBKeyTypeNumber;
+
+namespace content {
+
+class CreateObjectStoreOperation : public IndexedDBTransaction::Operation {
+ public:
+ CreateObjectStoreOperation(
+ scoped_refptr<IndexedDBBackingStore> backing_store,
+ const IndexedDBObjectStoreMetadata& object_store_metadata)
+ : backing_store_(backing_store),
+ object_store_metadata_(object_store_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const IndexedDBObjectStoreMetadata object_store_metadata_;
+};
+
+class DeleteObjectStoreOperation : public IndexedDBTransaction::Operation {
+ public:
+ DeleteObjectStoreOperation(
+ scoped_refptr<IndexedDBBackingStore> backing_store,
+ const IndexedDBObjectStoreMetadata& object_store_metadata)
+ : backing_store_(backing_store),
+ object_store_metadata_(object_store_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const IndexedDBObjectStoreMetadata object_store_metadata_;
+};
+
+class IndexedDBDatabase::VersionChangeOperation
+ : public IndexedDBTransaction::Operation {
+ public:
+ VersionChangeOperation(scoped_refptr<IndexedDBDatabase> database,
+ int64 transaction_id,
+ int64 version,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ WebKit::WebIDBCallbacks::DataLoss data_loss)
+ : database_(database),
+ transaction_id_(transaction_id),
+ version_(version),
+ callbacks_(callbacks),
+ connection_(connection.Pass()),
+ data_loss_(data_loss) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBDatabase> database_;
+ int64 transaction_id_;
+ int64 version_;
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+ scoped_ptr<IndexedDBConnection> connection_;
+ WebKit::WebIDBCallbacks::DataLoss data_loss_;
+};
+
+class CreateObjectStoreAbortOperation : public IndexedDBTransaction::Operation {
+ public:
+ CreateObjectStoreAbortOperation(scoped_refptr<IndexedDBDatabase> database,
+ int64 object_store_id)
+ : database_(database), object_store_id_(object_store_id) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBDatabase> database_;
+ const int64 object_store_id_;
+};
+
+class DeleteObjectStoreAbortOperation : public IndexedDBTransaction::Operation {
+ public:
+ DeleteObjectStoreAbortOperation(
+ scoped_refptr<IndexedDBDatabase> database,
+ const IndexedDBObjectStoreMetadata& object_store_metadata)
+ : database_(database), object_store_metadata_(object_store_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBDatabase> database_;
+ IndexedDBObjectStoreMetadata object_store_metadata_;
+};
+
+class IndexedDBDatabase::VersionChangeAbortOperation
+ : public IndexedDBTransaction::Operation {
+ public:
+ VersionChangeAbortOperation(scoped_refptr<IndexedDBDatabase> database,
+ const string16& previous_version,
+ int64 previous_int_version)
+ : database_(database),
+ previous_version_(previous_version),
+ previous_int_version_(previous_int_version) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBDatabase> database_;
+ string16 previous_version_;
+ int64 previous_int_version_;
+};
+
+class CreateIndexOperation : public IndexedDBTransaction::Operation {
+ public:
+ CreateIndexOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 object_store_id,
+ const IndexedDBIndexMetadata& index_metadata)
+ : backing_store_(backing_store),
+ object_store_id_(object_store_id),
+ index_metadata_(index_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 object_store_id_;
+ const IndexedDBIndexMetadata index_metadata_;
+};
+
+class DeleteIndexOperation : public IndexedDBTransaction::Operation {
+ public:
+ DeleteIndexOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 object_store_id,
+ const IndexedDBIndexMetadata& index_metadata)
+ : backing_store_(backing_store),
+ object_store_id_(object_store_id),
+ index_metadata_(index_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 object_store_id_;
+ const IndexedDBIndexMetadata index_metadata_;
+};
+
+class CreateIndexAbortOperation : public IndexedDBTransaction::Operation {
+ public:
+ CreateIndexAbortOperation(scoped_refptr<IndexedDBDatabase> database,
+ int64 object_store_id,
+ int64 index_id)
+ : database_(database),
+ object_store_id_(object_store_id),
+ index_id_(index_id) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBDatabase> database_;
+ const int64 object_store_id_;
+ const int64 index_id_;
+};
+
+class DeleteIndexAbortOperation : public IndexedDBTransaction::Operation {
+ public:
+ DeleteIndexAbortOperation(scoped_refptr<IndexedDBDatabase> database,
+ int64 object_store_id,
+ const IndexedDBIndexMetadata& index_metadata)
+ : database_(database),
+ object_store_id_(object_store_id),
+ index_metadata_(index_metadata) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBDatabase> database_;
+ const int64 object_store_id_;
+ const IndexedDBIndexMetadata index_metadata_;
+};
+
+class GetOperation : public IndexedDBTransaction::Operation {
+ public:
+ GetOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyPath& key_path,
+ const bool auto_increment,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ indexed_db::CursorType cursor_type,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(index_id),
+ key_path_(key_path),
+ auto_increment_(auto_increment),
+ key_range_(key_range.Pass()),
+ cursor_type_(cursor_type),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const int64 object_store_id_;
+ const int64 index_id_;
+ const IndexedDBKeyPath key_path_;
+ const bool auto_increment_;
+ const scoped_ptr<IndexedDBKeyRange> key_range_;
+ const indexed_db::CursorType cursor_type_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class PutOperation : public IndexedDBTransaction::Operation {
+ public:
+ PutOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ const IndexedDBObjectStoreMetadata& object_store,
+ std::string* value,
+ scoped_ptr<IndexedDBKey> key,
+ IndexedDBDatabase::PutMode put_mode,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexedDBDatabase::IndexKeys>& index_keys)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_(object_store),
+ key_(key.Pass()),
+ put_mode_(put_mode),
+ callbacks_(callbacks),
+ index_ids_(index_ids),
+ index_keys_(index_keys) {
+ value_.swap(*value);
+ }
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const IndexedDBObjectStoreMetadata object_store_;
+ std::string value_;
+ scoped_ptr<IndexedDBKey> key_;
+ const IndexedDBDatabase::PutMode put_mode_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+ const std::vector<int64> index_ids_;
+ const std::vector<IndexedDBDatabase::IndexKeys> index_keys_;
+};
+
+class SetIndexesReadyOperation : public IndexedDBTransaction::Operation {
+ public:
+ explicit SetIndexesReadyOperation(size_t index_count)
+ : index_count_(index_count) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const size_t index_count_;
+};
+
+class OpenCursorOperation : public IndexedDBTransaction::Operation {
+ public:
+ OpenCursorOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ indexed_db::CursorDirection direction,
+ indexed_db::CursorType cursor_type,
+ IndexedDBDatabase::TaskType task_type,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(index_id),
+ key_range_(key_range.Pass()),
+ direction_(direction),
+ cursor_type_(cursor_type),
+ task_type_(task_type),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const int64 object_store_id_;
+ const int64 index_id_;
+ const scoped_ptr<IndexedDBKeyRange> key_range_;
+ const indexed_db::CursorDirection direction_;
+ const indexed_db::CursorType cursor_type_;
+ const IndexedDBDatabase::TaskType task_type_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class CountOperation : public IndexedDBTransaction::Operation {
+ public:
+ CountOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(index_id),
+ key_range_(key_range.Pass()),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const int64 object_store_id_;
+ const int64 index_id_;
+ const scoped_ptr<IndexedDBKeyRange> key_range_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class DeleteRangeOperation : public IndexedDBTransaction::Operation {
+ public:
+ DeleteRangeOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ int64 object_store_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_id_(object_store_id),
+ key_range_(key_range.Pass()),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const int64 object_store_id_;
+ const scoped_ptr<IndexedDBKeyRange> key_range_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+class ClearOperation : public IndexedDBTransaction::Operation {
+ public:
+ ClearOperation(scoped_refptr<IndexedDBBackingStore> backing_store,
+ int64 database_id,
+ int64 object_store_id,
+ scoped_refptr<IndexedDBCallbacks> callbacks)
+ : backing_store_(backing_store),
+ database_id_(database_id),
+ object_store_id_(object_store_id),
+ callbacks_(callbacks) {}
+ virtual void Perform(IndexedDBTransaction* transaction) OVERRIDE;
+
+ private:
+ const scoped_refptr<IndexedDBBackingStore> backing_store_;
+ const int64 database_id_;
+ const int64 object_store_id_;
+ const scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+// PendingOpenCall has a scoped_refptr<IndexedDBDatabaseCallbacks> because it
+// isn't a connection yet.
+class IndexedDBDatabase::PendingOpenCall {
+ public:
+ PendingOpenCall(scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ int64 transaction_id,
+ int64 version)
+ : callbacks_(callbacks),
+ database_callbacks_(database_callbacks),
+ version_(version),
+ transaction_id_(transaction_id) {}
+ scoped_refptr<IndexedDBCallbacks> Callbacks() { return callbacks_; }
+ scoped_refptr<IndexedDBDatabaseCallbacks> DatabaseCallbacks() {
+ return database_callbacks_;
+ }
+ int64 Version() { return version_; }
+ int64 TransactionId() const { return transaction_id_; }
+
+ private:
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks_;
+ int64 version_;
+ const int64 transaction_id_;
+};
+
+// PendingUpgradeCall has a scoped_ptr<IndexedDBConnection> because it owns the
+// in-progress connection.
+class IndexedDBDatabase::PendingUpgradeCall {
+ public:
+ PendingUpgradeCall(scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 version)
+ : callbacks_(callbacks),
+ connection_(connection.Pass()),
+ version_(version),
+ transaction_id_(transaction_id) {}
+ scoped_refptr<IndexedDBCallbacks> Callbacks() { return callbacks_; }
+ scoped_ptr<IndexedDBConnection> Connection() { return connection_.Pass(); }
+ int64 Version() { return version_; }
+ int64 TransactionId() const { return transaction_id_; }
+
+ private:
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+ scoped_ptr<IndexedDBConnection> connection_;
+ int64 version_;
+ const int64 transaction_id_;
+};
+
+// PendingSuccessCall has a IndexedDBConnection* because the connection is now
+// owned elsewhere, but we need to cancel the success call if that connection
+// closes before it is sent.
+class IndexedDBDatabase::PendingSuccessCall {
+ public:
+ PendingSuccessCall(scoped_refptr<IndexedDBCallbacks> callbacks,
+ IndexedDBConnection* connection,
+ int64 transaction_id,
+ int64 version)
+ : callbacks_(callbacks),
+ connection_(connection),
+ version_(version),
+ transaction_id_(transaction_id) {}
+ scoped_refptr<IndexedDBCallbacks> Callbacks() { return callbacks_; }
+ IndexedDBConnection* Connection() { return connection_; }
+ int64 Version() { return version_; }
+ int64 TransactionId() const { return transaction_id_; }
+
+ private:
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+ IndexedDBConnection* connection_;
+ int64 version_;
+ const int64 transaction_id_;
+};
+
+class IndexedDBDatabase::PendingDeleteCall {
+ public:
+ explicit PendingDeleteCall(scoped_refptr<IndexedDBCallbacks> callbacks)
+ : callbacks_(callbacks) {}
+ scoped_refptr<IndexedDBCallbacks> Callbacks() { return callbacks_; }
+
+ private:
+ scoped_refptr<IndexedDBCallbacks> callbacks_;
+};
+
+scoped_refptr<IndexedDBDatabase> IndexedDBDatabase::Create(
+ const string16& name,
+ IndexedDBBackingStore* database,
+ IndexedDBFactory* factory,
+ const Identifier& unique_identifier) {
+ scoped_refptr<IndexedDBDatabase> backend =
+ new IndexedDBDatabase(name, database, factory, unique_identifier);
+ if (!backend->OpenInternal())
+ return 0;
+ return backend;
+}
+
+namespace {
+const base::string16::value_type kNoStringVersion[] = {0};
+
+template <typename T, typename U>
+bool Contains(const T& container, const U& item) {
+ return container.find(item) != container.end();
+}
+}
+
+IndexedDBDatabase::IndexedDBDatabase(
+ const string16& name,
+ IndexedDBBackingStore* backing_store,
+ IndexedDBFactory* factory,
+ const Identifier& unique_identifier)
+ : backing_store_(backing_store),
+ metadata_(name,
+ kInvalidId,
+ kNoStringVersion,
+ IndexedDBDatabaseMetadata::NO_INT_VERSION,
+ kInvalidId),
+ identifier_(unique_identifier),
+ factory_(factory),
+ running_version_change_transaction_(NULL),
+ closing_connection_(false) {
+ DCHECK(!metadata_.name.empty());
+}
+
+void IndexedDBDatabase::AddObjectStore(
+ const IndexedDBObjectStoreMetadata& object_store,
+ int64 new_max_object_store_id) {
+ DCHECK(metadata_.object_stores.find(object_store.id) ==
+ metadata_.object_stores.end());
+ if (new_max_object_store_id != IndexedDBObjectStoreMetadata::kInvalidId) {
+ DCHECK_LT(metadata_.max_object_store_id, new_max_object_store_id);
+ metadata_.max_object_store_id = new_max_object_store_id;
+ }
+ metadata_.object_stores[object_store.id] = object_store;
+}
+
+void IndexedDBDatabase::RemoveObjectStore(int64 object_store_id) {
+ DCHECK(metadata_.object_stores.find(object_store_id) !=
+ metadata_.object_stores.end());
+ metadata_.object_stores.erase(object_store_id);
+}
+
+void IndexedDBDatabase::AddIndex(int64 object_store_id,
+ const IndexedDBIndexMetadata& index,
+ int64 new_max_index_id) {
+ DCHECK(metadata_.object_stores.find(object_store_id) !=
+ metadata_.object_stores.end());
+ IndexedDBObjectStoreMetadata object_store =
+ metadata_.object_stores[object_store_id];
+
+ DCHECK(object_store.indexes.find(index.id) == object_store.indexes.end());
+ object_store.indexes[index.id] = index;
+ if (new_max_index_id != IndexedDBIndexMetadata::kInvalidId) {
+ DCHECK_LT(object_store.max_index_id, new_max_index_id);
+ object_store.max_index_id = new_max_index_id;
+ }
+ metadata_.object_stores[object_store_id] = object_store;
+}
+
+void IndexedDBDatabase::RemoveIndex(int64 object_store_id, int64 index_id) {
+ DCHECK(metadata_.object_stores.find(object_store_id) !=
+ metadata_.object_stores.end());
+ IndexedDBObjectStoreMetadata object_store =
+ metadata_.object_stores[object_store_id];
+
+ DCHECK(object_store.indexes.find(index_id) != object_store.indexes.end());
+ object_store.indexes.erase(index_id);
+ metadata_.object_stores[object_store_id] = object_store;
+}
+
+bool IndexedDBDatabase::OpenInternal() {
+ bool success = false;
+ bool ok = backing_store_->GetIDBDatabaseMetaData(
+ metadata_.name, &metadata_, &success);
+ DCHECK(success == (metadata_.id != kInvalidId)) << "success = " << success
+ << " id = " << metadata_.id;
+ if (!ok)
+ return false;
+ if (success)
+ return backing_store_->GetObjectStores(metadata_.id,
+ &metadata_.object_stores);
+
+ return backing_store_->CreateIDBDatabaseMetaData(
+ metadata_.name, metadata_.version, metadata_.int_version, &metadata_.id);
+}
+
+IndexedDBDatabase::~IndexedDBDatabase() {
+ DCHECK(transactions_.empty());
+ DCHECK(pending_open_calls_.empty());
+ DCHECK(pending_delete_calls_.empty());
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBDatabase::BackingStore() const {
+ return backing_store_;
+}
+
+IndexedDBTransaction* IndexedDBDatabase::GetTransaction(
+ int64 transaction_id) const {
+ TransactionMap::const_iterator trans_iterator =
+ transactions_.find(transaction_id);
+ if (trans_iterator == transactions_.end())
+ return NULL;
+ return trans_iterator->second;
+}
+
+bool IndexedDBDatabase::ValidateObjectStoreId(int64 object_store_id) const {
+ if (!Contains(metadata_.object_stores, object_store_id)) {
+ DLOG(ERROR) << "Invalid object_store_id";
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBDatabase::ValidateObjectStoreIdAndIndexId(int64 object_store_id,
+ int64 index_id) const {
+ if (!ValidateObjectStoreId(object_store_id))
+ return false;
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores.find(object_store_id)->second;
+ if (!Contains(object_store_metadata.indexes, index_id)) {
+ DLOG(ERROR) << "Invalid index_id";
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBDatabase::ValidateObjectStoreIdAndOptionalIndexId(
+ int64 object_store_id,
+ int64 index_id) const {
+ if (!ValidateObjectStoreId(object_store_id))
+ return false;
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores.find(object_store_id)->second;
+ if (index_id != IndexedDBIndexMetadata::kInvalidId &&
+ !Contains(object_store_metadata.indexes, index_id)) {
+ DLOG(ERROR) << "Invalid index_id";
+ return false;
+ }
+ return true;
+}
+
+bool IndexedDBDatabase::ValidateObjectStoreIdAndNewIndexId(
+ int64 object_store_id,
+ int64 index_id) const {
+ if (!ValidateObjectStoreId(object_store_id))
+ return false;
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores.find(object_store_id)->second;
+ if (Contains(object_store_metadata.indexes, index_id)) {
+ DLOG(ERROR) << "Invalid index_id";
+ return false;
+ }
+ return true;
+}
+
+void IndexedDBDatabase::CreateObjectStore(int64 transaction_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment) {
+ IDB_TRACE("IndexedDBDatabase::CreateObjectStore");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ if (Contains(metadata_.object_stores, object_store_id)) {
+ DLOG(ERROR) << "Invalid object_store_id";
+ return;
+ }
+
+ IndexedDBObjectStoreMetadata object_store_metadata(
+ name,
+ object_store_id,
+ key_path,
+ auto_increment,
+ IndexedDBDatabase::kMinimumIndexId);
+
+ transaction->ScheduleTask(
+ new CreateObjectStoreOperation(backing_store_, object_store_metadata),
+ new CreateObjectStoreAbortOperation(this, object_store_id));
+
+ AddObjectStore(object_store_metadata, object_store_id);
+}
+
+void CreateObjectStoreOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("CreateObjectStoreOperation");
+ if (!backing_store_->CreateObjectStore(
+ transaction->BackingStoreTransaction(),
+ transaction->database()->id(),
+ object_store_metadata_.id,
+ object_store_metadata_.name,
+ object_store_metadata_.key_path,
+ object_store_metadata_.auto_increment)) {
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16("Internal error creating object store '") +
+ object_store_metadata_.name + ASCIIToUTF16("'.")));
+ return;
+ }
+}
+
+void IndexedDBDatabase::DeleteObjectStore(int64 transaction_id,
+ int64 object_store_id) {
+ IDB_TRACE("IndexedDBDatabase::DeleteObjectStore");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ if (!ValidateObjectStoreId(object_store_id))
+ return;
+
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores[object_store_id];
+
+ transaction->ScheduleTask(
+ new DeleteObjectStoreOperation(backing_store_, object_store_metadata),
+ new DeleteObjectStoreAbortOperation(this, object_store_metadata));
+ RemoveObjectStore(object_store_id);
+}
+
+void IndexedDBDatabase::CreateIndex(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool unique,
+ bool multi_entry) {
+ IDB_TRACE("IndexedDBDatabase::CreateIndex");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ if (!ValidateObjectStoreIdAndNewIndexId(object_store_id, index_id))
+ return;
+ const IndexedDBIndexMetadata index_metadata(
+ name, index_id, key_path, unique, multi_entry);
+
+ transaction->ScheduleTask(
+ new CreateIndexOperation(backing_store_, object_store_id, index_metadata),
+ new CreateIndexAbortOperation(this, object_store_id, index_id));
+
+ AddIndex(object_store_id, index_metadata, index_id);
+}
+
+void CreateIndexOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("CreateIndexOperation");
+ if (!backing_store_->CreateIndex(transaction->BackingStoreTransaction(),
+ transaction->database()->id(),
+ object_store_id_,
+ index_metadata_.id,
+ index_metadata_.name,
+ index_metadata_.key_path,
+ index_metadata_.unique,
+ index_metadata_.multi_entry)) {
+ string16 error_string = ASCIIToUTF16("Internal error creating index '") +
+ index_metadata_.name + ASCIIToUTF16("'.");
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError, error_string));
+ return;
+ }
+}
+
+void CreateIndexAbortOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("CreateIndexAbortOperation");
+ DCHECK(!transaction);
+ database_->RemoveIndex(object_store_id_, index_id_);
+}
+
+void IndexedDBDatabase::DeleteIndex(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id) {
+ IDB_TRACE("IndexedDBDatabase::DeleteIndex");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ if (!ValidateObjectStoreIdAndIndexId(object_store_id, index_id))
+ return;
+ const IndexedDBIndexMetadata& index_metadata =
+ metadata_.object_stores[object_store_id].indexes[index_id];
+
+ transaction->ScheduleTask(
+ new DeleteIndexOperation(backing_store_, object_store_id, index_metadata),
+ new DeleteIndexAbortOperation(this, object_store_id, index_metadata));
+
+ RemoveIndex(object_store_id, index_id);
+}
+
+void DeleteIndexOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("DeleteIndexOperation");
+ bool ok = backing_store_->DeleteIndex(transaction->BackingStoreTransaction(),
+ transaction->database()->id(),
+ object_store_id_,
+ index_metadata_.id);
+ if (!ok) {
+ string16 error_string = ASCIIToUTF16("Internal error deleting index '") +
+ index_metadata_.name + ASCIIToUTF16("'.");
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError, error_string));
+ }
+}
+
+void DeleteIndexAbortOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("DeleteIndexAbortOperation");
+ DCHECK(!transaction);
+ database_->AddIndex(
+ object_store_id_, index_metadata_, IndexedDBIndexMetadata::kInvalidId);
+}
+
+void IndexedDBDatabase::Commit(int64 transaction_id) {
+ // The frontend suggests that we commit, but we may have previously initiated
+ // an abort, and so have disposed of the transaction. on_abort has already
+ // been dispatched to the frontend, so it will find out about that
+ // asynchronously.
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (transaction)
+ transaction->Commit();
+}
+
+void IndexedDBDatabase::Abort(int64 transaction_id) {
+ // If the transaction is unknown, then it has already been aborted by the
+ // backend before this call so it is safe to ignore it.
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (transaction)
+ transaction->Abort();
+}
+
+void IndexedDBDatabase::Abort(int64 transaction_id,
+ const IndexedDBDatabaseError& error) {
+ // If the transaction is unknown, then it has already been aborted by the
+ // backend before this call so it is safe to ignore it.
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (transaction)
+ transaction->Abort(error);
+}
+
+void IndexedDBDatabase::Get(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ bool key_only,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBDatabase::Get");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+
+ if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
+ return;
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores[object_store_id];
+
+ transaction->ScheduleTask(new GetOperation(
+ backing_store_,
+ metadata_.id,
+ object_store_id,
+ index_id,
+ object_store_metadata.key_path,
+ object_store_metadata.auto_increment,
+ key_range.Pass(),
+ key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE,
+ callbacks));
+}
+
+void GetOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("GetOperation");
+
+ const IndexedDBKey* key;
+
+ scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
+ if (key_range_->IsOnlyKey()) {
+ key = &key_range_->lower();
+ } else {
+ if (index_id_ == IndexedDBIndexMetadata::kInvalidId) {
+ DCHECK_NE(cursor_type_, indexed_db::CURSOR_KEY_ONLY);
+ // ObjectStore Retrieval Operation
+ backing_store_cursor = backing_store_->OpenObjectStoreCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ } else if (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) {
+ // Index Value Retrieval Operation
+ backing_store_cursor = backing_store_->OpenIndexKeyCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ } else {
+ // Index Referenced Value Retrieval Operation
+ backing_store_cursor = backing_store_->OpenIndexCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ }
+
+ if (!backing_store_cursor) {
+ callbacks_->OnSuccess();
+ return;
+ }
+
+ key = &backing_store_cursor->key();
+ }
+
+ scoped_ptr<IndexedDBKey> primary_key;
+ bool ok;
+ if (index_id_ == IndexedDBIndexMetadata::kInvalidId) {
+ // Object Store Retrieval Operation
+ std::string value;
+ ok = backing_store_->GetRecord(transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *key,
+ &value);
+ if (!ok) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error in GetRecord."));
+ return;
+ }
+
+ if (value.empty()) {
+ callbacks_->OnSuccess();
+ return;
+ }
+
+ if (auto_increment_ && !key_path_.IsNull()) {
+ callbacks_->OnSuccess(&value, *key, key_path_);
+ return;
+ }
+
+ callbacks_->OnSuccess(&value);
+ return;
+ }
+
+ // From here we are dealing only with indexes.
+ ok = backing_store_->GetPrimaryKeyViaIndex(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key,
+ &primary_key);
+ if (!ok) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error in GetPrimaryKeyViaIndex."));
+ return;
+ }
+ if (!primary_key) {
+ callbacks_->OnSuccess();
+ return;
+ }
+ if (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) {
+ // Index Value Retrieval Operation
+ callbacks_->OnSuccess(*primary_key);
+ return;
+ }
+
+ // Index Referenced Value Retrieval Operation
+ std::string value;
+ ok = backing_store_->GetRecord(transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *primary_key,
+ &value);
+ if (!ok) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error in GetRecord."));
+ return;
+ }
+
+ if (value.empty()) {
+ callbacks_->OnSuccess();
+ return;
+ }
+ if (auto_increment_ && !key_path_.IsNull()) {
+ callbacks_->OnSuccess(&value, *primary_key, key_path_);
+ return;
+ }
+ callbacks_->OnSuccess(&value);
+}
+
+static scoped_ptr<IndexedDBKey> GenerateKey(
+ scoped_refptr<IndexedDBBackingStore> backing_store,
+ scoped_refptr<IndexedDBTransaction> transaction,
+ int64 database_id,
+ int64 object_store_id) {
+ const int64 max_generator_value =
+ 9007199254740992LL; // Maximum integer storable as ECMAScript number.
+ int64 current_number;
+ bool ok = backing_store->GetKeyGeneratorCurrentNumber(
+ transaction->BackingStoreTransaction(),
+ database_id,
+ object_store_id,
+ &current_number);
+ if (!ok) {
+ LOG(ERROR) << "Failed to GetKeyGeneratorCurrentNumber";
+ return make_scoped_ptr(new IndexedDBKey());
+ }
+ if (current_number < 0 || current_number > max_generator_value)
+ return make_scoped_ptr(new IndexedDBKey());
+
+ return make_scoped_ptr(
+ new IndexedDBKey(current_number, WebIDBKeyTypeNumber));
+}
+
+static bool UpdateKeyGenerator(
+ scoped_refptr<IndexedDBBackingStore> backing_store,
+ scoped_refptr<IndexedDBTransaction> transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey* key,
+ bool check_current) {
+ DCHECK(key);
+ DCHECK_EQ(WebIDBKeyTypeNumber, key->type());
+ return backing_store->MaybeUpdateKeyGeneratorCurrentNumber(
+ transaction->BackingStoreTransaction(),
+ database_id,
+ object_store_id,
+ static_cast<int64>(floor(key->number())) + 1,
+ check_current);
+}
+
+void IndexedDBDatabase::Put(int64 transaction_id,
+ int64 object_store_id,
+ std::string* value,
+ scoped_ptr<IndexedDBKey> key,
+ PutMode put_mode,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexKeys>& index_keys) {
+ IDB_TRACE("IndexedDBDatabase::Put");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);
+
+ if (!ValidateObjectStoreId(object_store_id))
+ return;
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores[object_store_id];
+
+ DCHECK(key);
+ DCHECK(object_store_metadata.auto_increment || key->IsValid());
+ transaction->ScheduleTask(new PutOperation(backing_store_,
+ id(),
+ object_store_metadata,
+ value,
+ key.Pass(),
+ put_mode,
+ callbacks,
+ index_ids,
+ index_keys));
+}
+
+void PutOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("PutOperation");
+ DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);
+ DCHECK_EQ(index_ids_.size(), index_keys_.size());
+ bool key_was_generated = false;
+
+ scoped_ptr<IndexedDBKey> key;
+ if (put_mode_ != IndexedDBDatabase::CURSOR_UPDATE &&
+ object_store_.auto_increment && !key_->IsValid()) {
+ scoped_ptr<IndexedDBKey> auto_inc_key = GenerateKey(
+ backing_store_, transaction, database_id_, object_store_.id);
+ key_was_generated = true;
+ if (!auto_inc_key->IsValid()) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionConstraintError,
+ "Maximum key generator value reached."));
+ return;
+ }
+ key = auto_inc_key.Pass();
+ } else {
+ key = key_.Pass();
+ }
+
+ DCHECK(key->IsValid());
+
+ IndexedDBBackingStore::RecordIdentifier record_identifier;
+ if (put_mode_ == IndexedDBDatabase::ADD_ONLY) {
+ bool found = false;
+ bool ok = backing_store_->KeyExistsInObjectStore(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_.id,
+ *key.get(),
+ &record_identifier,
+ &found);
+ if (!ok) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error checking key existence."));
+ return;
+ }
+ if (found) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionConstraintError,
+ "Key already exists in the object store."));
+ return;
+ }
+ }
+
+ ScopedVector<IndexWriter> index_writers;
+ string16 error_message;
+ bool obeys_constraints = false;
+ bool backing_store_success = MakeIndexWriters(transaction,
+ backing_store_,
+ database_id_,
+ object_store_,
+ *key,
+ key_was_generated,
+ index_ids_,
+ index_keys_,
+ &index_writers,
+ &error_message,
+ &obeys_constraints);
+ if (!backing_store_success) {
+ callbacks_->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error: backing store error updating index keys."));
+ return;
+ }
+ if (!obeys_constraints) {
+ callbacks_->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionConstraintError, error_message));
+ return;
+ }
+
+ // Before this point, don't do any mutation. After this point, rollback the
+ // transaction in case of error.
+ backing_store_success =
+ backing_store_->PutRecord(transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_.id,
+ *key.get(),
+ value_,
+ &record_identifier);
+ if (!backing_store_success) {
+ callbacks_->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error: backing store error performing put/add."));
+ return;
+ }
+
+ for (size_t i = 0; i < index_writers.size(); ++i) {
+ IndexWriter* index_writer = index_writers[i];
+ index_writer->WriteIndexKeys(record_identifier,
+ backing_store_,
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_.id);
+ }
+
+ if (object_store_.auto_increment &&
+ put_mode_ != IndexedDBDatabase::CURSOR_UPDATE &&
+ key->type() == WebIDBKeyTypeNumber) {
+ bool ok = UpdateKeyGenerator(backing_store_,
+ transaction,
+ database_id_,
+ object_store_.id,
+ key.get(),
+ !key_was_generated);
+ if (!ok) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error updating key generator."));
+ return;
+ }
+ }
+
+ callbacks_->OnSuccess(*key);
+}
+
+void IndexedDBDatabase::SetIndexKeys(int64 transaction_id,
+ int64 object_store_id,
+ scoped_ptr<IndexedDBKey> primary_key,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexKeys>& index_keys) {
+ IDB_TRACE("IndexedDBDatabase::SetIndexKeys");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ scoped_refptr<IndexedDBBackingStore> store = BackingStore();
+ // TODO(alecflett): This method could be asynchronous, but we need to
+ // evaluate if it's worth the extra complexity.
+ IndexedDBBackingStore::RecordIdentifier record_identifier;
+ bool found = false;
+ bool ok =
+ store->KeyExistsInObjectStore(transaction->BackingStoreTransaction(),
+ metadata_.id,
+ object_store_id,
+ *primary_key,
+ &record_identifier,
+ &found);
+ if (!ok) {
+ transaction->Abort(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error setting index keys."));
+ return;
+ }
+ if (!found) {
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error setting index keys for object store."));
+ return;
+ }
+
+ ScopedVector<IndexWriter> index_writers;
+ string16 error_message;
+ bool obeys_constraints = false;
+ DCHECK(metadata_.object_stores.find(object_store_id) !=
+ metadata_.object_stores.end());
+ const IndexedDBObjectStoreMetadata& object_store_metadata =
+ metadata_.object_stores[object_store_id];
+ bool backing_store_success = MakeIndexWriters(transaction,
+ store,
+ id(),
+ object_store_metadata,
+ *primary_key,
+ false,
+ index_ids,
+ index_keys,
+ &index_writers,
+ &error_message,
+ &obeys_constraints);
+ if (!backing_store_success) {
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error: backing store error updating index keys."));
+ return;
+ }
+ if (!obeys_constraints) {
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionConstraintError, error_message));
+ return;
+ }
+
+ for (size_t i = 0; i < index_writers.size(); ++i) {
+ IndexWriter* index_writer = index_writers[i];
+ index_writer->WriteIndexKeys(record_identifier,
+ store,
+ transaction->BackingStoreTransaction(),
+ id(),
+ object_store_id);
+ }
+}
+
+void IndexedDBDatabase::SetIndexesReady(int64 transaction_id,
+ int64,
+ const std::vector<int64>& index_ids) {
+ IDB_TRACE("IndexedDBDatabase::SetIndexesReady");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_EQ(transaction->mode(), indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ transaction->ScheduleTask(IndexedDBDatabase::PREEMPTIVE_TASK,
+ new SetIndexesReadyOperation(index_ids.size()));
+}
+
+void SetIndexesReadyOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("SetIndexesReadyOperation");
+ for (size_t i = 0; i < index_count_; ++i)
+ transaction->DidCompletePreemptiveEvent();
+}
+
+void IndexedDBDatabase::OpenCursor(
+ int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ indexed_db::CursorDirection direction,
+ bool key_only,
+ TaskType task_type,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBDatabase::OpenCursor");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+
+ if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
+ return;
+
+ transaction->ScheduleTask(new OpenCursorOperation(
+ backing_store_,
+ id(),
+ object_store_id,
+ index_id,
+ key_range.Pass(),
+ direction,
+ key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE,
+ task_type,
+ callbacks));
+}
+
+void OpenCursorOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("OpenCursorOperation");
+
+ // The frontend has begun indexing, so this pauses the transaction
+ // until the indexing is complete. This can't happen any earlier
+ // because we don't want to switch to early mode in case multiple
+ // indexes are being created in a row, with Put()'s in between.
+ if (task_type_ == IndexedDBDatabase::PREEMPTIVE_TASK)
+ transaction->AddPreemptiveEvent();
+
+ scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
+ if (index_id_ == IndexedDBIndexMetadata::kInvalidId) {
+ DCHECK_NE(cursor_type_, indexed_db::CURSOR_KEY_ONLY);
+ backing_store_cursor = backing_store_->OpenObjectStoreCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *key_range_,
+ direction_);
+ } else {
+ DCHECK_EQ(task_type_, IndexedDBDatabase::NORMAL_TASK);
+ if (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) {
+ backing_store_cursor = backing_store_->OpenIndexKeyCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key_range_,
+ direction_);
+ } else {
+ backing_store_cursor = backing_store_->OpenIndexCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key_range_,
+ direction_);
+ }
+ }
+
+ if (!backing_store_cursor) {
+ callbacks_->OnSuccess(static_cast<std::string*>(NULL));
+ return;
+ }
+
+ scoped_refptr<IndexedDBCursor> cursor = new IndexedDBCursor(
+ backing_store_cursor.Pass(), cursor_type_, task_type_, transaction);
+ callbacks_->OnSuccess(
+ cursor, cursor->key(), cursor->primary_key(), cursor->Value());
+}
+
+void IndexedDBDatabase::Count(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBDatabase::Count");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+
+ if (!ValidateObjectStoreIdAndOptionalIndexId(object_store_id, index_id))
+ return;
+
+ transaction->ScheduleTask(new CountOperation(backing_store_,
+ id(),
+ object_store_id,
+ index_id,
+ key_range.Pass(),
+ callbacks));
+}
+
+void CountOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("CountOperation");
+ uint32 count = 0;
+ scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
+
+ if (index_id_ == IndexedDBIndexMetadata::kInvalidId) {
+ backing_store_cursor = backing_store_->OpenObjectStoreKeyCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ } else {
+ backing_store_cursor = backing_store_->OpenIndexKeyCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ index_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ }
+ if (!backing_store_cursor) {
+ callbacks_->OnSuccess(count);
+ return;
+ }
+
+ do {
+ ++count;
+ } while (backing_store_cursor->Continue());
+
+ callbacks_->OnSuccess(count);
+}
+
+void IndexedDBDatabase::DeleteRange(
+ int64 transaction_id,
+ int64 object_store_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBDatabase::DeleteRange");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);
+
+ if (!ValidateObjectStoreId(object_store_id))
+ return;
+
+ transaction->ScheduleTask(new DeleteRangeOperation(
+ backing_store_, id(), object_store_id, key_range.Pass(), callbacks));
+}
+
+void DeleteRangeOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("DeleteRangeOperation");
+ scoped_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor =
+ backing_store_->OpenObjectStoreCursor(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ *key_range_,
+ indexed_db::CURSOR_NEXT);
+ if (backing_store_cursor) {
+ do {
+ if (!backing_store_->DeleteRecord(
+ transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_,
+ backing_store_cursor->record_identifier())) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error deleting data in range"));
+ return;
+ }
+ } while (backing_store_cursor->Continue());
+ }
+
+ callbacks_->OnSuccess();
+}
+
+void IndexedDBDatabase::Clear(int64 transaction_id,
+ int64 object_store_id,
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ IDB_TRACE("IndexedDBDatabase::Clear");
+ IndexedDBTransaction* transaction = GetTransaction(transaction_id);
+ if (!transaction)
+ return;
+ DCHECK_NE(transaction->mode(), indexed_db::TRANSACTION_READ_ONLY);
+
+ if (!ValidateObjectStoreId(object_store_id))
+ return;
+
+ transaction->ScheduleTask(
+ new ClearOperation(backing_store_, id(), object_store_id, callbacks));
+}
+
+void ClearOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("ObjectStoreClearOperation");
+ if (!backing_store_->ClearObjectStore(transaction->BackingStoreTransaction(),
+ database_id_,
+ object_store_id_)) {
+ callbacks_->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error clearing object store"));
+ return;
+ }
+ callbacks_->OnSuccess();
+}
+
+void DeleteObjectStoreOperation::Perform(IndexedDBTransaction* transaction) {
+ IDB_TRACE("DeleteObjectStoreOperation");
+ bool ok =
+ backing_store_->DeleteObjectStore(transaction->BackingStoreTransaction(),
+ transaction->database()->id(),
+ object_store_metadata_.id);
+ if (!ok) {
+ string16 error_string =
+ ASCIIToUTF16("Internal error deleting object store '") +
+ object_store_metadata_.name + ASCIIToUTF16("'.");
+ transaction->Abort(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError, error_string));
+ }
+}
+
+void IndexedDBDatabase::VersionChangeOperation::Perform(
+ IndexedDBTransaction* transaction) {
+ IDB_TRACE("VersionChangeOperation");
+ int64 database_id = database_->id();
+ int64 old_version = database_->metadata_.int_version;
+ DCHECK_GT(version_, old_version);
+ database_->metadata_.int_version = version_;
+ if (!database_->backing_store_->UpdateIDBDatabaseIntVersion(
+ transaction->BackingStoreTransaction(),
+ database_id,
+ database_->metadata_.int_version)) {
+ IndexedDBDatabaseError error(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16(
+ "Internal error writing data to stable storage when "
+ "updating version."));
+ callbacks_->OnError(error);
+ transaction->Abort(error);
+ return;
+ }
+ DCHECK(!database_->pending_second_half_open_);
+
+ database_->pending_second_half_open_.reset(new PendingSuccessCall(
+ callbacks_, connection_.get(), transaction_id_, version_));
+ callbacks_->OnUpgradeNeeded(
+ old_version, connection_.Pass(), database_->metadata(), data_loss_);
+}
+
+void IndexedDBDatabase::TransactionStarted(IndexedDBTransaction* transaction) {
+
+ if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) {
+ DCHECK(!running_version_change_transaction_);
+ running_version_change_transaction_ = transaction;
+ }
+}
+
+void IndexedDBDatabase::TransactionFinished(IndexedDBTransaction* transaction) {
+
+ DCHECK(transactions_.find(transaction->id()) != transactions_.end());
+ DCHECK_EQ(transactions_[transaction->id()], transaction);
+ transactions_.erase(transaction->id());
+ if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) {
+ DCHECK_EQ(transaction, running_version_change_transaction_);
+ running_version_change_transaction_ = NULL;
+ }
+}
+
+void IndexedDBDatabase::TransactionFinishedAndAbortFired(
+ IndexedDBTransaction* transaction) {
+ if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) {
+ if (pending_second_half_open_) {
+ pending_second_half_open_->Callbacks()->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionAbortError,
+ "Version change transaction was aborted in "
+ "upgradeneeded event handler."));
+ pending_second_half_open_.reset();
+ }
+ ProcessPendingCalls();
+ }
+}
+
+void IndexedDBDatabase::TransactionFinishedAndCompleteFired(
+ IndexedDBTransaction* transaction) {
+ if (transaction->mode() == indexed_db::TRANSACTION_VERSION_CHANGE) {
+ DCHECK(pending_second_half_open_);
+ if (pending_second_half_open_) {
+ DCHECK_EQ(pending_second_half_open_->Version(), metadata_.int_version);
+ DCHECK(metadata_.id != kInvalidId);
+
+ // Connection was already minted for OnUpgradeNeeded callback.
+ scoped_ptr<IndexedDBConnection> connection;
+
+ pending_second_half_open_->Callbacks()->OnSuccess(
+ connection.Pass(), this->metadata());
+ pending_second_half_open_.reset();
+ }
+ ProcessPendingCalls();
+ }
+}
+
+size_t IndexedDBDatabase::ConnectionCount() const {
+ // This does not include pending open calls, as those should not block version
+ // changes and deletes.
+ return connections_.size();
+}
+
+size_t IndexedDBDatabase::PendingOpenCount() const {
+ return pending_open_calls_.size();
+}
+
+size_t IndexedDBDatabase::PendingUpgradeCount() const {
+ return pending_run_version_change_transaction_call_ ? 1 : 0;
+}
+
+size_t IndexedDBDatabase::RunningUpgradeCount() const {
+ return pending_second_half_open_ ? 1 : 0;
+}
+
+size_t IndexedDBDatabase::PendingDeleteCount() const {
+ return pending_delete_calls_.size();
+}
+
+void IndexedDBDatabase::ProcessPendingCalls() {
+ if (pending_run_version_change_transaction_call_ && ConnectionCount() == 1) {
+ DCHECK(pending_run_version_change_transaction_call_->Version() >
+ metadata_.int_version);
+ scoped_ptr<PendingUpgradeCall> pending_call =
+ pending_run_version_change_transaction_call_.Pass();
+ RunVersionChangeTransactionFinal(pending_call->Callbacks(),
+ pending_call->Connection(),
+ pending_call->TransactionId(),
+ pending_call->Version());
+ DCHECK_EQ(static_cast<size_t>(1), ConnectionCount());
+ // Fall through would be a no-op, since transaction must complete
+ // asynchronously.
+ DCHECK(IsDeleteDatabaseBlocked());
+ DCHECK(IsOpenConnectionBlocked());
+ return;
+ }
+
+ if (!IsDeleteDatabaseBlocked()) {
+ PendingDeleteCallList pending_delete_calls;
+ pending_delete_calls_.swap(pending_delete_calls);
+ while (!pending_delete_calls.empty()) {
+ // Only the first delete call will delete the database, but each must fire
+ // callbacks.
+ scoped_ptr<PendingDeleteCall> pending_delete_call(
+ pending_delete_calls.front());
+ pending_delete_calls.pop_front();
+ DeleteDatabaseFinal(pending_delete_call->Callbacks());
+ }
+ // delete_database_final should never re-queue calls.
+ DCHECK(pending_delete_calls_.empty());
+ // Fall through when complete, as pending opens may be unblocked.
+ }
+
+ if (!IsOpenConnectionBlocked()) {
+ PendingOpenCallList pending_open_calls;
+ pending_open_calls_.swap(pending_open_calls);
+ while (!pending_open_calls.empty()) {
+ scoped_ptr<PendingOpenCall> pending_open_call(pending_open_calls.front());
+ pending_open_calls.pop_front();
+ OpenConnection(pending_open_call->Callbacks(),
+ pending_open_call->DatabaseCallbacks(),
+ pending_open_call->TransactionId(),
+ pending_open_call->Version());
+ }
+ }
+}
+
+void IndexedDBDatabase::CreateTransaction(
+ int64 transaction_id,
+ IndexedDBConnection* connection,
+ const std::vector<int64>& object_store_ids,
+ uint16 mode) {
+
+ DCHECK(connections_.has(connection));
+
+ scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
+ transaction_id,
+ connection->callbacks(),
+ std::set<int64>(object_store_ids.begin(), object_store_ids.end()),
+ static_cast<indexed_db::TransactionMode>(mode),
+ this);
+ DCHECK(transactions_.find(transaction_id) == transactions_.end());
+ transactions_[transaction_id] = transaction;
+}
+
+bool IndexedDBDatabase::IsOpenConnectionBlocked() const {
+ return !pending_delete_calls_.empty() ||
+ running_version_change_transaction_ ||
+ pending_run_version_change_transaction_call_;
+}
+
+void IndexedDBDatabase::OpenConnection(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ int64 transaction_id,
+ int64 version) {
+ const WebKit::WebIDBCallbacks::DataLoss kDataLoss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+ OpenConnection(
+ callbacks, database_callbacks, transaction_id, version, kDataLoss);
+}
+
+void IndexedDBDatabase::OpenConnection(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ int64 transaction_id,
+ int64 version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss) {
+ DCHECK(backing_store_);
+
+ // TODO(jsbell): Should have a priority queue so that higher version
+ // requests are processed first. http://crbug.com/225850
+ if (IsOpenConnectionBlocked()) {
+ // The backing store only detects data loss when it is first opened. The
+ // presence of existing connections means we didn't even check for data loss
+ // so there'd better not be any.
+ DCHECK_NE(WebKit::WebIDBCallbacks::DataLossTotal, data_loss);
+ pending_open_calls_.push_back(new PendingOpenCall(
+ callbacks, database_callbacks, transaction_id, version));
+ return;
+ }
+
+ if (metadata_.id == kInvalidId) {
+ // The database was deleted then immediately re-opened; OpenInternal()
+ // recreates it in the backing store.
+ if (OpenInternal()) {
+ DCHECK_EQ(IndexedDBDatabaseMetadata::NO_INT_VERSION,
+ metadata_.int_version);
+ } else {
+ string16 message;
+ if (version == IndexedDBDatabaseMetadata::NO_INT_VERSION)
+ message = ASCIIToUTF16(
+ "Internal error opening database with no version specified.");
+ else
+ message =
+ ASCIIToUTF16("Internal error opening database with version ") +
+ Int64ToString16(version);
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError, message));
+ return;
+ }
+ }
+
+ // We infer that the database didn't exist from its lack of either type of
+ // version.
+ bool is_new_database =
+ metadata_.version == kNoStringVersion &&
+ metadata_.int_version == IndexedDBDatabaseMetadata::NO_INT_VERSION;
+
+ scoped_ptr<IndexedDBConnection> connection(
+ new IndexedDBConnection(this, database_callbacks));
+
+ if (version == IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION) {
+ // For unit tests only - skip upgrade steps. Calling from script with
+ // DEFAULT_INT_VERSION throws exception.
+ // TODO(jsbell): DCHECK that not in unit tests.
+ DCHECK(is_new_database);
+ connections_.insert(connection.get());
+ callbacks->OnSuccess(connection.Pass(), this->metadata());
+ return;
+ }
+
+ if (version == IndexedDBDatabaseMetadata::NO_INT_VERSION) {
+ if (!is_new_database) {
+ connections_.insert(connection.get());
+ callbacks->OnSuccess(connection.Pass(), this->metadata());
+ return;
+ }
+ // Spec says: If no version is specified and no database exists, set
+ // database version to 1.
+ version = 1;
+ }
+
+ if (version > metadata_.int_version) {
+ connections_.insert(connection.get());
+ RunVersionChangeTransaction(
+ callbacks, connection.Pass(), transaction_id, version, data_loss);
+ return;
+ }
+ if (version < metadata_.int_version) {
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionVersionError,
+ ASCIIToUTF16("The requested version (") + Int64ToString16(version) +
+ ASCIIToUTF16(") is less than the existing version (") +
+ Int64ToString16(metadata_.int_version) + ASCIIToUTF16(").")));
+ return;
+ }
+ DCHECK_EQ(version, metadata_.int_version);
+ connections_.insert(connection.get());
+ callbacks->OnSuccess(connection.Pass(), this->metadata());
+}
+
+void IndexedDBDatabase::RunVersionChangeTransaction(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss) {
+
+ DCHECK(callbacks);
+ DCHECK(connections_.has(connection.get()));
+ if (ConnectionCount() > 1) {
+ DCHECK_NE(WebKit::WebIDBCallbacks::DataLossTotal, data_loss);
+ // Front end ensures the event is not fired at connections that have
+ // close_pending set.
+ for (ConnectionSet::const_iterator it = connections_.begin();
+ it != connections_.end();
+ ++it) {
+ if (*it != connection.get()) {
+ (*it)->callbacks()->OnVersionChange(
+ metadata_.int_version, requested_version);
+ }
+ }
+ // TODO(jsbell): Remove the call to OnBlocked and instead wait
+ // until the frontend tells us that all the "versionchange" events
+ // have been delivered. http://crbug.com/100123
+ callbacks->OnBlocked(metadata_.int_version);
+
+ DCHECK(!pending_run_version_change_transaction_call_);
+ pending_run_version_change_transaction_call_.reset(new PendingUpgradeCall(
+ callbacks, connection.Pass(), transaction_id, requested_version));
+ return;
+ }
+ RunVersionChangeTransactionFinal(callbacks,
+ connection.Pass(),
+ transaction_id,
+ requested_version,
+ data_loss);
+}
+
+void IndexedDBDatabase::RunVersionChangeTransactionFinal(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version) {
+ const WebKit::WebIDBCallbacks::DataLoss kDataLoss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+ RunVersionChangeTransactionFinal(callbacks,
+ connection.Pass(),
+ transaction_id,
+ requested_version,
+ kDataLoss);
+}
+
+void IndexedDBDatabase::RunVersionChangeTransactionFinal(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss) {
+
+ std::vector<int64> object_store_ids;
+ CreateTransaction(transaction_id,
+ connection.get(),
+ object_store_ids,
+ indexed_db::TRANSACTION_VERSION_CHANGE);
+ scoped_refptr<IndexedDBTransaction> transaction =
+ transactions_[transaction_id];
+
+ transaction->ScheduleTask(
+ new VersionChangeOperation(this,
+ transaction_id,
+ requested_version,
+ callbacks,
+ connection.Pass(),
+ data_loss),
+ new VersionChangeAbortOperation(
+ this, metadata_.version, metadata_.int_version));
+
+ DCHECK(!pending_second_half_open_);
+}
+
+void IndexedDBDatabase::DeleteDatabase(
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+
+ if (IsDeleteDatabaseBlocked()) {
+ for (ConnectionSet::const_iterator it = connections_.begin();
+ it != connections_.end();
+ ++it) {
+ // Front end ensures the event is not fired at connections that have
+ // close_pending set.
+ (*it)->callbacks()->OnVersionChange(
+ metadata_.int_version, IndexedDBDatabaseMetadata::NO_INT_VERSION);
+ }
+ // TODO(jsbell): Only fire OnBlocked if there are open
+ // connections after the VersionChangeEvents are received, not
+ // just set up to fire. http://crbug.com/100123
+ callbacks->OnBlocked(metadata_.int_version);
+ pending_delete_calls_.push_back(new PendingDeleteCall(callbacks));
+ return;
+ }
+ DeleteDatabaseFinal(callbacks);
+}
+
+bool IndexedDBDatabase::IsDeleteDatabaseBlocked() const {
+ return !!ConnectionCount();
+}
+
+void IndexedDBDatabase::DeleteDatabaseFinal(
+ scoped_refptr<IndexedDBCallbacks> callbacks) {
+ DCHECK(!IsDeleteDatabaseBlocked());
+ DCHECK(backing_store_);
+ if (!backing_store_->DeleteDatabase(metadata_.name)) {
+ callbacks->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error deleting database."));
+ return;
+ }
+ metadata_.version = kNoStringVersion;
+ metadata_.id = kInvalidId;
+ metadata_.int_version = IndexedDBDatabaseMetadata::NO_INT_VERSION;
+ metadata_.object_stores.clear();
+ callbacks->OnSuccess();
+}
+
+void IndexedDBDatabase::Close(IndexedDBConnection* connection) {
+ DCHECK(connections_.has(connection));
+
+ // Close outstanding transactions from the closing connection. This
+ // can not happen if the close is requested by the connection itself
+ // as the front-end defers the close until all transactions are
+ // complete, so something unusual has happened e.g. unexpected
+ // process termination.
+ {
+ TransactionMap transactions(transactions_);
+ for (TransactionMap::const_iterator it = transactions.begin(),
+ end = transactions.end();
+ it != end;
+ ++it) {
+ if (it->second->connection() == connection->callbacks())
+ it->second->Abort(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Connection is closing."));
+ }
+ }
+
+ connections_.erase(connection);
+ if (pending_second_half_open_ &&
+ pending_second_half_open_->Connection() == connection) {
+ pending_second_half_open_->Callbacks()->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionAbortError,
+ "The connection was closed."));
+ pending_second_half_open_.reset();
+ }
+
+ // process_pending_calls allows the inspector to process a pending open call
+ // and call close, reentering IndexedDBDatabase::close. Then the
+ // backend would be removed both by the inspector closing its connection, and
+ // by the connection that first called close.
+ // To avoid that situation, don't proceed in case of reentrancy.
+ if (closing_connection_)
+ return;
+ base::AutoReset<bool> ClosingConnection(&closing_connection_, true);
+ ProcessPendingCalls();
+
+ // TODO(jsbell): Add a test for the pending_open_calls_ cases below.
+ if (!ConnectionCount() && !pending_open_calls_.size() &&
+ !pending_delete_calls_.size()) {
+ DCHECK(transactions_.empty());
+
+ backing_store_ = NULL;
+
+ // factory_ should only be null in unit tests.
+ // TODO(jsbell): DCHECK(factory_ || !in_unit_tests) - somehow.
+ if (factory_)
+ factory_->RemoveIDBDatabaseBackend(identifier_);
+ }
+}
+
+void CreateObjectStoreAbortOperation::Perform(
+ IndexedDBTransaction* transaction) {
+ IDB_TRACE("CreateObjectStoreAbortOperation");
+ DCHECK(!transaction);
+ database_->RemoveObjectStore(object_store_id_);
+}
+
+void DeleteObjectStoreAbortOperation::Perform(
+ IndexedDBTransaction* transaction) {
+ IDB_TRACE("DeleteObjectStoreAbortOperation");
+ DCHECK(!transaction);
+ database_->AddObjectStore(object_store_metadata_,
+ IndexedDBObjectStoreMetadata::kInvalidId);
+}
+
+void IndexedDBDatabase::VersionChangeAbortOperation::Perform(
+ IndexedDBTransaction* transaction) {
+ IDB_TRACE("VersionChangeAbortOperation");
+ DCHECK(!transaction);
+ database_->metadata_.version = previous_version_;
+ database_->metadata_.int_version = previous_int_version_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_database.h b/chromium/content/browser/indexed_db/indexed_db_database.h
new file mode 100644
index 00000000000..9c617f5ec63
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database.h
@@ -0,0 +1,260 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/indexed_db/indexed_db.h"
+#include "content/browser/indexed_db/indexed_db_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
+#include "content/browser/indexed_db/list_set.h"
+
+namespace content {
+
+class IndexedDBConnection;
+class IndexedDBDatabaseCallbacks;
+class IndexedDBBackingStore;
+class IndexedDBFactory;
+class IndexedDBKey;
+class IndexedDBKeyPath;
+class IndexedDBKeyRange;
+class IndexedDBTransaction;
+
+class CONTENT_EXPORT IndexedDBDatabase
+ : NON_EXPORTED_BASE(public base::RefCounted<IndexedDBDatabase>) {
+ public:
+ enum TaskType {
+ NORMAL_TASK = 0,
+ PREEMPTIVE_TASK
+ };
+
+ enum PutMode {
+ ADD_OR_UPDATE,
+ ADD_ONLY,
+ CURSOR_UPDATE
+ };
+
+ typedef std::vector<IndexedDBKey> IndexKeys;
+ // Identifier is pair of (origin identifier, database name).
+ typedef std::pair<std::string, base::string16> Identifier;
+
+ static const int64 kInvalidId = 0;
+ static const int64 kMinimumIndexId = 30;
+
+ static scoped_refptr<IndexedDBDatabase> Create(
+ const string16& name,
+ IndexedDBBackingStore* database,
+ IndexedDBFactory* factory,
+ const Identifier& unique_identifier);
+ scoped_refptr<IndexedDBBackingStore> BackingStore() const;
+
+ int64 id() const { return metadata_.id; }
+ const base::string16& name() const { return metadata_.name; }
+
+ void AddObjectStore(const IndexedDBObjectStoreMetadata& metadata,
+ int64 new_max_object_store_id);
+ void RemoveObjectStore(int64 object_store_id);
+ void AddIndex(int64 object_store_id,
+ const IndexedDBIndexMetadata& metadata,
+ int64 new_max_index_id);
+ void RemoveIndex(int64 object_store_id, int64 index_id);
+
+ void OpenConnection(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ int64 transaction_id,
+ int64 version);
+ void OpenConnection(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ int64 transaction_id,
+ int64 version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss);
+ void DeleteDatabase(scoped_refptr<IndexedDBCallbacks> callbacks);
+ const IndexedDBDatabaseMetadata& metadata() const { return metadata_; }
+
+ void CreateObjectStore(int64 transaction_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment);
+ void DeleteObjectStore(int64 transaction_id, int64 object_store_id);
+ void CreateTransaction(int64 transaction_id,
+ IndexedDBConnection* connection,
+ const std::vector<int64>& object_store_ids,
+ uint16 mode);
+ void Close(IndexedDBConnection* connection);
+
+ void Commit(int64 transaction_id);
+ void Abort(int64 transaction_id);
+ void Abort(int64 transaction_id, const IndexedDBDatabaseError& error);
+
+ void CreateIndex(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath& key_path,
+ bool unique,
+ bool multi_entry);
+ void DeleteIndex(int64 transaction_id, int64 object_store_id, int64 index_id);
+
+ IndexedDBTransactionCoordinator& transaction_coordinator() {
+ return transaction_coordinator_;
+ }
+ const IndexedDBTransactionCoordinator& transaction_coordinator() const {
+ return transaction_coordinator_;
+ }
+
+ void TransactionStarted(IndexedDBTransaction* transaction);
+ void TransactionFinished(IndexedDBTransaction* transaction);
+ void TransactionFinishedAndCompleteFired(IndexedDBTransaction* transaction);
+ void TransactionFinishedAndAbortFired(IndexedDBTransaction* transaction);
+
+ void Get(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ bool key_only,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void Put(int64 transaction_id,
+ int64 object_store_id,
+ std::string* value,
+ scoped_ptr<IndexedDBKey> key,
+ PutMode mode,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexKeys>& index_keys);
+ void SetIndexKeys(int64 transaction_id,
+ int64 object_store_id,
+ scoped_ptr<IndexedDBKey> primary_key,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexKeys>& index_keys);
+ void SetIndexesReady(int64 transaction_id,
+ int64 object_store_id,
+ const std::vector<int64>& index_ids);
+ void OpenCursor(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ indexed_db::CursorDirection,
+ bool key_only,
+ TaskType task_type,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void Count(int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void DeleteRange(int64 transaction_id,
+ int64 object_store_id,
+ scoped_ptr<IndexedDBKeyRange> key_range,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+ void Clear(int64 transaction_id,
+ int64 object_store_id,
+ scoped_refptr<IndexedDBCallbacks> callbacks);
+
+ // Number of connections that have progressed passed initial open call.
+ size_t ConnectionCount() const;
+ // Number of open calls that are blocked on other connections.
+ size_t PendingOpenCount() const;
+ // Number of pending upgrades (0 or 1). Also included in ConnectionCount().
+ size_t PendingUpgradeCount() const;
+ // Number of running upgrades (0 or 1). Also included in ConnectionCount().
+ size_t RunningUpgradeCount() const;
+ // Number of pending deletes, blocked on other connections.
+ size_t PendingDeleteCount() const;
+
+ private:
+ friend class base::RefCounted<IndexedDBDatabase>;
+
+ IndexedDBDatabase(
+ const string16& name,
+ IndexedDBBackingStore* database,
+ IndexedDBFactory* factory,
+ const Identifier& unique_identifier);
+ ~IndexedDBDatabase();
+
+ bool IsOpenConnectionBlocked() const;
+ bool OpenInternal();
+ void RunVersionChangeTransaction(scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss);
+ void RunVersionChangeTransactionFinal(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version);
+ void RunVersionChangeTransactionFinal(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_ptr<IndexedDBConnection> connection,
+ int64 transaction_id,
+ int64 requested_version,
+ WebKit::WebIDBCallbacks::DataLoss data_loss);
+ void ProcessPendingCalls();
+
+ bool IsDeleteDatabaseBlocked() const;
+ void DeleteDatabaseFinal(scoped_refptr<IndexedDBCallbacks> callbacks);
+
+ IndexedDBTransaction* GetTransaction(int64 transaction_id) const;
+
+ bool ValidateObjectStoreId(int64 object_store_id) const;
+ bool ValidateObjectStoreIdAndIndexId(int64 object_store_id,
+ int64 index_id) const;
+ bool ValidateObjectStoreIdAndOptionalIndexId(int64 object_store_id,
+ int64 index_id) const;
+ bool ValidateObjectStoreIdAndNewIndexId(int64 object_store_id,
+ int64 index_id) const;
+
+ class VersionChangeOperation;
+
+ // When a "versionchange" transaction aborts, these restore the back-end
+ // object hierarchy.
+ class VersionChangeAbortOperation;
+
+ scoped_refptr<IndexedDBBackingStore> backing_store_;
+ IndexedDBDatabaseMetadata metadata_;
+
+ const Identifier identifier_;
+ // This might not need to be a scoped_refptr since the factory's lifetime is
+ // that of the page group, but it's better to be conservitive than sorry.
+ scoped_refptr<IndexedDBFactory> factory_;
+
+ IndexedDBTransactionCoordinator transaction_coordinator_;
+ IndexedDBTransaction* running_version_change_transaction_;
+
+ typedef std::map<int64, IndexedDBTransaction*> TransactionMap;
+ TransactionMap transactions_;
+
+ class PendingOpenCall;
+ typedef std::list<PendingOpenCall*> PendingOpenCallList;
+ PendingOpenCallList pending_open_calls_;
+
+ class PendingUpgradeCall;
+ scoped_ptr<PendingUpgradeCall> pending_run_version_change_transaction_call_;
+ class PendingSuccessCall;
+ scoped_ptr<PendingSuccessCall> pending_second_half_open_;
+
+ class PendingDeleteCall;
+ typedef std::list<PendingDeleteCall*> PendingDeleteCallList;
+ PendingDeleteCallList pending_delete_calls_;
+
+ typedef list_set<IndexedDBConnection*> ConnectionSet;
+ ConnectionSet connections_;
+
+ bool closing_connection_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_database_callbacks.cc b/chromium/content/browser/indexed_db/indexed_db_database_callbacks.cc
new file mode 100644
index 00000000000..3d47ad0c11c
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database_callbacks.cc
@@ -0,0 +1,68 @@
+// 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 "content/browser/indexed_db/indexed_db_database_callbacks.h"
+
+#include "content/browser/indexed_db/indexed_db_database_error.h"
+#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
+#include "content/common/indexed_db/indexed_db_messages.h"
+
+namespace content {
+
+IndexedDBDatabaseCallbacks::IndexedDBDatabaseCallbacks(
+ IndexedDBDispatcherHost* dispatcher_host,
+ int ipc_thread_id,
+ int ipc_database_callbacks_id)
+ : dispatcher_host_(dispatcher_host),
+ ipc_thread_id_(ipc_thread_id),
+ ipc_database_callbacks_id_(ipc_database_callbacks_id) {}
+
+IndexedDBDatabaseCallbacks::~IndexedDBDatabaseCallbacks() {}
+
+void IndexedDBDatabaseCallbacks::OnForcedClose() {
+ if (!dispatcher_host_.get())
+ return;
+
+ dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksForcedClose(
+ ipc_thread_id_, ipc_database_callbacks_id_));
+
+ dispatcher_host_ = NULL;
+}
+
+void IndexedDBDatabaseCallbacks::OnVersionChange(int64 old_version,
+ int64 new_version) {
+ if (!dispatcher_host_.get())
+ return;
+
+ dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksIntVersionChange(
+ ipc_thread_id_, ipc_database_callbacks_id_, old_version, new_version));
+}
+
+void IndexedDBDatabaseCallbacks::OnAbort(
+ int64 host_transaction_id,
+ const IndexedDBDatabaseError& error) {
+ if (!dispatcher_host_.get())
+ return;
+
+ dispatcher_host_->FinishTransaction(host_transaction_id, false);
+ dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksAbort(
+ ipc_thread_id_,
+ ipc_database_callbacks_id_,
+ dispatcher_host_->RendererTransactionId(host_transaction_id),
+ error.code(),
+ error.message()));
+}
+
+void IndexedDBDatabaseCallbacks::OnComplete(int64 host_transaction_id) {
+ if (!dispatcher_host_.get())
+ return;
+
+ dispatcher_host_->FinishTransaction(host_transaction_id, true);
+ dispatcher_host_->Send(new IndexedDBMsg_DatabaseCallbacksComplete(
+ ipc_thread_id_,
+ ipc_database_callbacks_id_,
+ dispatcher_host_->RendererTransactionId(host_transaction_id)));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_database_callbacks.h b/chromium/content/browser/indexed_db/indexed_db_database_callbacks.h
new file mode 100644
index 00000000000..0fae7807fcb
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database_callbacks.h
@@ -0,0 +1,43 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+
+namespace content {
+class IndexedDBDatabaseError;
+class IndexedDBDispatcherHost;
+
+class CONTENT_EXPORT IndexedDBDatabaseCallbacks
+ : public base::RefCounted<IndexedDBDatabaseCallbacks> {
+ public:
+ IndexedDBDatabaseCallbacks(IndexedDBDispatcherHost* dispatcher_host,
+ int ipc_thread_id,
+ int ipc_database_callbacks_id);
+
+ virtual void OnForcedClose();
+ virtual void OnVersionChange(int64 old_version, int64 new_version);
+
+ virtual void OnAbort(int64 host_transaction_id,
+ const IndexedDBDatabaseError& error);
+ virtual void OnComplete(int64 host_transaction_id);
+
+ protected:
+ virtual ~IndexedDBDatabaseCallbacks();
+
+ private:
+ friend class base::RefCounted<IndexedDBDatabaseCallbacks>;
+
+ scoped_refptr<IndexedDBDispatcherHost> dispatcher_host_;
+ int ipc_thread_id_;
+ int ipc_database_callbacks_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_CALLBACKS_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_database_error.h b/chromium/content/browser/indexed_db/indexed_db_database_error.h
new file mode 100644
index 00000000000..297bec7be33
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database_error.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace content {
+
+class IndexedDBDatabaseError {
+ public:
+ IndexedDBDatabaseError(uint16 code)
+ : code_(code) {}
+ IndexedDBDatabaseError(uint16 code, const char* message)
+ : code_(code), message_(ASCIIToUTF16(message)) {}
+ IndexedDBDatabaseError(uint16 code, const string16& message)
+ : code_(code), message_(message) {}
+ ~IndexedDBDatabaseError() {}
+
+ uint16 code() const { return code_; }
+ const string16& message() const { return message_; }
+
+ private:
+ const uint16 code_;
+ const string16 message_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DATABASE_ERROR_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_database_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_database_unittest.cc
new file mode 100644
index 00000000000..fad99106e5a
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_database_unittest.cc
@@ -0,0 +1,224 @@
+// 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 "content/browser/indexed_db/indexed_db_database.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/indexed_db_fake_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(IndexedDBDatabaseTest, BackingStoreRetention) {
+ scoped_refptr<IndexedDBFakeBackingStore> backing_store =
+ new IndexedDBFakeBackingStore();
+ EXPECT_TRUE(backing_store->HasOneRef());
+
+ IndexedDBFactory* factory = 0;
+ scoped_refptr<IndexedDBDatabase> db = IndexedDBDatabase::Create(
+ ASCIIToUTF16("db"),
+ backing_store,
+ factory,
+ IndexedDBDatabase::Identifier());
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+ db = NULL;
+ EXPECT_TRUE(backing_store->HasOneRef()); // local
+}
+
+class MockOpenCallbacks : public IndexedDBCallbacks {
+ public:
+ MockOpenCallbacks() : IndexedDBCallbacks(NULL, 0, 0) {}
+
+ virtual void OnSuccess(scoped_ptr<IndexedDBConnection> connection,
+ const IndexedDBDatabaseMetadata& metadata) OVERRIDE {
+ connection_ = connection.Pass();
+ }
+
+ IndexedDBConnection* connection() { return connection_.get(); }
+
+ private:
+ virtual ~MockOpenCallbacks() { EXPECT_TRUE(connection_); }
+ scoped_ptr<IndexedDBConnection> connection_;
+};
+
+class FakeDatabaseCallbacks : public IndexedDBDatabaseCallbacks {
+ public:
+ FakeDatabaseCallbacks() : IndexedDBDatabaseCallbacks(NULL, 0, 0) {}
+
+ virtual void OnVersionChange(int64 old_version, int64 new_version) OVERRIDE {}
+ virtual void OnForcedClose() OVERRIDE {}
+ virtual void OnAbort(int64 transaction_id,
+ const IndexedDBDatabaseError& error) OVERRIDE {}
+ virtual void OnComplete(int64 transaction_id) OVERRIDE {}
+
+ private:
+ virtual ~FakeDatabaseCallbacks() {}
+};
+
+TEST(IndexedDBDatabaseTest, ConnectionLifecycle) {
+ scoped_refptr<IndexedDBFakeBackingStore> backing_store =
+ new IndexedDBFakeBackingStore();
+ EXPECT_TRUE(backing_store->HasOneRef()); // local
+
+ IndexedDBFactory* factory = 0;
+ scoped_refptr<IndexedDBDatabase> db =
+ IndexedDBDatabase::Create(ASCIIToUTF16("db"),
+ backing_store,
+ factory,
+ IndexedDBDatabase::Identifier());
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+
+ scoped_refptr<MockOpenCallbacks> request1(new MockOpenCallbacks());
+ scoped_refptr<FakeDatabaseCallbacks> callbacks1(new FakeDatabaseCallbacks());
+ const int64 transaction_id1 = 1;
+ db->OpenConnection(request1,
+ callbacks1,
+ transaction_id1,
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // db, connection count > 0
+
+ scoped_refptr<MockOpenCallbacks> request2(new MockOpenCallbacks());
+ scoped_refptr<FakeDatabaseCallbacks> callbacks2(new FakeDatabaseCallbacks());
+ const int64 transaction_id2 = 2;
+ db->OpenConnection(request2,
+ callbacks2,
+ transaction_id2,
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and connection
+
+ db->Close(request1->connection());
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and connection
+
+ db->Close(request2->connection());
+ EXPECT_TRUE(backing_store->HasOneRef());
+ EXPECT_FALSE(db->BackingStore());
+
+ db = NULL;
+}
+
+class MockAbortCallbacks : public IndexedDBDatabaseCallbacks {
+ public:
+ MockAbortCallbacks()
+ : IndexedDBDatabaseCallbacks(NULL, 0, 0), abort_called_(false) {}
+
+ virtual void OnAbort(int64 transaction_id,
+ const IndexedDBDatabaseError& error) OVERRIDE {
+ abort_called_ = true;
+ }
+
+ private:
+ virtual ~MockAbortCallbacks() { EXPECT_TRUE(abort_called_); }
+ bool abort_called_;
+};
+
+TEST(IndexedDBDatabaseTest, ForcedClose) {
+ scoped_refptr<IndexedDBFakeBackingStore> backing_store =
+ new IndexedDBFakeBackingStore();
+ EXPECT_TRUE(backing_store->HasOneRef());
+
+ IndexedDBFactory* factory = 0;
+ scoped_refptr<IndexedDBDatabase> database =
+ IndexedDBDatabase::Create(ASCIIToUTF16("db"),
+ backing_store,
+ factory,
+ IndexedDBDatabase::Identifier());
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+
+ scoped_refptr<MockAbortCallbacks> callbacks(new MockAbortCallbacks());
+ scoped_refptr<MockOpenCallbacks> request(new MockOpenCallbacks());
+ const int64 upgrade_transaction_id = 3;
+ database->OpenConnection(request,
+ callbacks,
+ upgrade_transaction_id,
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+ EXPECT_EQ(database, request->connection()->database());
+
+ const int64 transaction_id = 123;
+ const std::vector<int64> scope;
+ database->CreateTransaction(transaction_id,
+ request->connection(),
+ scope,
+ indexed_db::TRANSACTION_READ_ONLY);
+
+ request->connection()->ForceClose();
+
+ EXPECT_TRUE(backing_store->HasOneRef()); // local
+}
+
+class MockDeleteCallbacks : public IndexedDBCallbacks {
+ public:
+ MockDeleteCallbacks()
+ : IndexedDBCallbacks(NULL, 0, 0),
+ blocked_called_(false),
+ success_void_called_(false) {}
+
+ virtual void OnBlocked(int64 existing_version) OVERRIDE {
+ blocked_called_ = true;
+ }
+ virtual void OnSuccess() OVERRIDE { success_void_called_ = true; }
+
+ bool blocked_called() const { return blocked_called_; }
+
+ private:
+ virtual ~MockDeleteCallbacks() { EXPECT_TRUE(success_void_called_); }
+
+ scoped_ptr<IndexedDBConnection> connection_;
+ bool blocked_called_;
+ bool success_void_called_;
+};
+
+TEST(IndexedDBDatabaseTest, PendingDelete) {
+ scoped_refptr<IndexedDBFakeBackingStore> backing_store =
+ new IndexedDBFakeBackingStore();
+ EXPECT_TRUE(backing_store->HasOneRef()); // local
+
+ IndexedDBFactory* factory = 0;
+ scoped_refptr<IndexedDBDatabase> db =
+ IndexedDBDatabase::Create(ASCIIToUTF16("db"),
+ backing_store,
+ factory,
+ IndexedDBDatabase::Identifier());
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+
+ scoped_refptr<MockOpenCallbacks> request1(new MockOpenCallbacks());
+ scoped_refptr<FakeDatabaseCallbacks> callbacks1(new FakeDatabaseCallbacks());
+ const int64 transaction_id1 = 1;
+ db->OpenConnection(request1,
+ callbacks1,
+ transaction_id1,
+ IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
+
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+
+ scoped_refptr<MockDeleteCallbacks> request2(new MockDeleteCallbacks());
+ db->DeleteDatabase(request2);
+
+ EXPECT_TRUE(request2->blocked_called());
+ EXPECT_FALSE(backing_store->HasOneRef()); // local and db
+
+ db->Close(request1->connection());
+
+ EXPECT_FALSE(db->BackingStore());
+ EXPECT_TRUE(backing_store->HasOneRef()); // local
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.cc b/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.cc
new file mode 100644
index 00000000000..e926a9073d7
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.cc
@@ -0,0 +1,857 @@
+// 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 "content/browser/indexed_db/indexed_db_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/process/process.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/browser/renderer_host/render_message_filter.h"
+#include "content/common/indexed_db/indexed_db_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
+#include "url/gurl.h"
+#include "webkit/browser/database/database_util.h"
+#include "webkit/common/database/database_identifier.h"
+
+using webkit_database::DatabaseUtil;
+using WebKit::WebIDBKey;
+
+namespace content {
+
+IndexedDBDispatcherHost::IndexedDBDispatcherHost(
+ int ipc_process_id,
+ IndexedDBContextImpl* indexed_db_context)
+ : indexed_db_context_(indexed_db_context),
+ database_dispatcher_host_(new DatabaseDispatcherHost(this)),
+ cursor_dispatcher_host_(new CursorDispatcherHost(this)),
+ ipc_process_id_(ipc_process_id) {
+ DCHECK(indexed_db_context_);
+}
+
+IndexedDBDispatcherHost::~IndexedDBDispatcherHost() {}
+
+void IndexedDBDispatcherHost::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ bool success = indexed_db_context_->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBDispatcherHost::ResetDispatcherHosts, this));
+
+ if (!success)
+ ResetDispatcherHosts();
+}
+
+void IndexedDBDispatcherHost::OnDestruct() const {
+ // The last reference to the dispatcher may be a posted task, which would
+ // be destructed on the IndexedDB thread. Without this override, that would
+ // take the dispatcher with it. Since the dispatcher may be keeping the
+ // IndexedDBContext alive, it might be destructed to on its own thread,
+ // which is not supported. Ensure destruction runs on the IO thread instead.
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+void IndexedDBDispatcherHost::ResetDispatcherHosts() {
+ // It is important that the various *_dispatcher_host_ members are reset
+ // on the IndexedDB thread, since there might be incoming messages on that
+ // thread, and we must not reset the dispatcher hosts until after those
+ // messages are processed.
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+
+ // Note that we explicitly separate CloseAll() from destruction of the
+ // DatabaseDispatcherHost, since CloseAll() can invoke callbacks which need to
+ // be dispatched through database_dispatcher_host_.
+ database_dispatcher_host_->CloseAll();
+ database_dispatcher_host_.reset();
+ cursor_dispatcher_host_.reset();
+}
+
+base::TaskRunner* IndexedDBDispatcherHost::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ if (IPC_MESSAGE_CLASS(message) == IndexedDBMsgStart)
+ return indexed_db_context_->TaskRunner();
+ return NULL;
+}
+
+bool IndexedDBDispatcherHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ if (IPC_MESSAGE_CLASS(message) != IndexedDBMsgStart)
+ return false;
+
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+
+ bool handled =
+ database_dispatcher_host_->OnMessageReceived(message, message_was_ok) ||
+ cursor_dispatcher_host_->OnMessageReceived(message, message_was_ok);
+
+ if (!handled) {
+ handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryGetDatabaseNames,
+ OnIDBFactoryGetDatabaseNames)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryOpen, OnIDBFactoryOpen)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryDeleteDatabase,
+ OnIDBFactoryDeleteDatabase)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ }
+ return handled;
+}
+
+int32 IndexedDBDispatcherHost::Add(IndexedDBCursor* cursor) {
+ if (!cursor_dispatcher_host_) {
+ return 0;
+ }
+ return cursor_dispatcher_host_->map_.Add(cursor);
+}
+
+int32 IndexedDBDispatcherHost::Add(IndexedDBConnection* connection,
+ int32 ipc_thread_id,
+ const GURL& origin_url) {
+ if (!database_dispatcher_host_) {
+ connection->Close();
+ delete connection;
+ return -1;
+ }
+ int32 ipc_database_id = database_dispatcher_host_->map_.Add(connection);
+ Context()->ConnectionOpened(origin_url, connection);
+ database_dispatcher_host_->database_url_map_[ipc_database_id] = origin_url;
+ return ipc_database_id;
+}
+
+void IndexedDBDispatcherHost::RegisterTransactionId(int64 host_transaction_id,
+ const GURL& url) {
+ if (!database_dispatcher_host_)
+ return;
+ database_dispatcher_host_->transaction_url_map_[host_transaction_id] = url;
+}
+
+int64 IndexedDBDispatcherHost::HostTransactionId(int64 transaction_id) {
+ // Inject the renderer process id into the transaction id, to
+ // uniquely identify this transaction, and effectively bind it to
+ // the renderer that initiated it. The lower 32 bits of
+ // transaction_id are guaranteed to be unique within that renderer.
+ base::ProcessId pid = peer_pid();
+ DCHECK(!(transaction_id >> 32)) << "Transaction ids can only be 32 bits";
+ COMPILE_ASSERT(sizeof(base::ProcessId) <= sizeof(int32),
+ Process_ID_must_fit_in_32_bits);
+
+ return transaction_id | (static_cast<uint64>(pid) << 32);
+}
+
+int64 IndexedDBDispatcherHost::RendererTransactionId(
+ int64 host_transaction_id) {
+ DCHECK(host_transaction_id >> 32 == peer_pid())
+ << "Invalid renderer target for transaction id";
+ return host_transaction_id & 0xffffffff;
+}
+
+IndexedDBCursor* IndexedDBDispatcherHost::GetCursorFromId(int32 ipc_cursor_id) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ return cursor_dispatcher_host_->map_.Lookup(ipc_cursor_id);
+}
+
+::IndexedDBDatabaseMetadata IndexedDBDispatcherHost::ConvertMetadata(
+ const content::IndexedDBDatabaseMetadata& web_metadata) {
+ ::IndexedDBDatabaseMetadata metadata;
+ metadata.id = web_metadata.id;
+ metadata.name = web_metadata.name;
+ metadata.version = web_metadata.version;
+ metadata.int_version = web_metadata.int_version;
+ metadata.max_object_store_id = web_metadata.max_object_store_id;
+
+ for (content::IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator iter =
+ web_metadata.object_stores.begin();
+ iter != web_metadata.object_stores.end();
+ ++iter) {
+
+ const content::IndexedDBObjectStoreMetadata& web_store_metadata =
+ iter->second;
+ ::IndexedDBObjectStoreMetadata idb_store_metadata;
+ idb_store_metadata.id = web_store_metadata.id;
+ idb_store_metadata.name = web_store_metadata.name;
+ idb_store_metadata.keyPath = web_store_metadata.key_path;
+ idb_store_metadata.autoIncrement = web_store_metadata.auto_increment;
+ idb_store_metadata.max_index_id = web_store_metadata.max_index_id;
+
+ for (content::IndexedDBObjectStoreMetadata::IndexMap::const_iterator
+ index_iter = web_store_metadata.indexes.begin();
+ index_iter != web_store_metadata.indexes.end();
+ ++index_iter) {
+ const content::IndexedDBIndexMetadata& web_index_metadata =
+ index_iter->second;
+ ::IndexedDBIndexMetadata idb_index_metadata;
+ idb_index_metadata.id = web_index_metadata.id;
+ idb_index_metadata.name = web_index_metadata.name;
+ idb_index_metadata.keyPath = web_index_metadata.key_path;
+ idb_index_metadata.unique = web_index_metadata.unique;
+ idb_index_metadata.multiEntry = web_index_metadata.multi_entry;
+ idb_store_metadata.indexes.push_back(idb_index_metadata);
+ }
+ metadata.object_stores.push_back(idb_store_metadata);
+ }
+ return metadata;
+}
+
+void IndexedDBDispatcherHost::OnIDBFactoryGetDatabaseNames(
+ const IndexedDBHostMsg_FactoryGetDatabaseNames_Params& params) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ base::FilePath indexed_db_path = indexed_db_context_->data_path();
+
+ Context()->GetIDBFactory()->GetDatabaseNames(
+ new IndexedDBCallbacks(
+ this, params.ipc_thread_id, params.ipc_callbacks_id),
+ params.database_identifier,
+ indexed_db_path);
+}
+
+void IndexedDBDispatcherHost::OnIDBFactoryOpen(
+ const IndexedDBHostMsg_FactoryOpen_Params& params) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ base::FilePath indexed_db_path = indexed_db_context_->data_path();
+
+ GURL origin_url =
+ webkit_database::GetOriginFromIdentifier(params.database_identifier);
+
+ int64 host_transaction_id = HostTransactionId(params.transaction_id);
+
+ // TODO(dgrogan): Don't let a non-existing database be opened (and therefore
+ // created) if this origin is already over quota.
+ scoped_refptr<IndexedDBCallbacks> callbacks =
+ new IndexedDBCallbacks(this,
+ params.ipc_thread_id,
+ params.ipc_callbacks_id,
+ params.ipc_database_callbacks_id,
+ host_transaction_id,
+ origin_url);
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks =
+ new IndexedDBDatabaseCallbacks(
+ this, params.ipc_thread_id, params.ipc_database_callbacks_id);
+ Context()->GetIDBFactory()->Open(params.name,
+ params.version,
+ host_transaction_id,
+ callbacks,
+ database_callbacks,
+ params.database_identifier,
+ indexed_db_path);
+}
+
+void IndexedDBDispatcherHost::OnIDBFactoryDeleteDatabase(
+ const IndexedDBHostMsg_FactoryDeleteDatabase_Params& params) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ base::FilePath indexed_db_path = indexed_db_context_->data_path();
+ Context()->GetIDBFactory()->DeleteDatabase(
+ params.name,
+ new IndexedDBCallbacks(
+ this, params.ipc_thread_id, params.ipc_callbacks_id),
+ params.database_identifier,
+ indexed_db_path);
+}
+
+void IndexedDBDispatcherHost::FinishTransaction(int64 host_transaction_id,
+ bool committed) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ if (!database_dispatcher_host_)
+ return;
+ TransactionIDToURLMap& transaction_url_map =
+ database_dispatcher_host_->transaction_url_map_;
+ TransactionIDToSizeMap& transaction_size_map =
+ database_dispatcher_host_->transaction_size_map_;
+ TransactionIDToDatabaseIDMap& transaction_database_map =
+ database_dispatcher_host_->transaction_database_map_;
+ if (committed)
+ Context()->TransactionComplete(transaction_url_map[host_transaction_id]);
+ // It's unclear if std::map::erase(key) has defined behavior if the
+ // key is not found.
+ // TODO(alecflett): Remove if it is proven that it is safe.
+ if (transaction_url_map.find(host_transaction_id) !=
+ transaction_url_map.end())
+ transaction_url_map.erase(host_transaction_id);
+ if (transaction_size_map.find(host_transaction_id) !=
+ transaction_size_map.end())
+ transaction_size_map.erase(host_transaction_id);
+ if (transaction_database_map.find(host_transaction_id) !=
+ transaction_database_map.end())
+ transaction_database_map.erase(host_transaction_id);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Helper templates.
+//
+
+template <typename ObjectType>
+ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess(
+ IDMap<ObjectType, IDMapOwnPointer>* map,
+ int32 ipc_return_object_id) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ ObjectType* return_object = map->Lookup(ipc_return_object_id);
+ if (!return_object) {
+ NOTREACHED() << "Uh oh, couldn't find object with id "
+ << ipc_return_object_id;
+ RecordAction(UserMetricsAction("BadMessageTerminate_IDBMF"));
+ BadMessageReceived();
+ }
+ return return_object;
+}
+
+template <typename ObjectType>
+ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess(
+ RefIDMap<ObjectType>* map,
+ int32 ipc_return_object_id) {
+ DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ ObjectType* return_object = map->Lookup(ipc_return_object_id);
+ if (!return_object) {
+ NOTREACHED() << "Uh oh, couldn't find object with id "
+ << ipc_return_object_id;
+ RecordAction(UserMetricsAction("BadMessageTerminate_IDBMF"));
+ BadMessageReceived();
+ }
+ return return_object;
+}
+
+template <typename MapType>
+void IndexedDBDispatcherHost::DestroyObject(MapType* map, int32 ipc_object_id) {
+ GetOrTerminateProcess(map, ipc_object_id);
+ map->Remove(ipc_object_id);
+}
+
+//////////////////////////////////////////////////////////////////////
+// IndexedDBDispatcherHost::DatabaseDispatcherHost
+//
+
+IndexedDBDispatcherHost::DatabaseDispatcherHost::DatabaseDispatcherHost(
+ IndexedDBDispatcherHost* parent)
+ : parent_(parent) {
+ map_.set_check_on_null_data(true);
+}
+
+IndexedDBDispatcherHost::DatabaseDispatcherHost::~DatabaseDispatcherHost() {
+ // TODO(alecflett): uncomment these when we find the source of these leaks.
+ // DCHECK(transaction_size_map_.empty());
+ // DCHECK(transaction_url_map_.empty());
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::CloseAll() {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ // Abort outstanding transactions started by connections in the associated
+ // front-end to unblock later transactions. This should only occur on unclean
+ // (crash) or abrupt (process-kill) shutdowns.
+ for (TransactionIDToDatabaseIDMap::iterator iter =
+ transaction_database_map_.begin();
+ iter != transaction_database_map_.end();) {
+ int64 transaction_id = iter->first;
+ int32 ipc_database_id = iter->second;
+ ++iter;
+ IndexedDBConnection* connection = map_.Lookup(ipc_database_id);
+ if (connection) {
+ connection->database()->Abort(
+ transaction_id,
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError));
+ }
+ }
+ DCHECK(transaction_database_map_.empty());
+
+ for (WebIDBObjectIDToURLMap::iterator iter = database_url_map_.begin();
+ iter != database_url_map_.end();
+ iter++) {
+ IndexedDBConnection* connection = map_.Lookup(iter->first);
+ if (connection) {
+ connection->Close();
+ parent_->Context()->ConnectionClosed(iter->second, connection);
+ }
+ }
+}
+
+bool IndexedDBDispatcherHost::DatabaseDispatcherHost::OnMessageReceived(
+ const IPC::Message& message,
+ bool* msg_is_ok) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(
+ IndexedDBDispatcherHost::DatabaseDispatcherHost, message, *msg_is_ok)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateObjectStore,
+ OnCreateObjectStore)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteObjectStore,
+ OnDeleteObjectStore)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateTransaction,
+ OnCreateTransaction)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClose, OnClose)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDestroyed, OnDestroyed)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseGet, OnGet)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabasePut, OnPut)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexKeys, OnSetIndexKeys)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexesReady,
+ OnSetIndexesReady)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseOpenCursor, OnOpenCursor)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCount, OnCount)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteRange, OnDeleteRange)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClear, OnClear)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateIndex, OnCreateIndex)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteIndex, OnDeleteIndex)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseAbort, OnAbort)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCommit, OnCommit)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::Send(
+ IPC::Message* message) {
+ parent_->Send(message);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateObjectStore(
+ const IndexedDBHostMsg_DatabaseCreateObjectStore_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id);
+ connection->database()->CreateObjectStore(host_transaction_id,
+ params.object_store_id,
+ params.name,
+ params.key_path,
+ params.auto_increment);
+ if (parent_->Context()->IsOverQuota(
+ database_url_map_[params.ipc_database_id])) {
+ connection->database()->Abort(
+ host_transaction_id,
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError));
+ }
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteObjectStore(
+ int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ connection->database()->DeleteObjectStore(
+ parent_->HostTransactionId(transaction_id), object_store_id);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateTransaction(
+ const IndexedDBHostMsg_DatabaseCreateTransaction_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id);
+
+ connection->database()->CreateTransaction(
+ host_transaction_id, connection, params.object_store_ids, params.mode);
+ transaction_database_map_[host_transaction_id] = params.ipc_database_id;
+ parent_->RegisterTransactionId(host_transaction_id,
+ database_url_map_[params.ipc_database_id]);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClose(
+ int32 ipc_database_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+ connection->Close();
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDestroyed(
+ int32 ipc_object_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection = map_.Lookup(ipc_object_id);
+ parent_->Context()
+ ->ConnectionClosed(database_url_map_[ipc_object_id], connection);
+ database_url_map_.erase(ipc_object_id);
+ parent_->DestroyObject(&map_, ipc_object_id);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnGet(
+ const IndexedDBHostMsg_DatabaseGet_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks(
+ parent_, params.ipc_thread_id, params.ipc_callbacks_id));
+ connection->database()->Get(
+ parent_->HostTransactionId(params.transaction_id),
+ params.object_store_id,
+ params.index_id,
+ make_scoped_ptr(new IndexedDBKeyRange(params.key_range)),
+ params.key_only,
+ callbacks);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnPut(
+ const IndexedDBHostMsg_DatabasePut_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+ scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks(
+ parent_, params.ipc_thread_id, params.ipc_callbacks_id));
+
+ int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id);
+ // TODO(alecflett): Avoid a copy here.
+ std::string value_copy(params.value);
+ connection->database()->Put(
+ host_transaction_id,
+ params.object_store_id,
+ &value_copy,
+ make_scoped_ptr(new IndexedDBKey(params.key)),
+ static_cast<IndexedDBDatabase::PutMode>(params.put_mode),
+ callbacks,
+ params.index_ids,
+ params.index_keys);
+ TransactionIDToSizeMap* map =
+ &parent_->database_dispatcher_host_->transaction_size_map_;
+ // Size can't be big enough to overflow because it represents the
+ // actual bytes passed through IPC.
+ (*map)[host_transaction_id] += params.value.size();
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexKeys(
+ const IndexedDBHostMsg_DatabaseSetIndexKeys_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id);
+ if (params.index_ids.size() != params.index_keys.size()) {
+ connection->database()->Abort(
+ host_transaction_id,
+ IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Malformed IPC message: index_ids.size() != index_keys.size()"));
+ return;
+ }
+
+ connection->database()->SetIndexKeys(
+ host_transaction_id,
+ params.object_store_id,
+ make_scoped_ptr(new IndexedDBKey(params.primary_key)),
+ params.index_ids,
+ params.index_keys);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexesReady(
+ int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id,
+ const std::vector<int64>& index_ids) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ connection->database()->SetIndexesReady(
+ parent_->HostTransactionId(transaction_id), object_store_id, index_ids);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnOpenCursor(
+ const IndexedDBHostMsg_DatabaseOpenCursor_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks(
+ parent_, params.ipc_thread_id, params.ipc_callbacks_id, -1));
+ connection->database()->OpenCursor(
+ parent_->HostTransactionId(params.transaction_id),
+ params.object_store_id,
+ params.index_id,
+ make_scoped_ptr(new IndexedDBKeyRange(params.key_range)),
+ static_cast<indexed_db::CursorDirection>(params.direction),
+ params.key_only,
+ static_cast<IndexedDBDatabase::TaskType>(params.task_type),
+ callbacks);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCount(
+ const IndexedDBHostMsg_DatabaseCount_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks(
+ parent_, params.ipc_thread_id, params.ipc_callbacks_id));
+ connection->database()->Count(
+ parent_->HostTransactionId(params.transaction_id),
+ params.object_store_id,
+ params.index_id,
+ make_scoped_ptr(new IndexedDBKeyRange(params.key_range)),
+ callbacks);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteRange(
+ const IndexedDBHostMsg_DatabaseDeleteRange_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks(
+ parent_, params.ipc_thread_id, params.ipc_callbacks_id));
+ connection->database()->DeleteRange(
+ parent_->HostTransactionId(params.transaction_id),
+ params.object_store_id,
+ make_scoped_ptr(new IndexedDBKeyRange(params.key_range)),
+ callbacks);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClear(
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ scoped_refptr<IndexedDBCallbacks> callbacks(
+ new IndexedDBCallbacks(parent_, ipc_thread_id, ipc_callbacks_id));
+
+ connection->database()->Clear(
+ parent_->HostTransactionId(transaction_id), object_store_id, callbacks);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnAbort(
+ int32 ipc_database_id,
+ int64 transaction_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ connection->database()->Abort(parent_->HostTransactionId(transaction_id));
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCommit(
+ int32 ipc_database_id,
+ int64 transaction_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ int64 host_transaction_id = parent_->HostTransactionId(transaction_id);
+ int64 transaction_size = transaction_size_map_[host_transaction_id];
+ if (transaction_size &&
+ parent_->Context()->WouldBeOverQuota(
+ transaction_url_map_[host_transaction_id], transaction_size)) {
+ connection->database()->Abort(
+ host_transaction_id,
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError));
+ return;
+ }
+
+ connection->database()->Commit(host_transaction_id);
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateIndex(
+ const IndexedDBHostMsg_DatabaseCreateIndex_Params& params) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, params.ipc_database_id);
+ if (!connection)
+ return;
+
+ int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id);
+ connection->database()->CreateIndex(host_transaction_id,
+ params.object_store_id,
+ params.index_id,
+ params.name,
+ params.key_path,
+ params.unique,
+ params.multi_entry);
+ if (parent_->Context()->IsOverQuota(
+ database_url_map_[params.ipc_database_id])) {
+ connection->database()->Abort(
+ host_transaction_id,
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError));
+ }
+}
+
+void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteIndex(
+ int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBConnection* connection =
+ parent_->GetOrTerminateProcess(&map_, ipc_database_id);
+ if (!connection)
+ return;
+
+ connection->database()->DeleteIndex(
+ parent_->HostTransactionId(transaction_id), object_store_id, index_id);
+}
+
+//////////////////////////////////////////////////////////////////////
+// IndexedDBDispatcherHost::CursorDispatcherHost
+//
+
+IndexedDBDispatcherHost::CursorDispatcherHost::CursorDispatcherHost(
+ IndexedDBDispatcherHost* parent)
+ : parent_(parent) {
+ map_.set_check_on_null_data(true);
+}
+
+IndexedDBDispatcherHost::CursorDispatcherHost::~CursorDispatcherHost() {}
+
+bool IndexedDBDispatcherHost::CursorDispatcherHost::OnMessageReceived(
+ const IPC::Message& message,
+ bool* msg_is_ok) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(
+ IndexedDBDispatcherHost::CursorDispatcherHost, message, *msg_is_ok)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorAdvance, OnAdvance)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorContinue, OnContinue)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetch, OnPrefetch)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetchReset, OnPrefetchReset)
+ IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDestroyed, OnDestroyed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::Send(
+ IPC::Message* message) {
+ parent_->Send(message);
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::OnAdvance(
+ int32 ipc_cursor_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ unsigned long count) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBCursor* idb_cursor =
+ parent_->GetOrTerminateProcess(&map_, ipc_cursor_id);
+ if (!idb_cursor)
+ return;
+
+ idb_cursor->Advance(
+ count,
+ new IndexedDBCallbacks(
+ parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id));
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::OnContinue(
+ int32 ipc_cursor_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ const IndexedDBKey& key) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBCursor* idb_cursor =
+ parent_->GetOrTerminateProcess(&map_, ipc_cursor_id);
+ if (!idb_cursor)
+ return;
+
+ idb_cursor->Continue(
+ make_scoped_ptr(new IndexedDBKey(key)),
+ new IndexedDBCallbacks(
+ parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id));
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetch(
+ int32 ipc_cursor_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int n) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBCursor* idb_cursor =
+ parent_->GetOrTerminateProcess(&map_, ipc_cursor_id);
+ if (!idb_cursor)
+ return;
+
+ idb_cursor->PrefetchContinue(
+ n,
+ new IndexedDBCallbacks(
+ parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id));
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetchReset(
+ int32 ipc_cursor_id,
+ int used_prefetches,
+ int unused_prefetches) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ IndexedDBCursor* idb_cursor =
+ parent_->GetOrTerminateProcess(&map_, ipc_cursor_id);
+ if (!idb_cursor)
+ return;
+
+ idb_cursor->PrefetchReset(used_prefetches, unused_prefetches);
+}
+
+void IndexedDBDispatcherHost::CursorDispatcherHost::OnDestroyed(
+ int32 ipc_object_id) {
+ DCHECK(
+ parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread());
+ parent_->DestroyObject(&map_, ipc_object_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.h b/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.h
new file mode 100644
index 00000000000..e254cadf486
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_dispatcher_host.h
@@ -0,0 +1,243 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DISPATCHER_HOST_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/id_map.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "url/gurl.h"
+
+struct IndexedDBDatabaseMetadata;
+struct IndexedDBHostMsg_DatabaseCount_Params;
+struct IndexedDBHostMsg_DatabaseCreateIndex_Params;
+struct IndexedDBHostMsg_DatabaseCreateObjectStore_Params;
+struct IndexedDBHostMsg_DatabaseCreateTransaction_Params;
+struct IndexedDBHostMsg_DatabaseDeleteRange_Params;
+struct IndexedDBHostMsg_DatabaseGet_Params;
+struct IndexedDBHostMsg_DatabaseOpenCursor_Params;
+struct IndexedDBHostMsg_DatabasePut_Params;
+struct IndexedDBHostMsg_DatabaseSetIndexKeys_Params;
+struct IndexedDBHostMsg_FactoryDeleteDatabase_Params;
+struct IndexedDBHostMsg_FactoryGetDatabaseNames_Params;
+struct IndexedDBHostMsg_FactoryOpen_Params;
+
+namespace content {
+class IndexedDBConnection;
+class IndexedDBContextImpl;
+class IndexedDBCursor;
+class IndexedDBKey;
+class IndexedDBKeyPath;
+class IndexedDBKeyRange;
+struct IndexedDBDatabaseMetadata;
+
+// Handles all IndexedDB related messages from a particular renderer process.
+class IndexedDBDispatcherHost : public BrowserMessageFilter {
+ public:
+ // Only call the constructor from the UI thread.
+ IndexedDBDispatcherHost(int ipc_process_id,
+ IndexedDBContextImpl* indexed_db_context);
+
+ static ::IndexedDBDatabaseMetadata ConvertMetadata(
+ const content::IndexedDBDatabaseMetadata& metadata);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ void FinishTransaction(int64 host_transaction_id, bool committed);
+
+ // A shortcut for accessing our context.
+ IndexedDBContextImpl* Context() { return indexed_db_context_; }
+
+ // IndexedDBCallbacks call these methods to add the results into the
+ // applicable map. See below for more details.
+ int32 Add(IndexedDBCursor* cursor);
+ int32 Add(IndexedDBConnection* connection,
+ int32 ipc_thread_id,
+ const GURL& origin_url);
+
+ void RegisterTransactionId(int64 host_transaction_id, const GURL& origin_url);
+
+ IndexedDBCursor* GetCursorFromId(int32 ipc_cursor_id);
+
+ int64 HostTransactionId(int64 transaction_id);
+ int64 RendererTransactionId(int64 host_transaction_id);
+
+ private:
+ // Friends to enable OnDestruct() delegation.
+ friend class BrowserThread;
+ friend class base::DeleteHelper<IndexedDBDispatcherHost>;
+
+ virtual ~IndexedDBDispatcherHost();
+
+ // Message processing. Most of the work is delegated to the dispatcher hosts
+ // below.
+ void OnIDBFactoryGetDatabaseNames(
+ const IndexedDBHostMsg_FactoryGetDatabaseNames_Params& p);
+ void OnIDBFactoryOpen(const IndexedDBHostMsg_FactoryOpen_Params& p);
+
+ void OnIDBFactoryDeleteDatabase(
+ const IndexedDBHostMsg_FactoryDeleteDatabase_Params& p);
+
+ void ResetDispatcherHosts();
+
+ // IDMap for RefCounted types
+ template <typename RefCountedType>
+ class RefIDMap {
+ private:
+ typedef int32 KeyType;
+
+ public:
+ RefIDMap() {}
+ ~RefIDMap() {}
+
+ KeyType Add(RefCountedType* data) {
+ return map_.Add(new scoped_refptr<RefCountedType>(data));
+ }
+
+ RefCountedType* Lookup(KeyType id) {
+ scoped_refptr<RefCountedType>* ptr = map_.Lookup(id);
+ if (ptr == NULL)
+ return NULL;
+ return ptr->get();
+ }
+
+ void Remove(KeyType id) { map_.Remove(id); }
+
+ void set_check_on_null_data(bool value) {
+ map_.set_check_on_null_data(value);
+ }
+
+ private:
+ IDMap<scoped_refptr<RefCountedType>, IDMapOwnPointer> map_;
+ };
+
+ // Helper templates.
+ template <class ReturnType>
+ ReturnType* GetOrTerminateProcess(IDMap<ReturnType, IDMapOwnPointer>* map,
+ int32 ipc_return_object_id);
+ template <class ReturnType>
+ ReturnType* GetOrTerminateProcess(RefIDMap<ReturnType>* map,
+ int32 ipc_return_object_id);
+
+ template <typename MapType>
+ void DestroyObject(MapType* map, int32 ipc_object_id);
+
+ // Used in nested classes.
+ typedef std::map<int32, GURL> WebIDBObjectIDToURLMap;
+
+ typedef std::map<int64, GURL> TransactionIDToURLMap;
+ typedef std::map<int64, uint64> TransactionIDToSizeMap;
+ typedef std::map<int64, int64> TransactionIDToDatabaseIDMap;
+
+ class DatabaseDispatcherHost {
+ public:
+ explicit DatabaseDispatcherHost(IndexedDBDispatcherHost* parent);
+ ~DatabaseDispatcherHost();
+
+ void CloseAll();
+ bool OnMessageReceived(const IPC::Message& message, bool* msg_is_ok);
+ void Send(IPC::Message* message);
+
+ void OnCreateObjectStore(
+ const IndexedDBHostMsg_DatabaseCreateObjectStore_Params& params);
+ void OnDeleteObjectStore(int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id);
+ void OnCreateTransaction(
+ const IndexedDBHostMsg_DatabaseCreateTransaction_Params&);
+ void OnOpen(int32 ipc_database_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id);
+ void OnClose(int32 ipc_database_id);
+ void OnDestroyed(int32 ipc_database_id);
+
+ void OnGet(const IndexedDBHostMsg_DatabaseGet_Params& params);
+ void OnPut(const IndexedDBHostMsg_DatabasePut_Params& params);
+ void OnSetIndexKeys(
+ const IndexedDBHostMsg_DatabaseSetIndexKeys_Params& params);
+ void OnSetIndexesReady(int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id,
+ const std::vector<int64>& ids);
+ void OnOpenCursor(const IndexedDBHostMsg_DatabaseOpenCursor_Params& params);
+ void OnCount(const IndexedDBHostMsg_DatabaseCount_Params& params);
+ void OnDeleteRange(
+ const IndexedDBHostMsg_DatabaseDeleteRange_Params& params);
+ void OnClear(int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id);
+ void OnCreateIndex(
+ const IndexedDBHostMsg_DatabaseCreateIndex_Params& params);
+ void OnDeleteIndex(int32 ipc_database_id,
+ int64 transaction_id,
+ int64 object_store_id,
+ int64 index_id);
+
+ void OnAbort(int32 ipc_database_id, int64 transaction_id);
+ void OnCommit(int32 ipc_database_id, int64 transaction_id);
+ IndexedDBDispatcherHost* parent_;
+ IDMap<IndexedDBConnection, IDMapOwnPointer> map_;
+ WebIDBObjectIDToURLMap database_url_map_;
+ TransactionIDToSizeMap transaction_size_map_;
+ TransactionIDToURLMap transaction_url_map_;
+ TransactionIDToDatabaseIDMap transaction_database_map_;
+ };
+
+ class CursorDispatcherHost {
+ public:
+ explicit CursorDispatcherHost(IndexedDBDispatcherHost* parent);
+ ~CursorDispatcherHost();
+
+ bool OnMessageReceived(const IPC::Message& message, bool* msg_is_ok);
+ void Send(IPC::Message* message);
+
+ void OnAdvance(int32 ipc_object_store_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ unsigned long count);
+ void OnContinue(int32 ipc_object_store_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ const IndexedDBKey& key);
+ void OnPrefetch(int32 ipc_cursor_id,
+ int32 ipc_thread_id,
+ int32 ipc_callbacks_id,
+ int n);
+ void OnPrefetchReset(int32 ipc_cursor_id,
+ int used_prefetches,
+ int unused_prefetches);
+ void OnDestroyed(int32 ipc_cursor_id);
+
+ IndexedDBDispatcherHost* parent_;
+ RefIDMap<IndexedDBCursor> map_;
+ };
+
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context_;
+
+ // Only access on IndexedDB thread.
+ scoped_ptr<DatabaseDispatcherHost> database_dispatcher_host_;
+ scoped_ptr<CursorDispatcherHost> cursor_dispatcher_host_;
+
+ // Used to dispatch messages to the correct view host.
+ int ipc_process_id_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(IndexedDBDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_factory.cc b/chromium/content/browser/indexed_db/indexed_db_factory.cc
new file mode 100644
index 00000000000..6b8d0c80e00
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_factory.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_factory.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
+#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
+
+namespace content {
+
+template <typename K, typename M>
+static void CleanWeakMap(std::map<K, base::WeakPtr<M> >* map) {
+ std::map<K, base::WeakPtr<M> > other;
+ other.swap(*map);
+
+ typename std::map<K, base::WeakPtr<M> >::const_iterator iter = other.begin();
+ while (iter != other.end()) {
+ if (iter->second.get())
+ (*map)[iter->first] = iter->second;
+ ++iter;
+ }
+}
+
+static std::string ComputeFileIdentifier(const std::string& origin_identifier) {
+ return origin_identifier + "@1";
+}
+
+IndexedDBFactory::IndexedDBFactory() {}
+
+IndexedDBFactory::~IndexedDBFactory() {}
+
+void IndexedDBFactory::RemoveIDBDatabaseBackend(
+ const IndexedDBDatabase::Identifier& unique_identifier) {
+ DCHECK(database_backend_map_.find(unique_identifier) !=
+ database_backend_map_.end());
+ database_backend_map_.erase(unique_identifier);
+}
+
+void IndexedDBFactory::GetDatabaseNames(
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory) {
+ IDB_TRACE("IndexedDBFactory::GetDatabaseNames");
+ // TODO(dgrogan): Plumb data_loss back to script eventually?
+ WebKit::WebIDBCallbacks::DataLoss data_loss;
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ OpenBackingStore(origin_identifier, data_directory, &data_loss);
+ if (!backing_store) {
+ callbacks->OnError(
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error opening backing store for "
+ "indexedDB.webkitGetDatabaseNames."));
+ return;
+ }
+
+ callbacks->OnSuccess(backing_store->GetDatabaseNames());
+}
+
+void IndexedDBFactory::DeleteDatabase(
+ const string16& name,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory) {
+ IDB_TRACE("IndexedDBFactory::DeleteDatabase");
+ IndexedDBDatabase::Identifier unique_identifier(origin_identifier, name);
+ IndexedDBDatabaseMap::iterator it =
+ database_backend_map_.find(unique_identifier);
+ if (it != database_backend_map_.end()) {
+ // If there are any connections to the database, directly delete the
+ // database.
+ it->second->DeleteDatabase(callbacks);
+ return;
+ }
+
+ // TODO(dgrogan): Plumb data_loss back to script eventually?
+ WebKit::WebIDBCallbacks::DataLoss data_loss;
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ OpenBackingStore(origin_identifier, data_directory, &data_loss);
+ if (!backing_store) {
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16("Internal error opening backing store "
+ "for indexedDB.deleteDatabase.")));
+ return;
+ }
+
+ scoped_refptr<IndexedDBDatabase> database_backend =
+ IndexedDBDatabase::Create(name, backing_store, this, unique_identifier);
+ if (!database_backend) {
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16("Internal error creating database backend for "
+ "indexedDB.deleteDatabase.")));
+ return;
+ }
+
+ database_backend_map_[unique_identifier] = database_backend;
+ database_backend->DeleteDatabase(callbacks);
+ database_backend_map_.erase(unique_identifier);
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStore(
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss) {
+ const std::string file_identifier = ComputeFileIdentifier(origin_identifier);
+ const bool open_in_memory = data_directory.empty();
+
+ IndexedDBBackingStoreMap::iterator it2 =
+ backing_store_map_.find(file_identifier);
+ if (it2 != backing_store_map_.end() && it2->second.get())
+ return it2->second.get();
+
+ scoped_refptr<IndexedDBBackingStore> backing_store;
+ if (open_in_memory) {
+ backing_store = IndexedDBBackingStore::OpenInMemory(file_identifier);
+ } else {
+ backing_store = IndexedDBBackingStore::Open(
+ origin_identifier, data_directory, file_identifier, data_loss);
+ }
+
+ if (backing_store.get()) {
+ CleanWeakMap(&backing_store_map_);
+ backing_store_map_[file_identifier] = backing_store->GetWeakPtr();
+ // If an in-memory database, bind lifetime to this factory instance.
+ if (open_in_memory)
+ session_only_backing_stores_.insert(backing_store);
+
+ // All backing stores associated with this factory should be of the same
+ // type.
+ DCHECK(session_only_backing_stores_.empty() || open_in_memory);
+
+ return backing_store;
+ }
+
+ return 0;
+}
+
+void IndexedDBFactory::Open(
+ const string16& name,
+ int64 version,
+ int64 transaction_id,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory) {
+ IDB_TRACE("IndexedDBFactory::Open");
+ scoped_refptr<IndexedDBDatabase> database_backend;
+ IndexedDBDatabase::Identifier unique_identifier(origin_identifier, name);
+ IndexedDBDatabaseMap::iterator it =
+ database_backend_map_.find(unique_identifier);
+ WebKit::WebIDBCallbacks::DataLoss data_loss =
+ WebKit::WebIDBCallbacks::DataLossNone;
+ if (it == database_backend_map_.end()) {
+ scoped_refptr<IndexedDBBackingStore> backing_store =
+ OpenBackingStore(origin_identifier, data_directory, &data_loss);
+ if (!backing_store) {
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16(
+ "Internal error opening backing store for indexedDB.open.")));
+ return;
+ }
+
+ database_backend =
+ IndexedDBDatabase::Create(name, backing_store, this, unique_identifier);
+ if (!database_backend) {
+ callbacks->OnError(IndexedDBDatabaseError(
+ WebKit::WebIDBDatabaseExceptionUnknownError,
+ ASCIIToUTF16(
+ "Internal error creating database backend for indexedDB.open.")));
+ return;
+ }
+
+ database_backend_map_[unique_identifier] = database_backend;
+ } else {
+ database_backend = it->second;
+ }
+
+ database_backend->OpenConnection(
+ callbacks, database_callbacks, transaction_id, version, data_loss);
+}
+
+std::vector<IndexedDBDatabase*> IndexedDBFactory::GetOpenDatabasesForOrigin(
+ const std::string& origin_identifier) const {
+ std::vector<IndexedDBDatabase*> result;
+ for (IndexedDBDatabaseMap::const_iterator it = database_backend_map_.begin();
+ it != database_backend_map_.end(); ++it) {
+ if (it->first.first == origin_identifier)
+ result.push_back(it->second.get());
+ }
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_factory.h b/chromium/content/browser/indexed_db/indexed_db_factory.h
new file mode 100644
index 00000000000..1666af5e49c
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_factory.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "content/browser/indexed_db/indexed_db_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class IndexedDBBackingStore;
+
+class CONTENT_EXPORT IndexedDBFactory
+ : NON_EXPORTED_BASE(public base::RefCounted<IndexedDBFactory>) {
+ public:
+ IndexedDBFactory();
+
+ // Notifications from weak pointers.
+ void RemoveIDBDatabaseBackend(
+ const IndexedDBDatabase::Identifier& unique_identifier);
+
+ void GetDatabaseNames(scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory);
+ void Open(const string16& name,
+ int64 version,
+ int64 transaction_id,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory);
+
+ void DeleteDatabase(const string16& name,
+ scoped_refptr<IndexedDBCallbacks> callbacks,
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory);
+
+ // Iterates over all databases; for diagnostics only.
+ std::vector<IndexedDBDatabase*> GetOpenDatabasesForOrigin(
+ const std::string& origin_identifier) const;
+
+ protected:
+ friend class base::RefCounted<IndexedDBFactory>;
+
+ virtual ~IndexedDBFactory();
+
+ scoped_refptr<IndexedDBBackingStore> OpenBackingStore(
+ const std::string& origin_identifier,
+ const base::FilePath& data_directory,
+ WebKit::WebIDBCallbacks::DataLoss* data_loss);
+
+ private:
+ typedef std::map<IndexedDBDatabase::Identifier,
+ scoped_refptr<IndexedDBDatabase> > IndexedDBDatabaseMap;
+ IndexedDBDatabaseMap database_backend_map_;
+
+ typedef std::map<std::string, base::WeakPtr<IndexedDBBackingStore> >
+ IndexedDBBackingStoreMap;
+ IndexedDBBackingStoreMap backing_store_map_;
+
+ std::set<scoped_refptr<IndexedDBBackingStore> > session_only_backing_stores_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FACTORY_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.cc b/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.cc
new file mode 100644
index 00000000000..981f6f88b4e
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.cc
@@ -0,0 +1,155 @@
+// 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 "content/browser/indexed_db/indexed_db_fake_backing_store.h"
+
+#include "base/memory/scoped_ptr.h"
+
+namespace content {
+
+IndexedDBFakeBackingStore::~IndexedDBFakeBackingStore() {}
+
+std::vector<string16> IndexedDBFakeBackingStore::GetDatabaseNames() {
+ return std::vector<string16>();
+}
+bool IndexedDBFakeBackingStore::GetIDBDatabaseMetaData(
+ const string16& name,
+ IndexedDBDatabaseMetadata*,
+ bool* found) {
+ return true;
+}
+
+bool IndexedDBFakeBackingStore::CreateIDBDatabaseMetaData(
+ const string16& name,
+ const string16& version,
+ int64 int_version,
+ int64* row_id) {
+ return true;
+}
+bool IndexedDBFakeBackingStore::UpdateIDBDatabaseMetaData(
+ Transaction*,
+ int64 row_id,
+ const string16& version) {
+ return false;
+}
+bool IndexedDBFakeBackingStore::UpdateIDBDatabaseIntVersion(Transaction*,
+ int64 row_id,
+ int64 version) {
+ return false;
+}
+bool IndexedDBFakeBackingStore::DeleteDatabase(const string16& name) {
+ return true;
+}
+
+bool IndexedDBFakeBackingStore::CreateObjectStore(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath&,
+ bool auto_increment) {
+ return false;
+}
+
+bool IndexedDBFakeBackingStore::ClearObjectStore(Transaction*,
+ int64 database_id,
+ int64 object_store_id) {
+ return false;
+}
+bool IndexedDBFakeBackingStore::DeleteRecord(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const RecordIdentifier&) {
+ return false;
+}
+bool IndexedDBFakeBackingStore::GetKeyGeneratorCurrentNumber(
+ Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64* current_number) {
+ return true;
+}
+bool IndexedDBFakeBackingStore::MaybeUpdateKeyGeneratorCurrentNumber(
+ Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 new_number,
+ bool check_current) {
+ return true;
+}
+bool IndexedDBFakeBackingStore::KeyExistsInObjectStore(
+ Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey&,
+ RecordIdentifier* found_record_identifier,
+ bool* found) {
+ return true;
+}
+
+bool IndexedDBFakeBackingStore::CreateIndex(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath&,
+ bool is_unique,
+ bool is_multi_entry) {
+ return false;
+}
+
+bool IndexedDBFakeBackingStore::DeleteIndex(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ return false;
+}
+bool IndexedDBFakeBackingStore::PutIndexDataForRecord(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey&,
+ const RecordIdentifier&) {
+ return false;
+}
+
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBFakeBackingStore::OpenObjectStoreKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) {
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+}
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBFakeBackingStore::OpenObjectStoreCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) {
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+}
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBFakeBackingStore::OpenIndexKeyCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) {
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+}
+scoped_ptr<IndexedDBBackingStore::Cursor>
+IndexedDBFakeBackingStore::OpenIndexCursor(
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) {
+ return scoped_ptr<IndexedDBBackingStore::Cursor>();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.h b/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.h
new file mode 100644
index 00000000000..20ebf46f815
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_fake_backing_store.h
@@ -0,0 +1,120 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FAKE_BACKING_STORE_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FAKE_BACKING_STORE_H_
+
+#include <vector>
+
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+
+namespace content {
+
+class IndexedDBFakeBackingStore : public IndexedDBBackingStore {
+ public:
+ IndexedDBFakeBackingStore()
+ : IndexedDBBackingStore(std::string(),
+ scoped_ptr<LevelDBDatabase>(),
+ scoped_ptr<LevelDBComparator>()) {}
+ virtual std::vector<string16> GetDatabaseNames() OVERRIDE;
+ virtual bool GetIDBDatabaseMetaData(const string16& name,
+ IndexedDBDatabaseMetadata*,
+ bool* found) OVERRIDE;
+ virtual bool CreateIDBDatabaseMetaData(const string16& name,
+ const string16& version,
+ int64 int_version,
+ int64* row_id) OVERRIDE;
+ virtual bool UpdateIDBDatabaseMetaData(Transaction*,
+ int64 row_id,
+ const string16& version) OVERRIDE;
+ virtual bool UpdateIDBDatabaseIntVersion(Transaction*,
+ int64 row_id,
+ int64 version) OVERRIDE;
+ virtual bool DeleteDatabase(const string16& name) OVERRIDE;
+
+ virtual bool CreateObjectStore(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const string16& name,
+ const IndexedDBKeyPath&,
+ bool auto_increment) OVERRIDE;
+
+ virtual bool ClearObjectStore(Transaction*,
+ int64 database_id,
+ int64 object_store_id) OVERRIDE;
+ virtual bool DeleteRecord(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const RecordIdentifier&) OVERRIDE;
+ virtual bool GetKeyGeneratorCurrentNumber(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64* current_number) OVERRIDE;
+ virtual bool MaybeUpdateKeyGeneratorCurrentNumber(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 new_number,
+ bool check_current)
+ OVERRIDE;
+ virtual bool KeyExistsInObjectStore(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey&,
+ RecordIdentifier* found_record_identifier,
+ bool* found) OVERRIDE;
+
+ virtual bool CreateIndex(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const string16& name,
+ const IndexedDBKeyPath&,
+ bool is_unique,
+ bool is_multi_entry) OVERRIDE;
+ virtual bool DeleteIndex(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id) OVERRIDE;
+ virtual bool PutIndexDataForRecord(Transaction*,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey&,
+ const RecordIdentifier&) OVERRIDE;
+
+ virtual scoped_ptr<Cursor> OpenObjectStoreKeyCursor(
+ Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) OVERRIDE;
+ virtual scoped_ptr<Cursor> OpenObjectStoreCursor(
+ Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) OVERRIDE;
+ virtual scoped_ptr<Cursor> OpenIndexKeyCursor(
+ Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection) OVERRIDE;
+ virtual scoped_ptr<Cursor> OpenIndexCursor(Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKeyRange& key_range,
+ indexed_db::CursorDirection)
+ OVERRIDE;
+
+ protected:
+ friend class base::RefCounted<IndexedDBFakeBackingStore>;
+ virtual ~IndexedDBFakeBackingStore();
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_FAKE_BACKING_STORE_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_index_writer.cc b/chromium/content/browser/indexed_db/indexed_db_index_writer.cc
new file mode 100644
index 00000000000..3b36cad0ec9
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_index_writer.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_index_writer.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "content/common/indexed_db/indexed_db_key_range.h"
+
+namespace content {
+
+IndexWriter::IndexWriter(
+ const IndexedDBIndexMetadata& index_metadata)
+ : index_metadata_(index_metadata) {}
+
+IndexWriter::IndexWriter(
+ const IndexedDBIndexMetadata& index_metadata,
+ const IndexedDBDatabase::IndexKeys& index_keys)
+ : index_metadata_(index_metadata), index_keys_(index_keys) {}
+
+IndexWriter::~IndexWriter() {}
+
+bool IndexWriter::VerifyIndexKeys(
+ IndexedDBBackingStore* backing_store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ bool* can_add_keys,
+ const IndexedDBKey& primary_key,
+ string16* error_message) const {
+ *can_add_keys = false;
+ for (size_t i = 0; i < index_keys_.size(); ++i) {
+ bool ok = AddingKeyAllowed(backing_store,
+ transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ (index_keys_)[i],
+ primary_key,
+ can_add_keys);
+ if (!ok)
+ return false;
+ if (!*can_add_keys) {
+ if (error_message)
+ *error_message = ASCIIToUTF16("Unable to add key to index '") +
+ index_metadata_.name +
+ ASCIIToUTF16("': at least one key does not satisfy "
+ "the uniqueness requirements.");
+ return true;
+ }
+ }
+ *can_add_keys = true;
+ return true;
+}
+
+void IndexWriter::WriteIndexKeys(
+ const IndexedDBBackingStore::RecordIdentifier& record_identifier,
+ IndexedDBBackingStore* backing_store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) const {
+ int64 index_id = index_metadata_.id;
+ for (size_t i = 0; i < index_keys_.size(); ++i) {
+ bool ok = backing_store->PutIndexDataForRecord(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ index_keys_[i],
+ record_identifier);
+ // This should have already been verified as a valid write during
+ // verify_index_keys.
+ DCHECK(ok);
+ }
+}
+
+bool IndexWriter::AddingKeyAllowed(
+ IndexedDBBackingStore* backing_store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& index_key,
+ const IndexedDBKey& primary_key,
+ bool* allowed) const {
+ *allowed = false;
+ if (!index_metadata_.unique) {
+ *allowed = true;
+ return true;
+ }
+
+ scoped_ptr<IndexedDBKey> found_primary_key;
+ bool found = false;
+ bool ok = backing_store->KeyExistsInIndex(transaction,
+ database_id,
+ object_store_id,
+ index_id,
+ index_key,
+ &found_primary_key,
+ &found);
+ if (!ok)
+ return false;
+ if (!found ||
+ (primary_key.IsValid() && found_primary_key->IsEqual(primary_key)))
+ *allowed = true;
+ return true;
+}
+
+bool MakeIndexWriters(
+ scoped_refptr<IndexedDBTransaction> transaction,
+ IndexedDBBackingStore* backing_store,
+ int64 database_id,
+ const IndexedDBObjectStoreMetadata& object_store,
+ const IndexedDBKey& primary_key, // makes a copy
+ bool key_was_generated,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexedDBDatabase::IndexKeys>& index_keys,
+ ScopedVector<IndexWriter>* index_writers,
+ string16* error_message,
+ bool* completed) {
+ DCHECK_EQ(index_ids.size(), index_keys.size());
+ *completed = false;
+
+ std::map<int64, IndexedDBDatabase::IndexKeys> index_key_map;
+ for (size_t i = 0; i < index_ids.size(); ++i)
+ index_key_map[index_ids[i]] = index_keys[i];
+
+ for (IndexedDBObjectStoreMetadata::IndexMap::const_iterator it =
+ object_store.indexes.begin();
+ it != object_store.indexes.end();
+ ++it) {
+ const IndexedDBIndexMetadata& index = it->second;
+
+ IndexedDBDatabase::IndexKeys keys = index_key_map[it->first];
+ // If the object_store is using auto_increment, then any indexes with an
+ // identical key_path need to also use the primary (generated) key as a key.
+ if (key_was_generated && (index.key_path == object_store.key_path))
+ keys.push_back(primary_key);
+
+ scoped_ptr<IndexWriter> index_writer(new IndexWriter(index, keys));
+ bool can_add_keys = false;
+ bool backing_store_success =
+ index_writer->VerifyIndexKeys(backing_store,
+ transaction->BackingStoreTransaction(),
+ database_id,
+ object_store.id,
+ index.id,
+ &can_add_keys,
+ primary_key,
+ error_message);
+ if (!backing_store_success)
+ return false;
+ if (!can_add_keys)
+ return true;
+
+ index_writers->push_back(index_writer.release());
+ }
+
+ *completed = true;
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_index_writer.h b/chromium/content/browser/indexed_db/indexed_db_index_writer.h
new file mode 100644
index 00000000000..2ea1c98c8e5
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_index_writer.h
@@ -0,0 +1,76 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_metadata.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+
+namespace content {
+
+class IndexedDBTransaction;
+struct IndexedDBObjectStoreMetadata;
+
+class IndexWriter {
+ public:
+ explicit IndexWriter(const IndexedDBIndexMetadata& index_metadata);
+
+ IndexWriter(const IndexedDBIndexMetadata& index_metadata,
+ const IndexedDBDatabase::IndexKeys& index_keys);
+
+ bool VerifyIndexKeys(IndexedDBBackingStore* store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ bool* can_add_keys,
+ const IndexedDBKey& primary_key,
+ string16* error_message) const WARN_UNUSED_RESULT;
+
+ void WriteIndexKeys(const IndexedDBBackingStore::RecordIdentifier& record,
+ IndexedDBBackingStore* store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id) const;
+
+ ~IndexWriter();
+
+ private:
+ bool AddingKeyAllowed(IndexedDBBackingStore* store,
+ IndexedDBBackingStore::Transaction* transaction,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& index_key,
+ const IndexedDBKey& primary_key,
+ bool* allowed) const WARN_UNUSED_RESULT;
+
+ const IndexedDBIndexMetadata index_metadata_;
+ IndexedDBDatabase::IndexKeys index_keys_;
+};
+
+bool MakeIndexWriters(
+ scoped_refptr<IndexedDBTransaction> transaction,
+ IndexedDBBackingStore* store,
+ int64 database_id,
+ const IndexedDBObjectStoreMetadata& metadata,
+ const IndexedDBKey& primary_key,
+ bool key_was_generated,
+ const std::vector<int64>& index_ids,
+ const std::vector<IndexedDBDatabase::IndexKeys>& index_keys,
+ ScopedVector<IndexWriter>* index_writers,
+ string16* error_message,
+ bool* completed) WARN_UNUSED_RESULT;
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INDEX_WRITER_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_internals_ui.cc b/chromium/content/browser/indexed_db/indexed_db_internals_ui.cc
new file mode 100644
index 00000000000..230b7887552
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_internals_ui.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_internals_ui.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/threading/platform_thread.h"
+#include "base/values.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+#include "third_party/zlib/google/zip.h"
+#include "ui/base/text/bytes_formatting.h"
+#include "webkit/common/database/database_identifier.h"
+
+namespace content {
+
+IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->RegisterMessageCallback(
+ "getAllOrigins",
+ base::Bind(&IndexedDBInternalsUI::GetAllOrigins, base::Unretained(this)));
+
+ web_ui->RegisterMessageCallback(
+ "downloadOriginData",
+ base::Bind(&IndexedDBInternalsUI::DownloadOriginData,
+ base::Unretained(this)));
+ web_ui->RegisterMessageCallback(
+ "forceClose",
+ base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin,
+ base::Unretained(this)));
+
+ WebUIDataSource* source =
+ WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
+ source->SetUseJsonJSFormatV2();
+ source->SetJsonPath("strings.js");
+ source->AddResourcePath("indexeddb_internals.js",
+ IDR_INDEXED_DB_INTERNALS_JS);
+ source->AddResourcePath("indexeddb_internals.css",
+ IDR_INDEXED_DB_INTERNALS_CSS);
+ source->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML);
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, source);
+}
+
+IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
+
+void IndexedDBInternalsUI::AddContextFromStoragePartition(
+ StoragePartition* partition) {
+ scoped_refptr<IndexedDBContext> context = partition->GetIndexedDBContext();
+ context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread,
+ base::Unretained(this),
+ context,
+ partition->GetPath()));
+}
+
+void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BrowserContext* browser_context =
+ web_ui()->GetWebContents()->GetBrowserContext();
+
+ BrowserContext::StoragePartitionCallback cb =
+ base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition,
+ base::Unretained(this));
+ BrowserContext::ForEachStoragePartition(browser_context, cb);
+}
+
+void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread(
+ scoped_refptr<IndexedDBContext> context,
+ const base::FilePath& context_path) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+
+ scoped_ptr<ListValue> info_list(static_cast<IndexedDBContextImpl*>(
+ context.get())->GetAllOriginsDetails());
+
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::OnOriginsReady,
+ base::Unretained(this),
+ base::Passed(&info_list),
+ context_path));
+}
+
+void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr<ListValue> origins,
+ const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ web_ui()->CallJavascriptFunction(
+ "indexeddb.onOriginsReady", *origins, base::StringValue(path.value()));
+}
+
+static void FindContext(const base::FilePath& partition_path,
+ StoragePartition** result_partition,
+ scoped_refptr<IndexedDBContextImpl>* result_context,
+ StoragePartition* storage_partition) {
+ if (storage_partition->GetPath() == partition_path) {
+ *result_partition = storage_partition;
+ *result_context = static_cast<IndexedDBContextImpl*>(
+ storage_partition->GetIndexedDBContext());
+ }
+}
+
+bool IndexedDBInternalsUI::GetOriginData(
+ const base::ListValue* args,
+ base::FilePath* partition_path,
+ GURL* origin_url,
+ scoped_refptr<IndexedDBContextImpl>* context) {
+ base::FilePath::StringType path_string;
+ if (!args->GetString(0, &path_string))
+ return false;
+ *partition_path = base::FilePath(path_string);
+
+ std::string url_string;
+ if (!args->GetString(1, &url_string))
+ return false;
+
+ *origin_url = GURL(url_string);
+
+ return GetOriginContext(*partition_path, *origin_url, context);
+}
+
+bool IndexedDBInternalsUI::GetOriginContext(
+ const base::FilePath& path,
+ const GURL& origin_url,
+ scoped_refptr<IndexedDBContextImpl>* context) {
+ // search the origins to find the right context
+ BrowserContext* browser_context =
+ web_ui()->GetWebContents()->GetBrowserContext();
+
+ StoragePartition* result_partition;
+ BrowserContext::StoragePartitionCallback cb =
+ base::Bind(&FindContext, path, &result_partition, context);
+ BrowserContext::ForEachStoragePartition(browser_context, cb);
+
+ if (!result_partition || !(*context))
+ return false;
+
+ return true;
+}
+
+void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::FilePath partition_path;
+ GURL origin_url;
+ scoped_refptr<IndexedDBContextImpl> context;
+ if (!GetOriginData(args, &partition_path, &origin_url, &context))
+ return;
+
+ DCHECK(context);
+ context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread,
+ base::Unretained(this),
+ partition_path,
+ context,
+ origin_url));
+}
+
+void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::FilePath partition_path;
+ GURL origin_url;
+ scoped_refptr<IndexedDBContextImpl> context;
+ if (!GetOriginData(args, &partition_path, &origin_url, &context))
+ return;
+
+ context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread,
+ base::Unretained(this),
+ partition_path,
+ context,
+ origin_url));
+}
+
+void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
+ const base::FilePath& partition_path,
+ const scoped_refptr<IndexedDBContextImpl> context,
+ const GURL& origin_url) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+
+ // Make sure the database hasn't been deleted since the page was loaded.
+ if (!context->IsInOriginSet(origin_url))
+ return;
+
+ context->ForceClose(origin_url);
+ size_t connection_count = context->GetConnectionCount(origin_url);
+
+ base::ScopedTempDir temp_dir;
+ if (!temp_dir.CreateUniqueTempDir())
+ return;
+
+ // This will get cleaned up on the File thread after the download
+ // has completed.
+ base::FilePath temp_path = temp_dir.Take();
+
+ std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
+ base::FilePath zip_path =
+ temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip"));
+
+ // This happens on the "webkit" thread (which is really just the IndexedDB
+ // thread) as a simple way to avoid another script reopening the origin
+ // while we are zipping.
+ zip::Zip(context->GetFilePath(origin_url), zip_path, true);
+
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady,
+ base::Unretained(this),
+ partition_path,
+ origin_url,
+ temp_path,
+ zip_path,
+ connection_count));
+}
+
+void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
+ const base::FilePath& partition_path,
+ const scoped_refptr<IndexedDBContextImpl> context,
+ const GURL& origin_url) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+
+ // Make sure the database hasn't been deleted since the page was loaded.
+ if (!context->IsInOriginSet(origin_url))
+ return;
+
+ context->ForceClose(origin_url);
+ size_t connection_count = context->GetConnectionCount(origin_url);
+
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&IndexedDBInternalsUI::OnForcedClose,
+ base::Unretained(this),
+ partition_path,
+ origin_url,
+ connection_count));
+}
+
+void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
+ const GURL& origin_url,
+ size_t connection_count) {
+ web_ui()->CallJavascriptFunction(
+ "indexeddb.onForcedClose",
+ base::StringValue(partition_path.value()),
+ base::StringValue(origin_url.spec()),
+ base::FundamentalValue(double(connection_count)));
+}
+
+void IndexedDBInternalsUI::OnDownloadDataReady(
+ const base::FilePath& partition_path,
+ const GURL& origin_url,
+ const base::FilePath temp_path,
+ const base::FilePath zip_path,
+ size_t connection_count) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
+ BrowserContext* browser_context =
+ web_ui()->GetWebContents()->GetBrowserContext();
+ scoped_ptr<DownloadUrlParameters> dl_params(
+ DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url));
+ DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context);
+
+ const GURL referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
+ dl_params->set_referrer(
+ content::Referrer(referrer, WebKit::WebReferrerPolicyDefault));
+
+ // This is how to watch for the download to finish: first wait for it
+ // to start, then attach a DownloadItem::Observer to observe the
+ // state change to the finished state.
+ dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted,
+ base::Unretained(this),
+ partition_path,
+ origin_url,
+ temp_path,
+ connection_count));
+ dlm->DownloadUrl(dl_params.Pass());
+}
+
+// The entire purpose of this class is to delete the temp file after
+// the download is complete.
+class FileDeleter : public DownloadItem::Observer {
+ public:
+ explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
+ virtual ~FileDeleter();
+
+ virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
+ virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {}
+ virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {}
+ virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {}
+
+ private:
+ const base::FilePath temp_dir_;
+};
+
+void FileDeleter::OnDownloadUpdated(DownloadItem* item) {
+ switch (item->GetState()) {
+ case DownloadItem::IN_PROGRESS:
+ break;
+ case DownloadItem::COMPLETE:
+ case DownloadItem::CANCELLED:
+ case DownloadItem::INTERRUPTED: {
+ item->RemoveObserver(this);
+ BrowserThread::DeleteOnFileThread::Destruct(this);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+FileDeleter::~FileDeleter() {
+ base::ScopedTempDir path;
+ bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
+ DCHECK(will_delete);
+}
+
+void IndexedDBInternalsUI::OnDownloadStarted(
+ const base::FilePath& partition_path,
+ const GURL& origin_url,
+ const base::FilePath& temp_path,
+ size_t connection_count,
+ DownloadItem* item,
+ net::Error error) {
+
+ if (error != net::OK) {
+ LOG(ERROR)
+ << "Error downloading database dump: " << net::ErrorToString(error);
+ return;
+ }
+
+ item->AddObserver(new FileDeleter(temp_path));
+ web_ui()->CallJavascriptFunction(
+ "indexeddb.onOriginDownloadReady",
+ base::StringValue(partition_path.value()),
+ base::StringValue(origin_url.spec()),
+ base::FundamentalValue(double(connection_count)));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_internals_ui.h b/chromium/content/browser/indexed_db/indexed_db_internals_ui.h
new file mode 100644
index 00000000000..a196e036de1
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_internals_ui.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INTERNALS_UI_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INTERNALS_UI_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/indexed_db_context.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "net/base/net_errors.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+
+class DownloadItem;
+class IndexedDBContextImpl;
+class StoragePartition;
+
+// The implementation for the chrome://indexeddb-internals page.
+class IndexedDBInternalsUI : public WebUIController {
+ public:
+ explicit IndexedDBInternalsUI(WebUI* web_ui);
+ virtual ~IndexedDBInternalsUI();
+
+ private:
+ void GetAllOrigins(const base::ListValue* args);
+ void GetAllOriginsOnIndexedDBThread(scoped_refptr<IndexedDBContext> context,
+ const base::FilePath& context_path);
+ void OnOriginsReady(scoped_ptr<base::ListValue> origins,
+ const base::FilePath& path);
+
+ void AddContextFromStoragePartition(StoragePartition* partition);
+
+ void DownloadOriginData(const base::ListValue* args);
+ void DownloadOriginDataOnIndexedDBThread(
+ const base::FilePath& partition_path,
+ const scoped_refptr<IndexedDBContextImpl> context,
+ const GURL& origin_url);
+ void OnDownloadDataReady(const base::FilePath& partition_path,
+ const GURL& origin_url,
+ const base::FilePath temp_path,
+ const base::FilePath zip_path,
+ size_t connection_count);
+ void OnDownloadStarted(const base::FilePath& partition_path,
+ const GURL& origin_url,
+ const base::FilePath& temp_path,
+ size_t connection_count,
+ content::DownloadItem* item,
+ net::Error error);
+
+ void ForceCloseOrigin(const base::ListValue* args);
+ void ForceCloseOriginOnIndexedDBThread(
+ const base::FilePath& partition_path,
+ const scoped_refptr<IndexedDBContextImpl> context,
+ const GURL& origin_url);
+ void OnForcedClose(const base::FilePath& partition_path,
+ const GURL& origin_url,
+ size_t connection_count);
+ bool GetOriginContext(const base::FilePath& path,
+ const GURL& origin_url,
+ scoped_refptr<IndexedDBContextImpl>* context);
+ bool GetOriginData(const base::ListValue* args,
+ base::FilePath* path,
+ GURL* origin_url,
+ scoped_refptr<IndexedDBContextImpl>* context);
+
+ DISALLOW_COPY_AND_ASSIGN(IndexedDBInternalsUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_INTERNALS_UI_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.cc b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.cc
new file mode 100644
index 00000000000..61d32db9047
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.cc
@@ -0,0 +1,1846 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+
+#include <iterator>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+
+// LevelDB stores key/value pairs. Keys and values are strings of bytes,
+// normally of type std::string.
+//
+// The keys in the backing store are variable-length tuples with different types
+// of fields. Each key in the backing store starts with a ternary prefix:
+// (database id, object store id, index id). For each, 0 is reserved for
+// meta-data.
+// The prefix makes sure that data for a specific database, object store, and
+// index are grouped together. The locality is important for performance: common
+// operations should only need a minimal number of seek operations. For example,
+// all the meta-data for a database is grouped together so that reading that
+// meta-data only requires one seek.
+//
+// Each key type has a class (in square brackets below) which knows how to
+// encode, decode, and compare that key type.
+//
+// Global meta-data have keys with prefix (0,0,0), followed by a type byte:
+//
+// <0, 0, 0, 0> =>
+// IndexedDB/LevelDB schema version [SchemaVersionKey]
+// <0, 0, 0, 1> => The maximum
+// database id ever allocated [MaxDatabaseIdKey]
+// <0, 0, 0, 2> =>
+// SerializedScriptValue version [DataVersionKey]
+// <0, 0, 0, 100, database id> => Existence
+// implies the database id is in the free list [DatabaseFreeListKey]
+// <0, 0, 0, 201, utf16 origin name, utf16 database name> => Database id
+// [DatabaseNameKey]
+//
+//
+// Database meta-data:
+//
+// Again, the prefix is followed by a type byte.
+//
+// <database id, 0, 0, 0> => utf16 origin name [DatabaseMetaDataKey]
+// <database id, 0, 0, 1> => utf16 database name [DatabaseMetaDataKey]
+// <database id, 0, 0, 2> => utf16 user version data [DatabaseMetaDataKey]
+// <database id, 0, 0, 3> => maximum object store id ever allocated
+// [DatabaseMetaDataKey]
+// <database id, 0, 0, 4> => user integer version (var int)
+// [DatabaseMetaDataKey]
+//
+//
+// Object store meta-data:
+//
+// The prefix is followed by a type byte, then a variable-length integer,
+// and then another type byte.
+//
+// <database id, 0, 0, 50, object store id, 0> => utf16 object store name
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 1> => utf16 key path
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 2> => has auto increment
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 3> => is evictable
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 4> => last "version" number
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 5> => maximum index id ever
+// allocated [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 6> => has key path (vs. null)
+// [ObjectStoreMetaDataKey]
+// <database id, 0, 0, 50, object store id, 7> => key generator current
+// number [ObjectStoreMetaDataKey]
+//
+//
+// Index meta-data:
+//
+// The prefix is followed by a type byte, then two variable-length integers,
+// and then another type byte.
+//
+// <database id, 0, 0, 100, object store id, index id, 0> => utf16 index
+// name [IndexMetaDataKey]
+// <database id, 0, 0, 100, object store id, index id, 1> => are index keys
+// unique [IndexMetaDataKey]
+// <database id, 0, 0, 100, object store id, index id, 2> => utf16 key path
+// [IndexMetaDataKey]
+// <database id, 0, 0, 100, object store id, index id, 3> => is index
+// multi-entry [IndexMetaDataKey]
+//
+//
+// Other object store and index meta-data:
+//
+// The prefix is followed by a type byte. The object store and index id are
+// variable length integers, the utf16 strings are variable length strings.
+//
+// <database id, 0, 0, 150, object store id> => existence
+// implies the object store id is in the free list [ObjectStoreFreeListKey]
+// <database id, 0, 0, 151, object store id, index id> => existence
+// implies the index id is in the free list [IndexFreeListKey]
+// <database id, 0, 0, 200, utf16 object store name> => object
+// store id [ObjectStoreNamesKey]
+// <database id, 0, 0, 201, object store id, utf16 index name> => index id
+// [IndexNamesKey]
+//
+//
+// Object store data:
+//
+// The prefix is followed by a type byte. The user key is an encoded
+// IndexedDBKey.
+//
+// <database id, object store id, 1, user key> => "version", serialized
+// script value [ObjectStoreDataKey]
+//
+//
+// "Exists" entry:
+//
+// The prefix is followed by a type byte. The user key is an encoded
+// IndexedDBKey.
+//
+// <database id, object store id, 2, user key> => "version" [ExistsEntryKey]
+//
+//
+// Index data:
+//
+// The prefix is followed by a type byte. The index key is an encoded
+// IndexedDBKey. The sequence number is a variable length integer.
+// The primary key is an encoded IndexedDBKey.
+//
+// <database id, object store id, index id, index key, sequence number,
+// primary key> => "version", primary key [IndexDataKey]
+//
+// (The sequence number is obsolete; it was used to allow two entries with
+// the same user (index) key in non-unique indexes prior to the inclusion of
+// the primary key in the data. The "version" field is used to weed out
+// stale
+// index data. Whenever new object store data is inserted, it gets a new
+// "version" number, and new index data is written with this number. When
+// the index is used for look-ups, entries are validated against the
+// "exists" entries, and records with old "version" numbers are deleted
+// when they are encountered in get_primary_key_via_index,
+// IndexCursorImpl::load_current_row, and
+// IndexKeyCursorImpl::load_current_row).
+
+using base::StringPiece;
+using WebKit::WebIDBKeyType;
+using WebKit::WebIDBKeyTypeArray;
+using WebKit::WebIDBKeyTypeDate;
+using WebKit::WebIDBKeyTypeInvalid;
+using WebKit::WebIDBKeyTypeMin;
+using WebKit::WebIDBKeyTypeNull;
+using WebKit::WebIDBKeyTypeNumber;
+using WebKit::WebIDBKeyTypeString;
+using WebKit::WebIDBKeyPathType;
+using WebKit::WebIDBKeyPathTypeArray;
+using WebKit::WebIDBKeyPathTypeNull;
+using WebKit::WebIDBKeyPathTypeString;
+
+namespace content {
+
+// As most of the IndexedDBKeys and encoded values are short, we
+// initialize some Vectors with a default inline buffer size to reduce
+// the memory re-allocations when the Vectors are appended.
+static const size_t kDefaultInlineBufferSize = 32;
+
+static const unsigned char kIndexedDBKeyNullTypeByte = 0;
+static const unsigned char kIndexedDBKeyStringTypeByte = 1;
+static const unsigned char kIndexedDBKeyDateTypeByte = 2;
+static const unsigned char kIndexedDBKeyNumberTypeByte = 3;
+static const unsigned char kIndexedDBKeyArrayTypeByte = 4;
+static const unsigned char kIndexedDBKeyMinKeyTypeByte = 5;
+
+static const unsigned char kIndexedDBKeyPathTypeCodedByte1 = 0;
+static const unsigned char kIndexedDBKeyPathTypeCodedByte2 = 0;
+
+static const unsigned char kObjectStoreDataIndexId = 1;
+static const unsigned char kExistsEntryIndexId = 2;
+
+static const unsigned char kSchemaVersionTypeByte = 0;
+static const unsigned char kMaxDatabaseIdTypeByte = 1;
+static const unsigned char kDataVersionTypeByte = 2;
+static const unsigned char kMaxSimpleGlobalMetaDataTypeByte =
+ 3; // Insert before this and increment.
+static const unsigned char kDatabaseFreeListTypeByte = 100;
+static const unsigned char kDatabaseNameTypeByte = 201;
+
+static const unsigned char kObjectStoreMetaDataTypeByte = 50;
+static const unsigned char kIndexMetaDataTypeByte = 100;
+static const unsigned char kObjectStoreFreeListTypeByte = 150;
+static const unsigned char kIndexFreeListTypeByte = 151;
+static const unsigned char kObjectStoreNamesTypeByte = 200;
+static const unsigned char kIndexNamesKeyTypeByte = 201;
+
+static const unsigned char kObjectMetaDataTypeMaximum = 255;
+static const unsigned char kIndexMetaDataTypeMaximum = 255;
+
+const unsigned char kMinimumIndexId = 30;
+
+inline void EncodeIntSafely(int64 nParam, int64 max, std::string* into) {
+ DCHECK_LE(nParam, max);
+ return EncodeInt(nParam, into);
+}
+
+std::string MaxIDBKey() {
+ std::string ret;
+ EncodeByte(kIndexedDBKeyNullTypeByte, &ret);
+ return ret;
+}
+
+std::string MinIDBKey() {
+ std::string ret;
+ EncodeByte(kIndexedDBKeyMinKeyTypeByte, &ret);
+ return ret;
+}
+
+void EncodeByte(unsigned char value, std::string* into) {
+ into->push_back(value);
+}
+
+void EncodeBool(bool value, std::string* into) {
+ into->push_back(value ? 1 : 0);
+}
+
+void EncodeInt(int64 value, std::string* into) {
+#ifndef NDEBUG
+ // Exercised by unit tests in debug only.
+ DCHECK_GE(value, 0);
+#endif
+ uint64 n = static_cast<uint64>(value);
+
+ do {
+ unsigned char c = n;
+ into->push_back(c);
+ n >>= 8;
+ } while (n);
+}
+
+void EncodeVarInt(int64 value, std::string* into) {
+#ifndef NDEBUG
+ // Exercised by unit tests in debug only.
+ DCHECK_GE(value, 0);
+#endif
+ uint64 n = static_cast<uint64>(value);
+
+ do {
+ unsigned char c = n & 0x7f;
+ n >>= 7;
+ if (n)
+ c |= 0x80;
+ into->push_back(c);
+ } while (n);
+}
+
+void EncodeString(const string16& value, std::string* into) {
+ if (value.empty())
+ return;
+ // Backing store is UTF-16BE, convert from host endianness.
+ size_t length = value.length();
+ size_t current = into->size();
+ into->resize(into->size() + length * sizeof(char16));
+
+ const char16* src = value.c_str();
+ char16* dst = reinterpret_cast<char16*>(&*into->begin() + current);
+ for (unsigned i = 0; i < length; ++i)
+ *dst++ = htons(*src++);
+}
+
+void EncodeStringWithLength(const string16& value, std::string* into) {
+ EncodeVarInt(value.length(), into);
+ EncodeString(value, into);
+}
+
+void EncodeDouble(double value, std::string* into) {
+ // This always has host endianness.
+ const char* p = reinterpret_cast<char*>(&value);
+ into->insert(into->end(), p, p + sizeof(value));
+}
+
+void EncodeIDBKey(const IndexedDBKey& value, std::string* into) {
+ size_t previous_size = into->size();
+ DCHECK(value.IsValid());
+ switch (value.type()) {
+ case WebIDBKeyTypeNull:
+ case WebIDBKeyTypeInvalid:
+ case WebIDBKeyTypeMin: {
+ NOTREACHED();
+ EncodeByte(kIndexedDBKeyNullTypeByte, into);
+ return;
+ }
+ case WebIDBKeyTypeArray: {
+ EncodeByte(kIndexedDBKeyArrayTypeByte, into);
+ size_t length = value.array().size();
+ EncodeVarInt(length, into);
+ for (size_t i = 0; i < length; ++i)
+ EncodeIDBKey(value.array()[i], into);
+ DCHECK_GT(into->size(), previous_size);
+ return;
+ }
+ case WebIDBKeyTypeString: {
+ EncodeByte(kIndexedDBKeyStringTypeByte, into);
+ EncodeStringWithLength(value.string(), into);
+ DCHECK_GT(into->size(), previous_size);
+ return;
+ }
+ case WebIDBKeyTypeDate: {
+ EncodeByte(kIndexedDBKeyDateTypeByte, into);
+ EncodeDouble(value.date(), into);
+ DCHECK_EQ(static_cast<size_t>(9),
+ static_cast<size_t>(into->size() - previous_size));
+ return;
+ }
+ case WebIDBKeyTypeNumber: {
+ EncodeByte(kIndexedDBKeyNumberTypeByte, into);
+ EncodeDouble(value.number(), into);
+ DCHECK_EQ(static_cast<size_t>(9),
+ static_cast<size_t>(into->size() - previous_size));
+ return;
+ }
+ }
+
+ NOTREACHED();
+}
+
+void EncodeIDBKeyPath(const IndexedDBKeyPath& value, std::string* into) {
+ // May be typed, or may be a raw string. An invalid leading
+ // byte is used to identify typed coding. New records are
+ // always written as typed.
+ EncodeByte(kIndexedDBKeyPathTypeCodedByte1, into);
+ EncodeByte(kIndexedDBKeyPathTypeCodedByte2, into);
+ EncodeByte(static_cast<char>(value.type()), into);
+ switch (value.type()) {
+ case WebIDBKeyPathTypeNull:
+ break;
+ case WebIDBKeyPathTypeString: {
+ EncodeStringWithLength(value.string(), into);
+ break;
+ }
+ case WebIDBKeyPathTypeArray: {
+ const std::vector<string16>& array = value.array();
+ size_t count = array.size();
+ EncodeVarInt(count, into);
+ for (size_t i = 0; i < count; ++i) {
+ EncodeStringWithLength(array[i], into);
+ }
+ break;
+ }
+ }
+}
+
+bool DecodeByte(StringPiece* slice, unsigned char* value) {
+ if (slice->empty())
+ return false;
+
+ *value = (*slice)[0];
+ slice->remove_prefix(1);
+ return true;
+}
+
+bool DecodeBool(StringPiece* slice, bool* value) {
+ if (slice->empty())
+ return false;
+
+ *value = !!(*slice)[0];
+ slice->remove_prefix(1);
+ return true;
+}
+
+bool DecodeInt(StringPiece* slice, int64* value) {
+ if (slice->empty())
+ return false;
+
+ StringPiece::const_iterator it = slice->begin();
+ int shift = 0;
+ int64 ret = 0;
+ while (it != slice->end()) {
+ unsigned char c = *it++;
+ ret |= static_cast<int64>(c) << shift;
+ shift += 8;
+ }
+ *value = ret;
+ slice->remove_prefix(it - slice->begin());
+ return true;
+}
+
+bool DecodeVarInt(StringPiece* slice, int64* value) {
+ if (slice->empty())
+ return false;
+
+ StringPiece::const_iterator it = slice->begin();
+ int shift = 0;
+ int64 ret = 0;
+ do {
+ if (it == slice->end())
+ return false;
+
+ unsigned char c = *it;
+ ret |= static_cast<int64>(c & 0x7f) << shift;
+ shift += 7;
+ } while (*it++ & 0x80);
+ *value = ret;
+ slice->remove_prefix(it - slice->begin());
+ return true;
+}
+
+bool DecodeString(StringPiece* slice, string16* value) {
+ if (slice->empty()) {
+ value->clear();
+ return true;
+ }
+
+ // Backing store is UTF-16BE, convert to host endianness.
+ DCHECK(!(slice->size() % sizeof(char16)));
+ size_t length = slice->size() / sizeof(char16);
+ string16 decoded;
+ decoded.reserve(length);
+ const char16* encoded = reinterpret_cast<const char16*>(slice->begin());
+ for (unsigned i = 0; i < length; ++i)
+ decoded.push_back(ntohs(*encoded++));
+
+ *value = decoded;
+ slice->remove_prefix(length * sizeof(char16));
+ return true;
+}
+
+bool DecodeStringWithLength(StringPiece* slice, string16* value) {
+ if (slice->empty())
+ return false;
+
+ int64 length = 0;
+ if (!DecodeVarInt(slice, &length) || length < 0)
+ return false;
+ size_t bytes = length * sizeof(char16);
+ if (slice->size() < bytes)
+ return false;
+
+ StringPiece subpiece(slice->begin(), bytes);
+ slice->remove_prefix(bytes);
+ if (!DecodeString(&subpiece, value))
+ return false;
+
+ return true;
+}
+
+bool DecodeIDBKey(StringPiece* slice, scoped_ptr<IndexedDBKey>* value) {
+ if (slice->empty())
+ return false;
+
+ unsigned char type = (*slice)[0];
+ slice->remove_prefix(1);
+
+ switch (type) {
+ case kIndexedDBKeyNullTypeByte:
+ *value = make_scoped_ptr(new IndexedDBKey());
+ return true;
+
+ case kIndexedDBKeyArrayTypeByte: {
+ int64 length = 0;
+ if (!DecodeVarInt(slice, &length) || length < 0)
+ return false;
+ IndexedDBKey::KeyArray array;
+ while (length--) {
+ scoped_ptr<IndexedDBKey> key;
+ if (!DecodeIDBKey(slice, &key))
+ return false;
+ array.push_back(*key);
+ }
+ *value = make_scoped_ptr(new IndexedDBKey(array));
+ return true;
+ }
+ case kIndexedDBKeyStringTypeByte: {
+ string16 s;
+ if (!DecodeStringWithLength(slice, &s))
+ return false;
+ *value = make_scoped_ptr(new IndexedDBKey(s));
+ return true;
+ }
+ case kIndexedDBKeyDateTypeByte: {
+ double d;
+ if (!DecodeDouble(slice, &d))
+ return false;
+ *value = make_scoped_ptr(new IndexedDBKey(d, WebIDBKeyTypeDate));
+ return true;
+ }
+ case kIndexedDBKeyNumberTypeByte: {
+ double d;
+ if (!DecodeDouble(slice, &d))
+ return false;
+ *value = make_scoped_ptr(new IndexedDBKey(d, WebIDBKeyTypeNumber));
+ return true;
+ }
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+bool DecodeDouble(StringPiece* slice, double* value) {
+ if (slice->size() < sizeof(*value))
+ return false;
+
+ memcpy(value, slice->begin(), sizeof(*value));
+ slice->remove_prefix(sizeof(*value));
+ return true;
+}
+
+bool DecodeIDBKeyPath(StringPiece* slice, IndexedDBKeyPath* value) {
+ // May be typed, or may be a raw string. An invalid leading
+ // byte sequence is used to identify typed coding. New records are
+ // always written as typed.
+ if (slice->size() < 3 || (*slice)[0] != kIndexedDBKeyPathTypeCodedByte1 ||
+ (*slice)[1] != kIndexedDBKeyPathTypeCodedByte2) {
+ string16 s;
+ if (!DecodeString(slice, &s))
+ return false;
+ *value = IndexedDBKeyPath(s);
+ return true;
+ }
+
+ slice->remove_prefix(2);
+ DCHECK(!slice->empty());
+ WebIDBKeyPathType type = static_cast<WebIDBKeyPathType>((*slice)[0]);
+ slice->remove_prefix(1);
+
+ switch (type) {
+ case WebIDBKeyPathTypeNull:
+ DCHECK(slice->empty());
+ *value = IndexedDBKeyPath();
+ return true;
+ case WebIDBKeyPathTypeString: {
+ string16 string;
+ if (!DecodeStringWithLength(slice, &string))
+ return false;
+ DCHECK(slice->empty());
+ *value = IndexedDBKeyPath(string);
+ return true;
+ }
+ case WebIDBKeyPathTypeArray: {
+ std::vector<string16> array;
+ int64 count;
+ if (!DecodeVarInt(slice, &count))
+ return false;
+ DCHECK_GE(count, 0);
+ while (count--) {
+ string16 string;
+ if (!DecodeStringWithLength(slice, &string))
+ return false;
+ array.push_back(string);
+ }
+ DCHECK(slice->empty());
+ *value = IndexedDBKeyPath(array);
+ return true;
+ }
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool ConsumeEncodedIDBKey(StringPiece* slice) {
+ unsigned char type = (*slice)[0];
+ slice->remove_prefix(1);
+
+ switch (type) {
+ case kIndexedDBKeyNullTypeByte:
+ case kIndexedDBKeyMinKeyTypeByte:
+ return true;
+ case kIndexedDBKeyArrayTypeByte: {
+ int64 length;
+ if (!DecodeVarInt(slice, &length))
+ return false;
+ while (length--) {
+ if (!ConsumeEncodedIDBKey(slice))
+ return false;
+ }
+ return true;
+ }
+ case kIndexedDBKeyStringTypeByte: {
+ int64 length = 0;
+ if (!DecodeVarInt(slice, &length) || length < 0)
+ return false;
+ if (slice->size() < static_cast<size_t>(length) * sizeof(char16))
+ return false;
+ slice->remove_prefix(length * sizeof(char16));
+ return true;
+ }
+ case kIndexedDBKeyDateTypeByte:
+ case kIndexedDBKeyNumberTypeByte:
+ if (slice->size() < sizeof(double))
+ return false;
+ slice->remove_prefix(sizeof(double));
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool ExtractEncodedIDBKey(StringPiece* slice, std::string* result) {
+ const char* start = slice->begin();
+ if (!ConsumeEncodedIDBKey(slice))
+ return false;
+
+ if (result)
+ result->assign(start, slice->begin());
+ return true;
+}
+
+static WebIDBKeyType KeyTypeByteToKeyType(unsigned char type) {
+ switch (type) {
+ case kIndexedDBKeyNullTypeByte:
+ return WebIDBKeyTypeInvalid;
+ case kIndexedDBKeyArrayTypeByte:
+ return WebIDBKeyTypeArray;
+ case kIndexedDBKeyStringTypeByte:
+ return WebIDBKeyTypeString;
+ case kIndexedDBKeyDateTypeByte:
+ return WebIDBKeyTypeDate;
+ case kIndexedDBKeyNumberTypeByte:
+ return WebIDBKeyTypeNumber;
+ case kIndexedDBKeyMinKeyTypeByte:
+ return WebIDBKeyTypeMin;
+ }
+
+ NOTREACHED();
+ return WebIDBKeyTypeInvalid;
+}
+
+int CompareEncodedStringsWithLength(StringPiece* slice1,
+ StringPiece* slice2,
+ bool* ok) {
+ int64 len1, len2;
+ if (!DecodeVarInt(slice1, &len1) || !DecodeVarInt(slice2, &len2)) {
+ *ok = false;
+ return 0;
+ }
+ DCHECK_GE(len1, 0);
+ DCHECK_GE(len2, 0);
+ if (len1 < 0 || len2 < 0) {
+ *ok = false;
+ return 0;
+ }
+ DCHECK_GE(slice1->size(), len1 * sizeof(char16));
+ DCHECK_GE(slice2->size(), len2 * sizeof(char16));
+ if (slice1->size() < len1 * sizeof(char16) ||
+ slice2->size() < len2 * sizeof(char16)) {
+ *ok = false;
+ return 0;
+ }
+
+ StringPiece string1(slice1->begin(), len1 * sizeof(char16));
+ StringPiece string2(slice2->begin(), len2 * sizeof(char16));
+ slice1->remove_prefix(len1 * sizeof(char16));
+ slice2->remove_prefix(len2 * sizeof(char16));
+
+ *ok = true;
+ // Strings are UTF-16BE encoded, so a simple memcmp is sufficient.
+ return string1.compare(string2);
+}
+
+static int CompareInts(int64 a, int64 b) {
+#ifndef NDEBUG
+ // Exercised by unit tests in debug only.
+ DCHECK_GE(a, 0);
+ DCHECK_GE(b, 0);
+#endif
+ int64 diff = a - b;
+ if (diff < 0)
+ return -1;
+ if (diff > 0)
+ return 1;
+ return 0;
+}
+
+static int CompareTypes(WebIDBKeyType a, WebIDBKeyType b) { return b - a; }
+
+int CompareEncodedIDBKeys(StringPiece* slice_a,
+ StringPiece* slice_b,
+ bool* ok) {
+ *ok = true;
+ unsigned char type_a = (*slice_a)[0];
+ unsigned char type_b = (*slice_b)[0];
+ slice_a->remove_prefix(1);
+ slice_b->remove_prefix(1);
+
+ if (int x = CompareTypes(KeyTypeByteToKeyType(type_a),
+ KeyTypeByteToKeyType(type_b)))
+ return x;
+
+ switch (type_a) {
+ case kIndexedDBKeyNullTypeByte:
+ case kIndexedDBKeyMinKeyTypeByte:
+ // Null type or max type; no payload to compare.
+ return 0;
+ case kIndexedDBKeyArrayTypeByte: {
+ int64 length_a, length_b;
+ if (!DecodeVarInt(slice_a, &length_a) ||
+ !DecodeVarInt(slice_b, &length_b)) {
+ *ok = false;
+ return 0;
+ }
+ for (int64 i = 0; i < length_a && i < length_b; ++i) {
+ int result = CompareEncodedIDBKeys(slice_a, slice_b, ok);
+ if (!*ok || result)
+ return result;
+ }
+ return length_a - length_b;
+ }
+ case kIndexedDBKeyStringTypeByte:
+ return CompareEncodedStringsWithLength(slice_a, slice_b, ok);
+ case kIndexedDBKeyDateTypeByte:
+ case kIndexedDBKeyNumberTypeByte: {
+ double d, e;
+ if (!DecodeDouble(slice_a, &d) || !DecodeDouble(slice_b, &e)) {
+ *ok = false;
+ return 0;
+ }
+ if (d < e)
+ return -1;
+ if (d > e)
+ return 1;
+ return 0;
+ }
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+int CompareEncodedIDBKeys(const std::string& key_a,
+ const std::string& key_b,
+ bool* ok) {
+ DCHECK(!key_a.empty());
+ DCHECK(!key_b.empty());
+
+ StringPiece slice_a(key_a);
+ StringPiece slice_b(key_b);
+ return CompareEncodedIDBKeys(&slice_a, &slice_b, ok);
+}
+
+namespace {
+
+template <typename KeyType>
+int Compare(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys,
+ bool* ok) {
+ KeyType key_a;
+ KeyType key_b;
+
+ StringPiece slice_a(a);
+ if (!KeyType::Decode(&slice_a, &key_a)) {
+ *ok = false;
+ return 0;
+ }
+ StringPiece slice_b(b);
+ if (!KeyType::Decode(&slice_b, &key_b)) {
+ *ok = false;
+ return 0;
+ }
+
+ *ok = true;
+ return key_a.Compare(key_b);
+}
+
+template <>
+int Compare<ExistsEntryKey>(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys,
+ bool* ok) {
+ KeyPrefix prefix_a;
+ KeyPrefix prefix_b;
+ StringPiece slice_a(a);
+ StringPiece slice_b(b);
+ bool ok_a = KeyPrefix::Decode(&slice_a, &prefix_a);
+ bool ok_b = KeyPrefix::Decode(&slice_b, &prefix_b);
+ DCHECK(ok_a);
+ DCHECK(ok_b);
+ DCHECK(prefix_a.database_id_);
+ DCHECK(prefix_a.object_store_id_);
+ DCHECK_EQ(prefix_a.index_id_, ExistsEntryKey::kSpecialIndexNumber);
+ DCHECK(prefix_b.database_id_);
+ DCHECK(prefix_b.object_store_id_);
+ DCHECK_EQ(prefix_b.index_id_, ExistsEntryKey::kSpecialIndexNumber);
+ DCHECK(!slice_a.empty());
+ DCHECK(!slice_b.empty());
+ // Prefixes are not compared - it is assumed this was already done.
+ DCHECK(!prefix_a.Compare(prefix_b));
+
+ return CompareEncodedIDBKeys(&slice_a, &slice_b, ok);
+}
+
+template <>
+int Compare<ObjectStoreDataKey>(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys,
+ bool* ok) {
+ KeyPrefix prefix_a;
+ KeyPrefix prefix_b;
+ StringPiece slice_a(a);
+ StringPiece slice_b(b);
+ bool ok_a = KeyPrefix::Decode(&slice_a, &prefix_a);
+ bool ok_b = KeyPrefix::Decode(&slice_b, &prefix_b);
+ DCHECK(ok_a);
+ DCHECK(ok_b);
+ DCHECK(prefix_a.database_id_);
+ DCHECK(prefix_a.object_store_id_);
+ DCHECK_EQ(prefix_a.index_id_, ObjectStoreDataKey::kSpecialIndexNumber);
+ DCHECK(prefix_b.database_id_);
+ DCHECK(prefix_b.object_store_id_);
+ DCHECK_EQ(prefix_b.index_id_, ObjectStoreDataKey::kSpecialIndexNumber);
+ DCHECK(!slice_a.empty());
+ DCHECK(!slice_b.empty());
+ // Prefixes are not compared - it is assumed this was already done.
+ DCHECK(!prefix_a.Compare(prefix_b));
+
+ return CompareEncodedIDBKeys(&slice_a, &slice_b, ok);
+}
+
+template <>
+int Compare<IndexDataKey>(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys,
+ bool* ok) {
+ KeyPrefix prefix_a;
+ KeyPrefix prefix_b;
+ StringPiece slice_a(a);
+ StringPiece slice_b(b);
+ bool ok_a = KeyPrefix::Decode(&slice_a, &prefix_a);
+ bool ok_b = KeyPrefix::Decode(&slice_b, &prefix_b);
+ DCHECK(ok_a);
+ DCHECK(ok_b);
+ DCHECK(prefix_a.database_id_);
+ DCHECK(prefix_a.object_store_id_);
+ DCHECK_GE(prefix_a.index_id_, kMinimumIndexId);
+ DCHECK(prefix_b.database_id_);
+ DCHECK(prefix_b.object_store_id_);
+ DCHECK_GE(prefix_b.index_id_, kMinimumIndexId);
+ DCHECK(!slice_a.empty());
+ DCHECK(!slice_b.empty());
+ // Prefixes are not compared - it is assumed this was already done.
+ DCHECK(!prefix_a.Compare(prefix_b));
+
+ // index key
+ int result = CompareEncodedIDBKeys(&slice_a, &slice_b, ok);
+ if (!*ok || result)
+ return result;
+ if (only_compare_index_keys)
+ return 0;
+
+ // sequence number [optional]
+ int64 sequence_number_a = -1;
+ int64 sequence_number_b = -1;
+ if (!slice_a.empty()) {
+ if (!DecodeVarInt(&slice_a, &sequence_number_a))
+ return 0;
+ }
+ if (!slice_b.empty()) {
+ if (!DecodeVarInt(&slice_b, &sequence_number_b))
+ return 0;
+ }
+
+ // primary key [optional]
+ if (slice_a.empty() && slice_b.empty())
+ return 0;
+ if (slice_a.empty())
+ return -1;
+ if (slice_b.empty())
+ return 1;
+
+ result = CompareEncodedIDBKeys(&slice_a, &slice_b, ok);
+ if (!*ok || result)
+ return result;
+
+ return CompareInts(sequence_number_a, sequence_number_b);
+}
+
+int Compare(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys,
+ bool* ok) {
+ StringPiece slice_a(a);
+ StringPiece slice_b(b);
+ KeyPrefix prefix_a;
+ KeyPrefix prefix_b;
+ bool ok_a = KeyPrefix::Decode(&slice_a, &prefix_a);
+ bool ok_b = KeyPrefix::Decode(&slice_b, &prefix_b);
+ DCHECK(ok_a);
+ DCHECK(ok_b);
+ if (!ok_a || !ok_b) {
+ *ok = false;
+ return 0;
+ }
+
+ *ok = true;
+ if (int x = prefix_a.Compare(prefix_b))
+ return x;
+
+ switch (prefix_a.type()) {
+ case KeyPrefix::GLOBAL_METADATA: {
+ DCHECK(!slice_a.empty());
+ DCHECK(!slice_b.empty());
+
+ unsigned char type_byte_a;
+ if (!DecodeByte(&slice_a, &type_byte_a)) {
+ *ok = false;
+ return 0;
+ }
+
+ unsigned char type_byte_b;
+ if (!DecodeByte(&slice_b, &type_byte_b)) {
+ *ok = false;
+ return 0;
+ }
+
+ if (int x = type_byte_a - type_byte_b)
+ return x;
+ if (type_byte_a < kMaxSimpleGlobalMetaDataTypeByte)
+ return 0;
+
+ if (type_byte_a == kDatabaseFreeListTypeByte)
+ return Compare<DatabaseFreeListKey>(
+ a, b, only_compare_index_keys, ok);
+ if (type_byte_a == kDatabaseNameTypeByte)
+ return Compare<DatabaseNameKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ break;
+ }
+
+ case KeyPrefix::DATABASE_METADATA: {
+ DCHECK(!slice_a.empty());
+ DCHECK(!slice_b.empty());
+
+ unsigned char type_byte_a;
+ if (!DecodeByte(&slice_a, &type_byte_a)) {
+ *ok = false;
+ return 0;
+ }
+
+ unsigned char type_byte_b;
+ if (!DecodeByte(&slice_b, &type_byte_b)) {
+ *ok = false;
+ return 0;
+ }
+
+ if (int x = type_byte_a - type_byte_b)
+ return x;
+ if (type_byte_a < DatabaseMetaDataKey::MAX_SIMPLE_METADATA_TYPE)
+ return 0;
+
+ if (type_byte_a == kObjectStoreMetaDataTypeByte)
+ return Compare<ObjectStoreMetaDataKey>(
+ a, b, only_compare_index_keys, ok);
+ if (type_byte_a == kIndexMetaDataTypeByte)
+ return Compare<IndexMetaDataKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ if (type_byte_a == kObjectStoreFreeListTypeByte)
+ return Compare<ObjectStoreFreeListKey>(
+ a, b, only_compare_index_keys, ok);
+ if (type_byte_a == kIndexFreeListTypeByte)
+ return Compare<IndexFreeListKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ if (type_byte_a == kObjectStoreNamesTypeByte)
+ return Compare<ObjectStoreNamesKey>(
+ a, b, only_compare_index_keys, ok);
+ if (type_byte_a == kIndexNamesKeyTypeByte)
+ return Compare<IndexNamesKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ break;
+ }
+
+ case KeyPrefix::OBJECT_STORE_DATA: {
+ if (slice_a.empty() || slice_b.empty())
+ return slice_a.size() - slice_b.size();
+ // TODO(jsbell): This case of non-existing user keys should not have to be
+ // handled this way.
+
+ return Compare<ObjectStoreDataKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ }
+
+ case KeyPrefix::EXISTS_ENTRY: {
+ if (slice_a.empty() || slice_b.empty())
+ return slice_a.size() - slice_b.size();
+ // TODO(jsbell): This case of non-existing user keys should not have to be
+ // handled this way.
+
+ return Compare<ExistsEntryKey>(
+ a, b, /*only_compare_index_keys*/ false, ok);
+ }
+
+ case KeyPrefix::INDEX_DATA: {
+ if (slice_a.empty() || slice_b.empty())
+ return slice_a.size() - slice_b.size();
+ // TODO(jsbell): This case of non-existing user keys should not have to be
+ // handled this way.
+
+ return Compare<IndexDataKey>(a, b, only_compare_index_keys, ok);
+ }
+
+ case KeyPrefix::INVALID_TYPE:
+ break;
+ }
+
+ NOTREACHED();
+ *ok = false;
+ return 0;
+}
+
+} // namespace
+
+int Compare(const StringPiece& a,
+ const StringPiece& b,
+ bool only_compare_index_keys) {
+ bool ok;
+ int result = Compare(a, b, only_compare_index_keys, &ok);
+ DCHECK(ok);
+ if (!ok)
+ return 0;
+ return result;
+}
+
+KeyPrefix::KeyPrefix()
+ : database_id_(INVALID_TYPE),
+ object_store_id_(INVALID_TYPE),
+ index_id_(INVALID_TYPE) {}
+
+KeyPrefix::KeyPrefix(int64 database_id)
+ : database_id_(database_id), object_store_id_(0), index_id_(0) {
+ DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+}
+
+KeyPrefix::KeyPrefix(int64 database_id, int64 object_store_id)
+ : database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(0) {
+ DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+ DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id));
+}
+
+KeyPrefix::KeyPrefix(int64 database_id, int64 object_store_id, int64 index_id)
+ : database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(index_id) {
+ DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+ DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id));
+ DCHECK(KeyPrefix::IsValidIndexId(index_id));
+}
+
+KeyPrefix::KeyPrefix(enum Type type,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id)
+ : database_id_(database_id),
+ object_store_id_(object_store_id),
+ index_id_(index_id) {
+ DCHECK_EQ(type, INVALID_TYPE);
+ DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+ DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id));
+}
+
+KeyPrefix KeyPrefix::CreateWithSpecialIndex(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+ DCHECK(KeyPrefix::IsValidObjectStoreId(object_store_id));
+ DCHECK(index_id);
+ return KeyPrefix(INVALID_TYPE, database_id, object_store_id, index_id);
+}
+
+bool KeyPrefix::IsValidDatabaseId(int64 database_id) {
+ return (database_id > 0) && (database_id < KeyPrefix::kMaxDatabaseId);
+}
+
+bool KeyPrefix::IsValidObjectStoreId(int64 object_store_id) {
+ return (object_store_id > 0) &&
+ (object_store_id < KeyPrefix::kMaxObjectStoreId);
+}
+
+bool KeyPrefix::IsValidIndexId(int64 index_id) {
+ return (index_id >= kMinimumIndexId) && (index_id < KeyPrefix::kMaxIndexId);
+}
+
+bool KeyPrefix::Decode(StringPiece* slice, KeyPrefix* result) {
+ unsigned char first_byte;
+ if (!DecodeByte(slice, &first_byte))
+ return false;
+
+ size_t database_id_bytes = ((first_byte >> 5) & 0x7) + 1;
+ size_t object_store_id_bytes = ((first_byte >> 2) & 0x7) + 1;
+ size_t index_id_bytes = (first_byte & 0x3) + 1;
+
+ if (database_id_bytes + object_store_id_bytes + index_id_bytes >
+ slice->size())
+ return false;
+
+ {
+ StringPiece tmp(slice->begin(), database_id_bytes);
+ if (!DecodeInt(&tmp, &result->database_id_))
+ return false;
+ }
+ slice->remove_prefix(database_id_bytes);
+ {
+ StringPiece tmp(slice->begin(), object_store_id_bytes);
+ if (!DecodeInt(&tmp, &result->object_store_id_))
+ return false;
+ }
+ slice->remove_prefix(object_store_id_bytes);
+ {
+ StringPiece tmp(slice->begin(), index_id_bytes);
+ if (!DecodeInt(&tmp, &result->index_id_))
+ return false;
+ }
+ slice->remove_prefix(index_id_bytes);
+ return true;
+}
+
+std::string KeyPrefix::EncodeEmpty() {
+ const std::string result(4, 0);
+ DCHECK(EncodeInternal(0, 0, 0) == std::string(4, 0));
+ return result;
+}
+
+std::string KeyPrefix::Encode() const {
+ DCHECK(database_id_ != kInvalidId);
+ DCHECK(object_store_id_ != kInvalidId);
+ DCHECK(index_id_ != kInvalidId);
+ return EncodeInternal(database_id_, object_store_id_, index_id_);
+}
+
+std::string KeyPrefix::EncodeInternal(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ std::string database_id_string;
+ std::string object_store_id_string;
+ std::string index_id_string;
+
+ EncodeIntSafely(database_id, kMaxDatabaseId, &database_id_string);
+ EncodeIntSafely(object_store_id, kMaxObjectStoreId, &object_store_id_string);
+ EncodeIntSafely(index_id, kMaxIndexId, &index_id_string);
+
+ DCHECK(database_id_string.size() <= kMaxDatabaseIdSizeBytes);
+ DCHECK(object_store_id_string.size() <= kMaxObjectStoreIdSizeBytes);
+ DCHECK(index_id_string.size() <= kMaxIndexIdSizeBytes);
+
+ unsigned char first_byte =
+ (database_id_string.size() - 1) << (kMaxObjectStoreIdSizeBits +
+ kMaxIndexIdSizeBits) |
+ (object_store_id_string.size() - 1) << kMaxIndexIdSizeBits |
+ (index_id_string.size() - 1);
+ COMPILE_ASSERT(kMaxDatabaseIdSizeBits + kMaxObjectStoreIdSizeBits +
+ kMaxIndexIdSizeBits ==
+ sizeof(first_byte) * 8,
+ CANT_ENCODE_IDS);
+ std::string ret;
+ ret.reserve(kDefaultInlineBufferSize);
+ ret.push_back(first_byte);
+ ret.append(database_id_string);
+ ret.append(object_store_id_string);
+ ret.append(index_id_string);
+
+ DCHECK_LE(ret.size(), kDefaultInlineBufferSize);
+ return ret;
+}
+
+int KeyPrefix::Compare(const KeyPrefix& other) const {
+ DCHECK(database_id_ != kInvalidId);
+ DCHECK(object_store_id_ != kInvalidId);
+ DCHECK(index_id_ != kInvalidId);
+
+ if (database_id_ != other.database_id_)
+ return CompareInts(database_id_, other.database_id_);
+ if (object_store_id_ != other.object_store_id_)
+ return CompareInts(object_store_id_, other.object_store_id_);
+ if (index_id_ != other.index_id_)
+ return CompareInts(index_id_, other.index_id_);
+ return 0;
+}
+
+KeyPrefix::Type KeyPrefix::type() const {
+ DCHECK(database_id_ != kInvalidId);
+ DCHECK(object_store_id_ != kInvalidId);
+ DCHECK(index_id_ != kInvalidId);
+
+ if (!database_id_)
+ return GLOBAL_METADATA;
+ if (!object_store_id_)
+ return DATABASE_METADATA;
+ if (index_id_ == kObjectStoreDataIndexId)
+ return OBJECT_STORE_DATA;
+ if (index_id_ == kExistsEntryIndexId)
+ return EXISTS_ENTRY;
+ if (index_id_ >= kMinimumIndexId)
+ return INDEX_DATA;
+
+ NOTREACHED();
+ return INVALID_TYPE;
+}
+
+std::string SchemaVersionKey::Encode() {
+ std::string ret = KeyPrefix::EncodeEmpty();
+ ret.push_back(kSchemaVersionTypeByte);
+ return ret;
+}
+
+std::string MaxDatabaseIdKey::Encode() {
+ std::string ret = KeyPrefix::EncodeEmpty();
+ ret.push_back(kMaxDatabaseIdTypeByte);
+ return ret;
+}
+
+std::string DataVersionKey::Encode() {
+ std::string ret = KeyPrefix::EncodeEmpty();
+ ret.push_back(kDataVersionTypeByte);
+ return ret;
+}
+
+DatabaseFreeListKey::DatabaseFreeListKey() : database_id_(-1) {}
+
+bool DatabaseFreeListKey::Decode(StringPiece* slice,
+ DatabaseFreeListKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(!prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kDatabaseFreeListTypeByte);
+ if (!DecodeVarInt(slice, &result->database_id_))
+ return false;
+ return true;
+}
+
+std::string DatabaseFreeListKey::Encode(int64 database_id) {
+ std::string ret = KeyPrefix::EncodeEmpty();
+ ret.push_back(kDatabaseFreeListTypeByte);
+ EncodeVarInt(database_id, &ret);
+ return ret;
+}
+
+std::string DatabaseFreeListKey::EncodeMaxKey() {
+ return Encode(std::numeric_limits<int64>::max());
+}
+
+int64 DatabaseFreeListKey::DatabaseId() const {
+ DCHECK_GE(database_id_, 0);
+ return database_id_;
+}
+
+int DatabaseFreeListKey::Compare(const DatabaseFreeListKey& other) const {
+ DCHECK_GE(database_id_, 0);
+ return CompareInts(database_id_, other.database_id_);
+}
+
+bool DatabaseNameKey::Decode(StringPiece* slice, DatabaseNameKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(!prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kDatabaseNameTypeByte);
+ if (!DecodeStringWithLength(slice, &result->origin_))
+ return false;
+ if (!DecodeStringWithLength(slice, &result->database_name_))
+ return false;
+ return true;
+}
+
+std::string DatabaseNameKey::Encode(const std::string& origin_identifier,
+ const string16& database_name) {
+ std::string ret = KeyPrefix::EncodeEmpty();
+ ret.push_back(kDatabaseNameTypeByte);
+ EncodeStringWithLength(base::ASCIIToUTF16(origin_identifier), &ret);
+ EncodeStringWithLength(database_name, &ret);
+ return ret;
+}
+
+std::string DatabaseNameKey::EncodeMinKeyForOrigin(
+ const std::string& origin_identifier) {
+ return Encode(origin_identifier, string16());
+}
+
+std::string DatabaseNameKey::EncodeStopKeyForOrigin(
+ const std::string& origin_identifier) {
+ // just after origin in collation order
+ return EncodeMinKeyForOrigin(origin_identifier + '\x01');
+}
+
+int DatabaseNameKey::Compare(const DatabaseNameKey& other) {
+ if (int x = origin_.compare(other.origin_))
+ return x;
+ return database_name_.compare(other.database_name_);
+}
+
+std::string DatabaseMetaDataKey::Encode(int64 database_id,
+ MetaDataType meta_data_type) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(meta_data_type);
+ return ret;
+}
+
+ObjectStoreMetaDataKey::ObjectStoreMetaDataKey()
+ : object_store_id_(-1), meta_data_type_(-1) {}
+
+bool ObjectStoreMetaDataKey::Decode(StringPiece* slice,
+ ObjectStoreMetaDataKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kObjectStoreMetaDataTypeByte);
+ if (!DecodeVarInt(slice, &result->object_store_id_))
+ return false;
+ DCHECK(result->object_store_id_);
+ if (!DecodeByte(slice, &result->meta_data_type_))
+ return false;
+ return true;
+}
+
+std::string ObjectStoreMetaDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ unsigned char meta_data_type) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kObjectStoreMetaDataTypeByte);
+ EncodeVarInt(object_store_id, &ret);
+ ret.push_back(meta_data_type);
+ return ret;
+}
+
+std::string ObjectStoreMetaDataKey::EncodeMaxKey(int64 database_id) {
+ return Encode(database_id,
+ std::numeric_limits<int64>::max(),
+ kObjectMetaDataTypeMaximum);
+}
+
+std::string ObjectStoreMetaDataKey::EncodeMaxKey(int64 database_id,
+ int64 object_store_id) {
+ return Encode(database_id, object_store_id, kObjectMetaDataTypeMaximum);
+}
+
+int64 ObjectStoreMetaDataKey::ObjectStoreId() const {
+ DCHECK_GE(object_store_id_, 0);
+ return object_store_id_;
+}
+unsigned char ObjectStoreMetaDataKey::MetaDataType() const {
+ return meta_data_type_;
+}
+
+int ObjectStoreMetaDataKey::Compare(const ObjectStoreMetaDataKey& other) {
+ DCHECK_GE(object_store_id_, 0);
+ if (int x = CompareInts(object_store_id_, other.object_store_id_))
+ return x;
+ int64 result = meta_data_type_ - other.meta_data_type_;
+ if (result < 0)
+ return -1;
+ return (result > 0) ? 1 : result;
+}
+
+IndexMetaDataKey::IndexMetaDataKey()
+ : object_store_id_(-1), index_id_(-1), meta_data_type_(0) {}
+
+bool IndexMetaDataKey::Decode(StringPiece* slice, IndexMetaDataKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kIndexMetaDataTypeByte);
+ if (!DecodeVarInt(slice, &result->object_store_id_))
+ return false;
+ if (!DecodeVarInt(slice, &result->index_id_))
+ return false;
+ if (!DecodeByte(slice, &result->meta_data_type_))
+ return false;
+ return true;
+}
+
+std::string IndexMetaDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ unsigned char meta_data_type) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kIndexMetaDataTypeByte);
+ EncodeVarInt(object_store_id, &ret);
+ EncodeVarInt(index_id, &ret);
+ EncodeByte(meta_data_type, &ret);
+ return ret;
+}
+
+std::string IndexMetaDataKey::EncodeMaxKey(int64 database_id,
+ int64 object_store_id) {
+ return Encode(database_id,
+ object_store_id,
+ std::numeric_limits<int64>::max(),
+ kIndexMetaDataTypeMaximum);
+}
+
+std::string IndexMetaDataKey::EncodeMaxKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ return Encode(
+ database_id, object_store_id, index_id, kIndexMetaDataTypeMaximum);
+}
+
+int IndexMetaDataKey::Compare(const IndexMetaDataKey& other) {
+ DCHECK_GE(object_store_id_, 0);
+ DCHECK_GE(index_id_, 0);
+
+ if (int x = CompareInts(object_store_id_, other.object_store_id_))
+ return x;
+ if (int x = CompareInts(index_id_, other.index_id_))
+ return x;
+ return meta_data_type_ - other.meta_data_type_;
+}
+
+int64 IndexMetaDataKey::IndexId() const {
+ DCHECK_GE(index_id_, 0);
+ return index_id_;
+}
+
+ObjectStoreFreeListKey::ObjectStoreFreeListKey() : object_store_id_(-1) {}
+
+bool ObjectStoreFreeListKey::Decode(StringPiece* slice,
+ ObjectStoreFreeListKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kObjectStoreFreeListTypeByte);
+ if (!DecodeVarInt(slice, &result->object_store_id_))
+ return false;
+ return true;
+}
+
+std::string ObjectStoreFreeListKey::Encode(int64 database_id,
+ int64 object_store_id) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kObjectStoreFreeListTypeByte);
+ EncodeVarInt(object_store_id, &ret);
+ return ret;
+}
+
+std::string ObjectStoreFreeListKey::EncodeMaxKey(int64 database_id) {
+ return Encode(database_id, std::numeric_limits<int64>::max());
+}
+
+int64 ObjectStoreFreeListKey::ObjectStoreId() const {
+ DCHECK_GE(object_store_id_, 0);
+ return object_store_id_;
+}
+
+int ObjectStoreFreeListKey::Compare(const ObjectStoreFreeListKey& other) {
+ // TODO(jsbell): It may seem strange that we're not comparing database id's,
+ // but that comparison will have been made earlier.
+ // We should probably make this more clear, though...
+ DCHECK_GE(object_store_id_, 0);
+ return CompareInts(object_store_id_, other.object_store_id_);
+}
+
+IndexFreeListKey::IndexFreeListKey() : object_store_id_(-1), index_id_(-1) {}
+
+bool IndexFreeListKey::Decode(StringPiece* slice, IndexFreeListKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kIndexFreeListTypeByte);
+ if (!DecodeVarInt(slice, &result->object_store_id_))
+ return false;
+ if (!DecodeVarInt(slice, &result->index_id_))
+ return false;
+ return true;
+}
+
+std::string IndexFreeListKey::Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kIndexFreeListTypeByte);
+ EncodeVarInt(object_store_id, &ret);
+ EncodeVarInt(index_id, &ret);
+ return ret;
+}
+
+std::string IndexFreeListKey::EncodeMaxKey(int64 database_id,
+ int64 object_store_id) {
+ return Encode(
+ database_id, object_store_id, std::numeric_limits<int64>::max());
+}
+
+int IndexFreeListKey::Compare(const IndexFreeListKey& other) {
+ DCHECK_GE(object_store_id_, 0);
+ DCHECK_GE(index_id_, 0);
+ if (int x = CompareInts(object_store_id_, other.object_store_id_))
+ return x;
+ return CompareInts(index_id_, other.index_id_);
+}
+
+int64 IndexFreeListKey::ObjectStoreId() const {
+ DCHECK_GE(object_store_id_, 0);
+ return object_store_id_;
+}
+
+int64 IndexFreeListKey::IndexId() const {
+ DCHECK_GE(index_id_, 0);
+ return index_id_;
+}
+
+// TODO(jsbell): We never use this to look up object store ids,
+// because a mapping is kept in the IndexedDBDatabase. Can the
+// mapping become unreliable? Can we remove this?
+bool ObjectStoreNamesKey::Decode(StringPiece* slice,
+ ObjectStoreNamesKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kObjectStoreNamesTypeByte);
+ if (!DecodeStringWithLength(slice, &result->object_store_name_))
+ return false;
+ return true;
+}
+
+std::string ObjectStoreNamesKey::Encode(int64 database_id,
+ const string16& object_store_name) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kObjectStoreNamesTypeByte);
+ EncodeStringWithLength(object_store_name, &ret);
+ return ret;
+}
+
+int ObjectStoreNamesKey::Compare(const ObjectStoreNamesKey& other) {
+ return object_store_name_.compare(other.object_store_name_);
+}
+
+IndexNamesKey::IndexNamesKey() : object_store_id_(-1) {}
+
+// TODO(jsbell): We never use this to look up index ids, because a mapping
+// is kept at a higher level.
+bool IndexNamesKey::Decode(StringPiece* slice, IndexNamesKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(!prefix.object_store_id_);
+ DCHECK(!prefix.index_id_);
+ unsigned char type_byte = 0;
+ if (!DecodeByte(slice, &type_byte))
+ return false;
+ DCHECK_EQ(type_byte, kIndexNamesKeyTypeByte);
+ if (!DecodeVarInt(slice, &result->object_store_id_))
+ return false;
+ if (!DecodeStringWithLength(slice, &result->index_name_))
+ return false;
+ return true;
+}
+
+std::string IndexNamesKey::Encode(int64 database_id,
+ int64 object_store_id,
+ const string16& index_name) {
+ KeyPrefix prefix(database_id);
+ std::string ret = prefix.Encode();
+ ret.push_back(kIndexNamesKeyTypeByte);
+ EncodeVarInt(object_store_id, &ret);
+ EncodeStringWithLength(index_name, &ret);
+ return ret;
+}
+
+int IndexNamesKey::Compare(const IndexNamesKey& other) {
+ DCHECK_GE(object_store_id_, 0);
+ if (int x = CompareInts(object_store_id_, other.object_store_id_))
+ return x;
+ return index_name_.compare(other.index_name_);
+}
+
+ObjectStoreDataKey::ObjectStoreDataKey() {}
+ObjectStoreDataKey::~ObjectStoreDataKey() {}
+
+bool ObjectStoreDataKey::Decode(StringPiece* slice,
+ ObjectStoreDataKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(prefix.object_store_id_);
+ DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber);
+ if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_))
+ return false;
+ return true;
+}
+
+std::string ObjectStoreDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ const std::string encoded_user_key) {
+ KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex(
+ database_id, object_store_id, kSpecialIndexNumber));
+ std::string ret = prefix.Encode();
+ ret.append(encoded_user_key);
+
+ return ret;
+}
+
+std::string ObjectStoreDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& user_key) {
+ std::string encoded_key;
+ EncodeIDBKey(user_key, &encoded_key);
+ return Encode(database_id, object_store_id, encoded_key);
+}
+
+int ObjectStoreDataKey::Compare(const ObjectStoreDataKey& other, bool* ok) {
+ return CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok);
+}
+
+scoped_ptr<IndexedDBKey> ObjectStoreDataKey::user_key() const {
+ scoped_ptr<IndexedDBKey> key;
+ StringPiece slice(encoded_user_key_);
+ if (!DecodeIDBKey(&slice, &key)) {
+ // TODO(jsbell): Return error.
+ }
+ return key.Pass();
+}
+
+const int64 ObjectStoreDataKey::kSpecialIndexNumber = kObjectStoreDataIndexId;
+
+ExistsEntryKey::ExistsEntryKey() {}
+ExistsEntryKey::~ExistsEntryKey() {}
+
+bool ExistsEntryKey::Decode(StringPiece* slice, ExistsEntryKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(prefix.object_store_id_);
+ DCHECK_EQ(prefix.index_id_, kSpecialIndexNumber);
+ if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_))
+ return false;
+ return true;
+}
+
+std::string ExistsEntryKey::Encode(int64 database_id,
+ int64 object_store_id,
+ const std::string& encoded_key) {
+ KeyPrefix prefix(KeyPrefix::CreateWithSpecialIndex(
+ database_id, object_store_id, kSpecialIndexNumber));
+ std::string ret = prefix.Encode();
+ ret.append(encoded_key);
+ return ret;
+}
+
+std::string ExistsEntryKey::Encode(int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& user_key) {
+ std::string encoded_key;
+ EncodeIDBKey(user_key, &encoded_key);
+ return Encode(database_id, object_store_id, encoded_key);
+}
+
+int ExistsEntryKey::Compare(const ExistsEntryKey& other, bool* ok) {
+ return CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok);
+}
+
+scoped_ptr<IndexedDBKey> ExistsEntryKey::user_key() const {
+ scoped_ptr<IndexedDBKey> key;
+ StringPiece slice(encoded_user_key_);
+ if (!DecodeIDBKey(&slice, &key)) {
+ // TODO(jsbell): Return error.
+ }
+ return key.Pass();
+}
+
+const int64 ExistsEntryKey::kSpecialIndexNumber = kExistsEntryIndexId;
+
+IndexDataKey::IndexDataKey()
+ : database_id_(-1),
+ object_store_id_(-1),
+ index_id_(-1),
+ sequence_number_(-1) {}
+
+IndexDataKey::~IndexDataKey() {}
+
+bool IndexDataKey::Decode(StringPiece* slice, IndexDataKey* result) {
+ KeyPrefix prefix;
+ if (!KeyPrefix::Decode(slice, &prefix))
+ return false;
+ DCHECK(prefix.database_id_);
+ DCHECK(prefix.object_store_id_);
+ DCHECK_GE(prefix.index_id_, kMinimumIndexId);
+ result->database_id_ = prefix.database_id_;
+ result->object_store_id_ = prefix.object_store_id_;
+ result->index_id_ = prefix.index_id_;
+ result->sequence_number_ = -1;
+ result->encoded_primary_key_ = MinIDBKey();
+
+ if (!ExtractEncodedIDBKey(slice, &result->encoded_user_key_))
+ return false;
+
+ // [optional] sequence number
+ if (slice->empty())
+ return true;
+ if (!DecodeVarInt(slice, &result->sequence_number_))
+ return false;
+
+ // [optional] primary key
+ if (slice->empty())
+ return true;
+ if (!ExtractEncodedIDBKey(slice, &result->encoded_primary_key_))
+ return false;
+ return true;
+}
+
+std::string IndexDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const std::string& encoded_user_key,
+ const std::string& encoded_primary_key,
+ int64 sequence_number) {
+ KeyPrefix prefix(database_id, object_store_id, index_id);
+ std::string ret = prefix.Encode();
+ ret.append(encoded_user_key);
+ EncodeVarInt(sequence_number, &ret);
+ ret.append(encoded_primary_key);
+ return ret;
+}
+
+std::string IndexDataKey::Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& user_key) {
+ std::string encoded_key;
+ EncodeIDBKey(user_key, &encoded_key);
+ return Encode(
+ database_id, object_store_id, index_id, encoded_key, MinIDBKey(), 0);
+}
+
+std::string IndexDataKey::EncodeMinKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ return Encode(
+ database_id, object_store_id, index_id, MinIDBKey(), MinIDBKey(), 0);
+}
+
+std::string IndexDataKey::EncodeMaxKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ return Encode(database_id,
+ object_store_id,
+ index_id,
+ MaxIDBKey(),
+ MaxIDBKey(),
+ std::numeric_limits<int64>::max());
+}
+
+int IndexDataKey::Compare(const IndexDataKey& other,
+ bool only_compare_index_keys,
+ bool* ok) {
+ DCHECK_GE(database_id_, 0);
+ DCHECK_GE(object_store_id_, 0);
+ DCHECK_GE(index_id_, 0);
+ int result =
+ CompareEncodedIDBKeys(encoded_user_key_, other.encoded_user_key_, ok);
+ if (!*ok || result)
+ return result;
+ if (only_compare_index_keys)
+ return 0;
+ result = CompareEncodedIDBKeys(
+ encoded_primary_key_, other.encoded_primary_key_, ok);
+ if (!*ok || result)
+ return result;
+ return CompareInts(sequence_number_, other.sequence_number_);
+}
+
+int64 IndexDataKey::DatabaseId() const {
+ DCHECK_GE(database_id_, 0);
+ return database_id_;
+}
+
+int64 IndexDataKey::ObjectStoreId() const {
+ DCHECK_GE(object_store_id_, 0);
+ return object_store_id_;
+}
+
+int64 IndexDataKey::IndexId() const {
+ DCHECK_GE(index_id_, 0);
+ return index_id_;
+}
+
+scoped_ptr<IndexedDBKey> IndexDataKey::user_key() const {
+ scoped_ptr<IndexedDBKey> key;
+ StringPiece slice(encoded_user_key_);
+ if (!DecodeIDBKey(&slice, &key)) {
+ // TODO(jsbell): Return error.
+ }
+ return key.Pass();
+}
+
+scoped_ptr<IndexedDBKey> IndexDataKey::primary_key() const {
+ scoped_ptr<IndexedDBKey> key;
+ StringPiece slice(encoded_primary_key_);
+ if (!DecodeIDBKey(&slice, &key)) {
+ // TODO(jsbell): Return error.
+ }
+ return key.Pass();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.h b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.h
new file mode 100644
index 00000000000..974c27a4fa2
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding.h
@@ -0,0 +1,427 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+
+namespace content {
+
+CONTENT_EXPORT extern const unsigned char kMinimumIndexId;
+
+CONTENT_EXPORT std::string MaxIDBKey();
+CONTENT_EXPORT std::string MinIDBKey();
+
+CONTENT_EXPORT void EncodeByte(unsigned char value, std::string* into);
+CONTENT_EXPORT void EncodeBool(bool value, std::string* into);
+CONTENT_EXPORT void EncodeInt(int64 value, std::string* into);
+CONTENT_EXPORT void EncodeVarInt(int64 value, std::string* into);
+CONTENT_EXPORT void EncodeString(const string16& value, std::string* into);
+CONTENT_EXPORT void EncodeStringWithLength(const string16& value,
+ std::string* into);
+CONTENT_EXPORT void EncodeDouble(double value, std::string* into);
+CONTENT_EXPORT void EncodeIDBKey(const IndexedDBKey& value, std::string* into);
+CONTENT_EXPORT void EncodeIDBKeyPath(const IndexedDBKeyPath& value,
+ std::string* into);
+
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeByte(base::StringPiece* slice,
+ unsigned char* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeBool(base::StringPiece* slice,
+ bool* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeInt(base::StringPiece* slice,
+ int64* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeVarInt(base::StringPiece* slice,
+ int64* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeString(base::StringPiece* slice,
+ string16* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeStringWithLength(
+ base::StringPiece* slice,
+ string16* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeDouble(base::StringPiece* slice,
+ double* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeIDBKey(
+ base::StringPiece* slice,
+ scoped_ptr<IndexedDBKey>* value);
+CONTENT_EXPORT WARN_UNUSED_RESULT bool DecodeIDBKeyPath(
+ base::StringPiece* slice,
+ IndexedDBKeyPath* value);
+
+CONTENT_EXPORT int CompareEncodedStringsWithLength(base::StringPiece* slice1,
+ base::StringPiece* slice2,
+ bool* ok);
+
+CONTENT_EXPORT WARN_UNUSED_RESULT bool ExtractEncodedIDBKey(
+ base::StringPiece* slice,
+ std::string* result);
+
+CONTENT_EXPORT int CompareEncodedIDBKeys(const std::string& a,
+ const std::string& b,
+ bool* ok);
+
+CONTENT_EXPORT int Compare(const base::StringPiece& a,
+ const base::StringPiece& b,
+ bool index_keys);
+
+class KeyPrefix {
+ public:
+ KeyPrefix();
+ explicit KeyPrefix(int64 database_id);
+ KeyPrefix(int64 database_id, int64 object_store_id);
+ KeyPrefix(int64 database_id, int64 object_store_id, int64 index_id);
+ static KeyPrefix CreateWithSpecialIndex(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+
+ static bool Decode(base::StringPiece* slice, KeyPrefix* result);
+ std::string Encode() const;
+ static std::string EncodeEmpty();
+ int Compare(const KeyPrefix& other) const;
+
+ enum Type {
+ GLOBAL_METADATA,
+ DATABASE_METADATA,
+ OBJECT_STORE_DATA,
+ EXISTS_ENTRY,
+ INDEX_DATA,
+ INVALID_TYPE
+ };
+
+ static const size_t kMaxDatabaseIdSizeBits = 3;
+ static const size_t kMaxObjectStoreIdSizeBits = 3;
+ static const size_t kMaxIndexIdSizeBits = 2;
+
+ static const size_t kMaxDatabaseIdSizeBytes =
+ 1ULL << kMaxDatabaseIdSizeBits; // 8
+ static const size_t kMaxObjectStoreIdSizeBytes =
+ 1ULL << kMaxObjectStoreIdSizeBits; // 8
+ static const size_t kMaxIndexIdSizeBytes = 1ULL << kMaxIndexIdSizeBits; // 4
+
+ static const size_t kMaxDatabaseIdBits =
+ kMaxDatabaseIdSizeBytes * 8 - 1; // 63
+ static const size_t kMaxObjectStoreIdBits =
+ kMaxObjectStoreIdSizeBytes * 8 - 1; // 63
+ static const size_t kMaxIndexIdBits = kMaxIndexIdSizeBytes * 8 - 1; // 31
+
+ static const int64 kMaxDatabaseId =
+ (1ULL << kMaxDatabaseIdBits) - 1; // max signed int64
+ static const int64 kMaxObjectStoreId =
+ (1ULL << kMaxObjectStoreIdBits) - 1; // max signed int64
+ static const int64 kMaxIndexId =
+ (1ULL << kMaxIndexIdBits) - 1; // max signed int32
+
+ static bool IsValidDatabaseId(int64 database_id);
+ static bool IsValidObjectStoreId(int64 index_id);
+ static bool IsValidIndexId(int64 index_id);
+ static bool ValidIds(int64 database_id,
+ int64 object_store_id,
+ int64 index_id) {
+ return IsValidDatabaseId(database_id) &&
+ IsValidObjectStoreId(object_store_id) && IsValidIndexId(index_id);
+ }
+ static bool ValidIds(int64 database_id, int64 object_store_id) {
+ return IsValidDatabaseId(database_id) &&
+ IsValidObjectStoreId(object_store_id);
+ }
+
+ Type type() const;
+
+ int64 database_id_;
+ int64 object_store_id_;
+ int64 index_id_;
+
+ static const int64 kInvalidId = -1;
+
+ private:
+ static std::string EncodeInternal(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+ // Special constructor for CreateWithSpecialIndex()
+ KeyPrefix(enum Type,
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+};
+
+class SchemaVersionKey {
+ public:
+ CONTENT_EXPORT static std::string Encode();
+};
+
+class MaxDatabaseIdKey {
+ public:
+ CONTENT_EXPORT static std::string Encode();
+};
+
+class DataVersionKey {
+ public:
+ static std::string Encode();
+};
+
+class DatabaseFreeListKey {
+ public:
+ DatabaseFreeListKey();
+ static bool Decode(base::StringPiece* slice, DatabaseFreeListKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id);
+ static CONTENT_EXPORT std::string EncodeMaxKey();
+ int64 DatabaseId() const;
+ int Compare(const DatabaseFreeListKey& other) const;
+
+ private:
+ int64 database_id_;
+};
+
+class DatabaseNameKey {
+ public:
+ static bool Decode(base::StringPiece* slice, DatabaseNameKey* result);
+ CONTENT_EXPORT static std::string Encode(const std::string& origin_identifier,
+ const string16& database_name);
+ static std::string EncodeMinKeyForOrigin(
+ const std::string& origin_identifier);
+ static std::string EncodeStopKeyForOrigin(
+ const std::string& origin_identifier);
+ string16 origin() const { return origin_; }
+ string16 database_name() const { return database_name_; }
+ int Compare(const DatabaseNameKey& other);
+
+ private:
+ string16 origin_; // TODO(jsbell): Store encoded strings, or just pointers.
+ string16 database_name_;
+};
+
+class DatabaseMetaDataKey {
+ public:
+ enum MetaDataType {
+ ORIGIN_NAME = 0,
+ DATABASE_NAME = 1,
+ USER_VERSION = 2,
+ MAX_OBJECT_STORE_ID = 3,
+ USER_INT_VERSION = 4,
+ MAX_SIMPLE_METADATA_TYPE = 5
+ };
+
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ MetaDataType type);
+};
+
+class ObjectStoreMetaDataKey {
+ public:
+ enum MetaDataType {
+ NAME = 0,
+ KEY_PATH = 1,
+ AUTO_INCREMENT = 2,
+ EVICTABLE = 3,
+ LAST_VERSION = 4,
+ MAX_INDEX_ID = 5,
+ HAS_KEY_PATH = 6,
+ KEY_GENERATOR_CURRENT_NUMBER = 7
+ };
+
+ ObjectStoreMetaDataKey();
+ static bool Decode(base::StringPiece* slice, ObjectStoreMetaDataKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ unsigned char meta_data_type);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id,
+ int64 object_store_id);
+ int64 ObjectStoreId() const;
+ unsigned char MetaDataType() const;
+ int Compare(const ObjectStoreMetaDataKey& other);
+
+ private:
+ int64 object_store_id_;
+ unsigned char meta_data_type_;
+};
+
+class IndexMetaDataKey {
+ public:
+ enum MetaDataType {
+ NAME = 0,
+ UNIQUE = 1,
+ KEY_PATH = 2,
+ MULTI_ENTRY = 3
+ };
+
+ IndexMetaDataKey();
+ static bool Decode(base::StringPiece* slice, IndexMetaDataKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ unsigned char meta_data_type);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id,
+ int64 object_store_id);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+ int Compare(const IndexMetaDataKey& other);
+ int64 IndexId() const;
+ unsigned char meta_data_type() const { return meta_data_type_; }
+
+ private:
+ int64 object_store_id_;
+ int64 index_id_;
+ unsigned char meta_data_type_;
+};
+
+class ObjectStoreFreeListKey {
+ public:
+ ObjectStoreFreeListKey();
+ static bool Decode(base::StringPiece* slice, ObjectStoreFreeListKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id);
+ int64 ObjectStoreId() const;
+ int Compare(const ObjectStoreFreeListKey& other);
+
+ private:
+ int64 object_store_id_;
+};
+
+class IndexFreeListKey {
+ public:
+ IndexFreeListKey();
+ static bool Decode(base::StringPiece* slice, IndexFreeListKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id,
+ int64 object_store_id);
+ int Compare(const IndexFreeListKey& other);
+ int64 ObjectStoreId() const;
+ int64 IndexId() const;
+
+ private:
+ int64 object_store_id_;
+ int64 index_id_;
+};
+
+class ObjectStoreNamesKey {
+ public:
+ // TODO(jsbell): We never use this to look up object store ids,
+ // because a mapping is kept in the IndexedDBDatabase. Can the
+ // mapping become unreliable? Can we remove this?
+ static bool Decode(base::StringPiece* slice, ObjectStoreNamesKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ const string16& object_store_name);
+ int Compare(const ObjectStoreNamesKey& other);
+ string16 object_store_name() const { return object_store_name_; }
+
+ private:
+ // TODO(jsbell): Store the encoded string, or just pointers to it.
+ string16 object_store_name_;
+};
+
+class IndexNamesKey {
+ public:
+ IndexNamesKey();
+ // TODO(jsbell): We never use this to look up index ids, because a mapping
+ // is kept at a higher level.
+ static bool Decode(base::StringPiece* slice, IndexNamesKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ const string16& index_name);
+ int Compare(const IndexNamesKey& other);
+ string16 index_name() const { return index_name_; }
+
+ private:
+ int64 object_store_id_;
+ string16 index_name_;
+};
+
+class ObjectStoreDataKey {
+ public:
+ static bool Decode(base::StringPiece* slice, ObjectStoreDataKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ const std::string encoded_user_key);
+ static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& user_key);
+ int Compare(const ObjectStoreDataKey& other, bool* ok);
+ scoped_ptr<IndexedDBKey> user_key() const;
+ static const int64 kSpecialIndexNumber;
+ ObjectStoreDataKey();
+ ~ObjectStoreDataKey();
+
+ private:
+ std::string encoded_user_key_;
+};
+
+class ExistsEntryKey {
+ public:
+ ExistsEntryKey();
+ ~ExistsEntryKey();
+
+ static bool Decode(base::StringPiece* slice, ExistsEntryKey* result);
+ CONTENT_EXPORT static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ const std::string& encoded_key);
+ static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ const IndexedDBKey& user_key);
+ int Compare(const ExistsEntryKey& other, bool* ok);
+ scoped_ptr<IndexedDBKey> user_key() const;
+
+ static const int64 kSpecialIndexNumber;
+
+ private:
+ std::string encoded_user_key_;
+ DISALLOW_COPY_AND_ASSIGN(ExistsEntryKey);
+};
+
+class IndexDataKey {
+ public:
+ IndexDataKey();
+ ~IndexDataKey();
+ static bool Decode(base::StringPiece* slice, IndexDataKey* result);
+ CONTENT_EXPORT static std::string Encode(
+ int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const std::string& encoded_user_key,
+ const std::string& encoded_primary_key,
+ int64 sequence_number);
+ static std::string Encode(int64 database_id,
+ int64 object_store_id,
+ int64 index_id,
+ const IndexedDBKey& user_key);
+ static std::string EncodeMinKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+ CONTENT_EXPORT static std::string EncodeMaxKey(int64 database_id,
+ int64 object_store_id,
+ int64 index_id);
+ int Compare(const IndexDataKey& other,
+ bool only_compare_index_keys,
+ bool* ok);
+ int64 DatabaseId() const;
+ int64 ObjectStoreId() const;
+ int64 IndexId() const;
+ scoped_ptr<IndexedDBKey> user_key() const;
+ scoped_ptr<IndexedDBKey> primary_key() const;
+
+ private:
+ int64 database_id_;
+ int64 object_store_id_;
+ int64 index_id_;
+ std::string encoded_user_key_;
+ std::string encoded_primary_key_;
+ int64 sequence_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(IndexDataKey);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_CODING_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc
new file mode 100644
index 00000000000..c9f39762438
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc
@@ -0,0 +1,848 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+
+#include <limits>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/common/indexed_db/indexed_db_key.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using WebKit::WebIDBKeyTypeDate;
+using WebKit::WebIDBKeyTypeNumber;
+
+namespace content {
+
+namespace {
+
+static IndexedDBKey CreateArrayIDBKey() {
+ return IndexedDBKey(IndexedDBKey::KeyArray());
+}
+
+static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1) {
+ IndexedDBKey::KeyArray array;
+ array.push_back(key1);
+ return IndexedDBKey(array);
+}
+
+static IndexedDBKey CreateArrayIDBKey(const IndexedDBKey& key1,
+ const IndexedDBKey& key2) {
+ IndexedDBKey::KeyArray array;
+ array.push_back(key1);
+ array.push_back(key2);
+ return IndexedDBKey(array);
+}
+
+static std::string WrappedEncodeByte(char value) {
+ std::string buffer;
+ EncodeByte(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeByte) {
+ std::string expected;
+ expected.push_back(0);
+ unsigned char c;
+
+ c = 0;
+ expected[0] = c;
+ EXPECT_EQ(expected, WrappedEncodeByte(c));
+
+ c = 1;
+ expected[0] = c;
+ EXPECT_EQ(expected, WrappedEncodeByte(c));
+
+ c = 255;
+ expected[0] = c;
+ EXPECT_EQ(expected, WrappedEncodeByte(c));
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeByte) {
+ std::vector<unsigned char> test_cases;
+ test_cases.push_back(0);
+ test_cases.push_back(1);
+ test_cases.push_back(255);
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ unsigned char n = test_cases[i];
+ std::string v;
+ EncodeByte(n, &v);
+
+ unsigned char res;
+ ASSERT_GT(v.size(), static_cast<size_t>(0));
+ StringPiece slice(v);
+ EXPECT_TRUE(DecodeByte(&slice, &res));
+ EXPECT_EQ(n, res);
+ EXPECT_TRUE(slice.empty());
+ }
+
+ {
+ StringPiece slice;
+ unsigned char value;
+ EXPECT_FALSE(DecodeByte(&slice, &value));
+ }
+}
+
+static std::string WrappedEncodeBool(bool value) {
+ std::string buffer;
+ EncodeBool(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeBool) {
+ {
+ std::string expected;
+ expected.push_back(1);
+ EXPECT_EQ(expected, WrappedEncodeBool(true));
+ }
+ {
+ std::string expected;
+ expected.push_back(0);
+ EXPECT_EQ(expected, WrappedEncodeBool(false));
+ }
+}
+
+static int CompareKeys(const std::string& a, const std::string& b) {
+ bool ok;
+ int result = CompareEncodedIDBKeys(a, b, &ok);
+ EXPECT_TRUE(ok);
+ return result;
+}
+
+TEST(IndexedDBLevelDBCodingTest, MaxIDBKey) {
+ std::string max_key = MaxIDBKey();
+
+ std::string min_key = MinIDBKey();
+ std::string array_key;
+ EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray()), &array_key);
+ std::string string_key;
+ EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world")), &string_key);
+ std::string number_key;
+ EncodeIDBKey(IndexedDBKey(3.14, WebIDBKeyTypeNumber), &number_key);
+ std::string date_key;
+ EncodeIDBKey(IndexedDBKey(1000000, WebIDBKeyTypeDate), &date_key);
+
+ EXPECT_GT(CompareKeys(max_key, min_key), 0);
+ EXPECT_GT(CompareKeys(max_key, array_key), 0);
+ EXPECT_GT(CompareKeys(max_key, string_key), 0);
+ EXPECT_GT(CompareKeys(max_key, number_key), 0);
+ EXPECT_GT(CompareKeys(max_key, date_key), 0);
+}
+
+TEST(IndexedDBLevelDBCodingTest, MinIDBKey) {
+ std::string min_key = MinIDBKey();
+
+ std::string max_key = MaxIDBKey();
+ std::string array_key;
+ EncodeIDBKey(IndexedDBKey(IndexedDBKey::KeyArray()), &array_key);
+ std::string string_key;
+ EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world")), &string_key);
+ std::string number_key;
+ EncodeIDBKey(IndexedDBKey(3.14, WebIDBKeyTypeNumber), &number_key);
+ std::string date_key;
+ EncodeIDBKey(IndexedDBKey(1000000, WebIDBKeyTypeDate), &date_key);
+
+ EXPECT_LT(CompareKeys(min_key, max_key), 0);
+ EXPECT_LT(CompareKeys(min_key, array_key), 0);
+ EXPECT_LT(CompareKeys(min_key, string_key), 0);
+ EXPECT_LT(CompareKeys(min_key, number_key), 0);
+ EXPECT_LT(CompareKeys(min_key, date_key), 0);
+}
+
+static std::string WrappedEncodeInt(int64 value) {
+ std::string buffer;
+ EncodeInt(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeInt) {
+ EXPECT_EQ(static_cast<size_t>(1), WrappedEncodeInt(0).size());
+ EXPECT_EQ(static_cast<size_t>(1), WrappedEncodeInt(1).size());
+ EXPECT_EQ(static_cast<size_t>(1), WrappedEncodeInt(255).size());
+ EXPECT_EQ(static_cast<size_t>(2), WrappedEncodeInt(256).size());
+ EXPECT_EQ(static_cast<size_t>(4), WrappedEncodeInt(0xffffffff).size());
+#ifdef NDEBUG
+ EXPECT_EQ(static_cast<size_t>(8), WrappedEncodeInt(-1).size());
+#endif
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeBool) {
+ {
+ std::string encoded;
+ encoded.push_back(1);
+ StringPiece slice(encoded);
+ bool value;
+ EXPECT_TRUE(DecodeBool(&slice, &value));
+ EXPECT_TRUE(value);
+ EXPECT_TRUE(slice.empty());
+ }
+ {
+ std::string encoded;
+ encoded.push_back(0);
+ StringPiece slice(encoded);
+ bool value;
+ EXPECT_TRUE(DecodeBool(&slice, &value));
+ EXPECT_FALSE(value);
+ EXPECT_TRUE(slice.empty());
+ }
+ {
+ StringPiece slice;
+ bool value;
+ EXPECT_FALSE(DecodeBool(&slice, &value));
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeInt) {
+ std::vector<int64> test_cases;
+ test_cases.push_back(0);
+ test_cases.push_back(1);
+ test_cases.push_back(255);
+ test_cases.push_back(256);
+ test_cases.push_back(65535);
+ test_cases.push_back(655536);
+ test_cases.push_back(7711192431755665792ll);
+ test_cases.push_back(0x7fffffffffffffffll);
+#ifdef NDEBUG
+ test_cases.push_back(-3);
+#endif
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ int64 n = test_cases[i];
+ std::string v = WrappedEncodeInt(n);
+ ASSERT_GT(v.size(), static_cast<size_t>(0));
+ StringPiece slice(v);
+ int64 value;
+ EXPECT_TRUE(DecodeInt(&slice, &value));
+ EXPECT_EQ(n, value);
+ EXPECT_TRUE(slice.empty());
+
+ // Verify decoding at an offset, to detect unaligned memory access.
+ v.insert(v.begin(), static_cast<size_t>(1), static_cast<char>(0));
+ slice = StringPiece(&*v.begin() + 1, v.size() - 1);
+ EXPECT_TRUE(DecodeInt(&slice, &value));
+ EXPECT_EQ(n, value);
+ EXPECT_TRUE(slice.empty());
+ }
+ {
+ StringPiece slice;
+ int64 value;
+ EXPECT_FALSE(DecodeInt(&slice, &value));
+ }
+}
+
+static std::string WrappedEncodeVarInt(int64 value) {
+ std::string buffer;
+ EncodeVarInt(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeVarInt) {
+ EXPECT_EQ(static_cast<size_t>(1), WrappedEncodeVarInt(0).size());
+ EXPECT_EQ(static_cast<size_t>(1), WrappedEncodeVarInt(1).size());
+ EXPECT_EQ(static_cast<size_t>(2), WrappedEncodeVarInt(255).size());
+ EXPECT_EQ(static_cast<size_t>(2), WrappedEncodeVarInt(256).size());
+ EXPECT_EQ(static_cast<size_t>(5), WrappedEncodeVarInt(0xffffffff).size());
+ EXPECT_EQ(static_cast<size_t>(8),
+ WrappedEncodeVarInt(0xfffffffffffffLL).size());
+ EXPECT_EQ(static_cast<size_t>(9),
+ WrappedEncodeVarInt(0x7fffffffffffffffLL).size());
+#ifdef NDEBUG
+ EXPECT_EQ(static_cast<size_t>(10), WrappedEncodeVarInt(-100).size());
+#endif
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeVarInt) {
+ std::vector<int64> test_cases;
+ test_cases.push_back(0);
+ test_cases.push_back(1);
+ test_cases.push_back(255);
+ test_cases.push_back(256);
+ test_cases.push_back(65535);
+ test_cases.push_back(655536);
+ test_cases.push_back(7711192431755665792ll);
+ test_cases.push_back(0x7fffffffffffffffll);
+#ifdef NDEBUG
+ test_cases.push_back(-3);
+#endif
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ int64 n = test_cases[i];
+ std::string v = WrappedEncodeVarInt(n);
+ ASSERT_GT(v.size(), static_cast<size_t>(0));
+ StringPiece slice(v);
+ int64 res;
+ EXPECT_TRUE(DecodeVarInt(&slice, &res));
+ EXPECT_EQ(n, res);
+ EXPECT_TRUE(slice.empty());
+
+ slice = StringPiece(&*v.begin(), v.size() - 1);
+ EXPECT_FALSE(DecodeVarInt(&slice, &res));
+
+ slice = StringPiece(&*v.begin(), static_cast<size_t>(0));
+ EXPECT_FALSE(DecodeVarInt(&slice, &res));
+
+ // Verify decoding at an offset, to detect unaligned memory access.
+ v.insert(v.begin(), static_cast<size_t>(1), static_cast<char>(0));
+ slice = StringPiece(&*v.begin() + 1, v.size() - 1);
+ EXPECT_TRUE(DecodeVarInt(&slice, &res));
+ EXPECT_EQ(n, res);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+static std::string WrappedEncodeString(string16 value) {
+ std::string buffer;
+ EncodeString(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeString) {
+ const char16 test_string_a[] = {'f', 'o', 'o', '\0'};
+ const char16 test_string_b[] = {0xdead, 0xbeef, '\0'};
+
+ EXPECT_EQ(static_cast<size_t>(0),
+ WrappedEncodeString(ASCIIToUTF16("")).size());
+ EXPECT_EQ(static_cast<size_t>(2),
+ WrappedEncodeString(ASCIIToUTF16("a")).size());
+ EXPECT_EQ(static_cast<size_t>(6),
+ WrappedEncodeString(ASCIIToUTF16("foo")).size());
+ EXPECT_EQ(static_cast<size_t>(6),
+ WrappedEncodeString(string16(test_string_a)).size());
+ EXPECT_EQ(static_cast<size_t>(4),
+ WrappedEncodeString(string16(test_string_b)).size());
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeString) {
+ const char16 test_string_a[] = {'f', 'o', 'o', '\0'};
+ const char16 test_string_b[] = {0xdead, 0xbeef, '\0'};
+
+ std::vector<string16> test_cases;
+ test_cases.push_back(string16());
+ test_cases.push_back(ASCIIToUTF16("a"));
+ test_cases.push_back(ASCIIToUTF16("foo"));
+ test_cases.push_back(test_string_a);
+ test_cases.push_back(test_string_b);
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ const string16& test_case = test_cases[i];
+ std::string v = WrappedEncodeString(test_case);
+
+ StringPiece slice;
+ if (v.size()) {
+ slice = StringPiece(&*v.begin(), v.size());
+ }
+
+ string16 result;
+ EXPECT_TRUE(DecodeString(&slice, &result));
+ EXPECT_EQ(test_case, result);
+ EXPECT_TRUE(slice.empty());
+
+ // Verify decoding at an offset, to detect unaligned memory access.
+ v.insert(v.begin(), static_cast<size_t>(1), static_cast<char>(0));
+ slice = StringPiece(&*v.begin() + 1, v.size() - 1);
+ EXPECT_TRUE(DecodeString(&slice, &result));
+ EXPECT_EQ(test_case, result);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+static std::string WrappedEncodeStringWithLength(string16 value) {
+ std::string buffer;
+ EncodeStringWithLength(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeStringWithLength) {
+ const char16 test_string_a[] = {'f', 'o', 'o', '\0'};
+ const char16 test_string_b[] = {0xdead, 0xbeef, '\0'};
+
+ EXPECT_EQ(static_cast<size_t>(1),
+ WrappedEncodeStringWithLength(string16()).size());
+ EXPECT_EQ(static_cast<size_t>(3),
+ WrappedEncodeStringWithLength(ASCIIToUTF16("a")).size());
+ EXPECT_EQ(static_cast<size_t>(7),
+ WrappedEncodeStringWithLength(string16(test_string_a)).size());
+ EXPECT_EQ(static_cast<size_t>(5),
+ WrappedEncodeStringWithLength(string16(test_string_b)).size());
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeStringWithLength) {
+ const char16 test_string_a[] = {'f', 'o', 'o', '\0'};
+ const char16 test_string_b[] = {0xdead, 0xbeef, '\0'};
+
+ const int kLongStringLen = 1234;
+ char16 long_string[kLongStringLen + 1];
+ for (int i = 0; i < kLongStringLen; ++i)
+ long_string[i] = i;
+ long_string[kLongStringLen] = 0;
+
+ std::vector<string16> test_cases;
+ test_cases.push_back(ASCIIToUTF16(""));
+ test_cases.push_back(ASCIIToUTF16("a"));
+ test_cases.push_back(ASCIIToUTF16("foo"));
+ test_cases.push_back(string16(test_string_a));
+ test_cases.push_back(string16(test_string_b));
+ test_cases.push_back(string16(long_string));
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ string16 s = test_cases[i];
+ std::string v = WrappedEncodeStringWithLength(s);
+ ASSERT_GT(v.size(), static_cast<size_t>(0));
+ StringPiece slice(v);
+ string16 res;
+ EXPECT_TRUE(DecodeStringWithLength(&slice, &res));
+ EXPECT_EQ(s, res);
+ EXPECT_TRUE(slice.empty());
+
+ slice = StringPiece(&*v.begin(), v.size() - 1);
+ EXPECT_FALSE(DecodeStringWithLength(&slice, &res));
+
+ slice = StringPiece(&*v.begin(), static_cast<size_t>(0));
+ EXPECT_FALSE(DecodeStringWithLength(&slice, &res));
+
+ // Verify decoding at an offset, to detect unaligned memory access.
+ v.insert(v.begin(), static_cast<size_t>(1), static_cast<char>(0));
+ slice = StringPiece(&*v.begin() + 1, v.size() - 1);
+ EXPECT_TRUE(DecodeStringWithLength(&slice, &res));
+ EXPECT_EQ(s, res);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+static int CompareStrings(const std::string& p, const std::string& q) {
+ bool ok;
+ DCHECK(!p.empty());
+ DCHECK(!q.empty());
+ StringPiece slice_p(p);
+ StringPiece slice_q(q);
+ int result = CompareEncodedStringsWithLength(&slice_p, &slice_q, &ok);
+ EXPECT_TRUE(ok);
+ EXPECT_TRUE(slice_p.empty());
+ EXPECT_TRUE(slice_q.empty());
+ return result;
+}
+
+TEST(IndexedDBLevelDBCodingTest, CompareEncodedStringsWithLength) {
+ const char16 test_string_a[] = {0x1000, 0x1000, '\0'};
+ const char16 test_string_b[] = {0x1000, 0x1000, 0x1000, '\0'};
+ const char16 test_string_c[] = {0x1000, 0x1000, 0x1001, '\0'};
+ const char16 test_string_d[] = {0x1001, 0x1000, 0x1000, '\0'};
+ const char16 test_string_e[] = {0xd834, 0xdd1e, '\0'};
+ const char16 test_string_f[] = {0xfffd, '\0'};
+
+ std::vector<string16> test_cases;
+ test_cases.push_back(ASCIIToUTF16(""));
+ test_cases.push_back(ASCIIToUTF16("a"));
+ test_cases.push_back(ASCIIToUTF16("b"));
+ test_cases.push_back(ASCIIToUTF16("baaa"));
+ test_cases.push_back(ASCIIToUTF16("baab"));
+ test_cases.push_back(ASCIIToUTF16("c"));
+ test_cases.push_back(string16(test_string_a));
+ test_cases.push_back(string16(test_string_b));
+ test_cases.push_back(string16(test_string_c));
+ test_cases.push_back(string16(test_string_d));
+ test_cases.push_back(string16(test_string_e));
+ test_cases.push_back(string16(test_string_f));
+
+ for (size_t i = 0; i < test_cases.size() - 1; ++i) {
+ string16 a = test_cases[i];
+ string16 b = test_cases[i + 1];
+
+ EXPECT_LT(a.compare(b), 0);
+ EXPECT_GT(b.compare(a), 0);
+ EXPECT_EQ(a.compare(a), 0);
+ EXPECT_EQ(b.compare(b), 0);
+
+ std::string encoded_a = WrappedEncodeStringWithLength(a);
+ EXPECT_TRUE(encoded_a.size());
+ std::string encoded_b = WrappedEncodeStringWithLength(b);
+ EXPECT_TRUE(encoded_a.size());
+
+ EXPECT_LT(CompareStrings(encoded_a, encoded_b), 0);
+ EXPECT_GT(CompareStrings(encoded_b, encoded_a), 0);
+ EXPECT_EQ(CompareStrings(encoded_a, encoded_a), 0);
+ EXPECT_EQ(CompareStrings(encoded_b, encoded_b), 0);
+ }
+}
+
+static std::string WrappedEncodeDouble(double value) {
+ std::string buffer;
+ EncodeDouble(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeDouble) {
+ EXPECT_EQ(static_cast<size_t>(8), WrappedEncodeDouble(0).size());
+ EXPECT_EQ(static_cast<size_t>(8), WrappedEncodeDouble(3.14).size());
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeDouble) {
+ std::vector<double> test_cases;
+ test_cases.push_back(3.14);
+ test_cases.push_back(-3.14);
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ double value = test_cases[i];
+ std::string v = WrappedEncodeDouble(value);
+ ASSERT_GT(v.size(), static_cast<size_t>(0));
+ StringPiece slice(v);
+ double result;
+ EXPECT_TRUE(DecodeDouble(&slice, &result));
+ EXPECT_EQ(value, result);
+ EXPECT_TRUE(slice.empty());
+
+ slice = StringPiece(&*v.begin(), v.size() - 1);
+ EXPECT_FALSE(DecodeDouble(&slice, &result));
+
+ slice = StringPiece(&*v.begin(), static_cast<size_t>(0));
+ EXPECT_FALSE(DecodeDouble(&slice, &result));
+
+ // Verify decoding at an offset, to detect unaligned memory access.
+ v.insert(v.begin(), static_cast<size_t>(1), static_cast<char>(0));
+ slice = StringPiece(&*v.begin() + 1, v.size() - 1);
+ EXPECT_TRUE(DecodeDouble(&slice, &result));
+ EXPECT_EQ(value, result);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeDecodeIDBKey) {
+ IndexedDBKey expected_key;
+ scoped_ptr<IndexedDBKey> decoded_key;
+ std::string v;
+ StringPiece slice;
+
+ std::vector<IndexedDBKey> test_cases;
+ test_cases.push_back(IndexedDBKey(1234, WebIDBKeyTypeNumber));
+ test_cases.push_back(IndexedDBKey(7890, WebIDBKeyTypeDate));
+ test_cases.push_back(IndexedDBKey(ASCIIToUTF16("Hello World!")));
+ test_cases.push_back(IndexedDBKey(IndexedDBKey::KeyArray()));
+
+ IndexedDBKey::KeyArray array;
+ array.push_back(IndexedDBKey(1234, WebIDBKeyTypeNumber));
+ array.push_back(IndexedDBKey(7890, WebIDBKeyTypeDate));
+ array.push_back(IndexedDBKey(ASCIIToUTF16("Hello World!")));
+ array.push_back(IndexedDBKey(IndexedDBKey::KeyArray()));
+ test_cases.push_back(IndexedDBKey(array));
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ expected_key = test_cases[i];
+ v.clear();
+ EncodeIDBKey(expected_key, &v);
+ slice = StringPiece(&*v.begin(), v.size());
+ EXPECT_TRUE(DecodeIDBKey(&slice, &decoded_key));
+ EXPECT_TRUE(decoded_key->IsEqual(expected_key));
+ EXPECT_TRUE(slice.empty());
+
+ slice = StringPiece(&*v.begin(), v.size() - 1);
+ EXPECT_FALSE(DecodeIDBKey(&slice, &decoded_key));
+
+ slice = StringPiece(&*v.begin(), static_cast<size_t>(0));
+ EXPECT_FALSE(DecodeIDBKey(&slice, &decoded_key));
+ }
+}
+
+static std::string WrappedEncodeIDBKeyPath(const IndexedDBKeyPath& value) {
+ std::string buffer;
+ EncodeIDBKeyPath(value, &buffer);
+ return buffer;
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeDecodeIDBKeyPath) {
+ std::vector<IndexedDBKeyPath> key_paths;
+ std::vector<std::string> encoded_paths;
+
+ {
+ key_paths.push_back(IndexedDBKeyPath());
+ char expected[] = {0, 0, // Header
+ 0 // Type is null
+ };
+ encoded_paths.push_back(
+ std::string(expected, expected + arraysize(expected)));
+ }
+
+ {
+ key_paths.push_back(IndexedDBKeyPath(string16()));
+ char expected[] = {0, 0, // Header
+ 1, // Type is string
+ 0 // Length is 0
+ };
+ encoded_paths.push_back(
+ std::string(expected, expected + arraysize(expected)));
+ }
+
+ {
+ key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo")));
+ char expected[] = {0, 0, // Header
+ 1, // Type is string
+ 3, 0, 'f', 0, 'o', 0, 'o' // String length 3, UTF-16BE
+ };
+ encoded_paths.push_back(
+ std::string(expected, expected + arraysize(expected)));
+ }
+
+ {
+ key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo.bar")));
+ char expected[] = {0, 0, // Header
+ 1, // Type is string
+ 7, 0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0,
+ 'r' // String length 7, UTF-16BE
+ };
+ encoded_paths.push_back(
+ std::string(expected, expected + arraysize(expected)));
+ }
+
+ {
+ std::vector<string16> array;
+ array.push_back(string16());
+ array.push_back(ASCIIToUTF16("foo"));
+ array.push_back(ASCIIToUTF16("foo.bar"));
+
+ key_paths.push_back(IndexedDBKeyPath(array));
+ char expected[] = {0, 0, // Header
+ 2, 3, // Type is array, length is 3
+ 0, // Member 1 (String length 0)
+ 3, 0, 'f', 0, 'o', 0, 'o', // Member 2 (String length 3)
+ 7, 0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0,
+ 'r' // Member 3 (String length 7)
+ };
+ encoded_paths.push_back(
+ std::string(expected, expected + arraysize(expected)));
+ }
+
+ ASSERT_EQ(key_paths.size(), encoded_paths.size());
+ for (size_t i = 0; i < key_paths.size(); ++i) {
+ IndexedDBKeyPath key_path = key_paths[i];
+ std::string encoded = encoded_paths[i];
+
+ std::string v = WrappedEncodeIDBKeyPath(key_path);
+ EXPECT_EQ(encoded, v);
+
+ StringPiece slice(encoded);
+ IndexedDBKeyPath decoded;
+ EXPECT_TRUE(DecodeIDBKeyPath(&slice, &decoded));
+ EXPECT_EQ(key_path, decoded);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, DecodeLegacyIDBKeyPath) {
+ // Legacy encoding of string key paths.
+ std::vector<IndexedDBKeyPath> key_paths;
+ std::vector<std::string> encoded_paths;
+
+ {
+ key_paths.push_back(IndexedDBKeyPath(string16()));
+ encoded_paths.push_back(std::string());
+ }
+ {
+ key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo")));
+ char expected[] = {0, 'f', 0, 'o', 0, 'o'};
+ encoded_paths.push_back(std::string(expected, arraysize(expected)));
+ }
+ {
+ key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo.bar")));
+ char expected[] = {0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0, 'r'};
+ encoded_paths.push_back(std::string(expected, arraysize(expected)));
+ }
+
+ ASSERT_EQ(key_paths.size(), encoded_paths.size());
+ for (size_t i = 0; i < key_paths.size(); ++i) {
+ IndexedDBKeyPath key_path = key_paths[i];
+ std::string encoded = encoded_paths[i];
+
+ StringPiece slice(encoded);
+ IndexedDBKeyPath decoded;
+ EXPECT_TRUE(DecodeIDBKeyPath(&slice, &decoded));
+ EXPECT_EQ(key_path, decoded);
+ EXPECT_TRUE(slice.empty());
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, ExtractAndCompareIDBKeys) {
+ std::vector<IndexedDBKey> keys;
+
+ keys.push_back(IndexedDBKey(-10, WebIDBKeyTypeNumber));
+ keys.push_back(IndexedDBKey(0, WebIDBKeyTypeNumber));
+ keys.push_back(IndexedDBKey(3.14, WebIDBKeyTypeNumber));
+
+ keys.push_back(IndexedDBKey(0, WebIDBKeyTypeDate));
+ keys.push_back(IndexedDBKey(100, WebIDBKeyTypeDate));
+ keys.push_back(IndexedDBKey(100000, WebIDBKeyTypeDate));
+
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("")));
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("a")));
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("b")));
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("baaa")));
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("baab")));
+ keys.push_back(IndexedDBKey(ASCIIToUTF16("c")));
+
+ keys.push_back(CreateArrayIDBKey());
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKeyTypeNumber)));
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKeyTypeNumber),
+ IndexedDBKey(3.14, WebIDBKeyTypeNumber)));
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKeyTypeDate)));
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(0, WebIDBKeyTypeDate),
+ IndexedDBKey(0, WebIDBKeyTypeDate)));
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16(""))));
+ keys.push_back(CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16("")),
+ IndexedDBKey(ASCIIToUTF16("a"))));
+ keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey()));
+ keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey(), CreateArrayIDBKey()));
+ keys.push_back(CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey())));
+ keys.push_back(CreateArrayIDBKey(
+ CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey()))));
+
+ for (size_t i = 0; i < keys.size() - 1; ++i) {
+ const IndexedDBKey& key_a = keys[i];
+ const IndexedDBKey& key_b = keys[i + 1];
+
+ EXPECT_TRUE(key_a.IsLessThan(key_b));
+
+ std::string encoded_a;
+ EncodeIDBKey(key_a, &encoded_a);
+ EXPECT_TRUE(encoded_a.size());
+ std::string encoded_b;
+ EncodeIDBKey(key_b, &encoded_b);
+ EXPECT_TRUE(encoded_b.size());
+
+ std::string extracted_a;
+ std::string extracted_b;
+ StringPiece slice;
+
+ slice = StringPiece(encoded_a);
+ EXPECT_TRUE(ExtractEncodedIDBKey(&slice, &extracted_a));
+ EXPECT_TRUE(slice.empty());
+ EXPECT_EQ(encoded_a, extracted_a);
+
+ slice = StringPiece(encoded_b);
+ EXPECT_TRUE(ExtractEncodedIDBKey(&slice, &extracted_b));
+ EXPECT_TRUE(slice.empty());
+ EXPECT_EQ(encoded_b, extracted_b);
+
+ EXPECT_LT(CompareKeys(extracted_a, extracted_b), 0);
+ EXPECT_GT(CompareKeys(extracted_b, extracted_a), 0);
+ EXPECT_EQ(CompareKeys(extracted_a, extracted_a), 0);
+ EXPECT_EQ(CompareKeys(extracted_b, extracted_b), 0);
+
+ slice = StringPiece(&*encoded_a.begin(), encoded_a.size() - 1);
+ EXPECT_FALSE(ExtractEncodedIDBKey(&slice, &extracted_a));
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, ComparisonTest) {
+ std::vector<std::string> keys;
+ keys.push_back(SchemaVersionKey::Encode());
+ keys.push_back(MaxDatabaseIdKey::Encode());
+ keys.push_back(DatabaseFreeListKey::Encode(0));
+ keys.push_back(DatabaseFreeListKey::EncodeMaxKey());
+ keys.push_back(DatabaseNameKey::Encode("", ASCIIToUTF16("")));
+ keys.push_back(DatabaseNameKey::Encode("", ASCIIToUTF16("a")));
+ keys.push_back(DatabaseNameKey::Encode("a", ASCIIToUTF16("a")));
+ keys.push_back(
+ DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::ORIGIN_NAME));
+ keys.push_back(
+ DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::DATABASE_NAME));
+ keys.push_back(
+ DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_VERSION));
+ keys.push_back(
+ DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::MAX_OBJECT_STORE_ID));
+ keys.push_back(
+ DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::USER_INT_VERSION));
+ keys.push_back(
+ ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::NAME));
+ keys.push_back(
+ ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::KEY_PATH));
+ keys.push_back(ObjectStoreMetaDataKey::Encode(
+ 1, 1, ObjectStoreMetaDataKey::AUTO_INCREMENT));
+ keys.push_back(
+ ObjectStoreMetaDataKey::Encode(1, 1, ObjectStoreMetaDataKey::EVICTABLE));
+ keys.push_back(ObjectStoreMetaDataKey::Encode(
+ 1, 1, ObjectStoreMetaDataKey::LAST_VERSION));
+ keys.push_back(ObjectStoreMetaDataKey::Encode(
+ 1, 1, ObjectStoreMetaDataKey::MAX_INDEX_ID));
+ keys.push_back(ObjectStoreMetaDataKey::Encode(
+ 1, 1, ObjectStoreMetaDataKey::HAS_KEY_PATH));
+ keys.push_back(ObjectStoreMetaDataKey::Encode(
+ 1, 1, ObjectStoreMetaDataKey::KEY_GENERATOR_CURRENT_NUMBER));
+ keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1, 1));
+ keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1, 2));
+ keys.push_back(ObjectStoreMetaDataKey::EncodeMaxKey(1));
+ keys.push_back(IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::NAME));
+ keys.push_back(IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::UNIQUE));
+ keys.push_back(
+ IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::KEY_PATH));
+ keys.push_back(
+ IndexMetaDataKey::Encode(1, 1, 30, IndexMetaDataKey::MULTI_ENTRY));
+ keys.push_back(IndexMetaDataKey::Encode(1, 1, 31, 0));
+ keys.push_back(IndexMetaDataKey::Encode(1, 1, 31, 1));
+ keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1, 31));
+ keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1, 32));
+ keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 1));
+ keys.push_back(IndexMetaDataKey::EncodeMaxKey(1, 2));
+ keys.push_back(ObjectStoreFreeListKey::Encode(1, 1));
+ keys.push_back(ObjectStoreFreeListKey::EncodeMaxKey(1));
+ keys.push_back(IndexFreeListKey::Encode(1, 1, kMinimumIndexId));
+ keys.push_back(IndexFreeListKey::EncodeMaxKey(1, 1));
+ keys.push_back(IndexFreeListKey::Encode(1, 2, kMinimumIndexId));
+ keys.push_back(IndexFreeListKey::EncodeMaxKey(1, 2));
+ keys.push_back(ObjectStoreNamesKey::Encode(1, ASCIIToUTF16("")));
+ keys.push_back(ObjectStoreNamesKey::Encode(1, ASCIIToUTF16("a")));
+ keys.push_back(IndexNamesKey::Encode(1, 1, ASCIIToUTF16("")));
+ keys.push_back(IndexNamesKey::Encode(1, 1, ASCIIToUTF16("a")));
+ keys.push_back(IndexNamesKey::Encode(1, 2, ASCIIToUTF16("a")));
+ keys.push_back(ObjectStoreDataKey::Encode(1, 1, MinIDBKey()));
+ keys.push_back(ObjectStoreDataKey::Encode(1, 1, MaxIDBKey()));
+ keys.push_back(ExistsEntryKey::Encode(1, 1, MinIDBKey()));
+ keys.push_back(ExistsEntryKey::Encode(1, 1, MaxIDBKey()));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 0));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 1));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 0));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 1));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 0));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 1));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 0));
+ keys.push_back(IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MaxIDBKey(), 1));
+ keys.push_back(IndexDataKey::Encode(1, 1, 31, MinIDBKey(), MinIDBKey(), 0));
+ keys.push_back(IndexDataKey::Encode(1, 2, 30, MinIDBKey(), MinIDBKey(), 0));
+ keys.push_back(
+ IndexDataKey::EncodeMaxKey(1, 2, std::numeric_limits<int32>::max() - 1));
+
+ for (size_t i = 0; i < keys.size(); ++i) {
+ EXPECT_EQ(Compare(keys[i], keys[i], false), 0);
+
+ for (size_t j = i + 1; j < keys.size(); ++j) {
+ EXPECT_LT(Compare(keys[i], keys[j], false), 0);
+ EXPECT_GT(Compare(keys[j], keys[i], false), 0);
+ }
+ }
+}
+
+TEST(IndexedDBLevelDBCodingTest, EncodeVarIntVSEncodeByteTest) {
+ std::vector<unsigned char> test_cases;
+ test_cases.push_back(0);
+ test_cases.push_back(1);
+ test_cases.push_back(127);
+
+ for (size_t i = 0; i < test_cases.size(); ++i) {
+ unsigned char n = test_cases[i];
+
+ std::string vA = WrappedEncodeByte(n);
+ std::string vB = WrappedEncodeVarInt(static_cast<int64>(n));
+
+ EXPECT_EQ(vA.size(), vB.size());
+ EXPECT_EQ(*vA.begin(), *vB.begin());
+ }
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_metadata.cc b/chromium/content/browser/indexed_db/indexed_db_metadata.cc
new file mode 100644
index 00000000000..80d31424c5f
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_metadata.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_metadata.h"
+
+namespace content {
+
+IndexedDBObjectStoreMetadata::IndexedDBObjectStoreMetadata(
+ const string16& name,
+ int64 id,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment,
+ int64 max_index_id)
+ : name(name),
+ id(id),
+ key_path(key_path),
+ auto_increment(auto_increment),
+ max_index_id(max_index_id) {}
+
+IndexedDBObjectStoreMetadata::IndexedDBObjectStoreMetadata() {}
+IndexedDBObjectStoreMetadata::~IndexedDBObjectStoreMetadata() {}
+
+IndexedDBDatabaseMetadata::IndexedDBDatabaseMetadata()
+ : int_version(NO_INT_VERSION) {}
+IndexedDBDatabaseMetadata::IndexedDBDatabaseMetadata(const string16& name,
+ int64 id,
+ const string16& version,
+ int64 int_version,
+ int64 max_object_store_id)
+ : name(name),
+ id(id),
+ version(version),
+ int_version(int_version),
+ max_object_store_id(max_object_store_id) {}
+
+IndexedDBDatabaseMetadata::~IndexedDBDatabaseMetadata() {}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_metadata.h b/chromium/content/browser/indexed_db/indexed_db_metadata.h
new file mode 100644
index 00000000000..4e8562d1f68
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_metadata.h
@@ -0,0 +1,84 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "content/common/indexed_db/indexed_db_key_path.h"
+
+namespace content {
+
+struct IndexedDBIndexMetadata {
+ IndexedDBIndexMetadata() {}
+ IndexedDBIndexMetadata(const string16& name,
+ int64 id,
+ const IndexedDBKeyPath& key_path,
+ bool unique,
+ bool multi_entry)
+ : name(name),
+ id(id),
+ key_path(key_path),
+ unique(unique),
+ multi_entry(multi_entry) {}
+ string16 name;
+ int64 id;
+ IndexedDBKeyPath key_path;
+ bool unique;
+ bool multi_entry;
+
+ static const int64 kInvalidId = -1;
+};
+
+struct CONTENT_EXPORT IndexedDBObjectStoreMetadata {
+ IndexedDBObjectStoreMetadata();
+ IndexedDBObjectStoreMetadata(const string16& name,
+ int64 id,
+ const IndexedDBKeyPath& key_path,
+ bool auto_increment,
+ int64 max_index_id);
+ ~IndexedDBObjectStoreMetadata();
+ string16 name;
+ int64 id;
+ IndexedDBKeyPath key_path;
+ bool auto_increment;
+ int64 max_index_id;
+
+ static const int64 kInvalidId = -1;
+
+ typedef std::map<int64, IndexedDBIndexMetadata> IndexMap;
+ IndexMap indexes;
+};
+
+struct CONTENT_EXPORT IndexedDBDatabaseMetadata {
+ // TODO(jsbell): These can probably be collapsed into 0.
+ enum {
+ NO_INT_VERSION = -1,
+ DEFAULT_INT_VERSION = 0
+ };
+
+ typedef std::map<int64, IndexedDBObjectStoreMetadata> ObjectStoreMap;
+
+ IndexedDBDatabaseMetadata();
+ IndexedDBDatabaseMetadata(const string16& name,
+ int64 id,
+ const string16& version,
+ int64 int_version,
+ int64 max_object_store_id);
+ ~IndexedDBDatabaseMetadata();
+
+ string16 name;
+ int64 id;
+ string16 version;
+ int64 int_version;
+ int64 max_object_store_id;
+
+ ObjectStoreMap object_stores;
+};
+}
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_METADATA_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_quota_client.cc b/chromium/content/browser/indexed_db/indexed_db_quota_client.cc
new file mode 100644
index 00000000000..1b187c51339
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_quota_client.cc
@@ -0,0 +1,178 @@
+// 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 "content/browser/indexed_db/indexed_db_quota_client.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/net_util.h"
+#include "webkit/browser/database/database_util.h"
+
+using quota::QuotaClient;
+using webkit_database::DatabaseUtil;
+
+namespace content {
+namespace {
+
+quota::QuotaStatusCode DeleteOriginDataOnIndexedDBThread(
+ IndexedDBContextImpl* context,
+ const GURL& origin) {
+ context->DeleteForOrigin(origin);
+ return quota::kQuotaStatusOk;
+}
+
+int64 GetOriginUsageOnIndexedDBThread(IndexedDBContextImpl* context,
+ const GURL& origin) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+ return context->GetOriginDiskUsage(origin);
+}
+
+void GetAllOriginsOnIndexedDBThread(IndexedDBContextImpl* context,
+ std::set<GURL>* origins_to_return) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+ std::vector<GURL> all_origins = context->GetAllOrigins();
+ origins_to_return->insert(all_origins.begin(), all_origins.end());
+}
+
+void DidGetOrigins(const IndexedDBQuotaClient::GetOriginsCallback& callback,
+ const std::set<GURL>* origins) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ callback.Run(*origins);
+}
+
+void GetOriginsForHostOnIndexedDBThread(IndexedDBContextImpl* context,
+ const std::string& host,
+ std::set<GURL>* origins_to_return) {
+ DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
+ std::vector<GURL> all_origins = context->GetAllOrigins();
+ for (std::vector<GURL>::const_iterator iter = all_origins.begin();
+ iter != all_origins.end();
+ ++iter) {
+ if (host == net::GetHostOrSpecFromURL(*iter))
+ origins_to_return->insert(*iter);
+ }
+}
+
+} // namespace
+
+// IndexedDBQuotaClient --------------------------------------------------------
+
+IndexedDBQuotaClient::IndexedDBQuotaClient(
+ IndexedDBContextImpl* indexed_db_context)
+ : indexed_db_context_(indexed_db_context) {}
+
+IndexedDBQuotaClient::~IndexedDBQuotaClient() {}
+
+QuotaClient::ID IndexedDBQuotaClient::id() const { return kIndexedDatabase; }
+
+void IndexedDBQuotaClient::OnQuotaManagerDestroyed() { delete this; }
+
+void IndexedDBQuotaClient::GetOriginUsage(const GURL& origin_url,
+ quota::StorageType type,
+ const GetUsageCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(indexed_db_context_);
+
+ // IndexedDB is in the temp namespace for now.
+ if (type != quota::kStorageTypeTemporary) {
+ callback.Run(0);
+ return;
+ }
+
+ // No task runner means unit test; no cleanup necessary.
+ if (!indexed_db_context_->TaskRunner()) {
+ callback.Run(0);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ indexed_db_context_->TaskRunner(),
+ FROM_HERE,
+ base::Bind(
+ &GetOriginUsageOnIndexedDBThread, indexed_db_context_, origin_url),
+ callback);
+}
+
+void IndexedDBQuotaClient::GetOriginsForType(
+ quota::StorageType type,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(indexed_db_context_);
+
+ // All databases are in the temp namespace for now.
+ if (type != quota::kStorageTypeTemporary) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ // No task runner means unit test; no cleanup necessary.
+ if (!indexed_db_context_->TaskRunner()) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ std::set<GURL>* origins_to_return = new std::set<GURL>();
+ indexed_db_context_->TaskRunner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetAllOriginsOnIndexedDBThread,
+ indexed_db_context_,
+ base::Unretained(origins_to_return)),
+ base::Bind(&DidGetOrigins, callback, base::Owned(origins_to_return)));
+}
+
+void IndexedDBQuotaClient::GetOriginsForHost(
+ quota::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(indexed_db_context_);
+
+ // All databases are in the temp namespace for now.
+ if (type != quota::kStorageTypeTemporary) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ // No task runner means unit test; no cleanup necessary.
+ if (!indexed_db_context_->TaskRunner()) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ std::set<GURL>* origins_to_return = new std::set<GURL>();
+ indexed_db_context_->TaskRunner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetOriginsForHostOnIndexedDBThread,
+ indexed_db_context_,
+ host,
+ base::Unretained(origins_to_return)),
+ base::Bind(&DidGetOrigins, callback, base::Owned(origins_to_return)));
+}
+
+void IndexedDBQuotaClient::DeleteOriginData(const GURL& origin,
+ quota::StorageType type,
+ const DeletionCallback& callback) {
+ if (type != quota::kStorageTypeTemporary) {
+ callback.Run(quota::kQuotaErrorNotSupported);
+ return;
+ }
+
+ // No task runner means unit test; no cleanup necessary.
+ if (!indexed_db_context_->TaskRunner()) {
+ callback.Run(quota::kQuotaStatusOk);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ indexed_db_context_->TaskRunner(),
+ FROM_HERE,
+ base::Bind(
+ &DeleteOriginDataOnIndexedDBThread, indexed_db_context_, origin),
+ callback);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_quota_client.h b/chromium/content/browser/indexed_db/indexed_db_quota_client.h
new file mode 100644
index 00000000000..6c0f2bb2af7
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_quota_client.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_QUOTA_CLIENT_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_QUOTA_CLIENT_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/common/content_export.h"
+#include "webkit/browser/quota/quota_client.h"
+#include "webkit/browser/quota/quota_task.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace content {
+class IndexedDBContextImpl;
+
+// A QuotaClient implementation to integrate IndexedDB
+// with the quota management system. This interface is used
+// on the IO thread by the quota manager.
+class IndexedDBQuotaClient : public quota::QuotaClient,
+ public quota::QuotaTaskObserver {
+ public:
+ CONTENT_EXPORT explicit IndexedDBQuotaClient(
+ IndexedDBContextImpl* indexed_db_context);
+ CONTENT_EXPORT virtual ~IndexedDBQuotaClient();
+
+ // QuotaClient method overrides
+ virtual ID id() const OVERRIDE;
+ virtual void OnQuotaManagerDestroyed() OVERRIDE;
+ virtual void GetOriginUsage(const GURL& origin_url,
+ quota::StorageType type,
+ const GetUsageCallback& callback) OVERRIDE;
+ virtual void GetOriginsForType(quota::StorageType type,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void GetOriginsForHost(quota::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void DeleteOriginData(const GURL& origin,
+ quota::StorageType type,
+ const DeletionCallback& callback) OVERRIDE;
+
+ private:
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(IndexedDBQuotaClient);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_QUOTA_CLIENT_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_quota_client_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_quota_client_unittest.cc
new file mode 100644
index 00000000000..5f3f01916ac
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_quota_client_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2011 The Chromium 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 <map>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/indexed_db/indexed_db_quota_client.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/quota/mock_quota_manager.h"
+#include "webkit/common/database/database_identifier.h"
+
+// Declared to shorten the line lengths.
+static const quota::StorageType kTemp = quota::kStorageTypeTemporary;
+static const quota::StorageType kPerm = quota::kStorageTypePersistent;
+
+namespace content {
+
+// Base class for our test fixtures.
+class IndexedDBQuotaClientTest : public testing::Test {
+ public:
+ const GURL kOriginA;
+ const GURL kOriginB;
+ const GURL kOriginOther;
+
+ IndexedDBQuotaClientTest()
+ : kOriginA("http://host"),
+ kOriginB("http://host:8000"),
+ kOriginOther("http://other"),
+ usage_(0),
+ task_runner_(new base::TestSimpleTaskRunner),
+ weak_factory_(this) {
+ browser_context_.reset(new TestBrowserContext());
+
+ scoped_refptr<quota::QuotaManager> quota_manager =
+ new quota::MockQuotaManager(
+ false /*in_memory*/,
+ browser_context_->GetPath(),
+ base::MessageLoop::current()->message_loop_proxy(),
+ base::MessageLoop::current()->message_loop_proxy(),
+ browser_context_->GetSpecialStoragePolicy());
+
+ idb_context_ =
+ new IndexedDBContextImpl(browser_context_->GetPath(),
+ browser_context_->GetSpecialStoragePolicy(),
+ quota_manager->proxy(),
+ task_runner_);
+ base::MessageLoop::current()->RunUntilIdle();
+ setup_temp_dir();
+ }
+
+ void FlushIndexedDBTaskRunner() { task_runner_->RunUntilIdle(); }
+
+ void setup_temp_dir() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ base::FilePath indexeddb_dir =
+ temp_dir_.path().Append(IndexedDBContextImpl::kIndexedDBDirectory);
+ ASSERT_TRUE(file_util::CreateDirectory(indexeddb_dir));
+ idb_context()->set_data_path_for_testing(indexeddb_dir);
+ }
+
+ virtual ~IndexedDBQuotaClientTest() {
+ FlushIndexedDBTaskRunner();
+ idb_context_ = NULL;
+ browser_context_.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ int64 GetOriginUsage(quota::QuotaClient* client,
+ const GURL& origin,
+ quota::StorageType type) {
+ usage_ = -1;
+ client->GetOriginUsage(
+ origin,
+ type,
+ base::Bind(&IndexedDBQuotaClientTest::OnGetOriginUsageComplete,
+ weak_factory_.GetWeakPtr()));
+ FlushIndexedDBTaskRunner();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_GT(usage_, -1);
+ return usage_;
+ }
+
+ const std::set<GURL>& GetOriginsForType(quota::QuotaClient* client,
+ quota::StorageType type) {
+ origins_.clear();
+ client->GetOriginsForType(
+ type,
+ base::Bind(&IndexedDBQuotaClientTest::OnGetOriginsComplete,
+ weak_factory_.GetWeakPtr()));
+ FlushIndexedDBTaskRunner();
+ base::MessageLoop::current()->RunUntilIdle();
+ return origins_;
+ }
+
+ const std::set<GURL>& GetOriginsForHost(quota::QuotaClient* client,
+ quota::StorageType type,
+ const std::string& host) {
+ origins_.clear();
+ client->GetOriginsForHost(
+ type,
+ host,
+ base::Bind(&IndexedDBQuotaClientTest::OnGetOriginsComplete,
+ weak_factory_.GetWeakPtr()));
+ FlushIndexedDBTaskRunner();
+ base::MessageLoop::current()->RunUntilIdle();
+ return origins_;
+ }
+
+ quota::QuotaStatusCode DeleteOrigin(quota::QuotaClient* client,
+ const GURL& origin_url) {
+ delete_status_ = quota::kQuotaStatusUnknown;
+ client->DeleteOriginData(
+ origin_url,
+ kTemp,
+ base::Bind(&IndexedDBQuotaClientTest::OnDeleteOriginComplete,
+ weak_factory_.GetWeakPtr()));
+ FlushIndexedDBTaskRunner();
+ base::MessageLoop::current()->RunUntilIdle();
+ return delete_status_;
+ }
+
+ IndexedDBContextImpl* idb_context() { return idb_context_; }
+
+ void SetFileSizeTo(const base::FilePath& path, int size) {
+ std::string junk(size, 'a');
+ ASSERT_EQ(size, file_util::WriteFile(path, junk.c_str(), size));
+ }
+
+ void AddFakeIndexedDB(const GURL& origin, int size) {
+ base::FilePath file_path_origin = idb_context()->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(origin));
+ if (!file_util::CreateDirectory(file_path_origin)) {
+ LOG(ERROR) << "failed to file_util::CreateDirectory "
+ << file_path_origin.value();
+ }
+ file_path_origin = file_path_origin.Append(FILE_PATH_LITERAL("fake_file"));
+ SetFileSizeTo(file_path_origin, size);
+ idb_context()->ResetCaches();
+ }
+
+ private:
+ void OnGetOriginUsageComplete(int64 usage) { usage_ = usage; }
+
+ void OnGetOriginsComplete(const std::set<GURL>& origins) {
+ origins_ = origins;
+ }
+
+ void OnDeleteOriginComplete(quota::QuotaStatusCode code) {
+ delete_status_ = code;
+ }
+
+ base::ScopedTempDir temp_dir_;
+ int64 usage_;
+ std::set<GURL> origins_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ scoped_refptr<IndexedDBContextImpl> idb_context_;
+ base::WeakPtrFactory<IndexedDBQuotaClientTest> weak_factory_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_ptr<TestBrowserContext> browser_context_;
+ quota::QuotaStatusCode delete_status_;
+};
+
+TEST_F(IndexedDBQuotaClientTest, GetOriginUsage) {
+ IndexedDBQuotaClient client(idb_context());
+
+ AddFakeIndexedDB(kOriginA, 6);
+ AddFakeIndexedDB(kOriginB, 3);
+ EXPECT_EQ(6, GetOriginUsage(&client, kOriginA, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm));
+ EXPECT_EQ(3, GetOriginUsage(&client, kOriginB, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm));
+
+ AddFakeIndexedDB(kOriginA, 1000);
+ EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm));
+ EXPECT_EQ(3, GetOriginUsage(&client, kOriginB, kTemp));
+ EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm));
+}
+
+TEST_F(IndexedDBQuotaClientTest, GetOriginsForHost) {
+ IndexedDBQuotaClient client(idb_context());
+
+ EXPECT_EQ(kOriginA.host(), kOriginB.host());
+ EXPECT_NE(kOriginA.host(), kOriginOther.host());
+
+ std::set<GURL> origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
+ EXPECT_TRUE(origins.empty());
+
+ AddFakeIndexedDB(kOriginA, 1000);
+ origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
+ EXPECT_EQ(origins.size(), 1ul);
+ EXPECT_TRUE(origins.find(kOriginA) != origins.end());
+
+ AddFakeIndexedDB(kOriginB, 1000);
+ origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
+ EXPECT_EQ(origins.size(), 2ul);
+ EXPECT_TRUE(origins.find(kOriginA) != origins.end());
+ EXPECT_TRUE(origins.find(kOriginB) != origins.end());
+
+ EXPECT_TRUE(GetOriginsForHost(&client, kPerm, kOriginA.host()).empty());
+ EXPECT_TRUE(GetOriginsForHost(&client, kTemp, kOriginOther.host()).empty());
+}
+
+TEST_F(IndexedDBQuotaClientTest, GetOriginsForType) {
+ IndexedDBQuotaClient client(idb_context());
+
+ EXPECT_TRUE(GetOriginsForType(&client, kTemp).empty());
+ EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty());
+
+ AddFakeIndexedDB(kOriginA, 1000);
+ std::set<GURL> origins = GetOriginsForType(&client, kTemp);
+ EXPECT_EQ(origins.size(), 1ul);
+ EXPECT_TRUE(origins.find(kOriginA) != origins.end());
+
+ EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty());
+}
+
+TEST_F(IndexedDBQuotaClientTest, DeleteOrigin) {
+ IndexedDBQuotaClient client(idb_context());
+
+ AddFakeIndexedDB(kOriginA, 1000);
+ AddFakeIndexedDB(kOriginB, 50);
+ EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp));
+ EXPECT_EQ(50, GetOriginUsage(&client, kOriginB, kTemp));
+
+ quota::QuotaStatusCode delete_status = DeleteOrigin(&client, kOriginA);
+ EXPECT_EQ(quota::kQuotaStatusOk, delete_status);
+ EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kTemp));
+ EXPECT_EQ(50, GetOriginUsage(&client, kOriginB, kTemp));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_tracing.h b/chromium/content/browser/indexed_db/indexed_db_tracing.h
new file mode 100644
index 00000000000..86ddaa34bfa
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_tracing.h
@@ -0,0 +1,11 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_
+
+#include "base/debug/trace_event.h"
+#define IDB_TRACE(a) TRACE_EVENT0("IndexedDB", (a));
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRACING_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_transaction.cc b/chromium/content/browser/indexed_db/indexed_db_transaction.cc
new file mode 100644
index 00000000000..b096f3b3319
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_transaction.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_transaction.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
+#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
+
+namespace content {
+
+IndexedDBTransaction::TaskQueue::TaskQueue() {}
+IndexedDBTransaction::TaskQueue::~TaskQueue() { clear(); }
+
+void IndexedDBTransaction::TaskQueue::clear() {
+ while (!queue_.empty())
+ scoped_ptr<Operation> task(pop());
+}
+
+scoped_ptr<IndexedDBTransaction::Operation>
+IndexedDBTransaction::TaskQueue::pop() {
+ DCHECK(!queue_.empty());
+ scoped_ptr<Operation> task(queue_.front());
+ queue_.pop();
+ return task.Pass();
+}
+
+IndexedDBTransaction::TaskStack::TaskStack() {}
+IndexedDBTransaction::TaskStack::~TaskStack() { clear(); }
+
+void IndexedDBTransaction::TaskStack::clear() {
+ while (!stack_.empty())
+ scoped_ptr<Operation> task(pop());
+}
+
+scoped_ptr<IndexedDBTransaction::Operation>
+IndexedDBTransaction::TaskStack::pop() {
+ DCHECK(!stack_.empty());
+ scoped_ptr<Operation> task(stack_.top());
+ stack_.pop();
+ return task.Pass();
+}
+
+IndexedDBTransaction::IndexedDBTransaction(
+ int64 id,
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
+ const std::set<int64>& object_store_ids,
+ indexed_db::TransactionMode mode,
+ IndexedDBDatabase* database)
+ : id_(id),
+ object_store_ids_(object_store_ids),
+ mode_(mode),
+ state_(UNUSED),
+ commit_pending_(false),
+ callbacks_(callbacks),
+ database_(database),
+ transaction_(database->BackingStore().get()),
+ should_process_queue_(false),
+ pending_preemptive_events_(0) {
+ database_->transaction_coordinator().DidCreateTransaction(this);
+}
+
+IndexedDBTransaction::~IndexedDBTransaction() {
+ // It shouldn't be possible for this object to get deleted until it's either
+ // complete or aborted.
+ DCHECK_EQ(state_, FINISHED);
+ DCHECK(preemptive_task_queue_.empty());
+ DCHECK(task_queue_.empty());
+ DCHECK(abort_task_stack_.empty());
+}
+
+void IndexedDBTransaction::ScheduleTask(IndexedDBDatabase::TaskType type,
+ Operation* task,
+ Operation* abort_task) {
+ if (state_ == FINISHED)
+ return;
+
+ if (type == IndexedDBDatabase::NORMAL_TASK)
+ task_queue_.push(task);
+ else
+ preemptive_task_queue_.push(task);
+
+ if (abort_task)
+ abort_task_stack_.push(abort_task);
+
+ if (state_ == UNUSED) {
+ Start();
+ } else if (state_ == RUNNING && !should_process_queue_) {
+ should_process_queue_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
+ }
+}
+
+void IndexedDBTransaction::Abort() {
+ Abort(IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error (unknown cause)"));
+}
+
+void IndexedDBTransaction::Abort(const IndexedDBDatabaseError& error) {
+ IDB_TRACE("IndexedDBTransaction::Abort");
+ if (state_ == FINISHED)
+ return;
+
+ bool was_running = state_ == RUNNING;
+
+ // The last reference to this object may be released while performing the
+ // abort steps below. We therefore take a self reference to keep ourselves
+ // alive while executing this method.
+ scoped_refptr<IndexedDBTransaction> protect(this);
+
+ state_ = FINISHED;
+ should_process_queue_ = false;
+
+ if (was_running)
+ transaction_.Rollback();
+
+ // Run the abort tasks, if any.
+ while (!abort_task_stack_.empty()) {
+ scoped_ptr<Operation> task(abort_task_stack_.pop());
+ task->Perform(0);
+ }
+ preemptive_task_queue_.clear();
+ task_queue_.clear();
+
+ // Backing store resources (held via cursors) must be released
+ // before script callbacks are fired, as the script callbacks may
+ // release references and allow the backing store itself to be
+ // released, and order is critical.
+ CloseOpenCursors();
+ transaction_.Reset();
+
+ // Transactions must also be marked as completed before the
+ // front-end is notified, as the transaction completion unblocks
+ // operations like closing connections.
+ database_->transaction_coordinator().DidFinishTransaction(this);
+#ifndef NDEBUG
+ DCHECK(!database_->transaction_coordinator().IsActive(this));
+#endif
+ database_->TransactionFinished(this);
+
+ if (callbacks_.get())
+ callbacks_->OnAbort(id_, error);
+
+ database_->TransactionFinishedAndAbortFired(this);
+
+ database_ = NULL;
+}
+
+bool IndexedDBTransaction::IsTaskQueueEmpty() const {
+ return preemptive_task_queue_.empty() && task_queue_.empty();
+}
+
+bool IndexedDBTransaction::HasPendingTasks() const {
+ return pending_preemptive_events_ || !IsTaskQueueEmpty();
+}
+
+void IndexedDBTransaction::RegisterOpenCursor(IndexedDBCursor* cursor) {
+ open_cursors_.insert(cursor);
+}
+
+void IndexedDBTransaction::UnregisterOpenCursor(IndexedDBCursor* cursor) {
+ open_cursors_.erase(cursor);
+}
+
+void IndexedDBTransaction::Run() {
+ // TransactionCoordinator has started this transaction.
+ DCHECK(state_ == START_PENDING || state_ == RUNNING);
+ DCHECK(!should_process_queue_);
+
+ should_process_queue_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&IndexedDBTransaction::ProcessTaskQueue, this));
+}
+
+void IndexedDBTransaction::Start() {
+ DCHECK_EQ(state_, UNUSED);
+
+ state_ = START_PENDING;
+ database_->transaction_coordinator().DidStartTransaction(this);
+ database_->TransactionStarted(this);
+}
+
+void IndexedDBTransaction::Commit() {
+ IDB_TRACE("IndexedDBTransaction::Commit");
+
+ // In multiprocess ports, front-end may have requested a commit but
+ // an abort has already been initiated asynchronously by the
+ // back-end.
+ if (state_ == FINISHED)
+ return;
+
+ DCHECK(state_ == UNUSED || state_ == RUNNING);
+ commit_pending_ = true;
+
+ // Front-end has requested a commit, but there may be tasks like
+ // create_index which are considered synchronous by the front-end
+ // but are processed asynchronously.
+ if (HasPendingTasks())
+ return;
+
+ // The last reference to this object may be released while performing the
+ // commit steps below. We therefore take a self reference to keep ourselves
+ // alive while executing this method.
+ scoped_refptr<IndexedDBTransaction> protect(this);
+
+ // TODO(jsbell): Run abort tasks if commit fails? http://crbug.com/241843
+ abort_task_stack_.clear();
+
+ bool unused = state_ == UNUSED;
+ state_ = FINISHED;
+
+ bool committed = unused || transaction_.Commit();
+
+ // Backing store resources (held via cursors) must be released
+ // before script callbacks are fired, as the script callbacks may
+ // release references and allow the backing store itself to be
+ // released, and order is critical.
+ CloseOpenCursors();
+ transaction_.Reset();
+
+ // Transactions must also be marked as completed before the
+ // front-end is notified, as the transaction completion unblocks
+ // operations like closing connections.
+ database_->transaction_coordinator().DidFinishTransaction(this);
+ database_->TransactionFinished(this);
+
+ if (committed) {
+ callbacks_->OnComplete(id_);
+ database_->TransactionFinishedAndCompleteFired(this);
+ } else {
+ callbacks_->OnAbort(
+ id_,
+ IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
+ "Internal error committing transaction."));
+ database_->TransactionFinishedAndAbortFired(this);
+ }
+
+ database_ = NULL;
+}
+
+void IndexedDBTransaction::ProcessTaskQueue() {
+ IDB_TRACE("IndexedDBTransaction::ProcessTaskQueue");
+
+ // May have been aborted.
+ if (!should_process_queue_)
+ return;
+
+ DCHECK(!IsTaskQueueEmpty());
+ should_process_queue_ = false;
+
+ if (state_ == START_PENDING) {
+ transaction_.Begin();
+ state_ = RUNNING;
+ }
+
+ // The last reference to this object may be released while performing the
+ // tasks. Take take a self reference to keep this object alive so that
+ // the loop termination conditions can be checked.
+ scoped_refptr<IndexedDBTransaction> protect(this);
+
+ TaskQueue* task_queue =
+ pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
+ while (!task_queue->empty() && state_ != FINISHED) {
+ DCHECK_EQ(state_, RUNNING);
+ scoped_ptr<Operation> task(task_queue->pop());
+ task->Perform(this);
+
+ // Event itself may change which queue should be processed next.
+ task_queue =
+ pending_preemptive_events_ ? &preemptive_task_queue_ : &task_queue_;
+ }
+
+ // If there are no pending tasks, we haven't already committed/aborted,
+ // and the front-end requested a commit, it is now safe to do so.
+ if (!HasPendingTasks() && state_ != FINISHED && commit_pending_)
+ Commit();
+}
+
+void IndexedDBTransaction::CloseOpenCursors() {
+ for (std::set<IndexedDBCursor*>::iterator i = open_cursors_.begin();
+ i != open_cursors_.end();
+ ++i)
+ (*i)->Close();
+ open_cursors_.clear();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_transaction.h b/chromium/content/browser/indexed_db/indexed_db_transaction.h
new file mode 100644
index 00000000000..51b1c0ad2fb
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_transaction.h
@@ -0,0 +1,144 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_
+
+#include <queue>
+#include <set>
+#include <stack>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
+#include "content/browser/indexed_db/indexed_db_database.h"
+#include "content/browser/indexed_db/indexed_db_database_error.h"
+
+namespace content {
+
+class IndexedDBCursor;
+class IndexedDBDatabaseCallbacks;
+
+class IndexedDBTransaction : public base::RefCounted<IndexedDBTransaction> {
+ public:
+ IndexedDBTransaction(int64 id,
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks,
+ const std::set<int64>& object_store_ids,
+ indexed_db::TransactionMode,
+ IndexedDBDatabase* db);
+
+ virtual void Abort();
+ void Commit();
+
+ class Operation {
+ public:
+ Operation() {}
+ virtual ~Operation() {}
+ virtual void Perform(IndexedDBTransaction* transaction) = 0;
+ };
+
+ void Abort(const IndexedDBDatabaseError& error);
+ void Run();
+ indexed_db::TransactionMode mode() const { return mode_; }
+ const std::set<int64>& scope() const { return object_store_ids_; }
+ void ScheduleTask(Operation* task) {
+ ScheduleTask(IndexedDBDatabase::NORMAL_TASK, task, NULL);
+ }
+ void ScheduleTask(Operation* task, Operation* abort_task) {
+ ScheduleTask(IndexedDBDatabase::NORMAL_TASK, task, abort_task);
+ }
+ void ScheduleTask(IndexedDBDatabase::TaskType task_type, Operation* task) {
+ ScheduleTask(task_type, task, NULL);
+ }
+ void ScheduleTask(IndexedDBDatabase::TaskType task_type,
+ Operation* task,
+ Operation* abort_task);
+ void RegisterOpenCursor(IndexedDBCursor* cursor);
+ void UnregisterOpenCursor(IndexedDBCursor* cursor);
+ void AddPreemptiveEvent() { pending_preemptive_events_++; }
+ void DidCompletePreemptiveEvent() {
+ pending_preemptive_events_--;
+ DCHECK_GE(pending_preemptive_events_, 0);
+ }
+ IndexedDBBackingStore::Transaction* BackingStoreTransaction() {
+ return &transaction_;
+ }
+ int64 id() const { return id_; }
+
+ IndexedDBDatabase* database() const { return database_; }
+ IndexedDBDatabaseCallbacks* connection() const { return callbacks_; }
+ bool IsRunning() const { return state_ == RUNNING; }
+
+ protected:
+ virtual ~IndexedDBTransaction();
+ friend class base::RefCounted<IndexedDBTransaction>;
+
+ private:
+ enum State {
+ UNUSED, // Created, but no tasks yet.
+ START_PENDING, // Enqueued tasks, but backing store transaction not yet
+ // started.
+ RUNNING, // Backing store transaction started but not yet finished.
+ FINISHED, // Either aborted or committed.
+ };
+
+ void Start();
+
+ bool IsTaskQueueEmpty() const;
+ bool HasPendingTasks() const;
+
+ void ProcessTaskQueue();
+ void CloseOpenCursors();
+
+ const int64 id_;
+ const std::set<int64> object_store_ids_;
+ const indexed_db::TransactionMode mode_;
+
+ State state_;
+ bool commit_pending_;
+ scoped_refptr<IndexedDBDatabaseCallbacks> callbacks_;
+ scoped_refptr<IndexedDBDatabase> database_;
+
+ class TaskQueue {
+ public:
+ TaskQueue();
+ ~TaskQueue();
+ bool empty() const { return queue_.empty(); }
+ void push(Operation* task) { queue_.push(task); }
+ scoped_ptr<Operation> pop();
+ void clear();
+
+ private:
+ std::queue<Operation*> queue_;
+ };
+
+ class TaskStack {
+ public:
+ TaskStack();
+ ~TaskStack();
+ bool empty() const { return stack_.empty(); }
+ void push(Operation* task) { stack_.push(task); }
+ scoped_ptr<Operation> pop();
+ void clear();
+
+ private:
+ std::stack<Operation*> stack_;
+ };
+
+ TaskQueue task_queue_;
+ TaskQueue preemptive_task_queue_;
+ TaskStack abort_task_stack_;
+
+ IndexedDBBackingStore::Transaction transaction_;
+
+ bool should_process_queue_;
+ int pending_preemptive_events_;
+
+ std::set<IndexedDBCursor*> open_cursors_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.cc b/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.cc
new file mode 100644
index 00000000000..1f5100d26ac
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 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 "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+
+namespace content {
+
+IndexedDBTransactionCoordinator::IndexedDBTransactionCoordinator() {}
+
+IndexedDBTransactionCoordinator::~IndexedDBTransactionCoordinator() {
+ DCHECK(!transactions_.size());
+ DCHECK(!queued_transactions_.size());
+ DCHECK(!started_transactions_.size());
+}
+
+void IndexedDBTransactionCoordinator::DidCreateTransaction(
+ IndexedDBTransaction* transaction) {
+ DCHECK(transactions_.find(transaction) == transactions_.end());
+ transactions_[transaction] = transaction;
+}
+
+void IndexedDBTransactionCoordinator::DidStartTransaction(
+ IndexedDBTransaction* transaction) {
+ DCHECK(transactions_.find(transaction) != transactions_.end());
+
+ queued_transactions_.insert(transaction);
+ ProcessStartedTransactions();
+}
+
+void IndexedDBTransactionCoordinator::DidFinishTransaction(
+ IndexedDBTransaction* transaction) {
+ DCHECK(transactions_.find(transaction) != transactions_.end());
+
+ if (queued_transactions_.has(transaction)) {
+ DCHECK(!started_transactions_.has(transaction));
+ queued_transactions_.erase(transaction);
+ } else {
+ if (started_transactions_.has(transaction))
+ started_transactions_.erase(transaction);
+ }
+ transactions_.erase(transaction);
+
+ ProcessStartedTransactions();
+}
+
+#ifndef NDEBUG
+// Verifies internal consistency while returning whether anything is found.
+bool IndexedDBTransactionCoordinator::IsActive(
+ IndexedDBTransaction* transaction) {
+ bool found = false;
+ if (queued_transactions_.has(transaction))
+ found = true;
+ if (started_transactions_.has(transaction)) {
+ DCHECK(!found);
+ found = true;
+ }
+ DCHECK_EQ(found, (transactions_.find(transaction) != transactions_.end()));
+ return found;
+}
+#endif
+
+std::vector<const IndexedDBTransaction*>
+IndexedDBTransactionCoordinator::GetTransactions() const {
+ std::vector<const IndexedDBTransaction*> result;
+
+ for (list_set<IndexedDBTransaction*>::const_iterator it =
+ started_transactions_.begin();
+ it != started_transactions_.end();
+ ++it) {
+ result.push_back(*it);
+ }
+ for (list_set<IndexedDBTransaction*>::const_iterator it =
+ queued_transactions_.begin();
+ it != queued_transactions_.end();
+ ++it) {
+ result.push_back(*it);
+ }
+
+ return result;
+}
+
+void IndexedDBTransactionCoordinator::ProcessStartedTransactions() {
+ if (queued_transactions_.empty())
+ return;
+
+ DCHECK(started_transactions_.empty() ||
+ (*started_transactions_.begin())->mode() !=
+ indexed_db::TRANSACTION_VERSION_CHANGE);
+
+ list_set<IndexedDBTransaction*>::const_iterator it =
+ queued_transactions_.begin();
+ while (it != queued_transactions_.end()) {
+ IndexedDBTransaction* transaction = *it;
+ ++it;
+ if (CanRunTransaction(transaction)) {
+ queued_transactions_.erase(transaction);
+ started_transactions_.insert(transaction);
+ transaction->Run();
+ }
+ }
+}
+
+static bool DoScopesOverlap(const std::set<int64>& scope1,
+ const std::set<int64>& scope2) {
+ for (std::set<int64>::const_iterator it = scope1.begin(); it != scope1.end();
+ ++it) {
+ if (scope2.find(*it) != scope2.end())
+ return true;
+ }
+ return false;
+}
+
+bool IndexedDBTransactionCoordinator::CanRunTransaction(
+ IndexedDBTransaction* transaction) {
+ DCHECK(queued_transactions_.has(transaction));
+ switch (transaction->mode()) {
+ case indexed_db::TRANSACTION_VERSION_CHANGE:
+ DCHECK_EQ(static_cast<size_t>(1), queued_transactions_.size());
+ DCHECK(started_transactions_.empty());
+ return true;
+
+ case indexed_db::TRANSACTION_READ_ONLY:
+ return true;
+
+ case indexed_db::TRANSACTION_READ_WRITE:
+ for (list_set<IndexedDBTransaction*>::const_iterator it =
+ started_transactions_.begin();
+ it != started_transactions_.end();
+ ++it) {
+ IndexedDBTransaction* other = *it;
+ if (other->mode() == indexed_db::TRANSACTION_READ_WRITE &&
+ DoScopesOverlap(transaction->scope(), other->scope()))
+ return false;
+ }
+ for (list_set<IndexedDBTransaction*>::const_iterator it =
+ queued_transactions_.begin();
+ *it != transaction;
+ ++it) {
+ DCHECK(it != queued_transactions_.end());
+ IndexedDBTransaction* other = *it;
+ if (other->mode() == indexed_db::TRANSACTION_READ_WRITE &&
+ DoScopesOverlap(transaction->scope(), other->scope()))
+ return false;
+ }
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.h b/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.h
new file mode 100644
index 00000000000..d9f9cd53266
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_transaction_coordinator.h
@@ -0,0 +1,55 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_
+#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/indexed_db/list_set.h"
+
+namespace content {
+
+class IndexedDBTransaction;
+
+// Transactions are executed in the order the were created.
+class IndexedDBTransactionCoordinator {
+ public:
+ IndexedDBTransactionCoordinator();
+ ~IndexedDBTransactionCoordinator();
+
+ // Called by transactions as they start and finish.
+ void DidCreateTransaction(IndexedDBTransaction* transaction);
+ void DidStartTransaction(IndexedDBTransaction* transaction);
+ void DidFinishTransaction(IndexedDBTransaction* transaction);
+
+#ifndef NDEBUG
+ bool IsActive(IndexedDBTransaction* transaction);
+#endif
+
+ // Makes a snapshot of the transaction queue. For diagnostics only.
+ std::vector<const IndexedDBTransaction*> GetTransactions() const;
+
+ private:
+ void ProcessStartedTransactions();
+ bool CanRunTransaction(IndexedDBTransaction* transaction);
+
+ // This is just an efficient way to keep references to all transactions.
+ std::map<IndexedDBTransaction*, scoped_refptr<IndexedDBTransaction> >
+ transactions_;
+
+ // Transactions in different states are grouped below.
+ // list_set is used to provide stable ordering; required by spec
+ // for the queue, convenience for diagnostics for the rest.
+ list_set<IndexedDBTransaction*> queued_transactions_;
+ list_set<IndexedDBTransaction*> started_transactions_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_TRANSACTION_COORDINATOR_H_
diff --git a/chromium/content/browser/indexed_db/indexed_db_unittest.cc b/chromium/content/browser/indexed_db/indexed_db_unittest.cc
new file mode 100644
index 00000000000..c9aec2915f2
--- /dev/null
+++ b/chromium/content/browser/indexed_db/indexed_db_unittest.cc
@@ -0,0 +1,193 @@
+// 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+#include "webkit/common/database/database_identifier.h"
+
+namespace content {
+
+class IndexedDBTest : public testing::Test {
+ public:
+ const GURL kNormalOrigin;
+ const GURL kSessionOnlyOrigin;
+
+ IndexedDBTest()
+ : kNormalOrigin("http://normal/"),
+ kSessionOnlyOrigin("http://session-only/"),
+ message_loop_(base::MessageLoop::TYPE_IO),
+ task_runner_(new base::TestSimpleTaskRunner),
+ special_storage_policy_(new quota::MockSpecialStoragePolicy),
+ file_thread_(BrowserThread::FILE_USER_BLOCKING, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_) {
+ special_storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
+ }
+
+ protected:
+ void FlushIndexedDBTaskRunner() { task_runner_->RunUntilIdle(); }
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy_;
+
+ private:
+ BrowserThreadImpl file_thread_;
+ BrowserThreadImpl io_thread_;
+};
+
+TEST_F(IndexedDBTest, ClearSessionOnlyDatabases) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::FilePath normal_path;
+ base::FilePath session_only_path;
+
+ // Create the scope which will ensure we run the destructor of the context
+ // which should trigger the clean up.
+ {
+ scoped_refptr<IndexedDBContextImpl> idb_context = new IndexedDBContextImpl(
+ temp_dir.path(), special_storage_policy_, NULL, task_runner_);
+
+ normal_path = idb_context->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(kNormalOrigin));
+ session_only_path = idb_context->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(kSessionOnlyOrigin));
+ ASSERT_TRUE(file_util::CreateDirectory(normal_path));
+ ASSERT_TRUE(file_util::CreateDirectory(session_only_path));
+ FlushIndexedDBTaskRunner();
+ message_loop_.RunUntilIdle();
+ }
+
+ FlushIndexedDBTaskRunner();
+ message_loop_.RunUntilIdle();
+
+ EXPECT_TRUE(base::DirectoryExists(normal_path));
+ EXPECT_FALSE(base::DirectoryExists(session_only_path));
+}
+
+TEST_F(IndexedDBTest, SetForceKeepSessionState) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::FilePath normal_path;
+ base::FilePath session_only_path;
+
+ // Create the scope which will ensure we run the destructor of the context.
+ {
+ // Create some indexedDB paths.
+ // With the levelDB backend, these are directories.
+ scoped_refptr<IndexedDBContextImpl> idb_context = new IndexedDBContextImpl(
+ temp_dir.path(), special_storage_policy_, NULL, task_runner_);
+
+ // Save session state. This should bypass the destruction-time deletion.
+ idb_context->SetForceKeepSessionState();
+
+ normal_path = idb_context->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(kNormalOrigin));
+ session_only_path = idb_context->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(kSessionOnlyOrigin));
+ ASSERT_TRUE(file_util::CreateDirectory(normal_path));
+ ASSERT_TRUE(file_util::CreateDirectory(session_only_path));
+ message_loop_.RunUntilIdle();
+ }
+
+ // Make sure we wait until the destructor has run.
+ message_loop_.RunUntilIdle();
+
+ // No data was cleared because of SetForceKeepSessionState.
+ EXPECT_TRUE(base::DirectoryExists(normal_path));
+ EXPECT_TRUE(base::DirectoryExists(session_only_path));
+}
+
+class MockConnection : public IndexedDBConnection {
+ public:
+ explicit MockConnection(bool expect_force_close)
+ : IndexedDBConnection(NULL, NULL),
+ expect_force_close_(expect_force_close),
+ force_close_called_(false) {}
+
+ virtual ~MockConnection() {
+ EXPECT_TRUE(force_close_called_ == expect_force_close_);
+ }
+
+ virtual void ForceClose() OVERRIDE {
+ ASSERT_TRUE(expect_force_close_);
+ force_close_called_ = true;
+ }
+
+ private:
+ bool expect_force_close_;
+ bool force_close_called_;
+};
+
+TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnDelete) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::FilePath test_path;
+
+ // Create the scope which will ensure we run the destructor of the context.
+ {
+ TestBrowserContext browser_context;
+
+ const GURL kTestOrigin("http://test/");
+
+ scoped_refptr<IndexedDBContextImpl> idb_context = new IndexedDBContextImpl(
+ temp_dir.path(), special_storage_policy_, NULL, task_runner_);
+
+ test_path = idb_context->GetFilePathForTesting(
+ webkit_database::GetIdentifierFromOrigin(kTestOrigin));
+ ASSERT_TRUE(file_util::CreateDirectory(test_path));
+
+ const bool kExpectForceClose = true;
+
+ MockConnection connection1(kExpectForceClose);
+ idb_context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBContextImpl::ConnectionOpened,
+ idb_context,
+ kTestOrigin,
+ &connection1));
+
+ MockConnection connection2(!kExpectForceClose);
+ idb_context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBContextImpl::ConnectionOpened,
+ idb_context,
+ kTestOrigin,
+ &connection2));
+ idb_context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&IndexedDBContextImpl::ConnectionClosed,
+ idb_context,
+ kTestOrigin,
+ &connection2));
+
+ idb_context->TaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &IndexedDBContextImpl::DeleteForOrigin, idb_context, kTestOrigin));
+ FlushIndexedDBTaskRunner();
+ message_loop_.RunUntilIdle();
+ }
+
+ // Make sure we wait until the destructor has run.
+ message_loop_.RunUntilIdle();
+
+ EXPECT_FALSE(base::DirectoryExists(test_path));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/leveldb/avltree.h b/chromium/content/browser/indexed_db/leveldb/avltree.h
new file mode 100644
index 00000000000..91df04868c7
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/avltree.h
@@ -0,0 +1,977 @@
+// Copyright (c) 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.
+
+/*
+ * Copyright (C) 2008 Apple Inc. All rights reserved.
+ *
+ * Based on Abstract AVL Tree Template v1.5 by Walt Karas
+ * <http://geocities.com/wkaras/gen_cpp/avl_tree.html>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_
+
+#include "base/logging.h"
+#include "content/browser/indexed_db/leveldb/fixed_array.h"
+
+namespace content {
+
+// Here is the reference class for BSet.
+//
+// class BSet
+// {
+// public:
+//
+// class ANY_bitref
+// {
+// public:
+// operator bool ();
+// void operator = (bool b);
+// };
+//
+// // Does not have to initialize bits.
+// BSet();
+//
+// // Must return a valid value for index when 0 <= index < maxDepth
+// ANY_bitref operator [] (unsigned index);
+//
+// // Set all bits to 1.
+// void Set();
+//
+// // Set all bits to 0.
+// void Reset();
+// };
+
+template <unsigned maxDepth>
+class AVLTreeDefaultBSet {
+ public:
+ bool& operator[](unsigned i) {
+#if defined(ADDRESS_SANITIZER)
+ CHECK(i < maxDepth);
+#endif
+ return data_[i];
+ }
+ void Set() {
+ for (unsigned i = 0; i < maxDepth; ++i)
+ data_[i] = true;
+ }
+ void Reset() {
+ for (unsigned i = 0; i < maxDepth; ++i)
+ data_[i] = false;
+ }
+
+ private:
+ FixedArray<bool, maxDepth> data_;
+};
+
+// How to determine maxDepth:
+// d Minimum number of nodes
+// 2 2
+// 3 4
+// 4 7
+// 5 12
+// 6 20
+// 7 33
+// 8 54
+// 9 88
+// 10 143
+// 11 232
+// 12 376
+// 13 609
+// 14 986
+// 15 1,596
+// 16 2,583
+// 17 4,180
+// 18 6,764
+// 19 10,945
+// 20 17,710
+// 21 28,656
+// 22 46,367
+// 23 75,024
+// 24 121,392
+// 25 196,417
+// 26 317,810
+// 27 514,228
+// 28 832,039
+// 29 1,346,268
+// 30 2,178,308
+// 31 3,524,577
+// 32 5,702,886
+// 33 9,227,464
+// 34 14,930,351
+// 35 24,157,816
+// 36 39,088,168
+// 37 63,245,985
+// 38 102,334,154
+// 39 165,580,140
+// 40 267,914,295
+// 41 433,494,436
+// 42 701,408,732
+// 43 1,134,903,169
+// 44 1,836,311,902
+// 45 2,971,215,072
+//
+// E.g., if, in a particular instantiation, the maximum number of nodes in a
+// tree instance is 1,000,000, the maximum depth should be 28.
+// You pick 28 because MN(28) is 832,039, which is less than or equal to
+// 1,000,000, and MN(29) is 1,346,268, which is strictly greater than 1,000,000.
+
+template <class Abstractor,
+ unsigned maxDepth = 32,
+ class BSet = AVLTreeDefaultBSet<maxDepth> >
+class AVLTree {
+ public:
+ typedef typename Abstractor::key key;
+ typedef typename Abstractor::handle handle;
+ typedef typename Abstractor::size size;
+
+ enum SearchType {
+ EQUAL = 1,
+ LESS = 2,
+ GREATER = 4,
+ LESS_EQUAL = EQUAL | LESS,
+ GREATER_EQUAL = EQUAL | GREATER
+ };
+
+ Abstractor& abstractor() { return abs_; }
+
+ inline handle Insert(handle h);
+
+ inline handle Search(key k, SearchType st = EQUAL);
+ inline handle SearchLeast();
+ inline handle SearchGreatest();
+
+ inline handle Remove(key k);
+
+ inline handle Subst(handle new_node);
+
+ void Purge() { abs_.root = Null(); }
+
+ bool IsEmpty() { return abs_.root == Null(); }
+
+ AVLTree() { abs_.root = Null(); }
+
+ class Iterator {
+ public:
+ // Initialize depth to invalid value, to indicate iterator is
+ // invalid. (Depth is zero-base.)
+ Iterator() { depth_ = ~0U; }
+
+ void StartIter(AVLTree* tree, key k, SearchType st = EQUAL) {
+ // Mask of high bit in an int.
+ const int kMaskHighBit = static_cast<int>(~((~(unsigned)0) >> 1));
+
+ // Save the tree that we're going to iterate through in a
+ // member variable.
+ tree_ = tree;
+
+ int cmp, target_cmp;
+ handle h = tree_->abs_.root;
+ unsigned d = 0;
+
+ depth_ = ~0U;
+
+ if (h == Null()) {
+ // Tree is empty.
+ return;
+ }
+
+ if (st & LESS) {
+ // Key can be greater than key of starting node.
+ target_cmp = 1;
+ } else if (st & GREATER) {
+ // Key can be less than key of starting node.
+ target_cmp = -1;
+ } else {
+ // Key must be same as key of starting node.
+ target_cmp = 0;
+ }
+
+ for (;;) {
+ cmp = CmpKN(k, h);
+ if (cmp == 0) {
+ if (st & EQUAL) {
+ // Equal node was sought and found as starting node.
+ depth_ = d;
+ break;
+ }
+ cmp = -target_cmp;
+ } else if (target_cmp != 0) {
+ if (!((cmp ^ target_cmp) & kMaskHighBit)) {
+ // cmp and target_cmp are both negative or both positive.
+ depth_ = d;
+ }
+ }
+ h = cmp < 0 ? GetLT(h) : GetGT(h);
+ if (h == Null())
+ break;
+ branch_[d] = cmp > 0;
+ path_h_[d++] = h;
+ }
+ }
+
+ void StartIterLeast(AVLTree* tree) {
+ tree_ = tree;
+
+ handle h = tree_->abs_.root;
+
+ depth_ = ~0U;
+
+ branch_.Reset();
+
+ while (h != Null()) {
+ if (depth_ != ~0U)
+ path_h_[depth_] = h;
+ depth_++;
+ h = GetLT(h);
+ }
+ }
+
+ void StartIterGreatest(AVLTree* tree) {
+ tree_ = tree;
+
+ handle h = tree_->abs_.root;
+
+ depth_ = ~0U;
+
+ branch_.Set();
+
+ while (h != Null()) {
+ if (depth_ != ~0U)
+ path_h_[depth_] = h;
+ depth_++;
+ h = GetGT(h);
+ }
+ }
+
+ handle operator*() {
+ if (depth_ == ~0U)
+ return Null();
+
+ return depth_ == 0 ? tree_->abs_.root : path_h_[depth_ - 1];
+ }
+
+ void operator++() {
+ if (depth_ != ~0U) {
+ handle h = GetGT(**this);
+ if (h == Null()) {
+ do {
+ if (depth_ == 0) {
+ depth_ = ~0U;
+ break;
+ }
+ depth_--;
+ } while (branch_[depth_]);
+ } else {
+ branch_[depth_] = true;
+ path_h_[depth_++] = h;
+ for (;;) {
+ h = GetLT(h);
+ if (h == Null())
+ break;
+ branch_[depth_] = false;
+ path_h_[depth_++] = h;
+ }
+ }
+ }
+ }
+
+ void operator--() {
+ if (depth_ != ~0U) {
+ handle h = GetLT(**this);
+ if (h == Null()) {
+ do {
+ if (depth_ == 0) {
+ depth_ = ~0U;
+ break;
+ }
+ depth_--;
+ } while (!branch_[depth_]);
+ } else {
+ branch_[depth_] = false;
+ path_h_[depth_++] = h;
+ for (;;) {
+ h = GetGT(h);
+ if (h == Null())
+ break;
+ branch_[depth_] = true;
+ path_h_[depth_++] = h;
+ }
+ }
+ }
+ }
+
+ void operator++(int /*ignored*/) { ++(*this); }
+ void operator--(int /*ignored*/) { --(*this); }
+
+ protected:
+ // Tree being iterated over.
+ AVLTree* tree_;
+
+ // Records a path into the tree. If branch_[n] is true, indicates
+ // take greater branch from the nth node in the path, otherwise
+ // take the less branch. branch_[0] gives branch from root, and
+ // so on.
+ BSet branch_;
+
+ // Zero-based depth of path into tree.
+ unsigned depth_;
+
+ // Handles of nodes in path from root to current node (returned by *).
+ static const size_t kPathSize = maxDepth - 1;
+ handle path_h_[kPathSize];
+
+ int CmpKN(key k, handle h) { return tree_->abs_.CompareKeyNode(k, h); }
+ int CmpNN(handle h1, handle h2) {
+ return tree_->abs_.CompareNodeNode(h1, h2);
+ }
+ handle GetLT(handle h) { return tree_->abs_.GetLess(h); }
+ handle GetGT(handle h) { return tree_->abs_.GetGreater(h); }
+ handle Null() { return tree_->abs_.Null(); }
+ };
+
+ template <typename fwd_iter>
+ bool Build(fwd_iter p, size num_nodes) {
+ if (num_nodes == 0) {
+ abs_.root = Null();
+ return true;
+ }
+
+ // Gives path to subtree being built. If branch[N] is false, branch
+ // less from the node at depth N, if true branch greater.
+ BSet branch;
+
+ // If rem[N] is true, then for the current subtree at depth N, it's
+ // greater subtree has one more node than it's less subtree.
+ BSet rem;
+
+ // Depth of root node of current subtree.
+ unsigned depth = 0;
+
+ // Number of nodes in current subtree.
+ size num_sub = num_nodes;
+
+ // The algorithm relies on a stack of nodes whose less subtree has
+ // been built, but whose right subtree has not yet been built. The
+ // stack is implemented as linked list. The nodes are linked
+ // together by having the "greater" handle of a node set to the
+ // next node in the list. "less_parent" is the handle of the first
+ // node in the list.
+ handle less_parent = Null();
+
+ // h is root of current subtree, child is one of its children.
+ handle h, child;
+
+ for (;;) {
+ while (num_sub > 2) {
+ // Subtract one for root of subtree.
+ num_sub--;
+ rem[depth] = !!(num_sub & 1);
+ branch[depth++] = false;
+ num_sub >>= 1;
+ }
+
+ if (num_sub == 2) {
+ // Build a subtree with two nodes, slanting to greater.
+ // I arbitrarily chose to always have the extra node in the
+ // greater subtree when there is an odd number of nodes to
+ // split between the two subtrees.
+
+ h = *p;
+ p++;
+ child = *p;
+ p++;
+ SetLT(child, Null());
+ SetGT(child, Null());
+ SetBF(child, 0);
+ SetGT(h, child);
+ SetLT(h, Null());
+ SetBF(h, 1);
+ } else { // num_sub == 1
+ // Build a subtree with one node.
+
+ h = *p;
+ p++;
+ SetLT(h, Null());
+ SetGT(h, Null());
+ SetBF(h, 0);
+ }
+
+ while (depth) {
+ depth--;
+ if (!branch[depth]) {
+ // We've completed a less subtree.
+ break;
+ }
+
+ // We've completed a greater subtree, so attach it to
+ // its parent (that is less than it). We pop the parent
+ // off the stack of less parents.
+ child = h;
+ h = less_parent;
+ less_parent = GetGT(h);
+ SetGT(h, child);
+ // num_sub = 2 * (num_sub - rem[depth]) + rem[depth] + 1
+ num_sub <<= 1;
+ num_sub += 1 - rem[depth];
+ if (num_sub & (num_sub - 1)) {
+ // num_sub is not a power of 2
+ SetBF(h, 0);
+ } else {
+ // num_sub is a power of 2
+ SetBF(h, 1);
+ }
+ }
+
+ if (num_sub == num_nodes) {
+ // We've completed the full tree.
+ break;
+ }
+
+ // The subtree we've completed is the less subtree of the
+ // next node in the sequence.
+
+ child = h;
+ h = *p;
+ p++;
+ SetLT(h, child);
+
+ // Put h into stack of less parents.
+ SetGT(h, less_parent);
+ less_parent = h;
+
+ // Proceed to creating greater than subtree of h.
+ branch[depth] = true;
+ num_sub += rem[depth++];
+ } // end for (;;)
+
+ abs_.root = h;
+
+ return true;
+ }
+
+ protected:
+ friend class Iterator;
+
+ // Create a class whose sole purpose is to take advantage of
+ // the "empty member" optimization.
+ struct abs_plus_root : public Abstractor {
+ // The handle of the root element in the AVL tree.
+ handle root;
+ };
+
+ abs_plus_root abs_;
+
+ handle GetLT(handle h) { return abs_.GetLess(h); }
+ void SetLT(handle h, handle lh) { abs_.SetLess(h, lh); }
+
+ handle GetGT(handle h) { return abs_.GetGreater(h); }
+ void SetGT(handle h, handle gh) { abs_.SetGreater(h, gh); }
+
+ int GetBF(handle h) { return abs_.GetBalanceFactor(h); }
+ void SetBF(handle h, int bf) { abs_.SetBalanceFactor(h, bf); }
+
+ int CmpKN(key k, handle h) { return abs_.CompareKeyNode(k, h); }
+ int CmpNN(handle h1, handle h2) { return abs_.CompareNodeNode(h1, h2); }
+
+ handle Null() { return abs_.Null(); }
+
+ private:
+ // Balances subtree, returns handle of root node of subtree
+ // after balancing.
+ handle Balance(handle bal_h) {
+ handle deep_h;
+
+ // Either the "greater than" or the "less than" subtree of
+ // this node has to be 2 levels deeper (or else it wouldn't
+ // need balancing).
+
+ if (GetBF(bal_h) > 0) {
+ // "Greater than" subtree is deeper.
+
+ deep_h = GetGT(bal_h);
+
+ if (GetBF(deep_h) < 0) {
+ handle old_h = bal_h;
+ bal_h = GetLT(deep_h);
+
+ SetGT(old_h, GetLT(bal_h));
+ SetLT(deep_h, GetGT(bal_h));
+ SetLT(bal_h, old_h);
+ SetGT(bal_h, deep_h);
+
+ int bf = GetBF(bal_h);
+ if (bf != 0) {
+ if (bf > 0) {
+ SetBF(old_h, -1);
+ SetBF(deep_h, 0);
+ } else {
+ SetBF(deep_h, 1);
+ SetBF(old_h, 0);
+ }
+ SetBF(bal_h, 0);
+ } else {
+ SetBF(old_h, 0);
+ SetBF(deep_h, 0);
+ }
+ } else {
+ SetGT(bal_h, GetLT(deep_h));
+ SetLT(deep_h, bal_h);
+ if (GetBF(deep_h) == 0) {
+ SetBF(deep_h, -1);
+ SetBF(bal_h, 1);
+ } else {
+ SetBF(deep_h, 0);
+ SetBF(bal_h, 0);
+ }
+ bal_h = deep_h;
+ }
+ } else {
+ // "Less than" subtree is deeper.
+
+ deep_h = GetLT(bal_h);
+
+ if (GetBF(deep_h) > 0) {
+ handle old_h = bal_h;
+ bal_h = GetGT(deep_h);
+ SetLT(old_h, GetGT(bal_h));
+ SetGT(deep_h, GetLT(bal_h));
+ SetGT(bal_h, old_h);
+ SetLT(bal_h, deep_h);
+
+ int bf = GetBF(bal_h);
+ if (bf != 0) {
+ if (bf < 0) {
+ SetBF(old_h, 1);
+ SetBF(deep_h, 0);
+ } else {
+ SetBF(deep_h, -1);
+ SetBF(old_h, 0);
+ }
+ SetBF(bal_h, 0);
+ } else {
+ SetBF(old_h, 0);
+ SetBF(deep_h, 0);
+ }
+ } else {
+ SetLT(bal_h, GetGT(deep_h));
+ SetGT(deep_h, bal_h);
+ if (GetBF(deep_h) == 0) {
+ SetBF(deep_h, 1);
+ SetBF(bal_h, -1);
+ } else {
+ SetBF(deep_h, 0);
+ SetBF(bal_h, 0);
+ }
+ bal_h = deep_h;
+ }
+ }
+
+ return bal_h;
+ }
+};
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::Insert(handle h) {
+ SetLT(h, Null());
+ SetGT(h, Null());
+ SetBF(h, 0);
+
+ if (abs_.root == Null()) {
+ abs_.root = h;
+ } else {
+ // Last unbalanced node encountered in search for insertion point.
+ handle unbal = Null();
+ // Parent of last unbalanced node.
+ handle parent_unbal = Null();
+ // Balance factor of last unbalanced node.
+ int unbal_bf;
+
+ // Zero-based depth in tree.
+ unsigned depth = 0, unbal_depth = 0;
+
+ // Records a path into the tree. If branch[n] is true, indicates
+ // take greater branch from the nth node in the path, otherwise
+ // take the less branch. branch[0] gives branch from root, and
+ // so on.
+ BSet branch;
+
+ handle hh = abs_.root;
+ handle parent = Null();
+ int cmp;
+
+ do {
+ if (GetBF(hh) != 0) {
+ unbal = hh;
+ parent_unbal = parent;
+ unbal_depth = depth;
+ }
+ cmp = CmpNN(h, hh);
+ if (cmp == 0) {
+ // Duplicate key.
+ return hh;
+ }
+ parent = hh;
+ hh = cmp < 0 ? GetLT(hh) : GetGT(hh);
+ branch[depth++] = cmp > 0;
+ } while (hh != Null());
+
+ // Add node to insert as leaf of tree.
+ if (cmp < 0)
+ SetLT(parent, h);
+ else
+ SetGT(parent, h);
+
+ depth = unbal_depth;
+
+ if (unbal == Null()) {
+ hh = abs_.root;
+ } else {
+ cmp = branch[depth++] ? 1 : -1;
+ unbal_bf = GetBF(unbal);
+ if (cmp < 0)
+ unbal_bf--;
+ else // cmp > 0
+ unbal_bf++;
+ hh = cmp < 0 ? GetLT(unbal) : GetGT(unbal);
+ if ((unbal_bf != -2) && (unbal_bf != 2)) {
+ // No rebalancing of tree is necessary.
+ SetBF(unbal, unbal_bf);
+ unbal = Null();
+ }
+ }
+
+ if (hh != Null()) {
+ while (h != hh) {
+ cmp = branch[depth++] ? 1 : -1;
+ if (cmp < 0) {
+ SetBF(hh, -1);
+ hh = GetLT(hh);
+ } else { // cmp > 0
+ SetBF(hh, 1);
+ hh = GetGT(hh);
+ }
+ }
+ }
+
+ if (unbal != Null()) {
+ unbal = Balance(unbal);
+ if (parent_unbal == Null()) {
+ abs_.root = unbal;
+ } else {
+ depth = unbal_depth - 1;
+ cmp = branch[depth] ? 1 : -1;
+ if (cmp < 0)
+ SetLT(parent_unbal, unbal);
+ else // cmp > 0
+ SetGT(parent_unbal, unbal);
+ }
+ }
+ }
+
+ return h;
+}
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::Search(
+ key k,
+ typename AVLTree<Abstractor, maxDepth, BSet>::SearchType st) {
+ const int kMaskHighBit = static_cast<int>(~((~(unsigned)0) >> 1));
+
+ int cmp, target_cmp;
+ handle match_h = Null();
+ handle h = abs_.root;
+
+ if (st & LESS)
+ target_cmp = 1;
+ else if (st & GREATER)
+ target_cmp = -1;
+ else
+ target_cmp = 0;
+
+ while (h != Null()) {
+ cmp = CmpKN(k, h);
+ if (cmp == 0) {
+ if (st & EQUAL) {
+ match_h = h;
+ break;
+ }
+ cmp = -target_cmp;
+ } else if (target_cmp != 0) {
+ if (!((cmp ^ target_cmp) & kMaskHighBit)) {
+ // cmp and target_cmp are both positive or both negative.
+ match_h = h;
+ }
+ }
+ h = cmp < 0 ? GetLT(h) : GetGT(h);
+ }
+
+ return match_h;
+}
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::SearchLeast() {
+ handle h = abs_.root, parent = Null();
+
+ while (h != Null()) {
+ parent = h;
+ h = GetLT(h);
+ }
+
+ return parent;
+}
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::SearchGreatest() {
+ handle h = abs_.root, parent = Null();
+
+ while (h != Null()) {
+ parent = h;
+ h = GetGT(h);
+ }
+
+ return parent;
+}
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::Remove(key k) {
+ // Zero-based depth in tree.
+ unsigned depth = 0, rm_depth;
+
+ // Records a path into the tree. If branch[n] is true, indicates
+ // take greater branch from the nth node in the path, otherwise
+ // take the less branch. branch[0] gives branch from root, and
+ // so on.
+ BSet branch;
+
+ handle h = abs_.root;
+ handle parent = Null(), child;
+ int cmp, cmp_shortened_sub_with_path = 0;
+
+ for (;;) {
+ if (h == Null()) {
+ // No node in tree with given key.
+ return Null();
+ }
+ cmp = CmpKN(k, h);
+ if (cmp == 0) {
+ // Found node to remove.
+ break;
+ }
+ parent = h;
+ h = cmp < 0 ? GetLT(h) : GetGT(h);
+ branch[depth++] = cmp > 0;
+ cmp_shortened_sub_with_path = cmp;
+ }
+ handle rm = h;
+ handle parent_rm = parent;
+ rm_depth = depth;
+
+ // If the node to remove is not a leaf node, we need to get a
+ // leaf node, or a node with a single leaf as its child, to put
+ // in the place of the node to remove. We will get the greatest
+ // node in the less subtree (of the node to remove), or the least
+ // node in the greater subtree. We take the leaf node from the
+ // deeper subtree, if there is one.
+
+ if (GetBF(h) < 0) {
+ child = GetLT(h);
+ branch[depth] = false;
+ cmp = -1;
+ } else {
+ child = GetGT(h);
+ branch[depth] = true;
+ cmp = 1;
+ }
+ depth++;
+
+ if (child != Null()) {
+ cmp = -cmp;
+ do {
+ parent = h;
+ h = child;
+ if (cmp < 0) {
+ child = GetLT(h);
+ branch[depth] = false;
+ } else {
+ child = GetGT(h);
+ branch[depth] = true;
+ }
+ depth++;
+ } while (child != Null());
+
+ if (parent == rm) {
+ // Only went through do loop once. Deleted node will be replaced
+ // in the tree structure by one of its immediate children.
+ cmp_shortened_sub_with_path = -cmp;
+ } else {
+ cmp_shortened_sub_with_path = cmp;
+ }
+
+ // Get the handle of the opposite child, which may not be null.
+ child = cmp > 0 ? GetLT(h) : GetGT(h);
+ }
+
+ if (parent == Null()) {
+ // There were only 1 or 2 nodes in this tree.
+ abs_.root = child;
+ } else if (cmp_shortened_sub_with_path < 0) {
+ SetLT(parent, child);
+ } else {
+ SetGT(parent, child);
+ }
+
+ // "path" is the parent of the subtree being eliminated or reduced
+ // from a depth of 2 to 1. If "path" is the node to be removed, we
+ // set path to the node we're about to poke into the position of the
+ // node to be removed.
+ handle path = parent == rm ? h : parent;
+
+ if (h != rm) {
+ // Poke in the replacement for the node to be removed.
+ SetLT(h, GetLT(rm));
+ SetGT(h, GetGT(rm));
+ SetBF(h, GetBF(rm));
+ if (parent_rm == Null()) {
+ abs_.root = h;
+ } else {
+ depth = rm_depth - 1;
+ if (branch[depth])
+ SetGT(parent_rm, h);
+ else
+ SetLT(parent_rm, h);
+ }
+ }
+
+ if (path != Null()) {
+ // Create a temporary linked list from the parent of the path node
+ // to the root node.
+ h = abs_.root;
+ parent = Null();
+ depth = 0;
+ while (h != path) {
+ if (branch[depth++]) {
+ child = GetGT(h);
+ SetGT(h, parent);
+ } else {
+ child = GetLT(h);
+ SetLT(h, parent);
+ }
+ parent = h;
+ h = child;
+ }
+
+ // Climb from the path node to the root node using the linked
+ // list, restoring the tree structure and rebalancing as necessary.
+ bool reduced_depth = true;
+ int bf;
+ cmp = cmp_shortened_sub_with_path;
+ for (;;) {
+ if (reduced_depth) {
+ bf = GetBF(h);
+ if (cmp < 0)
+ bf++;
+ else // cmp > 0
+ bf--;
+ if ((bf == -2) || (bf == 2)) {
+ h = Balance(h);
+ bf = GetBF(h);
+ } else {
+ SetBF(h, bf);
+ }
+ reduced_depth = (bf == 0);
+ }
+ if (parent == Null())
+ break;
+ child = h;
+ h = parent;
+ cmp = branch[--depth] ? 1 : -1;
+ if (cmp < 0) {
+ parent = GetLT(h);
+ SetLT(h, child);
+ } else {
+ parent = GetGT(h);
+ SetGT(h, child);
+ }
+ }
+ abs_.root = h;
+ }
+
+ return rm;
+}
+
+template <class Abstractor, unsigned maxDepth, class BSet>
+inline typename AVLTree<Abstractor, maxDepth, BSet>::handle
+AVLTree<Abstractor, maxDepth, BSet>::Subst(handle new_node) {
+ handle h = abs_.root;
+ handle parent = Null();
+ int cmp, last_cmp;
+
+ // Search for node already in tree with same key.
+ for (;;) {
+ if (h == Null()) {
+ // No node in tree with same key as new node.
+ return Null();
+ }
+ cmp = CmpNN(new_node, h);
+ if (cmp == 0) {
+ // Found the node to substitute new one for.
+ break;
+ }
+ last_cmp = cmp;
+ parent = h;
+ h = cmp < 0 ? GetLT(h) : GetGT(h);
+ }
+
+ // Copy tree housekeeping fields from node in tree to new node.
+ SetLT(new_node, GetLT(h));
+ SetGT(new_node, GetGT(h));
+ SetBF(new_node, GetBF(h));
+
+ if (parent == Null()) {
+ // New node is also new root.
+ abs_.root = new_node;
+ } else {
+ // Make parent point to new node.
+ if (last_cmp < 0)
+ SetLT(parent, new_node);
+ else
+ SetGT(parent, new_node);
+ }
+
+ return h;
+}
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_AVLTREE_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/fixed_array.h b/chromium/content/browser/indexed_db/leveldb/fixed_array.h
new file mode 100644
index 00000000000..cef43973fb0
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/fixed_array.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+/*
+ * Copyright (C) 2010 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_
+
+#include "base/logging.h"
+
+namespace content {
+
+template <typename T, size_t Size>
+class FixedArray {
+ public:
+ T& operator[](size_t i) {
+#if defined(ADDRESS_SANITIZER)
+ CHECK(i < Size);
+#endif
+ return data_[i];
+ }
+
+ const T& operator[](size_t i) const {
+#if defined(ADDRESS_SANITIZER)
+ CHECK(i < Size);
+#endif
+ return data_[i];
+ }
+
+ T* data() { return data_; }
+ size_t size() const { return Size; }
+
+ private:
+ T data_[Size];
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_FIXED_ARRAY_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_comparator.h b/chromium/content/browser/indexed_db/leveldb/leveldb_comparator.h
new file mode 100644
index 00000000000..2e8e3299916
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_comparator.h
@@ -0,0 +1,23 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_
+
+#include "base/strings/string_piece.h"
+
+namespace content {
+
+class LevelDBComparator {
+ public:
+ virtual ~LevelDBComparator() {}
+
+ virtual int Compare(const base::StringPiece& a,
+ const base::StringPiece& b) const = 0;
+ virtual const char* Name() const = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_database.cc b/chromium/content/browser/indexed_db/leveldb/leveldb_database.cc
new file mode 100644
index 00000000000..03d8ca6ccf0
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_database.cc
@@ -0,0 +1,470 @@
+// Copyright (c) 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 "content/browser/indexed_db/leveldb/leveldb_database.h"
+
+#include <cerrno>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/env_idb.h"
+#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/env.h"
+#include "third_party/leveldatabase/src/include/leveldb/slice.h"
+
+using base::StringPiece;
+
+namespace content {
+
+static leveldb::Slice MakeSlice(const StringPiece& s) {
+ return leveldb::Slice(s.begin(), s.size());
+}
+
+static StringPiece MakeStringPiece(const leveldb::Slice& s) {
+ return StringPiece(s.data(), s.size());
+}
+
+class ComparatorAdapter : public leveldb::Comparator {
+ public:
+ explicit ComparatorAdapter(const LevelDBComparator* comparator)
+ : comparator_(comparator) {}
+
+ virtual int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const
+ OVERRIDE {
+ return comparator_->Compare(MakeStringPiece(a), MakeStringPiece(b));
+ }
+
+ virtual const char* Name() const OVERRIDE { return comparator_->Name(); }
+
+ // TODO(jsbell): Support the methods below in the future.
+ virtual void FindShortestSeparator(std::string* start,
+ const leveldb::Slice& limit) const
+ OVERRIDE {}
+ virtual void FindShortSuccessor(std::string* key) const OVERRIDE {}
+
+ private:
+ const LevelDBComparator* comparator_;
+};
+
+LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
+ : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
+
+LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); }
+
+LevelDBDatabase::LevelDBDatabase() {}
+
+LevelDBDatabase::~LevelDBDatabase() {
+ // db_'s destructor uses comparator_adapter_; order of deletion is important.
+ db_.reset();
+ comparator_adapter_.reset();
+ env_.reset();
+}
+
+static leveldb::Status OpenDB(leveldb::Comparator* comparator,
+ leveldb::Env* env,
+ const base::FilePath& path,
+ leveldb::DB** db) {
+ leveldb::Options options;
+ options.comparator = comparator;
+ options.create_if_missing = true;
+ options.paranoid_checks = true;
+
+ // Marking compression as explicitly off so snappy support can be
+ // compiled in for other leveldb clients without implicitly enabling
+ // it for IndexedDB. http://crbug.com/81384
+ options.compression = leveldb::kNoCompression;
+
+ // For info about the troubles we've run into with this parameter, see:
+ // https://code.google.com/p/chromium/issues/detail?id=227313#c11
+ options.max_open_files = 80;
+ options.env = env;
+
+ // ChromiumEnv assumes UTF8, converts back to FilePath before using.
+ return leveldb::DB::Open(options, path.AsUTF8Unsafe(), db);
+}
+
+bool LevelDBDatabase::Destroy(const base::FilePath& file_name) {
+ leveldb::Options options;
+ options.env = leveldb::IDBEnv();
+ // ChromiumEnv assumes UTF8, converts back to FilePath before using.
+ const leveldb::Status s =
+ leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
+ return s.ok();
+}
+
+static int CheckFreeSpace(const char* const type,
+ const base::FilePath& file_name) {
+ std::string name =
+ std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
+ int64 free_disk_space_in_k_bytes =
+ base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
+ if (free_disk_space_in_k_bytes < 0) {
+ base::Histogram::FactoryGet(
+ "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
+ 1,
+ 2 /*boundary*/,
+ 2 /*boundary*/ + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/);
+ return -1;
+ }
+ int clamped_disk_space_k_bytes =
+ free_disk_space_in_k_bytes > INT_MAX ? INT_MAX
+ : free_disk_space_in_k_bytes;
+ const uint64 histogram_max = static_cast<uint64>(1e9);
+ COMPILE_ASSERT(histogram_max <= INT_MAX, histogram_max_too_big);
+ base::Histogram::FactoryGet(name,
+ 1,
+ histogram_max,
+ 11 /*buckets*/,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(clamped_disk_space_k_bytes);
+ return clamped_disk_space_k_bytes;
+}
+
+static void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
+ const leveldb::Status& s) {
+ leveldb_env::MethodID method;
+ int error = -1;
+ leveldb_env::ErrorParsingResult result =
+ leveldb_env::ParseMethodAndError(s.ToString().c_str(), &method, &error);
+ if (result == leveldb_env::NONE)
+ return;
+ std::string method_histogram_name(histogram_name);
+ method_histogram_name.append(".EnvMethod");
+ base::LinearHistogram::FactoryGet(
+ method_histogram_name,
+ 1,
+ leveldb_env::kNumEntries,
+ leveldb_env::kNumEntries + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method);
+
+ std::string error_histogram_name(histogram_name);
+
+ if (result == leveldb_env::METHOD_AND_PFE) {
+ DCHECK(error < 0);
+ error_histogram_name.append(std::string(".PFE.") +
+ leveldb_env::MethodIDToString(method));
+ base::LinearHistogram::FactoryGet(
+ error_histogram_name,
+ 1,
+ -base::PLATFORM_FILE_ERROR_MAX,
+ -base::PLATFORM_FILE_ERROR_MAX + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error);
+ } else if (result == leveldb_env::METHOD_AND_ERRNO) {
+ error_histogram_name.append(std::string(".Errno.") +
+ leveldb_env::MethodIDToString(method));
+ base::LinearHistogram::FactoryGet(
+ error_histogram_name,
+ 1,
+ ERANGE + 1,
+ ERANGE + 2,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
+ }
+}
+
+static void ParseAndHistogramCorruptionDetails(
+ const std::string& histogram_name,
+ const leveldb::Status& status) {
+ DCHECK(!status.IsIOError());
+ DCHECK(!status.ok());
+ const int kOtherError = 0;
+ int error = kOtherError;
+ const std::string& str_error = status.ToString();
+ // Keep in sync with LevelDBCorruptionTypes in histograms.xml.
+ const char* patterns[] = {
+ "missing files",
+ "log record too small",
+ "corrupted internal key",
+ "partial record",
+ "missing start of fragmented record",
+ "error in middle of record",
+ "unknown record type",
+ "truncated record at end",
+ "bad record length",
+ "VersionEdit",
+ "FileReader invoked with unexpected value",
+ "corrupted key",
+ "CURRENT file does not end with newline",
+ "no meta-nextfile entry",
+ "no meta-lognumber entry",
+ "no last-sequence-number entry",
+ "malformed WriteBatch",
+ "bad WriteBatch Put",
+ "bad WriteBatch Delete",
+ "unknown WriteBatch tag",
+ "WriteBatch has wrong count",
+ "bad entry in block",
+ "bad block contents",
+ "bad block handle",
+ "truncated block read",
+ "block checksum mismatch",
+ "checksum mismatch",
+ "corrupted compressed block contents",
+ "bad block type",
+ "bad magic number",
+ "file is too short",
+ };
+ const size_t kNumPatterns = arraysize(patterns);
+ for (size_t i = 0; i < kNumPatterns; ++i) {
+ if (str_error.find(patterns[i]) != std::string::npos) {
+ error = i + 1;
+ break;
+ }
+ }
+ DCHECK(error >= 0);
+ std::string corruption_histogram_name(histogram_name);
+ corruption_histogram_name.append(".Corruption");
+ base::LinearHistogram::FactoryGet(
+ corruption_histogram_name,
+ 1,
+ kNumPatterns + 1,
+ kNumPatterns + 2,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
+}
+
+static void HistogramLevelDBError(const std::string& histogram_name,
+ const leveldb::Status& s) {
+ if (s.ok()) {
+ NOTREACHED();
+ return;
+ }
+ enum {
+ LEVEL_DB_NOT_FOUND,
+ LEVEL_DB_CORRUPTION,
+ LEVEL_DB_IO_ERROR,
+ LEVEL_DB_OTHER,
+ LEVEL_DB_MAX_ERROR
+ };
+ int leveldb_error = LEVEL_DB_OTHER;
+ if (s.IsNotFound())
+ leveldb_error = LEVEL_DB_NOT_FOUND;
+ else if (s.IsCorruption())
+ leveldb_error = LEVEL_DB_CORRUPTION;
+ else if (s.IsIOError())
+ leveldb_error = LEVEL_DB_IO_ERROR;
+ base::Histogram::FactoryGet(histogram_name,
+ 1,
+ LEVEL_DB_MAX_ERROR,
+ LEVEL_DB_MAX_ERROR + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+ ->Add(leveldb_error);
+ if (s.IsIOError())
+ ParseAndHistogramIOErrorDetails(histogram_name, s);
+ else
+ ParseAndHistogramCorruptionDetails(histogram_name, s);
+}
+
+leveldb::Status LevelDBDatabase::Open(
+ const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* result,
+ bool* is_disk_full) {
+ scoped_ptr<ComparatorAdapter> comparator_adapter(
+ new ComparatorAdapter(comparator));
+
+ leveldb::DB* db;
+ const leveldb::Status s =
+ OpenDB(comparator_adapter.get(), leveldb::IDBEnv(), file_name, &db);
+
+ if (!s.ok()) {
+ HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
+ int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
+ // Disks with <100k of free space almost never succeed in opening a
+ // leveldb database.
+ if (is_disk_full)
+ *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
+
+ LOG(ERROR) << "Failed to open LevelDB database from "
+ << file_name.AsUTF8Unsafe() << "," << s.ToString();
+ return s;
+ }
+
+ CheckFreeSpace("Success", file_name);
+
+ (*result).reset(new LevelDBDatabase);
+ (*result)->db_ = make_scoped_ptr(db);
+ (*result)->comparator_adapter_ = comparator_adapter.Pass();
+ (*result)->comparator_ = comparator;
+
+ return s;
+}
+
+scoped_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
+ const LevelDBComparator* comparator) {
+ scoped_ptr<ComparatorAdapter> comparator_adapter(
+ new ComparatorAdapter(comparator));
+ scoped_ptr<leveldb::Env> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
+
+ leveldb::DB* db;
+ const leveldb::Status s = OpenDB(
+ comparator_adapter.get(), in_memory_env.get(), base::FilePath(), &db);
+
+ if (!s.ok()) {
+ LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
+ return scoped_ptr<LevelDBDatabase>();
+ }
+
+ scoped_ptr<LevelDBDatabase> result(new LevelDBDatabase);
+ result->env_ = in_memory_env.Pass();
+ result->db_ = make_scoped_ptr(db);
+ result->comparator_adapter_ = comparator_adapter.Pass();
+ result->comparator_ = comparator;
+
+ return result.Pass();
+}
+
+bool LevelDBDatabase::Put(const StringPiece& key, std::string* value) {
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ const leveldb::Status s =
+ db_->Put(write_options, MakeSlice(key), MakeSlice(*value));
+ if (s.ok())
+ return true;
+ LOG(ERROR) << "LevelDB put failed: " << s.ToString();
+ return false;
+}
+
+bool LevelDBDatabase::Remove(const StringPiece& key) {
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ const leveldb::Status s = db_->Delete(write_options, MakeSlice(key));
+ if (s.ok())
+ return true;
+ if (s.IsNotFound())
+ return false;
+ LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
+ return false;
+}
+
+bool LevelDBDatabase::Get(const StringPiece& key,
+ std::string* value,
+ bool* found,
+ const LevelDBSnapshot* snapshot) {
+ *found = false;
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true; // TODO(jsbell): Disable this if the
+ // performance impact is too great.
+ read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
+
+ const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value);
+ if (s.ok()) {
+ *found = true;
+ return true;
+ }
+ if (s.IsNotFound())
+ return true;
+ HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
+ LOG(ERROR) << "LevelDB get failed: " << s.ToString();
+ return false;
+}
+
+bool LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch) {
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ const leveldb::Status s =
+ db_->Write(write_options, write_batch.write_batch_.get());
+ if (s.ok())
+ return true;
+ HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
+ LOG(ERROR) << "LevelDB write failed: " << s.ToString();
+ return false;
+}
+
+namespace {
+class IteratorImpl : public LevelDBIterator {
+ public:
+ virtual ~IteratorImpl() {}
+
+ virtual bool IsValid() const OVERRIDE;
+ virtual void SeekToLast() OVERRIDE;
+ virtual void Seek(const StringPiece& target) OVERRIDE;
+ virtual void Next() OVERRIDE;
+ virtual void Prev() OVERRIDE;
+ virtual StringPiece Key() const OVERRIDE;
+ virtual StringPiece Value() const OVERRIDE;
+
+ private:
+ friend class content::LevelDBDatabase;
+ explicit IteratorImpl(scoped_ptr<leveldb::Iterator> iterator);
+ void CheckStatus();
+
+ scoped_ptr<leveldb::Iterator> iterator_;
+};
+}
+
+IteratorImpl::IteratorImpl(scoped_ptr<leveldb::Iterator> it)
+ : iterator_(it.Pass()) {}
+
+void IteratorImpl::CheckStatus() {
+ const leveldb::Status s = iterator_->status();
+ if (!s.ok())
+ LOG(ERROR) << "LevelDB iterator error: " << s.ToString();
+}
+
+bool IteratorImpl::IsValid() const { return iterator_->Valid(); }
+
+void IteratorImpl::SeekToLast() {
+ iterator_->SeekToLast();
+ CheckStatus();
+}
+
+void IteratorImpl::Seek(const StringPiece& target) {
+ iterator_->Seek(MakeSlice(target));
+ CheckStatus();
+}
+
+void IteratorImpl::Next() {
+ DCHECK(IsValid());
+ iterator_->Next();
+ CheckStatus();
+}
+
+void IteratorImpl::Prev() {
+ DCHECK(IsValid());
+ iterator_->Prev();
+ CheckStatus();
+}
+
+StringPiece IteratorImpl::Key() const {
+ DCHECK(IsValid());
+ return MakeStringPiece(iterator_->key());
+}
+
+StringPiece IteratorImpl::Value() const {
+ DCHECK(IsValid());
+ return MakeStringPiece(iterator_->value());
+}
+
+scoped_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
+ const LevelDBSnapshot* snapshot) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true; // TODO(jsbell): Disable this if the
+ // performance impact is too great.
+ read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;
+
+ scoped_ptr<leveldb::Iterator> i(db_->NewIterator(read_options));
+ return scoped_ptr<LevelDBIterator>(new IteratorImpl(i.Pass()));
+}
+
+const LevelDBComparator* LevelDBDatabase::Comparator() const {
+ return comparator_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_database.h b/chromium/content/browser/indexed_db/leveldb/leveldb_database.h
new file mode 100644
index 00000000000..1efeefa2d17
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_database.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "content/common/content_export.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+
+namespace leveldb {
+class Comparator;
+class DB;
+class Env;
+class Snapshot;
+}
+
+namespace content {
+
+class LevelDBComparator;
+class LevelDBDatabase;
+class LevelDBIterator;
+class LevelDBWriteBatch;
+
+class LevelDBSnapshot {
+ private:
+ friend class LevelDBDatabase;
+ friend class LevelDBTransaction;
+
+ explicit LevelDBSnapshot(LevelDBDatabase* db);
+ ~LevelDBSnapshot();
+
+ leveldb::DB* db_;
+ const leveldb::Snapshot* snapshot_;
+};
+
+class CONTENT_EXPORT LevelDBDatabase {
+ public:
+ static leveldb::Status Open(const base::FilePath& file_name,
+ const LevelDBComparator* comparator,
+ scoped_ptr<LevelDBDatabase>* db,
+ bool* is_disk_full = 0);
+ static scoped_ptr<LevelDBDatabase> OpenInMemory(
+ const LevelDBComparator* comparator);
+ static bool Destroy(const base::FilePath& file_name);
+ virtual ~LevelDBDatabase();
+
+ bool Put(const base::StringPiece& key, std::string* value);
+ bool Remove(const base::StringPiece& key);
+ virtual bool Get(const base::StringPiece& key,
+ std::string* value,
+ bool* found,
+ const LevelDBSnapshot* = 0);
+ bool Write(const LevelDBWriteBatch& write_batch);
+ scoped_ptr<LevelDBIterator> CreateIterator(const LevelDBSnapshot* = 0);
+ const LevelDBComparator* Comparator() const;
+
+ protected:
+ LevelDBDatabase();
+
+ private:
+ friend class LevelDBSnapshot;
+
+ scoped_ptr<leveldb::Env> env_;
+ scoped_ptr<leveldb::Comparator> comparator_adapter_;
+ scoped_ptr<leveldb::DB> db_;
+ const LevelDBComparator* comparator_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_DATABASE_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_iterator.h b/chromium/content/browser/indexed_db/leveldb/leveldb_iterator.h
new file mode 100644
index 00000000000..96cd6b91281
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_iterator.h
@@ -0,0 +1,26 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_
+
+#include "base/strings/string_piece.h"
+
+namespace content {
+
+class LevelDBIterator {
+ public:
+ virtual ~LevelDBIterator() {}
+ virtual bool IsValid() const = 0;
+ virtual void SeekToLast() = 0;
+ virtual void Seek(const base::StringPiece& target) = 0;
+ virtual void Next() = 0;
+ virtual void Prev() = 0;
+ virtual base::StringPiece Key() const = 0;
+ virtual base::StringPiece Value() const = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ITERATOR_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.cc b/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.cc
new file mode 100644
index 00000000000..5388d44c6f5
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.cc
@@ -0,0 +1,476 @@
+// Copyright (c) 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 "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+
+#include "base/logging.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+using base::StringPiece;
+
+namespace content {
+
+LevelDBTransaction::LevelDBTransaction(LevelDBDatabase* db)
+ : db_(db), snapshot_(db), comparator_(db->Comparator()), finished_(false) {
+ tree_.abstractor().comparator_ = comparator_;
+}
+
+LevelDBTransaction::AVLTreeNode::AVLTreeNode() {}
+LevelDBTransaction::AVLTreeNode::~AVLTreeNode() {}
+
+void LevelDBTransaction::ClearTree() {
+ TreeType::Iterator iterator;
+ iterator.StartIterLeast(&tree_);
+
+ std::vector<AVLTreeNode*> nodes;
+
+ while (*iterator) {
+ nodes.push_back(*iterator);
+ ++iterator;
+ }
+ tree_.Purge();
+
+ for (size_t i = 0; i < nodes.size(); ++i)
+ delete nodes[i];
+}
+
+LevelDBTransaction::~LevelDBTransaction() { ClearTree(); }
+
+void LevelDBTransaction::Set(const StringPiece& key,
+ std::string* value,
+ bool deleted) {
+ DCHECK(!finished_);
+ bool new_node = false;
+ AVLTreeNode* node = tree_.Search(key);
+
+ if (!node) {
+ node = new AVLTreeNode;
+ node->key = key.as_string();
+ tree_.Insert(node);
+ new_node = true;
+ }
+ node->value.swap(*value);
+ node->deleted = deleted;
+
+ if (new_node)
+ NotifyIteratorsOfTreeChange();
+}
+
+void LevelDBTransaction::Put(const StringPiece& key, std::string* value) {
+ Set(key, value, false);
+}
+
+void LevelDBTransaction::Remove(const StringPiece& key) {
+ std::string empty;
+ Set(key, &empty, true);
+}
+
+bool LevelDBTransaction::Get(const StringPiece& key,
+ std::string* value,
+ bool* found) {
+ *found = false;
+ DCHECK(!finished_);
+ AVLTreeNode* node = tree_.Search(key);
+
+ if (node) {
+ if (node->deleted)
+ return true;
+
+ *value = node->value;
+ *found = true;
+ return true;
+ }
+
+ bool ok = db_->Get(key, value, found, &snapshot_);
+ if (!ok) {
+ DCHECK(!*found);
+ return false;
+ }
+ return true;
+}
+
+bool LevelDBTransaction::Commit() {
+ DCHECK(!finished_);
+
+ if (tree_.IsEmpty()) {
+ finished_ = true;
+ return true;
+ }
+
+ scoped_ptr<LevelDBWriteBatch> write_batch = LevelDBWriteBatch::Create();
+
+ TreeType::Iterator iterator;
+ iterator.StartIterLeast(&tree_);
+
+ while (*iterator) {
+ AVLTreeNode* node = *iterator;
+ if (!node->deleted)
+ write_batch->Put(node->key, node->value);
+ else
+ write_batch->Remove(node->key);
+ ++iterator;
+ }
+
+ if (!db_->Write(*write_batch))
+ return false;
+
+ ClearTree();
+ finished_ = true;
+ return true;
+}
+
+void LevelDBTransaction::Rollback() {
+ DCHECK(!finished_);
+ finished_ = true;
+ ClearTree();
+}
+
+scoped_ptr<LevelDBIterator> LevelDBTransaction::CreateIterator() {
+ return TransactionIterator::Create(this).PassAs<LevelDBIterator>();
+}
+
+scoped_ptr<LevelDBTransaction::TreeIterator>
+LevelDBTransaction::TreeIterator::Create(LevelDBTransaction* transaction) {
+ return make_scoped_ptr(new TreeIterator(transaction));
+}
+
+bool LevelDBTransaction::TreeIterator::IsValid() const { return !!*iterator_; }
+
+void LevelDBTransaction::TreeIterator::SeekToLast() {
+ iterator_.StartIterGreatest(tree_);
+ if (IsValid())
+ key_ = (*iterator_)->key;
+}
+
+void LevelDBTransaction::TreeIterator::Seek(const StringPiece& target) {
+ iterator_.StartIter(tree_, target, TreeType::EQUAL);
+ if (!IsValid())
+ iterator_.StartIter(tree_, target, TreeType::GREATER);
+
+ if (IsValid())
+ key_ = (*iterator_)->key;
+}
+
+void LevelDBTransaction::TreeIterator::Next() {
+ DCHECK(IsValid());
+ ++iterator_;
+ if (IsValid()) {
+ DCHECK_GE(transaction_->comparator_->Compare((*iterator_)->key, key_), 0);
+ key_ = (*iterator_)->key;
+ }
+}
+
+void LevelDBTransaction::TreeIterator::Prev() {
+ DCHECK(IsValid());
+ --iterator_;
+ if (IsValid()) {
+ DCHECK_LT(tree_->abstractor().comparator_->Compare((*iterator_)->key, key_),
+ 0);
+ key_ = (*iterator_)->key;
+ }
+}
+
+StringPiece LevelDBTransaction::TreeIterator::Key() const {
+ DCHECK(IsValid());
+ return key_;
+}
+
+StringPiece LevelDBTransaction::TreeIterator::Value() const {
+ DCHECK(IsValid());
+ DCHECK(!IsDeleted());
+ return (*iterator_)->value;
+}
+
+bool LevelDBTransaction::TreeIterator::IsDeleted() const {
+ DCHECK(IsValid());
+ return (*iterator_)->deleted;
+}
+
+void LevelDBTransaction::TreeIterator::Reset() {
+ DCHECK(IsValid());
+ iterator_.StartIter(tree_, key_, TreeType::EQUAL);
+ DCHECK(IsValid());
+}
+
+LevelDBTransaction::TreeIterator::~TreeIterator() {}
+
+LevelDBTransaction::TreeIterator::TreeIterator(LevelDBTransaction* transaction)
+ : tree_(&transaction->tree_), transaction_(transaction) {}
+
+scoped_ptr<LevelDBTransaction::TransactionIterator>
+LevelDBTransaction::TransactionIterator::Create(
+ scoped_refptr<LevelDBTransaction> transaction) {
+ return make_scoped_ptr(new TransactionIterator(transaction));
+}
+
+LevelDBTransaction::TransactionIterator::TransactionIterator(
+ scoped_refptr<LevelDBTransaction> transaction)
+ : transaction_(transaction),
+ comparator_(transaction_->comparator_),
+ tree_iterator_(TreeIterator::Create(transaction_.get())),
+ db_iterator_(transaction_->db_->CreateIterator(&transaction_->snapshot_)),
+ current_(0),
+ direction_(FORWARD),
+ tree_changed_(false) {
+ transaction_->RegisterIterator(this);
+}
+
+LevelDBTransaction::TransactionIterator::~TransactionIterator() {
+ transaction_->UnregisterIterator(this);
+}
+
+bool LevelDBTransaction::TransactionIterator::IsValid() const {
+ return !!current_;
+}
+
+void LevelDBTransaction::TransactionIterator::SeekToLast() {
+ tree_iterator_->SeekToLast();
+ db_iterator_->SeekToLast();
+ direction_ = REVERSE;
+
+ HandleConflictsAndDeletes();
+ SetCurrentIteratorToLargestKey();
+}
+
+void LevelDBTransaction::TransactionIterator::Seek(const StringPiece& target) {
+ tree_iterator_->Seek(target);
+ db_iterator_->Seek(target);
+ direction_ = FORWARD;
+
+ HandleConflictsAndDeletes();
+ SetCurrentIteratorToSmallestKey();
+}
+
+void LevelDBTransaction::TransactionIterator::Next() {
+ DCHECK(IsValid());
+ if (tree_changed_)
+ RefreshTreeIterator();
+
+ if (direction_ != FORWARD) {
+ // Ensure the non-current iterator is positioned after Key().
+
+ LevelDBIterator* non_current = (current_ == db_iterator_.get())
+ ? tree_iterator_.get()
+ : db_iterator_.get();
+
+ non_current->Seek(Key());
+ if (non_current->IsValid() &&
+ !comparator_->Compare(non_current->Key(), Key()))
+ non_current->Next(); // Take an extra step so the non-current key is
+ // strictly greater than Key().
+
+ DCHECK(!non_current->IsValid() ||
+ comparator_->Compare(non_current->Key(), Key()) > 0);
+
+ direction_ = FORWARD;
+ }
+
+ current_->Next();
+ HandleConflictsAndDeletes();
+ SetCurrentIteratorToSmallestKey();
+}
+
+void LevelDBTransaction::TransactionIterator::Prev() {
+ DCHECK(IsValid());
+ if (tree_changed_)
+ RefreshTreeIterator();
+
+ if (direction_ != REVERSE) {
+ // Ensure the non-current iterator is positioned before Key().
+
+ LevelDBIterator* non_current = (current_ == db_iterator_.get())
+ ? tree_iterator_.get()
+ : db_iterator_.get();
+
+ non_current->Seek(Key());
+ if (non_current->IsValid()) {
+ // Iterator is at first entry >= Key().
+ // Step back once to entry < key.
+ // This is why we don't check for the keys being the same before
+ // stepping, like we do in Next() above.
+ non_current->Prev();
+ } else {
+ non_current->SeekToLast(); // Iterator has no entries >= Key(). Position
+ // at last entry.
+ }
+ DCHECK(!non_current->IsValid() ||
+ comparator_->Compare(non_current->Key(), Key()) < 0);
+
+ direction_ = REVERSE;
+ }
+
+ current_->Prev();
+ HandleConflictsAndDeletes();
+ SetCurrentIteratorToLargestKey();
+}
+
+StringPiece LevelDBTransaction::TransactionIterator::Key() const {
+ DCHECK(IsValid());
+ if (tree_changed_)
+ RefreshTreeIterator();
+ return current_->Key();
+}
+
+StringPiece LevelDBTransaction::TransactionIterator::Value() const {
+ DCHECK(IsValid());
+ if (tree_changed_)
+ RefreshTreeIterator();
+ return current_->Value();
+}
+
+void LevelDBTransaction::TransactionIterator::TreeChanged() {
+ tree_changed_ = true;
+}
+
+void LevelDBTransaction::TransactionIterator::RefreshTreeIterator() const {
+ DCHECK(tree_changed_);
+
+ tree_changed_ = false;
+
+ if (tree_iterator_->IsValid() && tree_iterator_.get() == current_) {
+ tree_iterator_->Reset();
+ return;
+ }
+
+ if (db_iterator_->IsValid()) {
+ // There could be new nodes in the tree that we should iterate over.
+
+ if (direction_ == FORWARD) {
+ // Try to seek tree iterator to something greater than the db iterator.
+ tree_iterator_->Seek(db_iterator_->Key());
+ if (tree_iterator_->IsValid() &&
+ !comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key()))
+ tree_iterator_->Next(); // If equal, take another step so the tree
+ // iterator is strictly greater.
+ } else {
+ // If going backward, seek to a key less than the db iterator.
+ DCHECK_EQ(REVERSE, direction_);
+ tree_iterator_->Seek(db_iterator_->Key());
+ if (tree_iterator_->IsValid())
+ tree_iterator_->Prev();
+ }
+ }
+}
+
+bool LevelDBTransaction::TransactionIterator::TreeIteratorIsLower() const {
+ return comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key()) < 0;
+}
+
+bool LevelDBTransaction::TransactionIterator::TreeIteratorIsHigher() const {
+ return comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key()) > 0;
+}
+
+void LevelDBTransaction::TransactionIterator::HandleConflictsAndDeletes() {
+ bool loop = true;
+
+ while (loop) {
+ loop = false;
+
+ if (tree_iterator_->IsValid() && db_iterator_->IsValid() &&
+ !comparator_->Compare(tree_iterator_->Key(), db_iterator_->Key())) {
+ // For equal keys, the tree iterator takes precedence, so move the
+ // database iterator another step.
+ if (direction_ == FORWARD)
+ db_iterator_->Next();
+ else
+ db_iterator_->Prev();
+ }
+
+ // Skip over delete markers in the tree iterator until it catches up with
+ // the db iterator.
+ if (tree_iterator_->IsValid() && tree_iterator_->IsDeleted()) {
+ if (direction_ == FORWARD &&
+ (!db_iterator_->IsValid() || TreeIteratorIsLower())) {
+ tree_iterator_->Next();
+ loop = true;
+ } else if (direction_ == REVERSE &&
+ (!db_iterator_->IsValid() || TreeIteratorIsHigher())) {
+ tree_iterator_->Prev();
+ loop = true;
+ }
+ }
+ }
+}
+
+void
+LevelDBTransaction::TransactionIterator::SetCurrentIteratorToSmallestKey() {
+ LevelDBIterator* smallest = 0;
+
+ if (tree_iterator_->IsValid())
+ smallest = tree_iterator_.get();
+
+ if (db_iterator_->IsValid()) {
+ if (!smallest ||
+ comparator_->Compare(db_iterator_->Key(), smallest->Key()) < 0)
+ smallest = db_iterator_.get();
+ }
+
+ current_ = smallest;
+}
+
+void LevelDBTransaction::TransactionIterator::SetCurrentIteratorToLargestKey() {
+ LevelDBIterator* largest = 0;
+
+ if (tree_iterator_->IsValid())
+ largest = tree_iterator_.get();
+
+ if (db_iterator_->IsValid()) {
+ if (!largest ||
+ comparator_->Compare(db_iterator_->Key(), largest->Key()) > 0)
+ largest = db_iterator_.get();
+ }
+
+ current_ = largest;
+}
+
+void LevelDBTransaction::RegisterIterator(TransactionIterator* iterator) {
+ DCHECK(iterators_.find(iterator) == iterators_.end());
+ iterators_.insert(iterator);
+}
+
+void LevelDBTransaction::UnregisterIterator(TransactionIterator* iterator) {
+ DCHECK(iterators_.find(iterator) != iterators_.end());
+ iterators_.erase(iterator);
+}
+
+void LevelDBTransaction::NotifyIteratorsOfTreeChange() {
+ for (std::set<TransactionIterator*>::iterator i = iterators_.begin();
+ i != iterators_.end();
+ ++i) {
+ TransactionIterator* transaction_iterator = *i;
+ transaction_iterator->TreeChanged();
+ }
+}
+
+scoped_ptr<LevelDBWriteOnlyTransaction> LevelDBWriteOnlyTransaction::Create(
+ LevelDBDatabase* db) {
+ return make_scoped_ptr(new LevelDBWriteOnlyTransaction(db));
+}
+
+LevelDBWriteOnlyTransaction::LevelDBWriteOnlyTransaction(LevelDBDatabase* db)
+ : db_(db), write_batch_(LevelDBWriteBatch::Create()), finished_(false) {}
+
+LevelDBWriteOnlyTransaction::~LevelDBWriteOnlyTransaction() {
+ write_batch_->Clear();
+}
+
+void LevelDBWriteOnlyTransaction::Remove(const StringPiece& key) {
+ DCHECK(!finished_);
+ write_batch_->Remove(key);
+}
+
+bool LevelDBWriteOnlyTransaction::Commit() {
+ DCHECK(!finished_);
+
+ if (!db_->Write(*write_batch_))
+ return false;
+
+ finished_ = true;
+ write_batch_->Clear();
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.h b/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.h
new file mode 100644
index 00000000000..83b4763edc1
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_transaction.h
@@ -0,0 +1,177 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "content/browser/indexed_db/leveldb/avltree.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+
+namespace content {
+
+class LevelDBWriteBatch;
+
+class CONTENT_EXPORT LevelDBTransaction
+ : public base::RefCounted<LevelDBTransaction> {
+ public:
+ explicit LevelDBTransaction(LevelDBDatabase* db);
+
+ void Put(const base::StringPiece& key, std::string* value);
+ void Remove(const base::StringPiece& key);
+ bool Get(const base::StringPiece& key, std::string* value, bool* found);
+ bool Commit();
+ void Rollback();
+
+ scoped_ptr<LevelDBIterator> CreateIterator();
+
+ private:
+ virtual ~LevelDBTransaction();
+ friend class base::RefCounted<LevelDBTransaction>;
+
+ struct AVLTreeNode {
+ AVLTreeNode();
+ ~AVLTreeNode();
+ std::string key;
+ std::string value;
+ bool deleted;
+
+ AVLTreeNode* less;
+ AVLTreeNode* greater;
+ int balance_factor;
+ DISALLOW_COPY_AND_ASSIGN(AVLTreeNode);
+ };
+
+ struct AVLTreeAbstractor {
+ typedef AVLTreeNode* handle;
+ typedef size_t size;
+ typedef base::StringPiece key;
+
+ handle GetLess(handle h) { return h->less; }
+ void SetLess(handle h, handle less) { h->less = less; }
+ handle GetGreater(handle h) { return h->greater; }
+ void SetGreater(handle h, handle greater) { h->greater = greater; }
+
+ int GetBalanceFactor(handle h) { return h->balance_factor; }
+ void SetBalanceFactor(handle h, int bf) { h->balance_factor = bf; }
+
+ int CompareKeyKey(const key& ka, const key& kb) {
+ return comparator_->Compare(ka, kb);
+ }
+ int CompareKeyNode(const key& k, handle h) {
+ return CompareKeyKey(k, h->key);
+ }
+ int CompareNodeNode(handle ha, handle hb) {
+ return CompareKeyKey(ha->key, hb->key);
+ }
+
+ static handle Null() { return 0; }
+
+ const LevelDBComparator* comparator_;
+ };
+
+ typedef AVLTree<AVLTreeAbstractor> TreeType;
+
+ class TreeIterator : public LevelDBIterator {
+ public:
+ static scoped_ptr<TreeIterator> Create(LevelDBTransaction* transaction);
+ virtual ~TreeIterator();
+
+ virtual bool IsValid() const OVERRIDE;
+ virtual void SeekToLast() OVERRIDE;
+ virtual void Seek(const base::StringPiece& slice) OVERRIDE;
+ virtual void Next() OVERRIDE;
+ virtual void Prev() OVERRIDE;
+ virtual base::StringPiece Key() const OVERRIDE;
+ virtual base::StringPiece Value() const OVERRIDE;
+ bool IsDeleted() const;
+ void Reset();
+
+ private:
+ explicit TreeIterator(LevelDBTransaction* transaction);
+ mutable TreeType::Iterator iterator_; // Dereferencing this is non-const.
+ TreeType* tree_;
+ LevelDBTransaction* transaction_;
+ std::string key_;
+ };
+
+ class TransactionIterator : public LevelDBIterator {
+ public:
+ virtual ~TransactionIterator();
+ static scoped_ptr<TransactionIterator> Create(
+ scoped_refptr<LevelDBTransaction> transaction);
+
+ virtual bool IsValid() const OVERRIDE;
+ virtual void SeekToLast() OVERRIDE;
+ virtual void Seek(const base::StringPiece& target) OVERRIDE;
+ virtual void Next() OVERRIDE;
+ virtual void Prev() OVERRIDE;
+ virtual base::StringPiece Key() const OVERRIDE;
+ virtual base::StringPiece Value() const OVERRIDE;
+ void TreeChanged();
+
+ private:
+ explicit TransactionIterator(scoped_refptr<LevelDBTransaction> transaction);
+ void HandleConflictsAndDeletes();
+ void SetCurrentIteratorToSmallestKey();
+ void SetCurrentIteratorToLargestKey();
+ void RefreshTreeIterator() const;
+ bool TreeIteratorIsLower() const;
+ bool TreeIteratorIsHigher() const;
+
+ scoped_refptr<LevelDBTransaction> transaction_;
+ const LevelDBComparator* comparator_;
+ mutable scoped_ptr<TreeIterator> tree_iterator_;
+ scoped_ptr<LevelDBIterator> db_iterator_;
+ LevelDBIterator* current_;
+
+ enum Direction {
+ FORWARD,
+ REVERSE
+ };
+ Direction direction_;
+ mutable bool tree_changed_;
+ };
+
+ void Set(const base::StringPiece& key, std::string* value, bool deleted);
+ void ClearTree();
+ void RegisterIterator(TransactionIterator* iterator);
+ void UnregisterIterator(TransactionIterator* iterator);
+ void NotifyIteratorsOfTreeChange();
+
+ LevelDBDatabase* db_;
+ const LevelDBSnapshot snapshot_;
+ const LevelDBComparator* comparator_;
+ TreeType tree_;
+ bool finished_;
+ std::set<TransactionIterator*> iterators_;
+};
+
+class LevelDBWriteOnlyTransaction {
+ public:
+ static scoped_ptr<LevelDBWriteOnlyTransaction> Create(LevelDBDatabase* db);
+
+ ~LevelDBWriteOnlyTransaction();
+ void Remove(const base::StringPiece& key);
+ bool Commit();
+
+ private:
+ explicit LevelDBWriteOnlyTransaction(LevelDBDatabase* db);
+
+ LevelDBDatabase* db_;
+ scoped_ptr<LevelDBWriteBatch> write_batch_;
+ bool finished_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_TRANSACTION_H_
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_unittest.cc b/chromium/content/browser/indexed_db/leveldb/leveldb_unittest.cc
new file mode 100644
index 00000000000..9ce06641c61
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 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 <algorithm>
+#include <cstring>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/platform_file.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
+#include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+class SimpleComparator : public LevelDBComparator {
+ public:
+ virtual int Compare(const base::StringPiece& a,
+ const base::StringPiece& b) const OVERRIDE {
+ size_t len = std::min(a.size(), b.size());
+ return memcmp(a.begin(), b.begin(), len);
+ }
+ virtual const char* Name() const OVERRIDE { return "temp_comparator"; }
+};
+
+TEST(LevelDBDatabaseTest, CorruptionTest) {
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+
+ const std::string key("key");
+ const std::string value("value");
+ std::string put_value;
+ std::string got_value;
+ SimpleComparator comparator;
+
+ scoped_ptr<LevelDBDatabase> leveldb;
+ LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_TRUE(leveldb);
+ put_value = value;
+ bool success = leveldb->Put(key, &put_value);
+ EXPECT_TRUE(success);
+ leveldb.Pass();
+ EXPECT_FALSE(leveldb);
+
+ LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_TRUE(leveldb);
+ bool found = false;
+ success = leveldb->Get(key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(found);
+ EXPECT_EQ(value, got_value);
+ leveldb.Pass();
+ EXPECT_FALSE(leveldb);
+
+ base::FilePath file_path = temp_directory.path().AppendASCII("CURRENT");
+ base::PlatformFile handle = base::CreatePlatformFile(
+ file_path,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ NULL,
+ NULL);
+ base::TruncatePlatformFile(handle, 0);
+ base::ClosePlatformFile(handle);
+
+ leveldb::Status status =
+ LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_FALSE(leveldb);
+ EXPECT_FALSE(status.ok());
+
+ bool destroyed = LevelDBDatabase::Destroy(temp_directory.path());
+ EXPECT_TRUE(destroyed);
+
+ status = LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_TRUE(status.ok());
+ EXPECT_TRUE(leveldb);
+ success = leveldb->Get(key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_FALSE(found);
+}
+
+TEST(LevelDBDatabaseTest, Transaction) {
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+
+ const std::string key("key");
+ std::string got_value;
+ std::string put_value;
+ SimpleComparator comparator;
+
+ scoped_ptr<LevelDBDatabase> leveldb;
+ LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_TRUE(leveldb);
+
+ const std::string old_value("value");
+ put_value = old_value;
+ bool success = leveldb->Put(key, &put_value);
+ EXPECT_TRUE(success);
+
+ scoped_refptr<LevelDBTransaction> transaction =
+ new LevelDBTransaction(leveldb.get());
+
+ const std::string new_value("new value");
+ put_value = new_value;
+ success = leveldb->Put(key, &put_value);
+ EXPECT_TRUE(success);
+
+ bool found = false;
+ success = transaction->Get(key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(found);
+ EXPECT_EQ(comparator.Compare(got_value, old_value), 0);
+
+ found = false;
+ success = leveldb->Get(key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(found);
+ EXPECT_EQ(comparator.Compare(got_value, new_value), 0);
+
+ const std::string added_key("added key");
+ const std::string added_value("added value");
+ put_value = added_value;
+ success = leveldb->Put(added_key, &put_value);
+ EXPECT_TRUE(success);
+
+ success = leveldb->Get(added_key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(found);
+ EXPECT_EQ(comparator.Compare(got_value, added_value), 0);
+
+ success = transaction->Get(added_key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_FALSE(found);
+
+ const std::string another_key("another key");
+ const std::string another_value("another value");
+ put_value = another_value;
+ transaction->Put(another_key, &put_value);
+
+ success = transaction->Get(another_key, &got_value, &found);
+ EXPECT_TRUE(success);
+ EXPECT_TRUE(found);
+ EXPECT_EQ(comparator.Compare(got_value, another_value), 0);
+}
+
+TEST(LevelDBDatabaseTest, TransactionIterator) {
+ base::ScopedTempDir temp_directory;
+ ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
+
+ const std::string key1("key1");
+ const std::string value1("value1");
+ const std::string key2("key2");
+ const std::string value2("value2");
+ std::string put_value;
+ SimpleComparator comparator;
+ bool success;
+
+ scoped_ptr<LevelDBDatabase> leveldb;
+ LevelDBDatabase::Open(temp_directory.path(), &comparator, &leveldb);
+ EXPECT_TRUE(leveldb);
+
+ put_value = value1;
+ success = leveldb->Put(key1, &put_value);
+ EXPECT_TRUE(success);
+ put_value = value2;
+ success = leveldb->Put(key2, &put_value);
+ EXPECT_TRUE(success);
+
+ scoped_refptr<LevelDBTransaction> transaction =
+ new LevelDBTransaction(leveldb.get());
+
+ success = leveldb->Remove(key2);
+ EXPECT_TRUE(success);
+
+ scoped_ptr<LevelDBIterator> it = transaction->CreateIterator();
+
+ it->Seek(std::string());
+
+ EXPECT_TRUE(it->IsValid());
+ EXPECT_EQ(comparator.Compare(it->Key(), key1), 0);
+ EXPECT_EQ(comparator.Compare(it->Value(), value1), 0);
+
+ it->Next();
+
+ EXPECT_TRUE(it->IsValid());
+ EXPECT_EQ(comparator.Compare(it->Key(), key2), 0);
+ EXPECT_EQ(comparator.Compare(it->Value(), value2), 0);
+
+ it->Next();
+
+ EXPECT_FALSE(it->IsValid());
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.cc b/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.cc
new file mode 100644
index 00000000000..d9a12cb7bf1
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 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 "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
+
+#include "base/strings/string_piece.h"
+#include "third_party/leveldatabase/src/include/leveldb/slice.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace content {
+
+scoped_ptr<LevelDBWriteBatch> LevelDBWriteBatch::Create() {
+ return make_scoped_ptr(new LevelDBWriteBatch);
+}
+
+LevelDBWriteBatch::LevelDBWriteBatch()
+ : write_batch_(new leveldb::WriteBatch) {}
+
+LevelDBWriteBatch::~LevelDBWriteBatch() {}
+
+static leveldb::Slice MakeSlice(const base::StringPiece& s) {
+ return leveldb::Slice(s.begin(), s.size());
+}
+
+void LevelDBWriteBatch::Put(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ write_batch_->Put(MakeSlice(key), MakeSlice(value));
+}
+
+void LevelDBWriteBatch::Remove(const base::StringPiece& key) {
+ write_batch_->Delete(MakeSlice(key));
+}
+
+void LevelDBWriteBatch::Clear() { write_batch_->Clear(); }
+
+} // namespace content
diff --git a/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.h b/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.h
new file mode 100644
index 00000000000..3fbd103dd4c
--- /dev/null
+++ b/chromium/content/browser/indexed_db/leveldb/leveldb_write_batch.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+
+namespace leveldb {
+class WriteBatch;
+}
+
+namespace content {
+
+// Wrapper around leveldb::WriteBatch.
+// This class holds a collection of updates to apply atomically to a database.
+class LevelDBWriteBatch {
+ public:
+ static scoped_ptr<LevelDBWriteBatch> Create();
+ ~LevelDBWriteBatch();
+
+ void Put(const base::StringPiece& key, const base::StringPiece& value);
+ void Remove(const base::StringPiece& key); // Add remove operation to the
+ // batch.
+ void Clear();
+
+ private:
+ friend class LevelDBDatabase;
+ LevelDBWriteBatch();
+
+ scoped_ptr<leveldb::WriteBatch> write_batch_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_WRITE_BATCH_H_
diff --git a/chromium/content/browser/indexed_db/list_set.h b/chromium/content/browser/indexed_db/list_set.h
new file mode 100644
index 00000000000..1884b4bb925
--- /dev/null
+++ b/chromium/content/browser/indexed_db/list_set.h
@@ -0,0 +1,161 @@
+// Copyright (c) 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 CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_
+#define CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_
+
+#include <algorithm>
+#include <iterator>
+#include <list>
+#include <set>
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+
+//
+// A container class that provides fast containment test (like a set)
+// but maintains insertion order for iteration (like list).
+//
+// Member types of value (primitives and objects by value), raw pointers
+// and scoped_refptr<> are supported.
+//
+template <typename T>
+class list_set {
+ public:
+ list_set() {}
+ list_set(const list_set<T>& other) : list_(other.list_), set_(other.set_) {}
+ list_set& operator=(const list_set<T>& other) {
+ list_ = other.list_;
+ set_ = other.set_;
+ return *this;
+ }
+
+ void insert(const T& elem) {
+ if (set_.find(elem) != set_.end())
+ return;
+ set_.insert(elem);
+ list_.push_back(elem);
+ }
+
+ void erase(const T& elem) {
+ if (set_.find(elem) == set_.end())
+ return;
+ set_.erase(elem);
+ typename std::list<T>::iterator it =
+ std::find(list_.begin(), list_.end(), elem);
+ DCHECK(it != list_.end());
+ list_.erase(it);
+ }
+
+ bool has(const T& elem) { return set_.find(elem) != set_.end(); }
+
+ size_t size() const {
+ DCHECK_EQ(list_.size(), set_.size());
+ return set_.size();
+ }
+
+ bool empty() const {
+ DCHECK_EQ(list_.empty(), set_.empty());
+ return set_.empty();
+ }
+
+ class const_iterator;
+
+ class iterator {
+ public:
+ typedef iterator self_type;
+ typedef T value_type;
+ typedef T& reference;
+ typedef T* pointer;
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef std::ptrdiff_t difference_type;
+
+ explicit inline iterator(typename std::list<T>::iterator it) : it_(it) {}
+ inline self_type& operator++() {
+ ++it_;
+ return *this;
+ }
+ inline self_type operator++(int /*ignored*/) {
+ self_type result(*this);
+ ++(*this);
+ return result;
+ }
+ inline self_type& operator--() {
+ --it_;
+ return *this;
+ }
+ inline self_type operator--(int /*ignored*/) {
+ self_type result(*this);
+ --(*this);
+ return result;
+ }
+ inline value_type& operator*() { return *it_; }
+ inline value_type* operator->() { return &(*it_); }
+ inline bool operator==(const iterator& rhs) const { return it_ == rhs.it_; }
+ inline bool operator!=(const iterator& rhs) const { return it_ != rhs.it_; }
+
+ inline operator const_iterator() const { return const_iterator(it_); }
+
+ private:
+ typename std::list<T>::iterator it_;
+ };
+
+ class const_iterator {
+ public:
+ typedef const_iterator self_type;
+ typedef T value_type;
+ typedef T& reference;
+ typedef T* pointer;
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef std::ptrdiff_t difference_type;
+
+ explicit inline const_iterator(typename std::list<T>::const_iterator it)
+ : it_(it) {}
+ inline self_type& operator++() {
+ ++it_;
+ return *this;
+ }
+ inline self_type operator++(int ignored) {
+ self_type result(*this);
+ ++(*this);
+ return result;
+ }
+ inline self_type& operator--() {
+ --it_;
+ return *this;
+ }
+ inline self_type operator--(int ignored) {
+ self_type result(*this);
+ --(*this);
+ return result;
+ }
+ inline const value_type& operator*() { return *it_; }
+ inline const value_type* operator->() { return &(*it_); }
+ inline bool operator==(const const_iterator& rhs) const {
+ return it_ == rhs.it_;
+ }
+ inline bool operator!=(const const_iterator& rhs) const {
+ return it_ != rhs.it_;
+ }
+
+ private:
+ typename std::list<T>::const_iterator it_;
+ };
+
+ iterator begin() { return iterator(list_.begin()); }
+ iterator end() { return iterator(list_.end()); }
+ const_iterator begin() const { return const_iterator(list_.begin()); }
+ const_iterator end() const { return const_iterator(list_.end()); }
+
+ private:
+ std::list<T> list_;
+ std::set<T> set_;
+};
+
+// Prevent instantiation of list_set<scoped_ptr<T>> as the current
+// implementation would fail.
+// TODO(jsbell): Support scoped_ptr through specialization.
+template <typename T>
+class list_set<scoped_ptr<T> >;
+
+#endif // CONTENT_BROWSER_INDEXED_DB_LIST_SET_H_
diff --git a/chromium/content/browser/indexed_db/list_set_unittest.cc b/chromium/content/browser/indexed_db/list_set_unittest.cc
new file mode 100644
index 00000000000..1bfafa290bd
--- /dev/null
+++ b/chromium/content/browser/indexed_db/list_set_unittest.cc
@@ -0,0 +1,240 @@
+// 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 "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/indexed_db/list_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(ListSetTest, ListSetIterator) {
+ list_set<int> set;
+ for (int i = 3; i > 0; --i)
+ set.insert(i);
+
+ list_set<int>::iterator it = set.begin();
+ EXPECT_EQ(3, *it);
+ ++it;
+ EXPECT_EQ(2, *it);
+ it++;
+ EXPECT_EQ(1, *it);
+ --it;
+ EXPECT_EQ(2, *it);
+ it--;
+ EXPECT_EQ(3, *it);
+ ++it;
+ EXPECT_EQ(2, *it);
+ it++;
+ EXPECT_EQ(1, *it);
+ ++it;
+ EXPECT_EQ(set.end(), it);
+}
+
+TEST(ListSetTest, ListSetConstIterator) {
+ list_set<int> set;
+ for (int i = 5; i > 0; --i)
+ set.insert(i);
+
+ const list_set<int>& ref = set;
+
+ list_set<int>::const_iterator it = ref.begin();
+ for (int i = 5; i > 0; --i) {
+ EXPECT_EQ(i, *it);
+ ++it;
+ }
+ EXPECT_EQ(ref.end(), it);
+}
+
+TEST(ListSetTest, ListSetPrimitive) {
+ list_set<int> set;
+ EXPECT_TRUE(set.empty());
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<int>::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+
+ for (int i = 5; i > 0; --i)
+ set.insert(i);
+ EXPECT_EQ(static_cast<size_t>(5), set.size());
+ EXPECT_FALSE(set.empty());
+
+ set.erase(3);
+ EXPECT_EQ(static_cast<size_t>(4), set.size());
+
+ EXPECT_TRUE(set.has(2));
+ set.erase(2);
+ EXPECT_FALSE(set.has(2));
+ EXPECT_EQ(static_cast<size_t>(3), set.size());
+
+ {
+ list_set<int>::iterator it = set.begin();
+ EXPECT_EQ(5, *it);
+ ++it;
+ EXPECT_EQ(4, *it);
+ ++it;
+ EXPECT_EQ(1, *it);
+ ++it;
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.erase(1);
+ set.erase(4);
+ set.erase(5);
+
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ EXPECT_TRUE(set.empty());
+ {
+ list_set<int>::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+}
+
+template <typename T>
+class Wrapped {
+ public:
+ explicit Wrapped(const T& value) : value_(value) {}
+ explicit Wrapped(const Wrapped<T>& other) : value_(other.value_) {}
+ Wrapped& operator=(const Wrapped<T>& rhs) {
+ value_ = rhs.value_;
+ return *this;
+ }
+ int value() const { return value_; }
+ bool operator<(const Wrapped<T>& rhs) const { return value_ < rhs.value_; }
+ bool operator==(const Wrapped<T>& rhs) const { return value_ == rhs.value_; }
+
+ private:
+ T value_;
+};
+
+TEST(ListSetTest, ListSetObject) {
+ list_set<Wrapped<int> > set;
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<Wrapped<int> >::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.insert(Wrapped<int>(0));
+ set.insert(Wrapped<int>(1));
+ set.insert(Wrapped<int>(2));
+
+ EXPECT_EQ(static_cast<size_t>(3), set.size());
+
+ {
+ list_set<Wrapped<int> >::iterator it = set.begin();
+ EXPECT_EQ(0, it->value());
+ ++it;
+ EXPECT_EQ(1, it->value());
+ ++it;
+ EXPECT_EQ(2, it->value());
+ ++it;
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.erase(Wrapped<int>(0));
+ set.erase(Wrapped<int>(1));
+ set.erase(Wrapped<int>(2));
+
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<Wrapped<int> >::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+}
+
+TEST(ListSetTest, ListSetPointer) {
+ scoped_ptr<Wrapped<int> > w0(new Wrapped<int>(0));
+ scoped_ptr<Wrapped<int> > w1(new Wrapped<int>(1));
+ scoped_ptr<Wrapped<int> > w2(new Wrapped<int>(2));
+
+ list_set<Wrapped<int>*> set;
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<Wrapped<int>*>::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.insert(w0.get());
+ set.insert(w1.get());
+ set.insert(w2.get());
+
+ EXPECT_EQ(static_cast<size_t>(3), set.size());
+
+ {
+ list_set<Wrapped<int>*>::iterator it = set.begin();
+ EXPECT_EQ(0, (*it)->value());
+ ++it;
+ EXPECT_EQ(1, (*it)->value());
+ ++it;
+ EXPECT_EQ(2, (*it)->value());
+ ++it;
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.erase(w0.get());
+ set.erase(w1.get());
+ set.erase(w2.get());
+
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<Wrapped<int>*>::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+}
+
+template <typename T>
+class RefCounted : public base::RefCounted<RefCounted<T> > {
+ public:
+ explicit RefCounted(const T& value) : value_(value) {}
+ T value() { return value_; }
+
+ private:
+ virtual ~RefCounted() {}
+ friend class base::RefCounted<RefCounted<T> >;
+ T value_;
+};
+
+TEST(ListSetTest, ListSetRefCounted) {
+ list_set<scoped_refptr<RefCounted<int> > > set;
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+
+ scoped_refptr<RefCounted<int> > r0(new RefCounted<int>(0));
+ scoped_refptr<RefCounted<int> > r1(new RefCounted<int>(1));
+ scoped_refptr<RefCounted<int> > r2(new RefCounted<int>(2));
+
+ set.insert(r0);
+ set.insert(r1);
+ set.insert(r2);
+
+ EXPECT_EQ(static_cast<size_t>(3), set.size());
+
+ {
+ list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin();
+ EXPECT_EQ(0, (*it)->value());
+ ++it;
+ EXPECT_EQ(1, (*it)->value());
+ ++it;
+ EXPECT_EQ(2, (*it)->value());
+ ++it;
+ EXPECT_EQ(set.end(), it);
+ }
+
+ set.erase(r0);
+ set.erase(r1);
+ set.erase(r2);
+
+ EXPECT_EQ(static_cast<size_t>(0), set.size());
+ {
+ list_set<scoped_refptr<RefCounted<int> > >::iterator it = set.begin();
+ EXPECT_EQ(set.end(), it);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/OWNERS b/chromium/content/browser/loader/OWNERS
new file mode 100644
index 00000000000..5b22ddc2ba5
--- /dev/null
+++ b/chromium/content/browser/loader/OWNERS
@@ -0,0 +1 @@
+simonjam@chromium.org
diff --git a/chromium/content/browser/loader/async_resource_handler.cc b/chromium/content/browser/loader/async_resource_handler.cc
new file mode 100644
index 00000000000..02e5552b901
--- /dev/null
+++ b/chromium/content/browser/loader/async_resource_handler.cc
@@ -0,0 +1,365 @@
+// 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 "content/browser/loader/async_resource_handler.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/containers/hash_tables.h"
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "base/memory/shared_memory.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/devtools/devtools_netlog_observer.h"
+#include "content/browser/host_zoom_map_impl.h"
+#include "content/browser/loader/resource_buffer.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/common/resource_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/common/resource_response.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+
+using base::TimeTicks;
+
+namespace content {
+namespace {
+
+static int kBufferSize = 1024 * 512;
+static int kMinAllocationSize = 1024 * 4;
+static int kMaxAllocationSize = 1024 * 32;
+
+void GetNumericArg(const std::string& name, int* result) {
+ const std::string& value =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(name);
+ if (!value.empty())
+ base::StringToInt(value, result);
+}
+
+void InitializeResourceBufferConstants() {
+ static bool did_init = false;
+ if (did_init)
+ return;
+ did_init = true;
+
+ GetNumericArg("resource-buffer-size", &kBufferSize);
+ GetNumericArg("resource-buffer-min-allocation-size", &kMinAllocationSize);
+ GetNumericArg("resource-buffer-max-allocation-size", &kMaxAllocationSize);
+}
+
+int CalcUsedPercentage(int bytes_read, int buffer_size) {
+ double ratio = static_cast<double>(bytes_read) / buffer_size;
+ return static_cast<int>(ratio * 100.0 + 0.5); // Round to nearest integer.
+}
+
+} // namespace
+
+class DependentIOBuffer : public net::WrappedIOBuffer {
+ public:
+ DependentIOBuffer(ResourceBuffer* backing, char* memory)
+ : net::WrappedIOBuffer(memory),
+ backing_(backing) {
+ }
+ private:
+ virtual ~DependentIOBuffer() {}
+ scoped_refptr<ResourceBuffer> backing_;
+};
+
+AsyncResourceHandler::AsyncResourceHandler(
+ ResourceMessageFilter* filter,
+ int routing_id,
+ net::URLRequest* request,
+ ResourceDispatcherHostImpl* rdh)
+ : ResourceMessageDelegate(request),
+ filter_(filter),
+ routing_id_(routing_id),
+ request_(request),
+ rdh_(rdh),
+ pending_data_count_(0),
+ allocation_size_(0),
+ did_defer_(false),
+ has_checked_for_sufficient_resources_(false),
+ sent_received_response_msg_(false),
+ sent_first_data_msg_(false) {
+ InitializeResourceBufferConstants();
+}
+
+AsyncResourceHandler::~AsyncResourceHandler() {
+ if (has_checked_for_sufficient_resources_)
+ rdh_->FinishedWithResourcesForRequest(request_);
+}
+
+bool AsyncResourceHandler::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(AsyncResourceHandler, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_FollowRedirect, OnFollowRedirect)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_DataReceived_ACK, OnDataReceivedACK)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void AsyncResourceHandler::OnFollowRedirect(
+ int request_id,
+ bool has_new_first_party_for_cookies,
+ const GURL& new_first_party_for_cookies) {
+ if (!request_->status().is_success()) {
+ DVLOG(1) << "OnFollowRedirect for invalid request";
+ return;
+ }
+
+ if (has_new_first_party_for_cookies)
+ request_->set_first_party_for_cookies(new_first_party_for_cookies);
+
+ ResumeIfDeferred();
+}
+
+void AsyncResourceHandler::OnDataReceivedACK(int request_id) {
+ if (pending_data_count_) {
+ --pending_data_count_;
+
+ buffer_->RecycleLeastRecentlyAllocated();
+ if (buffer_->CanAllocate())
+ ResumeIfDeferred();
+ }
+}
+
+bool AsyncResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return filter_->Send(new ResourceMsg_UploadProgress(routing_id_, request_id,
+ position, size));
+}
+
+bool AsyncResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) {
+ *defer = did_defer_ = true;
+
+ if (rdh_->delegate()) {
+ rdh_->delegate()->OnRequestRedirected(new_url, request_,
+ filter_->resource_context(),
+ response);
+ }
+
+ DevToolsNetLogObserver::PopulateResponseInfo(request_, response);
+ response->head.request_start = request_->creation_time();
+ response->head.response_start = TimeTicks::Now();
+ return filter_->Send(new ResourceMsg_ReceivedRedirect(
+ routing_id_, request_id, new_url, response->head));
+}
+
+bool AsyncResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ // For changes to the main frame, inform the renderer of the new URL's
+ // per-host settings before the request actually commits. This way the
+ // renderer will be able to set these precisely at the time the
+ // request commits, avoiding the possibility of e.g. zooming the old content
+ // or of having to layout the new content twice.
+
+ ResourceContext* resource_context = filter_->resource_context();
+ if (rdh_->delegate()) {
+ rdh_->delegate()->OnResponseStarted(
+ request_, resource_context, response, filter_.get());
+ }
+
+ DevToolsNetLogObserver::PopulateResponseInfo(request_, response);
+
+ HostZoomMap* host_zoom_map =
+ GetHostZoomMapForResourceContext(resource_context);
+
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
+ if (info->GetResourceType() == ResourceType::MAIN_FRAME && host_zoom_map) {
+ const GURL& request_url = request_->url();
+ filter_->Send(new ViewMsg_SetZoomLevelForLoadingURL(
+ info->GetRouteID(),
+ request_url, host_zoom_map->GetZoomLevelForHostAndScheme(
+ request_url.scheme(),
+ net::GetHostOrSpecFromURL(request_url))));
+ }
+
+ response->head.request_start = request_->creation_time();
+ response->head.response_start = TimeTicks::Now();
+ filter_->Send(new ResourceMsg_ReceivedResponse(
+ routing_id_, request_id, response->head));
+ sent_received_response_msg_ = true;
+
+ if (request_->response_info().metadata.get()) {
+ std::vector<char> copy(request_->response_info().metadata->data(),
+ request_->response_info().metadata->data() +
+ request_->response_info().metadata->size());
+ filter_->Send(new ResourceMsg_ReceivedCachedMetadata(
+ routing_id_, request_id, copy));
+ }
+
+ return true;
+}
+
+bool AsyncResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+bool AsyncResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ DCHECK_EQ(-1, min_size);
+
+ if (!EnsureResourceBufferIsInitialized())
+ return false;
+
+ DCHECK(buffer_->CanAllocate());
+ char* memory = buffer_->Allocate(&allocation_size_);
+ CHECK(memory);
+
+ *buf = new DependentIOBuffer(buffer_.get(), memory);
+ *buf_size = allocation_size_;
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.AsyncResourceHandler_SharedIOBuffer_Alloc",
+ *buf_size, 0, kMaxAllocationSize, 100);
+ return true;
+}
+
+bool AsyncResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ if (!bytes_read)
+ return true;
+
+ buffer_->ShrinkLastAllocation(bytes_read);
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.AsyncResourceHandler_SharedIOBuffer_Used",
+ bytes_read, 0, kMaxAllocationSize, 100);
+ UMA_HISTOGRAM_PERCENTAGE(
+ "Net.AsyncResourceHandler_SharedIOBuffer_UsedPercentage",
+ CalcUsedPercentage(bytes_read, allocation_size_));
+
+ if (!sent_first_data_msg_) {
+ base::SharedMemoryHandle handle;
+ int size;
+ if (!buffer_->ShareToProcess(filter_->PeerHandle(), &handle, &size))
+ return false;
+ filter_->Send(
+ new ResourceMsg_SetDataBuffer(routing_id_, request_id, handle, size,
+ filter_->peer_pid()));
+ sent_first_data_msg_ = true;
+ }
+
+ int data_offset = buffer_->GetLastAllocationOffset();
+ int encoded_data_length =
+ DevToolsNetLogObserver::GetAndResetEncodedDataLength(request_);
+
+ filter_->Send(
+ new ResourceMsg_DataReceived(routing_id_, request_id, data_offset,
+ bytes_read, encoded_data_length));
+ ++pending_data_count_;
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.AsyncResourceHandler_PendingDataCount",
+ pending_data_count_, 0, 100, 100);
+
+ if (!buffer_->CanAllocate()) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.AsyncResourceHandler_PendingDataCount_WhenFull",
+ pending_data_count_, 0, 100, 100);
+ *defer = did_defer_ = true;
+ }
+
+ return true;
+}
+
+void AsyncResourceHandler::OnDataDownloaded(
+ int request_id, int bytes_downloaded) {
+ filter_->Send(new ResourceMsg_DataDownloaded(
+ routing_id_, request_id, bytes_downloaded));
+}
+
+bool AsyncResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ // If we crash here, figure out what URL the renderer was requesting.
+ // http://crbug.com/107692
+ char url_buf[128];
+ base::strlcpy(url_buf, request_->url().spec().c_str(), arraysize(url_buf));
+ base::debug::Alias(url_buf);
+
+ // TODO(gavinp): Remove this CHECK when we figure out the cause of
+ // http://crbug.com/124680 . This check mirrors closely check in
+ // WebURLLoaderImpl::OnCompletedRequest that routes this message to a WebCore
+ // ResourceHandleInternal which asserts on its state and crashes. By crashing
+ // when the message is sent, we should get better crash reports.
+ CHECK(status.status() != net::URLRequestStatus::SUCCESS ||
+ sent_received_response_msg_);
+
+ TimeTicks completion_time = TimeTicks::Now();
+
+ int error_code = status.error();
+ bool was_ignored_by_handler =
+ ResourceRequestInfoImpl::ForRequest(request_)->WasIgnoredByHandler();
+
+ DCHECK(status.status() != net::URLRequestStatus::IO_PENDING);
+ // If this check fails, then we're in an inconsistent state because all
+ // requests ignored by the handler should be canceled (which should result in
+ // the ERR_ABORTED error code).
+ DCHECK(!was_ignored_by_handler || error_code == net::ERR_ABORTED);
+
+ // TODO(mkosiba): Fix up cases where we create a URLRequestStatus
+ // with a status() != SUCCESS and an error_code() == net::OK.
+ if (status.status() == net::URLRequestStatus::CANCELED &&
+ error_code == net::OK) {
+ error_code = net::ERR_ABORTED;
+ } else if (status.status() == net::URLRequestStatus::FAILED &&
+ error_code == net::OK) {
+ error_code = net::ERR_FAILED;
+ }
+
+ filter_->Send(new ResourceMsg_RequestComplete(routing_id_,
+ request_id,
+ error_code,
+ was_ignored_by_handler,
+ security_info,
+ completion_time));
+ return true;
+}
+
+bool AsyncResourceHandler::EnsureResourceBufferIsInitialized() {
+ if (buffer_.get() && buffer_->IsInitialized())
+ return true;
+
+ if (!has_checked_for_sufficient_resources_) {
+ has_checked_for_sufficient_resources_ = true;
+ if (!rdh_->HasSufficientResourcesForRequest(request_)) {
+ controller()->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
+ return false;
+ }
+ }
+
+ buffer_ = new ResourceBuffer();
+ return buffer_->Initialize(kBufferSize,
+ kMinAllocationSize,
+ kMaxAllocationSize);
+}
+
+void AsyncResourceHandler::ResumeIfDeferred() {
+ if (did_defer_) {
+ did_defer_ = false;
+ controller()->Resume();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/async_resource_handler.h b/chromium/content/browser/loader/async_resource_handler.h
new file mode 100644
index 00000000000..5f36112a9bc
--- /dev/null
+++ b/chromium/content/browser/loader/async_resource_handler.h
@@ -0,0 +1,99 @@
+// 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 CONTENT_BROWSER_LOADER_ASYNC_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_ASYNC_RESOURCE_HANDLER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/loader/resource_handler.h"
+#include "content/browser/loader/resource_message_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+class ResourceBuffer;
+class ResourceDispatcherHostImpl;
+class ResourceMessageFilter;
+class SharedIOBuffer;
+
+// Used to complete an asynchronous resource request in response to resource
+// load events from the resource dispatcher host.
+class AsyncResourceHandler : public ResourceHandler,
+ public ResourceMessageDelegate {
+ public:
+ AsyncResourceHandler(ResourceMessageFilter* filter,
+ int routing_id,
+ net::URLRequest* request,
+ ResourceDispatcherHostImpl* rdh);
+ virtual ~AsyncResourceHandler();
+
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // ResourceHandler implementation:
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+ virtual void OnDataDownloaded(int request_id,
+ int bytes_downloaded) OVERRIDE;
+
+ private:
+ // IPC message handlers:
+ void OnFollowRedirect(int request_id,
+ bool has_new_first_party_for_cookies,
+ const GURL& new_first_party_for_cookies);
+ void OnDataReceivedACK(int request_id);
+
+ bool EnsureResourceBufferIsInitialized();
+ void ResumeIfDeferred();
+
+ scoped_refptr<ResourceBuffer> buffer_;
+ scoped_refptr<ResourceMessageFilter> filter_;
+ int routing_id_;
+ net::URLRequest* request_;
+ ResourceDispatcherHostImpl* rdh_;
+
+ // Number of messages we've sent to the renderer that we haven't gotten an
+ // ACK for. This allows us to avoid having too many messages in flight.
+ int pending_data_count_;
+
+ int allocation_size_;
+
+ bool did_defer_;
+
+ bool has_checked_for_sufficient_resources_;
+ bool sent_received_response_msg_;
+ bool sent_first_data_msg_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_ASYNC_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/buffered_resource_handler.cc b/chromium/content/browser/loader/buffered_resource_handler.cc
new file mode 100644
index 00000000000..a3afb3fa7b6
--- /dev/null
+++ b/chromium/content/browser/loader/buffered_resource_handler.cc
@@ -0,0 +1,473 @@
+// 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 "content/browser/loader/buffered_resource_handler.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/loader/certificate_resource_handler.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_save_info.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/common/resource_response.h"
+#include "content/public/common/webplugininfo.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_sniffer.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_content_disposition.h"
+#include "net/http/http_response_headers.h"
+
+namespace content {
+
+namespace {
+
+void RecordSnifferMetrics(bool sniffing_blocked,
+ bool we_would_like_to_sniff,
+ const std::string& mime_type) {
+ static base::HistogramBase* nosniff_usage(NULL);
+ if (!nosniff_usage)
+ nosniff_usage = base::BooleanHistogram::FactoryGet(
+ "nosniff.usage", base::HistogramBase::kUmaTargetedHistogramFlag);
+ nosniff_usage->AddBoolean(sniffing_blocked);
+
+ if (sniffing_blocked) {
+ static base::HistogramBase* nosniff_otherwise(NULL);
+ if (!nosniff_otherwise)
+ nosniff_otherwise = base::BooleanHistogram::FactoryGet(
+ "nosniff.otherwise", base::HistogramBase::kUmaTargetedHistogramFlag);
+ nosniff_otherwise->AddBoolean(we_would_like_to_sniff);
+
+ static base::HistogramBase* nosniff_empty_mime_type(NULL);
+ if (!nosniff_empty_mime_type)
+ nosniff_empty_mime_type = base::BooleanHistogram::FactoryGet(
+ "nosniff.empty_mime_type",
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ nosniff_empty_mime_type->AddBoolean(mime_type.empty());
+ }
+}
+
+// Used to write into an existing IOBuffer at a given offset.
+class DependentIOBuffer : public net::WrappedIOBuffer {
+ public:
+ DependentIOBuffer(net::IOBuffer* buf, int offset)
+ : net::WrappedIOBuffer(buf->data() + offset),
+ buf_(buf) {
+ }
+
+ private:
+ virtual ~DependentIOBuffer() {}
+
+ scoped_refptr<net::IOBuffer> buf_;
+};
+
+} // namespace
+
+BufferedResourceHandler::BufferedResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler,
+ ResourceDispatcherHostImpl* host,
+ net::URLRequest* request)
+ : LayeredResourceHandler(next_handler.Pass()),
+ state_(STATE_STARTING),
+ host_(host),
+ request_(request),
+ read_buffer_size_(0),
+ bytes_read_(0),
+ must_download_(false),
+ must_download_is_set_(false),
+ weak_ptr_factory_(this) {
+}
+
+BufferedResourceHandler::~BufferedResourceHandler() {
+}
+
+void BufferedResourceHandler::SetController(ResourceController* controller) {
+ ResourceHandler::SetController(controller);
+
+ // Downstream handlers see us as their ResourceController, which allows us to
+ // consume part or all of the resource response, and then later replay it to
+ // downstream handler.
+ DCHECK(next_handler_.get());
+ next_handler_->SetController(this);
+}
+
+bool BufferedResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ response_ = response;
+
+ // TODO(darin): It is very odd to special-case 304 responses at this level.
+ // We do so only because the code always has, see r24977 and r29355. The
+ // fact that 204 is no longer special-cased this way suggests that 304 need
+ // not be special-cased either.
+ //
+ // The network stack only forwards 304 responses that were not received in
+ // response to a conditional request (i.e., If-Modified-Since). Other 304
+ // responses end up being translated to 200 or whatever the cached response
+ // code happens to be. It should be very rare to see a 304 at this level.
+
+ if (!(response_->head.headers.get() &&
+ response_->head.headers->response_code() == 304)) {
+ if (ShouldSniffContent()) {
+ state_ = STATE_BUFFERING;
+ return true;
+ }
+
+ if (response_->head.mime_type.empty()) {
+ // Ugg. The server told us not to sniff the content but didn't give us
+ // a mime type. What's a browser to do? Turns out, we're supposed to
+ // treat the response as "text/plain". This is the most secure option.
+ response_->head.mime_type.assign("text/plain");
+ }
+
+ // Treat feed types as text/plain.
+ if (response_->head.mime_type == "application/rss+xml" ||
+ response_->head.mime_type == "application/atom+xml") {
+ response_->head.mime_type.assign("text/plain");
+ }
+ }
+
+ state_ = STATE_PROCESSING;
+ return ProcessResponse(defer);
+}
+
+// We'll let the original event handler provide a buffer, and reuse it for
+// subsequent reads until we're done buffering.
+bool BufferedResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ if (state_ == STATE_STREAMING)
+ return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
+
+ DCHECK_EQ(-1, min_size);
+
+ if (read_buffer_.get()) {
+ CHECK_LT(bytes_read_, read_buffer_size_);
+ *buf = new DependentIOBuffer(read_buffer_.get(), bytes_read_);
+ *buf_size = read_buffer_size_ - bytes_read_;
+ } else {
+ if (!next_handler_->OnWillRead(request_id, buf, buf_size, min_size))
+ return false;
+
+ read_buffer_ = *buf;
+ read_buffer_size_ = *buf_size;
+ DCHECK_GE(read_buffer_size_, net::kMaxBytesToSniff * 2);
+ }
+ return true;
+}
+
+bool BufferedResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ if (state_ == STATE_STREAMING)
+ return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
+
+ DCHECK_EQ(state_, STATE_BUFFERING);
+ bytes_read_ += bytes_read;
+
+ if (!DetermineMimeType() && (bytes_read > 0))
+ return true; // Needs more data, so keep buffering.
+
+ state_ = STATE_PROCESSING;
+ return ProcessResponse(defer);
+}
+
+bool BufferedResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ // Upon completion, act like a pass-through handler in case the downstream
+ // handler defers OnResponseCompleted.
+ state_ = STATE_STREAMING;
+
+ return next_handler_->OnResponseCompleted(request_id, status, security_info);
+}
+
+void BufferedResourceHandler::Resume() {
+ switch (state_) {
+ case STATE_BUFFERING:
+ case STATE_PROCESSING:
+ NOTREACHED();
+ break;
+ case STATE_REPLAYING:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferedResourceHandler::CallReplayReadCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+ break;
+ case STATE_STARTING:
+ case STATE_STREAMING:
+ controller()->Resume();
+ break;
+ }
+}
+
+void BufferedResourceHandler::Cancel() {
+ controller()->Cancel();
+}
+
+void BufferedResourceHandler::CancelAndIgnore() {
+ controller()->CancelAndIgnore();
+}
+
+void BufferedResourceHandler::CancelWithError(int error_code) {
+ controller()->CancelWithError(error_code);
+}
+
+bool BufferedResourceHandler::ProcessResponse(bool* defer) {
+ DCHECK_EQ(STATE_PROCESSING, state_);
+
+ // TODO(darin): Stop special-casing 304 responses.
+ if (!(response_->head.headers.get() &&
+ response_->head.headers->response_code() == 304)) {
+ if (!SelectNextHandler(defer))
+ return false;
+ if (*defer)
+ return true;
+ }
+
+ state_ = STATE_REPLAYING;
+
+ int request_id = ResourceRequestInfo::ForRequest(request_)->GetRequestID();
+ if (!next_handler_->OnResponseStarted(request_id, response_.get(), defer))
+ return false;
+
+ if (!read_buffer_.get()) {
+ state_ = STATE_STREAMING;
+ return true;
+ }
+
+ if (!*defer)
+ return ReplayReadCompleted(defer);
+
+ return true;
+}
+
+bool BufferedResourceHandler::ShouldSniffContent() {
+ const std::string& mime_type = response_->head.mime_type;
+
+ std::string content_type_options;
+ request_->GetResponseHeaderByName("x-content-type-options",
+ &content_type_options);
+
+ bool sniffing_blocked =
+ LowerCaseEqualsASCII(content_type_options, "nosniff");
+ bool we_would_like_to_sniff =
+ net::ShouldSniffMimeType(request_->url(), mime_type);
+
+ RecordSnifferMetrics(sniffing_blocked, we_would_like_to_sniff, mime_type);
+
+ if (!sniffing_blocked && we_would_like_to_sniff) {
+ // We're going to look at the data before deciding what the content type
+ // is. That means we need to delay sending the ResponseStarted message
+ // over the IPC channel.
+ VLOG(1) << "To buffer: " << request_->url().spec();
+ return true;
+ }
+
+ return false;
+}
+
+bool BufferedResourceHandler::DetermineMimeType() {
+ DCHECK_EQ(STATE_BUFFERING, state_);
+
+ const std::string& type_hint = response_->head.mime_type;
+
+ std::string new_type;
+ bool made_final_decision =
+ net::SniffMimeType(read_buffer_->data(), bytes_read_, request_->url(),
+ type_hint, &new_type);
+
+ // SniffMimeType() returns false if there is not enough data to determine
+ // the mime type. However, even if it returns false, it returns a new type
+ // that is probably better than the current one.
+ response_->head.mime_type.assign(new_type);
+
+ return made_final_decision;
+}
+
+bool BufferedResourceHandler::SelectNextHandler(bool* defer) {
+ DCHECK(!response_->head.mime_type.empty());
+
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_);
+ const std::string& mime_type = response_->head.mime_type;
+
+ if (net::IsSupportedCertificateMimeType(mime_type)) {
+ // Install certificate file.
+ scoped_ptr<ResourceHandler> handler(
+ new CertificateResourceHandler(request_,
+ info->GetChildID(),
+ info->GetRouteID()));
+ return UseAlternateNextHandler(handler.Pass());
+ }
+
+ if (!info->allow_download())
+ return true;
+
+ bool must_download = MustDownload();
+ if (!must_download) {
+ if (net::IsSupportedMimeType(mime_type))
+ return true;
+
+ scoped_ptr<ResourceHandler> handler(
+ host_->MaybeInterceptAsStream(request_, response_.get()));
+ if (handler)
+ return UseAlternateNextHandler(handler.Pass());
+
+#if defined(ENABLE_PLUGINS)
+ bool stale;
+ bool has_plugin = HasSupportingPlugin(&stale);
+ if (stale) {
+ // Refresh the plugins asynchronously.
+ PluginServiceImpl::GetInstance()->GetPlugins(
+ base::Bind(&BufferedResourceHandler::OnPluginsLoaded,
+ weak_ptr_factory_.GetWeakPtr()));
+ *defer = true;
+ return true;
+ }
+ if (has_plugin)
+ return true;
+#endif
+ }
+
+ // Install download handler
+ info->set_is_download(true);
+ scoped_ptr<ResourceHandler> handler(
+ host_->CreateResourceHandlerForDownload(
+ request_,
+ true, // is_content_initiated
+ must_download,
+ content::DownloadItem::kInvalidId,
+ scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo()),
+ DownloadUrlParameters::OnStartedCallback()));
+ return UseAlternateNextHandler(handler.Pass());
+}
+
+bool BufferedResourceHandler::UseAlternateNextHandler(
+ scoped_ptr<ResourceHandler> new_handler) {
+ if (response_->head.headers.get() && // Can be NULL if FTP.
+ response_->head.headers->response_code() / 100 != 2) {
+ // The response code indicates that this is an error page, but we don't
+ // know how to display the content. We follow Firefox here and show our
+ // own error page instead of triggering a download.
+ // TODO(abarth): We should abstract the response_code test, but this kind
+ // of check is scattered throughout our codebase.
+ request_->CancelWithError(net::ERR_FILE_NOT_FOUND);
+ return false;
+ }
+
+ int request_id = ResourceRequestInfo::ForRequest(request_)->GetRequestID();
+
+ // Inform the original ResourceHandler that this will be handled entirely by
+ // the new ResourceHandler.
+ // TODO(darin): We should probably check the return values of these.
+ bool defer_ignored = false;
+ next_handler_->OnResponseStarted(request_id, response_.get(), &defer_ignored);
+ DCHECK(!defer_ignored);
+ net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
+ net::ERR_ABORTED);
+ next_handler_->OnResponseCompleted(request_id, status, std::string());
+
+ // This is handled entirely within the new ResourceHandler, so just reset the
+ // original ResourceHandler.
+ next_handler_ = new_handler.Pass();
+ next_handler_->SetController(this);
+
+ return CopyReadBufferToNextHandler(request_id);
+}
+
+bool BufferedResourceHandler::ReplayReadCompleted(bool* defer) {
+ DCHECK(read_buffer_.get());
+
+ int request_id = ResourceRequestInfo::ForRequest(request_)->GetRequestID();
+ bool result = next_handler_->OnReadCompleted(request_id, bytes_read_, defer);
+
+ read_buffer_ = NULL;
+ read_buffer_size_ = 0;
+ bytes_read_ = 0;
+
+ state_ = STATE_STREAMING;
+
+ return result;
+}
+
+void BufferedResourceHandler::CallReplayReadCompleted() {
+ bool defer = false;
+ if (!ReplayReadCompleted(&defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ state_ = STATE_STREAMING;
+ controller()->Resume();
+ }
+}
+
+bool BufferedResourceHandler::MustDownload() {
+ if (must_download_is_set_)
+ return must_download_;
+
+ must_download_is_set_ = true;
+
+ std::string disposition;
+ request_->GetResponseHeaderByName("content-disposition", &disposition);
+ if (!disposition.empty() &&
+ net::HttpContentDisposition(disposition, std::string()).is_attachment()) {
+ must_download_ = true;
+ } else if (host_->delegate() &&
+ host_->delegate()->ShouldForceDownloadResource(
+ request_->url(), response_->head.mime_type)) {
+ must_download_ = true;
+ } else {
+ must_download_ = false;
+ }
+
+ return must_download_;
+}
+
+bool BufferedResourceHandler::HasSupportingPlugin(bool* stale) {
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_);
+
+ bool allow_wildcard = false;
+ WebPluginInfo plugin;
+ return PluginServiceImpl::GetInstance()->GetPluginInfo(
+ info->GetChildID(), info->GetRouteID(), info->GetContext(),
+ request_->url(), GURL(), response_->head.mime_type, allow_wildcard,
+ stale, &plugin, NULL);
+}
+
+bool BufferedResourceHandler::CopyReadBufferToNextHandler(int request_id) {
+ if (!bytes_read_)
+ return true;
+
+ net::IOBuffer* buf = NULL;
+ int buf_len = 0;
+ if (!next_handler_->OnWillRead(request_id, &buf, &buf_len, bytes_read_))
+ return false;
+
+ CHECK((buf_len >= bytes_read_) && (bytes_read_ >= 0));
+ memcpy(buf->data(), read_buffer_->data(), bytes_read_);
+ return true;
+}
+
+void BufferedResourceHandler::OnPluginsLoaded(
+ const std::vector<WebPluginInfo>& plugins) {
+ bool defer = false;
+ if (!ProcessResponse(&defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ controller()->Resume();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/buffered_resource_handler.h b/chromium/content/browser/loader/buffered_resource_handler.h
new file mode 100644
index 00000000000..c1a7a6e8120
--- /dev/null
+++ b/chromium/content/browser/loader/buffered_resource_handler.h
@@ -0,0 +1,113 @@
+// 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 CONTENT_BROWSER_LOADER_BUFFERED_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_BUFFERED_RESOURCE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "content/browser/loader/layered_resource_handler.h"
+#include "content/public/browser/resource_controller.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+class ResourceDispatcherHostImpl;
+struct WebPluginInfo;
+
+// Used to buffer a request until enough data has been received.
+class BufferedResourceHandler
+ : public LayeredResourceHandler,
+ public ResourceController {
+ public:
+ BufferedResourceHandler(scoped_ptr<ResourceHandler> next_handler,
+ ResourceDispatcherHostImpl* host,
+ net::URLRequest* request);
+ virtual ~BufferedResourceHandler();
+
+ private:
+ // ResourceHandler implementation:
+ virtual void SetController(ResourceController* controller) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+
+ // ResourceController implementation:
+ virtual void Resume() OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual void CancelAndIgnore() OVERRIDE;
+ virtual void CancelWithError(int error_code) OVERRIDE;
+
+ bool ProcessResponse(bool* defer);
+
+ bool ShouldSniffContent();
+ bool DetermineMimeType();
+ bool SelectNextHandler(bool* defer);
+ bool UseAlternateNextHandler(scoped_ptr<ResourceHandler> handler);
+
+ bool ReplayReadCompleted(bool* defer);
+ void CallReplayReadCompleted();
+
+ bool MustDownload();
+ bool HasSupportingPlugin(bool* is_stale);
+
+ // Copies data from |read_buffer_| to |next_handler_|.
+ bool CopyReadBufferToNextHandler(int request_id);
+
+ // Called on the IO thread once the list of plugins has been loaded.
+ void OnPluginsLoaded(const std::vector<WebPluginInfo>& plugins);
+
+ enum State {
+ STATE_STARTING,
+
+ // In this state, we are filling read_buffer_ with data for the purpose
+ // of sniffing the mime type of the response.
+ STATE_BUFFERING,
+
+ // In this state, we are select an appropriate downstream ResourceHandler
+ // based on the mime type of the response. We are also potentially waiting
+ // for plugins to load so that we can determine if a plugin is available to
+ // handle the mime type.
+ STATE_PROCESSING,
+
+ // In this state, we are replaying buffered events (OnResponseStarted and
+ // OnReadCompleted) to the downstream ResourceHandler.
+ STATE_REPLAYING,
+
+ // In this state, we are just a blind pass-through ResourceHandler.
+ STATE_STREAMING
+ };
+ State state_;
+
+ scoped_refptr<ResourceResponse> response_;
+ ResourceDispatcherHostImpl* host_;
+ net::URLRequest* request_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ int read_buffer_size_;
+ int bytes_read_;
+
+ bool must_download_;
+ bool must_download_is_set_;
+
+ base::WeakPtrFactory<BufferedResourceHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_BUFFERED_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/certificate_resource_handler.cc b/chromium/content/browser/loader/certificate_resource_handler.cc
new file mode 100644
index 00000000000..9d995428b61
--- /dev/null
+++ b/chromium/content/browser/loader/certificate_resource_handler.cc
@@ -0,0 +1,151 @@
+// 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 "content/browser/loader/certificate_resource_handler.h"
+
+#include "base/strings/string_util.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/resource_response.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_sniffer.h"
+#include "net/base/mime_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+
+CertificateResourceHandler::CertificateResourceHandler(
+ net::URLRequest* request,
+ int render_process_host_id,
+ int render_view_id)
+ : request_(request),
+ content_length_(0),
+ read_buffer_(NULL),
+ resource_buffer_(NULL),
+ render_process_host_id_(render_process_host_id),
+ render_view_id_(render_view_id),
+ cert_type_(net::CERTIFICATE_MIME_TYPE_UNKNOWN) {
+}
+
+CertificateResourceHandler::~CertificateResourceHandler() {
+}
+
+bool CertificateResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return true;
+}
+
+bool CertificateResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* resp,
+ bool* defer) {
+ url_ = url;
+ return true;
+}
+
+bool CertificateResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* resp,
+ bool* defer) {
+ cert_type_ = net::GetCertificateMimeTypeForMimeType(resp->head.mime_type);
+ return cert_type_ != net::CERTIFICATE_MIME_TYPE_UNKNOWN;
+}
+
+bool CertificateResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+bool CertificateResourceHandler::OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) {
+ static const int kReadBufSize = 32768;
+
+ // TODO(gauravsh): Should we use 'min_size' here?
+ DCHECK(buf && buf_size);
+ if (!read_buffer_.get()) {
+ read_buffer_ = new net::IOBuffer(kReadBufSize);
+ }
+ *buf = read_buffer_.get();
+ *buf_size = kReadBufSize;
+
+ return true;
+}
+
+bool CertificateResourceHandler::OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) {
+ if (!bytes_read)
+ return true;
+
+ // We have more data to read.
+ DCHECK(read_buffer_.get());
+ content_length_ += bytes_read;
+
+ // Release the ownership of the buffer, and store a reference
+ // to it. A new one will be allocated in OnWillRead().
+ net::IOBuffer* buffer = NULL;
+ read_buffer_.swap(&buffer);
+ // TODO(gauravsh): Should this be handled by a separate thread?
+ buffer_.push_back(std::make_pair(buffer, bytes_read));
+
+ return true;
+}
+
+bool CertificateResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& urs,
+ const std::string& sec_info) {
+ if (urs.status() != net::URLRequestStatus::SUCCESS)
+ return false;
+
+ AssembleResource();
+
+ const void* content_bytes = NULL;
+ if (resource_buffer_.get())
+ content_bytes = resource_buffer_->data();
+
+ // Note that it's up to the browser to verify that the certificate
+ // data is well-formed.
+ GetContentClient()->browser()->AddCertificate(
+ request_, cert_type_, content_bytes, content_length_,
+ render_process_host_id_, render_view_id_);
+
+ return true;
+}
+
+void CertificateResourceHandler::AssembleResource() {
+ // 0-length IOBuffers are not allowed.
+ if (content_length_ == 0) {
+ resource_buffer_ = NULL;
+ return;
+ }
+
+ // Create the new buffer.
+ resource_buffer_ = new net::IOBuffer(content_length_);
+
+ // Copy the data into it.
+ size_t bytes_copied = 0;
+ for (size_t i = 0; i < buffer_.size(); ++i) {
+ net::IOBuffer* data = buffer_[i].first.get();
+ size_t data_len = buffer_[i].second;
+ DCHECK(data != NULL);
+ DCHECK_LE(bytes_copied + data_len, content_length_);
+ memcpy(resource_buffer_->data() + bytes_copied, data->data(), data_len);
+ bytes_copied += data_len;
+ }
+ DCHECK_EQ(content_length_, bytes_copied);
+}
+
+void CertificateResourceHandler::OnDataDownloaded(
+ int request_id,
+ int bytes_downloaded) {
+ NOTREACHED();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/certificate_resource_handler.h b/chromium/content/browser/loader/certificate_resource_handler.h
new file mode 100644
index 00000000000..b1bd8f4a2fa
--- /dev/null
+++ b/chromium/content/browser/loader/certificate_resource_handler.h
@@ -0,0 +1,100 @@
+// 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 CONTENT_BROWSER_LOADER_CERTIFICATE_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_CERTIFICATE_RESOURCE_HANDLER_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/loader/resource_handler.h"
+#include "net/base/mime_util.h"
+#include "url/gurl.h"
+
+namespace net {
+class IOBuffer;
+class URLRequest;
+class URLRequestStatus;
+} // namespace net
+
+namespace content {
+
+// This class handles certificate mime types such as:
+// - "application/x-x509-user-cert"
+// - "application/x-x509-ca-cert"
+// - "application/x-pkcs12"
+//
+class CertificateResourceHandler : public ResourceHandler {
+ public:
+ CertificateResourceHandler(net::URLRequest* request,
+ int render_process_host_id,
+ int render_view_id);
+ virtual ~CertificateResourceHandler();
+
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+
+ // Not needed, as this event handler ought to be the final resource.
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* resp,
+ bool* defer) OVERRIDE;
+
+ // Check if this indeed an X509 cert.
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* resp,
+ bool* defer) OVERRIDE;
+
+ // Pass-through implementation.
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+
+ // Create a new buffer to store received data.
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+
+ // A read was completed, maybe allocate a new buffer for further data.
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+
+ // Done downloading the certificate.
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& urs,
+ const std::string& sec_info) OVERRIDE;
+
+ // N/A to cert downloading.
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ private:
+ typedef std::vector<std::pair<scoped_refptr<net::IOBuffer>,
+ size_t> > ContentVector;
+
+ void AssembleResource();
+
+ GURL url_;
+ net::URLRequest* request_;
+ size_t content_length_;
+ ContentVector buffer_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ scoped_refptr<net::IOBuffer> resource_buffer_; // Downloaded certificate.
+ // The id of the |RenderProcessHost| which started the download.
+ int render_process_host_id_;
+ // The id of the |RenderView| which started the download.
+ int render_view_id_;
+ net::CertificateMimeType cert_type_;
+ DISALLOW_COPY_AND_ASSIGN(CertificateResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_CERTIFICATE_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/cross_site_resource_handler.cc b/chromium/content/browser/loader/cross_site_resource_handler.cc
new file mode 100644
index 00000000000..6b735f6d4fd
--- /dev/null
+++ b/chromium/content/browser/loader/cross_site_resource_handler.cc
@@ -0,0 +1,219 @@
+// 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 "content/browser/loader/cross_site_resource_handler.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/resource_controller.h"
+#include "content/public/common/resource_response.h"
+#include "net/http/http_response_headers.h"
+
+namespace content {
+
+namespace {
+
+void OnCrossSiteResponseHelper(int render_process_id,
+ int render_view_id,
+ int request_id) {
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(render_process_id,
+ render_view_id);
+ if (rvh && rvh->GetDelegate()->GetRendererManagementDelegate()) {
+ rvh->GetDelegate()->GetRendererManagementDelegate()->OnCrossSiteResponse(
+ rvh, GlobalRequestID(render_process_id, request_id));
+ }
+}
+
+} // namespace
+
+CrossSiteResourceHandler::CrossSiteResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler,
+ int render_process_host_id,
+ int render_view_id,
+ net::URLRequest* request)
+ : LayeredResourceHandler(next_handler.Pass()),
+ render_process_host_id_(render_process_host_id),
+ render_view_id_(render_view_id),
+ request_(request),
+ has_started_response_(false),
+ in_cross_site_transition_(false),
+ request_id_(-1),
+ completed_during_transition_(false),
+ did_defer_(false),
+ completed_status_(),
+ response_(NULL) {
+}
+
+CrossSiteResourceHandler::~CrossSiteResourceHandler() {
+ // Cleanup back-pointer stored on the request info.
+ ResourceRequestInfoImpl::ForRequest(request_)->set_cross_site_handler(NULL);
+}
+
+bool CrossSiteResourceHandler::OnRequestRedirected(
+ int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) {
+ // We should not have started the transition before being redirected.
+ DCHECK(!in_cross_site_transition_);
+ return next_handler_->OnRequestRedirected(
+ request_id, new_url, response, defer);
+}
+
+bool CrossSiteResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ // At this point, we know that the response is safe to send back to the
+ // renderer: it is not a download, and it has passed the SSL and safe
+ // browsing checks.
+ // We should not have already started the transition before now.
+ DCHECK(!in_cross_site_transition_);
+ has_started_response_ = true;
+
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_);
+
+ // If this is a download, just pass the response through without doing a
+ // cross-site check. The renderer will see it is a download and abort the
+ // request.
+ //
+ // Similarly, HTTP 204 (No Content) responses leave us showing the previous
+ // page. We should allow the navigation to finish without running the unload
+ // handler or swapping in the pending RenderViewHost.
+ //
+ // In both cases, the pending RenderViewHost will stick around until the next
+ // cross-site navigation, since we are unable to tell when to destroy it.
+ // See RenderViewHostManager::RendererAbortedProvisionalLoad.
+ if (info->is_download() || (response->head.headers.get() &&
+ response->head.headers->response_code() == 204)) {
+ return next_handler_->OnResponseStarted(request_id, response, defer);
+ }
+
+ // Tell the renderer to run the onunload event handler.
+ StartCrossSiteTransition(request_id, response);
+
+ // Defer loading until after the onunload event handler has run.
+ did_defer_ = *defer = true;
+ return true;
+}
+
+bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) {
+ CHECK(!in_cross_site_transition_);
+ return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
+}
+
+bool CrossSiteResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ if (!in_cross_site_transition_) {
+ if (has_started_response_ ||
+ status.status() != net::URLRequestStatus::FAILED) {
+ // We've already completed the transition or we're canceling the request,
+ // so just pass it through.
+ return next_handler_->OnResponseCompleted(request_id, status,
+ security_info);
+ }
+
+ // An error occured, we should wait now for the cross-site transition,
+ // so that the error message (e.g., 404) can be displayed to the user.
+ // Also continue with the logic below to remember that we completed
+ // during the cross-site transition.
+ StartCrossSiteTransition(request_id, NULL);
+ }
+
+ // We have to buffer the call until after the transition completes.
+ completed_during_transition_ = true;
+ completed_status_ = status;
+ completed_security_info_ = security_info;
+
+ // Return false to tell RDH not to notify the world or clean up the
+ // pending request. We will do so in ResumeResponse.
+ did_defer_ = true;
+ return false;
+}
+
+// We can now send the response to the new renderer, which will cause
+// WebContentsImpl to swap in the new renderer and destroy the old one.
+void CrossSiteResourceHandler::ResumeResponse() {
+ DCHECK(request_id_ != -1);
+ DCHECK(in_cross_site_transition_);
+ in_cross_site_transition_ = false;
+
+ if (has_started_response_) {
+ // Send OnResponseStarted to the new renderer.
+ DCHECK(response_);
+ bool defer = false;
+ if (!next_handler_->OnResponseStarted(request_id_, response_, &defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ // Unpause the request to resume reading. Any further reads will be
+ // directed toward the new renderer.
+ ResumeIfDeferred();
+ }
+ }
+
+ // Remove ourselves from the ExtraRequestInfo.
+ ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request_);
+ info->set_cross_site_handler(NULL);
+
+ // If the response completed during the transition, notify the next
+ // event handler.
+ if (completed_during_transition_) {
+ if (next_handler_->OnResponseCompleted(request_id_, completed_status_,
+ completed_security_info_)) {
+ ResumeIfDeferred();
+ }
+ }
+}
+
+// Prepare to render the cross-site response in a new RenderViewHost, by
+// telling the old RenderViewHost to run its onunload handler.
+void CrossSiteResourceHandler::StartCrossSiteTransition(
+ int request_id,
+ ResourceResponse* response) {
+ in_cross_site_transition_ = true;
+ request_id_ = request_id;
+ response_ = response;
+
+ // Store this handler on the ExtraRequestInfo, so that RDH can call our
+ // ResumeResponse method when the close ACK is received.
+ ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request_);
+ info->set_cross_site_handler(this);
+
+ // Tell the contents responsible for this request that a cross-site response
+ // is starting, so that it can tell its old renderer to run its onunload
+ // handler now. We will wait to hear the corresponding ClosePage_ACK.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &OnCrossSiteResponseHelper,
+ render_process_host_id_,
+ render_view_id_,
+ request_id));
+
+ // TODO(creis): If the above call should fail, then we need to notify the IO
+ // thread to proceed anyway, using ResourceDispatcherHost::OnClosePageACK.
+}
+
+void CrossSiteResourceHandler::ResumeIfDeferred() {
+ if (did_defer_) {
+ did_defer_ = false;
+ controller()->Resume();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/cross_site_resource_handler.h b/chromium/content/browser/loader/cross_site_resource_handler.h
new file mode 100644
index 00000000000..378a2061164
--- /dev/null
+++ b/chromium/content/browser/loader/cross_site_resource_handler.h
@@ -0,0 +1,75 @@
+// 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 CONTENT_BROWSER_LOADER_CROSS_SITE_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_CROSS_SITE_RESOURCE_HANDLER_H_
+
+#include "content/browser/loader/layered_resource_handler.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+
+// Ensures that cross-site responses are delayed until the onunload handler of
+// the previous page is allowed to run. This handler wraps an
+// AsyncEventHandler, and it sits inside SafeBrowsing and Buffered event
+// handlers. This is important, so that it can intercept OnResponseStarted
+// after we determine that a response is safe and not a download.
+class CrossSiteResourceHandler : public LayeredResourceHandler {
+ public:
+ CrossSiteResourceHandler(scoped_ptr<ResourceHandler> next_handler,
+ int render_process_host_id,
+ int render_view_id,
+ net::URLRequest* request);
+ virtual ~CrossSiteResourceHandler();
+
+ // ResourceHandler implementation:
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+
+ // We can now send the response to the new renderer, which will cause
+ // WebContentsImpl to swap in the new renderer and destroy the old one.
+ void ResumeResponse();
+
+ private:
+ // Prepare to render the cross-site response in a new RenderViewHost, by
+ // telling the old RenderViewHost to run its onunload handler.
+ void StartCrossSiteTransition(
+ int request_id,
+ ResourceResponse* response);
+
+ void ResumeIfDeferred();
+
+ int render_process_host_id_;
+ int render_view_id_;
+ net::URLRequest* request_;
+ bool has_started_response_;
+ bool in_cross_site_transition_;
+ int request_id_;
+ bool completed_during_transition_;
+ bool did_defer_;
+ net::URLRequestStatus completed_status_;
+ std::string completed_security_info_;
+ ResourceResponse* response_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrossSiteResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_CROSS_SITE_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/doomed_resource_handler.cc b/chromium/content/browser/loader/doomed_resource_handler.cc
new file mode 100644
index 00000000000..d9f2f58486f
--- /dev/null
+++ b/chromium/content/browser/loader/doomed_resource_handler.cc
@@ -0,0 +1,78 @@
+// 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 "content/browser/loader/doomed_resource_handler.h"
+
+#include "base/logging.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+
+DoomedResourceHandler::DoomedResourceHandler(
+ scoped_ptr<ResourceHandler> old_handler)
+ : old_handler_(old_handler.Pass()) {
+}
+
+DoomedResourceHandler::~DoomedResourceHandler() {
+}
+
+bool DoomedResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) {
+ NOTREACHED();
+ return true;
+}
+
+bool DoomedResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ DCHECK(status.status() == net::URLRequestStatus::CANCELED ||
+ status.status() == net::URLRequestStatus::FAILED);
+ return true;
+}
+
+void DoomedResourceHandler::OnDataDownloaded(int request_id,
+ int bytes_downloaded) {
+ NOTREACHED();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/doomed_resource_handler.h b/chromium/content/browser/loader/doomed_resource_handler.h
new file mode 100644
index 00000000000..9533ef1c393
--- /dev/null
+++ b/chromium/content/browser/loader/doomed_resource_handler.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_LOADER_DOOMED_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_DOOMED_RESOURCE_HANDLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/loader/resource_handler.h"
+
+namespace content {
+
+// ResourceHandler that DCHECKs on all events but canceling and failing of
+// requests while activated for a URLRequest.
+class DoomedResourceHandler : public ResourceHandler {
+ public:
+ // As the DoomedResourceHandler is constructed and substituted from code
+ // of another ResourceHandler, we need to make sure that this other handler
+ // does not lose its last reference and gets destroyed by being substituted.
+ // Therefore, we retain a reference to |old_handler| that prevents the
+ // destruction.
+ explicit DoomedResourceHandler(scoped_ptr<ResourceHandler> old_handler);
+ virtual ~DoomedResourceHandler();
+
+ // ResourceHandler implementation:
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+ virtual void OnDataDownloaded(int request_id,
+ int bytes_downloaded) OVERRIDE;
+
+ private:
+ scoped_ptr<ResourceHandler> old_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(DoomedResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_DOOMED_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/global_routing_id.h b/chromium/content/browser/loader/global_routing_id.h
new file mode 100644
index 00000000000..4715c5a9ee8
--- /dev/null
+++ b/chromium/content/browser/loader/global_routing_id.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_PUBLIC_BROWSER_GLOBAL_ROUTING_ID_H_
+#define CONTENT_PUBLIC_BROWSER_GLOBAL_ROUTING_ID_H_
+
+namespace content {
+
+// Uniquely identifies the route from which a net::URLRequest comes.
+struct GlobalRoutingID {
+ GlobalRoutingID() : child_id(-1), route_id(-1) {
+ }
+
+ GlobalRoutingID(int child_id, int route_id)
+ : child_id(child_id),
+ route_id(route_id) {
+ }
+
+ // The unique ID of the child process (different from OS's PID).
+ int child_id;
+
+ // The route ID (unique for each URLRequest source).
+ int route_id;
+
+ bool operator<(const GlobalRoutingID& other) const {
+ if (child_id == other.child_id)
+ return route_id < other.route_id;
+ return child_id < other.child_id;
+ }
+ bool operator==(const GlobalRoutingID& other) const {
+ return child_id == other.child_id &&
+ route_id == other.route_id;
+ }
+ bool operator!=(const GlobalRoutingID& other) const {
+ return !(*this == other);
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_PUBLIC_BROWSER_GLOBAL_ROUTING_ID_H_
diff --git a/chromium/content/browser/loader/layered_resource_handler.cc b/chromium/content/browser/loader/layered_resource_handler.cc
new file mode 100644
index 00000000000..872bc7a422d
--- /dev/null
+++ b/chromium/content/browser/loader/layered_resource_handler.cc
@@ -0,0 +1,83 @@
+// 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 "content/browser/loader/layered_resource_handler.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+LayeredResourceHandler::LayeredResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler)
+ : next_handler_(next_handler.Pass()) {
+}
+
+LayeredResourceHandler::~LayeredResourceHandler() {
+}
+
+void LayeredResourceHandler::SetController(ResourceController* controller) {
+ ResourceHandler::SetController(controller);
+
+ // Pass the controller down to the next handler. This method is intended to
+ // be overriden by subclasses of LayeredResourceHandler that need to insert a
+ // different ResourceController.
+
+ DCHECK(next_handler_.get());
+ next_handler_->SetController(controller);
+}
+
+bool LayeredResourceHandler::OnUploadProgress(int request_id, uint64 position,
+ uint64 size) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnUploadProgress(request_id, position, size);
+}
+
+bool LayeredResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnRequestRedirected(request_id, url, response, defer);
+}
+
+bool LayeredResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnResponseStarted(request_id, response, defer);
+}
+
+bool LayeredResourceHandler::OnWillStart(int request_id, const GURL& url,
+ bool* defer) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnWillStart(request_id, url, defer);
+}
+
+bool LayeredResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
+}
+
+bool LayeredResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
+}
+
+bool LayeredResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ DCHECK(next_handler_.get());
+ return next_handler_->OnResponseCompleted(request_id, status, security_info);
+}
+
+void LayeredResourceHandler::OnDataDownloaded(int request_id,
+ int bytes_downloaded) {
+ DCHECK(next_handler_.get());
+ next_handler_->OnDataDownloaded(request_id, bytes_downloaded);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/layered_resource_handler.h b/chromium/content/browser/loader/layered_resource_handler.h
new file mode 100644
index 00000000000..d41f7478e3f
--- /dev/null
+++ b/chromium/content/browser/loader/layered_resource_handler.h
@@ -0,0 +1,47 @@
+// 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 CONTENT_BROWSER_LOADER_LAYERED_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_LAYERED_RESOURCE_HANDLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/loader/resource_handler.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// A ResourceHandler that simply delegates all calls to a next handler. This
+// class is intended to be subclassed.
+class CONTENT_EXPORT LayeredResourceHandler : public ResourceHandler {
+ public:
+ explicit LayeredResourceHandler(scoped_ptr<ResourceHandler> next_handler);
+ virtual ~LayeredResourceHandler();
+
+ // ResourceHandler implementation:
+ virtual void SetController(ResourceController* controller) OVERRIDE;
+ virtual bool OnUploadProgress(int request_id, uint64 position,
+ uint64 size) OVERRIDE;
+ virtual bool OnRequestRedirected(int request_id, const GURL& url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id, const GURL& url,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ scoped_ptr<ResourceHandler> next_handler_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_LAYERED_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/offline_policy.cc b/chromium/content/browser/loader/offline_policy.cc
new file mode 100644
index 00000000000..9c9361f5258
--- /dev/null
+++ b/chromium/content/browser/loader/offline_policy.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 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 "content/browser/loader/offline_policy.h"
+
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "content/public/common/content_switches.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+OfflinePolicy::OfflinePolicy()
+ : enabled_(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableOfflineCacheAccess)),
+ state_(INIT),
+ resource_loads_initiated_(0),
+ resource_loads_successfully_started_(0) {}
+
+OfflinePolicy::~OfflinePolicy() {
+ RecordAndResetStats();
+}
+
+void OfflinePolicy::RecordAndResetStats() {
+ if (enabled_ && OFFLINE == state_ && 0 != resource_loads_initiated_) {
+ UMA_HISTOGRAM_PERCENTAGE(
+ "OfflinePolicy.SuccessfulResourceLoadPercentage",
+ (resource_loads_successfully_started_ * 100 /
+ resource_loads_initiated_));
+ }
+ resource_loads_initiated_ = 0;
+ resource_loads_successfully_started_ = 0;
+}
+
+int OfflinePolicy::GetAdditionalLoadFlags(int current_flags,
+ bool reset_state) {
+ // Don't do anything if offline mode is disabled.
+ if (!enabled_)
+ return 0;
+
+ if (reset_state) {
+ RecordAndResetStats();
+ state_ = INIT;
+ }
+
+ ++resource_loads_initiated_;
+
+ // If a consumer has requested something contradictory, it wins; we
+ // don't modify the load flags.
+ if (current_flags &
+ (net::LOAD_BYPASS_CACHE | net::LOAD_PREFERRING_CACHE |
+ net::LOAD_ONLY_FROM_CACHE | net::LOAD_FROM_CACHE_IF_OFFLINE |
+ net::LOAD_DISABLE_CACHE)) {
+ return 0;
+ }
+
+ switch(state_) {
+ case INIT:
+ return net::LOAD_FROM_CACHE_IF_OFFLINE;
+ case ONLINE:
+ return 0;
+ case OFFLINE:
+ return net::LOAD_ONLY_FROM_CACHE;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+void OfflinePolicy::UpdateStateForSuccessfullyStartedRequest(
+ const net::HttpResponseInfo& response_info) {
+ // Don't do anything if offline mode is disabled.
+ if (!enabled_)
+ return;
+
+ // If we get here, we're going to be providing some amount of information
+ // to the renderer.
+ ++resource_loads_successfully_started_;
+
+ if (state_ != INIT)
+ // We've already made the decision for the rest of this set
+ // of navigations.
+ return;
+
+ if (response_info.server_data_unavailable) {
+ state_ = OFFLINE;
+ } else if (response_info.network_accessed) {
+ // If we got the response from the network or validated it as part
+ // of this request, that means our connection to the host is working.
+ state_ = ONLINE;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/offline_policy.h b/chromium/content/browser/loader/offline_policy.h
new file mode 100644
index 00000000000..975d4adb170
--- /dev/null
+++ b/chromium/content/browser/loader/offline_policy.h
@@ -0,0 +1,60 @@
+// Copyright (c) 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 CONTENT_BROWSER_LOADER_OFFLINE_POLICY
+#define CONTENT_BROWSER_LOADER_OFFLINE_POLICY
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+
+struct ResourceHostMsg_Request;
+
+namespace net {
+class HttpResponseInfo;
+class URLRequest;
+}
+
+namespace content {
+
+// This class controls under what conditions resources will be fetched
+// from cache even if stale rather than from the network. For example,
+// one policy would be that if requests for a particular route (e.g. "tab")
+// is unable to reach the server, other requests made with the same route
+// can be loaded from cache without first requiring a network timeout.
+//
+// There is a single OfflinePolicy object per user navigation unit
+// (generally a tab).
+class CONTENT_EXPORT OfflinePolicy {
+ public:
+ OfflinePolicy();
+ ~OfflinePolicy();
+
+ // Return any additional load flags to be ORed for a request from
+ // this route with the given |resource_type|. |reset_state| indicates
+ // that this request should reinitialized the internal state for this
+ // policy object (e.g. in the case of a main frame load).
+ int GetAdditionalLoadFlags(int current_flags, bool reset_state);
+
+ // Incorporate online/offline information from a successfully started request.
+ void UpdateStateForSuccessfullyStartedRequest(
+ const net::HttpResponseInfo& response_info);
+
+private:
+ enum State { INIT, ONLINE, OFFLINE };
+
+ void RecordAndResetStats();
+
+ bool enabled_;
+ State state_;
+ int resource_loads_initiated_;
+ int resource_loads_successfully_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(OfflinePolicy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_OFFLINE_POLICY
diff --git a/chromium/content/browser/loader/offline_policy_unittest.cc b/chromium/content/browser/loader/offline_policy_unittest.cc
new file mode 100644
index 00000000000..40243c8f04b
--- /dev/null
+++ b/chromium/content/browser/loader/offline_policy_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 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 "content/browser/loader/offline_policy.h"
+
+#include "base/command_line.h"
+#include "content/public/common/content_switches.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/common/resource_type.h"
+
+namespace content {
+
+class OfflinePolicyTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableOfflineCacheAccess);
+ policy_ = new OfflinePolicy;
+ }
+
+ virtual void TearDown() {
+ delete policy_;
+ policy_ = NULL;
+ }
+
+ OfflinePolicy* policy_;
+};
+
+// Confirm that the initial state of an offline object is to return
+// LOAD_FROM_CACHE_IF_OFFLINE until it gets changed.
+TEST_F(OfflinePolicyTest, InitialState) {
+ // Two loads without any reset, no UpdateStateForSuccessfullyStartedRequest.
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, false));
+}
+
+// Completion without any network probing doesn't change result value.
+TEST_F(OfflinePolicyTest, CompletedUncertain) {
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ net::HttpResponseInfo response_info;
+ policy_->UpdateStateForSuccessfullyStartedRequest(response_info);
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, false));
+}
+
+// Completion with a failed network probe changes result value.
+TEST_F(OfflinePolicyTest, CompletedNoNetwork) {
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ net::HttpResponseInfo response_info;
+ response_info.server_data_unavailable = true;
+ policy_->UpdateStateForSuccessfullyStartedRequest(response_info);
+ EXPECT_EQ(net::LOAD_ONLY_FROM_CACHE,
+ policy_->GetAdditionalLoadFlags(0, false));
+}
+
+// Completion with a successful network probe changes result value.
+TEST_F(OfflinePolicyTest, CompletedNetwork) {
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ net::HttpResponseInfo response_info;
+ response_info.network_accessed = true;
+ policy_->UpdateStateForSuccessfullyStartedRequest(response_info);
+ EXPECT_EQ(0, policy_->GetAdditionalLoadFlags(0, false));
+}
+
+// A new navigation resets a state change.
+TEST_F(OfflinePolicyTest, NewNavigationReset) {
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ net::HttpResponseInfo response_info;
+ response_info.network_accessed = true;
+ policy_->UpdateStateForSuccessfullyStartedRequest(response_info);
+ EXPECT_EQ(0, policy_->GetAdditionalLoadFlags(0, false));
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, true));
+ EXPECT_EQ(net::LOAD_FROM_CACHE_IF_OFFLINE,
+ policy_->GetAdditionalLoadFlags(0, false));
+}
+
+// Cache related flags inhibit the returning of any special flags.
+TEST_F(OfflinePolicyTest, ConsumerFlagOverride) {
+ EXPECT_EQ(0, policy_->GetAdditionalLoadFlags(net::LOAD_BYPASS_CACHE, true));
+ net::HttpResponseInfo response_info;
+ response_info.server_data_unavailable = true;
+ policy_->UpdateStateForSuccessfullyStartedRequest(response_info);
+ EXPECT_EQ(0, policy_->GetAdditionalLoadFlags(net::LOAD_BYPASS_CACHE, false));
+}
+
+}
diff --git a/chromium/content/browser/loader/power_save_block_resource_throttle.cc b/chromium/content/browser/loader/power_save_block_resource_throttle.cc
new file mode 100644
index 00000000000..d9b843cba25
--- /dev/null
+++ b/chromium/content/browser/loader/power_save_block_resource_throttle.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 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 "content/browser/loader/power_save_block_resource_throttle.h"
+
+#include "content/public/browser/power_save_blocker.h"
+
+namespace content {
+
+namespace {
+
+const int kPowerSaveBlockDelaySeconds = 30;
+
+} // namespace
+
+PowerSaveBlockResourceThrottle::PowerSaveBlockResourceThrottle() {
+}
+
+PowerSaveBlockResourceThrottle::~PowerSaveBlockResourceThrottle() {
+}
+
+void PowerSaveBlockResourceThrottle::WillStartRequest(bool* defer) {
+ // Delay PowerSaveBlocker activation to dismiss small requests.
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kPowerSaveBlockDelaySeconds),
+ this,
+ &PowerSaveBlockResourceThrottle::ActivatePowerSaveBlocker);
+}
+
+void PowerSaveBlockResourceThrottle::WillProcessResponse(bool* defer) {
+ // Stop blocking power save after request finishes.
+ power_save_blocker_.reset();
+ timer_.Stop();
+}
+
+void PowerSaveBlockResourceThrottle::ActivatePowerSaveBlocker() {
+ power_save_blocker_ = PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ "Uploading data.");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/power_save_block_resource_throttle.h b/chromium/content/browser/loader/power_save_block_resource_throttle.h
new file mode 100644
index 00000000000..e94b5426157
--- /dev/null
+++ b/chromium/content/browser/loader/power_save_block_resource_throttle.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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 CONTENT_BROWSER_LOADER_POWER_SAVE_BLOCK_RESOURCE_THROTTLE_H_
+#define CONTENT_BROWSER_LOADER_POWER_SAVE_BLOCK_RESOURCE_THROTTLE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "content/public/browser/resource_throttle.h"
+
+namespace content {
+
+class PowerSaveBlocker;
+
+// This ResourceThrottle blocks power save until large upload request finishes.
+class PowerSaveBlockResourceThrottle : public ResourceThrottle {
+ public:
+ PowerSaveBlockResourceThrottle();
+ virtual ~PowerSaveBlockResourceThrottle();
+
+ // ResourceThrottle overrides:
+ virtual void WillStartRequest(bool* defer) OVERRIDE;
+ virtual void WillProcessResponse(bool* defer) OVERRIDE;
+
+ private:
+ void ActivatePowerSaveBlocker();
+
+ base::OneShotTimer<PowerSaveBlockResourceThrottle> timer_;
+ scoped_ptr<PowerSaveBlocker> power_save_blocker_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockResourceThrottle);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_POWER_SAVE_BLOCK_RESOURCE_THROTTLE_H_
diff --git a/chromium/content/browser/loader/redirect_to_file_resource_handler.cc b/chromium/content/browser/loader/redirect_to_file_resource_handler.cc
new file mode 100644
index 00000000000..9d996dc3d52
--- /dev/null
+++ b/chromium/content/browser/loader/redirect_to_file_resource_handler.cc
@@ -0,0 +1,269 @@
+// 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 "content/browser/loader/redirect_to_file_resource_handler.h"
+
+#include "base/bind.h"
+#include "base/files/file_util_proxy.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/resource_response.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_sniffer.h"
+#include "net/base/net_errors.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+
+using webkit_blob::ShareableFileReference;
+
+namespace {
+
+// This class is similar to identically named classes in AsyncResourceHandler
+// and BufferedResourceHandler, but not quite.
+// TODO(ncbray) generalize and unify these cases?
+// In general, it's a bad idea to point to a subbuffer (particularly with
+// GrowableIOBuffer) because the backing IOBuffer may realloc its data. In this
+// particular case we know RedirectToFileResourceHandler will not realloc its
+// buffer while a write is occurring, so we should be safe. This property is
+// somewhat fragile, however, and depending on it is dangerous. A more
+// principled approach would require significant refactoring, however, so for
+// the moment we're relying on fragile properties.
+class DependentIOBuffer : public net::WrappedIOBuffer {
+ public:
+ DependentIOBuffer(net::IOBuffer* backing, char* memory)
+ : net::WrappedIOBuffer(memory),
+ backing_(backing) {
+ }
+ private:
+ virtual ~DependentIOBuffer() {}
+
+ scoped_refptr<net::IOBuffer> backing_;
+};
+
+} // namespace
+
+namespace content {
+
+static const int kInitialReadBufSize = 32768;
+static const int kMaxReadBufSize = 524288;
+
+RedirectToFileResourceHandler::RedirectToFileResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler,
+ int process_id,
+ ResourceDispatcherHostImpl* host)
+ : LayeredResourceHandler(next_handler.Pass()),
+ weak_factory_(this),
+ host_(host),
+ process_id_(process_id),
+ request_id_(-1),
+ buf_(new net::GrowableIOBuffer()),
+ buf_write_pending_(false),
+ write_cursor_(0),
+ write_callback_pending_(false),
+ next_buffer_size_(kInitialReadBufSize),
+ did_defer_(false),
+ completed_during_write_(false) {
+}
+
+RedirectToFileResourceHandler::~RedirectToFileResourceHandler() {
+}
+
+bool RedirectToFileResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ if (response->head.error_code == net::OK ||
+ response->head.error_code == net::ERR_IO_PENDING) {
+ DCHECK(deletable_file_.get() && !deletable_file_->path().empty());
+ response->head.download_file_path = deletable_file_->path();
+ }
+ return next_handler_->OnResponseStarted(request_id, response, defer);
+}
+
+bool RedirectToFileResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ request_id_ = request_id;
+ if (!deletable_file_.get()) {
+ // Defer starting the request until we have created the temporary file.
+ // TODO(darin): This is sub-optimal. We should not delay starting the
+ // network request like this.
+ did_defer_ = *defer = true;
+ base::FileUtilProxy::CreateTemporary(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
+ base::PLATFORM_FILE_ASYNC,
+ base::Bind(&RedirectToFileResourceHandler::DidCreateTemporaryFile,
+ weak_factory_.GetWeakPtr()));
+ return true;
+ }
+ return next_handler_->OnWillStart(request_id, url, defer);
+}
+
+bool RedirectToFileResourceHandler::OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) {
+ DCHECK_EQ(-1, min_size);
+
+ if (buf_->capacity() < next_buffer_size_)
+ buf_->SetCapacity(next_buffer_size_);
+
+ // We should have paused this network request already if the buffer is full.
+ DCHECK(!BufIsFull());
+
+ *buf = buf_.get();
+ *buf_size = buf_->RemainingCapacity();
+
+ buf_write_pending_ = true;
+ return true;
+}
+
+bool RedirectToFileResourceHandler::OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) {
+ DCHECK(buf_write_pending_);
+ buf_write_pending_ = false;
+
+ // We use the buffer's offset field to record the end of the buffer.
+ int new_offset = buf_->offset() + bytes_read;
+ DCHECK(new_offset <= buf_->capacity());
+ buf_->set_offset(new_offset);
+
+ if (BufIsFull()) {
+ did_defer_ = *defer = true;
+
+ if (buf_->capacity() == bytes_read) {
+ // The network layer has saturated our buffer in one read. Next time, we
+ // should give it a bigger buffer for it to fill.
+ next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize);
+ }
+ }
+
+ return WriteMore();
+}
+
+bool RedirectToFileResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ if (write_callback_pending_) {
+ completed_during_write_ = true;
+ completed_status_ = status;
+ completed_security_info_ = security_info;
+ return false;
+ }
+ return next_handler_->OnResponseCompleted(request_id, status, security_info);
+}
+
+void RedirectToFileResourceHandler::DidCreateTemporaryFile(
+ base::PlatformFileError /*error_code*/,
+ base::PassPlatformFile file_handle,
+ const base::FilePath& file_path) {
+ deletable_file_ = ShareableFileReference::GetOrCreate(
+ file_path,
+ ShareableFileReference::DELETE_ON_FINAL_RELEASE,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get());
+ file_stream_.reset(
+ new net::FileStream(file_handle.ReleaseValue(),
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC,
+ NULL));
+ host_->RegisterDownloadedTempFile(
+ process_id_, request_id_, deletable_file_.get());
+ ResumeIfDeferred();
+}
+
+void RedirectToFileResourceHandler::DidWriteToFile(int result) {
+ write_callback_pending_ = false;
+
+ bool failed = false;
+ if (result > 0) {
+ next_handler_->OnDataDownloaded(request_id_, result);
+ write_cursor_ += result;
+ failed = !WriteMore();
+ } else {
+ failed = true;
+ }
+
+ if (failed) {
+ ResumeIfDeferred();
+ } else if (completed_during_write_) {
+ if (next_handler_->OnResponseCompleted(request_id_, completed_status_,
+ completed_security_info_)) {
+ ResumeIfDeferred();
+ }
+ }
+}
+
+bool RedirectToFileResourceHandler::WriteMore() {
+ DCHECK(file_stream_.get());
+ for (;;) {
+ if (write_cursor_ == buf_->offset()) {
+ // We've caught up to the network load, but it may be in the process of
+ // appending more data to the buffer.
+ if (!buf_write_pending_) {
+ if (BufIsFull())
+ ResumeIfDeferred();
+ buf_->set_offset(0);
+ write_cursor_ = 0;
+ }
+ return true;
+ }
+ if (write_callback_pending_)
+ return true;
+ DCHECK(write_cursor_ < buf_->offset());
+
+ // Create a temporary buffer pointing to a subsection of the data buffer so
+ // that it can be passed to Write. This code makes some crazy scary
+ // assumptions about object lifetimes, thread sharing, and that buf_ will
+ // not realloc durring the write due to how the state machine in this class
+ // works.
+ // Note that buf_ is also shared with the code that writes data into the
+ // cache, so modifying it can cause some pretty subtle race conditions:
+ // https://code.google.com/p/chromium/issues/detail?id=152076
+ // We're using DependentIOBuffer instead of DrainableIOBuffer to dodge some
+ // of these issues, for the moment.
+ // TODO(ncbray) make this code less crazy scary.
+ // Also note that Write may increase the refcount of "wrapped" deep in the
+ // bowels of its implementation, the use of scoped_refptr here is not
+ // spurious.
+ scoped_refptr<DependentIOBuffer> wrapped = new DependentIOBuffer(
+ buf_.get(), buf_->StartOfBuffer() + write_cursor_);
+ int write_len = buf_->offset() - write_cursor_;
+
+ int rv = file_stream_->Write(
+ wrapped.get(),
+ write_len,
+ base::Bind(&RedirectToFileResourceHandler::DidWriteToFile,
+ base::Unretained(this)));
+ if (rv == net::ERR_IO_PENDING) {
+ write_callback_pending_ = true;
+ return true;
+ }
+ if (rv <= 0)
+ return false;
+ next_handler_->OnDataDownloaded(request_id_, rv);
+ write_cursor_ += rv;
+ }
+}
+
+bool RedirectToFileResourceHandler::BufIsFull() const {
+ // This is a hack to workaround BufferedResourceHandler's inability to
+ // deal with a ResourceHandler that returns a buffer size of less than
+ // 2 * net::kMaxBytesToSniff from its OnWillRead method.
+ // TODO(darin): Fix this retardation!
+ return buf_->RemainingCapacity() <= (2 * net::kMaxBytesToSniff);
+}
+
+void RedirectToFileResourceHandler::ResumeIfDeferred() {
+ if (did_defer_) {
+ did_defer_ = false;
+ controller()->Resume();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/redirect_to_file_resource_handler.h b/chromium/content/browser/loader/redirect_to_file_resource_handler.h
new file mode 100644
index 00000000000..459b98212c4
--- /dev/null
+++ b/chromium/content/browser/loader/redirect_to_file_resource_handler.h
@@ -0,0 +1,107 @@
+// 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 CONTENT_BROWSER_LOADER_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "content/browser/loader/layered_resource_handler.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+class FileStream;
+class GrowableIOBuffer;
+}
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+namespace content {
+class ResourceDispatcherHostImpl;
+
+// Redirects network data to a file. This is intended to be layered in front
+// of either the AsyncResourceHandler or the SyncResourceHandler.
+class RedirectToFileResourceHandler : public LayeredResourceHandler {
+ public:
+ RedirectToFileResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler,
+ int process_id,
+ ResourceDispatcherHostImpl* resource_dispatcher_host);
+ virtual ~RedirectToFileResourceHandler();
+
+ // ResourceHandler implementation:
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+
+ private:
+ void DidCreateTemporaryFile(base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle,
+ const base::FilePath& file_path);
+ void DidWriteToFile(int result);
+ bool WriteMore();
+ bool BufIsFull() const;
+ void ResumeIfDeferred();
+
+ base::WeakPtrFactory<RedirectToFileResourceHandler> weak_factory_;
+
+ ResourceDispatcherHostImpl* host_;
+ int process_id_;
+ int request_id_;
+
+ // We allocate a single, fixed-size IO buffer (buf_) used to read from the
+ // network (buf_write_pending_ is true while the system is copying data into
+ // buf_), and then write this buffer out to disk (write_callback_pending_ is
+ // true while writing to disk). Reading from the network is suspended while
+ // the buffer is full (BufIsFull returns true). The write_cursor_ member
+ // tracks the offset into buf_ that we are writing to disk.
+
+ scoped_refptr<net::GrowableIOBuffer> buf_;
+ bool buf_write_pending_;
+ int write_cursor_;
+
+ scoped_ptr<net::FileStream> file_stream_;
+ bool write_callback_pending_;
+
+ // |next_buffer_size_| is the size of the buffer to be allocated on the next
+ // OnWillRead() call. We exponentially grow the size of the buffer allocated
+ // when our owner fills our buffers. On the first OnWillRead() call, we
+ // allocate a buffer of 32k and double it in OnReadCompleted() if the buffer
+ // was filled, up to a maximum size of 512k.
+ int next_buffer_size_;
+
+ // We create a ShareableFileReference that's deletable for the temp
+ // file created as a result of the download.
+ scoped_refptr<webkit_blob::ShareableFileReference> deletable_file_;
+
+ bool did_defer_ ;
+
+ bool completed_during_write_;
+ net::URLRequestStatus completed_status_;
+ std::string completed_security_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(RedirectToFileResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/render_view_host_tracker.cc b/chromium/content/browser/loader/render_view_host_tracker.cc
new file mode 100644
index 00000000000..2f006e8e990
--- /dev/null
+++ b/chromium/content/browser/loader/render_view_host_tracker.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 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 "content/browser/loader/render_view_host_tracker.h"
+
+#include "base/bind_helpers.h"
+#include "base/stl_util.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_scheduler.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+
+namespace content {
+
+RenderViewHostTracker::RenderViewHostTracker()
+ : rvh_created_callback_(
+ base::Bind(&RenderViewHostTracker::RenderViewHostCreated,
+ base::Unretained(this))) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderViewHost::AddCreatedCallback(rvh_created_callback_);
+}
+
+RenderViewHostTracker::~RenderViewHostTracker() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(observers_.empty());
+ RenderViewHost::RemoveCreatedCallback(rvh_created_callback_);
+}
+
+void RenderViewHostTracker::RenderViewHostCreated(RenderViewHost* rvh) {
+ Observer* observer = new Observer(rvh, this);
+ observers_.insert(observer);
+
+ int child_id = rvh->GetProcess()->GetID();
+ int route_id = rvh->GetRoutingID();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ResourceDispatcherHostImpl::OnRenderViewHostCreated,
+ base::Unretained(ResourceDispatcherHostImpl::Get()),
+ child_id, route_id));
+}
+
+void RenderViewHostTracker::RemoveObserver(Observer* observer) {
+ DCHECK(ContainsKey(observers_, observer));
+ observers_.erase(observer);
+ delete observer;
+}
+
+RenderViewHostTracker::Observer::Observer(RenderViewHost* rvh,
+ RenderViewHostTracker* tracker)
+ : RenderViewHostObserver(rvh),
+ tracker_(tracker) {
+}
+
+RenderViewHostTracker::Observer::~Observer() {
+}
+
+void RenderViewHostTracker::Observer::RenderViewHostDestroyed(
+ RenderViewHost* rvh) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ int child_id = rvh->GetProcess()->GetID();
+ int route_id = rvh->GetRoutingID();
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ResourceDispatcherHostImpl::OnRenderViewHostDeleted,
+ base::Unretained(ResourceDispatcherHostImpl::Get()),
+ child_id, route_id));
+
+ tracker_->RemoveObserver(this);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/render_view_host_tracker.h b/chromium/content/browser/loader/render_view_host_tracker.h
new file mode 100644
index 00000000000..7fc5fa00b80
--- /dev/null
+++ b/chromium/content/browser/loader/render_view_host_tracker.h
@@ -0,0 +1,53 @@
+// Copyright (c) 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 CONTENT_BROWSER_LOADER_RENDER_VIEW_HOST_TRACKER_H_
+#define CONTENT_BROWSER_LOADER_RENDER_VIEW_HOST_TRACKER_H_
+
+#include <set>
+
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_view_host_observer.h"
+
+namespace content {
+
+// The ResourceDispatcherHost needs to know when renderers are created and
+// destroyed. That happens on the UI thread, but the ResourceDispatcherHost
+// operates on the IO thread. RenderViewHostTracker listens for renderer
+// notifications on the UI thread, then bounces them over to the IO thread so
+// the ResourceDispatcherHost can be notified.
+class CONTENT_EXPORT RenderViewHostTracker {
+ public:
+ RenderViewHostTracker();
+ virtual ~RenderViewHostTracker();
+
+ private:
+ // TODO(phajdan.jr): Move this declaration of inner class to the .cc file.
+ class Observer : public RenderViewHostObserver {
+ public:
+ Observer(RenderViewHost* rvh,
+ RenderViewHostTracker* tracker);
+ virtual ~Observer();
+
+ private:
+ // RenderViewHostObserver interface:
+ virtual void RenderViewHostDestroyed(RenderViewHost* rvh) OVERRIDE;
+
+ RenderViewHostTracker* tracker_;
+ };
+
+ friend class Observer;
+ typedef std::set<Observer*> ObserverSet;
+
+ void RenderViewHostCreated(RenderViewHost* rvh);
+
+ void RemoveObserver(Observer* observer);
+
+ RenderViewHost::CreatedCallback rvh_created_callback_;
+ ObserverSet observers_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RENDER_VIEW_HOST_TRACKER_H_
diff --git a/chromium/content/browser/loader/resource_buffer.cc b/chromium/content/browser/loader/resource_buffer.cc
new file mode 100644
index 00000000000..4364de18ba8
--- /dev/null
+++ b/chromium/content/browser/loader/resource_buffer.cc
@@ -0,0 +1,181 @@
+// 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 "content/browser/loader/resource_buffer.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+
+namespace content {
+
+// A circular buffer allocator.
+//
+// We keep track of the starting offset (alloc_start_) and the ending offset
+// (alloc_end_). There are two layouts to keep in mind:
+//
+// #1:
+// ------------[XXXXXXXXXXXXXXXXXXXXXXX]----
+// ^ ^
+// start end
+//
+// #2:
+// XXXXXXXXXX]---------------------[XXXXXXXX
+// ^ ^
+// end start
+//
+// If end <= start, then we have the buffer wraparound case (depicted second).
+// If the buffer is empty, then start and end will be set to -1.
+//
+// Allocations are always contiguous.
+
+ResourceBuffer::ResourceBuffer()
+ : buf_size_(0),
+ min_alloc_size_(0),
+ max_alloc_size_(0),
+ alloc_start_(-1),
+ alloc_end_(-1) {
+}
+
+ResourceBuffer::~ResourceBuffer() {
+}
+
+bool ResourceBuffer::Initialize(int buffer_size,
+ int min_allocation_size,
+ int max_allocation_size) {
+ DCHECK(!IsInitialized());
+
+ // It would be wasteful if these are not multiples of min_allocation_size.
+ DCHECK_EQ(0, buffer_size % min_allocation_size);
+ DCHECK_EQ(0, max_allocation_size % min_allocation_size);
+
+ buf_size_ = buffer_size;
+ min_alloc_size_ = min_allocation_size;
+ max_alloc_size_ = max_allocation_size;
+
+ return shared_mem_.CreateAndMapAnonymous(buf_size_);
+}
+
+bool ResourceBuffer::IsInitialized() const {
+ return shared_mem_.memory() != NULL;
+}
+
+bool ResourceBuffer::ShareToProcess(
+ base::ProcessHandle process_handle,
+ base::SharedMemoryHandle* shared_memory_handle,
+ int* shared_memory_size) {
+ DCHECK(IsInitialized());
+
+ if (!shared_mem_.ShareToProcess(process_handle, shared_memory_handle))
+ return false;
+
+ *shared_memory_size = buf_size_;
+ return true;
+}
+
+bool ResourceBuffer::CanAllocate() const {
+ DCHECK(IsInitialized());
+
+ if (alloc_start_ == -1)
+ return true;
+
+ int diff = alloc_end_ - alloc_start_;
+ if (diff > 0)
+ return (buf_size_ - diff) >= min_alloc_size_;
+
+ return -diff >= min_alloc_size_;
+}
+
+char* ResourceBuffer::Allocate(int* size) {
+ DCHECK(CanAllocate());
+
+ int alloc_offset = 0;
+ int alloc_size;
+
+ if (alloc_start_ == -1) {
+ // This is the first allocation.
+ alloc_start_ = 0;
+ alloc_end_ = buf_size_;
+ alloc_size = buf_size_;
+ } else if (alloc_start_ < alloc_end_) {
+ // Append the next allocation if it fits. Otherwise, wraparound.
+ //
+ // NOTE: We could look to see if a larger allocation is possible by
+ // wrapping around sooner, but instead we just look to fill the space at
+ // the end of the buffer provided that meets the min_alloc_size_
+ // requirement.
+ //
+ if ((buf_size_ - alloc_end_) >= min_alloc_size_) {
+ alloc_offset = alloc_end_;
+ alloc_size = buf_size_ - alloc_end_;
+ alloc_end_ = buf_size_;
+ } else {
+ // It must be possible to allocate a least min_alloc_size_.
+ DCHECK(alloc_start_ >= min_alloc_size_);
+ alloc_size = alloc_start_;
+ alloc_end_ = alloc_start_;
+ }
+ } else {
+ // This is the wraparound case.
+ DCHECK(alloc_end_ < alloc_start_);
+ alloc_offset = alloc_end_;
+ alloc_size = alloc_start_ - alloc_end_;
+ alloc_end_ = alloc_start_;
+ }
+
+ // Make sure alloc_size does not exceed max_alloc_size_. We store the
+ // current value of alloc_size, so that we can use ShrinkLastAllocation to
+ // trim it back. This allows us to reuse the alloc_end_ adjustment logic.
+
+ alloc_sizes_.push(alloc_size);
+
+ if (alloc_size > max_alloc_size_) {
+ alloc_size = max_alloc_size_;
+ ShrinkLastAllocation(alloc_size);
+ }
+
+ *size = alloc_size;
+ return static_cast<char*>(shared_mem_.memory()) + alloc_offset;
+}
+
+int ResourceBuffer::GetLastAllocationOffset() const {
+ DCHECK(!alloc_sizes_.empty());
+ DCHECK(alloc_end_ >= alloc_sizes_.back());
+ return alloc_end_ - alloc_sizes_.back();
+}
+
+void ResourceBuffer::ShrinkLastAllocation(int new_size) {
+ DCHECK(!alloc_sizes_.empty());
+
+ int aligned_size = (new_size / min_alloc_size_) * min_alloc_size_;
+ if (aligned_size < new_size)
+ aligned_size += min_alloc_size_;
+
+ DCHECK_LE(new_size, aligned_size);
+ DCHECK_GE(alloc_sizes_.back(), aligned_size);
+
+ int* last_allocation_size = &alloc_sizes_.back();
+ alloc_end_ -= (*last_allocation_size - aligned_size);
+ *last_allocation_size = aligned_size;
+}
+
+void ResourceBuffer::RecycleLeastRecentlyAllocated() {
+ DCHECK(!alloc_sizes_.empty());
+ int allocation_size = alloc_sizes_.front();
+ alloc_sizes_.pop();
+
+ alloc_start_ += allocation_size;
+ DCHECK(alloc_start_ <= buf_size_);
+
+ if (alloc_start_ == alloc_end_) {
+ DCHECK(alloc_sizes_.empty());
+ alloc_start_ = -1;
+ alloc_end_ = -1;
+ } else if (alloc_start_ == buf_size_) {
+ DCHECK(!alloc_sizes_.empty());
+ alloc_start_ = 0;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_buffer.h b/chromium/content/browser/loader/resource_buffer.h
new file mode 100644
index 00000000000..0908edbc083
--- /dev/null
+++ b/chromium/content/browser/loader/resource_buffer.h
@@ -0,0 +1,128 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_BUFFER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_BUFFER_H_
+
+#include <queue>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// ResourceBuffer implements a simple "circular buffer" allocation strategy.
+// Allocations are recycled in FIFO order.
+//
+// You can think of the ResourceBuffer as a FIFO. The Allocate method reserves
+// space in the buffer. Allocate may be called multiple times until the buffer
+// is fully reserved (at which point CanAllocate returns false). Allocations
+// are freed in FIFO order via a call to RecycleLeastRecentlyAllocated.
+//
+// ResourceBuffer is reference-counted for the benefit of consumers, who need
+// to ensure that ResourceBuffer stays alive while they are using its memory.
+//
+// EXAMPLE USAGE:
+//
+// // Writes data into the ResourceBuffer, and returns the location (byte
+// // offset and count) of the bytes written into the ResourceBuffer's shared
+// // memory buffer.
+// void WriteToBuffer(ResourceBuffer* buf, int* offset, int* count) {
+// DCHECK(buf->CanAllocate());
+//
+// *offset = -1;
+// *count = 0;
+//
+// int size;
+// char* ptr = buf->Allocate(&size);
+// if (!ptr) { /* handle error */ }
+//
+// int bytes_read = static_cast<int>(fread(ptr, 1, size, file_pointer_));
+// if (!bytes_read) { /* handle error */ }
+//
+// if (bytes_read < size)
+// buf->ShrinkLastAllocation(bytes_read);
+//
+// *offset = buf->GetLastAllocationOffset();
+// *count = bytes_read;
+// }
+//
+// NOTE: As the above example illustrates, the ResourceBuffer keeps track of
+// the last allocation made. Calling ShrinkLastAllocation is optional, as it
+// just helps the ResourceBuffer optimize storage and be more aggressive about
+// returning larger allocations from the Allocate method.
+//
+class CONTENT_EXPORT ResourceBuffer
+ : public base::RefCountedThreadSafe<ResourceBuffer> {
+ public:
+ ResourceBuffer();
+
+ // Initialize the shared memory buffer. It will be buffer_size bytes in
+ // length. The min/max_allocation_size parameters control the behavior of
+ // the Allocate method. It will prefer to return segments that are
+ // max_allocation_size in length, but will return segments less than that if
+ // space is limited. It will not return allocations smaller than
+ // min_allocation_size.
+ bool Initialize(int buffer_size,
+ int min_allocation_size,
+ int max_allocation_size);
+ bool IsInitialized() const;
+
+ // Returns a shared memory handle that can be passed to the given process.
+ // The shared memory handle is only intended to be interpretted by code
+ // running in the specified process. NOTE: The caller should ensure that
+ // this memory eventually be returned to the operating system.
+ bool ShareToProcess(base::ProcessHandle process_handle,
+ base::SharedMemoryHandle* shared_memory_handle,
+ int* shared_memory_size);
+
+ // Returns true if Allocate will succeed.
+ bool CanAllocate() const;
+
+ // Returns a pointer into the shared memory buffer or NULL if the buffer is
+ // already fully allocated. The returned size will be max_allocation_size
+ // unless the buffer is close to being full.
+ char* Allocate(int* size);
+
+ // Returns the offset into the shared memory buffer where the last allocation
+ // returned by Allocate can be found.
+ int GetLastAllocationOffset() const;
+
+ // Called to reduce the size of the last allocation returned by Allocate. It
+ // is OK for new_size to match the current size of the last allocation.
+ void ShrinkLastAllocation(int new_size);
+
+ // Called to allow reuse of memory that was previously allocated. See notes
+ // above the class for more details about this method.
+ void RecycleLeastRecentlyAllocated();
+
+ private:
+ friend class base::RefCountedThreadSafe<ResourceBuffer>;
+ ~ResourceBuffer();
+
+ base::SharedMemory shared_mem_;
+
+ int buf_size_;
+ int min_alloc_size_;
+ int max_alloc_size_;
+
+ // These point to the range of the shared memory that is currently allocated.
+ // If alloc_start_ is -1, then the range is empty and nothing is allocated.
+ // Otherwise, alloc_start_ points to the start of the allocated range, and
+ // alloc_end_ points just beyond the end of the previous allocation. In the
+ // wraparound case, alloc_end_ <= alloc_start_. See resource_buffer.cc for
+ // more details about these members.
+ int alloc_start_;
+ int alloc_end_;
+
+ std::queue<int> alloc_sizes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceBuffer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_BUFFER_H_
diff --git a/chromium/content/browser/loader/resource_buffer_unittest.cc b/chromium/content/browser/loader/resource_buffer_unittest.cc
new file mode 100644
index 00000000000..a9e90431544
--- /dev/null
+++ b/chromium/content/browser/loader/resource_buffer_unittest.cc
@@ -0,0 +1,137 @@
+// 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 "content/browser/loader/resource_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(ResourceBufferTest, BasicAllocations) {
+ scoped_refptr<ResourceBuffer> buf = new ResourceBuffer();
+ EXPECT_TRUE(buf->Initialize(100, 5, 10));
+ EXPECT_TRUE(buf->CanAllocate());
+
+ // First allocation
+ {
+ int size;
+ char* ptr = buf->Allocate(&size);
+ EXPECT_TRUE(ptr);
+ EXPECT_EQ(10, size);
+ EXPECT_TRUE(buf->CanAllocate());
+
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+
+ buf->ShrinkLastAllocation(2); // Less than our min allocation size.
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+ EXPECT_TRUE(buf->CanAllocate());
+ }
+
+ // Second allocation
+ {
+ int size;
+ char* ptr = buf->Allocate(&size);
+ EXPECT_TRUE(ptr);
+ EXPECT_EQ(10, size);
+ EXPECT_TRUE(buf->CanAllocate());
+
+ EXPECT_EQ(5, buf->GetLastAllocationOffset());
+
+ buf->ShrinkLastAllocation(4);
+ EXPECT_EQ(5, buf->GetLastAllocationOffset());
+
+ EXPECT_TRUE(buf->CanAllocate());
+ }
+}
+
+TEST(ResourceBufferTest, AllocateAndRecycle) {
+ scoped_refptr<ResourceBuffer> buf = new ResourceBuffer();
+ EXPECT_TRUE(buf->Initialize(100, 5, 10));
+
+ int size;
+
+ buf->Allocate(&size);
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+
+ buf->RecycleLeastRecentlyAllocated();
+
+ // Offset should again be 0.
+ buf->Allocate(&size);
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+}
+
+TEST(ResourceBufferTest, WrapAround) {
+ scoped_refptr<ResourceBuffer> buf = new ResourceBuffer();
+ EXPECT_TRUE(buf->Initialize(20, 10, 10));
+
+ int size;
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ // Create hole at the beginnning. Next allocation should go there.
+ buf->RecycleLeastRecentlyAllocated();
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+}
+
+TEST(ResourceBufferTest, WrapAround2) {
+ scoped_refptr<ResourceBuffer> buf = new ResourceBuffer();
+ EXPECT_TRUE(buf->Initialize(30, 10, 10));
+
+ int size;
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ EXPECT_FALSE(buf->CanAllocate());
+
+ // Create holes at first and second slots.
+ buf->RecycleLeastRecentlyAllocated();
+ buf->RecycleLeastRecentlyAllocated();
+
+ EXPECT_TRUE(buf->CanAllocate());
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+ EXPECT_EQ(0, buf->GetLastAllocationOffset());
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+ EXPECT_EQ(10, buf->GetLastAllocationOffset());
+
+ EXPECT_FALSE(buf->CanAllocate());
+}
+
+TEST(ResourceBufferTest, Full) {
+ scoped_refptr<ResourceBuffer> buf = new ResourceBuffer();
+ EXPECT_TRUE(buf->Initialize(20, 10, 10));
+
+ int size;
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ buf->Allocate(&size);
+ EXPECT_EQ(10, size);
+
+ // Full.
+ EXPECT_FALSE(buf->CanAllocate());
+
+ // Still full, even if there is a small hole at the end.
+ buf->ShrinkLastAllocation(5);
+ EXPECT_FALSE(buf->CanAllocate());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_dispatcher_host_browsertest.cc b/chromium/content/browser/loader/resource_dispatcher_host_browsertest.cc
new file mode 100644
index 00000000000..cb8896f2e05
--- /dev/null
+++ b/chromium/content/browser/loader/resource_dispatcher_host_browsertest.cc
@@ -0,0 +1,467 @@
+// 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 "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/download/download_manager_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/shell/shell_content_browser_client.h"
+#include "content/shell/shell_network_delegate.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_failed_job.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "net/base/net_errors.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace content {
+
+class ResourceDispatcherHostBrowserTest : public ContentBrowserTest,
+ public DownloadManager::Observer {
+ public:
+ ResourceDispatcherHostBrowserTest() : got_downloads_(false) {}
+
+ protected:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ base::FilePath path = GetTestFilePath("", "");
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestMockHTTPJob::AddUrlHandler, path));
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestFailedJob::AddUrlHandler));
+ }
+
+ virtual void OnDownloadCreated(
+ DownloadManager* manager,
+ DownloadItem* item) OVERRIDE {
+ if (!got_downloads_)
+ got_downloads_ = !!manager->InProgressCount();
+ }
+
+ GURL GetMockURL(const std::string& file) {
+ return URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath().AppendASCII(file));
+ }
+
+ void CheckTitleTest(const GURL& url,
+ const std::string& expected_title) {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ NavigateToURL(shell(), url);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+
+ bool GetPopupTitle(const GURL& url, string16* title) {
+ NavigateToURL(shell(), url);
+
+ ShellAddedObserver new_shell_observer;
+
+ // Create dynamic popup.
+ if (!ExecuteScript(shell()->web_contents(), "OpenPopup();"))
+ return false;
+
+ Shell* new_shell = new_shell_observer.GetShell();
+ *title = new_shell->web_contents()->GetTitle();
+ return true;
+ }
+
+ std::string GetCookies(const GURL& url) {
+ return content::GetCookies(
+ shell()->web_contents()->GetBrowserContext(), url);
+ }
+
+ bool got_downloads() const { return got_downloads_; }
+
+ private:
+ bool got_downloads_;
+};
+
+// Test title for content created by javascript window.open().
+// See http://crbug.com/5988
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, DynamicTitle1) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url(embedded_test_server()->GetURL("/dynamic1.html"));
+ string16 title;
+ ASSERT_TRUE(GetPopupTitle(url, &title));
+ EXPECT_TRUE(StartsWith(title, ASCIIToUTF16("My Popup Title"), true))
+ << "Actual title: " << title;
+}
+
+// Test title for content created by javascript window.open().
+// See http://crbug.com/5988
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, DynamicTitle2) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url(embedded_test_server()->GetURL("/dynamic2.html"));
+ string16 title;
+ ASSERT_TRUE(GetPopupTitle(url, &title));
+ EXPECT_TRUE(StartsWith(title, ASCIIToUTF16("My Dynamic Title"), true))
+ << "Actual title: " << title;
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ SniffHTMLWithNoContentType) {
+ CheckTitleTest(GetMockURL("content-sniffer-test0.html"),
+ "Content Sniffer Test 0");
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ RespectNoSniffDirective) {
+ CheckTitleTest(GetMockURL("nosniff-test.html"),
+ "mock.http/nosniff-test.html");
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ DoNotSniffHTMLFromTextPlain) {
+ CheckTitleTest(GetMockURL("content-sniffer-test1.html"),
+ "mock.http/content-sniffer-test1.html");
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ DoNotSniffHTMLFromImageGIF) {
+ CheckTitleTest(GetMockURL("content-sniffer-test2.html"),
+ "mock.http/content-sniffer-test2.html");
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ SniffNoContentTypeNoData) {
+ // Make sure no downloads start.
+ BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext())->AddObserver(this);
+ CheckTitleTest(GetMockURL("content-sniffer-test3.html"),
+ "Content Sniffer Test 3");
+ EXPECT_EQ(1u, Shell::windows().size());
+ ASSERT_FALSE(got_downloads());
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ ContentDispositionEmpty) {
+ CheckTitleTest(GetMockURL("content-disposition-empty.html"), "success");
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ ContentDispositionInline) {
+ CheckTitleTest(GetMockURL("content-disposition-inline.html"), "success");
+}
+
+// Test for bug #1091358.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, SyncXMLHttpRequest) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ NavigateToURL(
+ shell(), embedded_test_server()->GetURL("/sync_xmlhttprequest.html"));
+
+ // Let's check the XMLHttpRequest ran successfully.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(DidSyncRequestSucceed());",
+ &success));
+ EXPECT_TRUE(success);
+}
+
+// If this flakes, use http://crbug.com/62776.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ SyncXMLHttpRequest_Disallowed) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ NavigateToURL(
+ shell(),
+ embedded_test_server()->GetURL("/sync_xmlhttprequest_disallowed.html"));
+
+ // Let's check the XMLHttpRequest ran successfully.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(DidSucceed());",
+ &success));
+ EXPECT_TRUE(success);
+}
+
+// Test for bug #1159553 -- A synchronous xhr (whose content-type is
+// downloadable) would trigger download and hang the renderer process,
+// if executed while navigating to a new page.
+// Disabled on Mac: see http://crbug.com/56264
+#if defined(OS_MACOSX)
+#define MAYBE_SyncXMLHttpRequest_DuringUnload \
+ DISABLED_SyncXMLHttpRequest_DuringUnload
+#else
+#define MAYBE_SyncXMLHttpRequest_DuringUnload SyncXMLHttpRequest_DuringUnload
+#endif
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ MAYBE_SyncXMLHttpRequest_DuringUnload) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ BrowserContext::GetDownloadManager(
+ shell()->web_contents()->GetBrowserContext())->AddObserver(this);
+
+ CheckTitleTest(
+ embedded_test_server()->GetURL("/sync_xmlhttprequest_during_unload.html"),
+ "sync xhr on unload");
+
+ // Navigate to a new page, to dispatch unload event and trigger xhr.
+ // (the bug would make this step hang the renderer).
+ CheckTitleTest(
+ embedded_test_server()->GetURL("/title2.html"), "Title Of Awesomeness");
+
+ ASSERT_FALSE(got_downloads());
+}
+
+// Tests that onunload is run for cross-site requests. (Bug 1114994)
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteOnunloadCookie) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
+ CheckTitleTest(url, "set cookie on unload");
+
+ // Navigate to a new cross-site page, to dispatch unload event and set the
+ // cookie.
+ CheckTitleTest(GetMockURL("content-sniffer-test0.html"),
+ "Content Sniffer Test 0");
+
+ // Check that the cookie was set.
+ EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
+}
+
+// If this flakes, use http://crbug.com/130404
+// Tests that onunload is run for cross-site requests to URLs that complete
+// without network loads (e.g., about:blank, data URLs).
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ DISABLED_CrossSiteImmediateLoadOnunloadCookie) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
+ CheckTitleTest(url, "set cookie on unload");
+
+ // Navigate to a cross-site page that loads immediately without making a
+ // network request. The unload event should still be run.
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+
+ // Check that the cookie was set.
+ EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
+}
+
+namespace {
+
+// Handles |request| by serving a redirect response.
+scoped_ptr<net::test_server::HttpResponse> NoContentResponseHandler(
+ const std::string& path,
+ const net::test_server::HttpRequest& request) {
+ if (!StartsWithASCII(path, request.relative_url, true))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_NO_CONTENT);
+ return http_response.PassAs<net::test_server::HttpResponse>();
+}
+
+} // namespace
+
+// Tests that the unload handler is not run for 204 responses.
+// If this flakes use http://crbug.com/80596.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteNoUnloadOn204) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ // Start with a URL that sets a cookie in its unload handler.
+ GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
+ CheckTitleTest(url, "set cookie on unload");
+
+ // Navigate to a cross-site URL that returns a 204 No Content response.
+ const char kNoContentPath[] = "/nocontent";
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&NoContentResponseHandler, kNoContentPath));
+ NavigateToURL(shell(), embedded_test_server()->GetURL(kNoContentPath));
+
+ // Check that the unload cookie was not set.
+ EXPECT_EQ("", GetCookies(url));
+}
+
+#if !defined(OS_MACOSX)
+// Tests that the onbeforeunload and onunload logic is short-circuited if the
+// old renderer is gone. In that case, we don't want to wait for the old
+// renderer to run the handlers.
+// We need to disable this on Mac because the crash causes the OS CrashReporter
+// process to kick in to analyze the poor dead renderer. Unfortunately, if the
+// app isn't stripped of debug symbols, this takes about five minutes to
+// complete and isn't conducive to quick turnarounds. As we don't currently
+// strip the app on the build bots, this is bad times.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, CrossSiteAfterCrash) {
+ // Cause the renderer to crash.
+ WindowedNotificationObserver crash_observer(
+ NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ NotificationService::AllSources());
+ NavigateToURL(shell(), GURL(kChromeUICrashURL));
+ // Wait for browser to notice the renderer crash.
+ crash_observer.Wait();
+
+ // Navigate to a new cross-site page. The browser should not wait around for
+ // the old renderer's on{before}unload handlers to run.
+ CheckTitleTest(GetMockURL("content-sniffer-test0.html"),
+ "Content Sniffer Test 0");
+}
+#endif // !defined(OS_MACOSX)
+
+// Tests that cross-site navigations work when the new page does not go through
+// the BufferedEventHandler (e.g., non-http{s} URLs). (Bug 1225872)
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteNavigationNonBuffered) {
+ // Start with an HTTP page.
+ CheckTitleTest(GetMockURL("content-sniffer-test0.html"),
+ "Content Sniffer Test 0");
+
+ // Now load a file:// page, which does not use the BufferedEventHandler.
+ // Make sure that the page loads and displays a title, and doesn't get stuck.
+ GURL url = GetTestUrl("", "title2.html");
+ CheckTitleTest(url, "Title Of Awesomeness");
+}
+
+// Tests that a cross-site navigation to an error page (resulting in the link
+// doctor page) still runs the onunload handler and can support navigations
+// away from the link doctor page. (Bug 1235537)
+// Flaky: http://crbug.com/100823
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteNavigationErrorPage) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url(embedded_test_server()->GetURL("/onunload_cookie.html"));
+ CheckTitleTest(url, "set cookie on unload");
+
+ // Navigate to a new cross-site URL that results in an error.
+ // TODO(creis): If this causes crashes or hangs, it might be for the same
+ // reason as ErrorPageTest::DNSError. See bug 1199491 and
+ // http://crbug.com/22877.
+ GURL failed_url = URLRequestFailedJob::GetMockHttpUrl(
+ net::ERR_NAME_NOT_RESOLVED);
+ NavigateToURL(shell(), failed_url);
+
+ EXPECT_NE(ASCIIToUTF16("set cookie on unload"),
+ shell()->web_contents()->GetTitle());
+
+ // Check that the cookie was set, meaning that the onunload handler ran.
+ EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
+
+ // Check that renderer-initiated navigations still work. In a previous bug,
+ // the ResourceDispatcherHost would think that such navigations were
+ // cross-site, because we didn't clean up from the previous request. Since
+ // WebContentsImpl was in the NORMAL state, it would ignore the attempt to run
+ // the onunload handler, and the navigation would fail. We can't test by
+ // redirecting to javascript:window.location='someURL', since javascript:
+ // URLs are prohibited by policy from interacting with sensitive chrome
+ // pages of which the error page is one. Instead, use automation to kick
+ // off the navigation, and wait to see that the tab loads.
+ string16 expected_title16(ASCIIToUTF16("Title Of Awesomeness"));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+
+ bool success;
+ GURL test_url(embedded_test_server()->GetURL("/title2.html"));
+ std::string redirect_script = "window.location='" +
+ test_url.possibly_invalid_spec() + "';" +
+ "window.domAutomationController.send(true);";
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ redirect_script,
+ &success));
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteNavigationErrorPage2) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL url(embedded_test_server()->GetURL("/title2.html"));
+ CheckTitleTest(url, "Title Of Awesomeness");
+
+ // Navigate to a new cross-site URL that results in an error.
+ // TODO(creis): If this causes crashes or hangs, it might be for the same
+ // reason as ErrorPageTest::DNSError. See bug 1199491 and
+ // http://crbug.com/22877.
+ GURL failed_url = URLRequestFailedJob::GetMockHttpUrl(
+ net::ERR_NAME_NOT_RESOLVED);
+
+ NavigateToURL(shell(), failed_url);
+ EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"),
+ shell()->web_contents()->GetTitle());
+
+ // Repeat navigation. We are testing that this completes.
+ NavigateToURL(shell(), failed_url);
+ EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"),
+ shell()->web_contents()->GetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossOriginRedirectBlocked) {
+ // We expect the following URL requests from this test:
+ // 1- http://mock.http/cross-origin-redirect-blocked.html
+ // 2- http://mock.http/redirect-to-title2.html
+ // 3- http://mock.http/title2.html
+ //
+ // If the redirect in #2 were not blocked, we'd also see a request
+ // for http://mock.http:4000/title2.html, and the title would be different.
+ CheckTitleTest(GetMockURL("cross-origin-redirect-blocked.html"),
+ "Title Of More Awesomeness");
+}
+
+// Tests that ResourceRequestInfoImpl is updated correctly on failed
+// requests, to prevent calling Read on a request that has already failed.
+// See bug 40250.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
+ CrossSiteFailedRequest) {
+ // Visit another URL first to trigger a cross-site navigation.
+ NavigateToURL(shell(), GetTestUrl("", "simple_page.html"));
+
+ // Visit a URL that fails without calling ResourceDispatcherHost::Read.
+ GURL broken_url("chrome://theme");
+ NavigateToURL(shell(), broken_url);
+}
+
+namespace {
+
+scoped_ptr<net::test_server::HttpResponse> HandleRedirectRequest(
+ const std::string& request_path,
+ const net::test_server::HttpRequest& request) {
+ if (!StartsWithASCII(request.relative_url, request_path, true))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_FOUND);
+ http_response->AddCustomHeader(
+ "Location", request.relative_url.substr(request_path.length()));
+ return http_response.PassAs<net::test_server::HttpResponse>();
+}
+
+} // namespace
+
+// Test that we update the cookie policy URLs correctly when transferring
+// navigations.
+IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, CookiePolicy) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&HandleRedirectRequest, "/redirect?"));
+
+ std::string set_cookie_url(base::StringPrintf(
+ "http://localhost:%d/set_cookie.html", embedded_test_server()->port()));
+ GURL url(embedded_test_server()->GetURL("/redirect?" + set_cookie_url));
+
+ ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
+ ShellNetworkDelegate::SetAcceptAllCookies(false);
+
+ CheckTitleTest(url, "cookie set");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_dispatcher_host_impl.cc b/chromium/content/browser/loader/resource_dispatcher_host_impl.cc
new file mode 100644
index 00000000000..c37137dad89
--- /dev/null
+++ b/chromium/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -0,0 +1,1880 @@
+// 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.
+
+// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading
+
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/stl_util.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/cert_store_impl.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/cross_site_request_manager.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/browser/download/save_file_manager.h"
+#include "content/browser/download/save_file_resource_handler.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/loader/async_resource_handler.h"
+#include "content/browser/loader/buffered_resource_handler.h"
+#include "content/browser/loader/cross_site_resource_handler.h"
+#include "content/browser/loader/power_save_block_resource_throttle.h"
+#include "content/browser/loader/redirect_to_file_resource_handler.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/loader/stream_resource_handler.h"
+#include "content/browser/loader/sync_resource_handler.h"
+#include "content/browser/loader/throttling_resource_handler.h"
+#include "content/browser/loader/transfer_navigation_resource_throttle.h"
+#include "content/browser/loader/upload_data_stream_builder.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/resource_messages.h"
+#include "content/common/ssl_status_serialization.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/browser/resource_request_details.h"
+#include "content/public/browser/resource_throttle.h"
+#include "content/public/browser/stream_handle.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/url_constants.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_start.h"
+#include "net/base/auth.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_data_stream.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "webkit/browser/appcache/appcache_interceptor.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/common/appcache/appcache_interfaces.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/resource_request_body.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using webkit_blob::ShareableFileReference;
+using webkit_glue::ResourceRequestBody;
+
+// ----------------------------------------------------------------------------
+
+namespace content {
+
+namespace {
+
+static ResourceDispatcherHostImpl* g_resource_dispatcher_host;
+
+// The interval for calls to ResourceDispatcherHostImpl::UpdateLoadStates
+const int kUpdateLoadStatesIntervalMsec = 100;
+
+// Maximum byte "cost" of all the outstanding requests for a renderer.
+// See delcaration of |max_outstanding_requests_cost_per_process_| for details.
+// This bound is 25MB, which allows for around 6000 outstanding requests.
+const int kMaxOutstandingRequestsCostPerProcess = 26214400;
+
+// The number of milliseconds after noting a user gesture that we will
+// tag newly-created URLRequest objects with the
+// net::LOAD_MAYBE_USER_GESTURE load flag. This is a fairly arbitrary
+// guess at how long to expect direct impact from a user gesture, but
+// this should be OK as the load flag is a best-effort thing only,
+// rather than being intended as fully accurate.
+const int kUserGestureWindowMs = 3500;
+
+// Ratio of |max_num_in_flight_requests_| that any one renderer is allowed to
+// use. Arbitrarily chosen.
+const double kMaxRequestsPerProcessRatio = 0.45;
+
+// All possible error codes from the network module. Note that the error codes
+// are all positive (since histograms expect positive sample values).
+const int kAllNetErrorCodes[] = {
+#define NET_ERROR(label, value) -(value),
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+};
+
+// Aborts a request before an URLRequest has actually been created.
+void AbortRequestBeforeItStarts(ResourceMessageFilter* filter,
+ IPC::Message* sync_result,
+ int route_id,
+ int request_id) {
+ if (sync_result) {
+ SyncLoadResult result;
+ result.error_code = net::ERR_ABORTED;
+ ResourceHostMsg_SyncLoad::WriteReplyParams(sync_result, result);
+ filter->Send(sync_result);
+ } else {
+ // Tell the renderer that this request was disallowed.
+ filter->Send(new ResourceMsg_RequestComplete(
+ route_id,
+ request_id,
+ net::ERR_ABORTED,
+ false,
+ std::string(), // No security info needed, connection not established.
+ base::TimeTicks()));
+ }
+}
+
+void SetReferrerForRequest(net::URLRequest* request, const Referrer& referrer) {
+ if (!referrer.url.is_valid() ||
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoReferrers)) {
+ request->SetReferrer(std::string());
+ } else {
+ request->SetReferrer(referrer.url.spec());
+ }
+
+ net::URLRequest::ReferrerPolicy net_referrer_policy =
+ net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
+ switch (referrer.policy) {
+ case WebKit::WebReferrerPolicyDefault:
+ net_referrer_policy =
+ net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
+ break;
+ case WebKit::WebReferrerPolicyAlways:
+ case WebKit::WebReferrerPolicyNever:
+ case WebKit::WebReferrerPolicyOrigin:
+ net_referrer_policy = net::URLRequest::NEVER_CLEAR_REFERRER;
+ break;
+ }
+ request->set_referrer_policy(net_referrer_policy);
+}
+
+// Consults the RendererSecurity policy to determine whether the
+// ResourceDispatcherHostImpl should service this request. A request might be
+// disallowed if the renderer is not authorized to retrieve the request URL or
+// if the renderer is attempting to upload an unauthorized file.
+bool ShouldServiceRequest(int process_type,
+ int child_id,
+ const ResourceHostMsg_Request& request_data,
+ fileapi::FileSystemContext* file_system_context) {
+ if (process_type == PROCESS_TYPE_PLUGIN)
+ return true;
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // Check if the renderer is permitted to request the requested URL.
+ if (!policy->CanRequestURL(child_id, request_data.url)) {
+ VLOG(1) << "Denied unauthorized request for "
+ << request_data.url.possibly_invalid_spec();
+ return false;
+ }
+
+ // Check if the renderer is permitted to upload the requested files.
+ if (request_data.request_body.get()) {
+ const std::vector<ResourceRequestBody::Element>* uploads =
+ request_data.request_body->elements();
+ std::vector<ResourceRequestBody::Element>::const_iterator iter;
+ for (iter = uploads->begin(); iter != uploads->end(); ++iter) {
+ if (iter->type() == ResourceRequestBody::Element::TYPE_FILE &&
+ !policy->CanReadFile(child_id, iter->path())) {
+ NOTREACHED() << "Denied unauthorized upload of "
+ << iter->path().value();
+ return false;
+ }
+ if (iter->type() == ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM) {
+ fileapi::FileSystemURL url = file_system_context->CrackURL(iter->url());
+ if (!policy->HasPermissionsForFileSystemFile(
+ child_id, url, fileapi::kReadFilePermissions)) {
+ NOTREACHED() << "Denied unauthorized upload of "
+ << iter->url().spec();
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void RemoveDownloadFileFromChildSecurityPolicy(int child_id,
+ const base::FilePath& path) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->RevokeAllPermissionsForFile(
+ child_id, path);
+}
+
+#if defined(OS_WIN)
+#pragma warning(disable: 4748)
+#pragma optimize("", off)
+#endif
+
+#if defined(OS_WIN)
+#pragma optimize("", on)
+#pragma warning(default: 4748)
+#endif
+
+net::Error CallbackAndReturn(
+ const DownloadUrlParameters::OnStartedCallback& started_cb,
+ net::Error net_error) {
+ if (started_cb.is_null())
+ return net_error;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(started_cb, static_cast<DownloadItem*>(NULL), net_error));
+
+ return net_error;
+}
+
+int GetCertID(net::URLRequest* request, int child_id) {
+ if (request->ssl_info().cert.get()) {
+ return CertStore::GetInstance()->StoreCert(request->ssl_info().cert.get(),
+ child_id);
+ }
+ return 0;
+}
+
+template <class T>
+void NotifyOnUI(int type, int render_process_id, int render_view_id,
+ scoped_ptr<T> detail) {
+ RenderViewHostImpl* host =
+ RenderViewHostImpl::FromID(render_process_id, render_view_id);
+ if (host) {
+ RenderViewHostDelegate* delegate = host->GetDelegate();
+ NotificationService::current()->Notify(
+ type, Source<WebContents>(delegate->GetAsWebContents()),
+ Details<T>(detail.get()));
+ }
+}
+
+} // namespace
+
+// static
+ResourceDispatcherHost* ResourceDispatcherHost::Get() {
+ return g_resource_dispatcher_host;
+}
+
+ResourceDispatcherHostImpl::ResourceDispatcherHostImpl()
+ : save_file_manager_(new SaveFileManager()),
+ request_id_(-1),
+ is_shutdown_(false),
+ num_in_flight_requests_(0),
+ max_num_in_flight_requests_(base::SharedMemory::GetHandleLimit()),
+ max_num_in_flight_requests_per_process_(
+ static_cast<int>(
+ max_num_in_flight_requests_ * kMaxRequestsPerProcessRatio)),
+ max_outstanding_requests_cost_per_process_(
+ kMaxOutstandingRequestsCostPerProcess),
+ filter_(NULL),
+ delegate_(NULL),
+ allow_cross_origin_auth_prompt_(false) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!g_resource_dispatcher_host);
+ g_resource_dispatcher_host = this;
+
+ GetContentClient()->browser()->ResourceDispatcherHostCreated();
+
+ ANNOTATE_BENIGN_RACE(
+ &last_user_gesture_time_,
+ "We don't care about the precise value, see http://crbug.com/92889");
+
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ResourceDispatcherHostImpl::OnInit,
+ base::Unretained(this)));
+
+ update_load_states_timer_.reset(
+ new base::RepeatingTimer<ResourceDispatcherHostImpl>());
+}
+
+ResourceDispatcherHostImpl::~ResourceDispatcherHostImpl() {
+ DCHECK(outstanding_requests_stats_map_.empty());
+ DCHECK(g_resource_dispatcher_host);
+ g_resource_dispatcher_host = NULL;
+}
+
+// static
+ResourceDispatcherHostImpl* ResourceDispatcherHostImpl::Get() {
+ return g_resource_dispatcher_host;
+}
+
+void ResourceDispatcherHostImpl::SetDelegate(
+ ResourceDispatcherHostDelegate* delegate) {
+ delegate_ = delegate;
+}
+
+void ResourceDispatcherHostImpl::SetAllowCrossOriginAuthPrompt(bool value) {
+ allow_cross_origin_auth_prompt_ = value;
+}
+
+void ResourceDispatcherHostImpl::AddResourceContext(ResourceContext* context) {
+ active_resource_contexts_.insert(context);
+}
+
+void ResourceDispatcherHostImpl::RemoveResourceContext(
+ ResourceContext* context) {
+ CHECK(ContainsKey(active_resource_contexts_, context));
+ active_resource_contexts_.erase(context);
+}
+
+void ResourceDispatcherHostImpl::CancelRequestsForContext(
+ ResourceContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(context);
+
+ CHECK(ContainsKey(active_resource_contexts_, context));
+
+ // Note that request cancellation has side effects. Therefore, we gather all
+ // the requests to cancel first, and then we start cancelling. We assert at
+ // the end that there are no more to cancel since the context is about to go
+ // away.
+ typedef std::vector<linked_ptr<ResourceLoader> > LoaderList;
+ LoaderList loaders_to_cancel;
+
+ for (LoaderMap::iterator i = pending_loaders_.begin();
+ i != pending_loaders_.end();) {
+ if (i->second->GetRequestInfo()->GetContext() == context) {
+ loaders_to_cancel.push_back(i->second);
+ IncrementOutstandingRequestsMemory(-1, *i->second->GetRequestInfo());
+ pending_loaders_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ for (BlockedLoadersMap::iterator i = blocked_loaders_map_.begin();
+ i != blocked_loaders_map_.end();) {
+ BlockedLoadersList* loaders = i->second;
+ if (loaders->empty()) {
+ // This can happen if BlockRequestsForRoute() has been called for a route,
+ // but we haven't blocked any matching requests yet.
+ ++i;
+ continue;
+ }
+ ResourceRequestInfoImpl* info = loaders->front()->GetRequestInfo();
+ if (info->GetContext() == context) {
+ blocked_loaders_map_.erase(i++);
+ for (BlockedLoadersList::const_iterator it = loaders->begin();
+ it != loaders->end(); ++it) {
+ linked_ptr<ResourceLoader> loader = *it;
+ info = loader->GetRequestInfo();
+ // We make the assumption that all requests on the list have the same
+ // ResourceContext.
+ DCHECK_EQ(context, info->GetContext());
+ IncrementOutstandingRequestsMemory(-1, *info);
+ loaders_to_cancel.push_back(loader);
+ }
+ delete loaders;
+ } else {
+ ++i;
+ }
+ }
+
+#ifndef NDEBUG
+ for (LoaderList::iterator i = loaders_to_cancel.begin();
+ i != loaders_to_cancel.end(); ++i) {
+ // There is no strict requirement that this be the case, but currently
+ // downloads, streams and transferred requests are the only requests that
+ // aren't cancelled when the associated processes go away. It may be OK for
+ // this invariant to change in the future, but if this assertion fires
+ // without the invariant changing, then it's indicative of a leak.
+ DCHECK((*i)->GetRequestInfo()->is_download() ||
+ (*i)->GetRequestInfo()->is_stream() ||
+ (*i)->is_transferring());
+ }
+#endif
+
+ loaders_to_cancel.clear();
+
+ // Validate that no more requests for this context were added.
+ for (LoaderMap::const_iterator i = pending_loaders_.begin();
+ i != pending_loaders_.end(); ++i) {
+ // http://crbug.com/90971
+ CHECK_NE(i->second->GetRequestInfo()->GetContext(), context);
+ }
+
+ for (BlockedLoadersMap::const_iterator i = blocked_loaders_map_.begin();
+ i != blocked_loaders_map_.end(); ++i) {
+ BlockedLoadersList* loaders = i->second;
+ if (!loaders->empty()) {
+ ResourceRequestInfoImpl* info = loaders->front()->GetRequestInfo();
+ // http://crbug.com/90971
+ CHECK_NE(info->GetContext(), context);
+ }
+ }
+}
+
+net::Error ResourceDispatcherHostImpl::BeginDownload(
+ scoped_ptr<net::URLRequest> request,
+ const Referrer& referrer,
+ bool is_content_initiated,
+ ResourceContext* context,
+ int child_id,
+ int route_id,
+ bool prefer_cache,
+ scoped_ptr<DownloadSaveInfo> save_info,
+ uint32 download_id,
+ const DownloadStartedCallback& started_callback) {
+ if (is_shutdown_)
+ return CallbackAndReturn(started_callback, net::ERR_INSUFFICIENT_RESOURCES);
+
+ const GURL& url = request->original_url();
+
+ // http://crbug.com/90971
+ char url_buf[128];
+ base::strlcpy(url_buf, url.spec().c_str(), arraysize(url_buf));
+ base::debug::Alias(url_buf);
+ CHECK(ContainsKey(active_resource_contexts_, context));
+
+ SetReferrerForRequest(request.get(), referrer);
+
+ int extra_load_flags = net::LOAD_IS_DOWNLOAD;
+ if (prefer_cache) {
+ // If there is upload data attached, only retrieve from cache because there
+ // is no current mechanism to prompt the user for their consent for a
+ // re-post. For GETs, try to retrieve data from the cache and skip
+ // validating the entry if present.
+ if (request->get_upload() != NULL)
+ extra_load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ else
+ extra_load_flags |= net::LOAD_PREFERRING_CACHE;
+ } else {
+ extra_load_flags |= net::LOAD_DISABLE_CACHE;
+ }
+ request->set_load_flags(request->load_flags() | extra_load_flags);
+
+ // No need to get offline load flags for downloads, but make sure
+ // we have an OfflinePolicy to receive request completions.
+ GlobalRoutingID id(child_id, route_id);
+ if (!offline_policy_map_[id])
+ offline_policy_map_[id] = new OfflinePolicy();
+
+ // Check if the renderer is permitted to request the requested URL.
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanRequestURL(child_id, url)) {
+ VLOG(1) << "Denied unauthorized download request for "
+ << url.possibly_invalid_spec();
+ return CallbackAndReturn(started_callback, net::ERR_ACCESS_DENIED);
+ }
+
+ request_id_--;
+
+ const net::URLRequestContext* request_context = context->GetRequestContext();
+ if (!request_context->job_factory()->IsHandledURL(url)) {
+ VLOG(1) << "Download request for unsupported protocol: "
+ << url.possibly_invalid_spec();
+ return CallbackAndReturn(started_callback, net::ERR_ACCESS_DENIED);
+ }
+
+ ResourceRequestInfoImpl* extra_info =
+ CreateRequestInfo(child_id, route_id, true, context);
+ extra_info->AssociateWithRequest(request.get()); // Request takes ownership.
+
+ // From this point forward, the |DownloadResourceHandler| is responsible for
+ // |started_callback|.
+ scoped_ptr<ResourceHandler> handler(
+ CreateResourceHandlerForDownload(request.get(), is_content_initiated,
+ true, download_id, save_info.Pass(),
+ started_callback));
+
+ BeginRequestInternal(request.Pass(), handler.Pass());
+
+ return net::OK;
+}
+
+void ResourceDispatcherHostImpl::ClearLoginDelegateForRequest(
+ net::URLRequest* request) {
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request);
+ if (info) {
+ ResourceLoader* loader = GetLoader(info->GetGlobalRequestID());
+ if (loader)
+ loader->ClearLoginDelegate();
+ }
+}
+
+void ResourceDispatcherHostImpl::Shutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ResourceDispatcherHostImpl::OnShutdown,
+ base::Unretained(this)));
+}
+
+scoped_ptr<ResourceHandler>
+ResourceDispatcherHostImpl::CreateResourceHandlerForDownload(
+ net::URLRequest* request,
+ bool is_content_initiated,
+ bool must_download,
+ uint32 id,
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const DownloadUrlParameters::OnStartedCallback& started_cb) {
+ scoped_ptr<ResourceHandler> handler(
+ new DownloadResourceHandler(id, request, started_cb, save_info.Pass()));
+ if (delegate_) {
+ const ResourceRequestInfo* request_info(
+ ResourceRequestInfo::ForRequest(request));
+
+ ScopedVector<ResourceThrottle> throttles;
+ delegate_->DownloadStarting(
+ request, request_info->GetContext(), request_info->GetChildID(),
+ request_info->GetRouteID(), request_info->GetRequestID(),
+ is_content_initiated, must_download, &throttles);
+ if (!throttles.empty()) {
+ handler.reset(
+ new ThrottlingResourceHandler(
+ handler.Pass(), request_info->GetChildID(),
+ request_info->GetRequestID(), throttles.Pass()));
+ }
+ }
+ return handler.Pass();
+}
+
+scoped_ptr<ResourceHandler>
+ResourceDispatcherHostImpl::MaybeInterceptAsStream(net::URLRequest* request,
+ ResourceResponse* response) {
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request);
+ const std::string& mime_type = response->head.mime_type;
+
+ GURL origin;
+ std::string target_id;
+ if (!delegate_ ||
+ !delegate_->ShouldInterceptResourceAsStream(info->GetContext(),
+ request->url(),
+ mime_type,
+ &origin,
+ &target_id)) {
+ return scoped_ptr<ResourceHandler>();
+ }
+
+ StreamContext* stream_context =
+ GetStreamContextForResourceContext(info->GetContext());
+
+ scoped_ptr<StreamResourceHandler> handler(
+ new StreamResourceHandler(request,
+ stream_context->registry(),
+ origin));
+
+ info->set_is_stream(true);
+ delegate_->OnStreamCreated(
+ info->GetContext(),
+ info->GetChildID(),
+ info->GetRouteID(),
+ target_id,
+ handler->stream()->CreateHandle(request->url(), mime_type),
+ request->GetExpectedContentSize());
+ return handler.PassAs<ResourceHandler>();
+}
+
+void ResourceDispatcherHostImpl::ClearSSLClientAuthHandlerForRequest(
+ net::URLRequest* request) {
+ ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request);
+ if (info) {
+ ResourceLoader* loader = GetLoader(info->GetGlobalRequestID());
+ if (loader)
+ loader->ClearSSLClientAuthHandler();
+ }
+}
+
+ResourceDispatcherHostLoginDelegate*
+ResourceDispatcherHostImpl::CreateLoginDelegate(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) {
+ if (!delegate_)
+ return NULL;
+
+ return delegate_->CreateLoginDelegate(auth_info, loader->request());
+}
+
+bool ResourceDispatcherHostImpl::AcceptAuthRequest(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) {
+ if (delegate_ && !delegate_->AcceptAuthRequest(loader->request(), auth_info))
+ return false;
+
+ return true;
+}
+
+bool ResourceDispatcherHostImpl::AcceptSSLClientCertificateRequest(
+ ResourceLoader* loader,
+ net::SSLCertRequestInfo* cert_info) {
+ if (delegate_ && !delegate_->AcceptSSLClientCertificateRequest(
+ loader->request(), cert_info)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ResourceDispatcherHostImpl::HandleExternalProtocol(ResourceLoader* loader,
+ const GURL& url) {
+ if (!delegate_)
+ return false;
+
+ ResourceRequestInfoImpl* info = loader->GetRequestInfo();
+
+ if (!ResourceType::IsFrame(info->GetResourceType()))
+ return false;
+
+ const net::URLRequestJobFactory* job_factory =
+ info->GetContext()->GetRequestContext()->job_factory();
+ if (job_factory->IsHandledURL(url))
+ return false;
+
+ return delegate_->HandleExternalProtocol(url, info->GetChildID(),
+ info->GetRouteID());
+}
+
+void ResourceDispatcherHostImpl::DidStartRequest(ResourceLoader* loader) {
+ // Make sure we have the load state monitor running
+ if (!update_load_states_timer_->IsRunning()) {
+ update_load_states_timer_->Start(FROM_HERE,
+ TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec),
+ this, &ResourceDispatcherHostImpl::UpdateLoadStates);
+ }
+}
+
+void ResourceDispatcherHostImpl::DidReceiveRedirect(ResourceLoader* loader,
+ const GURL& new_url) {
+ ResourceRequestInfoImpl* info = loader->GetRequestInfo();
+
+ int render_process_id, render_view_id;
+ if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id))
+ return;
+
+ // Notify the observers on the UI thread.
+ scoped_ptr<ResourceRedirectDetails> detail(new ResourceRedirectDetails(
+ loader->request(),
+ GetCertID(loader->request(), info->GetChildID()),
+ new_url));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &NotifyOnUI<ResourceRedirectDetails>,
+ static_cast<int>(NOTIFICATION_RESOURCE_RECEIVED_REDIRECT),
+ render_process_id, render_view_id, base::Passed(&detail)));
+}
+
+void ResourceDispatcherHostImpl::DidReceiveResponse(ResourceLoader* loader) {
+ ResourceRequestInfoImpl* info = loader->GetRequestInfo();
+ // There should be an entry in the map created when we dispatched the
+ // request.
+ OfflineMap::iterator policy_it(
+ offline_policy_map_.find(info->GetGlobalRoutingID()));
+ if (offline_policy_map_.end() != policy_it) {
+ policy_it->second->UpdateStateForSuccessfullyStartedRequest(
+ loader->request()->response_info());
+ } else {
+ // We should always have an entry in offline_policy_map_ from when
+ // this request traversed Begin{Download,SaveFile,Request}.
+ // TODO(rdsmith): This isn't currently true; see http://crbug.com/241176.
+ NOTREACHED();
+ }
+
+ int render_process_id, render_view_id;
+ if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id))
+ return;
+
+ // Notify the observers on the UI thread.
+ scoped_ptr<ResourceRequestDetails> detail(new ResourceRequestDetails(
+ loader->request(),
+ GetCertID(loader->request(), info->GetChildID())));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &NotifyOnUI<ResourceRequestDetails>,
+ static_cast<int>(NOTIFICATION_RESOURCE_RESPONSE_STARTED),
+ render_process_id, render_view_id, base::Passed(&detail)));
+}
+
+void ResourceDispatcherHostImpl::DidFinishLoading(ResourceLoader* loader) {
+ ResourceRequestInfo* info = loader->GetRequestInfo();
+
+ // Record final result of all resource loads.
+ if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
+ // This enumeration has "3" appended to its name to distinguish it from
+ // older versions.
+ UMA_HISTOGRAM_SPARSE_SLOWLY(
+ "Net.ErrorCodesForMainFrame3",
+ -loader->request()->status().error());
+
+ if (loader->request()->url().SchemeIsSecure() &&
+ loader->request()->url().host() == "www.google.com") {
+ UMA_HISTOGRAM_SPARSE_SLOWLY(
+ "Net.ErrorCodesForHTTPSGoogleMainFrame2",
+ -loader->request()->status().error());
+ }
+ } else {
+ if (info->GetResourceType() == ResourceType::IMAGE) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY(
+ "Net.ErrorCodesForImages",
+ -loader->request()->status().error());
+ }
+ // This enumeration has "2" appended to distinguish it from older versions.
+ UMA_HISTOGRAM_SPARSE_SLOWLY(
+ "Net.ErrorCodesForSubresources2",
+ -loader->request()->status().error());
+ }
+
+ // Destroy the ResourceLoader.
+ RemovePendingRequest(info->GetChildID(), info->GetRequestID());
+}
+
+// static
+bool ResourceDispatcherHostImpl::RenderViewForRequest(
+ const net::URLRequest* request,
+ int* render_process_id,
+ int* render_view_id) {
+ const ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request);
+ if (!info) {
+ *render_process_id = -1;
+ *render_view_id = -1;
+ return false;
+ }
+
+ return info->GetAssociatedRenderView(render_process_id, render_view_id);
+}
+
+void ResourceDispatcherHostImpl::OnInit() {
+ scheduler_.reset(new ResourceScheduler);
+ appcache::AppCacheInterceptor::EnsureRegistered();
+}
+
+void ResourceDispatcherHostImpl::OnShutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ is_shutdown_ = true;
+ pending_loaders_.clear();
+
+ // Make sure we shutdown the timer now, otherwise by the time our destructor
+ // runs if the timer is still running the Task is deleted twice (once by
+ // the MessageLoop and the second time by RepeatingTimer).
+ update_load_states_timer_.reset();
+
+ // Clear blocked requests if any left.
+ // Note that we have to do this in 2 passes as we cannot call
+ // CancelBlockedRequestsForRoute while iterating over
+ // blocked_loaders_map_, as it modifies it.
+ std::set<GlobalRoutingID> ids;
+ for (BlockedLoadersMap::const_iterator iter = blocked_loaders_map_.begin();
+ iter != blocked_loaders_map_.end(); ++iter) {
+ std::pair<std::set<GlobalRoutingID>::iterator, bool> result =
+ ids.insert(iter->first);
+ // We should not have duplicates.
+ DCHECK(result.second);
+ }
+ for (std::set<GlobalRoutingID>::const_iterator iter = ids.begin();
+ iter != ids.end(); ++iter) {
+ CancelBlockedRequestsForRoute(iter->child_id, iter->route_id);
+ }
+
+ scheduler_.reset();
+}
+
+bool ResourceDispatcherHostImpl::OnMessageReceived(
+ const IPC::Message& message,
+ ResourceMessageFilter* filter,
+ bool* message_was_ok) {
+ filter_ = filter;
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ResourceDispatcherHostImpl, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_RequestResource, OnRequestResource)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ResourceHostMsg_SyncLoad, OnSyncLoad)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_ReleaseDownloadedFile,
+ OnReleaseDownloadedFile)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_DataDownloaded_ACK, OnDataDownloadedACK)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_UploadProgress_ACK, OnUploadProgressACK)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_CancelRequest, OnCancelRequest)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidLoadResourceFromMemoryCache,
+ OnDidLoadResourceFromMemoryCache)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!handled && IPC_MESSAGE_ID_CLASS(message.type()) == ResourceMsgStart) {
+ PickleIterator iter(message);
+ int request_id = -1;
+ bool ok = iter.ReadInt(&request_id);
+ DCHECK(ok);
+ GlobalRequestID id(filter_->child_id(), request_id);
+ DelegateMap::iterator it = delegate_map_.find(id);
+ if (it != delegate_map_.end()) {
+ ObserverList<ResourceMessageDelegate>::Iterator del_it(*it->second);
+ ResourceMessageDelegate* delegate;
+ while (!handled && (delegate = del_it.GetNext()) != NULL) {
+ handled = delegate->OnMessageReceived(message, message_was_ok);
+ }
+ }
+ }
+
+ if (message.type() == ViewHostMsg_DidLoadResourceFromMemoryCache::ID) {
+ // We just needed to peek at this message. We still want it to reach its
+ // normal destination.
+ handled = false;
+ }
+
+ filter_ = NULL;
+ return handled;
+}
+
+void ResourceDispatcherHostImpl::OnRequestResource(
+ const IPC::Message& message,
+ int request_id,
+ const ResourceHostMsg_Request& request_data) {
+ BeginRequest(request_id, request_data, NULL, message.routing_id());
+}
+
+// Begins a resource request with the given params on behalf of the specified
+// child process. Responses will be dispatched through the given receiver. The
+// process ID is used to lookup WebContentsImpl from routing_id's in the case of
+// a request from a renderer. request_context is the cookie/cache context to be
+// used for this request.
+//
+// If sync_result is non-null, then a SyncLoad reply will be generated, else
+// a normal asynchronous set of response messages will be generated.
+void ResourceDispatcherHostImpl::OnSyncLoad(
+ int request_id,
+ const ResourceHostMsg_Request& request_data,
+ IPC::Message* sync_result) {
+ BeginRequest(request_id, request_data, sync_result,
+ sync_result->routing_id());
+}
+
+void ResourceDispatcherHostImpl::BeginRequest(
+ int request_id,
+ const ResourceHostMsg_Request& request_data,
+ IPC::Message* sync_result, // only valid for sync
+ int route_id) {
+ int process_type = filter_->process_type();
+ int child_id = filter_->child_id();
+
+ // Reject invalid priority.
+ int priority = static_cast<int>(request_data.priority);
+ if (priority < net::MINIMUM_PRIORITY || priority >= net::NUM_PRIORITIES) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_RDH"));
+ filter_->BadMessageReceived();
+ return;
+ }
+
+ // If we crash here, figure out what URL the renderer was requesting.
+ // http://crbug.com/91398
+ char url_buf[128];
+ base::strlcpy(url_buf, request_data.url.spec().c_str(), arraysize(url_buf));
+ base::debug::Alias(url_buf);
+
+ // If the request that's coming in is being transferred from another process,
+ // we want to reuse and resume the old loader rather than start a new one.
+ linked_ptr<ResourceLoader> deferred_loader;
+ {
+ LoaderMap::iterator it = pending_loaders_.find(
+ GlobalRequestID(request_data.transferred_request_child_id,
+ request_data.transferred_request_request_id));
+ if (it != pending_loaders_.end()) {
+ if (it->second->is_transferring()) {
+ deferred_loader = it->second;
+ IncrementOutstandingRequestsMemory(-1,
+ *deferred_loader->GetRequestInfo());
+ pending_loaders_.erase(it);
+ } else {
+ RecordAction(UserMetricsAction("BadMessageTerminate_RDH"));
+ filter_->BadMessageReceived();
+ return;
+ }
+ }
+ }
+
+ ResourceContext* resource_context = filter_->resource_context();
+ // http://crbug.com/90971
+ CHECK(ContainsKey(active_resource_contexts_, resource_context));
+
+ if (is_shutdown_ ||
+ !ShouldServiceRequest(process_type, child_id, request_data,
+ filter_->file_system_context())) {
+ AbortRequestBeforeItStarts(filter_, sync_result, route_id, request_id);
+ return;
+ }
+
+ const Referrer referrer(request_data.referrer, request_data.referrer_policy);
+
+ // Allow the observer to block/handle the request.
+ if (delegate_ && !delegate_->ShouldBeginRequest(child_id,
+ route_id,
+ request_data.method,
+ request_data.url,
+ request_data.resource_type,
+ resource_context)) {
+ AbortRequestBeforeItStarts(filter_, sync_result, route_id, request_id);
+ return;
+ }
+
+ bool is_sync_load = sync_result != NULL;
+ int load_flags =
+ BuildLoadFlagsForRequest(request_data, child_id, is_sync_load);
+
+ GlobalRoutingID id(child_id, route_id);
+ if (!offline_policy_map_[id])
+ offline_policy_map_[id] = new OfflinePolicy();
+ load_flags |= offline_policy_map_[id]->GetAdditionalLoadFlags(
+ load_flags, request_data.resource_type == ResourceType::MAIN_FRAME);
+
+ // Construct the request.
+ scoped_ptr<net::URLRequest> new_request;
+ net::URLRequest* request;
+ if (deferred_loader.get()) {
+ request = deferred_loader->request();
+
+ // Give the ResourceLoader (or any of the ResourceHandlers held by it) a
+ // chance to reset some state before we complete the transfer.
+ deferred_loader->WillCompleteTransfer();
+ } else {
+ net::URLRequestContext* context =
+ filter_->GetURLRequestContext(request_data.resource_type);
+ new_request.reset(context->CreateRequest(request_data.url, NULL));
+ request = new_request.get();
+
+ request->set_method(request_data.method);
+ request->set_first_party_for_cookies(request_data.first_party_for_cookies);
+ SetReferrerForRequest(request, referrer);
+
+ net::HttpRequestHeaders headers;
+ headers.AddHeadersFromString(request_data.headers);
+ request->SetExtraRequestHeaders(headers);
+ }
+
+ // TODO(darin): Do we really need all of these URLRequest setters in the
+ // transferred navigation case?
+
+ request->set_load_flags(load_flags);
+ request->SetPriority(request_data.priority);
+
+ // Resolve elements from request_body and prepare upload data.
+ if (request_data.request_body.get()) {
+ request->set_upload(UploadDataStreamBuilder::Build(
+ request_data.request_body.get(),
+ filter_->blob_storage_context()->controller(),
+ filter_->file_system_context(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
+ .get()));
+ }
+
+ bool allow_download = request_data.allow_download &&
+ ResourceType::IsFrame(request_data.resource_type);
+
+ // Make extra info and read footer (contains request ID).
+ ResourceRequestInfoImpl* extra_info =
+ new ResourceRequestInfoImpl(
+ process_type,
+ child_id,
+ route_id,
+ request_data.origin_pid,
+ request_id,
+ request_data.is_main_frame,
+ request_data.frame_id,
+ request_data.parent_is_main_frame,
+ request_data.parent_frame_id,
+ request_data.resource_type,
+ request_data.transition_type,
+ false, // is download
+ false, // is stream
+ allow_download,
+ request_data.has_user_gesture,
+ request_data.referrer_policy,
+ resource_context,
+ !is_sync_load);
+ extra_info->AssociateWithRequest(request); // Request takes ownership.
+
+ if (request->url().SchemeIs(chrome::kBlobScheme)) {
+ // Hang on to a reference to ensure the blob is not released prior
+ // to the job being started.
+ extra_info->set_requested_blob_data(
+ filter_->blob_storage_context()->controller()->
+ GetBlobDataFromUrl(request->url()));
+ }
+
+ // Have the appcache associate its extra info with the request.
+ appcache::AppCacheInterceptor::SetExtraRequestInfo(
+ request, filter_->appcache_service(), child_id,
+ request_data.appcache_host_id, request_data.resource_type);
+
+ // Construct the IPC resource handler.
+ scoped_ptr<ResourceHandler> handler;
+ if (sync_result) {
+ handler.reset(new SyncResourceHandler(
+ filter_, request, sync_result, this));
+ } else {
+ handler.reset(new AsyncResourceHandler(
+ filter_, route_id, request, this));
+ }
+
+ // The RedirectToFileResourceHandler depends on being next in the chain.
+ if (request_data.download_to_file) {
+ handler.reset(
+ new RedirectToFileResourceHandler(handler.Pass(), child_id, this));
+ }
+
+ // Install a CrossSiteResourceHandler if this request is coming from a
+ // RenderViewHost with a pending cross-site request. We only check this for
+ // MAIN_FRAME requests. Unblock requests only come from a blocked page, do
+ // not count as cross-site, otherwise it gets blocked indefinitely.
+ if (request_data.resource_type == ResourceType::MAIN_FRAME &&
+ process_type == PROCESS_TYPE_RENDERER &&
+ CrossSiteRequestManager::GetInstance()->
+ HasPendingCrossSiteRequest(child_id, route_id)) {
+ // Wrap the event handler to be sure the current page's onunload handler
+ // has a chance to run before we render the new page.
+ handler.reset(new CrossSiteResourceHandler(handler.Pass(), child_id,
+ route_id, request));
+ }
+
+ // Insert a buffered event handler before the actual one.
+ handler.reset(
+ new BufferedResourceHandler(handler.Pass(), this, request));
+
+ ScopedVector<ResourceThrottle> throttles;
+ if (delegate_) {
+ bool is_continuation_of_transferred_request =
+ (deferred_loader.get() != NULL);
+
+ delegate_->RequestBeginning(request,
+ resource_context,
+ filter_->appcache_service(),
+ request_data.resource_type,
+ child_id,
+ route_id,
+ is_continuation_of_transferred_request,
+ &throttles);
+ }
+
+ if (request->has_upload()) {
+ // Block power save while uploading data.
+ throttles.push_back(new PowerSaveBlockResourceThrottle());
+ }
+
+ if (request_data.resource_type == ResourceType::MAIN_FRAME) {
+ throttles.insert(
+ throttles.begin(),
+ new TransferNavigationResourceThrottle(request));
+ }
+
+ throttles.push_back(
+ scheduler_->ScheduleRequest(child_id, route_id, request).release());
+
+ handler.reset(
+ new ThrottlingResourceHandler(handler.Pass(), child_id, request_id,
+ throttles.Pass()));
+
+ if (deferred_loader.get()) {
+ pending_loaders_[extra_info->GetGlobalRequestID()] = deferred_loader;
+ IncrementOutstandingRequestsMemory(1, *extra_info);
+ deferred_loader->CompleteTransfer(handler.Pass());
+ } else {
+ BeginRequestInternal(new_request.Pass(), handler.Pass());
+ }
+}
+
+void ResourceDispatcherHostImpl::OnReleaseDownloadedFile(int request_id) {
+ UnregisterDownloadedTempFile(filter_->child_id(), request_id);
+}
+
+void ResourceDispatcherHostImpl::OnDataDownloadedACK(int request_id) {
+ // TODO(michaeln): maybe throttle DataDownloaded messages
+}
+
+void ResourceDispatcherHostImpl::RegisterDownloadedTempFile(
+ int child_id, int request_id, ShareableFileReference* reference) {
+ registered_temp_files_[child_id][request_id] = reference;
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ child_id, reference->path());
+
+ // When the temp file is deleted, revoke permissions that the renderer has
+ // to that file. This covers an edge case where the file is deleted and then
+ // the same name is re-used for some other purpose, we don't want the old
+ // renderer to still have access to it.
+ //
+ // We do this when the file is deleted because the renderer can take a blob
+ // reference to the temp file that outlives the url loaded that it was
+ // loaded with to keep the file (and permissions) alive.
+ reference->AddFinalReleaseCallback(
+ base::Bind(&RemoveDownloadFileFromChildSecurityPolicy,
+ child_id));
+}
+
+void ResourceDispatcherHostImpl::UnregisterDownloadedTempFile(
+ int child_id, int request_id) {
+ DeletableFilesMap& map = registered_temp_files_[child_id];
+ DeletableFilesMap::iterator found = map.find(request_id);
+ if (found == map.end())
+ return;
+
+ map.erase(found);
+
+ // Note that we don't remove the security bits here. This will be done
+ // when all file refs are deleted (see RegisterDownloadedTempFile).
+}
+
+bool ResourceDispatcherHostImpl::Send(IPC::Message* message) {
+ delete message;
+ return false;
+}
+
+void ResourceDispatcherHostImpl::OnUploadProgressACK(int request_id) {
+ ResourceLoader* loader = GetLoader(filter_->child_id(), request_id);
+ if (loader)
+ loader->OnUploadProgressACK();
+}
+
+void ResourceDispatcherHostImpl::OnCancelRequest(int request_id) {
+ CancelRequest(filter_->child_id(), request_id, true);
+}
+
+ResourceRequestInfoImpl* ResourceDispatcherHostImpl::CreateRequestInfo(
+ int child_id,
+ int route_id,
+ bool download,
+ ResourceContext* context) {
+ return new ResourceRequestInfoImpl(
+ PROCESS_TYPE_RENDERER,
+ child_id,
+ route_id,
+ 0,
+ request_id_,
+ false, // is_main_frame
+ -1, // frame_id
+ false, // parent_is_main_frame
+ -1, // parent_frame_id
+ ResourceType::SUB_RESOURCE,
+ PAGE_TRANSITION_LINK,
+ download, // is_download
+ false, // is_stream
+ download, // allow_download
+ false, // has_user_gesture
+ WebKit::WebReferrerPolicyDefault,
+ context,
+ true); // is_async
+}
+
+
+void ResourceDispatcherHostImpl::OnDidLoadResourceFromMemoryCache(
+ const GURL& url,
+ const std::string& security_info,
+ const std::string& http_method,
+ const std::string& mime_type,
+ ResourceType::Type resource_type) {
+ if (!url.is_valid() || !(url.SchemeIs("http") || url.SchemeIs("https")))
+ return;
+
+ filter_->GetURLRequestContext(resource_type)->http_transaction_factory()->
+ GetCache()->OnExternalCacheHit(url, http_method);
+}
+
+void ResourceDispatcherHostImpl::OnRenderViewHostCreated(
+ int child_id,
+ int route_id) {
+ scheduler_->OnClientCreated(child_id, route_id);
+}
+
+void ResourceDispatcherHostImpl::OnRenderViewHostDeleted(
+ int child_id,
+ int route_id) {
+ scheduler_->OnClientDeleted(child_id, route_id);
+ CancelRequestsForRoute(child_id, route_id);
+}
+
+// This function is only used for saving feature.
+void ResourceDispatcherHostImpl::BeginSaveFile(
+ const GURL& url,
+ const Referrer& referrer,
+ int child_id,
+ int route_id,
+ ResourceContext* context) {
+ if (is_shutdown_)
+ return;
+
+ // http://crbug.com/90971
+ char url_buf[128];
+ base::strlcpy(url_buf, url.spec().c_str(), arraysize(url_buf));
+ base::debug::Alias(url_buf);
+ CHECK(ContainsKey(active_resource_contexts_, context));
+
+ scoped_ptr<ResourceHandler> handler(
+ new SaveFileResourceHandler(child_id,
+ route_id,
+ url,
+ save_file_manager_.get()));
+ request_id_--;
+
+ const net::URLRequestContext* request_context = context->GetRequestContext();
+ bool known_proto =
+ request_context->job_factory()->IsHandledURL(url);
+ if (!known_proto) {
+ // Since any URLs which have non-standard scheme have been filtered
+ // by save manager(see GURL::SchemeIsStandard). This situation
+ // should not happen.
+ NOTREACHED();
+ return;
+ }
+
+ scoped_ptr<net::URLRequest> request(
+ request_context->CreateRequest(url, NULL));
+ request->set_method("GET");
+ SetReferrerForRequest(request.get(), referrer);
+
+ // So far, for saving page, we need fetch content from cache, in the
+ // future, maybe we can use a configuration to configure this behavior.
+ request->set_load_flags(net::LOAD_PREFERRING_CACHE);
+
+ // No need to get offline load flags for save files, but make sure
+ // we have an OfflinePolicy to receive request completions.
+ GlobalRoutingID id(child_id, route_id);
+ if (!offline_policy_map_[id])
+ offline_policy_map_[id] = new OfflinePolicy();
+
+ // Since we're just saving some resources we need, disallow downloading.
+ ResourceRequestInfoImpl* extra_info =
+ CreateRequestInfo(child_id, route_id, false, context);
+ extra_info->AssociateWithRequest(request.get()); // Request takes ownership.
+
+ BeginRequestInternal(request.Pass(), handler.Pass());
+}
+
+void ResourceDispatcherHostImpl::MarkAsTransferredNavigation(
+ const GlobalRequestID& id, const GURL& target_url) {
+ GetLoader(id)->MarkAsTransferring(target_url);
+}
+
+void ResourceDispatcherHostImpl::ResumeDeferredNavigation(
+ const GlobalRequestID& id) {
+ ResourceLoader* loader = GetLoader(id);
+ if (loader) {
+ // The response we were meant to resume could have already been canceled.
+ ResourceRequestInfoImpl* info = loader->GetRequestInfo();
+ if (info->cross_site_handler())
+ info->cross_site_handler()->ResumeResponse();
+ }
+}
+
+// The object died, so cancel and detach all requests associated with it except
+// for downloads, which belong to the browser process even if initiated via a
+// renderer.
+void ResourceDispatcherHostImpl::CancelRequestsForProcess(int child_id) {
+ CancelRequestsForRoute(child_id, -1 /* cancel all */);
+ registered_temp_files_.erase(child_id);
+}
+
+void ResourceDispatcherHostImpl::CancelRequestsForRoute(int child_id,
+ int route_id) {
+ // Since pending_requests_ is a map, we first build up a list of all of the
+ // matching requests to be cancelled, and then we cancel them. Since there
+ // may be more than one request to cancel, we cannot simply hold onto the map
+ // iterators found in the first loop.
+
+ // Find the global ID of all matching elements.
+ std::vector<GlobalRequestID> matching_requests;
+ for (LoaderMap::const_iterator i = pending_loaders_.begin();
+ i != pending_loaders_.end(); ++i) {
+ if (i->first.child_id != child_id)
+ continue;
+
+ ResourceRequestInfoImpl* info = i->second->GetRequestInfo();
+
+ GlobalRequestID id(child_id, i->first.request_id);
+ DCHECK(id == i->first);
+
+ // Don't cancel navigations that are transferring to another process,
+ // since they belong to another process now.
+ if (!info->is_download() && !info->is_stream() &&
+ !IsTransferredNavigation(id) &&
+ (route_id == -1 || route_id == info->GetRouteID())) {
+ matching_requests.push_back(id);
+ }
+ }
+
+ // Remove matches.
+ for (size_t i = 0; i < matching_requests.size(); ++i) {
+ LoaderMap::iterator iter = pending_loaders_.find(matching_requests[i]);
+ // Although every matching request was in pending_requests_ when we built
+ // matching_requests, it is normal for a matching request to be not found
+ // in pending_requests_ after we have removed some matching requests from
+ // pending_requests_. For example, deleting a net::URLRequest that has
+ // exclusive (write) access to an HTTP cache entry may unblock another
+ // net::URLRequest that needs exclusive access to the same cache entry, and
+ // that net::URLRequest may complete and remove itself from
+ // pending_requests_. So we need to check that iter is not equal to
+ // pending_requests_.end().
+ if (iter != pending_loaders_.end())
+ RemovePendingLoader(iter);
+ }
+
+ // Now deal with blocked requests if any.
+ if (route_id != -1) {
+ if (blocked_loaders_map_.find(GlobalRoutingID(child_id, route_id)) !=
+ blocked_loaders_map_.end()) {
+ CancelBlockedRequestsForRoute(child_id, route_id);
+ }
+ } else {
+ // We have to do all render views for the process |child_id|.
+ // Note that we have to do this in 2 passes as we cannot call
+ // CancelBlockedRequestsForRoute while iterating over
+ // blocked_loaders_map_, as it modifies it.
+ std::set<int> route_ids;
+ for (BlockedLoadersMap::const_iterator iter = blocked_loaders_map_.begin();
+ iter != blocked_loaders_map_.end(); ++iter) {
+ if (iter->first.child_id == child_id)
+ route_ids.insert(iter->first.route_id);
+ }
+ for (std::set<int>::const_iterator iter = route_ids.begin();
+ iter != route_ids.end(); ++iter) {
+ CancelBlockedRequestsForRoute(child_id, *iter);
+ }
+ }
+
+ // Cleanup the offline state for the route.
+ if (-1 != route_id) {
+ OfflineMap::iterator it = offline_policy_map_.find(
+ GlobalRoutingID(child_id, route_id));
+ if (offline_policy_map_.end() != it) {
+ delete it->second;
+ offline_policy_map_.erase(it);
+ }
+ } else {
+ for (OfflineMap::iterator it = offline_policy_map_.begin();
+ offline_policy_map_.end() != it;) {
+ // Increment iterator so deletion doesn't invalidate it.
+ OfflineMap::iterator current_it = it++;
+
+ if (child_id == current_it->first.child_id) {
+ delete current_it->second;
+ offline_policy_map_.erase(current_it);
+ }
+ }
+ }
+}
+
+// Cancels the request and removes it from the list.
+void ResourceDispatcherHostImpl::RemovePendingRequest(int child_id,
+ int request_id) {
+ LoaderMap::iterator i = pending_loaders_.find(
+ GlobalRequestID(child_id, request_id));
+ if (i == pending_loaders_.end()) {
+ NOTREACHED() << "Trying to remove a request that's not here";
+ return;
+ }
+ RemovePendingLoader(i);
+}
+
+void ResourceDispatcherHostImpl::RemovePendingLoader(
+ const LoaderMap::iterator& iter) {
+ ResourceRequestInfoImpl* info = iter->second->GetRequestInfo();
+
+ // Remove the memory credit that we added when pushing the request onto
+ // the pending list.
+ IncrementOutstandingRequestsMemory(-1, *info);
+
+ pending_loaders_.erase(iter);
+
+ // If we have no more pending requests, then stop the load state monitor
+ if (pending_loaders_.empty() && update_load_states_timer_)
+ update_load_states_timer_->Stop();
+}
+
+void ResourceDispatcherHostImpl::CancelRequest(int child_id,
+ int request_id,
+ bool from_renderer) {
+ if (from_renderer) {
+ // When the old renderer dies, it sends a message to us to cancel its
+ // requests.
+ if (IsTransferredNavigation(GlobalRequestID(child_id, request_id)))
+ return;
+ }
+
+ ResourceLoader* loader = GetLoader(child_id, request_id);
+ if (!loader) {
+ // We probably want to remove this warning eventually, but I wanted to be
+ // able to notice when this happens during initial development since it
+ // should be rare and may indicate a bug.
+ DVLOG(1) << "Canceling a request that wasn't found";
+ return;
+ }
+
+ loader->CancelRequest(from_renderer);
+}
+
+ResourceDispatcherHostImpl::OustandingRequestsStats
+ResourceDispatcherHostImpl::GetOutstandingRequestsStats(
+ const ResourceRequestInfoImpl& info) {
+ OutstandingRequestsStatsMap::iterator entry =
+ outstanding_requests_stats_map_.find(info.GetChildID());
+ OustandingRequestsStats stats = { 0, 0 };
+ if (entry != outstanding_requests_stats_map_.end())
+ stats = entry->second;
+ return stats;
+}
+
+void ResourceDispatcherHostImpl::UpdateOutstandingRequestsStats(
+ const ResourceRequestInfoImpl& info,
+ const OustandingRequestsStats& stats) {
+ if (stats.memory_cost == 0 && stats.num_requests == 0)
+ outstanding_requests_stats_map_.erase(info.GetChildID());
+ else
+ outstanding_requests_stats_map_[info.GetChildID()] = stats;
+}
+
+ResourceDispatcherHostImpl::OustandingRequestsStats
+ResourceDispatcherHostImpl::IncrementOutstandingRequestsMemory(
+ int count,
+ const ResourceRequestInfoImpl& info) {
+ DCHECK_EQ(1, abs(count));
+
+ // Retrieve the previous value (defaulting to 0 if not found).
+ OustandingRequestsStats stats = GetOutstandingRequestsStats(info);
+
+ // Insert/update the total; delete entries when their count reaches 0.
+ stats.memory_cost += count * info.memory_cost();
+ DCHECK_GE(stats.memory_cost, 0);
+ UpdateOutstandingRequestsStats(info, stats);
+
+ return stats;
+}
+
+ResourceDispatcherHostImpl::OustandingRequestsStats
+ResourceDispatcherHostImpl::IncrementOutstandingRequestsCount(
+ int count,
+ const ResourceRequestInfoImpl& info) {
+ DCHECK_EQ(1, abs(count));
+ num_in_flight_requests_ += count;
+
+ OustandingRequestsStats stats = GetOutstandingRequestsStats(info);
+ stats.num_requests += count;
+ DCHECK_GE(stats.num_requests, 0);
+ UpdateOutstandingRequestsStats(info, stats);
+
+ return stats;
+}
+
+bool ResourceDispatcherHostImpl::HasSufficientResourcesForRequest(
+ const net::URLRequest* request_) {
+ const ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request_);
+ OustandingRequestsStats stats = IncrementOutstandingRequestsCount(1, *info);
+
+ if (stats.num_requests > max_num_in_flight_requests_per_process_)
+ return false;
+ if (num_in_flight_requests_ > max_num_in_flight_requests_)
+ return false;
+
+ return true;
+}
+
+void ResourceDispatcherHostImpl::FinishedWithResourcesForRequest(
+ const net::URLRequest* request_) {
+ const ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request_);
+ IncrementOutstandingRequestsCount(-1, *info);
+}
+
+// static
+int ResourceDispatcherHostImpl::CalculateApproximateMemoryCost(
+ net::URLRequest* request) {
+ // The following fields should be a minor size contribution (experimentally
+ // on the order of 100). However since they are variable length, it could
+ // in theory be a sizeable contribution.
+ int strings_cost = request->extra_request_headers().ToString().size() +
+ request->original_url().spec().size() +
+ request->referrer().size() +
+ request->method().size();
+
+ // Note that this expression will typically be dominated by:
+ // |kAvgBytesPerOutstandingRequest|.
+ return kAvgBytesPerOutstandingRequest + strings_cost;
+}
+
+void ResourceDispatcherHostImpl::BeginRequestInternal(
+ scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler) {
+ DCHECK(!request->is_pending());
+ ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request.get());
+
+ if ((TimeTicks::Now() - last_user_gesture_time_) <
+ TimeDelta::FromMilliseconds(kUserGestureWindowMs)) {
+ request->set_load_flags(
+ request->load_flags() | net::LOAD_MAYBE_USER_GESTURE);
+ }
+
+ // Add the memory estimate that starting this request will consume.
+ info->set_memory_cost(CalculateApproximateMemoryCost(request.get()));
+
+ // If enqueing/starting this request will exceed our per-process memory
+ // bound, abort it right away.
+ OustandingRequestsStats stats = IncrementOutstandingRequestsMemory(1, *info);
+ if (stats.memory_cost > max_outstanding_requests_cost_per_process_) {
+ // We call "CancelWithError()" as a way of setting the net::URLRequest's
+ // status -- it has no effect beyond this, since the request hasn't started.
+ request->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
+
+ if (!handler->OnResponseCompleted(info->GetRequestID(), request->status(),
+ std::string())) {
+ // TODO(darin): The handler is not ready for us to kill the request. Oops!
+ NOTREACHED();
+ }
+
+ IncrementOutstandingRequestsMemory(-1, *info);
+
+ // A ResourceHandler must not outlive its associated URLRequest.
+ handler.reset();
+ return;
+ }
+
+ linked_ptr<ResourceLoader> loader(
+ new ResourceLoader(request.Pass(), handler.Pass(), this));
+
+ GlobalRoutingID id(info->GetGlobalRoutingID());
+ BlockedLoadersMap::const_iterator iter = blocked_loaders_map_.find(id);
+ if (iter != blocked_loaders_map_.end()) {
+ // The request should be blocked.
+ iter->second->push_back(loader);
+ return;
+ }
+
+ StartLoading(info, loader);
+}
+
+void ResourceDispatcherHostImpl::StartLoading(
+ ResourceRequestInfoImpl* info,
+ const linked_ptr<ResourceLoader>& loader) {
+ pending_loaders_[info->GetGlobalRequestID()] = loader;
+
+ loader->StartRequest();
+}
+
+void ResourceDispatcherHostImpl::OnUserGesture(WebContentsImpl* contents) {
+ last_user_gesture_time_ = TimeTicks::Now();
+}
+
+net::URLRequest* ResourceDispatcherHostImpl::GetURLRequest(
+ const GlobalRequestID& id) {
+ ResourceLoader* loader = GetLoader(id);
+ if (!loader)
+ return NULL;
+
+ return loader->request();
+}
+
+namespace {
+
+// This function attempts to return the "more interesting" load state of |a|
+// and |b|. We don't have temporal information about these load states
+// (meaning we don't know when we transitioned into these states), so we just
+// rank them according to how "interesting" the states are.
+//
+// We take advantage of the fact that the load states are an enumeration listed
+// in the order in which they occur during the lifetime of a request, so we can
+// regard states with larger numeric values as being further along toward
+// completion. We regard those states as more interesting to report since they
+// represent progress.
+//
+// For example, by this measure "tranferring data" is a more interesting state
+// than "resolving host" because when we are transferring data we are actually
+// doing something that corresponds to changes that the user might observe,
+// whereas waiting for a host name to resolve implies being stuck.
+//
+const net::LoadStateWithParam& MoreInterestingLoadState(
+ const net::LoadStateWithParam& a, const net::LoadStateWithParam& b) {
+ return (a.state < b.state) ? b : a;
+}
+
+// Carries information about a load state change.
+struct LoadInfo {
+ GURL url;
+ net::LoadStateWithParam load_state;
+ uint64 upload_position;
+ uint64 upload_size;
+};
+
+// Map from ProcessID+RouteID pair to LoadState
+typedef std::map<GlobalRoutingID, LoadInfo> LoadInfoMap;
+
+// Used to marshal calls to LoadStateChanged from the IO to UI threads. We do
+// them all as a single callback to avoid spamming the UI thread.
+void LoadInfoUpdateCallback(const LoadInfoMap& info_map) {
+ LoadInfoMap::const_iterator i;
+ for (i = info_map.begin(); i != info_map.end(); ++i) {
+ RenderViewHostImpl* view =
+ RenderViewHostImpl::FromID(i->first.child_id, i->first.route_id);
+ if (view) // The view could be gone at this point.
+ view->LoadStateChanged(i->second.url, i->second.load_state,
+ i->second.upload_position,
+ i->second.upload_size);
+ }
+}
+
+} // namespace
+
+void ResourceDispatcherHostImpl::UpdateLoadStates() {
+ // Populate this map with load state changes, and then send them on to the UI
+ // thread where they can be passed along to the respective RVHs.
+ LoadInfoMap info_map;
+
+ LoaderMap::const_iterator i;
+
+ // Determine the largest upload size of all requests
+ // in each View (good chance it's zero).
+ std::map<GlobalRoutingID, uint64> largest_upload_size;
+ for (i = pending_loaders_.begin(); i != pending_loaders_.end(); ++i) {
+ net::URLRequest* request = i->second->request();
+ ResourceRequestInfoImpl* info = i->second->GetRequestInfo();
+ uint64 upload_size = request->GetUploadProgress().size();
+ if (request->GetLoadState().state != net::LOAD_STATE_SENDING_REQUEST)
+ upload_size = 0;
+ GlobalRoutingID id(info->GetGlobalRoutingID());
+ if (upload_size && largest_upload_size[id] < upload_size)
+ largest_upload_size[id] = upload_size;
+ }
+
+ for (i = pending_loaders_.begin(); i != pending_loaders_.end(); ++i) {
+ net::URLRequest* request = i->second->request();
+ ResourceRequestInfoImpl* info = i->second->GetRequestInfo();
+ net::LoadStateWithParam load_state = request->GetLoadState();
+ net::UploadProgress progress = request->GetUploadProgress();
+
+ // We also poll for upload progress on this timer and send upload
+ // progress ipc messages to the plugin process.
+ i->second->ReportUploadProgress();
+
+ GlobalRoutingID id(info->GetGlobalRoutingID());
+
+ // If a request is uploading data, ignore all other requests so that the
+ // upload progress takes priority for being shown in the status bar.
+ if (largest_upload_size.find(id) != largest_upload_size.end() &&
+ progress.size() < largest_upload_size[id])
+ continue;
+
+ net::LoadStateWithParam to_insert = load_state;
+ LoadInfoMap::iterator existing = info_map.find(id);
+ if (existing != info_map.end()) {
+ to_insert =
+ MoreInterestingLoadState(existing->second.load_state, load_state);
+ if (to_insert.state == existing->second.load_state.state)
+ continue;
+ }
+ LoadInfo& load_info = info_map[id];
+ load_info.url = request->url();
+ load_info.load_state = to_insert;
+ load_info.upload_size = progress.size();
+ load_info.upload_position = progress.position();
+ }
+
+ if (info_map.empty())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&LoadInfoUpdateCallback, info_map));
+}
+
+void ResourceDispatcherHostImpl::BlockRequestsForRoute(int child_id,
+ int route_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GlobalRoutingID key(child_id, route_id);
+ DCHECK(blocked_loaders_map_.find(key) == blocked_loaders_map_.end()) <<
+ "BlockRequestsForRoute called multiple time for the same RVH";
+ blocked_loaders_map_[key] = new BlockedLoadersList();
+}
+
+void ResourceDispatcherHostImpl::ResumeBlockedRequestsForRoute(int child_id,
+ int route_id) {
+ ProcessBlockedRequestsForRoute(child_id, route_id, false);
+}
+
+void ResourceDispatcherHostImpl::CancelBlockedRequestsForRoute(int child_id,
+ int route_id) {
+ ProcessBlockedRequestsForRoute(child_id, route_id, true);
+}
+
+void ResourceDispatcherHostImpl::ProcessBlockedRequestsForRoute(
+ int child_id,
+ int route_id,
+ bool cancel_requests) {
+ BlockedLoadersMap::iterator iter = blocked_loaders_map_.find(
+ GlobalRoutingID(child_id, route_id));
+ if (iter == blocked_loaders_map_.end()) {
+ // It's possible to reach here if the renderer crashed while an interstitial
+ // page was showing.
+ return;
+ }
+
+ BlockedLoadersList* loaders = iter->second;
+
+ // Removing the vector from the map unblocks any subsequent requests.
+ blocked_loaders_map_.erase(iter);
+
+ for (BlockedLoadersList::iterator loaders_iter = loaders->begin();
+ loaders_iter != loaders->end(); ++loaders_iter) {
+ linked_ptr<ResourceLoader> loader = *loaders_iter;
+ ResourceRequestInfoImpl* info = loader->GetRequestInfo();
+ if (cancel_requests) {
+ IncrementOutstandingRequestsMemory(-1, *info);
+ } else {
+ StartLoading(info, loader);
+ }
+ }
+
+ delete loaders;
+}
+
+ResourceDispatcherHostImpl::HttpAuthRelationType
+ResourceDispatcherHostImpl::HttpAuthRelationTypeOf(
+ const GURL& request_url,
+ const GURL& first_party) {
+ if (!first_party.is_valid())
+ return HTTP_AUTH_RELATION_TOP;
+
+ if (net::registry_controlled_domains::SameDomainOrHost(
+ first_party, request_url,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES))
+ return HTTP_AUTH_RELATION_SAME_DOMAIN;
+
+ if (allow_cross_origin_auth_prompt())
+ return HTTP_AUTH_RELATION_ALLOWED_CROSS;
+
+ return HTTP_AUTH_RELATION_BLOCKED_CROSS;
+}
+
+bool ResourceDispatcherHostImpl::allow_cross_origin_auth_prompt() {
+ return allow_cross_origin_auth_prompt_;
+}
+
+bool ResourceDispatcherHostImpl::IsTransferredNavigation(
+ const GlobalRequestID& id) const {
+ ResourceLoader* loader = GetLoader(id);
+ return loader ? loader->is_transferring() : false;
+}
+
+ResourceLoader* ResourceDispatcherHostImpl::GetLoader(
+ const GlobalRequestID& id) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ LoaderMap::const_iterator i = pending_loaders_.find(id);
+ if (i == pending_loaders_.end())
+ return NULL;
+
+ return i->second.get();
+}
+
+ResourceLoader* ResourceDispatcherHostImpl::GetLoader(int child_id,
+ int request_id) const {
+ return GetLoader(GlobalRequestID(child_id, request_id));
+}
+
+void ResourceDispatcherHostImpl::RegisterResourceMessageDelegate(
+ const GlobalRequestID& id, ResourceMessageDelegate* delegate) {
+ DelegateMap::iterator it = delegate_map_.find(id);
+ if (it == delegate_map_.end()) {
+ it = delegate_map_.insert(
+ std::make_pair(id, new ObserverList<ResourceMessageDelegate>)).first;
+ }
+ it->second->AddObserver(delegate);
+}
+
+void ResourceDispatcherHostImpl::UnregisterResourceMessageDelegate(
+ const GlobalRequestID& id, ResourceMessageDelegate* delegate) {
+ DCHECK(ContainsKey(delegate_map_, id));
+ DelegateMap::iterator it = delegate_map_.find(id);
+ DCHECK(it->second->HasObserver(delegate));
+ it->second->RemoveObserver(delegate);
+ if (it->second->size() == 0) {
+ delete it->second;
+ delegate_map_.erase(it);
+ }
+}
+
+int ResourceDispatcherHostImpl::BuildLoadFlagsForRequest(
+ const ResourceHostMsg_Request& request_data,
+ int child_id,
+ bool is_sync_load) {
+ int load_flags = request_data.load_flags;
+
+ // Although EV status is irrelevant to sub-frames and sub-resources, we have
+ // to perform EV certificate verification on all resources because an HTTP
+ // keep-alive connection created to load a sub-frame or a sub-resource could
+ // be reused to load a main frame.
+ load_flags |= net::LOAD_VERIFY_EV_CERT;
+ if (request_data.resource_type == ResourceType::MAIN_FRAME) {
+ load_flags |= net::LOAD_MAIN_FRAME;
+ } else if (request_data.resource_type == ResourceType::SUB_FRAME) {
+ load_flags |= net::LOAD_SUB_FRAME;
+ } else if (request_data.resource_type == ResourceType::PREFETCH) {
+ load_flags |= (net::LOAD_PREFETCH | net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
+ } else if (request_data.resource_type == ResourceType::FAVICON) {
+ load_flags |= net::LOAD_DO_NOT_PROMPT_FOR_LOGIN;
+ } else if (request_data.resource_type == ResourceType::IMAGE) {
+ // Prevent third-party image content from prompting for login, as this
+ // is often a scam to extract credentials for another domain from the user.
+ // Only block image loads, as the attack applies largely to the "src"
+ // property of the <img> tag. It is common for web properties to allow
+ // untrusted values for <img src>; this is considered a fair thing for an
+ // HTML sanitizer to do. Conversely, any HTML sanitizer that didn't
+ // filter sources for <script>, <link>, <embed>, <object>, <iframe> tags
+ // would be considered vulnerable in and of itself.
+ HttpAuthRelationType relation_type = HttpAuthRelationTypeOf(
+ request_data.url, request_data.first_party_for_cookies);
+ if (relation_type == HTTP_AUTH_RELATION_BLOCKED_CROSS) {
+ load_flags |= (net::LOAD_DO_NOT_SEND_AUTH_DATA |
+ net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
+ }
+ }
+
+ if (is_sync_load)
+ load_flags |= net::LOAD_IGNORE_LIMITS;
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanSendCookiesForOrigin(child_id, request_data.url)) {
+ load_flags |= (net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ }
+
+ // Raw headers are sensitive, as they include Cookie/Set-Cookie, so only
+ // allow requesting them if requester has ReadRawCookies permission.
+ if ((load_flags & net::LOAD_REPORT_RAW_HEADERS)
+ && !policy->CanReadRawCookies(child_id)) {
+ VLOG(1) << "Denied unauthorized request for raw headers";
+ load_flags &= ~net::LOAD_REPORT_RAW_HEADERS;
+ }
+
+ return load_flags;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_dispatcher_host_impl.h b/chromium/content/browser/loader/resource_dispatcher_host_impl.h
new file mode 100644
index 00000000000..af53b70ce75
--- /dev/null
+++ b/chromium/content/browser/loader/resource_dispatcher_host_impl.h
@@ -0,0 +1,515 @@
+// 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 is the browser side of the resource dispatcher, it receives requests
+// from the child process (i.e. [Renderer, Plugin, Worker]ProcessHost), and
+// dispatches them to URLRequests. It then forwards the messages from the
+// URLRequests back to the correct process for handling.
+//
+// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading
+
+#ifndef CONTENT_BROWSER_LOADER_RESOURCE_DISPATCHER_HOST_IMPL_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_DISPATCHER_HOST_IMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/browser/loader/global_routing_id.h"
+#include "content/browser/loader/offline_policy.h"
+#include "content/browser/loader/render_view_host_tracker.h"
+#include "content/browser/loader/resource_loader.h"
+#include "content/browser/loader/resource_loader_delegate.h"
+#include "content/browser/loader/resource_scheduler.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/resource_dispatcher_host.h"
+#include "ipc/ipc_message.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/url_request/url_request.h"
+#include "webkit/common/resource_type.h"
+
+class ResourceHandler;
+struct ResourceHostMsg_Request;
+
+namespace net {
+class URLRequestJobFactory;
+}
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+namespace content {
+class ResourceContext;
+class ResourceDispatcherHostDelegate;
+class ResourceMessageDelegate;
+class ResourceMessageFilter;
+class ResourceRequestInfoImpl;
+class SaveFileManager;
+class WebContentsImpl;
+struct DownloadSaveInfo;
+struct Referrer;
+
+class CONTENT_EXPORT ResourceDispatcherHostImpl
+ : public ResourceDispatcherHost,
+ public ResourceLoaderDelegate {
+ public:
+ ResourceDispatcherHostImpl();
+ virtual ~ResourceDispatcherHostImpl();
+
+ // Returns the current ResourceDispatcherHostImpl. May return NULL if it
+ // hasn't been created yet.
+ static ResourceDispatcherHostImpl* Get();
+
+ // ResourceDispatcherHost implementation:
+ virtual void SetDelegate(ResourceDispatcherHostDelegate* delegate) OVERRIDE;
+ virtual void SetAllowCrossOriginAuthPrompt(bool value) OVERRIDE;
+ virtual net::Error BeginDownload(
+ scoped_ptr<net::URLRequest> request,
+ const Referrer& referrer,
+ bool is_content_initiated,
+ ResourceContext* context,
+ int child_id,
+ int route_id,
+ bool prefer_cache,
+ scoped_ptr<DownloadSaveInfo> save_info,
+ uint32 download_id,
+ const DownloadStartedCallback& started_callback) OVERRIDE;
+ virtual void ClearLoginDelegateForRequest(net::URLRequest* request) OVERRIDE;
+ virtual void BlockRequestsForRoute(int child_id, int route_id) OVERRIDE;
+ virtual void ResumeBlockedRequestsForRoute(
+ int child_id, int route_id) OVERRIDE;
+
+ // Puts the resource dispatcher host in an inactive state (unable to begin
+ // new requests). Cancels all pending requests.
+ void Shutdown();
+
+ // Notify the ResourceDispatcherHostImpl of a new resource context.
+ void AddResourceContext(ResourceContext* context);
+
+ // Notify the ResourceDispatcherHostImpl of a resource context destruction.
+ void RemoveResourceContext(ResourceContext* context);
+
+ // Force cancels any pending requests for the given |context|. This is
+ // necessary to ensure that before |context| goes away, all requests
+ // for it are dead.
+ void CancelRequestsForContext(ResourceContext* context);
+
+ // Returns true if the message was a resource message that was processed.
+ // If it was, message_was_ok will be false iff the message was corrupt.
+ bool OnMessageReceived(const IPC::Message& message,
+ ResourceMessageFilter* filter,
+ bool* message_was_ok);
+
+ // Initiates a save file from the browser process (as opposed to a resource
+ // request from the renderer or another child process).
+ void BeginSaveFile(const GURL& url,
+ const Referrer& referrer,
+ int child_id,
+ int route_id,
+ ResourceContext* context);
+
+ // Cancels the given request if it still exists. We ignore cancels from the
+ // renderer in the event of a download.
+ void CancelRequest(int child_id,
+ int request_id,
+ bool from_renderer);
+
+ // Marks the request as "parked". This happens if a request is
+ // redirected cross-site and needs to be resumed by a new render view.
+ void MarkAsTransferredNavigation(const GlobalRequestID& id,
+ const GURL& target_url);
+
+ // Resumes the request without transferring it to a new render view.
+ void ResumeDeferredNavigation(const GlobalRequestID& id);
+
+ // Returns the number of pending requests. This is designed for the unittests
+ int pending_requests() const {
+ return static_cast<int>(pending_loaders_.size());
+ }
+
+ // Intended for unit-tests only. Overrides the outstanding requests bound.
+ void set_max_outstanding_requests_cost_per_process(int limit) {
+ max_outstanding_requests_cost_per_process_ = limit;
+ }
+ void set_max_num_in_flight_requests_per_process(int limit) {
+ max_num_in_flight_requests_per_process_ = limit;
+ }
+ void set_max_num_in_flight_requests(int limit) {
+ max_num_in_flight_requests_ = limit;
+ }
+
+ // The average private bytes increase of the browser for each new pending
+ // request. Experimentally obtained.
+ static const int kAvgBytesPerOutstandingRequest = 4400;
+
+ SaveFileManager* save_file_manager() const {
+ return save_file_manager_.get();
+ }
+
+ // Called when the renderer loads a resource from its internal cache.
+ void OnDidLoadResourceFromMemoryCache(const GURL& url,
+ const std::string& security_info,
+ const std::string& http_method,
+ const std::string& mime_type,
+ ResourceType::Type resource_type);
+
+ // Called when a RenderViewHost is created.
+ void OnRenderViewHostCreated(int child_id, int route_id);
+
+ // Called when a RenderViewHost is deleted.
+ void OnRenderViewHostDeleted(int child_id, int route_id);
+
+ // Force cancels any pending requests for the given process.
+ void CancelRequestsForProcess(int child_id);
+
+ void OnUserGesture(WebContentsImpl* contents);
+
+ // Retrieves a net::URLRequest. Must be called from the IO thread.
+ net::URLRequest* GetURLRequest(const GlobalRequestID& request_id);
+
+ void RemovePendingRequest(int child_id, int request_id);
+
+ // Cancels any blocked request for the specified route id.
+ void CancelBlockedRequestsForRoute(int child_id, int route_id);
+
+ // Maintains a collection of temp files created in support of
+ // the download_to_file capability. Used to grant access to the
+ // child process and to defer deletion of the file until it's
+ // no longer needed.
+ void RegisterDownloadedTempFile(
+ int child_id, int request_id,
+ webkit_blob::ShareableFileReference* reference);
+ void UnregisterDownloadedTempFile(int child_id, int request_id);
+
+ // Needed for the sync IPC message dispatcher macros.
+ bool Send(IPC::Message* message);
+
+ // Indicates whether third-party sub-content can pop-up HTTP basic auth
+ // dialog boxes.
+ bool allow_cross_origin_auth_prompt();
+
+ ResourceDispatcherHostDelegate* delegate() {
+ return delegate_;
+ }
+
+ // Must be called after the ResourceRequestInfo has been created
+ // and associated with the request.
+ // |id| should be |content::DownloadItem::kInvalidId| to request automatic
+ // assignment.
+ scoped_ptr<ResourceHandler> CreateResourceHandlerForDownload(
+ net::URLRequest* request,
+ bool is_content_initiated,
+ bool must_download,
+ uint32 id,
+ scoped_ptr<DownloadSaveInfo> save_info,
+ const DownloadUrlParameters::OnStartedCallback& started_cb);
+
+ // Must be called after the ResourceRequestInfo has been created
+ // and associated with the request.
+ scoped_ptr<ResourceHandler> MaybeInterceptAsStream(
+ net::URLRequest* request,
+ ResourceResponse* response);
+
+ void ClearSSLClientAuthHandlerForRequest(net::URLRequest* request);
+
+ ResourceScheduler* scheduler() { return scheduler_.get(); }
+
+ // Called by a ResourceHandler when it's ready to start reading data and
+ // sending it to the renderer. Returns true if there are enough file
+ // descriptors available for the shared memory buffer. If false is returned,
+ // the request should cancel.
+ bool HasSufficientResourcesForRequest(const net::URLRequest* request_);
+
+ // Called by a ResourceHandler after it has finished its request and is done
+ // using its shared memory buffer. Frees up that file descriptor to be used
+ // elsewhere.
+ void FinishedWithResourcesForRequest(const net::URLRequest* request_);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest,
+ TestBlockedRequestsProcessDies);
+ FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest,
+ CalculateApproximateMemoryCost);
+
+ class ShutdownTask;
+
+ struct OustandingRequestsStats {
+ int memory_cost;
+ int num_requests;
+ };
+
+ friend class ShutdownTask;
+ friend class ResourceMessageDelegate;
+
+ // ResourceLoaderDelegate implementation:
+ virtual ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) OVERRIDE;
+ virtual bool AcceptAuthRequest(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) OVERRIDE;
+ virtual bool AcceptSSLClientCertificateRequest(
+ ResourceLoader* loader,
+ net::SSLCertRequestInfo* cert_info) OVERRIDE;
+ virtual bool HandleExternalProtocol(ResourceLoader* loader,
+ const GURL& url) OVERRIDE;
+ virtual void DidStartRequest(ResourceLoader* loader) OVERRIDE;
+ virtual void DidReceiveRedirect(ResourceLoader* loader,
+ const GURL& new_url) OVERRIDE;
+ virtual void DidReceiveResponse(ResourceLoader* loader) OVERRIDE;
+ virtual void DidFinishLoading(ResourceLoader* loader) OVERRIDE;
+
+ // Extracts the render view/process host's identifiers from the given request
+ // and places them in the given out params (both required). If there are no
+ // such IDs associated with the request (such as non-page-related requests),
+ // this function will return false and both out params will be -1.
+ static bool RenderViewForRequest(const net::URLRequest* request,
+ int* render_process_host_id,
+ int* render_view_host_id);
+
+ // An init helper that runs on the IO thread.
+ void OnInit();
+
+ // A shutdown helper that runs on the IO thread.
+ void OnShutdown();
+
+ // Helper function for regular and download requests.
+ void BeginRequestInternal(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler);
+
+ void StartLoading(ResourceRequestInfoImpl* info,
+ const linked_ptr<ResourceLoader>& loader);
+
+ // We keep track of how much memory each request needs and how many requests
+ // are issued by each renderer. These are known as OustandingRequestStats.
+ // Memory limits apply to all requests sent to us by the renderers. There is a
+ // limit for each renderer. File descriptor limits apply to requests that are
+ // receiving their body. These are known as in-flight requests. There is a
+ // global limit that applies for the browser process. Each render is allowed
+ // to use up to a fraction of that.
+
+ // Returns the OustandingRequestsStats for |info|'s renderer, or an empty
+ // struct if that renderer has no outstanding requests.
+ OustandingRequestsStats GetOutstandingRequestsStats(
+ const ResourceRequestInfoImpl& info);
+
+ // Updates |outstanding_requests_stats_map_| with the specified |stats| for
+ // the renderer that made the request in |info|.
+ void UpdateOutstandingRequestsStats(const ResourceRequestInfoImpl& info,
+ const OustandingRequestsStats& stats);
+
+ // Called every time an outstanding request is created or deleted. |count|
+ // indicates whether the request is new or deleted. |count| must be 1 or -1.
+ OustandingRequestsStats IncrementOutstandingRequestsMemory(
+ int count,
+ const ResourceRequestInfoImpl& info);
+
+ // Called every time an in flight request is issued or finished. |count|
+ // indicates whether the request is issuing or finishing. |count| must be 1
+ // or -1.
+ OustandingRequestsStats IncrementOutstandingRequestsCount(
+ int count,
+ const ResourceRequestInfoImpl& info);
+
+ // Estimate how much heap space |request| will consume to run.
+ static int CalculateApproximateMemoryCost(net::URLRequest* request);
+
+ // Force cancels any pending requests for the given route id. This method
+ // acts like CancelRequestsForProcess when route_id is -1.
+ void CancelRequestsForRoute(int child_id, int route_id);
+
+ // The list of all requests that we have pending. This list is not really
+ // optimized, and assumes that we have relatively few requests pending at once
+ // since some operations require brute-force searching of the list.
+ //
+ // It may be enhanced in the future to provide some kind of prioritization
+ // mechanism. We should also consider a hashtable or binary tree if it turns
+ // out we have a lot of things here.
+ typedef std::map<GlobalRequestID, linked_ptr<ResourceLoader> > LoaderMap;
+
+ // Deletes the pending request identified by the iterator passed in.
+ // This function will invalidate the iterator passed in. Callers should
+ // not rely on this iterator being valid on return.
+ void RemovePendingLoader(const LoaderMap::iterator& iter);
+
+ // Checks all pending requests and updates the load states and upload
+ // progress if necessary.
+ void UpdateLoadStates();
+
+ // Resumes or cancels (if |cancel_requests| is true) any blocked requests.
+ void ProcessBlockedRequestsForRoute(int child_id,
+ int route_id,
+ bool cancel_requests);
+
+ void OnRequestResource(const IPC::Message& msg,
+ int request_id,
+ const ResourceHostMsg_Request& request_data);
+ void OnSyncLoad(int request_id,
+ const ResourceHostMsg_Request& request_data,
+ IPC::Message* sync_result);
+ void BeginRequest(int request_id,
+ const ResourceHostMsg_Request& request_data,
+ IPC::Message* sync_result, // only valid for sync
+ int route_id); // only valid for async
+ void OnDataDownloadedACK(int request_id);
+ void OnUploadProgressACK(int request_id);
+ void OnCancelRequest(int request_id);
+ void OnReleaseDownloadedFile(int request_id);
+
+ // Creates ResourceRequestInfoImpl for a download or page save.
+ // |download| should be true if the request is a file download.
+ ResourceRequestInfoImpl* CreateRequestInfo(
+ int child_id,
+ int route_id,
+ bool download,
+ ResourceContext* context);
+
+ // Relationship of resource being authenticated with the top level page.
+ enum HttpAuthRelationType {
+ HTTP_AUTH_RELATION_TOP, // Top-level page itself
+ HTTP_AUTH_RELATION_SAME_DOMAIN, // Sub-content from same domain
+ HTTP_AUTH_RELATION_BLOCKED_CROSS, // Blocked Sub-content from cross domain
+ HTTP_AUTH_RELATION_ALLOWED_CROSS, // Allowed Sub-content per command line
+ HTTP_AUTH_RELATION_LAST
+ };
+
+ HttpAuthRelationType HttpAuthRelationTypeOf(const GURL& request_url,
+ const GURL& first_party);
+
+ // Returns whether the URLRequest identified by |transferred_request_id| is
+ // currently in the process of being transferred to a different renderer.
+ // This happens if a request is redirected cross-site and needs to be resumed
+ // by a new render view.
+ bool IsTransferredNavigation(
+ const GlobalRequestID& transferred_request_id) const;
+
+ ResourceLoader* GetLoader(const GlobalRequestID& id) const;
+ ResourceLoader* GetLoader(int child_id, int request_id) const;
+
+ // Registers |delegate| to receive resource IPC messages targeted to the
+ // specified |id|.
+ void RegisterResourceMessageDelegate(const GlobalRequestID& id,
+ ResourceMessageDelegate* delegate);
+ void UnregisterResourceMessageDelegate(const GlobalRequestID& id,
+ ResourceMessageDelegate* delegate);
+
+ int BuildLoadFlagsForRequest(const ResourceHostMsg_Request& request_data,
+ int child_id,
+ bool is_sync_load);
+
+ LoaderMap pending_loaders_;
+
+ // Collection of temp files downloaded for child processes via
+ // the download_to_file mechanism. We avoid deleting them until
+ // the client no longer needs them.
+ typedef std::map<int, scoped_refptr<webkit_blob::ShareableFileReference> >
+ DeletableFilesMap; // key is request id
+ typedef std::map<int, DeletableFilesMap>
+ RegisteredTempFiles; // key is child process id
+ RegisteredTempFiles registered_temp_files_;
+
+ // A timer that periodically calls UpdateLoadStates while pending_requests_
+ // is not empty.
+ scoped_ptr<base::RepeatingTimer<ResourceDispatcherHostImpl> >
+ update_load_states_timer_;
+
+ // We own the save file manager.
+ scoped_refptr<SaveFileManager> save_file_manager_;
+
+ // Request ID for browser initiated requests. request_ids generated by
+ // child processes are counted up from 0, while browser created requests
+ // start at -2 and go down from there. (We need to start at -2 because -1 is
+ // used as a special value all over the resource_dispatcher_host for
+ // uninitialized variables.) This way, we no longer have the unlikely (but
+ // observed in the real world!) event where we have two requests with the same
+ // request_id_.
+ int request_id_;
+
+ // True if the resource dispatcher host has been shut down.
+ bool is_shutdown_;
+
+ typedef std::vector<linked_ptr<ResourceLoader> > BlockedLoadersList;
+ typedef std::map<GlobalRoutingID, BlockedLoadersList*> BlockedLoadersMap;
+ BlockedLoadersMap blocked_loaders_map_;
+
+ // Maps the child_ids to the approximate number of bytes
+ // being used to service its resource requests. No entry implies 0 cost.
+ typedef std::map<int, OustandingRequestsStats> OutstandingRequestsStatsMap;
+ OutstandingRequestsStatsMap outstanding_requests_stats_map_;
+
+ // |num_in_flight_requests_| is the total number of requests currently issued
+ // summed across all renderers.
+ int num_in_flight_requests_;
+
+ // |max_num_in_flight_requests_| is the upper bound on how many requests
+ // can be in flight at once. It's based on the maximum number of file
+ // descriptors open per process. We need a global limit for the browser
+ // process.
+ int max_num_in_flight_requests_;
+
+ // |max_num_in_flight_requests_| is the upper bound on how many requests
+ // can be issued at once. It's based on the maximum number of file
+ // descriptors open per process. We need a per-renderer limit so that no
+ // single renderer can hog the browser's limit.
+ int max_num_in_flight_requests_per_process_;
+
+ // |max_outstanding_requests_cost_per_process_| is the upper bound on how
+ // many outstanding requests can be issued per child process host.
+ // The constraint is expressed in terms of bytes (where the cost of
+ // individual requests is given by CalculateApproximateMemoryCost).
+ // The total number of outstanding requests is roughly:
+ // (max_outstanding_requests_cost_per_process_ /
+ // kAvgBytesPerOutstandingRequest)
+ int max_outstanding_requests_cost_per_process_;
+
+ // Time of the last user gesture. Stored so that we can add a load
+ // flag to requests occurring soon after a gesture to indicate they
+ // may be because of explicit user action.
+ base::TimeTicks last_user_gesture_time_;
+
+ // Used during IPC message dispatching so that the handlers can get a pointer
+ // to the source of the message.
+ ResourceMessageFilter* filter_;
+
+ ResourceDispatcherHostDelegate* delegate_;
+
+ bool allow_cross_origin_auth_prompt_;
+
+ // http://crbug.com/90971 - Assists in tracking down use-after-frees on
+ // shutdown.
+ std::set<const ResourceContext*> active_resource_contexts_;
+
+ typedef std::map<GlobalRequestID,
+ ObserverList<ResourceMessageDelegate>*> DelegateMap;
+ DelegateMap delegate_map_;
+
+ scoped_ptr<ResourceScheduler> scheduler_;
+
+ RenderViewHostTracker tracker_; // Lives on UI thread.
+
+ typedef std::map<GlobalRoutingID, OfflinePolicy*> OfflineMap;
+
+ OfflineMap offline_policy_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceDispatcherHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_DISPATCHER_HOST_IMPL_H_
diff --git a/chromium/content/browser/loader/resource_dispatcher_host_unittest.cc b/chromium/content/browser/loader/resource_dispatcher_host_unittest.cc
new file mode 100644
index 00000000000..be463398f8d
--- /dev/null
+++ b/chromium/content/browser/loader/resource_dispatcher_host_unittest.cc
@@ -0,0 +1,2007 @@
+// 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 <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pickle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/resource_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/browser/resource_throttle.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/resource_response.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/test/test_content_browser_client.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "net/url_request/url_request_test_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/common/appcache/appcache_interfaces.h"
+
+// TODO(eroman): Write unit tests for SafeBrowsing that exercise
+// SafeBrowsingResourceHandler.
+
+namespace content {
+
+namespace {
+
+// Returns the resource response header structure for this request.
+void GetResponseHead(const std::vector<IPC::Message>& messages,
+ ResourceResponseHead* response_head) {
+ ASSERT_GE(messages.size(), 2U);
+
+ // The first messages should be received response.
+ ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type());
+
+ PickleIterator iter(messages[0]);
+ int request_id;
+ ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, &request_id));
+ ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, response_head));
+}
+
+void GenerateIPCMessage(
+ scoped_refptr<ResourceMessageFilter> filter,
+ scoped_ptr<IPC::Message> message) {
+ bool msg_is_ok;
+ ResourceDispatcherHostImpl::Get()->OnMessageReceived(
+ *message, filter.get(), &msg_is_ok);
+}
+
+} // namespace
+
+static int RequestIDForMessage(const IPC::Message& msg) {
+ int request_id = -1;
+ switch (msg.type()) {
+ case ResourceMsg_UploadProgress::ID:
+ case ResourceMsg_ReceivedResponse::ID:
+ case ResourceMsg_ReceivedRedirect::ID:
+ case ResourceMsg_SetDataBuffer::ID:
+ case ResourceMsg_DataReceived::ID:
+ case ResourceMsg_RequestComplete::ID: {
+ bool result = PickleIterator(msg).ReadInt(&request_id);
+ DCHECK(result);
+ break;
+ }
+ }
+ return request_id;
+}
+
+static ResourceHostMsg_Request CreateResourceRequest(
+ const char* method,
+ ResourceType::Type type,
+ const GURL& url) {
+ ResourceHostMsg_Request request;
+ request.method = std::string(method);
+ request.url = url;
+ request.first_party_for_cookies = url; // bypass third-party cookie blocking
+ request.referrer_policy = WebKit::WebReferrerPolicyDefault;
+ request.load_flags = 0;
+ request.origin_pid = 0;
+ request.resource_type = type;
+ request.request_context = 0;
+ request.appcache_host_id = appcache::kNoHostId;
+ request.download_to_file = false;
+ request.is_main_frame = true;
+ request.frame_id = 0;
+ request.parent_is_main_frame = false;
+ request.parent_frame_id = -1;
+ request.transition_type = PAGE_TRANSITION_LINK;
+ request.allow_download = true;
+ return request;
+}
+
+// Spin up the message loop to kick off the request.
+static void KickOffRequest() {
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// We may want to move this to a shared space if it is useful for something else
+class ResourceIPCAccumulator {
+ public:
+ void AddMessage(const IPC::Message& msg) {
+ messages_.push_back(msg);
+ }
+
+ // This groups the messages by their request ID. The groups will be in order
+ // that the first message for each request ID was received, and the messages
+ // within the groups will be in the order that they appeared.
+ // Note that this clears messages_.
+ typedef std::vector< std::vector<IPC::Message> > ClassifiedMessages;
+ void GetClassifiedMessages(ClassifiedMessages* msgs);
+
+ private:
+ std::vector<IPC::Message> messages_;
+};
+
+// This is very inefficient as a result of repeatedly extracting the ID, use
+// only for tests!
+void ResourceIPCAccumulator::GetClassifiedMessages(ClassifiedMessages* msgs) {
+ while (!messages_.empty()) {
+ // Ignore unknown message types as it is valid for code to generated other
+ // IPCs as side-effects that we are not testing here.
+ int cur_id = RequestIDForMessage(messages_[0]);
+ if (cur_id != -1) {
+ std::vector<IPC::Message> cur_requests;
+ cur_requests.push_back(messages_[0]);
+ // find all other messages with this ID
+ for (int i = 1; i < static_cast<int>(messages_.size()); i++) {
+ int id = RequestIDForMessage(messages_[i]);
+ if (id == cur_id) {
+ cur_requests.push_back(messages_[i]);
+ messages_.erase(messages_.begin() + i);
+ i--;
+ }
+ }
+ msgs->push_back(cur_requests);
+ }
+ messages_.erase(messages_.begin());
+ }
+}
+
+class MockURLRequestContextSelector
+ : public ResourceMessageFilter::URLRequestContextSelector {
+ public:
+ explicit MockURLRequestContextSelector(
+ net::URLRequestContext* request_context)
+ : request_context_(request_context) {}
+
+ virtual net::URLRequestContext* GetRequestContext(
+ ResourceType::Type request_type) OVERRIDE {
+ return request_context_;
+ }
+
+ private:
+ net::URLRequestContext* const request_context_;
+};
+
+// This class forwards the incoming messages to the ResourceDispatcherHostTest.
+// This is used to emulate different sub-processes, since this filter will
+// have a different ID than the original. For the test, we want all the incoming
+// messages to go to the same place, which is why this forwards.
+class ForwardingFilter : public ResourceMessageFilter {
+ public:
+ explicit ForwardingFilter(IPC::Sender* dest,
+ ResourceContext* resource_context)
+ : ResourceMessageFilter(
+ ChildProcessHostImpl::GenerateChildProcessUniqueId(),
+ PROCESS_TYPE_RENDERER,
+ resource_context, NULL, NULL, NULL,
+ new MockURLRequestContextSelector(
+ resource_context->GetRequestContext())),
+ dest_(dest) {
+ OnChannelConnected(base::GetCurrentProcId());
+ }
+
+ // ResourceMessageFilter override
+ virtual bool Send(IPC::Message* msg) OVERRIDE {
+ if (!dest_)
+ return false;
+ return dest_->Send(msg);
+ }
+
+ protected:
+ virtual ~ForwardingFilter() {}
+
+ private:
+ IPC::Sender* dest_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForwardingFilter);
+};
+
+// This class is a variation on URLRequestTestJob in that it does
+// not complete start upon entry, only when specifically told to.
+class URLRequestTestDelayedStartJob : public net::URLRequestTestJob {
+ public:
+ URLRequestTestDelayedStartJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate)
+ : net::URLRequestTestJob(request, network_delegate) {
+ Init();
+ }
+ URLRequestTestDelayedStartJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ bool auto_advance)
+ : net::URLRequestTestJob(request, network_delegate, auto_advance) {
+ Init();
+ }
+ URLRequestTestDelayedStartJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance)
+ : net::URLRequestTestJob(request,
+ network_delegate,
+ response_headers,
+ response_data,
+ auto_advance) {
+ Init();
+ }
+
+ // Do nothing until you're told to.
+ virtual void Start() OVERRIDE {}
+
+ // Finish starting a URL request whose job is an instance of
+ // URLRequestTestDelayedStartJob. It is illegal to call this routine
+ // with a URLRequest that does not use URLRequestTestDelayedStartJob.
+ static void CompleteStart(net::URLRequest* request) {
+ for (URLRequestTestDelayedStartJob* job = list_head_;
+ job;
+ job = job->next_) {
+ if (job->request() == request) {
+ job->net::URLRequestTestJob::Start();
+ return;
+ }
+ }
+ NOTREACHED();
+ }
+
+ static bool DelayedStartQueueEmpty() {
+ return !list_head_;
+ }
+
+ static void ClearQueue() {
+ if (list_head_) {
+ LOG(ERROR)
+ << "Unreleased entries on URLRequestTestDelayedStartJob delay queue"
+ << "; may result in leaks.";
+ list_head_ = NULL;
+ }
+ }
+
+ protected:
+ virtual ~URLRequestTestDelayedStartJob() {
+ for (URLRequestTestDelayedStartJob** job = &list_head_; *job;
+ job = &(*job)->next_) {
+ if (*job == this) {
+ *job = (*job)->next_;
+ return;
+ }
+ }
+ NOTREACHED();
+ }
+
+ private:
+ void Init() {
+ next_ = list_head_;
+ list_head_ = this;
+ }
+
+ static URLRequestTestDelayedStartJob* list_head_;
+ URLRequestTestDelayedStartJob* next_;
+};
+
+URLRequestTestDelayedStartJob*
+URLRequestTestDelayedStartJob::list_head_ = NULL;
+
+// This class is a variation on URLRequestTestJob in that it
+// returns IO_pending errors before every read, not just the first one.
+class URLRequestTestDelayedCompletionJob : public net::URLRequestTestJob {
+ public:
+ URLRequestTestDelayedCompletionJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate)
+ : net::URLRequestTestJob(request, network_delegate) {}
+ URLRequestTestDelayedCompletionJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ bool auto_advance)
+ : net::URLRequestTestJob(request, network_delegate, auto_advance) {}
+ URLRequestTestDelayedCompletionJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance)
+ : net::URLRequestTestJob(request,
+ network_delegate,
+ response_headers,
+ response_data,
+ auto_advance) {}
+
+ protected:
+ virtual ~URLRequestTestDelayedCompletionJob() {}
+
+ private:
+ virtual bool NextReadAsync() OVERRIDE { return true; }
+};
+
+class URLRequestBigJob : public net::URLRequestSimpleJob {
+ public:
+ URLRequestBigJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate)
+ : net::URLRequestSimpleJob(request, network_delegate) {
+ }
+
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE {
+ *mime_type = "text/plain";
+ *charset = "UTF-8";
+
+ std::string text;
+ int count;
+ if (!ParseURL(request_->url(), &text, &count))
+ return net::ERR_INVALID_URL;
+
+ data->reserve(text.size() * count);
+ for (int i = 0; i < count; ++i)
+ data->append(text);
+
+ return net::OK;
+ }
+
+ private:
+ virtual ~URLRequestBigJob() {}
+
+ // big-job:substring,N
+ static bool ParseURL(const GURL& url, std::string* text, int* count) {
+ std::vector<std::string> parts;
+ base::SplitString(url.path(), ',', &parts);
+
+ if (parts.size() != 2)
+ return false;
+
+ *text = parts[0];
+ return base::StringToInt(parts[1], count);
+ }
+};
+
+// Associated with an URLRequest to determine if the URLRequest gets deleted.
+class TestUserData : public base::SupportsUserData::Data {
+ public:
+ explicit TestUserData(bool* was_deleted)
+ : was_deleted_(was_deleted) {
+ }
+
+ virtual ~TestUserData() {
+ *was_deleted_ = true;
+ }
+
+ private:
+ bool* was_deleted_;
+};
+
+class TransfersAllNavigationsContentBrowserClient
+ : public TestContentBrowserClient {
+ public:
+ virtual bool ShouldSwapProcessesForRedirect(ResourceContext* resource_context,
+ const GURL& current_url,
+ const GURL& new_url) OVERRIDE {
+ return true;
+ }
+};
+
+enum GenericResourceThrottleFlags {
+ NONE = 0,
+ DEFER_STARTING_REQUEST = 1 << 0,
+ DEFER_PROCESSING_RESPONSE = 1 << 1,
+ CANCEL_BEFORE_START = 1 << 2
+};
+
+// Throttle that tracks the current throttle blocking a request. Only one
+// can throttle any request at a time.
+class GenericResourceThrottle : public ResourceThrottle {
+ public:
+ // The value is used to indicate that the throttle should not provide
+ // a error code when cancelling a request. net::OK is used, because this
+ // is not an error code.
+ static const int USE_DEFAULT_CANCEL_ERROR_CODE = net::OK;
+
+ GenericResourceThrottle(int flags, int code)
+ : flags_(flags),
+ error_code_for_cancellation_(code) {
+ }
+
+ virtual ~GenericResourceThrottle() {
+ if (active_throttle_ == this)
+ active_throttle_ = NULL;
+ }
+
+ // ResourceThrottle implementation:
+ virtual void WillStartRequest(bool* defer) OVERRIDE {
+ ASSERT_EQ(NULL, active_throttle_);
+ if (flags_ & DEFER_STARTING_REQUEST) {
+ active_throttle_ = this;
+ *defer = true;
+ }
+
+ if (flags_ & CANCEL_BEFORE_START) {
+ if (error_code_for_cancellation_ == USE_DEFAULT_CANCEL_ERROR_CODE) {
+ controller()->Cancel();
+ } else {
+ controller()->CancelWithError(error_code_for_cancellation_);
+ }
+ }
+ }
+
+ virtual void WillProcessResponse(bool* defer) OVERRIDE {
+ ASSERT_EQ(NULL, active_throttle_);
+ if (flags_ & DEFER_PROCESSING_RESPONSE) {
+ active_throttle_ = this;
+ *defer = true;
+ }
+ }
+
+ void Resume() {
+ ASSERT_TRUE(this == active_throttle_);
+ active_throttle_ = NULL;
+ controller()->Resume();
+ }
+
+ static GenericResourceThrottle* active_throttle() {
+ return active_throttle_;
+ }
+
+ private:
+ int flags_; // bit-wise union of GenericResourceThrottleFlags.
+ int error_code_for_cancellation_;
+
+ // The currently active throttle, if any.
+ static GenericResourceThrottle* active_throttle_;
+};
+// static
+GenericResourceThrottle* GenericResourceThrottle::active_throttle_ = NULL;
+
+class TestResourceDispatcherHostDelegate
+ : public ResourceDispatcherHostDelegate {
+ public:
+ TestResourceDispatcherHostDelegate()
+ : create_two_throttles_(false),
+ flags_(NONE),
+ error_code_for_cancellation_(
+ GenericResourceThrottle::USE_DEFAULT_CANCEL_ERROR_CODE) {
+ }
+
+ void set_url_request_user_data(base::SupportsUserData::Data* user_data) {
+ user_data_.reset(user_data);
+ }
+
+ void set_flags(int value) {
+ flags_ = value;
+ }
+
+ void set_error_code_for_cancellation(int code) {
+ error_code_for_cancellation_ = code;
+ }
+
+ void set_create_two_throttles(bool create_two_throttles) {
+ create_two_throttles_ = create_two_throttles;
+ }
+
+ // ResourceDispatcherHostDelegate implementation:
+
+ virtual void RequestBeginning(
+ net::URLRequest* request,
+ ResourceContext* resource_context,
+ appcache::AppCacheService* appcache_service,
+ ResourceType::Type resource_type,
+ int child_id,
+ int route_id,
+ bool is_continuation_of_transferred_request,
+ ScopedVector<ResourceThrottle>* throttles) OVERRIDE {
+ if (user_data_) {
+ const void* key = user_data_.get();
+ request->SetUserData(key, user_data_.release());
+ }
+
+ if (flags_ != NONE) {
+ throttles->push_back(new GenericResourceThrottle(
+ flags_, error_code_for_cancellation_));
+ if (create_two_throttles_)
+ throttles->push_back(new GenericResourceThrottle(
+ flags_, error_code_for_cancellation_));
+ }
+ }
+
+ private:
+ bool create_two_throttles_;
+ int flags_;
+ int error_code_for_cancellation_;
+ scoped_ptr<base::SupportsUserData::Data> user_data_;
+};
+
+class ResourceDispatcherHostTest : public testing::Test,
+ public IPC::Sender {
+ public:
+ ResourceDispatcherHostTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ file_thread_(BrowserThread::FILE_USER_BLOCKING, &message_loop_),
+ cache_thread_(BrowserThread::CACHE, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_),
+ old_factory_(NULL),
+ resource_type_(ResourceType::SUB_RESOURCE),
+ send_data_received_acks_(false) {
+ browser_context_.reset(new TestBrowserContext());
+ BrowserContext::EnsureResourceContextInitialized(browser_context_.get());
+ message_loop_.RunUntilIdle();
+ filter_ = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+ }
+
+ virtual ~ResourceDispatcherHostTest() {
+ for (std::set<int>::iterator it = child_ids_.begin();
+ it != child_ids_.end(); ++it) {
+ host_.CancelRequestsForProcess(*it);
+ }
+ }
+
+ // IPC::Sender implementation
+ virtual bool Send(IPC::Message* msg) OVERRIDE {
+ accum_.AddMessage(*msg);
+
+ if (send_data_received_acks_ &&
+ msg->type() == ResourceMsg_DataReceived::ID) {
+ GenerateDataReceivedACK(*msg);
+ }
+
+ delete msg;
+ return true;
+ }
+
+ protected:
+ // testing::Test
+ virtual void SetUp() {
+ DCHECK(!test_fixture_);
+ test_fixture_ = this;
+ ChildProcessSecurityPolicyImpl::GetInstance()->Add(0);
+ net::URLRequest::Deprecated::RegisterProtocolFactory(
+ "test",
+ &ResourceDispatcherHostTest::Factory);
+ EnsureTestSchemeIsAllowed();
+ delay_start_ = false;
+ delay_complete_ = false;
+ url_request_jobs_created_count_ = 0;
+ }
+
+ virtual void TearDown() {
+ net::URLRequest::Deprecated::RegisterProtocolFactory("test", NULL);
+ if (!scheme_.empty())
+ net::URLRequest::Deprecated::RegisterProtocolFactory(
+ scheme_, old_factory_);
+
+ EXPECT_TRUE(URLRequestTestDelayedStartJob::DelayedStartQueueEmpty());
+ URLRequestTestDelayedStartJob::ClearQueue();
+
+ DCHECK(test_fixture_ == this);
+ test_fixture_ = NULL;
+
+ host_.Shutdown();
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->Remove(0);
+
+ // Flush the message loop to make application verifiers happy.
+ if (ResourceDispatcherHostImpl::Get())
+ ResourceDispatcherHostImpl::Get()->CancelRequestsForContext(
+ browser_context_->GetResourceContext());
+
+ WorkerServiceImpl::GetInstance()->PerformTeardownForTesting();
+
+ browser_context_.reset();
+ message_loop_.RunUntilIdle();
+ }
+
+ // Creates a request using the current test object as the filter.
+ void MakeTestRequest(int render_view_id,
+ int request_id,
+ const GURL& url);
+
+ // Generates a request using the given filter. This will probably be a
+ // ForwardingFilter.
+ void MakeTestRequest(ResourceMessageFilter* filter,
+ int render_view_id,
+ int request_id,
+ const GURL& url);
+
+ void CancelRequest(int request_id);
+
+ void CompleteStartRequest(int request_id);
+ void CompleteStartRequest(ResourceMessageFilter* filter, int request_id);
+
+ void EnsureSchemeIsAllowed(const std::string& scheme) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->IsWebSafeScheme(scheme))
+ policy->RegisterWebSafeScheme(scheme);
+ }
+
+ void EnsureTestSchemeIsAllowed() {
+ EnsureSchemeIsAllowed("test");
+ }
+
+ // Sets a particular response for any request from now on. To switch back to
+ // the default bahavior, pass an empty |headers|. |headers| should be raw-
+ // formatted (NULLs instead of EOLs).
+ void SetResponse(const std::string& headers, const std::string& data) {
+ response_headers_ = net::HttpUtil::AssembleRawHeaders(headers.data(),
+ headers.size());
+ response_data_ = data;
+ }
+ void SetResponse(const std::string& headers) {
+ SetResponse(headers, std::string());
+ }
+
+ // Sets a particular resource type for any request from now on.
+ void SetResourceType(ResourceType::Type type) {
+ resource_type_ = type;
+ }
+
+ void SendDataReceivedACKs(bool send_acks) {
+ send_data_received_acks_ = send_acks;
+ }
+
+ // Intercepts requests for the given protocol.
+ void HandleScheme(const std::string& scheme) {
+ DCHECK(scheme_.empty());
+ DCHECK(!old_factory_);
+ scheme_ = scheme;
+ old_factory_ = net::URLRequest::Deprecated::RegisterProtocolFactory(
+ scheme_, &ResourceDispatcherHostTest::Factory);
+ EnsureSchemeIsAllowed(scheme);
+ }
+
+ // Our own net::URLRequestJob factory.
+ static net::URLRequestJob* Factory(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ url_request_jobs_created_count_++;
+ if (test_fixture_->response_headers_.empty()) {
+ if (delay_start_) {
+ return new URLRequestTestDelayedStartJob(request, network_delegate);
+ } else if (delay_complete_) {
+ return new URLRequestTestDelayedCompletionJob(request,
+ network_delegate);
+ } else if (scheme == "big-job") {
+ return new URLRequestBigJob(request, network_delegate);
+ } else {
+ return new net::URLRequestTestJob(request, network_delegate);
+ }
+ } else {
+ if (delay_start_) {
+ return new URLRequestTestDelayedStartJob(
+ request, network_delegate,
+ test_fixture_->response_headers_, test_fixture_->response_data_,
+ false);
+ } else if (delay_complete_) {
+ return new URLRequestTestDelayedCompletionJob(
+ request, network_delegate,
+ test_fixture_->response_headers_, test_fixture_->response_data_,
+ false);
+ } else {
+ return new net::URLRequestTestJob(
+ request, network_delegate,
+ test_fixture_->response_headers_, test_fixture_->response_data_,
+ false);
+ }
+ }
+ }
+
+ void SetDelayedStartJobGeneration(bool delay_job_start) {
+ delay_start_ = delay_job_start;
+ }
+
+ void SetDelayedCompleteJobGeneration(bool delay_job_complete) {
+ delay_complete_ = delay_job_complete;
+ }
+
+ void GenerateDataReceivedACK(const IPC::Message& msg) {
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msg.type());
+
+ int request_id = -1;
+ bool result = PickleIterator(msg).ReadInt(&request_id);
+ DCHECK(result);
+ scoped_ptr<IPC::Message> ack(
+ new ResourceHostMsg_DataReceived_ACK(msg.routing_id(), request_id));
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&GenerateIPCMessage, filter_, base::Passed(&ack)));
+ }
+
+ base::MessageLoopForIO message_loop_;
+ BrowserThreadImpl ui_thread_;
+ BrowserThreadImpl file_thread_;
+ BrowserThreadImpl cache_thread_;
+ BrowserThreadImpl io_thread_;
+ scoped_ptr<TestBrowserContext> browser_context_;
+ scoped_refptr<ForwardingFilter> filter_;
+ ResourceDispatcherHostImpl host_;
+ ResourceIPCAccumulator accum_;
+ std::string response_headers_;
+ std::string response_data_;
+ std::string scheme_;
+ net::URLRequest::ProtocolFactory* old_factory_;
+ ResourceType::Type resource_type_;
+ bool send_data_received_acks_;
+ std::set<int> child_ids_;
+ static ResourceDispatcherHostTest* test_fixture_;
+ static bool delay_start_;
+ static bool delay_complete_;
+ static int url_request_jobs_created_count_;
+};
+// Static.
+ResourceDispatcherHostTest* ResourceDispatcherHostTest::test_fixture_ = NULL;
+bool ResourceDispatcherHostTest::delay_start_ = false;
+bool ResourceDispatcherHostTest::delay_complete_ = false;
+int ResourceDispatcherHostTest::url_request_jobs_created_count_ = 0;
+
+void ResourceDispatcherHostTest::MakeTestRequest(int render_view_id,
+ int request_id,
+ const GURL& url) {
+ MakeTestRequest(filter_.get(), render_view_id, request_id, url);
+}
+
+void ResourceDispatcherHostTest::MakeTestRequest(
+ ResourceMessageFilter* filter,
+ int render_view_id,
+ int request_id,
+ const GURL& url) {
+ // If it's already there, this'll be dropped on the floor, which is fine.
+ child_ids_.insert(filter->child_id());
+
+ ResourceHostMsg_Request request =
+ CreateResourceRequest("GET", resource_type_, url);
+ ResourceHostMsg_RequestResource msg(render_view_id, request_id, request);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter, &msg_was_ok);
+ KickOffRequest();
+}
+
+void ResourceDispatcherHostTest::CancelRequest(int request_id) {
+ host_.CancelRequest(filter_->child_id(), request_id, false);
+}
+
+void ResourceDispatcherHostTest::CompleteStartRequest(int request_id) {
+ CompleteStartRequest(filter_.get(), request_id);
+}
+
+void ResourceDispatcherHostTest::CompleteStartRequest(
+ ResourceMessageFilter* filter,
+ int request_id) {
+ GlobalRequestID gid(filter->child_id(), request_id);
+ net::URLRequest* req = host_.GetURLRequest(gid);
+ EXPECT_TRUE(req);
+ if (req)
+ URLRequestTestDelayedStartJob::CompleteStart(req);
+}
+
+void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages,
+ const std::string& reference_data) {
+ // A successful request will have received 4 messages:
+ // ReceivedResponse (indicates headers received)
+ // SetDataBuffer (contains shared memory handle)
+ // DataReceived (data offset and length into shared memory)
+ // RequestComplete (request is done)
+ //
+ // This function verifies that we received 4 messages and that they
+ // are appropriate.
+ ASSERT_EQ(4U, messages.size());
+
+ // The first messages should be received response
+ ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type());
+
+ ASSERT_EQ(ResourceMsg_SetDataBuffer::ID, messages[1].type());
+
+ PickleIterator iter(messages[1]);
+ int request_id;
+ ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &request_id));
+ base::SharedMemoryHandle shm_handle;
+ ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_handle));
+ int shm_size;
+ ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_size));
+
+ // Followed by the data, currently we only do the data in one chunk, but
+ // should probably test multiple chunks later
+ ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2].type());
+
+ PickleIterator iter2(messages[2]);
+ ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &request_id));
+ int data_offset;
+ ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &data_offset));
+ int data_length;
+ ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &data_length));
+
+ ASSERT_EQ(reference_data.size(), static_cast<size_t>(data_length));
+ ASSERT_GE(shm_size, data_length);
+
+ base::SharedMemory shared_mem(shm_handle, true); // read only
+ shared_mem.Map(data_length);
+ const char* data = static_cast<char*>(shared_mem.memory()) + data_offset;
+ ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_length));
+
+ // The last message should be all data received.
+ ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[3].type());
+}
+
+void CheckFailedRequest(const std::vector<IPC::Message>& messages,
+ const std::string& reference_data,
+ int expected_error) {
+ ASSERT_LT(0U, messages.size());
+ ASSERT_GE(2U, messages.size());
+ size_t failure_index = messages.size() - 1;
+
+ if (messages.size() == 2) {
+ EXPECT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type());
+ }
+ EXPECT_EQ(ResourceMsg_RequestComplete::ID, messages[failure_index].type());
+
+ int request_id;
+ int error_code;
+
+ PickleIterator iter(messages[failure_index]);
+ EXPECT_TRUE(IPC::ReadParam(&messages[failure_index], &iter, &request_id));
+ EXPECT_TRUE(IPC::ReadParam(&messages[failure_index], &iter, &error_code));
+ EXPECT_EQ(expected_error, error_code);
+}
+
+// Tests whether many messages get dispatched properly.
+TEST_F(ResourceDispatcherHostTest, TestMany) {
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3());
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // sorts out all the messages we saw by request
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // there are three requests, so we should have gotten them classified as such
+ ASSERT_EQ(3U, msgs.size());
+
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_2());
+ CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3());
+}
+
+void CheckCancelledRequestCompleteMessage(const IPC::Message& message) {
+ ASSERT_EQ(ResourceMsg_RequestComplete::ID, message.type());
+
+ int request_id;
+ int error_code;
+
+ PickleIterator iter(message);
+ ASSERT_TRUE(IPC::ReadParam(&message, &iter, &request_id));
+ ASSERT_TRUE(IPC::ReadParam(&message, &iter, &error_code));
+
+ EXPECT_EQ(net::ERR_ABORTED, error_code);
+}
+
+// Tests whether messages get canceled properly. We issue three requests,
+// cancel one of them, and make sure that each sent the proper notifications.
+TEST_F(ResourceDispatcherHostTest, Cancel) {
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3());
+ CancelRequest(2);
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // there are three requests, so we should have gotten them classified as such
+ ASSERT_EQ(3U, msgs.size());
+
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+ CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3());
+
+ // Check that request 2 got canceled.
+ ASSERT_EQ(2U, msgs[1].size());
+ ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[1][0].type());
+ CheckCancelledRequestCompleteMessage(msgs[1][1]);
+}
+
+TEST_F(ResourceDispatcherHostTest, CancelWhileStartIsDeferred) {
+ bool was_deleted = false;
+
+ // Arrange to have requests deferred before starting.
+ TestResourceDispatcherHostDelegate delegate;
+ delegate.set_flags(DEFER_STARTING_REQUEST);
+ delegate.set_url_request_user_data(new TestUserData(&was_deleted));
+ host_.SetDelegate(&delegate);
+
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ CancelRequest(1);
+
+ // Our TestResourceThrottle should not have been deleted yet. This is to
+ // ensure that destruction of the URLRequest happens asynchronously to
+ // calling CancelRequest.
+ EXPECT_FALSE(was_deleted);
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_TRUE(was_deleted);
+}
+
+// Tests if cancel is called in ResourceThrottle::WillStartRequest, then the
+// URLRequest will not be started.
+TEST_F(ResourceDispatcherHostTest, CancelInResourceThrottleWillStartRequest) {
+ TestResourceDispatcherHostDelegate delegate;
+ delegate.set_flags(CANCEL_BEFORE_START);
+ host_.SetDelegate(&delegate);
+
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // Check that request got canceled.
+ ASSERT_EQ(1U, msgs[0].size());
+ CheckCancelledRequestCompleteMessage(msgs[0][0]);
+
+ // Make sure URLRequest is never started.
+ EXPECT_EQ(0, url_request_jobs_created_count_);
+}
+
+TEST_F(ResourceDispatcherHostTest, PausedStartError) {
+ // Arrange to have requests deferred before processing response headers.
+ TestResourceDispatcherHostDelegate delegate;
+ delegate.set_flags(DEFER_PROCESSING_RESPONSE);
+ host_.SetDelegate(&delegate);
+
+ SetDelayedStartJobGeneration(true);
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_error());
+ CompleteStartRequest(1);
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(0, host_.pending_requests());
+}
+
+TEST_F(ResourceDispatcherHostTest, ThrottleAndResumeTwice) {
+ // Arrange to have requests deferred before starting.
+ TestResourceDispatcherHostDelegate delegate;
+ delegate.set_flags(DEFER_STARTING_REQUEST);
+ delegate.set_create_two_throttles(true);
+ host_.SetDelegate(&delegate);
+
+ // Make sure the first throttle blocked the request, and then resume.
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ GenericResourceThrottle* first_throttle =
+ GenericResourceThrottle::active_throttle();
+ ASSERT_TRUE(first_throttle);
+ first_throttle->Resume();
+
+ // Make sure the second throttle blocked the request, and then resume.
+ ASSERT_TRUE(GenericResourceThrottle::active_throttle());
+ ASSERT_NE(first_throttle, GenericResourceThrottle::active_throttle());
+ GenericResourceThrottle::active_throttle()->Resume();
+
+ ASSERT_FALSE(GenericResourceThrottle::active_throttle());
+
+ // The request is started asynchronously.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ EXPECT_EQ(0, host_.pending_requests());
+
+ // Make sure the request completed successfully.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+}
+
+
+// Tests that the delegate can cancel a request and provide a error code.
+TEST_F(ResourceDispatcherHostTest, CancelInDelegate) {
+ TestResourceDispatcherHostDelegate delegate;
+ delegate.set_flags(CANCEL_BEFORE_START);
+ delegate.set_error_code_for_cancellation(net::ERR_ACCESS_DENIED);
+ host_.SetDelegate(&delegate);
+
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ // The request will get cancelled by the throttle.
+
+ // flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // Check the cancellation
+ ASSERT_EQ(1U, msgs.size());
+ ASSERT_EQ(1U, msgs[0].size());
+ ASSERT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][0].type());
+
+ int request_id;
+ int error_code;
+
+ PickleIterator iter(msgs[0][0]);
+ ASSERT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id));
+ ASSERT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code));
+
+ EXPECT_EQ(net::ERR_ACCESS_DENIED, error_code);
+}
+
+// The host delegate acts as a second one so we can have some requests
+// pending and some canceled.
+class TestFilter : public ForwardingFilter {
+ public:
+ explicit TestFilter(ResourceContext* resource_context)
+ : ForwardingFilter(NULL, resource_context),
+ has_canceled_(false),
+ received_after_canceled_(0) {
+ }
+
+ // ForwardingFilter override
+ virtual bool Send(IPC::Message* msg) OVERRIDE {
+ // no messages should be received when the process has been canceled
+ if (has_canceled_)
+ received_after_canceled_++;
+ delete msg;
+ return true;
+ }
+
+ bool has_canceled_;
+ int received_after_canceled_;
+
+ private:
+ virtual ~TestFilter() {}
+};
+
+// Tests CancelRequestsForProcess
+TEST_F(ResourceDispatcherHostTest, TestProcessCancel) {
+ scoped_refptr<TestFilter> test_filter = new TestFilter(
+ browser_context_->GetResourceContext());
+
+ // request 1 goes to the test delegate
+ ResourceHostMsg_Request request = CreateResourceRequest(
+ "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1());
+
+ MakeTestRequest(test_filter.get(), 0, 1,
+ net::URLRequestTestJob::test_url_1());
+
+ // request 2 goes to us
+ MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2());
+
+ // request 3 goes to the test delegate
+ MakeTestRequest(test_filter.get(), 0, 3,
+ net::URLRequestTestJob::test_url_3());
+
+ // Make sure all requests have finished stage one. test_url_1 will have
+ // finished.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // TODO(mbelshe):
+ // Now that the async IO path is in place, the IO always completes on the
+ // initial call; so the requests have already completed. This basically
+ // breaks the whole test.
+ //EXPECT_EQ(3, host_.pending_requests());
+
+ // Process each request for one level so one callback is called.
+ for (int i = 0; i < 2; i++)
+ EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
+
+ // Cancel the requests to the test process.
+ host_.CancelRequestsForProcess(filter_->child_id());
+ test_filter->has_canceled_ = true;
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ EXPECT_EQ(0, host_.pending_requests());
+
+ // The test delegate should not have gotten any messages after being canceled.
+ ASSERT_EQ(0, test_filter->received_after_canceled_);
+
+ // We should have gotten exactly one result.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2());
+}
+
+// Tests blocking and resuming requests.
+TEST_F(ResourceDispatcherHostTest, TestBlockingResumingRequests) {
+ host_.BlockRequestsForRoute(filter_->child_id(), 1);
+ host_.BlockRequestsForRoute(filter_->child_id(), 2);
+ host_.BlockRequestsForRoute(filter_->child_id(), 3);
+
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(1, 2, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3());
+ MakeTestRequest(1, 4, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(2, 5, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(3, 6, net::URLRequestTestJob::test_url_3());
+
+ // Flush all the pending requests
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sort out all the messages we saw by request
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // All requests but the 2 for the RVH 0 should have been blocked.
+ ASSERT_EQ(2U, msgs.size());
+
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3());
+
+ // Resume requests for RVH 1 and flush pending requests.
+ host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 1);
+ KickOffRequest();
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(2U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_1());
+
+ // Test that new requests are not blocked for RVH 1.
+ MakeTestRequest(1, 7, net::URLRequestTestJob::test_url_1());
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+
+ // Now resumes requests for all RVH (2 and 3).
+ host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 2);
+ host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 3);
+ KickOffRequest();
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(2U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3());
+}
+
+// Tests blocking and canceling requests.
+TEST_F(ResourceDispatcherHostTest, TestBlockingCancelingRequests) {
+ host_.BlockRequestsForRoute(filter_->child_id(), 1);
+
+ MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(1, 2, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3());
+ MakeTestRequest(1, 4, net::URLRequestTestJob::test_url_1());
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sort out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // The 2 requests for the RVH 0 should have been processed.
+ ASSERT_EQ(2U, msgs.size());
+
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3());
+
+ // Cancel requests for RVH 1.
+ host_.CancelBlockedRequestsForRoute(filter_->child_id(), 1);
+ KickOffRequest();
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(0U, msgs.size());
+}
+
+// Tests that blocked requests are canceled if their associated process dies.
+TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsProcessDies) {
+ // This second filter is used to emulate a second process.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ host_.BlockRequestsForRoute(second_filter->child_id(), 0);
+
+ MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(second_filter.get(), 0, 2,
+ net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3());
+ MakeTestRequest(second_filter.get(), 0, 4,
+ net::URLRequestTestJob::test_url_1());
+
+ // Simulate process death.
+ host_.CancelRequestsForProcess(second_filter->child_id());
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sort out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // The 2 requests for the RVH 0 should have been processed.
+ ASSERT_EQ(2U, msgs.size());
+
+ CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1());
+ CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3());
+
+ EXPECT_TRUE(host_.blocked_loaders_map_.empty());
+}
+
+// Tests that blocked requests don't leak when the ResourceDispatcherHost goes
+// away. Note that we rely on Purify for finding the leaks if any.
+// If this test turns the Purify bot red, check the ResourceDispatcherHost
+// destructor to make sure the blocked requests are deleted.
+TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsDontLeak) {
+ // This second filter is used to emulate a second process.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ host_.BlockRequestsForRoute(filter_->child_id(), 1);
+ host_.BlockRequestsForRoute(filter_->child_id(), 2);
+ host_.BlockRequestsForRoute(second_filter->child_id(), 1);
+
+ MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(filter_.get(), 1, 2, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3());
+ MakeTestRequest(second_filter.get(), 1, 4,
+ net::URLRequestTestJob::test_url_1());
+ MakeTestRequest(filter_.get(), 2, 5, net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(filter_.get(), 2, 6, net::URLRequestTestJob::test_url_3());
+
+ host_.CancelRequestsForProcess(filter_->child_id());
+ host_.CancelRequestsForProcess(second_filter->child_id());
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+}
+
+// Test the private helper method "CalculateApproximateMemoryCost()".
+TEST_F(ResourceDispatcherHostTest, CalculateApproximateMemoryCost) {
+ net::URLRequestContext context;
+ net::URLRequest req(GURL("http://www.google.com"), NULL, &context);
+ EXPECT_EQ(4427,
+ ResourceDispatcherHostImpl::CalculateApproximateMemoryCost(&req));
+
+ // Add 9 bytes of referrer.
+ req.SetReferrer("123456789");
+ EXPECT_EQ(4436,
+ ResourceDispatcherHostImpl::CalculateApproximateMemoryCost(&req));
+
+ // Add 33 bytes of upload content.
+ std::string upload_content;
+ upload_content.resize(33);
+ std::fill(upload_content.begin(), upload_content.end(), 'x');
+ scoped_ptr<net::UploadElementReader> reader(new net::UploadBytesElementReader(
+ upload_content.data(), upload_content.size()));
+ req.set_upload(make_scoped_ptr(
+ net::UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+
+ // Since the upload throttling is disabled, this has no effect on the cost.
+ EXPECT_EQ(4436,
+ ResourceDispatcherHostImpl::CalculateApproximateMemoryCost(&req));
+}
+
+// Test that too much memory for outstanding requests for a particular
+// render_process_host_id causes requests to fail.
+TEST_F(ResourceDispatcherHostTest, TooMuchOutstandingRequestsMemory) {
+ // Expected cost of each request as measured by
+ // ResourceDispatcherHost::CalculateApproximateMemoryCost().
+ int kMemoryCostOfTest2Req =
+ ResourceDispatcherHostImpl::kAvgBytesPerOutstandingRequest +
+ std::string("GET").size() +
+ net::URLRequestTestJob::test_url_2().spec().size();
+
+ // Tighten the bound on the ResourceDispatcherHost, to speed things up.
+ int kMaxCostPerProcess = 440000;
+ host_.set_max_outstanding_requests_cost_per_process(kMaxCostPerProcess);
+
+ // Determine how many instance of test_url_2() we can request before
+ // throttling kicks in.
+ size_t kMaxRequests = kMaxCostPerProcess / kMemoryCostOfTest2Req;
+
+ // This second filter is used to emulate a second process.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ // Saturate the number of outstanding requests for our process.
+ for (size_t i = 0; i < kMaxRequests; ++i) {
+ MakeTestRequest(filter_.get(), 0, i + 1,
+ net::URLRequestTestJob::test_url_2());
+ }
+
+ // Issue two more requests for our process -- these should fail immediately.
+ MakeTestRequest(filter_.get(), 0, kMaxRequests + 1,
+ net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(filter_.get(), 0, kMaxRequests + 2,
+ net::URLRequestTestJob::test_url_2());
+
+ // Issue two requests for the second process -- these should succeed since
+ // it is just process 0 that is saturated.
+ MakeTestRequest(second_filter.get(), 0, kMaxRequests + 3,
+ net::URLRequestTestJob::test_url_2());
+ MakeTestRequest(second_filter.get(), 0, kMaxRequests + 4,
+ net::URLRequestTestJob::test_url_2());
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // We issued (kMaxRequests + 4) total requests.
+ ASSERT_EQ(kMaxRequests + 4, msgs.size());
+
+ // Check that the first kMaxRequests succeeded.
+ for (size_t i = 0; i < kMaxRequests; ++i)
+ CheckSuccessfulRequest(msgs[i], net::URLRequestTestJob::test_data_2());
+
+ // Check that the subsequent two requests (kMaxRequests + 1) and
+ // (kMaxRequests + 2) were failed, since the per-process bound was reached.
+ for (int i = 0; i < 2; ++i) {
+ // Should have sent a single RequestComplete message.
+ int index = kMaxRequests + i;
+ CheckFailedRequest(msgs[index], net::URLRequestTestJob::test_data_2(),
+ net::ERR_INSUFFICIENT_RESOURCES);
+ }
+
+ // The final 2 requests should have succeeded.
+ CheckSuccessfulRequest(msgs[kMaxRequests + 2],
+ net::URLRequestTestJob::test_data_2());
+ CheckSuccessfulRequest(msgs[kMaxRequests + 3],
+ net::URLRequestTestJob::test_data_2());
+}
+
+// Test that when too many requests are outstanding for a particular
+// render_process_host_id, any subsequent request from it fails. Also verify
+// that the global limit is honored.
+TEST_F(ResourceDispatcherHostTest, TooManyOutstandingRequests) {
+ // Tighten the bound on the ResourceDispatcherHost, to speed things up.
+ const size_t kMaxRequestsPerProcess = 2;
+ host_.set_max_num_in_flight_requests_per_process(kMaxRequestsPerProcess);
+ const size_t kMaxRequests = 3;
+ host_.set_max_num_in_flight_requests(kMaxRequests);
+
+ // Needed to emulate additional processes.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+ scoped_refptr<ForwardingFilter> third_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ // Saturate the number of outstanding requests for our process.
+ for (size_t i = 0; i < kMaxRequestsPerProcess; ++i) {
+ MakeTestRequest(filter_.get(), 0, i + 1,
+ net::URLRequestTestJob::test_url_2());
+ }
+
+ // Issue another request for our process -- this should fail immediately.
+ MakeTestRequest(filter_.get(), 0, kMaxRequestsPerProcess + 1,
+ net::URLRequestTestJob::test_url_2());
+
+ // Issue a request for the second process -- this should succeed, because it
+ // is just process 0 that is saturated.
+ MakeTestRequest(second_filter.get(), 0, kMaxRequestsPerProcess + 2,
+ net::URLRequestTestJob::test_url_2());
+
+ // Issue a request for the third process -- this should fail, because the
+ // global limit has been reached.
+ MakeTestRequest(third_filter.get(), 0, kMaxRequestsPerProcess + 3,
+ net::URLRequestTestJob::test_url_2());
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // The processes issued the following requests:
+ // #1 issued kMaxRequestsPerProcess that passed + 1 that failed
+ // #2 issued 1 request that passed
+ // #3 issued 1 request that failed
+ ASSERT_EQ((kMaxRequestsPerProcess + 1) + 1 + 1, msgs.size());
+
+ for (size_t i = 0; i < kMaxRequestsPerProcess; ++i)
+ CheckSuccessfulRequest(msgs[i], net::URLRequestTestJob::test_data_2());
+
+ CheckFailedRequest(msgs[kMaxRequestsPerProcess + 0],
+ net::URLRequestTestJob::test_data_2(),
+ net::ERR_INSUFFICIENT_RESOURCES);
+ CheckSuccessfulRequest(msgs[kMaxRequestsPerProcess + 1],
+ net::URLRequestTestJob::test_data_2());
+ CheckFailedRequest(msgs[kMaxRequestsPerProcess + 2],
+ net::URLRequestTestJob::test_data_2(),
+ net::ERR_INSUFFICIENT_RESOURCES);
+}
+
+// Tests that we sniff the mime type for a simple request.
+TEST_F(ResourceDispatcherHostTest, MimeSniffed) {
+ std::string raw_headers("HTTP/1.1 200 OK\n\n");
+ std::string response_data("<html><title>Test One</title></html>");
+ SetResponse(raw_headers, response_data);
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+
+ ResourceResponseHead response_head;
+ GetResponseHead(msgs[0], &response_head);
+ ASSERT_EQ("text/html", response_head.mime_type);
+}
+
+// Tests that we don't sniff the mime type when the server provides one.
+TEST_F(ResourceDispatcherHostTest, MimeNotSniffed) {
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Content-type: image/jpeg\n\n");
+ std::string response_data("<html><title>Test One</title></html>");
+ SetResponse(raw_headers, response_data);
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+
+ ResourceResponseHead response_head;
+ GetResponseHead(msgs[0], &response_head);
+ ASSERT_EQ("image/jpeg", response_head.mime_type);
+}
+
+// Tests that we don't sniff the mime type when there is no message body.
+TEST_F(ResourceDispatcherHostTest, MimeNotSniffed2) {
+ SetResponse("HTTP/1.1 304 Not Modified\n\n");
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+
+ ResourceResponseHead response_head;
+ GetResponseHead(msgs[0], &response_head);
+ ASSERT_EQ("", response_head.mime_type);
+}
+
+TEST_F(ResourceDispatcherHostTest, MimeSniff204) {
+ SetResponse("HTTP/1.1 204 No Content\n\n");
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+
+ ResourceResponseHead response_head;
+ GetResponseHead(msgs[0], &response_head);
+ ASSERT_EQ("text/plain", response_head.mime_type);
+}
+
+TEST_F(ResourceDispatcherHostTest, MimeSniffEmpty) {
+ SetResponse("HTTP/1.1 200 OK\n\n");
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+ ASSERT_EQ(1U, msgs.size());
+
+ ResourceResponseHead response_head;
+ GetResponseHead(msgs[0], &response_head);
+ ASSERT_EQ("text/plain", response_head.mime_type);
+}
+
+// Tests for crbug.com/31266 (Non-2xx + application/octet-stream).
+TEST_F(ResourceDispatcherHostTest, ForbiddenDownload) {
+ std::string raw_headers("HTTP/1.1 403 Forbidden\n"
+ "Content-disposition: attachment; filename=blah\n"
+ "Content-type: application/octet-stream\n\n");
+ std::string response_data("<html><title>Test One</title></html>");
+ SetResponse(raw_headers, response_data);
+
+ // Only MAIN_FRAMEs can trigger a download.
+ SetResourceType(ResourceType::MAIN_FRAME);
+
+ HandleScheme("http");
+ MakeTestRequest(0, 1, GURL("http:bla"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sorts out all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // We should have gotten one RequestComplete message.
+ ASSERT_EQ(1U, msgs[0].size());
+ EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][0].type());
+
+ // The RequestComplete message should have had the error code of
+ // ERR_FILE_NOT_FOUND.
+ int request_id;
+ int error_code;
+
+ PickleIterator iter(msgs[0][0]);
+ EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id));
+ EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code));
+
+ EXPECT_EQ(1, request_id);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, error_code);
+}
+
+// Test for http://crbug.com/76202 . We don't want to destroy a
+// download request prematurely when processing a cancellation from
+// the renderer.
+TEST_F(ResourceDispatcherHostTest, IgnoreCancelForDownloads) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ int render_view_id = 0;
+ int request_id = 1;
+
+ std::string raw_headers("HTTP\n"
+ "Content-disposition: attachment; filename=foo\n\n");
+ std::string response_data("01234567890123456789\x01foobar");
+
+ // Get past sniffing metrics in the BufferedResourceHandler. Note that
+ // if we don't get past the sniffing metrics, the result will be that
+ // the BufferedResourceHandler won't have figured out that it's a download,
+ // won't have constructed a DownloadResourceHandler, and and the request
+ // will be successfully canceled below, failing the test.
+ response_data.resize(1025, ' ');
+
+ SetResponse(raw_headers, response_data);
+ SetResourceType(ResourceType::MAIN_FRAME);
+ SetDelayedCompleteJobGeneration(true);
+ HandleScheme("http");
+
+ MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah"));
+ // Return some data so that the request is identified as a download
+ // and the proper resource handlers are created.
+ EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
+
+ // And now simulate a cancellation coming from the renderer.
+ ResourceHostMsg_CancelRequest msg(filter_->child_id(), request_id);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+
+ // Since the request had already started processing as a download,
+ // the cancellation above should have been ignored and the request
+ // should still be alive.
+ EXPECT_EQ(1, host_.pending_requests());
+
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+}
+
+TEST_F(ResourceDispatcherHostTest, CancelRequestsForContext) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ int render_view_id = 0;
+ int request_id = 1;
+
+ std::string raw_headers("HTTP\n"
+ "Content-disposition: attachment; filename=foo\n\n");
+ std::string response_data("01234567890123456789\x01foobar");
+ // Get past sniffing metrics.
+ response_data.resize(1025, ' ');
+
+ SetResponse(raw_headers, response_data);
+ SetResourceType(ResourceType::MAIN_FRAME);
+ SetDelayedCompleteJobGeneration(true);
+ HandleScheme("http");
+
+ MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah"));
+ // Return some data so that the request is identified as a download
+ // and the proper resource handlers are created.
+ EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
+
+ // And now simulate a cancellation coming from the renderer.
+ ResourceHostMsg_CancelRequest msg(filter_->child_id(), request_id);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+
+ // Since the request had already started processing as a download,
+ // the cancellation above should have been ignored and the request
+ // should still be alive.
+ EXPECT_EQ(1, host_.pending_requests());
+
+ // Cancelling by other methods shouldn't work either.
+ host_.CancelRequestsForProcess(render_view_id);
+ EXPECT_EQ(1, host_.pending_requests());
+
+ // Cancelling by context should work.
+ host_.CancelRequestsForContext(filter_->resource_context());
+ EXPECT_EQ(0, host_.pending_requests());
+}
+
+// Test the cancelling of requests that are being transferred to a new renderer
+// due to a redirection.
+TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextTransferred) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ int render_view_id = 0;
+ int request_id = 1;
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Content-Type: text/html; charset=utf-8\n\n");
+ std::string response_data("<html>foobar</html>");
+
+ SetResponse(raw_headers, response_data);
+ SetResourceType(ResourceType::MAIN_FRAME);
+ HandleScheme("http");
+
+ MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah"));
+
+ GlobalRequestID global_request_id(filter_->child_id(), request_id);
+ host_.MarkAsTransferredNavigation(global_request_id,
+ GURL("http://example.com/blah"));
+
+ // And now simulate a cancellation coming from the renderer.
+ ResourceHostMsg_CancelRequest msg(filter_->child_id(), request_id);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+
+ // Since the request is marked as being transferred,
+ // the cancellation above should have been ignored and the request
+ // should still be alive.
+ EXPECT_EQ(1, host_.pending_requests());
+
+ // Cancelling by other methods shouldn't work either.
+ host_.CancelRequestsForProcess(render_view_id);
+ EXPECT_EQ(1, host_.pending_requests());
+
+ // Cancelling by context should work.
+ host_.CancelRequestsForContext(filter_->resource_context());
+ EXPECT_EQ(0, host_.pending_requests());
+}
+
+TEST_F(ResourceDispatcherHostTest, TransferNavigation) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ int render_view_id = 0;
+ int request_id = 1;
+
+ // Configure initial request.
+ SetResponse("HTTP/1.1 302 Found\n"
+ "Location: http://other.com/blech\n\n");
+
+ SetResourceType(ResourceType::MAIN_FRAME);
+ HandleScheme("http");
+
+ // Temporarily replace ContentBrowserClient with one that will trigger the
+ // transfer navigation code paths.
+ TransfersAllNavigationsContentBrowserClient new_client;
+ ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client);
+
+ MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah"));
+
+ // Restore.
+ SetBrowserClientForTesting(old_client);
+
+ // This second filter is used to emulate a second process.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ int new_render_view_id = 1;
+ int new_request_id = 2;
+
+ const std::string kResponseBody = "hello world";
+ SetResponse("HTTP/1.1 200 OK\n"
+ "Content-Type: text/plain\n\n",
+ kResponseBody);
+
+ ResourceHostMsg_Request request =
+ CreateResourceRequest("GET", ResourceType::MAIN_FRAME,
+ GURL("http://other.com/blech"));
+ request.transferred_request_child_id = filter_->child_id();
+ request.transferred_request_request_id = request_id;
+
+ // For cleanup.
+ child_ids_.insert(second_filter->child_id());
+ ResourceHostMsg_RequestResource transfer_request_msg(
+ new_render_view_id, new_request_id, request);
+ bool msg_was_ok;
+ host_.OnMessageReceived(
+ transfer_request_msg, second_filter.get(), &msg_was_ok);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Check generated messages.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ ASSERT_EQ(1U, msgs.size());
+ CheckSuccessfulRequest(msgs[0], kResponseBody);
+}
+
+TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ int render_view_id = 0;
+ int request_id = 1;
+
+ // Configure initial request.
+ SetResponse("HTTP/1.1 302 Found\n"
+ "Location: http://other.com/blech\n\n");
+
+ SetResourceType(ResourceType::MAIN_FRAME);
+ HandleScheme("http");
+
+ // Temporarily replace ContentBrowserClient with one that will trigger the
+ // transfer navigation code paths.
+ TransfersAllNavigationsContentBrowserClient new_client;
+ ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client);
+
+ MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah"));
+
+ // Restore.
+ SetBrowserClientForTesting(old_client);
+
+ // This second filter is used to emulate a second process.
+ scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(
+ this, browser_context_->GetResourceContext());
+
+ int new_render_view_id = 1;
+ int new_request_id = 2;
+
+ // Delay the start of the next request so that we can setup the response for
+ // the next URL.
+ SetDelayedStartJobGeneration(true);
+
+ SetResponse("HTTP/1.1 302 Found\n"
+ "Location: http://other.com/blerg\n\n");
+
+ ResourceHostMsg_Request request =
+ CreateResourceRequest("GET", ResourceType::MAIN_FRAME,
+ GURL("http://other.com/blech"));
+ request.transferred_request_child_id = filter_->child_id();
+ request.transferred_request_request_id = request_id;
+
+ // For cleanup.
+ child_ids_.insert(second_filter->child_id());
+ ResourceHostMsg_RequestResource transfer_request_msg(
+ new_render_view_id, new_request_id, request);
+ bool msg_was_ok;
+ host_.OnMessageReceived(
+ transfer_request_msg, second_filter.get(), &msg_was_ok);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Response data for "http://other.com/blerg":
+ const std::string kResponseBody = "hello world";
+ SetResponse("HTTP/1.1 200 OK\n"
+ "Content-Type: text/plain\n\n",
+ kResponseBody);
+
+ // OK, let the redirect happen.
+ SetDelayedStartJobGeneration(false);
+ CompleteStartRequest(second_filter.get(), new_request_id);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Now, simulate the renderer choosing to follow the redirect.
+ ResourceHostMsg_FollowRedirect redirect_msg(
+ new_render_view_id, new_request_id, false, GURL());
+ host_.OnMessageReceived(redirect_msg, second_filter.get(), &msg_was_ok);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Flush all the pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Check generated messages.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ ASSERT_EQ(1U, msgs.size());
+
+ // We should have received a redirect followed by a "normal" payload.
+ EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type());
+ msgs[0].erase(msgs[0].begin());
+ CheckSuccessfulRequest(msgs[0], kResponseBody);
+}
+
+TEST_F(ResourceDispatcherHostTest, UnknownURLScheme) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ SetResourceType(ResourceType::MAIN_FRAME);
+ HandleScheme("http");
+
+ MakeTestRequest(0, 1, GURL("foo://bar"));
+
+ // Flush all pending requests.
+ while (net::URLRequestTestJob::ProcessOnePendingMessage()) {}
+
+ // Sort all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // We should have gotten one RequestComplete message.
+ ASSERT_EQ(1U, msgs[0].size());
+ EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][0].type());
+
+ // The RequestComplete message should have the error code of
+ // ERR_UNKNOWN_URL_SCHEME.
+ int request_id;
+ int error_code;
+
+ PickleIterator iter(msgs[0][0]);
+ EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id));
+ EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code));
+
+ EXPECT_EQ(1, request_id);
+ EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, error_code);
+}
+
+TEST_F(ResourceDispatcherHostTest, DataReceivedACKs) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ SendDataReceivedACKs(true);
+
+ HandleScheme("big-job");
+ MakeTestRequest(0, 1, GURL("big-job:0123456789,1000000"));
+
+ // Sort all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ size_t size = msgs[0].size();
+
+ EXPECT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[0][0].type());
+ EXPECT_EQ(ResourceMsg_SetDataBuffer::ID, msgs[0][1].type());
+ for (size_t i = 2; i < size - 1; ++i)
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msgs[0][i].type());
+ EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][size - 1].type());
+}
+
+TEST_F(ResourceDispatcherHostTest, DelayedDataReceivedACKs) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ HandleScheme("big-job");
+ MakeTestRequest(0, 1, GURL("big-job:0123456789,1000000"));
+
+ // Sort all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // We expect 1x ReceivedResponse, 1x SetDataBuffer, Nx ReceivedData messages.
+ EXPECT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[0][0].type());
+ EXPECT_EQ(ResourceMsg_SetDataBuffer::ID, msgs[0][1].type());
+ for (size_t i = 2; i < msgs[0].size(); ++i)
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msgs[0][i].type());
+
+ // NOTE: If we fail the above checks then it means that we probably didn't
+ // load a big enough response to trigger the delay mechanism we are trying to
+ // test!
+
+ msgs[0].erase(msgs[0].begin());
+ msgs[0].erase(msgs[0].begin());
+
+ // ACK all DataReceived messages until we find a RequestComplete message.
+ bool complete = false;
+ while (!complete) {
+ for (size_t i = 0; i < msgs[0].size(); ++i) {
+ if (msgs[0][i].type() == ResourceMsg_RequestComplete::ID) {
+ complete = true;
+ break;
+ }
+
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msgs[0][i].type());
+
+ ResourceHostMsg_DataReceived_ACK msg(0, 1);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ }
+}
+
+// Flakyness of this test might indicate memory corruption issues with
+// for example the ResourceBuffer of AsyncResourceHandler.
+TEST_F(ResourceDispatcherHostTest, DataReceivedUnexpectedACKs) {
+ EXPECT_EQ(0, host_.pending_requests());
+
+ HandleScheme("big-job");
+ MakeTestRequest(0, 1, GURL("big-job:0123456789,1000000"));
+
+ // Sort all the messages we saw by request.
+ ResourceIPCAccumulator::ClassifiedMessages msgs;
+ accum_.GetClassifiedMessages(&msgs);
+
+ // We expect 1x ReceivedResponse, 1x SetDataBuffer, Nx ReceivedData messages.
+ EXPECT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[0][0].type());
+ EXPECT_EQ(ResourceMsg_SetDataBuffer::ID, msgs[0][1].type());
+ for (size_t i = 2; i < msgs[0].size(); ++i)
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msgs[0][i].type());
+
+ // NOTE: If we fail the above checks then it means that we probably didn't
+ // load a big enough response to trigger the delay mechanism.
+
+ // Send some unexpected ACKs.
+ for (size_t i = 0; i < 128; ++i) {
+ ResourceHostMsg_DataReceived_ACK msg(0, 1);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+ }
+
+ msgs[0].erase(msgs[0].begin());
+ msgs[0].erase(msgs[0].begin());
+
+ // ACK all DataReceived messages until we find a RequestComplete message.
+ bool complete = false;
+ while (!complete) {
+ for (size_t i = 0; i < msgs[0].size(); ++i) {
+ if (msgs[0][i].type() == ResourceMsg_RequestComplete::ID) {
+ complete = true;
+ break;
+ }
+
+ EXPECT_EQ(ResourceMsg_DataReceived::ID, msgs[0][i].type());
+
+ ResourceHostMsg_DataReceived_ACK msg(0, 1);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok);
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ msgs.clear();
+ accum_.GetClassifiedMessages(&msgs);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_handler.cc b/chromium/content/browser/loader/resource_handler.cc
new file mode 100644
index 00000000000..2a5d85ee253
--- /dev/null
+++ b/chromium/content/browser/loader/resource_handler.cc
@@ -0,0 +1,13 @@
+// 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 "content/browser/loader/resource_handler.h"
+
+namespace content {
+
+void ResourceHandler::SetController(ResourceController* controller) {
+ controller_ = controller;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_handler.h b/chromium/content/browser/loader/resource_handler.h
new file mode 100644
index 00000000000..de8bd9dc1cf
--- /dev/null
+++ b/chromium/content/browser/loader/resource_handler.h
@@ -0,0 +1,114 @@
+// 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 is the browser side of the resource dispatcher, it receives requests
+// from the RenderProcessHosts, and dispatches them to URLRequests. It then
+// fowards the messages from the URLRequests back to the correct process for
+// handling.
+//
+// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading
+
+#ifndef CONTENT_BROWSER_LOADER_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_HANDLER_H_
+
+#include <string>
+
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace net {
+class IOBuffer;
+class URLRequestStatus;
+} // namespace net
+
+namespace content {
+class ResourceController;
+struct ResourceResponse;
+
+// The resource dispatcher host uses this interface to process network events
+// for an URLRequest instance. A ResourceHandler's lifetime is bound to its
+// associated URLRequest.
+class CONTENT_EXPORT ResourceHandler
+ : public NON_EXPORTED_BASE(base::NonThreadSafe) {
+ public:
+ virtual ~ResourceHandler() {}
+
+ // Sets the controller for this handler.
+ virtual void SetController(ResourceController* controller);
+
+ // Called as upload progress is made. The return value is ignored.
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) = 0;
+
+ // The request was redirected to a new URL. |*defer| has an initial value of
+ // false. Set |*defer| to true to defer the redirect. The redirect may be
+ // followed later on via ResourceDispatcherHost::FollowDeferredRedirect. If
+ // the handler returns false, then the request is cancelled.
+ virtual bool OnRequestRedirected(int request_id, const GURL& url,
+ ResourceResponse* response,
+ bool* defer) = 0;
+
+ // Response headers and meta data are available. If the handler returns
+ // false, then the request is cancelled. Set |*defer| to true to defer
+ // processing of the response. Call ResourceDispatcherHostImpl::
+ // ResumeDeferredRequest to continue processing the response.
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) = 0;
+
+ // Called before the net::URLRequest for |request_id| (whose url is |url|) is
+ // to be started. If the handler returns false, then the request is
+ // cancelled. Otherwise if the return value is true, the ResourceHandler can
+ // delay the request from starting by setting |*defer = true|. A deferred
+ // request will not have called net::URLRequest::Start(), and will not resume
+ // until someone calls ResourceDispatcherHost::StartDeferredRequest().
+ virtual bool OnWillStart(int request_id, const GURL& url, bool* defer) = 0;
+
+ // Data will be read for the response. Upon success, this method places the
+ // size and address of the buffer where the data is to be written in its
+ // out-params. This call will be followed by either OnReadCompleted or
+ // OnResponseCompleted, at which point the buffer may be recycled.
+ //
+ // If the handler returns false, then the request is cancelled. Otherwise,
+ // once data is available, OnReadCompleted will be called.
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) = 0;
+
+ // Data (*bytes_read bytes) was written into the buffer provided by
+ // OnWillRead. A return value of false cancels the request, true continues
+ // reading data. Set |*defer| to true to defer reading more response data.
+ // Call controller()->Resume() to continue reading response data.
+ virtual bool OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) = 0;
+
+ // The response is complete. The final response status is given. Returns
+ // false if the handler is deferring the call to a later time. Otherwise,
+ // the request will be destroyed upon return.
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) = 0;
+
+ // This notification is synthesized by the RedirectToFileResourceHandler
+ // to indicate progress of 'download_to_file' requests. OnReadCompleted
+ // calls are consumed by the RedirectToFileResourceHandler and replaced
+ // with OnDataDownloaded calls.
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) = 0;
+
+ protected:
+ ResourceHandler() : controller_(NULL) {}
+ ResourceController* controller() { return controller_; }
+
+ private:
+ ResourceController* controller_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/resource_loader.cc b/chromium/content/browser/loader/resource_loader.cc
new file mode 100644
index 00000000000..5b0a9f4a284
--- /dev/null
+++ b/chromium/content/browser/loader/resource_loader.cc
@@ -0,0 +1,675 @@
+// 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 "content/browser/loader/resource_loader.h"
+
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/loader/doomed_resource_handler.h"
+#include "content/browser/loader/resource_loader_delegate.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/ssl/ssl_client_auth_handler.h"
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/common/ssl_status_serialization.h"
+#include "content/public/browser/cert_store.h"
+#include "content/public/browser/resource_dispatcher_host_login_delegate.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/resource_response.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/ssl/client_cert_store.h"
+#include "net/ssl/client_cert_store_impl.h"
+#include "webkit/browser/appcache/appcache_interceptor.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace content {
+namespace {
+
+void PopulateResourceResponse(net::URLRequest* request,
+ ResourceResponse* response) {
+ response->head.error_code = request->status().error();
+ response->head.request_time = request->request_time();
+ response->head.response_time = request->response_time();
+ response->head.headers = request->response_headers();
+ request->GetCharset(&response->head.charset);
+ response->head.content_length = request->GetExpectedContentSize();
+ request->GetMimeType(&response->head.mime_type);
+ net::HttpResponseInfo response_info = request->response_info();
+ response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy;
+ response->head.was_npn_negotiated = response_info.was_npn_negotiated;
+ response->head.npn_negotiated_protocol =
+ response_info.npn_negotiated_protocol;
+ response->head.connection_info = response_info.connection_info;
+ response->head.was_fetched_via_proxy = request->was_fetched_via_proxy();
+ response->head.socket_address = request->GetSocketAddress();
+ appcache::AppCacheInterceptor::GetExtraResponseInfo(
+ request,
+ &response->head.appcache_id,
+ &response->head.appcache_manifest_url);
+ // TODO(mmenke): Figure out if LOAD_ENABLE_LOAD_TIMING is safe to remove.
+ if (request->load_flags() & net::LOAD_ENABLE_LOAD_TIMING)
+ request->GetLoadTimingInfo(&response->head.load_timing);
+}
+
+} // namespace
+
+ResourceLoader::ResourceLoader(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate)
+ : weak_ptr_factory_(this) {
+ scoped_ptr<net::ClientCertStore> client_cert_store;
+#if !defined(USE_OPENSSL)
+ client_cert_store.reset(new net::ClientCertStoreImpl());
+#endif
+ Init(request.Pass(), handler.Pass(), delegate, client_cert_store.Pass());
+}
+
+ResourceLoader::~ResourceLoader() {
+ if (login_delegate_.get())
+ login_delegate_->OnRequestCancelled();
+ if (ssl_client_auth_handler_.get())
+ ssl_client_auth_handler_->OnRequestCancelled();
+
+ // Run ResourceHandler destructor before we tear-down the rest of our state
+ // as the ResourceHandler may want to inspect the URLRequest and other state.
+ handler_.reset();
+}
+
+void ResourceLoader::StartRequest() {
+ if (delegate_->HandleExternalProtocol(this, request_->url())) {
+ CancelAndIgnore();
+ return;
+ }
+
+ // Give the handler a chance to delay the URLRequest from being started.
+ bool defer_start = false;
+ if (!handler_->OnWillStart(GetRequestInfo()->GetRequestID(), request_->url(),
+ &defer_start)) {
+ Cancel();
+ return;
+ }
+
+ if (defer_start) {
+ deferred_stage_ = DEFERRED_START;
+ } else {
+ StartRequestInternal();
+ }
+}
+
+void ResourceLoader::CancelRequest(bool from_renderer) {
+ CancelRequestInternal(net::ERR_ABORTED, from_renderer);
+}
+
+void ResourceLoader::CancelAndIgnore() {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+ info->set_was_ignored_by_handler(true);
+ CancelRequest(false);
+}
+
+void ResourceLoader::CancelWithError(int error_code) {
+ CancelRequestInternal(error_code, false);
+}
+
+void ResourceLoader::ReportUploadProgress() {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ if (waiting_for_upload_progress_ack_)
+ return; // Send one progress event at a time.
+
+ net::UploadProgress progress = request_->GetUploadProgress();
+ if (!progress.size())
+ return; // Nothing to upload.
+
+ if (progress.position() == last_upload_position_)
+ return; // No progress made since last time.
+
+ const uint64 kHalfPercentIncrements = 200;
+ const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000);
+
+ uint64 amt_since_last = progress.position() - last_upload_position_;
+ TimeDelta time_since_last = TimeTicks::Now() - last_upload_ticks_;
+
+ bool is_finished = (progress.size() == progress.position());
+ bool enough_new_progress =
+ (amt_since_last > (progress.size() / kHalfPercentIncrements));
+ bool too_much_time_passed = time_since_last > kOneSecond;
+
+ if (is_finished || enough_new_progress || too_much_time_passed) {
+ if (request_->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS) {
+ handler_->OnUploadProgress(
+ info->GetRequestID(), progress.position(), progress.size());
+ waiting_for_upload_progress_ack_ = true;
+ }
+ last_upload_ticks_ = TimeTicks::Now();
+ last_upload_position_ = progress.position();
+ }
+}
+
+void ResourceLoader::MarkAsTransferring(const GURL& target_url) {
+ CHECK_EQ(GetRequestInfo()->GetResourceType(), ResourceType::MAIN_FRAME)
+ << "Cannot transfer non-main frame navigations";
+ is_transferring_ = true;
+
+ // When transferring a request to another process, the renderer doesn't get
+ // a chance to update the cookie policy URL. Do it here instead.
+ request()->set_first_party_for_cookies(target_url);
+
+ // When an URLRequest is transferred to a new RenderViewHost, its
+ // ResourceHandler should not receive any notifications because it may depend
+ // on the state of the old RVH. We set a ResourceHandler that only allows
+ // canceling requests, because on shutdown of the RDH all pending requests
+ // are canceled. The RVH of requests that are being transferred may be gone
+ // by that time. In CompleteTransfer, the ResoureHandlers are substituted
+ // again.
+ handler_.reset(new DoomedResourceHandler(handler_.Pass()));
+}
+
+void ResourceLoader::WillCompleteTransfer() {
+ handler_.reset();
+}
+
+void ResourceLoader::CompleteTransfer(scoped_ptr<ResourceHandler> new_handler) {
+ DCHECK_EQ(DEFERRED_REDIRECT, deferred_stage_);
+ DCHECK(!handler_.get());
+
+ handler_ = new_handler.Pass();
+ handler_->SetController(this);
+ is_transferring_ = false;
+
+ Resume();
+}
+
+ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() {
+ return ResourceRequestInfoImpl::ForRequest(request_.get());
+}
+
+void ResourceLoader::ClearLoginDelegate() {
+ login_delegate_ = NULL;
+}
+
+void ResourceLoader::ClearSSLClientAuthHandler() {
+ ssl_client_auth_handler_ = NULL;
+}
+
+void ResourceLoader::OnUploadProgressACK() {
+ waiting_for_upload_progress_ack_ = false;
+}
+
+ResourceLoader::ResourceLoader(
+ scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate,
+ scoped_ptr<net::ClientCertStore> client_cert_store)
+ : weak_ptr_factory_(this) {
+ Init(request.Pass(), handler.Pass(), delegate, client_cert_store.Pass());
+}
+
+void ResourceLoader::Init(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate,
+ scoped_ptr<net::ClientCertStore> client_cert_store) {
+ deferred_stage_ = DEFERRED_NONE;
+ request_ = request.Pass();
+ handler_ = handler.Pass();
+ delegate_ = delegate;
+ last_upload_position_ = 0;
+ waiting_for_upload_progress_ack_ = false;
+ is_transferring_ = false;
+ client_cert_store_ = client_cert_store.Pass();
+
+ request_->set_delegate(this);
+ handler_->SetController(this);
+}
+
+void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused,
+ const GURL& new_url,
+ bool* defer) {
+ DCHECK_EQ(request_.get(), unused);
+
+ VLOG(1) << "OnReceivedRedirect: " << request_->url().spec();
+ DCHECK(request_->status().is_success());
+
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ if (info->process_type() != PROCESS_TYPE_PLUGIN &&
+ !ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanRequestURL(info->GetChildID(), new_url)) {
+ VLOG(1) << "Denied unauthorized request for "
+ << new_url.possibly_invalid_spec();
+
+ // Tell the renderer that this request was disallowed.
+ Cancel();
+ return;
+ }
+
+ delegate_->DidReceiveRedirect(this, new_url);
+
+ if (delegate_->HandleExternalProtocol(this, new_url)) {
+ // The request is complete so we can remove it.
+ CancelAndIgnore();
+ return;
+ }
+
+ scoped_refptr<ResourceResponse> response(new ResourceResponse());
+ PopulateResourceResponse(request_.get(), response.get());
+
+ if (!handler_->OnRequestRedirected(
+ info->GetRequestID(), new_url, response.get(), defer)) {
+ Cancel();
+ } else if (*defer) {
+ deferred_stage_ = DEFERRED_REDIRECT; // Follow redirect when resumed.
+ }
+}
+
+void ResourceLoader::OnAuthRequired(net::URLRequest* unused,
+ net::AuthChallengeInfo* auth_info) {
+ DCHECK_EQ(request_.get(), unused);
+
+ if (request_->load_flags() & net::LOAD_DO_NOT_PROMPT_FOR_LOGIN) {
+ request_->CancelAuth();
+ return;
+ }
+
+ if (!delegate_->AcceptAuthRequest(this, auth_info)) {
+ request_->CancelAuth();
+ return;
+ }
+
+ // Create a login dialog on the UI thread to get authentication data, or pull
+ // from cache and continue on the IO thread.
+
+ DCHECK(!login_delegate_.get())
+ << "OnAuthRequired called with login_delegate pending";
+ login_delegate_ = delegate_->CreateLoginDelegate(this, auth_info);
+ if (!login_delegate_.get())
+ request_->CancelAuth();
+}
+
+void ResourceLoader::OnCertificateRequested(
+ net::URLRequest* unused,
+ net::SSLCertRequestInfo* cert_info) {
+ DCHECK_EQ(request_.get(), unused);
+
+ if (!delegate_->AcceptSSLClientCertificateRequest(this, cert_info)) {
+ request_->Cancel();
+ return;
+ }
+
+#if !defined(USE_OPENSSL)
+ client_cert_store_->GetClientCerts(*cert_info, &cert_info->client_certs);
+ if (cert_info->client_certs.empty()) {
+ // No need to query the user if there are no certs to choose from.
+ request_->ContinueWithCertificate(NULL);
+ return;
+ }
+#endif
+
+ DCHECK(!ssl_client_auth_handler_.get())
+ << "OnCertificateRequested called with ssl_client_auth_handler pending";
+ ssl_client_auth_handler_ = new SSLClientAuthHandler(request_.get(),
+ cert_info);
+ ssl_client_auth_handler_->SelectCertificate();
+}
+
+void ResourceLoader::OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ int render_process_id;
+ int render_view_id;
+ if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id))
+ NOTREACHED();
+
+ SSLManager::OnSSLCertificateError(
+ weak_ptr_factory_.GetWeakPtr(),
+ info->GetGlobalRequestID(),
+ info->GetResourceType(),
+ request_->url(),
+ render_process_id,
+ render_view_id,
+ ssl_info,
+ fatal);
+}
+
+void ResourceLoader::OnResponseStarted(net::URLRequest* unused) {
+ DCHECK_EQ(request_.get(), unused);
+
+ VLOG(1) << "OnResponseStarted: " << request_->url().spec();
+
+ // The CanLoadPage check should take place after any server redirects have
+ // finished, at the point in time that we know a page will commit in the
+ // renderer process.
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanLoadPage(info->GetChildID(),
+ request_->url(),
+ info->GetResourceType())) {
+ Cancel();
+ return;
+ }
+
+ if (!request_->status().is_success()) {
+ ResponseCompleted();
+ return;
+ }
+
+ // We want to send a final upload progress message prior to sending the
+ // response complete message even if we're waiting for an ack to to a
+ // previous upload progress message.
+ waiting_for_upload_progress_ack_ = false;
+ ReportUploadProgress();
+
+ CompleteResponseStarted();
+
+ if (is_deferred())
+ return;
+
+ if (request_->status().is_success()) {
+ StartReading(false); // Read the first chunk.
+ } else {
+ ResponseCompleted();
+ }
+}
+
+void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {
+ DCHECK_EQ(request_.get(), unused);
+ VLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\""
+ << " bytes_read = " << bytes_read;
+
+ // bytes_read == -1 always implies an error.
+ if (bytes_read == -1 || !request_->status().is_success()) {
+ ResponseCompleted();
+ return;
+ }
+
+ CompleteRead(bytes_read);
+
+ if (is_deferred())
+ return;
+
+ if (request_->status().is_success() && bytes_read > 0) {
+ StartReading(true); // Read the next chunk.
+ } else {
+ ResponseCompleted();
+ }
+}
+
+void ResourceLoader::CancelSSLRequest(const GlobalRequestID& id,
+ int error,
+ const net::SSLInfo* ssl_info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // The request can be NULL if it was cancelled by the renderer (as the
+ // request of the user navigating to a new page from the location bar).
+ if (!request_->is_pending())
+ return;
+ DVLOG(1) << "CancelSSLRequest() url: " << request_->url().spec();
+
+ if (ssl_info) {
+ request_->CancelWithSSLError(error, *ssl_info);
+ } else {
+ request_->CancelWithError(error);
+ }
+}
+
+void ResourceLoader::ContinueSSLRequest(const GlobalRequestID& id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec();
+
+ request_->ContinueDespiteLastError();
+}
+
+void ResourceLoader::Resume() {
+ DCHECK(!is_transferring_);
+
+ DeferredStage stage = deferred_stage_;
+ deferred_stage_ = DEFERRED_NONE;
+ switch (stage) {
+ case DEFERRED_NONE:
+ NOTREACHED();
+ break;
+ case DEFERRED_START:
+ StartRequestInternal();
+ break;
+ case DEFERRED_REDIRECT:
+ request_->FollowDeferredRedirect();
+ break;
+ case DEFERRED_READ:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ResourceLoader::ResumeReading,
+ weak_ptr_factory_.GetWeakPtr()));
+ break;
+ case DEFERRED_FINISH:
+ // Delay self-destruction since we don't know how we were reached.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ResourceLoader::CallDidFinishLoading,
+ weak_ptr_factory_.GetWeakPtr()));
+ break;
+ }
+}
+
+void ResourceLoader::Cancel() {
+ CancelRequest(false);
+}
+
+void ResourceLoader::StartRequestInternal() {
+ DCHECK(!request_->is_pending());
+ request_->Start();
+
+ delegate_->DidStartRequest(this);
+}
+
+void ResourceLoader::CancelRequestInternal(int error, bool from_renderer) {
+ VLOG(1) << "CancelRequestInternal: " << request_->url().spec();
+
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ // WebKit will send us a cancel for downloads since it no longer handles
+ // them. In this case, ignore the cancel since we handle downloads in the
+ // browser.
+ if (from_renderer && (info->is_download() || info->is_stream()))
+ return;
+
+ // TODO(darin): Perhaps we should really be looking to see if the status is
+ // IO_PENDING?
+ bool was_pending = request_->is_pending();
+
+ if (login_delegate_.get()) {
+ login_delegate_->OnRequestCancelled();
+ login_delegate_ = NULL;
+ }
+ if (ssl_client_auth_handler_.get()) {
+ ssl_client_auth_handler_->OnRequestCancelled();
+ ssl_client_auth_handler_ = NULL;
+ }
+
+ request_->CancelWithError(error);
+
+ if (!was_pending) {
+ // If the request isn't in flight, then we won't get an asynchronous
+ // notification from the request, so we have to signal ourselves to finish
+ // this request.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ResourceLoader::ResponseCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+void ResourceLoader::CompleteResponseStarted() {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ scoped_refptr<ResourceResponse> response(new ResourceResponse());
+ PopulateResourceResponse(request_.get(), response.get());
+
+ // The --site-per-process flag enables an out-of-process iframes
+ // prototype. It works by changing the MIME type of cross-site subframe
+ // responses to a Chrome specific one. This new type causes the subframe
+ // to be replaced by a <webview> tag with the same URL, which results in
+ // using a renderer in a different process.
+ //
+ // For prototyping purposes, we will use a small hack to ensure same site
+ // iframes are not changed. We can compare the URL for the subframe
+ // request with the referrer. If the two don't match, then it should be a
+ // cross-site iframe.
+ // Also, we don't do the MIME type change for chrome:// URLs, as those
+ // require different privileges and are not allowed in regular renderers.
+ //
+ // The usage of SiteInstance::IsSameWebSite is safe on the IO thread,
+ // if the browser_context parameter is NULL. This does not work for hosted
+ // apps, but should be fine for prototyping.
+ // TODO(nasko): Once the SiteInstance check is fixed, ensure we do the
+ // right thing here. http://crbug.com/160576
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kSitePerProcess) &&
+ GetRequestInfo()->GetResourceType() == ResourceType::SUB_FRAME &&
+ response->head.mime_type == "text/html" &&
+ !request_->url().SchemeIs(chrome::kChromeUIScheme) &&
+ !SiteInstance::IsSameWebSite(NULL, request_->url(),
+ GURL(request_->referrer()))) {
+ response->head.mime_type = "application/browser-plugin";
+ }
+
+ if (request_->ssl_info().cert.get()) {
+ int cert_id = CertStore::GetInstance()->StoreCert(
+ request_->ssl_info().cert.get(), info->GetChildID());
+ response->head.security_info = SerializeSecurityInfo(
+ cert_id,
+ request_->ssl_info().cert_status,
+ request_->ssl_info().security_bits,
+ request_->ssl_info().connection_status);
+ } else {
+ // We should not have any SSL state.
+ DCHECK(!request_->ssl_info().cert_status &&
+ request_->ssl_info().security_bits == -1 &&
+ !request_->ssl_info().connection_status);
+ }
+
+ delegate_->DidReceiveResponse(this);
+
+ bool defer = false;
+ if (!handler_->OnResponseStarted(
+ info->GetRequestID(), response.get(), &defer)) {
+ Cancel();
+ } else if (defer) {
+ read_deferral_start_time_ = base::TimeTicks::Now();
+ deferred_stage_ = DEFERRED_READ; // Read first chunk when resumed.
+ }
+}
+
+void ResourceLoader::StartReading(bool is_continuation) {
+ int bytes_read = 0;
+ ReadMore(&bytes_read);
+
+ // If IO is pending, wait for the URLRequest to call OnReadCompleted.
+ if (request_->status().is_io_pending())
+ return;
+
+ if (!is_continuation || bytes_read <= 0) {
+ OnReadCompleted(request_.get(), bytes_read);
+ } else {
+ // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
+ // thread in case the URLRequest can provide data synchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ResourceLoader::OnReadCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ request_.get(),
+ bytes_read));
+ }
+}
+
+void ResourceLoader::ResumeReading() {
+ DCHECK(!is_deferred());
+
+ if (!read_deferral_start_time_.is_null()) {
+ UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral",
+ base::TimeTicks::Now() - read_deferral_start_time_);
+ read_deferral_start_time_ = base::TimeTicks();
+ }
+ if (request_->status().is_success()) {
+ StartReading(false); // Read the next chunk (OK to complete synchronously).
+ } else {
+ ResponseCompleted();
+ }
+}
+
+void ResourceLoader::ReadMore(int* bytes_read) {
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+ DCHECK(!is_deferred());
+
+ net::IOBuffer* buf;
+ int buf_size;
+ if (!handler_->OnWillRead(info->GetRequestID(), &buf, &buf_size, -1)) {
+ Cancel();
+ return;
+ }
+
+ DCHECK(buf);
+ DCHECK(buf_size > 0);
+
+ request_->Read(buf, buf_size, bytes_read);
+
+ // No need to check the return value here as we'll detect errors by
+ // inspecting the URLRequest's status.
+}
+
+void ResourceLoader::CompleteRead(int bytes_read) {
+ DCHECK(bytes_read >= 0);
+ DCHECK(request_->status().is_success());
+
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ bool defer = false;
+ if (!handler_->OnReadCompleted(info->GetRequestID(), bytes_read, &defer)) {
+ Cancel();
+ } else if (defer) {
+ deferred_stage_ = DEFERRED_READ; // Read next chunk when resumed.
+ }
+}
+
+void ResourceLoader::ResponseCompleted() {
+ VLOG(1) << "ResponseCompleted: " << request_->url().spec();
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+
+ std::string security_info;
+ const net::SSLInfo& ssl_info = request_->ssl_info();
+ if (ssl_info.cert.get() != NULL) {
+ int cert_id = CertStore::GetInstance()->StoreCert(ssl_info.cert.get(),
+ info->GetChildID());
+ security_info = SerializeSecurityInfo(
+ cert_id, ssl_info.cert_status, ssl_info.security_bits,
+ ssl_info.connection_status);
+ }
+
+ if (handler_->OnResponseCompleted(info->GetRequestID(), request_->status(),
+ security_info)) {
+ // This will result in our destruction.
+ CallDidFinishLoading();
+ } else {
+ // The handler is not ready to die yet. We will call DidFinishLoading when
+ // we resume.
+ deferred_stage_ = DEFERRED_FINISH;
+ }
+}
+
+void ResourceLoader::CallDidFinishLoading() {
+ delegate_->DidFinishLoading(this);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_loader.h b/chromium/content/browser/loader/resource_loader.h
new file mode 100644
index 00000000000..76f72e2ec43
--- /dev/null
+++ b/chromium/content/browser/loader/resource_loader.h
@@ -0,0 +1,146 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_LOADER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_LOADER_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/loader/resource_handler.h"
+#include "content/browser/ssl/ssl_error_handler.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/resource_controller.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+class ClientCertStore;
+}
+
+namespace content {
+class ResourceDispatcherHostLoginDelegate;
+class ResourceLoaderDelegate;
+class ResourceRequestInfoImpl;
+class SSLClientAuthHandler;
+
+// This class is responsible for driving the URLRequest (i.e., calling Start,
+// Read, and servicing events). It has a ResourceHandler, which is typically a
+// chain of ResourceHandlers, and is the ResourceController for its handler.
+class CONTENT_EXPORT ResourceLoader : public net::URLRequest::Delegate,
+ public SSLErrorHandler::Delegate,
+ public ResourceController {
+ public:
+ ResourceLoader(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate);
+ virtual ~ResourceLoader();
+
+ void StartRequest();
+ void CancelRequest(bool from_renderer);
+
+ void ReportUploadProgress();
+
+ bool is_transferring() const { return is_transferring_; }
+ void MarkAsTransferring(const GURL& target_url);
+ void WillCompleteTransfer();
+ void CompleteTransfer(scoped_ptr<ResourceHandler> new_handler);
+
+ net::URLRequest* request() { return request_.get(); }
+ ResourceRequestInfoImpl* GetRequestInfo();
+
+ void ClearLoginDelegate();
+ void ClearSSLClientAuthHandler();
+
+ // IPC message handlers:
+ void OnUploadProgressACK();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ResourceLoaderTest, ClientCertStoreLookup);
+
+ ResourceLoader(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate,
+ scoped_ptr<net::ClientCertStore> client_cert_store);
+
+ // Initialization logic shared between the public and private constructor.
+ void Init(scoped_ptr<net::URLRequest> request,
+ scoped_ptr<ResourceHandler> handler,
+ ResourceLoaderDelegate* delegate,
+ scoped_ptr<net::ClientCertStore> client_cert_store);
+
+ // net::URLRequest::Delegate implementation:
+ virtual void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer) OVERRIDE;
+ virtual void OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* info) OVERRIDE;
+ virtual void OnCertificateRequested(net::URLRequest* request,
+ net::SSLCertRequestInfo* info) OVERRIDE;
+ virtual void OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& info,
+ bool fatal) OVERRIDE;
+ virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(net::URLRequest* request,
+ int bytes_read) OVERRIDE;
+
+ // SSLErrorHandler::Delegate implementation:
+ virtual void CancelSSLRequest(const GlobalRequestID& id,
+ int error,
+ const net::SSLInfo* ssl_info) OVERRIDE;
+ virtual void ContinueSSLRequest(const GlobalRequestID& id) OVERRIDE;
+
+ // ResourceController implementation:
+ virtual void Resume() OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual void CancelAndIgnore() OVERRIDE;
+ virtual void CancelWithError(int error_code) OVERRIDE;
+
+ void StartRequestInternal();
+ void CancelRequestInternal(int error, bool from_renderer);
+ void CompleteResponseStarted();
+ void StartReading(bool is_continuation);
+ void ResumeReading();
+ void ReadMore(int* bytes_read);
+ void CompleteRead(int bytes_read);
+ void ResponseCompleted();
+ void CallDidFinishLoading();
+
+ bool is_deferred() const { return deferred_stage_ != DEFERRED_NONE; }
+
+ enum DeferredStage {
+ DEFERRED_NONE,
+ DEFERRED_START,
+ DEFERRED_REDIRECT,
+ DEFERRED_READ,
+ DEFERRED_FINISH
+ };
+ DeferredStage deferred_stage_;
+
+ scoped_ptr<net::URLRequest> request_;
+ scoped_ptr<ResourceHandler> handler_;
+ ResourceLoaderDelegate* delegate_;
+
+ scoped_refptr<ResourceDispatcherHostLoginDelegate> login_delegate_;
+ scoped_refptr<SSLClientAuthHandler> ssl_client_auth_handler_;
+
+ uint64 last_upload_position_;
+ bool waiting_for_upload_progress_ack_;
+ base::TimeTicks last_upload_ticks_;
+ base::TimeTicks read_deferral_start_time_;
+
+ // Indicates that we are in a state of being transferred to a new downstream
+ // consumer. We are waiting for a notification to complete the transfer, at
+ // which point we'll receive a new ResourceHandler.
+ bool is_transferring_;
+
+ scoped_ptr<net::ClientCertStore> client_cert_store_;
+
+ base::WeakPtrFactory<ResourceLoader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceLoader);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_LOADER_H_
diff --git a/chromium/content/browser/loader/resource_loader_delegate.h b/chromium/content/browser/loader/resource_loader_delegate.h
new file mode 100644
index 00000000000..871debc3e45
--- /dev/null
+++ b/chromium/content/browser/loader/resource_loader_delegate.h
@@ -0,0 +1,49 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_LOADER_DELEGATE_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_LOADER_DELEGATE_H_
+
+#include "content/common/content_export.h"
+
+namespace net {
+class AuthChallengeInfo;
+class SSLCertRequestInfo;
+}
+
+namespace content {
+class ResourceDispatcherHostLoginDelegate;
+class ResourceLoader;
+
+class CONTENT_EXPORT ResourceLoaderDelegate {
+ public:
+ virtual ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) = 0;
+
+ virtual bool AcceptAuthRequest(ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) = 0;
+ virtual bool AcceptSSLClientCertificateRequest(
+ ResourceLoader* loader,
+ net::SSLCertRequestInfo* cert_info) = 0;
+
+ virtual bool HandleExternalProtocol(ResourceLoader* loader,
+ const GURL& url) = 0;
+
+ virtual void DidStartRequest(ResourceLoader* loader) = 0;
+ virtual void DidReceiveRedirect(ResourceLoader* loader,
+ const GURL& new_url) = 0;
+ virtual void DidReceiveResponse(ResourceLoader* loader) = 0;
+
+ // This method informs the delegate that the loader is done, and the loader
+ // expects to be destroyed as a side-effect of this call.
+ virtual void DidFinishLoading(ResourceLoader* loader) = 0;
+
+ protected:
+ virtual ~ResourceLoaderDelegate() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_LOADER_DELEGATE_H_
diff --git a/chromium/content/browser/loader/resource_loader_unittest.cc b/chromium/content/browser/loader/resource_loader_unittest.cc
new file mode 100644
index 00000000000..634a553dfaf
--- /dev/null
+++ b/chromium/content/browser/loader/resource_loader_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 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 "content/browser/loader/resource_loader.h"
+
+#include "base/run_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/loader/resource_loader_delegate.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/test_content_browser_client.h"
+#include "net/cert/x509_certificate.h"
+#include "net/ssl/client_cert_store.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+// Stub client certificate store that returns a preset list of certificates for
+// each request and records the arguments of the most recent request for later
+// inspection.
+class ClientCertStoreStub : public net::ClientCertStore {
+ public:
+ ClientCertStoreStub(const net::CertificateList& certs)
+ : response_(certs),
+ request_count_(0) {}
+
+ virtual ~ClientCertStoreStub() {}
+
+ // Returns |cert_authorities| field of the certificate request passed in the
+ // most recent call to GetClientCerts().
+ // TODO(ppi): Make the stub independent from the internal representation of
+ // SSLCertRequestInfo. For now it seems that we cannot neither save the
+ // scoped_refptr<> (since it is never passed to us) nor copy the entire
+ // CertificateRequestInfo (since there is no copy constructor).
+ std::vector<std::string> requested_authorities() {
+ return requested_authorities_;
+ }
+
+ // Returns the number of calls to GetClientCerts().
+ int request_count() {
+ return request_count_;
+ }
+
+ // net::ClientCertStore:
+ virtual bool GetClientCerts(const net::SSLCertRequestInfo& cert_request_info,
+ net::CertificateList* selected_certs) OVERRIDE {
+ ++request_count_;
+ requested_authorities_ = cert_request_info.cert_authorities;
+ *selected_certs = response_;
+ return true;
+ }
+
+ private:
+ const net::CertificateList response_;
+ int request_count_;
+ std::vector<std::string> requested_authorities_;
+};
+
+// Dummy implementation of ResourceHandler, instance of which is needed to
+// initialize ResourceLoader.
+class ResourceHandlerStub : public ResourceHandler {
+ public:
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE { return true; }
+
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE {
+ return true;
+ }
+
+ virtual void OnDataDownloaded(int request_id,
+ int bytes_downloaded) OVERRIDE {}
+};
+
+// Test browser client that captures calls to SelectClientCertificates and
+// records the arguments of the most recent call for later inspection.
+class SelectCertificateBrowserClient : public TestContentBrowserClient {
+ public:
+ SelectCertificateBrowserClient() : call_count_(0) {}
+
+ virtual void SelectClientCertificate(
+ int render_process_id,
+ int render_view_id,
+ const net::HttpNetworkSession* network_session,
+ net::SSLCertRequestInfo* cert_request_info,
+ const base::Callback<void(net::X509Certificate*)>& callback) OVERRIDE {
+ ++call_count_;
+ passed_certs_ = cert_request_info->client_certs;
+ }
+
+ int call_count() {
+ return call_count_;
+ }
+
+ net::CertificateList passed_certs() {
+ return passed_certs_;
+ }
+
+ private:
+ net::CertificateList passed_certs_;
+ int call_count_;
+};
+
+} // namespace
+
+class ResourceLoaderTest : public testing::Test,
+ public ResourceLoaderDelegate {
+ protected:
+ ResourceLoaderTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+ resource_context_(&test_url_request_context_) {
+ }
+
+ // ResourceLoaderDelegate:
+ virtual ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) OVERRIDE {
+ return NULL;
+ }
+ virtual bool AcceptAuthRequest(
+ ResourceLoader* loader,
+ net::AuthChallengeInfo* auth_info) OVERRIDE {
+ return false;
+ };
+ virtual bool AcceptSSLClientCertificateRequest(
+ ResourceLoader* loader,
+ net::SSLCertRequestInfo* cert_info) OVERRIDE {
+ return true;
+ }
+ virtual bool HandleExternalProtocol(ResourceLoader* loader,
+ const GURL& url) OVERRIDE {
+ return false;
+ }
+ virtual void DidStartRequest(ResourceLoader* loader) OVERRIDE {}
+ virtual void DidReceiveRedirect(ResourceLoader* loader,
+ const GURL& new_url) OVERRIDE {}
+ virtual void DidReceiveResponse(ResourceLoader* loader) OVERRIDE {}
+ virtual void DidFinishLoading(ResourceLoader* loader) OVERRIDE {}
+
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ net::TestURLRequestContext test_url_request_context_;
+ content::MockResourceContext resource_context_;
+};
+
+// When OpenSSL is used, client cert store is not being queried in
+// ResourceLoader.
+#if !defined(USE_OPENSSL)
+// Verifies if a call to net::UrlRequest::Delegate::OnCertificateRequested()
+// causes client cert store to be queried for certificates and if the returned
+// certificates are correctly passed to the content browser client for
+// selection.
+TEST_F(ResourceLoaderTest, ClientCertStoreLookup) {
+ const int kRenderProcessId = 1;
+ const int kRenderViewId = 2;
+
+ scoped_ptr<net::URLRequest> request(new net::URLRequest(
+ GURL("dummy"), NULL,
+ resource_context_.GetRequestContext()));
+ ResourceRequestInfo::AllocateForTesting(request.get(),
+ ResourceType::MAIN_FRAME,
+ &resource_context_,
+ kRenderProcessId,
+ kRenderViewId);
+
+ // Set up the test client cert store.
+ net::CertificateList dummy_certs(1, scoped_refptr<net::X509Certificate>(
+ new net::X509Certificate("test", "test", base::Time(), base::Time())));
+ scoped_ptr<ClientCertStoreStub> test_store(
+ new ClientCertStoreStub(dummy_certs));
+ EXPECT_EQ(0, test_store->request_count());
+
+ // Ownership of the |request| and |test_store| is about to be turned over to
+ // ResourceLoader. We need to keep raw pointer copies to access these objects
+ // later.
+ net::URLRequest* raw_ptr_to_request = request.get();
+ ClientCertStoreStub* raw_ptr_to_store = test_store.get();
+
+ scoped_ptr<ResourceHandler> resource_handler(new ResourceHandlerStub());
+ ResourceLoader loader(request.Pass(), resource_handler.Pass(), this,
+ test_store.PassAs<net::ClientCertStore>());
+
+ // Prepare a dummy certificate request.
+ scoped_refptr<net::SSLCertRequestInfo> cert_request_info(
+ new net::SSLCertRequestInfo());
+ std::vector<std::string> dummy_authority(1, "dummy");
+ cert_request_info->cert_authorities = dummy_authority;
+
+ // Plug in test content browser client.
+ SelectCertificateBrowserClient test_client;
+ ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);
+
+ // Everything is set up. Trigger the resource loader certificate request event
+ // and run the message loop.
+ loader.OnCertificateRequested(raw_ptr_to_request, cert_request_info.get());
+ base::RunLoop().RunUntilIdle();
+
+ // Restore the original content browser client.
+ SetBrowserClientForTesting(old_client);
+
+ // Check if the test store was queried against correct |cert_authorities|.
+ EXPECT_EQ(1, raw_ptr_to_store->request_count());
+ EXPECT_EQ(dummy_authority, raw_ptr_to_store->requested_authorities());
+
+ // Check if the retrieved certificates were passed to the content browser
+ // client.
+ EXPECT_EQ(1, test_client.call_count());
+ EXPECT_EQ(dummy_certs, test_client.passed_certs());
+}
+#endif // !defined(OPENSSL)
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_message_delegate.cc b/chromium/content/browser/loader/resource_message_delegate.cc
new file mode 100644
index 00000000000..01d3595e904
--- /dev/null
+++ b/chromium/content/browser/loader/resource_message_delegate.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 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 "content/browser/loader/resource_message_delegate.h"
+
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+ResourceMessageDelegate::ResourceMessageDelegate(const net::URLRequest* request)
+ : id_(ResourceRequestInfoImpl::ForRequest(request)->GetGlobalRequestID()) {
+ ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
+ rdh->RegisterResourceMessageDelegate(id_, this);
+}
+
+ResourceMessageDelegate::~ResourceMessageDelegate() {
+ ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
+ rdh->UnregisterResourceMessageDelegate(id_, this);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_message_delegate.h b/chromium/content/browser/loader/resource_message_delegate.h
new file mode 100644
index 00000000000..d5716e3ac25
--- /dev/null
+++ b/chromium/content/browser/loader/resource_message_delegate.h
@@ -0,0 +1,42 @@
+// Copyright (c) 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 CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_DELEGATE_
+#define CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_DELEGATE_
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/global_request_id.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+
+// A ResourceMessageDelegate receives IPC ResourceMsg_* messages for a specified
+// URLRequest. The delegate should implement its own IPC handler. It will
+// receive the message _after_ the ResourceDispatcherHost has handled it.
+class CONTENT_EXPORT ResourceMessageDelegate {
+ public:
+ ResourceMessageDelegate(const net::URLRequest* request);
+ virtual ~ResourceMessageDelegate();
+
+ // Called when the ResourceDispatcherHostImpl receives a message specifically
+ // for this delegate.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) = 0;
+
+ private:
+ GlobalRequestID id_;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceMessageDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_DELEGATE_
diff --git a/chromium/content/browser/loader/resource_message_filter.cc b/chromium/content/browser/loader/resource_message_filter.cc
new file mode 100644
index 00000000000..7c523975fba
--- /dev/null
+++ b/chromium/content/browser/loader/resource_message_filter.cc
@@ -0,0 +1,57 @@
+// 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 "content/browser/loader/resource_message_filter.h"
+
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/public/browser/resource_context.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+
+namespace content {
+
+ResourceMessageFilter::ResourceMessageFilter(
+ int child_id,
+ int process_type,
+ ResourceContext* resource_context,
+ ChromeAppCacheService* appcache_service,
+ ChromeBlobStorageContext* blob_storage_context,
+ fileapi::FileSystemContext* file_system_context,
+ URLRequestContextSelector* url_request_context_selector)
+ : child_id_(child_id),
+ process_type_(process_type),
+ resource_context_(resource_context),
+ appcache_service_(appcache_service),
+ blob_storage_context_(blob_storage_context),
+ file_system_context_(file_system_context),
+ url_request_context_selector_(url_request_context_selector) {
+ DCHECK(resource_context);
+ DCHECK(url_request_context_selector);
+ // |appcache_service| and |blob_storage_context| may be NULL in unittests.
+}
+
+ResourceMessageFilter::~ResourceMessageFilter() {
+}
+
+void ResourceMessageFilter::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Unhook us from all pending network requests so they don't get sent to a
+ // deleted object.
+ ResourceDispatcherHostImpl::Get()->CancelRequestsForProcess(child_id_);
+}
+
+bool ResourceMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ return ResourceDispatcherHostImpl::Get()->OnMessageReceived(
+ message, this, message_was_ok);
+}
+
+net::URLRequestContext* ResourceMessageFilter::GetURLRequestContext(
+ ResourceType::Type type) {
+ return url_request_context_selector_->GetRequestContext(type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_message_filter.h b/chromium/content/browser/loader/resource_message_filter.h
new file mode 100644
index 00000000000..2c56af0dd6d
--- /dev/null
+++ b/chromium/content/browser/loader/resource_message_filter.h
@@ -0,0 +1,108 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_FILTER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "webkit/common/resource_type.h"
+
+namespace fileapi {
+class FileSystemContext;
+} // namespace fileapi
+
+namespace net {
+class URLRequestContext;
+} // namespace net
+
+
+namespace content {
+class ChromeAppCacheService;
+class ChromeBlobStorageContext;
+class ResourceContext;
+
+// This class filters out incoming IPC messages for network requests and
+// processes them on the IPC thread. As a result, network requests are not
+// delayed by costly UI processing that may be occuring on the main thread of
+// the browser. It also means that any hangs in starting a network request
+// will not interfere with browser UI.
+class CONTENT_EXPORT ResourceMessageFilter : public BrowserMessageFilter {
+ public:
+ // Allows selecting the net::URLRequestContext used to service requests.
+ class URLRequestContextSelector {
+ public:
+ URLRequestContextSelector() {}
+ virtual ~URLRequestContextSelector() {}
+
+ virtual net::URLRequestContext* GetRequestContext(
+ ResourceType::Type request_type) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContextSelector);
+ };
+
+ ResourceMessageFilter(
+ int child_id,
+ int process_type,
+ ResourceContext* resource_context,
+ ChromeAppCacheService* appcache_service,
+ ChromeBlobStorageContext* blob_storage_context,
+ fileapi::FileSystemContext* file_system_context,
+ URLRequestContextSelector* url_request_context_selector);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ ResourceContext* resource_context() const {
+ return resource_context_;
+ }
+
+ ChromeAppCacheService* appcache_service() const {
+ return appcache_service_.get();
+ }
+
+ ChromeBlobStorageContext* blob_storage_context() const {
+ return blob_storage_context_.get();
+ }
+
+ fileapi::FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+
+ // Returns the net::URLRequestContext for the given request.
+ net::URLRequestContext* GetURLRequestContext(
+ ResourceType::Type request_type);
+
+ int child_id() const { return child_id_; }
+ int process_type() const { return process_type_; }
+
+ protected:
+ // Protected destructor so that we can be overriden in tests.
+ virtual ~ResourceMessageFilter();
+
+ private:
+ // The ID of the child process.
+ int child_id_;
+
+ int process_type_;
+
+ // Owned by ProfileIOData* which is guaranteed to outlive us.
+ ResourceContext* resource_context_;
+
+ scoped_refptr<ChromeAppCacheService> appcache_service_;
+ scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
+ scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+
+ const scoped_ptr<URLRequestContextSelector> url_request_context_selector_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/loader/resource_request_info_impl.cc b/chromium/content/browser/loader/resource_request_info_impl.cc
new file mode 100644
index 00000000000..51e39ff3ddd
--- /dev/null
+++ b/chromium/content/browser/loader/resource_request_info_impl.cc
@@ -0,0 +1,234 @@
+// 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 "content/browser/loader/resource_request_info_impl.h"
+
+#include "content/browser/loader/global_routing_id.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/net/url_request_user_data.h"
+#include "content/public/browser/global_request_id.h"
+#include "net/url_request/url_request.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace content {
+
+// ----------------------------------------------------------------------------
+// ResourceRequestInfo
+
+// static
+const ResourceRequestInfo* ResourceRequestInfo::ForRequest(
+ const net::URLRequest* request) {
+ return ResourceRequestInfoImpl::ForRequest(request);
+}
+
+// static
+void ResourceRequestInfo::AllocateForTesting(
+ net::URLRequest* request,
+ ResourceType::Type resource_type,
+ ResourceContext* context,
+ int render_process_id,
+ int render_view_id) {
+ ResourceRequestInfoImpl* info =
+ new ResourceRequestInfoImpl(
+ PROCESS_TYPE_RENDERER, // process_type
+ render_process_id, // child_id
+ render_view_id, // route_id
+ 0, // origin_pid
+ 0, // request_id
+ resource_type == ResourceType::MAIN_FRAME, // is_main_frame
+ 0, // frame_id
+ false, // parent_is_main_frame
+ 0, // parent_frame_id
+ resource_type, // resource_type
+ PAGE_TRANSITION_LINK, // transition_type
+ false, // is_download
+ false, // is_stream
+ true, // allow_download
+ false, // has_user_gesture
+ WebKit::WebReferrerPolicyDefault, // referrer_policy
+ context, // context
+ false); // is_async
+ info->AssociateWithRequest(request);
+}
+
+// static
+bool ResourceRequestInfo::GetRenderViewForRequest(
+ const net::URLRequest* request,
+ int* render_process_id,
+ int* render_view_id) {
+ URLRequestUserData* user_data = static_cast<URLRequestUserData*>(
+ request->GetUserData(URLRequestUserData::kUserDataKey));
+ if (!user_data)
+ return false;
+ *render_process_id = user_data->render_process_id();
+ *render_view_id = user_data->render_view_id();
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// ResourceRequestInfoImpl
+
+// static
+ResourceRequestInfoImpl* ResourceRequestInfoImpl::ForRequest(
+ net::URLRequest* request) {
+ return static_cast<ResourceRequestInfoImpl*>(request->GetUserData(NULL));
+}
+
+// static
+const ResourceRequestInfoImpl* ResourceRequestInfoImpl::ForRequest(
+ const net::URLRequest* request) {
+ return ForRequest(const_cast<net::URLRequest*>(request));
+}
+
+ResourceRequestInfoImpl::ResourceRequestInfoImpl(
+ int process_type,
+ int child_id,
+ int route_id,
+ int origin_pid,
+ int request_id,
+ bool is_main_frame,
+ int64 frame_id,
+ bool parent_is_main_frame,
+ int64 parent_frame_id,
+ ResourceType::Type resource_type,
+ PageTransition transition_type,
+ bool is_download,
+ bool is_stream,
+ bool allow_download,
+ bool has_user_gesture,
+ WebKit::WebReferrerPolicy referrer_policy,
+ ResourceContext* context,
+ bool is_async)
+ : cross_site_handler_(NULL),
+ process_type_(process_type),
+ child_id_(child_id),
+ route_id_(route_id),
+ origin_pid_(origin_pid),
+ request_id_(request_id),
+ is_main_frame_(is_main_frame),
+ frame_id_(frame_id),
+ parent_is_main_frame_(parent_is_main_frame),
+ parent_frame_id_(parent_frame_id),
+ is_download_(is_download),
+ is_stream_(is_stream),
+ allow_download_(allow_download),
+ has_user_gesture_(has_user_gesture),
+ was_ignored_by_handler_(false),
+ resource_type_(resource_type),
+ transition_type_(transition_type),
+ memory_cost_(0),
+ referrer_policy_(referrer_policy),
+ context_(context),
+ is_async_(is_async) {
+}
+
+ResourceRequestInfoImpl::~ResourceRequestInfoImpl() {
+}
+
+ResourceContext* ResourceRequestInfoImpl::GetContext() const {
+ return context_;
+}
+
+int ResourceRequestInfoImpl::GetChildID() const {
+ return child_id_;
+}
+
+int ResourceRequestInfoImpl::GetRouteID() const {
+ return route_id_;
+}
+
+int ResourceRequestInfoImpl::GetOriginPID() const {
+ return origin_pid_;
+}
+
+int ResourceRequestInfoImpl::GetRequestID() const {
+ return request_id_;
+}
+
+bool ResourceRequestInfoImpl::IsMainFrame() const {
+ return is_main_frame_;
+}
+
+int64 ResourceRequestInfoImpl::GetFrameID() const {
+ return frame_id_;
+}
+
+bool ResourceRequestInfoImpl::ParentIsMainFrame() const {
+ return parent_is_main_frame_;
+}
+
+int64 ResourceRequestInfoImpl::GetParentFrameID() const {
+ return parent_frame_id_;
+}
+
+ResourceType::Type ResourceRequestInfoImpl::GetResourceType() const {
+ return resource_type_;
+}
+
+WebKit::WebReferrerPolicy ResourceRequestInfoImpl::GetReferrerPolicy() const {
+ return referrer_policy_;
+}
+
+PageTransition ResourceRequestInfoImpl::GetPageTransition() const {
+ return transition_type_;
+}
+
+bool ResourceRequestInfoImpl::HasUserGesture() const {
+ return has_user_gesture_;
+}
+
+bool ResourceRequestInfoImpl::WasIgnoredByHandler() const {
+ return was_ignored_by_handler_;
+}
+
+bool ResourceRequestInfoImpl::GetAssociatedRenderView(
+ int* render_process_id,
+ int* render_view_id) const {
+ // If the request is from the worker process, find a content that owns the
+ // worker.
+ if (process_type_ == PROCESS_TYPE_WORKER) {
+ // Need to display some related UI for this network request - pick an
+ // arbitrary parent to do so.
+ if (!WorkerServiceImpl::GetInstance()->GetRendererForWorker(
+ child_id_, render_process_id, render_view_id)) {
+ *render_process_id = -1;
+ *render_view_id = -1;
+ return false;
+ }
+ } else {
+ *render_process_id = child_id_;
+ *render_view_id = route_id_;
+ }
+ return true;
+}
+
+bool ResourceRequestInfoImpl::IsAsync() const {
+ return is_async_;
+}
+
+void ResourceRequestInfoImpl::AssociateWithRequest(net::URLRequest* request) {
+ request->SetUserData(NULL, this);
+ int render_process_id;
+ int render_view_id;
+ if (GetAssociatedRenderView(&render_process_id, &render_view_id)) {
+ request->SetUserData(
+ URLRequestUserData::kUserDataKey,
+ new URLRequestUserData(render_process_id, render_view_id));
+ }
+}
+
+GlobalRequestID ResourceRequestInfoImpl::GetGlobalRequestID() const {
+ return GlobalRequestID(child_id_, request_id_);
+}
+
+GlobalRoutingID ResourceRequestInfoImpl::GetGlobalRoutingID() const {
+ return GlobalRoutingID(child_id_, route_id_);
+}
+
+void ResourceRequestInfoImpl::set_requested_blob_data(
+ webkit_blob::BlobData* data) {
+ requested_blob_data_ = data;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_request_info_impl.h b/chromium/content/browser/loader/resource_request_info_impl.h
new file mode 100644
index 00000000000..d99586530ab
--- /dev/null
+++ b/chromium/content/browser/loader/resource_request_info_impl.h
@@ -0,0 +1,157 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_REQUEST_INFO_IMPL_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_REQUEST_INFO_IMPL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/supports_user_data.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/referrer.h"
+#include "net/base/load_states.h"
+#include "webkit/common/resource_type.h"
+
+namespace webkit_blob {
+class BlobData;
+}
+
+namespace content {
+class CrossSiteResourceHandler;
+class ResourceContext;
+struct GlobalRequestID;
+struct GlobalRoutingID;
+
+// Holds the data ResourceDispatcherHost associates with each request.
+// Retrieve this data by calling ResourceDispatcherHost::InfoForRequest.
+class ResourceRequestInfoImpl : public ResourceRequestInfo,
+ public base::SupportsUserData::Data {
+ public:
+ // Returns the ResourceRequestInfoImpl associated with the given URLRequest.
+ CONTENT_EXPORT static ResourceRequestInfoImpl* ForRequest(
+ net::URLRequest* request);
+
+ // And, a const version for cases where you only need read access.
+ CONTENT_EXPORT static const ResourceRequestInfoImpl* ForRequest(
+ const net::URLRequest* request);
+
+ CONTENT_EXPORT ResourceRequestInfoImpl(
+ int process_type,
+ int child_id,
+ int route_id,
+ int origin_pid,
+ int request_id,
+ bool is_main_frame,
+ int64 frame_id,
+ bool parent_is_main_frame,
+ int64 parent_frame_id,
+ ResourceType::Type resource_type,
+ PageTransition transition_type,
+ bool is_download,
+ bool is_stream,
+ bool allow_download,
+ bool has_user_gesture,
+ WebKit::WebReferrerPolicy referrer_policy,
+ ResourceContext* context,
+ bool is_async);
+ virtual ~ResourceRequestInfoImpl();
+
+ // ResourceRequestInfo implementation:
+ virtual ResourceContext* GetContext() const OVERRIDE;
+ virtual int GetChildID() const OVERRIDE;
+ virtual int GetRouteID() const OVERRIDE;
+ virtual int GetOriginPID() const OVERRIDE;
+ virtual int GetRequestID() const OVERRIDE;
+ virtual bool IsMainFrame() const OVERRIDE;
+ virtual int64 GetFrameID() const OVERRIDE;
+ virtual bool ParentIsMainFrame() const OVERRIDE;
+ virtual int64 GetParentFrameID() const OVERRIDE;
+ virtual ResourceType::Type GetResourceType() const OVERRIDE;
+ virtual WebKit::WebReferrerPolicy GetReferrerPolicy() const OVERRIDE;
+ virtual PageTransition GetPageTransition() const OVERRIDE;
+ virtual bool HasUserGesture() const OVERRIDE;
+ virtual bool WasIgnoredByHandler() const OVERRIDE;
+ virtual bool GetAssociatedRenderView(int* render_process_id,
+ int* render_view_id) const OVERRIDE;
+ virtual bool IsAsync() const OVERRIDE;
+
+
+ CONTENT_EXPORT void AssociateWithRequest(net::URLRequest* request);
+
+ CONTENT_EXPORT GlobalRequestID GetGlobalRequestID() const;
+ GlobalRoutingID GetGlobalRoutingID() const;
+
+ // CrossSiteResourceHandler for this request. May be null.
+ CrossSiteResourceHandler* cross_site_handler() {
+ return cross_site_handler_;
+ }
+ void set_cross_site_handler(CrossSiteResourceHandler* h) {
+ cross_site_handler_ = h;
+ }
+
+ // Identifies the type of process (renderer, plugin, etc.) making the request.
+ int process_type() const { return process_type_; }
+
+ // Downloads are allowed only as a top level request.
+ bool allow_download() const { return allow_download_; }
+
+ // Whether this is a download.
+ bool is_download() const { return is_download_; }
+ void set_is_download(bool download) { is_download_ = download; }
+
+ // Whether this is a stream.
+ bool is_stream() const { return is_stream_; }
+ void set_is_stream(bool stream) { is_stream_ = stream; }
+
+ void set_was_ignored_by_handler(bool value) {
+ was_ignored_by_handler_ = value;
+ }
+
+ // The approximate in-memory size (bytes) that we credited this request
+ // as consuming in |outstanding_requests_memory_cost_map_|.
+ int memory_cost() const { return memory_cost_; }
+ void set_memory_cost(int cost) { memory_cost_ = cost; }
+
+ // We hold a reference to the requested blob data to ensure it doesn't
+ // get finally released prior to the net::URLRequestJob being started.
+ webkit_blob::BlobData* requested_blob_data() const {
+ return requested_blob_data_.get();
+ }
+ void set_requested_blob_data(webkit_blob::BlobData* data);
+
+ private:
+ // Non-owning, may be NULL.
+ CrossSiteResourceHandler* cross_site_handler_;
+
+ int process_type_;
+ int child_id_;
+ int route_id_;
+ int origin_pid_;
+ int request_id_;
+ bool is_main_frame_;
+ int64 frame_id_;
+ bool parent_is_main_frame_;
+ int64 parent_frame_id_;
+ bool is_download_;
+ bool is_stream_;
+ bool allow_download_;
+ bool has_user_gesture_;
+ bool was_ignored_by_handler_;
+ ResourceType::Type resource_type_;
+ PageTransition transition_type_;
+ int memory_cost_;
+ scoped_refptr<webkit_blob::BlobData> requested_blob_data_;
+ WebKit::WebReferrerPolicy referrer_policy_;
+ ResourceContext* context_;
+ bool is_async_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceRequestInfoImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_REQUEST_INFO_IMPL_H_
diff --git a/chromium/content/browser/loader/resource_scheduler.cc b/chromium/content/browser/loader/resource_scheduler.cc
new file mode 100644
index 00000000000..7e70c457814
--- /dev/null
+++ b/chromium/content/browser/loader/resource_scheduler.cc
@@ -0,0 +1,383 @@
+// 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 "content/browser/loader/resource_scheduler.h"
+
+#include "base/stl_util.h"
+#include "content/common/resource_messages.h"
+#include "content/browser/loader/resource_message_delegate.h"
+#include "content/public/browser/resource_controller.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/browser/resource_throttle.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_server_properties.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace content {
+
+static const size_t kMaxNumDelayableRequestsPerClient = 10;
+
+// A thin wrapper around net::PriorityQueue that deals with
+// ScheduledResourceRequests instead of PriorityQueue::Pointers.
+class ResourceScheduler::RequestQueue {
+ public:
+ RequestQueue() : queue_(net::NUM_PRIORITIES) {}
+ ~RequestQueue() {}
+
+ // Adds |request| to the queue with given |priority|.
+ void Insert(ScheduledResourceRequest* request,
+ net::RequestPriority priority) {
+ DCHECK(!ContainsKey(pointers_, request));
+ NetQueue::Pointer pointer = queue_.Insert(request, priority);
+ pointers_[request] = pointer;
+ }
+
+ // Removes |request| from the queue.
+ void Erase(ScheduledResourceRequest* request) {
+ PointerMap::iterator it = pointers_.find(request);
+ DCHECK(it != pointers_.end());
+ queue_.Erase(it->second);
+ pointers_.erase(it);
+ }
+
+ // Returns the highest priority request that's queued, or NULL if none are.
+ ScheduledResourceRequest* FirstMax() {
+ return queue_.FirstMax().value();
+ }
+
+ // Returns true if |request| is queued.
+ bool IsQueued(ScheduledResourceRequest* request) const {
+ return ContainsKey(pointers_, request);
+ }
+
+ // Returns true if no requests are queued.
+ bool IsEmpty() const { return queue_.size() == 0; }
+
+ private:
+ typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue;
+ typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap;
+
+ NetQueue queue_;
+ PointerMap pointers_;
+};
+
+// This is the handle we return to the ResourceDispatcherHostImpl so it can
+// interact with the request.
+class ResourceScheduler::ScheduledResourceRequest
+ : public ResourceMessageDelegate,
+ public ResourceThrottle {
+ public:
+ ScheduledResourceRequest(const ClientId& client_id,
+ net::URLRequest* request,
+ ResourceScheduler* scheduler)
+ : ResourceMessageDelegate(request),
+ client_id_(client_id),
+ request_(request),
+ ready_(false),
+ deferred_(false),
+ scheduler_(scheduler) {
+ }
+
+ virtual ~ScheduledResourceRequest() {
+ scheduler_->RemoveRequest(this);
+ }
+
+ void Start() {
+ ready_ = true;
+ if (deferred_ && request_->status().is_success()) {
+ deferred_ = false;
+ controller()->Resume();
+ }
+ }
+
+ const ClientId& client_id() const { return client_id_; }
+ net::URLRequest* url_request() { return request_; }
+ const net::URLRequest* url_request() const { return request_; }
+
+ private:
+ // ResourceMessageDelegate interface:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ScheduledResourceRequest, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ResourceHostMsg_DidChangePriority, DidChangePriority)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+ }
+
+ // ResourceThrottle interface:
+ virtual void WillStartRequest(bool* defer) OVERRIDE {
+ deferred_ = *defer = !ready_;
+ }
+
+ void DidChangePriority(int request_id, net::RequestPriority new_priority) {
+ scheduler_->ReprioritizeRequest(this, new_priority);
+ }
+
+ ClientId client_id_;
+ net::URLRequest* request_;
+ bool ready_;
+ bool deferred_;
+ ResourceScheduler* scheduler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
+};
+
+// Each client represents a tab.
+struct ResourceScheduler::Client {
+ Client() : has_body(false) {}
+ ~Client() {}
+
+ bool has_body;
+ RequestQueue pending_requests;
+ RequestSet in_flight_requests;
+};
+
+ResourceScheduler::ResourceScheduler() {
+}
+
+ResourceScheduler::~ResourceScheduler() {
+ DCHECK(unowned_requests_.empty());
+ DCHECK(client_map_.empty());
+}
+
+scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
+ int child_id,
+ int route_id,
+ net::URLRequest* url_request) {
+ DCHECK(CalledOnValidThread());
+ ClientId client_id = MakeClientId(child_id, route_id);
+ scoped_ptr<ScheduledResourceRequest> request(
+ new ScheduledResourceRequest(client_id, url_request, this));
+
+ ClientMap::iterator it = client_map_.find(client_id);
+ if (it == client_map_.end()) {
+ // There are several ways this could happen:
+ // 1. <a ping> requests don't have a route_id.
+ // 2. Most unittests don't send the IPCs needed to register Clients.
+ // 3. The tab is closed while a RequestResource IPC is in flight.
+ unowned_requests_.insert(request.get());
+ request->Start();
+ return request.PassAs<ResourceThrottle>();
+ }
+
+ Client* client = it->second;
+ if (ShouldStartRequest(request.get(), client)) {
+ StartRequest(request.get(), client);
+ } else {
+ client->pending_requests.Insert(request.get(), url_request->priority());
+ }
+ return request.PassAs<ResourceThrottle>();
+}
+
+void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
+ DCHECK(CalledOnValidThread());
+ if (ContainsKey(unowned_requests_, request)) {
+ unowned_requests_.erase(request);
+ return;
+ }
+
+ ClientMap::iterator client_it = client_map_.find(request->client_id());
+ if (client_it == client_map_.end()) {
+ return;
+ }
+
+ Client* client = client_it->second;
+
+ if (client->pending_requests.IsQueued(request)) {
+ client->pending_requests.Erase(request);
+ DCHECK(!ContainsKey(client->in_flight_requests, request));
+ } else {
+ size_t erased = client->in_flight_requests.erase(request);
+ DCHECK(erased);
+
+ // Removing this request may have freed up another to load.
+ LoadAnyStartablePendingRequests(client);
+ }
+}
+
+void ResourceScheduler::OnClientCreated(int child_id, int route_id) {
+ DCHECK(CalledOnValidThread());
+ ClientId client_id = MakeClientId(child_id, route_id);
+ DCHECK(!ContainsKey(client_map_, client_id));
+
+ client_map_[client_id] = new Client;
+}
+
+void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
+ DCHECK(CalledOnValidThread());
+ ClientId client_id = MakeClientId(child_id, route_id);
+ DCHECK(ContainsKey(client_map_, client_id));
+ ClientMap::iterator it = client_map_.find(client_id);
+ Client* client = it->second;
+
+ // FYI, ResourceDispatcherHost cancels all of the requests after this function
+ // is called. It should end up canceling all of the requests except for a
+ // cross-renderer navigation.
+ for (RequestSet::iterator it = client->in_flight_requests.begin();
+ it != client->in_flight_requests.end(); ++it) {
+ unowned_requests_.insert(*it);
+ }
+ client->in_flight_requests.clear();
+
+ delete client;
+ client_map_.erase(it);
+}
+
+void ResourceScheduler::OnNavigate(int child_id, int route_id) {
+ DCHECK(CalledOnValidThread());
+ ClientId client_id = MakeClientId(child_id, route_id);
+
+ ClientMap::iterator it = client_map_.find(client_id);
+ if (it == client_map_.end()) {
+ // The client was likely deleted shortly before we received this IPC.
+ return;
+ }
+
+ Client* client = it->second;
+ client->has_body = false;
+}
+
+void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
+ DCHECK(CalledOnValidThread());
+ ClientId client_id = MakeClientId(child_id, route_id);
+
+ ClientMap::iterator it = client_map_.find(client_id);
+ if (it == client_map_.end()) {
+ // The client was likely deleted shortly before we received this IPC.
+ return;
+ }
+
+ Client* client = it->second;
+ client->has_body = false;
+ if (!client->has_body) {
+ client->has_body = true;
+ LoadAnyStartablePendingRequests(client);
+ }
+}
+
+void ResourceScheduler::StartRequest(ScheduledResourceRequest* request,
+ Client* client) {
+ client->in_flight_requests.insert(request);
+ request->Start();
+}
+
+void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
+ net::RequestPriority new_priority) {
+ net::RequestPriority old_priority = request->url_request()->priority();
+ DCHECK_NE(new_priority, old_priority);
+ request->url_request()->SetPriority(new_priority);
+ ClientMap::iterator client_it = client_map_.find(request->client_id());
+ if (client_it == client_map_.end()) {
+ // The client was likely deleted shortly before we received this IPC.
+ return;
+ }
+
+ Client *client = client_it->second;
+ if (!client->pending_requests.IsQueued(request)) {
+ DCHECK(ContainsKey(client->in_flight_requests, request));
+ // Request has already started.
+ return;
+ }
+
+ client->pending_requests.Erase(request);
+ client->pending_requests.Insert(request, request->url_request()->priority());
+
+ if (new_priority > old_priority) {
+ // Check if this request is now able to load at its new priority.
+ LoadAnyStartablePendingRequests(client);
+ }
+}
+
+void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) {
+ while (!client->pending_requests.IsEmpty()) {
+ ScheduledResourceRequest* request = client->pending_requests.FirstMax();
+ if (ShouldStartRequest(request, client)) {
+ client->pending_requests.Erase(request);
+ StartRequest(request, client);
+ } else {
+ break;
+ }
+ }
+}
+
+size_t ResourceScheduler::GetNumDelayableRequestsInFlight(
+ Client* client) const {
+ size_t count = 0;
+ for (RequestSet::iterator it = client->in_flight_requests.begin();
+ it != client->in_flight_requests.end(); ++it) {
+ if ((*it)->url_request()->priority() < net::LOW) {
+ const net::HttpServerProperties& http_server_properties =
+ *(*it)->url_request()->context()->http_server_properties();
+ if (!http_server_properties.SupportsSpdy(
+ net::HostPortPair::FromURL((*it)->url_request()->url()))) {
+ ++count;
+ }
+ }
+ }
+ return count;
+}
+
+// ShouldStartRequest is the main scheduling algorithm.
+//
+// Requests are categorized into two categories:
+//
+// 1. Immediately issued requests, which are:
+//
+// * Higher priority requests (>= net::LOW).
+// * Synchronous requests.
+// * Requests to SPDY-capable origin servers.
+//
+// 2. The remainder are delayable requests, which follow these rules:
+//
+// * If no high priority requests are in flight, start loading low priority
+// requests.
+// * Once the renderer has a <body>, start loading delayable requests.
+// * Never exceed 10 delayable requests in flight per client.
+// * Prior to <body>, allow one delayable request to load at a time.
+bool ResourceScheduler::ShouldStartRequest(ScheduledResourceRequest* request,
+ Client* client) const {
+ const net::URLRequest& url_request = *request->url_request();
+ const net::HttpServerProperties& http_server_properties =
+ *url_request.context()->http_server_properties();
+
+ // TODO(willchan): We should really improve this algorithm as described in
+ // crbug.com/164101. Also, theoretically we should not count a SPDY request
+ // against the delayable requests limit.
+ bool origin_supports_spdy = http_server_properties.SupportsSpdy(
+ net::HostPortPair::FromURL(url_request.url()));
+
+ if (url_request.priority() >= net::LOW ||
+ !ResourceRequestInfo::ForRequest(&url_request)->IsAsync() ||
+ origin_supports_spdy) {
+ return true;
+ }
+
+ size_t num_delayable_requests_in_flight =
+ GetNumDelayableRequestsInFlight(client);
+ if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) {
+ return false;
+ }
+
+ bool have_immediate_requests_in_flight =
+ client->in_flight_requests.size() > num_delayable_requests_in_flight;
+ if (have_immediate_requests_in_flight && !client->has_body &&
+ num_delayable_requests_in_flight != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
+ int child_id, int route_id) {
+ return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_scheduler.h b/chromium/content/browser/loader/resource_scheduler.h
new file mode 100644
index 00000000000..d9711134421
--- /dev/null
+++ b/chromium/content/browser/loader/resource_scheduler.h
@@ -0,0 +1,126 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+#include "net/base/priority_queue.h"
+#include "net/base/request_priority.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+class ResourceThrottle;
+
+// There is one ResourceScheduler. All renderer-initiated HTTP requests are
+// expected to pass through it.
+//
+// There are two types of input to the scheduler:
+// 1. Requests to start, cancel, or finish fetching a resource.
+// 2. Notifications for renderer events, such as new tabs, navigation and
+// painting.
+//
+// These input come from different threads, so they may not be in sync. The UI
+// thread is considered the authority on renderer lifetime, which means some
+// IPCs may be meaningless if they arrive after the UI thread signals a renderer
+// has been deleted.
+//
+// The ResourceScheduler tracks many Clients, which should correlate with tabs.
+// A client is uniquely identified by its child_id and route_id.
+//
+// Each Client may have many Requests in flight. Requests are uniquely
+// identified within a Client by its ScheduledResourceRequest.
+//
+// Users should call ScheduleRequest() to notify this ResourceScheduler of a
+// new request. The returned ResourceThrottle should be destroyed when the load
+// finishes or is canceled.
+//
+// The scheduler may defer issuing the request via the ResourceThrottle
+// interface or it may alter the request's priority by calling set_priority() on
+// the URLRequest.
+class CONTENT_EXPORT ResourceScheduler : public base::NonThreadSafe {
+ public:
+ ResourceScheduler();
+ ~ResourceScheduler();
+
+ // Requests that this ResourceScheduler schedule, and eventually loads, the
+ // specified |url_request|. Caller should delete the returned ResourceThrottle
+ // when the load completes or is canceled.
+ scoped_ptr<ResourceThrottle> ScheduleRequest(
+ int child_id, int route_id, net::URLRequest* url_request);
+
+ // Signals from the UI thread, posted as tasks on the IO thread:
+
+ // Called when a renderer is created.
+ void OnClientCreated(int child_id, int route_id);
+
+ // Called when a renderer is destroyed.
+ void OnClientDeleted(int child_id, int route_id);
+
+ // Signals from IPC messages directly from the renderers:
+
+ // Called when a client navigates to a new main document.
+ void OnNavigate(int child_id, int route_id);
+
+ // Called when the client has parsed the <body> element. This is a signal that
+ // resource loads won't interfere with first paint.
+ void OnWillInsertBody(int child_id, int route_id);
+
+ private:
+ class RequestQueue;
+ class ScheduledResourceRequest;
+ struct Client;
+
+ typedef int64 ClientId;
+ typedef std::map<ClientId, Client*> ClientMap;
+ typedef std::set<ScheduledResourceRequest*> RequestSet;
+
+ // Called when a ScheduledResourceRequest is destroyed.
+ void RemoveRequest(ScheduledResourceRequest* request);
+
+ // Unthrottles the |request| and adds it to |client|.
+ void StartRequest(ScheduledResourceRequest* request, Client* client);
+
+ // Update the queue position for |request|, possibly causing it to start
+ // loading.
+ //
+ // Queues are maintained for each priority level. When |request| is
+ // reprioritized, it will move to the end of the queue for that priority
+ // level.
+ void ReprioritizeRequest(ScheduledResourceRequest* request,
+ net::RequestPriority new_priority);
+
+ // Attempts to load any pending requests in |client|, based on the
+ // results of ShouldStartRequest().
+ void LoadAnyStartablePendingRequests(Client* client);
+
+ // Returns the number of requests with priority < LOW that are currently in
+ // flight.
+ size_t GetNumDelayableRequestsInFlight(Client* client) const;
+
+ // Returns true if the request should start. This is the core scheduling
+ // algorithm.
+ bool ShouldStartRequest(ScheduledResourceRequest* request,
+ Client* client) const;
+
+ // Returns the client ID for the given |child_id| and |route_id| combo.
+ ClientId MakeClientId(int child_id, int route_id);
+
+ ClientMap client_map_;
+ RequestSet unowned_requests_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_H_
diff --git a/chromium/content/browser/loader/resource_scheduler_filter.cc b/chromium/content/browser/loader/resource_scheduler_filter.cc
new file mode 100644
index 00000000000..fb5a7fa60f1
--- /dev/null
+++ b/chromium/content/browser/loader/resource_scheduler_filter.cc
@@ -0,0 +1,51 @@
+// 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 "content/browser/loader/resource_scheduler_filter.h"
+
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_scheduler.h"
+#include "content/common/view_messages.h"
+#include "content/public/common/page_transition_types.h"
+
+namespace content {
+
+ResourceSchedulerFilter::ResourceSchedulerFilter(int child_id)
+ : child_id_(child_id) {
+}
+
+ResourceSchedulerFilter::~ResourceSchedulerFilter() {
+}
+
+bool ResourceSchedulerFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ switch (message.type()) {
+ case ViewHostMsg_FrameNavigate::ID: {
+ PickleIterator iter(message);
+ ViewHostMsg_FrameNavigate_Params params;
+ if (!IPC::ParamTraits<ViewHostMsg_FrameNavigate_Params>::Read(
+ &message, &iter, &params)) {
+ break;
+ }
+ if (PageTransitionIsMainFrame(params.transition) &&
+ !params.was_within_same_page) {
+ ResourceDispatcherHostImpl::Get()->scheduler()->OnNavigate(
+ child_id_, message.routing_id());
+ }
+ break;
+ }
+
+ case ViewHostMsg_WillInsertBody::ID:
+ ResourceDispatcherHostImpl::Get()->scheduler()->OnWillInsertBody(
+ child_id_, message.routing_id());
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/resource_scheduler_filter.h b/chromium/content/browser/loader/resource_scheduler_filter.h
new file mode 100644
index 00000000000..05a8631a2a0
--- /dev/null
+++ b/chromium/content/browser/loader/resource_scheduler_filter.h
@@ -0,0 +1,33 @@
+// 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 CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_FILTER_H_
+#define CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_FILTER_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+// This class listens for incoming ViewHostMsgs that are applicable to the
+// ResourceScheduler and invokes the appropriate notifications. It must be
+// inserted before the RenderMessageFilter, because the ResourceScheduler runs
+// on the IO thread and we want to see the messages before the view messages are
+// bounced to the UI thread.
+class ResourceSchedulerFilter : public BrowserMessageFilter {
+ public:
+ explicit ResourceSchedulerFilter(int child_id);
+
+ // BrowserMessageFilter methods:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~ResourceSchedulerFilter();
+
+ int child_id_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_RESOURCE_SCHEDULER_FILTER_H_
diff --git a/chromium/content/browser/loader/resource_scheduler_unittest.cc b/chromium/content/browser/loader/resource_scheduler_unittest.cc
new file mode 100644
index 00000000000..9d87f2df047
--- /dev/null
+++ b/chromium/content/browser/loader/resource_scheduler_unittest.cc
@@ -0,0 +1,436 @@
+// 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 "content/browser/loader/resource_scheduler.h"
+
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/common/resource_messages.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/resource_controller.h"
+#include "content/public/browser/resource_throttle.h"
+#include "content/public/common/process_type.h"
+#include "net/base/host_port_pair.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/common/resource_type.h"
+
+namespace content {
+
+namespace {
+
+class TestRequestFactory;
+
+const int kChildId = 30;
+const int kRouteId = 75;
+
+class TestRequest : public ResourceController {
+ public:
+ TestRequest(scoped_ptr<ResourceThrottle> throttle,
+ scoped_ptr<net::URLRequest> url_request)
+ : started_(false),
+ throttle_(throttle.Pass()),
+ url_request_(url_request.Pass()) {
+ throttle_->set_controller_for_testing(this);
+ }
+
+ bool started() const { return started_; }
+
+ void Start() {
+ bool deferred = false;
+ throttle_->WillStartRequest(&deferred);
+ started_ = !deferred;
+ }
+
+ const net::URLRequest* url_request() const { return url_request_.get(); }
+
+ protected:
+ // ResourceController interface:
+ virtual void Cancel() OVERRIDE {}
+ virtual void CancelAndIgnore() OVERRIDE {}
+ virtual void CancelWithError(int error_code) OVERRIDE {}
+ virtual void Resume() OVERRIDE { started_ = true; }
+
+ private:
+ bool started_;
+ scoped_ptr<ResourceThrottle> throttle_;
+ scoped_ptr<net::URLRequest> url_request_;
+};
+
+class CancelingTestRequest : public TestRequest {
+ public:
+ CancelingTestRequest(scoped_ptr<ResourceThrottle> throttle,
+ scoped_ptr<net::URLRequest> url_request)
+ : TestRequest(throttle.Pass(), url_request.Pass()) {
+ }
+
+ void set_request_to_cancel(scoped_ptr<TestRequest> request_to_cancel) {
+ request_to_cancel_ = request_to_cancel.Pass();
+ }
+
+ private:
+ virtual void Resume() OVERRIDE {
+ TestRequest::Resume();
+ request_to_cancel_.reset();
+ }
+
+ scoped_ptr<TestRequest> request_to_cancel_;
+};
+
+class FakeResourceContext : public ResourceContext {
+ private:
+ virtual net::HostResolver* GetHostResolver() OVERRIDE { return NULL; }
+ virtual net::URLRequestContext* GetRequestContext() OVERRIDE { return NULL; }
+ virtual bool AllowMicAccess(const GURL& origin) OVERRIDE { return false; }
+ virtual bool AllowCameraAccess(const GURL& origin) OVERRIDE { return false; }
+};
+
+class FakeURLRequestContextSelector
+ : public ResourceMessageFilter::URLRequestContextSelector {
+ private:
+ virtual net::URLRequestContext* GetRequestContext(
+ ResourceType::Type) OVERRIDE {
+ return NULL;
+ }
+};
+
+class FakeResourceMessageFilter : public ResourceMessageFilter {
+ public:
+ FakeResourceMessageFilter(int child_id)
+ : ResourceMessageFilter(child_id,
+ PROCESS_TYPE_RENDERER,
+ &context_,
+ NULL /* appcache_service */,
+ NULL /* blob_storage_context */,
+ NULL /* file_system_context */,
+ new FakeURLRequestContextSelector) {
+ }
+
+ private:
+ virtual ~FakeResourceMessageFilter() {}
+
+ FakeResourceContext context_;
+};
+
+class ResourceSchedulerTest : public testing::Test {
+ protected:
+ ResourceSchedulerTest()
+ : next_request_id_(0),
+ message_loop_(base::MessageLoop::TYPE_IO),
+ ui_thread_(BrowserThread::UI, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_) {
+ scheduler_.OnClientCreated(kChildId, kRouteId);
+ context_.set_http_server_properties(http_server_properties_.GetWeakPtr());
+ }
+
+ virtual ~ResourceSchedulerTest() {
+ scheduler_.OnClientDeleted(kChildId, kRouteId);
+ }
+
+ scoped_ptr<net::URLRequest> NewURLRequestWithRoute(
+ const char* url,
+ net::RequestPriority priority,
+ int route_id) {
+ scoped_ptr<net::URLRequest> url_request(
+ context_.CreateRequest(GURL(url), NULL));
+ url_request->SetPriority(priority);
+ ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl(
+ PROCESS_TYPE_RENDERER, // process_type
+ kChildId, // child_id
+ route_id, // route_id
+ 0, // origin_pid
+ ++next_request_id_, // request_id
+ false, // is_main_frame
+ 0, // frame_id
+ false, // parent_is_main_frame
+ 0, // parent_frame_id
+ ResourceType::SUB_RESOURCE, // resource_type
+ PAGE_TRANSITION_LINK, // transition_type
+ false, // is_download
+ false, // is_stream
+ true, // allow_download
+ false, // has_user_gesture
+ WebKit::WebReferrerPolicyDefault, // referrer_policy
+ NULL, // context
+ true); // is_async
+ info->AssociateWithRequest(url_request.get());
+ return url_request.Pass();
+ }
+
+ scoped_ptr<net::URLRequest> NewURLRequest(const char* url,
+ net::RequestPriority priority) {
+ return NewURLRequestWithRoute(url, priority, kRouteId);
+ }
+
+ TestRequest* NewRequestWithRoute(const char* url,
+ net::RequestPriority priority,
+ int route_id) {
+ scoped_ptr<net::URLRequest> url_request(
+ NewURLRequestWithRoute(url, priority, route_id));
+ scoped_ptr<ResourceThrottle> throttle(scheduler_.ScheduleRequest(
+ kChildId, route_id, url_request.get()));
+ TestRequest* request = new TestRequest(throttle.Pass(), url_request.Pass());
+ request->Start();
+ return request;
+ }
+
+ TestRequest* NewRequest(const char* url, net::RequestPriority priority) {
+ return NewRequestWithRoute(url, priority, kRouteId);
+ }
+
+ void ChangeRequestPriority(TestRequest* request,
+ net::RequestPriority new_priority) {
+ scoped_refptr<FakeResourceMessageFilter> filter(
+ new FakeResourceMessageFilter(kChildId));
+ const ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(
+ request->url_request());
+ const GlobalRequestID& id = info->GetGlobalRequestID();
+ ResourceHostMsg_DidChangePriority msg(
+ kRouteId, id.request_id, new_priority);
+ bool ok = false;
+ rdh_.OnMessageReceived(msg, filter.get(), &ok);
+ EXPECT_TRUE(ok);
+ }
+
+ int next_request_id_;
+ base::MessageLoop message_loop_;
+ BrowserThreadImpl ui_thread_;
+ BrowserThreadImpl io_thread_;
+ ResourceDispatcherHostImpl rdh_;
+ ResourceScheduler scheduler_;
+ net::HttpServerPropertiesImpl http_server_properties_;
+ net::TestURLRequestContext context_;
+};
+
+TEST_F(ResourceSchedulerTest, OneIsolatedLowRequest) {
+ scoped_ptr<TestRequest> request(NewRequest("http://host/1", net::LOWEST));
+ EXPECT_TRUE(request->started());
+}
+
+TEST_F(ResourceSchedulerTest, OneLowLoadsUntilIdle) {
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+ scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
+ EXPECT_TRUE(high->started());
+ EXPECT_TRUE(low->started());
+ EXPECT_FALSE(low2->started());
+ high.reset();
+ EXPECT_TRUE(low2->started());
+}
+
+TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInserted) {
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+ scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
+ EXPECT_TRUE(high->started());
+ EXPECT_TRUE(low->started());
+ EXPECT_FALSE(low2->started());
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ EXPECT_TRUE(low2->started());
+}
+
+TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInsertedExceptSpdy) {
+ http_server_properties_.SetSupportsSpdy(
+ net::HostPortPair("spdyhost", 443), true);
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low_spdy(
+ NewRequest("https://spdyhost/high", net::LOWEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+ scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
+ EXPECT_TRUE(high->started());
+ EXPECT_TRUE(low_spdy->started());
+ EXPECT_TRUE(low->started());
+ EXPECT_FALSE(low2->started());
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ EXPECT_TRUE(low2->started());
+}
+
+TEST_F(ResourceSchedulerTest, NavigationResetsState) {
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ scheduler_.OnNavigate(kChildId, kRouteId);
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+ scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
+ EXPECT_TRUE(high->started());
+ EXPECT_TRUE(low->started());
+ EXPECT_FALSE(low2->started());
+}
+
+TEST_F(ResourceSchedulerTest, BackgroundRequestStartsImmediately) {
+ const int route_id = 0; // Indicates a background request.
+ scoped_ptr<TestRequest> request(NewRequestWithRoute("http://host/1",
+ net::LOWEST, route_id));
+ EXPECT_TRUE(request->started());
+}
+
+TEST_F(ResourceSchedulerTest, StartMultipleLowRequestsWhenIdle) {
+ scoped_ptr<TestRequest> high1(NewRequest("http://host/high1", net::HIGHEST));
+ scoped_ptr<TestRequest> high2(NewRequest("http://host/high2", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+ scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
+ EXPECT_TRUE(high1->started());
+ EXPECT_TRUE(high2->started());
+ EXPECT_TRUE(low->started());
+ EXPECT_FALSE(low2->started());
+ high1.reset();
+ EXPECT_FALSE(low2->started());
+ high2.reset();
+ EXPECT_TRUE(low2->started());
+}
+
+TEST_F(ResourceSchedulerTest, CancelOtherRequestsWhileResuming) {
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low1(NewRequest("http://host/low1", net::LOWEST));
+
+ scoped_ptr<net::URLRequest> url_request(
+ NewURLRequest("http://host/low2", net::LOWEST));
+ scoped_ptr<ResourceThrottle> throttle(scheduler_.ScheduleRequest(
+ kChildId, kRouteId, url_request.get()));
+ scoped_ptr<CancelingTestRequest> low2(new CancelingTestRequest(
+ throttle.Pass(), url_request.Pass()));
+ low2->Start();
+
+ scoped_ptr<TestRequest> low3(NewRequest("http://host/low3", net::LOWEST));
+ low2->set_request_to_cancel(low3.Pass());
+ scoped_ptr<TestRequest> low4(NewRequest("http://host/low4", net::LOWEST));
+
+ EXPECT_TRUE(high->started());
+ EXPECT_FALSE(low2->started());
+ high.reset();
+ EXPECT_TRUE(low1->started());
+ EXPECT_TRUE(low2->started());
+ EXPECT_TRUE(low4->started());
+}
+
+TEST_F(ResourceSchedulerTest, LimitedNumberOfDelayableRequestsInFlight) {
+ // We only load low priority resources if there's a body.
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+
+ // Throw in one high priority request to make sure that's not a factor.
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ EXPECT_TRUE(high->started());
+
+ const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
+ ScopedVector<TestRequest> lows;
+ for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) {
+ string url = "http://host/low" + base::IntToString(i);
+ lows.push_back(NewRequest(url.c_str(), net::LOWEST));
+ EXPECT_TRUE(lows[i]->started());
+ }
+
+ scoped_ptr<TestRequest> last(NewRequest("http://host/last", net::LOWEST));
+ EXPECT_FALSE(last->started());
+ high.reset();
+ EXPECT_FALSE(last->started());
+ lows.erase(lows.begin());
+ EXPECT_TRUE(last->started());
+}
+
+TEST_F(ResourceSchedulerTest, RaisePriorityAndStart) {
+ // Dummies to enforce scheduling.
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/req", net::LOWEST));
+
+ scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
+ EXPECT_FALSE(request->started());
+
+ ChangeRequestPriority(request.get(), net::HIGHEST);
+ EXPECT_TRUE(request->started());
+}
+
+TEST_F(ResourceSchedulerTest, RaisePriorityInQueue) {
+ // Dummies to enforce scheduling.
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+
+ scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::IDLE));
+ scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ ChangeRequestPriority(request.get(), net::LOWEST);
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
+ ScopedVector<TestRequest> lows;
+ for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) {
+ string url = "http://host/low" + base::IntToString(i);
+ lows.push_back(NewRequest(url.c_str(), net::LOWEST));
+ }
+
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ EXPECT_TRUE(request->started());
+ EXPECT_FALSE(idle->started());
+}
+
+TEST_F(ResourceSchedulerTest, LowerPriority) {
+ // Dummies to enforce scheduling.
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
+
+ scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
+ scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ ChangeRequestPriority(request.get(), net::IDLE);
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
+ // 2 fewer filler requests: 1 for the "low" dummy at the start, and 1 for the
+ // one at the end, which will be tested.
+ const int kNumFillerRequests = kMaxNumDelayableRequestsPerClient - 2;
+ ScopedVector<TestRequest> lows;
+ for (int i = 0; i < kNumFillerRequests; ++i) {
+ string url = "http://host/low" + base::IntToString(i);
+ lows.push_back(NewRequest(url.c_str(), net::LOWEST));
+ }
+
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ EXPECT_FALSE(request->started());
+ EXPECT_TRUE(idle->started());
+}
+
+TEST_F(ResourceSchedulerTest, ReprioritizedRequestGoesToBackOfQueue) {
+ // Dummies to enforce scheduling.
+ scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
+ scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST));
+
+ scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
+ scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
+ ScopedVector<TestRequest> lows;
+ for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) {
+ string url = "http://host/low" + base::IntToString(i);
+ lows.push_back(NewRequest(url.c_str(), net::LOWEST));
+ }
+
+ ChangeRequestPriority(request.get(), net::IDLE);
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ ChangeRequestPriority(request.get(), net::LOWEST);
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+
+ scheduler_.OnWillInsertBody(kChildId, kRouteId);
+ EXPECT_FALSE(request->started());
+ EXPECT_FALSE(idle->started());
+}
+
+} // unnamed namespace
+
+} // namespace content
diff --git a/chromium/content/browser/loader/stream_resource_handler.cc b/chromium/content/browser/loader/stream_resource_handler.cc
new file mode 100644
index 00000000000..1e2acc877f5
--- /dev/null
+++ b/chromium/content/browser/loader/stream_resource_handler.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 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 "content/browser/loader/stream_resource_handler.h"
+
+#include "base/guid.h"
+#include "base/logging.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/public/browser/resource_controller.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+
+StreamResourceHandler::StreamResourceHandler(
+ net::URLRequest* request,
+ StreamRegistry* registry,
+ const GURL& origin)
+ : request_(request),
+ read_buffer_(NULL) {
+ // TODO(tyoshino): Find a way to share this with the blob URL creation in
+ // WebKit.
+ GURL url(std::string(chrome::kBlobScheme) + ":" +
+ origin.spec() + base::GenerateGUID());
+ stream_ = new Stream(registry, this, url);
+}
+
+StreamResourceHandler::~StreamResourceHandler() {
+ stream_->RemoveWriteObserver(this);
+}
+
+bool StreamResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return true;
+}
+
+bool StreamResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* resp,
+ bool* defer) {
+ return true;
+}
+
+bool StreamResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* resp,
+ bool* defer) {
+ return true;
+}
+
+bool StreamResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+bool StreamResourceHandler::OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) {
+ static const int kReadBufSize = 32768;
+
+ DCHECK(buf && buf_size);
+ if (!read_buffer_.get())
+ read_buffer_ = new net::IOBuffer(kReadBufSize);
+ *buf = read_buffer_.get();
+ *buf_size = kReadBufSize;
+
+ return true;
+}
+
+bool StreamResourceHandler::OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) {
+ if (!bytes_read)
+ return true;
+
+ // We have more data to read.
+ DCHECK(read_buffer_.get());
+
+ // Release the ownership of the buffer, and store a reference
+ // to it. A new one will be allocated in OnWillRead().
+ net::IOBuffer* buffer = NULL;
+ read_buffer_.swap(&buffer);
+ stream_->AddData(buffer, bytes_read);
+
+ if (!stream_->can_add_data())
+ *defer = true;
+
+ return true;
+}
+
+bool StreamResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& sec_info) {
+ stream_->Finalize();
+ return status.status() == net::URLRequestStatus::SUCCESS;
+}
+
+void StreamResourceHandler::OnDataDownloaded(
+ int request_id,
+ int bytes_downloaded) {
+ NOTREACHED();
+}
+
+void StreamResourceHandler::OnSpaceAvailable(Stream* stream) {
+ controller()->Resume();
+}
+
+void StreamResourceHandler::OnClose(Stream* stream) {
+ controller()->Cancel();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/stream_resource_handler.h b/chromium/content/browser/loader/stream_resource_handler.h
new file mode 100644
index 00000000000..037ebdc9104
--- /dev/null
+++ b/chromium/content/browser/loader/stream_resource_handler.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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 CONTENT_BROWSER_LOADER_STREAM_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_STREAM_RESOURCE_HANDLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/loader/resource_handler.h"
+#include "content/browser/streams/stream_write_observer.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace content {
+
+class StreamRegistry;
+
+// Redirect this resource to a stream.
+class StreamResourceHandler : public StreamWriteObserver,
+ public ResourceHandler {
+ public:
+ // |origin| will be used to construct the URL for the Stream. See
+ // WebCore::BlobURL and and WebCore::SecurityOrigin in Blink to understand
+ // how origin check is done on resource loading.
+ StreamResourceHandler(net::URLRequest* request,
+ StreamRegistry* registry,
+ const GURL& origin);
+ virtual ~StreamResourceHandler();
+
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+
+ // Not needed, as this event handler ought to be the final resource.
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& url,
+ ResourceResponse* resp,
+ bool* defer) OVERRIDE;
+
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* resp,
+ bool* defer) OVERRIDE;
+
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+
+ // Create a new buffer to store received data.
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+
+ // A read was completed, forward the data to the Stream.
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& sec_info) OVERRIDE;
+
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ Stream* stream() { return stream_.get(); }
+
+ private:
+ virtual void OnSpaceAvailable(Stream* stream) OVERRIDE;
+ virtual void OnClose(Stream* stream) OVERRIDE;
+
+ net::URLRequest* request_;
+ scoped_refptr<Stream> stream_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(StreamResourceHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_STREAM_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/sync_resource_handler.cc b/chromium/content/browser/loader/sync_resource_handler.cc
new file mode 100644
index 00000000000..1db8dca79b0
--- /dev/null
+++ b/chromium/content/browser/loader/sync_resource_handler.cc
@@ -0,0 +1,135 @@
+// 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 "content/browser/loader/sync_resource_handler.h"
+
+#include "base/logging.h"
+#include "content/browser/devtools/devtools_netlog_observer.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/common/resource_messages.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_response_headers.h"
+
+namespace content {
+
+SyncResourceHandler::SyncResourceHandler(
+ ResourceMessageFilter* filter,
+ net::URLRequest* request,
+ IPC::Message* result_message,
+ ResourceDispatcherHostImpl* resource_dispatcher_host)
+ : read_buffer_(new net::IOBuffer(kReadBufSize)),
+ filter_(filter),
+ request_(request),
+ result_message_(result_message),
+ rdh_(resource_dispatcher_host) {
+ result_.final_url = request_->url();
+}
+
+SyncResourceHandler::~SyncResourceHandler() {
+ if (result_message_) {
+ result_message_->set_reply_error();
+ filter_->Send(result_message_);
+ }
+}
+
+bool SyncResourceHandler::OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) {
+ return true;
+}
+
+bool SyncResourceHandler::OnRequestRedirected(
+ int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) {
+ if (rdh_->delegate()) {
+ rdh_->delegate()->OnRequestRedirected(new_url, request_,
+ filter_->resource_context(),
+ response);
+ }
+
+ DevToolsNetLogObserver::PopulateResponseInfo(request_, response);
+ // TODO(darin): It would be much better if this could live in WebCore, but
+ // doing so requires API changes at all levels. Similar code exists in
+ // WebCore/platform/network/cf/ResourceHandleCFNet.cpp :-(
+ if (new_url.GetOrigin() != result_.final_url.GetOrigin()) {
+ LOG(ERROR) << "Cross origin redirect denied";
+ return false;
+ }
+ result_.final_url = new_url;
+ return true;
+}
+
+bool SyncResourceHandler::OnResponseStarted(
+ int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ if (rdh_->delegate()) {
+ rdh_->delegate()->OnResponseStarted(
+ request_, filter_->resource_context(), response, filter_.get());
+ }
+
+ DevToolsNetLogObserver::PopulateResponseInfo(request_, response);
+
+ // We don't care about copying the status here.
+ result_.headers = response->head.headers;
+ result_.mime_type = response->head.mime_type;
+ result_.charset = response->head.charset;
+ result_.download_file_path = response->head.download_file_path;
+ result_.request_time = response->head.request_time;
+ result_.response_time = response->head.response_time;
+ result_.load_timing = response->head.load_timing;
+ result_.devtools_info = response->head.devtools_info;
+ return true;
+}
+
+bool SyncResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ return true;
+}
+
+bool SyncResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
+ int* buf_size, int min_size) {
+ DCHECK(min_size == -1);
+ *buf = read_buffer_.get();
+ *buf_size = kReadBufSize;
+ return true;
+}
+
+bool SyncResourceHandler::OnReadCompleted(int request_id, int bytes_read,
+ bool* defer) {
+ if (!bytes_read)
+ return true;
+ result_.data.append(read_buffer_->data(), bytes_read);
+ return true;
+}
+
+bool SyncResourceHandler::OnResponseCompleted(
+ int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) {
+ result_.error_code = status.error();
+
+ result_.encoded_data_length =
+ DevToolsNetLogObserver::GetAndResetEncodedDataLength(request_);
+
+ ResourceHostMsg_SyncLoad::WriteReplyParams(result_message_, result_);
+ filter_->Send(result_message_);
+ result_message_ = NULL;
+ return true;
+}
+
+void SyncResourceHandler::OnDataDownloaded(
+ int request_id,
+ int bytes_downloaded) {
+ // Sync requests don't involve ResourceMsg_DataDownloaded messages
+ // being sent back to renderers as progress is made.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/sync_resource_handler.h b/chromium/content/browser/loader/sync_resource_handler.h
new file mode 100644
index 00000000000..af254729332
--- /dev/null
+++ b/chromium/content/browser/loader/sync_resource_handler.h
@@ -0,0 +1,75 @@
+// 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 CONTENT_BROWSER_LOADER_SYNC_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_SYNC_RESOURCE_HANDLER_H_
+
+#include <string>
+
+#include "content/browser/loader/resource_handler.h"
+#include "content/public/common/resource_response.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace net {
+class IOBuffer;
+class URLRequest;
+}
+
+namespace content {
+class ResourceDispatcherHostImpl;
+class ResourceMessageFilter;
+
+// Used to complete a synchronous resource request in response to resource load
+// events from the resource dispatcher host.
+class SyncResourceHandler : public ResourceHandler {
+ public:
+ SyncResourceHandler(ResourceMessageFilter* filter,
+ net::URLRequest* request,
+ IPC::Message* result_message,
+ ResourceDispatcherHostImpl* resource_dispatcher_host);
+ virtual ~SyncResourceHandler();
+
+ virtual bool OnUploadProgress(int request_id,
+ uint64 position,
+ uint64 size) OVERRIDE;
+ virtual bool OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillRead(int request_id,
+ net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) OVERRIDE;
+ virtual bool OnReadCompleted(int request_id,
+ int bytes_read,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseCompleted(int request_id,
+ const net::URLRequestStatus& status,
+ const std::string& security_info) OVERRIDE;
+ virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE;
+
+ private:
+ enum { kReadBufSize = 3840 };
+
+ scoped_refptr<net::IOBuffer> read_buffer_;
+
+ SyncLoadResult result_;
+ scoped_refptr<ResourceMessageFilter> filter_;
+ net::URLRequest* request_;
+ IPC::Message* result_message_;
+ ResourceDispatcherHostImpl* rdh_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_SYNC_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/throttling_resource_handler.cc b/chromium/content/browser/loader/throttling_resource_handler.cc
new file mode 100644
index 00000000000..5725e035a80
--- /dev/null
+++ b/chromium/content/browser/loader/throttling_resource_handler.cc
@@ -0,0 +1,184 @@
+// 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 "content/browser/loader/throttling_resource_handler.h"
+
+#include "content/public/browser/resource_throttle.h"
+#include "content/public/common/resource_response.h"
+
+namespace content {
+
+ThrottlingResourceHandler::ThrottlingResourceHandler(
+ scoped_ptr<ResourceHandler> next_handler,
+ int child_id,
+ int request_id,
+ ScopedVector<ResourceThrottle> throttles)
+ : LayeredResourceHandler(next_handler.Pass()),
+ deferred_stage_(DEFERRED_NONE),
+ request_id_(request_id),
+ throttles_(throttles.Pass()),
+ index_(0),
+ cancelled_by_resource_throttle_(false) {
+ for (size_t i = 0; i < throttles_.size(); ++i)
+ throttles_[i]->set_controller(this);
+}
+
+ThrottlingResourceHandler::~ThrottlingResourceHandler() {
+}
+
+bool ThrottlingResourceHandler::OnRequestRedirected(int request_id,
+ const GURL& new_url,
+ ResourceResponse* response,
+ bool* defer) {
+ DCHECK_EQ(request_id_, request_id);
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ *defer = false;
+ while (index_ < throttles_.size()) {
+ throttles_[index_]->WillRedirectRequest(new_url, defer);
+ index_++;
+ if (cancelled_by_resource_throttle_)
+ return false;
+ if (*defer) {
+ deferred_stage_ = DEFERRED_REDIRECT;
+ deferred_url_ = new_url;
+ deferred_response_ = response;
+ return true; // Do not cancel.
+ }
+ }
+
+ index_ = 0; // Reset for next time.
+
+ return next_handler_->OnRequestRedirected(request_id, new_url, response,
+ defer);
+}
+
+bool ThrottlingResourceHandler::OnWillStart(int request_id,
+ const GURL& url,
+ bool* defer) {
+ DCHECK_EQ(request_id_, request_id);
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ *defer = false;
+ while (index_ < throttles_.size()) {
+ throttles_[index_]->WillStartRequest(defer);
+ index_++;
+ if (cancelled_by_resource_throttle_)
+ return false;
+ if (*defer) {
+ deferred_stage_ = DEFERRED_START;
+ deferred_url_ = url;
+ return true; // Do not cancel.
+ }
+ }
+
+ index_ = 0; // Reset for next time.
+
+ return next_handler_->OnWillStart(request_id, url, defer);
+}
+
+bool ThrottlingResourceHandler::OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) {
+ DCHECK_EQ(request_id_, request_id);
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ while (index_ < throttles_.size()) {
+ throttles_[index_]->WillProcessResponse(defer);
+ index_++;
+ if (cancelled_by_resource_throttle_)
+ return false;
+ if (*defer) {
+ deferred_stage_ = DEFERRED_RESPONSE;
+ deferred_response_ = response;
+ return true; // Do not cancel.
+ }
+ }
+
+ index_ = 0; // Reset for next time.
+
+ return next_handler_->OnResponseStarted(request_id, response, defer);
+}
+
+void ThrottlingResourceHandler::Cancel() {
+ cancelled_by_resource_throttle_ = true;
+ controller()->Cancel();
+}
+
+void ThrottlingResourceHandler::CancelAndIgnore() {
+ cancelled_by_resource_throttle_ = true;
+ controller()->CancelAndIgnore();
+}
+
+void ThrottlingResourceHandler::CancelWithError(int error_code) {
+ cancelled_by_resource_throttle_ = true;
+ controller()->CancelWithError(error_code);
+}
+
+void ThrottlingResourceHandler::Resume() {
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ DeferredStage last_deferred_stage = deferred_stage_;
+ deferred_stage_ = DEFERRED_NONE;
+ switch (last_deferred_stage) {
+ case DEFERRED_NONE:
+ NOTREACHED();
+ break;
+ case DEFERRED_START:
+ ResumeStart();
+ break;
+ case DEFERRED_REDIRECT:
+ ResumeRedirect();
+ break;
+ case DEFERRED_RESPONSE:
+ ResumeResponse();
+ break;
+ }
+}
+
+void ThrottlingResourceHandler::ResumeStart() {
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ GURL url = deferred_url_;
+ deferred_url_ = GURL();
+
+ bool defer = false;
+ if (!OnWillStart(request_id_, url, &defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ controller()->Resume();
+ }
+}
+
+void ThrottlingResourceHandler::ResumeRedirect() {
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ GURL new_url = deferred_url_;
+ deferred_url_ = GURL();
+ scoped_refptr<ResourceResponse> response;
+ deferred_response_.swap(response);
+
+ bool defer = false;
+ if (!OnRequestRedirected(request_id_, new_url, response.get(), &defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ controller()->Resume();
+ }
+}
+
+void ThrottlingResourceHandler::ResumeResponse() {
+ DCHECK(!cancelled_by_resource_throttle_);
+
+ scoped_refptr<ResourceResponse> response;
+ deferred_response_.swap(response);
+
+ bool defer = false;
+ if (!OnResponseStarted(request_id_, response.get(), &defer)) {
+ controller()->Cancel();
+ } else if (!defer) {
+ controller()->Resume();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/throttling_resource_handler.h b/chromium/content/browser/loader/throttling_resource_handler.h
new file mode 100644
index 00000000000..2661cdd7f3b
--- /dev/null
+++ b/chromium/content/browser/loader/throttling_resource_handler.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_LOADER_THROTTLING_RESOURCE_HANDLER_H_
+#define CONTENT_BROWSER_LOADER_THROTTLING_RESOURCE_HANDLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "content/browser/loader/layered_resource_handler.h"
+#include "content/public/browser/resource_controller.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class ResourceThrottle;
+struct ResourceResponse;
+
+// Used to apply a list of ResourceThrottle instances to an URLRequest.
+class ThrottlingResourceHandler : public LayeredResourceHandler,
+ public ResourceController {
+ public:
+ // Takes ownership of the ResourceThrottle instances.
+ ThrottlingResourceHandler(scoped_ptr<ResourceHandler> next_handler,
+ int child_id,
+ int request_id,
+ ScopedVector<ResourceThrottle> throttles);
+ virtual ~ThrottlingResourceHandler();
+
+ // LayeredResourceHandler overrides:
+ virtual bool OnRequestRedirected(int request_id, const GURL& url,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnResponseStarted(int request_id,
+ ResourceResponse* response,
+ bool* defer) OVERRIDE;
+ virtual bool OnWillStart(int request_id, const GURL& url,
+ bool* defer) OVERRIDE;
+
+ // ResourceThrottleController implementation:
+ virtual void Cancel() OVERRIDE;
+ virtual void CancelAndIgnore() OVERRIDE;
+ virtual void CancelWithError(int error_code) OVERRIDE;
+ virtual void Resume() OVERRIDE;
+
+ private:
+ void ResumeStart();
+ void ResumeRedirect();
+ void ResumeResponse();
+
+ enum DeferredStage {
+ DEFERRED_NONE,
+ DEFERRED_START,
+ DEFERRED_REDIRECT,
+ DEFERRED_RESPONSE
+ };
+ DeferredStage deferred_stage_;
+
+ int request_id_;
+
+ ScopedVector<ResourceThrottle> throttles_;
+ size_t index_;
+
+ GURL deferred_url_;
+ scoped_refptr<ResourceResponse> deferred_response_;
+
+ bool cancelled_by_resource_throttle_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_THROTTLING_RESOURCE_HANDLER_H_
diff --git a/chromium/content/browser/loader/transfer_navigation_resource_throttle.cc b/chromium/content/browser/loader/transfer_navigation_resource_throttle.cc
new file mode 100644
index 00000000000..8021bce49d5
--- /dev/null
+++ b/chromium/content/browser/loader/transfer_navigation_resource_throttle.cc
@@ -0,0 +1,93 @@
+// 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 "content/browser/loader/transfer_navigation_resource_throttle.h"
+
+#include "base/bind.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/referrer.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+namespace {
+
+void RequestTransferURLOnUIThread(int render_process_id,
+ int render_view_id,
+ const GURL& new_url,
+ const Referrer& referrer,
+ WindowOpenDisposition window_open_disposition,
+ int64 frame_id,
+ const GlobalRequestID& global_request_id) {
+ RenderViewHost* rvh =
+ RenderViewHost::FromID(render_process_id, render_view_id);
+ if (!rvh)
+ return;
+
+ RenderViewHostDelegate* delegate = rvh->GetDelegate();
+ if (!delegate)
+ return;
+
+ // We don't know whether the original request had |user_action| set to true.
+ // However, since we force the navigation to be in the current tab, it doesn't
+ // matter.
+ delegate->RequestTransferURL(
+ new_url, referrer, window_open_disposition,
+ frame_id, global_request_id, false, true);
+}
+
+} // namespace
+
+TransferNavigationResourceThrottle::TransferNavigationResourceThrottle(
+ net::URLRequest* request)
+ : request_(request) {
+}
+
+TransferNavigationResourceThrottle::~TransferNavigationResourceThrottle() {
+}
+
+void TransferNavigationResourceThrottle::WillRedirectRequest(
+ const GURL& new_url,
+ bool* defer) {
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
+
+ // If a toplevel request is redirecting across extension extents, we want to
+ // switch processes. We do this by deferring the redirect and resuming the
+ // request once the navigation controller properly assigns the right process
+ // to host the new URL.
+ // TODO(mpcomplete): handle for cases other than extensions (e.g. WebUI).
+ ResourceContext* resource_context = info->GetContext();
+ if (GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
+ resource_context, request_->url(), new_url)) {
+ int render_process_id, render_view_id;
+ if (info->GetAssociatedRenderView(&render_process_id, &render_view_id)) {
+ GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
+
+ ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id,
+ new_url);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&RequestTransferURLOnUIThread,
+ render_process_id,
+ render_view_id,
+ new_url,
+ Referrer(GURL(request_->referrer()), info->GetReferrerPolicy()),
+ CURRENT_TAB,
+ info->GetFrameID(),
+ global_id));
+
+ *defer = true;
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/transfer_navigation_resource_throttle.h b/chromium/content/browser/loader/transfer_navigation_resource_throttle.h
new file mode 100644
index 00000000000..6d08c2aaed1
--- /dev/null
+++ b/chromium/content/browser/loader/transfer_navigation_resource_throttle.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_LOADER_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_
+#define CONTENT_BROWSER_LOADER_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/public/browser/resource_throttle.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace content {
+
+// This ResourceThrottle checks whether a navigation redirect will cause a
+// renderer process swap. When that happens, we remember the request so
+// that we can transfer it to be handled by the new renderer. This fixes
+// http://crbug.com/79520
+class TransferNavigationResourceThrottle : public ResourceThrottle {
+ public:
+ explicit TransferNavigationResourceThrottle(net::URLRequest* request);
+ virtual ~TransferNavigationResourceThrottle();
+
+ // ResourceThrottle implementation:
+ virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE;
+
+ private:
+ net::URLRequest* request_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransferNavigationResourceThrottle);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_
diff --git a/chromium/content/browser/loader/upload_data_stream_builder.cc b/chromium/content/browser/loader/upload_data_stream_builder.cc
new file mode 100644
index 00000000000..1a7dfc33e09
--- /dev/null
+++ b/chromium/content/browser/loader/upload_data_stream_builder.cc
@@ -0,0 +1,146 @@
+// 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 "content/browser/loader/upload_data_stream_builder.h"
+
+#include "base/logging.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/fileapi/upload_file_system_file_element_reader.h"
+#include "webkit/common/resource_request_body.h"
+
+using webkit_blob::BlobData;
+using webkit_blob::BlobStorageController;
+using webkit_glue::ResourceRequestBody;
+
+namespace content {
+namespace {
+
+// A subclass of net::UploadBytesElementReader which owns ResourceRequestBody.
+class BytesElementReader : public net::UploadBytesElementReader {
+ public:
+ BytesElementReader(ResourceRequestBody* resource_request_body,
+ const ResourceRequestBody::Element& element)
+ : net::UploadBytesElementReader(element.bytes(), element.length()),
+ resource_request_body_(resource_request_body) {
+ DCHECK_EQ(ResourceRequestBody::Element::TYPE_BYTES, element.type());
+ }
+
+ virtual ~BytesElementReader() {}
+
+ private:
+ scoped_refptr<ResourceRequestBody> resource_request_body_;
+
+ DISALLOW_COPY_AND_ASSIGN(BytesElementReader);
+};
+
+// A subclass of net::UploadFileElementReader which owns ResourceRequestBody.
+// This class is necessary to ensure the BlobData and any attached shareable
+// files survive until upload completion.
+class FileElementReader : public net::UploadFileElementReader {
+ public:
+ FileElementReader(ResourceRequestBody* resource_request_body,
+ base::TaskRunner* task_runner,
+ const ResourceRequestBody::Element& element)
+ : net::UploadFileElementReader(task_runner,
+ element.path(),
+ element.offset(),
+ element.length(),
+ element.expected_modification_time()),
+ resource_request_body_(resource_request_body) {
+ DCHECK_EQ(ResourceRequestBody::Element::TYPE_FILE, element.type());
+ }
+
+ virtual ~FileElementReader() {}
+
+ private:
+ scoped_refptr<ResourceRequestBody> resource_request_body_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileElementReader);
+};
+
+void ResolveBlobReference(
+ ResourceRequestBody* body,
+ webkit_blob::BlobStorageController* blob_controller,
+ const GURL& blob_url,
+ std::vector<const ResourceRequestBody::Element*>* resolved_elements) {
+ DCHECK(blob_controller);
+ BlobData* blob_data = blob_controller->GetBlobDataFromUrl(blob_url);
+ DCHECK(blob_data);
+ if (!blob_data)
+ return;
+
+ // If there is no element in the referred blob data, just return.
+ if (blob_data->items().empty())
+ return;
+
+ // Ensure the blob and any attached shareable files survive until
+ // upload completion.
+ body->SetUserData(blob_data, new base::UserDataAdapter<BlobData>(blob_data));
+
+ // Append the elements in the referred blob data.
+ for (size_t i = 0; i < blob_data->items().size(); ++i) {
+ const BlobData::Item& item = blob_data->items().at(i);
+ DCHECK_NE(BlobData::Item::TYPE_BLOB, item.type());
+ resolved_elements->push_back(&item);
+ }
+}
+
+} // namespace
+
+scoped_ptr<net::UploadDataStream> UploadDataStreamBuilder::Build(
+ ResourceRequestBody* body,
+ BlobStorageController* blob_controller,
+ fileapi::FileSystemContext* file_system_context,
+ base::TaskRunner* file_task_runner) {
+ // Resolve all blob elements.
+ std::vector<const ResourceRequestBody::Element*> resolved_elements;
+ for (size_t i = 0; i < body->elements()->size(); ++i) {
+ const ResourceRequestBody::Element& element = (*body->elements())[i];
+ if (element.type() == ResourceRequestBody::Element::TYPE_BLOB) {
+ ResolveBlobReference(body, blob_controller, element.url(),
+ &resolved_elements);
+ } else {
+ // No need to resolve, just append the element.
+ resolved_elements.push_back(&element);
+ }
+ }
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ for (size_t i = 0; i < resolved_elements.size(); ++i) {
+ const ResourceRequestBody::Element& element = *resolved_elements[i];
+ switch (element.type()) {
+ case ResourceRequestBody::Element::TYPE_BYTES:
+ element_readers.push_back(new BytesElementReader(body, element));
+ break;
+ case ResourceRequestBody::Element::TYPE_FILE:
+ element_readers.push_back(
+ new FileElementReader(body, file_task_runner, element));
+ break;
+ case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
+ element_readers.push_back(
+ new fileapi::UploadFileSystemFileElementReader(
+ file_system_context,
+ element.url(),
+ element.offset(),
+ element.length(),
+ element.expected_modification_time()));
+ break;
+ case ResourceRequestBody::Element::TYPE_BLOB:
+ // Blob elements should be resolved beforehand.
+ NOTREACHED();
+ break;
+ case ResourceRequestBody::Element::TYPE_UNKNOWN:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ return make_scoped_ptr(
+ new net::UploadDataStream(&element_readers, body->identifier()));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/loader/upload_data_stream_builder.h b/chromium/content/browser/loader/upload_data_stream_builder.h
new file mode 100644
index 00000000000..b5115466565
--- /dev/null
+++ b/chromium/content/browser/loader/upload_data_stream_builder.h
@@ -0,0 +1,54 @@
+// 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 CONTENT_BROWSER_LOADER_UPLOAD_DATA_STREAM_BUILDER_H_
+#define CONTENT_BROWSER_LOADER_UPLOAD_DATA_STREAM_BUILDER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace fileapi {
+class FileSystemContext;
+}
+
+namespace net {
+class UploadDataStream;
+}
+
+namespace webkit_blob {
+class BlobStorageController;
+}
+
+namespace webkit_glue {
+class ResourceRequestBody;
+}
+
+namespace content {
+
+class CONTENT_EXPORT UploadDataStreamBuilder {
+ public:
+ // Creates a new UploadDataStream from this request body.
+ //
+ // This also resolves any blob references using the given |blob_controller|
+ // and binds those blob references to the ResourceRequestBody ensuring that
+ // the blob data remains valid for the lifetime of the ResourceRequestBody
+ // object.
+ //
+ // |file_system_context| is used to create a FileStreamReader for files with
+ // filesystem URLs. |file_task_runner| is used to perform file operations
+ // when the data gets uploaded.
+ static scoped_ptr<net::UploadDataStream> Build(
+ webkit_glue::ResourceRequestBody* body,
+ webkit_blob::BlobStorageController* blob_controller,
+ fileapi::FileSystemContext* file_system_context,
+ base::TaskRunner* file_task_runner);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_LOADER_UPLOAD_DATA_STREAM_BUILDER_H_
diff --git a/chromium/content/browser/loader/upload_data_stream_builder_unittest.cc b/chromium/content/browser/loader/upload_data_stream_builder_unittest.cc
new file mode 100644
index 00000000000..9972418a06d
--- /dev/null
+++ b/chromium/content/browser/loader/upload_data_stream_builder_unittest.cc
@@ -0,0 +1,266 @@
+// 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 "content/browser/loader/upload_data_stream_builder.h"
+
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/common/resource_request_body.h"
+
+using webkit_blob::BlobData;
+using webkit_blob::BlobStorageController;
+using webkit_glue::ResourceRequestBody;
+
+namespace content {
+namespace {
+
+bool AreElementsEqual(const net::UploadElementReader& reader,
+ const ResourceRequestBody::Element& element) {
+ switch(element.type()) {
+ case ResourceRequestBody::Element::TYPE_BYTES: {
+ const net::UploadBytesElementReader* bytes_reader =
+ reader.AsBytesReader();
+ return bytes_reader &&
+ element.length() == bytes_reader->length() &&
+ std::equal(element.bytes(), element.bytes() + element.length(),
+ bytes_reader->bytes());
+ }
+ case ResourceRequestBody::Element::TYPE_FILE: {
+ const net::UploadFileElementReader* file_reader = reader.AsFileReader();
+ return file_reader &&
+ file_reader->path() == element.path() &&
+ file_reader->range_offset() == element.offset() &&
+ file_reader->range_length() == element.length() &&
+ file_reader->expected_modification_time() ==
+ element.expected_modification_time();
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ return false;
+}
+
+} // namespace
+
+TEST(UploadDataStreamBuilderTest, CreateUploadDataStreamWithoutBlob) {
+ base::MessageLoop message_loop;
+ scoped_refptr<ResourceRequestBody> request_body = new ResourceRequestBody;
+
+ const char kData[] = "123";
+ const base::FilePath::StringType kFilePath = FILE_PATH_LITERAL("abc");
+ const uint64 kFileOffset = 10U;
+ const uint64 kFileLength = 100U;
+ const base::Time kFileTime = base::Time::FromDoubleT(999);
+ const int64 kIdentifier = 12345;
+
+ request_body->AppendBytes(kData, arraysize(kData) - 1);
+ request_body->AppendFileRange(base::FilePath(kFilePath),
+ kFileOffset, kFileLength, kFileTime);
+ request_body->set_identifier(kIdentifier);
+
+ scoped_ptr<net::UploadDataStream> upload(UploadDataStreamBuilder::Build(
+ request_body.get(), NULL, NULL, base::MessageLoopProxy::current().get()));
+
+ EXPECT_EQ(kIdentifier, upload->identifier());
+ ASSERT_EQ(request_body->elements()->size(), upload->element_readers().size());
+
+ const net::UploadBytesElementReader* r1 =
+ upload->element_readers()[0]->AsBytesReader();
+ ASSERT_TRUE(r1);
+ EXPECT_EQ(kData, std::string(r1->bytes(), r1->length()));
+
+ const net::UploadFileElementReader* r2 =
+ upload->element_readers()[1]->AsFileReader();
+ ASSERT_TRUE(r2);
+ EXPECT_EQ(kFilePath, r2->path().value());
+ EXPECT_EQ(kFileOffset, r2->range_offset());
+ EXPECT_EQ(kFileLength, r2->range_length());
+ EXPECT_EQ(kFileTime, r2->expected_modification_time());
+}
+
+TEST(UploadDataStreamBuilderTest, ResolveBlobAndCreateUploadDataStream) {
+ base::MessageLoop message_loop;
+ // Setup blob data for testing.
+ base::Time time1, time2;
+ base::Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", &time1);
+ base::Time::FromString("Mon, 14 Nov 1994, 11:30:49 GMT", &time2);
+
+ BlobStorageController blob_storage_controller;
+ scoped_refptr<BlobData> blob_data(new BlobData());
+
+ GURL blob_url0("blob://url_0");
+ blob_storage_controller.AddFinishedBlob(blob_url0, blob_data.get());
+
+ blob_data->AppendData("BlobData");
+ blob_data->AppendFile(
+ base::FilePath(FILE_PATH_LITERAL("BlobFile.txt")), 0, 20, time1);
+
+ GURL blob_url1("blob://url_1");
+ blob_storage_controller.AddFinishedBlob(blob_url1, blob_data.get());
+
+ GURL blob_url2("blob://url_2");
+ blob_storage_controller.CloneBlob(blob_url2, blob_url1);
+
+ GURL blob_url3("blob://url_3");
+ blob_storage_controller.CloneBlob(blob_url3, blob_url2);
+
+ // Setup upload data elements for comparison.
+ ResourceRequestBody::Element blob_element1, blob_element2;
+ blob_element1.SetToBytes(
+ blob_data->items().at(0).bytes() +
+ static_cast<int>(blob_data->items().at(0).offset()),
+ static_cast<int>(blob_data->items().at(0).length()));
+ blob_element2.SetToFilePathRange(
+ blob_data->items().at(1).path(),
+ blob_data->items().at(1).offset(),
+ blob_data->items().at(1).length(),
+ blob_data->items().at(1).expected_modification_time());
+
+ ResourceRequestBody::Element upload_element1, upload_element2;
+ upload_element1.SetToBytes("Hello", 5);
+ upload_element2.SetToFilePathRange(
+ base::FilePath(FILE_PATH_LITERAL("foo1.txt")), 0, 20, time2);
+
+ // Test no blob reference.
+ scoped_refptr<ResourceRequestBody> request_body(new ResourceRequestBody());
+ request_body->AppendBytes(upload_element1.bytes(), upload_element1.length());
+ request_body->AppendFileRange(upload_element2.path(),
+ upload_element2.offset(),
+ upload_element2.length(),
+ upload_element2.expected_modification_time());
+
+ scoped_ptr<net::UploadDataStream> upload(
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get()));
+
+ ASSERT_EQ(2U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], upload_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], upload_element2));
+
+ // Test having only one blob reference that refers to empty blob data.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBlob(blob_url0);
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(0U, upload->element_readers().size());
+
+ // Test having only one blob reference.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBlob(blob_url1);
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(2U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], blob_element2));
+
+ // Test having one blob reference at the beginning.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBlob(blob_url1);
+ request_body->AppendBytes(upload_element1.bytes(), upload_element1.length());
+ request_body->AppendFileRange(upload_element2.path(),
+ upload_element2.offset(),
+ upload_element2.length(),
+ upload_element2.expected_modification_time());
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(4U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], blob_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[2], upload_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[3], upload_element2));
+
+ // Test having one blob reference at the end.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBytes(upload_element1.bytes(), upload_element1.length());
+ request_body->AppendFileRange(upload_element2.path(),
+ upload_element2.offset(),
+ upload_element2.length(),
+ upload_element2.expected_modification_time());
+ request_body->AppendBlob(blob_url1);
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(4U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], upload_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], upload_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[2], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[3], blob_element2));
+
+ // Test having one blob reference in the middle.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBytes(upload_element1.bytes(), upload_element1.length());
+ request_body->AppendBlob(blob_url1);
+ request_body->AppendFileRange(upload_element2.path(),
+ upload_element2.offset(),
+ upload_element2.length(),
+ upload_element2.expected_modification_time());
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(4U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], upload_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[2], blob_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[3], upload_element2));
+
+ // Test having multiple blob references.
+ request_body = new ResourceRequestBody();
+ request_body->AppendBlob(blob_url1);
+ request_body->AppendBytes(upload_element1.bytes(), upload_element1.length());
+ request_body->AppendBlob(blob_url2);
+ request_body->AppendBlob(blob_url3);
+ request_body->AppendFileRange(upload_element2.path(),
+ upload_element2.offset(),
+ upload_element2.length(),
+ upload_element2.expected_modification_time());
+
+ upload =
+ UploadDataStreamBuilder::Build(request_body.get(),
+ &blob_storage_controller,
+ NULL,
+ base::MessageLoopProxy::current().get());
+ ASSERT_EQ(8U, upload->element_readers().size());
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[0], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[1], blob_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[2], upload_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[3], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[4], blob_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[5], blob_element1));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[6], blob_element2));
+ EXPECT_TRUE(AreElementsEqual(*upload->element_readers()[7], upload_element2));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/mach_broker_mac.h b/chromium/content/browser/mach_broker_mac.h
new file mode 100644
index 00000000000..51038bfa010
--- /dev/null
+++ b/chromium/content/browser/mach_broker_mac.h
@@ -0,0 +1,121 @@
+// 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 CONTENT_BROWSER_MACH_BROKER_MAC_H_
+#define CONTENT_BROWSER_MACH_BROKER_MAC_H_
+
+#include <map>
+#include <string>
+
+#include <mach/mach.h>
+
+#include "base/memory/singleton.h"
+#include "base/process/process_handle.h"
+#include "base/process/process_metrics.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/browser_child_process_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace content {
+
+// On OS X, the mach_port_t of a process is required to collect metrics about
+// the process. Running |task_for_pid()| is only allowed for privileged code.
+// However, a process has port rights to all its subprocesses, so let the
+// browser's child processes send their Mach port to the browser over IPC.
+// This way, the brower can at least collect metrics of its child processes,
+// which is what it's most interested in anyway.
+//
+// Mach ports can only be sent over Mach IPC, not over the |socketpair()| that
+// the regular IPC system uses. Hence, the child processes open a Mach
+// connection shortly after launching and ipc their mach data to the browser
+// process. This data is kept in a global |MachBroker| object.
+//
+// Since this data arrives over a separate channel, it is not available
+// immediately after a child process has been started.
+class CONTENT_EXPORT MachBroker : public base::ProcessMetrics::PortProvider,
+ public BrowserChildProcessObserver,
+ public NotificationObserver {
+ public:
+ // For use in child processes. This will send the task port of the current
+ // process over Mach IPC to the port registered by name (via this class) in
+ // the parent process. Returns true if the message was sent successfully
+ // and false if otherwise.
+ static bool ChildSendTaskPortToParent();
+
+ // Returns the global MachBroker.
+ static MachBroker* GetInstance();
+
+ // The lock that protects this MachBroker object. Clients MUST acquire and
+ // release this lock around calls to EnsureRunning(), PlaceholderForPid(),
+ // and FinalizePid().
+ base::Lock& GetLock();
+
+ // Performs any necessary setup that cannot happen in the constructor.
+ // Callers MUST acquire the lock given by GetLock() before calling this
+ // method (and release the lock afterwards).
+ void EnsureRunning();
+
+ // Adds a placeholder to the map for the given pid with MACH_PORT_NULL.
+ // Callers are expected to later update the port with FinalizePid(). Callers
+ // MUST acquire the lock given by GetLock() before calling this method (and
+ // release the lock afterwards).
+ void AddPlaceholderForPid(base::ProcessHandle pid);
+
+ // Implement |ProcessMetrics::PortProvider|.
+ virtual mach_port_t TaskForPid(base::ProcessHandle process) const OVERRIDE;
+
+ // Implement |BrowserChildProcessObserver|.
+ virtual void BrowserChildProcessHostDisconnected(
+ const ChildProcessData& data) OVERRIDE;
+ virtual void BrowserChildProcessCrashed(
+ const ChildProcessData& data) OVERRIDE;
+
+ // Implement |NotificationObserver|.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+ private:
+ friend class MachBrokerTest;
+ friend class MachListenerThreadDelegate;
+ friend struct DefaultSingletonTraits<MachBroker>;
+
+ MachBroker();
+ virtual ~MachBroker();
+
+ // Updates the mapping for |pid| to include the given |mach_info|. Does
+ // nothing if PlaceholderForPid() has not already been called for the given
+ // |pid|. Callers MUST acquire the lock given by GetLock() before calling
+ // this method (and release the lock afterwards).
+ void FinalizePid(base::ProcessHandle pid, mach_port_t task_port);
+
+ // Removes all mappings belonging to |pid| from the broker.
+ void InvalidatePid(base::ProcessHandle pid);
+
+ // Returns the Mach port name to use when sending or receiving messages.
+ // Does the Right Thing in the browser and in child processes.
+ static std::string GetMachPortName();
+ // Callback used to register notifications on the UI thread.
+ void RegisterNotifications();
+
+ // True if the listener thread has been started.
+ bool listener_thread_started_;
+
+ // Used to register for notifications received by NotificationObserver.
+ // Accessed only on the UI thread.
+ NotificationRegistrar registrar_;
+
+ // Stores mach info for every process in the broker.
+ typedef std::map<base::ProcessHandle, mach_port_t> MachMap;
+ MachMap mach_map_;
+
+ // Mutex that guards |mach_map_|.
+ mutable base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachBroker);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MACH_BROKER_MAC_H_
diff --git a/chromium/content/browser/mach_broker_mac.mm b/chromium/content/browser/mach_broker_mac.mm
new file mode 100644
index 00000000000..24a0e249813
--- /dev/null
+++ b/chromium/content/browser/mach_broker_mac.mm
@@ -0,0 +1,309 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/mach_broker_mac.h"
+
+#include <bsm/libbsm.h>
+#include <servers/bootstrap.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/common/content_switches.h"
+
+namespace content {
+
+namespace {
+
+// Prints a string representation of a Mach error code.
+std::string MachErrorCode(kern_return_t err) {
+ return base::StringPrintf("0x%x %s", err, mach_error_string(err));
+}
+
+// Mach message structure used in the child as a sending message.
+struct MachBroker_ChildSendMsg {
+ mach_msg_header_t header;
+ mach_msg_body_t body;
+ mach_msg_port_descriptor_t child_task_port;
+};
+
+// Complement to the ChildSendMsg, this is used in the parent for receiving
+// a message. Contains a message trailer with audit information.
+struct MachBroker_ParentRecvMsg : public MachBroker_ChildSendMsg {
+ mach_msg_audit_trailer_t trailer;
+};
+
+} // namespace
+
+class MachListenerThreadDelegate : public base::PlatformThread::Delegate {
+ public:
+ explicit MachListenerThreadDelegate(MachBroker* broker)
+ : broker_(broker),
+ server_port_(MACH_PORT_NULL) {
+ DCHECK(broker_);
+ }
+
+ bool Init() {
+ DCHECK(server_port_ == MACH_PORT_NULL);
+
+ mach_port_t port;
+ kern_return_t kr = mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_RECEIVE,
+ &port);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "Failed to allocate MachBroker server port: "
+ << MachErrorCode(kr);
+ return false;
+ }
+
+ // Allocate a send right for the server port.
+ kr = mach_port_insert_right(
+ mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "Failed to insert send right for MachBroker server port: "
+ << MachErrorCode(kr);
+ return false;
+ }
+
+ server_port_.reset(port);
+
+ // Register the port with the bootstrap server. Because bootstrap_register
+ // is deprecated, this has to be wraped in an ObjC interface.
+ NSPort* ns_port = [NSMachPort portWithMachPort:port
+ options:NSMachPortDeallocateNone];
+ NSString* name = base::SysUTF8ToNSString(broker_->GetMachPortName());
+ return [[NSMachBootstrapServer sharedInstance] registerPort:ns_port
+ name:name];
+ }
+
+ // Implement |PlatformThread::Delegate|.
+ virtual void ThreadMain() OVERRIDE {
+ MachBroker_ParentRecvMsg msg;
+ bzero(&msg, sizeof(msg));
+ msg.header.msgh_size = sizeof(msg);
+ msg.header.msgh_local_port = server_port_.get();
+
+ kern_return_t kr;
+ do {
+ // Use the kernel audit information to make sure this message is from
+ // a task that this process spawned. The kernel audit token contains the
+ // unspoofable pid of the task that sent the message.
+ mach_msg_option_t options = MACH_RCV_MSG |
+ MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) |
+ MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
+
+ kr = mach_msg(&msg.header, options, 0, sizeof(msg), server_port_,
+ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ if (kr == KERN_SUCCESS) {
+ // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid().
+ pid_t child_pid;
+ audit_token_to_au32(msg.trailer.msgh_audit,
+ NULL, NULL, NULL, NULL, NULL, &child_pid, NULL, NULL);
+
+ mach_port_t child_task_port = msg.child_task_port.name;
+
+ // Take the lock and update the broker information.
+ base::AutoLock lock(broker_->GetLock());
+ broker_->FinalizePid(child_pid, child_task_port);
+ }
+ } while (kr == KERN_SUCCESS);
+
+ LOG(ERROR) << "MachBroker thread exiting; mach_msg() likely failed: "
+ << MachErrorCode(kr);
+ }
+
+ private:
+ // The MachBroker to use when new child task rights are received. Can be
+ // NULL.
+ MachBroker* broker_; // weak
+
+ base::mac::ScopedMachPort server_port_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachListenerThreadDelegate);
+};
+
+bool MachBroker::ChildSendTaskPortToParent() {
+ // Look up the named MachBroker port that's been registered with the
+ // bootstrap server.
+ mach_port_t bootstrap_port;
+ kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "Failed to look up bootstrap port: " << MachErrorCode(kr);
+ return false;
+ }
+
+ mach_port_t parent_port;
+ kr = bootstrap_look_up(bootstrap_port,
+ const_cast<char*>(GetMachPortName().c_str()), &parent_port);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "Failed to look up named parent port: " << MachErrorCode(kr);
+ return false;
+ }
+
+ // Create the check in message. This will copy a send right on this process'
+ // (the child's) task port and send it to the parent.
+ MachBroker_ChildSendMsg msg;
+ bzero(&msg, sizeof(msg));
+ msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) |
+ MACH_MSGH_BITS_COMPLEX;
+ msg.header.msgh_remote_port = parent_port;
+ msg.header.msgh_size = sizeof(msg);
+ msg.body.msgh_descriptor_count = 1;
+ msg.child_task_port.name = mach_task_self();
+ msg.child_task_port.disposition = MACH_MSG_TYPE_PORT_SEND;
+ msg.child_task_port.type = MACH_MSG_PORT_DESCRIPTOR;
+
+ kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(msg),
+ 0, MACH_PORT_NULL, 100 /*milliseconds*/, MACH_PORT_NULL);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "Failed to send task port to parent: " << MachErrorCode(kr);
+ return false;
+ }
+
+ return true;
+}
+
+MachBroker* MachBroker::GetInstance() {
+ return Singleton<MachBroker, LeakySingletonTraits<MachBroker> >::get();
+}
+
+base::Lock& MachBroker::GetLock() {
+ return lock_;
+}
+
+void MachBroker::EnsureRunning() {
+ lock_.AssertAcquired();
+
+ if (!listener_thread_started_) {
+ listener_thread_started_ = true;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MachBroker::RegisterNotifications, base::Unretained(this)));
+
+ // Intentional leak. This thread is never joined or reaped.
+ MachListenerThreadDelegate* thread = new MachListenerThreadDelegate(this);
+ if (thread->Init()) {
+ base::PlatformThread::CreateNonJoinable(0, thread);
+ } else {
+ LOG(ERROR) << "Failed to initialize the MachListenerThreadDelegate";
+ }
+ }
+}
+
+void MachBroker::AddPlaceholderForPid(base::ProcessHandle pid) {
+ lock_.AssertAcquired();
+
+ DCHECK_EQ(0u, mach_map_.count(pid));
+ mach_map_[pid] = MACH_PORT_NULL;
+}
+
+mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const {
+ base::AutoLock lock(lock_);
+ MachBroker::MachMap::const_iterator it = mach_map_.find(pid);
+ if (it == mach_map_.end())
+ return MACH_PORT_NULL;
+ return it->second;
+}
+
+void MachBroker::BrowserChildProcessHostDisconnected(
+ const ChildProcessData& data) {
+ InvalidatePid(data.handle);
+}
+
+void MachBroker::BrowserChildProcessCrashed(const ChildProcessData& data) {
+ InvalidatePid(data.handle);
+}
+
+void MachBroker::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ // TODO(rohitrao): These notifications do not always carry the proper PIDs,
+ // especially when the renderer is already gone or has crashed. Find a better
+ // way to listen for child process deaths. http://crbug.com/55734
+ base::ProcessHandle handle = 0;
+ switch (type) {
+ case NOTIFICATION_RENDERER_PROCESS_CLOSED:
+ handle = Details<RenderProcessHost::RendererClosedDetails>(
+ details)->handle;
+ break;
+ case NOTIFICATION_RENDERER_PROCESS_TERMINATED:
+ handle = Source<RenderProcessHost>(source)->GetHandle();
+ break;
+ default:
+ NOTREACHED() << "Unexpected notification";
+ break;
+ }
+ InvalidatePid(handle);
+}
+
+MachBroker::MachBroker() : listener_thread_started_(false) {
+}
+
+MachBroker::~MachBroker() {}
+
+void MachBroker::FinalizePid(base::ProcessHandle pid,
+ mach_port_t task_port) {
+ lock_.AssertAcquired();
+
+ MachMap::iterator it = mach_map_.find(pid);
+ if (it == mach_map_.end()) {
+ // Do nothing for unknown pids.
+ LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!";
+ return;
+ }
+
+ DCHECK(it->second == MACH_PORT_NULL);
+ if (it->second == MACH_PORT_NULL)
+ it->second = task_port;
+}
+
+void MachBroker::InvalidatePid(base::ProcessHandle pid) {
+ base::AutoLock lock(lock_);
+ MachBroker::MachMap::iterator it = mach_map_.find(pid);
+ if (it == mach_map_.end())
+ return;
+
+ kern_return_t kr = mach_port_deallocate(mach_task_self(),
+ it->second);
+ LOG_IF(WARNING, kr != KERN_SUCCESS)
+ << "Failed to mach_port_deallocate mach task " << it->second
+ << ", error " << MachErrorCode(kr);
+ mach_map_.erase(it);
+}
+
+// static
+std::string MachBroker::GetMachPortName() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const bool is_child = command_line->HasSwitch(switches::kProcessType);
+
+ // In non-browser (child) processes, use the parent's pid.
+ const pid_t pid = is_child ? getppid() : getpid();
+ return base::StringPrintf("%s.rohitfork.%d", base::mac::BaseBundleID(), pid);
+}
+
+void MachBroker::RegisterNotifications() {
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+
+ // No corresponding StopObservingBrowserChildProcesses,
+ // we leak this singleton.
+ BrowserChildProcessObserver::Add(this);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/mach_broker_mac_unittest.cc b/chromium/content/browser/mach_broker_mac_unittest.cc
new file mode 100644
index 00000000000..a7eca4fd2f4
--- /dev/null
+++ b/chromium/content/browser/mach_broker_mac_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2009 The Chromium 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 "content/browser/mach_broker_mac.h"
+
+#include "base/synchronization/lock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class MachBrokerTest : public testing::Test {
+ public:
+ // Helper function to acquire/release locks and call |PlaceholderForPid()|.
+ void AddPlaceholderForPid(base::ProcessHandle pid) {
+ base::AutoLock lock(broker_.GetLock());
+ broker_.AddPlaceholderForPid(pid);
+ }
+
+ void InvalidatePid(base::ProcessHandle pid) {
+ broker_.InvalidatePid(pid);
+ }
+
+ // Helper function to acquire/release locks and call |FinalizePid()|.
+ void FinalizePid(base::ProcessHandle pid,
+ mach_port_t task_port) {
+ base::AutoLock lock(broker_.GetLock());
+ broker_.FinalizePid(pid, task_port);
+ }
+
+ protected:
+ MachBroker broker_;
+};
+
+TEST_F(MachBrokerTest, Locks) {
+ // Acquire and release the locks. Nothing bad should happen.
+ base::AutoLock lock(broker_.GetLock());
+}
+
+TEST_F(MachBrokerTest, AddPlaceholderAndFinalize) {
+ // Add a placeholder for PID 1.
+ AddPlaceholderForPid(1);
+ EXPECT_EQ(0u, broker_.TaskForPid(1));
+
+ // Finalize PID 1.
+ FinalizePid(1, 100u);
+ EXPECT_EQ(100u, broker_.TaskForPid(1));
+
+ // Should be no entry for PID 2.
+ EXPECT_EQ(0u, broker_.TaskForPid(2));
+}
+
+TEST_F(MachBrokerTest, Invalidate) {
+ AddPlaceholderForPid(1);
+ FinalizePid(1, 100u);
+
+ EXPECT_EQ(100u, broker_.TaskForPid(1));
+ InvalidatePid(1u);
+ EXPECT_EQ(0u, broker_.TaskForPid(1));
+}
+
+TEST_F(MachBrokerTest, FinalizeUnknownPid) {
+ // Finalizing an entry for an unknown pid should not add it to the map.
+ FinalizePid(1u, 100u);
+ EXPECT_EQ(0u, broker_.TaskForPid(1u));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/OWNERS b/chromium/content/browser/media/OWNERS
new file mode 100644
index 00000000000..d132d0e6061
--- /dev/null
+++ b/chromium/content/browser/media/OWNERS
@@ -0,0 +1,11 @@
+acolwell@chromium.org
+dalecurtis@chromium.org
+ddorwin@chromium.org
+fischman@chromium.org
+scherkus@chromium.org
+shadi@chromium.org
+tommi@chromium.org
+vrk@chromium.org
+wjia@chromium.org
+xhwang@chromium.org
+xians@chromium.org
diff --git a/chromium/content/browser/media/encrypted_media_browsertest.cc b/chromium/content/browser/media/encrypted_media_browsertest.cc
new file mode 100644
index 00000000000..ce5dc4e6ba0
--- /dev/null
+++ b/chromium/content/browser/media/encrypted_media_browsertest.cc
@@ -0,0 +1,310 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "content/browser/media/media_browsertest.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+
+#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
+
+#if defined(WIDEVINE_CDM_AVAILABLE) && defined(OS_LINUX)
+#include <gnu/libc-version.h>
+#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(OS_LINUX)
+
+#if defined(ENABLE_PEPPER_CDMS)
+// Platform-specific filename relative to the chrome executable.
+static const char kClearKeyCdmAdapterFileName[] =
+#if defined(OS_MACOSX)
+ "clearkeycdmadapter.plugin";
+#elif defined(OS_WIN)
+ "clearkeycdmadapter.dll";
+#elif defined(OS_POSIX)
+ "libclearkeycdmadapter.so";
+#endif
+#endif // defined(ENABLE_PEPPER_CDMS)
+
+// Available key systems.
+static const char kClearKeyKeySystem[] = "webkit-org.w3.clearkey";
+static const char kExternalClearKeyKeySystem[] =
+ "org.chromium.externalclearkey";
+
+// Supported media types.
+static const char kWebMAudioOnly[] = "audio/webm; codecs=\"vorbis\"";
+static const char kWebMVideoOnly[] = "video/webm; codecs=\"vp8\"";
+static const char kWebMAudioVideo[] = "video/webm; codecs=\"vorbis, vp8\"";
+static const char kMP4AudioOnly[] = "audio/mp4; codecs=\"mp4a.40.2\"";
+static const char kMP4VideoOnly[] = "video/mp4; codecs=\"avc1.4D4041\"";
+
+// Common test expectations.
+static const char kKeyError[] = "KEYERROR";
+
+// The type of video src used to load media.
+enum SrcType {
+ SRC,
+ MSE
+};
+
+namespace content {
+
+// Tests encrypted media playback with a combination of parameters:
+// - char*: Key system name.
+// - bool: True to load media using MSE, otherwise use src.
+class EncryptedMediaTest : public content::MediaBrowserTest,
+ public testing::WithParamInterface<std::tr1::tuple<const char*, SrcType> > {
+ public:
+ void TestSimplePlayback(const char* encrypted_media, const char* media_type,
+ const std::tr1::tuple<const char*, SrcType> test_params,
+ const char* expectation) {
+ const char* key_system = std::tr1::get<0>(test_params);
+ SrcType src_type = std::tr1::get<1>(test_params);
+ RunEncryptedMediaTest("encrypted_media_player.html", encrypted_media,
+ media_type, key_system, src_type, expectation);
+ }
+
+ void TestMSESimplePlayback(const char* encrypted_media,
+ const char* media_type, const char* key_system,
+ const char* expectation) {
+ RunEncryptedMediaTest("encrypted_media_player.html", encrypted_media,
+ media_type, key_system, MSE, expectation);
+ }
+
+ void TestFrameSizeChange(
+ const std::tr1::tuple<const char*, SrcType> test_params,
+ const char* expectation) {
+ const char* key_system = std::tr1::get<0>(test_params);
+ SrcType src_type = std::tr1::get<1>(test_params);
+ RunEncryptedMediaTest("encrypted_frame_size_change.html",
+ "frame_size_change-av-enc-v.webm", kWebMAudioVideo,
+ key_system, src_type, expectation);
+ }
+
+ void TestConfigChange(const char* key_system, const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("keysystem", key_system));
+ query_params.push_back(std::make_pair("runencrypted", "1"));
+ RunMediaTestPage("mse_config_change.html", &query_params, expectation,
+ true);
+ }
+
+ void RunEncryptedMediaTest(const char* html_page, const char* media_file,
+ const char* media_type, const char* key_system,
+ SrcType src_type, const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("mediafile", media_file));
+ query_params.push_back(std::make_pair("mediatype", media_type));
+ query_params.push_back(std::make_pair("keysystem", key_system));
+ if (src_type == MSE)
+ query_params.push_back(std::make_pair("usemse", "1"));
+ RunMediaTestPage(html_page, &query_params, expectation, true);
+ }
+
+ protected:
+#if defined(ENABLE_PEPPER_CDMS)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ RegisterPepperCdm(command_line, kClearKeyCdmAdapterFileName,
+ kExternalClearKeyKeySystem);
+ }
+
+ virtual void RegisterPepperCdm(CommandLine* command_line,
+ const std::string& adapter_name,
+ const std::string& key_system) {
+ // Append the switch to register the Clear Key CDM Adapter.
+ base::FilePath plugin_dir;
+ EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &plugin_dir));
+ base::FilePath plugin_lib = plugin_dir.AppendASCII(adapter_name);
+ EXPECT_TRUE(base::PathExists(plugin_lib));
+ base::FilePath::StringType pepper_plugin = plugin_lib.value();
+ pepper_plugin.append(FILE_PATH_LITERAL("#CDM#0.1.0.0;"));
+#if defined(OS_WIN)
+ pepper_plugin.append(ASCIIToWide(GetPepperType(key_system)));
+#else
+ pepper_plugin.append(GetPepperType(key_system));
+#endif
+ command_line->AppendSwitchNative(switches::kRegisterPepperPlugins,
+ pepper_plugin);
+ }
+
+ // Adapted from key_systems.cc.
+ std::string GetPepperType(const std::string& key_system) {
+ if (key_system == kExternalClearKeyKeySystem)
+ return "application/x-ppapi-clearkey-cdm";
+#if defined(WIDEVINE_CDM_AVAILABLE)
+ if (key_system == kWidevineKeySystem)
+ return "application/x-ppapi-widevine-cdm";
+#endif // WIDEVINE_CDM_AVAILABLE
+
+ NOTREACHED();
+ return "";
+ }
+#endif // defined(ENABLE_PEPPER_CDMS)
+};
+
+#if defined(WIDEVINE_CDM_AVAILABLE)
+class WVEncryptedMediaTest : public EncryptedMediaTest {
+ public:
+ // Tests that the following happen after trying to play encrypted media:
+ // - webkitneedkey event is fired.
+ // - webkitGenerateKeyRequest() does not fail.
+ // - webkitkeymessage is fired
+ // - webkitAddKey() generates a WebKitKeyError since no real WV key is added.
+ void TestMSESimplePlayback(const char* encrypted_media,
+ const char* media_type, const char* key_system) {
+ // TODO(shadi): Remove after bots upgrade to precise.
+ // Don't run on lucid bots since the CDM is not compatible (glibc < 2.14)
+#if defined(OS_LINUX)
+ if (strcmp(gnu_get_libc_version(), "2.11.1") == 0) {
+ LOG(INFO) << "Skipping test; not supported on glibc version: "
+ << gnu_get_libc_version();
+ return;
+ }
+#endif // defined(OS_LINUX)
+ EncryptedMediaTest::TestMSESimplePlayback(encrypted_media, media_type,
+ key_system, kKeyError);
+ bool receivedKeyMessage = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(video.receivedKeyMessage);",
+ &receivedKeyMessage));
+ ASSERT_TRUE(receivedKeyMessage);
+ }
+
+ protected:
+#if defined(ENABLE_PEPPER_CDMS)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ RegisterPepperCdm(command_line, kWidevineCdmAdapterFileName,
+ kWidevineKeySystem);
+ }
+#endif // defined(ENABLE_PEPPER_CDMS)
+};
+#endif // defined(WIDEVINE_CDM_AVAILABLE)
+
+INSTANTIATE_TEST_CASE_P(ClearKey, EncryptedMediaTest,
+ ::testing::Combine(
+ ::testing::Values(kClearKeyKeySystem), ::testing::Values(SRC, MSE)));
+
+// External Clear Key is currently only used on platforms that use Pepper CDMs.
+#if defined(ENABLE_PEPPER_CDMS)
+INSTANTIATE_TEST_CASE_P(ExternalClearKey, EncryptedMediaTest,
+ ::testing::Combine(
+ ::testing::Values(kExternalClearKeyKeySystem),
+ ::testing::Values(SRC, MSE)));
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, ConfigChangeVideo_ExternalClearKey) {
+ TestConfigChange(kExternalClearKeyKeySystem, kEnded);
+}
+#endif // defined(ENABLE_PEPPER_CDMS)
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, ConfigChangeVideo_ClearKey) {
+ TestConfigChange(kClearKeyKeySystem, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(EncryptedMediaTest, InvalidKeySystem) {
+ TestMSESimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo,
+ "com.example.invalid",
+ "GENERATE_KEY_REQUEST_EXCEPTION");
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioOnly_WebM) {
+ TestSimplePlayback("bear-a-enc_a.webm", kWebMAudioOnly, GetParam(), kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioClearVideo_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_a.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoAudio_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_WebM) {
+ TestSimplePlayback("bear-320x240-v-enc_v.webm", kWebMVideoOnly, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoClearAudio_WebM) {
+ TestSimplePlayback("bear-320x240-av-enc_v.webm", kWebMAudioVideo, GetParam(),
+ kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, FrameChangeVideo) {
+ // Times out on Windows XP. http://crbug.com/171937
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return;
+#endif
+ TestFrameSizeChange(GetParam(), kEnded);
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_MP4) {
+ std::tr1::tuple<const char*, SrcType> test_params = GetParam();
+ // MP4 without MSE is not support yet, http://crbug.com/170793.
+ if (std::tr1::get<1>(test_params) != MSE) {
+ LOG(INFO) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
+ return;
+ }
+ TestMSESimplePlayback("bear-640x360-v_frag-cenc.mp4", kMP4VideoOnly,
+ std::tr1::get<0>(test_params), kEnded);
+}
+
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_AudioOnly_MP4) {
+ std::tr1::tuple<const char*, SrcType> test_params = GetParam();
+ // MP4 without MSE is not support yet, http://crbug.com/170793.
+ if (std::tr1::get<1>(test_params) != MSE) {
+ LOG(INFO) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
+ return;
+ }
+ TestMSESimplePlayback("bear-640x360-a_frag-cenc.mp4", kMP4AudioOnly,
+ std::tr1::get<0>(test_params), kEnded);
+}
+#endif
+
+// Run only when WV CDM is available.
+#if defined(WIDEVINE_CDM_AVAILABLE)
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioOnly_WebM) {
+ TestMSESimplePlayback("bear-a-enc_a.webm", kWebMAudioOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioClearVideo_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_a.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoAudio_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_av.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoOnly_WebM) {
+ TestMSESimplePlayback("bear-320x240-v-enc_v.webm", kWebMVideoOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoClearAudio_WebM) {
+ TestMSESimplePlayback("bear-320x240-av-enc_v.webm", kWebMAudioVideo,
+ kWidevineKeySystem);
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_VideoOnly_MP4) {
+ TestMSESimplePlayback("bear-640x360-v_frag-cenc.mp4", kMP4VideoOnly,
+ kWidevineKeySystem);
+}
+
+IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, Playback_AudioOnly_MP4) {
+ TestMSESimplePlayback("bear-640x360-a_frag-cenc.mp4", kMP4AudioOnly,
+ kWidevineKeySystem);
+}
+#endif // defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+#endif // defined(WIDEVINE_CDM_AVAILABLE)
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_browsertest.cc b/chromium/content/browser/media/media_browsertest.cc
new file mode 100644
index 00000000000..1d830354c9e
--- /dev/null
+++ b/chromium/content/browser/media/media_browsertest.cc
@@ -0,0 +1,245 @@
+// Copyright (c) 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 "content/browser/media/media_browsertest.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test_utils.h"
+
+// TODO(wolenetz): Fix Media.YUV* tests on MSVS 2012 x64. crbug.com/180074
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) && _MSC_VER == 1700
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+namespace content {
+
+// Common test results.
+const char MediaBrowserTest::kEnded[] = "ENDED";
+const char MediaBrowserTest::kError[] = "ERROR";
+const char MediaBrowserTest::kFailed[] = "FAILED";
+
+void MediaBrowserTest::SetUp() {
+ // TODO(danakj): The GPU Video Decoder needs real GL bindings.
+ // crbug.com/269087
+ UseRealGLBindings();
+
+ ContentBrowserTest::SetUp();
+}
+
+void MediaBrowserTest::RunMediaTestPage(
+ const char* html_page, std::vector<StringPair>* query_params,
+ const char* expected, bool http) {
+ GURL gurl;
+ std::string query = "";
+ if (query_params != NULL && !query_params->empty()) {
+ std::vector<StringPair>::const_iterator itr = query_params->begin();
+ query = base::StringPrintf("%s=%s", itr->first, itr->second);
+ ++itr;
+ for (; itr != query_params->end(); ++itr) {
+ query.append(base::StringPrintf("&%s=%s", itr->first, itr->second));
+ }
+ }
+ if (http) {
+ ASSERT_TRUE(test_server()->Start());
+ gurl = test_server()->GetURL(
+ base::StringPrintf("files/media/%s?%s", html_page, query.c_str()));
+ } else {
+ base::FilePath test_file_path = GetTestFilePath("media", html_page);
+ gurl = GetFileUrlWithQuery(test_file_path, query);
+ }
+ RunTest(gurl, expected);
+}
+
+void MediaBrowserTest::RunTest(const GURL& gurl, const char* expected) {
+ const string16 kExpected = ASCIIToUTF16(expected);
+ DVLOG(1) << "Running test URL: " << gurl;
+ TitleWatcher title_watcher(shell()->web_contents(), kExpected);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kEnded));
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kError));
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16(kFailed));
+ NavigateToURL(shell(), gurl);
+
+ string16 final_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(kExpected, final_title);
+}
+
+// Tests playback and seeking of an audio or video file over file or http based
+// on a test parameter. Test starts with playback, then, after X seconds or the
+// ended event fires, seeks near end of file; see player.html for details. The
+// test completes when either the last 'ended' or an 'error' event fires.
+class MediaTest : public testing::WithParamInterface<bool>,
+ public MediaBrowserTest {
+ public:
+ // Play specified audio over http:// or file:// depending on |http| setting.
+ void PlayAudio(const char* media_file, bool http) {
+ PlayMedia("audio", media_file, http);
+ }
+
+ // Play specified video over http:// or file:// depending on |http| setting.
+ void PlayVideo(const char* media_file, bool http) {
+ PlayMedia("video", media_file, http);
+ }
+
+ // Run specified color format test with the expected result.
+ void RunColorFormatTest(const char* media_file, const char* expected) {
+ base::FilePath test_file_path = GetTestFilePath("media", "blackwhite.html");
+ RunTest(GetFileUrlWithQuery(test_file_path, media_file), expected);
+ }
+
+ void PlayMedia(const char* tag, const char* media_file, bool http) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair(tag, media_file));
+ RunMediaTestPage("player.html", &query_params, kEnded, http);
+ }
+};
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearTheora) {
+ PlayVideo("bear.ogv", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentTheora) {
+ PlayVideo("bear_silent.ogv", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWebm) {
+ PlayVideo("bear.webm", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentWebm) {
+ PlayVideo("bear_silent.webm", GetParam());
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMp4) {
+ PlayVideo("bear.mp4", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentMp4) {
+ PlayVideo("bear_silent.mp4", GetParam());
+}
+
+// While we support the big endian (be) PCM codecs on Chromium, Quicktime seems
+// to be the only creator of this format and only for .mov files.
+// TODO(dalecurtis/ihf): Find or create some .wav test cases for "be" format.
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMovPcmS16be) {
+ PlayVideo("bear_pcm_s16be.mov", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearMovPcmS24be) {
+ PlayVideo("bear_pcm_s24be.mov", GetParam());
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Mpeg4) {
+ PlayVideo("bear_mpeg4_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Mpeg4Asp) {
+ PlayVideo("bear_mpeg4asp_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearAviMp3Divx) {
+ PlayVideo("bear_divx_mp3.avi", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear3gpAacH264) {
+ PlayVideo("bear_h264_aac.3gp", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear3gpAmrnbMpeg4) {
+ PlayVideo("bear_mpeg4_amrnb.3gp", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavGsmms) {
+ PlayAudio("bear_gsm_ms.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavMulaw) {
+ PlayAudio("bear_mulaw.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearFlac) {
+ PlayAudio("bear.flac", GetParam());
+}
+#endif
+#endif
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm) {
+ PlayAudio("bear_pcm.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm3kHz) {
+ PlayAudio("bear_3kHz.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearWavPcm192kHz) {
+ PlayAudio("bear_192kHz.wav", GetParam());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaTest, VideoTulipWebm) {
+ PlayVideo("tulip2.webm", GetParam());
+}
+
+// Covers tear-down when navigating away as opposed to browser exiting.
+IN_PROC_BROWSER_TEST_F(MediaTest, Navigate) {
+ PlayVideo("bear.ogv", false);
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+INSTANTIATE_TEST_CASE_P(File, MediaTest, ::testing::Values(false));
+INSTANTIATE_TEST_CASE_P(Http, MediaTest, ::testing::Values(true));
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pTheora)) {
+ RunColorFormatTest("yuv420p.ogv", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv422pTheora)) {
+ RunColorFormatTest("yuv422p.ogv", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv444pTheora)) {
+ // TODO(scherkus): Support YUV444 http://crbug.com/104711
+ RunColorFormatTest("yuv424p.ogv", "ERROR");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pVp8)) {
+ RunColorFormatTest("yuv420p.webm", "ENDED");
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv420pH264)) {
+ RunColorFormatTest("yuv420p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuvj420pH264)) {
+ RunColorFormatTest("yuvj420p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv422pH264)) {
+ RunColorFormatTest("yuv422p.mp4", "ENDED");
+}
+
+IN_PROC_BROWSER_TEST_F(MediaTest, MAYBE(Yuv444pH264)) {
+ // TODO(scherkus): Support YUV444 http://crbug.com/104711
+ RunColorFormatTest("yuv444p.mp4", "ERROR");
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(MediaTest, Yuv420pMpeg4) {
+ RunColorFormatTest("yuv420p.avi", "ENDED");
+}
+#endif
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_browsertest.h b/chromium/content/browser/media/media_browsertest.h
new file mode 100644
index 00000000000..013283b1ed6
--- /dev/null
+++ b/chromium/content/browser/media/media_browsertest.h
@@ -0,0 +1,35 @@
+// 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 "content/test/content_browser_test.h"
+
+namespace content {
+
+// Class used to automate running media related browser tests. The functions
+// assume that media files are located under files/media/ folder known to
+// the test http server.
+class MediaBrowserTest : public ContentBrowserTest {
+ public:
+ static const char kEnded[];
+ static const char kError[];
+ static const char kFailed[];
+
+ typedef std::pair<const char*, const char*> StringPair;
+
+ virtual void SetUp() OVERRIDE;
+
+ // Runs a html page with a list of URL query parameters.
+ // If http is true, the test starts a local http test server to load the test
+ // page, otherwise a local file URL is loaded inside the content shell.
+ // It uses RunTest() to check for expected test output.
+ void RunMediaTestPage(const char* html_page,
+ std::vector<StringPair>* query_params,
+ const char* expected, bool http);
+
+ // Opens a URL and waits for the document title to match either one of the
+ // default strings or the expected string.
+ void RunTest(const GURL& gurl, const char* expected);
+};
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals.cc b/chromium/content/browser/media/media_internals.cc
new file mode 100644
index 00000000000..273eeaeaf9f
--- /dev/null
+++ b/chromium/content/browser/media/media_internals.cc
@@ -0,0 +1,136 @@
+// 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 "content/browser/media/media_internals.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_ui.h"
+#include "media/base/media_log.h"
+#include "media/base/media_log_event.h"
+
+namespace content {
+
+MediaInternals* MediaInternals::GetInstance() {
+ return Singleton<MediaInternals>::get();
+}
+
+MediaInternals::~MediaInternals() {}
+
+void MediaInternals::OnDeleteAudioStream(void* host, int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ std::string stream = base::StringPrintf("audio_streams.%p:%d",
+ host, stream_id);
+ DeleteItem(stream);
+}
+
+void MediaInternals::OnSetAudioStreamPlaying(
+ void* host, int stream_id, bool playing) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "playing", new base::FundamentalValue(playing));
+}
+
+void MediaInternals::OnSetAudioStreamStatus(
+ void* host, int stream_id, const std::string& status) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "status", new base::StringValue(status));
+}
+
+void MediaInternals::OnSetAudioStreamVolume(
+ void* host, int stream_id, double volume) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ UpdateAudioStream(host, stream_id,
+ "volume", new base::FundamentalValue(volume));
+}
+
+void MediaInternals::OnMediaEvents(
+ int render_process_id, const std::vector<media::MediaLogEvent>& events) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Notify observers that |event| has occured.
+ for (std::vector<media::MediaLogEvent>::const_iterator event = events.begin();
+ event != events.end(); ++event) {
+ base::DictionaryValue dict;
+ dict.SetInteger("renderer", render_process_id);
+ dict.SetInteger("player", event->id);
+ dict.SetString("type", media::MediaLog::EventTypeToString(event->type));
+
+ int64 ticks = event->time.ToInternalValue();
+ double ticks_millis =
+ ticks / static_cast<double>(base::Time::kMicrosecondsPerMillisecond);
+
+ dict.SetDouble("ticksMillis", ticks_millis);
+ dict.Set("params", event->params.DeepCopy());
+ SendUpdate("media.onMediaEvent", &dict);
+ }
+}
+
+void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
+ update_callbacks_.push_back(callback);
+}
+
+void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
+ for (size_t i = 0; i < update_callbacks_.size(); ++i) {
+ if (update_callbacks_[i].Equals(callback)) {
+ update_callbacks_.erase(update_callbacks_.begin() + i);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void MediaInternals::SendEverything() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ SendUpdate("media.onReceiveEverything", &data_);
+}
+
+MediaInternals::MediaInternals() {
+}
+
+void MediaInternals::UpdateAudioStream(void* host,
+ int stream_id,
+ const std::string& property,
+ base::Value* value) {
+ std::string stream = base::StringPrintf("audio_streams.%p:%d",
+ host, stream_id);
+ UpdateItem("media.addAudioStream", stream, property, value);
+}
+
+void MediaInternals::DeleteItem(const std::string& item) {
+ data_.Remove(item, NULL);
+ scoped_ptr<base::Value> value(new base::StringValue(item));
+ SendUpdate("media.onItemDeleted", value.get());
+}
+
+void MediaInternals::UpdateItem(
+ const std::string& update_fn, const std::string& id,
+ const std::string& property, base::Value* value) {
+ base::DictionaryValue* item_properties;
+ if (!data_.GetDictionary(id, &item_properties)) {
+ item_properties = new base::DictionaryValue();
+ data_.Set(id, item_properties);
+ item_properties->SetString("id", id);
+ }
+ item_properties->Set(property, value);
+ SendUpdate(update_fn, item_properties);
+}
+
+void MediaInternals::SendUpdate(const std::string& function,
+ base::Value* value) {
+ // Only bother serializing the update to JSON if someone is watching.
+ if (update_callbacks_.empty())
+ return;
+
+ std::vector<const base::Value*> args;
+ args.push_back(value);
+ string16 update = WebUI::GetJavascriptCall(function, args);
+ for (size_t i = 0; i < update_callbacks_.size(); i++)
+ update_callbacks_[i].Run(update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals.h b/chromium/content/browser/media/media_internals.h
new file mode 100644
index 00000000000..4a4d2efc7d1
--- /dev/null
+++ b/chromium/content/browser/media/media_internals.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "content/common/content_export.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace media {
+struct MediaLogEvent;
+}
+
+namespace content {
+
+// This class stores information about currently active media.
+// It's constructed on the UI thread but all of its methods are called on the IO
+// thread.
+class CONTENT_EXPORT MediaInternals {
+ public:
+ virtual ~MediaInternals();
+
+ static MediaInternals* GetInstance();
+
+ // The following methods are virtual for gmock.
+
+ // Called when an audio stream is deleted.
+ virtual void OnDeleteAudioStream(void* host, int stream_id);
+
+ // Called when an audio stream is set to playing or paused.
+ virtual void OnSetAudioStreamPlaying(void* host, int stream_id,
+ bool playing);
+
+ // Called when the status of an audio stream is set to "created", "closed", or
+ // "error".
+ virtual void OnSetAudioStreamStatus(void* host, int stream_id,
+ const std::string& status);
+
+ // Called when the volume of an audio stream is set.
+ virtual void OnSetAudioStreamVolume(void* host, int stream_id,
+ double volume);
+
+ // Called when a MediaEvent occurs.
+ virtual void OnMediaEvents(int render_process_id,
+ const std::vector<media::MediaLogEvent>& events);
+
+ // Called with the update string.
+ typedef base::Callback<void(const string16&)> UpdateCallback;
+
+ // Add/remove update callbacks (see above).
+ void AddUpdateCallback(const UpdateCallback& callback);
+ void RemoveUpdateCallback(const UpdateCallback& callback);
+ void SendEverything();
+
+ private:
+ friend class MockMediaInternals;
+ friend class MediaInternalsTest;
+ friend struct DefaultSingletonTraits<MediaInternals>;
+
+ MediaInternals();
+
+ // Sets |property| of an audio stream to |value| and notifies observers.
+ // (host, stream_id) is a unique id for the audio stream.
+ // |host| will never be dereferenced.
+ void UpdateAudioStream(void* host, int stream_id,
+ const std::string& property, base::Value* value);
+
+ // Removes |item| from |data_|.
+ void DeleteItem(const std::string& item);
+
+ // Sets data_.id.property = value and notifies attached UIs using update_fn.
+ // id may be any depth, e.g. "video.decoders.1.2.3"
+ void UpdateItem(const std::string& update_fn, const std::string& id,
+ const std::string& property, base::Value* value);
+
+ // Calls javascript |function|(|value|) on each attached UI.
+ void SendUpdate(const std::string& function, base::Value* value);
+
+ base::DictionaryValue data_;
+
+ std::vector<UpdateCallback> update_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternals);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_H_
diff --git a/chromium/content/browser/media/media_internals_handler.cc b/chromium/content/browser/media/media_internals_handler.cc
new file mode 100644
index 00000000000..1ea6b04c663
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_handler.cc
@@ -0,0 +1,46 @@
+// 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 "content/browser/media/media_internals_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/values.h"
+#include "content/browser//media/media_internals_proxy.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+MediaInternalsMessageHandler::MediaInternalsMessageHandler()
+ : proxy_(new MediaInternalsProxy()) {}
+
+MediaInternalsMessageHandler::~MediaInternalsMessageHandler() {
+ proxy_->Detach();
+}
+
+void MediaInternalsMessageHandler::RegisterMessages() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ proxy_->Attach(this);
+
+ web_ui()->RegisterMessageCallback("getEverything",
+ base::Bind(&MediaInternalsMessageHandler::OnGetEverything,
+ base::Unretained(this)));
+}
+
+void MediaInternalsMessageHandler::OnGetEverything(
+ const base::ListValue* list) {
+ proxy_->GetEverything();
+}
+
+void MediaInternalsMessageHandler::OnUpdate(const string16& update) {
+ // Don't try to execute JavaScript in a RenderView that no longer exists.
+ RenderViewHost* host = web_ui()->GetWebContents()->GetRenderViewHost();
+ if (host)
+ host->ExecuteJavascriptInWebFrame(string16(), update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_handler.h b/chromium/content/browser/media/media_internals_handler.h
new file mode 100644
index 00000000000..4833aee285f
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_handler.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+class MediaInternalsProxy;
+
+// This class handles messages to and from MediaInternalsUI.
+// It does all its work on the IO thread through the proxy below.
+class MediaInternalsMessageHandler : public WebUIMessageHandler {
+ public:
+ MediaInternalsMessageHandler();
+ virtual ~MediaInternalsMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // Javascript message handlers.
+ void OnGetEverything(const base::ListValue* list);
+
+ // MediaInternals message handlers.
+ void OnUpdate(const string16& update);
+
+ private:
+ scoped_refptr<MediaInternalsProxy> proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsMessageHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_HANDLER_H_
diff --git a/chromium/content/browser/media/media_internals_proxy.cc b/chromium/content/browser/media/media_internals_proxy.cc
new file mode 100644
index 00000000000..d0e0622b41b
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_proxy.cc
@@ -0,0 +1,182 @@
+// 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 "content/browser/media/media_internals_proxy.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/media/media_internals_handler.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+static const int kMediaInternalsProxyEventDelayMilliseconds = 100;
+
+static const net::NetLog::EventType kNetEventTypeFilter[] = {
+ net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL,
+ net::NetLog::TYPE_SPARSE_READ,
+ net::NetLog::TYPE_SPARSE_WRITE,
+ net::NetLog::TYPE_URL_REQUEST_START_JOB,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+};
+
+MediaInternalsProxy::MediaInternalsProxy() {
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+void MediaInternalsProxy::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ RenderProcessHost* process = Source<RenderProcessHost>(source).ptr();
+ CallJavaScriptFunctionOnUIThread("media.onRendererTerminated",
+ new base::FundamentalValue(process->GetID()));
+}
+
+void MediaInternalsProxy::Attach(MediaInternalsMessageHandler* handler) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ handler_ = handler;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::ObserveMediaInternalsOnIOThread, this));
+}
+
+void MediaInternalsProxy::Detach() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ handler_ = NULL;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &MediaInternalsProxy::StopObservingMediaInternalsOnIOThread, this));
+}
+
+void MediaInternalsProxy::GetEverything() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Ask MediaInternals for all its data.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::GetEverythingOnIOThread, this));
+
+ // Send the page names for constants.
+ CallJavaScriptFunctionOnUIThread("media.onReceiveConstants", GetConstants());
+}
+
+void MediaInternalsProxy::OnUpdate(const string16& update) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::UpdateUIOnUIThread, this, update));
+}
+
+void MediaInternalsProxy::OnAddEntry(const net::NetLog::Entry& entry) {
+ bool is_event_interesting = false;
+ for (size_t i = 0; i < arraysize(kNetEventTypeFilter); i++) {
+ if (entry.type() == kNetEventTypeFilter[i]) {
+ is_event_interesting = true;
+ break;
+ }
+ }
+
+ if (!is_event_interesting)
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&MediaInternalsProxy::AddNetEventOnUIThread, this,
+ entry.ToValue()));
+}
+
+MediaInternalsProxy::~MediaInternalsProxy() {}
+
+base::Value* MediaInternalsProxy::GetConstants() {
+ base::DictionaryValue* event_phases = new base::DictionaryValue();
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_NONE),
+ net::NetLog::PHASE_NONE);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_BEGIN),
+ net::NetLog::PHASE_BEGIN);
+ event_phases->SetInteger(
+ net::NetLog::EventPhaseToString(net::NetLog::PHASE_END),
+ net::NetLog::PHASE_END);
+
+ base::DictionaryValue* constants = new base::DictionaryValue();
+ constants->Set("eventTypes", net::NetLog::GetEventTypesAsValue());
+ constants->Set("eventPhases", event_phases);
+
+ return constants;
+}
+
+void MediaInternalsProxy::ObserveMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ update_callback_ = base::Bind(&MediaInternalsProxy::OnUpdate,
+ base::Unretained(this));
+ MediaInternals::GetInstance()->AddUpdateCallback(update_callback_);
+ if (GetContentClient()->browser()->GetNetLog()) {
+ net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
+ net_log->AddThreadSafeObserver(this, net::NetLog::LOG_ALL_BUT_BYTES);
+ }
+}
+
+void MediaInternalsProxy::StopObservingMediaInternalsOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ MediaInternals::GetInstance()->RemoveUpdateCallback(update_callback_);
+ if (GetContentClient()->browser()->GetNetLog()) {
+ net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
+ net_log->RemoveThreadSafeObserver(this);
+ }
+}
+
+void MediaInternalsProxy::GetEverythingOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ MediaInternals::GetInstance()->SendEverything();
+}
+
+void MediaInternalsProxy::UpdateUIOnUIThread(const string16& update) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Don't forward updates to a destructed UI.
+ if (handler_)
+ handler_->OnUpdate(update);
+}
+
+void MediaInternalsProxy::AddNetEventOnUIThread(base::Value* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Send the updates to the page in kMediaInternalsProxyEventDelayMilliseconds
+ // if an update is not already pending.
+ if (!pending_net_updates_) {
+ pending_net_updates_.reset(new base::ListValue());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MediaInternalsProxy::SendNetEventsOnUIThread, this),
+ base::TimeDelta::FromMilliseconds(
+ kMediaInternalsProxyEventDelayMilliseconds));
+ }
+ pending_net_updates_->Append(entry);
+}
+
+void MediaInternalsProxy::SendNetEventsOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ CallJavaScriptFunctionOnUIThread("media.onNetUpdate",
+ pending_net_updates_.release());
+}
+
+void MediaInternalsProxy::CallJavaScriptFunctionOnUIThread(
+ const std::string& function, base::Value* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<base::Value> args_value(args);
+ std::vector<const base::Value*> args_vector;
+ args_vector.push_back(args_value.get());
+ string16 update = WebUI::GetJavascriptCall(function, args_vector);
+ UpdateUIOnUIThread(update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_proxy.h b/chromium/content/browser/media/media_internals_proxy.h
new file mode 100644
index 00000000000..5d173b3e738
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_proxy.h
@@ -0,0 +1,90 @@
+// 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 CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/browser/media/media_internals.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "net/base/net_log.h"
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace content {
+class MediaInternalsMessageHandler;
+
+// This class is a proxy between MediaInternals (on the IO thread) and
+// MediaInternalsMessageHandler (on the UI thread).
+// It is ref_counted to ensure that it completes all pending Tasks on both
+// threads before destruction.
+class MediaInternalsProxy
+ : public base::RefCountedThreadSafe<
+ MediaInternalsProxy,
+ BrowserThread::DeleteOnUIThread>,
+ public net::NetLog::ThreadSafeObserver,
+ public NotificationObserver {
+ public:
+ MediaInternalsProxy();
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Register a Handler and start receiving callbacks from MediaInternals.
+ void Attach(MediaInternalsMessageHandler* handler);
+
+ // Unregister the same and stop receiving callbacks.
+ void Detach();
+
+ // Have MediaInternals send all the data it has.
+ void GetEverything();
+
+ // MediaInternals callback. Called on the IO thread.
+ void OnUpdate(const string16& update);
+
+ // net::NetLog::ThreadSafeObserver implementation. Callable from any thread:
+ virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE;
+
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
+ friend class base::DeleteHelper<MediaInternalsProxy>;
+ virtual ~MediaInternalsProxy();
+
+ // Build a dictionary mapping constant names to values.
+ base::Value* GetConstants();
+
+ void ObserveMediaInternalsOnIOThread();
+ void StopObservingMediaInternalsOnIOThread();
+ void GetEverythingOnIOThread();
+ void UpdateUIOnUIThread(const string16& update);
+
+ // Put |entry| on a list of events to be sent to the page.
+ void AddNetEventOnUIThread(base::Value* entry);
+
+ // Send all pending events to the page.
+ void SendNetEventsOnUIThread();
+
+ // Call a JavaScript function on the page. Takes ownership of |args|.
+ void CallJavaScriptFunctionOnUIThread(const std::string& function,
+ base::Value* args);
+
+ MediaInternalsMessageHandler* handler_;
+ scoped_ptr<base::ListValue> pending_net_updates_;
+ NotificationRegistrar registrar_;
+ MediaInternals::UpdateCallback update_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsProxy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_PROXY_H_
diff --git a/chromium/content/browser/media/media_internals_ui.cc b/chromium/content/browser/media/media_internals_ui.cc
new file mode 100644
index 00000000000..83638148bf9
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_ui.cc
@@ -0,0 +1,53 @@
+// 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 "content/browser/media/media_internals_ui.h"
+
+#include "base/command_line.h"
+#include "content/browser/media/media_internals_handler.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+
+namespace content {
+namespace {
+
+WebUIDataSource* CreateMediaInternalsHTMLSource() {
+ WebUIDataSource* source =
+ WebUIDataSource::Create(kChromeUIMediaInternalsHost);
+
+ source->SetJsonPath("strings.js");
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableNewMediaInternals)) {
+ source->AddResourcePath("media_internals.js", IDR_MEDIA_INTERNALS_NEW_JS);
+ source->SetDefaultResource(IDR_MEDIA_INTERNALS_NEW_HTML);
+ return source;
+ }
+
+ source->AddResourcePath("media_internals.js", IDR_MEDIA_INTERNALS_JS);
+ source->SetDefaultResource(IDR_MEDIA_INTERNALS_HTML);
+ return source;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MediaInternalsUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+MediaInternalsUI::MediaInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new MediaInternalsMessageHandler());
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateMediaInternalsHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_internals_ui.h b/chromium/content/browser/media/media_internals_ui.h
new file mode 100644
index 00000000000..f7a17a6765a
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
+#define CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+// The implementation for the chrome://media-internals page.
+class MediaInternalsUI : public WebUIController {
+ public:
+ explicit MediaInternalsUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaInternalsUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_MEDIA_INTERNALS_UI_H_
diff --git a/chromium/content/browser/media/media_internals_unittest.cc b/chromium/content/browser/media/media_internals_unittest.cc
new file mode 100644
index 00000000000..58c8f91dc68
--- /dev/null
+++ b/chromium/content/browser/media/media_internals_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/media/media_internals.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class MockObserverBaseClass {
+ public:
+ ~MockObserverBaseClass() {}
+ virtual void OnUpdate(const string16& javascript) = 0;
+};
+
+class MockMediaInternalsObserver : public MockObserverBaseClass {
+ public:
+ virtual ~MockMediaInternalsObserver() {}
+ MOCK_METHOD1(OnUpdate, void(const string16& javascript));
+};
+
+} // namespace
+
+class MediaInternalsTest : public testing::Test {
+ public:
+ MediaInternalsTest() : io_thread_(BrowserThread::IO, &loop_) {}
+ base::DictionaryValue* data() {
+ return &internals_->data_;
+ }
+
+ void DeleteItem(const std::string& item) {
+ internals_->DeleteItem(item);
+ }
+
+ void UpdateItem(const std::string& item, const std::string& property,
+ base::Value* value) {
+ internals_->UpdateItem(std::string(), item, property, value);
+ }
+
+ void SendUpdate(const std::string& function, base::Value* value) {
+ internals_->SendUpdate(function, value);
+ }
+
+ protected:
+ virtual void SetUp() {
+ internals_.reset(new MediaInternals());
+ }
+
+ base::MessageLoop loop_;
+ TestBrowserThread io_thread_;
+ scoped_ptr<MediaInternals> internals_;
+};
+
+TEST_F(MediaInternalsTest, UpdateAddsNewItem) {
+ UpdateItem("some.item", "testing", new base::FundamentalValue(true));
+ bool testing = false;
+ std::string id;
+
+ EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
+ EXPECT_TRUE(testing);
+
+ EXPECT_TRUE(data()->GetString("some.item.id", &id));
+ EXPECT_EQ(id, "some.item");
+}
+
+TEST_F(MediaInternalsTest, UpdateModifiesExistingItem) {
+ UpdateItem("some.item", "testing", new base::FundamentalValue(true));
+ UpdateItem("some.item", "value", new base::FundamentalValue(5));
+ UpdateItem("some.item", "testing", new base::FundamentalValue(false));
+ bool testing = true;
+ int value = 0;
+ std::string id;
+
+ EXPECT_TRUE(data()->GetBoolean("some.item.testing", &testing));
+ EXPECT_FALSE(testing);
+
+ EXPECT_TRUE(data()->GetInteger("some.item.value", &value));
+ EXPECT_EQ(value, 5);
+
+ EXPECT_TRUE(data()->GetString("some.item.id", &id));
+ EXPECT_EQ(id, "some.item");
+}
+
+TEST_F(MediaInternalsTest, ObserversReceiveNotifications) {
+ scoped_ptr<MockMediaInternalsObserver> observer(
+ new MockMediaInternalsObserver());
+
+ EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(1);
+
+ MediaInternals::UpdateCallback callback = base::Bind(
+ &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
+
+ internals_->AddUpdateCallback(callback);
+ SendUpdate("fn", data());
+}
+
+TEST_F(MediaInternalsTest, RemovedObserversReceiveNoNotifications) {
+ scoped_ptr<MockMediaInternalsObserver> observer(
+ new MockMediaInternalsObserver());
+
+ EXPECT_CALL(*observer.get(), OnUpdate(testing::_)).Times(0);
+
+ MediaInternals::UpdateCallback callback = base::Bind(
+ &MockMediaInternalsObserver::OnUpdate, base::Unretained(observer.get()));
+
+ internals_->AddUpdateCallback(callback);
+ internals_->RemoveUpdateCallback(callback);
+ SendUpdate("fn", data());
+}
+
+TEST_F(MediaInternalsTest, DeleteRemovesItem) {
+ base::Value* out;
+
+ UpdateItem("some.item", "testing", base::Value::CreateNullValue());
+ EXPECT_TRUE(data()->Get("some.item", &out));
+ EXPECT_TRUE(data()->Get("some", &out));
+
+ DeleteItem("some.item");
+ EXPECT_FALSE(data()->Get("some.item", &out));
+ EXPECT_TRUE(data()->Get("some", &out));
+
+ DeleteItem("some");
+ EXPECT_FALSE(data()->Get("some.item", &out));
+ EXPECT_FALSE(data()->Get("some", &out));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/media_source_browsertest.cc b/chromium/content/browser/media/media_source_browsertest.cc
new file mode 100644
index 00000000000..bd119ddff07
--- /dev/null
+++ b/chromium/content/browser/media/media_source_browsertest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "content/browser/media/media_browsertest.h"
+#include "content/public/common/content_switches.h"
+
+// Common media types.
+static const char kWebMAudioOnly[] = "audio/webm; codecs=\"vorbis\"";
+static const char kWebMVideoOnly[] = "video/webm; codecs=\"vp8\"";
+static const char kWebMAudioVideo[] = "video/webm; codecs=\"vorbis, vp8\"";
+
+namespace content {
+
+class MediaSourceTest : public content::MediaBrowserTest {
+ public:
+ void TestSimplePlayback(const char* media_file, const char* media_type,
+ const char* expectation) {
+ std::vector<StringPair> query_params;
+ query_params.push_back(std::make_pair("mediafile", media_file));
+ query_params.push_back(std::make_pair("mediatype", media_type));
+ RunMediaTestPage("media_source_player.html", &query_params, expectation,
+ true);
+ }
+
+#if defined(OS_ANDROID)
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(
+ switches::kDisableGestureRequirementForMediaPlayback);
+ }
+#endif
+};
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_VideoAudio_WebM) {
+ TestSimplePlayback("bear-320x240.webm", kWebMAudioVideo, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_VideoOnly_WebM) {
+ TestSimplePlayback("bear-320x240-video-only.webm", kWebMVideoOnly, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_AudioOnly_WebM) {
+ TestSimplePlayback("bear-320x240-audio-only.webm", kWebMAudioOnly, kEnded);
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_Type_Error) {
+ TestSimplePlayback("bear-320x240-video-only.webm", kWebMAudioOnly, kError);
+}
+
+// Flaky test crbug.com/246308
+// Test changed to skip checks resulting in flakiness. Proper fix still needed.
+IN_PROC_BROWSER_TEST_F(MediaSourceTest, ConfigChangeVideo) {
+ RunMediaTestPage("mse_config_change.html", NULL, kEnded, true);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_browsertest.cc b/chromium/content/browser/media/webrtc_browsertest.cc
new file mode 100644
index 00000000000..7cf48b0ad77
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_browsertest.cc
@@ -0,0 +1,301 @@
+// 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 "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace {
+
+std::string GenerateGetUserMediaCall(int min_width,
+ int max_width,
+ int min_height,
+ int max_height,
+ int min_frame_rate,
+ int max_frame_rate) {
+ return base::StringPrintf(
+ "getUserMedia({video: {mandatory: {minWidth: %d, maxWidth: %d, "
+ "minHeight: %d, maxHeight: %d, minFrameRate: %d, maxFrameRate: %d}, "
+ "optional: []}});",
+ min_width,
+ max_width,
+ min_height,
+ max_height,
+ min_frame_rate,
+ max_frame_rate);
+}
+}
+
+namespace content {
+
+class WebrtcBrowserTest: public ContentBrowserTest {
+ public:
+ WebrtcBrowserTest() {}
+ virtual ~WebrtcBrowserTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // We need fake devices in this test since we want to run on naked VMs. We
+ // assume these switches are set by default in content_browsertests.
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+
+ // The video playback will not work without a GPU, so force its use here.
+ // This may not be available on all VMs though.
+ command_line->AppendSwitch(switches::kUseGpuInTests);
+ }
+
+ protected:
+ bool ExecuteJavascript(const std::string& javascript) {
+ return ExecuteScript(shell()->web_contents(), javascript);
+ }
+
+ void ExpectTitle(const std::string& expected_title) const {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+};
+
+// These tests will all make a getUserMedia call with different constraints and
+// see that the success callback is called. If the error callback is called or
+// none of the callbacks are called the tests will simply time out and fail.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetVideoStreamAndStop) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMedia({video: true});"));
+
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetAudioAndVideoStreamAndStop) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMedia({video: true, audio: true});"));
+
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, GetAudioAndVideoStreamAndClone) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("getUserMediaAndClone();"));
+
+ ExpectTitle("OK");
+}
+
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CanSetupVideoCall DISABLED_CanSetupVideoCall
+#else
+#define MAYBE_CanSetupVideoCall CanSetupVideoCall
+#endif
+
+// These tests will make a complete PeerConnection-based call and verify that
+// video is playing for the call.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CanSetupVideoCall) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("call({video: true});"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux, see http://crbug.com/240376
+#define MAYBE_CanSetupAudioAndVideoCall DISABLED_CanSetupAudioAndVideoCall
+#else
+#define MAYBE_CanSetupAudioAndVideoCall CanSetupAudioAndVideoCall
+#endif
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CanSetupAudioAndVideoCall) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("call({video: true, audio: true});"));
+ ExpectTitle("OK");
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MANUAL_CanSetupCallAndSendDtmf) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("callAndSendDtmf('123,abc');"));
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest,
+ CanMakeEmptyCallThenAddStreamsAndRenegotiate) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ const char* kJavascript =
+ "callEmptyThenAddOneStreamAndRenegotiate({video: true, audio: true});";
+ EXPECT_TRUE(ExecuteJavascript(kJavascript));
+ ExpectTitle("OK");
+}
+
+// This test will make a complete PeerConnection-based call but remove the
+// MSID and bundle attribute from the initial offer to verify that
+// video is playing for the call even if the initiating client don't support
+// MSID. http://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02
+#if defined(OS_WIN) && defined(USE_AURA)
+// Disabled for win7_aura, see http://crbug.com/235089.
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ DISABLED_CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux, see http://crbug.com/240373
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ DISABLED_CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#else
+#define MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle\
+ CanSetupAudioAndVideoCallWithoutMsidAndBundle
+#endif
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest,
+ MAYBE_CanSetupAudioAndVideoCallWithoutMsidAndBundle) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithoutMsidAndBundle();"));
+ ExpectTitle("OK");
+}
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, CallWithDataOnly) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataOnly();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithDataAndMedia DISABLED_CallWithDataAndMedia
+#else
+#define MAYBE_CallWithDataAndMedia CallWithDataAndMedia
+#endif
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel and audio and video tracks.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithDataAndMedia) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataAndMedia();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithDataAndLaterAddMedia DISABLED_CallWithDataAndLaterAddMedia
+#else
+#define MAYBE_CallWithDataAndLaterAddMedia CallWithDataAndLaterAddMedia
+#endif
+
+// This test will make a PeerConnection-based call and test an unreliable text
+// dataChannel and later add an audio and video track.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithDataAndLaterAddMedia) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithDataAndLaterAddMedia();"));
+ ExpectTitle("OK");
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+// Timing out on ARM linux bot: http://crbug.com/238490
+#define MAYBE_CallWithNewVideoMediaStream DISABLED_CallWithNewVideoMediaStream
+#else
+#define MAYBE_CallWithNewVideoMediaStream CallWithNewVideoMediaStream
+#endif
+
+// This test will make a PeerConnection-based call and send a new Video
+// MediaStream that has been created based on a MediaStream created with
+// getUserMedia.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MAYBE_CallWithNewVideoMediaStream) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(ExecuteJavascript("callWithNewVideoMediaStream();"));
+ ExpectTitle("OK");
+}
+
+// This test will make a PeerConnection-based call and send a new Video
+// MediaStream that has been created based on a MediaStream created with
+// getUserMedia. When video is flowing, the VideoTrack is removed and an
+// AudioTrack is added instead.
+// TODO(phoglund): This test is manual since not all buildbots has an audio
+// input.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, MANUAL_CallAndModifyStream) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("callWithNewVideoMediaStreamLaterSwitchToAudio();"));
+ ExpectTitle("OK");
+}
+
+// This test calls getUserMedia in sequence with different constraints.
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, TestGetUserMediaConstraints) {
+ GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
+
+ std::vector<std::string> list_of_get_user_media_calls;
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(320, 320, 180, 180, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(320, 320, 240, 240, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(640, 640, 360, 360, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(640, 640, 480, 480, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(960, 960, 720, 720, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(1280, 1280, 720, 720, 30, 30));
+ list_of_get_user_media_calls.push_back(
+ GenerateGetUserMediaCall(1920, 1920, 1080, 1080, 30, 30));
+
+ for (std::vector<std::string>::iterator const_iterator =
+ list_of_get_user_media_calls.begin();
+ const_iterator != list_of_get_user_media_calls.end();
+ ++const_iterator) {
+ DVLOG(1) << "Calling getUserMedia: " << *const_iterator;
+ NavigateToURL(shell(), url);
+ EXPECT_TRUE(ExecuteJavascript(*const_iterator));
+ ExpectTitle("OK");
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(WebrtcBrowserTest, AddTwoMediaStreamsToOnePC) {
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+
+ EXPECT_TRUE(
+ ExecuteJavascript("addTwoMediaStreamsToOneConnection();"));
+ ExpectTitle("OK");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store.cc b/chromium/content/browser/media/webrtc_identity_store.cc
new file mode 100644
index 00000000000..67a25851fde
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store.cc
@@ -0,0 +1,307 @@
+// 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 "content/browser/media/webrtc_identity_store.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/threading/worker_pool.h"
+#include "content/browser/media/webrtc_identity_store_backend.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_util.h"
+#include "url/gurl.h"
+
+namespace content {
+
+struct WebRTCIdentityRequestResult {
+ WebRTCIdentityRequestResult(int error,
+ const std::string& certificate,
+ const std::string& private_key)
+ : error(error), certificate(certificate), private_key(private_key) {}
+
+ int error;
+ std::string certificate;
+ std::string private_key;
+};
+
+// Generates a new identity using |common_name| and returns the result in
+// |result|.
+static void GenerateIdentityWorker(const std::string& common_name,
+ WebRTCIdentityRequestResult* result) {
+ result->error = net::OK;
+ int serial_number = base::RandInt(0, std::numeric_limits<int>::max());
+
+ scoped_ptr<crypto::RSAPrivateKey> key(crypto::RSAPrivateKey::Create(1024));
+ if (!key.get()) {
+ DLOG(ERROR) << "Unable to create key pair for client";
+ result->error = net::ERR_KEY_GENERATION_FAILED;
+ return;
+ }
+
+ base::Time now = base::Time::Now();
+ bool success =
+ net::x509_util::CreateSelfSignedCert(key.get(),
+ "CN=" + common_name,
+ serial_number,
+ now,
+ now + base::TimeDelta::FromDays(30),
+ &result->certificate);
+ if (!success) {
+ DLOG(ERROR) << "Unable to create x509 cert for client";
+ result->error = net::ERR_SELF_SIGNED_CERT_GENERATION_FAILED;
+ return;
+ }
+
+ std::vector<uint8> private_key_info;
+ if (!key->ExportPrivateKey(&private_key_info)) {
+ DLOG(ERROR) << "Unable to export private key";
+ result->error = net::ERR_PRIVATE_KEY_EXPORT_FAILED;
+ return;
+ }
+
+ result->private_key =
+ std::string(private_key_info.begin(), private_key_info.end());
+}
+
+class WebRTCIdentityRequestHandle;
+
+// The class represents an identity request internal to WebRTCIdentityStore.
+// It has a one-to-many mapping to the external version of the request,
+// WebRTCIdentityRequestHandle, i.e. multiple identical external requests are
+// combined into one internal request.
+// It's deleted automatically when the request is completed.
+class WebRTCIdentityRequest {
+ public:
+ WebRTCIdentityRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name)
+ : origin_(origin),
+ identity_name_(identity_name),
+ common_name_(common_name) {}
+
+ void Cancel(WebRTCIdentityRequestHandle* handle) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (callbacks_.find(handle) == callbacks_.end())
+ return;
+ callbacks_.erase(handle);
+ }
+
+ private:
+ friend class WebRTCIdentityStore;
+
+ void AddCallback(WebRTCIdentityRequestHandle* handle,
+ const WebRTCIdentityStore::CompletionCallback& callback) {
+ DCHECK(callbacks_.find(handle) == callbacks_.end());
+ callbacks_[handle] = callback;
+ }
+
+ // This method deletes "this" and no one should access it after the request
+ // completes.
+ // We do not use base::Owned to tie its lifetime to the callback for
+ // WebRTCIdentityStoreBackend::FindIdentity, because it needs to live longer
+ // than that if the identity does not exist in DB.
+ void Post(const WebRTCIdentityRequestResult& result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ for (CallbackMap::iterator it = callbacks_.begin(); it != callbacks_.end();
+ ++it)
+ it->second.Run(result.error, result.certificate, result.private_key);
+ delete this;
+ }
+
+ GURL origin_;
+ std::string identity_name_;
+ std::string common_name_;
+ typedef std::map<WebRTCIdentityRequestHandle*,
+ WebRTCIdentityStore::CompletionCallback> CallbackMap;
+ CallbackMap callbacks_;
+};
+
+// The class represents an identity request which calls back to the external
+// client when the request completes.
+// Its lifetime is tied with the Callback held by the corresponding
+// WebRTCIdentityRequest.
+class WebRTCIdentityRequestHandle {
+ public:
+ WebRTCIdentityRequestHandle(
+ WebRTCIdentityStore* store,
+ const WebRTCIdentityStore::CompletionCallback& callback)
+ : store_(store), request_(NULL), callback_(callback) {}
+
+ private:
+ friend class WebRTCIdentityStore;
+
+ // Cancel the request. Does nothing if the request finished or was already
+ // cancelled.
+ void Cancel() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!request_)
+ return;
+
+ callback_.Reset();
+ WebRTCIdentityRequest* request = request_;
+ request_ = NULL;
+ // "this" will be deleted after the following call, because "this" is
+ // owned by the Callback held by |request|.
+ request->Cancel(this);
+ }
+
+ void OnRequestStarted(WebRTCIdentityRequest* request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(request);
+ request_ = request;
+ }
+
+ void OnRequestComplete(int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(request_);
+ request_ = NULL;
+ base::ResetAndReturn(&callback_).Run(error, certificate, private_key);
+ }
+
+ WebRTCIdentityStore* store_;
+ WebRTCIdentityRequest* request_;
+ WebRTCIdentityStore::CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityRequestHandle);
+};
+
+WebRTCIdentityStore::WebRTCIdentityStore(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : task_runner_(base::WorkerPool::GetTaskRunner(true)),
+ backend_(new WebRTCIdentityStoreBackend(path, policy)) {}
+
+WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); }
+
+base::Closure WebRTCIdentityStore::RequestIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const CompletionCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ WebRTCIdentityRequest* request =
+ FindRequest(origin, identity_name, common_name);
+ // If there is no identical request in flight, create a new one, queue it,
+ // and make the backend request.
+ if (!request) {
+ request = new WebRTCIdentityRequest(origin, identity_name, common_name);
+ // |request| will delete itself after the result is posted.
+ if (!backend_->FindIdentity(
+ origin,
+ identity_name,
+ common_name,
+ base::Bind(
+ &WebRTCIdentityStore::BackendFindCallback, this, request))) {
+ // Bail out if the backend failed to start the task.
+ delete request;
+ return base::Closure();
+ }
+ in_flight_requests_.push_back(request);
+ }
+
+ WebRTCIdentityRequestHandle* handle =
+ new WebRTCIdentityRequestHandle(this, callback);
+
+ request->AddCallback(
+ handle,
+ base::Bind(&WebRTCIdentityRequestHandle::OnRequestComplete,
+ base::Owned(handle)));
+ handle->OnRequestStarted(request);
+ return base::Bind(&WebRTCIdentityRequestHandle::Cancel,
+ base::Unretained(handle));
+}
+
+void WebRTCIdentityStore::DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ backend_->DeleteBetween(delete_begin, delete_end, callback);
+}
+
+void WebRTCIdentityStore::SetTaskRunnerForTesting(
+ const scoped_refptr<base::TaskRunner>& task_runner) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ task_runner_ = task_runner;
+}
+
+void WebRTCIdentityStore::BackendFindCallback(WebRTCIdentityRequest* request,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (error == net::OK) {
+ DVLOG(2) << "Identity found in DB.";
+ WebRTCIdentityRequestResult result(error, certificate, private_key);
+ PostRequestResult(request, result);
+ return;
+ }
+ // Generate a new identity if not found in the DB.
+ WebRTCIdentityRequestResult* result =
+ new WebRTCIdentityRequestResult(0, "", "");
+ if (!task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GenerateIdentityWorker, request->common_name_, result),
+ base::Bind(&WebRTCIdentityStore::GenerateIdentityCallback,
+ this,
+ request,
+ base::Owned(result)))) {
+ // Completes the request with error if failed to post the task.
+ WebRTCIdentityRequestResult result(net::ERR_UNEXPECTED, "", "");
+ PostRequestResult(request, result);
+ }
+}
+
+void WebRTCIdentityStore::GenerateIdentityCallback(
+ WebRTCIdentityRequest* request,
+ WebRTCIdentityRequestResult* result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (result->error == net::OK) {
+ DVLOG(2) << "New identity generated and added to the backend.";
+ backend_->AddIdentity(request->origin_,
+ request->identity_name_,
+ request->common_name_,
+ result->certificate,
+ result->private_key);
+ }
+ PostRequestResult(request, *result);
+}
+
+void WebRTCIdentityStore::PostRequestResult(
+ WebRTCIdentityRequest* request,
+ const WebRTCIdentityRequestResult& result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Removes the in flight request from the queue.
+ for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
+ if (in_flight_requests_[i] == request) {
+ in_flight_requests_.erase(in_flight_requests_.begin() + i);
+ break;
+ }
+ }
+ // |request| will be deleted after this call.
+ request->Post(result);
+}
+
+// Find an identical request from the in flight requests.
+WebRTCIdentityRequest* WebRTCIdentityStore::FindRequest(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name) {
+ for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
+ if (in_flight_requests_[i]->origin_ == origin &&
+ in_flight_requests_[i]->identity_name_ == identity_name &&
+ in_flight_requests_[i]->common_name_ == common_name) {
+ return in_flight_requests_[i];
+ }
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store.h b/chromium/content/browser/media/webrtc_identity_store.h
new file mode 100644
index 00000000000..c2b523e2dfe
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store.h
@@ -0,0 +1,114 @@
+// 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 CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class TaskRunner;
+} // namespace base
+
+namespace quota {
+class SpecialStoragePolicy;
+} // namespace quota
+
+namespace content {
+class WebRTCIdentityRequest;
+struct WebRTCIdentityRequestResult;
+class WebRTCIdentityStoreBackend;
+class WebRTCIdentityStoreTest;
+
+// A class for creating and fetching DTLS identities, i.e. the private key and
+// the self-signed certificate.
+// It can be created/destroyed on any thread, but the public methods must be
+// called on the IO thread.
+class CONTENT_EXPORT WebRTCIdentityStore
+ : public base::RefCountedThreadSafe<WebRTCIdentityStore> {
+ public:
+ typedef base::Callback<void(int error,
+ const std::string& certificate,
+ const std::string& private_key)>
+ CompletionCallback;
+
+ // If |path| is empty, nothing will be saved to disk.
+ WebRTCIdentityStore(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy);
+
+ // Retrieve the cached DTLS private key and certificate, i.e. identity, for
+ // the |origin| and |identity_name| pair, or generate a new identity using
+ // |common_name| if such an identity does not exist.
+ // If the given |common_name| is different from the common name in the cached
+ // identity that has the same origin and identity_name, a new private key and
+ // a new certificate will be generated, overwriting the old one.
+ //
+ // |origin| is the origin of the DTLS connection;
+ // |identity_name| is used to identify an identity within an origin; it is
+ // opaque to WebRTCIdentityStore and remains private to the caller, i.e. not
+ // present in the certificate;
+ // |common_name| is the common name used to generate the certificate and will
+ // be shared with the peer of the DTLS connection. Identities created for
+ // different origins or different identity names may have the same common
+ // name.
+ // |callback| is the callback to return the result as DER strings.
+ //
+ // Returns the Closure used to cancel the request if the request is accepted.
+ // The Closure can only be called before the request completes.
+ virtual base::Closure RequestIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const CompletionCallback& callback);
+
+ // Delete the identities created between |delete_begin| and |delete_end|.
+ // |callback| will be called when the operation is done.
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ protected:
+ // Only virtual to allow subclassing for test mock.
+ virtual ~WebRTCIdentityStore();
+
+ private:
+ friend class base::RefCountedThreadSafe<WebRTCIdentityStore>;
+ friend class WebRTCIdentityStoreTest;
+
+ void SetTaskRunnerForTesting(
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ void BackendFindCallback(WebRTCIdentityRequest* request,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key);
+ void GenerateIdentityCallback(WebRTCIdentityRequest* request,
+ WebRTCIdentityRequestResult* result);
+ WebRTCIdentityRequest* FindRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name);
+ void PostRequestResult(WebRTCIdentityRequest* request,
+ const WebRTCIdentityRequestResult& result);
+
+ // The TaskRunner for doing work on a worker thread.
+ scoped_refptr<base::TaskRunner> task_runner_;
+ // Weak references of the in flight requests. Used to join identical external
+ // requests.
+ std::vector<WebRTCIdentityRequest*> in_flight_requests_;
+
+ scoped_refptr<WebRTCIdentityStoreBackend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_
diff --git a/chromium/content/browser/media/webrtc_identity_store_backend.cc b/chromium/content/browser/media/webrtc_identity_store_backend.cc
new file mode 100644
index 00000000000..9ec73e92f69
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_backend.cc
@@ -0,0 +1,546 @@
+// 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 "content/browser/media/webrtc_identity_store_backend.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/net_errors.h"
+#include "sql/error_delegate_util.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "url/gurl.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+namespace content {
+
+static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store";
+
+static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
+ FILE_PATH_LITERAL("WebRTCIdentityStore");
+
+// Initializes the identity table, returning true on success.
+static bool InitDB(sql::Connection* db) {
+ if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
+ if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
+ db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
+ return true;
+ if (!db->Execute("DROP TABLE webrtc_identity_store"))
+ return false;
+ }
+
+ return db->Execute(
+ "CREATE TABLE webrtc_identity_store"
+ " ("
+ "origin TEXT NOT NULL,"
+ "identity_name TEXT NOT NULL,"
+ "common_name TEXT NOT NULL,"
+ "certificate BLOB NOT NULL,"
+ "private_key BLOB NOT NULL,"
+ "creation_time INTEGER)");
+}
+
+struct WebRTCIdentityStoreBackend::IdentityKey {
+ IdentityKey(const GURL& origin, const std::string& identity_name)
+ : origin(origin), identity_name(identity_name) {}
+
+ bool operator<(const IdentityKey& other) const {
+ return origin < other.origin ||
+ (origin == other.origin && identity_name < other.identity_name);
+ }
+
+ GURL origin;
+ std::string identity_name;
+};
+
+struct WebRTCIdentityStoreBackend::Identity {
+ Identity(const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key)
+ : common_name(common_name),
+ certificate(certificate),
+ private_key(private_key),
+ creation_time(base::Time::Now().ToInternalValue()) {}
+
+ Identity(const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key,
+ int64 creation_time)
+ : common_name(common_name),
+ certificate(certificate),
+ private_key(private_key),
+ creation_time(creation_time) {}
+
+ std::string common_name;
+ std::string certificate;
+ std::string private_key;
+ int64 creation_time;
+};
+
+struct WebRTCIdentityStoreBackend::PendingFindRequest {
+ PendingFindRequest(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback)
+ : origin(origin),
+ identity_name(identity_name),
+ common_name(common_name),
+ callback(callback) {}
+
+ ~PendingFindRequest() {}
+
+ GURL origin;
+ std::string identity_name;
+ std::string common_name;
+ FindIdentityCallback callback;
+};
+
+// The class encapsulates the database operations. All members except ctor and
+// dtor should be accessed on the DB thread.
+// It can be created/destroyed on any thread.
+class WebRTCIdentityStoreBackend::SqlLiteStorage
+ : public base::RefCountedThreadSafe<SqlLiteStorage> {
+ public:
+ SqlLiteStorage(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : special_storage_policy_(policy) {
+ if (!path.empty())
+ path_ = path.Append(kWebRTCIdentityStoreDirectory);
+ }
+
+ void Load(IdentityMap* out_map);
+ void Close();
+ void AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void DeleteIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<SqlLiteStorage>;
+
+ enum OperationType {
+ ADD_IDENTITY,
+ DELETE_IDENTITY
+ };
+ struct PendingOperation {
+ PendingOperation(OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity)
+ : type(type),
+ origin(origin),
+ identity_name(identity_name),
+ identity(identity) {}
+
+ OperationType type;
+ GURL origin;
+ std::string identity_name;
+ Identity identity;
+ };
+ typedef std::vector<PendingOperation*> PendingOperationList;
+
+ virtual ~SqlLiteStorage() {}
+ void OnDatabaseError(int error, sql::Statement* stmt);
+ void BatchOperation(OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity);
+ void Commit();
+
+ // The file path of the DB. Empty if temporary.
+ base::FilePath path_;
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ scoped_ptr<sql::Connection> db_;
+ // Batched DB operations pending to commit.
+ PendingOperationList pending_operations_;
+
+ DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
+};
+
+WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
+ const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy)
+ : state_(NOT_STARTED),
+ sql_lite_storage_(new SqlLiteStorage(path, policy)) {}
+
+bool WebRTCIdentityStoreBackend::FindIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (state_ == CLOSED)
+ return false;
+
+ if (state_ != LOADED) {
+ // Queues the request to wait for the DB to load.
+ pending_find_requests_.push_back(
+ new PendingFindRequest(origin, identity_name, common_name, callback));
+ if (state_ == LOADING)
+ return true;
+
+ DCHECK_EQ(state_, NOT_STARTED);
+
+ // Kick off loading the DB.
+ scoped_ptr<IdentityMap> out_map(new IdentityMap());
+ base::Closure task(
+ base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
+ // |out_map| will be NULL after this call.
+ if (BrowserThread::PostTaskAndReply(
+ BrowserThread::DB,
+ FROM_HERE,
+ task,
+ base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
+ this,
+ base::Passed(&out_map)))) {
+ state_ = LOADING;
+ return true;
+ }
+ // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
+ }
+
+ IdentityKey key(origin, identity_name);
+ IdentityMap::iterator iter = identities_.find(key);
+ if (iter != identities_.end() && iter->second.common_name == common_name) {
+ // Identity found.
+ return BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback,
+ net::OK,
+ iter->second.certificate,
+ iter->second.private_key));
+ }
+
+ return BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
+}
+
+void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (state_ == CLOSED)
+ return;
+
+ // If there is an existing identity for the same origin and identity_name,
+ // delete it.
+ IdentityKey key(origin, identity_name);
+ Identity identity(common_name, certificate, private_key);
+
+ if (identities_.find(key) != identities_.end()) {
+ if (!BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::DeleteIdentity,
+ sql_lite_storage_,
+ origin,
+ identity_name,
+ identities_.find(key)->second)))
+ return;
+ }
+ identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
+
+ BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::AddIdentity,
+ sql_lite_storage_,
+ origin,
+ identity_name,
+ identity));
+}
+
+void WebRTCIdentityStoreBackend::Close() {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&WebRTCIdentityStoreBackend::Close, this));
+ return;
+ }
+
+ if (state_ == CLOSED)
+ return;
+
+ state_ = CLOSED;
+ BrowserThread::PostTask(
+ BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
+}
+
+void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Delete the in-memory cache.
+ IdentityMap::iterator it = identities_.begin();
+ while (it != identities_.end()) {
+ if (it->second.creation_time >= delete_begin.ToInternalValue() &&
+ it->second.creation_time <= delete_end.ToInternalValue())
+ identities_.erase(it++);
+ else
+ it++;
+ }
+
+ BrowserThread::PostTaskAndReply(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::DeleteBetween,
+ sql_lite_storage_,
+ delete_begin,
+ delete_end,
+ callback),
+ callback);
+}
+
+WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
+
+void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state_ = LOADED;
+ identities_.swap(*out_map);
+
+ for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
+ FindIdentity(pending_find_requests_[i]->origin,
+ pending_find_requests_[i]->identity_name,
+ pending_find_requests_[i]->common_name,
+ pending_find_requests_[i]->callback);
+ delete pending_find_requests_[i];
+ }
+ pending_find_requests_.clear();
+}
+
+//
+// Implementation of SqlLiteStorage.
+//
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ DCHECK(!db_.get());
+
+ // Ensure the parent directory for storing certs is created before reading
+ // from it.
+ const base::FilePath dir = path_.DirName();
+ if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) {
+ DLOG(ERROR) << "Unable to open DB file path.";
+ return;
+ }
+
+ db_.reset(new sql::Connection());
+
+ db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
+
+ if (!db_->Open(path_)) {
+ DLOG(ERROR) << "Unable to open DB.";
+ db_.reset();
+ return;
+ }
+
+ if (!InitDB(db_.get())) {
+ DLOG(ERROR) << "Unable to init DB.";
+ db_.reset();
+ return;
+ }
+
+ db_->Preload();
+
+ // Slurp all the identities into the out_map.
+ sql::Statement stmt(db_->GetUniqueStatement(
+ "SELECT origin, identity_name, common_name, "
+ "certificate, private_key, creation_time "
+ "FROM webrtc_identity_store"));
+ CHECK(stmt.is_valid());
+
+ while (stmt.Step()) {
+ IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
+ std::string common_name(stmt.ColumnString(2));
+ std::string cert, private_key;
+ stmt.ColumnBlobAsString(3, &cert);
+ stmt.ColumnBlobAsString(4, &private_key);
+ int64 creation_time = stmt.ColumnInt64(5);
+ std::pair<IdentityMap::iterator, bool> result =
+ out_map->insert(std::pair<IdentityKey, Identity>(
+ key, Identity(common_name, cert, private_key, creation_time)));
+ DCHECK(result.second);
+ }
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ Commit();
+ db_.reset();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+
+ // Do not add for session only origins.
+ if (special_storage_policy_.get() &&
+ !special_storage_policy_->IsStorageProtected(origin) &&
+ special_storage_policy_->IsStorageSessionOnly(origin)) {
+ return;
+ }
+ BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+ BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
+ int error,
+ sql::Statement* stmt) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!sql::IsErrorCatastrophic(error))
+ return;
+ db_->RazeAndClose();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
+ OperationType type,
+ const GURL& origin,
+ const std::string& identity_name,
+ const Identity& identity) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Commit every 30 seconds.
+ static const base::TimeDelta kCommitInterval(
+ base::TimeDelta::FromSeconds(30));
+ // Commit right away if we have more than 512 outstanding operations.
+ static const size_t kCommitAfterBatchSize = 512;
+
+ // We do a full copy of the cert here, and hopefully just here.
+ scoped_ptr<PendingOperation> operation(
+ new PendingOperation(type, origin, identity_name, identity));
+
+ pending_operations_.push_back(operation.release());
+
+ if (pending_operations_.size() == 1) {
+ // We've gotten our first entry for this batch, fire off the timer.
+ BrowserThread::PostDelayedTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Commit, this),
+ kCommitInterval);
+ } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
+ // We've reached a big enough batch, fire off a commit now.
+ BrowserThread::PostTask(BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&SqlLiteStorage::Commit, this));
+ }
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Maybe an old timer fired or we are already Close()'ed.
+ if (!db_.get() || pending_operations_.empty())
+ return;
+
+ sql::Statement add_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "INSERT INTO webrtc_identity_store "
+ "(origin, identity_name, common_name, certificate,"
+ " private_key, creation_time) VALUES"
+ " (?,?,?,?,?,?)"));
+
+ CHECK(add_stmt.is_valid());
+
+ sql::Statement del_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
+
+ CHECK(del_stmt.is_valid());
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ DLOG(ERROR) << "Failed to begin the transaction.";
+ return;
+ }
+
+ for (PendingOperationList::iterator it = pending_operations_.begin();
+ it != pending_operations_.end();
+ ++it) {
+ scoped_ptr<PendingOperation> po(*it);
+ switch (po->type) {
+ case ADD_IDENTITY: {
+ add_stmt.Reset(true);
+ add_stmt.BindString(0, po->origin.spec());
+ add_stmt.BindString(1, po->identity_name);
+ add_stmt.BindString(2, po->identity.common_name);
+ const std::string& cert = po->identity.certificate;
+ add_stmt.BindBlob(3, cert.data(), cert.size());
+ const std::string& private_key = po->identity.private_key;
+ add_stmt.BindBlob(4, private_key.data(), private_key.size());
+ add_stmt.BindInt64(5, po->identity.creation_time);
+ CHECK(add_stmt.Run());
+ break;
+ }
+ case DELETE_IDENTITY:
+ del_stmt.Reset(true);
+ del_stmt.BindString(0, po->origin.spec());
+ add_stmt.BindString(1, po->identity_name);
+ CHECK(del_stmt.Run());
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ transaction.Commit();
+ pending_operations_.clear();
+}
+
+void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ if (!db_.get())
+ return;
+
+ // Commit pending operations first.
+ Commit();
+
+ sql::Statement del_stmt(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "DELETE FROM webrtc_identity_store"
+ " WHERE creation_time >= ? AND creation_time <= ?"));
+ CHECK(del_stmt.is_valid());
+
+ del_stmt.BindInt64(0, delete_begin.ToInternalValue());
+ del_stmt.BindInt64(1, delete_end.ToInternalValue());
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ DLOG(ERROR) << "Failed to begin the transaction.";
+ return;
+ }
+
+ CHECK(del_stmt.Run());
+ transaction.Commit();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_identity_store_backend.h b/chromium/content/browser/media/webrtc_identity_store_backend.h
new file mode 100644
index 00000000000..ab4e1ed7e1e
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_backend.h
@@ -0,0 +1,111 @@
+// 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 CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
+
+#include <map>
+#include <string>
+
+#include "base/time/time.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace quota {
+class SpecialStoragePolicy;
+} // namespace quota
+
+namespace content {
+
+// This class represents a persistent cache of WebRTC identities.
+// It can be created/destroyed/Close() on any thread. All other members should
+// be accessed on the IO thread.
+class WebRTCIdentityStoreBackend
+ : public base::RefCountedThreadSafe<WebRTCIdentityStoreBackend> {
+ public:
+ typedef base::Callback<void(int error,
+ const std::string& certificate,
+ const std::string& private_key)>
+ FindIdentityCallback;
+
+ // No data is saved on disk if |path| is empty.
+ WebRTCIdentityStoreBackend(const base::FilePath& path,
+ quota::SpecialStoragePolicy* policy);
+
+ // Finds the identity with |origin|, |identity_name|, and |common_name| from
+ // the DB.
+ // |origin| is the origin of the identity;
+ // |identity_name| is used to identify an identity within an origin;
+ // |common_name| is the common name used to generate the certificate;
+ // |callback| is the callback to return the find result.
+ // Returns true if |callback| will be called.
+ // Should be called on the IO thread.
+ bool FindIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const FindIdentityCallback& callback);
+
+ // Adds the identity to the DB and overwrites any existing identity having the
+ // same origin and identity_name.
+ // |origin| is the origin of the identity;
+ // |identity_name| is used to identify an identity within an origin;
+ // |common_name| is the common name used to generate the certificate;
+ // |certificate| is the DER string of the certificate;
+ // |private_key| is the DER string of the private key.
+ // Should be called on the IO thread.
+ void AddIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const std::string& certificate,
+ const std::string& private_key);
+
+ // Commits all pending DB operations and closes the DB connection. Any API
+ // call after this will fail.
+ // Can be called on any thread.
+ void Close();
+
+ // Delete the data created between |delete_begin| and |delete_end|.
+ // Should be called on the IO thread.
+ void DeleteBetween(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<WebRTCIdentityStoreBackend>;
+ class SqlLiteStorage;
+ enum LoadingState {
+ NOT_STARTED,
+ LOADING,
+ LOADED,
+ CLOSED,
+ };
+ struct PendingFindRequest;
+ struct IdentityKey;
+ struct Identity;
+ typedef std::map<IdentityKey, Identity> IdentityMap;
+
+ ~WebRTCIdentityStoreBackend();
+
+ void OnLoaded(scoped_ptr<IdentityMap> out_map);
+
+ // In-memory copy of the identities.
+ IdentityMap identities_;
+ // "Find identity" requests waiting for the DB to load.
+ std::vector<PendingFindRequest*> pending_find_requests_;
+ // The persistent storage loading state.
+ LoadingState state_;
+ // The persistent storage of identities.
+ scoped_refptr<SqlLiteStorage> sql_lite_storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStoreBackend);
+};
+}
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_
diff --git a/chromium/content/browser/media/webrtc_identity_store_unittest.cc b/chromium/content/browser/media/webrtc_identity_store_unittest.cc
new file mode 100644
index 00000000000..81c25d7f63f
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_identity_store_unittest.cc
@@ -0,0 +1,278 @@
+// 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 "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "content/browser/media/webrtc_identity_store.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// TODO(jiayl): the tests fail on Android since the openssl version of
+// CreateSelfSignedCert is not implemented. We should mock out this dependency
+// and remove the if-defined.
+#if !defined(OS_ANDROID)
+
+static const char* kFakeOrigin = "http://foo.com";
+static const char* kFakeIdentityName1 = "name1";
+static const char* kFakeIdentityName2 = "name2";
+static const char* kFakeCommonName1 = "cname1";
+static const char* kFakeCommonName2 = "cname2";
+
+static void OnRequestCompleted(bool* completed,
+ std::string* out_cert,
+ std::string* out_key,
+ int error,
+ const std::string& certificate,
+ const std::string& private_key) {
+ ASSERT_EQ(net::OK, error);
+ ASSERT_NE("", certificate);
+ ASSERT_NE("", private_key);
+ *completed = true;
+ *out_cert = certificate;
+ *out_key = private_key;
+}
+
+class WebRTCIdentityStoreTest : public testing::Test {
+ public:
+ WebRTCIdentityStoreTest()
+ : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP |
+ TestBrowserThreadBundle::REAL_DB_THREAD),
+ pool_owner_(
+ new base::SequencedWorkerPoolOwner(3, "WebRTCIdentityStoreTest")),
+ webrtc_identity_store_(
+ new WebRTCIdentityStore(base::FilePath(), NULL)) {
+ webrtc_identity_store_->SetTaskRunnerForTesting(pool_owner_->pool());
+ }
+
+ virtual ~WebRTCIdentityStoreTest() {
+ pool_owner_->pool()->Shutdown();
+ }
+
+ void RunUntilIdle() {
+ RunAllPendingInMessageLoop(BrowserThread::DB);
+ RunAllPendingInMessageLoop(BrowserThread::IO);
+ pool_owner_->pool()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ base::Closure RequestIdentityAndRunUtilIdle(const std::string& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ bool* completed,
+ std::string* certificate,
+ std::string* private_key) {
+ base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity(
+ GURL(origin),
+ identity_name,
+ common_name,
+ base::Bind(&OnRequestCompleted, completed, certificate, private_key));
+ EXPECT_FALSE(cancel_callback.is_null());
+ RunUntilIdle();
+ return cancel_callback;
+ }
+
+ protected:
+ TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_;
+ scoped_refptr<WebRTCIdentityStore> webrtc_identity_store_;
+};
+
+TEST_F(WebRTCIdentityStoreTest, RequestIdentity) {
+ bool completed = false;
+ std::string dummy;
+ base::Closure cancel_callback =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed,
+ &dummy,
+ &dummy);
+ EXPECT_TRUE(completed);
+}
+
+TEST_F(WebRTCIdentityStoreTest, CancelRequest) {
+ bool completed = false;
+ std::string dummy;
+ base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback.is_null());
+ cancel_callback.Run();
+
+ RunUntilIdle();
+ EXPECT_FALSE(completed);
+}
+
+TEST_F(WebRTCIdentityStoreTest, ConcurrentUniqueRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string dummy;
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName2,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &dummy, &dummy));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ RunUntilIdle();
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, DifferentCommonNameReturnNewIdentity) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName2,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_NE(cert_1, cert_2);
+ EXPECT_NE(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, SerialIdenticalRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_EQ(cert_1, cert_2);
+ EXPECT_EQ(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, ConcurrentIdenticalRequestsJoined) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ RunUntilIdle();
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_EQ(cert_1, cert_2);
+ EXPECT_EQ(key_1, key_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, CancelOneOfIdenticalRequests) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1));
+ ASSERT_FALSE(cancel_callback_1.is_null());
+
+ base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity(
+ GURL(kFakeOrigin),
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2));
+ ASSERT_FALSE(cancel_callback_2.is_null());
+
+ cancel_callback_1.Run();
+
+ RunUntilIdle();
+ EXPECT_FALSE(completed_1);
+ EXPECT_TRUE(completed_2);
+}
+
+TEST_F(WebRTCIdentityStoreTest, DeleteDataAndGenerateNewIdentity) {
+ bool completed_1 = false;
+ bool completed_2 = false;
+ std::string cert_1, cert_2, key_1, key_2;
+
+ // Generate the first identity.
+ base::Closure cancel_callback_1 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_1,
+ &cert_1,
+ &key_1);
+
+ // Clear the data and the second request should return a new identity.
+ webrtc_identity_store_->DeleteBetween(
+ base::Time(), base::Time::Now(), base::Bind(&base::DoNothing));
+ RunUntilIdle();
+
+ base::Closure cancel_callback_2 =
+ RequestIdentityAndRunUtilIdle(kFakeOrigin,
+ kFakeIdentityName1,
+ kFakeCommonName1,
+ &completed_2,
+ &cert_2,
+ &key_2);
+
+ EXPECT_TRUE(completed_1);
+ EXPECT_TRUE(completed_2);
+ EXPECT_NE(cert_1, cert_2);
+ EXPECT_NE(key_1, key_2);
+}
+
+#endif
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals.cc b/chromium/content/browser/media/webrtc_internals.cc
new file mode 100644
index 00000000000..f8bcdc3d321
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals.h"
+
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+
+using base::ProcessId;
+using std::string;
+
+namespace content {
+
+namespace {
+// Makes sure that |dict| has a ListValue under path "log".
+static base::ListValue* EnsureLogList(base::DictionaryValue* dict) {
+ base::ListValue* log = NULL;
+ if (!dict->GetList("log", &log)) {
+ log = new base::ListValue();
+ if (log)
+ dict->Set("log", log);
+ }
+ return log;
+}
+
+} // namespace
+
+WebRTCInternals::WebRTCInternals() : is_recording_rtp_(false) {
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+
+ BrowserChildProcessObserver::Add(this);
+}
+
+WebRTCInternals::~WebRTCInternals() {
+ BrowserChildProcessObserver::Remove(this);
+}
+
+WebRTCInternals* WebRTCInternals::GetInstance() {
+ return Singleton<WebRTCInternals>::get();
+}
+
+void WebRTCInternals::OnAddPeerConnection(int render_process_id,
+ ProcessId pid,
+ int lid,
+ const string& url,
+ const string& servers,
+ const string& constraints) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (!dict)
+ return;
+
+ dict->SetInteger("rid", render_process_id);
+ dict->SetInteger("pid", static_cast<int>(pid));
+ dict->SetInteger("lid", lid);
+ dict->SetString("servers", servers);
+ dict->SetString("constraints", constraints);
+ dict->SetString("url", url);
+ peer_connection_data_.Append(dict);
+
+ if (observers_.size() > 0)
+ SendUpdate("addPeerConnection", dict);
+}
+
+void WebRTCInternals::OnRemovePeerConnection(ProcessId pid, int lid) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
+ base::DictionaryValue* dict = NULL;
+ peer_connection_data_.GetDictionary(i, &dict);
+
+ int this_pid = 0;
+ int this_lid = 0;
+ dict->GetInteger("pid", &this_pid);
+ dict->GetInteger("lid", &this_lid);
+
+ if (this_pid != static_cast<int>(pid) || this_lid != lid)
+ continue;
+
+ peer_connection_data_.Remove(i, NULL);
+
+ if (observers_.size() > 0) {
+ base::DictionaryValue id;
+ id.SetInteger("pid", static_cast<int>(pid));
+ id.SetInteger("lid", lid);
+ SendUpdate("removePeerConnection", &id);
+ }
+ break;
+ }
+}
+
+void WebRTCInternals::OnUpdatePeerConnection(
+ ProcessId pid, int lid, const string& type, const string& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
+ base::DictionaryValue* record = NULL;
+ peer_connection_data_.GetDictionary(i, &record);
+
+ int this_pid = 0, this_lid = 0;
+ record->GetInteger("pid", &this_pid);
+ record->GetInteger("lid", &this_lid);
+
+ if (this_pid != static_cast<int>(pid) || this_lid != lid)
+ continue;
+
+ // Append the update to the end of the log.
+ base::ListValue* log = EnsureLogList(record);
+ if (!log)
+ return;
+
+ base::DictionaryValue* log_entry = new base::DictionaryValue();
+ if (!log_entry)
+ return;
+
+ log_entry->SetString("type", type);
+ log_entry->SetString("value", value);
+ log->Append(log_entry);
+
+ if (observers_.size() > 0) {
+ base::DictionaryValue update;
+ update.SetInteger("pid", static_cast<int>(pid));
+ update.SetInteger("lid", lid);
+ update.SetString("type", type);
+ update.SetString("value", value);
+
+ SendUpdate("updatePeerConnection", &update);
+ }
+ return;
+ }
+}
+
+void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid,
+ const base::ListValue& value) {
+ if (observers_.size() == 0)
+ return;
+
+ base::DictionaryValue dict;
+ dict.SetInteger("pid", static_cast<int>(pid));
+ dict.SetInteger("lid", lid);
+
+ base::ListValue* list = value.DeepCopy();
+ if (!list)
+ return;
+
+ dict.Set("reports", list);
+
+ SendUpdate("addStats", &dict);
+}
+
+void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ observers_.AddObserver(observer);
+}
+
+void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ observers_.RemoveObserver(observer);
+}
+
+void WebRTCInternals::SendAllUpdates() {
+ if (observers_.size() > 0)
+ SendUpdate("updateAllPeerConnections", &peer_connection_data_);
+}
+
+void WebRTCInternals::StartRtpRecording() {
+ if (!is_recording_rtp_) {
+ is_recording_rtp_ = true;
+ // TODO(justinlin): start RTP recording.
+ }
+}
+
+void WebRTCInternals::StopRtpRecording() {
+ if (is_recording_rtp_) {
+ is_recording_rtp_ = false;
+ // TODO(justinlin): stop RTP recording.
+ }
+}
+
+void WebRTCInternals::SendUpdate(const string& command, base::Value* value) {
+ DCHECK_GT(observers_.size(), (size_t)0);
+
+ FOR_EACH_OBSERVER(WebRTCInternalsUIObserver,
+ observers_,
+ OnUpdate(command, value));
+}
+
+void WebRTCInternals::BrowserChildProcessCrashed(
+ const ChildProcessData& data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ OnRendererExit(data.id);
+}
+
+void WebRTCInternals::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ OnRendererExit(Source<RenderProcessHost>(source)->GetID());
+}
+
+void WebRTCInternals::OnRendererExit(int render_process_id) {
+ // Iterates from the end of the list to remove the PeerConnections created
+ // by the exitting renderer.
+ for (int i = peer_connection_data_.GetSize() - 1; i >= 0; --i) {
+ base::DictionaryValue* record = NULL;
+ peer_connection_data_.GetDictionary(i, &record);
+
+ int this_rid = 0;
+ record->GetInteger("rid", &this_rid);
+
+ if (this_rid == render_process_id) {
+ if (observers_.size() > 0) {
+ int lid = 0, pid = 0;
+ record->GetInteger("lid", &lid);
+ record->GetInteger("pid", &pid);
+
+ base::DictionaryValue update;
+ update.SetInteger("lid", lid);
+ update.SetInteger("pid", pid);
+ SendUpdate("removePeerConnection", &update);
+ }
+ peer_connection_data_.Remove(i, NULL);
+ }
+ }
+}
+
+// TODO(justlin): Calls this method as necessary to update the recording status
+// UI.
+void WebRTCInternals::SendRtpRecordingUpdate() {
+ DCHECK(is_recording_rtp_);
+ base::DictionaryValue update;
+ // TODO(justinlin): Fill in |update| with values as appropriate.
+ SendUpdate("updateDumpStatus", &update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals.h b/chromium/content/browser/media/webrtc_internals.h
new file mode 100644
index 00000000000..58e9a9fbb9b
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals.h
@@ -0,0 +1,117 @@
+// Copyright (c) 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 CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
+
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "base/process/process.h"
+#include "base/values.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_child_process_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace content {
+class WebRTCInternalsUIObserver;
+
+// This is a singleton class running in the browser UI thread.
+// It collects peer connection infomation from the renderers,
+// forwards the data to WebRTCInternalsUIObserver and
+// sends data collecting commands to the renderers.
+class CONTENT_EXPORT WebRTCInternals : public BrowserChildProcessObserver,
+ public NotificationObserver {
+ public:
+ static WebRTCInternals* GetInstance();
+
+ // This method is called when a PeerConnection is created.
+ // |render_process_id| is the id of the render process (not OS pid), which is
+ // needed because we might not be able to get the OS process id when the
+ // render process terminates and we want to clean up.
+ // |pid| is the renderer process id, |lid| is the renderer local id used to
+ // identify a PeerConnection, |url| is the url of the tab owning the
+ // PeerConnection, |servers| is the servers configuration, |constraints| is
+ // the media constraints used to initialize the PeerConnection.
+ void OnAddPeerConnection(int render_process_id,
+ base::ProcessId pid,
+ int lid,
+ const std::string& url,
+ const std::string& servers,
+ const std::string& constraints);
+
+ // This method is called when PeerConnection is destroyed.
+ // |pid| is the renderer process id, |lid| is the renderer local id.
+ void OnRemovePeerConnection(base::ProcessId pid, int lid);
+
+ // This method is called when a PeerConnection is updated.
+ // |pid| is the renderer process id, |lid| is the renderer local id,
+ // |type| is the update type, |value| is the detail of the update.
+ void OnUpdatePeerConnection(base::ProcessId pid,
+ int lid,
+ const std::string& type,
+ const std::string& value);
+
+ // This method is called when results from PeerConnectionInterface::GetStats
+ // are available. |pid| is the renderer process id, |lid| is the renderer
+ // local id, |value| is the list of stats reports.
+ void OnAddStats(base::ProcessId pid, int lid, const base::ListValue& value);
+
+ // Methods for adding or removing WebRTCInternalsUIObserver.
+ void AddObserver(WebRTCInternalsUIObserver *observer);
+ void RemoveObserver(WebRTCInternalsUIObserver *observer);
+
+ // Sends all update data to the observers.
+ void SendAllUpdates();
+
+ // Tells the renderer processes to start or stop recording RTP packets.
+ void StartRtpRecording();
+ void StopRtpRecording();
+
+ private:
+ friend struct DefaultSingletonTraits<WebRTCInternals>;
+
+ WebRTCInternals();
+ virtual ~WebRTCInternals();
+
+ void SendUpdate(const std::string& command, base::Value* value);
+
+ // BrowserChildProcessObserver implementation.
+ virtual void BrowserChildProcessCrashed(
+ const ChildProcessData& data) OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Called when a renderer exits (including crashes).
+ void OnRendererExit(int render_process_id);
+
+ void SendRtpRecordingUpdate();
+
+ ObserverList<WebRTCInternalsUIObserver> observers_;
+
+ // |peer_connection_data_| is a list containing all the PeerConnection
+ // updates.
+ // Each item of the list represents the data for one PeerConnection, which
+ // contains these fields:
+ // "pid" -- processId of the renderer that creates the PeerConnection.
+ // "lid" -- local Id assigned to the PeerConnection.
+ // "url" -- url of the web page that created the PeerConnection.
+ // "servers" and "constraints" -- server configuration and media constraints
+ // used to initialize the PeerConnection respectively.
+ // "log" -- a ListValue contains all the updates for the PeerConnection. Each
+ // list item is a DictionaryValue containing "type" and "value", both of which
+ // are strings.
+ base::ListValue peer_connection_data_;
+
+ NotificationRegistrar registrar_;
+
+ bool is_recording_rtp_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_H_
diff --git a/chromium/content/browser/media/webrtc_internals_browsertest.cc b/chromium/content/browser/media/webrtc_internals_browsertest.cc
new file mode 100644
index 00000000000..71bbe53ed9f
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_browsertest.cc
@@ -0,0 +1,709 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using std::string;
+namespace content {
+
+struct SsrcEntry {
+ string GetSsrcAttributeString() const {
+ std::stringstream ss;
+ ss << "a=ssrc:" << id;
+ std::map<string, string>::const_iterator iter;
+ for (iter = properties.begin(); iter != properties.end(); ++iter) {
+ ss << " " << iter->first << ":" << iter->second;
+ }
+ return ss.str();
+ }
+
+ string GetAsJSON() const {
+ std::stringstream ss;
+ ss << "{";
+ std::map<string, string>::const_iterator iter;
+ for (iter = properties.begin(); iter != properties.end(); ++iter) {
+ if (iter != properties.begin())
+ ss << ",";
+ ss << "\"" << iter->first << "\":\"" << iter->second << "\"";
+ }
+ ss << "}";
+ return ss.str();
+ }
+
+ string id;
+ std::map<string, string> properties;
+};
+
+struct EventEntry {
+ string type;
+ string value;
+};
+
+struct StatsUnit {
+ string GetString() const {
+ std::stringstream ss;
+ ss << "{timestamp:" << timestamp << ", values:[";
+ std::map<string, string>::const_iterator iter;
+ for (iter = values.begin(); iter != values.end(); ++iter) {
+ ss << "'" << iter->first << "','" << iter->second << "',";
+ }
+ ss << "]}";
+ return ss.str();
+ }
+
+ int64 timestamp;
+ std::map<string, string> values;
+};
+
+struct StatsEntry {
+ string type;
+ string id;
+ StatsUnit stats;
+};
+
+typedef std::map<string, std::vector<string> > StatsMap;
+
+class PeerConnectionEntry {
+ public:
+ PeerConnectionEntry(int pid, int lid) : pid_(pid), lid_(lid) {}
+
+ void AddEvent(const string& type, const string& value) {
+ EventEntry entry = {type, value};
+ events_.push_back(entry);
+ }
+
+ string getIdString() const {
+ std::stringstream ss;
+ ss << pid_ << "-" << lid_;
+ return ss.str();
+ }
+
+ string getLogIdString() const {
+ std::stringstream ss;
+ ss << pid_ << "-" << lid_ << "-update-log";
+ return ss.str();
+ }
+
+ string getAllUpdateString() const {
+ std::stringstream ss;
+ ss << "{pid:" << pid_ << ", lid:" << lid_ << ", log:[";
+ for (size_t i = 0; i < events_.size(); ++i) {
+ ss << "{type:'" << events_[i].type <<
+ "', value:'" << events_[i].value << "'},";
+ }
+ ss << "]}";
+ return ss.str();
+ }
+
+ int pid_;
+ int lid_;
+ std::vector<EventEntry> events_;
+ // This is a record of the history of stats value reported for each stats
+ // report id (e.g. ssrc-1234) for each stats name (e.g. framerate).
+ // It a 2-D map with each map entry is a vector of reported values.
+ // It is used to verify the graph data series.
+ std::map<string, StatsMap> stats_;
+};
+
+static const int64 FAKE_TIME_STAMP = 3600000;
+
+class WebRTCInternalsBrowserTest: public ContentBrowserTest {
+ public:
+ WebRTCInternalsBrowserTest() {}
+ virtual ~WebRTCInternalsBrowserTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ // We need fake devices in this test since we want to run on naked VMs. We
+ // assume these switches are set by default in content_browsertests.
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream));
+ ASSERT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeUIForMediaStream));
+ }
+
+ protected:
+ bool ExecuteJavascript(const string& javascript) {
+ return ExecuteScript(shell()->web_contents(), javascript);
+ }
+
+ void ExpectTitle(const std::string& expected_title) const {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+
+ // Execute the javascript of addPeerConnection.
+ void ExecuteAddPeerConnectionJs(const PeerConnectionEntry& pc) {
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << ", " <<
+ "url:'u', servers:'s', constraints:'c'}";
+ ASSERT_TRUE(ExecuteJavascript("addPeerConnection(" + ss.str() + ");"));
+ }
+
+ // Execute the javascript of removePeerConnection.
+ void ExecuteRemovePeerConnectionJs(const PeerConnectionEntry& pc) {
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << "}";
+
+ ASSERT_TRUE(ExecuteJavascript("removePeerConnection(" + ss.str() + ");"));
+ }
+
+ // Verifies that the DOM element with id |id| exists.
+ void VerifyElementWithId(const string& id) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send($('" + id + "') != null);",
+ &result));
+ EXPECT_TRUE(result);
+ }
+
+ // Verifies that the DOM element with id |id| does not exist.
+ void VerifyNoElementWithId(const string& id) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send($('" + id + "') == null);",
+ &result));
+ EXPECT_TRUE(result);
+ }
+
+ // Verifies that DOM for |pc| is correctly created with the right content.
+ void VerifyPeerConnectionEntry(const PeerConnectionEntry& pc) {
+ VerifyElementWithId(pc.getIdString());
+ if (pc.events_.size() == 0)
+ return;
+
+ string log_id = pc.getLogIdString();
+ VerifyElementWithId(log_id);
+ string result;
+ for (size_t i = 0; i < pc.events_.size(); ++i) {
+ std::stringstream ss;
+ ss << "var row = $('" << log_id << "').rows[" << (i + 1) << "];"
+ "var cell = row.lastChild;"
+ "window.domAutomationController.send(cell.firstChild.textContent);";
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(), ss.str(), &result));
+ EXPECT_EQ(pc.events_[i].type + pc.events_[i].value, result);
+ }
+ }
+
+ // Executes the javascript of updatePeerConnection and verifies the result.
+ void ExecuteAndVerifyUpdatePeerConnection(
+ PeerConnectionEntry& pc, const string& type, const string& value) {
+ pc.AddEvent(type, value);
+
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ <<
+ ", type:'" << type << "', value:'" << value << "'}";
+ ASSERT_TRUE(ExecuteJavascript("updatePeerConnection(" + ss.str() + ")"));
+
+ VerifyPeerConnectionEntry(pc);
+ }
+
+ // Execute addStats and verifies that the stats table has the right content.
+ void ExecuteAndVerifyAddStats(
+ PeerConnectionEntry& pc, const string& type, const string& id,
+ StatsUnit& stats) {
+ StatsEntry entry = {type, id, stats};
+
+ // Adds each new value to the map of stats history.
+ std::map<string, string>::iterator iter;
+ for (iter = stats.values.begin(); iter != stats.values.end(); iter++) {
+ pc.stats_[id][iter->first].push_back(iter->second);
+ }
+ std::stringstream ss;
+ ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ","
+ "reports:[" << "{id:'" << id << "', type:'" << type << "', "
+ "stats:" << stats.GetString() << "}]}";
+
+ ASSERT_TRUE(ExecuteJavascript("addStats(" + ss.str() + ")"));
+ VerifyStatsTable(pc, entry);
+ }
+
+
+ // Verifies that the stats table has the right content.
+ void VerifyStatsTable(const PeerConnectionEntry& pc,
+ const StatsEntry& report) {
+ string table_id =
+ pc.getIdString() + "-table-" + report.id;
+ VerifyElementWithId(table_id);
+
+ std::map<string, string>::const_iterator iter;
+ for (iter = report.stats.values.begin();
+ iter != report.stats.values.end(); iter++) {
+ VerifyStatsTableRow(table_id, iter->first, iter->second);
+ }
+ }
+
+ // Verifies that the row named as |name| of the stats table |table_id| has
+ // the correct content as |name| : |value|.
+ void VerifyStatsTableRow(const string& table_id,
+ const string& name,
+ const string& value) {
+ VerifyElementWithId(table_id + "-" + name);
+
+ string result;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "var row = $('" + table_id + "-" + name + "');"
+ "var name = row.cells[0].textContent;"
+ "var value = row.cells[1].textContent;"
+ "window.domAutomationController.send(name + ':' + value)",
+ &result));
+ EXPECT_EQ(name + ":" + value, result);
+ }
+
+ // Verifies that the graph data series consistent with pc.stats_.
+ void VerifyStatsGraph(const PeerConnectionEntry& pc) {
+ std::map<string, StatsMap>::const_iterator stream_iter;
+ for (stream_iter = pc.stats_.begin();
+ stream_iter != pc.stats_.end(); stream_iter++) {
+ StatsMap::const_iterator stats_iter;
+ for (stats_iter = stream_iter->second.begin();
+ stats_iter != stream_iter->second.end();
+ stats_iter++) {
+ string graph_id = stream_iter->first + "-" + stats_iter->first;
+ for (size_t i = 0; i < stats_iter->second.size(); ++i) {
+ float number;
+ std::stringstream stream(stats_iter->second[i]);
+ stream >> number;
+ if (stream.fail())
+ continue;
+ VerifyGraphDataPoint(
+ pc.getIdString(), graph_id, i, stats_iter->second[i]);
+ }
+ }
+ }
+ }
+
+ // Verifies that the graph data point at index |index| has value |value|.
+ void VerifyGraphDataPoint(const string& pc_id, const string& graph_id,
+ int index, const string& value) {
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "graphViews['" + pc_id + "-" + graph_id + "'] != null)",
+ &result));
+ EXPECT_TRUE(result);
+
+ std::stringstream ss;
+ ss << "var dp = peerConnectionDataStore['" << pc_id << "']"
+ ".getDataSeries('" << graph_id << "').dataPoints_[" << index << "];"
+ "window.domAutomationController.send(dp.value.toString())";
+ string actual_value;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(), ss.str(), &actual_value));
+ EXPECT_EQ(value, actual_value);
+ }
+
+ // Get the JSON string of the ssrc info from the page.
+ string GetSsrcInfo(const string& ssrc_id) {
+ string result;
+ EXPECT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send(JSON.stringify("
+ "ssrcInfoManager.streamInfoContainer_['" + ssrc_id + "']))",
+ &result));
+ return result;
+ }
+
+ int GetSsrcInfoBlockCount(Shell* shell) {
+ int count = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ shell->web_contents(),
+ "window.domAutomationController.send("
+ "document.getElementsByClassName("
+ "ssrcInfoManager.SSRC_INFO_BLOCK_CLASS).length);",
+ &count));
+ return count;
+ }
+
+ // Verifies |dump| contains |peer_connection_number| peer connection dumps,
+ // each containing |update_number| updates and |stats_number| stats tables.
+ void VerifyPageDumpStructure(base::Value* dump,
+ int peer_connection_number,
+ int update_number,
+ int stats_number) {
+ EXPECT_NE((base::Value*)NULL, dump);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, dump->GetType());
+
+ base::DictionaryValue* dict_dump =
+ static_cast<base::DictionaryValue*>(dump);
+ EXPECT_EQ((size_t) peer_connection_number, dict_dump->size());
+
+ base::DictionaryValue::Iterator it(*dict_dump);
+ for (; !it.IsAtEnd(); it.Advance()) {
+ base::Value* value = NULL;
+ dict_dump->Get(it.key(), &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+ base::DictionaryValue* pc_dump =
+ static_cast<base::DictionaryValue*>(value);
+ EXPECT_TRUE(pc_dump->HasKey("updateLog"));
+ EXPECT_TRUE(pc_dump->HasKey("stats"));
+
+ // Verifies the number of updates.
+ pc_dump->Get("updateLog", &value);
+ EXPECT_EQ(base::Value::TYPE_LIST, value->GetType());
+ base::ListValue* list = static_cast<base::ListValue*>(value);
+ EXPECT_EQ((size_t) update_number, list->GetSize());
+
+ // Verifies the number of stats tables.
+ pc_dump->Get("stats", &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+ base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
+ EXPECT_EQ((size_t) stats_number, dict->size());
+ }
+ }
+
+ // Verifies |dump| contains the correct statsTable and statsDataSeries for
+ // |pc|.
+ void VerifyStatsDump(base::Value* dump,
+ const PeerConnectionEntry& pc,
+ const string& report_type,
+ const string& report_id,
+ const StatsUnit& stats) {
+ EXPECT_NE((base::Value*)NULL, dump);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, dump->GetType());
+
+ base::DictionaryValue* dict_dump =
+ static_cast<base::DictionaryValue*>(dump);
+ base::Value* value = NULL;
+ dict_dump->Get(pc.getIdString(), &value);
+ base::DictionaryValue* pc_dump = static_cast<base::DictionaryValue*>(value);
+
+ // Verifies there is one data series per stats name.
+ value = NULL;
+ pc_dump->Get("stats", &value);
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+
+ base::DictionaryValue* dataSeries =
+ static_cast<base::DictionaryValue*>(value);
+ EXPECT_EQ(stats.values.size(), dataSeries->size());
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, AddAndRemovePeerConnection) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ // Add two PeerConnections and then remove them.
+ PeerConnectionEntry pc_1(1, 0);
+ ExecuteAddPeerConnectionJs(pc_1);
+ VerifyPeerConnectionEntry(pc_1);
+
+ PeerConnectionEntry pc_2(2, 1);
+ ExecuteAddPeerConnectionJs(pc_2);
+ VerifyPeerConnectionEntry(pc_2);
+
+ ExecuteRemovePeerConnectionJs(pc_1);
+ VerifyNoElementWithId(pc_1.getIdString());
+ VerifyPeerConnectionEntry(pc_2);
+
+ ExecuteRemovePeerConnectionJs(pc_2);
+ VerifyNoElementWithId(pc_2.getIdString());
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, UpdateAllPeerConnections) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc_0(1, 0);
+ pc_0.AddEvent("e1", "v1");
+ pc_0.AddEvent("e2", "v2");
+ PeerConnectionEntry pc_1(1, 1);
+ pc_1.AddEvent("e3", "v3");
+ pc_1.AddEvent("e4", "v4");
+ string pc_array = "[" + pc_0.getAllUpdateString() + ", " +
+ pc_1.getAllUpdateString() + "]";
+ EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+ VerifyPeerConnectionEntry(pc_0);
+ VerifyPeerConnectionEntry(pc_1);
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, UpdatePeerConnection) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ // Add one PeerConnection and send one update.
+ PeerConnectionEntry pc_1(1, 0);
+ ExecuteAddPeerConnectionJs(pc_1);
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_1, "e1", "v1");
+
+ // Add another PeerConnection and send two updates.
+ PeerConnectionEntry pc_2(1, 1);
+ ExecuteAddPeerConnectionJs(pc_2);
+
+ SsrcEntry ssrc1, ssrc2;
+ ssrc1.id = "ssrcid1";
+ ssrc1.properties["msid"] = "mymsid";
+ ssrc2.id = "ssrcid2";
+ ssrc2.properties["label"] = "mylabel";
+ ssrc2.properties["cname"] = "mycname";
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_2, "setRemoteDescription",
+ ssrc1.GetSsrcAttributeString());
+
+ ExecuteAndVerifyUpdatePeerConnection(pc_2, "setLocalDescription",
+ ssrc2.GetSsrcAttributeString());
+
+ EXPECT_EQ(ssrc1.GetAsJSON(), GetSsrcInfo(ssrc1.id));
+ EXPECT_EQ(ssrc2.GetAsJSON(), GetSsrcInfo(ssrc2.id));
+
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["ssrc"] = ssrc1.id;
+ ExecuteAndVerifyAddStats(pc_2, "ssrc", "dummyId", stats);
+ EXPECT_GT(GetSsrcInfoBlockCount(shell()), 0);
+}
+
+// Tests that adding random named stats updates the dataSeries and graphs.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, AddStats) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ const string type = "ssrc";
+ const string id = "ssrc-1234";
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["trackId"] = "abcd";
+ stats.values["bitrate"] = "2000";
+ stats.values["framerate"] = "30";
+
+ // Add new stats and verify the stats table and graphs.
+ ExecuteAndVerifyAddStats(pc, type, id, stats);
+ VerifyStatsGraph(pc);
+
+ // Update existing stats and verify the stats table and graphs.
+ stats.values["bitrate"] = "2001";
+ stats.values["framerate"] = "31";
+ ExecuteAndVerifyAddStats(pc, type, id, stats);
+ VerifyStatsGraph(pc);
+}
+
+// Tests that the bandwidth estimation values are drawn on a single graph.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, BweCompoundGraph) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ stats.values["googAvailableSendBandwidth"] = "1000000";
+ stats.values["googTargetEncBitrate"] = "1000";
+ stats.values["googActualEncBitrate"] = "1000000";
+ stats.values["googRetransmitBitrate"] = "10";
+ stats.values["googTransmitBitrate"] = "1000000";
+ const string stats_type = "bwe";
+ const string stats_id = "videobwe";
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ string graph_id =
+ pc.getIdString() + "-" + stats_id + "-bweCompound";
+ bool result = false;
+ // Verify that the bweCompound graph exists.
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ " graphViews['" + graph_id + "'] != null)",
+ &result));
+ EXPECT_TRUE(result);
+
+ // Verify that the bweCompound graph contains multiple dataSeries.
+ int count = 0;
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ " graphViews['" + graph_id + "'].getDataSeriesCount())",
+ &count));
+ EXPECT_EQ((int)stats.values.size(), count);
+}
+
+// Tests that the total packet/byte count is converted to count per second,
+// and the converted data is drawn.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, ConvertedGraphs) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc(1, 0);
+ ExecuteAddPeerConnectionJs(pc);
+
+ const string stats_type = "s";
+ const string stats_id = "1";
+ const int num_converted_stats = 4;
+ const string stats_names[] =
+ {"packetsSent", "bytesSent", "packetsReceived", "bytesReceived"};
+ const string converted_names[] =
+ {"packetsSentPerSecond", "bitsSentPerSecond",
+ "packetsReceivedPerSecond", "bitsReceivedPerSecond"};
+ const string first_value = "1000";
+ const string second_value = "2000";
+ const string converted_values[] = {"1000", "8000", "1000", "8000"};
+
+ // Send the first data point.
+ StatsUnit stats = {FAKE_TIME_STAMP};
+ for (int i = 0; i < num_converted_stats; ++i)
+ stats.values[stats_names[i]] = first_value;
+
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ // Send the second data point at 1000ms after the first data point.
+ stats.timestamp += 1000;
+ for (int i = 0; i < num_converted_stats; ++i)
+ stats.values[stats_names[i]] = second_value;
+ ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);
+
+ // Verifies the graph data matches converted_values.
+ for (int i = 0; i < num_converted_stats; ++i) {
+ VerifyGraphDataPoint(pc.getIdString(), stats_id + "-" + converted_names[i],
+ 1, converted_values[i]);
+ }
+}
+
+// Timing out on ARM linux bot: http://crbug.com/238490
+// Disabling due to failure on Linux, Mac, Win: http://crbug.com/272413
+// Sanity check of the page content under a real PeerConnection call.
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest,
+ DISABLED_WithRealPeerConnectionCall) {
+ // Start a peerconnection call in the first window.
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
+ NavigateToURL(shell(), url);
+ ASSERT_TRUE(ExecuteJavascript("call({video:true});"));
+ ExpectTitle("OK");
+
+ // Open webrtc-internals in the second window.
+ GURL url2("chrome://webrtc-internals");
+ Shell* shell2 = CreateBrowser();
+ NavigateToURL(shell2, url2);
+
+ const int NUMBER_OF_PEER_CONNECTIONS = 2;
+
+ // Verifies the number of peerconnections.
+ int count = 0;
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send("
+ "$('peer-connections-list').getElementsByTagName('li').length);",
+ &count));
+ EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
+
+ // Verifies the the event tables.
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table').length);",
+ &count));
+ EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table')[0].rows.length);",
+ &count));
+ EXPECT_GT(count, 1);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send($('peer-connections-list')"
+ ".getElementsByClassName('update-log-table')[1].rows.length);",
+ &count));
+ EXPECT_GT(count, 1);
+
+ // Wait until the stats table containers are created.
+ count = 0;
+ while (count != NUMBER_OF_PEER_CONNECTIONS) {
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell2->web_contents(),
+ "window.domAutomationController.send("
+ "$('peer-connections-list').getElementsByClassName("
+ "'stats-table-container').length);",
+ &count));
+ }
+
+ // Verifies each stats table having more than one rows.
+ bool result = false;
+ ASSERT_TRUE(ExecuteScriptAndExtractBool(
+ shell2->web_contents(),
+ "var tableContainers = $('peer-connections-list')"
+ ".getElementsByClassName('stats-table-container');"
+ "var result = true;"
+ "for (var i = 0; i < tableContainers.length && result; ++i) {"
+ "var tables = tableContainers[i].getElementsByTagName('table');"
+ "for (var j = 0; j < tables.length && result; ++j) {"
+ "result = (tables[j].rows.length > 1);"
+ "}"
+ "if (!result) {"
+ "console.log(tableContainers[i].innerHTML);"
+ "}"
+ "}"
+ "window.domAutomationController.send(result);",
+ &result));
+
+ EXPECT_TRUE(result);
+
+ count = GetSsrcInfoBlockCount(shell2);
+ EXPECT_GT(count, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(WebRTCInternalsBrowserTest, CreatePageDump) {
+ GURL url("chrome://webrtc-internals");
+ NavigateToURL(shell(), url);
+
+ PeerConnectionEntry pc_0(1, 0);
+ pc_0.AddEvent("e1", "v1");
+ pc_0.AddEvent("e2", "v2");
+ PeerConnectionEntry pc_1(1, 1);
+ pc_1.AddEvent("e3", "v3");
+ pc_1.AddEvent("e4", "v4");
+ string pc_array =
+ "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
+ EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+
+ // Verifies the peer connection data store can be created without stats.
+ string dump_json;
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "JSON.stringify(peerConnectionDataStore));",
+ &dump_json));
+ scoped_ptr<base::Value> dump;
+ dump.reset(base::JSONReader::Read(dump_json));
+ VerifyPageDumpStructure(dump.get(),
+ 2 /*peer_connection_number*/,
+ 2 /*update_number*/,
+ 0 /*stats_number*/);
+
+ // Adds a stats report.
+ const string type = "dummy";
+ const string id = "1234";
+ StatsUnit stats = { FAKE_TIME_STAMP };
+ stats.values["bitrate"] = "2000";
+ stats.values["framerate"] = "30";
+ ExecuteAndVerifyAddStats(pc_0, type, id, stats);
+
+ ASSERT_TRUE(ExecuteScriptAndExtractString(
+ shell()->web_contents(),
+ "window.domAutomationController.send("
+ "JSON.stringify(peerConnectionDataStore));",
+ &dump_json));
+ dump.reset(base::JSONReader::Read(dump_json));
+ VerifyStatsDump(dump.get(), pc_0, type, id, stats);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_message_handler.cc b/chromium/content/browser/media/webrtc_internals_message_handler.cc
new file mode 100644
index 00000000000..2ba75227380
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_message_handler.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals_message_handler.h"
+
+#include "content/browser/media/webrtc_internals.h"
+#include "content/common/media/peer_connection_tracker_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+
+namespace content {
+
+WebRTCInternalsMessageHandler::WebRTCInternalsMessageHandler() {
+ WebRTCInternals::GetInstance()->AddObserver(this);
+}
+
+WebRTCInternalsMessageHandler::~WebRTCInternalsMessageHandler() {
+ WebRTCInternals::GetInstance()->RemoveObserver(this);
+}
+
+void WebRTCInternalsMessageHandler::RegisterMessages() {
+ web_ui()->RegisterMessageCallback("getAllUpdates",
+ base::Bind(&WebRTCInternalsMessageHandler::OnGetAllUpdates,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("getAllStats",
+ base::Bind(&WebRTCInternalsMessageHandler::OnGetAllStats,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("startRtpRecording",
+ base::Bind(&WebRTCInternalsMessageHandler::OnStartRtpRecording,
+ base::Unretained(this)));
+
+ web_ui()->RegisterMessageCallback("stopRtpRecording",
+ base::Bind(&WebRTCInternalsMessageHandler::OnStopRtpRecording,
+ base::Unretained(this)));
+}
+
+void WebRTCInternalsMessageHandler::OnGetAllUpdates(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->SendAllUpdates();
+}
+
+void WebRTCInternalsMessageHandler::OnGetAllStats(const base::ListValue* list) {
+ for (RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ i.GetCurrentValue()->Send(new PeerConnectionTracker_GetAllStats());
+ }
+}
+
+void WebRTCInternalsMessageHandler::OnStartRtpRecording(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->StartRtpRecording();
+}
+
+void WebRTCInternalsMessageHandler::OnStopRtpRecording(
+ const base::ListValue* list) {
+ WebRTCInternals::GetInstance()->StopRtpRecording();
+}
+
+void WebRTCInternalsMessageHandler::OnUpdate(const std::string& command,
+ const base::Value* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::vector<const base::Value*> args_vector;
+ args_vector.push_back(args);
+ string16 update = WebUI::GetJavascriptCall(command, args_vector);
+
+ RenderViewHost* host = web_ui()->GetWebContents()->GetRenderViewHost();
+ if (host)
+ host->ExecuteJavascriptInWebFrame(string16(), update);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_message_handler.h b/chromium/content/browser/media/webrtc_internals_message_handler.h
new file mode 100644
index 00000000000..ee2059a646d
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_message_handler.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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 CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace content {
+
+// This class handles messages to and from WebRTCInternalsUI.
+// It delegates all its work to WebRTCInternalsProxy on the IO thread.
+class WebRTCInternalsMessageHandler : public WebUIMessageHandler,
+ public WebRTCInternalsUIObserver{
+ public:
+ WebRTCInternalsMessageHandler();
+ virtual ~WebRTCInternalsMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // WebRTCInternalsUIObserver override.
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* args) OVERRIDE;
+
+ // Javascript message handler.
+ void OnGetAllUpdates(const base::ListValue* list);
+ void OnGetAllStats(const base::ListValue* list);
+ void OnStartRtpRecording(const base::ListValue* list);
+ void OnStopRtpRecording(const base::ListValue* list);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRTCInternalsMessageHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_MESSAGE_HANDLER_H_
diff --git a/chromium/content/browser/media/webrtc_internals_ui.cc b/chromium/content/browser/media/webrtc_internals_ui.cc
new file mode 100644
index 00000000000..5c4adc591a9
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 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 "content/browser/media/webrtc_internals_ui.h"
+
+#include "content/browser/media/webrtc_internals_message_handler.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/url_constants.h"
+#include "grit/content_resources.h"
+
+namespace content {
+namespace {
+
+WebUIDataSource* CreateWebRTCInternalsHTMLSource() {
+ WebUIDataSource* source =
+ WebUIDataSource::Create(kChromeUIWebRTCInternalsHost);
+
+ source->SetJsonPath("strings.js");
+ source->AddResourcePath("webrtc_internals.js", IDR_WEBRTC_INTERNALS_JS);
+ source->SetDefaultResource(IDR_WEBRTC_INTERNALS_HTML);
+ return source;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// WebRTCInternalsUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+WebRTCInternalsUI::WebRTCInternalsUI(WebUI* web_ui)
+ : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new WebRTCInternalsMessageHandler());
+
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateWebRTCInternalsHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media/webrtc_internals_ui.h b/chromium/content/browser/media/webrtc_internals_ui.h
new file mode 100644
index 00000000000..29745b18681
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 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 CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+// The implementation for the chrome://webrtc-internals page.
+class WebRTCInternalsUI : public WebUIController {
+ public:
+ explicit WebRTCInternalsUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRTCInternalsUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_H_
diff --git a/chromium/content/browser/media/webrtc_internals_ui_observer.h b/chromium/content/browser/media/webrtc_internals_ui_observer.h
new file mode 100644
index 00000000000..8d2c60e63b8
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_ui_observer.h
@@ -0,0 +1,28 @@
+// Copyright (c) 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 CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
+#define CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
+
+#include <string>
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace content {
+
+// Implement this interface to receive WebRTCInternals updates.
+class WebRTCInternalsUIObserver {
+ public:
+ virtual ~WebRTCInternalsUIObserver() {}
+
+ // This is called on the browser IO thread.
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* args) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_WEBRTC_INTERNALS_UI_OBSERVER_H_
diff --git a/chromium/content/browser/media/webrtc_internals_unittest.cc b/chromium/content/browser/media/webrtc_internals_unittest.cc
new file mode 100644
index 00000000000..a00ccb9877c
--- /dev/null
+++ b/chromium/content/browser/media/webrtc_internals_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 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 "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/browser/media/webrtc_internals.h"
+#include "content/browser/media/webrtc_internals_ui_observer.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+static const std::string kContraints = "c";
+static const std::string kServers = "s";
+static const std::string kUrl = "u";
+
+class MockWebRTCInternalsProxy : public WebRTCInternalsUIObserver {
+ public:
+ virtual void OnUpdate(const std::string& command,
+ const base::Value* value) OVERRIDE {
+ command_ = command;
+ value_.reset(value->DeepCopy());
+ }
+
+ std::string command() {
+ return command_;
+ }
+
+ base::Value* value() {
+ return value_.get();
+ }
+
+ private:
+ std::string command_;
+ scoped_ptr<base::Value> value_;
+};
+
+class WebRTCInternalsTest : public testing::Test {
+ public:
+ WebRTCInternalsTest() : io_thread_(BrowserThread::UI, &io_loop_) {}
+
+ protected:
+ std::string ExpectedInfo(std::string prefix,
+ std::string id,
+ std::string suffix) {
+ static const std::string kstatic_part1 = std::string(
+ "{\"constraints\":\"c\",");
+ static const std::string kstatic_part2 = std::string(
+ ",\"servers\":\"s\",\"url\":\"u\"}");
+ return prefix + kstatic_part1 + id + kstatic_part2 + suffix;
+ }
+
+ base::MessageLoop io_loop_;
+ TestBrowserThread io_thread_;
+};
+
+} // namespace
+
+TEST_F(WebRTCInternalsTest, AddRemoveObserver) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 3, 4, kUrl, kServers, kContraints);
+ EXPECT_EQ("", observer->command());
+
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(3, 4);
+}
+
+TEST_F(WebRTCInternalsTest, SendAddPeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+ EXPECT_EQ("addPeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ std::string value;
+ EXPECT_TRUE(dict->GetString("url", &value));
+ EXPECT_EQ(kUrl, value);
+ EXPECT_TRUE(dict->GetString("servers", &value));
+ EXPECT_EQ(kServers, value);
+ EXPECT_TRUE(dict->GetString("constraints", &value));
+ EXPECT_EQ(kContraints, value);
+
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+}
+
+TEST_F(WebRTCInternalsTest, SendRemovePeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+ EXPECT_EQ("removePeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+}
+
+TEST_F(WebRTCInternalsTest, SendUpdatePeerConnectionUpdate) {
+ scoped_ptr<MockWebRTCInternalsProxy> observer(
+ new MockWebRTCInternalsProxy());
+ WebRTCInternals::GetInstance()->AddObserver(observer.get());
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ 0, 1, 2, kUrl, kServers, kContraints);
+
+ const std::string update_type = "fakeType";
+ const std::string update_value = "fakeValue";
+ WebRTCInternals::GetInstance()->OnUpdatePeerConnection(
+ 1, 2, update_type, update_value);
+
+ EXPECT_EQ("updatePeerConnection", observer->command());
+
+ base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(observer->value()->GetAsDictionary(&dict));
+
+ int int_value;
+ EXPECT_TRUE(dict->GetInteger("pid", &int_value));
+ EXPECT_EQ(1, int_value);
+ EXPECT_TRUE(dict->GetInteger("lid", &int_value));
+ EXPECT_EQ(2, int_value);
+
+ std::string value;
+ EXPECT_TRUE(dict->GetString("type", &value));
+ EXPECT_EQ(update_type, value);
+ EXPECT_TRUE(dict->GetString("value", &value));
+ EXPECT_EQ(update_value, value);
+
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(1, 2);
+ WebRTCInternals::GetInstance()->RemoveObserver(observer.get());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/media_devices_monitor.cc b/chromium/content/browser/media_devices_monitor.cc
new file mode 100644
index 00000000000..72c3b4b1cef
--- /dev/null
+++ b/chromium/content/browser/media_devices_monitor.cc
@@ -0,0 +1,29 @@
+// 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 "content/public/browser/media_devices_monitor.h"
+
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+namespace {
+void EnsureMonitorCaptureDevicesInternal(
+ MediaStreamManager* media_stream_manager) {
+ media_stream_manager->EnumerateDevices(
+ NULL, -1, -1, -1, MEDIA_DEVICE_AUDIO_CAPTURE, GURL());
+}
+}
+
+void EnsureMonitorCaptureDevices() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&EnsureMonitorCaptureDevicesInternal,
+ BrowserMainLoop::GetInstance()->media_stream_manager()));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/mime_registry_message_filter.cc b/chromium/content/browser/mime_registry_message_filter.cc
new file mode 100644
index 00000000000..f23cc004e4b
--- /dev/null
+++ b/chromium/content/browser/mime_registry_message_filter.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/mime_registry_message_filter.h"
+
+#include "content/common/mime_registry_messages.h"
+#include "net/base/mime_util.h"
+
+namespace content {
+
+MimeRegistryMessageFilter::MimeRegistryMessageFilter() {
+}
+
+MimeRegistryMessageFilter::~MimeRegistryMessageFilter() {
+}
+
+void MimeRegistryMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ if (IPC_MESSAGE_CLASS(message) == MimeRegistryMsgStart)
+ *thread = BrowserThread::FILE;
+}
+
+bool MimeRegistryMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MimeRegistryMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(MimeRegistryMsg_GetMimeTypeFromExtension,
+ OnGetMimeTypeFromExtension)
+ IPC_MESSAGE_HANDLER(MimeRegistryMsg_GetMimeTypeFromFile,
+ OnGetMimeTypeFromFile)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void MimeRegistryMessageFilter::OnGetMimeTypeFromExtension(
+ const base::FilePath::StringType& ext, std::string* mime_type) {
+ net::GetMimeTypeFromExtension(ext, mime_type);
+}
+
+void MimeRegistryMessageFilter::OnGetMimeTypeFromFile(
+ const base::FilePath& file_path, std::string* mime_type) {
+ net::GetMimeTypeFromFile(file_path, mime_type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/mime_registry_message_filter.h b/chromium/content/browser/mime_registry_message_filter.h
new file mode 100644
index 00000000000..e86fe190b1d
--- /dev/null
+++ b/chromium/content/browser/mime_registry_message_filter.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_MIME_REGISTRY_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_MIME_REGISTRY_MESSAGE_FILTER_H_
+
+#include "base/files/file_path.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class MimeRegistryMessageFilter : public BrowserMessageFilter {
+ public:
+ MimeRegistryMessageFilter();
+
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~MimeRegistryMessageFilter();
+
+ void OnGetMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type);
+ void OnGetMimeTypeFromFile(const base::FilePath& file_path,
+ std::string* mime_type);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MIME_REGISTRY_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/net/OWNERS b/chromium/content/browser/net/OWNERS
new file mode 100644
index 00000000000..82e44d8afb0
--- /dev/null
+++ b/chromium/content/browser/net/OWNERS
@@ -0,0 +1 @@
+erikwright@chromium.org
diff --git a/chromium/content/browser/net/browser_online_state_observer.cc b/chromium/content/browser/net/browser_online_state_observer.cc
new file mode 100644
index 00000000000..2276fd70a0a
--- /dev/null
+++ b/chromium/content/browser/net/browser_online_state_observer.cc
@@ -0,0 +1,29 @@
+// 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 "content/browser/net/browser_online_state_observer.h"
+
+#include "content/common/view_messages.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+
+namespace content {
+
+BrowserOnlineStateObserver::BrowserOnlineStateObserver() {
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+}
+
+BrowserOnlineStateObserver::~BrowserOnlineStateObserver() {
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+}
+
+void BrowserOnlineStateObserver::OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ it.GetCurrentValue()->Send(new ViewMsg_NetworkStateChanged(
+ type != net::NetworkChangeNotifier::CONNECTION_NONE));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/browser_online_state_observer.h b/chromium/content/browser/net/browser_online_state_observer.h
new file mode 100644
index 00000000000..fbb0db4bf71
--- /dev/null
+++ b/chromium/content/browser/net/browser_online_state_observer.h
@@ -0,0 +1,31 @@
+// 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 CONTENT_BROWSER_NET_BROWSER_ONLINE_STATE_OBSERVER_H_
+#define CONTENT_BROWSER_NET_BROWSER_ONLINE_STATE_OBSERVER_H_
+
+#include "base/basictypes.h"
+#include "net/base/network_change_notifier.h"
+
+namespace content {
+
+// Listens for changes to the online state and manages sending
+// updates to each RenderProcess via RenderProcessHost IPC.
+class BrowserOnlineStateObserver
+ : public net::NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ BrowserOnlineStateObserver();
+ virtual ~BrowserOnlineStateObserver();
+
+ // ConnectionTypeObserver implementation.
+ virtual void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BrowserOnlineStateObserver);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_NET_BROWSER_ONLINE_STATE_OBSERVER_H_
diff --git a/chromium/content/browser/net/sqlite_persistent_cookie_store.cc b/chromium/content/browser/net/sqlite_persistent_cookie_store.cc
new file mode 100644
index 00000000000..c23692d4750
--- /dev/null
+++ b/chromium/content/browser/net/sqlite_persistent_cookie_store.cc
@@ -0,0 +1,1221 @@
+// 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 "content/browser/net/sqlite_persistent_cookie_store.h"
+
+#include <list>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/cookie_store_factory.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_util.h"
+#include "sql/error_delegate_util.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "third_party/sqlite/sqlite3.h"
+#include "url/gurl.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+using base::Time;
+
+namespace content {
+
+// This class is designed to be shared between any client thread and the
+// background task runner. It batches operations and commits them on a timer.
+//
+// SQLitePersistentCookieStore::Load is called to load all cookies. It
+// delegates to Backend::Load, which posts a Backend::LoadAndNotifyOnDBThread
+// task to the background runner. This task calls Backend::ChainLoadCookies(),
+// which repeatedly posts itself to the BG runner to load each eTLD+1's cookies
+// in separate tasks. When this is complete, Backend::CompleteLoadOnIOThread is
+// posted to the client runner, which notifies the caller of
+// SQLitePersistentCookieStore::Load that the load is complete.
+//
+// If a priority load request is invoked via SQLitePersistentCookieStore::
+// LoadCookiesForKey, it is delegated to Backend::LoadCookiesForKey, which posts
+// Backend::LoadKeyAndNotifyOnDBThread to the BG runner. That routine loads just
+// that single domain key (eTLD+1)'s cookies, and posts a Backend::
+// CompleteLoadForKeyOnIOThread to the client runner to notify the caller of
+// SQLitePersistentCookieStore::LoadCookiesForKey that that load is complete.
+//
+// Subsequent to loading, mutations may be queued by any thread using
+// AddCookie, UpdateCookieAccessTime, and DeleteCookie. These are flushed to
+// disk on the BG runner every 30 seconds, 512 operations, or call to Flush(),
+// whichever occurs first.
+class SQLitePersistentCookieStore::Backend
+ : public base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend> {
+ public:
+ Backend(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ bool restore_old_session_cookies,
+ quota::SpecialStoragePolicy* special_storage_policy)
+ : path_(path),
+ num_pending_(0),
+ force_keep_session_state_(false),
+ initialized_(false),
+ corruption_detected_(false),
+ restore_old_session_cookies_(restore_old_session_cookies),
+ special_storage_policy_(special_storage_policy),
+ num_cookies_read_(0),
+ client_task_runner_(client_task_runner),
+ background_task_runner_(background_task_runner),
+ num_priority_waiting_(0),
+ total_priority_requests_(0) {}
+
+ // Creates or loads the SQLite database.
+ void Load(const LoadedCallback& loaded_callback);
+
+ // Loads cookies for the domain key (eTLD+1).
+ void LoadCookiesForKey(const std::string& domain,
+ const LoadedCallback& loaded_callback);
+
+ // Batch a cookie addition.
+ void AddCookie(const net::CanonicalCookie& cc);
+
+ // Batch a cookie access time update.
+ void UpdateCookieAccessTime(const net::CanonicalCookie& cc);
+
+ // Batch a cookie deletion.
+ void DeleteCookie(const net::CanonicalCookie& cc);
+
+ // Commit pending operations as soon as possible.
+ void Flush(const base::Closure& callback);
+
+ // Commit any pending operations and close the database. This must be called
+ // before the object is destructed.
+ void Close();
+
+ void SetForceKeepSessionState();
+
+ private:
+ friend class base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend>;
+
+ // You should call Close() before destructing this object.
+ ~Backend() {
+ DCHECK(!db_.get()) << "Close should have already been called.";
+ DCHECK(num_pending_ == 0 && pending_.empty());
+ }
+
+ // Database upgrade statements.
+ bool EnsureDatabaseVersion();
+
+ class PendingOperation {
+ public:
+ typedef enum {
+ COOKIE_ADD,
+ COOKIE_UPDATEACCESS,
+ COOKIE_DELETE,
+ } OperationType;
+
+ PendingOperation(OperationType op, const net::CanonicalCookie& cc)
+ : op_(op), cc_(cc) { }
+
+ OperationType op() const { return op_; }
+ const net::CanonicalCookie& cc() const { return cc_; }
+
+ private:
+ OperationType op_;
+ net::CanonicalCookie cc_;
+ };
+
+ private:
+ // Creates or loads the SQLite database on background runner.
+ void LoadAndNotifyInBackground(const LoadedCallback& loaded_callback,
+ const base::Time& posted_at);
+
+ // Loads cookies for the domain key (eTLD+1) on background runner.
+ void LoadKeyAndNotifyInBackground(const std::string& domains,
+ const LoadedCallback& loaded_callback,
+ const base::Time& posted_at);
+
+ // Notifies the CookieMonster when loading completes for a specific domain key
+ // or for all domain keys. Triggers the callback and passes it all cookies
+ // that have been loaded from DB since last IO notification.
+ void Notify(const LoadedCallback& loaded_callback, bool load_success);
+
+ // Sends notification when the entire store is loaded, and reports metrics
+ // for the total time to load and aggregated results from any priority loads
+ // that occurred.
+ void CompleteLoadInForeground(const LoadedCallback& loaded_callback,
+ bool load_success);
+
+ // Sends notification when a single priority load completes. Updates priority
+ // load metric data. The data is sent only after the final load completes.
+ void CompleteLoadForKeyInForeground(const LoadedCallback& loaded_callback,
+ bool load_success);
+
+ // Sends all metrics, including posting a ReportMetricsInBackground task.
+ // Called after all priority and regular loading is complete.
+ void ReportMetrics();
+
+ // Sends background-runner owned metrics (i.e., the combined duration of all
+ // BG-runner tasks).
+ void ReportMetricsInBackground();
+
+ // Initialize the data base.
+ bool InitializeDatabase();
+
+ // Loads cookies for the next domain key from the DB, then either reschedules
+ // itself or schedules the provided callback to run on the client runner (if
+ // all domains are loaded).
+ void ChainLoadCookies(const LoadedCallback& loaded_callback);
+
+ // Load all cookies for a set of domains/hosts
+ bool LoadCookiesForDomains(const std::set<std::string>& key);
+
+ // Batch a cookie operation (add or delete)
+ void BatchOperation(PendingOperation::OperationType op,
+ const net::CanonicalCookie& cc);
+ // Commit our pending operations to the database.
+ void Commit();
+ // Close() executed on the background runner.
+ void InternalBackgroundClose();
+
+ void DeleteSessionCookiesOnStartup();
+
+ void DeleteSessionCookiesOnShutdown();
+
+ void DatabaseErrorCallback(int error, sql::Statement* stmt);
+ void KillDatabase();
+
+ void PostBackgroundTask(const tracked_objects::Location& origin,
+ const base::Closure& task);
+ void PostClientTask(const tracked_objects::Location& origin,
+ const base::Closure& task);
+
+ base::FilePath path_;
+ scoped_ptr<sql::Connection> db_;
+ sql::MetaTable meta_table_;
+
+ typedef std::list<PendingOperation*> PendingOperationsList;
+ PendingOperationsList pending_;
+ PendingOperationsList::size_type num_pending_;
+ // True if the persistent store should skip delete on exit rules.
+ bool force_keep_session_state_;
+ // Guard |cookies_|, |pending_|, |num_pending_|, |force_keep_session_state_|
+ base::Lock lock_;
+
+ // Temporary buffer for cookies loaded from DB. Accumulates cookies to reduce
+ // the number of messages sent to the client runner. Sent back in response to
+ // individual load requests for domain keys or when all loading completes.
+ std::vector<net::CanonicalCookie*> cookies_;
+
+ // Map of domain keys(eTLD+1) to domains/hosts that are to be loaded from DB.
+ std::map<std::string, std::set<std::string> > keys_to_load_;
+
+ // Map of (domain keys(eTLD+1), is secure cookie) to number of cookies in the
+ // database.
+ typedef std::pair<std::string, bool> CookieOrigin;
+ typedef std::map<CookieOrigin, int> CookiesPerOriginMap;
+ CookiesPerOriginMap cookies_per_origin_;
+
+ // Indicates if DB has been initialized.
+ bool initialized_;
+
+ // Indicates if the kill-database callback has been scheduled.
+ bool corruption_detected_;
+
+ // If false, we should filter out session cookies when reading the DB.
+ bool restore_old_session_cookies_;
+
+ // Policy defining what data is deleted on shutdown.
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+
+ // The cumulative time spent loading the cookies on the background runner.
+ // Incremented and reported from the background runner.
+ base::TimeDelta cookie_load_duration_;
+
+ // The total number of cookies read. Incremented and reported on the
+ // background runner.
+ int num_cookies_read_;
+
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ // Guards the following metrics-related properties (only accessed when
+ // starting/completing priority loads or completing the total load).
+ base::Lock metrics_lock_;
+ int num_priority_waiting_;
+ // The total number of priority requests.
+ int total_priority_requests_;
+ // The time when |num_priority_waiting_| incremented to 1.
+ base::Time current_priority_wait_start_;
+ // The cumulative duration of time when |num_priority_waiting_| was greater
+ // than 1.
+ base::TimeDelta priority_wait_duration_;
+
+ DISALLOW_COPY_AND_ASSIGN(Backend);
+};
+
+namespace {
+
+// Version number of the database.
+//
+// Version 6 adds cookie priorities. This allows developers to influence the
+// order in which cookies are evicted in order to meet domain cookie limits.
+//
+// Version 5 adds the columns has_expires and is_persistent, so that the
+// database can store session cookies as well as persistent cookies. Databases
+// of version 5 are incompatible with older versions of code. If a database of
+// version 5 is read by older code, session cookies will be treated as normal
+// cookies. Currently, these fields are written, but not read anymore.
+//
+// In version 4, we migrated the time epoch. If you open the DB with an older
+// version on Mac or Linux, the times will look wonky, but the file will likely
+// be usable. On Windows version 3 and 4 are the same.
+//
+// Version 3 updated the database to include the last access time, so we can
+// expire them in decreasing order of use when we've reached the maximum
+// number of cookies.
+const int kCurrentVersionNumber = 6;
+const int kCompatibleVersionNumber = 5;
+
+// Possible values for the 'priority' column.
+enum DBCookiePriority {
+ kCookiePriorityLow = 0,
+ kCookiePriorityMedium = 1,
+ kCookiePriorityHigh = 2,
+};
+
+DBCookiePriority CookiePriorityToDBCookiePriority(net::CookiePriority value) {
+ switch (value) {
+ case net::COOKIE_PRIORITY_LOW:
+ return kCookiePriorityLow;
+ case net::COOKIE_PRIORITY_MEDIUM:
+ return kCookiePriorityMedium;
+ case net::COOKIE_PRIORITY_HIGH:
+ return kCookiePriorityHigh;
+ }
+
+ NOTREACHED();
+ return kCookiePriorityMedium;
+}
+
+net::CookiePriority DBCookiePriorityToCookiePriority(DBCookiePriority value) {
+ switch (value) {
+ case kCookiePriorityLow:
+ return net::COOKIE_PRIORITY_LOW;
+ case kCookiePriorityMedium:
+ return net::COOKIE_PRIORITY_MEDIUM;
+ case kCookiePriorityHigh:
+ return net::COOKIE_PRIORITY_HIGH;
+ }
+
+ NOTREACHED();
+ return net::COOKIE_PRIORITY_DEFAULT;
+}
+
+// Increments a specified TimeDelta by the duration between this object's
+// constructor and destructor. Not thread safe. Multiple instances may be
+// created with the same delta instance as long as their lifetimes are nested.
+// The shortest lived instances have no impact.
+class IncrementTimeDelta {
+ public:
+ explicit IncrementTimeDelta(base::TimeDelta* delta) :
+ delta_(delta),
+ original_value_(*delta),
+ start_(base::Time::Now()) {}
+
+ ~IncrementTimeDelta() {
+ *delta_ = original_value_ + base::Time::Now() - start_;
+ }
+
+ private:
+ base::TimeDelta* delta_;
+ base::TimeDelta original_value_;
+ base::Time start_;
+
+ DISALLOW_COPY_AND_ASSIGN(IncrementTimeDelta);
+};
+
+// Initializes the cookies table, returning true on success.
+bool InitTable(sql::Connection* db) {
+ if (!db->DoesTableExist("cookies")) {
+ std::string stmt(base::StringPrintf(
+ "CREATE TABLE cookies ("
+ "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY,"
+ "host_key TEXT NOT NULL,"
+ "name TEXT NOT NULL,"
+ "value TEXT NOT NULL,"
+ "path TEXT NOT NULL,"
+ "expires_utc INTEGER NOT NULL,"
+ "secure INTEGER NOT NULL,"
+ "httponly INTEGER NOT NULL,"
+ "last_access_utc INTEGER NOT NULL, "
+ "has_expires INTEGER NOT NULL DEFAULT 1, "
+ "persistent INTEGER NOT NULL DEFAULT 1,"
+ "priority INTEGER NOT NULL DEFAULT %d)",
+ CookiePriorityToDBCookiePriority(net::COOKIE_PRIORITY_DEFAULT)));
+ if (!db->Execute(stmt.c_str()))
+ return false;
+ }
+
+ // Older code created an index on creation_utc, which is already
+ // primary key for the table.
+ if (!db->Execute("DROP INDEX IF EXISTS cookie_times"))
+ return false;
+
+ if (!db->Execute("CREATE INDEX IF NOT EXISTS domain ON cookies(host_key)"))
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+void SQLitePersistentCookieStore::Backend::Load(
+ const LoadedCallback& loaded_callback) {
+ // This function should be called only once per instance.
+ DCHECK(!db_.get());
+ PostBackgroundTask(FROM_HERE, base::Bind(
+ &Backend::LoadAndNotifyInBackground, this,
+ loaded_callback, base::Time::Now()));
+}
+
+void SQLitePersistentCookieStore::Backend::LoadCookiesForKey(
+ const std::string& key,
+ const LoadedCallback& loaded_callback) {
+ {
+ base::AutoLock locked(metrics_lock_);
+ if (num_priority_waiting_ == 0)
+ current_priority_wait_start_ = base::Time::Now();
+ num_priority_waiting_++;
+ total_priority_requests_++;
+ }
+
+ PostBackgroundTask(FROM_HERE, base::Bind(
+ &Backend::LoadKeyAndNotifyInBackground,
+ this, key, loaded_callback, base::Time::Now()));
+}
+
+void SQLitePersistentCookieStore::Backend::LoadAndNotifyInBackground(
+ const LoadedCallback& loaded_callback, const base::Time& posted_at) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ IncrementTimeDelta increment(&cookie_load_duration_);
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeLoadDBQueueWait",
+ base::Time::Now() - posted_at,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ if (!InitializeDatabase()) {
+ PostClientTask(FROM_HERE, base::Bind(
+ &Backend::CompleteLoadInForeground, this, loaded_callback, false));
+ } else {
+ ChainLoadCookies(loaded_callback);
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::LoadKeyAndNotifyInBackground(
+ const std::string& key,
+ const LoadedCallback& loaded_callback,
+ const base::Time& posted_at) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ IncrementTimeDelta increment(&cookie_load_duration_);
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeKeyLoadDBQueueWait",
+ base::Time::Now() - posted_at,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ bool success = false;
+ if (InitializeDatabase()) {
+ std::map<std::string, std::set<std::string> >::iterator
+ it = keys_to_load_.find(key);
+ if (it != keys_to_load_.end()) {
+ success = LoadCookiesForDomains(it->second);
+ keys_to_load_.erase(it);
+ } else {
+ success = true;
+ }
+ }
+
+ PostClientTask(FROM_HERE, base::Bind(
+ &SQLitePersistentCookieStore::Backend::CompleteLoadForKeyInForeground,
+ this, loaded_callback, success));
+}
+
+void SQLitePersistentCookieStore::Backend::CompleteLoadForKeyInForeground(
+ const LoadedCallback& loaded_callback,
+ bool load_success) {
+ DCHECK(client_task_runner_->RunsTasksOnCurrentThread());
+
+ Notify(loaded_callback, load_success);
+
+ {
+ base::AutoLock locked(metrics_lock_);
+ num_priority_waiting_--;
+ if (num_priority_waiting_ == 0) {
+ priority_wait_duration_ +=
+ base::Time::Now() - current_priority_wait_start_;
+ }
+ }
+
+}
+
+void SQLitePersistentCookieStore::Backend::ReportMetricsInBackground() {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeLoad",
+ cookie_load_duration_,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+}
+
+void SQLitePersistentCookieStore::Backend::ReportMetrics() {
+ PostBackgroundTask(FROM_HERE, base::Bind(
+ &SQLitePersistentCookieStore::Backend::ReportMetricsInBackground, this));
+
+ {
+ base::AutoLock locked(metrics_lock_);
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.PriorityBlockingTime",
+ priority_wait_duration_,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ UMA_HISTOGRAM_COUNTS_100(
+ "Cookie.PriorityLoadCount",
+ total_priority_requests_);
+
+ UMA_HISTOGRAM_COUNTS_10000(
+ "Cookie.NumberOfLoadedCookies",
+ num_cookies_read_);
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::CompleteLoadInForeground(
+ const LoadedCallback& loaded_callback, bool load_success) {
+ Notify(loaded_callback, load_success);
+
+ if (load_success)
+ ReportMetrics();
+}
+
+void SQLitePersistentCookieStore::Backend::Notify(
+ const LoadedCallback& loaded_callback,
+ bool load_success) {
+ DCHECK(client_task_runner_->RunsTasksOnCurrentThread());
+
+ std::vector<net::CanonicalCookie*> cookies;
+ {
+ base::AutoLock locked(lock_);
+ cookies.swap(cookies_);
+ }
+
+ loaded_callback.Run(cookies);
+}
+
+bool SQLitePersistentCookieStore::Backend::InitializeDatabase() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (initialized_ || corruption_detected_) {
+ // Return false if we were previously initialized but the DB has since been
+ // closed, or if corruption caused a database reset during initialization.
+ return db_ != NULL;
+ }
+
+ base::Time start = base::Time::Now();
+
+ const base::FilePath dir = path_.DirName();
+ if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) {
+ return false;
+ }
+
+ int64 db_size = 0;
+ if (file_util::GetFileSize(path_, &db_size))
+ UMA_HISTOGRAM_COUNTS("Cookie.DBSizeInKB", db_size / 1024 );
+
+ db_.reset(new sql::Connection);
+ db_->set_histogram_tag("Cookie");
+
+ // Unretained to avoid a ref loop with |db_|.
+ db_->set_error_callback(
+ base::Bind(&SQLitePersistentCookieStore::Backend::DatabaseErrorCallback,
+ base::Unretained(this)));
+
+ if (!db_->Open(path_)) {
+ NOTREACHED() << "Unable to open cookie DB.";
+ if (corruption_detected_)
+ db_->Raze();
+ meta_table_.Reset();
+ db_.reset();
+ return false;
+ }
+
+ if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
+ NOTREACHED() << "Unable to open cookie DB.";
+ if (corruption_detected_)
+ db_->Raze();
+ meta_table_.Reset();
+ db_.reset();
+ return false;
+ }
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeInitializeDB",
+ base::Time::Now() - start,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ start = base::Time::Now();
+
+ // Retrieve all the domains
+ sql::Statement smt(db_->GetUniqueStatement(
+ "SELECT DISTINCT host_key FROM cookies"));
+
+ if (!smt.is_valid()) {
+ if (corruption_detected_)
+ db_->Raze();
+ meta_table_.Reset();
+ db_.reset();
+ return false;
+ }
+
+ std::vector<std::string> host_keys;
+ while (smt.Step())
+ host_keys.push_back(smt.ColumnString(0));
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeLoadDomains",
+ base::Time::Now() - start,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ base::Time start_parse = base::Time::Now();
+
+ // Build a map of domain keys (always eTLD+1) to domains.
+ for (size_t idx = 0; idx < host_keys.size(); ++idx) {
+ const std::string& domain = host_keys[idx];
+ std::string key =
+ net::registry_controlled_domains::GetDomainAndRegistry(
+ domain,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+
+ keys_to_load_[key].insert(domain);
+ }
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeParseDomains",
+ base::Time::Now() - start_parse,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Cookie.TimeInitializeDomainMap",
+ base::Time::Now() - start,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50);
+
+ initialized_ = true;
+ return true;
+}
+
+void SQLitePersistentCookieStore::Backend::ChainLoadCookies(
+ const LoadedCallback& loaded_callback) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ IncrementTimeDelta increment(&cookie_load_duration_);
+
+ bool load_success = true;
+
+ if (!db_) {
+ // Close() has been called on this store.
+ load_success = false;
+ } else if (keys_to_load_.size() > 0) {
+ // Load cookies for the first domain key.
+ std::map<std::string, std::set<std::string> >::iterator
+ it = keys_to_load_.begin();
+ load_success = LoadCookiesForDomains(it->second);
+ keys_to_load_.erase(it);
+ }
+
+ // If load is successful and there are more domain keys to be loaded,
+ // then post a background task to continue chain-load;
+ // Otherwise notify on client runner.
+ if (load_success && keys_to_load_.size() > 0) {
+ PostBackgroundTask(FROM_HERE, base::Bind(
+ &Backend::ChainLoadCookies, this, loaded_callback));
+ } else {
+ PostClientTask(FROM_HERE, base::Bind(
+ &Backend::CompleteLoadInForeground, this,
+ loaded_callback, load_success));
+ if (load_success && !restore_old_session_cookies_)
+ DeleteSessionCookiesOnStartup();
+ }
+}
+
+bool SQLitePersistentCookieStore::Backend::LoadCookiesForDomains(
+ const std::set<std::string>& domains) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ sql::Statement smt;
+ if (restore_old_session_cookies_) {
+ smt.Assign(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "SELECT creation_utc, host_key, name, value, path, expires_utc, "
+ "secure, httponly, last_access_utc, has_expires, persistent, priority "
+ "FROM cookies WHERE host_key = ?"));
+ } else {
+ smt.Assign(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "SELECT creation_utc, host_key, name, value, path, expires_utc, "
+ "secure, httponly, last_access_utc, has_expires, persistent, priority "
+ "FROM cookies WHERE host_key = ? AND persistent = 1"));
+ }
+ if (!smt.is_valid()) {
+ smt.Clear(); // Disconnect smt_ref from db_.
+ meta_table_.Reset();
+ db_.reset();
+ return false;
+ }
+
+ std::vector<net::CanonicalCookie*> cookies;
+ std::set<std::string>::const_iterator it = domains.begin();
+ for (; it != domains.end(); ++it) {
+ smt.BindString(0, *it);
+ while (smt.Step()) {
+ scoped_ptr<net::CanonicalCookie> cc(new net::CanonicalCookie(
+ // The "source" URL is not used with persisted cookies.
+ GURL(), // Source
+ smt.ColumnString(2), // name
+ smt.ColumnString(3), // value
+ smt.ColumnString(1), // domain
+ smt.ColumnString(4), // path
+ Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc
+ Time::FromInternalValue(smt.ColumnInt64(5)), // expires_utc
+ Time::FromInternalValue(smt.ColumnInt64(8)), // last_access_utc
+ smt.ColumnInt(6) != 0, // secure
+ smt.ColumnInt(7) != 0, // httponly
+ DBCookiePriorityToCookiePriority(
+ static_cast<DBCookiePriority>(smt.ColumnInt(11))))); // priority
+ DLOG_IF(WARNING,
+ cc->CreationDate() > Time::Now()) << L"CreationDate too recent";
+ cookies_per_origin_[CookieOrigin(cc->Domain(), cc->IsSecure())]++;
+ cookies.push_back(cc.release());
+ ++num_cookies_read_;
+ }
+ smt.Reset(true);
+ }
+ {
+ base::AutoLock locked(lock_);
+ cookies_.insert(cookies_.end(), cookies.begin(), cookies.end());
+ }
+ return true;
+}
+
+bool SQLitePersistentCookieStore::Backend::EnsureDatabaseVersion() {
+ // Version check.
+ if (!meta_table_.Init(
+ db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
+ return false;
+ }
+
+ if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
+ LOG(WARNING) << "Cookie database is too new.";
+ return false;
+ }
+
+ int cur_version = meta_table_.GetVersionNumber();
+ if (cur_version == 2) {
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+ if (!db_->Execute("ALTER TABLE cookies ADD COLUMN last_access_utc "
+ "INTEGER DEFAULT 0") ||
+ !db_->Execute("UPDATE cookies SET last_access_utc = creation_utc")) {
+ LOG(WARNING) << "Unable to update cookie database to version 3.";
+ return false;
+ }
+ ++cur_version;
+ meta_table_.SetVersionNumber(cur_version);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(cur_version, kCompatibleVersionNumber));
+ transaction.Commit();
+ }
+
+ if (cur_version == 3) {
+ // The time epoch changed for Mac & Linux in this version to match Windows.
+ // This patch came after the main epoch change happened, so some
+ // developers have "good" times for cookies added by the more recent
+ // versions. So we have to be careful to only update times that are under
+ // the old system (which will appear to be from before 1970 in the new
+ // system). The magic number used below is 1970 in our time units.
+ sql::Transaction transaction(db_.get());
+ transaction.Begin();
+#if !defined(OS_WIN)
+ ignore_result(db_->Execute(
+ "UPDATE cookies "
+ "SET creation_utc = creation_utc + 11644473600000000 "
+ "WHERE rowid IN "
+ "(SELECT rowid FROM cookies WHERE "
+ "creation_utc > 0 AND creation_utc < 11644473600000000)"));
+ ignore_result(db_->Execute(
+ "UPDATE cookies "
+ "SET expires_utc = expires_utc + 11644473600000000 "
+ "WHERE rowid IN "
+ "(SELECT rowid FROM cookies WHERE "
+ "expires_utc > 0 AND expires_utc < 11644473600000000)"));
+ ignore_result(db_->Execute(
+ "UPDATE cookies "
+ "SET last_access_utc = last_access_utc + 11644473600000000 "
+ "WHERE rowid IN "
+ "(SELECT rowid FROM cookies WHERE "
+ "last_access_utc > 0 AND last_access_utc < 11644473600000000)"));
+#endif
+ ++cur_version;
+ meta_table_.SetVersionNumber(cur_version);
+ transaction.Commit();
+ }
+
+ if (cur_version == 4) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+ if (!db_->Execute("ALTER TABLE cookies "
+ "ADD COLUMN has_expires INTEGER DEFAULT 1") ||
+ !db_->Execute("ALTER TABLE cookies "
+ "ADD COLUMN persistent INTEGER DEFAULT 1")) {
+ LOG(WARNING) << "Unable to update cookie database to version 5.";
+ return false;
+ }
+ ++cur_version;
+ meta_table_.SetVersionNumber(cur_version);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(cur_version, kCompatibleVersionNumber));
+ transaction.Commit();
+ UMA_HISTOGRAM_TIMES("Cookie.TimeDatabaseMigrationToV5",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ if (cur_version == 5) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+ // Alter the table to add the priority column with a default value.
+ std::string stmt(base::StringPrintf(
+ "ALTER TABLE cookies ADD COLUMN priority INTEGER DEFAULT %d",
+ CookiePriorityToDBCookiePriority(net::COOKIE_PRIORITY_DEFAULT)));
+ if (!db_->Execute(stmt.c_str())) {
+ LOG(WARNING) << "Unable to update cookie database to version 6.";
+ return false;
+ }
+ ++cur_version;
+ meta_table_.SetVersionNumber(cur_version);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(cur_version, kCompatibleVersionNumber));
+ transaction.Commit();
+ UMA_HISTOGRAM_TIMES("Cookie.TimeDatabaseMigrationToV6",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ // Put future migration cases here.
+
+ if (cur_version < kCurrentVersionNumber) {
+ UMA_HISTOGRAM_COUNTS_100("Cookie.CorruptMetaTable", 1);
+
+ meta_table_.Reset();
+ db_.reset(new sql::Connection);
+ if (!base::DeleteFile(path_, false) ||
+ !db_->Open(path_) ||
+ !meta_table_.Init(
+ db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
+ UMA_HISTOGRAM_COUNTS_100("Cookie.CorruptMetaTableRecoveryFailed", 1);
+ NOTREACHED() << "Unable to reset the cookie DB.";
+ meta_table_.Reset();
+ db_.reset();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void SQLitePersistentCookieStore::Backend::AddCookie(
+ const net::CanonicalCookie& cc) {
+ BatchOperation(PendingOperation::COOKIE_ADD, cc);
+}
+
+void SQLitePersistentCookieStore::Backend::UpdateCookieAccessTime(
+ const net::CanonicalCookie& cc) {
+ BatchOperation(PendingOperation::COOKIE_UPDATEACCESS, cc);
+}
+
+void SQLitePersistentCookieStore::Backend::DeleteCookie(
+ const net::CanonicalCookie& cc) {
+ BatchOperation(PendingOperation::COOKIE_DELETE, cc);
+}
+
+void SQLitePersistentCookieStore::Backend::BatchOperation(
+ PendingOperation::OperationType op,
+ const net::CanonicalCookie& cc) {
+ // Commit every 30 seconds.
+ static const int kCommitIntervalMs = 30 * 1000;
+ // Commit right away if we have more than 512 outstanding operations.
+ static const size_t kCommitAfterBatchSize = 512;
+ DCHECK(!background_task_runner_->RunsTasksOnCurrentThread());
+
+ // We do a full copy of the cookie here, and hopefully just here.
+ scoped_ptr<PendingOperation> po(new PendingOperation(op, cc));
+
+ PendingOperationsList::size_type num_pending;
+ {
+ base::AutoLock locked(lock_);
+ pending_.push_back(po.release());
+ num_pending = ++num_pending_;
+ }
+
+ if (num_pending == 1) {
+ // We've gotten our first entry for this batch, fire off the timer.
+ if (!background_task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&Backend::Commit, this),
+ base::TimeDelta::FromMilliseconds(kCommitIntervalMs))) {
+ NOTREACHED() << "background_task_runner_ is not running.";
+ }
+ } else if (num_pending == kCommitAfterBatchSize) {
+ // We've reached a big enough batch, fire off a commit now.
+ PostBackgroundTask(FROM_HERE, base::Bind(&Backend::Commit, this));
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::Commit() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ PendingOperationsList ops;
+ {
+ base::AutoLock locked(lock_);
+ pending_.swap(ops);
+ num_pending_ = 0;
+ }
+
+ // Maybe an old timer fired or we are already Close()'ed.
+ if (!db_.get() || ops.empty())
+ return;
+
+ sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO cookies (creation_utc, host_key, name, value, path, "
+ "expires_utc, secure, httponly, last_access_utc, has_expires, "
+ "persistent, priority) "
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"));
+ if (!add_smt.is_valid())
+ return;
+
+ sql::Statement update_access_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "UPDATE cookies SET last_access_utc=? WHERE creation_utc=?"));
+ if (!update_access_smt.is_valid())
+ return;
+
+ sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM cookies WHERE creation_utc=?"));
+ if (!del_smt.is_valid())
+ return;
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return;
+
+ for (PendingOperationsList::iterator it = ops.begin();
+ it != ops.end(); ++it) {
+ // Free the cookies as we commit them to the database.
+ scoped_ptr<PendingOperation> po(*it);
+ switch (po->op()) {
+ case PendingOperation::COOKIE_ADD:
+ cookies_per_origin_[
+ CookieOrigin(po->cc().Domain(), po->cc().IsSecure())]++;
+ add_smt.Reset(true);
+ add_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue());
+ add_smt.BindString(1, po->cc().Domain());
+ add_smt.BindString(2, po->cc().Name());
+ add_smt.BindString(3, po->cc().Value());
+ add_smt.BindString(4, po->cc().Path());
+ add_smt.BindInt64(5, po->cc().ExpiryDate().ToInternalValue());
+ add_smt.BindInt(6, po->cc().IsSecure());
+ add_smt.BindInt(7, po->cc().IsHttpOnly());
+ add_smt.BindInt64(8, po->cc().LastAccessDate().ToInternalValue());
+ add_smt.BindInt(9, po->cc().IsPersistent());
+ add_smt.BindInt(10, po->cc().IsPersistent());
+ add_smt.BindInt(
+ 11, CookiePriorityToDBCookiePriority(po->cc().Priority()));
+ if (!add_smt.Run())
+ NOTREACHED() << "Could not add a cookie to the DB.";
+ break;
+
+ case PendingOperation::COOKIE_UPDATEACCESS:
+ update_access_smt.Reset(true);
+ update_access_smt.BindInt64(0,
+ po->cc().LastAccessDate().ToInternalValue());
+ update_access_smt.BindInt64(1,
+ po->cc().CreationDate().ToInternalValue());
+ if (!update_access_smt.Run())
+ NOTREACHED() << "Could not update cookie last access time in the DB.";
+ break;
+
+ case PendingOperation::COOKIE_DELETE:
+ cookies_per_origin_[
+ CookieOrigin(po->cc().Domain(), po->cc().IsSecure())]--;
+ del_smt.Reset(true);
+ del_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue());
+ if (!del_smt.Run())
+ NOTREACHED() << "Could not delete a cookie from the DB.";
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ bool succeeded = transaction.Commit();
+ UMA_HISTOGRAM_ENUMERATION("Cookie.BackingStoreUpdateResults",
+ succeeded ? 0 : 1, 2);
+}
+
+void SQLitePersistentCookieStore::Backend::Flush(
+ const base::Closure& callback) {
+ DCHECK(!background_task_runner_->RunsTasksOnCurrentThread());
+ PostBackgroundTask(FROM_HERE, base::Bind(&Backend::Commit, this));
+
+ if (!callback.is_null()) {
+ // We want the completion task to run immediately after Commit() returns.
+ // Posting it from here means there is less chance of another task getting
+ // onto the message queue first, than if we posted it from Commit() itself.
+ PostBackgroundTask(FROM_HERE, callback);
+ }
+}
+
+// Fire off a close message to the background runner. We could still have a
+// pending commit timer or Load operations holding references on us, but if/when
+// this fires we will already have been cleaned up and it will be ignored.
+void SQLitePersistentCookieStore::Backend::Close() {
+ if (background_task_runner_->RunsTasksOnCurrentThread()) {
+ InternalBackgroundClose();
+ } else {
+ // Must close the backend on the background runner.
+ PostBackgroundTask(FROM_HERE,
+ base::Bind(&Backend::InternalBackgroundClose, this));
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::InternalBackgroundClose() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ // Commit any pending operations
+ Commit();
+
+ if (!force_keep_session_state_ && special_storage_policy_.get() &&
+ special_storage_policy_->HasSessionOnlyOrigins()) {
+ DeleteSessionCookiesOnShutdown();
+ }
+
+ meta_table_.Reset();
+ db_.reset();
+}
+
+void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnShutdown() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (!db_)
+ return;
+
+ if (!special_storage_policy_.get())
+ return;
+
+ sql::Statement del_smt(db_->GetCachedStatement(
+ SQL_FROM_HERE, "DELETE FROM cookies WHERE host_key=? AND secure=?"));
+ if (!del_smt.is_valid()) {
+ LOG(WARNING) << "Unable to delete cookies on shutdown.";
+ return;
+ }
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ LOG(WARNING) << "Unable to delete cookies on shutdown.";
+ return;
+ }
+
+ for (CookiesPerOriginMap::iterator it = cookies_per_origin_.begin();
+ it != cookies_per_origin_.end(); ++it) {
+ if (it->second <= 0) {
+ DCHECK_EQ(0, it->second);
+ continue;
+ }
+ const GURL url(net::cookie_util::CookieOriginToURL(it->first.first,
+ it->first.second));
+ if (!url.is_valid() || !special_storage_policy_->IsStorageSessionOnly(url))
+ continue;
+
+ del_smt.Reset(true);
+ del_smt.BindString(0, it->first.first);
+ del_smt.BindInt(1, it->first.second);
+ if (!del_smt.Run())
+ NOTREACHED() << "Could not delete a cookie from the DB.";
+ }
+
+ if (!transaction.Commit())
+ LOG(WARNING) << "Unable to delete cookies on shutdown.";
+}
+
+void SQLitePersistentCookieStore::Backend::DatabaseErrorCallback(
+ int error,
+ sql::Statement* stmt) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (!sql::IsErrorCatastrophic(error))
+ return;
+
+ // TODO(shess): Running KillDatabase() multiple times should be
+ // safe.
+ if (corruption_detected_)
+ return;
+
+ corruption_detected_ = true;
+
+ // Don't just do the close/delete here, as we are being called by |db| and
+ // that seems dangerous.
+ // TODO(shess): Consider just calling RazeAndClose() immediately.
+ // db_ may not be safe to reset at this point, but RazeAndClose()
+ // would cause the stack to unwind safely with errors.
+ PostBackgroundTask(FROM_HERE, base::Bind(&Backend::KillDatabase, this));
+}
+
+void SQLitePersistentCookieStore::Backend::KillDatabase() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (db_) {
+ // This Backend will now be in-memory only. In a future run we will recreate
+ // the database. Hopefully things go better then!
+ bool success = db_->RazeAndClose();
+ UMA_HISTOGRAM_BOOLEAN("Cookie.KillDatabaseResult", success);
+ meta_table_.Reset();
+ db_.reset();
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::SetForceKeepSessionState() {
+ base::AutoLock locked(lock_);
+ force_keep_session_state_ = true;
+}
+
+void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnStartup() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ if (!db_->Execute("DELETE FROM cookies WHERE persistent == 0"))
+ LOG(WARNING) << "Unable to delete session cookies.";
+}
+
+void SQLitePersistentCookieStore::Backend::PostBackgroundTask(
+ const tracked_objects::Location& origin, const base::Closure& task) {
+ if (!background_task_runner_->PostTask(origin, task)) {
+ LOG(WARNING) << "Failed to post task from " << origin.ToString()
+ << " to background_task_runner_.";
+ }
+}
+
+void SQLitePersistentCookieStore::Backend::PostClientTask(
+ const tracked_objects::Location& origin, const base::Closure& task) {
+ if (!client_task_runner_->PostTask(origin, task)) {
+ LOG(WARNING) << "Failed to post task from " << origin.ToString()
+ << " to client_task_runner_.";
+ }
+}
+
+SQLitePersistentCookieStore::SQLitePersistentCookieStore(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ bool restore_old_session_cookies,
+ quota::SpecialStoragePolicy* special_storage_policy)
+ : backend_(new Backend(path,
+ client_task_runner,
+ background_task_runner,
+ restore_old_session_cookies,
+ special_storage_policy)) {
+}
+
+void SQLitePersistentCookieStore::Load(const LoadedCallback& loaded_callback) {
+ backend_->Load(loaded_callback);
+}
+
+void SQLitePersistentCookieStore::LoadCookiesForKey(
+ const std::string& key,
+ const LoadedCallback& loaded_callback) {
+ backend_->LoadCookiesForKey(key, loaded_callback);
+}
+
+void SQLitePersistentCookieStore::AddCookie(const net::CanonicalCookie& cc) {
+ backend_->AddCookie(cc);
+}
+
+void SQLitePersistentCookieStore::UpdateCookieAccessTime(
+ const net::CanonicalCookie& cc) {
+ backend_->UpdateCookieAccessTime(cc);
+}
+
+void SQLitePersistentCookieStore::DeleteCookie(const net::CanonicalCookie& cc) {
+ backend_->DeleteCookie(cc);
+}
+
+void SQLitePersistentCookieStore::SetForceKeepSessionState() {
+ backend_->SetForceKeepSessionState();
+}
+
+void SQLitePersistentCookieStore::Flush(const base::Closure& callback) {
+ backend_->Flush(callback);
+}
+
+SQLitePersistentCookieStore::~SQLitePersistentCookieStore() {
+ backend_->Close();
+ // We release our reference to the Backend, though it will probably still have
+ // a reference if the background runner has not run Close() yet.
+}
+
+net::CookieStore* CreatePersistentCookieStore(
+ const base::FilePath& path,
+ bool restore_old_session_cookies,
+ quota::SpecialStoragePolicy* storage_policy,
+ net::CookieMonster::Delegate* cookie_monster_delegate) {
+ SQLitePersistentCookieStore* persistent_store =
+ new SQLitePersistentCookieStore(
+ path,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO),
+ BrowserThread::GetBlockingPool()->GetSequencedTaskRunner(
+ BrowserThread::GetBlockingPool()->GetSequenceToken()),
+ restore_old_session_cookies,
+ storage_policy);
+ net::CookieMonster* cookie_monster =
+ new net::CookieMonster(persistent_store, cookie_monster_delegate);
+
+ const std::string cookie_priority_experiment_group =
+ base::FieldTrialList::FindFullName("CookieRetentionPriorityStudy");
+ cookie_monster->SetPriorityAwareGarbageCollection(
+ cookie_priority_experiment_group == "ExperimentOn");
+
+ return cookie_monster;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/sqlite_persistent_cookie_store.h b/chromium/content/browser/net/sqlite_persistent_cookie_store.h
new file mode 100644
index 00000000000..65e39824eac
--- /dev/null
+++ b/chromium/content/browser/net/sqlite_persistent_cookie_store.h
@@ -0,0 +1,77 @@
+// 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.
+
+// A sqlite implementation of a cookie monster persistent store.
+
+#ifndef CONTENT_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_
+#define CONTENT_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "net/cookies/cookie_monster.h"
+
+class Task;
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace net {
+class CanonicalCookie;
+}
+
+namespace quota {
+class SpecialStoragePolicy;
+}
+
+namespace content {
+
+// Implements the PersistentCookieStore interface in terms of a SQLite database.
+// For documentation about the actual member functions consult the documentation
+// of the parent class |net::CookieMonster::PersistentCookieStore|.
+// If provided, a |SpecialStoragePolicy| is consulted when the SQLite database
+// is closed to decide which cookies to keep.
+class CONTENT_EXPORT SQLitePersistentCookieStore
+ : public net::CookieMonster::PersistentCookieStore {
+ public:
+ // All blocking database accesses will be performed on
+ // |background_task_runner|, while |client_task_runner| is used to invoke
+ // callbacks.
+ SQLitePersistentCookieStore(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ bool restore_old_session_cookies,
+ quota::SpecialStoragePolicy* special_storage_policy);
+
+ // net::CookieMonster::PersistentCookieStore:
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
+ virtual void LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& callback) OVERRIDE;
+ virtual void AddCookie(const net::CanonicalCookie& cc) OVERRIDE;
+ virtual void UpdateCookieAccessTime(const net::CanonicalCookie& cc) OVERRIDE;
+ virtual void DeleteCookie(const net::CanonicalCookie& cc) OVERRIDE;
+ virtual void SetForceKeepSessionState() OVERRIDE;
+ virtual void Flush(const base::Closure& callback) OVERRIDE;
+
+ protected:
+ virtual ~SQLitePersistentCookieStore();
+
+ private:
+ class Backend;
+
+ scoped_refptr<Backend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(SQLitePersistentCookieStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_
diff --git a/chromium/content/browser/net/sqlite_persistent_cookie_store_perftest.cc b/chromium/content/browser/net/sqlite_persistent_cookie_store_perftest.cc
new file mode 100644
index 00000000000..4f627062ec5
--- /dev/null
+++ b/chromium/content/browser/net/sqlite_persistent_cookie_store_perftest.cc
@@ -0,0 +1,142 @@
+// 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 "content/browser/net/sqlite_persistent_cookie_store.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/perftimer.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+const base::FilePath::CharType cookie_filename[] = FILE_PATH_LITERAL("Cookies");
+
+} // namespace
+
+class SQLitePersistentCookieStorePerfTest : public testing::Test {
+ public:
+ SQLitePersistentCookieStorePerfTest()
+ : pool_owner_(new base::SequencedWorkerPoolOwner(1, "Background Pool")),
+ loaded_event_(false, false),
+ key_loaded_event_(false, false) {
+ }
+
+ void OnLoaded(const std::vector<net::CanonicalCookie*>& cookies) {
+ cookies_ = cookies;
+ loaded_event_.Signal();
+ }
+
+ void OnKeyLoaded(const std::vector<net::CanonicalCookie*>& cookies) {
+ cookies_ = cookies;
+ key_loaded_event_.Signal();
+ }
+
+ void Load() {
+ store_->Load(base::Bind(&SQLitePersistentCookieStorePerfTest::OnLoaded,
+ base::Unretained(this)));
+ loaded_event_.Wait();
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner() {
+ return pool_owner_->pool()->GetSequencedTaskRunner(
+ pool_owner_->pool()->GetNamedSequenceToken("background"));
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner() {
+ return pool_owner_->pool()->GetSequencedTaskRunner(
+ pool_owner_->pool()->GetNamedSequenceToken("client"));
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ store_ = new SQLitePersistentCookieStore(
+ temp_dir_.path().Append(cookie_filename),
+ client_task_runner(),
+ background_task_runner(),
+ false, NULL);
+ std::vector<net::CanonicalCookie*> cookies;
+ Load();
+ ASSERT_EQ(0u, cookies_.size());
+ // Creates 15000 cookies from 300 eTLD+1s.
+ base::Time t = base::Time::Now();
+ for (int domain_num = 0; domain_num < 300; domain_num++) {
+ std::string domain_name(base::StringPrintf(".domain_%d.com", domain_num));
+ GURL gurl("www" + domain_name);
+ for (int cookie_num = 0; cookie_num < 50; ++cookie_num) {
+ t += base::TimeDelta::FromInternalValue(10);
+ store_->AddCookie(
+ net::CanonicalCookie(gurl,
+ base::StringPrintf("Cookie_%d", cookie_num), "1",
+ domain_name, "/", t, t, t, false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+ }
+ }
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its data to disk.
+ store_ = NULL;
+
+ // Shut down the pool, causing deferred (no-op) commits to be discarded.
+ pool_owner_->pool()->Shutdown();
+ // ~SequencedWorkerPoolOwner blocks on pool shutdown.
+ pool_owner_.reset(new base::SequencedWorkerPoolOwner(1, "pool"));
+
+ store_ = new SQLitePersistentCookieStore(
+ temp_dir_.path().Append(cookie_filename),
+ client_task_runner(),
+ background_task_runner(),
+ false, NULL);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ store_ = NULL;
+ pool_owner_->pool()->Shutdown();
+ }
+
+ protected:
+ scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_;
+ base::WaitableEvent loaded_event_;
+ base::WaitableEvent key_loaded_event_;
+ std::vector<net::CanonicalCookie*> cookies_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<SQLitePersistentCookieStore> store_;
+};
+
+// Test the performance of priority load of cookies for a specfic domain key
+TEST_F(SQLitePersistentCookieStorePerfTest, TestLoadForKeyPerformance) {
+ for (int domain_num = 0; domain_num < 3; ++domain_num) {
+ std::string domain_name(base::StringPrintf("domain_%d.com", domain_num));
+ PerfTimeLogger timer(
+ ("Load cookies for the eTLD+1 " + domain_name).c_str());
+ store_->LoadCookiesForKey(domain_name,
+ base::Bind(&SQLitePersistentCookieStorePerfTest::OnKeyLoaded,
+ base::Unretained(this)));
+ key_loaded_event_.Wait();
+ timer.Done();
+
+ ASSERT_EQ(50U, cookies_.size());
+ }
+}
+
+// Test the performance of load
+TEST_F(SQLitePersistentCookieStorePerfTest, TestLoadPerformance) {
+ PerfTimeLogger timer("Load all cookies");
+ Load();
+ timer.Done();
+
+ ASSERT_EQ(15000U, cookies_.size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/sqlite_persistent_cookie_store_unittest.cc b/chromium/content/browser/net/sqlite_persistent_cookie_store_unittest.cc
new file mode 100644
index 00000000000..4adb7906685
--- /dev/null
+++ b/chromium/content/browser/net/sqlite_persistent_cookie_store_unittest.cc
@@ -0,0 +1,488 @@
+// 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 "content/browser/net/sqlite_persistent_cookie_store.h"
+
+#include <map>
+#include <set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+const base::FilePath::CharType kCookieFilename[] = FILE_PATH_LITERAL("Cookies");
+
+} // namespace
+
+typedef std::vector<net::CanonicalCookie*> CanonicalCookieVector;
+
+class SQLitePersistentCookieStoreTest : public testing::Test {
+ public:
+ SQLitePersistentCookieStoreTest()
+ : pool_owner_(new base::SequencedWorkerPoolOwner(3, "Background Pool")),
+ loaded_event_(false, false),
+ key_loaded_event_(false, false),
+ db_thread_event_(false, false) {
+ }
+
+ void OnLoaded(const CanonicalCookieVector& cookies) {
+ cookies_ = cookies;
+ loaded_event_.Signal();
+ }
+
+ void OnKeyLoaded(const CanonicalCookieVector& cookies) {
+ cookies_ = cookies;
+ key_loaded_event_.Signal();
+ }
+
+ void Load(CanonicalCookieVector* cookies) {
+ EXPECT_FALSE(loaded_event_.IsSignaled());
+ store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded,
+ base::Unretained(this)));
+ loaded_event_.Wait();
+ *cookies = cookies_;
+ }
+
+ void Flush() {
+ base::WaitableEvent event(false, false);
+ store_->Flush(base::Bind(&base::WaitableEvent::Signal,
+ base::Unretained(&event)));
+ event.Wait();
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner() {
+ return pool_owner_->pool()->GetSequencedTaskRunner(
+ pool_owner_->pool()->GetNamedSequenceToken("background"));
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner() {
+ return pool_owner_->pool()->GetSequencedTaskRunner(
+ pool_owner_->pool()->GetNamedSequenceToken("client"));
+ }
+
+ void DestroyStore() {
+ store_ = NULL;
+ // Make sure we wait until the destructor has run by shutting down the pool
+ // resetting the owner (whose destructor blocks on the pool completion).
+ pool_owner_->pool()->Shutdown();
+ // Create a new pool for the few tests that create multiple stores. In other
+ // cases this is wasted but harmless.
+ pool_owner_.reset(new base::SequencedWorkerPoolOwner(3, "Background Pool"));
+ }
+
+ void CreateAndLoad(bool restore_old_session_cookies,
+ CanonicalCookieVector* cookies) {
+ store_ = new SQLitePersistentCookieStore(
+ temp_dir_.path().Append(kCookieFilename),
+ client_task_runner(),
+ background_task_runner(),
+ restore_old_session_cookies,
+ NULL);
+ Load(cookies);
+ }
+
+ void InitializeStore(bool restore_old_session_cookies) {
+ CanonicalCookieVector cookies;
+ CreateAndLoad(restore_old_session_cookies, &cookies);
+ EXPECT_EQ(0U, cookies.size());
+ }
+
+ // We have to create this method to wrap WaitableEvent::Wait, since we cannot
+ // bind a non-void returning method as a Closure.
+ void WaitOnDBEvent() {
+ db_thread_event_.Wait();
+ }
+
+ // Adds a persistent cookie to store_.
+ void AddCookie(const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& creation) {
+ store_->AddCookie(
+ net::CanonicalCookie(GURL(), name, value, domain, path, creation,
+ creation, creation, false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ DestroyStore();
+ pool_owner_->pool()->Shutdown();
+ }
+
+ protected:
+ base::MessageLoop main_loop_;
+ scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_;
+ base::WaitableEvent loaded_event_;
+ base::WaitableEvent key_loaded_event_;
+ base::WaitableEvent db_thread_event_;
+ CanonicalCookieVector cookies_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<SQLitePersistentCookieStore> store_;
+};
+
+TEST_F(SQLitePersistentCookieStoreTest, TestInvalidMetaTableRecovery) {
+ InitializeStore(false);
+ AddCookie("A", "B", "foo.bar", "/", base::Time::Now());
+ DestroyStore();
+
+ // Load up the store and verify that it has good data in it.
+ CanonicalCookieVector cookies;
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(1U, cookies.size());
+ ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str());
+ ASSERT_STREQ("A", cookies[0]->Name().c_str());
+ ASSERT_STREQ("B", cookies[0]->Value().c_str());
+ DestroyStore();
+ STLDeleteElements(&cookies);
+
+ // Now corrupt the meta table.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(temp_dir_.path().Append(kCookieFilename)));
+ sql::MetaTable meta_table_;
+ meta_table_.Init(&db, 1, 1);
+ ASSERT_TRUE(db.Execute("DELETE FROM meta"));
+ db.Close();
+ }
+
+ // Upon loading, the database should be reset to a good, blank state.
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(0U, cookies.size());
+
+ // Verify that, after, recovery, the database persists properly.
+ AddCookie("X", "Y", "foo.bar", "/", base::Time::Now());
+ DestroyStore();
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(1U, cookies.size());
+ ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str());
+ ASSERT_STREQ("X", cookies[0]->Name().c_str());
+ ASSERT_STREQ("Y", cookies[0]->Value().c_str());
+ STLDeleteElements(&cookies);
+}
+
+// Test if data is stored as expected in the SQLite database.
+TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) {
+ InitializeStore(false);
+ AddCookie("A", "B", "foo.bar", "/", base::Time::Now());
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its data to disk. Then we can see if after loading it again it
+ // is still there.
+ DestroyStore();
+ // Reload and test for persistence
+ CanonicalCookieVector cookies;
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(1U, cookies.size());
+ ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str());
+ ASSERT_STREQ("A", cookies[0]->Name().c_str());
+ ASSERT_STREQ("B", cookies[0]->Value().c_str());
+
+ // Now delete the cookie and check persistence again.
+ store_->DeleteCookie(*cookies[0]);
+ DestroyStore();
+ STLDeleteElements(&cookies);
+
+ // Reload and check if the cookie has been removed.
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(0U, cookies.size());
+}
+
+// Test that priority load of cookies for a specfic domain key could be
+// completed before the entire store is loaded
+TEST_F(SQLitePersistentCookieStoreTest, TestLoadCookiesForKey) {
+ InitializeStore(false);
+ base::Time t = base::Time::Now();
+ AddCookie("A", "B", "foo.bar", "/", t);
+ t += base::TimeDelta::FromInternalValue(10);
+ AddCookie("A", "B", "www.aaa.com", "/", t);
+ t += base::TimeDelta::FromInternalValue(10);
+ AddCookie("A", "B", "travel.aaa.com", "/", t);
+ t += base::TimeDelta::FromInternalValue(10);
+ AddCookie("A", "B", "www.bbb.com", "/", t);
+ DestroyStore();
+
+ store_ = new SQLitePersistentCookieStore(
+ temp_dir_.path().Append(kCookieFilename),
+ client_task_runner(),
+ background_task_runner(),
+ false, NULL);
+ // Posting a blocking task to db_thread_ makes sure that the DB thread waits
+ // until both Load and LoadCookiesForKey have been posted to its task queue.
+ background_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&SQLitePersistentCookieStoreTest::WaitOnDBEvent,
+ base::Unretained(this)));
+ store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded,
+ base::Unretained(this)));
+ store_->LoadCookiesForKey("aaa.com",
+ base::Bind(&SQLitePersistentCookieStoreTest::OnKeyLoaded,
+ base::Unretained(this)));
+ background_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&SQLitePersistentCookieStoreTest::WaitOnDBEvent,
+ base::Unretained(this)));
+
+ // Now the DB-thread queue contains:
+ // (active:)
+ // 1. Wait (on db_event)
+ // (pending:)
+ // 2. "Init And Chain-Load First Domain"
+ // 3. Priority Load (aaa.com)
+ // 4. Wait (on db_event)
+ db_thread_event_.Signal();
+ key_loaded_event_.Wait();
+ ASSERT_EQ(loaded_event_.IsSignaled(), false);
+ std::set<std::string> cookies_loaded;
+ for (CanonicalCookieVector::const_iterator it = cookies_.begin();
+ it != cookies_.end();
+ ++it) {
+ cookies_loaded.insert((*it)->Domain().c_str());
+ }
+ STLDeleteElements(&cookies_);
+ ASSERT_GT(4U, cookies_loaded.size());
+ ASSERT_EQ(true, cookies_loaded.find("www.aaa.com") != cookies_loaded.end());
+ ASSERT_EQ(true,
+ cookies_loaded.find("travel.aaa.com") != cookies_loaded.end());
+
+ db_thread_event_.Signal();
+ loaded_event_.Wait();
+ for (CanonicalCookieVector::const_iterator it = cookies_.begin();
+ it != cookies_.end();
+ ++it) {
+ cookies_loaded.insert((*it)->Domain().c_str());
+ }
+ ASSERT_EQ(4U, cookies_loaded.size());
+ ASSERT_EQ(cookies_loaded.find("foo.bar") != cookies_loaded.end(),
+ true);
+ ASSERT_EQ(cookies_loaded.find("www.bbb.com") != cookies_loaded.end(), true);
+ STLDeleteElements(&cookies_);
+}
+
+// Test that we can force the database to be written by calling Flush().
+TEST_F(SQLitePersistentCookieStoreTest, TestFlush) {
+ InitializeStore(false);
+ // File timestamps don't work well on all platforms, so we'll determine
+ // whether the DB file has been modified by checking its size.
+ base::FilePath path = temp_dir_.path().Append(kCookieFilename);
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(path, &info));
+ int64 base_size = info.size;
+
+ // Write some large cookies, so the DB will have to expand by several KB.
+ for (char c = 'a'; c < 'z'; ++c) {
+ // Each cookie needs a unique timestamp for creation_utc (see DB schema).
+ base::Time t = base::Time::Now() + base::TimeDelta::FromMicroseconds(c);
+ std::string name(1, c);
+ std::string value(1000, c);
+ AddCookie(name, value, "foo.bar", "/", t);
+ }
+
+ Flush();
+
+ // We forced a write, so now the file will be bigger.
+ ASSERT_TRUE(file_util::GetFileInfo(path, &info));
+ ASSERT_GT(info.size, base_size);
+}
+
+// Test loading old session cookies from the disk.
+TEST_F(SQLitePersistentCookieStoreTest, TestLoadOldSessionCookies) {
+ InitializeStore(true);
+
+ // Add a session cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), "C", "D", "sessioncookie.com", "/", base::Time::Now(),
+ base::Time(), base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+
+ // Force the store to write its data to the disk.
+ DestroyStore();
+
+ // Create a store that loads session cookies and test that the session cookie
+ // was loaded.
+ CanonicalCookieVector cookies;
+ CreateAndLoad(true, &cookies);
+
+ ASSERT_EQ(1U, cookies.size());
+ ASSERT_STREQ("sessioncookie.com", cookies[0]->Domain().c_str());
+ ASSERT_STREQ("C", cookies[0]->Name().c_str());
+ ASSERT_STREQ("D", cookies[0]->Value().c_str());
+ ASSERT_EQ(net::COOKIE_PRIORITY_DEFAULT, cookies[0]->Priority());
+
+ STLDeleteElements(&cookies);
+}
+
+// Test loading old session cookies from the disk.
+TEST_F(SQLitePersistentCookieStoreTest, TestDontLoadOldSessionCookies) {
+ InitializeStore(true);
+
+ // Add a session cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), "C", "D", "sessioncookie.com", "/", base::Time::Now(),
+ base::Time(), base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+
+ // Force the store to write its data to the disk.
+ DestroyStore();
+
+ // Create a store that doesn't load old session cookies and test that the
+ // session cookie was not loaded.
+ CanonicalCookieVector cookies;
+ CreateAndLoad(false, &cookies);
+ ASSERT_EQ(0U, cookies.size());
+
+ // The store should also delete the session cookie. Wait until that has been
+ // done.
+ DestroyStore();
+
+ // Create a store that loads old session cookies and test that the session
+ // cookie is gone.
+ CreateAndLoad(true, &cookies);
+ ASSERT_EQ(0U, cookies.size());
+}
+
+TEST_F(SQLitePersistentCookieStoreTest, PersistIsPersistent) {
+ InitializeStore(true);
+ static const char kSessionName[] = "session";
+ static const char kPersistentName[] = "persistent";
+
+ // Add a session cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), kSessionName, "val", "sessioncookie.com", "/",
+ base::Time::Now(), base::Time(), base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+ // Add a persistent cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), kPersistentName, "val", "sessioncookie.com", "/",
+ base::Time::Now() - base::TimeDelta::FromDays(1),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_DEFAULT));
+
+ // Force the store to write its data to the disk.
+ DestroyStore();
+
+ // Create a store that loads session cookie and test that the IsPersistent
+ // attribute is restored.
+ CanonicalCookieVector cookies;
+ CreateAndLoad(true, &cookies);
+ ASSERT_EQ(2U, cookies.size());
+
+ std::map<std::string, net::CanonicalCookie*> cookie_map;
+ for (CanonicalCookieVector::const_iterator it = cookies.begin();
+ it != cookies.end();
+ ++it) {
+ cookie_map[(*it)->Name()] = *it;
+ }
+
+ std::map<std::string, net::CanonicalCookie*>::const_iterator it =
+ cookie_map.find(kSessionName);
+ ASSERT_TRUE(it != cookie_map.end());
+ EXPECT_FALSE(cookie_map[kSessionName]->IsPersistent());
+
+ it = cookie_map.find(kPersistentName);
+ ASSERT_TRUE(it != cookie_map.end());
+ EXPECT_TRUE(cookie_map[kPersistentName]->IsPersistent());
+
+ STLDeleteElements(&cookies);
+}
+
+TEST_F(SQLitePersistentCookieStoreTest, PriorityIsPersistent) {
+ static const char kLowName[] = "low";
+ static const char kMediumName[] = "medium";
+ static const char kHighName[] = "high";
+ static const char kCookieDomain[] = "sessioncookie.com";
+ static const char kCookieValue[] = "value";
+ static const char kCookiePath[] = "/";
+
+ InitializeStore(true);
+
+ // Add a low-priority persistent cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), kLowName, kCookieValue, kCookieDomain, kCookiePath,
+ base::Time::Now() - base::TimeDelta::FromMinutes(1),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_LOW));
+
+ // Add a medium-priority persistent cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), kMediumName, kCookieValue, kCookieDomain, kCookiePath,
+ base::Time::Now() - base::TimeDelta::FromMinutes(2),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_MEDIUM));
+
+ // Add a high-priority peristent cookie.
+ store_->AddCookie(
+ net::CanonicalCookie(
+ GURL(), kHighName, kCookieValue, kCookieDomain, kCookiePath,
+ base::Time::Now() - base::TimeDelta::FromMinutes(3),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ base::Time::Now(), false, false,
+ net::COOKIE_PRIORITY_HIGH));
+
+ // Force the store to write its data to the disk.
+ DestroyStore();
+
+ // Create a store that loads session cookie and test that the priority
+ // attribute values are restored.
+ CanonicalCookieVector cookies;
+ CreateAndLoad(true, &cookies);
+ ASSERT_EQ(3U, cookies.size());
+
+ // Put the cookies into a map, by name, so we can easily find them.
+ std::map<std::string, net::CanonicalCookie*> cookie_map;
+ for (CanonicalCookieVector::const_iterator it = cookies.begin();
+ it != cookies.end();
+ ++it) {
+ cookie_map[(*it)->Name()] = *it;
+ }
+
+ // Validate that each cookie has the correct priority.
+ std::map<std::string, net::CanonicalCookie*>::const_iterator it =
+ cookie_map.find(kLowName);
+ ASSERT_TRUE(it != cookie_map.end());
+ EXPECT_EQ(net::COOKIE_PRIORITY_LOW, cookie_map[kLowName]->Priority());
+
+ it = cookie_map.find(kMediumName);
+ ASSERT_TRUE(it != cookie_map.end());
+ EXPECT_EQ(net::COOKIE_PRIORITY_MEDIUM, cookie_map[kMediumName]->Priority());
+
+ it = cookie_map.find(kHighName);
+ ASSERT_TRUE(it != cookie_map.end());
+ EXPECT_EQ(net::COOKIE_PRIORITY_HIGH, cookie_map[kHighName]->Priority());
+
+ STLDeleteElements(&cookies);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/view_blob_internals_job_factory.cc b/chromium/content/browser/net/view_blob_internals_job_factory.cc
new file mode 100644
index 00000000000..e877a418e54
--- /dev/null
+++ b/chromium/content/browser/net/view_blob_internals_job_factory.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/net/view_blob_internals_job_factory.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "content/public/common/url_constants.h"
+#include "webkit/browser/blob/view_blob_internals_job.h"
+
+namespace content {
+
+// static.
+bool ViewBlobInternalsJobFactory::IsSupportedURL(const GURL& url) {
+ return url.SchemeIs(chrome::kChromeUIScheme) &&
+ url.host() == kChromeUIBlobInternalsHost;
+}
+
+// static.
+net::URLRequestJob* ViewBlobInternalsJobFactory::CreateJobForRequest(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ webkit_blob::BlobStorageController* blob_storage_controller) {
+ return new webkit_blob::ViewBlobInternalsJob(
+ request, network_delegate, blob_storage_controller);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/view_blob_internals_job_factory.h b/chromium/content/browser/net/view_blob_internals_job_factory.h
new file mode 100644
index 00000000000..0843e0651e6
--- /dev/null
+++ b/chromium/content/browser/net/view_blob_internals_job_factory.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 CONTENT_BROWSER_NET_VIEW_BLOB_INTERNALS_JOB_FACTORY_H_
+#define CONTENT_BROWSER_NET_VIEW_BLOB_INTERNALS_JOB_FACTORY_H_
+
+namespace net {
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+} // namespace net
+namespace webkit_blob {
+class BlobStorageController;
+} // webkit_blob
+
+class GURL;
+
+namespace content {
+
+class ViewBlobInternalsJobFactory {
+ public:
+ static bool IsSupportedURL(const GURL& url);
+ static net::URLRequestJob* CreateJobForRequest(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ webkit_blob::BlobStorageController* blob_storage_controller);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_NET_VIEW_BLOB_INTERNALS_JOB_FACTORY_H_
diff --git a/chromium/content/browser/net/view_http_cache_job_factory.cc b/chromium/content/browser/net/view_http_cache_job_factory.cc
new file mode 100644
index 00000000000..4264389d10c
--- /dev/null
+++ b/chromium/content/browser/net/view_http_cache_job_factory.cc
@@ -0,0 +1,203 @@
+// 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 "content/browser/net/view_http_cache_job_factory.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "net/url_request/view_cache_helper.h"
+
+namespace content {
+namespace {
+
+// A job subclass that dumps an HTTP cache entry.
+class ViewHttpCacheJob : public net::URLRequestJob {
+ public:
+ ViewHttpCacheJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate)
+ : net::URLRequestJob(request, network_delegate),
+ core_(new Core),
+ weak_factory_(this),
+ callback_(base::Bind(&ViewHttpCacheJob::OnStartCompleted,
+ base::Unretained(this))) {
+ }
+
+ // net::URLRequestJob implementation.
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE{
+ return core_->GetMimeType(mime_type);
+ }
+ virtual bool GetCharset(std::string* charset) OVERRIDE{
+ return core_->GetCharset(charset);
+ }
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size, int *bytes_read) OVERRIDE{
+ return core_->ReadRawData(buf, buf_size, bytes_read);
+ }
+
+ private:
+ class Core : public base::RefCounted<Core> {
+ public:
+ Core()
+ : data_offset_(0),
+ callback_(base::Bind(&Core::OnIOComplete, this)) {
+ }
+
+ int Start(const net::URLRequest& request, const base::Closure& callback);
+
+ // Prevents it from invoking its callback. It will self-delete.
+ void Orphan() {
+ user_callback_.Reset();
+ }
+
+ bool GetMimeType(std::string* mime_type) const;
+ bool GetCharset(std::string* charset);
+ bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read);
+
+ private:
+ friend class base::RefCounted<Core>;
+
+ ~Core() {}
+
+ // Called when ViewCacheHelper completes the operation.
+ void OnIOComplete(int result);
+
+ std::string data_;
+ int data_offset_;
+ net::ViewCacheHelper cache_helper_;
+ net::CompletionCallback callback_;
+ base::Closure user_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+ };
+
+ virtual ~ViewHttpCacheJob() {}
+
+ void StartAsync();
+ void OnStartCompleted();
+
+ scoped_refptr<Core> core_;
+ base::WeakPtrFactory<ViewHttpCacheJob> weak_factory_;
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewHttpCacheJob);
+};
+
+void ViewHttpCacheJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ViewHttpCacheJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+void ViewHttpCacheJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ if (core_.get()) {
+ core_->Orphan();
+ core_ = NULL;
+ }
+ net::URLRequestJob::Kill();
+}
+
+void ViewHttpCacheJob::StartAsync() {
+ DCHECK(request());
+
+ if (!request())
+ return;
+
+ int rv = core_->Start(*request(), callback_);
+ if (rv != net::ERR_IO_PENDING) {
+ DCHECK_EQ(net::OK, rv);
+ OnStartCompleted();
+ }
+}
+
+void ViewHttpCacheJob::OnStartCompleted() {
+ NotifyHeadersComplete();
+}
+
+int ViewHttpCacheJob::Core::Start(const net::URLRequest& request,
+ const base::Closure& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(user_callback_.is_null());
+
+ AddRef(); // Released on OnIOComplete().
+ std::string cache_key =
+ request.url().spec().substr(strlen(kChromeUINetworkViewCacheURL));
+
+ int rv;
+ if (cache_key.empty()) {
+ rv = cache_helper_.GetContentsHTML(request.context(),
+ kChromeUINetworkViewCacheURL,
+ &data_, callback_);
+ } else {
+ rv = cache_helper_.GetEntryInfoHTML(cache_key, request.context(),
+ &data_, callback_);
+ }
+
+ if (rv == net::ERR_IO_PENDING)
+ user_callback_ = callback;
+
+ return rv;
+}
+
+bool ViewHttpCacheJob::Core::GetMimeType(std::string* mime_type) const {
+ mime_type->assign("text/html");
+ return true;
+}
+
+bool ViewHttpCacheJob::Core::GetCharset(std::string* charset) {
+ charset->assign("UTF-8");
+ return true;
+}
+
+bool ViewHttpCacheJob::Core::ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+ int remaining = static_cast<int>(data_.size()) - data_offset_;
+ if (buf_size > remaining)
+ buf_size = remaining;
+ memcpy(buf->data(), data_.data() + data_offset_, buf_size);
+ data_offset_ += buf_size;
+ *bytes_read = buf_size;
+ return true;
+}
+
+void ViewHttpCacheJob::Core::OnIOComplete(int result) {
+ DCHECK_EQ(net::OK, result);
+
+ if (!user_callback_.is_null())
+ user_callback_.Run();
+
+ // We may be holding the last reference to this job. Do not access |this|
+ // after Release().
+ Release(); // Acquired on Start().
+}
+
+} // namespace.
+
+// Static.
+bool ViewHttpCacheJobFactory::IsSupportedURL(const GURL& url) {
+ return url.SchemeIs(chrome::kChromeUIScheme) &&
+ url.host() == kChromeUINetworkViewCacheHost;
+}
+
+// Static.
+net::URLRequestJob* ViewHttpCacheJobFactory::CreateJobForRequest(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) {
+ return new ViewHttpCacheJob(request, network_delegate);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/net/view_http_cache_job_factory.h b/chromium/content/browser/net/view_http_cache_job_factory.h
new file mode 100644
index 00000000000..45bca5d5a5f
--- /dev/null
+++ b/chromium/content/browser/net/view_http_cache_job_factory.h
@@ -0,0 +1,27 @@
+// 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 CONTENT_BROWSER_NET_VIEW_HTTP_CACHE_JOB_FACTORY_H_
+#define CONTENT_BROWSER_NET_VIEW_HTTP_CACHE_JOB_FACTORY_H_
+
+namespace net {
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+} // namespace net
+
+class GURL;
+
+namespace content {
+
+class ViewHttpCacheJobFactory {
+ public:
+ static bool IsSupportedURL(const GURL& url);
+ static net::URLRequestJob* CreateJobForRequest(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_NET_VIEW_HTTP_CACHE_JOB_FACTORY_H_
diff --git a/chromium/content/browser/notification_service_impl.cc b/chromium/content/browser/notification_service_impl.cc
new file mode 100644
index 00000000000..49132883f18
--- /dev/null
+++ b/chromium/content/browser/notification_service_impl.cc
@@ -0,0 +1,159 @@
+// 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 "content/browser/notification_service_impl.h"
+
+#include "base/lazy_instance.h"
+#include "base/threading/thread_local.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_types.h"
+
+namespace content {
+
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<NotificationServiceImpl> >
+ lazy_tls_ptr = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+NotificationServiceImpl* NotificationServiceImpl::current() {
+ return lazy_tls_ptr.Pointer()->Get();
+}
+
+// static
+NotificationService* NotificationService::current() {
+ return NotificationServiceImpl::current();
+}
+
+// static
+NotificationService* NotificationService::Create() {
+ return new NotificationServiceImpl;
+}
+
+// static
+bool NotificationServiceImpl::HasKey(const NotificationSourceMap& map,
+ const NotificationSource& source) {
+ return map.find(source.map_key()) != map.end();
+}
+
+NotificationServiceImpl::NotificationServiceImpl() {
+ DCHECK(current() == NULL);
+ lazy_tls_ptr.Pointer()->Set(this);
+}
+
+void NotificationServiceImpl::AddObserver(NotificationObserver* observer,
+ int type,
+ const NotificationSource& source) {
+ // We have gotten some crashes where the observer pointer is NULL. The problem
+ // is that this happens when we actually execute a notification, so have no
+ // way of knowing who the bad observer was. We want to know when this happens
+ // in release mode so we know what code to blame the crash on (since this is
+ // guaranteed to crash later).
+ CHECK(observer);
+
+ NotificationObserverList* observer_list;
+ if (HasKey(observers_[type], source)) {
+ observer_list = observers_[type][source.map_key()];
+ } else {
+ observer_list = new NotificationObserverList;
+ observers_[type][source.map_key()] = observer_list;
+ }
+
+ observer_list->AddObserver(observer);
+#ifndef NDEBUG
+ ++observer_counts_[type];
+#endif
+}
+
+void NotificationServiceImpl::RemoveObserver(NotificationObserver* observer,
+ int type,
+ const NotificationSource& source) {
+ // This is a very serious bug. An object is most likely being deleted on
+ // the wrong thread, and as a result another thread's NotificationServiceImpl
+ // has its deleted pointer in its map. A garbge object will be called in the
+ // future.
+ // NOTE: when this check shows crashes, use BrowserThread::DeleteOnIOThread or
+ // other variants as the trait on the object.
+ CHECK(HasKey(observers_[type], source));
+
+ NotificationObserverList* observer_list =
+ observers_[type][source.map_key()];
+ if (observer_list) {
+ observer_list->RemoveObserver(observer);
+ if (!observer_list->size()) {
+ observers_[type].erase(source.map_key());
+ delete observer_list;
+ }
+#ifndef NDEBUG
+ --observer_counts_[type];
+#endif
+ }
+}
+
+void NotificationServiceImpl::Notify(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK_GT(type, NOTIFICATION_ALL) <<
+ "Allowed for observing, but not posting.";
+
+ // There's no particular reason for the order in which the different
+ // classes of observers get notified here.
+
+ // Notify observers of all types and all sources
+ if (HasKey(observers_[NOTIFICATION_ALL], AllSources()) &&
+ source != AllSources()) {
+ FOR_EACH_OBSERVER(NotificationObserver,
+ *observers_[NOTIFICATION_ALL][AllSources().map_key()],
+ Observe(type, source, details));
+ }
+
+ // Notify observers of all types and the given source
+ if (HasKey(observers_[NOTIFICATION_ALL], source)) {
+ FOR_EACH_OBSERVER(NotificationObserver,
+ *observers_[NOTIFICATION_ALL][source.map_key()],
+ Observe(type, source, details));
+ }
+
+ // Notify observers of the given type and all sources
+ if (HasKey(observers_[type], AllSources()) &&
+ source != AllSources()) {
+ FOR_EACH_OBSERVER(NotificationObserver,
+ *observers_[type][AllSources().map_key()],
+ Observe(type, source, details));
+ }
+
+ // Notify observers of the given type and the given source
+ if (HasKey(observers_[type], source)) {
+ FOR_EACH_OBSERVER(NotificationObserver,
+ *observers_[type][source.map_key()],
+ Observe(type, source, details));
+ }
+}
+
+
+NotificationServiceImpl::~NotificationServiceImpl() {
+ lazy_tls_ptr.Pointer()->Set(NULL);
+
+#ifndef NDEBUG
+ for (int i = 0; i < static_cast<int>(observer_counts_.size()); i++) {
+ if (observer_counts_[i] > 0) {
+ // This may not be completely fixable -- see
+ // http://code.google.com/p/chromium/issues/detail?id=11010 .
+ VLOG(1) << observer_counts_[i] << " notification observer(s) leaked "
+ "of notification type " << i;
+ }
+ }
+#endif
+
+ for (int i = 0; i < static_cast<int>(observers_.size()); i++) {
+ NotificationSourceMap omap = observers_[i];
+ for (NotificationSourceMap::iterator it = omap.begin();
+ it != omap.end(); ++it)
+ delete it->second;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/notification_service_impl.h b/chromium/content/browser/notification_service_impl.h
new file mode 100644
index 00000000000..c9dce92cb60
--- /dev/null
+++ b/chromium/content/browser/notification_service_impl.h
@@ -0,0 +1,95 @@
+// 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 CONTENT_PUBLIC_BROWSER_NOTIFICATION_SERVICE_IMPL_H_
+#define CONTENT_PUBLIC_BROWSER_NOTIFICATION_SERVICE_IMPL_H_
+
+#include <map>
+
+#include "base/observer_list.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_service.h"
+
+namespace content {
+
+class NotificationObserver;
+class NotificationRegistrar;
+
+class CONTENT_EXPORT NotificationServiceImpl : public NotificationService {
+ public:
+ static NotificationServiceImpl* current();
+
+ // Normally instantiated when the thread is created. Not all threads have
+ // a NotificationService. Only one instance should be created per thread.
+ NotificationServiceImpl();
+ virtual ~NotificationServiceImpl();
+
+ // NotificationService:
+ virtual void Notify(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ private:
+ friend class NotificationRegistrar;
+
+ typedef ObserverList<NotificationObserver> NotificationObserverList;
+ typedef std::map<uintptr_t, NotificationObserverList*> NotificationSourceMap;
+ typedef std::map<int, NotificationSourceMap> NotificationObserverMap;
+ typedef std::map<int, int> NotificationObserverCount;
+
+ // Convenience function to determine whether a source has a
+ // NotificationObserverList in the given map;
+ static bool HasKey(const NotificationSourceMap& map,
+ const NotificationSource& source);
+
+ // NOTE: Rather than using this directly, you should use a
+ // NotificationRegistrar.
+ //
+ // Registers a NotificationObserver to be called whenever a matching
+ // notification is posted. Observer is a pointer to an object subclassing
+ // NotificationObserver to be notified when an event matching the other two
+ // parameters is posted to this service. Type is the type of events to be
+ // notified about (or NOTIFICATION_ALL to receive events of all
+ // types).
+ // Source is a NotificationSource object (created using
+ // "Source<classname>(pointer)"), if this observer only wants to
+ // receive events from that object, or NotificationService::AllSources()
+ // to receive events from all sources.
+ //
+ // A given observer can be registered only once for each combination of
+ // type and source. If the same object is registered more than once,
+ // it must be removed for each of those combinations of type and source later.
+ //
+ // The caller retains ownership of the object pointed to by observer.
+ void AddObserver(NotificationObserver* observer,
+ int type,
+ const NotificationSource& source);
+
+ // NOTE: Rather than using this directly, you should use a
+ // NotificationRegistrar.
+ //
+ // Removes the object pointed to by observer from receiving notifications
+ // that match type and source. If no object matching the parameters is
+ // currently registered, this method is a no-op.
+ void RemoveObserver(NotificationObserver* observer,
+ int type,
+ const NotificationSource& source);
+
+ // Keeps track of the observers for each type of notification.
+ // Until we get a prohibitively large number of notification types,
+ // a simple array is probably the fastest way to dispatch.
+ NotificationObserverMap observers_;
+
+#ifndef NDEBUG
+ // Used to check to see that AddObserver and RemoveObserver calls are
+ // balanced.
+ NotificationObserverCount observer_counts_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationServiceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_PUBLIC_BROWSER_NOTIFICATION_SERVICE_IMPL_H_
diff --git a/chromium/content/browser/notification_service_impl_unittest.cc b/chromium/content/browser/notification_service_impl_unittest.cc
new file mode 100644
index 00000000000..1317e4a4e7e
--- /dev/null
+++ b/chromium/content/browser/notification_service_impl_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/notification_service_impl.h"
+
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// Bogus class to act as a NotificationSource for the messages.
+class TestSource {};
+
+class TestObserver : public NotificationObserver {
+ public:
+ TestObserver() : notification_count_(0) {}
+
+ int notification_count() const { return notification_count_; }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ ++notification_count_;
+ }
+
+ private:
+ int notification_count_;
+};
+
+const int kNotification1 = 1;
+const int kNotification2 = 2;
+
+} // namespace
+
+
+class NotificationServiceImplTest : public testing::Test {
+ protected:
+ NotificationRegistrar registrar_;
+};
+
+TEST_F(NotificationServiceImplTest, Basic) {
+ TestSource test_source;
+ TestSource other_source;
+
+ // Check the equality operators defined for NotificationSource
+ EXPECT_TRUE(Source<TestSource>(&test_source) ==
+ Source<TestSource>(&test_source));
+ EXPECT_TRUE(Source<TestSource>(&test_source) !=
+ Source<TestSource>(&other_source));
+
+ TestObserver all_types_all_sources;
+ TestObserver idle_all_sources;
+ TestObserver all_types_test_source;
+ TestObserver idle_test_source;
+
+ // Make sure it doesn't freak out when there are no observers.
+ NotificationService* service = NotificationService::current();
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+
+ registrar_.Add(&all_types_all_sources, NOTIFICATION_ALL,
+ NotificationService::AllSources());
+ registrar_.Add(&idle_all_sources, kNotification1,
+ NotificationService::AllSources());
+ registrar_.Add(&all_types_test_source, NOTIFICATION_ALL,
+ Source<TestSource>(&test_source));
+ registrar_.Add(&idle_test_source, kNotification1,
+ Source<TestSource>(&test_source));
+
+ EXPECT_EQ(0, all_types_all_sources.notification_count());
+ EXPECT_EQ(0, idle_all_sources.notification_count());
+ EXPECT_EQ(0, all_types_test_source.notification_count());
+ EXPECT_EQ(0, idle_test_source.notification_count());
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(1, all_types_all_sources.notification_count());
+ EXPECT_EQ(1, idle_all_sources.notification_count());
+ EXPECT_EQ(1, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+
+ service->Notify(kNotification2,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(2, all_types_all_sources.notification_count());
+ EXPECT_EQ(1, idle_all_sources.notification_count());
+ EXPECT_EQ(2, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&other_source),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(3, all_types_all_sources.notification_count());
+ EXPECT_EQ(2, idle_all_sources.notification_count());
+ EXPECT_EQ(2, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+
+ service->Notify(kNotification2,
+ Source<TestSource>(&other_source),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(4, all_types_all_sources.notification_count());
+ EXPECT_EQ(2, idle_all_sources.notification_count());
+ EXPECT_EQ(2, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+
+ // Try send with NULL source.
+ service->Notify(kNotification1,
+ NotificationService::AllSources(),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(5, all_types_all_sources.notification_count());
+ EXPECT_EQ(3, idle_all_sources.notification_count());
+ EXPECT_EQ(2, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+
+ registrar_.RemoveAll();
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+
+ EXPECT_EQ(5, all_types_all_sources.notification_count());
+ EXPECT_EQ(3, idle_all_sources.notification_count());
+ EXPECT_EQ(2, all_types_test_source.notification_count());
+ EXPECT_EQ(1, idle_test_source.notification_count());
+}
+
+TEST_F(NotificationServiceImplTest, MultipleRegistration) {
+ TestSource test_source;
+
+ TestObserver idle_test_source;
+
+ NotificationService* service = NotificationService::current();
+
+ registrar_.Add(&idle_test_source, kNotification1,
+ Source<TestSource>(&test_source));
+ registrar_.Add(&idle_test_source, NOTIFICATION_ALL,
+ Source<TestSource>(&test_source));
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+ EXPECT_EQ(2, idle_test_source.notification_count());
+
+ registrar_.Remove(&idle_test_source, kNotification1,
+ Source<TestSource>(&test_source));
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+ EXPECT_EQ(3, idle_test_source.notification_count());
+
+ registrar_.Remove(&idle_test_source, NOTIFICATION_ALL,
+ Source<TestSource>(&test_source));
+
+ service->Notify(kNotification1,
+ Source<TestSource>(&test_source),
+ NotificationService::NoDetails());
+ EXPECT_EQ(3, idle_test_source.notification_count());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/pepper_flash_settings_helper_impl.cc b/chromium/content/browser/pepper_flash_settings_helper_impl.cc
new file mode 100644
index 00000000000..f51248cd564
--- /dev/null
+++ b/chromium/content/browser/pepper_flash_settings_helper_impl.cc
@@ -0,0 +1,72 @@
+// 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 "content/browser/pepper_flash_settings_helper_impl.h"
+
+#include "base/files/file_path.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "ipc/ipc_channel_handle.h"
+
+namespace content {
+
+// static
+scoped_refptr<PepperFlashSettingsHelper> PepperFlashSettingsHelper::Create() {
+ return new PepperFlashSettingsHelperImpl();
+}
+
+PepperFlashSettingsHelperImpl::PepperFlashSettingsHelperImpl() {
+}
+
+PepperFlashSettingsHelperImpl::~PepperFlashSettingsHelperImpl() {
+}
+
+void PepperFlashSettingsHelperImpl::OpenChannelToBroker(
+ const base::FilePath& path,
+ const OpenChannelCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (callback.is_null())
+ return;
+ if (!callback_.is_null())
+ callback.Run(false, IPC::ChannelHandle());
+
+ // Balanced in OnPpapiChannelOpened(). We need to keep this object around
+ // until then.
+ AddRef();
+
+ callback_ = callback;
+ PluginServiceImpl* plugin_service = PluginServiceImpl::GetInstance();
+ plugin_service->OpenChannelToPpapiBroker(0, path, this);
+}
+
+void PepperFlashSettingsHelperImpl::GetPpapiChannelInfo(
+ base::ProcessHandle* renderer_handle,
+ int* renderer_id) {
+ *renderer_handle = base::kNullProcessHandle;
+ *renderer_id = 0;
+}
+
+void PepperFlashSettingsHelperImpl::OnPpapiChannelOpened(
+ const IPC::ChannelHandle& channel_handle,
+ base::ProcessId /* plugin_pid */,
+ int /* plugin_child_id */) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!callback_.is_null());
+
+ if (!channel_handle.name.empty())
+ callback_.Run(true, channel_handle);
+ else
+ callback_.Run(false, IPC::ChannelHandle());
+
+ callback_.Reset();
+ // Balance the AddRef() call in Initialize().
+ Release();
+}
+
+bool PepperFlashSettingsHelperImpl::OffTheRecord() {
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/pepper_flash_settings_helper_impl.h b/chromium/content/browser/pepper_flash_settings_helper_impl.h
new file mode 100644
index 00000000000..19584a8cec3
--- /dev/null
+++ b/chromium/content/browser/pepper_flash_settings_helper_impl.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_PEPPER_FLASH_SETTINGS_HELPER_IMPL_H_
+#define CONTENT_BROWSER_PEPPER_FLASH_SETTINGS_HELPER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/public/browser/pepper_flash_settings_helper.h"
+
+namespace content {
+
+class CONTENT_EXPORT PepperFlashSettingsHelperImpl
+ : public PepperFlashSettingsHelper,
+ NON_EXPORTED_BASE(public PpapiPluginProcessHost::BrokerClient) {
+ public:
+ PepperFlashSettingsHelperImpl();
+
+ // PepperFlashSettingsHelper implementation.
+ virtual void OpenChannelToBroker(
+ const base::FilePath& path,
+ const OpenChannelCallback& callback) OVERRIDE;
+
+ // PpapiPluginProcessHost::BrokerClient implementation.
+ virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
+ int* renderer_id) OVERRIDE;
+ virtual void OnPpapiChannelOpened(const IPC::ChannelHandle& channel_handle,
+ base::ProcessId plugin_pid,
+ int plugin_child_id) OVERRIDE;
+ virtual bool OffTheRecord() OVERRIDE;
+
+ protected:
+ virtual ~PepperFlashSettingsHelperImpl();
+
+ private:
+ OpenChannelCallback callback_;
+ DISALLOW_COPY_AND_ASSIGN(PepperFlashSettingsHelperImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PEPPER_FLASH_SETTINGS_HELPER_IMPL_H_
diff --git a/chromium/content/browser/plugin_browsertest.cc b/chromium/content/browser/plugin_browsertest.cc
new file mode 100644
index 00000000000..c1dfa1f1953
--- /dev/null
+++ b/chromium/content/browser/plugin_browsertest.cc
@@ -0,0 +1,462 @@
+// 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 "base/command_line.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/common/shell_switches.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "ui/gfx/rect.h"
+
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#endif
+
+// TODO(jschuh): Finish plugins on Win64. crbug.com/180861
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64)
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+namespace content {
+namespace {
+
+void SetUrlRequestMock(const base::FilePath& path) {
+ URLRequestMockHTTPJob::AddUrlHandler(path);
+}
+
+}
+
+class PluginTest : public ContentBrowserTest {
+ protected:
+ PluginTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // Some NPAPI tests schedule garbage collection to force object tear-down.
+ command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose_gc");
+
+#if defined(OS_WIN)
+ const testing::TestInfo* const test_info =
+ testing::UnitTest::GetInstance()->current_test_info();
+ if (strcmp(test_info->name(), "MediaPlayerNew") == 0) {
+ // The installer adds our process names to the registry key below. Since
+ // the installer might not have run on this machine, add it manually.
+ base::win::RegKey regkey;
+ if (regkey.Open(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\MediaPlayer\\ShimInclusionList",
+ KEY_WRITE) == ERROR_SUCCESS) {
+ regkey.CreateKey(L"BROWSER_TESTS.EXE", KEY_READ);
+ }
+ } else if (strcmp(test_info->name(), "FlashSecurity") == 0) {
+ command_line->AppendSwitchASCII(switches::kTestSandbox,
+ "security_tests.dll");
+ }
+#elif defined(OS_MACOSX)
+ base::FilePath plugin_dir;
+ PathService::Get(base::DIR_MODULE, &plugin_dir);
+ plugin_dir = plugin_dir.AppendASCII("plugins");
+ // The plugins directory isn't read by default on the Mac, so it needs to be
+ // explicitly registered.
+ command_line->AppendSwitchPath(switches::kExtraPluginDir, plugin_dir);
+#endif
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ base::FilePath path = GetTestFilePath("", "");
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(&SetUrlRequestMock, path));
+ }
+
+ static void LoadAndWaitInWindow(Shell* window, const GURL& url) {
+ string16 expected_title(ASCIIToUTF16("OK"));
+ TitleWatcher title_watcher(window->web_contents(), expected_title);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16("plugin_not_found"));
+ NavigateToURL(window, url);
+ string16 title = title_watcher.WaitAndGetTitle();
+ if (title == ASCIIToUTF16("plugin_not_found")) {
+ const testing::TestInfo* const test_info =
+ testing::UnitTest::GetInstance()->current_test_info();
+ LOG(INFO) << "PluginTest." << test_info->name() <<
+ " not running because plugin not installed.";
+ } else {
+ EXPECT_EQ(expected_title, title);
+ }
+ }
+
+ void LoadAndWait(const GURL& url) {
+ LoadAndWaitInWindow(shell(), url);
+ }
+
+ GURL GetURL(const char* filename) {
+ return GetTestUrl("npapi", filename);
+ }
+
+ void NavigateAway() {
+ GURL url = GetTestUrl("", "simple_page.html");
+ LoadAndWait(url);
+ }
+
+ void TestPlugin(const char* filename) {
+ base::FilePath path = GetTestFilePath("plugin", filename);
+ if (!base::PathExists(path)) {
+ const testing::TestInfo* const test_info =
+ testing::UnitTest::GetInstance()->current_test_info();
+ LOG(INFO) << "PluginTest." << test_info->name() <<
+ " not running because test data wasn't found.";
+ return;
+ }
+
+ GURL url = GetTestUrl("plugin", filename);
+ LoadAndWait(url);
+ }
+};
+
+// Make sure that navigating away from a plugin referenced by JS doesn't
+// crash.
+IN_PROC_BROWSER_TEST_F(PluginTest, UnloadNoCrash) {
+ LoadAndWait(GetURL("layout_test_plugin.html"));
+ NavigateAway();
+}
+
+// Tests if a plugin executing a self deleting script using NPN_GetURL
+// works without crashing or hanging
+// Flaky: http://crbug.com/59327
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(SelfDeletePluginGetUrl)) {
+ LoadAndWait(GetURL("self_delete_plugin_geturl.html"));
+}
+
+// Tests if a plugin executing a self deleting script using Invoke
+// works without crashing or hanging
+// Flaky. See http://crbug.com/30702
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(SelfDeletePluginInvoke)) {
+ LoadAndWait(GetURL("self_delete_plugin_invoke.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NPObjectReleasedOnDestruction)) {
+ NavigateToURL(shell(), GetURL("npobject_released_on_destruction.html"));
+ NavigateAway();
+}
+
+// Test that a dialog is properly created when a plugin throws an
+// exception. Should be run for in and out of process plugins, but
+// the more interesting case is out of process, where we must route
+// the exception to the correct renderer.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NPObjectSetException)) {
+ LoadAndWait(GetURL("npobject_set_exception.html"));
+}
+
+#if defined(OS_WIN)
+// Tests if a plugin executing a self deleting script in the context of
+// a synchronous mouseup works correctly.
+// This was never ported to Mac. The only thing remaining is to make
+// SimulateMouseClick get to Mac plugins, currently it doesn't work.
+IN_PROC_BROWSER_TEST_F(PluginTest,
+ MAYBE(SelfDeletePluginInvokeInSynchronousMouseUp)) {
+ NavigateToURL(shell(), GetURL("execute_script_delete_in_mouse_up.html"));
+
+ string16 expected_title(ASCIIToUTF16("OK"));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+ SimulateMouseClick(shell()->web_contents(), 0,
+ WebKit::WebMouseEvent::ButtonLeft);
+ EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
+}
+#endif
+
+// Flaky, http://crbug.com/60071.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(GetURLRequest404Response)) {
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath().AppendASCII("npapi").
+ AppendASCII("plugin_url_request_404.html")));
+ LoadAndWait(url);
+}
+
+// Tests if a plugin executing a self deleting script using Invoke with
+// a modal dialog showing works without crashing or hanging
+// Disabled, flakily exceeds timeout, http://crbug.com/46257.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(SelfDeletePluginInvokeAlert)) {
+ // Navigate asynchronously because if we waitd until it completes, there's a
+ // race condition where the alert can come up before we start watching for it.
+ shell()->LoadURL(GetURL("self_delete_plugin_invoke_alert.html"));
+
+ string16 expected_title(ASCIIToUTF16("OK"));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+
+ WaitForAppModalDialog(shell());
+
+ EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
+}
+
+// Test passing arguments to a plugin.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(Arguments)) {
+ LoadAndWait(GetURL("arguments.html"));
+}
+
+// Test invoking many plugins within a single page.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(ManyPlugins)) {
+ LoadAndWait(GetURL("many_plugins.html"));
+}
+
+// Test various calls to GetURL from a plugin.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(GetURL)) {
+ LoadAndWait(GetURL("geturl.html"));
+}
+
+// Test various calls to GetURL for javascript URLs with
+// non NULL targets from a plugin.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(GetJavaScriptURL)) {
+ LoadAndWait(GetURL("get_javascript_url.html"));
+}
+
+// Test that calling GetURL with a javascript URL and target=_self
+// works properly when the plugin is embedded in a subframe.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(GetJavaScriptURL2)) {
+ LoadAndWait(GetURL("get_javascript_url2.html"));
+}
+
+// Test is flaky on linux/cros/win builders. http://crbug.com/71904
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_GetURLRedirectNotification) {
+ LoadAndWait(GetURL("geturl_redirect_notify.html"));
+}
+
+// Tests that identity is preserved for NPObjects passed from a plugin
+// into JavaScript.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NPObjectIdentity)) {
+ LoadAndWait(GetURL("npobject_identity.html"));
+}
+
+// Tests that if an NPObject is proxies back to its original process, the
+// original pointer is returned and not a proxy. If this fails the plugin
+// will crash.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NPObjectProxy)) {
+ LoadAndWait(GetURL("npobject_proxy.html"));
+}
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+// Tests if a plugin executing a self deleting script in the context of
+// a synchronous paint event works correctly
+// http://crbug.com/44960
+IN_PROC_BROWSER_TEST_F(PluginTest,
+ MAYBE(SelfDeletePluginInvokeInSynchronousPaint)) {
+ LoadAndWait(GetURL("execute_script_delete_in_paint.html"));
+}
+#endif
+
+// Tests that if a plugin executes a self resizing script in the context of a
+// synchronous paint, the plugin doesn't use deallocated memory.
+// http://crbug.com/139462
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(ResizeDuringPaint)) {
+ LoadAndWait(GetURL("resize_during_paint.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(SelfDeletePluginInNewStream)) {
+ LoadAndWait(GetURL("self_delete_plugin_stream.html"));
+}
+
+// This test asserts on Mac in plugin_host in the NPNVWindowNPObject case.
+#if !(defined(OS_MACOSX) && !defined(NDEBUG))
+// If this test flakes use http://crbug.com/95558.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(DeletePluginInDeallocate)) {
+ LoadAndWait(GetURL("plugin_delete_in_deallocate.html"));
+}
+#endif
+
+#if defined(OS_WIN)
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(VerifyPluginWindowRect)) {
+ LoadAndWait(GetURL("verify_plugin_window_rect.html"));
+}
+
+// Tests that creating a new instance of a plugin while another one is handling
+// a paint message doesn't cause deadlock.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(CreateInstanceInPaint)) {
+ LoadAndWait(GetURL("create_instance_in_paint.html"));
+}
+
+// Tests that putting up an alert in response to a paint doesn't deadlock.
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_AlertInWindowMessage) {
+ NavigateToURL(shell(), GetURL("alert_in_window_message.html"));
+
+ WaitForAppModalDialog(shell());
+ WaitForAppModalDialog(shell());
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(VerifyNPObjectLifetimeTest)) {
+ LoadAndWait(GetURL("npobject_lifetime_test.html"));
+}
+
+// Tests that we don't crash or assert if NPP_New fails
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NewFails)) {
+ LoadAndWait(GetURL("new_fails.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(SelfDeletePluginInNPNEvaluate)) {
+ LoadAndWait(GetURL("execute_script_delete_in_npn_evaluate.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest,
+ MAYBE(SelfDeleteCreatePluginInNPNEvaluate)) {
+ LoadAndWait(GetURL("npn_plugin_delete_create_in_evaluate.html"));
+}
+
+#endif // OS_WIN
+
+// If this flakes, reopen http://crbug.com/17645
+// As of 6 July 2011, this test is flaky on Windows (perhaps due to timing out).
+#if !defined(OS_MACOSX)
+// Disabled on Mac because the plugin side isn't implemented yet, see
+// "TODO(port)" in plugin_javascript_open_popup.cc.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(OpenPopupWindowWithPlugin)) {
+ LoadAndWait(GetURL("get_javascript_open_popup_with_plugin.html"));
+}
+#endif
+
+// Test checking the privacy mode is off.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(PrivateDisabled)) {
+ LoadAndWait(GetURL("private.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(ScheduleTimer)) {
+ LoadAndWait(GetURL("schedule_timer.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(PluginThreadAsyncCall)) {
+ LoadAndWait(GetURL("plugin_thread_async_call.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, PluginSingleRangeRequest) {
+ LoadAndWait(GetURL("plugin_single_range_request.html"));
+}
+
+// Test checking the privacy mode is on.
+// If this flakes on Linux, use http://crbug.com/104380
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(PrivateEnabled)) {
+ GURL url = GetURL("private.html");
+ url = GURL(url.spec() + "?private");
+ LoadAndWaitInWindow(CreateOffTheRecordBrowser(), url);
+}
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+// Test a browser hang due to special case of multiple
+// plugin instances indulged in sync calls across renderer.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(MultipleInstancesSyncCalls)) {
+ LoadAndWait(GetURL("multiple_instances_sync_calls.html"));
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(GetURLRequestFailWrite)) {
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath().AppendASCII("npapi").
+ AppendASCII("plugin_url_request_fail_write.html")));
+ LoadAndWait(url);
+}
+
+#if defined(OS_WIN)
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(EnsureScriptingWorksInDestroy)) {
+ LoadAndWait(GetURL("ensure_scripting_works_in_destroy.html"));
+}
+
+// This test uses a Windows Event to signal to the plugin that it should crash
+// on NP_Initialize.
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(NoHangIfInitCrashes)) {
+ HANDLE crash_event = CreateEvent(NULL, TRUE, FALSE, L"TestPluginCrashOnInit");
+ SetEvent(crash_event);
+ LoadAndWait(GetURL("no_hang_if_init_crashes.html"));
+ CloseHandle(crash_event);
+}
+#endif
+
+// If this flakes on Mac, use http://crbug.com/111508
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(PluginReferrerTest)) {
+ GURL url(URLRequestMockHTTPJob::GetMockUrl(
+ base::FilePath().AppendASCII("npapi").
+ AppendASCII("plugin_url_request_referrer_test.html")));
+ LoadAndWait(url);
+}
+
+#if defined(OS_MACOSX)
+// Test is flaky, see http://crbug.com/134515.
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_PluginConvertPointTest) {
+ gfx::Rect bounds(50, 50, 400, 400);
+ SetWindowBounds(shell()->window(), bounds);
+
+ NavigateToURL(shell(), GetURL("convert_point.html"));
+
+ string16 expected_title(ASCIIToUTF16("OK"));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title);
+ title_watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+ // TODO(stuartmorgan): When the automation system supports sending clicks,
+ // change the test to trigger on mouse-down rather than window focus.
+
+ // TODO: is this code still needed? It was here when it used to run in
+ // browser_tests.
+ //static_cast<WebContentsDelegate*>(shell())->
+ // ActivateContents(shell()->web_contents());
+ EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(Flash)) {
+ TestPlugin("flash.html");
+}
+
+#if defined(OS_WIN)
+// Windows only test
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_FlashSecurity) {
+ TestPlugin("flash.html");
+}
+#endif // defined(OS_WIN)
+
+#if defined(OS_WIN)
+// TODO(port) Port the following tests to platforms that have the required
+// plugins.
+// Flaky: http://crbug.com/55915
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(Quicktime)) {
+ TestPlugin("quicktime.html");
+}
+
+// Disabled - http://crbug.com/44662
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(MediaPlayerNew)) {
+ TestPlugin("wmp_new.html");
+}
+
+// Disabled - http://crbug.com/44673
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(Real)) {
+ TestPlugin("real.html");
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(FlashOctetStream)) {
+ TestPlugin("flash-octet-stream.html");
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/53926
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_FlashLayoutWhilePainting) {
+#else
+IN_PROC_BROWSER_TEST_F(PluginTest, FlashLayoutWhilePainting) {
+#endif
+ TestPlugin("flash-layout-while-painting.html");
+}
+
+// http://crbug.com/8690
+IN_PROC_BROWSER_TEST_F(PluginTest, DISABLED_Java) {
+ TestPlugin("Java.html");
+}
+
+IN_PROC_BROWSER_TEST_F(PluginTest, MAYBE(Silverlight)) {
+ TestPlugin("silverlight.html");
+}
+#endif // defined(OS_WIN)
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_data_remover_impl.cc b/chromium/content/browser/plugin_data_remover_impl.cc
new file mode 100644
index 00000000000..670d80dd4b6
--- /dev/null
+++ b/chromium/content/browser/plugin_data_remover_impl.cc
@@ -0,0 +1,318 @@
+// 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 "content/browser/plugin_data_remover_impl.h"
+
+#include <limits>
+
+#include "base/bind.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/version.h"
+#include "content/browser/plugin_process_host.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/plugin_process_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/pepper_plugin_info.h"
+#include "ppapi/proxy/ppapi_messages.h"
+
+namespace content {
+
+namespace {
+
+// The minimum Flash Player version that implements NPP_ClearSiteData.
+const char kMinFlashVersion[] = "10.3";
+const int64 kRemovalTimeoutMs = 10000;
+const uint64 kClearAllData = 0;
+
+} // namespace
+
+// static
+PluginDataRemover* PluginDataRemover::Create(BrowserContext* browser_context) {
+ return new PluginDataRemoverImpl(browser_context);
+}
+
+// static
+void PluginDataRemover::GetSupportedPlugins(
+ std::vector<WebPluginInfo>* supported_plugins) {
+ bool allow_wildcard = false;
+ std::vector<WebPluginInfo> plugins;
+ PluginService::GetInstance()->GetPluginInfoArray(
+ GURL(), kFlashPluginSwfMimeType, allow_wildcard, &plugins, NULL);
+ Version min_version(kMinFlashVersion);
+ for (std::vector<WebPluginInfo>::iterator it = plugins.begin();
+ it != plugins.end(); ++it) {
+ Version version;
+ WebPluginInfo::CreateVersionFromString(it->version, &version);
+ if (version.IsValid() && min_version.CompareTo(version) == -1)
+ supported_plugins->push_back(*it);
+ }
+}
+
+class PluginDataRemoverImpl::Context
+ : public PluginProcessHost::Client,
+ public PpapiPluginProcessHost::BrokerClient,
+ public IPC::Listener,
+ public base::RefCountedThreadSafe<Context,
+ BrowserThread::DeleteOnIOThread> {
+ public:
+ Context(base::Time begin_time, BrowserContext* browser_context)
+ : event_(new base::WaitableEvent(true, false)),
+ begin_time_(begin_time),
+ is_removing_(false),
+ browser_context_path_(browser_context->GetPath()),
+ resource_context_(browser_context->GetResourceContext()) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ }
+
+ void Init(const std::string& mime_type) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&Context::InitOnIOThread, this, mime_type));
+ BrowserThread::PostDelayedTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&Context::OnTimeout, this),
+ base::TimeDelta::FromMilliseconds(kRemovalTimeoutMs));
+ }
+
+ void InitOnIOThread(const std::string& mime_type) {
+ PluginServiceImpl* plugin_service = PluginServiceImpl::GetInstance();
+
+ // Get the plugin file path.
+ std::vector<WebPluginInfo> plugins;
+ plugin_service->GetPluginInfoArray(
+ GURL(), mime_type, false, &plugins, NULL);
+ base::FilePath plugin_path;
+ if (!plugins.empty()) // May be empty for some tests.
+ plugin_path = plugins[0].path;
+
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ remove_start_time_ = base::Time::Now();
+ is_removing_ = true;
+ // Balanced in On[Ppapi]ChannelOpened or OnError. Exactly one them will
+ // eventually be called, so we need to keep this object around until then.
+ AddRef();
+
+ PepperPluginInfo* pepper_info =
+ plugin_service->GetRegisteredPpapiPluginInfo(plugin_path);
+ if (pepper_info) {
+ plugin_name_ = pepper_info->name;
+ // Use the broker since we run this function outside the sandbox.
+ plugin_service->OpenChannelToPpapiBroker(0, plugin_path, this);
+ } else {
+ plugin_service->OpenChannelToNpapiPlugin(
+ 0, 0, GURL(), GURL(), mime_type, this);
+ }
+ }
+
+ // Called when a timeout happens in order not to block the client
+ // indefinitely.
+ void OnTimeout() {
+ LOG_IF(ERROR, is_removing_) << "Timed out";
+ SignalDone();
+ }
+
+ // PluginProcessHost::Client methods.
+ virtual int ID() OVERRIDE {
+ // Generate a unique identifier for this PluginProcessHostClient.
+ return ChildProcessHostImpl::GenerateChildProcessUniqueId();
+ }
+
+ virtual bool OffTheRecord() OVERRIDE {
+ return false;
+ }
+
+ virtual ResourceContext* GetResourceContext() OVERRIDE {
+ return resource_context_;
+ }
+
+ virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {}
+
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {}
+
+ virtual void OnSentPluginChannelRequest() OVERRIDE {}
+
+ virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
+ ConnectToChannel(handle, false);
+ // Balancing the AddRef call.
+ Release();
+ }
+
+ virtual void OnError() OVERRIDE {
+ LOG(ERROR) << "Couldn't open plugin channel";
+ SignalDone();
+ // Balancing the AddRef call.
+ Release();
+ }
+
+ // PpapiPluginProcessHost::BrokerClient implementation.
+ virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
+ int* renderer_id) OVERRIDE {
+ *renderer_handle = base::kNullProcessHandle;
+ *renderer_id = 0;
+ }
+
+ virtual void OnPpapiChannelOpened(
+ const IPC::ChannelHandle& channel_handle,
+ base::ProcessId /* peer_pid */,
+ int /* child_id */) OVERRIDE {
+ if (!channel_handle.name.empty())
+ ConnectToChannel(channel_handle, true);
+
+ // Balancing the AddRef call.
+ Release();
+ }
+
+ // IPC::Listener methods.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ IPC_BEGIN_MESSAGE_MAP(Context, message)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ClearSiteDataResult,
+ OnClearSiteDataResult)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_ClearSiteDataResult,
+ OnPpapiClearSiteDataResult)
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP()
+
+ return true;
+ }
+
+ virtual void OnChannelError() OVERRIDE {
+ if (is_removing_) {
+ NOTREACHED() << "Channel error";
+ SignalDone();
+ }
+ }
+
+ base::WaitableEvent* event() { return event_.get(); }
+
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
+ friend class base::DeleteHelper<Context>;
+ virtual ~Context() {}
+
+ IPC::Message* CreatePpapiClearSiteDataMsg(uint64 max_age) {
+ base::FilePath profile_path =
+ PepperFlashFileMessageFilter::GetDataDirName(browser_context_path_);
+ // TODO(vtl): This "duplicates" logic in webkit/plugins/ppapi/file_path.cc
+ // (which prepends the plugin name to the relative part of the path
+ // instead, with the absolute, profile-dependent part being enforced by
+ // the browser).
+#if defined(OS_WIN)
+ base::FilePath plugin_data_path =
+ profile_path.Append(base::FilePath(UTF8ToUTF16(plugin_name_)));
+#else
+ base::FilePath plugin_data_path =
+ profile_path.Append(base::FilePath(plugin_name_));
+#endif // defined(OS_WIN)
+ return new PpapiMsg_ClearSiteData(0u, plugin_data_path, std::string(),
+ kClearAllData, max_age);
+ }
+
+ // Connects the client side of a newly opened plug-in channel.
+ void ConnectToChannel(const IPC::ChannelHandle& handle, bool is_ppapi) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // If we timed out, don't bother connecting.
+ if (!is_removing_)
+ return;
+
+ DCHECK(!channel_.get());
+ channel_.reset(new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this));
+ if (!channel_->Connect()) {
+ NOTREACHED() << "Couldn't connect to plugin";
+ SignalDone();
+ return;
+ }
+
+ uint64 max_age = begin_time_.is_null() ?
+ std::numeric_limits<uint64>::max() :
+ (base::Time::Now() - begin_time_).InSeconds();
+
+ IPC::Message* msg;
+ if (is_ppapi) {
+ msg = CreatePpapiClearSiteDataMsg(max_age);
+ } else {
+ msg = new PluginProcessMsg_ClearSiteData(
+ std::string(), kClearAllData, max_age);
+ }
+ if (!channel_->Send(msg)) {
+ NOTREACHED() << "Couldn't send ClearSiteData message";
+ SignalDone();
+ return;
+ }
+ }
+
+ // Handles the PpapiHostMsg_ClearSiteDataResult message by delegating to the
+ // PluginProcessHostMsg_ClearSiteDataResult handler.
+ void OnPpapiClearSiteDataResult(uint32 request_id, bool success) {
+ DCHECK_EQ(0u, request_id);
+ OnClearSiteDataResult(success);
+ }
+
+ // Handles the PluginProcessHostMsg_ClearSiteDataResult message.
+ void OnClearSiteDataResult(bool success) {
+ LOG_IF(ERROR, !success) << "ClearSiteData returned error";
+ UMA_HISTOGRAM_TIMES("ClearPluginData.time",
+ base::Time::Now() - remove_start_time_);
+ SignalDone();
+ }
+
+ // Signals that we are finished with removing data (successful or not). This
+ // method is safe to call multiple times.
+ void SignalDone() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!is_removing_)
+ return;
+ is_removing_ = false;
+ event_->Signal();
+ }
+
+ scoped_ptr<base::WaitableEvent> event_;
+ // The point in time when we start removing data.
+ base::Time remove_start_time_;
+ // The point in time from which on we remove data.
+ base::Time begin_time_;
+ bool is_removing_;
+
+ // Path for the current profile. Must be retrieved on the UI thread from the
+ // browser context when we start so we can use it later on the I/O thread.
+ base::FilePath browser_context_path_;
+
+ // The resource context for the profile. Use only on the I/O thread.
+ ResourceContext* resource_context_;
+
+ // The name of the plugin. Use only on the I/O thread.
+ std::string plugin_name_;
+
+ // The channel is NULL until we have opened a connection to the plug-in
+ // process.
+ scoped_ptr<IPC::Channel> channel_;
+};
+
+
+PluginDataRemoverImpl::PluginDataRemoverImpl(BrowserContext* browser_context)
+ : mime_type_(kFlashPluginSwfMimeType),
+ browser_context_(browser_context) {
+}
+
+PluginDataRemoverImpl::~PluginDataRemoverImpl() {
+}
+
+base::WaitableEvent* PluginDataRemoverImpl::StartRemoving(
+ base::Time begin_time) {
+ DCHECK(!context_.get());
+ context_ = new Context(begin_time, browser_context_);
+ context_->Init(mime_type_);
+ return context_->event();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_data_remover_impl.h b/chromium/content/browser/plugin_data_remover_impl.h
new file mode 100644
index 00000000000..6f4be32538a
--- /dev/null
+++ b/chromium/content/browser/plugin_data_remover_impl.h
@@ -0,0 +1,47 @@
+// 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 CONTENT_BROWSER_PLUGIN_DATA_REMOVER_IMPL_H_
+#define CONTENT_BROWSER_PLUGIN_DATA_REMOVER_IMPL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "content/public/browser/plugin_data_remover.h"
+
+namespace content {
+
+class CONTENT_EXPORT PluginDataRemoverImpl : public PluginDataRemover {
+ public:
+ explicit PluginDataRemoverImpl(BrowserContext* browser_context);
+ virtual ~PluginDataRemoverImpl();
+
+ // PluginDataRemover implementation:
+ virtual base::WaitableEvent* StartRemoving(base::Time begin_time) OVERRIDE;
+
+ // The plug-in whose data should be removed (usually Flash) is specified via
+ // its MIME type. This method sets a different MIME type in order to call a
+ // different plug-in (for example in tests).
+ void set_mime_type(const std::string& mime_type) { mime_type_ = mime_type; }
+
+ private:
+ class Context;
+
+ std::string mime_type_;
+
+ // The browser context for the profile.
+ BrowserContext* browser_context_;
+
+ // This allows this object to be deleted on the UI thread while it's still
+ // being used on the IO thread.
+ scoped_refptr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginDataRemoverImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PLUGIN_DATA_REMOVER_IMPL_H_
diff --git a/chromium/content/browser/plugin_data_remover_impl_browsertest.cc b/chromium/content/browser/plugin_data_remover_impl_browsertest.cc
new file mode 100644
index 00000000000..37672eef947
--- /dev/null
+++ b/chromium/content/browser/plugin_data_remover_impl_browsertest.cc
@@ -0,0 +1,60 @@
+// 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 "base/base_paths.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/synchronization/waitable_event_watcher.h"
+#include "content/browser/plugin_data_remover_impl.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+
+namespace content {
+
+namespace {
+const char* kNPAPITestPluginMimeType = "application/vnd.npapi-test";
+}
+
+class PluginDataRemoverTest : public ContentBrowserTest {
+ public:
+ PluginDataRemoverTest() {}
+
+ void OnWaitableEventSignaled(base::WaitableEvent* waitable_event) {
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+#if defined(OS_MACOSX)
+ base::FilePath browser_directory;
+ PathService::Get(base::DIR_MODULE, &browser_directory);
+ command_line->AppendSwitchPath(switches::kExtraPluginDir,
+ browser_directory.AppendASCII("plugins"));
+#endif
+ // TODO(jam): since these plugin tests are running under Chrome, we need to
+ // tell it to disable its security features for old plugins. Once this is
+ // running under content_browsertests, these flags won't be needed.
+ // http://crbug.com/90448
+ // switches::kAlwaysAuthorizePlugins
+ command_line->AppendSwitch("always-authorize-plugins");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(PluginDataRemoverTest, RemoveData) {
+ PluginDataRemoverImpl plugin_data_remover(
+ shell()->web_contents()->GetBrowserContext());
+ plugin_data_remover.set_mime_type(kNPAPITestPluginMimeType);
+ base::WaitableEventWatcher watcher;
+ base::WaitableEvent* event =
+ plugin_data_remover.StartRemoving(base::Time());
+ watcher.StartWatching(
+ event,
+ base::Bind(&PluginDataRemoverTest::OnWaitableEventSignaled, this));
+ RunMessageLoop();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_loader_posix.cc b/chromium/content/browser/plugin_loader_posix.cc
new file mode 100644
index 00000000000..e6bc3ea27f5
--- /dev/null
+++ b/chromium/content/browser/plugin_loader_posix.cc
@@ -0,0 +1,201 @@
+// 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 "content/browser/plugin_loader_posix.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "content/browser/utility_process_host_impl.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/plugin_list.h"
+#include "content/common/utility_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/plugin_service.h"
+
+namespace content {
+
+PluginLoaderPosix::PluginLoaderPosix()
+ : next_load_index_(0) {
+}
+
+void PluginLoaderPosix::LoadPlugins(
+ scoped_refptr<base::MessageLoopProxy> target_loop,
+ const PluginService::GetPluginsCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ callbacks_.push_back(PendingCallback(target_loop, callback));
+
+ if (callbacks_.size() == 1) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&PluginLoaderPosix::GetPluginsToLoad, this));
+ }
+}
+
+bool PluginLoaderPosix::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(PluginLoaderPosix, message)
+ IPC_MESSAGE_HANDLER(UtilityHostMsg_LoadedPlugin, OnPluginLoaded)
+ IPC_MESSAGE_HANDLER(UtilityHostMsg_LoadPluginFailed, OnPluginLoadFailed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void PluginLoaderPosix::OnProcessCrashed(int exit_code) {
+ if (next_load_index_ == canonical_list_.size()) {
+ // How this case occurs is unknown. See crbug.com/111935.
+ canonical_list_.clear();
+ } else {
+ canonical_list_.erase(canonical_list_.begin(),
+ canonical_list_.begin() + next_load_index_ + 1);
+ }
+
+ next_load_index_ = 0;
+
+ LoadPluginsInternal();
+}
+
+bool PluginLoaderPosix::Send(IPC::Message* message) {
+ if (process_host_.get())
+ return process_host_->Send(message);
+ return false;
+}
+
+PluginLoaderPosix::~PluginLoaderPosix() {
+}
+
+void PluginLoaderPosix::GetPluginsToLoad() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ base::TimeTicks start_time(base::TimeTicks::Now());
+
+ loaded_plugins_.clear();
+ next_load_index_ = 0;
+
+ canonical_list_.clear();
+ PluginList::Singleton()->GetPluginPathsToLoad(
+ &canonical_list_,
+ PluginService::GetInstance()->NPAPIPluginsSupported());
+
+ internal_plugins_.clear();
+ PluginList::Singleton()->GetInternalPlugins(&internal_plugins_);
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&PluginLoaderPosix::LoadPluginsInternal,
+ make_scoped_refptr(this)));
+
+ HISTOGRAM_TIMES("PluginLoaderPosix.GetPluginList",
+ (base::TimeTicks::Now() - start_time) *
+ base::Time::kMicrosecondsPerMillisecond);
+}
+
+void PluginLoaderPosix::LoadPluginsInternal() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Check if the list is empty or all plugins have already been loaded before
+ // forking.
+ if (MaybeRunPendingCallbacks())
+ return;
+
+ if (load_start_time_.is_null())
+ load_start_time_ = base::TimeTicks::Now();
+
+ UtilityProcessHostImpl* host = new UtilityProcessHostImpl(
+ this,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get());
+ process_host_ = host->AsWeakPtr();
+ process_host_->DisableSandbox();
+#if defined(OS_MACOSX)
+ host->set_child_flags(ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION);
+#endif
+
+ process_host_->Send(new UtilityMsg_LoadPlugins(canonical_list_));
+}
+
+void PluginLoaderPosix::OnPluginLoaded(uint32 index,
+ const WebPluginInfo& plugin) {
+ if (index != next_load_index_) {
+ LOG(ERROR) << "Received unexpected plugin load message for "
+ << plugin.path.value() << "; index=" << index;
+ return;
+ }
+
+ if (!MaybeAddInternalPlugin(plugin.path))
+ loaded_plugins_.push_back(plugin);
+
+ ++next_load_index_;
+
+ MaybeRunPendingCallbacks();
+}
+
+void PluginLoaderPosix::OnPluginLoadFailed(uint32 index,
+ const base::FilePath& plugin_path) {
+ if (index != next_load_index_) {
+ LOG(ERROR) << "Received unexpected plugin load failure message for "
+ << plugin_path.value() << "; index=" << index;
+ return;
+ }
+
+ ++next_load_index_;
+
+ MaybeAddInternalPlugin(plugin_path);
+ MaybeRunPendingCallbacks();
+}
+
+bool PluginLoaderPosix::MaybeAddInternalPlugin(
+ const base::FilePath& plugin_path) {
+ for (std::vector<WebPluginInfo>::iterator it = internal_plugins_.begin();
+ it != internal_plugins_.end();
+ ++it) {
+ if (it->path == plugin_path) {
+ loaded_plugins_.push_back(*it);
+ internal_plugins_.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool PluginLoaderPosix::MaybeRunPendingCallbacks() {
+ if (next_load_index_ < canonical_list_.size())
+ return false;
+
+ PluginList::Singleton()->SetPlugins(loaded_plugins_);
+
+ // Only call the first callback with loaded plugins because there may be
+ // some extra plugin paths added since the first callback is added.
+ if (!callbacks_.empty()) {
+ PendingCallback callback = callbacks_.front();
+ callbacks_.pop_front();
+ callback.target_loop->PostTask(
+ FROM_HERE,
+ base::Bind(callback.callback, loaded_plugins_));
+ }
+
+ HISTOGRAM_TIMES("PluginLoaderPosix.LoadDone",
+ (base::TimeTicks::Now() - load_start_time_)
+ * base::Time::kMicrosecondsPerMillisecond);
+ load_start_time_ = base::TimeTicks();
+
+ if (!callbacks_.empty()) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&PluginLoaderPosix::GetPluginsToLoad, this));
+ return false;
+ }
+ return true;
+}
+
+PluginLoaderPosix::PendingCallback::PendingCallback(
+ scoped_refptr<base::MessageLoopProxy> loop,
+ const PluginService::GetPluginsCallback& cb)
+ : target_loop(loop),
+ callback(cb) {
+}
+
+PluginLoaderPosix::PendingCallback::~PendingCallback() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_loader_posix.h b/chromium/content/browser/plugin_loader_posix.h
new file mode 100644
index 00000000000..60cd5c61221
--- /dev/null
+++ b/chromium/content/browser/plugin_loader_posix.h
@@ -0,0 +1,127 @@
+// 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 CONTENT_BROWSER_PLUGIN_LOADER_POSIX_H_
+#define CONTENT_BROWSER_PLUGIN_LOADER_POSIX_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/public/browser/utility_process_host_client.h"
+#include "content/public/common/webplugininfo.h"
+#include "ipc/ipc_sender.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace content {
+class UtilityProcessHost;
+
+// This class is responsible for managing the out-of-process plugin loading on
+// POSIX systems. It primarily lives on the IO thread, but has a brief stay on
+// the FILE thread to iterate over plugin directories when it is first
+// constructed.
+//
+// The following is the algorithm used to load plugins:
+// 1. This asks the PluginList for the list of all potential plugins to attempt
+// to load. This is referred to as the canonical list.
+// 2. The child process this hosts is forked and the canonical list is sent to
+// it.
+// 3. The child process iterates over the canonical list, attempting to load
+// each plugin in the order specified by the list. It sends an IPC message
+// to the browser after each load, indicating success or failure. The two
+// processes synchronize the position in the vector that will be used to
+// attempt to load the next plugin.
+// 4. If the child dies during this process, the host forks another child and
+// resumes loading at the position past the plugin that it just attempted to
+// load, bypassing the problematic plugin.
+// 5. This algorithm continues until the canonical list has been walked to the
+// end, after which the list of loaded plugins is set on the PluginList and
+// the completion callback is run.
+class CONTENT_EXPORT PluginLoaderPosix
+ : public NON_EXPORTED_BASE(UtilityProcessHostClient),
+ public IPC::Sender {
+ public:
+ PluginLoaderPosix();
+
+ // Must be called from the IO thread.
+ void LoadPlugins(
+ scoped_refptr<base::MessageLoopProxy> target_loop,
+ const PluginService::GetPluginsCallback& callback);
+
+ // UtilityProcessHostClient:
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // IPC::Sender:
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ private:
+ struct PendingCallback {
+ PendingCallback(scoped_refptr<base::MessageLoopProxy> target_loop,
+ const PluginService::GetPluginsCallback& callback);
+ ~PendingCallback();
+
+ scoped_refptr<base::MessageLoopProxy> target_loop;
+ PluginService::GetPluginsCallback callback;
+ };
+
+ virtual ~PluginLoaderPosix();
+
+ // Called on the FILE thread to get the list of plugin paths to probe.
+ void GetPluginsToLoad();
+
+ // Must be called on the IO thread.
+ virtual void LoadPluginsInternal();
+
+ // Message handlers.
+ void OnPluginLoaded(uint32 index, const WebPluginInfo& plugin);
+ void OnPluginLoadFailed(uint32 index, const base::FilePath& plugin_path);
+
+ // Checks if the plugin path is an internal plugin, and, if it is, adds it to
+ // |loaded_plugins_|.
+ bool MaybeAddInternalPlugin(const base::FilePath& plugin_path);
+
+ // Runs all the registered callbacks on each's target loop if the condition
+ // for ending the load process is done (i.e. the |next_load_index_| is outside
+ // the range of the |canonical_list_|).
+ bool MaybeRunPendingCallbacks();
+
+ // The process host for which this is a client.
+ base::WeakPtr<UtilityProcessHost> process_host_;
+
+ // A list of paths to plugins which will be loaded by the utility process, in
+ // the order specified by this vector.
+ std::vector<base::FilePath> canonical_list_;
+
+ // The index in |canonical_list_| of the plugin that the child process will
+ // attempt to load next.
+ size_t next_load_index_;
+
+ // Internal plugins that have been registered at the time of loading.
+ std::vector<WebPluginInfo> internal_plugins_;
+
+ // A vector of plugins that have been loaded successfully.
+ std::vector<WebPluginInfo> loaded_plugins_;
+
+ // The callback and message loop on which the callback will be run when the
+ // plugin loading process has been completed.
+ std::deque<PendingCallback> callbacks_;
+
+ // The time at which plugin loading started.
+ base::TimeTicks load_start_time_;
+
+ friend class MockPluginLoaderPosix;
+ DISALLOW_COPY_AND_ASSIGN(PluginLoaderPosix);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PLUGIN_LOADER_POSIX_H_
diff --git a/chromium/content/browser/plugin_loader_posix_unittest.cc b/chromium/content/browser/plugin_loader_posix_unittest.cc
new file mode 100644
index 00000000000..8f907232121
--- /dev/null
+++ b/chromium/content/browser/plugin_loader_posix_unittest.cc
@@ -0,0 +1,373 @@
+// 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 "content/browser/plugin_loader_posix.h"
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_thread_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class MockPluginLoaderPosix : public PluginLoaderPosix {
+ public:
+ MOCK_METHOD0(LoadPluginsInternal, void(void));
+
+ size_t number_of_pending_callbacks() {
+ return callbacks_.size();
+ }
+
+ std::vector<base::FilePath>* canonical_list() {
+ return &canonical_list_;
+ }
+
+ size_t next_load_index() {
+ return next_load_index_;
+ }
+
+ const std::vector<WebPluginInfo>& loaded_plugins() {
+ return loaded_plugins_;
+ }
+
+ std::vector<WebPluginInfo>* internal_plugins() {
+ return &internal_plugins_;
+ }
+
+ void RealLoadPluginsInternal() {
+ PluginLoaderPosix::LoadPluginsInternal();
+ }
+
+ void TestOnPluginLoaded(uint32 index, const WebPluginInfo& plugin) {
+ OnPluginLoaded(index, plugin);
+ }
+
+ void TestOnPluginLoadFailed(uint32 index, const base::FilePath& path) {
+ OnPluginLoadFailed(index, path);
+ }
+
+ protected:
+ virtual ~MockPluginLoaderPosix() {}
+};
+
+void VerifyCallback(int* run_count, const std::vector<WebPluginInfo>&) {
+ ++(*run_count);
+}
+
+class PluginLoaderPosixTest : public testing::Test {
+ public:
+ PluginLoaderPosixTest()
+ : plugin1_(ASCIIToUTF16("plugin1"), base::FilePath("/tmp/one.plugin"),
+ ASCIIToUTF16("1.0"), string16()),
+ plugin2_(ASCIIToUTF16("plugin2"), base::FilePath("/tmp/two.plugin"),
+ ASCIIToUTF16("2.0"), string16()),
+ plugin3_(ASCIIToUTF16("plugin3"), base::FilePath("/tmp/three.plugin"),
+ ASCIIToUTF16("3.0"), string16()),
+ file_thread_(BrowserThread::FILE, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_),
+ plugin_loader_(new MockPluginLoaderPosix) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ PluginServiceImpl::GetInstance()->Init();
+ }
+
+ base::MessageLoop* message_loop() { return &message_loop_; }
+ MockPluginLoaderPosix* plugin_loader() { return plugin_loader_.get(); }
+
+ void AddThreePlugins() {
+ plugin_loader_->canonical_list()->clear();
+ plugin_loader_->canonical_list()->push_back(plugin1_.path);
+ plugin_loader_->canonical_list()->push_back(plugin2_.path);
+ plugin_loader_->canonical_list()->push_back(plugin3_.path);
+ }
+
+ // Data used for testing.
+ WebPluginInfo plugin1_;
+ WebPluginInfo plugin2_;
+ WebPluginInfo plugin3_;
+
+ private:
+ base::ShadowingAtExitManager at_exit_manager_; // Destroys PluginService.
+
+ base::MessageLoopForIO message_loop_;
+ BrowserThreadImpl file_thread_;
+ BrowserThreadImpl io_thread_;
+
+ scoped_refptr<MockPluginLoaderPosix> plugin_loader_;
+};
+
+TEST_F(PluginLoaderPosixTest, QueueRequests) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ EXPECT_EQ(0u, plugin_loader()->number_of_pending_callbacks());
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+ EXPECT_EQ(1u, plugin_loader()->number_of_pending_callbacks());
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+ EXPECT_EQ(2u, plugin_loader()->number_of_pending_callbacks());
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(2);
+ message_loop()->RunUntilIdle();
+
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->canonical_list()->clear();
+ plugin_loader()->canonical_list()->push_back(plugin1_.path);
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ message_loop()->RunUntilIdle();
+
+ EXPECT_EQ(1, did_callback);
+ EXPECT_EQ(1u, plugin_loader()->number_of_pending_callbacks());
+
+ plugin_loader()->canonical_list()->clear();
+ plugin_loader()->canonical_list()->push_back(plugin1_.path);
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ message_loop()->RunUntilIdle();
+
+ EXPECT_EQ(2, did_callback);
+ EXPECT_EQ(0u, plugin_loader()->number_of_pending_callbacks());
+}
+
+TEST_F(PluginLoaderPosixTest, ThreeSuccessfulLoads) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ message_loop()->RunUntilIdle();
+
+ AddThreePlugins();
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ const std::vector<WebPluginInfo>& plugins(plugin_loader()->loaded_plugins());
+
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ EXPECT_EQ(1u, plugin_loader()->next_load_index());
+ EXPECT_EQ(1u, plugins.size());
+ EXPECT_EQ(plugin1_.name, plugins[0].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(1, plugin2_);
+ EXPECT_EQ(2u, plugin_loader()->next_load_index());
+ EXPECT_EQ(2u, plugins.size());
+ EXPECT_EQ(plugin2_.name, plugins[1].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(2, plugin3_);
+ EXPECT_EQ(3u, plugins.size());
+ EXPECT_EQ(plugin3_.name, plugins[2].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(1, did_callback);
+}
+
+TEST_F(PluginLoaderPosixTest, ThreeSuccessfulLoadsThenCrash) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(2);
+ message_loop()->RunUntilIdle();
+
+ AddThreePlugins();
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ const std::vector<WebPluginInfo>& plugins(plugin_loader()->loaded_plugins());
+
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ EXPECT_EQ(1u, plugin_loader()->next_load_index());
+ EXPECT_EQ(1u, plugins.size());
+ EXPECT_EQ(plugin1_.name, plugins[0].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(1, plugin2_);
+ EXPECT_EQ(2u, plugin_loader()->next_load_index());
+ EXPECT_EQ(2u, plugins.size());
+ EXPECT_EQ(plugin2_.name, plugins[1].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(2, plugin3_);
+ EXPECT_EQ(3u, plugins.size());
+ EXPECT_EQ(plugin3_.name, plugins[2].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(1, did_callback);
+
+ plugin_loader()->OnProcessCrashed(42);
+}
+
+TEST_F(PluginLoaderPosixTest, TwoFailures) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ message_loop()->RunUntilIdle();
+
+ AddThreePlugins();
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ const std::vector<WebPluginInfo>& plugins(plugin_loader()->loaded_plugins());
+
+ plugin_loader()->TestOnPluginLoadFailed(0, plugin1_.path);
+ EXPECT_EQ(1u, plugin_loader()->next_load_index());
+ EXPECT_EQ(0u, plugins.size());
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(1, plugin2_);
+ EXPECT_EQ(2u, plugin_loader()->next_load_index());
+ EXPECT_EQ(1u, plugins.size());
+ EXPECT_EQ(plugin2_.name, plugins[0].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoadFailed(2, plugin3_.path);
+ EXPECT_EQ(1u, plugins.size());
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(1, did_callback);
+}
+
+TEST_F(PluginLoaderPosixTest, CrashedProcess) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ message_loop()->RunUntilIdle();
+
+ AddThreePlugins();
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ const std::vector<WebPluginInfo>& plugins(plugin_loader()->loaded_plugins());
+
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ EXPECT_EQ(1u, plugin_loader()->next_load_index());
+ EXPECT_EQ(1u, plugins.size());
+ EXPECT_EQ(plugin1_.name, plugins[0].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ plugin_loader()->OnProcessCrashed(42);
+ EXPECT_EQ(1u, plugin_loader()->canonical_list()->size());
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+ EXPECT_EQ(plugin3_.path.value(),
+ plugin_loader()->canonical_list()->at(0).value());
+}
+
+TEST_F(PluginLoaderPosixTest, InternalPlugin) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ message_loop()->RunUntilIdle();
+
+ plugin2_.path = base::FilePath("/internal/plugin.plugin");
+
+ AddThreePlugins();
+
+ plugin_loader()->internal_plugins()->clear();
+ plugin_loader()->internal_plugins()->push_back(plugin2_);
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ const std::vector<WebPluginInfo>& plugins(plugin_loader()->loaded_plugins());
+
+ plugin_loader()->TestOnPluginLoaded(0, plugin1_);
+ EXPECT_EQ(1u, plugin_loader()->next_load_index());
+ EXPECT_EQ(1u, plugins.size());
+ EXPECT_EQ(plugin1_.name, plugins[0].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ // Internal plugins can fail to load if they're built-in with manual
+ // entrypoint functions.
+ plugin_loader()->TestOnPluginLoadFailed(1, plugin2_.path);
+ EXPECT_EQ(2u, plugin_loader()->next_load_index());
+ EXPECT_EQ(2u, plugins.size());
+ EXPECT_EQ(plugin2_.name, plugins[1].name);
+ EXPECT_EQ(0u, plugin_loader()->internal_plugins()->size());
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(0, did_callback);
+
+ plugin_loader()->TestOnPluginLoaded(2, plugin3_);
+ EXPECT_EQ(3u, plugins.size());
+ EXPECT_EQ(plugin3_.name, plugins[2].name);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(1, did_callback);
+}
+
+TEST_F(PluginLoaderPosixTest, AllCrashed) {
+ int did_callback = 0;
+ PluginService::GetPluginsCallback callback =
+ base::Bind(&VerifyCallback, base::Unretained(&did_callback));
+
+ plugin_loader()->LoadPlugins(message_loop()->message_loop_proxy(), callback);
+
+ // Spin the loop so that the canonical list of plugins can be set.
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(1);
+ message_loop()->RunUntilIdle();
+ AddThreePlugins();
+
+ EXPECT_EQ(0u, plugin_loader()->next_load_index());
+
+ // Mock the first two calls like normal.
+ testing::Expectation first =
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal()).Times(2);
+ // On the last call, go through the default impl.
+ EXPECT_CALL(*plugin_loader(), LoadPluginsInternal())
+ .After(first)
+ .WillOnce(
+ testing::Invoke(plugin_loader(),
+ &MockPluginLoaderPosix::RealLoadPluginsInternal));
+ plugin_loader()->OnProcessCrashed(42);
+ plugin_loader()->OnProcessCrashed(42);
+ plugin_loader()->OnProcessCrashed(42);
+
+ message_loop()->RunUntilIdle();
+ EXPECT_EQ(1, did_callback);
+
+ EXPECT_EQ(0u, plugin_loader()->loaded_plugins().size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_process_host.cc b/chromium/content/browser/plugin_process_host.cc
new file mode 100644
index 00000000000..bffd16ef50e
--- /dev/null
+++ b/chromium/content/browser/plugin_process_host.cc
@@ -0,0 +1,417 @@
+// 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 "content/browser/plugin_process_host.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_POSIX)
+#include <utility> // for pair<>
+#endif
+
+#include <vector>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/plugin_process_messages.h"
+#include "content/common/resource_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_switches.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gl/gl_switches.h"
+
+#if defined(USE_X11)
+#include "ui/gfx/gtk_native_view_id_manager.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#include "content/common/plugin_carbon_interpose_constants_mac.h"
+#include "ui/gfx/rect.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "content/common/plugin_constants_win.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#endif
+
+namespace content {
+
+#if defined(OS_WIN)
+void PluginProcessHost::OnPluginWindowDestroyed(HWND window, HWND parent) {
+ // The window is destroyed at this point, we just care about its parent, which
+ // is the intermediate window we created.
+ std::set<HWND>::iterator window_index =
+ plugin_parent_windows_set_.find(parent);
+ if (window_index == plugin_parent_windows_set_.end())
+ return;
+
+ plugin_parent_windows_set_.erase(window_index);
+ PostMessage(parent, WM_CLOSE, 0, 0);
+}
+
+void PluginProcessHost::AddWindow(HWND window) {
+ plugin_parent_windows_set_.insert(window);
+}
+
+// NOTE: changes to this class need to be reviewed by the security team.
+class PluginSandboxedProcessLauncherDelegate
+ : public SandboxedProcessLauncherDelegate {
+ public:
+ PluginSandboxedProcessLauncherDelegate() {}
+ virtual ~PluginSandboxedProcessLauncherDelegate() {}
+
+ virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE {
+ *in_sandbox = false;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PluginSandboxedProcessLauncherDelegate);
+};
+
+#endif // defined(OS_WIN)
+
+#if defined(TOOLKIT_GTK)
+void PluginProcessHost::OnMapNativeViewId(gfx::NativeViewId id,
+ gfx::PluginWindowHandle* output) {
+ *output = 0;
+#if !defined(USE_AURA)
+ GtkNativeViewManager::GetInstance()->GetXIDForId(output, id);
+#endif
+}
+#endif // defined(TOOLKIT_GTK)
+
+PluginProcessHost::PluginProcessHost()
+#if defined(OS_MACOSX)
+ : plugin_cursor_visible_(true)
+#endif
+{
+ process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_PLUGIN, this));
+}
+
+PluginProcessHost::~PluginProcessHost() {
+#if defined(OS_WIN)
+ // We erase HWNDs from the plugin_parent_windows_set_ when we receive a
+ // notification that the window is being destroyed. If we don't receive this
+ // notification and the PluginProcessHost instance is being destroyed, it
+ // means that the plugin process crashed. We paint a sad face in this case in
+ // the renderer process. To ensure that the sad face shows up, and we don't
+ // leak HWNDs, we should destroy existing plugin parent windows.
+ std::set<HWND>::iterator window_index;
+ for (window_index = plugin_parent_windows_set_.begin();
+ window_index != plugin_parent_windows_set_.end();
+ ++window_index) {
+ PostMessage(*window_index, WM_CLOSE, 0, 0);
+ }
+#elif defined(OS_MACOSX)
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // If the plugin process crashed but had fullscreen windows open at the time,
+ // make sure that the menu bar is visible.
+ for (size_t i = 0; i < plugin_fullscreen_windows_set_.size(); ++i) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::ReleaseFullScreen,
+ base::mac::kFullScreenModeHideAll));
+ }
+ // If the plugin hid the cursor, reset that.
+ if (!plugin_cursor_visible_) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::SetCursorVisibility, true));
+ }
+#endif
+ // Cancel all pending and sent requests.
+ CancelRequests();
+}
+
+bool PluginProcessHost::Send(IPC::Message* message) {
+ return process_->Send(message);
+}
+
+bool PluginProcessHost::Init(const WebPluginInfo& info) {
+ info_ = info;
+ process_->SetName(info_.name);
+
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+ // Build command line for plugin. When we have a plugin launcher, we can't
+ // allow "self" on linux and we need the real file path.
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ CommandLine::StringType plugin_launcher =
+ browser_command_line.GetSwitchValueNative(switches::kPluginLauncher);
+
+#if defined(OS_MACOSX)
+ // Run the plug-in process in a mode tolerant of heap execution without
+ // explicit mprotect calls. Some plug-ins still rely on this quaint and
+ // archaic "feature." See http://crbug.com/93551.
+ int flags = ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION;
+#elif defined(OS_LINUX)
+ int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
+ ChildProcessHost::CHILD_NORMAL;
+#else
+ int flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
+ if (exe_path.empty())
+ return false;
+
+ CommandLine* cmd_line = new CommandLine(exe_path);
+ // Put the process type and plugin path first so they're easier to see
+ // in process listings using native process management tools.
+ cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kPluginProcess);
+ cmd_line->AppendSwitchPath(switches::kPluginPath, info.path);
+
+ // Propagate the following switches to the plugin command line (along with
+ // any associated values) if present in the browser command line
+ static const char* const kSwitchNames[] = {
+ switches::kDisableBreakpad,
+#if defined(OS_MACOSX)
+ switches::kDisableCoreAnimationPlugins,
+ switches::kEnableSandboxLogging,
+#endif
+ switches::kEnableStatsTable,
+ switches::kFullMemoryCrashReport,
+ switches::kLoggingLevel,
+ switches::kLogPluginMessages,
+ switches::kNoSandbox,
+ switches::kPluginStartupDialog,
+ switches::kTestSandbox,
+ switches::kTraceStartup,
+ switches::kUseGL,
+ switches::kUserAgent,
+ };
+
+ cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
+ arraysize(kSwitchNames));
+
+ GpuDataManagerImpl::GetInstance()->AppendPluginCommandLine(cmd_line);
+
+ // If specified, prepend a launcher program to the command line.
+ if (!plugin_launcher.empty())
+ cmd_line->PrependWrapper(plugin_launcher);
+
+ std::string locale = GetContentClient()->browser()->GetApplicationLocale();
+ if (!locale.empty()) {
+ // Pass on the locale so the null plugin will use the right language in the
+ // prompt to install the desired plugin.
+ cmd_line->AppendSwitchASCII(switches::kLang, locale);
+ }
+
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+
+#if defined(OS_POSIX)
+ base::EnvironmentVector env;
+#if defined(OS_MACOSX) && !defined(__LP64__)
+ if (!browser_command_line.HasSwitch(switches::kDisableCarbonInterposing)) {
+ std::string interpose_list = GetContentClient()->GetCarbonInterposePath();
+ if (!interpose_list.empty()) {
+ // Add our interposing library for Carbon. This is stripped back out in
+ // plugin_main.cc, so changes here should be reflected there.
+ const char* existing_list = getenv(kDYLDInsertLibrariesKey);
+ if (existing_list) {
+ interpose_list.insert(0, ":");
+ interpose_list.insert(0, existing_list);
+ }
+ }
+ env.push_back(std::pair<std::string, std::string>(
+ kDYLDInsertLibrariesKey, interpose_list));
+ }
+#endif
+#endif
+
+ process_->Launch(
+#if defined(OS_WIN)
+ new PluginSandboxedProcessLauncherDelegate,
+#elif defined(OS_POSIX)
+ false,
+ env,
+#endif
+ cmd_line);
+
+ // The plugin needs to be shutdown gracefully, i.e. NP_Shutdown needs to be
+ // called on the plugin. The plugin process exits when it receives the
+ // OnChannelError notification indicating that the browser plugin channel has
+ // been destroyed.
+ process_->SetTerminateChildOnShutdown(false);
+
+ return true;
+}
+
+void PluginProcessHost::ForceShutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ Send(new PluginProcessMsg_NotifyRenderersOfPendingShutdown());
+ process_->ForceShutdown();
+}
+
+void PluginProcessHost::AddFilter(IPC::ChannelProxy::MessageFilter* filter) {
+ process_->GetHost()->AddFilter(filter);
+}
+
+bool PluginProcessHost::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(PluginProcessHost, msg)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelCreated, OnChannelCreated)
+#if defined(OS_WIN)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginWindowDestroyed,
+ OnPluginWindowDestroyed)
+#endif
+#if defined(TOOLKIT_GTK)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_MapNativeViewId,
+ OnMapNativeViewId)
+#endif
+#if defined(OS_MACOSX)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSelectWindow,
+ OnPluginSelectWindow)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginShowWindow,
+ OnPluginShowWindow)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginHideWindow,
+ OnPluginHideWindow)
+ IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSetCursorVisibility,
+ OnPluginSetCursorVisibility)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ DCHECK(handled);
+ return handled;
+}
+
+void PluginProcessHost::OnChannelConnected(int32 peer_pid) {
+ for (size_t i = 0; i < pending_requests_.size(); ++i) {
+ RequestPluginChannel(pending_requests_[i]);
+ }
+
+ pending_requests_.clear();
+}
+
+void PluginProcessHost::OnChannelError() {
+ CancelRequests();
+}
+
+bool PluginProcessHost::CanShutdown() {
+ return sent_requests_.empty();
+}
+
+void PluginProcessHost::OnProcessCrashed(int exit_code) {
+ PluginServiceImpl::GetInstance()->RegisterPluginCrash(info_.path);
+}
+
+void PluginProcessHost::CancelRequests() {
+ for (size_t i = 0; i < pending_requests_.size(); ++i)
+ pending_requests_[i]->OnError();
+ pending_requests_.clear();
+
+ while (!sent_requests_.empty()) {
+ Client* client = sent_requests_.front();
+ if (client)
+ client->OnError();
+ sent_requests_.pop_front();
+ }
+}
+
+// static
+void PluginProcessHost::CancelPendingRequestsForResourceContext(
+ ResourceContext* context) {
+ for (PluginProcessHostIterator host_it; !host_it.Done(); ++host_it) {
+ PluginProcessHost* host = *host_it;
+ for (size_t i = 0; i < host->pending_requests_.size(); ++i) {
+ if (host->pending_requests_[i]->GetResourceContext() == context) {
+ host->pending_requests_[i]->OnError();
+ host->pending_requests_.erase(host->pending_requests_.begin() + i);
+ --i;
+ }
+ }
+ }
+}
+
+void PluginProcessHost::OpenChannelToPlugin(Client* client) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&BrowserChildProcessHostImpl::NotifyProcessInstanceCreated,
+ process_->GetData()));
+ client->SetPluginInfo(info_);
+ if (process_->GetHost()->IsChannelOpening()) {
+ // The channel is already in the process of being opened. Put
+ // this "open channel" request into a queue of requests that will
+ // be run once the channel is open.
+ pending_requests_.push_back(client);
+ return;
+ }
+
+ // We already have an open channel, send a request right away to plugin.
+ RequestPluginChannel(client);
+}
+
+void PluginProcessHost::CancelPendingRequest(Client* client) {
+ std::vector<Client*>::iterator it = pending_requests_.begin();
+ while (it != pending_requests_.end()) {
+ if (client == *it) {
+ pending_requests_.erase(it);
+ return;
+ }
+ ++it;
+ }
+ DCHECK(it != pending_requests_.end());
+}
+
+void PluginProcessHost::CancelSentRequest(Client* client) {
+ std::list<Client*>::iterator it = sent_requests_.begin();
+ while (it != sent_requests_.end()) {
+ if (client == *it) {
+ *it = NULL;
+ return;
+ }
+ ++it;
+ }
+ DCHECK(it != sent_requests_.end());
+}
+
+void PluginProcessHost::RequestPluginChannel(Client* client) {
+ // We can't send any sync messages from the browser because it might lead to
+ // a hang. However this async messages must be answered right away by the
+ // plugin process (i.e. unblocks a Send() call like a sync message) otherwise
+ // a deadlock can occur if the plugin creation request from the renderer is
+ // a result of a sync message by the plugin process.
+ PluginProcessMsg_CreateChannel* msg =
+ new PluginProcessMsg_CreateChannel(
+ client->ID(),
+ client->OffTheRecord());
+ msg->set_unblock(true);
+ if (Send(msg)) {
+ sent_requests_.push_back(client);
+ client->OnSentPluginChannelRequest();
+ } else {
+ client->OnError();
+ }
+}
+
+void PluginProcessHost::OnChannelCreated(
+ const IPC::ChannelHandle& channel_handle) {
+ Client* client = sent_requests_.front();
+
+ if (client)
+ client->OnChannelOpened(channel_handle);
+ sent_requests_.pop_front();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_process_host.h b/chromium/content/browser/plugin_process_host.h
new file mode 100644
index 00000000000..61ce44db221
--- /dev/null
+++ b/chromium/content/browser/plugin_process_host.h
@@ -0,0 +1,192 @@
+// 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 CONTENT_BROWSER_PLUGIN_PROCESS_HOST_H_
+#define CONTENT_BROWSER_PLUGIN_PROCESS_HOST_H_
+
+#include "build/build_config.h"
+
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/webplugininfo.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace IPC {
+struct ChannelHandle;
+}
+
+namespace content {
+class BrowserChildProcessHostImpl;
+class ResourceContext;
+
+// Represents the browser side of the browser <--> plugin communication
+// channel. Different plugins run in their own process, but multiple instances
+// of the same plugin run in the same process. There will be one
+// PluginProcessHost per plugin process, matched with a corresponding
+// PluginProcess running in the plugin process. The browser is responsible for
+// starting the plugin process when a plugin is created that doesn't already
+// have a process. After that, most of the communication is directly between
+// the renderer and plugin processes.
+class CONTENT_EXPORT PluginProcessHost : public BrowserChildProcessHostDelegate,
+ public IPC::Sender {
+ public:
+ class Client {
+ public:
+ // Returns an opaque unique identifier for the process requesting
+ // the channel.
+ virtual int ID() = 0;
+ // Returns the resource context for the renderer requesting the channel.
+ virtual ResourceContext* GetResourceContext() = 0;
+ virtual bool OffTheRecord() = 0;
+ virtual void SetPluginInfo(const WebPluginInfo& info) = 0;
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) = 0;
+ virtual void OnSentPluginChannelRequest() = 0;
+ // The client should delete itself when one of these methods is called.
+ virtual void OnChannelOpened(const IPC::ChannelHandle& handle) = 0;
+ virtual void OnError() = 0;
+
+ protected:
+ virtual ~Client() {}
+ };
+
+ PluginProcessHost();
+ virtual ~PluginProcessHost();
+
+ // IPC::Sender implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // Initialize the new plugin process, returning true on success. This must
+ // be called before the object can be used.
+ bool Init(const WebPluginInfo& info);
+
+ // Force the plugin process to shutdown (cleanly).
+ void ForceShutdown();
+
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ // Tells the plugin process to create a new channel for communication with a
+ // renderer. When the plugin process responds with the channel name,
+ // OnChannelOpened in the client is called.
+ void OpenChannelToPlugin(Client* client);
+
+ // Cancels all pending channel requests for the given resource context.
+ static void CancelPendingRequestsForResourceContext(ResourceContext* context);
+
+ // This function is called to cancel pending requests to open new channels.
+ void CancelPendingRequest(Client* client);
+
+ // This function is called to cancel sent requests to open new channels.
+ void CancelSentRequest(Client* client);
+
+ // This function is called on the IO thread once we receive a reply from the
+ // modal HTML dialog (in the form of a JSON string). This function forwards
+ // that reply back to the plugin that requested the dialog.
+ void OnModalDialogResponse(const std::string& json_retval,
+ IPC::Message* sync_result);
+
+#if defined(OS_MACOSX)
+ // This function is called on the IO thread when the browser becomes the
+ // active application.
+ void OnAppActivation();
+#endif
+
+ const WebPluginInfo& info() const { return info_; }
+
+#if defined(OS_WIN)
+ // Tracks plugin parent windows created on the browser UI thread.
+ void AddWindow(HWND window);
+#endif
+
+ // Adds an IPC message filter. A reference will be kept to the filter.
+ void AddFilter(IPC::ChannelProxy::MessageFilter* filter);
+
+ private:
+ // Sends a message to the plugin process to request creation of a new channel
+ // for the given mime type.
+ void RequestPluginChannel(Client* client);
+
+ // Message handlers.
+ void OnChannelCreated(const IPC::ChannelHandle& channel_handle);
+
+#if defined(OS_WIN)
+ void OnPluginWindowDestroyed(HWND window, HWND parent);
+#endif
+
+#if defined(USE_X11)
+ void OnMapNativeViewId(gfx::NativeViewId id, gfx::PluginWindowHandle* output);
+#endif
+
+#if defined(OS_MACOSX)
+ void OnPluginSelectWindow(uint32 window_id, gfx::Rect window_rect,
+ bool modal);
+ void OnPluginShowWindow(uint32 window_id, gfx::Rect window_rect,
+ bool modal);
+ void OnPluginHideWindow(uint32 window_id, gfx::Rect window_rect);
+ void OnPluginSetCursorVisibility(bool visible);
+#endif
+
+ virtual bool CanShutdown() OVERRIDE;
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+
+ void CancelRequests();
+
+ // These are channel requests that we are waiting to send to the
+ // plugin process once the channel is opened.
+ std::vector<Client*> pending_requests_;
+
+ // These are the channel requests that we have already sent to
+ // the plugin process, but haven't heard back about yet.
+ std::list<Client*> sent_requests_;
+
+ // Information about the plugin.
+ WebPluginInfo info_;
+
+#if defined(OS_WIN)
+ // Tracks plugin parent windows created on the UI thread.
+ std::set<HWND> plugin_parent_windows_set_;
+#endif
+#if defined(OS_MACOSX)
+ // Tracks plugin windows currently visible.
+ std::set<uint32> plugin_visible_windows_set_;
+ // Tracks full screen windows currently visible.
+ std::set<uint32> plugin_fullscreen_windows_set_;
+ // Tracks modal windows currently visible.
+ std::set<uint32> plugin_modal_windows_set_;
+ // Tracks the current visibility of the cursor.
+ bool plugin_cursor_visible_;
+#endif
+
+ scoped_ptr<BrowserChildProcessHostImpl> process_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginProcessHost);
+};
+
+class PluginProcessHostIterator
+ : public BrowserChildProcessHostTypeIterator<PluginProcessHost> {
+ public:
+ PluginProcessHostIterator()
+ : BrowserChildProcessHostTypeIterator<PluginProcessHost>(
+ PROCESS_TYPE_PLUGIN) {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PLUGIN_PROCESS_HOST_H_
diff --git a/chromium/content/browser/plugin_process_host_mac.cc b/chromium/content/browser/plugin_process_host_mac.cc
new file mode 100644
index 00000000000..93503009f3d
--- /dev/null
+++ b/chromium/content/browser/plugin_process_host_mac.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2011 The Chromium 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 <Carbon/Carbon.h>
+
+#include "build/build_config.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/plugin_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+void PluginProcessHost::OnPluginSelectWindow(uint32 window_id,
+ gfx::Rect window_rect,
+ bool modal) {
+ plugin_visible_windows_set_.insert(window_id);
+ if (modal)
+ plugin_modal_windows_set_.insert(window_id);
+}
+
+void PluginProcessHost::OnPluginShowWindow(uint32 window_id,
+ gfx::Rect window_rect,
+ bool modal) {
+ plugin_visible_windows_set_.insert(window_id);
+ if (modal)
+ plugin_modal_windows_set_.insert(window_id);
+ CGRect window_bounds = {
+ { window_rect.x(), window_rect.y() },
+ { window_rect.width(), window_rect.height() }
+ };
+ CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID());
+ if (CGRectEqualToRect(window_bounds, main_display_bounds) &&
+ (plugin_fullscreen_windows_set_.find(window_id) ==
+ plugin_fullscreen_windows_set_.end())) {
+ plugin_fullscreen_windows_set_.insert(window_id);
+ // If the plugin has just shown a window that's the same dimensions as
+ // the main display, hide the menubar so that it has the whole screen.
+ // (but only if we haven't already seen this fullscreen window, since
+ // otherwise our refcounting can get skewed).
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::RequestFullScreen,
+ base::mac::kFullScreenModeHideAll));
+ }
+}
+
+// Must be called on the UI thread.
+// If plugin_pid is -1, the browser will be the active process on return,
+// otherwise that process will be given focus back before this function returns.
+static void ReleasePluginFullScreen(pid_t plugin_pid) {
+ // Releasing full screen only works if we are the frontmost process; grab
+ // focus, but give it back to the plugin process if requested.
+ base::mac::ActivateProcess(base::GetCurrentProcId());
+ base::mac::ReleaseFullScreen(base::mac::kFullScreenModeHideAll);
+ if (plugin_pid != -1) {
+ base::mac::ActivateProcess(plugin_pid);
+ }
+}
+
+void PluginProcessHost::OnPluginHideWindow(uint32 window_id,
+ gfx::Rect window_rect) {
+ bool had_windows = !plugin_visible_windows_set_.empty();
+ plugin_visible_windows_set_.erase(window_id);
+ bool browser_needs_activation = had_windows &&
+ plugin_visible_windows_set_.empty();
+
+ plugin_modal_windows_set_.erase(window_id);
+ if (plugin_fullscreen_windows_set_.find(window_id) !=
+ plugin_fullscreen_windows_set_.end()) {
+ plugin_fullscreen_windows_set_.erase(window_id);
+ pid_t plugin_pid =
+ browser_needs_activation ? -1 : process_->GetData().handle;
+ browser_needs_activation = false;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(ReleasePluginFullScreen, plugin_pid));
+ }
+
+ if (browser_needs_activation) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::ActivateProcess, base::GetCurrentProcId()));
+ }
+}
+
+void PluginProcessHost::OnAppActivation() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // If our plugin process has any modal windows up, we need to bring it forward
+ // so that they act more like an in-process modal window would.
+ if (!plugin_modal_windows_set_.empty()) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::ActivateProcess, process_->GetData().handle));
+ }
+}
+
+void PluginProcessHost::OnPluginSetCursorVisibility(bool visible) {
+ if (plugin_cursor_visible_ != visible) {
+ plugin_cursor_visible_ = visible;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(base::mac::SetCursorVisibility,
+ visible));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_service_impl.cc b/chromium/content/browser/plugin_service_impl.cc
new file mode 100644
index 00000000000..6682a4d4604
--- /dev/null
+++ b/chromium/content/browser/plugin_service_impl.cc
@@ -0,0 +1,846 @@
+// 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 "content/browser/plugin_service_impl.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/pepper_plugin_list.h"
+#include "content/common/plugin_list.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/plugin_service_filter.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/webplugininfo.h"
+
+#if defined(OS_WIN)
+#include "content/common/plugin_constants_win.h"
+#include "ui/base/win/hwnd_util.h"
+#endif
+
+#if defined(OS_POSIX)
+#include "content/browser/plugin_loader_posix.h"
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+using ::base::FilePathWatcher;
+#endif
+
+namespace content {
+namespace {
+
+// This enum is used to collect Flash usage data.
+enum FlashUsage {
+ // Number of browser processes that have started at least one NPAPI Flash
+ // process during their lifetime.
+ START_NPAPI_FLASH_AT_LEAST_ONCE,
+ // Number of browser processes that have started at least one PPAPI Flash
+ // process during their lifetime.
+ START_PPAPI_FLASH_AT_LEAST_ONCE,
+ // Total number of browser processes.
+ TOTAL_BROWSER_PROCESSES,
+ FLASH_USAGE_ENUM_COUNT
+};
+
+bool LoadPluginListInProcess() {
+#if defined(OS_WIN)
+ return true;
+#else
+ // If on POSIX, we don't want to load the list of NPAPI plugins in-process as
+ // that causes instability.
+
+ // Can't load the plugins on the utility thread when in single process mode
+ // since that requires GTK which can only be used on the main thread.
+ if (RenderProcessHost::run_renderer_in_process())
+ return true;
+
+ return !PluginService::GetInstance()->NPAPIPluginsSupported();
+#endif
+}
+
+// Callback set on the PluginList to assert that plugin loading happens on the
+// correct thread.
+void WillLoadPluginsCallback(
+ base::SequencedWorkerPool::SequenceToken token) {
+ if (LoadPluginListInProcess()) {
+ CHECK(BrowserThread::GetBlockingPool()->IsRunningSequenceOnCurrentThread(
+ token));
+ } else {
+ CHECK(false) << "Plugin loading should happen out-of-process.";
+ }
+}
+
+#if defined(OS_MACOSX)
+void NotifyPluginsOfActivation() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (PluginProcessHostIterator iter; !iter.Done(); ++iter)
+ iter->OnAppActivation();
+}
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+void NotifyPluginDirChanged(const base::FilePath& path, bool error) {
+ if (error) {
+ // TODO(pastarmovj): Add some sensible error handling. Maybe silently
+ // stopping the watcher would be enough. Or possibly restart it.
+ NOTREACHED();
+ return;
+ }
+ VLOG(1) << "Watched path changed: " << path.value();
+ // Make the plugin list update itself
+ PluginList::Singleton()->RefreshPlugins();
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PluginService::PurgePluginListCache,
+ static_cast<BrowserContext*>(NULL), false));
+}
+#endif
+
+} // namespace
+
+// static
+PluginService* PluginService::GetInstance() {
+ return PluginServiceImpl::GetInstance();
+}
+
+void PluginService::PurgePluginListCache(BrowserContext* browser_context,
+ bool reload_pages) {
+ for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator();
+ !it.IsAtEnd(); it.Advance()) {
+ RenderProcessHost* host = it.GetCurrentValue();
+ if (!browser_context || host->GetBrowserContext() == browser_context)
+ host->Send(new ViewMsg_PurgePluginListCache(reload_pages));
+ }
+}
+
+// static
+PluginServiceImpl* PluginServiceImpl::GetInstance() {
+ return Singleton<PluginServiceImpl>::get();
+}
+
+PluginServiceImpl::PluginServiceImpl()
+ : filter_(NULL) {
+ // Collect the total number of browser processes (which create
+ // PluginServiceImpl objects, to be precise). The number is used to normalize
+ // the number of processes which start at least one NPAPI/PPAPI Flash process.
+ static bool counted = false;
+ if (!counted) {
+ counted = true;
+ UMA_HISTOGRAM_ENUMERATION("Plugin.FlashUsage", TOTAL_BROWSER_PROCESSES,
+ FLASH_USAGE_ENUM_COUNT);
+ }
+}
+
+PluginServiceImpl::~PluginServiceImpl() {
+#if defined(OS_WIN)
+ // Release the events since they're owned by RegKey, not WaitableEvent.
+ hkcu_watcher_.StopWatching();
+ hklm_watcher_.StopWatching();
+ if (hkcu_event_)
+ hkcu_event_->Release();
+ if (hklm_event_)
+ hklm_event_->Release();
+#endif
+ // Make sure no plugin channel requests have been leaked.
+ DCHECK(pending_plugin_clients_.empty());
+}
+
+void PluginServiceImpl::Init() {
+ plugin_list_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken();
+ PluginList::Singleton()->set_will_load_plugins_callback(
+ base::Bind(&WillLoadPluginsCallback, plugin_list_token_));
+
+ RegisterPepperPlugins();
+
+ // The --site-per-process flag enables an out-of-process iframes
+ // prototype, which uses WebView for rendering. We need to register the MIME
+ // type we use with the plugin, so the renderer can instantiate it.
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kSitePerProcess)) {
+ WebPluginInfo webview_plugin(
+ ASCIIToUTF16("WebView Tag"),
+ base::FilePath(),
+ ASCIIToUTF16("1.2.3.4"),
+ ASCIIToUTF16("Browser Plugin."));
+ webview_plugin.type = WebPluginInfo::PLUGIN_TYPE_NPAPI;
+ WebPluginMimeType webview_plugin_mime_type;
+ webview_plugin_mime_type.mime_type = "application/browser-plugin";
+ webview_plugin_mime_type.file_extensions.push_back("*");
+ webview_plugin.mime_types.push_back(webview_plugin_mime_type);
+ RegisterInternalPlugin(webview_plugin, true);
+ }
+
+ // Load any specified on the command line as well.
+ base::FilePath path =
+ command_line->GetSwitchValuePath(switches::kLoadPlugin);
+ if (!path.empty())
+ AddExtraPluginPath(path);
+ path = command_line->GetSwitchValuePath(switches::kExtraPluginDir);
+ if (!path.empty())
+ PluginList::Singleton()->AddExtraPluginDir(path);
+
+ if (command_line->HasSwitch(switches::kDisablePluginsDiscovery))
+ PluginList::Singleton()->DisablePluginsDiscovery();
+}
+
+void PluginServiceImpl::StartWatchingPlugins() {
+ // Start watching for changes in the plugin list. This means watching
+ // for changes in the Windows registry keys and on both Windows and POSIX
+ // watch for changes in the paths that are expected to contain plugins.
+#if defined(OS_WIN)
+ if (hkcu_key_.Create(HKEY_CURRENT_USER,
+ kRegistryMozillaPlugins,
+ KEY_NOTIFY) == ERROR_SUCCESS) {
+ if (hkcu_key_.StartWatching() == ERROR_SUCCESS) {
+ hkcu_event_.reset(new base::WaitableEvent(hkcu_key_.watch_event()));
+ base::WaitableEventWatcher::EventCallback callback =
+ base::Bind(&PluginServiceImpl::OnWaitableEventSignaled,
+ base::Unretained(this));
+ hkcu_watcher_.StartWatching(hkcu_event_.get(), callback);
+ }
+ }
+ if (hklm_key_.Create(HKEY_LOCAL_MACHINE,
+ kRegistryMozillaPlugins,
+ KEY_NOTIFY) == ERROR_SUCCESS) {
+ if (hklm_key_.StartWatching() == ERROR_SUCCESS) {
+ hklm_event_.reset(new base::WaitableEvent(hklm_key_.watch_event()));
+ base::WaitableEventWatcher::EventCallback callback =
+ base::Bind(&PluginServiceImpl::OnWaitableEventSignaled,
+ base::Unretained(this));
+ hklm_watcher_.StartWatching(hklm_event_.get(), callback);
+ }
+ }
+#endif
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+// On ChromeOS the user can't install plugins anyway and on Windows all
+// important plugins register themselves in the registry so no need to do that.
+
+ // Get the list of all paths for registering the FilePathWatchers
+ // that will track and if needed reload the list of plugins on runtime.
+ std::vector<base::FilePath> plugin_dirs;
+ PluginList::Singleton()->GetPluginDirectories(&plugin_dirs);
+
+ for (size_t i = 0; i < plugin_dirs.size(); ++i) {
+ // FilePathWatcher can not handle non-absolute paths under windows.
+ // We don't watch for file changes in windows now but if this should ever
+ // be extended to Windows these lines might save some time of debugging.
+#if defined(OS_WIN)
+ if (!plugin_dirs[i].IsAbsolute())
+ continue;
+#endif
+ FilePathWatcher* watcher = new FilePathWatcher();
+ VLOG(1) << "Watching for changes in: " << plugin_dirs[i].value();
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&PluginServiceImpl::RegisterFilePathWatcher, watcher,
+ plugin_dirs[i]));
+ file_watchers_.push_back(watcher);
+ }
+#endif
+}
+
+PluginProcessHost* PluginServiceImpl::FindNpapiPluginProcess(
+ const base::FilePath& plugin_path) {
+ for (PluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->info().path == plugin_path)
+ return *iter;
+ }
+
+ return NULL;
+}
+
+PpapiPluginProcessHost* PluginServiceImpl::FindPpapiPluginProcess(
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory) {
+ for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->plugin_path() == plugin_path &&
+ iter->profile_data_directory() == profile_data_directory) {
+ return *iter;
+ }
+ }
+ return NULL;
+}
+
+PpapiPluginProcessHost* PluginServiceImpl::FindPpapiBrokerProcess(
+ const base::FilePath& broker_path) {
+ for (PpapiBrokerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->plugin_path() == broker_path)
+ return *iter;
+ }
+
+ return NULL;
+}
+
+PluginProcessHost* PluginServiceImpl::FindOrStartNpapiPluginProcess(
+ int render_process_id,
+ const base::FilePath& plugin_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (filter_ && !filter_->CanLoadPlugin(render_process_id, plugin_path))
+ return NULL;
+
+ PluginProcessHost* plugin_host = FindNpapiPluginProcess(plugin_path);
+ if (plugin_host)
+ return plugin_host;
+
+ WebPluginInfo info;
+ if (!GetPluginInfoByPath(plugin_path, &info)) {
+ return NULL;
+ }
+
+ // Record when NPAPI Flash process is started for the first time.
+ static bool counted = false;
+ if (!counted && UTF16ToUTF8(info.name) == kFlashPluginName) {
+ counted = true;
+ UMA_HISTOGRAM_ENUMERATION("Plugin.FlashUsage",
+ START_NPAPI_FLASH_AT_LEAST_ONCE,
+ FLASH_USAGE_ENUM_COUNT);
+ }
+
+ // This plugin isn't loaded by any plugin process, so create a new process.
+ scoped_ptr<PluginProcessHost> new_host(new PluginProcessHost());
+ if (!new_host->Init(info)) {
+ NOTREACHED(); // Init is not expected to fail.
+ return NULL;
+ }
+ return new_host.release();
+}
+
+PpapiPluginProcessHost* PluginServiceImpl::FindOrStartPpapiPluginProcess(
+ int render_process_id,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ PpapiPluginProcessHost::PluginClient* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (filter_ && !filter_->CanLoadPlugin(render_process_id, plugin_path))
+ return NULL;
+
+ PpapiPluginProcessHost* plugin_host =
+ FindPpapiPluginProcess(plugin_path, profile_data_directory);
+ if (plugin_host)
+ return plugin_host;
+
+ // Validate that the plugin is actually registered.
+ PepperPluginInfo* info = GetRegisteredPpapiPluginInfo(plugin_path);
+ if (!info)
+ return NULL;
+
+ // Record when PPAPI Flash process is started for the first time.
+ static bool counted = false;
+ if (!counted && info->name == kFlashPluginName) {
+ counted = true;
+ UMA_HISTOGRAM_ENUMERATION("Plugin.FlashUsage",
+ START_PPAPI_FLASH_AT_LEAST_ONCE,
+ FLASH_USAGE_ENUM_COUNT);
+ }
+
+ // This plugin isn't loaded by any plugin process, so create a new process.
+ return PpapiPluginProcessHost::CreatePluginHost(
+ *info, profile_data_directory,
+ client->GetResourceContext()->GetHostResolver());
+}
+
+PpapiPluginProcessHost* PluginServiceImpl::FindOrStartPpapiBrokerProcess(
+ int render_process_id,
+ const base::FilePath& plugin_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (filter_ && !filter_->CanLoadPlugin(render_process_id, plugin_path))
+ return NULL;
+
+ PpapiPluginProcessHost* plugin_host = FindPpapiBrokerProcess(plugin_path);
+ if (plugin_host)
+ return plugin_host;
+
+ // Validate that the plugin is actually registered.
+ PepperPluginInfo* info = GetRegisteredPpapiPluginInfo(plugin_path);
+ if (!info)
+ return NULL;
+
+ // TODO(ddorwin): Uncomment once out of process is supported.
+ // DCHECK(info->is_out_of_process);
+
+ // This broker isn't loaded by any broker process, so create a new process.
+ return PpapiPluginProcessHost::CreateBrokerHost(*info);
+}
+
+void PluginServiceImpl::OpenChannelToNpapiPlugin(
+ int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!ContainsKey(pending_plugin_clients_, client));
+ pending_plugin_clients_.insert(client);
+
+ // Make sure plugins are loaded if necessary.
+ PluginServiceFilterParams params = {
+ render_process_id,
+ render_view_id,
+ page_url,
+ client->GetResourceContext()
+ };
+ GetPlugins(base::Bind(
+ &PluginServiceImpl::ForwardGetAllowedPluginForOpenChannelToPlugin,
+ base::Unretained(this), params, url, mime_type, client));
+}
+
+void PluginServiceImpl::OpenChannelToPpapiPlugin(
+ int render_process_id,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ PpapiPluginProcessHost::PluginClient* client) {
+ PpapiPluginProcessHost* plugin_host = FindOrStartPpapiPluginProcess(
+ render_process_id, plugin_path, profile_data_directory, client);
+ if (plugin_host) {
+ plugin_host->OpenChannelToPlugin(client);
+ } else {
+ // Send error.
+ client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0);
+ }
+}
+
+void PluginServiceImpl::OpenChannelToPpapiBroker(
+ int render_process_id,
+ const base::FilePath& path,
+ PpapiPluginProcessHost::BrokerClient* client) {
+ PpapiPluginProcessHost* plugin_host = FindOrStartPpapiBrokerProcess(
+ render_process_id, path);
+ if (plugin_host) {
+ plugin_host->OpenChannelToPlugin(client);
+ } else {
+ // Send error.
+ client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0);
+ }
+}
+
+void PluginServiceImpl::CancelOpenChannelToNpapiPlugin(
+ PluginProcessHost::Client* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(ContainsKey(pending_plugin_clients_, client));
+ pending_plugin_clients_.erase(client);
+}
+
+void PluginServiceImpl::ForwardGetAllowedPluginForOpenChannelToPlugin(
+ const PluginServiceFilterParams& params,
+ const GURL& url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client,
+ const std::vector<WebPluginInfo>&) {
+ GetAllowedPluginForOpenChannelToPlugin(params.render_process_id,
+ params.render_view_id, url, params.page_url, mime_type, client,
+ params.resource_context);
+}
+
+void PluginServiceImpl::GetAllowedPluginForOpenChannelToPlugin(
+ int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client,
+ ResourceContext* resource_context) {
+ WebPluginInfo info;
+ bool allow_wildcard = true;
+ bool found = GetPluginInfo(
+ render_process_id, render_view_id, resource_context,
+ url, page_url, mime_type, allow_wildcard,
+ NULL, &info, NULL);
+ base::FilePath plugin_path;
+ if (found)
+ plugin_path = info.path;
+
+ // Now we jump back to the IO thread to finish opening the channel.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PluginServiceImpl::FinishOpenChannelToPlugin,
+ base::Unretained(this),
+ render_process_id,
+ plugin_path,
+ client));
+}
+
+void PluginServiceImpl::FinishOpenChannelToPlugin(
+ int render_process_id,
+ const base::FilePath& plugin_path,
+ PluginProcessHost::Client* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Make sure it hasn't been canceled yet.
+ if (!ContainsKey(pending_plugin_clients_, client))
+ return;
+ pending_plugin_clients_.erase(client);
+
+ PluginProcessHost* plugin_host = FindOrStartNpapiPluginProcess(
+ render_process_id, plugin_path);
+ if (plugin_host) {
+ client->OnFoundPluginProcessHost(plugin_host);
+ plugin_host->OpenChannelToPlugin(client);
+ } else {
+ client->OnError();
+ }
+}
+
+bool PluginServiceImpl::GetPluginInfoArray(
+ const GURL& url,
+ const std::string& mime_type,
+ bool allow_wildcard,
+ std::vector<WebPluginInfo>* plugins,
+ std::vector<std::string>* actual_mime_types) {
+ bool use_stale = false;
+ PluginList::Singleton()->GetPluginInfoArray(
+ url, mime_type, allow_wildcard, &use_stale, NPAPIPluginsSupported(),
+ plugins, actual_mime_types);
+ return use_stale;
+}
+
+bool PluginServiceImpl::GetPluginInfo(int render_process_id,
+ int render_view_id,
+ ResourceContext* context,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ bool allow_wildcard,
+ bool* is_stale,
+ WebPluginInfo* info,
+ std::string* actual_mime_type) {
+ std::vector<WebPluginInfo> plugins;
+ std::vector<std::string> mime_types;
+ bool stale = GetPluginInfoArray(
+ url, mime_type, allow_wildcard, &plugins, &mime_types);
+ if (is_stale)
+ *is_stale = stale;
+
+ for (size_t i = 0; i < plugins.size(); ++i) {
+ if (!filter_ || filter_->IsPluginAvailable(render_process_id,
+ render_view_id,
+ context,
+ url,
+ page_url,
+ &plugins[i])) {
+ *info = plugins[i];
+ if (actual_mime_type)
+ *actual_mime_type = mime_types[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+bool PluginServiceImpl::GetPluginInfoByPath(const base::FilePath& plugin_path,
+ WebPluginInfo* info) {
+ std::vector<WebPluginInfo> plugins;
+ PluginList::Singleton()->GetPluginsNoRefresh(&plugins);
+
+ for (std::vector<WebPluginInfo>::iterator it = plugins.begin();
+ it != plugins.end();
+ ++it) {
+ if (it->path == plugin_path) {
+ *info = *it;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+string16 PluginServiceImpl::GetPluginDisplayNameByPath(
+ const base::FilePath& path) {
+ string16 plugin_name = path.LossyDisplayName();
+ WebPluginInfo info;
+ if (PluginService::GetInstance()->GetPluginInfoByPath(path, &info) &&
+ !info.name.empty()) {
+ plugin_name = info.name;
+#if defined(OS_MACOSX)
+ // Many plugins on the Mac have .plugin in the actual name, which looks
+ // terrible, so look for that and strip it off if present.
+ const std::string kPluginExtension = ".plugin";
+ if (EndsWith(plugin_name, ASCIIToUTF16(kPluginExtension), true))
+ plugin_name.erase(plugin_name.length() - kPluginExtension.length());
+#endif // OS_MACOSX
+ }
+ return plugin_name;
+}
+
+void PluginServiceImpl::GetPlugins(const GetPluginsCallback& callback) {
+ scoped_refptr<base::MessageLoopProxy> target_loop(
+ base::MessageLoop::current()->message_loop_proxy());
+
+ if (LoadPluginListInProcess()) {
+ BrowserThread::GetBlockingPool()->
+ PostSequencedWorkerTaskWithShutdownBehavior(
+ plugin_list_token_,
+ FROM_HERE,
+ base::Bind(&PluginServiceImpl::GetPluginsInternal,
+ base::Unretained(this),
+ target_loop, callback),
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+ return;
+ }
+#if defined(OS_POSIX)
+ std::vector<WebPluginInfo> cached_plugins;
+ if (PluginList::Singleton()->GetPluginsNoRefresh(&cached_plugins)) {
+ // Can't assume the caller is reentrant.
+ target_loop->PostTask(FROM_HERE,
+ base::Bind(callback, cached_plugins));
+ } else {
+ // If we switch back to loading plugins in process, then we need to make
+ // sure g_thread_init() gets called since plugins may call glib at load.
+ if (!plugin_loader_.get())
+ plugin_loader_ = new PluginLoaderPosix;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&PluginLoaderPosix::LoadPlugins, plugin_loader_,
+ target_loop, callback));
+ }
+#else
+ NOTREACHED();
+#endif
+}
+
+void PluginServiceImpl::GetPluginsInternal(
+ base::MessageLoopProxy* target_loop,
+ const PluginService::GetPluginsCallback& callback) {
+ DCHECK(BrowserThread::GetBlockingPool()->IsRunningSequenceOnCurrentThread(
+ plugin_list_token_));
+
+ std::vector<WebPluginInfo> plugins;
+ PluginList::Singleton()->GetPlugins(&plugins, NPAPIPluginsSupported());
+
+ target_loop->PostTask(FROM_HERE,
+ base::Bind(callback, plugins));
+}
+
+void PluginServiceImpl::OnWaitableEventSignaled(
+ base::WaitableEvent* waitable_event) {
+#if defined(OS_WIN)
+ if (waitable_event == hkcu_event_) {
+ hkcu_key_.StartWatching();
+ } else {
+ hklm_key_.StartWatching();
+ }
+
+ PluginList::Singleton()->RefreshPlugins();
+ PurgePluginListCache(NULL, false);
+#else
+ // This event should only get signaled on a Windows machine.
+ NOTREACHED();
+#endif // defined(OS_WIN)
+}
+
+void PluginServiceImpl::RegisterPepperPlugins() {
+ ComputePepperPluginList(&ppapi_plugins_);
+ for (size_t i = 0; i < ppapi_plugins_.size(); ++i) {
+ RegisterInternalPlugin(ppapi_plugins_[i].ToWebPluginInfo(), true);
+ }
+}
+
+// There should generally be very few plugins so a brute-force search is fine.
+PepperPluginInfo* PluginServiceImpl::GetRegisteredPpapiPluginInfo(
+ const base::FilePath& plugin_path) {
+ PepperPluginInfo* info = NULL;
+ for (size_t i = 0; i < ppapi_plugins_.size(); i++) {
+ if (ppapi_plugins_[i].path == plugin_path) {
+ info = &ppapi_plugins_[i];
+ break;
+ }
+ }
+ if (info)
+ return info;
+ // We did not find the plugin in our list. But wait! the plugin can also
+ // be a latecomer, as it happens with pepper flash. This information
+ // can be obtained from the PluginList singleton and we can use it to
+ // construct it and add it to the list. This same deal needs to be done
+ // in the renderer side in PepperPluginRegistry.
+ WebPluginInfo webplugin_info;
+ if (!GetPluginInfoByPath(plugin_path, &webplugin_info))
+ return NULL;
+ PepperPluginInfo new_pepper_info;
+ if (!MakePepperPluginInfo(webplugin_info, &new_pepper_info))
+ return NULL;
+ ppapi_plugins_.push_back(new_pepper_info);
+ return &ppapi_plugins_[ppapi_plugins_.size() - 1];
+}
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+// static
+void PluginServiceImpl::RegisterFilePathWatcher(FilePathWatcher* watcher,
+ const base::FilePath& path) {
+ bool result = watcher->Watch(path, false,
+ base::Bind(&NotifyPluginDirChanged));
+ DCHECK(result);
+}
+#endif
+
+void PluginServiceImpl::SetFilter(PluginServiceFilter* filter) {
+ filter_ = filter;
+}
+
+PluginServiceFilter* PluginServiceImpl::GetFilter() {
+ return filter_;
+}
+
+void PluginServiceImpl::ForcePluginShutdown(const base::FilePath& plugin_path) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PluginServiceImpl::ForcePluginShutdown,
+ base::Unretained(this), plugin_path));
+ return;
+ }
+
+ PluginProcessHost* plugin = FindNpapiPluginProcess(plugin_path);
+ if (plugin)
+ plugin->ForceShutdown();
+}
+
+static const unsigned int kMaxCrashesPerInterval = 3;
+static const unsigned int kCrashesInterval = 120;
+
+void PluginServiceImpl::RegisterPluginCrash(const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ std::map<base::FilePath, std::vector<base::Time> >::iterator i =
+ crash_times_.find(path);
+ if (i == crash_times_.end()) {
+ crash_times_[path] = std::vector<base::Time>();
+ i = crash_times_.find(path);
+ }
+ if (i->second.size() == kMaxCrashesPerInterval) {
+ i->second.erase(i->second.begin());
+ }
+ base::Time time = base::Time::Now();
+ i->second.push_back(time);
+}
+
+bool PluginServiceImpl::IsPluginUnstable(const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ std::map<base::FilePath, std::vector<base::Time> >::const_iterator i =
+ crash_times_.find(path);
+ if (i == crash_times_.end()) {
+ return false;
+ }
+ if (i->second.size() != kMaxCrashesPerInterval) {
+ return false;
+ }
+ base::TimeDelta delta = base::Time::Now() - i->second[0];
+ return delta.InSeconds() <= kCrashesInterval;
+}
+
+void PluginServiceImpl::RefreshPlugins() {
+ PluginList::Singleton()->RefreshPlugins();
+}
+
+void PluginServiceImpl::AddExtraPluginPath(const base::FilePath& path) {
+ if (!NPAPIPluginsSupported()) {
+ // TODO(jam): remove and just have CHECK once we're sure this doesn't get
+ // triggered.
+ DLOG(INFO) << "NPAPI plugins not supported";
+ return;
+ }
+ PluginList::Singleton()->AddExtraPluginPath(path);
+}
+
+void PluginServiceImpl::RemoveExtraPluginPath(const base::FilePath& path) {
+ PluginList::Singleton()->RemoveExtraPluginPath(path);
+}
+
+void PluginServiceImpl::AddExtraPluginDir(const base::FilePath& path) {
+ PluginList::Singleton()->AddExtraPluginDir(path);
+}
+
+void PluginServiceImpl::RegisterInternalPlugin(
+ const WebPluginInfo& info,
+ bool add_at_beginning) {
+ if (!NPAPIPluginsSupported() &&
+ info.type == WebPluginInfo::PLUGIN_TYPE_NPAPI) {
+ DLOG(INFO) << "Don't register NPAPI plugins when they're not supported";
+ return;
+ }
+ PluginList::Singleton()->RegisterInternalPlugin(info, add_at_beginning);
+}
+
+void PluginServiceImpl::UnregisterInternalPlugin(const base::FilePath& path) {
+ PluginList::Singleton()->UnregisterInternalPlugin(path);
+}
+
+void PluginServiceImpl::GetInternalPlugins(
+ std::vector<WebPluginInfo>* plugins) {
+ PluginList::Singleton()->GetInternalPlugins(plugins);
+}
+
+bool PluginServiceImpl::NPAPIPluginsSupported() {
+#if defined(OS_WIN) || defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(USE_AURA))
+ return true;
+#else
+ return false;
+#endif
+}
+
+void PluginServiceImpl::DisablePluginsDiscoveryForTesting() {
+ PluginList::Singleton()->DisablePluginsDiscovery();
+}
+
+#if defined(OS_MACOSX)
+void PluginServiceImpl::AppActivated() {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&NotifyPluginsOfActivation));
+}
+#elif defined(OS_WIN)
+
+bool GetPluginPropertyFromWindow(
+ HWND window, const wchar_t* plugin_atom_property,
+ base::string16* plugin_property) {
+ ATOM plugin_atom = reinterpret_cast<ATOM>(
+ GetPropW(window, plugin_atom_property));
+ if (plugin_atom != 0) {
+ WCHAR plugin_property_local[MAX_PATH] = {0};
+ GlobalGetAtomNameW(plugin_atom,
+ plugin_property_local,
+ ARRAYSIZE(plugin_property_local));
+ *plugin_property = plugin_property_local;
+ return true;
+ }
+ return false;
+}
+
+bool PluginServiceImpl::GetPluginInfoFromWindow(
+ HWND window,
+ base::string16* plugin_name,
+ base::string16* plugin_version) {
+ if (!IsPluginWindow(window))
+ return false;
+
+ GetPluginPropertyFromWindow(
+ window, kPluginNameAtomProperty, plugin_name);
+ GetPluginPropertyFromWindow(
+ window, kPluginVersionAtomProperty, plugin_version);
+ return true;
+}
+
+bool PluginServiceImpl::IsPluginWindow(HWND window) {
+ return ui::GetClassName(window) == base::string16(kNativeWindowClassName);
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/plugin_service_impl.h b/chromium/content/browser/plugin_service_impl.h
new file mode 100644
index 00000000000..6d358fd21a9
--- /dev/null
+++ b/chromium/content/browser/plugin_service_impl.h
@@ -0,0 +1,254 @@
+// 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 class responds to requests from renderers for the list of plugins, and
+// also a proxy object for plugin instances.
+
+#ifndef CONTENT_BROWSER_PLUGIN_SERVICE_IMPL_H_
+#define CONTENT_BROWSER_PLUGIN_SERVICE_IMPL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/waitable_event_watcher.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "content/browser/plugin_process_host.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/pepper_plugin_info.h"
+#include "ipc/ipc_channel_handle.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "base/memory/scoped_ptr.h"
+#include "base/win/registry.h"
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+#include "base/files/file_path_watcher.h"
+#endif
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace webkit {
+namespace npapi {
+class PluginList;
+}
+}
+
+namespace content {
+class BrowserContext;
+class PluginDirWatcherDelegate;
+class PluginLoaderPosix;
+class PluginServiceFilter;
+class ResourceContext;
+struct PepperPluginInfo;
+
+// base::Bind() has limited arity, and the filter-related methods tend to
+// surpass that limit.
+struct PluginServiceFilterParams {
+ int render_process_id;
+ int render_view_id;
+ GURL page_url;
+ ResourceContext* resource_context;
+};
+
+class CONTENT_EXPORT PluginServiceImpl
+ : NON_EXPORTED_BASE(public PluginService) {
+ public:
+ // Returns the PluginServiceImpl singleton.
+ static PluginServiceImpl* GetInstance();
+
+ // PluginService implementation:
+ virtual void Init() OVERRIDE;
+ virtual void StartWatchingPlugins() OVERRIDE;
+ virtual bool GetPluginInfoArray(
+ const GURL& url,
+ const std::string& mime_type,
+ bool allow_wildcard,
+ std::vector<WebPluginInfo>* info,
+ std::vector<std::string>* actual_mime_types) OVERRIDE;
+ virtual bool GetPluginInfo(int render_process_id,
+ int render_view_id,
+ ResourceContext* context,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ bool allow_wildcard,
+ bool* is_stale,
+ WebPluginInfo* info,
+ std::string* actual_mime_type) OVERRIDE;
+ virtual bool GetPluginInfoByPath(const base::FilePath& plugin_path,
+ WebPluginInfo* info) OVERRIDE;
+ virtual string16 GetPluginDisplayNameByPath(
+ const base::FilePath& path) OVERRIDE;
+ virtual void GetPlugins(const GetPluginsCallback& callback) OVERRIDE;
+ virtual PepperPluginInfo* GetRegisteredPpapiPluginInfo(
+ const base::FilePath& plugin_path) OVERRIDE;
+ virtual void SetFilter(PluginServiceFilter* filter) OVERRIDE;
+ virtual PluginServiceFilter* GetFilter() OVERRIDE;
+ virtual void ForcePluginShutdown(const base::FilePath& plugin_path) OVERRIDE;
+ virtual bool IsPluginUnstable(const base::FilePath& plugin_path) OVERRIDE;
+ virtual void RefreshPlugins() OVERRIDE;
+ virtual void AddExtraPluginPath(const base::FilePath& path) OVERRIDE;
+ virtual void RemoveExtraPluginPath(const base::FilePath& path) OVERRIDE;
+ virtual void AddExtraPluginDir(const base::FilePath& path) OVERRIDE;
+ virtual void RegisterInternalPlugin(
+ const WebPluginInfo& info, bool add_at_beginning) OVERRIDE;
+ virtual void UnregisterInternalPlugin(const base::FilePath& path) OVERRIDE;
+ virtual void GetInternalPlugins(
+ std::vector<WebPluginInfo>* plugins) OVERRIDE;
+ virtual bool NPAPIPluginsSupported() OVERRIDE;
+ virtual void DisablePluginsDiscoveryForTesting() OVERRIDE;
+#if defined(OS_MACOSX)
+ virtual void AppActivated() OVERRIDE;
+#elif defined(OS_WIN)
+ virtual bool GetPluginInfoFromWindow(HWND window,
+ base::string16* plugin_name,
+ base::string16* plugin_version) OVERRIDE;
+
+ // Returns true iff the given HWND is a plugin.
+ bool IsPluginWindow(HWND window);
+#endif
+
+ // Returns the plugin process host corresponding to the plugin process that
+ // has been started by this service. This will start a process to host the
+ // 'plugin_path' if needed. If the process fails to start, the return value
+ // is NULL. Must be called on the IO thread.
+ PluginProcessHost* FindOrStartNpapiPluginProcess(
+ int render_process_id, const base::FilePath& plugin_path);
+ PpapiPluginProcessHost* FindOrStartPpapiPluginProcess(
+ int render_process_id,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ PpapiPluginProcessHost::PluginClient* client);
+ PpapiPluginProcessHost* FindOrStartPpapiBrokerProcess(
+ int render_process_id, const base::FilePath& plugin_path);
+
+ // Opens a channel to a plugin process for the given mime type, starting
+ // a new plugin process if necessary. This must be called on the IO thread
+ // or else a deadlock can occur.
+ void OpenChannelToNpapiPlugin(int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client);
+ void OpenChannelToPpapiPlugin(int render_process_id,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ PpapiPluginProcessHost::PluginClient* client);
+ void OpenChannelToPpapiBroker(int render_process_id,
+ const base::FilePath& path,
+ PpapiPluginProcessHost::BrokerClient* client);
+
+ // Cancels opening a channel to a NPAPI plugin.
+ void CancelOpenChannelToNpapiPlugin(PluginProcessHost::Client* client);
+
+ // Used to monitor plug-in stability.
+ void RegisterPluginCrash(const base::FilePath& plugin_path);
+
+ private:
+ friend struct DefaultSingletonTraits<PluginServiceImpl>;
+
+ // Creates the PluginServiceImpl object, but doesn't actually build the plugin
+ // list yet. It's generated lazily.
+ PluginServiceImpl();
+ virtual ~PluginServiceImpl();
+
+ void OnWaitableEventSignaled(base::WaitableEvent* waitable_event);
+
+ // Returns the plugin process host corresponding to the plugin process that
+ // has been started by this service. Returns NULL if no process has been
+ // started.
+ PluginProcessHost* FindNpapiPluginProcess(const base::FilePath& plugin_path);
+ PpapiPluginProcessHost* FindPpapiPluginProcess(
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory);
+ PpapiPluginProcessHost* FindPpapiBrokerProcess(
+ const base::FilePath& broker_path);
+
+ void RegisterPepperPlugins();
+
+ // Run on the blocking pool to load the plugins synchronously.
+ void GetPluginsInternal(base::MessageLoopProxy* target_loop,
+ const GetPluginsCallback& callback);
+
+ // Binding directly to GetAllowedPluginForOpenChannelToPlugin() isn't possible
+ // because more arity is needed <http://crbug.com/98542>. This just forwards.
+ void ForwardGetAllowedPluginForOpenChannelToPlugin(
+ const PluginServiceFilterParams& params,
+ const GURL& url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client,
+ const std::vector<WebPluginInfo>&);
+ // Helper so we can do the plugin lookup on the FILE thread.
+ void GetAllowedPluginForOpenChannelToPlugin(
+ int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ PluginProcessHost::Client* client,
+ ResourceContext* resource_context);
+
+ // Helper so we can finish opening the channel after looking up the
+ // plugin.
+ void FinishOpenChannelToPlugin(int render_process_id,
+ const base::FilePath& plugin_path,
+ PluginProcessHost::Client* client);
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+ // Registers a new FilePathWatcher for a given path.
+ static void RegisterFilePathWatcher(base::FilePathWatcher* watcher,
+ const base::FilePath& path);
+#endif
+
+#if defined(OS_WIN)
+ // Registry keys for getting notifications when new plugins are installed.
+ base::win::RegKey hkcu_key_;
+ base::win::RegKey hklm_key_;
+ scoped_ptr<base::WaitableEvent> hkcu_event_;
+ scoped_ptr<base::WaitableEvent> hklm_event_;
+ base::WaitableEventWatcher hkcu_watcher_;
+ base::WaitableEventWatcher hklm_watcher_;
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
+ ScopedVector<base::FilePathWatcher> file_watchers_;
+#endif
+
+ std::vector<PepperPluginInfo> ppapi_plugins_;
+
+ // Weak pointer; outlives us.
+ PluginServiceFilter* filter_;
+
+ std::set<PluginProcessHost::Client*> pending_plugin_clients_;
+
+ // Used to sequentialize loading plug-ins from disk.
+ base::SequencedWorkerPool::SequenceToken plugin_list_token_;
+
+#if defined(OS_POSIX)
+ scoped_refptr<PluginLoaderPosix> plugin_loader_;
+#endif
+
+ // Used to detect if a given plug-in is crashing over and over.
+ std::map<base::FilePath, std::vector<base::Time> > crash_times_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginServiceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PLUGIN_SERVICE_IMPL_H_
diff --git a/chromium/content/browser/plugin_service_impl_browsertest.cc b/chromium/content/browser/plugin_service_impl_browsertest.cc
new file mode 100644
index 00000000000..1abf725e435
--- /dev/null
+++ b/chromium/content/browser/plugin_service_impl_browsertest.cc
@@ -0,0 +1,377 @@
+// 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 "content/browser/plugin_service_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/plugin_service_filter.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+
+const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test";
+
+void OpenChannel(PluginProcessHost::Client* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Start opening the channel
+ PluginServiceImpl::GetInstance()->OpenChannelToNpapiPlugin(
+ 0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, client);
+}
+
+// Mock up of the Client and the Listener classes that would supply the
+// communication channel with the plugin.
+class MockPluginProcessHostClient : public PluginProcessHost::Client,
+ public IPC::Listener {
+ public:
+ MockPluginProcessHostClient(ResourceContext* context, bool expect_fail)
+ : context_(context),
+ channel_(NULL),
+ set_plugin_info_called_(false),
+ expect_fail_(expect_fail) {
+ }
+
+ virtual ~MockPluginProcessHostClient() {
+ if (channel_)
+ BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
+ }
+
+ // PluginProcessHost::Client implementation.
+ virtual int ID() OVERRIDE { return 42; }
+ virtual bool OffTheRecord() OVERRIDE { return false; }
+ virtual ResourceContext* GetResourceContext() OVERRIDE {
+ return context_;
+ }
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {}
+ virtual void OnSentPluginChannelRequest() OVERRIDE {}
+
+ virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
+ ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ASSERT_TRUE(set_plugin_info_called_);
+ ASSERT_TRUE(!channel_);
+ channel_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this);
+ ASSERT_TRUE(channel_->Connect());
+ }
+
+ virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
+ ASSERT_TRUE(info.mime_types.size());
+ ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type);
+ set_plugin_info_called_ = true;
+ }
+
+ virtual void OnError() OVERRIDE {
+ Fail();
+ }
+
+ // IPC::Listener implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ Fail();
+ return false;
+ }
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE {
+ if (expect_fail_)
+ FAIL();
+ QuitMessageLoop();
+ }
+ virtual void OnChannelError() OVERRIDE {
+ Fail();
+ }
+#if defined(OS_POSIX)
+ virtual void OnChannelDenied() OVERRIDE {
+ Fail();
+ }
+ virtual void OnChannelListenError() OVERRIDE {
+ Fail();
+ }
+#endif
+
+ private:
+ void Fail() {
+ if (!expect_fail_)
+ FAIL();
+ QuitMessageLoop();
+ }
+
+ void QuitMessageLoop() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ ResourceContext* context_;
+ IPC::Channel* channel_;
+ bool set_plugin_info_called_;
+ bool expect_fail_;
+ DISALLOW_COPY_AND_ASSIGN(MockPluginProcessHostClient);
+};
+
+class MockPluginServiceFilter : public content::PluginServiceFilter {
+ public:
+ MockPluginServiceFilter() {}
+
+ virtual bool IsPluginAvailable(
+ int render_process_id,
+ int render_view_id,
+ const void* context,
+ const GURL& url,
+ const GURL& policy_url,
+ WebPluginInfo* plugin) OVERRIDE { return true; }
+
+ virtual bool CanLoadPlugin(
+ int render_process_id,
+ const base::FilePath& path) OVERRIDE { return false; }
+};
+
+class PluginServiceTest : public ContentBrowserTest {
+ public:
+ PluginServiceTest() {}
+
+ ResourceContext* GetResourceContext() {
+ return shell()->web_contents()->GetBrowserContext()->GetResourceContext();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+#if defined(OS_MACOSX)
+ base::FilePath browser_directory;
+ PathService::Get(base::DIR_MODULE, &browser_directory);
+ command_line->AppendSwitchPath(switches::kExtraPluginDir,
+ browser_directory.AppendASCII("plugins"));
+#endif
+ // TODO(jam): since these plugin tests are running under Chrome, we need to
+ // tell it to disable its security features for old plugins. Once this is
+ // running under content_browsertests, these flags won't be needed.
+ // http://crbug.com/90448
+ // switches::kAlwaysAuthorizePlugins
+ command_line->AppendSwitch("always-authorize-plugins");
+ }
+};
+
+// Try to open a channel to the test plugin. Minimal plugin process spawning
+// test for the PluginService interface.
+IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) {
+ if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
+ return;
+ MockPluginProcessHostClient mock_client(GetResourceContext(), false);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&OpenChannel, &mock_client));
+ RunMessageLoop();
+}
+
+IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToDeniedPlugin) {
+ if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
+ return;
+ MockPluginServiceFilter filter;
+ PluginServiceImpl::GetInstance()->SetFilter(&filter);
+ MockPluginProcessHostClient mock_client(GetResourceContext(), true);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&OpenChannel, &mock_client));
+ RunMessageLoop();
+}
+
+// A strict mock that fails if any of the methods are called. They shouldn't be
+// called since the request should get canceled before then.
+class MockCanceledPluginServiceClient : public PluginProcessHost::Client {
+ public:
+ MockCanceledPluginServiceClient(ResourceContext* context)
+ : context_(context),
+ get_resource_context_called_(false) {
+ }
+
+ virtual ~MockCanceledPluginServiceClient() {}
+
+ // Client implementation.
+ MOCK_METHOD0(ID, int());
+ virtual ResourceContext* GetResourceContext() OVERRIDE {
+ get_resource_context_called_ = true;
+ return context_;
+ }
+ MOCK_METHOD0(OffTheRecord, bool());
+ MOCK_METHOD1(OnFoundPluginProcessHost, void(PluginProcessHost* host));
+ MOCK_METHOD0(OnSentPluginChannelRequest, void());
+ MOCK_METHOD1(OnChannelOpened, void(const IPC::ChannelHandle& handle));
+ MOCK_METHOD1(SetPluginInfo, void(const WebPluginInfo& info));
+ MOCK_METHOD0(OnError, void());
+
+ bool get_resource_context_called() const {
+ return get_resource_context_called_;
+ }
+
+ private:
+ ResourceContext* context_;
+ bool get_resource_context_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCanceledPluginServiceClient);
+};
+
+void QuitUIMessageLoopFromIOThread() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+void OpenChannelAndThenCancel(PluginProcessHost::Client* client) {
+ OpenChannel(client);
+ // Immediately cancel it. This is guaranteed to work since PluginService needs
+ // to consult its filter on the FILE thread.
+ PluginServiceImpl::GetInstance()->CancelOpenChannelToNpapiPlugin(client);
+ // Before we terminate the test, add a roundtrip through the FILE thread to
+ // make sure that it's had a chance to post back to the IO thread. Then signal
+ // the UI thread to stop and exit the test.
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&base::DoNothing),
+ base::Bind(&QuitUIMessageLoopFromIOThread));
+}
+
+// Should not attempt to open a channel, since it should be canceled early on.
+IN_PROC_BROWSER_TEST_F(PluginServiceTest, CancelOpenChannelToPluginService) {
+ ::testing::StrictMock<MockCanceledPluginServiceClient> mock_client(
+ GetResourceContext());
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(OpenChannelAndThenCancel, &mock_client));
+ RunMessageLoop();
+ EXPECT_TRUE(mock_client.get_resource_context_called());
+}
+
+class MockCanceledBeforeSentPluginProcessHostClient
+ : public MockCanceledPluginServiceClient {
+ public:
+ MockCanceledBeforeSentPluginProcessHostClient(
+ ResourceContext* context)
+ : MockCanceledPluginServiceClient(context),
+ set_plugin_info_called_(false),
+ on_found_plugin_process_host_called_(false),
+ host_(NULL) {}
+
+ virtual ~MockCanceledBeforeSentPluginProcessHostClient() {}
+
+ // Client implementation.
+ virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ASSERT_TRUE(info.mime_types.size());
+ ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type);
+ set_plugin_info_called_ = true;
+ }
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ set_on_found_plugin_process_host_called();
+ set_host(host);
+ // This gets called right before we request the plugin<=>renderer channel,
+ // so we have to post a task to cancel it.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&PluginProcessHost::CancelPendingRequest,
+ base::Unretained(host),
+ this));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&QuitUIMessageLoopFromIOThread));
+ }
+
+ bool set_plugin_info_called() const {
+ return set_plugin_info_called_;
+ }
+
+ bool on_found_plugin_process_host_called() const {
+ return on_found_plugin_process_host_called_;
+ }
+
+ protected:
+ void set_on_found_plugin_process_host_called() {
+ on_found_plugin_process_host_called_ = true;
+ }
+ void set_host(PluginProcessHost* host) {
+ host_ = host;
+ }
+
+ PluginProcessHost* host() const { return host_; }
+
+ private:
+ bool set_plugin_info_called_;
+ bool on_found_plugin_process_host_called_;
+ PluginProcessHost* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient);
+};
+
+IN_PROC_BROWSER_TEST_F(
+ PluginServiceTest, CancelBeforeSentOpenChannelToPluginProcessHost) {
+ if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
+ return;
+ ::testing::StrictMock<MockCanceledBeforeSentPluginProcessHostClient>
+ mock_client(GetResourceContext());
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&OpenChannel, &mock_client));
+ RunMessageLoop();
+ EXPECT_TRUE(mock_client.get_resource_context_called());
+ EXPECT_TRUE(mock_client.set_plugin_info_called());
+ EXPECT_TRUE(mock_client.on_found_plugin_process_host_called());
+}
+
+class MockCanceledAfterSentPluginProcessHostClient
+ : public MockCanceledBeforeSentPluginProcessHostClient {
+ public:
+ MockCanceledAfterSentPluginProcessHostClient(
+ ResourceContext* context)
+ : MockCanceledBeforeSentPluginProcessHostClient(context),
+ on_sent_plugin_channel_request_called_(false) {}
+ virtual ~MockCanceledAfterSentPluginProcessHostClient() {}
+
+ // Client implementation.
+
+ virtual int ID() OVERRIDE { return 42; }
+ virtual bool OffTheRecord() OVERRIDE { return false; }
+
+ // We override this guy again since we don't want to cancel yet.
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ set_on_found_plugin_process_host_called();
+ set_host(host);
+ }
+
+ virtual void OnSentPluginChannelRequest() OVERRIDE {
+ on_sent_plugin_channel_request_called_ = true;
+ host()->CancelSentRequest(this);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ bool on_sent_plugin_channel_request_called() const {
+ return on_sent_plugin_channel_request_called_;
+ }
+
+ private:
+ bool on_sent_plugin_channel_request_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCanceledAfterSentPluginProcessHostClient);
+};
+
+// Should not attempt to open a channel, since it should be canceled early on.
+IN_PROC_BROWSER_TEST_F(
+ PluginServiceTest, CancelAfterSentOpenChannelToPluginProcessHost) {
+ if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
+ return;
+ ::testing::StrictMock<MockCanceledAfterSentPluginProcessHostClient>
+ mock_client(GetResourceContext());
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&OpenChannel, &mock_client));
+ RunMessageLoop();
+ EXPECT_TRUE(mock_client.get_resource_context_called());
+ EXPECT_TRUE(mock_client.set_plugin_info_called());
+ EXPECT_TRUE(mock_client.on_found_plugin_process_host_called());
+ EXPECT_TRUE(mock_client.on_sent_plugin_channel_request_called());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_monitor_message_broadcaster.cc b/chromium/content/browser/power_monitor_message_broadcaster.cc
new file mode 100644
index 00000000000..77abcb959f9
--- /dev/null
+++ b/chromium/content/browser/power_monitor_message_broadcaster.cc
@@ -0,0 +1,39 @@
+// 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 "content/browser/power_monitor_message_broadcaster.h"
+
+#include "base/power_monitor/power_monitor.h"
+#include "content/common/power_monitor_messages.h"
+#include "ipc/ipc_sender.h"
+
+namespace content {
+
+PowerMonitorMessageBroadcaster::PowerMonitorMessageBroadcaster(
+ IPC::Sender* sender)
+ : sender_(sender) {
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ if (power_monitor)
+ power_monitor->AddObserver(this);
+}
+
+PowerMonitorMessageBroadcaster::~PowerMonitorMessageBroadcaster() {
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ if (power_monitor)
+ power_monitor->RemoveObserver(this);
+}
+
+void PowerMonitorMessageBroadcaster::OnPowerStateChange(bool on_battery_power) {
+ sender_->Send(new PowerMonitorMsg_PowerStateChange(on_battery_power));
+}
+
+void PowerMonitorMessageBroadcaster::OnSuspend() {
+ sender_->Send(new PowerMonitorMsg_Suspend());
+}
+
+void PowerMonitorMessageBroadcaster::OnResume() {
+ sender_->Send(new PowerMonitorMsg_Resume());
+}
+
+} // namespace content \ No newline at end of file
diff --git a/chromium/content/browser/power_monitor_message_broadcaster.h b/chromium/content/browser/power_monitor_message_broadcaster.h
new file mode 100644
index 00000000000..f0e3a207e2d
--- /dev/null
+++ b/chromium/content/browser/power_monitor_message_broadcaster.h
@@ -0,0 +1,41 @@
+// 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 CONTENT_BROWSER_POWER_MONITOR_MESSAGE_BROADCASTER_H_
+#define CONTENT_BROWSER_POWER_MONITOR_MESSAGE_BROADCASTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/power_monitor/power_observer.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace IPC {
+ class Sender;
+}
+
+namespace content {
+
+// A class used to monitor the power state change and communicate it to child
+// processes via IPC.
+class CONTENT_EXPORT PowerMonitorMessageBroadcaster
+ : public base::PowerObserver {
+ public:
+ explicit PowerMonitorMessageBroadcaster(IPC::Sender* sender);
+ virtual ~PowerMonitorMessageBroadcaster();
+
+ // Implement PowerObserver.
+ virtual void OnPowerStateChange(bool on_battery_power) OVERRIDE;
+ virtual void OnSuspend() OVERRIDE;
+ virtual void OnResume() OVERRIDE;
+
+ private:
+ IPC::Sender* sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerMonitorMessageBroadcaster);
+};
+
+} // namespace base
+
+#endif // CONTENT_BROWSER_POWER_MONITOR_MESSAGE_BROADCASTER_H_ \ No newline at end of file
diff --git a/chromium/content/browser/power_monitor_message_broadcaster_unittest.cc b/chromium/content/browser/power_monitor_message_broadcaster_unittest.cc
new file mode 100644
index 00000000000..515508266cf
--- /dev/null
+++ b/chromium/content/browser/power_monitor_message_broadcaster_unittest.cc
@@ -0,0 +1,109 @@
+// 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 "base/test/power_monitor_test_base.h"
+#include "content/browser/power_monitor_message_broadcaster.h"
+#include "content/common/power_monitor_messages.h"
+#include "ipc/ipc_sender.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class PowerMonitorMessageSender : public IPC::Sender {
+ public:
+ PowerMonitorMessageSender()
+ : power_state_changes_(0),
+ suspends_(0),
+ resumes_(0) {
+ }
+ virtual ~PowerMonitorMessageSender() {}
+
+ virtual bool Send(IPC::Message* msg) OVERRIDE {
+ switch (msg->type()) {
+ case PowerMonitorMsg_Suspend::ID:
+ suspends_++;
+ break;
+ case PowerMonitorMsg_Resume::ID:
+ resumes_++;
+ break;
+ case PowerMonitorMsg_PowerStateChange::ID:
+ power_state_changes_++;
+ break;
+ }
+ delete msg;
+ return true;
+ };
+
+ // Test status counts.
+ int power_state_changes() { return power_state_changes_; }
+ int suspends() { return suspends_; }
+ int resumes() { return resumes_; }
+
+ private:
+ int power_state_changes_; // Count of OnPowerStateChange notifications.
+ int suspends_; // Count of OnSuspend notifications.
+ int resumes_; // Count of OnResume notifications.
+};
+
+class PowerMonitorMessageBroadcasterTest : public testing::Test {
+ protected:
+ PowerMonitorMessageBroadcasterTest() {
+ power_monitor_source_ = new base::PowerMonitorTestSource();
+ power_monitor_.reset(new base::PowerMonitor(
+ scoped_ptr<base::PowerMonitorSource>(power_monitor_source_)));
+ }
+ virtual ~PowerMonitorMessageBroadcasterTest() {};
+
+ base::PowerMonitorTestSource* source() { return power_monitor_source_; }
+ base::PowerMonitor* monitor() { return power_monitor_.get(); }
+
+ private:
+ base::PowerMonitorTestSource* power_monitor_source_;
+ scoped_ptr<base::PowerMonitor> power_monitor_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerMonitorMessageBroadcasterTest);
+};
+
+TEST_F(PowerMonitorMessageBroadcasterTest, PowerMessageBroadcast) {
+ PowerMonitorMessageSender sender;
+ PowerMonitorMessageBroadcaster broadcaster(&sender);
+
+ // Sending resume when not suspended should have no effect.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(sender.resumes(), 0);
+
+ // Pretend we suspended.
+ source()->GenerateSuspendEvent();
+ EXPECT_EQ(sender.suspends(), 1);
+
+ // Send a second suspend notification. This should be suppressed.
+ source()->GenerateSuspendEvent();
+ EXPECT_EQ(sender.suspends(), 1);
+
+ // Pretend we were awakened.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(sender.resumes(), 1);
+
+ // Send a duplicate resume notification. This should be suppressed.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(sender.resumes(), 1);
+
+ // Pretend the device has gone on battery power
+ source()->GeneratePowerStateEvent(true);
+ EXPECT_EQ(sender.power_state_changes(), 1);
+
+ // Repeated indications the device is on battery power should be suppressed.
+ source()->GeneratePowerStateEvent(true);
+ EXPECT_EQ(sender.power_state_changes(), 1);
+
+ // Pretend the device has gone off battery power
+ source()->GeneratePowerStateEvent(false);
+ EXPECT_EQ(sender.power_state_changes(), 2);
+
+ // Repeated indications the device is off battery power should be suppressed.
+ source()->GeneratePowerStateEvent(false);
+ EXPECT_EQ(sender.power_state_changes(), 2);
+}
+
+} // namespace base
diff --git a/chromium/content/browser/power_save_blocker_android.cc b/chromium/content/browser/power_save_blocker_android.cc
new file mode 100644
index 00000000000..720fbb2bcd5
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_android.cc
@@ -0,0 +1,87 @@
+// 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 "content/browser/power_save_blocker_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_helper.h"
+#include "base/logging.h"
+#include "content/browser/power_save_blocker_impl.h"
+#include "content/public/browser/android/content_view_core.h"
+#include "content/public/browser/browser_thread.h"
+#include "jni/PowerSaveBlocker_jni.h"
+#include "ui/android/view_android.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+using gfx::NativeView;
+
+namespace content {
+
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ explicit Delegate(NativeView view_android) {
+ j_view_android_ = JavaObjectWeakGlobalRef(
+ AttachCurrentThread(), view_android->GetJavaObject().obj());
+ }
+
+ // Does the actual work to apply or remove the desired power save block.
+ void ApplyBlock();
+ void RemoveBlock();
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ ~Delegate() {}
+
+ JavaObjectWeakGlobalRef j_view_android_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_object = j_view_android_.get(env);
+ if (j_object.obj())
+ Java_PowerSaveBlocker_applyBlock(env, j_object.obj());
+}
+
+void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_object = j_view_android_.get(env);
+ if (j_object.obj())
+ Java_PowerSaveBlocker_removeBlock(env, j_object.obj());
+}
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
+ const std::string& reason) {
+ // Don't support kPowerSaveBlockPreventAppSuspension
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
+ if (delegate_) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::RemoveBlock, delegate_));
+ }
+}
+
+void PowerSaveBlockerImpl::InitDisplaySleepBlocker(NativeView view_android) {
+ if (!view_android)
+ return;
+
+ delegate_ = new Delegate(view_android);
+ // This may be called on any thread.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::ApplyBlock, delegate_));
+}
+
+bool RegisterPowerSaveBlocker(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_android.h b/chromium/content/browser/power_save_blocker_android.h
new file mode 100644
index 00000000000..1df7d697b53
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_android.h
@@ -0,0 +1,11 @@
+// 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 <jni.h>
+
+namespace content {
+
+bool RegisterPowerSaveBlocker(JNIEnv* env);
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_chromeos.cc b/chromium/content/browser/power_save_blocker_chromeos.cc
new file mode 100644
index 00000000000..9eda5c45027
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_chromeos.cc
@@ -0,0 +1,84 @@
+// 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 "content/browser/power_save_blocker_impl.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/power_policy_controller.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ Delegate(PowerSaveBlockerType type, const std::string& reason)
+ : type_(type),
+ reason_(reason),
+ block_id_(0) {}
+
+ void ApplyBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!chromeos::DBusThreadManager::IsInitialized()) {
+ LOG(WARNING) << "DBusThreadManager not initialized";
+ return;
+ }
+
+ chromeos::PowerPolicyController* controller =
+ chromeos::DBusThreadManager::Get()->GetPowerPolicyController();
+ switch (type_) {
+ case kPowerSaveBlockPreventAppSuspension:
+ block_id_ = controller->AddSystemWakeLock(reason_);
+ break;
+ case kPowerSaveBlockPreventDisplaySleep:
+ block_id_ = controller->AddScreenWakeLock(reason_);
+ break;
+ default:
+ NOTREACHED() << "Unhandled block type " << type_;
+ }
+ }
+
+ void RemoveBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!chromeos::DBusThreadManager::IsInitialized()) {
+ LOG(WARNING) << "DBusThreadManager not initialized";
+ return;
+ }
+ chromeos::DBusThreadManager::Get()->GetPowerPolicyController()->
+ RemoveWakeLock(block_id_);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ virtual ~Delegate() {}
+
+ PowerSaveBlockerType type_;
+ std::string reason_;
+
+ // ID corresponding to the block request in PowerPolicyController.
+ int block_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
+ const std::string& reason)
+ : delegate_(new Delegate(type, reason)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::ApplyBlock, delegate_));
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::RemoveBlock, delegate_));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_impl.cc b/chromium/content/browser/power_save_blocker_impl.cc
new file mode 100644
index 00000000000..01007ad9afa
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_impl.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 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 "content/browser/power_save_blocker_impl.h"
+
+namespace content {
+
+PowerSaveBlocker::~PowerSaveBlocker() {}
+
+// static
+scoped_ptr<PowerSaveBlocker> PowerSaveBlocker::Create(
+ PowerSaveBlockerType type,
+ const std::string& reason) {
+ return scoped_ptr<PowerSaveBlocker>(new PowerSaveBlockerImpl(type, reason));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_impl.h b/chromium/content/browser/power_save_blocker_impl.h
new file mode 100644
index 00000000000..948aced4cb4
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_impl.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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 CONTENT_BROWSER_POWER_SAVE_BLOCKER_IMPL_H_
+#define CONTENT_BROWSER_POWER_SAVE_BLOCKER_IMPL_H_
+
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace content {
+
+class PowerSaveBlockerImpl : public PowerSaveBlocker {
+ public:
+ PowerSaveBlockerImpl(PowerSaveBlockerType type, const std::string& reason);
+ virtual ~PowerSaveBlockerImpl();
+
+#if defined(OS_ANDROID)
+ // In Android platform, the kPowerSaveBlockPreventDisplaySleep type of
+ // PowerSaveBlocker should associated with the ViewAndroid,
+ // so the blocker could be removed by platform if the view isn't visble
+ void InitDisplaySleepBlocker(gfx::NativeView view_android);
+#endif
+
+ private:
+ class Delegate;
+
+ // Implementations of this class may need a second object with different
+ // lifetime than the RAII container, or additional storage. This member is
+ // here for that purpose. If not used, just define the class as an empty
+ // RefCounted (or RefCountedThreadSafe) like so to make it compile:
+ // class PowerSaveBlocker::Delegate
+ // : public base::RefCounted<PowerSaveBlocker::Delegate> {
+ // private:
+ // friend class base::RefCounted<Delegate>;
+ // ~Delegate() {}
+ // };
+ scoped_refptr<Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_POWER_SAVE_BLOCKER_IMPL_H_
diff --git a/chromium/content/browser/power_save_blocker_mac.cc b/chromium/content/browser/power_save_blocker_mac.cc
new file mode 100644
index 00000000000..ec340a1c2c6
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_mac.cc
@@ -0,0 +1,112 @@
+// 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 "content/browser/power_save_blocker_impl.h"
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+
+namespace content {
+namespace {
+
+// Power management cannot be done on the UI thread. IOPMAssertionCreate does a
+// synchronous MIG call to configd, so if it is called on the main thread the UI
+// is at the mercy of another process. See http://crbug.com/79559 and
+// http://www.opensource.apple.com/source/IOKitUser/IOKitUser-514.16.31/pwr_mgt.subproj/IOPMLibPrivate.c .
+struct PowerSaveBlockerLazyInstanceTraits {
+ static const bool kRegisterOnExit = false;
+ static const bool kAllowedToAccessOnNonjoinableThread = true;
+
+ static base::Thread* New(void* instance) {
+ base::Thread* thread = new (instance) base::Thread("PowerSaveBlocker");
+ thread->Start();
+ return thread;
+ }
+ static void Delete(base::Thread* instance) { }
+};
+base::LazyInstance<base::Thread, PowerSaveBlockerLazyInstanceTraits>
+ g_power_thread = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ Delegate(PowerSaveBlockerType type, const std::string& reason)
+ : type_(type), reason_(reason), assertion_(kIOPMNullAssertionID) {}
+
+ // Does the actual work to apply or remove the desired power save block.
+ void ApplyBlock();
+ void RemoveBlock();
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ ~Delegate() {}
+ PowerSaveBlockerType type_;
+ std::string reason_;
+ IOPMAssertionID assertion_;
+};
+
+void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
+ DCHECK_EQ(base::PlatformThread::CurrentId(),
+ g_power_thread.Pointer()->thread_id());
+
+ CFStringRef level = NULL;
+ // See QA1340 <http://developer.apple.com/library/mac/#qa/qa1340/> for more
+ // details.
+ switch (type_) {
+ case PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension:
+ level = kIOPMAssertionTypeNoIdleSleep;
+ break;
+ case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
+ level = kIOPMAssertionTypeNoDisplaySleep;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ if (level) {
+ base::ScopedCFTypeRef<CFStringRef> cf_reason(
+ base::SysUTF8ToCFStringRef(reason_));
+ IOReturn result = IOPMAssertionCreateWithName(level,
+ kIOPMAssertionLevelOn,
+ cf_reason,
+ &assertion_);
+ LOG_IF(ERROR, result != kIOReturnSuccess)
+ << "IOPMAssertionCreate: " << result;
+ }
+}
+
+void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
+ DCHECK_EQ(base::PlatformThread::CurrentId(),
+ g_power_thread.Pointer()->thread_id());
+
+ if (assertion_ != kIOPMNullAssertionID) {
+ IOReturn result = IOPMAssertionRelease(assertion_);
+ LOG_IF(ERROR, result != kIOReturnSuccess)
+ << "IOPMAssertionRelease: " << result;
+ }
+}
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
+ const std::string& reason)
+ : delegate_(new Delegate(type, reason)) {
+ g_power_thread.Pointer()->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&Delegate::ApplyBlock, delegate_));
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
+ g_power_thread.Pointer()->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&Delegate::RemoveBlock, delegate_));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_ozone.cc b/chromium/content/browser/power_save_blocker_ozone.cc
new file mode 100644
index 00000000000..f21f4144cf6
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_ozone.cc
@@ -0,0 +1,34 @@
+// 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 "content/browser/power_save_blocker_impl.h"
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+
+namespace content {
+
+// TODO(rjkroege): Add display power saving control to the ozone interface.
+// This implementation is necessary to satisfy linkage.
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ Delegate() {}
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ virtual ~Delegate() {}
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
+ const std::string& reason)
+ : delegate_(new Delegate()) {
+ NOTIMPLEMENTED();
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() { NOTIMPLEMENTED(); }
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_win.cc b/chromium/content/browser/power_save_blocker_win.cc
new file mode 100644
index 00000000000..fce72c65b62
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_win.cc
@@ -0,0 +1,174 @@
+// 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 "content/browser/power_save_blocker_impl.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_version.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+namespace {
+
+int g_blocker_count[2];
+
+HANDLE CreatePowerRequest(POWER_REQUEST_TYPE type, const std::string& reason) {
+ typedef HANDLE (WINAPI* PowerCreateRequestPtr)(PREASON_CONTEXT);
+ typedef BOOL (WINAPI* PowerSetRequestPtr)(HANDLE, POWER_REQUEST_TYPE);
+
+ if (type == PowerRequestExecutionRequired &&
+ base::win::GetVersion() < base::win::VERSION_WIN8) {
+ return INVALID_HANDLE_VALUE;
+ }
+
+ static PowerCreateRequestPtr PowerCreateRequestFn = NULL;
+ static PowerSetRequestPtr PowerSetRequestFn = NULL;
+
+ if (!PowerCreateRequestFn || !PowerSetRequestFn) {
+ HMODULE module = GetModuleHandle(L"kernel32.dll");
+ PowerCreateRequestFn = reinterpret_cast<PowerCreateRequestPtr>(
+ GetProcAddress(module, "PowerCreateRequest"));
+ PowerSetRequestFn = reinterpret_cast<PowerSetRequestPtr>(
+ GetProcAddress(module, "PowerSetRequest"));
+
+ if (!PowerCreateRequestFn || !PowerSetRequestFn)
+ return INVALID_HANDLE_VALUE;
+ }
+ string16 wide_reason = ASCIIToUTF16(reason);
+ REASON_CONTEXT context = {0};
+ context.Version = POWER_REQUEST_CONTEXT_VERSION;
+ context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
+ context.Reason.SimpleReasonString = const_cast<wchar_t*>(wide_reason.c_str());
+
+ base::win::ScopedHandle handle(PowerCreateRequestFn(&context));
+ if (!handle.IsValid())
+ return INVALID_HANDLE_VALUE;
+
+ if (PowerSetRequestFn(handle, type))
+ return handle.Take();
+
+ // Something went wrong.
+ return INVALID_HANDLE_VALUE;
+}
+
+// Takes ownership of the |handle|.
+void DeletePowerRequest(POWER_REQUEST_TYPE type, HANDLE handle) {
+ base::win::ScopedHandle request_handle(handle);
+ if (!request_handle.IsValid())
+ return;
+
+ if (type == PowerRequestExecutionRequired &&
+ base::win::GetVersion() < base::win::VERSION_WIN8) {
+ return;
+ }
+
+ typedef BOOL (WINAPI* PowerClearRequestPtr)(HANDLE, POWER_REQUEST_TYPE);
+ HMODULE module = GetModuleHandle(L"kernel32.dll");
+ PowerClearRequestPtr PowerClearRequestFn =
+ reinterpret_cast<PowerClearRequestPtr>(
+ GetProcAddress(module, "PowerClearRequest"));
+
+ if (!PowerClearRequestFn)
+ return;
+
+ BOOL success = PowerClearRequestFn(request_handle, type);
+ DCHECK(success);
+}
+
+void ApplySimpleBlock(PowerSaveBlocker::PowerSaveBlockerType type,
+ int delta) {
+ g_blocker_count[type] += delta;
+ DCHECK_GE(g_blocker_count[type], 0);
+
+ if (g_blocker_count[type] > 1)
+ return;
+
+ DWORD this_flag = 0;
+ if (type == PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension)
+ this_flag |= ES_SYSTEM_REQUIRED;
+ else
+ this_flag |= ES_DISPLAY_REQUIRED;
+
+ DCHECK(this_flag);
+
+ static DWORD flags = ES_CONTINUOUS;
+ if (!g_blocker_count[type])
+ flags &= ~this_flag;
+ else
+ flags |= this_flag;
+
+ SetThreadExecutionState(flags);
+}
+
+} // namespace
+
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ Delegate(PowerSaveBlockerType type, const std::string& reason)
+ : type_(type), reason_(reason) {}
+
+ // Does the actual work to apply or remove the desired power save block.
+ void ApplyBlock();
+ void RemoveBlock();
+
+ // Returns the equivalent POWER_REQUEST_TYPE for this request.
+ POWER_REQUEST_TYPE RequestType();
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ ~Delegate() {}
+
+ PowerSaveBlockerType type_;
+ const std::string reason_;
+ base::win::ScopedHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return ApplySimpleBlock(type_, 1);
+
+ handle_.Set(CreatePowerRequest(RequestType(), reason_));
+}
+
+void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return ApplySimpleBlock(type_, -1);
+
+ DeletePowerRequest(RequestType(), handle_.Take());
+}
+
+POWER_REQUEST_TYPE PowerSaveBlockerImpl::Delegate::RequestType() {
+ if (type_ == kPowerSaveBlockPreventDisplaySleep)
+ return PowerRequestDisplayRequired;
+
+ if (base::win::GetVersion() < base::win::VERSION_WIN8)
+ return PowerRequestSystemRequired;
+
+ return PowerRequestExecutionRequired;
+}
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
+ const std::string& reason)
+ : delegate_(new Delegate(type, reason)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::ApplyBlock, delegate_));
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::RemoveBlock, delegate_));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/power_save_blocker_x11.cc b/chromium/content/browser/power_save_blocker_x11.cc
new file mode 100644
index 00000000000..d955643f281
--- /dev/null
+++ b/chromium/content/browser/power_save_blocker_x11.cc
@@ -0,0 +1,343 @@
+// 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 "content/browser/power_save_blocker_impl.h"
+
+#include <X11/Xlib.h>
+#include <X11/extensions/dpms.h>
+// Xlib #defines Status, but we can't have that for some of our headers.
+#ifdef Status
+#undef Status
+#endif
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/nix/xdg_util.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/browser_thread.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+
+#if defined(TOOLKIT_GTK)
+#include "base/message_loop/message_pump_gtk.h"
+#else
+#include "base/message_loop/message_pump_aurax11.h"
+#endif
+
+namespace {
+
+enum DBusAPI {
+ NO_API, // Disable. No supported API available.
+ GNOME_API, // Use the GNOME API. (Supports more features.)
+ FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE.
+};
+
+// Inhibit flags defined in the org.gnome.SessionManager interface.
+// Can be OR'd together and passed as argument to the Inhibit() method
+// to specify which power management features we want to suspend.
+enum GnomeAPIInhibitFlags {
+ INHIBIT_LOGOUT = 1,
+ INHIBIT_SWITCH_USER = 2,
+ INHIBIT_SUSPEND_SESSION = 4,
+ INHIBIT_MARK_SESSION_IDLE = 8
+};
+
+const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
+const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
+const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
+
+const char kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement";
+const char kFreeDesktopAPIInterfaceName[] =
+ "org.freedesktop.PowerManagement.Inhibit";
+const char kFreeDesktopAPIObjectPath[] =
+ "/org/freedesktop/PowerManagement/Inhibit";
+
+} // namespace
+
+namespace content {
+
+class PowerSaveBlockerImpl::Delegate
+ : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
+ public:
+ // Picks an appropriate D-Bus API to use based on the desktop environment.
+ Delegate(PowerSaveBlockerType type, const std::string& reason);
+
+ // Post a task to initialize the delegate on the UI thread, which will itself
+ // then post a task to apply the power save block on the FILE thread.
+ void Init();
+
+ // Post a task to remove the power save block on the FILE thread, unless it
+ // hasn't yet been applied, in which case we just prevent it from applying.
+ void CleanUp();
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+ ~Delegate() {}
+
+ // Selects an appropriate D-Bus API to use for this object. Must be called on
+ // the UI thread. Checks enqueue_apply_ once an API has been selected, and
+ // enqueues a call back to ApplyBlock() if it is true. See the comments for
+ // enqueue_apply_ below.
+ void InitOnUIThread();
+
+ // Apply or remove the power save block, respectively. These methods should be
+ // called once each, on the same thread, per instance. They block waiting for
+ // the action to complete (with a timeout); the thread must thus allow I/O.
+ void ApplyBlock(DBusAPI api);
+ void RemoveBlock(DBusAPI api);
+
+ // If DPMS (the power saving system in X11) is not enabled, then we don't want
+ // to try to disable power saving, since on some desktop environments that may
+ // enable DPMS with very poor default settings (e.g. turning off the display
+ // after only 1 second). Must be called on the UI thread.
+ static bool DPMSEnabled();
+
+ // Returns an appropriate D-Bus API to use based on the desktop environment.
+ // Must be called on the UI thread, as it may call DPMSEnabled() above.
+ static DBusAPI SelectAPI();
+
+ const PowerSaveBlockerType type_;
+ const std::string reason_;
+
+ // Initially, we post a message to the UI thread to select an API. When it
+ // finishes, it will post a message to the FILE thread to perform the actual
+ // application of the block, unless enqueue_apply_ is false. We set it to
+ // false when we post that message, or when RemoveBlock() is called before
+ // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
+ DBusAPI api_;
+ bool enqueue_apply_;
+ base::Lock lock_;
+
+ scoped_refptr<dbus::Bus> bus_;
+
+ // The cookie that identifies our inhibit request,
+ // or 0 if there is no active inhibit request.
+ uint32 inhibit_cookie_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+PowerSaveBlockerImpl::Delegate::Delegate(PowerSaveBlockerType type,
+ const std::string& reason)
+ : type_(type),
+ reason_(reason),
+ api_(NO_API),
+ enqueue_apply_(false),
+ inhibit_cookie_(0) {
+ // We're on the client's thread here, so we don't allocate the dbus::Bus
+ // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
+}
+
+void PowerSaveBlockerImpl::Delegate::Init() {
+ base::AutoLock lock(lock_);
+ DCHECK(!enqueue_apply_);
+ enqueue_apply_ = true;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&Delegate::InitOnUIThread, this));
+}
+
+void PowerSaveBlockerImpl::Delegate::CleanUp() {
+ base::AutoLock lock(lock_);
+ if (enqueue_apply_) {
+ // If a call to ApplyBlock() has not yet been enqueued because we are still
+ // initializing on the UI thread, then just cancel it. We don't need to
+ // remove the block because we haven't even applied it yet.
+ enqueue_apply_ = false;
+ } else if (api_ != NO_API) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&Delegate::RemoveBlock, this, api_));
+ }
+}
+
+void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ base::AutoLock lock(lock_);
+ api_ = SelectAPI();
+ if (enqueue_apply_ && api_ != NO_API) {
+ // The thread we use here becomes the origin and D-Bus thread for the D-Bus
+ // library, so we need to use the same thread above for RemoveBlock(). It
+ // must be a thread that allows I/O operations, so we use the FILE thread.
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&Delegate::ApplyBlock, this, api_));
+ }
+ enqueue_apply_ = false;
+}
+
+void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!bus_.get()); // ApplyBlock() should only be called once.
+
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SESSION;
+ options.connection_type = dbus::Bus::PRIVATE;
+ bus_ = new dbus::Bus(options);
+
+ scoped_refptr<dbus::ObjectProxy> object_proxy;
+ scoped_ptr<dbus::MethodCall> method_call;
+ scoped_ptr<dbus::MessageWriter> message_writer;
+
+ switch (api) {
+ case NO_API:
+ NOTREACHED(); // We should never call this method with this value.
+ return;
+ case GNOME_API:
+ object_proxy = bus_->GetObjectProxy(
+ kGnomeAPIServiceName,
+ dbus::ObjectPath(kGnomeAPIObjectPath));
+ method_call.reset(
+ new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
+ message_writer.reset(new dbus::MessageWriter(method_call.get()));
+ // The arguments of the method are:
+ // app_id: The application identifier
+ // toplevel_xid: The toplevel X window identifier
+ // reason: The reason for the inhibit
+ // flags: Flags that spefify what should be inhibited
+ message_writer->AppendString(
+ CommandLine::ForCurrentProcess()->GetProgram().value());
+ message_writer->AppendUint32(0); // should be toplevel_xid
+ message_writer->AppendString(reason_);
+ {
+ uint32 flags = 0;
+ switch (type_) {
+ case kPowerSaveBlockPreventDisplaySleep:
+ flags |= INHIBIT_MARK_SESSION_IDLE;
+ flags |= INHIBIT_SUSPEND_SESSION;
+ break;
+ case kPowerSaveBlockPreventAppSuspension:
+ flags |= INHIBIT_SUSPEND_SESSION;
+ break;
+ }
+ message_writer->AppendUint32(flags);
+ }
+ break;
+ case FREEDESKTOP_API:
+ object_proxy = bus_->GetObjectProxy(
+ kFreeDesktopAPIServiceName,
+ dbus::ObjectPath(kFreeDesktopAPIObjectPath));
+ method_call.reset(
+ new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit"));
+ message_writer.reset(new dbus::MessageWriter(method_call.get()));
+ // The arguments of the method are:
+ // app_id: The application identifier
+ // reason: The reason for the inhibit
+ message_writer->AppendString(
+ CommandLine::ForCurrentProcess()->GetProgram().value());
+ message_writer->AppendString(reason_);
+ break;
+ }
+
+ // We could do this method call asynchronously, but if we did, we'd need to
+ // handle the case where we want to cancel the block before we get a reply.
+ // We're on the FILE thread so it should be OK to block briefly here.
+ scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
+ method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (response) {
+ // The method returns an inhibit_cookie, used to uniquely identify
+ // this request. It should be used as an argument to Uninhibit()
+ // in order to remove the request.
+ dbus::MessageReader message_reader(response.get());
+ if (!message_reader.PopUint32(&inhibit_cookie_))
+ LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
+ } else {
+ LOG(ERROR) << "No response to Inhibit() request!";
+ }
+}
+
+void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(bus_.get()); // RemoveBlock() should only be called once.
+
+ scoped_refptr<dbus::ObjectProxy> object_proxy;
+ scoped_ptr<dbus::MethodCall> method_call;
+
+ switch (api) {
+ case NO_API:
+ NOTREACHED(); // We should never call this method with this value.
+ return;
+ case GNOME_API:
+ object_proxy = bus_->GetObjectProxy(
+ kGnomeAPIServiceName,
+ dbus::ObjectPath(kGnomeAPIObjectPath));
+ method_call.reset(
+ new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
+ break;
+ case FREEDESKTOP_API:
+ object_proxy = bus_->GetObjectProxy(
+ kFreeDesktopAPIServiceName,
+ dbus::ObjectPath(kFreeDesktopAPIObjectPath));
+ method_call.reset(
+ new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "UnInhibit"));
+ break;
+ }
+
+ dbus::MessageWriter message_writer(method_call.get());
+ message_writer.AppendUint32(inhibit_cookie_);
+ scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
+ method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (!response)
+ LOG(ERROR) << "No response to Uninhibit() request!";
+ // We don't care about checking the result. We assume it works; we can't
+ // really do anything about it anyway if it fails.
+ inhibit_cookie_ = 0;
+
+ bus_->ShutdownAndBlock();
+ bus_ = NULL;
+}
+
+// static
+bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
+ Display* display = base::MessagePumpForUI::GetDefaultXDisplay();
+ BOOL enabled = false;
+ int dummy;
+ if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
+ CARD16 state;
+ DPMSInfo(display, &state, &enabled);
+ }
+ return enabled;
+}
+
+// static
+DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ switch (base::nix::GetDesktopEnvironment(env.get())) {
+ case base::nix::DESKTOP_ENVIRONMENT_GNOME:
+ case base::nix::DESKTOP_ENVIRONMENT_UNITY:
+ if (DPMSEnabled())
+ return GNOME_API;
+ break;
+ case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+ case base::nix::DESKTOP_ENVIRONMENT_KDE4:
+ if (DPMSEnabled())
+ return FREEDESKTOP_API;
+ break;
+ case base::nix::DESKTOP_ENVIRONMENT_KDE3:
+ case base::nix::DESKTOP_ENVIRONMENT_OTHER:
+ // Not supported.
+ break;
+ }
+ return NO_API;
+}
+
+PowerSaveBlockerImpl::PowerSaveBlockerImpl(
+ PowerSaveBlockerType type, const std::string& reason)
+ : delegate_(new Delegate(type, reason)) {
+ delegate_->Init();
+}
+
+PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
+ delegate_->CleanUp();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ppapi_plugin_process_host.cc b/chromium/content/browser/ppapi_plugin_process_host.cc
new file mode 100644
index 00000000000..c9aa6994660
--- /dev/null
+++ b/chromium/content/browser/ppapi_plugin_process_host.cc
@@ -0,0 +1,438 @@
+// 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 "content/browser/ppapi_plugin_process_host.h"
+
+#include <string>
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/renderer_host/render_message_filter.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/pepper_plugin_info.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_switches.h"
+#include "net/base/network_change_notifier.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ui/base/ui_base_switches.h"
+
+#if defined(OS_WIN)
+#include "content/common/sandbox_win.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#include "sandbox/win/src/sandbox_policy.h"
+#endif
+
+namespace content {
+
+#if defined(OS_WIN)
+// NOTE: changes to this class need to be reviewed by the security team.
+class PpapiPluginSandboxedProcessLauncherDelegate
+ : public content::SandboxedProcessLauncherDelegate {
+ public:
+ explicit PpapiPluginSandboxedProcessLauncherDelegate(bool is_broker)
+ : is_broker_(is_broker) {}
+ virtual ~PpapiPluginSandboxedProcessLauncherDelegate() {}
+
+ virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE {
+ if (is_broker_)
+ *in_sandbox = false;
+ }
+
+ virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
+ bool* success) {
+ if (is_broker_)
+ return;
+ // The Pepper process as locked-down as a renderer execpt that it can
+ // create the server side of chrome pipes.
+ sandbox::ResultCode result;
+ result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_NAMED_PIPES,
+ sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY,
+ L"\\\\.\\pipe\\chrome.*");
+ *success = (result == sandbox::SBOX_ALL_OK);
+ }
+
+ private:
+ bool is_broker_;
+
+ DISALLOW_COPY_AND_ASSIGN(PpapiPluginSandboxedProcessLauncherDelegate);
+};
+#endif // OS_WIN
+
+class PpapiPluginProcessHost::PluginNetworkObserver
+ : public net::NetworkChangeNotifier::IPAddressObserver,
+ public net::NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ explicit PluginNetworkObserver(PpapiPluginProcessHost* process_host)
+ : process_host_(process_host) {
+ net::NetworkChangeNotifier::AddIPAddressObserver(this);
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+ }
+
+ virtual ~PluginNetworkObserver() {
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ }
+
+ // IPAddressObserver implementation.
+ virtual void OnIPAddressChanged() OVERRIDE {
+ // TODO(brettw) bug 90246: This doesn't seem correct. The online/offline
+ // notification seems like it should be sufficient, but I don't see that
+ // when I unplug and replug my network cable. Sending this notification when
+ // "something" changes seems to make Flash reasonably happy, but seems
+ // wrong. We should really be able to provide the real online state in
+ // OnConnectionTypeChanged().
+ process_host_->Send(new PpapiMsg_SetNetworkState(true));
+ }
+
+ // ConnectionTypeObserver implementation.
+ virtual void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) OVERRIDE {
+ process_host_->Send(new PpapiMsg_SetNetworkState(
+ type != net::NetworkChangeNotifier::CONNECTION_NONE));
+ }
+
+ private:
+ PpapiPluginProcessHost* const process_host_;
+};
+
+PpapiPluginProcessHost::~PpapiPluginProcessHost() {
+ DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "")
+ << "~PpapiPluginProcessHost()";
+ CancelRequests();
+}
+
+// static
+PpapiPluginProcessHost* PpapiPluginProcessHost::CreatePluginHost(
+ const PepperPluginInfo& info,
+ const base::FilePath& profile_data_directory,
+ net::HostResolver* host_resolver) {
+ PpapiPluginProcessHost* plugin_host = new PpapiPluginProcessHost(
+ info, profile_data_directory, host_resolver);
+ if (plugin_host->Init(info))
+ return plugin_host;
+
+ NOTREACHED(); // Init is not expected to fail.
+ return NULL;
+}
+
+// static
+PpapiPluginProcessHost* PpapiPluginProcessHost::CreateBrokerHost(
+ const PepperPluginInfo& info) {
+ PpapiPluginProcessHost* plugin_host =
+ new PpapiPluginProcessHost();
+ if (plugin_host->Init(info))
+ return plugin_host;
+
+ NOTREACHED(); // Init is not expected to fail.
+ return NULL;
+}
+
+// static
+void PpapiPluginProcessHost::DidCreateOutOfProcessInstance(
+ int plugin_process_id,
+ int32 pp_instance,
+ const PepperRendererInstanceData& instance_data) {
+ for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->process_.get() &&
+ iter->process_->GetData().id == plugin_process_id) {
+ // Found the plugin.
+ iter->host_impl_->AddInstance(pp_instance, instance_data);
+ return;
+ }
+ }
+ // We'll see this passed with a 0 process ID for the browser tag stuff that
+ // is currently in the process of being removed.
+ //
+ // TODO(brettw) When old browser tag impl is removed
+ // (PepperPluginDelegateImpl::CreateBrowserPluginModule passes a 0 plugin
+ // process ID) this should be converted to a NOTREACHED().
+ DCHECK(plugin_process_id == 0)
+ << "Renderer sent a bad plugin process host ID";
+}
+
+// static
+void PpapiPluginProcessHost::DidDeleteOutOfProcessInstance(
+ int plugin_process_id,
+ int32 pp_instance) {
+ for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->process_.get() &&
+ iter->process_->GetData().id == plugin_process_id) {
+ // Found the plugin.
+ iter->host_impl_->DeleteInstance(pp_instance);
+ return;
+ }
+ }
+ // Note: It's possible that the plugin process has already been deleted by
+ // the time this message is received. For example, it could have crashed.
+ // That's OK, we can just ignore this message.
+}
+
+// static
+void PpapiPluginProcessHost::FindByName(
+ const string16& name,
+ std::vector<PpapiPluginProcessHost*>* hosts) {
+ for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->process_.get() && iter->process_->GetData().name == name)
+ hosts->push_back(*iter);
+ }
+}
+
+bool PpapiPluginProcessHost::Send(IPC::Message* message) {
+ return process_->Send(message);
+}
+
+void PpapiPluginProcessHost::OpenChannelToPlugin(Client* client) {
+ if (process_->GetHost()->IsChannelOpening()) {
+ // The channel is already in the process of being opened. Put
+ // this "open channel" request into a queue of requests that will
+ // be run once the channel is open.
+ pending_requests_.push_back(client);
+ return;
+ }
+
+ // We already have an open channel, send a request right away to plugin.
+ RequestPluginChannel(client);
+}
+
+PpapiPluginProcessHost::PpapiPluginProcessHost(
+ const PepperPluginInfo& info,
+ const base::FilePath& profile_data_directory,
+ net::HostResolver* host_resolver)
+ : permissions_(
+ ppapi::PpapiPermissions::GetForCommandLine(info.permissions)),
+ profile_data_directory_(profile_data_directory),
+ is_broker_(false) {
+ process_.reset(new BrowserChildProcessHostImpl(
+ PROCESS_TYPE_PPAPI_PLUGIN, this));
+
+ filter_ = new PepperMessageFilter(permissions_, host_resolver);
+
+ host_impl_.reset(new BrowserPpapiHostImpl(this, permissions_, info.name,
+ info.path, profile_data_directory,
+ false,
+ filter_));
+
+ process_->GetHost()->AddFilter(filter_.get());
+ process_->GetHost()->AddFilter(host_impl_->message_filter().get());
+
+ GetContentClient()->browser()->DidCreatePpapiPlugin(host_impl_.get());
+
+ // Only request network status updates if the plugin has dev permissions.
+ if (permissions_.HasPermission(ppapi::PERMISSION_DEV))
+ network_observer_.reset(new PluginNetworkObserver(this));
+}
+
+PpapiPluginProcessHost::PpapiPluginProcessHost()
+ : is_broker_(true) {
+ process_.reset(new BrowserChildProcessHostImpl(
+ PROCESS_TYPE_PPAPI_BROKER, this));
+
+ ppapi::PpapiPermissions permissions; // No permissions.
+ // The plugin name, path and profile data directory shouldn't be needed for
+ // the broker.
+ host_impl_.reset(new BrowserPpapiHostImpl(this, permissions,
+ std::string(), base::FilePath(),
+ base::FilePath(),
+ false,
+ NULL));
+}
+
+bool PpapiPluginProcessHost::Init(const PepperPluginInfo& info) {
+ plugin_path_ = info.path;
+ if (info.name.empty()) {
+ process_->SetName(plugin_path_.BaseName().LossyDisplayName());
+ } else {
+ process_->SetName(UTF8ToUTF16(info.name));
+ }
+
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ CommandLine::StringType plugin_launcher =
+ browser_command_line.GetSwitchValueNative(switches::kPpapiPluginLauncher);
+
+#if defined(OS_LINUX)
+ int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
+ ChildProcessHost::CHILD_NORMAL;
+#else
+ int flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
+ if (exe_path.empty())
+ return false;
+
+ CommandLine* cmd_line = new CommandLine(exe_path);
+ cmd_line->AppendSwitchASCII(switches::kProcessType,
+ is_broker_ ? switches::kPpapiBrokerProcess
+ : switches::kPpapiPluginProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+
+ // These switches are forwarded to both plugin and broker pocesses.
+ static const char* kCommonForwardSwitches[] = {
+ switches::kVModule
+ };
+ cmd_line->CopySwitchesFrom(browser_command_line, kCommonForwardSwitches,
+ arraysize(kCommonForwardSwitches));
+
+ if (!is_broker_) {
+ static const char* kPluginForwardSwitches[] = {
+ switches::kDisableSeccompFilterSandbox,
+#if defined(OS_MACOSX)
+ switches::kEnableSandboxLogging,
+#endif
+ switches::kNoSandbox,
+ switches::kPpapiStartupDialog,
+ };
+ cmd_line->CopySwitchesFrom(browser_command_line, kPluginForwardSwitches,
+ arraysize(kPluginForwardSwitches));
+
+ // Copy any flash args over and introduce field trials if necessary.
+ // TODO(vtl): Stop passing flash args in the command line, or windows is
+ // going to explode.
+ std::string field_trial =
+ base::FieldTrialList::FindFullName(kLowLatencyFlashAudioFieldTrialName);
+ std::string existing_args =
+ browser_command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs);
+ if (field_trial == kLowLatencyFlashAudioFieldTrialEnabledName)
+ existing_args.append(" enable_low_latency_audio=1");
+ cmd_line->AppendSwitchASCII(switches::kPpapiFlashArgs, existing_args);
+ }
+
+ std::string locale = GetContentClient()->browser()->GetApplicationLocale();
+ if (!locale.empty()) {
+ // Pass on the locale so the plugin will know what language we're using.
+ cmd_line->AppendSwitchASCII(switches::kLang, locale);
+ }
+
+ if (!plugin_launcher.empty())
+ cmd_line->PrependWrapper(plugin_launcher);
+
+ // On posix, never use the zygote for the broker. Also, only use the zygote if
+ // the plugin is sandboxed, and we are not using a plugin launcher - having a
+ // plugin launcher means we need to use another process instead of just
+ // forking the zygote.
+#if defined(OS_POSIX)
+ bool use_zygote = !is_broker_ && plugin_launcher.empty() && info.is_sandboxed;
+ if (!info.is_sandboxed)
+ cmd_line->AppendSwitchASCII(switches::kNoSandbox, std::string());
+#endif // OS_POSIX
+ process_->Launch(
+#if defined(OS_WIN)
+ new PpapiPluginSandboxedProcessLauncherDelegate(is_broker_),
+#elif defined(OS_POSIX)
+ use_zygote,
+ base::EnvironmentVector(),
+#endif
+ cmd_line);
+ return true;
+}
+
+void PpapiPluginProcessHost::RequestPluginChannel(Client* client) {
+ base::ProcessHandle process_handle;
+ int renderer_child_id;
+ client->GetPpapiChannelInfo(&process_handle, &renderer_child_id);
+
+ base::ProcessId process_id = (process_handle == base::kNullProcessHandle) ?
+ 0 : base::GetProcId(process_handle);
+
+ // We can't send any sync messages from the browser because it might lead to
+ // a hang. See the similar code in PluginProcessHost for more description.
+ PpapiMsg_CreateChannel* msg = new PpapiMsg_CreateChannel(
+ process_id, renderer_child_id, client->OffTheRecord());
+ msg->set_unblock(true);
+ if (Send(msg)) {
+ sent_requests_.push(client);
+ } else {
+ client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0);
+ }
+}
+
+void PpapiPluginProcessHost::OnProcessLaunched() {
+ host_impl_->set_plugin_process_handle(process_->GetHandle());
+}
+
+void PpapiPluginProcessHost::OnProcessCrashed(int exit_code) {
+ PluginServiceImpl::GetInstance()->RegisterPluginCrash(plugin_path_);
+}
+
+bool PpapiPluginProcessHost::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(PpapiPluginProcessHost, msg)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated,
+ OnRendererPluginChannelCreated)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ DCHECK(handled);
+ return handled;
+}
+
+// Called when the browser <--> plugin channel has been established.
+void PpapiPluginProcessHost::OnChannelConnected(int32 peer_pid) {
+ // This will actually load the plugin. Errors will actually not be reported
+ // back at this point. Instead, the plugin will fail to establish the
+ // connections when we request them on behalf of the renderer(s).
+ Send(new PpapiMsg_LoadPlugin(plugin_path_, permissions_));
+
+ // Process all pending channel requests from the renderers.
+ for (size_t i = 0; i < pending_requests_.size(); i++)
+ RequestPluginChannel(pending_requests_[i]);
+ pending_requests_.clear();
+}
+
+// Called when the browser <--> plugin channel has an error. This normally
+// means the plugin has crashed.
+void PpapiPluginProcessHost::OnChannelError() {
+ DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "")
+ << "::OnChannelError()";
+ // We don't need to notify the renderers that were communicating with the
+ // plugin since they have their own channels which will go into the error
+ // state at the same time. Instead, we just need to notify any renderers
+ // that have requested a connection but have not yet received one.
+ CancelRequests();
+}
+
+void PpapiPluginProcessHost::CancelRequests() {
+ DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "")
+ << "CancelRequests()";
+ for (size_t i = 0; i < pending_requests_.size(); i++) {
+ pending_requests_[i]->OnPpapiChannelOpened(IPC::ChannelHandle(),
+ base::kNullProcessId, 0);
+ }
+ pending_requests_.clear();
+
+ while (!sent_requests_.empty()) {
+ sent_requests_.front()->OnPpapiChannelOpened(IPC::ChannelHandle(),
+ base::kNullProcessId, 0);
+ sent_requests_.pop();
+ }
+}
+
+// Called when a new plugin <--> renderer channel has been created.
+void PpapiPluginProcessHost::OnRendererPluginChannelCreated(
+ const IPC::ChannelHandle& channel_handle) {
+ if (sent_requests_.empty())
+ return;
+
+ // All requests should be processed FIFO, so the next item in the
+ // sent_requests_ queue should be the one that the plugin just created.
+ Client* client = sent_requests_.front();
+ sent_requests_.pop();
+
+ const ChildProcessData& data = process_->GetData();
+ client->OnPpapiChannelOpened(channel_handle, base::GetProcId(data.handle),
+ data.id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ppapi_plugin_process_host.h b/chromium/content/browser/ppapi_plugin_process_host.h
new file mode 100644
index 00000000000..78d6bd63c50
--- /dev/null
+++ b/chromium/content/browser/ppapi_plugin_process_host.h
@@ -0,0 +1,198 @@
+// 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 CONTENT_BROWSER_PPAPI_PLUGIN_PROCESS_HOST_H_
+#define CONTENT_BROWSER_PPAPI_PLUGIN_PROCESS_HOST_H_
+
+#include <queue>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "base/strings/string16.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "ipc/ipc_sender.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+
+namespace net {
+class HostResolver;
+}
+
+namespace content {
+class BrowserChildProcessHostImpl;
+class ResourceContext;
+struct PepperPluginInfo;
+
+// Process host for PPAPI plugin and broker processes.
+// When used for the broker, interpret all references to "plugin" with "broker".
+class PpapiPluginProcessHost : public BrowserChildProcessHostDelegate,
+ public IPC::Sender {
+ public:
+ class Client {
+ public:
+ // Gets the information about the renderer that's requesting the channel.
+ virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
+ int* renderer_id) = 0;
+
+ // Called when the channel is asynchronously opened to the plugin or on
+ // error. On error, the parameters should be:
+ // base::kNullProcessHandle
+ // IPC::ChannelHandle(),
+ // 0
+ virtual void OnPpapiChannelOpened(
+ const IPC::ChannelHandle& channel_handle,
+ base::ProcessId plugin_pid,
+ int plugin_child_id) = 0;
+
+ // Returns true if the current connection is off-the-record.
+ virtual bool OffTheRecord() = 0;
+
+ protected:
+ virtual ~Client() {}
+ };
+
+ class PluginClient : public Client {
+ public:
+ // Returns the resource context for the renderer requesting the channel.
+ virtual ResourceContext* GetResourceContext() = 0;
+
+ protected:
+ virtual ~PluginClient() {}
+ };
+
+ class BrokerClient : public Client {
+ protected:
+ virtual ~BrokerClient() {}
+ };
+
+ virtual ~PpapiPluginProcessHost();
+
+ static PpapiPluginProcessHost* CreatePluginHost(
+ const PepperPluginInfo& info,
+ const base::FilePath& profile_data_directory,
+ net::HostResolver* host_resolver);
+ static PpapiPluginProcessHost* CreateBrokerHost(
+ const PepperPluginInfo& info);
+
+ // Notification that a PP_Instance has been created and the associated
+ // renderer related data including the RenderView/Process pair for the given
+ // plugin. This is necessary so that when the plugin calls us with a
+ // PP_Instance we can find the RenderView associated with it without trusting
+ // the plugin.
+ static void DidCreateOutOfProcessInstance(
+ int plugin_process_id,
+ int32 pp_instance,
+ const PepperRendererInstanceData& instance_data);
+
+ // The opposite of DIdCreate... above.
+ static void DidDeleteOutOfProcessInstance(int plugin_process_id,
+ int32 pp_instance);
+
+ // Returns the instances that match the specified process name.
+ // It can only be called on the IO thread.
+ static void FindByName(const string16& name,
+ std::vector<PpapiPluginProcessHost*>* hosts);
+
+ // IPC::Sender implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // Opens a new channel to the plugin. The client will be notified when the
+ // channel is ready or if there's an error.
+ void OpenChannelToPlugin(Client* client);
+
+ BrowserPpapiHostImpl* host_impl() { return host_impl_.get(); }
+ const BrowserChildProcessHostImpl* process() { return process_.get(); }
+ const base::FilePath& plugin_path() const { return plugin_path_; }
+ const base::FilePath& profile_data_directory() const {
+ return profile_data_directory_;
+ }
+
+ // The client pointer must remain valid until its callback is issued.
+
+ private:
+ class PluginNetworkObserver;
+
+ // Constructors for plugin and broker process hosts, respectively.
+ // You must call Init before doing anything else.
+ PpapiPluginProcessHost(const PepperPluginInfo& info,
+ const base::FilePath& profile_data_directory,
+ net::HostResolver* host_resolver);
+ PpapiPluginProcessHost();
+
+ // Actually launches the process with the given plugin info. Returns true
+ // on success (the process was spawned).
+ bool Init(const PepperPluginInfo& info);
+
+ void RequestPluginChannel(Client* client);
+
+ virtual void OnProcessLaunched() OVERRIDE;
+
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ void CancelRequests();
+
+ // IPC message handlers.
+ void OnRendererPluginChannelCreated(const IPC::ChannelHandle& handle);
+
+ // Handles most requests from the plugin. May be NULL.
+ scoped_refptr<PepperMessageFilter> filter_;
+
+ ppapi::PpapiPermissions permissions_;
+ scoped_ptr<BrowserPpapiHostImpl> host_impl_;
+
+ // Observes network changes. May be NULL.
+ scoped_ptr<PluginNetworkObserver> network_observer_;
+
+ // Channel requests that we are waiting to send to the plugin process once
+ // the channel is opened.
+ std::vector<Client*> pending_requests_;
+
+ // Channel requests that we have already sent to the plugin process, but
+ // haven't heard back about yet.
+ std::queue<Client*> sent_requests_;
+
+ // Path to the plugin library.
+ base::FilePath plugin_path_;
+
+ // Path to the top-level plugin data directory (differs based upon profile).
+ base::FilePath profile_data_directory_;
+
+ const bool is_broker_;
+
+ scoped_ptr<BrowserChildProcessHostImpl> process_;
+
+ DISALLOW_COPY_AND_ASSIGN(PpapiPluginProcessHost);
+};
+
+class PpapiPluginProcessHostIterator
+ : public BrowserChildProcessHostTypeIterator<
+ PpapiPluginProcessHost> {
+ public:
+ PpapiPluginProcessHostIterator()
+ : BrowserChildProcessHostTypeIterator<
+ PpapiPluginProcessHost>(PROCESS_TYPE_PPAPI_PLUGIN) {}
+};
+
+class PpapiBrokerProcessHostIterator
+ : public BrowserChildProcessHostTypeIterator<
+ PpapiPluginProcessHost> {
+ public:
+ PpapiBrokerProcessHostIterator()
+ : BrowserChildProcessHostTypeIterator<
+ PpapiPluginProcessHost>(PROCESS_TYPE_PPAPI_BROKER) {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PPAPI_PLUGIN_PROCESS_HOST_H_
+
diff --git a/chromium/content/browser/profiler_controller_impl.cc b/chromium/content/browser/profiler_controller_impl.cc
new file mode 100644
index 00000000000..14c2f38b7a5
--- /dev/null
+++ b/chromium/content/browser/profiler_controller_impl.cc
@@ -0,0 +1,126 @@
+// 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 "content/browser/profiler_controller_impl.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/tracked_objects.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/profiler_subscriber.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+
+namespace content {
+
+ProfilerController* ProfilerController::GetInstance() {
+ return ProfilerControllerImpl::GetInstance();
+}
+
+ProfilerControllerImpl* ProfilerControllerImpl::GetInstance() {
+ return Singleton<ProfilerControllerImpl>::get();
+}
+
+ProfilerControllerImpl::ProfilerControllerImpl() : subscriber_(NULL) {
+}
+
+ProfilerControllerImpl::~ProfilerControllerImpl() {
+}
+
+void ProfilerControllerImpl::OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber_)
+ subscriber_->OnPendingProcesses(sequence_number, pending_processes, end);
+}
+
+void ProfilerControllerImpl::OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ int process_type) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&ProfilerControllerImpl::OnProfilerDataCollected,
+ base::Unretained(this),
+ sequence_number,
+ profiler_data,
+ process_type));
+ return;
+ }
+
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber_) {
+ subscriber_->OnProfilerDataCollected(sequence_number, profiler_data,
+ process_type);
+ }
+}
+
+void ProfilerControllerImpl::Register(ProfilerSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!subscriber_);
+ subscriber_ = subscriber;
+}
+
+void ProfilerControllerImpl::Unregister(const ProfilerSubscriber* subscriber) {
+ DCHECK_EQ(subscriber_, subscriber);
+ subscriber_ = NULL;
+}
+
+void ProfilerControllerImpl::GetProfilerDataFromChildProcesses(
+ int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ int pending_processes = 0;
+ for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
+ // Skips requesting profiler data from the "GPU Process" if we are using in
+ // process GPU. Those stats should be in the Browser-process's GPU thread.
+ if (iter.GetData().process_type == PROCESS_TYPE_GPU &&
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessGPU)) {
+ continue;
+ }
+
+ ++pending_processes;
+ if (!iter.Send(new ChildProcessMsg_GetChildProfilerData(sequence_number)))
+ --pending_processes;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &ProfilerControllerImpl::OnPendingProcesses,
+ base::Unretained(this),
+ sequence_number,
+ pending_processes,
+ true));
+}
+
+void ProfilerControllerImpl::GetProfilerData(int sequence_number) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ int pending_processes = 0;
+ for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ ++pending_processes;
+ if (!it.GetCurrentValue()->Send(
+ new ChildProcessMsg_GetChildProfilerData(sequence_number))) {
+ --pending_processes;
+ }
+ }
+ OnPendingProcesses(sequence_number, pending_processes, false);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ProfilerControllerImpl::GetProfilerDataFromChildProcesses,
+ base::Unretained(this),
+ sequence_number));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/profiler_controller_impl.h b/chromium/content/browser/profiler_controller_impl.h
new file mode 100644
index 00000000000..34c76ec56de
--- /dev/null
+++ b/chromium/content/browser/profiler_controller_impl.h
@@ -0,0 +1,62 @@
+// 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 CONTENT_BROWSER_PROFILER_CONTROLLER_IMPL_H_
+#define CONTENT_BROWSER_PROFILER_CONTROLLER_IMPL_H_
+
+#include "base/memory/singleton.h"
+#include "base/process/process.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/profiler_controller.h"
+#include "content/public/common/process_type.h"
+
+namespace tracked_objects {
+struct ProcessDataSnapshot;
+}
+
+namespace content {
+
+// ProfilerController's implementation.
+class ProfilerControllerImpl : public ProfilerController {
+ public:
+ static ProfilerControllerImpl* GetInstance();
+
+ // Normally instantiated when the child process is launched. Only one instance
+ // should be created per process.
+ ProfilerControllerImpl();
+ virtual ~ProfilerControllerImpl();
+
+ // Notify the |subscriber_| that it should expect at least |pending_processes|
+ // additional calls to OnProfilerDataCollected(). OnPendingProcess() may be
+ // called repeatedly; the last call will have |end| set to true, indicating
+ // that there is no longer a possibility for the count of pending processes to
+ // increase. This is called on the UI thread.
+ void OnPendingProcesses(int sequence_number, int pending_processes, bool end);
+
+ // Send the |profiler_data| back to the |subscriber_|.
+ // This can be called from any thread.
+ void OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ int process_type);
+
+ // ProfilerController implementation:
+ virtual void Register(ProfilerSubscriber* subscriber) OVERRIDE;
+ virtual void Unregister(const ProfilerSubscriber* subscriber) OVERRIDE;
+ virtual void GetProfilerData(int sequence_number) OVERRIDE;
+
+ private:
+ friend struct DefaultSingletonTraits<ProfilerControllerImpl>;
+
+ // Contact child processes and get their profiler data.
+ void GetProfilerDataFromChildProcesses(int sequence_number);
+
+ ProfilerSubscriber* subscriber_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfilerControllerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PROFILER_CONTROLLER_IMPL_H_
diff --git a/chromium/content/browser/profiler_message_filter.cc b/chromium/content/browser/profiler_message_filter.cc
new file mode 100644
index 00000000000..cb30f257d96
--- /dev/null
+++ b/chromium/content/browser/profiler_message_filter.cc
@@ -0,0 +1,56 @@
+// 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 "content/browser/profiler_message_filter.h"
+
+#include "base/tracked_objects.h"
+#include "content/browser/profiler_controller_impl.h"
+#include "content/browser/tcmalloc_internals_request_job.h"
+#include "content/common/child_process_messages.h"
+
+namespace content {
+
+ProfilerMessageFilter::ProfilerMessageFilter(int process_type)
+ : process_type_(process_type) {
+}
+
+void ProfilerMessageFilter::OnChannelConnected(int32 peer_pid) {
+ BrowserMessageFilter::OnChannelConnected(peer_pid);
+
+ tracked_objects::ThreadData::Status status =
+ tracked_objects::ThreadData::status();
+ Send(new ChildProcessMsg_SetProfilerStatus(status));
+}
+
+bool ProfilerMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ProfilerMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_ChildProfilerData,
+ OnChildProfilerData)
+#if defined(USE_TCMALLOC)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_TcmallocStats, OnTcmallocStats)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+ProfilerMessageFilter::~ProfilerMessageFilter() {}
+
+void ProfilerMessageFilter::OnChildProfilerData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data) {
+ ProfilerControllerImpl::GetInstance()->OnProfilerDataCollected(
+ sequence_number, profiler_data, process_type_);
+}
+
+#if defined(USE_TCMALLOC)
+void ProfilerMessageFilter::OnTcmallocStats(const std::string& output) {
+ AboutTcmallocOutputs::GetInstance()->OnStatsForChildProcess(
+ peer_pid(), process_type_, output);
+}
+#endif
+
+}
diff --git a/chromium/content/browser/profiler_message_filter.h b/chromium/content/browser/profiler_message_filter.h
new file mode 100644
index 00000000000..bbac0babbbb
--- /dev/null
+++ b/chromium/content/browser/profiler_message_filter.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_PROFILER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_PROFILER_MESSAGE_FILTER_H_
+
+#include <string>
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace tracked_objects {
+struct ProcessDataSnapshot;
+}
+
+namespace content {
+
+// This class sends and receives profiler messages in the browser process.
+class ProfilerMessageFilter : public BrowserMessageFilter {
+ public:
+ explicit ProfilerMessageFilter(int process_type);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ protected:
+ virtual ~ProfilerMessageFilter();
+
+ private:
+ // Message handlers.
+ void OnChildProfilerData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data);
+
+#if defined(USE_TCMALLOC)
+ void OnTcmallocStats(const std::string& output);
+#endif
+
+ int process_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfilerMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PROFILER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/quota_dispatcher_host.cc b/chromium/content/browser/quota_dispatcher_host.cc
new file mode 100644
index 00000000000..89ee710fad1
--- /dev/null
+++ b/chromium/content/browser/quota_dispatcher_host.cc
@@ -0,0 +1,258 @@
+// 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 "content/browser/quota_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/quota_messages.h"
+#include "content/public/browser/quota_permission_context.h"
+#include "net/base/net_util.h"
+#include "url/gurl.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+using quota::QuotaClient;
+using quota::QuotaManager;
+using quota::QuotaStatusCode;
+using quota::StorageType;
+
+namespace content {
+
+// Created one per request to carry the request's request_id around.
+// Dispatches requests from renderer/worker to the QuotaManager and
+// sends back the response to the renderer/worker.
+class QuotaDispatcherHost::RequestDispatcher {
+ public:
+ RequestDispatcher(QuotaDispatcherHost* dispatcher_host,
+ int request_id)
+ : dispatcher_host_(dispatcher_host),
+ request_id_(request_id) {
+ dispatcher_host_->outstanding_requests_.AddWithID(this, request_id_);
+ }
+ virtual ~RequestDispatcher() {}
+
+ protected:
+ // Subclass must call this when it's done with the request.
+ void Completed() {
+ dispatcher_host_->outstanding_requests_.Remove(request_id_);
+ }
+
+ QuotaDispatcherHost* dispatcher_host() const { return dispatcher_host_; }
+ quota::QuotaManager* quota_manager() const {
+ return dispatcher_host_->quota_manager_;
+ }
+ QuotaPermissionContext* permission_context() const {
+ return dispatcher_host_->permission_context_.get();
+ }
+ int render_process_id() const { return dispatcher_host_->process_id_; }
+ int request_id() const { return request_id_; }
+
+ private:
+ QuotaDispatcherHost* dispatcher_host_;
+ int request_id_;
+};
+
+class QuotaDispatcherHost::QueryUsageAndQuotaDispatcher
+ : public RequestDispatcher {
+ public:
+ QueryUsageAndQuotaDispatcher(
+ QuotaDispatcherHost* dispatcher_host,
+ int request_id)
+ : RequestDispatcher(dispatcher_host, request_id),
+ weak_factory_(this) {}
+ virtual ~QueryUsageAndQuotaDispatcher() {}
+
+ void QueryStorageUsageAndQuota(const GURL& origin, StorageType type) {
+ quota_manager()->GetUsageAndQuotaForWebApps(
+ origin, type,
+ base::Bind(&QueryUsageAndQuotaDispatcher::DidQueryStorageUsageAndQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ private:
+ void DidQueryStorageUsageAndQuota(
+ QuotaStatusCode status, int64 usage, int64 quota) {
+ DCHECK(dispatcher_host());
+ if (status != quota::kQuotaStatusOk) {
+ dispatcher_host()->Send(new QuotaMsg_DidFail(request_id(), status));
+ } else {
+ dispatcher_host()->Send(new QuotaMsg_DidQueryStorageUsageAndQuota(
+ request_id(), usage, quota));
+ }
+ Completed();
+ }
+
+ base::WeakPtrFactory<QueryUsageAndQuotaDispatcher> weak_factory_;
+};
+
+class QuotaDispatcherHost::RequestQuotaDispatcher
+ : public RequestDispatcher {
+ public:
+ typedef RequestQuotaDispatcher self_type;
+
+ RequestQuotaDispatcher(QuotaDispatcherHost* dispatcher_host,
+ int request_id,
+ const GURL& origin,
+ StorageType type,
+ int64 requested_quota,
+ int render_view_id)
+ : RequestDispatcher(dispatcher_host, request_id),
+ origin_(origin),
+ host_(net::GetHostOrSpecFromURL(origin)),
+ type_(type),
+ current_quota_(0),
+ requested_quota_(requested_quota),
+ render_view_id_(render_view_id),
+ weak_factory_(this) {}
+ virtual ~RequestQuotaDispatcher() {}
+
+ void Start() {
+ DCHECK(type_ == quota::kStorageTypeTemporary ||
+ type_ == quota::kStorageTypePersistent ||
+ type_ == quota::kStorageTypeSyncable);
+ if (type_ == quota::kStorageTypePersistent) {
+ quota_manager()->GetPersistentHostQuota(
+ host_,
+ base::Bind(&self_type::DidGetHostQuota,
+ weak_factory_.GetWeakPtr(), host_, type_));
+ } else {
+ quota_manager()->GetUsageAndQuotaForWebApps(
+ origin_, type_,
+ base::Bind(&self_type::DidGetTemporaryUsageAndQuota,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+
+ private:
+ void DidGetHostQuota(const std::string& host,
+ StorageType type,
+ QuotaStatusCode status,
+ int64 quota) {
+ DCHECK_EQ(type_, type);
+ DCHECK_EQ(host_, host);
+ if (status != quota::kQuotaStatusOk) {
+ DidFinish(status, 0);
+ return;
+ }
+ if (requested_quota_ < 0) {
+ DidFinish(quota::kQuotaErrorInvalidModification, 0);
+ return;
+ }
+ if (requested_quota_ <= quota) {
+ // Seems like we can just let it go.
+ DidFinish(quota::kQuotaStatusOk, requested_quota_);
+ return;
+ }
+ current_quota_ = quota;
+ // Otherwise we need to consult with the permission context and
+ // possibly show an infobar.
+ DCHECK(permission_context());
+ permission_context()->RequestQuotaPermission(
+ origin_, type_, requested_quota_, render_process_id(), render_view_id_,
+ base::Bind(&self_type::DidGetPermissionResponse,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ void DidGetTemporaryUsageAndQuota(QuotaStatusCode status,
+ int64 usage_unused,
+ int64 quota) {
+ DidFinish(status, std::min(requested_quota_, quota));
+ }
+
+ void DidGetPermissionResponse(
+ QuotaPermissionContext::QuotaPermissionResponse response) {
+ if (response != QuotaPermissionContext::QUOTA_PERMISSION_RESPONSE_ALLOW) {
+ // User didn't allow the new quota. Just returning the current quota.
+ DidFinish(quota::kQuotaStatusOk, current_quota_);
+ return;
+ }
+ // Now we're allowed to set the new quota.
+ quota_manager()->SetPersistentHostQuota(
+ host_, requested_quota_,
+ base::Bind(&self_type::DidSetHostQuota, weak_factory_.GetWeakPtr()));
+ }
+
+ void DidSetHostQuota(QuotaStatusCode status, int64 new_quota) {
+ DidFinish(status, new_quota);
+ }
+
+ void DidFinish(QuotaStatusCode status, int64 granted_quota) {
+ DCHECK(dispatcher_host());
+ if (status != quota::kQuotaStatusOk) {
+ dispatcher_host()->Send(new QuotaMsg_DidFail(request_id(), status));
+ } else {
+ dispatcher_host()->Send(new QuotaMsg_DidGrantStorageQuota(
+ request_id(), granted_quota));
+ }
+ Completed();
+ }
+
+ const GURL origin_;
+ const std::string host_;
+ const StorageType type_;
+ int64 current_quota_;
+ const int64 requested_quota_;
+ const int render_view_id_;
+ base::WeakPtrFactory<self_type> weak_factory_;
+};
+
+QuotaDispatcherHost::QuotaDispatcherHost(
+ int process_id,
+ QuotaManager* quota_manager,
+ QuotaPermissionContext* permission_context)
+ : process_id_(process_id),
+ quota_manager_(quota_manager),
+ permission_context_(permission_context) {
+}
+
+bool QuotaDispatcherHost::OnMessageReceived(
+ const IPC::Message& message, bool* message_was_ok) {
+ *message_was_ok = true;
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(QuotaDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(QuotaHostMsg_QueryStorageUsageAndQuota,
+ OnQueryStorageUsageAndQuota)
+ IPC_MESSAGE_HANDLER(QuotaHostMsg_RequestStorageQuota,
+ OnRequestStorageQuota)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+QuotaDispatcherHost::~QuotaDispatcherHost() {}
+
+void QuotaDispatcherHost::OnQueryStorageUsageAndQuota(
+ int request_id,
+ const GURL& origin,
+ StorageType type) {
+ QueryUsageAndQuotaDispatcher* dispatcher = new QueryUsageAndQuotaDispatcher(
+ this, request_id);
+ dispatcher->QueryStorageUsageAndQuota(origin, type);
+}
+
+void QuotaDispatcherHost::OnRequestStorageQuota(
+ int render_view_id,
+ int request_id,
+ const GURL& origin,
+ StorageType type,
+ int64 requested_size) {
+ if (quota_manager_->IsStorageUnlimited(origin, type)) {
+ // If the origin is marked 'unlimited' we always just return ok.
+ Send(new QuotaMsg_DidGrantStorageQuota(request_id, requested_size));
+ return;
+ }
+
+ if (type != quota::kStorageTypeTemporary &&
+ type != quota::kStorageTypePersistent) {
+ // Unsupported storage types.
+ Send(new QuotaMsg_DidFail(request_id, quota::kQuotaErrorNotSupported));
+ return;
+ }
+
+ RequestQuotaDispatcher* dispatcher = new RequestQuotaDispatcher(
+ this, request_id, origin, type, requested_size, render_view_id);
+ dispatcher->Start();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/quota_dispatcher_host.h b/chromium/content/browser/quota_dispatcher_host.h
new file mode 100644
index 00000000000..724b2ad975f
--- /dev/null
+++ b/chromium/content/browser/quota_dispatcher_host.h
@@ -0,0 +1,68 @@
+// 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 CONTENT_BROWSER_QUOTA_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_QUOTA_DISPATCHER_HOST_H_
+
+#include "base/basictypes.h"
+#include "base/id_map.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "webkit/common/quota/quota_types.h"
+
+class GURL;
+
+namespace IPC {
+class Message;
+}
+
+namespace quota {
+class QuotaManager;
+}
+
+namespace content {
+class QuotaPermissionContext;
+
+class QuotaDispatcherHost : public BrowserMessageFilter {
+ public:
+ QuotaDispatcherHost(int process_id,
+ quota::QuotaManager* quota_manager,
+ QuotaPermissionContext* permission_context);
+
+ // BrowserMessageFilter:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ protected:
+ virtual ~QuotaDispatcherHost();
+
+ private:
+ class RequestDispatcher;
+ class QueryUsageAndQuotaDispatcher;
+ class RequestQuotaDispatcher;
+
+ void OnQueryStorageUsageAndQuota(
+ int request_id,
+ const GURL& origin_url,
+ quota::StorageType type);
+ void OnRequestStorageQuota(
+ int render_view_id,
+ int request_id,
+ const GURL& origin_url,
+ quota::StorageType type,
+ int64 requested_size);
+
+ // The ID of this process.
+ int process_id_;
+
+ quota::QuotaManager* quota_manager_;
+ scoped_refptr<QuotaPermissionContext> permission_context_;
+
+ IDMap<RequestDispatcher, IDMapOwnPointer> outstanding_requests_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(QuotaDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_QUOTA_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/DEPS b/chromium/content/browser/renderer_host/DEPS
new file mode 100644
index 00000000000..34decdb0223
--- /dev/null
+++ b/chromium/content/browser/renderer_host/DEPS
@@ -0,0 +1,30 @@
+include_rules = [
+ "+cc/switches.h", # For cc command line switches.
+ "+media/base", # For media command line switches.
+ "+media/audio/audio_util.h", # For audio hardware sample-rate.
+ "+third_party/zlib",
+ "+third_party/libyuv",
+
+ # For single-process mode.
+ "+content/renderer/render_process_impl.h",
+ "+content/renderer/render_thread_impl.h",
+
+ # The renderer_host files should only call upwards in the layering via the
+ # delegate interfaces.
+ "-content/browser/web_contents",
+ "-content/public/browser/web_contents.h",
+ "-content/public/browser/web_contents_view.h",
+]
+
+specific_include_rules = {
+ ".*_(unit|browser)test\.cc": [
+ "+content/browser/web_contents",
+ "+content/public/browser/web_contents.h",
+ "+content/public/browser/web_contents_view.h",
+ "+media/filters",
+ ],
+ "render_sandbox_host_linux\.cc": [
+ "+third_party/WebKit/public/web/WebKit.h",
+ "+third_party/WebKit/public/web/linux/WebFontInfo.h",
+ ],
+}
diff --git a/chromium/content/browser/renderer_host/OWNERS b/chromium/content/browser/renderer_host/OWNERS
new file mode 100644
index 00000000000..920b6c56429
--- /dev/null
+++ b/chromium/content/browser/renderer_host/OWNERS
@@ -0,0 +1,21 @@
+jochen@chromium.org
+
+# for *aura*
+per-file *aura*=ben@chromium.org
+
+# for *mac*
+thakis@chromium.org
+
+# for GPU-related stuff in *mac*
+kbr@chromium.org
+ccameron@chromium.org
+per-file compositing_iosurface*=hclam@chromium.org
+per-file compositing_iosurface*=miu@chromium.org
+
+# for *android*
+sievers@chromium.org
+aelias@chromium.org
+
+# For touch/gesture specific changes
+rjkroege@chromium.org
+sadrul@chromium.org
diff --git a/chromium/content/browser/renderer_host/backing_store.cc b/chromium/content/browser/renderer_host/backing_store.cc
new file mode 100644
index 00000000000..8557437fb25
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store.cc
@@ -0,0 +1,21 @@
+// 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 "content/browser/renderer_host/backing_store.h"
+
+namespace content {
+
+BackingStore::BackingStore(RenderWidgetHost* widget, const gfx::Size& size)
+ : render_widget_host_(widget),
+ size_(size) {
+}
+
+BackingStore::~BackingStore() {
+}
+
+size_t BackingStore::MemorySize() {
+ return size_.GetArea() * 4;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store.h b/chromium/content/browser/renderer_host/backing_store.h
new file mode 100644
index 00000000000..ba36920401c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store.h
@@ -0,0 +1,93 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "content/common/content_export.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d.h"
+#include "ui/surface/transport_dib.h"
+
+class RenderProcessHost;
+
+namespace gfx {
+class Rect;
+}
+
+namespace skia {
+class PlatformBitmap;
+}
+
+namespace content {
+class RenderProcessHost;
+class RenderWidgetHost;
+
+// Represents a backing store for the pixels in a RenderWidgetHost.
+class CONTENT_EXPORT BackingStore {
+ public:
+ virtual ~BackingStore();
+
+ RenderWidgetHost* render_widget_host() const {
+ return render_widget_host_;
+ }
+ const gfx::Size& size() { return size_; }
+
+ // The number of bytes that this backing store consumes. The default
+ // implementation just assumes there's 32 bits per pixel over the current
+ // size of the screen. Implementations may override this if they have more
+ // information about the color depth.
+ virtual size_t MemorySize();
+
+ // Paints the bitmap from the renderer onto the backing store. bitmap_rect
+ // gives the location of bitmap, and copy_rects specifies the subregion(s) of
+ // the backingstore to be painted from the bitmap. All coordinates are in
+ // DIPs. |scale_factor| contains the expected device scale factor of the
+ // backing store.
+ //
+ // PaintToBackingStore does not need to guarantee that this has happened by
+ // the time it returns, in which case it will set |scheduled_callback| to
+ // true and will call |callback| when completed.
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) = 0;
+
+ // Extracts the gives subset of the backing store and copies it to the given
+ // PlatformCanvas. The PlatformCanvas should not be initialized. This function
+ // will call initialize() with the correct size. The return value indicates
+ // success.
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) = 0;
+
+ // Scrolls the contents of clip_rect in the backing store by |delta| (but
+ // |delta|.x() and |delta|.y() cannot both be non-zero).
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) = 0;
+ protected:
+ // Can only be constructed via subclasses.
+ BackingStore(RenderWidgetHost* widget, const gfx::Size& size);
+
+ private:
+ // The owner of this backing store.
+ RenderWidgetHost* render_widget_host_;
+
+ // The size of the backing store.
+ gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_
diff --git a/chromium/content/browser/renderer_host/backing_store_aura.cc b/chromium/content/browser/renderer_host/backing_store_aura.cc
new file mode 100644
index 00000000000..5f976c45ea3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_aura.cc
@@ -0,0 +1,176 @@
+// 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 "content/browser/renderer_host/backing_store_aura.h"
+
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/public/browser/render_widget_host.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace {
+
+gfx::Size ToPixelSize(gfx::Size dipSize, float scale) {
+ return gfx::ToCeiledSize(gfx::ScaleSize(dipSize, scale));
+}
+
+} // namespace
+
+
+namespace content {
+
+// Assume that somewhere along the line, someone will do width * height * 4
+// with signed numbers. If the maximum value is 2**31, then 2**31 / 4 =
+// 2**29 and floor(sqrt(2**29)) = 23170.
+
+// Max height and width for layers
+static const int kMaxVideoLayerSize = 23170;
+
+BackingStoreAura::BackingStoreAura(RenderWidgetHost* widget,
+ const gfx::Size& size)
+ : BackingStore(widget, size) {
+ device_scale_factor_ =
+ ui::GetScaleFactorScale(GetScaleFactorForView(widget->GetView()));
+ gfx::Size pixel_size = ToPixelSize(size, device_scale_factor_);
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config,
+ pixel_size.width(), pixel_size.height());
+ bitmap_.allocPixels();
+ canvas_.reset(new SkCanvas(bitmap_));
+}
+
+BackingStoreAura::~BackingStoreAura() {
+}
+
+void BackingStoreAura::SkiaShowRect(const gfx::Point& point,
+ gfx::Canvas* canvas) {
+ gfx::ImageSkia image = gfx::ImageSkia(gfx::ImageSkiaRep(bitmap_,
+ ui::GetScaleFactorFromScale(device_scale_factor_)));
+ canvas->DrawImageInt(image, point.x(), point.y());
+}
+
+void BackingStoreAura::ScaleFactorChanged(float device_scale_factor) {
+ if (device_scale_factor == device_scale_factor_)
+ return;
+
+ gfx::Size old_pixel_size = ToPixelSize(size(), device_scale_factor_);
+ device_scale_factor_ = device_scale_factor;
+
+ gfx::Size pixel_size = ToPixelSize(size(), device_scale_factor_);
+ SkBitmap new_bitmap;
+ new_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ pixel_size.width(), pixel_size.height());
+ new_bitmap.allocPixels();
+ scoped_ptr<SkCanvas> new_canvas(new SkCanvas(new_bitmap));
+
+ // Copy old contents; a low-res flash is better than a black flash.
+ SkPaint copy_paint;
+ copy_paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ SkIRect src_rect = SkIRect::MakeWH(old_pixel_size.width(),
+ old_pixel_size.height());
+ SkRect dst_rect = SkRect::MakeWH(pixel_size.width(), pixel_size.height());
+ new_canvas.get()->drawBitmapRect(bitmap_, &src_rect, dst_rect, &copy_paint);
+
+ canvas_.swap(new_canvas);
+ bitmap_ = new_bitmap;
+}
+
+size_t BackingStoreAura::MemorySize() {
+ // NOTE: The computation may be different when the canvas is a subrectangle of
+ // a larger bitmap.
+ return ToPixelSize(size(), device_scale_factor_).GetArea() * 4;
+}
+
+void BackingStoreAura::PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) {
+ *scheduled_completion_callback = false;
+ if (bitmap_rect.IsEmpty())
+ return;
+
+ gfx::Rect pixel_bitmap_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(bitmap_rect, scale_factor));
+
+ const int width = pixel_bitmap_rect.width();
+ const int height = pixel_bitmap_rect.height();
+
+ if (width <= 0 || width > kMaxVideoLayerSize ||
+ height <= 0 || height > kMaxVideoLayerSize)
+ return;
+
+ TransportDIB* dib = process->GetTransportDIB(bitmap);
+ if (!dib)
+ return;
+
+ SkPaint copy_paint;
+ copy_paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+
+ SkBitmap sk_bitmap;
+ sk_bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ sk_bitmap.setPixels(dib->memory());
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ const gfx::Rect pixel_copy_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(copy_rects[i], scale_factor));
+ int x = pixel_copy_rect.x() - pixel_bitmap_rect.x();
+ int y = pixel_copy_rect.y() - pixel_bitmap_rect.y();
+ SkIRect srcrect = SkIRect::MakeXYWH(x, y,
+ pixel_copy_rect.width(),
+ pixel_copy_rect.height());
+
+ const gfx::Rect pixel_copy_dst_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(copy_rects[i], device_scale_factor_));
+ SkRect dstrect = SkRect::MakeXYWH(
+ SkIntToScalar(pixel_copy_dst_rect.x()),
+ SkIntToScalar(pixel_copy_dst_rect.y()),
+ SkIntToScalar(pixel_copy_dst_rect.width()),
+ SkIntToScalar(pixel_copy_dst_rect.height()));
+ canvas_.get()->drawBitmapRect(sk_bitmap, &srcrect, dstrect, &copy_paint);
+ }
+}
+
+void BackingStoreAura::ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+ gfx::Rect pixel_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(clip_rect, device_scale_factor_));
+ gfx::Vector2d pixel_delta = gfx::ToFlooredVector2d(
+ gfx::ScaleVector2d(delta, device_scale_factor_));
+
+ int x = std::min(pixel_rect.x(), pixel_rect.x() - pixel_delta.x());
+ int y = std::min(pixel_rect.y(), pixel_rect.y() - pixel_delta.y());
+ int w = pixel_rect.width() + abs(pixel_delta.x());
+ int h = pixel_rect.height() + abs(pixel_delta.y());
+ SkIRect rect = SkIRect::MakeXYWH(x, y, w, h);
+ bitmap_.scrollRect(&rect, pixel_delta.x(), pixel_delta.y());
+}
+
+bool BackingStoreAura::CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) {
+ const int width =
+ std::min(size().width(), rect.width()) * device_scale_factor_;
+ const int height =
+ std::min(size().height(), rect.height()) * device_scale_factor_;
+ if (!output->Allocate(width, height, true))
+ return false;
+
+ SkIRect skrect = SkIRect::MakeXYWH(rect.x(), rect.y(), width, height);
+ SkBitmap b;
+ if (!canvas_->readPixels(skrect, &b))
+ return false;
+ SkCanvas(output->GetBitmap()).writePixels(b, rect.x(), rect.y());
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store_aura.h b/chromium/content/browser/renderer_host/backing_store_aura.h
new file mode 100644
index 00000000000..0b8e0729d0e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_aura.h
@@ -0,0 +1,66 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_AURA_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_AURA_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/common/content_export.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class SkCanvas;
+
+namespace gfx {
+class Point;
+class Canvas;
+}
+
+namespace content {
+class RenderProcessHost;
+
+// A backing store that uses skia. This is the backing store used by
+// RenderWidgetHostViewAura.
+class BackingStoreAura : public BackingStore {
+ public:
+ CONTENT_EXPORT BackingStoreAura(
+ RenderWidgetHost* widget,
+ const gfx::Size& size);
+
+ virtual ~BackingStoreAura();
+
+ CONTENT_EXPORT void SkiaShowRect(const gfx::Point& point,
+ gfx::Canvas* canvas);
+
+ // Called when the view's backing scale factor changes.
+ void ScaleFactorChanged(float device_scale_factor);
+
+ // BackingStore implementation.
+ virtual size_t MemorySize() OVERRIDE;
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) OVERRIDE;
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) OVERRIDE;
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) OVERRIDE;
+
+ private:
+ SkBitmap bitmap_;
+
+ scoped_ptr<SkCanvas> canvas_;
+ float device_scale_factor_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreAura);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_AURA_H_
diff --git a/chromium/content/browser/renderer_host/backing_store_gtk.cc b/chromium/content/browser/renderer_host/backing_store_gtk.cc
new file mode 100644
index 00000000000..62365bed873
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_gtk.cc
@@ -0,0 +1,692 @@
+// 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 "content/browser/renderer_host/backing_store_gtk.h"
+
+#include <cairo-xlib.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/sync.h>
+
+#if defined(OS_OPENBSD) || defined(OS_FREEBSD)
+#include <sys/endian.h>
+#endif
+
+#include <algorithm>
+#include <limits>
+#include <queue>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/base/x/x11_util_internal.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/surface/transport_dib.h"
+
+namespace content {
+namespace {
+
+// Assume that somewhere along the line, someone will do width * height * 4
+// with signed numbers. If the maximum value is 2**31, then 2**31 / 4 =
+// 2**29 and floor(sqrt(2**29)) = 23170.
+
+// Max height and width for layers
+static const int kMaxVideoLayerSize = 23170;
+
+
+// X Backing Stores:
+//
+// Unlike Windows, where the backing store is kept in heap memory, we keep our
+// backing store in the X server, as a pixmap. Thus expose events just require
+// instructing the X server to copy from the backing store to the window.
+//
+// The backing store is in the same format as the visual which our main window
+// is using. Bitmaps from the renderer are uploaded to the X server, either via
+// shared memory or over the wire, and XRENDER is used to convert them to the
+// correct format for the backing store.
+
+// Destroys the image and the associated shared memory structures. This is a
+// helper function for code using shared memory.
+void DestroySharedImage(Display* display,
+ XImage* image,
+ XShmSegmentInfo* shminfo) {
+ XShmDetach(display, shminfo);
+ XDestroyImage(image);
+ shmdt(shminfo->shmaddr);
+}
+
+// So we don't don't want to call XSync(), which can block the UI loop for
+// ~100ms on first paint and is generally slow. We optionally use the
+// XSyncExtension to push a callback into the X11 event queue and get a
+// callback instead of blocking until the event queue is cleared.
+//
+// TODO(erg): If ui::GetXDisplay() ever gets fixed to handle multiple Displays,
+// this must be modified to be per Display instead of a Singleton.
+class XSyncHandler {
+ public:
+ static XSyncHandler* GetInstance() {
+ return Singleton<XSyncHandler>::get();
+ }
+
+ bool Enabled() {
+ return loaded_extension_;
+ }
+
+ void PushPaintCounter(TransportDIB* dib,
+ Display* display,
+ Picture picture,
+ Pixmap pixmap,
+ const base::Closure& completion_callback);
+
+ private:
+ friend struct DefaultSingletonTraits<XSyncHandler>;
+
+ // A struct that has cleanup and callback tasks that were queued into the
+ // future and are run on |g_backing_store_sync_alarm| firing.
+ struct BackingStoreEvents {
+ BackingStoreEvents(TransportDIB* dib, Display* d, Picture pic, Pixmap pix,
+ const base::Closure& c)
+ : dib(dib),
+ display(d),
+ picture(pic),
+ pixmap(pix),
+ closure(c) {
+ dib->IncreaseInFlightCounter();
+ }
+
+ TransportDIB* dib;
+
+ // The display we're running on.
+ Display* display;
+
+ // Data to delete.
+ Picture picture;
+ Pixmap pixmap;
+
+ // Callback once everything else is done.
+ base::Closure closure;
+ };
+
+ XSyncHandler();
+ ~XSyncHandler();
+
+ // An event filter notified about all XEvents. We then filter out XSync
+ // events that are on counters that we made.
+ CHROMEG_CALLBACK_1(XSyncHandler, GdkFilterReturn, OnEvent, GdkXEvent*,
+ GdkEvent*);
+
+ // Whether we successfully loaded XSyncExtension.
+ bool loaded_extension_;
+
+ // The event ids returned to us by XSyncQueryExtension().
+ int xsync_event_base_;
+ int xsync_error_base_;
+
+ XSyncCounter backing_store_sync_counter_;
+ XSyncAlarm backing_store_sync_alarm_;
+
+ // A queue of pending paints that we clean up after as alarms fire.
+ std::queue<BackingStoreEvents*> backing_store_events_;
+};
+
+void XSyncHandler::PushPaintCounter(TransportDIB* dib,
+ Display* display,
+ Picture picture,
+ Pixmap pixmap,
+ const base::Closure& completion_callback) {
+ backing_store_events_.push(new BackingStoreEvents(
+ dib, display, picture, pixmap, completion_callback));
+
+ // Push a change counter event into the X11 event queue that will trigger our
+ // alarm when it is processed.
+ XSyncValue value;
+ XSyncIntToValue(&value, 1);
+ XSyncChangeCounter(ui::GetXDisplay(),
+ backing_store_sync_counter_,
+ value);
+}
+
+XSyncHandler::XSyncHandler()
+ : loaded_extension_(false),
+ xsync_event_base_(0),
+ xsync_error_base_(0),
+ backing_store_sync_counter_(0),
+ backing_store_sync_alarm_(0) {
+ Display* display = ui::GetXDisplay();
+ if (XSyncQueryExtension(display,
+ &xsync_event_base_,
+ &xsync_error_base_)) {
+ // Create our monotonically increasing counter.
+ XSyncValue value;
+ XSyncIntToValue(&value, 0);
+ backing_store_sync_counter_ = XSyncCreateCounter(display, value);
+
+ // Cerate our alarm that watches for changes to our counter.
+ XSyncAlarmAttributes attributes;
+ attributes.trigger.counter = backing_store_sync_counter_;
+ backing_store_sync_alarm_ = XSyncCreateAlarm(display,
+ XSyncCACounter,
+ &attributes);
+
+ // Add our filter to the message loop to handle alarm triggers.
+ gdk_window_add_filter(NULL, &OnEventThunk, this);
+
+ loaded_extension_ = true;
+ }
+}
+
+XSyncHandler::~XSyncHandler() {
+ if (loaded_extension_)
+ gdk_window_remove_filter(NULL, &OnEventThunk, this);
+
+ XSync(ui::GetXDisplay(), False);
+ while (!backing_store_events_.empty()) {
+ // We delete the X11 resources we're holding onto. We don't run the
+ // callbacks because we are shutting down.
+ BackingStoreEvents* data = backing_store_events_.front();
+ backing_store_events_.pop();
+ XRenderFreePicture(data->display, data->picture);
+ XFreePixmap(data->display, data->pixmap);
+ data->dib->DecreaseInFlightCounter();
+ delete data;
+ }
+}
+
+GdkFilterReturn XSyncHandler::OnEvent(GdkXEvent* gdkxevent,
+ GdkEvent* event) {
+ XEvent* xevent = reinterpret_cast<XEvent*>(gdkxevent);
+ if (xevent->type == xsync_event_base_ + XSyncAlarmNotify) {
+ XSyncAlarmNotifyEvent* alarm_event =
+ reinterpret_cast<XSyncAlarmNotifyEvent*>(xevent);
+ if (alarm_event->alarm == backing_store_sync_alarm_) {
+ if (alarm_event->counter_value.hi == 0 &&
+ alarm_event->counter_value.lo == 0) {
+ // We receive an event about the initial state of the counter during
+ // alarm creation. We must ignore this event instead of responding to
+ // it.
+ return GDK_FILTER_REMOVE;
+ }
+
+ DCHECK(!backing_store_events_.empty());
+ BackingStoreEvents* data = backing_store_events_.front();
+ backing_store_events_.pop();
+
+ // We are responsible for deleting all the data in the struct now that
+ // we are finished with it.
+ XRenderFreePicture(data->display, data->picture);
+ XFreePixmap(data->display, data->pixmap);
+
+ // Dispatch the closure we were given.
+ data->closure.Run();
+
+ data->dib->DecreaseInFlightCounter();
+ delete data;
+
+ return GDK_FILTER_REMOVE;
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+} // namespace
+
+BackingStoreGtk::BackingStoreGtk(RenderWidgetHost* widget,
+ const gfx::Size& size,
+ void* visual,
+ int depth)
+ : BackingStore(widget, size),
+ display_(ui::GetXDisplay()),
+ shared_memory_support_(ui::QuerySharedMemorySupport(display_)),
+ use_render_(ui::QueryRenderSupport(display_)),
+ visual_(visual),
+ visual_depth_(depth),
+ root_window_(ui::GetX11RootWindow()) {
+#if defined(OS_OPENBSD) || defined(OS_FREEBSD)
+ COMPILE_ASSERT(_BYTE_ORDER == _LITTLE_ENDIAN, assumes_little_endian);
+#else
+ COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian);
+#endif
+
+ pixmap_ = XCreatePixmap(display_, root_window_,
+ size.width(), size.height(), depth);
+
+ if (use_render_) {
+ picture_ = XRenderCreatePicture(
+ display_, pixmap_,
+ ui::GetRenderVisualFormat(display_,
+ static_cast<Visual*>(visual)),
+ 0, NULL);
+ pixmap_bpp_ = 0;
+ } else {
+ picture_ = 0;
+ pixmap_bpp_ = ui::BitsPerPixelForPixmapDepth(display_, depth);
+ }
+
+ pixmap_gc_ = XCreateGC(display_, pixmap_, 0, NULL);
+}
+
+BackingStoreGtk::BackingStoreGtk(RenderWidgetHost* widget,
+ const gfx::Size& size)
+ : BackingStore(widget, size),
+ display_(NULL),
+ shared_memory_support_(ui::SHARED_MEMORY_NONE),
+ use_render_(false),
+ pixmap_bpp_(0),
+ visual_(NULL),
+ visual_depth_(-1),
+ root_window_(0),
+ pixmap_(0),
+ picture_(0),
+ pixmap_gc_(NULL) {
+}
+
+BackingStoreGtk::~BackingStoreGtk() {
+ // In unit tests, display_ may be NULL.
+ if (!display_)
+ return;
+
+ XRenderFreePicture(display_, picture_);
+ XFreePixmap(display_, pixmap_);
+ XFreeGC(display_, static_cast<GC>(pixmap_gc_));
+}
+
+size_t BackingStoreGtk::MemorySize() {
+ if (!use_render_)
+ return size().GetArea() * (pixmap_bpp_ / 8);
+ else
+ return size().GetArea() * 4;
+}
+
+void BackingStoreGtk::PaintRectWithoutXrender(
+ TransportDIB* bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects) {
+ const int width = bitmap_rect.width();
+ const int height = bitmap_rect.height();
+ Pixmap pixmap = XCreatePixmap(display_, root_window_, width, height,
+ visual_depth_);
+
+ // Draw ARGB transport DIB onto our pixmap.
+ ui::PutARGBImage(display_, visual_, visual_depth_, pixmap,
+ pixmap_gc_, static_cast<uint8*>(bitmap->memory()),
+ width, height);
+
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ const gfx::Rect& copy_rect = copy_rects[i];
+ XCopyArea(display_,
+ pixmap, // src
+ pixmap_, // dest
+ static_cast<GC>(pixmap_gc_), // gc
+ copy_rect.x() - bitmap_rect.x(), // src_x
+ copy_rect.y() - bitmap_rect.y(), // src_y
+ copy_rect.width(), // width
+ copy_rect.height(), // height
+ copy_rect.x(), // dest_x
+ copy_rect.y()); // dest_y
+ }
+
+ XFreePixmap(display_, pixmap);
+}
+
+void BackingStoreGtk::PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) {
+ *scheduled_completion_callback = false;
+
+ if (!display_)
+ return;
+
+ if (bitmap_rect.IsEmpty())
+ return;
+
+ gfx::Rect pixel_bitmap_rect = gfx::ToEnclosedRect(
+ gfx::ScaleRect(bitmap_rect, scale_factor));
+ const int width = pixel_bitmap_rect.width();
+ const int height = pixel_bitmap_rect.height();
+
+ if (width <= 0 || width > kMaxVideoLayerSize ||
+ height <= 0 || height > kMaxVideoLayerSize)
+ return;
+
+ TransportDIB* dib = process->GetTransportDIB(bitmap);
+ if (!dib)
+ return;
+
+ if (!use_render_)
+ return PaintRectWithoutXrender(dib, bitmap_rect, copy_rects);
+
+ Picture picture;
+ Pixmap pixmap;
+
+ if (shared_memory_support_ == ui::SHARED_MEMORY_PIXMAP) {
+ XShmSegmentInfo shminfo = {0};
+ shminfo.shmseg = dib->MapToX(display_);
+
+ // The NULL in the following is the |data| pointer: this is an artifact of
+ // Xlib trying to be helpful, rather than just exposing the X protocol. It
+ // assumes that we have the shared memory segment mapped into our memory,
+ // which we don't, and it's trying to calculate an offset by taking the
+ // difference between the |data| pointer and the address of the mapping in
+ // |shminfo|. Since both are NULL, the offset will be calculated to be 0,
+ // which is correct for us.
+ pixmap = XShmCreatePixmap(display_, root_window_, NULL, &shminfo,
+ width, height, 32);
+ } else {
+ // We don't have shared memory pixmaps. Fall back to creating a pixmap
+ // ourselves and putting an image on it.
+ pixmap = XCreatePixmap(display_, root_window_, width, height, 32);
+ GC gc = XCreateGC(display_, pixmap, 0, NULL);
+
+ if (shared_memory_support_ == ui::SHARED_MEMORY_PUTIMAGE) {
+ const XID shmseg = dib->MapToX(display_);
+
+ XShmSegmentInfo shminfo;
+ memset(&shminfo, 0, sizeof(shminfo));
+ shminfo.shmseg = shmseg;
+ shminfo.shmaddr = static_cast<char*>(dib->memory());
+
+ XImage* image = XShmCreateImage(display_, static_cast<Visual*>(visual_),
+ 32, ZPixmap,
+ shminfo.shmaddr, &shminfo,
+ width, height);
+
+ // This code path is important for performance and we have found that
+ // different techniques work better on different platforms. See
+ // http://code.google.com/p/chromium/issues/detail?id=44124.
+ //
+ // Checking for ARM is an approximation, but it seems to be a good one so
+ // far.
+#if defined(ARCH_CPU_ARM_FAMILY)
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ const gfx::Rect& copy_rect = copy_rects[i];
+ gfx::Rect pixel_copy_rect = gfx::ToEnclosedRect(
+ gfx::ScaleRect(copy_rect, scale_factor));
+ XShmPutImage(display_, pixmap, gc, image,
+ pixel_copy_rect.x() - pixel_bitmap_rect.x(), /* source x */
+ pixel_copy_rect.y() - pixel_bitmap_rect.y(), /* source y */
+ pixel_copy_rect.x() - pixel_bitmap_rect.x(), /* dest x */
+ pixel_copy_rect.y() - pixel_bitmap_rect.y(), /* dest y */
+ pixel_copy_rect.width(), pixel_copy_rect.height(),
+ False /* send_event */);
+ }
+#else
+ XShmPutImage(display_, pixmap, gc, image,
+ 0, 0 /* source x, y */, 0, 0 /* dest x, y */,
+ width, height, False /* send_event */);
+#endif
+ XDestroyImage(image);
+ } else { // case SHARED_MEMORY_NONE
+ // No shared memory support, we have to copy the bitmap contents
+ // to the X server. Xlib wraps the underlying PutImage call
+ // behind several layers of functions which try to convert the
+ // image into the format which the X server expects. The
+ // following values hopefully disable all conversions.
+ XImage image;
+ memset(&image, 0, sizeof(image));
+
+ image.width = width;
+ image.height = height;
+ image.depth = 32;
+ image.bits_per_pixel = 32;
+ image.format = ZPixmap;
+ image.byte_order = LSBFirst;
+ image.bitmap_unit = 8;
+ image.bitmap_bit_order = LSBFirst;
+ image.bytes_per_line = width * 4;
+ image.red_mask = 0xff;
+ image.green_mask = 0xff00;
+ image.blue_mask = 0xff0000;
+ image.data = static_cast<char*>(dib->memory());
+
+ XPutImage(display_, pixmap, gc, &image,
+ 0, 0 /* source x, y */, 0, 0 /* dest x, y */,
+ width, height);
+ }
+ XFreeGC(display_, gc);
+ }
+
+ picture = ui::CreatePictureFromSkiaPixmap(display_, pixmap);
+
+ if (scale_factor != 1.0) {
+ float up_scale = 1.0 / scale_factor;
+ XTransform scaling = { {
+ { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) },
+ { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) },
+ { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(up_scale) }
+ } };
+ XRenderSetPictureTransform(display_, picture, &scaling);
+ XRenderSetPictureFilter(display_, picture, FilterGood, NULL, 0);
+ }
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ const gfx::Rect& copy_rect = copy_rects[i];
+ XRenderComposite(display_,
+ PictOpSrc, // op
+ picture, // src
+ 0, // mask
+ picture_, // dest
+ copy_rect.x() - bitmap_rect.x(), // src_x
+ copy_rect.y() - bitmap_rect.y(), // src_y
+ 0, // mask_x
+ 0, // mask_y
+ copy_rect.x(), // dest_x
+ copy_rect.y(), // dest_y
+ copy_rect.width(), // width
+ copy_rect.height()); // height
+ }
+
+ // In the case of shared memory, we wait for the composite to complete so that
+ // we are sure that the X server has finished reading from the shared memory
+ // segment.
+ if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) {
+ XSyncHandler* handler = XSyncHandler::GetInstance();
+ if (handler->Enabled()) {
+ *scheduled_completion_callback = true;
+ handler->PushPaintCounter(
+ dib, display_, picture, pixmap, completion_callback);
+ } else {
+ XSync(display_, False);
+ }
+ }
+
+ if (*scheduled_completion_callback == false) {
+ // If we didn't schedule a callback, we need to delete our resources now.
+ XRenderFreePicture(display_, picture);
+ XFreePixmap(display_, pixmap);
+ }
+}
+
+bool BackingStoreGtk::CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) {
+ base::TimeTicks begin_time = base::TimeTicks::Now();
+
+ if (visual_depth_ < 24) {
+ // CopyFromBackingStore() copies pixels out of the XImage
+ // in a way that assumes that each component (red, green,
+ // blue) is a byte. This doesn't work on visuals which
+ // encode a pixel color with less than a byte per color.
+ return false;
+ }
+
+ const int width = std::min(size().width(), rect.width());
+ const int height = std::min(size().height(), rect.height());
+
+ XImage* image;
+ XShmSegmentInfo shminfo; // Used only when shared memory is enabled.
+ if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) {
+ // Use shared memory for faster copies when it's available.
+ Visual* visual = static_cast<Visual*>(visual_);
+ memset(&shminfo, 0, sizeof(shminfo));
+ image = XShmCreateImage(display_, visual, 32,
+ ZPixmap, NULL, &shminfo, width, height);
+ if (!image) {
+ return false;
+ }
+ // Create the shared memory segment for the image and map it.
+ if (image->bytes_per_line == 0 || image->height == 0 ||
+ static_cast<size_t>(image->height) >
+ (std::numeric_limits<size_t>::max() / image->bytes_per_line)) {
+ XDestroyImage(image);
+ return false;
+ }
+ shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height,
+ IPC_CREAT|0600);
+ if (shminfo.shmid == -1) {
+ XDestroyImage(image);
+ LOG(WARNING) << "Failed to get shared memory segment. "
+ "Performance may be degraded.";
+ return false;
+ } else {
+ VLOG(1) << "Got shared memory segment " << shminfo.shmid;
+ }
+
+ void* mapped_memory = shmat(shminfo.shmid, NULL, SHM_RDONLY);
+ shmctl(shminfo.shmid, IPC_RMID, 0);
+ if (mapped_memory == (void*)-1) {
+ XDestroyImage(image);
+ return false;
+ }
+ shminfo.shmaddr = image->data = static_cast<char*>(mapped_memory);
+
+ if (!XShmAttach(display_, &shminfo) ||
+ !XShmGetImage(display_, pixmap_, image, rect.x(), rect.y(),
+ AllPlanes)) {
+ DestroySharedImage(display_, image, &shminfo);
+ LOG(WARNING) << "X failed to get shared memory segment. "
+ "Performance may be degraded.";
+ return false;
+ }
+
+ VLOG(1) << "Using X shared memory segment " << shminfo.shmid;
+ } else {
+ LOG(WARNING) << "Not using X shared memory.";
+ // Non-shared memory case just copy the image from the server.
+ image = XGetImage(display_, pixmap_,
+ rect.x(), rect.y(), width, height,
+ AllPlanes, ZPixmap);
+ }
+
+ // TODO(jhawkins): Need to convert the image data if the image bits per pixel
+ // is not 32.
+ // Note that this also initializes the output bitmap as opaque.
+ if (!output->Allocate(width, height, true) ||
+ image->bits_per_pixel != 32) {
+ if (shared_memory_support_ != ui::SHARED_MEMORY_NONE)
+ DestroySharedImage(display_, image, &shminfo);
+ else
+ XDestroyImage(image);
+ return false;
+ }
+
+ // The X image might have a different row stride, so iterate through
+ // it and copy each row out, only up to the pixels we're actually
+ // using. This code assumes a visual mode where a pixel is
+ // represented using a 32-bit unsigned int, with a byte per component.
+ const SkBitmap& bitmap = output->GetBitmap();
+ SkAutoLockPixels alp(bitmap);
+
+ for (int y = 0; y < height; y++) {
+ const uint32* src_row = reinterpret_cast<uint32*>(
+ &image->data[image->bytes_per_line * y]);
+ uint32* dest_row = bitmap.getAddr32(0, y);
+ for (int x = 0; x < width; ++x, ++dest_row) {
+ // Force alpha to be 0xff, because otherwise it causes rendering problems.
+ *dest_row = src_row[x] | 0xff000000;
+ }
+ }
+
+ if (shared_memory_support_ != ui::SHARED_MEMORY_NONE)
+ DestroySharedImage(display_, image, &shminfo);
+ else
+ XDestroyImage(image);
+
+ HISTOGRAM_TIMES("BackingStore.RetrievalFromX",
+ base::TimeTicks::Now() - begin_time);
+ return true;
+}
+
+void BackingStoreGtk::ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+ if (!display_)
+ return;
+
+ // We only support scrolling in one direction at a time.
+ DCHECK(delta.x() == 0 || delta.y() == 0);
+
+ if (delta.y()) {
+ // Positive values of |delta|.y() scroll up
+ if (abs(delta.y()) < clip_rect.height()) {
+ XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_),
+ clip_rect.x() /* source x */,
+ std::max(clip_rect.y(), clip_rect.y() - delta.y()),
+ clip_rect.width(),
+ clip_rect.height() - abs(delta.y()),
+ clip_rect.x() /* dest x */,
+ std::max(clip_rect.y(), clip_rect.y() + delta.y()) /* dest y */
+ );
+ }
+ } else if (delta.x()) {
+ // Positive values of |delta|.x() scroll right
+ if (abs(delta.x()) < clip_rect.width()) {
+ XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_),
+ std::max(clip_rect.x(), clip_rect.x() - delta.x()),
+ clip_rect.y() /* source y */,
+ clip_rect.width() - abs(delta.x()),
+ clip_rect.height(),
+ std::max(clip_rect.x(), clip_rect.x() + delta.x()) /* dest x */,
+ clip_rect.y() /* dest x */);
+ }
+ }
+}
+
+void BackingStoreGtk::XShowRect(const gfx::Point &origin,
+ const gfx::Rect& rect, XID target) {
+ XCopyArea(display_, pixmap_, target, static_cast<GC>(pixmap_gc_),
+ rect.x(), rect.y(), rect.width(), rect.height(),
+ rect.x() + origin.x(), rect.y() + origin.y());
+}
+
+#if defined(TOOLKIT_GTK)
+void BackingStoreGtk::PaintToRect(const gfx::Rect& rect, GdkDrawable* target) {
+ cairo_surface_t* surface = cairo_xlib_surface_create(
+ display_, pixmap_, static_cast<Visual*>(visual_),
+ size().width(), size().height());
+ cairo_t* cr = gdk_cairo_create(target);
+
+ cairo_translate(cr, rect.x(), rect.y());
+ double x_scale = static_cast<double>(rect.width()) / size().width();
+ double y_scale = static_cast<double>(rect.height()) / size().height();
+ cairo_scale(cr, x_scale, y_scale);
+
+ cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
+ cairo_pattern_set_filter(pattern, CAIRO_FILTER_BEST);
+ cairo_set_source(cr, pattern);
+ cairo_pattern_destroy(pattern);
+
+ cairo_identity_matrix(cr);
+
+ cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
+ cairo_fill(cr);
+ cairo_destroy(cr);
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store_gtk.h b/chromium/content/browser/renderer_host/backing_store_gtk.h
new file mode 100644
index 00000000000..0e81597c0b6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_gtk.h
@@ -0,0 +1,107 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_GTK_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_GTK_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/common/content_export.h"
+#include "ui/base/x/x11_util.h"
+
+namespace gfx {
+class Point;
+class Rect;
+}
+
+typedef struct _GdkDrawable GdkDrawable;
+
+namespace content {
+
+class CONTENT_EXPORT BackingStoreGtk : public BackingStore {
+ public:
+ // Create a backing store on the X server. The visual is an Xlib Visual
+ // describing the format of the target window and the depth is the color
+ // depth of the X window which will be drawn into.
+ BackingStoreGtk(RenderWidgetHost* widget,
+ const gfx::Size& size,
+ void* visual,
+ int depth);
+
+ // This is for unittesting only. An object constructed using this constructor
+ // will silently ignore all paints
+ BackingStoreGtk(RenderWidgetHost* widget, const gfx::Size& size);
+
+ virtual ~BackingStoreGtk();
+
+ Display* display() const { return display_; }
+ XID root_window() const { return root_window_; }
+
+ // Copy from the server-side backing store to the target window
+ // origin: the destination rectangle origin
+ // damage: the area to copy
+ // target: the X id of the target window
+ void XShowRect(const gfx::Point &origin, const gfx::Rect& damage,
+ XID target);
+
+#if defined(TOOLKIT_GTK)
+ // Paint the backing store into the target's |dest_rect|.
+ void PaintToRect(const gfx::Rect& dest_rect, GdkDrawable* target);
+#endif
+
+ // BackingStore implementation.
+ virtual size_t MemorySize() OVERRIDE;
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) OVERRIDE;
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) OVERRIDE;
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) OVERRIDE;
+
+ private:
+ // Paints the bitmap from the renderer onto the backing store without
+ // using Xrender to composite the pixmaps.
+ void PaintRectWithoutXrender(TransportDIB* bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects);
+
+ // This is the connection to the X server where this backing store will be
+ // displayed.
+ Display* const display_;
+ // What flavor, if any, MIT-SHM (X shared memory) support we have.
+ const ui::SharedMemorySupport shared_memory_support_;
+ // If this is true, then we can use Xrender to composite our pixmaps.
+ const bool use_render_;
+ // If |use_render_| is false, this is the number of bits-per-pixel for |depth|
+ int pixmap_bpp_;
+ // if |use_render_| is false, we need the Visual to get the RGB masks.
+ void* const visual_;
+ // This is the depth of the target window.
+ const int visual_depth_;
+ // The parent window (probably a GtkDrawingArea) for this backing store.
+ const XID root_window_;
+ // This is a handle to the server side pixmap which is our backing store.
+ XID pixmap_;
+ // This is the RENDER picture pointing at |pixmap_|.
+ XID picture_;
+ // This is a default graphic context, used in XCopyArea
+ void* pixmap_gc_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_GTK_H_
diff --git a/chromium/content/browser/renderer_host/backing_store_mac.h b/chromium/content/browser/renderer_host/backing_store_mac.h
new file mode 100644
index 00000000000..698ca3d028c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_mac.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "content/browser/renderer_host/backing_store.h"
+
+namespace content {
+
+class BackingStoreMac : public BackingStore {
+ public:
+ // |size| is in view units, |device_scale_factor| is the backingScaleFactor.
+ // The pixel size of the backing store is size.Scale(device_scale_factor).
+ BackingStoreMac(RenderWidgetHost* widget,
+ const gfx::Size& size,
+ float device_scale_factor);
+ virtual ~BackingStoreMac();
+
+ // A CGLayer that stores the contents of the backing store, cached in GPU
+ // memory if possible.
+ CGLayerRef cg_layer() { return cg_layer_; }
+
+ // A CGBitmapContext that stores the contents of the backing store if the
+ // corresponding Cocoa view has not been inserted into an NSWindow yet.
+ CGContextRef cg_bitmap() { return cg_bitmap_; }
+
+ // Called when the view's backing scale factor changes.
+ void ScaleFactorChanged(float device_scale_factor);
+
+ // BackingStore implementation.
+ virtual size_t MemorySize() OVERRIDE;
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) OVERRIDE;
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) OVERRIDE;
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) OVERRIDE;
+
+ void CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
+ CGContextRef context);
+
+ private:
+ // Creates a CGLayer associated with its owner view's window's graphics
+ // context, sized properly for the backing store. Returns NULL if the owner
+ // is not in a window with a CGContext. cg_layer_ is assigned this method's
+ // result.
+ CGLayerRef CreateCGLayer();
+
+ // Creates a CGBitmapContext sized properly for the backing store. The
+ // owner view need not be in a window. cg_bitmap_ is assigned this method's
+ // result.
+ CGContextRef CreateCGBitmapContext();
+
+ base::ScopedCFTypeRef<CGContextRef> cg_bitmap_;
+ base::ScopedCFTypeRef<CGLayerRef> cg_layer_;
+
+ // Number of physical pixels per view unit. This is 1 or 2 in practice.
+ float device_scale_factor_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_
diff --git a/chromium/content/browser/renderer_host/backing_store_mac.mm b/chromium/content/browser/renderer_host/backing_store_mac.mm
new file mode 100644
index 00000000000..8b3fda9840d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_mac.mm
@@ -0,0 +1,297 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "content/browser/renderer_host/backing_store_mac.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
+#include "ui/surface/transport_dib.h"
+
+namespace content {
+
+// Mac Backing Stores:
+//
+// Since backing stores are only ever written to or drawn into windows, we keep
+// our backing store in a CGLayer that can get cached in GPU memory. This
+// allows acclerated drawing into the layer and lets scrolling and such happen
+// all or mostly on the GPU, which is good for performance.
+
+BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget,
+ const gfx::Size& size,
+ float device_scale_factor)
+ : BackingStore(widget, size), device_scale_factor_(device_scale_factor) {
+ cg_layer_.reset(CreateCGLayer());
+ if (!cg_layer_) {
+ // The view isn't in a window yet. Use a CGBitmapContext for now.
+ cg_bitmap_.reset(CreateCGBitmapContext());
+ CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
+ }
+}
+
+BackingStoreMac::~BackingStoreMac() {
+}
+
+void BackingStoreMac::ScaleFactorChanged(float device_scale_factor) {
+ if (device_scale_factor == device_scale_factor_)
+ return;
+
+ device_scale_factor_ = device_scale_factor;
+
+ base::ScopedCFTypeRef<CGLayerRef> new_layer(CreateCGLayer());
+ // If we have a layer, copy the old contents. A pixelated flash is better
+ // than a white flash.
+ if (new_layer && cg_layer_) {
+ CGContextRef layer = CGLayerGetContext(new_layer);
+ CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer_);
+ }
+
+ cg_layer_.swap(new_layer);
+ if (!cg_layer_) {
+ // The view isn't in a window yet. Use a CGBitmapContext for now.
+ cg_bitmap_.reset(CreateCGBitmapContext());
+ CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
+ }
+}
+
+size_t BackingStoreMac::MemorySize() {
+ return gfx::ToFlooredSize(
+ gfx::ScaleSize(size(), device_scale_factor_)).GetArea() * 4;
+}
+
+void BackingStoreMac::PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) {
+ *scheduled_completion_callback = false;
+ DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
+
+ TransportDIB* dib = process->GetTransportDIB(bitmap);
+ if (!dib)
+ return;
+
+ gfx::Size pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(size(), device_scale_factor_));
+ gfx::Rect pixel_bitmap_rect = ToFlooredRectDeprecated(
+ gfx::ScaleRect(bitmap_rect, scale_factor));
+
+ size_t bitmap_byte_count =
+ pixel_bitmap_rect.width() * pixel_bitmap_rect.height() * 4;
+ DCHECK_GE(dib->size(), bitmap_byte_count);
+
+ base::ScopedCFTypeRef<CGDataProviderRef> data_provider(
+ CGDataProviderCreateWithData(
+ NULL, dib->memory(), bitmap_byte_count, NULL));
+
+ base::ScopedCFTypeRef<CGImageRef> bitmap_image(
+ CGImageCreate(pixel_bitmap_rect.width(),
+ pixel_bitmap_rect.height(),
+ 8,
+ 32,
+ 4 * pixel_bitmap_rect.width(),
+ base::mac::GetSystemColorSpace(),
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ data_provider,
+ NULL,
+ false,
+ kCGRenderingIntentDefault));
+
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ const gfx::Rect& copy_rect = copy_rects[i];
+ gfx::Rect pixel_copy_rect = ToFlooredRectDeprecated(
+ gfx::ScaleRect(copy_rect, scale_factor));
+
+ // Only the subpixels given by copy_rect have pixels to copy.
+ base::ScopedCFTypeRef<CGImageRef> image(CGImageCreateWithImageInRect(
+ bitmap_image,
+ CGRectMake(pixel_copy_rect.x() - pixel_bitmap_rect.x(),
+ pixel_copy_rect.y() - pixel_bitmap_rect.y(),
+ pixel_copy_rect.width(),
+ pixel_copy_rect.height())));
+
+ if (!cg_layer()) {
+ // The view may have moved to a window. Try to get a CGLayer.
+ cg_layer_.reset(CreateCGLayer());
+ if (cg_layer()) {
+ // Now that we have a layer, copy the cached image into it.
+ base::ScopedCFTypeRef<CGImageRef> bitmap_image(
+ CGBitmapContextCreateImage(cg_bitmap_));
+ CGContextDrawImage(CGLayerGetContext(cg_layer()),
+ CGRectMake(0, 0, size().width(), size().height()),
+ bitmap_image);
+ // Discard the cache bitmap, since we no longer need it.
+ cg_bitmap_.reset(NULL);
+ }
+ }
+
+ DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
+
+ if (cg_layer()) {
+ // The CGLayer's origin is in the lower left, but flipping the CTM would
+ // cause the image to get drawn upside down. So we move the rectangle
+ // to the right position before drawing the image.
+ CGContextRef layer = CGLayerGetContext(cg_layer());
+ gfx::Rect paint_rect = copy_rect;
+ paint_rect.set_y(size().height() - copy_rect.bottom());
+ CGContextDrawImage(layer, paint_rect.ToCGRect(), image);
+ } else {
+ // The layer hasn't been created yet, so draw into the cache bitmap.
+ gfx::Rect paint_rect = copy_rect;
+ paint_rect.set_y(size().height() - copy_rect.bottom());
+ CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image);
+ }
+ }
+}
+
+bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) {
+ // TODO(thakis): Make sure this works with HiDPI backing stores.
+ if (!output->Allocate(rect.width(), rect.height(), true))
+ return false;
+
+ CGContextRef temp_context = output->GetSurface();
+ gfx::ScopedCGContextSaveGState save_gstate(temp_context);
+ CGContextTranslateCTM(temp_context, 0.0, size().height());
+ CGContextScaleCTM(temp_context, 1.0, -1.0);
+ if (cg_layer()) {
+ CGContextDrawLayerAtPoint(temp_context, CGPointMake(-rect.x(), -rect.y()),
+ cg_layer());
+ } else {
+ base::ScopedCFTypeRef<CGImageRef> bitmap_image(
+ CGBitmapContextCreateImage(cg_bitmap_));
+ CGContextDrawImage(
+ temp_context,
+ CGRectMake(-rect.x(), -rect.y(), rect.width(), rect.height()),
+ bitmap_image);
+ }
+
+ return true;
+}
+
+// Scroll the contents of our CGLayer
+void BackingStoreMac::ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+ DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
+
+ // "Scroll" the contents of the layer by creating a new CGLayer,
+ // copying the contents of the old one into the new one offset by the scroll
+ // amount, swapping in the new CGLayer, and then painting in the new data.
+ //
+ // The Windows code always sets the whole backing store as the source of the
+ // scroll. Thus, we only have to worry about pixels which will end up inside
+ // the clipping rectangle. (Note that the clipping rectangle is not
+ // translated by the scroll.)
+
+ // We assume |clip_rect| is contained within the backing store.
+ DCHECK(clip_rect.bottom() <= size().height());
+ DCHECK(clip_rect.right() <= size().width());
+
+ if ((delta.x() || delta.y()) &&
+ abs(delta.x()) < size().width() && abs(delta.y()) < size().height()) {
+ if (cg_layer()) {
+ CGContextRef layer = CGLayerGetContext(cg_layer());
+ gfx::ScopedCGContextSaveGState save_gstate(layer);
+ CGContextClipToRect(layer,
+ CGRectMake(clip_rect.x(),
+ size().height() - clip_rect.bottom(),
+ clip_rect.width(),
+ clip_rect.height()));
+ CGContextDrawLayerAtPoint(layer,
+ CGPointMake(delta.x(), -delta.y()), cg_layer());
+ } else {
+ // We don't have a layer, so scroll the contents of the CGBitmapContext.
+ base::ScopedCFTypeRef<CGImageRef> bitmap_image(
+ CGBitmapContextCreateImage(cg_bitmap_));
+ gfx::ScopedCGContextSaveGState save_gstate(cg_bitmap_);
+ CGContextClipToRect(cg_bitmap_,
+ CGRectMake(clip_rect.x(),
+ size().height() - clip_rect.bottom(),
+ clip_rect.width(),
+ clip_rect.height()));
+ CGContextDrawImage(cg_bitmap_,
+ CGRectMake(delta.x(), -delta.y(),
+ size().width(), size().height()),
+ bitmap_image);
+ }
+ }
+}
+
+void BackingStoreMac::CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
+ CGContextRef context) {
+ gfx::ScopedCGContextSaveGState save_gstate(context);
+ CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
+ if (cg_layer_) {
+ CGContextDrawLayerInRect(context, dest_rect, cg_layer_);
+ } else {
+ base::ScopedCFTypeRef<CGImageRef> image(
+ CGBitmapContextCreateImage(cg_bitmap_));
+ CGContextDrawImage(context, dest_rect, image);
+ }
+}
+
+CGLayerRef BackingStoreMac::CreateCGLayer() {
+ // The CGLayer should be optimized for drawing into the containing window,
+ // so extract a CGContext corresponding to the window to be passed to
+ // CGLayerCreateWithContext.
+ NSWindow* window = [render_widget_host()->GetView()->GetNativeView() window];
+ if ([window windowNumber] <= 0) {
+ // This catches a nil |window|, as well as windows that exist but that
+ // aren't yet connected to WindowServer.
+ return NULL;
+ }
+
+ NSGraphicsContext* ns_context = [window graphicsContext];
+ DCHECK(ns_context);
+
+ CGContextRef cg_context = static_cast<CGContextRef>(
+ [ns_context graphicsPort]);
+ DCHECK(cg_context);
+
+ // Note: This takes the backingScaleFactor of cg_context into account. The
+ // bitmap backing |layer| will be size() * 2 in HiDPI mode automatically.
+ CGLayerRef layer = CGLayerCreateWithContext(cg_context,
+ size().ToCGSize(),
+ NULL);
+ DCHECK(layer);
+
+ return layer;
+}
+
+CGContextRef BackingStoreMac::CreateCGBitmapContext() {
+ gfx::Size pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(size(), device_scale_factor_));
+ // A CGBitmapContext serves as a stand-in for the layer before the view is
+ // in a containing window.
+ CGContextRef context = CGBitmapContextCreate(NULL,
+ pixel_size.width(),
+ pixel_size.height(),
+ 8, pixel_size.width() * 4,
+ base::mac::GetSystemColorSpace(),
+ kCGImageAlphaPremultipliedFirst |
+ kCGBitmapByteOrder32Host);
+ DCHECK(context);
+
+ return context;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store_manager.cc b/chromium/content/browser/renderer_host/backing_store_manager.cc
new file mode 100644
index 00000000000..4d79b0ff4f2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_manager.cc
@@ -0,0 +1,282 @@
+// 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 "content/browser/renderer_host/backing_store_manager.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/mru_cache.h"
+#include "base/sys_info.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/common/content_switches.h"
+
+namespace content {
+namespace {
+
+// There are two separate caches, |large_cache| and |small_cache|. large_cache
+// is meant for large items (tabs, popup windows), while small_cache is meant
+// for small items (extension popups, HTML5 notifications). The idea is that
+// we'll almost always try to evict from large_cache first since small_cache
+// items will tend to be visible more of the time.
+typedef base::OwningMRUCache<RenderWidgetHost*, BackingStore*>
+ BackingStoreCache;
+BackingStoreCache* large_cache = NULL;
+BackingStoreCache* small_cache = NULL;
+
+// Threshold is based on a single large-monitor-width toolstrip.
+// (32bpp, 32 pixels high, 1920 pixels wide)
+// TODO(aa): The extension system no longer supports toolstrips, but we think
+// this might be helping for other examples of small HTML views in Chrome.
+// Maybe this cache should be redesigned to simply prefer smaller objects to
+// larger ones, rather than having a fixed threshold.
+// For more background, see: crbug.com/100506.
+const size_t kSmallThreshold = 4 * 32 * 1920;
+
+// Pick a large monitor size to use as a multiplier. This is multiplied by the
+// max number of large backing stores (usually tabs) to pick a ceiling on the
+// max memory to use.
+// TODO(erikkay) Perhaps we should actually use monitor size? That way we
+// could make an assertion like "worse case, there are two tabs in the cache".
+// However, the small_cache might mess up these calculations a bit.
+// TODO(erikkay) 32bpp assumption isn't great.
+const size_t kMemoryMultiplier = 4 * 1920 * 1200; // ~9MB
+
+// The maximum number of large BackingStoreCache objects (tabs) to use.
+// Use a minimum of 2, and add one for each 256MB of physical memory you have.
+// Cap at 5, the thinking being that even if you have a gigantic amount of
+// RAM, there's a limit to how much caching helps beyond a certain number
+// of tabs. If users *really* want unlimited stores, allow it via the
+// --disable-backing-store-limit flag.
+static size_t MaxNumberOfBackingStores() {
+ static bool unlimited = false;
+ const CommandLine& command = *CommandLine::ForCurrentProcess();
+ unlimited = command.HasSwitch(switches::kDisableBackingStoreLimit);
+
+
+ if (unlimited) {
+ // 100 isn't truly unlimited, but given that backing stores count against
+ // GDI memory, it's well past any reasonable number. Many systems will
+ // begin to fail in strange ways well before they hit 100 stores.
+ return 100;
+ } else {
+ return std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256));
+ }
+}
+
+// The maximum about of memory to use for all BackingStoreCache object combined.
+static size_t MaxBackingStoreMemory() {
+ // Compute in terms of the number of large monitor's worth of backing-store.
+ return MaxNumberOfBackingStores() * kMemoryMultiplier;
+}
+
+// Expires the given |backing_store| from |cache|.
+void ExpireBackingStoreAt(BackingStoreCache* cache,
+ BackingStoreCache::iterator backing_store) {
+ cache->Erase(backing_store);
+}
+
+size_t ExpireLastBackingStore(BackingStoreCache* cache) {
+ if (cache->size() < 1)
+ return 0;
+
+ // Crazy C++ alert: rbegin.base() is a forward iterator pointing to end(),
+ // so we need to do -- to move one back to the actual last item.
+ BackingStoreCache::iterator entry = --cache->rbegin().base();
+ size_t entry_size = entry->second->MemorySize();
+ ExpireBackingStoreAt(cache, entry);
+ return entry_size;
+}
+
+void CreateCacheSpace(size_t size) {
+ // Given a request for |size|, first free from the large cache (until there's
+ // only one item left) and then do the same from the small cache if we still
+ // don't have enough.
+ while (size > 0 && (large_cache->size() > 1 || small_cache->size() > 1)) {
+ BackingStoreCache* cache =
+ (large_cache->size() > 1) ? large_cache : small_cache;
+ while (size > 0 && cache->size() > 1) {
+ size_t entry_size = ExpireLastBackingStore(cache);
+ if (size > entry_size)
+ size -= entry_size;
+ else
+ size = 0;
+ }
+ }
+ DCHECK(size == 0);
+}
+
+// Creates the backing store for the host based on the dimensions passed in.
+// Removes the existing backing store if there is one.
+BackingStore* CreateBackingStore(RenderWidgetHost* host,
+ const gfx::Size& backing_store_size) {
+ // Remove any existing backing store in case we're replacing it.
+ BackingStoreManager::RemoveBackingStore(host);
+
+ if (!large_cache) {
+ large_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT);
+ small_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT);
+ }
+
+ // TODO(erikkay) 32bpp is not always accurate
+ size_t new_mem = backing_store_size.GetArea() * 4;
+ size_t current_mem = BackingStoreManager::MemorySize();
+ size_t max_mem = MaxBackingStoreMemory();
+ DCHECK(new_mem < max_mem);
+ if (current_mem + new_mem > max_mem) {
+ // Need to remove old backing stores to make room for the new one. We
+ // don't want to do this when the backing store is being replace by a new
+ // one for the same WebContents, but this case won't get called then: we'll
+ // have removed the old one in the RemoveBackingStore above, and the cache
+ // won't be over-sized.
+ CreateCacheSpace((current_mem + new_mem) - max_mem);
+ }
+ DCHECK((BackingStoreManager::MemorySize() + new_mem) <= max_mem);
+
+ BackingStoreCache* cache;
+ if (new_mem > kSmallThreshold) {
+ // Limit the number of large backing stores (tabs) to the memory tier number
+ // (between 2-5). While we allow a larger amount of memory for people who
+ // have large windows, this means that those who use small browser windows
+ // won't ever cache more than 5 tabs, so they pay a smaller memory cost.
+ if (large_cache->size() >= MaxNumberOfBackingStores())
+ ExpireLastBackingStore(large_cache);
+ cache = large_cache;
+ } else {
+ cache = small_cache;
+ }
+ BackingStore* backing_store = RenderWidgetHostImpl::From(
+ host)->AllocBackingStore(backing_store_size);
+ if (backing_store)
+ cache->Put(host, backing_store);
+ return backing_store;
+}
+
+int ComputeTotalArea(const std::vector<gfx::Rect>& rects) {
+ // We assume that the given rects are non-overlapping, which is a property of
+ // the paint rects generated by the PaintAggregator.
+#ifndef NDEBUG
+ for (size_t i = 0; i < rects.size(); ++i) {
+ for (size_t j = 0; j < rects.size(); ++j) {
+ if (i != j)
+ DCHECK(!rects[i].Intersects(rects[j]));
+ }
+ }
+#endif
+ int area = 0;
+ for (size_t i = 0; i < rects.size(); ++i)
+ area += rects[i].size().GetArea();
+ return area;
+}
+
+} // namespace
+
+// BackingStoreManager ---------------------------------------------------------
+
+// static
+BackingStore* BackingStoreManager::GetBackingStore(
+ RenderWidgetHost* host,
+ const gfx::Size& desired_size) {
+ BackingStore* backing_store = Lookup(host);
+ if (backing_store) {
+ // If we already have a backing store, then make sure it is the correct
+ // size.
+ if (backing_store->size() == desired_size)
+ return backing_store;
+ backing_store = NULL;
+ }
+
+ return backing_store;
+}
+
+// static
+void BackingStoreManager::PrepareBackingStore(
+ RenderWidgetHost* host,
+ const gfx::Size& backing_store_size,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* needs_full_paint,
+ bool* scheduled_completion_callback) {
+ BackingStore* backing_store = GetBackingStore(host, backing_store_size);
+ if (!backing_store) {
+ // We need to get Webkit to generate a new paint here, as we
+ // don't have a previous snapshot.
+ if (bitmap_rect.size() != backing_store_size ||
+ bitmap_rect.x() != 0 || bitmap_rect.y() != 0 ||
+ ComputeTotalArea(copy_rects) != backing_store_size.GetArea() ||
+ !(backing_store = CreateBackingStore(host, backing_store_size))) {
+ DCHECK(needs_full_paint != NULL);
+ *needs_full_paint = true;
+ *scheduled_completion_callback = false;
+ // Makes no sense to paint the transport dib if we are going
+ // to request a full paint.
+ return;
+ }
+ }
+
+ backing_store->PaintToBackingStore(host->GetProcess(), bitmap,
+ bitmap_rect, copy_rects, scale_factor,
+ completion_callback,
+ scheduled_completion_callback);
+}
+
+// static
+BackingStore* BackingStoreManager::Lookup(RenderWidgetHost* host) {
+ if (large_cache) {
+ BackingStoreCache::iterator it = large_cache->Get(host);
+ if (it != large_cache->end())
+ return it->second;
+
+ // This moves host to the front of the MRU.
+ it = small_cache->Get(host);
+ if (it != small_cache->end())
+ return it->second;
+ }
+ return NULL;
+}
+
+// static
+void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) {
+ if (!large_cache)
+ return;
+
+ BackingStoreCache* cache = large_cache;
+ BackingStoreCache::iterator it = cache->Peek(host);
+ if (it == cache->end()) {
+ cache = small_cache;
+ it = cache->Peek(host);
+ if (it == cache->end())
+ return;
+ }
+ cache->Erase(it);
+}
+
+// static
+void BackingStoreManager::RemoveAllBackingStores() {
+ if (large_cache) {
+ large_cache->Clear();
+ small_cache->Clear();
+ }
+}
+
+// static
+size_t BackingStoreManager::MemorySize() {
+ if (!large_cache)
+ return 0;
+
+ size_t mem = 0;
+ BackingStoreCache::iterator it;
+ for (it = large_cache->begin(); it != large_cache->end(); ++it)
+ mem += it->second->MemorySize();
+
+ for (it = small_cache->begin(); it != small_cache->end(); ++it)
+ mem += it->second->MemorySize();
+
+ return mem;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store_manager.h b/chromium/content/browser/renderer_host/backing_store_manager.h
new file mode 100644
index 00000000000..86111889d11
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_manager.h
@@ -0,0 +1,86 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/process/process.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/surface/transport_dib.h"
+
+namespace content {
+class BackingStore;
+class RenderWidgetHost;
+
+// This class manages backing stores in the browsr. Every RenderWidgetHost is
+// associated with a backing store which it requests from this class. The
+// hosts don't maintain any references to the backing stores. These backing
+// stores are maintained in a cache which can be trimmed as needed.
+class BackingStoreManager {
+ public:
+ // Returns a backing store which matches the desired dimensions.
+ //
+ // backing_store_rect
+ // The desired backing store dimensions.
+ // Returns a pointer to the backing store on success, NULL on failure.
+ static BackingStore* GetBackingStore(RenderWidgetHost* host,
+ const gfx::Size& desired_size);
+
+ // Makes a backing store which is fully ready for consumption, i.e. the
+ // bitmap from the renderer has been copied into the backing store.
+ //
+ // backing_store_size
+ // The desired backing store dimensions, in DIPs.
+ // bitmap_section
+ // The bitmap section from the renderer.
+ // bitmap_rect
+ // The rect to be painted into the backing store, in DIPs.
+ // scale_factor
+ // The device scale factor the backing store is expected to be at.
+ // If the backing store's device scale factor doesn't match, it will need
+ // to scale |bitmap| at paint time. This will only be out of sync with the
+ // backing store scale factor for a few frames, right after device scale
+ // changes.
+ // needs_full_paint
+ // Set if we need to send out a request to paint the view
+ // to the renderer.
+ static void PrepareBackingStore(
+ RenderWidgetHost* host,
+ const gfx::Size& backing_store_size,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* needs_full_paint,
+ bool* scheduled_completion_callback);
+
+ // Returns a matching backing store for the host.
+ // Returns NULL if we fail to find one.
+ static BackingStore* Lookup(RenderWidgetHost* host);
+
+ // Removes the backing store for the host.
+ static void RemoveBackingStore(RenderWidgetHost* host);
+
+ // Removes all backing stores.
+ static void RemoveAllBackingStores();
+
+ // Current size in bytes of the backing store cache.
+ static size_t MemorySize();
+
+ private:
+ // Not intended for instantiation.
+ BackingStoreManager() {}
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/backing_store_win.cc b/chromium/content/browser/renderer_host/backing_store_win.cc
new file mode 100644
index 00000000000..5ccafbb062b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_win.cc
@@ -0,0 +1,183 @@
+// 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 "content/browser/renderer_host/backing_store_win.h"
+
+#include "base/command_line.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/common/content_switches.h"
+#include "skia/ext/platform_canvas.h"
+#include "ui/base/win/dpi.h"
+#include "ui/gfx/gdi_util.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/surface/transport_dib.h"
+
+namespace content {
+namespace {
+
+// Creates a dib conforming to the height/width/section parameters passed in.
+HANDLE CreateDIB(HDC dc, int width, int height, int color_depth) {
+ BITMAPV5HEADER hdr = {0};
+ ZeroMemory(&hdr, sizeof(BITMAPV5HEADER));
+
+ // These values are shared with gfx::PlatformDevice
+ hdr.bV5Size = sizeof(BITMAPINFOHEADER);
+ hdr.bV5Width = width;
+ hdr.bV5Height = -height; // minus means top-down bitmap
+ hdr.bV5Planes = 1;
+ hdr.bV5BitCount = color_depth;
+ hdr.bV5Compression = BI_RGB; // no compression
+ hdr.bV5SizeImage = 0;
+ hdr.bV5XPelsPerMeter = 1;
+ hdr.bV5YPelsPerMeter = 1;
+ hdr.bV5ClrUsed = 0;
+ hdr.bV5ClrImportant = 0;
+
+ if (BackingStoreWin::ColorManagementEnabled()) {
+ hdr.bV5CSType = LCS_sRGB;
+ hdr.bV5Intent = LCS_GM_IMAGES;
+ }
+
+ void* data = NULL;
+ HANDLE dib = CreateDIBSection(dc, reinterpret_cast<BITMAPINFO*>(&hdr),
+ 0, &data, NULL, 0);
+ DCHECK(data);
+ return dib;
+}
+
+} // namespace
+
+BackingStoreWin::BackingStoreWin(RenderWidgetHost* widget,
+ const gfx::Size& size)
+ : BackingStore(widget, size),
+ backing_store_dib_(NULL),
+ original_bitmap_(NULL) {
+ HDC screen_dc = ::GetDC(NULL);
+ color_depth_ = ::GetDeviceCaps(screen_dc, BITSPIXEL);
+ // Color depths less than 16 bpp require a palette to be specified. Instead,
+ // we specify the desired color depth as 16 which lets the OS to come up
+ // with an approximation.
+ if (color_depth_ < 16)
+ color_depth_ = 16;
+ hdc_ = CreateCompatibleDC(screen_dc);
+ ReleaseDC(NULL, screen_dc);
+}
+
+BackingStoreWin::~BackingStoreWin() {
+ DCHECK(hdc_);
+ if (original_bitmap_) {
+ SelectObject(hdc_, original_bitmap_);
+ }
+ if (backing_store_dib_) {
+ DeleteObject(backing_store_dib_);
+ backing_store_dib_ = NULL;
+ }
+ DeleteDC(hdc_);
+}
+
+// static
+bool BackingStoreWin::ColorManagementEnabled() {
+ static bool enabled = false;
+ static bool checked = false;
+ if (!checked) {
+ checked = true;
+ const CommandLine& command = *CommandLine::ForCurrentProcess();
+ enabled = command.HasSwitch(switches::kEnableMonitorProfile);
+ }
+ return enabled;
+}
+
+size_t BackingStoreWin::MemorySize() {
+ gfx::Size size_in_pixels = gfx::ToCeiledSize(gfx::ScaleSize(size(),
+ ui::win::GetDeviceScaleFactor()));
+ return size_in_pixels.GetArea() * (color_depth_ / 8);
+}
+
+void BackingStoreWin::PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) {
+ *scheduled_completion_callback = false;
+ gfx::Size size_in_pixels = gfx::ToCeiledSize(gfx::ScaleSize(
+ size(), scale_factor));
+ if (!backing_store_dib_) {
+ backing_store_dib_ = CreateDIB(hdc_,
+ size_in_pixels.width(),
+ size_in_pixels.height(),
+ color_depth_);
+ if (!backing_store_dib_) {
+ NOTREACHED();
+ return;
+ }
+ original_bitmap_ = SelectObject(hdc_, backing_store_dib_);
+ }
+
+ TransportDIB* dib = process->GetTransportDIB(bitmap);
+ if (!dib)
+ return;
+
+ gfx::Rect pixel_bitmap_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(bitmap_rect, scale_factor));
+
+ BITMAPINFO bitmap_info;
+ memset(&bitmap_info, 0, sizeof(bitmap_info));
+ gfx::CreateBitmapHeader(pixel_bitmap_rect.width(),
+ pixel_bitmap_rect.height(),
+ &bitmap_info.bmiHeader);
+ // Account for a bitmap_rect that exceeds the bounds of our view.
+ gfx::Rect view_rect(size());
+
+ for (size_t i = 0; i < copy_rects.size(); i++) {
+ gfx::Rect paint_rect = gfx::IntersectRects(view_rect, copy_rects[i]);
+ gfx::Rect pixel_copy_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(paint_rect, scale_factor));
+ gfx::Rect target_rect = pixel_copy_rect;
+ gfx::StretchDIBits(hdc_,
+ target_rect.x(),
+ target_rect.y(),
+ target_rect.width(),
+ target_rect.height(),
+ pixel_copy_rect.x() - pixel_bitmap_rect.x(),
+ pixel_copy_rect.y() - pixel_bitmap_rect.y(),
+ pixel_copy_rect.width(),
+ pixel_copy_rect.height(),
+ dib->memory(),
+ &bitmap_info);
+ }
+}
+
+bool BackingStoreWin::CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) {
+ // TODO(kevers): Make sure this works with HiDPI backing stores.
+ if (!output->Allocate(rect.width(), rect.height(), true))
+ return false;
+
+ HDC temp_dc = output->GetSurface();
+ BitBlt(temp_dc, 0, 0, rect.width(), rect.height(),
+ hdc(), rect.x(), rect.y(), SRCCOPY);
+ return true;
+}
+
+void BackingStoreWin::ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+ // TODO(darin): this doesn't work if delta x() and y() are both non-zero!
+ DCHECK(delta.x() == 0 || delta.y() == 0);
+
+ float scale = ui::win::GetDeviceScaleFactor();
+ gfx::Rect screen_rect = gfx::ToEnclosingRect(
+ gfx::ScaleRect(clip_rect, scale));
+ int dx = static_cast<int>(delta.x() * scale);
+ int dy = static_cast<int>(delta.y() * scale);
+ RECT damaged_rect, r = screen_rect.ToRECT();
+ ScrollDC(hdc_, dx, dy, NULL, &r, NULL, &damaged_rect);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/backing_store_win.h b/chromium/content/browser/renderer_host/backing_store_win.h
new file mode 100644
index 00000000000..abce4b19f37
--- /dev/null
+++ b/chromium/content/browser/renderer_host/backing_store_win.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/renderer_host/backing_store.h"
+
+namespace content {
+
+class BackingStoreWin : public BackingStore {
+ public:
+ BackingStoreWin(RenderWidgetHost* widget, const gfx::Size& size);
+ virtual ~BackingStoreWin();
+
+ HDC hdc() { return hdc_; }
+
+ // Returns true if we should convert to the monitor profile when painting.
+ static bool ColorManagementEnabled();
+
+ // BackingStore implementation.
+ virtual size_t MemorySize() OVERRIDE;
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) OVERRIDE;
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) OVERRIDE;
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) OVERRIDE;
+
+ private:
+ // The backing store dc.
+ HDC hdc_;
+
+ // Handle to the backing store dib.
+ HANDLE backing_store_dib_;
+
+ // Handle to the original bitmap in the dc.
+ HANDLE original_bitmap_;
+
+ // Number of bits per pixel of the screen.
+ int color_depth_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_
diff --git a/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.cc b/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.cc
new file mode 100644
index 00000000000..1bcc8223e97
--- /dev/null
+++ b/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.cc
@@ -0,0 +1,61 @@
+// 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 "content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h"
+
+#include "base/debug/trace_event.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+
+namespace content {
+
+BasicMouseWheelSmoothScrollGesture::BasicMouseWheelSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll,
+ int mouse_event_x, int mouse_event_y)
+ : scroll_down_(scroll_down),
+ pixels_scrolled_(0),
+ pixels_to_scroll_(pixels_to_scroll),
+ mouse_event_x_(mouse_event_x),
+ mouse_event_y_(mouse_event_y) { }
+
+BasicMouseWheelSmoothScrollGesture::~BasicMouseWheelSmoothScrollGesture() { }
+
+bool BasicMouseWheelSmoothScrollGesture::ForwardInputEvents(
+ base::TimeTicks now, RenderWidgetHost* host) {
+
+ if (pixels_scrolled_ >= pixels_to_scroll_)
+ return false;
+
+ double position_delta = smooth_scroll_calculator_.GetScrollDelta(
+ now,
+ RenderWidgetHostImpl::From(host)->GetSyntheticScrollMessageInterval());
+
+
+ WebKit::WebMouseWheelEvent event;
+ event.type = WebKit::WebInputEvent::MouseWheel;
+ event.hasPreciseScrollingDeltas = 0;
+ event.deltaY = scroll_down_ ? -position_delta : position_delta;
+ // TODO(vollick): find a proper way to access
+ // WebCore::WheelEvent::tickMultiplier.
+ event.wheelTicksY = event.deltaY / 120;
+ event.modifiers = 0;
+
+ // TODO(nduca): Figure out plausible x and y values.
+ event.globalX = 0;
+ event.globalY = 0;
+ event.x = mouse_event_x_;
+ event.y = mouse_event_y_;
+ event.windowX = event.x;
+ event.windowY = event.y;
+ host->ForwardWheelEvent(event);
+
+ pixels_scrolled_ += abs(event.deltaY);
+
+ TRACE_COUNTER_ID1(
+ "gpu", "smooth_scroll_by_pixels_scrolled", this, pixels_scrolled_);
+
+ return true;
+}
+
+} // content
+
diff --git a/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h b/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h
new file mode 100644
index 00000000000..adea7a30b74
--- /dev/null
+++ b/chromium/content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BASIC_MOUSE_WHEEL_SMOOTH_SCROLL_GESTURE_
+#define CONTENT_BROWSER_RENDERER_HOST_BASIC_MOUSE_WHEEL_SMOOTH_SCROLL_GESTURE_
+
+#include "base/time/time.h"
+#include "content/browser/renderer_host/smooth_scroll_calculator.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+
+namespace content {
+
+class RenderWidgetHost;
+
+class BasicMouseWheelSmoothScrollGesture : public SmoothScrollGesture {
+ public:
+ BasicMouseWheelSmoothScrollGesture(bool scroll_down, int pixels_to_scroll,
+ int mouse_event_x, int mouse_event_y);
+
+ virtual bool ForwardInputEvents(base::TimeTicks now,
+ RenderWidgetHost* host) OVERRIDE;
+ private:
+ virtual ~BasicMouseWheelSmoothScrollGesture();
+
+ SmoothScrollCalculator smooth_scroll_calculator_;
+
+ bool scroll_down_;
+ int pixels_scrolled_;
+ int pixels_to_scroll_;
+ int mouse_event_x_;
+ int mouse_event_y_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BASIC_MOUSE_WHEEL_SMOOTH_SCROLL_GESTURE_
diff --git a/chromium/content/browser/renderer_host/clipboard_message_filter.cc b/chromium/content/browser/renderer_host/clipboard_message_filter.cc
new file mode 100644
index 00000000000..2ad35468244
--- /dev/null
+++ b/chromium/content/browser/renderer_host/clipboard_message_filter.cc
@@ -0,0 +1,245 @@
+// 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 "content/browser/renderer_host/clipboard_message_filter.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/stl_util.h"
+#include "content/common/clipboard_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/zlib/zlib.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/size.h"
+#include "url/gurl.h"
+
+namespace content {
+
+#if defined(OS_WIN)
+
+namespace {
+
+// The write must be performed on the UI thread because the clipboard object
+// from the IO thread cannot create windows so it cannot be the "owner" of the
+// clipboard's contents. // See http://crbug.com/5823.
+void WriteObjectsOnUIThread(ui::Clipboard::ObjectMap* objects) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ static ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+ clipboard->WriteObjects(ui::Clipboard::BUFFER_STANDARD, *objects);
+}
+
+} // namespace
+
+#endif
+
+ClipboardMessageFilter::ClipboardMessageFilter() {}
+
+void ClipboardMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+ // Clipboard writes should always occur on the UI thread due the restrictions
+ // of various platform APIs. In general, the clipboard is not thread-safe, so
+ // all clipboard calls should be serviced from the UI thread.
+ //
+ // Windows needs clipboard reads to be serviced from the IO thread because
+ // these are sync IPCs which can result in deadlocks with NPAPI plugins if
+ // serviced from the UI thread. Note that Windows clipboard calls ARE
+ // thread-safe so it is ok for reads and writes to be serviced from different
+ // threads.
+#if !defined(OS_WIN)
+ if (IPC_MESSAGE_CLASS(message) == ClipboardMsgStart)
+ *thread = BrowserThread::UI;
+#endif
+
+#if defined(OS_WIN)
+ if (message.type() == ClipboardHostMsg_ReadImage::ID)
+ *thread = BrowserThread::FILE;
+#endif
+}
+
+bool ClipboardMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ClipboardMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteObjectsAsync, OnWriteObjectsAsync)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteObjectsSync, OnWriteObjectsSync)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_GetSequenceNumber, OnGetSequenceNumber)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_IsFormatAvailable, OnIsFormatAvailable)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_Clear, OnClear)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadAvailableTypes,
+ OnReadAvailableTypes)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadText, OnReadText)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadAsciiText, OnReadAsciiText)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadHTML, OnReadHTML)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadRTF, OnReadRTF)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ClipboardHostMsg_ReadImage, OnReadImage)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadCustomData, OnReadCustomData)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadData, OnReadData)
+#if defined(OS_MACOSX)
+ IPC_MESSAGE_HANDLER(ClipboardHostMsg_FindPboardWriteStringAsync,
+ OnFindPboardWriteString)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+ClipboardMessageFilter::~ClipboardMessageFilter() {
+}
+
+void ClipboardMessageFilter::OnWriteObjectsSync(
+ ui::Clipboard::ObjectMap objects,
+ base::SharedMemoryHandle bitmap_handle) {
+ DCHECK(base::SharedMemory::IsHandleValid(bitmap_handle))
+ << "Bad bitmap handle";
+ // Splice the shared memory handle into the clipboard data.
+ ui::Clipboard::ReplaceSharedMemHandle(&objects, bitmap_handle, PeerHandle());
+#if defined(OS_WIN)
+ // We cannot write directly from the IO thread, and cannot service the IPC
+ // on the UI thread. We'll copy the relevant data and get a handle to any
+ // shared memory so it doesn't go away when we resume the renderer, and post
+ // a task to perform the write on the UI thread.
+ ui::Clipboard::ObjectMap* long_living_objects = new ui::Clipboard::ObjectMap;
+ long_living_objects->swap(objects);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WriteObjectsOnUIThread, base::Owned(long_living_objects)));
+#else
+ GetClipboard()->WriteObjects(ui::Clipboard::BUFFER_STANDARD, objects);
+#endif
+}
+
+void ClipboardMessageFilter::OnWriteObjectsAsync(
+ const ui::Clipboard::ObjectMap& objects) {
+#if defined(OS_WIN)
+ // We cannot write directly from the IO thread, and cannot service the IPC
+ // on the UI thread. We'll copy the relevant data and post a task to preform
+ // the write on the UI thread.
+ ui::Clipboard::ObjectMap* long_living_objects =
+ new ui::Clipboard::ObjectMap(objects);
+
+ // This async message doesn't support shared-memory based bitmaps; they must
+ // be removed otherwise we might dereference a rubbish pointer.
+ long_living_objects->erase(ui::Clipboard::CBF_SMBITMAP);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WriteObjectsOnUIThread, base::Owned(long_living_objects)));
+#else
+ GetClipboard()->WriteObjects(ui::Clipboard::BUFFER_STANDARD, objects);
+#endif
+}
+
+void ClipboardMessageFilter::OnGetSequenceNumber(
+ ui::Clipboard::Buffer buffer, uint64* sequence_number) {
+ *sequence_number = GetClipboard()->GetSequenceNumber(buffer);
+}
+
+void ClipboardMessageFilter::OnReadAvailableTypes(
+ ui::Clipboard::Buffer buffer, std::vector<string16>* types,
+ bool* contains_filenames) {
+ GetClipboard()->ReadAvailableTypes(buffer, types, contains_filenames);
+}
+
+void ClipboardMessageFilter::OnIsFormatAvailable(
+ const ui::Clipboard::FormatType& format, ui::Clipboard::Buffer buffer,
+ bool* result) {
+ *result = GetClipboard()->IsFormatAvailable(format, buffer);
+}
+
+void ClipboardMessageFilter::OnClear(ui::Clipboard::Buffer buffer) {
+ GetClipboard()->Clear(buffer);
+}
+
+void ClipboardMessageFilter::OnReadText(
+ ui::Clipboard::Buffer buffer, string16* result) {
+ GetClipboard()->ReadText(buffer, result);
+}
+
+void ClipboardMessageFilter::OnReadAsciiText(
+ ui::Clipboard::Buffer buffer, std::string* result) {
+ GetClipboard()->ReadAsciiText(buffer, result);
+}
+
+void ClipboardMessageFilter::OnReadHTML(
+ ui::Clipboard::Buffer buffer, string16* markup, GURL* url,
+ uint32* fragment_start, uint32* fragment_end) {
+ std::string src_url_str;
+ GetClipboard()->ReadHTML(buffer, markup, &src_url_str, fragment_start,
+ fragment_end);
+ *url = GURL(src_url_str);
+}
+
+void ClipboardMessageFilter::OnReadRTF(
+ ui::Clipboard::Buffer buffer, std::string* result) {
+ GetClipboard()->ReadRTF(buffer, result);
+}
+
+void ClipboardMessageFilter::OnReadImage(
+ ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) {
+ SkBitmap bitmap = GetClipboard()->ReadImage(buffer);
+
+#if defined(USE_X11)
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(
+ &ClipboardMessageFilter::OnReadImageReply, this, bitmap, reply_msg));
+#else
+ OnReadImageReply(bitmap, reply_msg);
+#endif
+}
+
+void ClipboardMessageFilter::OnReadImageReply(
+ const SkBitmap& bitmap, IPC::Message* reply_msg) {
+ base::SharedMemoryHandle image_handle = base::SharedMemory::NULLHandle();
+ uint32 image_size = 0;
+ if (!bitmap.isNull()) {
+ std::vector<unsigned char> png_data;
+ SkAutoLockPixels lock(bitmap);
+ if (gfx::PNGCodec::EncodeWithCompressionLevel(
+ static_cast<const unsigned char*>(bitmap.getPixels()),
+ gfx::PNGCodec::FORMAT_BGRA,
+ gfx::Size(bitmap.width(), bitmap.height()),
+ bitmap.rowBytes(),
+ false,
+ std::vector<gfx::PNGCodec::Comment>(),
+ Z_BEST_SPEED,
+ &png_data)) {
+ base::SharedMemory buffer;
+ if (buffer.CreateAndMapAnonymous(png_data.size())) {
+ memcpy(buffer.memory(), vector_as_array(&png_data), png_data.size());
+ if (buffer.GiveToProcess(PeerHandle(), &image_handle)) {
+ image_size = png_data.size();
+ }
+ }
+ }
+ }
+ ClipboardHostMsg_ReadImage::WriteReplyParams(reply_msg, image_handle,
+ image_size);
+ Send(reply_msg);
+}
+
+void ClipboardMessageFilter::OnReadCustomData(
+ ui::Clipboard::Buffer buffer, const string16& type, string16* result) {
+ GetClipboard()->ReadCustomData(buffer, type, result);
+}
+
+void ClipboardMessageFilter::OnReadData(const ui::Clipboard::FormatType& format,
+ std::string* data) {
+ GetClipboard()->ReadData(format, data);
+}
+
+// static
+ui::Clipboard* ClipboardMessageFilter::GetClipboard() {
+ // We have a static instance of the clipboard service for use by all message
+ // filters. This instance lives for the life of the browser processes.
+ static ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+ return clipboard;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/clipboard_message_filter.h b/chromium/content/browser/renderer_host/clipboard_message_filter.h
new file mode 100644
index 00000000000..bd3962f6042
--- /dev/null
+++ b/chromium/content/browser/renderer_host/clipboard_message_filter.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_CLIPBOARD_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_CLIPBOARD_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "ui/base/clipboard/clipboard.h"
+
+class GURL;
+
+namespace content {
+
+class ClipboardMessageFilter : public BrowserMessageFilter {
+ public:
+ ClipboardMessageFilter();
+
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ private:
+ virtual ~ClipboardMessageFilter();
+
+ void OnWriteObjectsAsync(const ui::Clipboard::ObjectMap& objects);
+ void OnWriteObjectsSync(ui::Clipboard::ObjectMap objects,
+ base::SharedMemoryHandle bitmap_handle);
+
+ void OnGetSequenceNumber(const ui::Clipboard::Buffer buffer,
+ uint64* sequence_number);
+ void OnIsFormatAvailable(const ui::Clipboard::FormatType& format,
+ ui::Clipboard::Buffer buffer,
+ bool* result);
+ void OnClear(ui::Clipboard::Buffer buffer);
+ void OnReadAvailableTypes(ui::Clipboard::Buffer buffer,
+ std::vector<string16>* types,
+ bool* contains_filenames);
+ void OnReadText(ui::Clipboard::Buffer buffer, string16* result);
+ void OnReadAsciiText(ui::Clipboard::Buffer buffer, std::string* result);
+ void OnReadHTML(ui::Clipboard::Buffer buffer, string16* markup, GURL* url,
+ uint32* fragment_start, uint32* fragment_end);
+ void OnReadRTF(ui::Clipboard::Buffer buffer, std::string* result);
+ void OnReadImage(ui::Clipboard::Buffer buffer, IPC::Message* reply_msg);
+ void OnReadImageReply(const SkBitmap& bitmap, IPC::Message* reply_msg);
+ void OnReadCustomData(ui::Clipboard::Buffer buffer,
+ const string16& type,
+ string16* result);
+ void OnReadData(const ui::Clipboard::FormatType& format,
+ std::string* data);
+
+#if defined(OS_MACOSX)
+ void OnFindPboardWriteString(const string16& text);
+#endif
+
+ // We have our own clipboard because we want to access the clipboard on the
+ // IO thread instead of forwarding (possibly synchronous) messages to the UI
+ // thread. This instance of the clipboard should be accessed only on the IO
+ // thread.
+ static ui::Clipboard* GetClipboard();
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_CLIPBOARD_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/clipboard_message_filter_mac.mm b/chromium/content/browser/renderer_host/clipboard_message_filter_mac.mm
new file mode 100644
index 00000000000..ee8f5a3e428
--- /dev/null
+++ b/chromium/content/browser/renderer_host/clipboard_message_filter_mac.mm
@@ -0,0 +1,56 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/clipboard_message_filter.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#import "ui/base/cocoa/find_pasteboard.h"
+
+namespace content {
+namespace {
+
+// The number of utf16 code units that will be written to the find pasteboard,
+// longer texts are silently ignored. This is to prevent that a compromised
+// renderer can write unlimited amounts of data into the find pasteboard.
+static const size_t kMaxFindPboardStringLength = 4096;
+
+class WriteFindPboardWrapper {
+ public:
+ explicit WriteFindPboardWrapper(NSString* text)
+ : text_([text retain]) {}
+
+ void Run() {
+ [[FindPasteboard sharedInstance] setFindText:text_];
+ }
+
+ private:
+ base::scoped_nsobject<NSString> text_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteFindPboardWrapper);
+};
+
+} // namespace
+
+// Called on the IO thread.
+void ClipboardMessageFilter::OnFindPboardWriteString(const string16& text) {
+ if (text.length() <= kMaxFindPboardStringLength) {
+ NSString* nsText = base::SysUTF16ToNSString(text);
+ if (nsText) {
+ // FindPasteboard must be used on the UI thread.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(
+ &WriteFindPboardWrapper::Run,
+ base::Owned(new WriteFindPboardWrapper(nsText))));
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.h b/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.h
new file mode 100644
index 00000000000..84ffc632e7a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.h
@@ -0,0 +1,71 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_CONTEXT_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_CONTEXT_MAC_H_
+
+#import <AppKit/NSOpenGL.h>
+#include <OpenGL/OpenGL.h>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace content {
+
+class CompositingIOSurfaceShaderPrograms;
+
+class CompositingIOSurfaceContext
+ : public base::RefCounted<CompositingIOSurfaceContext> {
+ public:
+ // Get or create a GL context for the specified window with the specified
+ // surface ordering. Share these GL contexts as much as possible because
+ // creating and destroying them can be expensive
+ // http://crbug.com/180463
+ static scoped_refptr<CompositingIOSurfaceContext> Get(int window_number);
+
+ // Mark that all the currently existing GL contexts shouldn't be returned
+ // anymore by Get, but rather, new contexts should be created. This is
+ // called as a precaution when unexpected GL errors occur.
+ static void MarkExistingContextsAsNotShareable();
+
+ CompositingIOSurfaceShaderPrograms* shader_program_cache() const {
+ return shader_program_cache_.get();
+ }
+ NSOpenGLContext* nsgl_context() const { return nsgl_context_; }
+ CGLContextObj cgl_context() const { return cgl_context_; }
+ bool is_vsync_disabled() const { return is_vsync_disabled_; }
+ int window_number() const { return window_number_; }
+
+ private:
+ friend class base::RefCounted<CompositingIOSurfaceContext>;
+
+ CompositingIOSurfaceContext(
+ int window_number,
+ NSOpenGLContext* nsgl_context,
+ CGLContextObj clg_context,
+ bool is_vsync_disabled_,
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache);
+ ~CompositingIOSurfaceContext();
+
+ int window_number_;
+ base::scoped_nsobject<NSOpenGLContext> nsgl_context_;
+ CGLContextObj cgl_context_; // weak, backed by |nsgl_context_|
+ bool is_vsync_disabled_;
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache_;
+ bool can_be_shared_;
+
+ // The global map from window number and window ordering to
+ // context data.
+ typedef std::map<int, CompositingIOSurfaceContext*> WindowMap;
+ static base::LazyInstance<WindowMap> window_map_;
+ static WindowMap* window_map();
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_CONTEXT_MAC_H_
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.mm b/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.mm
new file mode 100644
index 00000000000..6394fe370cf
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_context_mac.mm
@@ -0,0 +1,146 @@
+// Copyright (c) 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 "content/browser/renderer_host/compositing_iosurface_context_mac.h"
+
+#include <OpenGL/gl.h>
+#include <OpenGL/OpenGL.h>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "ui/gl/gl_switches.h"
+#include "ui/gl/gpu_switching_manager.h"
+
+namespace content {
+
+// static
+scoped_refptr<CompositingIOSurfaceContext>
+CompositingIOSurfaceContext::Get(int window_number) {
+ TRACE_EVENT0("browser", "CompositingIOSurfaceContext::Get");
+
+ // Return the context for this window_number, if it exists.
+ WindowMap::iterator found = window_map()->find(window_number);
+ if (found != window_map()->end()) {
+ DCHECK(found->second->can_be_shared_);
+ return found->second;
+ }
+
+ std::vector<NSOpenGLPixelFormatAttribute> attributes;
+ attributes.push_back(NSOpenGLPFADoubleBuffer);
+ // We don't need a depth buffer - try setting its size to 0...
+ attributes.push_back(NSOpenGLPFADepthSize); attributes.push_back(0);
+ if (ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus())
+ attributes.push_back(NSOpenGLPFAAllowOfflineRenderers);
+ attributes.push_back(0);
+
+ base::scoped_nsobject<NSOpenGLPixelFormat> glPixelFormat(
+ [[NSOpenGLPixelFormat alloc] initWithAttributes:&attributes.front()]);
+ if (!glPixelFormat) {
+ LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed";
+ return NULL;
+ }
+
+ // Create all contexts in the same share group so that the textures don't
+ // need to be recreated when transitioning contexts.
+ NSOpenGLContext* share_context = nil;
+ if (!window_map()->empty())
+ share_context = window_map()->begin()->second->nsgl_context();
+ base::scoped_nsobject<NSOpenGLContext> nsgl_context(
+ [[NSOpenGLContext alloc] initWithFormat:glPixelFormat
+ shareContext:share_context]);
+ if (!nsgl_context) {
+ LOG(ERROR) << "NSOpenGLContext initWithFormat failed";
+ return NULL;
+ }
+
+ CGLContextObj cgl_context = (CGLContextObj)[nsgl_context CGLContextObj];
+ if (!cgl_context) {
+ LOG(ERROR) << "CGLContextObj failed";
+ return NULL;
+ }
+
+ // Draw at beam vsync.
+ bool is_vsync_disabled =
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync);
+ GLint swapInterval = is_vsync_disabled ? 0 : 1;
+ [nsgl_context setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+
+ // Prepare the shader program cache. Precompile only the shader programs
+ // needed to draw the IO Surface.
+ CGLSetCurrentContext(cgl_context);
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache(
+ new CompositingIOSurfaceShaderPrograms());
+ const bool prepared = (
+ shader_program_cache->UseBlitProgram() &&
+ shader_program_cache->UseSolidWhiteProgram());
+ glUseProgram(0u);
+ CGLSetCurrentContext(0);
+ if (!prepared) {
+ LOG(ERROR) << "IOSurface failed to compile/link required shader programs.";
+ return NULL;
+ }
+
+ return new CompositingIOSurfaceContext(
+ window_number,
+ nsgl_context.release(),
+ cgl_context,
+ is_vsync_disabled,
+ shader_program_cache.Pass());
+}
+
+// static
+void CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable() {
+ for (WindowMap::iterator it = window_map()->begin();
+ it != window_map()->end();
+ ++it) {
+ it->second->can_be_shared_ = false;
+ }
+ window_map()->clear();
+}
+
+CompositingIOSurfaceContext::CompositingIOSurfaceContext(
+ int window_number,
+ NSOpenGLContext* nsgl_context,
+ CGLContextObj cgl_context,
+ bool is_vsync_disabled,
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache)
+ : window_number_(window_number),
+ nsgl_context_(nsgl_context),
+ cgl_context_(cgl_context),
+ is_vsync_disabled_(is_vsync_disabled),
+ shader_program_cache_(shader_program_cache.Pass()),
+ can_be_shared_(true) {
+ DCHECK(window_map()->find(window_number_) == window_map()->end());
+ window_map()->insert(std::make_pair(window_number_, this));
+}
+
+CompositingIOSurfaceContext::~CompositingIOSurfaceContext() {
+ CGLSetCurrentContext(cgl_context_);
+ shader_program_cache_->Reset();
+ CGLSetCurrentContext(0);
+ if (can_be_shared_) {
+ DCHECK(window_map()->find(window_number_) != window_map()->end());
+ DCHECK(window_map()->find(window_number_)->second == this);
+ window_map()->erase(window_number_);
+ } else {
+ WindowMap::const_iterator found = window_map()->find(window_number_);
+ if (found != window_map()->end())
+ DCHECK(found->second != this);
+ }
+}
+
+// static
+CompositingIOSurfaceContext::WindowMap*
+ CompositingIOSurfaceContext::window_map() {
+ return window_map_.Pointer();
+}
+
+// static
+base::LazyInstance<CompositingIOSurfaceContext::WindowMap>
+ CompositingIOSurfaceContext::window_map_;
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.h b/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.h
new file mode 100644
index 00000000000..a03ab1cb5df
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.h
@@ -0,0 +1,40 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_LAYER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_LAYER_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/ref_counted.h"
+
+namespace content {
+class CompositingIOSurfaceContext;
+class RenderWidgetHostViewMac;
+}
+
+// The CoreAnimation layer for drawing accelerated content.
+@interface CompositingIOSurfaceLayer : CAOpenGLLayer {
+ @private
+ content::RenderWidgetHostViewMac* renderWidgetHostView_;
+ scoped_refptr<content::CompositingIOSurfaceContext> context_;
+}
+
+@property(nonatomic, readonly)
+ scoped_refptr<content::CompositingIOSurfaceContext> context;
+
+- (id)initWithRenderWidgetHostViewMac:(content::RenderWidgetHostViewMac*)r;
+
+// Update the scale factor of the layer to match the scale factor of the
+// IOSurface.
+- (void)updateScaleFactor;
+
+// Remove this layer from the layer heirarchy, and mark that
+// |renderWidgetHostView_| is no longer valid and may no longer be dereferenced.
+- (void)disableCompositing;
+
+@end
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_LAYER_MAC_H_
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.mm b/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.mm
new file mode 100644
index 00000000000..04ae21c300b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_layer_mac.mm
@@ -0,0 +1,133 @@
+// 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 "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <OpenGL/gl.h>
+
+#include "base/mac/sdk_forward_declarations.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_mac.h"
+#include "ui/base/cocoa/animation_utils.h"
+#include "ui/gfx/size_conversions.h"
+
+@implementation CompositingIOSurfaceLayer
+
+@synthesize context = context_;
+
+- (id)initWithRenderWidgetHostViewMac:(content::RenderWidgetHostViewMac*)r {
+ if (self = [super init]) {
+ renderWidgetHostView_ = r;
+
+ ScopedCAActionDisabler disabler;
+ [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
+ [self setContentsGravity:kCAGravityTopLeft];
+ [self setFrame:NSRectToCGRect(
+ [renderWidgetHostView_->cocoa_view() bounds])];
+ [self setNeedsDisplay];
+ [self updateScaleFactor];
+ }
+ return self;
+}
+
+- (void)updateScaleFactor {
+ if (!renderWidgetHostView_ ||
+ ![self respondsToSelector:(@selector(contentsScale))] ||
+ ![self respondsToSelector:(@selector(setContentsScale:))])
+ return;
+
+ float current_scale_factor = [self contentsScale];
+ float new_scale_factor = current_scale_factor;
+ if (renderWidgetHostView_->compositing_iosurface_) {
+ new_scale_factor =
+ renderWidgetHostView_->compositing_iosurface_->scale_factor();
+ }
+
+ if (new_scale_factor == current_scale_factor)
+ return;
+
+ ScopedCAActionDisabler disabler;
+ [self setContentsScale:new_scale_factor];
+}
+
+- (void)disableCompositing{
+ ScopedCAActionDisabler disabler;
+ [self removeFromSuperlayer];
+ renderWidgetHostView_ = nil;
+}
+
+// The remaining methods implement the CAOpenGLLayer interface.
+
+- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
+ if (!renderWidgetHostView_)
+ return nil;
+
+ context_ = renderWidgetHostView_->compositing_iosurface_context_;
+ if (!context_)
+ return nil;
+
+ return context_->cgl_context();
+}
+
+- (void)releaseCGLContext:(CGLContextObj)glContext {
+ if (!context_.get())
+ return;
+
+ DCHECK(glContext == context_->cgl_context());
+ context_ = nil;
+}
+
+- (void)drawInCGLContext:(CGLContextObj)glContext
+ pixelFormat:(CGLPixelFormatObj)pixelFormat
+ forLayerTime:(CFTimeInterval)timeInterval
+ displayTime:(const CVTimeStamp*)timeStamp {
+ TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawInCGLContext");
+
+ if (!context_.get() || !renderWidgetHostView_ ||
+ !renderWidgetHostView_->compositing_iosurface_) {
+ glClearColor(1, 1, 1, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ return;
+ }
+
+ DCHECK(glContext == context_->cgl_context());
+
+ // Cache a copy of renderWidgetHostView_ because it may be reset if
+ // a software frame is received in GetBackingStore.
+ content::RenderWidgetHostViewMac* cached_view = renderWidgetHostView_;
+
+ // If a resize is in progress then GetBackingStore request a frame of the
+ // current window size and block until a frame of the right size comes in.
+ // This makes the window content not lag behind the resize (at the cost of
+ // blocking on the browser's main thread).
+ if (cached_view->render_widget_host_) {
+ cached_view->about_to_validate_and_paint_ = true;
+ (void)cached_view->render_widget_host_->GetBackingStore(true);
+ cached_view->about_to_validate_and_paint_ = false;
+ }
+
+ // If a transition to software mode has occurred, this layer should be
+ // removed from the heirarchy now, so don't draw anything.
+ if (!renderWidgetHostView_)
+ return;
+
+ gfx::Size window_size([self frame].size);
+ float window_scale_factor = 1.f;
+ if ([self respondsToSelector:(@selector(contentsScale))])
+ window_scale_factor = [self contentsScale];
+
+ CGLSetCurrentContext(glContext);
+ if (!renderWidgetHostView_->compositing_iosurface_->DrawIOSurface(
+ window_size,
+ window_scale_factor,
+ renderWidgetHostView_->frame_subscriber(),
+ true)) {
+ renderWidgetHostView_->GotAcceleratedCompositingError();
+ }
+}
+
+@end
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_mac.h b/chromium/content/browser/renderer_host/compositing_iosurface_mac.h
new file mode 100644
index 00000000000..674e4e512d6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_mac.h
@@ -0,0 +1,365 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
+
+#include <deque>
+#include <vector>
+
+#import <Cocoa/Cocoa.h>
+#import <QuartzCore/CVDisplayLink.h>
+#include <QuartzCore/QuartzCore.h>
+
+#include "base/callback.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "media/base/video_frame.h"
+#include "ui/base/latency_info.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size.h"
+
+class IOSurfaceSupport;
+class SkBitmap;
+
+namespace gfx {
+class Rect;
+}
+
+namespace content {
+
+class CompositingIOSurfaceContext;
+class CompositingIOSurfaceShaderPrograms;
+class CompositingIOSurfaceTransformer;
+class RenderWidgetHostViewFrameSubscriber;
+class RenderWidgetHostViewMac;
+
+// This class manages an OpenGL context and IOSurface for the accelerated
+// compositing code path. The GL context is attached to
+// RenderWidgetHostViewCocoa for blitting the IOSurface.
+class CompositingIOSurfaceMac {
+ public:
+ // Returns NULL if IOSurface support is missing or GL APIs fail. Specify in
+ // |order| the desired ordering relationship of the surface to the containing
+ // window.
+ static CompositingIOSurfaceMac* Create(
+ const scoped_refptr<CompositingIOSurfaceContext>& context);
+ ~CompositingIOSurfaceMac();
+
+ // Set IOSurface that will be drawn on the next NSView drawRect.
+ bool SetIOSurface(uint64 io_surface_handle,
+ const gfx::Size& size,
+ float scale_factor,
+ const ui::LatencyInfo& latency_info);
+
+ // Get the CGL renderer ID currently associated with this context.
+ int GetRendererID();
+
+ // Blit the IOSurface at the upper-left corner of the of the specified
+ // window_size. If the window size is larger than the IOSurface, the
+ // remaining right and bottom edges will be white. |scaleFactor| is 1
+ // in normal views, 2 in HiDPI views. |frame_subscriber| listens to
+ // this draw event and provides output buffer for copying this frame into.
+ bool DrawIOSurface(const gfx::Size& window_size,
+ float window_scale_factor,
+ RenderWidgetHostViewFrameSubscriber* frame_subscriber,
+ bool using_core_animation);
+
+ // Copy the data of the "live" OpenGL texture referring to this IOSurfaceRef
+ // into |out|. The copied region is specified with |src_pixel_subrect| and
+ // the data is transformed so that it fits in |dst_pixel_size|.
+ // |src_pixel_subrect| and |dst_pixel_size| are not in DIP but in pixel.
+ // Caller must ensure that |out| is allocated to dimensions that match
+ // dst_pixel_size, with no additional padding.
+ // |callback| is invoked when the operation is completed or failed.
+ // Do no call this method again before |callback| is invoked.
+ void CopyTo(const gfx::Rect& src_pixel_subrect,
+ const gfx::Size& dst_pixel_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback);
+
+ // Transfer the contents of the surface to an already-allocated YV12
+ // VideoFrame, and invoke a callback to indicate success or failure.
+ void CopyToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback);
+
+ // Unref the IOSurface and delete the associated GL texture. If the GPU
+ // process is no longer referencing it, this will delete the IOSurface.
+ void UnrefIOSurface();
+
+ bool HasIOSurface() { return !!io_surface_.get(); }
+
+ const gfx::Size& pixel_io_surface_size() const {
+ return pixel_io_surface_size_;
+ }
+ // In cocoa view units / DIPs.
+ const gfx::Size& dip_io_surface_size() const { return dip_io_surface_size_; }
+ float scale_factor() const { return scale_factor_; }
+
+ bool is_vsync_disabled() const;
+
+ void SetContext(
+ const scoped_refptr<CompositingIOSurfaceContext>& new_context);
+
+ const scoped_refptr<CompositingIOSurfaceContext>& context() {
+ return context_;
+ }
+
+ // Get vsync scheduling parameters.
+ // |interval_numerator/interval_denominator| equates to fractional number of
+ // seconds between vsyncs.
+ void GetVSyncParameters(base::TimeTicks* timebase,
+ uint32* interval_numerator,
+ uint32* interval_denominator);
+
+ // Returns true if asynchronous readback is supported on this system.
+ bool IsAsynchronousReadbackSupported();
+
+ private:
+ friend CVReturn DisplayLinkCallback(CVDisplayLinkRef,
+ const CVTimeStamp*,
+ const CVTimeStamp*,
+ CVOptionFlags,
+ CVOptionFlags*,
+ void*);
+
+ // Vertex structure for use in glDraw calls.
+ struct SurfaceVertex {
+ SurfaceVertex() : x_(0.0f), y_(0.0f), tx_(0.0f), ty_(0.0f) { }
+ void set(float x, float y, float tx, float ty) {
+ x_ = x;
+ y_ = y;
+ tx_ = tx;
+ ty_ = ty;
+ }
+ void set_position(float x, float y) {
+ x_ = x;
+ y_ = y;
+ }
+ void set_texcoord(float tx, float ty) {
+ tx_ = tx;
+ ty_ = ty;
+ }
+ float x_;
+ float y_;
+ float tx_;
+ float ty_;
+ };
+
+ // Counter-clockwise verts starting from upper-left corner (0, 0).
+ struct SurfaceQuad {
+ void set_size(gfx::Size vertex_size, gfx::Size texcoord_size) {
+ // Texture coordinates are flipped vertically so they can be drawn on
+ // a projection with a flipped y-axis (origin is top left).
+ float vw = static_cast<float>(vertex_size.width());
+ float vh = static_cast<float>(vertex_size.height());
+ float tw = static_cast<float>(texcoord_size.width());
+ float th = static_cast<float>(texcoord_size.height());
+ verts_[0].set(0.0f, 0.0f, 0.0f, th);
+ verts_[1].set(0.0f, vh, 0.0f, 0.0f);
+ verts_[2].set(vw, vh, tw, 0.0f);
+ verts_[3].set(vw, 0.0f, tw, th);
+ }
+ void set_rect(float x1, float y1, float x2, float y2) {
+ verts_[0].set_position(x1, y1);
+ verts_[1].set_position(x1, y2);
+ verts_[2].set_position(x2, y2);
+ verts_[3].set_position(x2, y1);
+ }
+ void set_texcoord_rect(float tx1, float ty1, float tx2, float ty2) {
+ // Texture coordinates are flipped vertically so they can be drawn on
+ // a projection with a flipped y-axis (origin is top left).
+ verts_[0].set_texcoord(tx1, ty2);
+ verts_[1].set_texcoord(tx1, ty1);
+ verts_[2].set_texcoord(tx2, ty1);
+ verts_[3].set_texcoord(tx2, ty2);
+ }
+ SurfaceVertex verts_[4];
+ };
+
+ // Keeps track of states and buffers for readback of IOSurface.
+ //
+ // TODO(miu): Major code refactoring is badly needed! To be done in a
+ // soon-upcoming change. For now, we blatantly violate the style guide with
+ // respect to struct vs. class usage:
+ struct CopyContext {
+ explicit CopyContext(const scoped_refptr<CompositingIOSurfaceContext>& ctx);
+ ~CopyContext();
+
+ // Delete any references to owned OpenGL objects. This must be called
+ // within the OpenGL context just before destruction.
+ void ReleaseCachedGLObjects();
+
+ // The following two methods assume |num_outputs| has been set, and are
+ // being called within the OpenGL context.
+ void PrepareReadbackFramebuffers();
+ void PrepareForAsynchronousReadback();
+
+ const scoped_ptr<CompositingIOSurfaceTransformer> transformer;
+ GLenum output_readback_format;
+ int num_outputs;
+ GLuint output_textures[3]; // Not owned.
+ // Note: For YUV, the |output_texture_sizes| widths are in terms of 4-byte
+ // quads, not pixels.
+ gfx::Size output_texture_sizes[3];
+ GLuint frame_buffers[3];
+ GLuint pixel_buffers[3];
+ GLuint fence; // When non-zero, doing an asynchronous copy.
+ int cycles_elapsed;
+ base::Callback<bool(const void*, int)> map_buffer_callback;
+ base::Callback<void(bool)> done_callback;
+ };
+
+ CompositingIOSurfaceMac(
+ IOSurfaceSupport* io_surface_support,
+ const scoped_refptr<CompositingIOSurfaceContext>& context);
+
+ void SetupCVDisplayLink();
+
+ // If this IOSurface has moved to a different window, use that window's
+ // GL context (if multiple visible windows are using the same GL context
+ // then call to setView call can stall and prevent reaching 60fps).
+ void SwitchToContextOnNewWindow(NSView* view,
+ int window_number);
+
+ bool IsVendorIntel();
+
+ // Returns true if IOSurface is ready to render. False otherwise.
+ bool MapIOSurfaceToTexture(uint64 io_surface_handle);
+
+ void UnrefIOSurfaceWithContextCurrent();
+
+ void DrawQuad(const SurfaceQuad& quad);
+
+ // Called on display-link thread.
+ void DisplayLinkTick(CVDisplayLinkRef display_link,
+ const CVTimeStamp* time);
+
+ void CalculateVsyncParametersLockHeld(const CVTimeStamp* time);
+
+ // Prevent from spinning on CGLFlushDrawable when it fails to throttle to
+ // VSync frequency.
+ void RateLimitDraws();
+
+ void StartOrContinueDisplayLink();
+ void StopDisplayLink();
+
+ // Copy current frame to |target| video frame. This method must be called
+ // within a CGL context. Returns a callback that should be called outside
+ // of the CGL context.
+ // If |called_within_draw| is true this method is called within a drawing
+ // operations. This allow certain optimizations.
+ base::Closure CopyToVideoFrameWithinContext(
+ const gfx::Rect& src_subrect,
+ bool called_within_draw,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback);
+
+ // Common GPU-readback copy path. Only one of |bitmap_output| or
+ // |video_frame_output| may be specified: Either ARGB is written to
+ // |bitmap_output| or letter-boxed YV12 is written to |video_frame_output|.
+ base::Closure CopyToSelectedOutputWithinContext(
+ const gfx::Rect& src_pixel_subrect,
+ const gfx::Rect& dst_pixel_rect,
+ bool called_within_draw,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output,
+ const base::Callback<void(bool)>& done_callback);
+
+ // TODO(hclam): These two methods should be static.
+ void AsynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ bool called_within_draw,
+ CopyContext* copy_context,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output);
+ bool SynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ CopyContext* copy_context,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output);
+
+ // Scan the list of started asynchronous copies and test if each one has
+ // completed. If |block_until_finished| is true, then block until all
+ // pending copies are finished.
+ void CheckIfAllCopiesAreFinished(bool block_until_finished);
+ void CheckIfAllCopiesAreFinishedWithinContext(
+ bool block_until_finished,
+ std::vector<base::Closure>* done_callbacks);
+
+ void FailAllCopies();
+ void DestroyAllCopyContextsWithinContext();
+
+ // Check for GL errors and store the result in error_. Only return new
+ // errors
+ GLenum GetAndSaveGLError();
+
+ gfx::Rect IntersectWithIOSurface(const gfx::Rect& rect) const;
+
+ // Cached pointer to IOSurfaceSupport Singleton.
+ IOSurfaceSupport* io_surface_support_;
+
+ // GL context, and parameters for context sharing. This may change when
+ // moving between windows, but will never be NULL.
+ scoped_refptr<CompositingIOSurfaceContext> context_;
+
+ // IOSurface data.
+ uint64 io_surface_handle_;
+ base::ScopedCFTypeRef<CFTypeRef> io_surface_;
+
+ // The width and height of the io surface.
+ gfx::Size pixel_io_surface_size_; // In pixels.
+ gfx::Size dip_io_surface_size_; // In view / density independent pixels.
+ float scale_factor_;
+
+ // The "live" OpenGL texture referring to this IOSurfaceRef. Note
+ // that per the CGLTexImageIOSurface2D API we do not need to
+ // explicitly update this texture's contents once created. All we
+ // need to do is ensure it is re-bound before attempting to draw
+ // with it.
+ GLuint texture_;
+
+ // A pool of CopyContexts with OpenGL objects ready for re-use. Prefer to
+ // pull one from the pool before creating a new CopyContext.
+ std::vector<CopyContext*> copy_context_pool_;
+
+ // CopyContexts being used for in-flight copy operations.
+ std::deque<CopyContext*> copy_requests_;
+
+ // Timer for finishing a copy operation.
+ base::Timer finish_copy_timer_;
+
+ // CVDisplayLink for querying Vsync timing info and throttling swaps.
+ CVDisplayLinkRef display_link_;
+
+ // Timer for stopping display link after a timeout with no swaps.
+ base::DelayTimer<CompositingIOSurfaceMac> display_link_stop_timer_;
+
+ // Lock for sharing data between UI thread and display-link thread.
+ base::Lock lock_;
+
+ // Vsync timing data.
+ base::TimeTicks vsync_timebase_;
+ uint32 vsync_interval_numerator_;
+ uint32 vsync_interval_denominator_;
+
+ bool initialized_is_intel_;
+ bool is_intel_;
+ GLint screen_;
+
+ // Error saved by GetAndSaveGLError
+ GLint gl_error_;
+
+ ui::LatencyInfo latency_info_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_mac.mm b/chromium/content/browser/renderer_host/compositing_iosurface_mac.mm
new file mode 100644
index 00000000000..a9ca7079919
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_mac.mm
@@ -0,0 +1,1066 @@
+// 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 "content/browser/renderer_host/compositing_iosurface_mac.h"
+
+#include <OpenGL/CGLRenderers.h>
+#include <OpenGL/OpenGL.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/common/content_constants_internal.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "media/base/video_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/io_surface_support_mac.h"
+
+#ifdef NDEBUG
+#define CHECK_GL_ERROR()
+#define CHECK_AND_SAVE_GL_ERROR()
+#else
+#define CHECK_GL_ERROR() do { \
+ GLenum gl_error = glGetError(); \
+ LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error :" << gl_error; \
+ } while (0)
+#define CHECK_AND_SAVE_GL_ERROR() do { \
+ GLenum gl_error = GetAndSaveGLError(); \
+ LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error :" << gl_error; \
+ } while (0)
+#endif
+
+namespace content {
+namespace {
+
+// How many times to test if asynchronous copy has completed.
+// This value is chosen such that we allow at most 1 second to finish a copy.
+const int kFinishCopyRetryCycles = 100;
+
+// Time in milliseconds to allow asynchronous copy to finish.
+// This value is shorter than 16ms such that copy can complete within a vsync.
+const int kFinishCopyPollingPeriodMs = 10;
+
+bool HasAppleFenceExtension() {
+ static bool initialized_has_fence = false;
+ static bool has_fence = false;
+
+ if (!initialized_has_fence) {
+ has_fence =
+ strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
+ "GL_APPLE_fence") != NULL;
+ initialized_has_fence = true;
+ }
+ return has_fence;
+}
+
+bool HasPixelBufferObjectExtension() {
+ static bool initialized_has_pbo = false;
+ static bool has_pbo = false;
+
+ if (!initialized_has_pbo) {
+ has_pbo =
+ strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
+ "GL_ARB_pixel_buffer_object") != NULL;
+ initialized_has_pbo = true;
+ }
+ return has_pbo;
+}
+
+// Helper function to reverse the argument order. Also takes ownership of
+// |bitmap_output| for the life of the binding.
+void ReverseArgumentOrder(
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<SkBitmap> bitmap_output, bool success) {
+ callback.Run(success, *bitmap_output);
+}
+
+// Called during an async GPU readback with a pointer to the pixel buffer. In
+// the snapshot path, we just memcpy the data into our output bitmap since the
+// width, height, and stride should all be equal.
+bool MapBufferToSkBitmap(const SkBitmap* output, const void* buf, int ignored) {
+ TRACE_EVENT0("browser", "MapBufferToSkBitmap");
+
+ if (buf) {
+ SkAutoLockPixels output_lock(*output);
+ memcpy(output->getPixels(), buf, output->getSize());
+ }
+ return buf != NULL;
+}
+
+// Copies tightly-packed scanlines from |buf| to |region_in_frame| in the given
+// |target| VideoFrame's |plane|. Assumption: |buf|'s width is
+// |region_in_frame.width()| and its stride is always in 4-byte alignment.
+//
+// TODO(miu): Refactor by moving this function into media/video_util.
+// http://crbug.com/219779
+bool MapBufferToVideoFrame(
+ const scoped_refptr<media::VideoFrame>& target,
+ const gfx::Rect& region_in_frame,
+ const void* buf,
+ int plane) {
+ COMPILE_ASSERT(media::VideoFrame::kYPlane == 0, VideoFrame_kYPlane_mismatch);
+ COMPILE_ASSERT(media::VideoFrame::kUPlane == 1, VideoFrame_kUPlane_mismatch);
+ COMPILE_ASSERT(media::VideoFrame::kVPlane == 2, VideoFrame_kVPlane_mismatch);
+
+ TRACE_EVENT1("browser", "MapBufferToVideoFrame", "plane", plane);
+
+ // Apply black-out in the regions surrounding the view area (for
+ // letterboxing/pillarboxing). Only do this once, since this is performed on
+ // all planes in the VideoFrame here.
+ if (plane == 0)
+ media::LetterboxYUV(target.get(), region_in_frame);
+
+ if (buf) {
+ int packed_width = region_in_frame.width();
+ int packed_height = region_in_frame.height();
+ // For planes 1 and 2, the width and height are 1/2 size (rounded up).
+ if (plane > 0) {
+ packed_width = (packed_width + 1) / 2;
+ packed_height = (packed_height + 1) / 2;
+ }
+ const uint8* src = reinterpret_cast<const uint8*>(buf);
+ const int src_stride = (packed_width % 4 == 0 ?
+ packed_width :
+ (packed_width + 4 - (packed_width % 4)));
+ const uint8* const src_end = src + packed_height * src_stride;
+
+ // Calculate starting offset and stride into the destination buffer.
+ const int dst_stride = target->stride(plane);
+ uint8* dst = target->data(plane);
+ if (plane == 0)
+ dst += (region_in_frame.y() * dst_stride) + region_in_frame.x();
+ else
+ dst += (region_in_frame.y() / 2 * dst_stride) + (region_in_frame.x() / 2);
+
+ // Copy each row, accounting for strides in the source and destination.
+ for (; src < src_end; src += src_stride, dst += dst_stride)
+ memcpy(dst, src, packed_width);
+ }
+ return buf != NULL;
+}
+
+} // namespace
+
+CVReturn DisplayLinkCallback(CVDisplayLinkRef display_link,
+ const CVTimeStamp* now,
+ const CVTimeStamp* output_time,
+ CVOptionFlags flags_in,
+ CVOptionFlags* flags_out,
+ void* context) {
+ CompositingIOSurfaceMac* surface =
+ static_cast<CompositingIOSurfaceMac*>(context);
+ surface->DisplayLinkTick(display_link, output_time);
+ return kCVReturnSuccess;
+}
+
+CompositingIOSurfaceMac::CopyContext::CopyContext(
+ const scoped_refptr<CompositingIOSurfaceContext>& context)
+ : transformer(new CompositingIOSurfaceTransformer(
+ GL_TEXTURE_RECTANGLE_ARB, true, context->shader_program_cache())),
+ output_readback_format(GL_BGRA),
+ num_outputs(0),
+ fence(0),
+ cycles_elapsed(0) {
+ memset(output_textures, 0, sizeof(output_textures));
+ memset(frame_buffers, 0, sizeof(frame_buffers));
+ memset(pixel_buffers, 0, sizeof(pixel_buffers));
+}
+
+CompositingIOSurfaceMac::CopyContext::~CopyContext() {
+ DCHECK_EQ(frame_buffers[0], 0u) << "Failed to call ReleaseCachedGLObjects().";
+}
+
+void CompositingIOSurfaceMac::CopyContext::ReleaseCachedGLObjects() {
+ // No outstanding callbacks should be pending.
+ DCHECK(map_buffer_callback.is_null());
+ DCHECK(done_callback.is_null());
+
+ // For an asynchronous read-back, there are more objects to delete:
+ if (fence) {
+ glDeleteBuffers(arraysize(pixel_buffers), pixel_buffers); CHECK_GL_ERROR();
+ memset(pixel_buffers, 0, sizeof(pixel_buffers));
+ glDeleteFencesAPPLE(1, &fence); CHECK_GL_ERROR();
+ fence = 0;
+ }
+
+ glDeleteFramebuffersEXT(arraysize(frame_buffers), frame_buffers);
+ CHECK_GL_ERROR();
+ memset(frame_buffers, 0, sizeof(frame_buffers));
+
+ // Note: |output_textures| are owned by the transformer.
+ if (transformer)
+ transformer->ReleaseCachedGLObjects();
+}
+
+void CompositingIOSurfaceMac::CopyContext::PrepareReadbackFramebuffers() {
+ for (int i = 0; i < num_outputs; ++i) {
+ if (!frame_buffers[i]) {
+ glGenFramebuffersEXT(1, &frame_buffers[i]); CHECK_GL_ERROR();
+ }
+ }
+}
+
+void CompositingIOSurfaceMac::CopyContext::PrepareForAsynchronousReadback() {
+ PrepareReadbackFramebuffers();
+ if (!fence) {
+ glGenFencesAPPLE(1, &fence); CHECK_GL_ERROR();
+ }
+ for (int i = 0; i < num_outputs; ++i) {
+ if (!pixel_buffers[i]) {
+ glGenBuffersARB(1, &pixel_buffers[i]); CHECK_GL_ERROR();
+ }
+ }
+}
+
+
+// static
+CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create(
+ const scoped_refptr<CompositingIOSurfaceContext>& context) {
+ IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
+ if (!io_surface_support) {
+ LOG(ERROR) << "No IOSurface support";
+ return NULL;
+ }
+
+ return new CompositingIOSurfaceMac(io_surface_support,
+ context);
+}
+
+CompositingIOSurfaceMac::CompositingIOSurfaceMac(
+ IOSurfaceSupport* io_surface_support,
+ const scoped_refptr<CompositingIOSurfaceContext>& context)
+ : io_surface_support_(io_surface_support),
+ context_(context),
+ io_surface_handle_(0),
+ scale_factor_(1.f),
+ texture_(0),
+ finish_copy_timer_(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kFinishCopyPollingPeriodMs),
+ base::Bind(&CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished,
+ base::Unretained(this),
+ false),
+ true),
+ display_link_(0),
+ display_link_stop_timer_(FROM_HERE, base::TimeDelta::FromSeconds(1),
+ this, &CompositingIOSurfaceMac::StopDisplayLink),
+ vsync_interval_numerator_(0),
+ vsync_interval_denominator_(0),
+ initialized_is_intel_(false),
+ is_intel_(false),
+ screen_(0),
+ gl_error_(GL_NO_ERROR) {
+ CHECK(context_);
+}
+
+void CompositingIOSurfaceMac::SetupCVDisplayLink() {
+ if (display_link_) {
+ LOG(ERROR) << "DisplayLink already setup";
+ return;
+ }
+
+ CVDisplayLinkRef display_link;
+ CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&display_link);
+ if (ret != kCVReturnSuccess) {
+ LOG(WARNING) << "CVDisplayLinkCreateWithActiveCGDisplays failed: " << ret;
+ return;
+ }
+
+ display_link_ = display_link;
+
+ ret = CVDisplayLinkSetOutputCallback(display_link_,
+ &DisplayLinkCallback, this);
+ DCHECK(ret == kCVReturnSuccess)
+ << "CVDisplayLinkSetOutputCallback failed: " << ret;
+
+ StartOrContinueDisplayLink();
+
+ CVTimeStamp cv_time;
+ ret = CVDisplayLinkGetCurrentTime(display_link_, &cv_time);
+ DCHECK(ret == kCVReturnSuccess)
+ << "CVDisplayLinkGetCurrentTime failed: " << ret;
+
+ {
+ base::AutoLock lock(lock_);
+ CalculateVsyncParametersLockHeld(&cv_time);
+ }
+
+ // Stop display link for now, it will be started when needed during Draw.
+ StopDisplayLink();
+}
+
+void CompositingIOSurfaceMac::SetContext(
+ const scoped_refptr<CompositingIOSurfaceContext>& new_context) {
+ CHECK(new_context);
+
+ if (context_ == new_context)
+ return;
+
+ // Asynchronous copies must complete in the same context they started in.
+ CheckIfAllCopiesAreFinished(true);
+ CGLSetCurrentContext(context_->cgl_context());
+ DestroyAllCopyContextsWithinContext();
+ CGLSetCurrentContext(0);
+
+ context_ = new_context;
+}
+
+bool CompositingIOSurfaceMac::is_vsync_disabled() const {
+ return context_->is_vsync_disabled();
+}
+
+void CompositingIOSurfaceMac::GetVSyncParameters(base::TimeTicks* timebase,
+ uint32* interval_numerator,
+ uint32* interval_denominator) {
+ base::AutoLock lock(lock_);
+ *timebase = vsync_timebase_;
+ *interval_numerator = vsync_interval_numerator_;
+ *interval_denominator = vsync_interval_denominator_;
+}
+
+CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
+ FailAllCopies();
+ CVDisplayLinkRelease(display_link_);
+ CGLSetCurrentContext(context_->cgl_context());
+ DestroyAllCopyContextsWithinContext();
+ UnrefIOSurfaceWithContextCurrent();
+ CGLSetCurrentContext(0);
+ context_ = NULL;
+}
+
+bool CompositingIOSurfaceMac::SetIOSurface(
+ uint64 io_surface_handle,
+ const gfx::Size& size,
+ float scale_factor,
+ const ui::LatencyInfo& latency_info) {
+ pixel_io_surface_size_ = size;
+ scale_factor_ = scale_factor;
+ dip_io_surface_size_ = gfx::ToFlooredSize(
+ gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_));
+
+ CGLError cgl_error = CGLSetCurrentContext(context_->cgl_context());
+ if (cgl_error != kCGLNoError) {
+ LOG(ERROR) << "CGLSetCurrentContext error in SetIOSurface: " << cgl_error;
+ return false;
+ }
+ bool result = MapIOSurfaceToTexture(io_surface_handle);
+ CGLSetCurrentContext(0);
+ latency_info_.MergeWith(latency_info);
+ return result;
+}
+
+int CompositingIOSurfaceMac::GetRendererID() {
+ GLint current_renderer_id = -1;
+ if (CGLGetParameter(context_->cgl_context(),
+ kCGLCPCurrentRendererID,
+ &current_renderer_id) == kCGLNoError)
+ return current_renderer_id & kCGLRendererIDMatchingMask;
+ return -1;
+}
+
+bool CompositingIOSurfaceMac::DrawIOSurface(
+ const gfx::Size& window_size,
+ float window_scale_factor,
+ RenderWidgetHostViewFrameSubscriber* frame_subscriber,
+ bool using_core_animation) {
+ bool result = true;
+
+ if (display_link_ == NULL)
+ SetupCVDisplayLink();
+
+ bool has_io_surface = HasIOSurface();
+ TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface",
+ "has_io_surface", has_io_surface);
+
+ gfx::Size pixel_window_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(window_size, window_scale_factor));
+ glViewport(0, 0, pixel_window_size.width(), pixel_window_size.height());
+
+ SurfaceQuad quad;
+ quad.set_size(dip_io_surface_size_, pixel_io_surface_size_);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ // Note that the projection keeps things in view units, so the use of
+ // window_size / dip_io_surface_size_ (as opposed to the pixel_ variants)
+ // below is correct.
+ glOrtho(0, window_size.width(), window_size.height(), 0, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ if (has_io_surface) {
+ context_->shader_program_cache()->UseBlitProgram();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
+
+ DrawQuad(quad);
+
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
+
+ // Fill the resize gutters with white.
+ if (window_size.width() > dip_io_surface_size_.width() ||
+ window_size.height() > dip_io_surface_size_.height()) {
+ context_->shader_program_cache()->UseSolidWhiteProgram();
+ SurfaceQuad filler_quad;
+ if (window_size.width() > dip_io_surface_size_.width()) {
+ // Draw right-side gutter down to the bottom of the window.
+ filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f,
+ window_size.width(), window_size.height());
+ DrawQuad(filler_quad);
+ }
+ if (window_size.height() > dip_io_surface_size_.height()) {
+ // Draw bottom gutter to the width of the IOSurface.
+ filler_quad.set_rect(
+ 0.0f, dip_io_surface_size_.height(),
+ dip_io_surface_size_.width(), window_size.height());
+ DrawQuad(filler_quad);
+ }
+ }
+
+ // Workaround for issue 158469. Issue a dummy draw call with texture_ not
+ // bound to blit_rgb_sampler_location_, in order to shake all references
+ // to the IOSurface out of the driver.
+ glBegin(GL_TRIANGLES);
+ glEnd();
+
+ glUseProgram(0); CHECK_AND_SAVE_GL_ERROR();
+ } else {
+ // Should match the clear color of RenderWidgetHostViewMac.
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+
+ static bool initialized_workaround = false;
+ static bool force_on_workaround = false;
+ static bool force_off_workaround = false;
+ if (!initialized_workaround) {
+ force_on_workaround = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceGLFinishWorkaround);
+ force_off_workaround = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableGpuDriverBugWorkarounds);
+
+ initialized_workaround = true;
+ }
+
+ const bool workaround_needed =
+ IsVendorIntel() && !base::mac::IsOSMountainLionOrLater();
+ const bool use_glfinish_workaround =
+ (workaround_needed || force_on_workaround) && !force_off_workaround;
+
+ if (use_glfinish_workaround) {
+ TRACE_EVENT0("gpu", "glFinish");
+ // http://crbug.com/123409 : work around bugs in graphics driver on
+ // MacBook Air with Intel HD graphics, and possibly on other models,
+ // by forcing the graphics pipeline to be completely drained at this
+ // point.
+ // This workaround is not necessary on Mountain Lion.
+ glFinish();
+ }
+
+ base::Closure copy_done_callback;
+ if (frame_subscriber) {
+ const base::Time present_time = base::Time::Now();
+ scoped_refptr<media::VideoFrame> frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
+ if (frame_subscriber->ShouldCaptureFrame(present_time, &frame, &callback)) {
+ copy_done_callback = CopyToVideoFrameWithinContext(
+ gfx::Rect(pixel_io_surface_size_), true, frame,
+ base::Bind(callback, present_time));
+ }
+ }
+
+ if (!using_core_animation) {
+ CGLError cgl_error = CGLFlushDrawable(context_->cgl_context());
+ if (cgl_error != kCGLNoError) {
+ LOG(ERROR) << "CGLFlushDrawable error in DrawIOSurface: " << cgl_error;
+ result = false;
+ }
+ }
+
+ latency_info_.swap_timestamp = base::TimeTicks::HighResNow();
+ RenderWidgetHostImpl::CompositorFrameDrawn(latency_info_);
+ latency_info_.Clear();
+
+ // Try to finish previous copy requests after flush to get better pipelining.
+ std::vector<base::Closure> copy_done_callbacks;
+ CheckIfAllCopiesAreFinishedWithinContext(false, &copy_done_callbacks);
+
+ // Check if any of the drawing calls result in an error.
+ GetAndSaveGLError();
+ if (gl_error_ != GL_NO_ERROR) {
+ LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
+ result = false;
+ }
+
+ if (!using_core_animation)
+ CGLSetCurrentContext(0);
+
+ if (!copy_done_callback.is_null())
+ copy_done_callbacks.push_back(copy_done_callback);
+ for (size_t i = 0; i < copy_done_callbacks.size(); ++i)
+ copy_done_callbacks[i].Run();
+
+ StartOrContinueDisplayLink();
+
+ return result;
+}
+
+void CompositingIOSurfaceMac::CopyTo(
+ const gfx::Rect& src_pixel_subrect,
+ const gfx::Size& dst_pixel_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ scoped_ptr<SkBitmap> output(new SkBitmap());
+ output->setConfig(SkBitmap::kARGB_8888_Config,
+ dst_pixel_size.width(), dst_pixel_size.height());
+ if (!output->allocPixels()) {
+ DLOG(ERROR) << "Failed to allocate SkBitmap pixels!";
+ callback.Run(false, *output);
+ return;
+ }
+ DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width())
+ << "Stride is required to be equal to width for GPU readback.";
+ output->setIsOpaque(true);
+
+ CGLSetCurrentContext(context_->cgl_context());
+ const base::Closure copy_done_callback = CopyToSelectedOutputWithinContext(
+ src_pixel_subrect, gfx::Rect(dst_pixel_size), false,
+ output.get(), NULL,
+ base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output)));
+ CGLSetCurrentContext(0);
+ if (!copy_done_callback.is_null())
+ copy_done_callback.Run();
+}
+
+void CompositingIOSurfaceMac::CopyToVideoFrame(
+ const gfx::Rect& src_pixel_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ CGLSetCurrentContext(context_->cgl_context());
+ const base::Closure copy_done_callback = CopyToVideoFrameWithinContext(
+ src_pixel_subrect, false, target, callback);
+ CGLSetCurrentContext(0);
+ if (!copy_done_callback.is_null())
+ copy_done_callback.Run();
+}
+
+base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext(
+ const gfx::Rect& src_pixel_subrect,
+ bool called_within_draw,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
+ gfx::Rect(target->coded_size()), src_pixel_subrect.size());
+ // Make coordinates and sizes even because we letterbox in YUV space right
+ // now (see CopyRGBToVideoFrame). They need to be even for the UV samples to
+ // line up correctly.
+ region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
+ region_in_frame.y() & ~1,
+ region_in_frame.width() & ~1,
+ region_in_frame.height() & ~1);
+ DCHECK_LE(region_in_frame.right(), target->coded_size().width());
+ DCHECK_LE(region_in_frame.bottom(), target->coded_size().height());
+
+ return CopyToSelectedOutputWithinContext(
+ src_pixel_subrect, region_in_frame, called_within_draw,
+ NULL, target, callback);
+}
+
+bool CompositingIOSurfaceMac::MapIOSurfaceToTexture(
+ uint64 io_surface_handle) {
+ if (io_surface_.get() && io_surface_handle == io_surface_handle_)
+ return true;
+
+ TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture");
+ UnrefIOSurfaceWithContextCurrent();
+
+ io_surface_.reset(io_surface_support_->IOSurfaceLookup(
+ static_cast<uint32>(io_surface_handle)));
+ // Can fail if IOSurface with that ID was already released by the gpu
+ // process.
+ if (!io_surface_) {
+ UnrefIOSurfaceWithContextCurrent();
+ return false;
+ }
+
+ io_surface_handle_ = io_surface_handle;
+
+ // Actual IOSurface size is rounded up to reduce reallocations during window
+ // resize. Get the actual size to properly map the texture.
+ gfx::Size rounded_size(
+ io_surface_support_->IOSurfaceGetWidth(io_surface_),
+ io_surface_support_->IOSurfaceGetHeight(io_surface_));
+
+ glGenTextures(1, &texture_);
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
+ glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ CHECK_AND_SAVE_GL_ERROR();
+ GLuint plane = 0;
+ CGLError cgl_error = io_surface_support_->CGLTexImageIOSurface2D(
+ context_->cgl_context(),
+ GL_TEXTURE_RECTANGLE_ARB,
+ GL_RGBA,
+ rounded_size.width(),
+ rounded_size.height(),
+ GL_BGRA,
+ GL_UNSIGNED_INT_8_8_8_8_REV,
+ io_surface_.get(),
+ plane);
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+ if (cgl_error != kCGLNoError) {
+ LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error;
+ UnrefIOSurfaceWithContextCurrent();
+ return false;
+ }
+ GetAndSaveGLError();
+ if (gl_error_ != GL_NO_ERROR) {
+ LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_;
+ UnrefIOSurfaceWithContextCurrent();
+ return false;
+ }
+ return true;
+}
+
+void CompositingIOSurfaceMac::UnrefIOSurface() {
+ CGLSetCurrentContext(context_->cgl_context());
+ UnrefIOSurfaceWithContextCurrent();
+ CGLSetCurrentContext(0);
+}
+
+void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) {
+ glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR();
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR();
+
+ glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_);
+ glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_);
+ glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR();
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+}
+
+bool CompositingIOSurfaceMac::IsVendorIntel() {
+ GLint screen;
+ CGLGetVirtualScreen(context_->cgl_context(), &screen);
+ if (screen != screen_)
+ initialized_is_intel_ = false;
+ screen_ = screen;
+ if (!initialized_is_intel_) {
+ is_intel_ = strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
+ "Intel") != NULL;
+ initialized_is_intel_ = true;
+ }
+ return is_intel_;
+}
+
+void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() {
+ if (texture_) {
+ glDeleteTextures(1, &texture_);
+ texture_ = 0;
+ }
+
+ io_surface_.reset();
+
+ // Forget the ID, because even if it is still around when we want to use it
+ // again, OSX may have reused the same ID for a new tab and we don't want to
+ // blit random tab contents.
+ io_surface_handle_ = 0;
+}
+
+void CompositingIOSurfaceMac::DisplayLinkTick(CVDisplayLinkRef display_link,
+ const CVTimeStamp* time) {
+ TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DisplayLinkTick");
+ base::AutoLock lock(lock_);
+ CalculateVsyncParametersLockHeld(time);
+}
+
+void CompositingIOSurfaceMac::CalculateVsyncParametersLockHeld(
+ const CVTimeStamp* time) {
+ lock_.AssertAcquired();
+ vsync_interval_numerator_ = static_cast<uint32>(time->videoRefreshPeriod);
+ vsync_interval_denominator_ = time->videoTimeScale;
+ // Verify that videoRefreshPeriod is 32 bits.
+ DCHECK((time->videoRefreshPeriod & ~0xffffFFFFull) == 0ull);
+
+ vsync_timebase_ =
+ base::TimeTicks::FromInternalValue(time->hostTime / 1000);
+}
+
+void CompositingIOSurfaceMac::StartOrContinueDisplayLink() {
+ if (display_link_ == NULL)
+ return;
+
+ if (!CVDisplayLinkIsRunning(display_link_)) {
+ CVDisplayLinkStart(display_link_);
+ }
+ display_link_stop_timer_.Reset();
+}
+
+void CompositingIOSurfaceMac::StopDisplayLink() {
+ if (display_link_ == NULL)
+ return;
+
+ if (CVDisplayLinkIsRunning(display_link_))
+ CVDisplayLinkStop(display_link_);
+}
+
+bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() {
+ // Using PBO crashes on Intel drivers but not on newer Mountain Lion
+ // systems. See bug http://crbug.com/152225.
+ const bool forced_synchronous = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceSynchronousGLReadPixels);
+ return (!forced_synchronous &&
+ HasAppleFenceExtension() &&
+ HasPixelBufferObjectExtension() &&
+ (base::mac::IsOSMountainLionOrLater() || !IsVendorIntel()));
+}
+
+base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext(
+ const gfx::Rect& src_pixel_subrect,
+ const gfx::Rect& dst_pixel_rect,
+ bool called_within_draw,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output,
+ const base::Callback<void(bool)>& done_callback) {
+ DCHECK_NE(bitmap_output != NULL, video_frame_output.get() != NULL);
+ DCHECK(!done_callback.is_null());
+
+ // SWIZZLE_RGBA_FOR_ASYNC_READPIXELS workaround: Fall-back to synchronous
+ // readback for SkBitmap output since the Blit shader program doesn't support
+ // switchable output formats.
+ const bool require_sync_copy_for_workaround = bitmap_output &&
+ context_->shader_program_cache()->rgb_to_yv12_output_format() == GL_RGBA;
+ const bool async_copy = !require_sync_copy_for_workaround &&
+ IsAsynchronousReadbackSupported();
+ TRACE_EVENT2(
+ "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext",
+ "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)",
+ "async_readback", async_copy);
+
+ const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect);
+ if (src_rect.IsEmpty() || dst_pixel_rect.IsEmpty())
+ return base::Bind(done_callback, false);
+
+ CopyContext* copy_context;
+ if (copy_context_pool_.empty()) {
+ // Limit the maximum number of simultaneous copies to two. Rationale:
+ // Really, only one should ever be in-progress at a time, as we should
+ // depend on the speed of the hardware to rate-limit the copying naturally.
+ // In the asynchronous read-back case, the one currently in-flight copy is
+ // highly likely to have finished by this point (i.e., it's just waiting for
+ // us to make a glMapBuffer() call). Therefore, we allow a second copy to
+ // be started here.
+ if (copy_requests_.size() >= 2)
+ return base::Bind(done_callback, false);
+ copy_context = new CopyContext(context_);
+ } else {
+ copy_context = copy_context_pool_.back();
+ copy_context_pool_.pop_back();
+ }
+
+ if (!HasIOSurface())
+ return base::Bind(done_callback, false);
+
+ // Send transform commands to the GPU.
+ copy_context->num_outputs = 0;
+ if (bitmap_output) {
+ if (copy_context->transformer->ResizeBilinear(
+ texture_, src_rect, dst_pixel_rect.size(),
+ &copy_context->output_textures[0])) {
+ copy_context->output_readback_format = GL_BGRA;
+ copy_context->num_outputs = 1;
+ copy_context->output_texture_sizes[0] = dst_pixel_rect.size();
+ }
+ } else {
+ if (copy_context->transformer->TransformRGBToYV12(
+ texture_, src_rect, dst_pixel_rect.size(),
+ &copy_context->output_textures[0],
+ &copy_context->output_textures[1],
+ &copy_context->output_textures[2],
+ &copy_context->output_texture_sizes[0],
+ &copy_context->output_texture_sizes[1])) {
+ copy_context->output_readback_format =
+ context_->shader_program_cache()->rgb_to_yv12_output_format();
+ copy_context->num_outputs = 3;
+ copy_context->output_texture_sizes[2] =
+ copy_context->output_texture_sizes[1];
+ }
+ }
+ if (!copy_context->num_outputs)
+ return base::Bind(done_callback, false);
+
+ // In the asynchronous case, issue commands to the GPU and return a null
+ // closure here. In the synchronous case, perform a blocking readback and
+ // return a callback to be run outside the CGL context to indicate success.
+ if (async_copy) {
+ copy_context->done_callback = done_callback;
+ AsynchronousReadbackForCopy(
+ dst_pixel_rect, called_within_draw, copy_context, bitmap_output,
+ video_frame_output);
+ copy_requests_.push_back(copy_context);
+ if (!finish_copy_timer_.IsRunning())
+ finish_copy_timer_.Reset();
+ return base::Closure();
+ } else {
+ const bool success = SynchronousReadbackForCopy(
+ dst_pixel_rect, copy_context, bitmap_output, video_frame_output);
+ return base::Bind(done_callback, success);
+ }
+}
+
+void CompositingIOSurfaceMac::AsynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ bool called_within_draw,
+ CopyContext* copy_context,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output) {
+ copy_context->PrepareForAsynchronousReadback();
+
+ // Copy the textures to their corresponding PBO.
+ for (int i = 0; i < copy_context->num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy",
+ "plane", i);
+
+ // Attach the output texture to the FBO.
+ glBindFramebufferEXT(
+ GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
+ glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Create a PBO and issue an asynchronous read-back.
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
+ CHECK_AND_SAVE_GL_ERROR();
+ glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
+ copy_context->output_texture_sizes[i].GetArea() * 4,
+ NULL, GL_STREAM_READ_ARB);
+ CHECK_AND_SAVE_GL_ERROR();
+ glReadPixels(0, 0,
+ copy_context->output_texture_sizes[i].width(),
+ copy_context->output_texture_sizes[i].height(),
+ copy_context->output_readback_format,
+ GL_UNSIGNED_INT_8_8_8_8_REV, 0);
+ CHECK_AND_SAVE_GL_ERROR();
+ }
+
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
+
+ glSetFenceAPPLE(copy_context->fence); CHECK_GL_ERROR();
+ copy_context->cycles_elapsed = 0;
+
+ // When this asynchronous copy happens in a draw operaton there is no need
+ // to explicitly flush because there will be a swap buffer and this flush
+ // hurts performance.
+ if (!called_within_draw) {
+ glFlush(); CHECK_AND_SAVE_GL_ERROR();
+ }
+
+ copy_context->map_buffer_callback = bitmap_output ?
+ base::Bind(&MapBufferToSkBitmap, bitmap_output) :
+ base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect);
+}
+
+void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished(
+ bool block_until_finished) {
+ std::vector<base::Closure> done_callbacks;
+ CGLSetCurrentContext(context_->cgl_context());
+ CheckIfAllCopiesAreFinishedWithinContext(
+ block_until_finished, &done_callbacks);
+ CGLSetCurrentContext(0);
+ for (size_t i = 0; i < done_callbacks.size(); ++i)
+ done_callbacks[i].Run();
+}
+
+void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext(
+ bool block_until_finished,
+ std::vector<base::Closure>* done_callbacks) {
+ while (!copy_requests_.empty()) {
+ CopyContext* const copy_context = copy_requests_.front();
+
+ if (copy_context->fence && !glTestFenceAPPLE(copy_context->fence)) {
+ CHECK_AND_SAVE_GL_ERROR();
+ // Doing a glFinishFenceAPPLE can cause transparent window flashes when
+ // switching tabs, so only do it when required.
+ if (block_until_finished) {
+ glFinishFenceAPPLE(copy_context->fence);
+ CHECK_AND_SAVE_GL_ERROR();
+ } else if (copy_context->cycles_elapsed < kFinishCopyRetryCycles) {
+ ++copy_context->cycles_elapsed;
+ // This copy has not completed there is no need to test subsequent
+ // requests.
+ break;
+ }
+ }
+ CHECK_AND_SAVE_GL_ERROR();
+
+ bool success = true;
+ for (int i = 0; success && i < copy_context->num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser",
+ "CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext",
+ "plane", i);
+
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
+ CHECK_AND_SAVE_GL_ERROR();
+
+ void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
+ CHECK_AND_SAVE_GL_ERROR();
+ success &= copy_context->map_buffer_callback.Run(buf, i);
+ glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_AND_SAVE_GL_ERROR();
+ }
+ copy_context->map_buffer_callback.Reset();
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
+
+ copy_requests_.pop_front();
+ done_callbacks->push_back(base::Bind(copy_context->done_callback, success));
+ copy_context->done_callback.Reset();
+ copy_context_pool_.push_back(copy_context);
+ }
+ if (copy_requests_.empty())
+ finish_copy_timer_.Stop();
+
+ CHECK(copy_requests_.empty() || !block_until_finished);
+}
+
+bool CompositingIOSurfaceMac::SynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ CopyContext* copy_context,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output) {
+ bool success = true;
+ copy_context->PrepareReadbackFramebuffers();
+ for (int i = 0; i < copy_context->num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy",
+ "plane", i);
+
+ // Attach the output texture to the FBO.
+ glBindFramebufferEXT(
+ GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
+ glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Blocking read-back of pixels from textures.
+ void* buf;
+ // When data must be transferred into a VideoFrame one scanline at a time,
+ // it is necessary to allocate a separate buffer for glReadPixels() that can
+ // be populated one-shot.
+ //
+ // TODO(miu): Don't keep allocating/deleting this buffer for every frame.
+ // Keep it cached, allocated on first use.
+ scoped_ptr<uint32[]> temp_readback_buffer;
+ if (bitmap_output) {
+ // The entire SkBitmap is populated, never a region within. So, read the
+ // texture directly into the bitmap's pixel memory.
+ buf = bitmap_output->getPixels();
+ } else {
+ // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and
+ // its stride is equal to the stride of the data being read back, then
+ // readback directly into the VideoFrame's buffer to save a round of
+ // memcpy'ing.
+ //
+ // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset()
+ // method). http://crbug.com/219779
+ const int src_stride = copy_context->output_texture_sizes[i].width() * 4;
+ const int dst_stride = video_frame_output->stride(i);
+ if (src_stride == dst_stride && dst_pixel_rect.x() == 0) {
+ const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2);
+ buf = video_frame_output->data(i) + y_offset * dst_stride;
+ } else {
+ // Create and readback into a temporary buffer because the data must be
+ // transferred to VideoFrame's pixel memory one scanline at a time.
+ temp_readback_buffer.reset(
+ new uint32[copy_context->output_texture_sizes[i].GetArea()]);
+ buf = temp_readback_buffer.get();
+ }
+ }
+ glReadPixels(0, 0,
+ copy_context->output_texture_sizes[i].width(),
+ copy_context->output_texture_sizes[i].height(),
+ copy_context->output_readback_format,
+ GL_UNSIGNED_INT_8_8_8_8_REV, buf);
+ CHECK_AND_SAVE_GL_ERROR();
+ if (video_frame_output.get()) {
+ if (!temp_readback_buffer) {
+ // Apply letterbox black-out around view region.
+ media::LetterboxYUV(video_frame_output.get(), dst_pixel_rect);
+ } else {
+ // Copy from temporary buffer and fully render the VideoFrame.
+ success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect,
+ temp_readback_buffer.get(), i);
+ }
+ }
+ }
+
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
+ copy_context_pool_.push_back(copy_context);
+ return success;
+}
+
+void CompositingIOSurfaceMac::FailAllCopies() {
+ for (size_t i = 0; i < copy_requests_.size(); ++i) {
+ copy_requests_[i]->map_buffer_callback.Reset();
+
+ base::Callback<void(bool)>& done_callback =
+ copy_requests_[i]->done_callback;
+ if (!done_callback.is_null()) {
+ done_callback.Run(false);
+ done_callback.Reset();
+ }
+ }
+}
+
+void CompositingIOSurfaceMac::DestroyAllCopyContextsWithinContext() {
+ // Move all in-flight copies, if any, back into the pool. Then, destroy all
+ // the CopyContexts in the pool.
+ copy_context_pool_.insert(copy_context_pool_.end(),
+ copy_requests_.begin(), copy_requests_.end());
+ copy_requests_.clear();
+ while (!copy_context_pool_.empty()) {
+ scoped_ptr<CopyContext> copy_context(copy_context_pool_.back());
+ copy_context_pool_.pop_back();
+ copy_context->ReleaseCachedGLObjects();
+ }
+}
+
+gfx::Rect CompositingIOSurfaceMac::IntersectWithIOSurface(
+ const gfx::Rect& rect) const {
+ return gfx::IntersectRects(rect,
+ gfx::ToEnclosingRect(gfx::Rect(pixel_io_surface_size_)));
+}
+
+GLenum CompositingIOSurfaceMac::GetAndSaveGLError() {
+ GLenum gl_error = glGetError();
+ if (gl_error_ == GL_NO_ERROR)
+ gl_error_ = gl_error;
+ return gl_error;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc b/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc
new file mode 100644
index 00000000000..87f0f341be2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc
@@ -0,0 +1,450 @@
+// Copyright (c) 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 "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+
+#include <string>
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "gpu/config/gpu_driver_bug_workaround_type.h"
+
+namespace content {
+
+namespace {
+
+// Convenience macro allowing GLSL programs to be specified inline, and to be
+// automatically converted into string form by the C preprocessor.
+#define GLSL_PROGRAM_AS_STRING(shader_code) #shader_code
+
+// As required by the spec, add the version directive to the beginning of each
+// program to activate the expected syntax and built-in features. GLSL version
+// 1.2 is the latest version supported by MacOS 10.6.
+const char kVersionDirective[] = "#version 120\n";
+
+// Allow switchable output swizzling from RGBToYV12 fragment shaders (needed for
+// workaround; see comments in CompositingIOSurfaceShaderPrograms ctor).
+const char kOutputSwizzleMacroNormal[] = "#define OUTPUT_PIXEL_ORDERING bgra\n";
+const char kOutputSwizzleMacroSwapRB[] = "#define OUTPUT_PIXEL_ORDERING rgba\n";
+
+// Only the bare-bones calculations here for speed.
+const char kvsBlit[] = GLSL_PROGRAM_AS_STRING(
+ varying vec2 texture_coord;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ texture_coord = gl_MultiTexCoord0.xy;
+ }
+);
+
+// Just samples the texture.
+const char kfsBlit[] = GLSL_PROGRAM_AS_STRING(
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord;
+ void main() {
+ gl_FragColor = vec4(texture2DRect(texture_, texture_coord).rgb, 1.0);
+ }
+);
+
+
+// Only calculates position.
+const char kvsSolidWhite[] = GLSL_PROGRAM_AS_STRING(
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ }
+);
+
+// Always white.
+const char kfsSolidWhite[] = GLSL_PROGRAM_AS_STRING(
+ void main() {
+ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+ }
+);
+
+
+///////////////////////////////////////////////////////////////////////
+// RGB24 to YV12 in two passes; writing two 8888 targets each pass.
+//
+// YV12 is full-resolution luma and half-resolution blue/red chroma.
+//
+// (original)
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// |
+// | (y plane) (temporary)
+// | YYYY YYYY UUVV UUVV
+// +--> { YYYY YYYY + UUVV UUVV }
+// YYYY YYYY UUVV UUVV
+// First YYYY YYYY UUVV UUVV
+// pass YYYY YYYY UUVV UUVV
+// YYYY YYYY UUVV UUVV
+// |
+// | (u plane) (v plane)
+// Second | UUUU VVVV
+// pass +--> { UUUU + VVVV }
+// UUUU VVVV
+//
+///////////////////////////////////////////////////////////////////////
+
+// Phase one of RGB24->YV12 conversion: vsFetch4Pixels/fsConvertRGBtoY8UV44
+//
+// Writes four source pixels at a time to a full-size Y plane and a half-width
+// interleaved UV plane. After execution, the Y plane is complete but the UV
+// planes still need to be de-interleaved and vertically scaled.
+const char kRGBtoYV12_vsFetch4Pixels[] = GLSL_PROGRAM_AS_STRING(
+ uniform float texel_scale_x_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ varying vec2 texture_coord2;
+ varying vec2 texture_coord3;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+
+ vec2 texcoord_base = gl_MultiTexCoord0.xy;
+ vec2 one_texel_x = vec2(texel_scale_x_, 0.0);
+ texture_coord0 = texcoord_base - 1.5 * one_texel_x;
+ texture_coord1 = texcoord_base - 0.5 * one_texel_x;
+ texture_coord2 = texcoord_base + 0.5 * one_texel_x;
+ texture_coord3 = texcoord_base + 1.5 * one_texel_x;
+ }
+);
+
+const char kRGBtoYV12_fsConvertRGBtoY8UV44[] = GLSL_PROGRAM_AS_STRING(
+ const vec3 rgb_to_y = vec3(0.257, 0.504, 0.098);
+ const vec3 rgb_to_u = vec3(-0.148, -0.291, 0.439);
+ const vec3 rgb_to_v = vec3(0.439, -0.368, -0.071);
+ const float y_bias = 0.0625;
+ const float uv_bias = 0.5;
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ varying vec2 texture_coord2;
+ varying vec2 texture_coord3;
+ void main() {
+ // Load the four texture samples.
+ vec3 pixel0 = texture2DRect(texture_, texture_coord0).rgb;
+ vec3 pixel1 = texture2DRect(texture_, texture_coord1).rgb;
+ vec3 pixel2 = texture2DRect(texture_, texture_coord2).rgb;
+ vec3 pixel3 = texture2DRect(texture_, texture_coord3).rgb;
+
+ // RGB -> Y conversion (x4).
+ vec4 yyyy = vec4(dot(pixel0, rgb_to_y),
+ dot(pixel1, rgb_to_y),
+ dot(pixel2, rgb_to_y),
+ dot(pixel3, rgb_to_y)) + y_bias;
+
+ // Average adjacent texture samples while converting RGB->UV. This is the
+ // same as color converting then averaging, but slightly less math. These
+ // values will be in the range [-0.439f, +0.439f] and still need to have
+ // the bias term applied.
+ vec3 blended_pixel0 = pixel0 + pixel1;
+ vec3 blended_pixel1 = pixel2 + pixel3;
+ vec2 uu = vec2(dot(blended_pixel0, rgb_to_u),
+ dot(blended_pixel1, rgb_to_u)) / 2.0;
+ vec2 vv = vec2(dot(blended_pixel0, rgb_to_v),
+ dot(blended_pixel1, rgb_to_v)) / 2.0;
+
+ gl_FragData[0] = yyyy.OUTPUT_PIXEL_ORDERING;
+ gl_FragData[1] = vec4(uu, vv) + uv_bias;
+ }
+);
+
+// Phase two of RGB24->YV12 conversion: vsFetch2Pixels/fsConvertUV44toU2V2
+//
+// Deals with UV only. Input is two UUVV quads. The pixels have already been
+// scaled horizontally prior to this point, and vertical scaling will now happen
+// via bilinear interpolation during texture sampling. Output is two color
+// planes U and V, packed four pixels to a "RGBA" quad.
+const char kRGBtoYV12_vsFetch2Pixels[] = GLSL_PROGRAM_AS_STRING(
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+
+ vec2 texcoord_base = gl_MultiTexCoord0.xy;
+ texture_coord0 = texcoord_base - vec2(0.5, 0.0);
+ texture_coord1 = texcoord_base + vec2(0.5, 0.0);
+ }
+);
+
+const char kRGBtoYV12_fsConvertUV44toU2V2[] = GLSL_PROGRAM_AS_STRING(
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ void main() {
+ // We're just sampling two pixels and unswizzling them. There's no need
+ // to do vertical scaling with math, since bilinear interpolation in the
+ // sampler takes care of that.
+ vec4 lo_uuvv = texture2DRect(texture_, texture_coord0);
+ vec4 hi_uuvv = texture2DRect(texture_, texture_coord1);
+ gl_FragData[0] = vec4(lo_uuvv.rg, hi_uuvv.rg).OUTPUT_PIXEL_ORDERING;
+ gl_FragData[1] = vec4(lo_uuvv.ba, hi_uuvv.ba).OUTPUT_PIXEL_ORDERING;
+ }
+);
+
+
+enum ShaderProgram {
+ SHADER_PROGRAM_BLIT = 0,
+ SHADER_PROGRAM_SOLID_WHITE,
+ SHADER_PROGRAM_RGB_TO_YV12__1_OF_2,
+ SHADER_PROGRAM_RGB_TO_YV12__2_OF_2,
+ NUM_SHADER_PROGRAMS
+};
+
+// The code snippets that together make up an entire vertex shader program.
+const char* kVertexShaderSourceCodeMap[] = {
+ // SHADER_PROGRAM_BLIT
+ kvsBlit,
+ // SHADER_PROGRAM_SOLID_WHITE
+ kvsSolidWhite,
+
+ // SHADER_PROGRAM_RGB_TO_YV12__1_OF_2
+ kRGBtoYV12_vsFetch4Pixels,
+ // SHADER_PROGRAM_RGB_TO_YV12__2_OF_2
+ kRGBtoYV12_vsFetch2Pixels,
+};
+
+// The code snippets that together make up an entire fragment shader program.
+const char* kFragmentShaderSourceCodeMap[] = {
+ // SHADER_PROGRAM_BLIT
+ kfsBlit,
+ // SHADER_PROGRAM_SOLID_WHITE
+ kfsSolidWhite,
+
+ // SHADER_PROGRAM_RGB_TO_YV12__1_OF_2
+ kRGBtoYV12_fsConvertRGBtoY8UV44,
+ // SHADER_PROGRAM_RGB_TO_YV12__2_OF_2
+ kRGBtoYV12_fsConvertUV44toU2V2,
+};
+
+GLuint CompileShaderGLSL(ShaderProgram shader_program, GLenum shader_type,
+ bool output_swap_rb) {
+ TRACE_EVENT2("gpu", "CompileShaderGLSL",
+ "program", shader_program,
+ "type", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment");
+
+ DCHECK_GE(shader_program, 0);
+ DCHECK_LT(shader_program, NUM_SHADER_PROGRAMS);
+
+ const GLuint shader = glCreateShader(shader_type);
+ DCHECK_NE(shader, 0u);
+
+ // Select and compile the shader program source code.
+ if (shader_type == GL_VERTEX_SHADER) {
+ const GLchar* source_snippets[] = {
+ kVersionDirective,
+ kVertexShaderSourceCodeMap[shader_program],
+ };
+ glShaderSource(shader, arraysize(source_snippets), source_snippets, NULL);
+ } else {
+ DCHECK(shader_type == GL_FRAGMENT_SHADER);
+ const GLchar* source_snippets[] = {
+ kVersionDirective,
+ output_swap_rb ? kOutputSwizzleMacroSwapRB : kOutputSwizzleMacroNormal,
+ kFragmentShaderSourceCodeMap[shader_program],
+ };
+ glShaderSource(shader, arraysize(source_snippets), source_snippets, NULL);
+ }
+ glCompileShader(shader);
+
+ // Check for successful compilation. On error in debug builds, pull the info
+ // log and emit the compiler messages.
+ GLint error;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &error);
+ if (error != GL_TRUE) {
+#ifndef NDEBUG
+ static const int kMaxInfoLogLength = 8192;
+ scoped_ptr<char[]> buffer(new char[kMaxInfoLogLength]);
+ GLsizei length_returned = 0;
+ glGetShaderInfoLog(shader, kMaxInfoLogLength - 1, &length_returned,
+ buffer.get());
+ buffer[kMaxInfoLogLength - 1] = '\0';
+ DLOG(ERROR) << "Failed to compile "
+ << (shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment")
+ << " shader for program " << shader_program << ":\n"
+ << buffer.get()
+ << (length_returned >= kMaxInfoLogLength ?
+ "\n*** TRUNCATED! ***" : "");
+#endif
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ // Success!
+ return shader;
+}
+
+GLuint CompileAndLinkProgram(ShaderProgram which, bool output_swap_rb) {
+ TRACE_EVENT1("gpu", "CompileAndLinkProgram", "program", which);
+
+ // Compile and link a new shader program.
+ const GLuint vertex_shader =
+ CompileShaderGLSL(which, GL_VERTEX_SHADER, false);
+ const GLuint fragment_shader =
+ CompileShaderGLSL(which, GL_FRAGMENT_SHADER, output_swap_rb);
+ const GLuint program = glCreateProgram();
+ DCHECK_NE(program, 0u);
+ glAttachShader(program, vertex_shader);
+ glAttachShader(program, fragment_shader);
+ glLinkProgram(program);
+
+ // Flag shaders for deletion so that they will be deleted automatically when
+ // the program is later deleted.
+ glDeleteShader(vertex_shader);
+ glDeleteShader(fragment_shader);
+
+ // Check that the program successfully linked.
+ GLint error = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &error);
+ if (error != GL_TRUE) {
+ glDeleteProgram(program);
+ return 0;
+ }
+ return program;
+}
+
+} // namespace
+
+
+CompositingIOSurfaceShaderPrograms::CompositingIOSurfaceShaderPrograms()
+ : rgb_to_yv12_output_format_(GL_BGRA) {
+ COMPILE_ASSERT(kNumShaderPrograms == NUM_SHADER_PROGRAMS,
+ header_constant_disagrees_with_enum);
+ COMPILE_ASSERT(arraysize(kVertexShaderSourceCodeMap) == NUM_SHADER_PROGRAMS,
+ vertex_shader_source_code_map_incorrect_size);
+ COMPILE_ASSERT(arraysize(kFragmentShaderSourceCodeMap) == NUM_SHADER_PROGRAMS,
+ fragment_shader_source_code_map_incorrect_size);
+
+ memset(shader_programs_, 0, sizeof(shader_programs_));
+ for (size_t i = 0; i < arraysize(texture_var_locations_); ++i)
+ texture_var_locations_[i] = -1;
+ for (size_t i = 0; i < arraysize(texel_scale_x_var_locations_); ++i)
+ texel_scale_x_var_locations_[i] = -1;
+
+ // Look for the swizzle_rgba_for_async_readpixels driver bug workaround and
+ // modify rgb_to_yv12_output_format_ if necessary.
+ // See: http://crbug.com/265115
+ GpuDataManagerImpl* const manager = GpuDataManagerImpl::GetInstance();
+ if (manager) {
+ base::ListValue workarounds;
+ manager->GetDriverBugWorkarounds(&workarounds);
+ base::ListValue::const_iterator it = workarounds.Find(
+ base::StringValue(gpu::GpuDriverBugWorkaroundTypeToString(
+ gpu::SWIZZLE_RGBA_FOR_ASYNC_READPIXELS)));
+ if (it != workarounds.end())
+ rgb_to_yv12_output_format_ = GL_RGBA;
+ }
+ DVLOG(1) << "Using RGBToYV12 fragment shader output format: "
+ << (rgb_to_yv12_output_format_ == GL_BGRA ? "BGRA" : "RGBA");
+}
+
+CompositingIOSurfaceShaderPrograms::~CompositingIOSurfaceShaderPrograms() {
+#ifndef NDEBUG
+ for (size_t i = 0; i < arraysize(shader_programs_); ++i)
+ DCHECK_EQ(shader_programs_[i], 0u) << "Failed to call Reset().";
+#endif
+}
+
+void CompositingIOSurfaceShaderPrograms::Reset() {
+ for (size_t i = 0; i < arraysize(shader_programs_); ++i) {
+ if (shader_programs_[i] != 0u) {
+ glDeleteProgram(shader_programs_[i]);
+ DCHECK(glGetError() == GL_NO_ERROR)
+ << "when calling glDeleteProgram(shader_programs_[" << i << "])";
+ shader_programs_[i] = 0u;
+ }
+ }
+ for (size_t i = 0; i < arraysize(texture_var_locations_); ++i)
+ texture_var_locations_[i] = -1;
+ for (size_t i = 0; i < arraysize(texel_scale_x_var_locations_); ++i)
+ texel_scale_x_var_locations_[i] = -1;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseBlitProgram() {
+ const GLuint program = GetShaderProgram(SHADER_PROGRAM_BLIT);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ BindUniformTextureVariable(SHADER_PROGRAM_BLIT, 0);
+ return true;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseSolidWhiteProgram() {
+ const GLuint program = GetShaderProgram(SHADER_PROGRAM_SOLID_WHITE);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ return true;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseRGBToYV12Program(
+ int pass_number, float texel_scale_x) {
+ const int which = SHADER_PROGRAM_RGB_TO_YV12__1_OF_2 + pass_number - 1;
+ DCHECK_GE(which, SHADER_PROGRAM_RGB_TO_YV12__1_OF_2);
+ DCHECK_LE(which, SHADER_PROGRAM_RGB_TO_YV12__2_OF_2);
+
+ const GLuint program = GetShaderProgram(which);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ BindUniformTextureVariable(which, 0);
+ if (which == SHADER_PROGRAM_RGB_TO_YV12__1_OF_2) {
+ BindUniformTexelScaleXVariable(which, texel_scale_x);
+ } else {
+ // The second pass doesn't have a texel_scale_x uniform variable since it's
+ // never supposed to be doing any scaling (i.e., outside of the usual
+ // 2x2-->1x1 that's already built into the process).
+ DCHECK_EQ(texel_scale_x, 1.0f);
+ }
+ return true;
+}
+
+void CompositingIOSurfaceShaderPrograms::SetOutputFormatForTesting(
+ GLenum format) {
+ rgb_to_yv12_output_format_ = format;
+ Reset();
+}
+
+GLuint CompositingIOSurfaceShaderPrograms::GetShaderProgram(int which) {
+ if (shader_programs_[which] == 0u) {
+ shader_programs_[which] =
+ CompileAndLinkProgram(static_cast<ShaderProgram>(which),
+ rgb_to_yv12_output_format_ == GL_RGBA);
+ DCHECK_NE(shader_programs_[which], 0u)
+ << "Failed to create ShaderProgram " << which;
+ }
+ return shader_programs_[which];
+}
+
+void CompositingIOSurfaceShaderPrograms::BindUniformTextureVariable(
+ int which, int texture_unit_offset) {
+ if (texture_var_locations_[which] == -1) {
+ texture_var_locations_[which] =
+ glGetUniformLocation(GetShaderProgram(which), "texture_");
+ DCHECK_NE(texture_var_locations_[which], -1)
+ << "Failed to find location of uniform variable: texture_";
+ }
+ glUniform1i(texture_var_locations_[which], texture_unit_offset);
+}
+
+void CompositingIOSurfaceShaderPrograms::BindUniformTexelScaleXVariable(
+ int which, float texel_scale_x) {
+ if (texel_scale_x_var_locations_[which] == -1) {
+ texel_scale_x_var_locations_[which] =
+ glGetUniformLocation(GetShaderProgram(which), "texel_scale_x_");
+ DCHECK_NE(texel_scale_x_var_locations_[which], -1)
+ << "Failed to find location of uniform variable: texel_scale_x_";
+ }
+ glUniform1f(texel_scale_x_var_locations_[which], texel_scale_x);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h b/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h
new file mode 100644
index 00000000000..de1987f2e03
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h
@@ -0,0 +1,81 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
+
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+
+namespace content {
+
+// Provides caching of the compile-and-link step for shader programs at runtime
+// since, once compiled and linked, the programs can be shared. Callers invoke
+// one of the UseXXX() methods to glUseProgram() the program and have its
+// uniform variables bound with the given parameters.
+//
+// Note: All public methods must be invoked within the the same GL context!
+class CompositingIOSurfaceShaderPrograms {
+ public:
+ CompositingIOSurfaceShaderPrograms();
+ ~CompositingIOSurfaceShaderPrograms();
+
+ // Reset the cache, deleting any references to currently-cached shader
+ // programs. This must be called within an active OpenGL context just before
+ // destruction.
+ void Reset();
+
+ // Begin using the "blit" program, which is set up to sample the texture at
+ // GL_TEXTURE_0. Returns false on error.
+ bool UseBlitProgram();
+
+ // Begin using the program that just draws solid white very efficiently.
+ // Returns false on error.
+ bool UseSolidWhiteProgram();
+
+ // Begin using one of the two RGB-to-YV12 color conversion programs, as
+ // specified by |pass_number| 1 or 2. The programs will sample the texture at
+ // GL_TEXTURE0, and account for scaling in the X direction by |texel_scale_x|.
+ // Returns false on error.
+ bool UseRGBToYV12Program(int pass_number, float texel_scale_x);
+
+ // |format| argument to use for glReadPixels() when reading back textures
+ // generated by the RGBToYV12 program.
+ GLenum rgb_to_yv12_output_format() const {
+ return rgb_to_yv12_output_format_;
+ }
+
+ protected:
+ FRIEND_TEST_ALL_PREFIXES(CompositingIOSurfaceTransformerTest,
+ TransformsRGBToYV12);
+
+ // Side effect: Calls Reset(), deleting any cached programs.
+ void SetOutputFormatForTesting(GLenum format);
+
+ private:
+ enum { kNumShaderPrograms = 4 };
+
+ // Helper methods to cache uniform variable locations.
+ GLuint GetShaderProgram(int which);
+ void BindUniformTextureVariable(int which, int texture_unit_offset);
+ void BindUniformTexelScaleXVariable(int which, float texel_scale_x);
+
+ // Cached values for previously-compiled/linked shader programs, and the
+ // locations of their uniform variables.
+ GLuint shader_programs_[kNumShaderPrograms];
+ GLint texture_var_locations_[kNumShaderPrograms];
+ GLint texel_scale_x_var_locations_[kNumShaderPrograms];
+
+ // Byte order of the quads generated by the RGBToYV12 shader program. Must
+ // always be GL_BGRA (default) or GL_RGBA (workaround case).
+ GLenum rgb_to_yv12_output_format_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceShaderPrograms);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
new file mode 100644
index 00000000000..2e0401ff476
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 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 "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+namespace {
+
+const GLenum kColorAttachments[] = {
+ GL_COLOR_ATTACHMENT0_EXT,
+ GL_COLOR_ATTACHMENT1_EXT
+};
+
+// Set viewport and model/projection matrices for drawing to a framebuffer of
+// size dst_size, with coordinates starting at (0, 0).
+void SetTransformationsForOffScreenRendering(const gfx::Size& dst_size) {
+ glViewport(0, 0, dst_size.width(), dst_size.height());
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, dst_size.width(), 0, dst_size.height(), -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
+// Configure texture sampling parameters.
+void SetTextureParameters(GLenum target, GLint min_mag_filter, GLint wrap) {
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_mag_filter);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, min_mag_filter);
+ glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
+ glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
+}
+
+// Draw the currently-bound texture. The src region is applied to the entire
+// destination framebuffer of the given size. Specify |flip_y| is the src
+// texture is upside-down relative to the destination.
+//
+// Assumption: The orthographic projection is set up as
+// (0,0)x(dst_width,dst_height).
+void DrawQuad(float src_x, float src_y, float src_width, float src_height,
+ bool flip_y, float dst_width, float dst_height) {
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ float vertices[4][2] = {
+ { 0.0f, dst_height },
+ { 0.0f, 0.0f },
+ { dst_width, 0.0f },
+ { dst_width, dst_height }
+ };
+ glVertexPointer(arraysize(vertices[0]), GL_FLOAT, sizeof(vertices[0]),
+ vertices);
+
+ float tex_coords[4][2] = {
+ { src_x, src_y + src_height },
+ { src_x, src_y },
+ { src_x + src_width, src_y },
+ { src_x + src_width, src_y + src_height }
+ };
+ if (flip_y) {
+ std::swap(tex_coords[0][1], tex_coords[1][1]);
+ std::swap(tex_coords[2][1], tex_coords[3][1]);
+ }
+ glTexCoordPointer(arraysize(tex_coords[0]), GL_FLOAT, sizeof(tex_coords[0]),
+ tex_coords);
+
+ COMPILE_ASSERT(arraysize(vertices) == arraysize(tex_coords),
+ same_number_of_points_in_both);
+ glDrawArrays(GL_QUADS, 0, arraysize(vertices));
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+}
+
+} // namespace
+
+CompositingIOSurfaceTransformer::CompositingIOSurfaceTransformer(
+ GLenum texture_target, bool src_texture_needs_y_flip,
+ CompositingIOSurfaceShaderPrograms* shader_program_cache)
+ : texture_target_(texture_target),
+ src_texture_needs_y_flip_(src_texture_needs_y_flip),
+ shader_program_cache_(shader_program_cache),
+ frame_buffer_(0) {
+ DCHECK(texture_target_ == GL_TEXTURE_RECTANGLE_ARB)
+ << "Fragment shaders currently only support RECTANGLE textures.";
+ DCHECK(shader_program_cache_);
+
+ memset(textures_, 0, sizeof(textures_));
+
+ // The RGB-to-YV12 transform requires that the driver/hardware supports
+ // multiple draw buffers.
+ GLint max_draw_buffers = 1;
+ glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
+ system_supports_multiple_draw_buffers_ = (max_draw_buffers >= 2);
+}
+
+CompositingIOSurfaceTransformer::~CompositingIOSurfaceTransformer() {
+ for (int i = 0; i < NUM_CACHED_TEXTURES; ++i)
+ DCHECK_EQ(textures_[i], 0u) << "Failed to call ReleaseCachedGLObjects().";
+ DCHECK_EQ(frame_buffer_, 0u) << "Failed to call ReleaseCachedGLObjects().";
+}
+
+void CompositingIOSurfaceTransformer::ReleaseCachedGLObjects() {
+ for (int i = 0; i < NUM_CACHED_TEXTURES; ++i) {
+ if (textures_[i]) {
+ glDeleteTextures(1, &textures_[i]);
+ textures_[i] = 0;
+ texture_sizes_[i] = gfx::Size();
+ }
+ }
+ if (frame_buffer_) {
+ glDeleteFramebuffersEXT(1, &frame_buffer_);
+ frame_buffer_ = 0;
+ }
+}
+
+bool CompositingIOSurfaceTransformer::ResizeBilinear(
+ GLuint src_texture, const gfx::Rect& src_subrect, const gfx::Size& dst_size,
+ GLuint* texture) {
+ if (src_subrect.IsEmpty() || dst_size.IsEmpty())
+ return false;
+
+ glActiveTexture(GL_TEXTURE0);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ PrepareTexture(RGBA_OUTPUT, dst_size);
+ PrepareFramebuffer();
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ texture_target_, textures_[RGBA_OUTPUT], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ glBindTexture(texture_target_, src_texture);
+ SetTextureParameters(
+ texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
+ GL_CLAMP_TO_EDGE);
+
+ const bool prepared = shader_program_cache_->UseBlitProgram();
+ DCHECK(prepared);
+ SetTransformationsForOffScreenRendering(dst_size);
+ DrawQuad(src_subrect.x(), src_subrect.y(),
+ src_subrect.width(), src_subrect.height(),
+ src_texture_needs_y_flip_,
+ dst_size.width(), dst_size.height());
+ glUseProgram(0);
+
+ glBindTexture(texture_target_, 0);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
+ *texture = textures_[RGBA_OUTPUT];
+ return true;
+}
+
+bool CompositingIOSurfaceTransformer::TransformRGBToYV12(
+ GLuint src_texture,
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ GLuint* texture_y,
+ GLuint* texture_u,
+ GLuint* texture_v,
+ gfx::Size* packed_y_size,
+ gfx::Size* packed_uv_size) {
+ if (!system_supports_multiple_draw_buffers_)
+ return false;
+ if (src_subrect.IsEmpty() || dst_size.IsEmpty())
+ return false;
+
+ TRACE_EVENT0("gpu", "TransformRGBToYV12");
+
+ glActiveTexture(GL_TEXTURE0);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ // Resize output textures for each plane, and for the intermediate UUVV one
+ // that becomes an input into pass #2. |packed_y_size| is the size of the Y
+ // output texture, where its width is 1/4 the number of Y pixels because 4 Y
+ // pixels are packed into a single quad. |packed_uv_size| is half the size of
+ // Y in both dimensions, rounded up.
+ *packed_y_size = gfx::Size((dst_size.width() + 3) / 4, dst_size.height());
+ *packed_uv_size = gfx::Size((packed_y_size->width() + 1) / 2,
+ (packed_y_size->height() + 1) / 2);
+ PrepareTexture(Y_PLANE_OUTPUT, *packed_y_size);
+ PrepareTexture(UUVV_INTERMEDIATE, *packed_y_size);
+ PrepareTexture(U_PLANE_OUTPUT, *packed_uv_size);
+ PrepareTexture(V_PLANE_OUTPUT, *packed_uv_size);
+
+ /////////////////////////////////////////
+ // Pass 1: RGB --(scaled)--> YYYY + UUVV
+ PrepareFramebuffer();
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ texture_target_, textures_[Y_PLANE_OUTPUT], 0);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
+ texture_target_, textures_[UUVV_INTERMEDIATE], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+ glDrawBuffers(2, kColorAttachments);
+
+ // Read from |src_texture|. Enable bilinear filtering only if scaling is
+ // required. The filtering will take place entirely in the first pass.
+ glBindTexture(texture_target_, src_texture);
+ SetTextureParameters(
+ texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
+ GL_CLAMP_TO_EDGE);
+
+ // Use the first-pass shader program and draw the scene.
+ const bool prepared_pass_1 = shader_program_cache_->UseRGBToYV12Program(
+ 1,
+ static_cast<float>(src_subrect.width()) / dst_size.width());
+ DCHECK(prepared_pass_1);
+ SetTransformationsForOffScreenRendering(*packed_y_size);
+ DrawQuad(src_subrect.x(), src_subrect.y(),
+ ((packed_y_size->width() * 4.0f) / dst_size.width()) *
+ src_subrect.width(),
+ src_subrect.height(),
+ src_texture_needs_y_flip_,
+ packed_y_size->width(), packed_y_size->height());
+
+ /////////////////////////////////////////
+ // Pass 2: UUVV -> UUUU + VVVV
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ texture_target_, textures_[U_PLANE_OUTPUT], 0);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
+ texture_target_, textures_[V_PLANE_OUTPUT], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Read from the intermediate UUVV texture. The second pass uses bilinear
+ // minification to achieve vertical scaling, so enable it always.
+ glBindTexture(texture_target_, textures_[UUVV_INTERMEDIATE]);
+ SetTextureParameters(texture_target_, GL_LINEAR, GL_CLAMP_TO_EDGE);
+
+ // Use the second-pass shader program and draw the scene.
+ const bool prepared_pass_2 =
+ shader_program_cache_->UseRGBToYV12Program(2, 1.0f);
+ DCHECK(prepared_pass_2);
+ SetTransformationsForOffScreenRendering(*packed_uv_size);
+ DrawQuad(0.0f, 0.0f,
+ packed_uv_size->width() * 2.0f,
+ packed_uv_size->height() * 2.0f,
+ false,
+ packed_uv_size->width(), packed_uv_size->height());
+ glUseProgram(0);
+
+ // Before leaving, put back to drawing to a single rendering output.
+ glDrawBuffers(1, kColorAttachments);
+
+ glBindTexture(texture_target_, 0);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
+ *texture_y = textures_[Y_PLANE_OUTPUT];
+ *texture_u = textures_[U_PLANE_OUTPUT];
+ *texture_v = textures_[V_PLANE_OUTPUT];
+ return true;
+}
+
+void CompositingIOSurfaceTransformer::PrepareTexture(
+ CachedTexture which, const gfx::Size& size) {
+ DCHECK_GE(which, 0);
+ DCHECK_LT(which, NUM_CACHED_TEXTURES);
+ DCHECK(!size.IsEmpty());
+
+ if (!textures_[which]) {
+ glGenTextures(1, &textures_[which]);
+ DCHECK_NE(textures_[which], 0u);
+ texture_sizes_[which] = gfx::Size();
+ }
+
+ // Re-allocate the texture if its size has changed since last use.
+ if (texture_sizes_[which] != size) {
+ TRACE_EVENT2("gpu", "Resize Texture",
+ "which", which,
+ "new_size", size.ToString());
+ glBindTexture(texture_target_, textures_[which]);
+ glTexImage2D(texture_target_, 0, GL_RGBA, size.width(), size.height(), 0,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
+ texture_sizes_[which] = size;
+ }
+}
+
+void CompositingIOSurfaceTransformer::PrepareFramebuffer() {
+ if (!frame_buffer_) {
+ glGenFramebuffersEXT(1, &frame_buffer_);
+ DCHECK_NE(frame_buffer_, 0u);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.h b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.h
new file mode 100644
index 00000000000..48900741944
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac.h
@@ -0,0 +1,123 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
+
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+class Rect;
+} // namespace gfx
+
+namespace content {
+
+// Provides useful image filtering operations that are implemented efficiently
+// using OpenGL shader programs.
+//
+// Note: All methods assume to be called within an active OpenGL context.
+class CompositingIOSurfaceTransformer {
+ public:
+ // Construct a transformer that always uses the given parameters for texture
+ // bindings. |texture_target| is one of the valid enums to use with
+ // glBindTexture().
+ // |src_texture_needs_y_flip| is true when the |src_texture| argument to any
+ // of the methods below uses upside-down Y coordinates.
+ // |shader_program_cache| is not owned by this instance.
+ CompositingIOSurfaceTransformer(
+ GLenum texture_target, bool src_texture_needs_y_flip,
+ CompositingIOSurfaceShaderPrograms* shader_program_cache);
+
+ ~CompositingIOSurfaceTransformer();
+
+ // Delete any references to currently-cached OpenGL objects. This must be
+ // called within the OpenGL context just before destruction.
+ void ReleaseCachedGLObjects();
+
+ // Resize using bilinear interpolation. Returns false on error. Otherwise,
+ // the |texture| argument will point to the result. Ownership of the returned
+ // |texture| remains with CompositingIOSurfaceTransformer (i.e., the caller
+ // must not delete this texture). The |texture| remains valid until the next
+ // call to ResizeBilinear() or ReleaseCachedGLObjects().
+ //
+ // If the src and dst sizes are identical, this becomes a simple copy into a
+ // new texture.
+ //
+ // Note: This implementation is faulty in that minifications by more than 2X
+ // will undergo aliasing.
+ bool ResizeBilinear(GLuint src_texture, const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size, GLuint* texture);
+
+ // Color format conversion from RGB to planar YV12 (also known as YUV420).
+ //
+ // YV12 is effectively a twelve bit per pixel format consisting of a full-
+ // size y (luminance) plane and half-width, half-height u and v (blue and
+ // red chrominance) planes. This method will return three off-screen
+ // textures, one for each plane, via the output arguments |texture_y|,
+ // |texture_u|, and |texture_v|. While the textures are in GL_RGBA format,
+ // they should be interpreted as the appropriate single-byte, planar format
+ // after reading the pixel data. The output arguments |packed_y_size| and
+ // |packed_uv_size| follow from these special semantics: They represent the
+ // size of their corresponding texture, if it was to be treated like RGBA
+ // pixel data. That means their widths are in terms of "quads," where one
+ // quad contains 4 Y (or U or V) pixels.
+ //
+ // Ownership of the returned textures remains with
+ // CompositingIOSurfaceTransformer (i.e., the caller must not delete the
+ // textures). The textures remain valid until the next call to
+ // TransformRGBToYV12() or ReleaseCachedGLObjects().
+ //
+ // If |src_subrect|'s size does not match |dst_size|, the source will be
+ // bilinearly interpolated during conversion.
+ //
+ // Returns true if successful, and the caller is responsible for deleting the
+ // output textures.
+ bool TransformRGBToYV12(
+ GLuint src_texture, const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ GLuint* texture_y, GLuint* texture_u, GLuint* texture_v,
+ gfx::Size* packed_y_size, gfx::Size* packed_uv_size);
+
+ private:
+ enum CachedTexture {
+ RGBA_OUTPUT = 0,
+ Y_PLANE_OUTPUT,
+ UUVV_INTERMEDIATE,
+ U_PLANE_OUTPUT,
+ V_PLANE_OUTPUT,
+ NUM_CACHED_TEXTURES
+ };
+
+ // If necessary, generate the texture and/or resize it to the given |size|.
+ void PrepareTexture(CachedTexture which, const gfx::Size& size);
+
+ // If necessary, generate a framebuffer object to be used as an intermediate
+ // destination for drawing.
+ void PrepareFramebuffer();
+
+ // Target to bind all input and output textures to (which defines the type of
+ // textures being created and read). Generally, this is
+ // GL_TEXTURE_RECTANGLE_ARB.
+ const GLenum texture_target_;
+ const bool src_texture_needs_y_flip_;
+ CompositingIOSurfaceShaderPrograms* const shader_program_cache_;
+
+ // Cached OpenGL objects.
+ GLuint textures_[NUM_CACHED_TEXTURES];
+ gfx::Size texture_sizes_[NUM_CACHED_TEXTURES];
+ GLuint frame_buffer_;
+
+ // Auto-detected and set once in the constructor.
+ bool system_supports_multiple_draw_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceTransformer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
diff --git a/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc
new file mode 100644
index 00000000000..7d9452fd01e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc
@@ -0,0 +1,533 @@
+// Copyright (c) 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 "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
+
+#include <OpenGL/CGLCurrent.h>
+#include <OpenGL/CGLRenderers.h>
+#include <OpenGL/CGLTypes.h>
+#include <OpenGL/OpenGL.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glu.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <sstream>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "media/base/yuv_convert.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+#define EXPECT_NO_GL_ERROR(stmt) \
+ do { \
+ stmt; \
+ const GLenum error_code = glGetError(); \
+ EXPECT_TRUE(GL_NO_ERROR == error_code) \
+ << "for error code " << error_code \
+ << ": " << gluErrorString(error_code); \
+ } while(0)
+
+namespace {
+
+const GLenum kGLTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
+
+enum RendererRestriction {
+ RESTRICTION_NONE,
+ RESTRICTION_SOFTWARE_ONLY,
+ RESTRICTION_HARDWARE_ONLY
+};
+
+bool InitializeGLContext(CGLContextObj* context,
+ RendererRestriction restriction) {
+ std::vector<CGLPixelFormatAttribute> attribs;
+ // Select off-screen renderers only.
+ attribs.push_back(kCGLPFAOffScreen);
+ // By default, the library will prefer hardware-accelerated renderers, but
+ // falls back on the software ones if necessary. However, there are use cases
+ // where we want to force a restriction (e.g., benchmarking performance).
+ if (restriction == RESTRICTION_SOFTWARE_ONLY) {
+ attribs.push_back(kCGLPFARendererID);
+ attribs.push_back(static_cast<CGLPixelFormatAttribute>(
+ kCGLRendererGenericFloatID));
+ } else if (restriction == RESTRICTION_HARDWARE_ONLY) {
+ attribs.push_back(kCGLPFAAccelerated);
+ }
+ attribs.push_back(static_cast<CGLPixelFormatAttribute>(0));
+
+ CGLPixelFormatObj format;
+ GLint num_pixel_formats = 0;
+ bool success = true;
+ if (CGLChoosePixelFormat(&attribs.front(), &format, &num_pixel_formats) !=
+ kCGLNoError) {
+ LOG(ERROR) << "Error choosing pixel format.";
+ success = false;
+ }
+ if (success && num_pixel_formats <= 0) {
+ LOG(ERROR) << "num_pixel_formats <= 0; actual value is "
+ << num_pixel_formats;
+ success = false;
+ }
+ if (success && CGLCreateContext(format, NULL, context) != kCGLNoError) {
+ LOG(ERROR) << "Error creating context.";
+ success = false;
+ }
+ CGLDestroyPixelFormat(format);
+ return success;
+}
+
+// Returns a decent test pattern for testing all of: 1) orientation, 2) scaling,
+// 3) color space conversion (e.g., 4 pixels --> one U or V pixel), and 4)
+// texture alignment/processing. Example 32x32 bitmap:
+//
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+//
+// Key: G = Gray, R = Red, B = Blue, Y = Yellow, C = Cyan
+SkBitmap GenerateTestPatternBitmap(const gfx::Size& size) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ CHECK(bitmap.allocPixels());
+ SkAutoLockPixels lock_bitmap(bitmap);
+ bitmap.eraseColor(SK_ColorGRAY);
+ for (int y = 0; y < size.height(); ++y) {
+ uint32_t* p = bitmap.getAddr32(0, y);
+ for (int x = 0; x < size.width(); ++x, ++p) {
+ if ((x < (size.width() / 2)) && (y < (size.height() / 2)))
+ continue; // Leave upper-left quadrant gray.
+ *p = SkColorSetARGB(255,
+ x % 4 < 2 ? 255 : 0,
+ y % 4 < 2 ? 255 : 0,
+ x % 4 < 2 ? 0 : 255);
+ }
+ }
+ return bitmap;
+}
+
+// Creates a new texture consisting of the given |bitmap|.
+GLuint CreateTextureWithImage(const SkBitmap& bitmap) {
+ GLuint texture;
+ EXPECT_NO_GL_ERROR(glGenTextures(1, &texture));
+ EXPECT_NO_GL_ERROR(glBindTexture(kGLTextureTarget, texture));
+ {
+ SkAutoLockPixels lock_bitmap(bitmap);
+ EXPECT_NO_GL_ERROR(glTexImage2D(
+ kGLTextureTarget, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap.getPixels()));
+ }
+ glBindTexture(kGLTextureTarget, 0);
+ return texture;
+}
+
+// Read back a texture from the GPU, returning the image data as an SkBitmap.
+SkBitmap ReadBackTexture(GLuint texture, const gfx::Size& size, GLenum format) {
+ SkBitmap result;
+ result.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ CHECK(result.allocPixels());
+
+ GLuint frame_buffer;
+ EXPECT_NO_GL_ERROR(glGenFramebuffersEXT(1, &frame_buffer));
+ EXPECT_NO_GL_ERROR(
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, frame_buffer));
+ EXPECT_NO_GL_ERROR(glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, kGLTextureTarget,
+ texture, 0));
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ {
+ SkAutoLockPixels lock_result(result);
+ EXPECT_NO_GL_ERROR(glReadPixels(
+ 0, 0, size.width(), size.height(), format, GL_UNSIGNED_INT_8_8_8_8_REV,
+ result.getPixels()));
+ }
+
+ EXPECT_NO_GL_ERROR(glDeleteFramebuffersEXT(1, &frame_buffer));
+
+ return result;
+}
+
+// Returns the |src_rect| region of |src| scaled to |to_size| by drawing on a
+// Skia canvas, and using bilinear filtering (just like a GPU would).
+SkBitmap ScaleBitmapWithSkia(const SkBitmap& src,
+ const gfx::Rect& src_rect,
+ const gfx::Size& to_size) {
+ SkBitmap cropped_src;
+ if (src_rect == gfx::Rect(0, 0, src.width(), src.height())) {
+ cropped_src = src;
+ } else {
+ CHECK(src.extractSubset(
+ &cropped_src,
+ SkIRect::MakeXYWH(src_rect.x(), src_rect.y(),
+ src_rect.width(), src_rect.height())));
+ }
+
+ SkBitmap result;
+ result.setConfig(cropped_src.config(), to_size.width(), to_size.height());
+ CHECK(result.allocPixels());
+
+ SkCanvas canvas(result);
+ canvas.scale(static_cast<double>(result.width()) / cropped_src.width(),
+ static_cast<double>(result.height()) / cropped_src.height());
+ SkPaint paint;
+ paint.setFilterBitmap(true); // Use bilinear filtering.
+ canvas.drawBitmap(cropped_src, 0, 0, &paint);
+
+ return result;
+}
+
+// The maximum value by which a pixel value may deviate from the expected value
+// before considering it "significantly different." This is meant to account
+// for the slight differences in filtering techniques used between the various
+// GPUs and software implementations.
+const int kDifferenceThreshold = 16;
+
+// Returns the number of pixels significantly different between |expected| and
+// |actual|.
+int ImageDifference(const SkBitmap& expected, const SkBitmap& actual) {
+ SkAutoLockPixels lock_expected(expected);
+ SkAutoLockPixels lock_actual(actual);
+
+ // Sanity-check assumed image properties.
+ DCHECK_EQ(expected.width(), actual.width());
+ DCHECK_EQ(expected.height(), actual.height());
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, expected.config());
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, actual.config());
+
+ // Compare both images.
+ int num_pixels_different = 0;
+ for (int y = 0; y < expected.height(); ++y) {
+ const uint32_t* p = expected.getAddr32(0, y);
+ const uint32_t* q = actual.getAddr32(0, y);
+ for (int x = 0; x < expected.width(); ++x, ++p, ++q) {
+ if (abs(static_cast<int>(SkColorGetR(*p)) -
+ static_cast<int>(SkColorGetR(*q))) > kDifferenceThreshold ||
+ abs(static_cast<int>(SkColorGetG(*p)) -
+ static_cast<int>(SkColorGetG(*q))) > kDifferenceThreshold ||
+ abs(static_cast<int>(SkColorGetB(*p)) -
+ static_cast<int>(SkColorGetB(*q))) > kDifferenceThreshold) {
+ ++num_pixels_different;
+ }
+ }
+ }
+
+ return num_pixels_different;
+}
+
+// Returns the number of pixels significantly different between |expected| and
+// |actual|. It is understood that |actual| contains 4-byte quads, and so we
+// may need to be ignoring a mod-4 number of pixels at the end of each of its
+// rows.
+int ImagePlaneDifference(const uint8* expected, const SkBitmap& actual,
+ const gfx::Size& dst_size) {
+ SkAutoLockPixels actual_lock(actual);
+
+ int num_pixels_different = 0;
+ for (int y = 0; y < dst_size.height(); ++y) {
+ const uint8* p = expected + y * dst_size.width();
+ const uint8* const p_end = p + dst_size.width();
+ const uint8* q =
+ reinterpret_cast<uint8*>(actual.getPixels()) + y * actual.rowBytes();
+ for (; p < p_end; ++p, ++q) {
+ if (abs(static_cast<int>(*p) - static_cast<int>(*q)) >
+ kDifferenceThreshold) {
+ ++num_pixels_different;
+ }
+ }
+ }
+
+ return num_pixels_different;
+}
+
+} // namespace
+
+// Note: All tests fixtures operate within an off-screen OpenGL context.
+class CompositingIOSurfaceTransformerTest : public testing::Test {
+ public:
+ CompositingIOSurfaceTransformerTest() {
+ // TODO(miu): Try to use RESTRICTION_NONE to speed up the execution time of
+ // unit tests, once it's established that the trybots and buildbots behave
+ // well when using the GPU.
+ CHECK(InitializeGLContext(&context_, RESTRICTION_SOFTWARE_ONLY));
+ CGLSetCurrentContext(context_);
+ shader_program_cache_.reset(new CompositingIOSurfaceShaderPrograms());
+ transformer_.reset(new CompositingIOSurfaceTransformer(
+ kGLTextureTarget, false, shader_program_cache_.get()));
+ }
+
+ virtual ~CompositingIOSurfaceTransformerTest() {
+ transformer_->ReleaseCachedGLObjects();
+ shader_program_cache_->Reset();
+ CGLSetCurrentContext(NULL);
+ CGLDestroyContext(context_);
+ }
+
+ protected:
+ void RunResizeTest(const SkBitmap& src_bitmap, const gfx::Rect& src_rect,
+ const gfx::Size& dst_size) {
+ SCOPED_TRACE(::testing::Message()
+ << "src_rect=(" << src_rect.x() << ',' << src_rect.y()
+ << ")x[" << src_rect.width() << 'x' << src_rect.height()
+ << "]; dst_size=[" << dst_size.width() << 'x'
+ << dst_size.height() << ']');
+
+ // Do the scale operation on the GPU.
+ const GLuint original_texture = CreateTextureWithImage(src_bitmap);
+ ASSERT_NE(0u, original_texture);
+ GLuint scaled_texture = 0u;
+ ASSERT_TRUE(transformer_->ResizeBilinear(
+ original_texture, src_rect, dst_size, &scaled_texture));
+ EXPECT_NE(0u, scaled_texture);
+ CGLFlushDrawable(context_); // Account for some buggy driver impls.
+ const SkBitmap result_bitmap =
+ ReadBackTexture(scaled_texture, dst_size, GL_BGRA);
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture));
+
+ // Compare the image read back to the version produced by a known-working
+ // software implementation. Allow up to 2 lines of mismatch due to how
+ // implementations disagree on resolving the processing of edges.
+ const SkBitmap expected_bitmap =
+ ScaleBitmapWithSkia(src_bitmap, src_rect, dst_size);
+ EXPECT_GE(std::max(expected_bitmap.width(), expected_bitmap.height()) * 2,
+ ImageDifference(expected_bitmap, result_bitmap));
+ }
+
+ void RunTransformRGBToYV12Test(
+ const SkBitmap& src_bitmap, const gfx::Rect& src_rect,
+ const gfx::Size& dst_size) {
+ SCOPED_TRACE(::testing::Message()
+ << "src_rect=(" << src_rect.x() << ',' << src_rect.y()
+ << ")x[" << src_rect.width() << 'x' << src_rect.height()
+ << "]; dst_size=[" << dst_size.width() << 'x'
+ << dst_size.height() << ']');
+
+ // Perform the RGB to YV12 conversion.
+ const GLuint original_texture = CreateTextureWithImage(src_bitmap);
+ ASSERT_NE(0u, original_texture);
+ GLuint texture_y = 0u;
+ GLuint texture_u = 0u;
+ GLuint texture_v = 0u;
+ gfx::Size packed_y_size;
+ gfx::Size packed_uv_size;
+ ASSERT_TRUE(transformer_->TransformRGBToYV12(
+ original_texture, src_rect, dst_size,
+ &texture_y, &texture_u, &texture_v, &packed_y_size, &packed_uv_size));
+ EXPECT_NE(0u, texture_y);
+ EXPECT_NE(0u, texture_u);
+ EXPECT_NE(0u, texture_v);
+ EXPECT_FALSE(packed_y_size.IsEmpty());
+ EXPECT_FALSE(packed_uv_size.IsEmpty());
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture));
+
+ // Read-back the texture for each plane.
+ CGLFlushDrawable(context_); // Account for some buggy driver impls.
+ const GLenum format = shader_program_cache_->rgb_to_yv12_output_format();
+ const SkBitmap result_y_bitmap =
+ ReadBackTexture(texture_y, packed_y_size, format);
+ const SkBitmap result_u_bitmap =
+ ReadBackTexture(texture_u, packed_uv_size, format);
+ const SkBitmap result_v_bitmap =
+ ReadBackTexture(texture_v, packed_uv_size, format);
+
+ // Compare the Y, U, and V planes read-back to the version produced by a
+ // known-working software implementation. Allow up to 2 lines of mismatch
+ // due to how implementations disagree on resolving the processing of edges.
+ const SkBitmap expected_bitmap =
+ ScaleBitmapWithSkia(src_bitmap, src_rect, dst_size);
+ const gfx::Size dst_uv_size(
+ (dst_size.width() + 1) / 2, (dst_size.height() + 1) / 2);
+ scoped_ptr<uint8[]> expected_y_plane(
+ new uint8[dst_size.width() * dst_size.height()]);
+ scoped_ptr<uint8[]> expected_u_plane(
+ new uint8[dst_uv_size.width() * dst_uv_size.height()]);
+ scoped_ptr<uint8[]> expected_v_plane(
+ new uint8[dst_uv_size.width() * dst_uv_size.height()]);
+ {
+ SkAutoLockPixels src_bitmap_lock(expected_bitmap);
+ media::ConvertRGB32ToYUV(
+ reinterpret_cast<const uint8*>(expected_bitmap.getPixels()),
+ expected_y_plane.get(), expected_u_plane.get(),
+ expected_v_plane.get(),
+ expected_bitmap.width(), expected_bitmap.height(),
+ expected_bitmap.rowBytes(),
+ dst_size.width(), (dst_size.width() + 1) / 2);
+ }
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()) * 2,
+ ImagePlaneDifference(expected_y_plane.get(), result_y_bitmap, dst_size))
+ << " for RGB --> Y Plane";
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()),
+ ImagePlaneDifference(expected_u_plane.get(), result_u_bitmap,
+ dst_uv_size))
+ << " for RGB --> U Plane";
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()),
+ ImagePlaneDifference(expected_v_plane.get(), result_v_bitmap,
+ dst_uv_size))
+ << " for RGB --> V Plane";
+ }
+
+ CompositingIOSurfaceShaderPrograms* shader_program_cache() const {
+ return shader_program_cache_.get();
+ }
+
+ private:
+ CGLContextObj context_;
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache_;
+ scoped_ptr<CompositingIOSurfaceTransformer> transformer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceTransformerTest);
+};
+
+TEST_F(CompositingIOSurfaceTransformerTest, ShaderProgramsCompileAndLink) {
+ // Attempt to use each program, binding its required uniform variables.
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseBlitProgram());
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseSolidWhiteProgram());
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(1, 1.0f));
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(2, 1.0f));
+
+ EXPECT_NO_GL_ERROR(glUseProgram(0));
+}
+
+namespace {
+
+const struct TestParameters {
+ int src_width;
+ int src_height;
+ int scaled_width;
+ int scaled_height;
+} kTestParameters[] = {
+ // Test 1:1 copies, but exposing varying pixel packing configurations.
+ { 64, 64, 64, 64 },
+ { 63, 63, 63, 63 },
+ { 62, 62, 62, 62 },
+ { 61, 61, 61, 61 },
+ { 60, 60, 60, 60 },
+ { 59, 59, 59, 59 },
+ { 58, 58, 58, 58 },
+ { 57, 57, 57, 57 },
+ { 56, 56, 56, 56 },
+
+ // Even-size, one or both dimensions upscaled.
+ { 32, 32, 64, 32 }, { 32, 32, 32, 64 }, { 32, 32, 64, 64 },
+ // Even-size, one or both dimensions downscaled by 2X.
+ { 32, 32, 16, 32 }, { 32, 32, 32, 16 }, { 32, 32, 16, 16 },
+ // Even-size, one or both dimensions downscaled by 1 pixel.
+ { 32, 32, 31, 32 }, { 32, 32, 32, 31 }, { 32, 32, 31, 31 },
+ // Even-size, one or both dimensions downscaled by 2 pixels.
+ { 32, 32, 30, 32 }, { 32, 32, 32, 30 }, { 32, 32, 30, 30 },
+ // Even-size, one or both dimensions downscaled by 3 pixels.
+ { 32, 32, 29, 32 }, { 32, 32, 32, 29 }, { 32, 32, 29, 29 },
+
+ // Odd-size, one or both dimensions upscaled.
+ { 33, 33, 66, 33 }, { 33, 33, 33, 66 }, { 33, 33, 66, 66 },
+ // Odd-size, one or both dimensions downscaled by 2X.
+ { 33, 33, 16, 33 }, { 33, 33, 33, 16 }, { 33, 33, 16, 16 },
+ // Odd-size, one or both dimensions downscaled by 1 pixel.
+ { 33, 33, 32, 33 }, { 33, 33, 33, 32 }, { 33, 33, 32, 32 },
+ // Odd-size, one or both dimensions downscaled by 2 pixels.
+ { 33, 33, 31, 33 }, { 33, 33, 33, 31 }, { 33, 33, 31, 31 },
+ // Odd-size, one or both dimensions downscaled by 3 pixels.
+ { 33, 33, 30, 33 }, { 33, 33, 33, 30 }, { 33, 33, 30, 30 },
+};
+
+} // namespace
+
+TEST_F(CompositingIOSurfaceTransformerTest, ResizesTexturesCorrectly) {
+ for (size_t i = 0; i < arraysize(kTestParameters); ++i) {
+ SCOPED_TRACE(::testing::Message() << "kTestParameters[" << i << ']');
+
+ const TestParameters& params = kTestParameters[i];
+ const gfx::Size src_size(params.src_width, params.src_height);
+ const gfx::Size dst_size(params.scaled_width, params.scaled_height);
+ const SkBitmap src_bitmap = GenerateTestPatternBitmap(src_size);
+
+ // Full texture resize test.
+ RunResizeTest(src_bitmap, gfx::Rect(src_size), dst_size);
+ // Subrect resize test: missing top row in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(0, 1, params.src_width, params.src_height - 1),
+ dst_size);
+ // Subrect resize test: missing left column in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 0, params.src_width - 1, params.src_height),
+ dst_size);
+ // Subrect resize test: missing top+bottom rows, and left column in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 1, params.src_height - 2),
+ dst_size);
+ // Subrect resize test: missing top row, and left+right columns in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 2, params.src_height - 1),
+ dst_size);
+ }
+}
+
+TEST_F(CompositingIOSurfaceTransformerTest, TransformsRGBToYV12) {
+ static const GLenum kOutputFormats[] = { GL_BGRA, GL_RGBA };
+
+ for (size_t i = 0; i < arraysize(kOutputFormats); ++i) {
+ SCOPED_TRACE(::testing::Message() << "kOutputFormats[" << i << ']');
+
+ shader_program_cache()->SetOutputFormatForTesting(kOutputFormats[i]);
+
+ for (size_t j = 0; j < arraysize(kTestParameters); ++j) {
+ SCOPED_TRACE(::testing::Message() << "kTestParameters[" << j << ']');
+
+ const TestParameters& params = kTestParameters[j];
+ const gfx::Size src_size(params.src_width, params.src_height);
+ const gfx::Size dst_size(params.scaled_width, params.scaled_height);
+ const SkBitmap src_bitmap = GenerateTestPatternBitmap(src_size);
+
+ // Full texture resize test.
+ RunTransformRGBToYV12Test(src_bitmap, gfx::Rect(src_size), dst_size);
+ // Subrect resize test: missing top row in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap, gfx::Rect(0, 1, params.src_width, params.src_height - 1),
+ dst_size);
+ // Subrect resize test: missing left column in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap, gfx::Rect(1, 0, params.src_width - 1, params.src_height),
+ dst_size);
+ // Subrect resize test: missing top+bottom rows, and left column in
+ // source.
+ RunTransformRGBToYV12Test(
+ src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 1, params.src_height - 2),
+ dst_size);
+ // Subrect resize test: missing top row, and left+right columns in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 2, params.src_height - 1),
+ dst_size);
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositor_impl_android.cc b/chromium/content/browser/renderer_host/compositor_impl_android.cc
new file mode 100644
index 00000000000..94ab1393c84
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositor_impl_android.cc
@@ -0,0 +1,506 @@
+// 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 "content/browser/renderer_host/compositor_impl_android.h"
+
+#include <android/bitmap.h>
+#include <android/native_window_jni.h>
+#include <map>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "cc/input/input_handler.h"
+#include "cc/layers/layer.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/output_surface.h"
+#include "cc/trees/layer_tree_host.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/common/gpu/client/command_buffer_proxy_impl.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "content/common/gpu/client/gpu_channel_host.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/common/gpu/gpu_process_launch_causes.h"
+#include "content/public/browser/android/compositor_client.h"
+#include "content/public/common/content_switches.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/android/device_display_info.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h"
+
+namespace gfx {
+class JavaBitmap;
+}
+
+namespace {
+
+// Used for drawing directly to the screen. Bypasses resizing and swaps.
+class DirectOutputSurface : public cc::OutputSurface {
+ public:
+ DirectOutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
+ : cc::OutputSurface(context3d.Pass()) {
+ capabilities_.adjust_deadline_for_parent = false;
+ }
+
+ virtual void Reshape(gfx::Size size, float scale_factor) OVERRIDE {
+ surface_size_ = size;
+ }
+ virtual void SwapBuffers(cc::CompositorFrame*) OVERRIDE {
+ context3d()->shallowFlushCHROMIUM();
+ }
+};
+
+// Used to override capabilities_.adjust_deadline_for_parent to false
+class OutputSurfaceWithoutParent : public cc::OutputSurface {
+ public:
+ OutputSurfaceWithoutParent(scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
+ : cc::OutputSurface(context3d.Pass()) {
+ capabilities_.adjust_deadline_for_parent = false;
+ }
+
+ virtual void SwapBuffers(cc::CompositorFrame* frame) OVERRIDE {
+ content::WebGraphicsContext3DCommandBufferImpl* command_buffer =
+ static_cast<content::WebGraphicsContext3DCommandBufferImpl*>(context3d());
+ content::CommandBufferProxyImpl* command_buffer_proxy =
+ command_buffer->GetCommandBufferProxy();
+ DCHECK(command_buffer_proxy);
+ command_buffer_proxy->SetLatencyInfo(frame->metadata.latency_info);
+
+ OutputSurface::SwapBuffers(frame);
+ }
+};
+
+static bool g_initialized = false;
+static base::Thread* g_impl_thread = NULL;
+static bool g_use_direct_gl = false;
+
+} // anonymous namespace
+
+namespace content {
+
+typedef std::map<int, base::android::ScopedJavaGlobalRef<jobject> >
+ SurfaceMap;
+static base::LazyInstance<SurfaceMap>
+ g_surface_map = LAZY_INSTANCE_INITIALIZER;
+static base::LazyInstance<base::Lock> g_surface_map_lock;
+
+// static
+Compositor* Compositor::Create(CompositorClient* client) {
+ return client ? new CompositorImpl(client) : NULL;
+}
+
+// static
+void Compositor::Initialize() {
+ DCHECK(!CompositorImpl::IsInitialized());
+ g_initialized = true;
+}
+
+// static
+void Compositor::InitializeWithFlags(uint32 flags) {
+ g_use_direct_gl = flags & DIRECT_CONTEXT_ON_DRAW_THREAD;
+ if (flags & ENABLE_COMPOSITOR_THREAD) {
+ TRACE_EVENT_INSTANT0("test_gpu", "ThreadedCompositingInitialization",
+ TRACE_EVENT_SCOPE_THREAD);
+ g_impl_thread = new base::Thread("Browser Compositor");
+ g_impl_thread->Start();
+ }
+ Compositor::Initialize();
+}
+
+// static
+bool CompositorImpl::IsInitialized() {
+ return g_initialized;
+}
+
+// static
+bool CompositorImpl::IsThreadingEnabled() {
+ return g_impl_thread;
+}
+
+// static
+bool CompositorImpl::UsesDirectGL() {
+ return g_use_direct_gl;
+}
+
+// static
+jobject CompositorImpl::GetSurface(int surface_id) {
+ base::AutoLock lock(g_surface_map_lock.Get());
+ SurfaceMap* surfaces = g_surface_map.Pointer();
+ SurfaceMap::iterator it = surfaces->find(surface_id);
+ jobject jsurface = it == surfaces->end() ? NULL : it->second.obj();
+
+ LOG_IF(WARNING, !jsurface) << "No surface for surface id " << surface_id;
+ return jsurface;
+}
+
+CompositorImpl::CompositorImpl(CompositorClient* client)
+ : root_layer_(cc::Layer::Create()),
+ has_transparent_background_(false),
+ window_(NULL),
+ surface_id_(0),
+ client_(client),
+ weak_factory_(this) {
+ DCHECK(client);
+ ImageTransportFactoryAndroid::AddObserver(this);
+}
+
+CompositorImpl::~CompositorImpl() {
+ ImageTransportFactoryAndroid::RemoveObserver(this);
+ // Clean-up any surface references.
+ SetSurface(NULL);
+}
+
+void CompositorImpl::SetNeedsRedraw() {
+ if (host_)
+ host_->SetNeedsRedraw();
+}
+
+void CompositorImpl::Composite() {
+ if (host_)
+ host_->Composite(base::TimeTicks::Now());
+}
+
+void CompositorImpl::SetRootLayer(scoped_refptr<cc::Layer> root_layer) {
+ root_layer_->RemoveAllChildren();
+ root_layer_->AddChild(root_layer);
+}
+
+void CompositorImpl::SetWindowSurface(ANativeWindow* window) {
+ GpuSurfaceTracker* tracker = GpuSurfaceTracker::Get();
+
+ if (window_) {
+ tracker->RemoveSurface(surface_id_);
+ ANativeWindow_release(window_);
+ window_ = NULL;
+ surface_id_ = 0;
+ SetVisible(false);
+ }
+
+ if (window) {
+ window_ = window;
+ ANativeWindow_acquire(window);
+ surface_id_ = tracker->AddSurfaceForNativeWidget(window);
+ tracker->SetSurfaceHandle(
+ surface_id_,
+ gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_DIRECT));
+ SetVisible(true);
+ }
+}
+
+void CompositorImpl::SetSurface(jobject surface) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> j_surface(env, surface);
+
+ // First, cleanup any existing surface references.
+ if (surface_id_) {
+ DCHECK(g_surface_map.Get().find(surface_id_) !=
+ g_surface_map.Get().end());
+ base::AutoLock lock(g_surface_map_lock.Get());
+ g_surface_map.Get().erase(surface_id_);
+ }
+ SetWindowSurface(NULL);
+
+ // Now, set the new surface if we have one.
+ ANativeWindow* window = NULL;
+ if (surface)
+ window = ANativeWindow_fromSurface(env, surface);
+ if (window) {
+ SetWindowSurface(window);
+ ANativeWindow_release(window);
+ {
+ base::AutoLock lock(g_surface_map_lock.Get());
+ g_surface_map.Get().insert(std::make_pair(surface_id_, j_surface));
+ }
+ }
+}
+
+void CompositorImpl::SetVisible(bool visible) {
+ if (!visible) {
+ host_.reset();
+ } else if (!host_) {
+ cc::LayerTreeSettings settings;
+ settings.compositor_name = "BrowserCompositor";
+ settings.refresh_rate = 60.0;
+ settings.impl_side_painting = false;
+ settings.allow_antialiasing = false;
+ settings.calculate_top_controls_position = false;
+ settings.top_controls_height = 0.f;
+ settings.use_memory_management = false;
+ settings.highp_threshold_min = 2048;
+
+ // Do not clear the framebuffer when rendering into external GL contexts
+ // like Android View System's.
+ if (UsesDirectGL())
+ settings.should_clear_root_render_pass = false;
+
+ scoped_refptr<base::SingleThreadTaskRunner> impl_thread_task_runner =
+ g_impl_thread ? g_impl_thread->message_loop()->message_loop_proxy()
+ : NULL;
+
+ host_ = cc::LayerTreeHost::Create(this, settings, impl_thread_task_runner);
+ host_->SetRootLayer(root_layer_);
+
+ host_->SetVisible(true);
+ host_->SetLayerTreeHostClientReady();
+ host_->SetViewportSize(size_);
+ host_->set_has_transparent_background(has_transparent_background_);
+ }
+}
+
+void CompositorImpl::setDeviceScaleFactor(float factor) {
+ if (host_)
+ host_->SetDeviceScaleFactor(factor);
+}
+
+void CompositorImpl::SetWindowBounds(const gfx::Size& size) {
+ if (size_ == size)
+ return;
+
+ size_ = size;
+ if (host_)
+ host_->SetViewportSize(size);
+ root_layer_->SetBounds(size);
+}
+
+void CompositorImpl::SetHasTransparentBackground(bool flag) {
+ has_transparent_background_ = flag;
+ if (host_)
+ host_->set_has_transparent_background(flag);
+}
+
+bool CompositorImpl::CompositeAndReadback(void *pixels, const gfx::Rect& rect) {
+ if (host_)
+ return host_->CompositeAndReadback(pixels, rect);
+ else
+ return false;
+}
+
+WebKit::WebGLId CompositorImpl::GenerateTexture(gfx::JavaBitmap& bitmap) {
+ unsigned int texture_id = BuildBasicTexture();
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ if (texture_id == 0 || context->isContextLost() ||
+ !context->makeContextCurrent())
+ return 0;
+ WebKit::WebGLId format = GetGLFormatForBitmap(bitmap);
+ WebKit::WebGLId type = GetGLTypeForBitmap(bitmap);
+
+ context->texImage2D(GL_TEXTURE_2D,
+ 0,
+ format,
+ bitmap.size().width(),
+ bitmap.size().height(),
+ 0,
+ format,
+ type,
+ bitmap.pixels());
+ context->shallowFlushCHROMIUM();
+ return texture_id;
+}
+
+WebKit::WebGLId CompositorImpl::GenerateCompressedTexture(gfx::Size& size,
+ int data_size,
+ void* data) {
+ unsigned int texture_id = BuildBasicTexture();
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ if (texture_id == 0 || context->isContextLost() ||
+ !context->makeContextCurrent())
+ return 0;
+ context->compressedTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_ETC1_RGB8_OES,
+ size.width(),
+ size.height(),
+ 0,
+ data_size,
+ data);
+ context->shallowFlushCHROMIUM();
+ return texture_id;
+}
+
+void CompositorImpl::DeleteTexture(WebKit::WebGLId texture_id) {
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ if (context->isContextLost() || !context->makeContextCurrent())
+ return;
+ context->deleteTexture(texture_id);
+ context->shallowFlushCHROMIUM();
+}
+
+bool CompositorImpl::CopyTextureToBitmap(WebKit::WebGLId texture_id,
+ gfx::JavaBitmap& bitmap) {
+ return CopyTextureToBitmap(texture_id, gfx::Rect(bitmap.size()), bitmap);
+}
+
+bool CompositorImpl::CopyTextureToBitmap(WebKit::WebGLId texture_id,
+ const gfx::Rect& sub_rect,
+ gfx::JavaBitmap& bitmap) {
+ // The sub_rect should match the bitmap size.
+ DCHECK(bitmap.size() == sub_rect.size());
+ if (bitmap.size() != sub_rect.size() || texture_id == 0) return false;
+
+ GLHelper* helper = ImageTransportFactoryAndroid::GetInstance()->GetGLHelper();
+ helper->ReadbackTextureSync(texture_id,
+ sub_rect,
+ static_cast<unsigned char*> (bitmap.pixels()));
+ return true;
+}
+
+scoped_ptr<cc::OutputSurface> CompositorImpl::CreateOutputSurface(
+ bool fallback) {
+ WebKit::WebGraphicsContext3D::Attributes attrs;
+ attrs.shareResources = true;
+ attrs.noAutomaticFlushes = true;
+
+ if (g_use_direct_gl) {
+ scoped_ptr<WebKit::WebGraphicsContext3D> context(
+ webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl::
+ CreateViewContext(attrs, window_));
+ if (!window_) {
+ return scoped_ptr<cc::OutputSurface>(
+ new DirectOutputSurface(context.Pass()));
+ }
+
+ return make_scoped_ptr(new cc::OutputSurface(context.Pass()));
+ } else {
+ DCHECK(window_ && surface_id_);
+ GpuChannelHostFactory* factory = BrowserGpuChannelHostFactory::instance();
+ GURL url("chrome://gpu/Compositor::createContext3D");
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context(
+ new WebGraphicsContext3DCommandBufferImpl(surface_id_,
+ url,
+ factory,
+ weak_factory_.GetWeakPtr()));
+ static const size_t kBytesPerPixel = 4;
+ gfx::DeviceDisplayInfo display_info;
+ size_t full_screen_texture_size_in_bytes =
+ display_info.GetDisplayHeight() *
+ display_info.GetDisplayWidth() *
+ kBytesPerPixel;
+ if (!context->Initialize(
+ attrs,
+ false,
+ CAUSE_FOR_GPU_LAUNCH_WEBGRAPHICSCONTEXT3DCOMMANDBUFFERIMPL_INITIALIZE,
+ 64 * 1024, // command buffer size
+ std::min(full_screen_texture_size_in_bytes,
+ kDefaultStartTransferBufferSize),
+ kDefaultMinTransferBufferSize,
+ std::min(3 * full_screen_texture_size_in_bytes,
+ kDefaultMaxTransferBufferSize))) {
+ LOG(ERROR) << "Failed to create 3D context for compositor.";
+ return scoped_ptr<cc::OutputSurface>();
+ }
+ return scoped_ptr<cc::OutputSurface>(
+ new OutputSurfaceWithoutParent(
+ context.PassAs<WebKit::WebGraphicsContext3D>()));
+ }
+}
+
+void CompositorImpl::OnLostResources() {
+ client_->DidLoseResources();
+}
+
+void CompositorImpl::DidCompleteSwapBuffers() {
+ client_->OnSwapBuffersCompleted();
+}
+
+void CompositorImpl::ScheduleComposite() {
+ client_->ScheduleComposite();
+}
+
+scoped_refptr<cc::ContextProvider>
+CompositorImpl::OffscreenContextProviderForMainThread() {
+ // There is no support for offscreen contexts, or compositor filters that
+ // would require them in this compositor instance. If they are needed,
+ // then implement a context provider that provides contexts from
+ // ImageTransportSurfaceAndroid.
+ return NULL;
+}
+
+scoped_refptr<cc::ContextProvider>
+CompositorImpl::OffscreenContextProviderForCompositorThread() {
+ // There is no support for offscreen contexts, or compositor filters that
+ // would require them in this compositor instance. If they are needed,
+ // then implement a context provider that provides contexts from
+ // ImageTransportSurfaceAndroid.
+ return NULL;
+}
+
+void CompositorImpl::OnViewContextSwapBuffersPosted() {
+ TRACE_EVENT0("compositor", "CompositorImpl::OnViewContextSwapBuffersPosted");
+ client_->OnSwapBuffersPosted();
+}
+
+void CompositorImpl::OnViewContextSwapBuffersComplete() {
+ TRACE_EVENT0("compositor",
+ "CompositorImpl::OnViewContextSwapBuffersComplete");
+ client_->OnSwapBuffersCompleted();
+}
+
+void CompositorImpl::OnViewContextSwapBuffersAborted() {
+ TRACE_EVENT0("compositor", "CompositorImpl::OnViewContextSwapBuffersAborted");
+ client_->OnSwapBuffersCompleted();
+}
+
+WebKit::WebGLId CompositorImpl::BuildBasicTexture() {
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ if (context->isContextLost() || !context->makeContextCurrent())
+ return 0;
+ WebKit::WebGLId texture_id = context->createTexture();
+ context->bindTexture(GL_TEXTURE_2D, texture_id);
+ context->texParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ context->texParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ context->texParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ context->texParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ return texture_id;
+}
+
+WebKit::WGC3Denum CompositorImpl::GetGLFormatForBitmap(
+ gfx::JavaBitmap& bitmap) {
+ switch (bitmap.format()) {
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return GL_ALPHA;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return GL_RGBA;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return GL_RGBA;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ default:
+ return GL_RGB;
+ }
+}
+
+WebKit::WGC3Denum CompositorImpl::GetGLTypeForBitmap(gfx::JavaBitmap& bitmap) {
+ switch (bitmap.format()) {
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return GL_UNSIGNED_BYTE;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return GL_UNSIGNED_BYTE;
+ break;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ default:
+ return GL_UNSIGNED_SHORT_5_6_5;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/compositor_impl_android.h b/chromium/content/browser/renderer_host/compositor_impl_android.h
new file mode 100644
index 00000000000..845e830aad4
--- /dev/null
+++ b/chromium/content/browser/renderer_host/compositor_impl_android.h
@@ -0,0 +1,125 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_COMPOSITOR_IMPL_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITOR_IMPL_ANDROID_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/trees/layer_tree_host_client.h"
+#include "content/browser/renderer_host/image_transport_factory_android.h"
+#include "content/common/content_export.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/public/browser/android/compositor.h"
+
+struct ANativeWindow;
+
+namespace cc {
+class InputHandlerClient;
+class Layer;
+class LayerTreeHost;
+}
+
+namespace content {
+class CompositorClient;
+class GraphicsContext;
+
+// -----------------------------------------------------------------------------
+// Browser-side compositor that manages a tree of content and UI layers.
+// -----------------------------------------------------------------------------
+class CONTENT_EXPORT CompositorImpl
+ : public Compositor,
+ public cc::LayerTreeHostClient,
+ public WebGraphicsContext3DSwapBuffersClient,
+ public ImageTransportFactoryAndroidObserver {
+ public:
+ explicit CompositorImpl(CompositorClient* client);
+ virtual ~CompositorImpl();
+
+ static bool IsInitialized();
+ static bool IsThreadingEnabled();
+
+ // Returns true if initialized with DIRECT_CONTEXT_ON_DRAW_THREAD.
+ static bool UsesDirectGL();
+
+ // Returns the Java Surface object for a given view surface id.
+ static jobject GetSurface(int surface_id);
+
+ // Compositor implementation.
+ virtual void SetRootLayer(scoped_refptr<cc::Layer> root) OVERRIDE;
+ virtual void SetWindowSurface(ANativeWindow* window) OVERRIDE;
+ virtual void SetSurface(jobject surface) OVERRIDE;
+ virtual void SetVisible(bool visible) OVERRIDE;
+ virtual void setDeviceScaleFactor(float factor) OVERRIDE;
+ virtual void SetWindowBounds(const gfx::Size& size) OVERRIDE;
+ virtual void SetHasTransparentBackground(bool flag) OVERRIDE;
+ virtual bool CompositeAndReadback(
+ void *pixels, const gfx::Rect& rect) OVERRIDE;
+ virtual void SetNeedsRedraw() OVERRIDE;
+ virtual void Composite() OVERRIDE;
+ virtual WebKit::WebGLId GenerateTexture(gfx::JavaBitmap& bitmap) OVERRIDE;
+ virtual WebKit::WebGLId GenerateCompressedTexture(
+ gfx::Size& size, int data_size, void* data) OVERRIDE;
+ virtual void DeleteTexture(WebKit::WebGLId texture_id) OVERRIDE;
+ virtual bool CopyTextureToBitmap(WebKit::WebGLId texture_id,
+ gfx::JavaBitmap& bitmap) OVERRIDE;
+ virtual bool CopyTextureToBitmap(WebKit::WebGLId texture_id,
+ const gfx::Rect& sub_rect,
+ gfx::JavaBitmap& bitmap) OVERRIDE;
+
+ // LayerTreeHostClient implementation.
+ virtual void WillBeginFrame() OVERRIDE {}
+ virtual void DidBeginFrame() OVERRIDE {}
+ virtual void Animate(double frame_begin_time) OVERRIDE {}
+ virtual void Layout() OVERRIDE {}
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float page_scale) OVERRIDE {}
+ virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE;
+ virtual void DidInitializeOutputSurface(bool success) OVERRIDE {}
+ virtual void WillCommit() OVERRIDE {}
+ virtual void DidCommit() OVERRIDE {}
+ virtual void DidCommitAndDrawFrame() OVERRIDE {}
+ virtual void DidCompleteSwapBuffers() OVERRIDE;
+ virtual void ScheduleComposite() OVERRIDE;
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForMainThread() OVERRIDE;
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForCompositorThread() OVERRIDE;
+
+ // WebGraphicsContext3DSwapBuffersClient implementation.
+ virtual void OnViewContextSwapBuffersPosted() OVERRIDE;
+ virtual void OnViewContextSwapBuffersComplete() OVERRIDE;
+ virtual void OnViewContextSwapBuffersAborted() OVERRIDE;
+
+ // ImageTransportFactoryAndroidObserver implementation.
+ virtual void OnLostResources() OVERRIDE;
+
+ private:
+ WebKit::WebGLId BuildBasicTexture();
+ WebKit::WGC3Denum GetGLFormatForBitmap(gfx::JavaBitmap& bitmap);
+ WebKit::WGC3Denum GetGLTypeForBitmap(gfx::JavaBitmap& bitmap);
+
+ scoped_refptr<cc::Layer> root_layer_;
+ scoped_ptr<cc::LayerTreeHost> host_;
+
+ gfx::Size size_;
+ bool has_transparent_background_;
+
+ ANativeWindow* window_;
+ int surface_id_;
+
+ CompositorClient* client_;
+ base::WeakPtrFactory<CompositorImpl> weak_factory_;
+
+ scoped_refptr<cc::ContextProvider> null_offscreen_context_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositorImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITOR_IMPL_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/database_message_filter.cc b/chromium/content/browser/renderer_host/database_message_filter.cc
new file mode 100644
index 00000000000..38cc49a844b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/database_message_filter.cc
@@ -0,0 +1,362 @@
+// 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 "content/browser/renderer_host/database_message_filter.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/platform_file.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "content/common/database_messages.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/result_codes.h"
+#include "third_party/sqlite/sqlite3.h"
+#include "webkit/browser/database/database_util.h"
+#include "webkit/browser/database/vfs_backend.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/database/database_identifier.h"
+
+#if defined(OS_POSIX)
+#include "base/file_descriptor_posix.h"
+#endif
+
+using quota::QuotaManager;
+using quota::QuotaManagerProxy;
+using quota::QuotaStatusCode;
+using webkit_database::DatabaseTracker;
+using webkit_database::DatabaseUtil;
+using webkit_database::VfsBackend;
+
+namespace content {
+namespace {
+
+const int kNumDeleteRetries = 2;
+const int kDelayDeleteRetryMs = 100;
+
+} // namespace
+
+DatabaseMessageFilter::DatabaseMessageFilter(
+ webkit_database::DatabaseTracker* db_tracker)
+ : db_tracker_(db_tracker),
+ observer_added_(false) {
+ DCHECK(db_tracker_.get());
+}
+
+void DatabaseMessageFilter::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+ if (observer_added_) {
+ observer_added_ = false;
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DatabaseMessageFilter::RemoveObserver, this));
+ }
+}
+
+void DatabaseMessageFilter::AddObserver() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ db_tracker_->AddObserver(this);
+}
+
+void DatabaseMessageFilter::RemoveObserver() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ db_tracker_->RemoveObserver(this);
+
+ // If the renderer process died without closing all databases,
+ // then we need to manually close those connections
+ db_tracker_->CloseDatabases(database_connections_);
+ database_connections_.RemoveAllConnections();
+}
+
+void DatabaseMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ if (message.type() == DatabaseHostMsg_GetSpaceAvailable::ID)
+ *thread = BrowserThread::IO;
+ else if (IPC_MESSAGE_CLASS(message) == DatabaseMsgStart)
+ *thread = BrowserThread::FILE;
+
+ if (message.type() == DatabaseHostMsg_Opened::ID && !observer_added_) {
+ observer_added_ = true;
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DatabaseMessageFilter::AddObserver, this));
+ }
+}
+
+bool DatabaseMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(DatabaseMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_OpenFile,
+ OnDatabaseOpenFile)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_DeleteFile,
+ OnDatabaseDeleteFile)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileAttributes,
+ OnDatabaseGetFileAttributes)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileSize,
+ OnDatabaseGetFileSize)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetSpaceAvailable,
+ OnDatabaseGetSpaceAvailable)
+ IPC_MESSAGE_HANDLER(DatabaseHostMsg_Opened, OnDatabaseOpened)
+ IPC_MESSAGE_HANDLER(DatabaseHostMsg_Modified, OnDatabaseModified)
+ IPC_MESSAGE_HANDLER(DatabaseHostMsg_Closed, OnDatabaseClosed)
+ IPC_MESSAGE_HANDLER(DatabaseHostMsg_HandleSqliteError, OnHandleSqliteError)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+DatabaseMessageFilter::~DatabaseMessageFilter() {
+}
+
+void DatabaseMessageFilter::OnDatabaseOpenFile(const string16& vfs_file_name,
+ int desired_flags,
+ IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::PlatformFile file_handle = base::kInvalidPlatformFileValue;
+ std::string origin_identifier;
+ string16 database_name;
+
+ // When in incognito mode, we want to make sure that all DB files are
+ // removed when the incognito browser context goes away, so we add the
+ // SQLITE_OPEN_DELETEONCLOSE flag when opening all files, and keep
+ // open handles to them in the database tracker to make sure they're
+ // around for as long as needed.
+ if (vfs_file_name.empty()) {
+ VfsBackend::OpenTempFileInDirectory(db_tracker_->DatabaseDirectory(),
+ desired_flags, &file_handle);
+ } else if (DatabaseUtil::CrackVfsFileName(vfs_file_name, &origin_identifier,
+ &database_name, NULL) &&
+ !db_tracker_->IsDatabaseScheduledForDeletion(origin_identifier,
+ database_name)) {
+ base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(
+ db_tracker_.get(), vfs_file_name);
+ if (!db_file.empty()) {
+ if (db_tracker_->IsIncognitoProfile()) {
+ db_tracker_->GetIncognitoFileHandle(vfs_file_name, &file_handle);
+ if (file_handle == base::kInvalidPlatformFileValue) {
+ VfsBackend::OpenFile(db_file,
+ desired_flags | SQLITE_OPEN_DELETEONCLOSE,
+ &file_handle);
+ if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE))
+ db_tracker_->SaveIncognitoFileHandle(vfs_file_name, file_handle);
+ }
+ } else {
+ VfsBackend::OpenFile(db_file, desired_flags, &file_handle);
+ }
+ }
+ }
+
+ // Then we duplicate the file handle to make it useable in the renderer
+ // process. The original handle is closed, unless we saved it in the
+ // database tracker.
+ bool auto_close = !db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name);
+ IPC::PlatformFileForTransit target_handle =
+ IPC::GetFileHandleForProcess(file_handle, PeerHandle(), auto_close);
+
+ DatabaseHostMsg_OpenFile::WriteReplyParams(reply_msg, target_handle);
+ Send(reply_msg);
+}
+
+void DatabaseMessageFilter::OnDatabaseDeleteFile(const string16& vfs_file_name,
+ const bool& sync_dir,
+ IPC::Message* reply_msg) {
+ DatabaseDeleteFile(vfs_file_name, sync_dir, reply_msg, kNumDeleteRetries);
+}
+
+void DatabaseMessageFilter::DatabaseDeleteFile(const string16& vfs_file_name,
+ bool sync_dir,
+ IPC::Message* reply_msg,
+ int reschedule_count) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ // Return an error if the file name is invalid or if the file could not
+ // be deleted after kNumDeleteRetries attempts.
+ int error_code = SQLITE_IOERR_DELETE;
+ base::FilePath db_file =
+ DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
+ if (!db_file.empty()) {
+ // In order to delete a journal file in incognito mode, we only need to
+ // close the open handle to it that's stored in the database tracker.
+ if (db_tracker_->IsIncognitoProfile()) {
+ const string16 wal_suffix(ASCIIToUTF16("-wal"));
+ string16 sqlite_suffix;
+
+ // WAL files can be deleted without having previously been opened.
+ if (!db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name) &&
+ DatabaseUtil::CrackVfsFileName(vfs_file_name,
+ NULL, NULL, &sqlite_suffix) &&
+ sqlite_suffix == wal_suffix) {
+ error_code = SQLITE_OK;
+ } else if (db_tracker_->CloseIncognitoFileHandle(vfs_file_name)) {
+ error_code = SQLITE_OK;
+ }
+ } else {
+ error_code = VfsBackend::DeleteFile(db_file, sync_dir);
+ }
+
+ if ((error_code == SQLITE_IOERR_DELETE) && reschedule_count) {
+ // If the file could not be deleted, try again.
+ BrowserThread::PostDelayedTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DatabaseMessageFilter::DatabaseDeleteFile, this,
+ vfs_file_name, sync_dir, reply_msg, reschedule_count - 1),
+ base::TimeDelta::FromMilliseconds(kDelayDeleteRetryMs));
+ return;
+ }
+ }
+
+ DatabaseHostMsg_DeleteFile::WriteReplyParams(reply_msg, error_code);
+ Send(reply_msg);
+}
+
+void DatabaseMessageFilter::OnDatabaseGetFileAttributes(
+ const string16& vfs_file_name,
+ IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ int32 attributes = -1;
+ base::FilePath db_file =
+ DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
+ if (!db_file.empty())
+ attributes = VfsBackend::GetFileAttributes(db_file);
+
+ DatabaseHostMsg_GetFileAttributes::WriteReplyParams(
+ reply_msg, attributes);
+ Send(reply_msg);
+}
+
+void DatabaseMessageFilter::OnDatabaseGetFileSize(
+ const string16& vfs_file_name, IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ int64 size = 0;
+ base::FilePath db_file =
+ DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
+ if (!db_file.empty())
+ size = VfsBackend::GetFileSize(db_file);
+
+ DatabaseHostMsg_GetFileSize::WriteReplyParams(reply_msg, size);
+ Send(reply_msg);
+}
+
+void DatabaseMessageFilter::OnDatabaseGetSpaceAvailable(
+ const std::string& origin_identifier, IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(db_tracker_->quota_manager_proxy());
+
+ QuotaManager* quota_manager =
+ db_tracker_->quota_manager_proxy()->quota_manager();
+ if (!quota_manager) {
+ NOTREACHED(); // The system is shutting down, messages are unexpected.
+ DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(
+ reply_msg, static_cast<int64>(0));
+ Send(reply_msg);
+ return;
+ }
+
+ quota_manager->GetUsageAndQuota(
+ webkit_database::GetOriginFromIdentifier(origin_identifier),
+ quota::kStorageTypeTemporary,
+ base::Bind(&DatabaseMessageFilter::OnDatabaseGetUsageAndQuota,
+ this, reply_msg));
+}
+
+void DatabaseMessageFilter::OnDatabaseGetUsageAndQuota(
+ IPC::Message* reply_msg,
+ quota::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ int64 available = 0;
+ if ((status == quota::kQuotaStatusOk) && (usage < quota))
+ available = quota - usage;
+ DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(reply_msg, available);
+ Send(reply_msg);
+}
+
+void DatabaseMessageFilter::OnDatabaseOpened(
+ const std::string& origin_identifier,
+ const string16& database_name,
+ const string16& description,
+ int64 estimated_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
+ BadMessageReceived();
+ return;
+ }
+
+ int64 database_size = 0;
+ db_tracker_->DatabaseOpened(origin_identifier, database_name, description,
+ estimated_size, &database_size);
+ database_connections_.AddConnection(origin_identifier, database_name);
+ Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
+ database_size));
+}
+
+void DatabaseMessageFilter::OnDatabaseModified(
+ const std::string& origin_identifier,
+ const string16& database_name) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (!database_connections_.IsDatabaseOpened(
+ origin_identifier, database_name)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
+ BadMessageReceived();
+ return;
+ }
+
+ db_tracker_->DatabaseModified(origin_identifier, database_name);
+}
+
+void DatabaseMessageFilter::OnDatabaseClosed(
+ const std::string& origin_identifier,
+ const string16& database_name) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (!database_connections_.IsDatabaseOpened(
+ origin_identifier, database_name)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
+ BadMessageReceived();
+ return;
+ }
+
+ database_connections_.RemoveConnection(origin_identifier, database_name);
+ db_tracker_->DatabaseClosed(origin_identifier, database_name);
+}
+
+void DatabaseMessageFilter::OnHandleSqliteError(
+ const std::string& origin_identifier,
+ const string16& database_name,
+ int error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
+ BadMessageReceived();
+ return;
+ }
+
+ db_tracker_->HandleSqliteError(origin_identifier, database_name, error);
+}
+
+void DatabaseMessageFilter::OnDatabaseSizeChanged(
+ const std::string& origin_identifier,
+ const string16& database_name,
+ int64 database_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (database_connections_.IsOriginUsed(origin_identifier)) {
+ Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
+ database_size));
+ }
+}
+
+void DatabaseMessageFilter::OnDatabaseScheduledForDeletion(
+ const std::string& origin_identifier,
+ const string16& database_name) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ Send(new DatabaseMsg_CloseImmediately(origin_identifier, database_name));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/database_message_filter.h b/chromium/content/browser/renderer_host/database_message_filter.h
new file mode 100644
index 00000000000..9e168c5404d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/database_message_filter.h
@@ -0,0 +1,102 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/strings/string16.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "webkit/browser/database/database_tracker.h"
+#include "webkit/common/database/database_connections.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace content {
+
+class DatabaseMessageFilter
+ : public BrowserMessageFilter,
+ public webkit_database::DatabaseTracker::Observer {
+ public:
+ explicit DatabaseMessageFilter(webkit_database::DatabaseTracker* db_tracker);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ webkit_database::DatabaseTracker* database_tracker() const {
+ return db_tracker_.get();
+ }
+
+ private:
+ virtual ~DatabaseMessageFilter();
+
+ class PromptDelegate;
+
+ void AddObserver();
+ void RemoveObserver();
+
+ // VFS message handlers (file thread)
+ void OnDatabaseOpenFile(const string16& vfs_file_name,
+ int desired_flags,
+ IPC::Message* reply_msg);
+ void OnDatabaseDeleteFile(const string16& vfs_file_name,
+ const bool& sync_dir,
+ IPC::Message* reply_msg);
+ void OnDatabaseGetFileAttributes(const string16& vfs_file_name,
+ IPC::Message* reply_msg);
+ void OnDatabaseGetFileSize(const string16& vfs_file_name,
+ IPC::Message* reply_msg);
+
+ // Quota message handler (io thread)
+ void OnDatabaseGetSpaceAvailable(const std::string& origin_identifier,
+ IPC::Message* reply_msg);
+ void OnDatabaseGetUsageAndQuota(IPC::Message* reply_msg,
+ quota::QuotaStatusCode status,
+ int64 usage,
+ int64 quota);
+
+ // Database tracker message handlers (file thread)
+ void OnDatabaseOpened(const std::string& origin_identifier,
+ const string16& database_name,
+ const string16& description,
+ int64 estimated_size);
+ void OnDatabaseModified(const std::string& origin_identifier,
+ const string16& database_name);
+ void OnDatabaseClosed(const std::string& origin_identifier,
+ const string16& database_name);
+ void OnHandleSqliteError(const std::string& origin_identifier,
+ const string16& database_name,
+ int error);
+
+ // DatabaseTracker::Observer callbacks (file thread)
+ virtual void OnDatabaseSizeChanged(const std::string& origin_identifier,
+ const string16& database_name,
+ int64 database_size) OVERRIDE;
+ virtual void OnDatabaseScheduledForDeletion(
+ const std::string& origin_identifier,
+ const string16& database_name) OVERRIDE;
+
+ void DatabaseDeleteFile(const string16& vfs_file_name,
+ bool sync_dir,
+ IPC::Message* reply_msg,
+ int reschedule_count);
+
+ // The database tracker for the current browser context.
+ scoped_refptr<webkit_database::DatabaseTracker> db_tracker_;
+
+ // True if and only if this instance was added as an observer
+ // to DatabaseTracker.
+ bool observer_added_;
+
+ // Keeps track of all DB connections opened by this renderer
+ webkit_database::DatabaseConnections database_connections_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/dip_util.cc b/chromium/content/browser/renderer_host/dip_util.cc
new file mode 100644
index 00000000000..8ef2d6c4661
--- /dev/null
+++ b/chromium/content/browser/renderer_host/dip_util.cc
@@ -0,0 +1,66 @@
+// 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 "content/browser/renderer_host/dip_util.h"
+
+#include "content/public/browser/render_widget_host_view.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace content {
+namespace {
+
+float GetScaleForView(const RenderWidgetHostView* view) {
+ return ui::GetScaleFactorScale(GetScaleFactorForView(view));
+}
+
+} // namespace
+
+ui::ScaleFactor GetScaleFactorForView(const RenderWidgetHostView* view) {
+ return ui::GetScaleFactorForNativeView(view ? view->GetNativeView() : NULL);
+}
+
+gfx::Point ConvertViewPointToDIP(const RenderWidgetHostView* view,
+ const gfx::Point& point_in_pixel) {
+ return gfx::ToFlooredPoint(
+ gfx::ScalePoint(point_in_pixel, 1.0f / GetScaleForView(view)));
+}
+
+gfx::Size ConvertViewSizeToPixel(const RenderWidgetHostView* view,
+ const gfx::Size& size_in_dip) {
+ return gfx::ToFlooredSize(
+ gfx::ScaleSize(size_in_dip, GetScaleForView(view)));
+}
+
+gfx::Rect ConvertViewRectToPixel(const RenderWidgetHostView* view,
+ const gfx::Rect& rect_in_dip) {
+ return ConvertRectToPixel(GetScaleForView(view), rect_in_dip);
+}
+
+gfx::Size ConvertSizeToDIP(float scale_factor,
+ const gfx::Size& size_in_pixel) {
+ return gfx::ToFlooredSize(
+ gfx::ScaleSize(size_in_pixel, 1.0f / scale_factor));
+}
+
+gfx::Rect ConvertRectToDIP(float scale_factor,
+ const gfx::Rect& rect_in_pixel) {
+ return gfx::ToFlooredRectDeprecated(
+ gfx::ScaleRect(rect_in_pixel, 1.0f / scale_factor));
+}
+
+
+gfx::Rect ConvertRectToPixel(float scale_factor,
+ const gfx::Rect& rect_in_dip) {
+ return gfx::ToFlooredRectDeprecated(
+ gfx::ScaleRect(rect_in_dip, scale_factor));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/dip_util.h b/chromium/content/browser/renderer_host/dip_util.h
new file mode 100644
index 00000000000..a7e1876ec79
--- /dev/null
+++ b/chromium/content/browser/renderer_host/dip_util.h
@@ -0,0 +1,43 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_DIP_UTIL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_DIP_UTIL_H_
+
+#include "content/common/content_export.h"
+#include "ui/base/layout.h"
+
+namespace gfx {
+class Point;
+class Rect;
+class Size;
+} // namespace gfx
+
+namespace content {
+class RenderWidgetHostView;
+
+// Returns scale factor of the display nearest to |view|.
+// Returns ui::SCALE_FACTOR_100P if the platform does not support DIP.
+CONTENT_EXPORT ui::ScaleFactor GetScaleFactorForView(
+ const RenderWidgetHostView* view);
+
+// Utility functions that convert point/size/rect between DIP and pixel
+// coordinate system.
+CONTENT_EXPORT gfx::Point ConvertViewPointToDIP(
+ const RenderWidgetHostView* view, const gfx::Point& point_in_pixel);
+CONTENT_EXPORT gfx::Size ConvertViewSizeToPixel(
+ const RenderWidgetHostView* view, const gfx::Size& size_in_dip);
+CONTENT_EXPORT gfx::Rect ConvertViewRectToPixel(
+ const RenderWidgetHostView* view, const gfx::Rect& rect_in_dip);
+
+CONTENT_EXPORT gfx::Size ConvertSizeToDIP(
+ float scale_factor, const gfx::Size& size_in_pixel);
+CONTENT_EXPORT gfx::Rect ConvertRectToDIP(
+ float scale_factor, const gfx::Rect& rect_in_pixel);
+CONTENT_EXPORT gfx::Rect ConvertRectToPixel(
+ float scale_factor, const gfx::Rect& rect_in_dip);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_DIP_UTIL_H_
diff --git a/chromium/content/browser/renderer_host/file_utilities_message_filter.cc b/chromium/content/browser/renderer_host/file_utilities_message_filter.cc
new file mode 100644
index 00000000000..65645e014ef
--- /dev/null
+++ b/chromium/content/browser/renderer_host/file_utilities_message_filter.cc
@@ -0,0 +1,55 @@
+// 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 "content/browser/renderer_host/file_utilities_message_filter.h"
+
+#include "base/file_util.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/common/file_utilities_messages.h"
+
+namespace content {
+
+FileUtilitiesMessageFilter::FileUtilitiesMessageFilter(int process_id)
+ : process_id_(process_id) {
+}
+
+FileUtilitiesMessageFilter::~FileUtilitiesMessageFilter() {
+}
+
+void FileUtilitiesMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ if (IPC_MESSAGE_CLASS(message) == FileUtilitiesMsgStart)
+ *thread = BrowserThread::FILE;
+}
+
+bool FileUtilitiesMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(FileUtilitiesMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(FileUtilitiesMsg_GetFileInfo, OnGetFileInfo)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void FileUtilitiesMessageFilter::OnGetFileInfo(
+ const base::FilePath& path,
+ base::PlatformFileInfo* result,
+ base::PlatformFileError* status) {
+ *result = base::PlatformFileInfo();
+ *status = base::PLATFORM_FILE_OK;
+
+ // Get file metadata only when the child process has been granted
+ // permission to read the file.
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
+ process_id_, path)) {
+ return;
+ }
+
+ if (!file_util::GetFileInfo(path, result))
+ *status = base::PLATFORM_FILE_ERROR_FAILED;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/file_utilities_message_filter.h b/chromium/content/browser/renderer_host/file_utilities_message_filter.h
new file mode 100644
index 00000000000..948e6c20b4a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/file_utilities_message_filter.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "ipc/ipc_platform_file.h"
+
+namespace base {
+struct PlatformFileInfo;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace content {
+
+class FileUtilitiesMessageFilter : public BrowserMessageFilter {
+ public:
+ explicit FileUtilitiesMessageFilter(int process_id);
+
+ // BrowserMessageFilter implementation.
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ private:
+ virtual ~FileUtilitiesMessageFilter();
+
+ typedef void (*FileInfoWriteFunc)(IPC::Message* reply_msg,
+ const base::PlatformFileInfo& file_info);
+
+ void OnGetFileInfo(const base::FilePath& path,
+ base::PlatformFileInfo* result,
+ base::PlatformFileError* status);
+
+ // The ID of this process.
+ int process_id_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtilitiesMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/gamepad_browser_message_filter.cc b/chromium/content/browser/renderer_host/gamepad_browser_message_filter.cc
new file mode 100644
index 00000000000..6b5972e5a0a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gamepad_browser_message_filter.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/gamepad_browser_message_filter.h"
+
+#include "content/browser/gamepad/gamepad_service.h"
+#include "content/common/gamepad_messages.h"
+
+namespace content {
+
+GamepadBrowserMessageFilter::GamepadBrowserMessageFilter()
+ : is_started_(false) {
+}
+
+GamepadBrowserMessageFilter::~GamepadBrowserMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (is_started_)
+ GamepadService::GetInstance()->RemoveConsumer();
+}
+
+bool GamepadBrowserMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(GamepadBrowserMessageFilter,
+ message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(GamepadHostMsg_StartPolling, OnGamepadStartPolling)
+ IPC_MESSAGE_HANDLER(GamepadHostMsg_StopPolling, OnGamepadStopPolling)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void GamepadBrowserMessageFilter::OnGamepadStartPolling(
+ base::SharedMemoryHandle* renderer_handle) {
+ GamepadService* service = GamepadService::GetInstance();
+ if (!is_started_) {
+ is_started_ = true;
+ service->AddConsumer();
+ *renderer_handle = service->GetSharedMemoryHandleForProcess(PeerHandle());
+ } else {
+ // Currently we only expect the renderer to tell us once to start.
+ NOTREACHED();
+ }
+}
+
+void GamepadBrowserMessageFilter::OnGamepadStopPolling() {
+ // TODO(scottmg): Probably get rid of this message. We can't trust it will
+ // arrive anyway if the renderer crashes, etc.
+ if (is_started_) {
+ is_started_ = false;
+ GamepadService::GetInstance()->RemoveConsumer();
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gamepad_browser_message_filter.h b/chromium/content/browser/renderer_host/gamepad_browser_message_filter.h
new file mode 100644
index 00000000000..8661e71d7dc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gamepad_browser_message_filter.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_GAMEPAD_BROWSER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GAMEPAD_BROWSER_MESSAGE_FILTER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/shared_memory.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class GamepadService;
+class RenderProcessHost;
+
+class GamepadBrowserMessageFilter : public BrowserMessageFilter {
+ public:
+ GamepadBrowserMessageFilter();
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~GamepadBrowserMessageFilter();
+
+ void OnGamepadStartPolling(base::SharedMemoryHandle* renderer_handle);
+ void OnGamepadStopPolling();
+
+ bool is_started_;
+
+ DISALLOW_COPY_AND_ASSIGN(GamepadBrowserMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GAMEPAD_BROWSER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/gpu_message_filter.cc b/chromium/content/browser/renderer_host/gpu_message_filter.cc
new file mode 100644
index 00000000000..8ab2280a0aa
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gpu_message_filter.cc
@@ -0,0 +1,298 @@
+// 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.
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "content/browser/renderer_host/gpu_message_filter.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/public/common/content_switches.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+
+namespace content {
+
+struct GpuMessageFilter::CreateViewCommandBufferRequest {
+ CreateViewCommandBufferRequest(
+ int32 surface_id_,
+ const GPUCreateCommandBufferConfig& init_params_,
+ IPC::Message* reply_)
+ : surface_id(surface_id_),
+ init_params(init_params_),
+ reply(reply_) {
+ }
+ int32 surface_id;
+ GPUCreateCommandBufferConfig init_params;
+ IPC::Message* reply;
+};
+
+struct GpuMessageFilter::FrameSubscription {
+ FrameSubscription(
+ int in_route_id,
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> in_subscriber)
+ : route_id(in_route_id),
+ surface_id(0),
+ subscriber(in_subscriber.Pass()),
+ factory(subscriber.get()) {
+ }
+
+ int route_id;
+ int surface_id;
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber;
+ base::WeakPtrFactory<RenderWidgetHostViewFrameSubscriber> factory;
+};
+
+GpuMessageFilter::GpuMessageFilter(int render_process_id,
+ RenderWidgetHelper* render_widget_helper)
+ : gpu_process_id_(0),
+ render_process_id_(render_process_id),
+ share_contexts_(false),
+ render_widget_helper_(render_widget_helper),
+ weak_ptr_factory_(this) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+#if defined(USE_AURA) || defined(OS_ANDROID)
+ // We use the GPU process for UI on Aura, and we need to share renderer GL
+ // contexts with the compositor context.
+ share_contexts_ = true;
+#else
+ // Share contexts when compositing webview plugin or using share groups
+ // for asynchronous texture uploads.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableBrowserPluginCompositing) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableShareGroupAsyncTextureUpload))
+ share_contexts_ = true;
+#endif
+}
+
+GpuMessageFilter::~GpuMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ EndAllFrameSubscriptions();
+}
+
+bool GpuMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(GpuMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_EstablishGpuChannel,
+ OnEstablishGpuChannel)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_CreateViewCommandBuffer,
+ OnCreateViewCommandBuffer)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void GpuMessageFilter::SurfaceUpdated(int32 surface_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ typedef std::vector<linked_ptr<CreateViewCommandBufferRequest> > RequestList;
+ RequestList retry_requests;
+ retry_requests.swap(pending_requests_);
+ for (RequestList::iterator it = retry_requests.begin();
+ it != retry_requests.end(); ++it) {
+ if ((*it)->surface_id != surface_id) {
+ pending_requests_.push_back(*it);
+ } else {
+ linked_ptr<CreateViewCommandBufferRequest> request = *it;
+ OnCreateViewCommandBuffer(request->surface_id,
+ request->init_params,
+ request->reply);
+ }
+ }
+}
+
+void GpuMessageFilter::BeginFrameSubscription(
+ int route_id,
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ linked_ptr<FrameSubscription> subscription(
+ new FrameSubscription(route_id, subscriber.Pass()));
+ BeginFrameSubscriptionInternal(subscription);
+}
+
+void GpuMessageFilter::EndFrameSubscription(int route_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ FrameSubscriptionList frame_subscription_list;
+ frame_subscription_list.swap(frame_subscription_list_);
+ for (FrameSubscriptionList::iterator it = frame_subscription_list.begin();
+ it != frame_subscription_list.end(); ++it) {
+ if ((*it)->route_id != route_id)
+ frame_subscription_list_.push_back(*it);
+ else
+ EndFrameSubscriptionInternal(*it);
+ }
+}
+
+void GpuMessageFilter::OnEstablishGpuChannel(
+ CauseForGpuLaunch cause_for_gpu_launch,
+ IPC::Message* reply) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // TODO(apatrick): Eventually, this will return the route ID of a
+ // GpuProcessStub, from which the renderer process will create a
+ // GpuProcessProxy. The renderer will use the proxy for all subsequent
+ // communication with the GPU process. This means if the GPU process
+ // terminates, the renderer process will not find itself unknowingly sending
+ // IPCs to a newly launched GPU process. Also, I will rename this function
+ // to something like OnCreateGpuProcess.
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
+ if (!host) {
+ host = GpuProcessHost::Get(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
+ cause_for_gpu_launch);
+ if (!host) {
+ reply->set_reply_error();
+ Send(reply);
+ return;
+ }
+
+ gpu_process_id_ = host->host_id();
+
+ // Apply all frame subscriptions to the new GpuProcessHost.
+ BeginAllFrameSubscriptions();
+ }
+
+ host->EstablishGpuChannel(
+ render_process_id_,
+ share_contexts_,
+ base::Bind(&GpuMessageFilter::EstablishChannelCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ reply));
+}
+
+void GpuMessageFilter::OnCreateViewCommandBuffer(
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params,
+ IPC::Message* reply) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ GpuSurfaceTracker* surface_tracker = GpuSurfaceTracker::Get();
+ gfx::GLSurfaceHandle compositing_surface;
+
+ int renderer_id = 0;
+ int render_widget_id = 0;
+ bool result = surface_tracker->GetRenderWidgetIDForSurface(
+ surface_id, &renderer_id, &render_widget_id);
+ if (result && renderer_id == render_process_id_) {
+ compositing_surface = surface_tracker->GetSurfaceHandle(surface_id);
+ } else {
+ DLOG(ERROR) << "Renderer " << render_process_id_
+ << " tried to access a surface for renderer " << renderer_id;
+ }
+
+ if (compositing_surface.parent_gpu_process_id &&
+ compositing_surface.parent_gpu_process_id != gpu_process_id_) {
+ // If the current handle for the surface is using a different (older) gpu
+ // host, it means the GPU process died and we need to wait until the UI
+ // re-allocates the surface in the new process.
+ linked_ptr<CreateViewCommandBufferRequest> request(
+ new CreateViewCommandBufferRequest(surface_id, init_params, reply));
+ pending_requests_.push_back(request);
+ return;
+ }
+
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
+ if (!host || compositing_surface.is_null()) {
+ // TODO(apatrick): Eventually, this IPC message will be routed to a
+ // GpuProcessStub with a particular routing ID. The error will be set if
+ // the GpuProcessStub with that routing ID is not in the MessageRouter.
+ reply->set_reply_error();
+ Send(reply);
+ return;
+ }
+
+ host->CreateViewCommandBuffer(
+ compositing_surface,
+ surface_id,
+ render_process_id_,
+ init_params,
+ base::Bind(&GpuMessageFilter::CreateCommandBufferCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ reply));
+}
+
+void GpuMessageFilter::EstablishChannelCallback(
+ IPC::Message* reply,
+ const IPC::ChannelHandle& channel,
+ const gpu::GPUInfo& gpu_info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ GpuHostMsg_EstablishGpuChannel::WriteReplyParams(
+ reply, render_process_id_, channel, gpu_info);
+ Send(reply);
+}
+
+void GpuMessageFilter::CreateCommandBufferCallback(
+ IPC::Message* reply, int32 route_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GpuHostMsg_CreateViewCommandBuffer::WriteReplyParams(reply, route_id);
+ Send(reply);
+}
+
+void GpuMessageFilter::BeginAllFrameSubscriptions() {
+ FrameSubscriptionList frame_subscription_list;
+ frame_subscription_list.swap(frame_subscription_list_);
+ for (FrameSubscriptionList::iterator it = frame_subscription_list.begin();
+ it != frame_subscription_list.end(); ++it) {
+ BeginFrameSubscriptionInternal(*it);
+ }
+}
+
+void GpuMessageFilter::EndAllFrameSubscriptions() {
+ for (FrameSubscriptionList::iterator it = frame_subscription_list_.begin();
+ it != frame_subscription_list_.end(); ++it) {
+ EndFrameSubscriptionInternal(*it);
+ }
+ frame_subscription_list_.clear();
+}
+
+void GpuMessageFilter::BeginFrameSubscriptionInternal(
+ linked_ptr<FrameSubscription> subscription) {
+ if (!subscription->surface_id) {
+ GpuSurfaceTracker* surface_tracker = GpuSurfaceTracker::Get();
+ subscription->surface_id = surface_tracker->LookupSurfaceForRenderer(
+ render_process_id_, subscription->route_id);
+
+ // If the surface ID cannot be found this subscription is dropped.
+ if (!subscription->surface_id)
+ return;
+ }
+ frame_subscription_list_.push_back(subscription);
+
+ // Frame subscriber is owned by this object, but it is shared with
+ // GpuProcessHost. GpuProcessHost can be destroyed in the case of crashing
+ // and we do not get a signal. This object can also be destroyed independent
+ // of GpuProcessHost. To ensure that GpuProcessHost does not reference a
+ // deleted frame subscriber, a weak reference is shared.
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
+ if (!host)
+ return;
+ host->BeginFrameSubscription(subscription->surface_id,
+ subscription->factory.GetWeakPtr());
+}
+
+void GpuMessageFilter::EndFrameSubscriptionInternal(
+ linked_ptr<FrameSubscription> subscription) {
+ GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
+
+ // An empty surface ID means subscription has never started in GpuProcessHost
+ // so it is not necessary to end it.
+ if (!host || !subscription->surface_id)
+ return;
+
+ // Note that GpuProcessHost here might not be the same one that frame
+ // subscription has applied.
+ host->EndFrameSubscription(subscription->surface_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gpu_message_filter.h b/chromium/content/browser/renderer_host/gpu_message_filter.h
new file mode 100644
index 00000000000..bd9c10fefa5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gpu_message_filter.h
@@ -0,0 +1,97 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_
+
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/gpu/gpu_process_launch_causes.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "ui/gfx/native_widget_types.h"
+
+class GpuProcessHost;
+struct GPUCreateCommandBufferConfig;
+
+namespace gpu {
+struct GPUInfo;
+}
+
+namespace content {
+class RenderWidgetHelper;
+class RenderWidgetHostViewFrameSubscriber;
+
+// A message filter for messages from the renderer to the GpuProcessHost(UIShim)
+// in the browser. Such messages are typically destined for the GPU process,
+// but need to be mediated by the browser.
+class GpuMessageFilter : public BrowserMessageFilter {
+ public:
+ GpuMessageFilter(int render_process_id,
+ RenderWidgetHelper* render_widget_helper);
+
+ // BrowserMessageFilter methods:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // Signals that the handle for a surface id was updated, and it may be time to
+ // unblock existing CreateViewCommandBuffer requests using that surface.
+ void SurfaceUpdated(int32 surface_id);
+
+ // This set of API is used to subscribe to frame presentation events.
+ // See RenderWidgetHostViewFrameSubscriber for more details.
+ void BeginFrameSubscription(
+ int route_id,
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber);
+ void EndFrameSubscription(int route_id);
+
+ private:
+ friend class BrowserThread;
+ friend class base::DeleteHelper<GpuMessageFilter>;
+ struct CreateViewCommandBufferRequest;
+ struct FrameSubscription;
+
+ virtual ~GpuMessageFilter();
+
+ // Message handlers called on the browser IO thread:
+ void OnEstablishGpuChannel(CauseForGpuLaunch,
+ IPC::Message* reply);
+ void OnCreateViewCommandBuffer(
+ int32 surface_id,
+ const GPUCreateCommandBufferConfig& init_params,
+ IPC::Message* reply);
+ // Helper callbacks for the message handlers.
+ void EstablishChannelCallback(IPC::Message* reply,
+ const IPC::ChannelHandle& channel,
+ const gpu::GPUInfo& gpu_info);
+ void CreateCommandBufferCallback(IPC::Message* reply, int32 route_id);
+
+ void BeginAllFrameSubscriptions();
+ void EndAllFrameSubscriptions();
+ void BeginFrameSubscriptionInternal(
+ linked_ptr<FrameSubscription> subscription);
+ void EndFrameSubscriptionInternal(
+ linked_ptr<FrameSubscription> subscription);
+
+ int gpu_process_id_;
+ int render_process_id_;
+ bool share_contexts_;
+
+ scoped_refptr<RenderWidgetHelper> render_widget_helper_;
+ std::vector<linked_ptr<CreateViewCommandBufferRequest> > pending_requests_;
+
+ base::WeakPtrFactory<GpuMessageFilter> weak_ptr_factory_;
+
+ typedef std::vector<linked_ptr<FrameSubscription> > FrameSubscriptionList;
+ FrameSubscriptionList frame_subscription_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/gtk_im_context_wrapper.cc b/chromium/content/browser/renderer_host/gtk_im_context_wrapper.cc
new file mode 100644
index 00000000000..55d2e76bb30
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_im_context_wrapper.cc
@@ -0,0 +1,665 @@
+// 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 "content/browser/renderer_host/gtk_im_context_wrapper.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "ui/base/gtk/gtk_im_context_util.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+namespace {
+
+// Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp
+//
+// Match key code of composition keydown event on windows.
+// IE sends VK_PROCESSKEY which has value 229;
+//
+// Please refer to following documents for detals:
+// - Virtual-Key Codes
+// http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx
+// - How the IME System Works
+// http://msdn.microsoft.com/en-us/library/cc194848.aspx
+// - ImmGetVirtualKey Function
+// http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx
+const int kCompositionEventKeyCode = 229;
+
+} // namespace
+
+// ui::CompositionUnderline should be identical to
+// WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
+// TODO(suzhe): remove it after migrating all code in chrome to use
+// ui::CompositionUnderline.
+COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
+ sizeof(WebKit::WebCompositionUnderline),
+ ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
+
+GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view)
+ : host_view_(host_view),
+ context_(gtk_im_multicontext_new()),
+ context_simple_(gtk_im_context_simple_new()),
+ is_focused_(false),
+ is_composing_text_(false),
+ is_enabled_(false),
+ is_in_key_event_handler_(false),
+ is_composition_changed_(false),
+ suppress_next_commit_(false),
+ last_key_code_(0),
+ last_key_was_up_(false),
+ last_key_filtered_no_result_(false) {
+ DCHECK(context_);
+ DCHECK(context_simple_);
+
+ // context_ and context_simple_ share the same callback handlers.
+ // All data come from them are treated equally.
+ // context_ is for full input method support.
+ // context_simple_ is for supporting dead/compose keys when input method is
+ // disabled by webkit, eg. in password input box.
+ g_signal_connect(context_, "preedit-start",
+ G_CALLBACK(HandlePreeditStartThunk), this);
+ g_signal_connect(context_, "preedit-end",
+ G_CALLBACK(HandlePreeditEndThunk), this);
+ g_signal_connect(context_, "preedit-changed",
+ G_CALLBACK(HandlePreeditChangedThunk), this);
+ g_signal_connect(context_, "commit",
+ G_CALLBACK(HandleCommitThunk), this);
+ g_signal_connect(context_, "retrieve-surrounding",
+ G_CALLBACK(HandleRetrieveSurroundingThunk), this);
+
+ g_signal_connect(context_simple_, "preedit-start",
+ G_CALLBACK(HandlePreeditStartThunk), this);
+ g_signal_connect(context_simple_, "preedit-end",
+ G_CALLBACK(HandlePreeditEndThunk), this);
+ g_signal_connect(context_simple_, "preedit-changed",
+ G_CALLBACK(HandlePreeditChangedThunk), this);
+ g_signal_connect(context_simple_, "commit",
+ G_CALLBACK(HandleCommitThunk), this);
+ g_signal_connect(context_simple_, "retrieve-surrounding",
+ G_CALLBACK(HandleRetrieveSurroundingThunk), this);
+
+ GtkWidget* widget = host_view->GetNativeView();
+ DCHECK(widget);
+
+ g_signal_connect(widget, "realize",
+ G_CALLBACK(HandleHostViewRealizeThunk), this);
+ g_signal_connect(widget, "unrealize",
+ G_CALLBACK(HandleHostViewUnrealizeThunk), this);
+
+ // Set client window if the widget is already realized.
+ HandleHostViewRealize(widget);
+}
+
+GtkIMContextWrapper::~GtkIMContextWrapper() {
+ if (context_)
+ g_object_unref(context_);
+ if (context_simple_)
+ g_object_unref(context_simple_);
+}
+
+void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
+ suppress_next_commit_ = false;
+
+ // Indicates preedit-changed and commit signal handlers that we are
+ // processing a key event.
+ is_in_key_event_handler_ = true;
+ // Reset this flag so that we can know if preedit is changed after
+ // processing this key event.
+ is_composition_changed_ = false;
+ // Clear it so that we can know if something needs committing after
+ // processing this key event.
+ commit_text_.clear();
+
+ // According to Document Object Model (DOM) Level 3 Events Specification
+ // Appendix A: Keyboard events and key identifiers
+ // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html:
+ // The event sequence would be:
+ // 1. keydown
+ // 2. textInput
+ // 3. keyup
+ //
+ // So keydown must be sent to webkit before sending input method result,
+ // while keyup must be sent afterwards.
+ // However on Windows, if a keydown event has been processed by IME, its
+ // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent
+ // to application.
+ // To emulate the windows behavior as much as possible, we need to send the
+ // key event to the GtkIMContext object first, and decide whether or not to
+ // send the original key event to webkit according to the result from IME.
+ //
+ // If IME is enabled by WebKit, this event will be dispatched to context_
+ // to get full IME support. Otherwise it'll be dispatched to
+ // context_simple_, so that dead/compose keys can still work.
+ //
+ // It sends a "commit" signal when it has a character to be inserted
+ // even when we use a US keyboard so that we can send a Char event
+ // (or an IME event) to the renderer in our "commit"-signal handler.
+ // We should send a KeyDown (or a KeyUp) event before dispatching this
+ // event to the GtkIMContext object (and send a Char event) so that WebKit
+ // can dispatch the JavaScript events in the following order: onkeydown(),
+ // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.)
+ gboolean filtered = false;
+ if (is_enabled_) {
+ filtered = gtk_im_context_filter_keypress(context_, event);
+ } else {
+ filtered = gtk_im_context_filter_keypress(context_simple_, event);
+ }
+
+ // Reset this flag here, as it's only used in input method callbacks.
+ is_in_key_event_handler_ = false;
+
+ NativeWebKeyboardEvent wke(reinterpret_cast<GdkEvent*>(event));
+
+ // If the key event was handled by the input method, then we need to prevent
+ // RenderView::UnhandledKeyboardEvent() from processing it.
+ // Otherwise unexpected result may occur. For example if it's a
+ // Backspace key event, the browser may go back to previous page.
+ // We just send all keyup events to the browser to avoid breaking the
+ // browser's MENU key function, which is actually the only keyup event
+ // handled in the browser.
+ if (filtered && event->type == GDK_KEY_PRESS)
+ wke.skip_in_browser = true;
+
+ const int key_code = wke.windowsKeyCode;
+ const bool has_result = HasInputMethodResult();
+
+ // Send filtered keydown event before sending IME result.
+ // In order to workaround http://crosbug.com/6582, we only send a filtered
+ // keydown event if it generated any input method result.
+ if (event->type == GDK_KEY_PRESS && filtered && has_result)
+ ProcessFilteredKeyPressEvent(&wke);
+
+ // Send IME results. In most cases, it's only available if the key event
+ // is filtered by IME. But in rare cases, an unfiltered key event may also
+ // generate IME results.
+ // Any IME results generated by a unfiltered key down event must be sent
+ // before the key down event, to avoid some tricky issues. For example,
+ // when using latin-post input method, pressing 'a' then Backspace, may
+ // generate following events in sequence:
+ // 1. keydown 'a' (filtered)
+ // 2. preedit changed to "a"
+ // 3. keyup 'a' (unfiltered)
+ // 4. keydown Backspace (unfiltered)
+ // 5. commit "a"
+ // 6. preedit end
+ // 7. keyup Backspace (unfiltered)
+ //
+ // In this case, the input box will be in a strange state if keydown
+ // Backspace is sent to webkit before commit "a" and preedit end.
+ if (has_result)
+ ProcessInputMethodResult(event, filtered);
+
+ // Send unfiltered keydown and keyup events after sending IME result.
+ if (event->type == GDK_KEY_PRESS && !filtered) {
+ ProcessUnfilteredKeyPressEvent(&wke);
+ } else if (event->type == GDK_KEY_RELEASE) {
+ // In order to workaround http://crosbug.com/6582, we need to suppress
+ // the keyup event if corresponding keydown event was suppressed, or
+ // the last key event was a keyup event with the same keycode.
+ const bool suppress = (last_key_code_ == key_code) &&
+ (last_key_was_up_ || last_key_filtered_no_result_);
+
+ if (!suppress)
+ host_view_->ForwardKeyboardEvent(wke);
+ }
+
+ last_key_code_ = key_code;
+ last_key_was_up_ = (event->type == GDK_KEY_RELEASE);
+ last_key_filtered_no_result_ = (filtered && !has_result);
+}
+
+void GtkIMContextWrapper::UpdateInputMethodState(
+ ui::TextInputType type,
+ bool can_compose_inline) {
+ suppress_next_commit_ = false;
+
+ // The renderer has updated its IME status.
+ // Control the GtkIMContext object according to this status.
+ if (!context_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE &&
+ type != ui::TEXT_INPUT_TYPE_PASSWORD);
+ if (is_enabled_ != is_enabled) {
+ is_enabled_ = is_enabled;
+ if (is_enabled && is_focused_)
+ gtk_im_context_focus_in(context_);
+ else
+ gtk_im_context_focus_out(context_);
+ }
+
+ if (is_enabled) {
+ // If the focused element supports inline rendering of composition text,
+ // we receive and send related events to it. Otherwise, the events related
+ // to the updates of composition text are directed to the candidate window.
+ gtk_im_context_set_use_preedit(context_, can_compose_inline);
+ }
+}
+
+void GtkIMContextWrapper::UpdateCaretBounds(
+ const gfx::Rect& caret_bounds) {
+ if (is_enabled_) {
+ // Updates the position of the IME candidate window.
+ // The position sent from the renderer is a relative one, so we need to
+ // attach the GtkIMContext object to this window before changing the
+ // position.
+ GdkRectangle cursor_rect(caret_bounds.ToGdkRectangle());
+ gtk_im_context_set_cursor_location(context_, &cursor_rect);
+ }
+}
+
+void GtkIMContextWrapper::OnFocusIn() {
+ if (is_focused_)
+ return;
+
+ // Tracks the focused state so that we can give focus to the
+ // GtkIMContext object correctly later when IME is enabled by WebKit.
+ is_focused_ = true;
+
+ last_key_code_ = 0;
+ last_key_was_up_ = false;
+ last_key_filtered_no_result_ = false;
+
+ // Notify the GtkIMContext object of this focus-in event only if IME is
+ // enabled by WebKit.
+ if (is_enabled_)
+ gtk_im_context_focus_in(context_);
+
+ // context_simple_ is always enabled.
+ // Actually it doesn't care focus state at all.
+ gtk_im_context_focus_in(context_simple_);
+
+ // Enables RenderWidget's IME related events, so that we can be notified
+ // when WebKit wants to enable or disable IME.
+ if (host_view_->GetRenderWidgetHost()) {
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->SetInputMethodActive(true);
+ }
+}
+
+void GtkIMContextWrapper::OnFocusOut() {
+ if (!is_focused_)
+ return;
+
+ // Tracks the focused state so that we won't give focus to the
+ // GtkIMContext object unexpectly.
+ is_focused_ = false;
+
+ // Notify the GtkIMContext object of this focus-out event only if IME is
+ // enabled by WebKit.
+ if (is_enabled_) {
+ // To reset the GtkIMContext object and prevent data loss.
+ ConfirmComposition();
+ gtk_im_context_focus_out(context_);
+ }
+
+ // To make sure it'll be in correct state when focused in again.
+ gtk_im_context_reset(context_simple_);
+ gtk_im_context_focus_out(context_simple_);
+
+ is_composing_text_ = false;
+
+ // Disable RenderWidget's IME related events to save bandwidth.
+ if (host_view_->GetRenderWidgetHost()) {
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->SetInputMethodActive(false);
+ }
+}
+
+GtkWidget* GtkIMContextWrapper::BuildInputMethodsGtkMenu() {
+ GtkWidget* submenu = gtk_menu_new();
+ gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_),
+ GTK_MENU_SHELL(submenu));
+ return submenu;
+}
+
+void GtkIMContextWrapper::CancelComposition() {
+ if (!is_enabled_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ // To prevent any text from being committed when resetting the |context_|;
+ is_in_key_event_handler_ = true;
+ suppress_next_commit_ = true;
+
+ gtk_im_context_reset(context_);
+ gtk_im_context_reset(context_simple_);
+
+ if (is_focused_) {
+ // Some input methods may not honour the reset call. Focusing out/in the
+ // |context_| to make sure it gets reset correctly.
+ gtk_im_context_focus_out(context_);
+ gtk_im_context_focus_in(context_);
+ }
+
+ is_composing_text_ = false;
+ composition_.Clear();
+ commit_text_.clear();
+
+ is_in_key_event_handler_ = false;
+}
+
+bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const {
+ // If there is no composition text and has only one character to be
+ // committed, then the character will be send to webkit as a Char event
+ // instead of a confirmed composition text.
+ // It should be fine to handle BMP character only, as non-BMP characters
+ // can always be committed as confirmed composition text.
+ return !is_composing_text_ && commit_text_.length() == 1;
+}
+
+bool GtkIMContextWrapper::HasInputMethodResult() const {
+ return commit_text_.length() || is_composition_changed_;
+}
+
+void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
+ NativeWebKeyboardEvent* wke) {
+ // If IME has filtered this event, then replace virtual key code with
+ // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details.
+ // It's only required for keydown events.
+ // To emulate windows behavior, when input method is enabled, if the commit
+ // text can be emulated by a Char event, then don't do this replacement.
+ if (!NeedCommitByForwardingCharEvent()) {
+ wke->windowsKeyCode = kCompositionEventKeyCode;
+ // keyidentifier must be updated accordingly, otherwise this key event may
+ // still be processed by webkit.
+ wke->setKeyIdentifierFromWindowsKeyCode();
+ }
+ host_view_->ForwardKeyboardEvent(*wke);
+}
+
+void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
+ NativeWebKeyboardEvent* wke) {
+ // Send keydown event as it, because it's not filtered by IME.
+ host_view_->ForwardKeyboardEvent(*wke);
+
+ // IME is disabled by WebKit or the GtkIMContext object cannot handle
+ // this key event.
+ // This case is caused by two reasons:
+ // 1. The given key event is a control-key event, (e.g. return, page up,
+ // page down, tab, arrows, etc.) or;
+ // 2. The given key event is not a control-key event but printable
+ // characters aren't assigned to the event, (e.g. alt+d, etc.)
+ // Create a Char event manually from this key event and send it to the
+ // renderer when this Char event contains a printable character which
+ // should be processed by WebKit.
+ // isSystemKey will be set to true if this key event has Alt modifier,
+ // see WebInputEventFactory::keyboardEvent() for details.
+ if (wke->text[0]) {
+ wke->type = WebKit::WebInputEvent::Char;
+ wke->skip_in_browser = true;
+ host_view_->ForwardKeyboardEvent(*wke);
+ }
+}
+
+void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
+ bool filtered) {
+ RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost());
+ if (!host)
+ return;
+
+ bool committed = false;
+ // We do commit before preedit change, so that we can optimize some
+ // unnecessary preedit changes.
+ if (commit_text_.length()) {
+ if (filtered && NeedCommitByForwardingCharEvent()) {
+ // Send a Char event when we input a composed character without IMEs
+ // so that this event is to be dispatched to onkeypress() handlers,
+ // autofill, etc.
+ // Only commit text generated by a filtered key down event can be sent
+ // as a Char event, because a unfiltered key down event will probably
+ // generate another Char event.
+ // TODO(james.su@gmail.com): Is it necessary to support non BMP chars
+ // here?
+ NativeWebKeyboardEvent char_event(commit_text_[0],
+ event->state,
+ base::Time::Now().ToDoubleT());
+ char_event.skip_in_browser = true;
+ host_view_->ForwardKeyboardEvent(char_event);
+ } else {
+ committed = true;
+ // Send an IME event.
+ // Unlike a Char event, an IME event is NOT dispatched to onkeypress()
+ // handlers or autofill.
+ host->ImeConfirmComposition(
+ commit_text_,ui::Range::InvalidRange(),false);
+ // Set this flag to false, as this composition session has been
+ // finished.
+ is_composing_text_ = false;
+ }
+ }
+
+ // Send preedit text only if it's changed.
+ // If a text has been committed, then we don't need to send the empty
+ // preedit text again.
+ if (is_composition_changed_) {
+ if (composition_.text.length()) {
+ // Another composition session has been started.
+ is_composing_text_ = true;
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition_.underlines);
+ host->ImeSetComposition(composition_.text, underlines,
+ composition_.selection.start(),
+ composition_.selection.end());
+ } else if (!committed) {
+ host->ImeCancelComposition();
+ }
+ }
+}
+
+void GtkIMContextWrapper::ConfirmComposition() {
+ if (!is_enabled_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ if (is_composing_text_) {
+ if (host_view_->GetRenderWidgetHost()) {
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
+ string16(), ui::Range::InvalidRange(), false);
+ }
+
+ // Reset the input method.
+ CancelComposition();
+ }
+}
+
+void GtkIMContextWrapper::HandleCommit(const string16& text) {
+ if (suppress_next_commit_)
+ return;
+
+ // Append the text to the buffer, because commit signal might be fired
+ // multiple times when processing a key event.
+ commit_text_.append(text);
+ // Nothing needs to do, if it's currently in ProcessKeyEvent()
+ // handler, which will send commit text to webkit later. Otherwise,
+ // we need send it here.
+ // It's possible that commit signal is fired without a key event, for
+ // example when user input via a voice or handwriting recognition software.
+ // In this case, the text must be committed directly.
+ if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
+ // Workaround http://crbug.com/45478 by sending fake key down/up events.
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->ImeConfirmComposition(
+ text, ui::Range::InvalidRange(), false);
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
+ }
+}
+
+void GtkIMContextWrapper::HandlePreeditStart() {
+ // Ignore preedit related signals triggered by CancelComposition() method.
+ if (suppress_next_commit_)
+ return;
+ is_composing_text_ = true;
+}
+
+void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text,
+ PangoAttrList* attrs,
+ int cursor_position) {
+ // Ignore preedit related signals triggered by CancelComposition() method.
+ if (suppress_next_commit_)
+ return;
+
+ // Don't set is_composition_changed_ to false if there is no change, because
+ // this handler might be called multiple times with the same data.
+ is_composition_changed_ = true;
+ composition_.Clear();
+
+ ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
+ &composition_);
+
+ // TODO(suzhe): due to a bug of webkit, we currently can't use selection range
+ // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805
+ composition_.selection = ui::Range(cursor_position);
+
+ // In case we are using a buggy input method which doesn't fire
+ // "preedit_start" signal.
+ if (composition_.text.length())
+ is_composing_text_ = true;
+
+ // Nothing needs to do, if it's currently in ProcessKeyEvent()
+ // handler, which will send preedit text to webkit later.
+ // Otherwise, we need send it here if it's been changed.
+ if (!is_in_key_event_handler_ && is_composing_text_ &&
+ host_view_->GetRenderWidgetHost()) {
+ // Workaround http://crbug.com/45478 by sending fake key down/up events.
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition_.underlines);
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->ImeSetComposition(
+ composition_.text, underlines, composition_.selection.start(),
+ composition_.selection.end());
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
+ }
+}
+
+void GtkIMContextWrapper::HandlePreeditEnd() {
+ if (composition_.text.length()) {
+ // The composition session has been finished.
+ composition_.Clear();
+ is_composition_changed_ = true;
+
+ // If there is still a preedit text when firing "preedit-end" signal,
+ // we need inform webkit to clear it.
+ // It's only necessary when it's not in ProcessKeyEvent ().
+ if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
+ RenderWidgetHostImpl::From(
+ host_view_->GetRenderWidgetHost())->ImeCancelComposition();
+ }
+ }
+
+ // Don't set is_composing_text_ to false here, because "preedit_end"
+ // signal may be fired before "commit" signal.
+}
+
+gboolean GtkIMContextWrapper::HandleRetrieveSurrounding(GtkIMContext* context) {
+ if (!is_enabled_)
+ return TRUE;
+
+ std::string text;
+ size_t cursor_index = 0;
+
+ if (!is_enabled_ || !host_view_->RetrieveSurrounding(&text, &cursor_index)) {
+ gtk_im_context_set_surrounding(context, "", 0, 0);
+ return TRUE;
+ }
+
+ gtk_im_context_set_surrounding(context, text.c_str(), text.length(),
+ cursor_index);
+
+ return TRUE;
+}
+
+void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) {
+ // We should only set im context's client window once, because when setting
+ // client window.im context may destroy and recreate its internal states and
+ // objects.
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ if (gdk_window) {
+ gtk_im_context_set_client_window(context_, gdk_window);
+ gtk_im_context_set_client_window(context_simple_, gdk_window);
+ }
+}
+
+void GtkIMContextWrapper::HandleHostViewUnrealize() {
+ gtk_im_context_set_client_window(context_, NULL);
+ gtk_im_context_set_client_window(context_simple_, NULL);
+}
+
+void GtkIMContextWrapper::SendFakeCompositionKeyEvent(
+ WebKit::WebInputEvent::Type type) {
+ NativeWebKeyboardEvent fake_event;
+ fake_event.windowsKeyCode = kCompositionEventKeyCode;
+ fake_event.skip_in_browser = true;
+ fake_event.type = type;
+ host_view_->ForwardKeyboardEvent(fake_event);
+}
+
+void GtkIMContextWrapper::HandleCommitThunk(
+ GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) {
+ self->HandleCommit(UTF8ToUTF16(text));
+}
+
+void GtkIMContextWrapper::HandlePreeditStartThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ self->HandlePreeditStart();
+}
+
+void GtkIMContextWrapper::HandlePreeditChangedThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ gchar* text = NULL;
+ PangoAttrList* attrs = NULL;
+ gint cursor_position = 0;
+ gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
+ self->HandlePreeditChanged(text, attrs, cursor_position);
+ g_free(text);
+ pango_attr_list_unref(attrs);
+}
+
+void GtkIMContextWrapper::HandlePreeditEndThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ self->HandlePreeditEnd();
+}
+
+gboolean GtkIMContextWrapper::HandleRetrieveSurroundingThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ return self->HandleRetrieveSurrounding(context);
+}
+
+void GtkIMContextWrapper::HandleHostViewRealizeThunk(
+ GtkWidget* widget, GtkIMContextWrapper* self) {
+ self->HandleHostViewRealize(widget);
+}
+
+void GtkIMContextWrapper::HandleHostViewUnrealizeThunk(
+ GtkWidget* widget, GtkIMContextWrapper* self) {
+ self->HandleHostViewUnrealize();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_im_context_wrapper.h b/chromium/content/browser/renderer_host/gtk_im_context_wrapper.h
new file mode 100644
index 00000000000..9f1355143b9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_im_context_wrapper.h
@@ -0,0 +1,201 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
+
+#include <gdk/gdk.h>
+#include <pango/pango-attributes.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/strings/string16.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_type.h"
+
+typedef struct _GtkIMContext GtkIMContext;
+typedef struct _GtkWidget GtkWidget;
+
+namespace gfx {
+class Rect;
+}
+
+namespace content {
+class RenderWidgetHostViewGtk;
+struct NativeWebKeyboardEvent;
+
+// This class is a convenience wrapper for GtkIMContext.
+// It creates and manages two GtkIMContext instances, one is GtkIMMulticontext,
+// for plain text input box, another is GtkIMContextSimple, for password input
+// box.
+//
+// This class is in charge of dispatching key events to these two GtkIMContext
+// instances and handling signals emitted by them. Key events then will be
+// forwarded to renderer along with input method results via corresponding host
+// view.
+//
+// This class is used solely by RenderWidgetHostViewGtk.
+class GtkIMContextWrapper {
+ public:
+ explicit GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view);
+ ~GtkIMContextWrapper();
+
+ // Processes a gdk key event received by |host_view|.
+ void ProcessKeyEvent(GdkEventKey* event);
+
+ void UpdateInputMethodState(ui::TextInputType type,
+ bool can_compose_inline);
+ void UpdateCaretBounds(const gfx::Rect& caret_bounds);
+ void OnFocusIn();
+ void OnFocusOut();
+ bool is_focused() const { return is_focused_; }
+
+ GtkWidget* BuildInputMethodsGtkMenu();
+
+ void CancelComposition();
+
+ void ConfirmComposition();
+
+ private:
+ // Check if a text needs commit by forwarding a char event instead of
+ // by confirming as a composition text.
+ bool NeedCommitByForwardingCharEvent() const;
+
+ // Check if the input method returned any result, eg. preedit and commit text.
+ bool HasInputMethodResult() const;
+
+ void ProcessFilteredKeyPressEvent(NativeWebKeyboardEvent* wke);
+ void ProcessUnfilteredKeyPressEvent(NativeWebKeyboardEvent* wke);
+
+ // Processes result returned from input method after filtering a key event.
+ // |filtered| indicates if the key event was filtered by the input method.
+ void ProcessInputMethodResult(const GdkEventKey* event, bool filtered);
+
+ // Real code of "commit" signal handler.
+ void HandleCommit(const string16& text);
+
+ // Real code of "preedit-start" signal handler.
+ void HandlePreeditStart();
+
+ // Real code of "preedit-changed" signal handler.
+ void HandlePreeditChanged(const gchar* text,
+ PangoAttrList* attrs,
+ int cursor_position);
+
+ // Real code of "preedit-end" signal handler.
+ void HandlePreeditEnd();
+
+ // Real code of "retrieve-surrounding" signal handler.
+ gboolean HandleRetrieveSurrounding(GtkIMContext* context);
+
+ // Real code of "realize" signal handler, used for setting im context's client
+ // window.
+ void HandleHostViewRealize(GtkWidget* widget);
+
+ // Real code of "unrealize" signal handler, used for unsetting im context's
+ // client window.
+ void HandleHostViewUnrealize();
+
+ // Sends a fake composition key event with specified event type. A composition
+ // key event is a key event with special key code 229.
+ void SendFakeCompositionKeyEvent(WebKit::WebInputEvent::Type type);
+
+ // Signal handlers of GtkIMContext object.
+ static void HandleCommitThunk(GtkIMContext* context, gchar* text,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditStartThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditChangedThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditEndThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+ static gboolean HandleRetrieveSurroundingThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+
+ // Signal handlers connecting to |host_view_|'s native view widget.
+ static void HandleHostViewRealizeThunk(GtkWidget* widget,
+ GtkIMContextWrapper* self);
+ static void HandleHostViewUnrealizeThunk(GtkWidget* widget,
+ GtkIMContextWrapper* self);
+
+ // The parent object.
+ RenderWidgetHostViewGtk* host_view_;
+
+ // The GtkIMContext object.
+ // In terms of the DOM event specification Appendix A
+ // <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>,
+ // GTK uses a GtkIMContext object for the following two purposes:
+ // 1. Composing Latin characters (A.1.2), and;
+ // 2. Composing CJK characters with an IME (A.1.3).
+ // Many JavaScript pages assume composed Latin characters are dispatched to
+ // their onkeypress() handlers but not dispatched CJK characters composed
+ // with an IME. To emulate this behavior, we should monitor the status of
+ // this GtkIMContext object and prevent sending Char events when a
+ // GtkIMContext object sends a "commit" signal with the CJK characters
+ // composed by an IME.
+ GtkIMContext* context_;
+
+ // A GtkIMContextSimple object, for supporting dead/compose keys when input
+ // method is disabled, eg. in password input box.
+ GtkIMContext* context_simple_;
+
+ // Whether or not this widget is focused.
+ bool is_focused_;
+
+ // Whether or not the above GtkIMContext is composing a text with an IME.
+ // This flag is used in "commit" signal handler of the GtkIMContext object,
+ // which determines how to submit the result text to WebKit according to this
+ // flag.
+ // If this flag is true or there are more than one characters in the result,
+ // then the result text will be committed to WebKit as a confirmed
+ // composition. Otherwise, it'll be forwarded as a key event.
+ //
+ // The GtkIMContext object sends a "preedit_start" before it starts composing
+ // a text and a "preedit_end" signal after it finishes composing it.
+ // "preedit_start" signal is monitored to turn it on.
+ // We don't monitor "preedit_end" signal to turn it off, because an input
+ // method may fire "preedit_end" signal before "commit" signal.
+ // A buggy input method may not fire "preedit_start" and/or "preedit_end"
+ // at all, so this flag will also be set to true when "preedit_changed" signal
+ // is fired with non-empty preedit text.
+ bool is_composing_text_;
+
+ // Whether or not the IME is enabled.
+ bool is_enabled_;
+
+ // Whether or not it's currently running inside key event handler.
+ // If it's true, then preedit-changed and commit handler will backup the
+ // preedit or commit text instead of sending them down to webkit.
+ // key event handler will send them later.
+ bool is_in_key_event_handler_;
+
+ // The most recent composition text information retrieved from context_;
+ ui::CompositionText composition_;
+
+ // Whether or not the composition has been changed since last key event.
+ bool is_composition_changed_;
+
+ // Stores a copy of the most recent commit text received by commit signal
+ // handler.
+ string16 commit_text_;
+
+ // If it's true then the next "commit" signal will be suppressed.
+ // It's only used to workaround http://crbug.com/50485.
+ // TODO(suzhe): Remove it after input methods get fixed.
+ bool suppress_next_commit_;
+
+ // Information of the last key event, for working around
+ // http://crosbug.com/6582
+ int last_key_code_;
+ bool last_key_was_up_;
+ bool last_key_filtered_no_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(GtkIMContextWrapper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
diff --git a/chromium/content/browser/renderer_host/gtk_key_bindings_handler.cc b/chromium/content/browser/renderer_host/gtk_key_bindings_handler.cc
new file mode 100644
index 00000000000..5a05eeb318b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_key_bindings_handler.cc
@@ -0,0 +1,293 @@
+// 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 "content/browser/renderer_host/gtk_key_bindings_handler.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+
+namespace content {
+
+GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget)
+ : handler_(CreateNewHandler()) {
+ DCHECK(GTK_IS_FIXED(parent_widget));
+ // We need add the |handler_| object into gtk widget hierarchy, so that
+ // gtk_bindings_activate_event() can find correct display and keymaps from
+ // the |handler_| object.
+ gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1);
+}
+
+GtkKeyBindingsHandler::~GtkKeyBindingsHandler() {
+ handler_.Destroy();
+}
+
+bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke,
+ EditCommands* edit_commands) {
+ if (wke.type == WebKit::WebInputEvent::Char || !wke.os_event)
+ return false;
+
+ edit_commands_.clear();
+ // If this key event matches a predefined key binding, corresponding signal
+ // will be emitted.
+ gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), &wke.os_event->key);
+
+ bool matched = !edit_commands_.empty();
+ if (edit_commands)
+ edit_commands->swap(edit_commands_);
+ return matched;
+}
+
+GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() {
+ Handler* handler =
+ static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
+
+ handler->owner = this;
+
+ // We don't need to show the |handler| object on screen, so set its size to
+ // zero.
+ gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
+
+ // Prevents it from handling any events by itself.
+ gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
+ gtk_widget_set_events(GTK_WIDGET(handler), 0);
+ gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE);
+
+ return GTK_WIDGET(handler);
+}
+
+void GtkKeyBindingsHandler::EditCommandMatched(
+ const std::string& name, const std::string& value) {
+ edit_commands_.push_back(EditCommand(name, value));
+}
+
+void GtkKeyBindingsHandler::HandlerInit(Handler *self) {
+ self->owner = NULL;
+}
+
+void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
+ GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+
+ // Overrides all virtual methods related to editor key bindings.
+ text_view_class->backspace = BackSpace;
+ text_view_class->copy_clipboard = CopyClipboard;
+ text_view_class->cut_clipboard = CutClipboard;
+ text_view_class->delete_from_cursor = DeleteFromCursor;
+ text_view_class->insert_at_cursor = InsertAtCursor;
+ text_view_class->move_cursor = MoveCursor;
+ text_view_class->paste_clipboard = PasteClipboard;
+ text_view_class->set_anchor = SetAnchor;
+ text_view_class->toggle_overwrite = ToggleOverwrite;
+ widget_class->show_help = ShowHelp;
+
+ // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible"
+ // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14),
+ // g_signal_override_class_handler() is introduced to override a signal
+ // handler.
+ g_signal_override_class_handler("move-focus",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(MoveFocus));
+
+ g_signal_override_class_handler("move-viewport",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(MoveViewport));
+
+ g_signal_override_class_handler("select-all",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(SelectAll));
+
+ g_signal_override_class_handler("toggle-cursor-visible",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(ToggleCursorVisible));
+}
+
+GType GtkKeyBindingsHandler::HandlerGetType() {
+ static volatile gsize type_id_volatile = 0;
+ if (g_once_init_enter(&type_id_volatile)) {
+ GType type_id = g_type_register_static_simple(
+ GTK_TYPE_TEXT_VIEW,
+ g_intern_static_string("GtkKeyBindingsHandler"),
+ sizeof(HandlerClass),
+ reinterpret_cast<GClassInitFunc>(HandlerClassInit),
+ sizeof(Handler),
+ reinterpret_cast<GInstanceInitFunc>(HandlerInit),
+ static_cast<GTypeFlags>(0));
+ g_once_init_leave(&type_id_volatile, type_id);
+ }
+ return type_id_volatile;
+}
+
+GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner(
+ GtkTextView* text_view) {
+ Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
+ text_view, HandlerGetType(), Handler);
+ DCHECK(handler);
+ return handler->owner;
+}
+
+void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)
+ ->EditCommandMatched("DeleteBackward", std::string());
+}
+
+void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Copy", std::string());
+}
+
+void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Cut", std::string());
+}
+
+void GtkKeyBindingsHandler::DeleteFromCursor(
+ GtkTextView* text_view, GtkDeleteType type, gint count) {
+ if (!count)
+ return;
+
+ const char *commands[3] = { NULL, NULL, NULL };
+ switch (type) {
+ case GTK_DELETE_CHARS:
+ commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward");
+ break;
+ case GTK_DELETE_WORD_ENDS:
+ commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward");
+ break;
+ case GTK_DELETE_WORDS:
+ if (count > 0) {
+ commands[0] = "MoveWordForward";
+ commands[1] = "DeleteWordBackward";
+ } else {
+ commands[0] = "MoveWordBackward";
+ commands[1] = "DeleteWordForward";
+ }
+ break;
+ case GTK_DELETE_DISPLAY_LINES:
+ commands[0] = "MoveToBeginningOfLine";
+ commands[1] = "DeleteToEndOfLine";
+ break;
+ case GTK_DELETE_DISPLAY_LINE_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfLine" :
+ "DeleteToBeginningOfLine");
+ break;
+ case GTK_DELETE_PARAGRAPH_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfParagraph" :
+ "DeleteToBeginningOfParagraph");
+ break;
+ case GTK_DELETE_PARAGRAPHS:
+ commands[0] = "MoveToBeginningOfParagraph";
+ commands[1] = "DeleteToEndOfParagraph";
+ break;
+ default:
+ // GTK_DELETE_WHITESPACE has no corresponding editor command.
+ return;
+ }
+
+ GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count) {
+ for (const char* const* p = commands; *p; ++p)
+ owner->EditCommandMatched(*p, std::string());
+ }
+}
+
+void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
+ const gchar* str) {
+ if (str && *str)
+ GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str);
+}
+
+void GtkKeyBindingsHandler::MoveCursor(
+ GtkTextView* text_view, GtkMovementStep step, gint count,
+ gboolean extend_selection) {
+ if (!count)
+ return;
+
+ std::string command;
+ switch (step) {
+ case GTK_MOVEMENT_LOGICAL_POSITIONS:
+ command = (count > 0 ? "MoveForward" : "MoveBackward");
+ break;
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ command = (count > 0 ? "MoveRight" : "MoveLeft");
+ break;
+ case GTK_MOVEMENT_WORDS:
+ command = (count > 0 ? "MoveWordRight" : "MoveWordLeft");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ command = (count > 0 ? "MoveDown" : "MoveUp");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+ command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine");
+ break;
+ case GTK_MOVEMENT_PARAGRAPH_ENDS:
+ command = (count > 0 ? "MoveToEndOfParagraph" :
+ "MoveToBeginningOfParagraph");
+ break;
+ case GTK_MOVEMENT_PAGES:
+ command = (count > 0 ? "MovePageDown" : "MovePageUp");
+ break;
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ command = (count > 0 ? "MoveToEndOfDocument" :
+ "MoveToBeginningOfDocument");
+ break;
+ default:
+ // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
+ // no corresponding editor commands.
+ return;
+ }
+
+ GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
+ if (extend_selection)
+ command.append("AndModifySelection");
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count)
+ owner->EditCommandMatched(command, std::string());
+}
+
+void GtkKeyBindingsHandler::MoveViewport(
+ GtkTextView* text_view, GtkScrollStep step, gint count) {
+ // Not supported by webkit.
+}
+
+void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Paste", std::string());
+}
+
+void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) {
+ if (select)
+ GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", std::string());
+ else
+ GetHandlerOwner(text_view)->EditCommandMatched("Unselect", std::string());
+}
+
+void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("SetMark", std::string());
+}
+
+void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
+ // Not supported by webkit.
+}
+
+void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
+ // Not supported by webkit.
+}
+
+gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget,
+ GtkWidgetHelpType arg1) {
+ // Just for disabling the default handler.
+ return FALSE;
+}
+
+void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget,
+ GtkDirectionType arg1) {
+ // Just for disabling the default handler.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_key_bindings_handler.h b/chromium/content/browser/renderer_host/gtk_key_bindings_handler.h
new file mode 100644
index 00000000000..9006f5d0727
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_key_bindings_handler.h
@@ -0,0 +1,132 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+
+#include <gtk/gtk.h>
+
+#include <string>
+
+#include "content/common/edit_command.h"
+#include "content/common/content_export.h"
+#include "ui/base/gtk/owned_widget_gtk.h"
+
+namespace content {
+struct NativeWebKeyboardEvent;
+
+// This class is a convenience class for handling editor key bindings defined
+// in gtk keyboard theme.
+// In gtk, only GtkEntry and GtkTextView support customizing editor key bindings
+// through keyboard theme. And in gtk keyboard theme definition file, each key
+// binding must be bound to a specific class or object. So existing keyboard
+// themes only define editor key bindings exactly for GtkEntry and GtkTextView.
+// Then, the only way for us to intercept editor key bindings defined in
+// keyboard theme, is to create a GtkEntry or GtkTextView object and call
+// gtk_bindings_activate_event() against it for the key events. If a key event
+// matches a predefined key binding, corresponding signal will be emitted.
+// GtkTextView is used here because it supports more key bindings than GtkEntry,
+// but in order to minimize the side effect of using a GtkTextView object, a new
+// class derived from GtkTextView is used, which overrides all signals related
+// to key bindings, to make sure GtkTextView won't receive them.
+//
+// See third_party/WebKit/Source/WebCore/editing/EditorCommand.cpp for detailed
+// definition of webkit edit commands.
+// See webkit/glue/editor_client_impl.cc for key bindings predefined in our
+// webkit glue.
+class CONTENT_EXPORT GtkKeyBindingsHandler {
+ public:
+ explicit GtkKeyBindingsHandler(GtkWidget* parent_widget);
+ ~GtkKeyBindingsHandler();
+
+ // Matches a key event against predefined gtk key bindings, false will be
+ // returned if the key event doesn't correspond to a predefined key binding.
+ // Edit commands matched with |wke| will be stored in |edit_commands|.
+ bool Match(const NativeWebKeyboardEvent& wke,
+ EditCommands* edit_commands);
+
+ private:
+ // Object structure of Handler class, which is derived from GtkTextView.
+ struct Handler {
+ GtkTextView parent_object;
+ GtkKeyBindingsHandler *owner;
+ };
+
+ // Class structure of Handler class.
+ struct HandlerClass {
+ GtkTextViewClass parent_class;
+ };
+
+ // Creates a new instance of Handler class.
+ GtkWidget* CreateNewHandler();
+
+ // Adds an edit command to the key event.
+ void EditCommandMatched(const std::string& name, const std::string& value);
+
+ // Initializes Handler structure.
+ static void HandlerInit(Handler *self);
+
+ // Initializes HandlerClass structure.
+ static void HandlerClassInit(HandlerClass *klass);
+
+ // Registeres Handler class to GObject type system and return its type id.
+ static GType HandlerGetType();
+
+ // Gets the GtkKeyBindingsHandler object which owns the Handler object.
+ static GtkKeyBindingsHandler* GetHandlerOwner(GtkTextView* text_view);
+
+ // Handler of "backspace" signal.
+ static void BackSpace(GtkTextView* text_view);
+
+ // Handler of "copy-clipboard" signal.
+ static void CopyClipboard(GtkTextView* text_view);
+
+ // Handler of "cut-clipboard" signal.
+ static void CutClipboard(GtkTextView* text_view);
+
+ // Handler of "delete-from-cursor" signal.
+ static void DeleteFromCursor(GtkTextView* text_view, GtkDeleteType type,
+ gint count);
+
+ // Handler of "insert-at-cursor" signal.
+ static void InsertAtCursor(GtkTextView* text_view, const gchar* str);
+
+ // Handler of "move-cursor" signal.
+ static void MoveCursor(GtkTextView* text_view, GtkMovementStep step,
+ gint count, gboolean extend_selection);
+
+ // Handler of "move-viewport" signal.
+ static void MoveViewport(GtkTextView* text_view, GtkScrollStep step,
+ gint count);
+
+ // Handler of "paste-clipboard" signal.
+ static void PasteClipboard(GtkTextView* text_view);
+
+ // Handler of "select-all" signal.
+ static void SelectAll(GtkTextView* text_view, gboolean select);
+
+ // Handler of "set-anchor" signal.
+ static void SetAnchor(GtkTextView* text_view);
+
+ // Handler of "toggle-cursor-visible" signal.
+ static void ToggleCursorVisible(GtkTextView* text_view);
+
+ // Handler of "toggle-overwrite" signal.
+ static void ToggleOverwrite(GtkTextView* text_view);
+
+ // Handler of "show-help" signal.
+ static gboolean ShowHelp(GtkWidget* widget, GtkWidgetHelpType arg1);
+
+ // Handler of "move-focus" signal.
+ static void MoveFocus(GtkWidget* widget, GtkDirectionType arg1);
+
+ ui::OwnedWidgetGtk handler_;
+
+ // Buffer to store the match results.
+ EditCommands edit_commands_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
diff --git a/chromium/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc b/chromium/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
new file mode 100644
index 00000000000..fed67d5e370
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
@@ -0,0 +1,226 @@
+// 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 "content/browser/renderer_host/gtk_key_bindings_handler.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "content/common/edit_command.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/common/content_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class GtkKeyBindingsHandlerTest : public testing::Test {
+ protected:
+ struct EditCommand {
+ const char* name;
+ const char* value;
+ };
+
+ GtkKeyBindingsHandlerTest()
+ : window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
+ handler_(NULL) {
+ base::FilePath gtkrc;
+ PathService::Get(DIR_TEST_DATA, &gtkrc);
+ gtkrc = gtkrc.AppendASCII("gtk_key_bindings_test_gtkrc");
+ EXPECT_TRUE(base::PathExists(gtkrc));
+
+ gtk_rc_parse(gtkrc.value().c_str());
+
+ GtkWidget* fixed = gtk_fixed_new();
+ handler_ = new GtkKeyBindingsHandler(fixed);
+ gtk_container_add(GTK_CONTAINER(window_), fixed);
+ gtk_widget_show(fixed);
+ gtk_widget_show(window_);
+ }
+ virtual ~GtkKeyBindingsHandlerTest() {
+ gtk_widget_destroy(window_);
+ delete handler_;
+ }
+
+ NativeWebKeyboardEvent NewNativeWebKeyboardEvent(guint keyval, guint state) {
+ GdkKeymap* keymap =
+ gdk_keymap_get_for_display(gtk_widget_get_display(window_));
+
+ GdkKeymapKey *keys = NULL;
+ gint n_keys = 0;
+ if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
+ GdkEventKey event;
+ event.type = GDK_KEY_PRESS;
+ event.window = NULL;
+ event.send_event = 0;
+ event.time = 0;
+ event.state = state;
+ event.keyval = keyval;
+ event.length = 0;
+ event.string = NULL;
+ event.hardware_keycode = keys[0].keycode;
+ event.group = keys[0].group;
+ event.is_modifier = 0;
+ g_free(keys);
+ return NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(&event));
+ }
+ LOG(ERROR) << "Failed to create key event for keyval:" << keyval;
+ return NativeWebKeyboardEvent();
+ }
+
+ void TestKeyBinding(const NativeWebKeyboardEvent& event,
+ const EditCommand expected_result[],
+ size_t size) {
+ EditCommands result;
+ ASSERT_TRUE(handler_->Match(event, &result));
+ ASSERT_EQ(size, result.size());
+ for (size_t i = 0; i < size; ++i) {
+ ASSERT_STREQ(expected_result[i].name, result[i].name.c_str());
+ ASSERT_STREQ(expected_result[i].value, result[i].value.c_str());
+ }
+ }
+
+ protected:
+ GtkWidget* window_;
+ GtkKeyBindingsHandler* handler_;
+};
+
+TEST_F(GtkKeyBindingsHandlerTest, MoveCursor) {
+ static const EditCommand kEditCommands[] = {
+ // "move-cursor" (logical-positions, -2, 0)
+ { "MoveBackward", "" },
+ { "MoveBackward", "" },
+ // "move-cursor" (logical-positions, 2, 0)
+ { "MoveForward", "" },
+ { "MoveForward", "" },
+ // "move-cursor" (visual-positions, -1, 1)
+ { "MoveLeftAndModifySelection", "" },
+ // "move-cursor" (visual-positions, 1, 1)
+ { "MoveRightAndModifySelection", "" },
+ // "move-cursor" (words, -1, 0)
+ { "MoveWordLeft", "" },
+ // "move-cursor" (words, 1, 0)
+ { "MoveWordRight", "" },
+ // "move-cursor" (display-lines, -1, 0)
+ { "MoveUp", "" },
+ // "move-cursor" (display-lines, 1, 0)
+ { "MoveDown", "" },
+ // "move-cursor" (display-line-ends, -1, 0)
+ { "MoveToBeginningOfLine", "" },
+ // "move-cursor" (display-line-ends, 1, 0)
+ { "MoveToEndOfLine", "" },
+ // "move-cursor" (paragraph-ends, -1, 0)
+ { "MoveToBeginningOfParagraph", "" },
+ // "move-cursor" (paragraph-ends, 1, 0)
+ { "MoveToEndOfParagraph", "" },
+ // "move-cursor" (pages, -1, 0)
+ { "MovePageUp", "" },
+ // "move-cursor" (pages, 1, 0)
+ { "MovePageDown", "" },
+ // "move-cursor" (buffer-ends, -1, 0)
+ { "MoveToBeginningOfDocument", "" },
+ // "move-cursor" (buffer-ends, 1, 0)
+ { "MoveToEndOfDocument", "" }
+ };
+
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_1, GDK_CONTROL_MASK),
+ kEditCommands, arraysize(kEditCommands));
+}
+
+TEST_F(GtkKeyBindingsHandlerTest, DeleteFromCursor) {
+ static const EditCommand kEditCommands[] = {
+ // "delete-from-cursor" (chars, -2)
+ { "DeleteBackward", "" },
+ { "DeleteBackward", "" },
+ // "delete-from-cursor" (chars, 2)
+ { "DeleteForward", "" },
+ { "DeleteForward", "" },
+ // "delete-from-cursor" (word-ends, -1)
+ { "DeleteWordBackward", "" },
+ // "delete-from-cursor" (word-ends, 1)
+ { "DeleteWordForward", "" },
+ // "delete-from-cursor" (words, -1)
+ { "MoveWordBackward", "" },
+ { "DeleteWordForward", "" },
+ // "delete-from-cursor" (words, 1)
+ { "MoveWordForward", "" },
+ { "DeleteWordBackward", "" },
+ // "delete-from-cursor" (display-lines, -1)
+ { "MoveToBeginningOfLine", "" },
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (display-lines, 1)
+ { "MoveToBeginningOfLine", "" },
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (display-line-ends, -1)
+ { "DeleteToBeginningOfLine", "" },
+ // "delete-from-cursor" (display-line-ends, 1)
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (paragraph-ends, -1)
+ { "DeleteToBeginningOfParagraph", "" },
+ // "delete-from-cursor" (paragraph-ends, 1)
+ { "DeleteToEndOfParagraph", "" },
+ // "delete-from-cursor" (paragraphs, -1)
+ { "MoveToBeginningOfParagraph", "" },
+ { "DeleteToEndOfParagraph", "" },
+ // "delete-from-cursor" (paragraphs, 1)
+ { "MoveToBeginningOfParagraph", "" },
+ { "DeleteToEndOfParagraph", "" },
+ };
+
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_2, GDK_CONTROL_MASK),
+ kEditCommands, arraysize(kEditCommands));
+}
+
+TEST_F(GtkKeyBindingsHandlerTest, OtherActions) {
+ static const EditCommand kBackspace[] = {
+ { "DeleteBackward", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_3, GDK_CONTROL_MASK),
+ kBackspace, arraysize(kBackspace));
+
+ static const EditCommand kCopyClipboard[] = {
+ { "Copy", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_4, GDK_CONTROL_MASK),
+ kCopyClipboard, arraysize(kCopyClipboard));
+
+ static const EditCommand kCutClipboard[] = {
+ { "Cut", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_5, GDK_CONTROL_MASK),
+ kCutClipboard, arraysize(kCutClipboard));
+
+ static const EditCommand kInsertAtCursor[] = {
+ { "InsertText", "hello" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_6, GDK_CONTROL_MASK),
+ kInsertAtCursor, arraysize(kInsertAtCursor));
+
+ static const EditCommand kPasteClipboard[] = {
+ { "Paste", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_7, GDK_CONTROL_MASK),
+ kPasteClipboard, arraysize(kPasteClipboard));
+
+ static const EditCommand kSelectAll[] = {
+ { "Unselect", "" },
+ { "SelectAll", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_8, GDK_CONTROL_MASK),
+ kSelectAll, arraysize(kSelectAll));
+
+ static const EditCommand kSetAnchor[] = {
+ { "SetMark", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_9, GDK_CONTROL_MASK),
+ kSetAnchor, arraysize(kSetAnchor));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_plugin_container.cc b/chromium/content/browser/renderer_host/gtk_plugin_container.cc
new file mode 100644
index 00000000000..0b83f2ee69f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_plugin_container.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 The Chromium 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 "content/browser/renderer_host/gtk_plugin_container.h"
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+
+namespace content {
+
+namespace {
+
+// NOTE: This class doesn't have constructors/destructors, it is created
+// through GLib's object management.
+class GtkPluginContainer : public GtkSocket {
+ public:
+ // Sets the requested size of the widget.
+ void set_size(int width, int height) {
+ width_ = width;
+ height_ = height;
+ }
+
+ // Casts a widget into a GtkPluginContainer, after checking the type.
+ template <class T>
+ static GtkPluginContainer *CastChecked(T *instance) {
+ return G_TYPE_CHECK_INSTANCE_CAST(instance, GetType(), GtkPluginContainer);
+ }
+
+ // Create and register our custom container type with GTK.
+ static GType GetType() {
+ static GType type = 0; // We only want to register our type once.
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(GtkSocketClass),
+ NULL, NULL,
+ static_cast<GClassInitFunc>(&ClassInit),
+ NULL, NULL,
+ sizeof(GtkPluginContainer),
+ 0, &InstanceInit,
+ };
+ type = g_type_register_static(GTK_TYPE_SOCKET,
+ "GtkPluginContainer",
+ &info,
+ static_cast<GTypeFlags>(0));
+ }
+ return type;
+ }
+
+ // Implementation of the class initializer.
+ static void ClassInit(gpointer klass, gpointer class_data_unusued) {
+ GtkWidgetClass* widget_class = reinterpret_cast<GtkWidgetClass*>(klass);
+ widget_class->size_request = &HandleSizeRequest;
+ }
+
+ // Implementation of the instance initializer (constructor).
+ static void InstanceInit(GTypeInstance *instance, gpointer klass) {
+ GtkPluginContainer *container = CastChecked(instance);
+ container->set_size(0, 0);
+ }
+
+ // Report our allocation size during size requisition.
+ static void HandleSizeRequest(GtkWidget* widget,
+ GtkRequisition* requisition) {
+ GtkPluginContainer *container = CastChecked(widget);
+ requisition->width = container->width_;
+ requisition->height = container->height_;
+ }
+
+ int width_;
+ int height_;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(GtkPluginContainer);
+};
+
+} // namespace
+
+// Create a new instance of our GTK widget object.
+GtkWidget* gtk_plugin_container_new() {
+ return GTK_WIDGET(g_object_new(GtkPluginContainer::GetType(), NULL));
+}
+
+void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height) {
+ GtkPluginContainer::CastChecked(widget)->set_size(width, height);
+ // Signal the parent that the size request has changed.
+ gtk_widget_queue_resize_no_redraw(widget);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_plugin_container.h b/chromium/content/browser/renderer_host/gtk_plugin_container.h
new file mode 100644
index 00000000000..1713cd4bb5a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_plugin_container.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2009 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_H_
+
+// Windowed plugins are embedded via XEmbed, which is implemented by
+// GtkPlug/GtkSocket. But we want to control sizing and positioning
+// directly, so we need a subclass of GtkSocket that sidesteps the
+// size_request handler.
+//
+// The custom size_request handler just reports the size set by
+// gtk_plugin_container_set_size.
+
+typedef struct _GtkWidget GtkWidget;
+
+namespace content {
+
+// Return a new GtkPluginContainer.
+// Intentionally GTK-style here since we're creating a custom GTK widget.
+// This is a GtkSocket subclass; see its documentation for available methods.
+GtkWidget* gtk_plugin_container_new();
+
+// Sets the size of the GtkPluginContainer.
+void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_H_
diff --git a/chromium/content/browser/renderer_host/gtk_plugin_container_manager.cc b/chromium/content/browser/renderer_host/gtk_plugin_container_manager.cc
new file mode 100644
index 00000000000..93e20778607
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_plugin_container_manager.cc
@@ -0,0 +1,161 @@
+// 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 "content/browser/renderer_host/gtk_plugin_container_manager.h"
+
+#include <gtk/gtk.h>
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/gtk_plugin_container.h"
+#include "content/common/webplugin_geometry.h"
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/gfx/gtk_util.h"
+
+namespace content {
+
+GtkPluginContainerManager::GtkPluginContainerManager() : host_widget_(NULL) {}
+
+GtkPluginContainerManager::~GtkPluginContainerManager() {}
+
+GtkWidget* GtkPluginContainerManager::CreatePluginContainer(
+ gfx::PluginWindowHandle id) {
+ DCHECK(host_widget_);
+ GtkWidget *widget = gtk_plugin_container_new();
+ plugin_window_to_widget_map_.insert(std::make_pair(id, widget));
+
+ // The Realize callback is responsible for adding the plug into the socket.
+ // The reason is 2-fold:
+ // - the plug can't be added until the socket is realized, but this may not
+ // happen until the socket is attached to a top-level window, which isn't the
+ // case for background tabs.
+ // - when dragging tabs, the socket gets unrealized, which breaks the XEMBED
+ // connection. We need to make it again when the tab is reattached, and the
+ // socket gets realized again.
+ //
+ // Note, the RealizeCallback relies on the plugin_window_to_widget_map_ to
+ // have the mapping.
+ g_signal_connect(widget, "realize",
+ G_CALLBACK(RealizeCallback), this);
+
+ // Don't destroy the widget when the plug is removed.
+ g_signal_connect(widget, "plug-removed",
+ G_CALLBACK(gtk_true), NULL);
+
+ gtk_container_add(GTK_CONTAINER(host_widget_), widget);
+ gtk_widget_show(widget);
+
+ return widget;
+}
+
+void GtkPluginContainerManager::DestroyPluginContainer(
+ gfx::PluginWindowHandle id) {
+ DCHECK(host_widget_);
+ GtkWidget* widget = MapIDToWidget(id);
+ if (widget)
+ gtk_widget_destroy(widget);
+
+ plugin_window_to_widget_map_.erase(id);
+}
+
+void GtkPluginContainerManager::MovePluginContainer(
+ const WebPluginGeometry& move) {
+ DCHECK(host_widget_);
+ GtkWidget *widget = MapIDToWidget(move.window);
+ if (!widget)
+ return;
+
+ DCHECK(gtk_widget_get_has_window(widget));
+
+ if (!move.visible) {
+ gtk_widget_hide(widget);
+ return;
+ }
+
+ gtk_widget_show(widget);
+
+ if (!move.rects_valid)
+ return;
+
+ // TODO(piman): if the widget hasn't been realized (e.g. the tab has been
+ // torn off and the parent gtk widget has been detached from the hierarchy),
+ // we lose the cutout information.
+ if (gtk_widget_get_realized(widget)) {
+ GdkRectangle clip_rect = move.clip_rect.ToGdkRectangle();
+ GdkRegion* clip_region = gdk_region_rectangle(&clip_rect);
+ gfx::SubtractRectanglesFromRegion(clip_region, move.cutout_rects);
+ gdk_window_shape_combine_region(gtk_widget_get_window(widget),
+ clip_region, 0, 0);
+ gdk_region_destroy(clip_region);
+ }
+
+ // Update the window position. Resizing is handled by WebPluginDelegate.
+ // TODO(deanm): Verify that we only need to move and not resize.
+ // TODO(evanm): we should cache the last shape and position and skip all
+ // of this business in the common case where nothing has changed.
+ int current_x, current_y;
+
+ // Until the above TODO is resolved, we can grab the last position
+ // off of the GtkFixed with a bit of hackery.
+ GValue value = {0};
+ g_value_init(&value, G_TYPE_INT);
+ gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget,
+ "x", &value);
+ current_x = g_value_get_int(&value);
+ gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget,
+ "y", &value);
+ current_y = g_value_get_int(&value);
+ g_value_unset(&value);
+
+ if (move.window_rect.x() != current_x ||
+ move.window_rect.y() != current_y) {
+ // Calling gtk_fixed_move unnecessarily is a no-no, as it causes the
+ // parent window to repaint!
+ gtk_fixed_move(GTK_FIXED(host_widget_),
+ widget,
+ move.window_rect.x(),
+ move.window_rect.y());
+ }
+
+ gtk_plugin_container_set_size(widget,
+ move.window_rect.width(),
+ move.window_rect.height());
+}
+
+GtkWidget* GtkPluginContainerManager::MapIDToWidget(
+ gfx::PluginWindowHandle id) {
+ PluginWindowToWidgetMap::const_iterator i =
+ plugin_window_to_widget_map_.find(id);
+ if (i != plugin_window_to_widget_map_.end())
+ return i->second;
+
+ LOG(ERROR) << "Request for widget host for unknown window id " << id;
+
+ return NULL;
+}
+
+gfx::PluginWindowHandle GtkPluginContainerManager::MapWidgetToID(
+ GtkWidget* widget) {
+ for (PluginWindowToWidgetMap::const_iterator i =
+ plugin_window_to_widget_map_.begin();
+ i != plugin_window_to_widget_map_.end(); ++i) {
+ if (i->second == widget)
+ return i->first;
+ }
+
+ LOG(ERROR) << "Request for id for unknown widget";
+ return 0;
+}
+
+// static
+void GtkPluginContainerManager::RealizeCallback(GtkWidget* widget,
+ void* user_data) {
+ GtkPluginContainerManager* plugin_container_manager =
+ static_cast<GtkPluginContainerManager*>(user_data);
+
+ gfx::PluginWindowHandle id = plugin_container_manager->MapWidgetToID(widget);
+ if (id)
+ gtk_socket_add_id(GTK_SOCKET(widget), id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_plugin_container_manager.h b/chromium/content/browser/renderer_host/gtk_plugin_container_manager.h
new file mode 100644
index 00000000000..bd0e7c1017a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_plugin_container_manager.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_MANAGER_H_
+
+#include <gtk/gtk.h>
+#include <map>
+
+#include "ui/gfx/native_widget_types.h"
+
+typedef struct _GtkWidget GtkWidget;
+
+namespace content {
+struct WebPluginGeometry;
+
+// Helper class that creates and manages plugin containers (GtkSocket).
+class GtkPluginContainerManager {
+ public:
+ GtkPluginContainerManager();
+ ~GtkPluginContainerManager();
+
+ // Sets the widget that will host the plugin containers. Must be a GtkFixed.
+ void set_host_widget(GtkWidget *widget) { host_widget_ = widget; }
+
+ // Creates a new plugin container, for a given plugin XID.
+ GtkWidget* CreatePluginContainer(gfx::PluginWindowHandle id);
+
+ // Destroys a plugin container, given the plugin XID.
+ void DestroyPluginContainer(gfx::PluginWindowHandle id);
+
+ // Takes an update from WebKit about a plugin's position and side and moves
+ // the plugin accordingly.
+ void MovePluginContainer(const WebPluginGeometry& move);
+
+ private:
+ // Maps a plugin XID to the corresponding container widget.
+ GtkWidget* MapIDToWidget(gfx::PluginWindowHandle id);
+
+ // Maps a container widget to the corresponding plugin XID.
+ gfx::PluginWindowHandle MapWidgetToID(GtkWidget* widget);
+
+ // Callback for when the plugin container gets realized, at which point it
+ // plugs the plugin XID.
+ static void RealizeCallback(GtkWidget *widget, void *user_data);
+
+ // Parent of the plugin containers.
+ GtkWidget* host_widget_;
+
+ // A map that associates plugin containers to the plugin XID.
+ typedef std::map<gfx::PluginWindowHandle, GtkWidget*> PluginWindowToWidgetMap;
+ PluginWindowToWidgetMap plugin_window_to_widget_map_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_PLUGIN_CONTAINER_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/gtk_window_utils.cc b/chromium/content/browser/renderer_host/gtk_window_utils.cc
new file mode 100644
index 00000000000..979c58d05a5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_window_utils.cc
@@ -0,0 +1,88 @@
+// 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 "content/browser/renderer_host/gtk_window_utils.h"
+
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <vector>
+
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/rect.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+
+namespace content {
+
+namespace {
+
+// Returns the region of |window|'s desktop that isn't occupied by docks or
+// panels. Returns an empty rect on failure.
+gfx::Rect GetWorkArea(Window window) {
+ Window root = ui::GetX11RootWindow();
+ int desktop = -1;
+ if (!ui::GetIntProperty(window, "_NET_WM_DESKTOP", &desktop)) {
+ // Hack for ion3: _NET_WM_DESKTOP doesn't get set on individual windows, but
+ // _NET_CURRENT_DESKTOP and _NET_WORKAREA do get set.
+ ui::GetIntProperty(root, "_NET_CURRENT_DESKTOP", &desktop);
+ }
+ if (desktop < 0)
+ return gfx::Rect();
+
+ std::vector<int> property;
+ if (!ui::GetIntArrayProperty(root, "_NET_WORKAREA", &property))
+ return gfx::Rect();
+
+ size_t start_index = 4 * desktop;
+ if (property.size() < start_index + 4)
+ return gfx::Rect();
+
+ return gfx::Rect(property[start_index], property[start_index + 1],
+ property[start_index + 2], property[start_index + 3]);
+}
+
+WebKit::WebScreenInfo GetScreenInfo(Display* display, int screenNumber) {
+ // XDisplayWidth() and XDisplayHeight() return cached values. To ensure that
+ // we return the correct dimensions after the screen is resized, query the
+ // root window's geometry each time.
+ Window root = RootWindow(display, screenNumber);
+ Window root_ret;
+ int x, y;
+ unsigned int width, height, border, depth;
+ XGetGeometry(
+ display, root, &root_ret, &x, &y, &width, &height, &border, &depth);
+
+ WebKit::WebScreenInfo results;
+ results.depthPerComponent = 8; // Assume 8bpp, which is usually right.
+ results.depth = depth;
+ results.isMonochrome = depth == 1;
+ results.rect = WebKit::WebRect(x, y, width, height);
+ results.availableRect = results.rect;
+ return results;
+}
+
+} // namespace
+
+void GetScreenInfoFromNativeWindow(
+ GdkWindow* gdk_window, WebKit::WebScreenInfo* results) {
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ *results = GetScreenInfo(gdk_x11_drawable_get_xdisplay(gdk_window),
+ gdk_x11_screen_get_screen_number(screen));
+
+ int monitor_number = gdk_screen_get_monitor_at_window(screen, gdk_window);
+ GdkRectangle monitor_rect;
+ gdk_screen_get_monitor_geometry(screen, monitor_number, &monitor_rect);
+ results->rect = WebKit::WebRect(monitor_rect.x, monitor_rect.y,
+ monitor_rect.width, monitor_rect.height);
+
+ gfx::Rect available_rect = results->rect;
+ gfx::Rect work_area = GetWorkArea(GDK_WINDOW_XID(gdk_window));
+ if (!work_area.IsEmpty())
+ available_rect.Intersect(work_area);
+ results->availableRect = available_rect;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/gtk_window_utils.h b/chromium/content/browser/renderer_host/gtk_window_utils.h
new file mode 100644
index 00000000000..ed0cfe13895
--- /dev/null
+++ b/chromium/content/browser/renderer_host/gtk_window_utils.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_GTK_WINDOW_UTILS_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_WINDOW_UTILS_H_
+
+#include "content/common/content_export.h"
+
+typedef struct _GdkDrawable GdkWindow;
+
+namespace WebKit {
+struct WebScreenInfo;
+}
+
+namespace content {
+
+CONTENT_EXPORT void GetScreenInfoFromNativeWindow(
+ GdkWindow* gdk_window, WebKit::WebScreenInfo* results);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_WINDOW_UTILS_H_
diff --git a/chromium/content/browser/renderer_host/image_transport_factory_android.cc b/chromium/content/browser/renderer_host/image_transport_factory_android.cc
new file mode 100644
index 00000000000..efe9235f170
--- /dev/null
+++ b/chromium/content/browser/renderer_host/image_transport_factory_android.cc
@@ -0,0 +1,240 @@
+// 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 "content/browser/renderer_host/image_transport_factory_android.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/singleton.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
+#include "content/browser/renderer_host/compositor_impl_android.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
+#include "content/common/gpu/gpu_process_launch_causes.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/android/device_display_info.h"
+#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h"
+
+namespace content {
+
+base::LazyInstance<ObserverList<ImageTransportFactoryAndroidObserver> >::Leaky
+ g_factory_observers = LAZY_INSTANCE_INITIALIZER;
+
+class GLContextLostListener
+ : public WebKit::WebGraphicsContext3D::WebGraphicsContextLostCallback {
+ public:
+ // WebGraphicsContextLostCallback implementation.
+ virtual void onContextLost() OVERRIDE;
+ private:
+ static void DidLoseContext();
+};
+
+namespace {
+
+using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl;
+
+static ImageTransportFactoryAndroid* g_factory = NULL;
+
+class DirectGLImageTransportFactory : public ImageTransportFactoryAndroid {
+ public:
+ DirectGLImageTransportFactory();
+ virtual ~DirectGLImageTransportFactory();
+
+ virtual uint32_t InsertSyncPoint() OVERRIDE { return 0; }
+ virtual void WaitSyncPoint(uint32_t sync_point) OVERRIDE {}
+ virtual uint32_t CreateTexture() OVERRIDE {
+ return context_->createTexture();
+ }
+ virtual void DeleteTexture(uint32_t id) OVERRIDE {
+ context_->deleteTexture(id);
+ }
+ virtual void AcquireTexture(
+ uint32 texture_id, const signed char* mailbox_name) OVERRIDE {}
+ virtual WebKit::WebGraphicsContext3D* GetContext3D() OVERRIDE {
+ return context_.get();
+ }
+ virtual GLHelper* GetGLHelper() OVERRIDE { return NULL; }
+
+ private:
+ scoped_ptr<WebKit::WebGraphicsContext3D> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectGLImageTransportFactory);
+};
+
+DirectGLImageTransportFactory::DirectGLImageTransportFactory() {
+ WebKit::WebGraphicsContext3D::Attributes attrs;
+ attrs.shareResources = true;
+ attrs.noAutomaticFlushes = true;
+ context_ = webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl::
+ CreateViewContext(attrs, NULL);
+ context_->setContextLostCallback(context_lost_listener_.get());
+ if (context_->makeContextCurrent())
+ context_->pushGroupMarkerEXT(
+ base::StringPrintf("DirectGLImageTransportFactory-%p",
+ context_.get()).c_str());
+}
+
+DirectGLImageTransportFactory::~DirectGLImageTransportFactory() {
+ context_->setContextLostCallback(NULL);
+}
+
+class CmdBufferImageTransportFactory : public ImageTransportFactoryAndroid {
+ public:
+ CmdBufferImageTransportFactory();
+ virtual ~CmdBufferImageTransportFactory();
+
+ virtual uint32_t InsertSyncPoint() OVERRIDE;
+ virtual void WaitSyncPoint(uint32_t sync_point) OVERRIDE;
+ virtual uint32_t CreateTexture() OVERRIDE;
+ virtual void DeleteTexture(uint32_t id) OVERRIDE;
+ virtual void AcquireTexture(
+ uint32 texture_id, const signed char* mailbox_name) OVERRIDE;
+ virtual WebKit::WebGraphicsContext3D* GetContext3D() OVERRIDE {
+ return context_.get();
+ }
+ virtual GLHelper* GetGLHelper() OVERRIDE;
+
+ private:
+ scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context_;
+ scoped_ptr<GLHelper> gl_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(CmdBufferImageTransportFactory);
+};
+
+CmdBufferImageTransportFactory::CmdBufferImageTransportFactory() {
+ WebKit::WebGraphicsContext3D::Attributes attrs;
+ attrs.shareResources = true;
+ GpuChannelHostFactory* factory = BrowserGpuChannelHostFactory::instance();
+ GURL url("chrome://gpu/ImageTransportFactoryAndroid");
+ base::WeakPtr<WebGraphicsContext3DSwapBuffersClient> swap_client;
+ context_.reset(new WebGraphicsContext3DCommandBufferImpl(0, // offscreen
+ url,
+ factory,
+ swap_client));
+ static const size_t kBytesPerPixel = 4;
+ gfx::DeviceDisplayInfo display_info;
+ size_t full_screen_texture_size_in_bytes =
+ display_info.GetDisplayHeight() *
+ display_info.GetDisplayWidth() *
+ kBytesPerPixel;
+ context_->setContextLostCallback(context_lost_listener_.get());
+ context_->Initialize(
+ attrs,
+ false,
+ CAUSE_FOR_GPU_LAUNCH_WEBGRAPHICSCONTEXT3DCOMMANDBUFFERIMPL_INITIALIZE,
+ 64 * 1024, // command buffer size
+ std::min(full_screen_texture_size_in_bytes,
+ kDefaultStartTransferBufferSize),
+ kDefaultMinTransferBufferSize,
+ std::min(3 * full_screen_texture_size_in_bytes,
+ kDefaultMaxTransferBufferSize));
+
+ if (context_->makeContextCurrent())
+ context_->pushGroupMarkerEXT(
+ base::StringPrintf("CmdBufferImageTransportFactory-%p",
+ context_.get()).c_str());
+}
+
+CmdBufferImageTransportFactory::~CmdBufferImageTransportFactory() {
+ context_->setContextLostCallback(NULL);
+}
+
+uint32_t CmdBufferImageTransportFactory::InsertSyncPoint() {
+ if (!context_->makeContextCurrent()) {
+ LOG(ERROR) << "Failed to make helper context current.";
+ return 0;
+ }
+ return context_->insertSyncPoint();
+}
+
+void CmdBufferImageTransportFactory::WaitSyncPoint(uint32_t sync_point) {
+ if (!context_->makeContextCurrent()) {
+ LOG(ERROR) << "Failed to make helper context current.";
+ return;
+ }
+ context_->waitSyncPoint(sync_point);
+}
+
+uint32_t CmdBufferImageTransportFactory::CreateTexture() {
+ if (!context_->makeContextCurrent()) {
+ LOG(ERROR) << "Failed to make helper context current.";
+ return false;
+ }
+ return context_->createTexture();
+}
+
+void CmdBufferImageTransportFactory::DeleteTexture(uint32_t id) {
+ if (!context_->makeContextCurrent()) {
+ LOG(ERROR) << "Failed to make helper context current.";
+ return;
+ }
+ context_->deleteTexture(id);
+}
+
+void CmdBufferImageTransportFactory::AcquireTexture(
+ uint32 texture_id, const signed char* mailbox_name) {
+ if (!context_->makeContextCurrent()) {
+ LOG(ERROR) << "Failed to make helper context current.";
+ return;
+ }
+ context_->bindTexture(GL_TEXTURE_2D, texture_id);
+ context_->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox_name);
+ context_->flush();
+}
+
+GLHelper* CmdBufferImageTransportFactory::GetGLHelper() {
+ if (!gl_helper_)
+ gl_helper_.reset(new GLHelper(context_.get()));
+
+ return gl_helper_.get();
+}
+
+} // anonymous namespace
+
+// static
+ImageTransportFactoryAndroid* ImageTransportFactoryAndroid::GetInstance() {
+ if (!g_factory) {
+ if (CompositorImpl::UsesDirectGL())
+ g_factory = new DirectGLImageTransportFactory();
+ else
+ g_factory = new CmdBufferImageTransportFactory();
+ }
+
+ return g_factory;
+}
+
+ImageTransportFactoryAndroid::ImageTransportFactoryAndroid()
+ : context_lost_listener_(new GLContextLostListener()) {}
+
+ImageTransportFactoryAndroid::~ImageTransportFactoryAndroid() {}
+
+void ImageTransportFactoryAndroid::AddObserver(
+ ImageTransportFactoryAndroidObserver* observer) {
+ g_factory_observers.Get().AddObserver(observer);
+}
+
+void ImageTransportFactoryAndroid::RemoveObserver(
+ ImageTransportFactoryAndroidObserver* observer) {
+ g_factory_observers.Get().RemoveObserver(observer);
+}
+
+void GLContextLostListener::onContextLost() {
+ // Need to post a task because the command buffer client cannot be deleted
+ // from within this callback.
+ LOG(ERROR) << "Context lost.";
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&GLContextLostListener::DidLoseContext));
+}
+
+void GLContextLostListener::DidLoseContext() {
+ delete g_factory;
+ g_factory = NULL;
+ FOR_EACH_OBSERVER(ImageTransportFactoryAndroidObserver,
+ g_factory_observers.Get(),
+ OnLostResources());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/image_transport_factory_android.h b/chromium/content/browser/renderer_host/image_transport_factory_android.h
new file mode 100644
index 00000000000..22dc09ef435
--- /dev/null
+++ b/chromium/content/browser/renderer_host/image_transport_factory_android.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_IMAGE_TRANSPORT_FACTORY_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_IMAGE_TRANSPORT_FACTORY_ANDROID_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class GLShareGroup;
+}
+
+namespace WebKit {
+class WebGraphicsContext3D;
+}
+
+namespace content {
+class GLHelper;
+class GLContextLostListener;
+
+class ImageTransportFactoryAndroidObserver {
+ public:
+ virtual ~ImageTransportFactoryAndroidObserver() {}
+ virtual void OnLostResources() = 0;
+};
+
+class ImageTransportFactoryAndroid {
+ public:
+ virtual ~ImageTransportFactoryAndroid();
+
+ static ImageTransportFactoryAndroid* GetInstance();
+
+ virtual uint32_t InsertSyncPoint() = 0;
+ virtual void WaitSyncPoint(uint32_t sync_point) = 0;
+ virtual uint32_t CreateTexture() = 0;
+ virtual void DeleteTexture(uint32_t id) = 0;
+ virtual void AcquireTexture(
+ uint32 texture_id, const signed char* mailbox_name) = 0;
+
+ virtual WebKit::WebGraphicsContext3D* GetContext3D() = 0;
+ virtual GLHelper* GetGLHelper() = 0;
+
+ static void AddObserver(ImageTransportFactoryAndroidObserver* observer);
+ static void RemoveObserver(ImageTransportFactoryAndroidObserver* observer);
+
+protected:
+ ImageTransportFactoryAndroid();
+
+ scoped_ptr<GLContextLostListener> context_lost_listener_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_IMAGE_TRANSPORT_FACTORY_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/ime_adapter_android.cc b/chromium/content/browser/renderer_host/ime_adapter_android.cc
new file mode 100644
index 00000000000..588c5ada8e6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/ime_adapter_android.cc
@@ -0,0 +1,286 @@
+// 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 "content/browser/renderer_host/ime_adapter_android.h"
+
+#include <android/input.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_android.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "jni/ImeAdapter_jni.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF16;
+
+namespace content {
+namespace {
+
+// Maps a java KeyEvent into a NativeWebKeyboardEvent.
+// |java_key_event| is used to maintain a globalref for KeyEvent.
+// |action| will help determine the WebInputEvent type.
+// type, |modifiers|, |time_ms|, |key_code|, |unicode_char| is used to create
+// WebKeyboardEvent. |key_code| is also needed ad need to treat the enter key
+// as a key press of character \r.
+NativeWebKeyboardEvent NativeWebKeyboardEventFromKeyEvent(
+ JNIEnv* env,
+ jobject java_key_event,
+ int action,
+ int modifiers,
+ long time_ms,
+ int key_code,
+ bool is_system_key,
+ int unicode_char) {
+ WebKit::WebInputEvent::Type type = WebKit::WebInputEvent::Undefined;
+ if (action == AKEY_EVENT_ACTION_DOWN)
+ type = WebKit::WebInputEvent::RawKeyDown;
+ else if (action == AKEY_EVENT_ACTION_UP)
+ type = WebKit::WebInputEvent::KeyUp;
+ return NativeWebKeyboardEvent(java_key_event, type, modifiers,
+ time_ms, key_code, unicode_char, is_system_key);
+}
+
+} // anonymous namespace
+
+bool RegisterImeAdapter(JNIEnv* env) {
+ if (!RegisterNativesImpl(env))
+ return false;
+
+ Java_ImeAdapter_initializeWebInputEvents(env,
+ WebKit::WebInputEvent::RawKeyDown,
+ WebKit::WebInputEvent::KeyUp,
+ WebKit::WebInputEvent::Char,
+ WebKit::WebInputEvent::ShiftKey,
+ WebKit::WebInputEvent::AltKey,
+ WebKit::WebInputEvent::ControlKey,
+ WebKit::WebInputEvent::CapsLockOn,
+ WebKit::WebInputEvent::NumLockOn);
+ // TODO(miguelg): remove date time related enums after
+ // https://bugs.webkit.org/show_bug.cgi?id=100935.
+ Java_ImeAdapter_initializeTextInputTypes(
+ env,
+ ui::TEXT_INPUT_TYPE_NONE,
+ ui::TEXT_INPUT_TYPE_TEXT,
+ ui::TEXT_INPUT_TYPE_TEXT_AREA,
+ ui::TEXT_INPUT_TYPE_PASSWORD,
+ ui::TEXT_INPUT_TYPE_SEARCH,
+ ui::TEXT_INPUT_TYPE_URL,
+ ui::TEXT_INPUT_TYPE_EMAIL,
+ ui::TEXT_INPUT_TYPE_TELEPHONE,
+ ui::TEXT_INPUT_TYPE_NUMBER,
+ ui::TEXT_INPUT_TYPE_DATE,
+ ui::TEXT_INPUT_TYPE_DATE_TIME,
+ ui::TEXT_INPUT_TYPE_DATE_TIME_LOCAL,
+ ui::TEXT_INPUT_TYPE_MONTH,
+ ui::TEXT_INPUT_TYPE_TIME,
+ ui::TEXT_INPUT_TYPE_WEEK,
+ ui::TEXT_INPUT_TYPE_CONTENT_EDITABLE);
+ return true;
+}
+
+ImeAdapterAndroid::ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva)
+ : rwhva_(rwhva) {
+}
+
+ImeAdapterAndroid::~ImeAdapterAndroid() {
+ JNIEnv* env = AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> obj = java_ime_adapter_.get(env);
+ if (!obj.is_null())
+ Java_ImeAdapter_detach(env, obj.obj());
+}
+
+bool ImeAdapterAndroid::SendSyntheticKeyEvent(JNIEnv*,
+ jobject,
+ int type,
+ long time_ms,
+ int key_code,
+ int text) {
+ NativeWebKeyboardEvent event(static_cast<WebKit::WebInputEvent::Type>(type),
+ 0 /* modifiers */, time_ms / 1000.0, key_code,
+ text, false /* is_system_key */);
+ rwhva_->SendKeyEvent(event);
+ return true;
+}
+
+bool ImeAdapterAndroid::SendKeyEvent(JNIEnv* env, jobject,
+ jobject original_key_event,
+ int action, int modifiers,
+ long time_ms, int key_code,
+ bool is_system_key, int unicode_char) {
+ NativeWebKeyboardEvent event = NativeWebKeyboardEventFromKeyEvent(
+ env, original_key_event, action, modifiers,
+ time_ms, key_code, is_system_key, unicode_char);
+ bool key_down_text_insertion =
+ event.type == WebKit::WebInputEvent::RawKeyDown && event.text[0];
+ // If we are going to follow up with a synthetic Char event, then that's the
+ // one we expect to test if it's handled or unhandled, so skip handling the
+ // "real" event in the browser.
+ event.skip_in_browser = key_down_text_insertion;
+ rwhva_->SendKeyEvent(event);
+ if (key_down_text_insertion) {
+ // Send a Char event, but without an os_event since we don't want to
+ // roundtrip back to java such synthetic event.
+ NativeWebKeyboardEvent char_event(WebKit::WebInputEvent::Char, modifiers,
+ time_ms, key_code, unicode_char,
+ is_system_key);
+ char_event.skip_in_browser = key_down_text_insertion;
+ rwhva_->SendKeyEvent(char_event);
+ }
+ return true;
+}
+
+void ImeAdapterAndroid::SetComposingText(JNIEnv* env, jobject, jstring text,
+ int new_cursor_pos) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ string16 text16 = ConvertJavaStringToUTF16(env, text);
+ std::vector<WebKit::WebCompositionUnderline> underlines;
+ underlines.push_back(
+ WebKit::WebCompositionUnderline(0, text16.length(), SK_ColorBLACK,
+ false));
+ // new_cursor_position is as described in the Android API for
+ // InputConnection#setComposingText, whereas the parameters for
+ // ImeSetComposition are relative to the start of the composition.
+ if (new_cursor_pos > 0)
+ new_cursor_pos = text16.length() + new_cursor_pos - 1;
+
+ rwhi->ImeSetComposition(text16, underlines, new_cursor_pos, new_cursor_pos);
+}
+
+void ImeAdapterAndroid::ImeBatchStateChanged(JNIEnv* env,
+ jobject,
+ jboolean is_begin) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Send(new ViewMsg_ImeBatchStateChanged(rwhi->GetRoutingID(), is_begin));
+}
+
+void ImeAdapterAndroid::CommitText(JNIEnv* env, jobject, jstring text) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ string16 text16 = ConvertJavaStringToUTF16(env, text);
+ rwhi->ImeConfirmComposition(text16, ui::Range::InvalidRange(), false);
+}
+
+void ImeAdapterAndroid::FinishComposingText(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->ImeConfirmComposition(string16(), ui::Range::InvalidRange(), true);
+}
+
+void ImeAdapterAndroid::AttachImeAdapter(JNIEnv* env, jobject java_object) {
+ java_ime_adapter_ = JavaObjectWeakGlobalRef(env, java_object);
+}
+
+void ImeAdapterAndroid::CancelComposition() {
+ base::android::ScopedJavaLocalRef<jobject> obj =
+ java_ime_adapter_.get(AttachCurrentThread());
+ if (!obj.is_null())
+ Java_ImeAdapter_cancelComposition(AttachCurrentThread(), obj.obj());
+}
+
+void ImeAdapterAndroid::SetEditableSelectionOffsets(JNIEnv*, jobject,
+ int start, int end) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Send(new ViewMsg_SetEditableSelectionOffsets(rwhi->GetRoutingID(),
+ start, end));
+}
+
+void ImeAdapterAndroid::SetComposingRegion(JNIEnv*, jobject,
+ int start, int end) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ std::vector<WebKit::WebCompositionUnderline> underlines;
+ underlines.push_back(
+ WebKit::WebCompositionUnderline(0, end - start, SK_ColorBLACK, false));
+
+ rwhi->Send(new ViewMsg_SetCompositionFromExistingText(
+ rwhi->GetRoutingID(), start, end, underlines));
+}
+
+void ImeAdapterAndroid::DeleteSurroundingText(JNIEnv*, jobject,
+ int before, int after) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Send(new ViewMsg_ExtendSelectionAndDelete(rwhi->GetRoutingID(),
+ before, after));
+}
+
+void ImeAdapterAndroid::Unselect(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Unselect();
+}
+
+void ImeAdapterAndroid::SelectAll(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->SelectAll();
+}
+
+void ImeAdapterAndroid::Cut(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Cut();
+}
+
+void ImeAdapterAndroid::Copy(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Copy();
+}
+
+void ImeAdapterAndroid::Paste(JNIEnv* env, jobject) {
+ RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
+ if (!rwhi)
+ return;
+
+ rwhi->Paste();
+}
+
+void ImeAdapterAndroid::ResetImeAdapter(JNIEnv* env, jobject) {
+ java_ime_adapter_.reset();
+}
+
+RenderWidgetHostImpl* ImeAdapterAndroid::GetRenderWidgetHostImpl() {
+ DCHECK(rwhva_);
+ RenderWidgetHost* rwh = rwhva_->GetRenderWidgetHost();
+ if (!rwh)
+ return NULL;
+
+ return RenderWidgetHostImpl::From(rwh);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/ime_adapter_android.h b/chromium/content/browser/renderer_host/ime_adapter_android.h
new file mode 100644
index 00000000000..d55f0d532ac
--- /dev/null
+++ b/chromium/content/browser/renderer_host/ime_adapter_android.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/jni_helper.h"
+
+namespace content {
+
+class RenderWidgetHostImpl;
+class RenderWidgetHostViewAndroid;
+struct NativeWebKeyboardEvent;
+
+// This class is in charge of dispatching key events from the java side
+// and forward to renderer along with input method results via
+// corresponding host view.
+// Ownership of these objects remains on the native side (see
+// RenderWidgetHostViewAndroid).
+class ImeAdapterAndroid {
+ public:
+ explicit ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva);
+ ~ImeAdapterAndroid();
+
+ // Called from java -> native
+ // The java side is responsible to translate android KeyEvent various enums
+ // and values into the corresponding WebKit::WebInputEvent.
+ bool SendKeyEvent(JNIEnv* env, jobject,
+ jobject original_key_event,
+ int action, int meta_state,
+ long event_time, int key_code,
+ bool is_system_key, int unicode_text);
+ // |event_type| is a value of WebInputEvent::Type.
+ bool SendSyntheticKeyEvent(JNIEnv*,
+ jobject,
+ int event_type,
+ long timestamp_ms,
+ int native_key_code,
+ int unicode_char);
+ void SetComposingText(JNIEnv*, jobject, jstring text, int new_cursor_pos);
+ void ImeBatchStateChanged(JNIEnv*, jobject, jboolean is_begin);
+ void CommitText(JNIEnv*, jobject, jstring text);
+ void FinishComposingText(JNIEnv* env, jobject);
+ void AttachImeAdapter(JNIEnv*, jobject java_object);
+ void SetEditableSelectionOffsets(JNIEnv*, jobject, int start, int end);
+ void SetComposingRegion(JNIEnv*, jobject, int start, int end);
+ void DeleteSurroundingText(JNIEnv*, jobject, int before, int after);
+ void Unselect(JNIEnv*, jobject);
+ void SelectAll(JNIEnv*, jobject);
+ void Cut(JNIEnv*, jobject);
+ void Copy(JNIEnv*, jobject);
+ void Paste(JNIEnv*, jobject);
+ void ResetImeAdapter(JNIEnv*, jobject);
+
+ // Called from native -> java
+ void CancelComposition();
+
+ private:
+ RenderWidgetHostImpl* GetRenderWidgetHostImpl();
+
+ RenderWidgetHostViewAndroid* rwhva_;
+ JavaObjectWeakGlobalRef java_ime_adapter_;
+};
+
+bool RegisterImeAdapter(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/input/OWNERS b/chromium/content/browser/renderer_host/input/OWNERS
new file mode 100644
index 00000000000..4dabaf0da2c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/OWNERS
@@ -0,0 +1,2 @@
+nduca@chromium.org
+aelias@chromium.org
diff --git a/chromium/content/browser/renderer_host/input/gesture_event_filter.cc b/chromium/content/browser/renderer_host/input/gesture_event_filter.cc
new file mode 100644
index 00000000000..c2ff8875c85
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/gesture_event_filter.cc
@@ -0,0 +1,434 @@
+// 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 "content/browser/renderer_host/input/gesture_event_filter.h"
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/renderer_host/input/input_router.h"
+#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
+#include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
+#include "content/public/common/content_switches.h"
+
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+
+namespace content {
+namespace {
+
+// Default maximum time between the GestureRecognizer generating a
+// GestureTapDown and when it is forwarded to the renderer.
+#if !defined(OS_ANDROID)
+static const int kTapDownDeferralTimeMs = 150;
+#else
+// Android OS sends this gesture with a delay already.
+static const int kTapDownDeferralTimeMs = 0;
+#endif
+
+// Default debouncing interval duration: if a scroll is in progress, non-scroll
+// events during this interval are deferred to either its end or discarded on
+// receipt of another GestureScrollUpdate.
+static const int kDebouncingIntervalTimeMs = 30;
+
+// Sets |*value| to |switchKey| if it exists or sets it to |defaultValue|.
+static void GetParamHelper(int* value,
+ int defaultValue,
+ const char switchKey[]) {
+ if (*value < 0) {
+ *value = defaultValue;
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ std::string command_line_param =
+ command_line->GetSwitchValueASCII(switchKey);
+ if (!command_line_param.empty()) {
+ int v;
+ if (base::StringToInt(command_line_param, &v))
+ *value = v;
+ }
+ DCHECK_GE(*value, 0);
+ }
+}
+
+static int GetTapDownDeferralTimeMs() {
+ static int tap_down_deferral_time_window = -1;
+ GetParamHelper(&tap_down_deferral_time_window,
+ kTapDownDeferralTimeMs,
+ switches::kTapDownDeferralTimeMs);
+ return tap_down_deferral_time_window;
+}
+} // namespace
+
+GestureEventFilter::GestureEventFilter(InputRouter* input_router)
+ : input_router_(input_router),
+ fling_in_progress_(false),
+ scrolling_in_progress_(false),
+ ignore_next_ack_(false),
+ combined_scroll_pinch_(gfx::Transform()),
+ touchpad_tap_suppression_controller_(
+ new TouchpadTapSuppressionController(input_router)),
+ touchscreen_tap_suppression_controller_(
+ new TouchscreenTapSuppressionController(this)),
+ maximum_tap_gap_time_ms_(GetTapDownDeferralTimeMs()),
+ debounce_interval_time_ms_(kDebouncingIntervalTimeMs) {
+}
+
+GestureEventFilter::~GestureEventFilter() { }
+
+bool GestureEventFilter::ShouldDiscardFlingCancelEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ if (coalesced_gesture_events_.empty() && fling_in_progress_)
+ return false;
+ GestureEventQueue::const_reverse_iterator it =
+ coalesced_gesture_events_.rbegin();
+ while (it != coalesced_gesture_events_.rend()) {
+ if (it->event.type == WebInputEvent::GestureFlingStart)
+ return false;
+ if (it->event.type == WebInputEvent::GestureFlingCancel)
+ return true;
+ it++;
+ }
+ return true;
+}
+
+bool GestureEventFilter::ShouldForwardForBounceReduction(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (debounce_interval_time_ms_ == 0)
+ return true;
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureScrollUpdate:
+ if (!scrolling_in_progress_) {
+ debounce_deferring_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(debounce_interval_time_ms_),
+ this,
+ &GestureEventFilter::SendScrollEndingEventsNow);
+ } else {
+ // Extend the bounce interval.
+ debounce_deferring_timer_.Reset();
+ }
+ scrolling_in_progress_ = true;
+ debouncing_deferral_queue_.clear();
+ return true;
+ case WebInputEvent::GesturePinchBegin:
+ case WebInputEvent::GesturePinchEnd:
+ case WebInputEvent::GesturePinchUpdate:
+ // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
+ return true;
+ default:
+ if (scrolling_in_progress_) {
+ debouncing_deferral_queue_.push_back(gesture_event);
+ return false;
+ }
+ return true;
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+// NOTE: The filters are applied successively. This simplifies the change.
+bool GestureEventFilter::ShouldForward(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ return ShouldForwardForZeroVelocityFlingStart(gesture_event) &&
+ ShouldForwardForBounceReduction(gesture_event) &&
+ ShouldForwardForGFCFiltering(gesture_event) &&
+ ShouldForwardForTapSuppression(gesture_event) &&
+ ShouldForwardForTapDeferral(gesture_event) &&
+ ShouldForwardForCoalescing(gesture_event);
+}
+
+bool GestureEventFilter::ShouldForwardForZeroVelocityFlingStart(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ return gesture_event.event.type != WebInputEvent::GestureFlingStart ||
+ gesture_event.event.sourceDevice != WebGestureEvent::Touchpad ||
+ gesture_event.event.data.flingStart.velocityX != 0 ||
+ gesture_event.event.data.flingStart.velocityY != 0;
+}
+
+bool GestureEventFilter::ShouldForwardForGFCFiltering(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ return gesture_event.event.type != WebInputEvent::GestureFlingCancel ||
+ !ShouldDiscardFlingCancelEvent(gesture_event);
+}
+
+bool GestureEventFilter::ShouldForwardForTapSuppression(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureFlingCancel:
+ if (gesture_event.event.sourceDevice == WebGestureEvent::Touchscreen)
+ touchscreen_tap_suppression_controller_->GestureFlingCancel();
+ else
+ touchpad_tap_suppression_controller_->GestureFlingCancel();
+ return true;
+ case WebInputEvent::GestureTapDown:
+ return !touchscreen_tap_suppression_controller_->
+ ShouldDeferGestureTapDown(gesture_event);
+ case WebInputEvent::GestureTapCancel:
+ return !touchscreen_tap_suppression_controller_->
+ ShouldSuppressGestureTapCancel();
+ case WebInputEvent::GestureTap:
+ case WebInputEvent::GestureTapUnconfirmed:
+ return !touchscreen_tap_suppression_controller_->
+ ShouldSuppressGestureTap();
+ default:
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool GestureEventFilter::ShouldForwardForTapDeferral(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureTapDown:
+ // GestureTapDown is always paired with either a Tap, or TapCancel, so it
+ // should be impossible to have more than one outstanding at a time.
+ DCHECK_EQ(deferred_tap_down_event_.event.type, WebInputEvent::Undefined);
+ deferred_tap_down_event_ = gesture_event;
+ send_gtd_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(maximum_tap_gap_time_ms_),
+ this,
+ &GestureEventFilter::SendGestureTapDownNow);
+ return false;
+ case WebInputEvent::GestureTapCancel:
+ if (deferred_tap_down_event_.event.type == WebInputEvent::Undefined) {
+ // The TapDown has already been put in the queue, must send the
+ // corresponding TapCancel as well.
+ return true;
+ }
+ // Cancelling a deferred TapDown, just drop them on the floor.
+ send_gtd_timer_.Stop();
+ deferred_tap_down_event_.event.type = WebInputEvent::Undefined;
+ return false;
+ case WebInputEvent::GestureTap:
+ send_gtd_timer_.Stop();
+ if (deferred_tap_down_event_.event.type != WebInputEvent::Undefined) {
+ ForwardGestureEventSkipDeferral(deferred_tap_down_event_);
+ deferred_tap_down_event_.event.type = WebInputEvent::Undefined;
+ }
+ return true;
+ case WebInputEvent::GestureFlingStart:
+ case WebInputEvent::GestureScrollBegin:
+ case WebInputEvent::GesturePinchBegin:
+ send_gtd_timer_.Stop();
+ deferred_tap_down_event_.event.type = WebInputEvent::Undefined;
+ return true;
+ default:
+ return true;
+ }
+
+ NOTREACHED();
+ return true;
+}
+
+bool GestureEventFilter::ShouldForwardForCoalescing(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ switch (gesture_event.event.type) {
+ case WebInputEvent::GestureFlingCancel:
+ fling_in_progress_ = false;
+ break;
+ case WebInputEvent::GestureFlingStart:
+ fling_in_progress_ = true;
+ break;
+ case WebInputEvent::GesturePinchUpdate:
+ case WebInputEvent::GestureScrollUpdate:
+ MergeOrInsertScrollAndPinchEvent(gesture_event);
+ return ShouldHandleEventNow();
+ default:
+ break;
+ }
+ coalesced_gesture_events_.push_back(gesture_event);
+ return ShouldHandleEventNow();
+}
+
+void GestureEventFilter::ProcessGestureAck(bool processed, int type) {
+ if (coalesced_gesture_events_.empty()) {
+ DLOG(ERROR) << "Received unexpected ACK for event type " << type;
+ return;
+ }
+ DCHECK_EQ(coalesced_gesture_events_.front().event.type, type);
+ if (type == WebInputEvent::GestureFlingCancel) {
+ if (coalesced_gesture_events_.front().event.sourceDevice ==
+ WebGestureEvent::Touchscreen)
+ touchscreen_tap_suppression_controller_->GestureFlingCancelAck(processed);
+ else
+ touchpad_tap_suppression_controller_->GestureFlingCancelAck(processed);
+ }
+ coalesced_gesture_events_.pop_front();
+ if (ignore_next_ack_) {
+ ignore_next_ack_ = false;
+ } else if (!coalesced_gesture_events_.empty()) {
+ const GestureEventWithLatencyInfo& next_gesture_event =
+ coalesced_gesture_events_.front();
+ input_router_->SendGestureEventImmediately(next_gesture_event);
+ // TODO(yusufo): Introduce GesturePanScroll so that these can be combined
+ // into one gesture and kept inside the queue that way.
+ if (coalesced_gesture_events_.size() > 1) {
+ const GestureEventWithLatencyInfo& second_gesture_event =
+ coalesced_gesture_events_[1];
+ if (next_gesture_event.event.type ==
+ WebInputEvent::GestureScrollUpdate &&
+ second_gesture_event.event.type ==
+ WebInputEvent::GesturePinchUpdate) {
+ input_router_->SendGestureEventImmediately(second_gesture_event);
+ ignore_next_ack_ = true;
+ combined_scroll_pinch_ = gfx::Transform();
+ }
+ }
+ }
+}
+
+TouchpadTapSuppressionController*
+ GestureEventFilter::GetTouchpadTapSuppressionController() {
+ return touchpad_tap_suppression_controller_.get();
+}
+
+bool GestureEventFilter::HasQueuedGestureEvents() const {
+ return !coalesced_gesture_events_.empty();
+}
+
+const WebKit::WebGestureEvent&
+GestureEventFilter::GetGestureEventAwaitingAck() const {
+ DCHECK(!coalesced_gesture_events_.empty());
+ if (!ignore_next_ack_)
+ return coalesced_gesture_events_.front().event;
+ else
+ return coalesced_gesture_events_.at(1).event;
+}
+
+void GestureEventFilter::FlingHasBeenHalted() {
+ fling_in_progress_ = false;
+}
+
+bool GestureEventFilter::ShouldHandleEventNow() const {
+ return coalesced_gesture_events_.size() == 1;
+}
+
+void GestureEventFilter::ForwardGestureEventForDeferral(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (ShouldForwardForTapDeferral(gesture_event))
+ ForwardGestureEventSkipDeferral(gesture_event);
+}
+
+void GestureEventFilter::ForwardGestureEventSkipDeferral(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (ShouldForwardForCoalescing(gesture_event))
+ input_router_->SendGestureEventImmediately(gesture_event);
+}
+
+void GestureEventFilter::SendGestureTapDownNow() {
+ // We must not have already sent the deferred TapDown (if we did, we would
+ // have stopped the timer, which prevents this task from running - even if
+ // it's time had already elapsed).
+ DCHECK_EQ(deferred_tap_down_event_.event.type, WebInputEvent::GestureTapDown);
+ ForwardGestureEventSkipDeferral(deferred_tap_down_event_);
+ deferred_tap_down_event_.event.type = WebInputEvent::Undefined;
+}
+
+void GestureEventFilter::SendScrollEndingEventsNow() {
+ scrolling_in_progress_ = false;
+ for (GestureEventQueue::const_iterator it =
+ debouncing_deferral_queue_.begin();
+ it != debouncing_deferral_queue_.end(); it++) {
+ if (ShouldForwardForGFCFiltering(*it) &&
+ ShouldForwardForTapSuppression(*it) &&
+ ShouldForwardForTapDeferral(*it) &&
+ ShouldForwardForCoalescing(*it)) {
+ input_router_->SendGestureEventImmediately(*it);
+ }
+ }
+ debouncing_deferral_queue_.clear();
+}
+
+void GestureEventFilter::MergeOrInsertScrollAndPinchEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (coalesced_gesture_events_.size() <= 1) {
+ coalesced_gesture_events_.push_back(gesture_event);
+ return;
+ }
+ GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
+ if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate &&
+ last_event->event.type == WebInputEvent::GestureScrollUpdate &&
+ last_event->event.modifiers == gesture_event.event.modifiers) {
+ last_event->event.data.scrollUpdate.deltaX +=
+ gesture_event.event.data.scrollUpdate.deltaX;
+ last_event->event.data.scrollUpdate.deltaY +=
+ gesture_event.event.data.scrollUpdate.deltaY;
+ last_event->latency.MergeWith(gesture_event.latency);
+ return;
+ }
+ if (coalesced_gesture_events_.size() == 2 ||
+ (coalesced_gesture_events_.size() == 3 && ignore_next_ack_) ||
+ !ShouldTryMerging(gesture_event, *last_event)) {
+ coalesced_gesture_events_.push_back(gesture_event);
+ return;
+ }
+ GestureEventWithLatencyInfo scroll_event;
+ GestureEventWithLatencyInfo pinch_event;
+ scroll_event.event.modifiers |= gesture_event.event.modifiers;
+ scroll_event.event.timeStampSeconds = gesture_event.event.timeStampSeconds;
+ scroll_event.latency = gesture_event.latency;
+ scroll_event.latency.MergeWith(last_event->latency);
+ pinch_event = scroll_event;
+ scroll_event.event.type = WebInputEvent::GestureScrollUpdate;
+ pinch_event.event.type = WebInputEvent::GesturePinchUpdate;
+ pinch_event.event.x = gesture_event.event.type ==
+ WebInputEvent::GesturePinchUpdate ?
+ gesture_event.event.x : last_event->event.x;
+ pinch_event.event.y = gesture_event.event.type ==
+ WebInputEvent::GesturePinchUpdate ?
+ gesture_event.event.y : last_event->event.y;
+
+ combined_scroll_pinch_.ConcatTransform(GetTransformForEvent(gesture_event));
+ GestureEventWithLatencyInfo* second_last_event = &coalesced_gesture_events_
+ [coalesced_gesture_events_.size() - 2];
+ if (ShouldTryMerging(gesture_event, *second_last_event)) {
+ scroll_event.latency.MergeWith(second_last_event->latency);
+ pinch_event.latency.MergeWith(second_last_event->latency);
+ coalesced_gesture_events_.pop_back();
+ } else {
+ DCHECK(combined_scroll_pinch_ == GetTransformForEvent(gesture_event));
+ combined_scroll_pinch_.
+ PreconcatTransform(GetTransformForEvent(*last_event));
+ }
+ coalesced_gesture_events_.pop_back();
+ float combined_scale = combined_scroll_pinch_.matrix().getDouble(0, 0);
+ scroll_event.event.data.scrollUpdate.deltaX =
+ (combined_scroll_pinch_.matrix().getDouble(0, 3) + pinch_event.event.x)
+ / combined_scale - pinch_event.event.x;
+ scroll_event.event.data.scrollUpdate.deltaY =
+ (combined_scroll_pinch_.matrix().getDouble(1, 3) + pinch_event.event.y)
+ / combined_scale - pinch_event.event.y;
+ coalesced_gesture_events_.push_back(scroll_event);
+ pinch_event.event.data.pinchUpdate.scale = combined_scale;
+ coalesced_gesture_events_.push_back(pinch_event);
+}
+
+bool GestureEventFilter::ShouldTryMerging(
+ const GestureEventWithLatencyInfo& new_event,
+ const GestureEventWithLatencyInfo& event_in_queue) const {
+ DLOG_IF(WARNING,
+ new_event.event.timeStampSeconds <
+ event_in_queue.event.timeStampSeconds)
+ << "Event time not monotonic?\n";
+ return (event_in_queue.event.type == WebInputEvent::GestureScrollUpdate ||
+ event_in_queue.event.type == WebInputEvent::GesturePinchUpdate) &&
+ event_in_queue.event.modifiers == new_event.event.modifiers;
+}
+
+gfx::Transform GestureEventFilter::GetTransformForEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ gfx::Transform gesture_transform = gfx::Transform();
+ if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate) {
+ gesture_transform.Translate(gesture_event.event.data.scrollUpdate.deltaX,
+ gesture_event.event.data.scrollUpdate.deltaY);
+ } else if (gesture_event.event.type == WebInputEvent::GesturePinchUpdate) {
+ float scale = gesture_event.event.data.pinchUpdate.scale;
+ gesture_transform.Translate(-gesture_event.event.x, -gesture_event.event.y);
+ gesture_transform.Scale(scale,scale);
+ gesture_transform.Translate(gesture_event.event.x, gesture_event.event.y);
+ }
+ return gesture_transform;
+}
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/gesture_event_filter.h b/chromium/content/browser/renderer_host/input/gesture_event_filter.h
new file mode 100644
index 00000000000..b0c378222ea
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/gesture_event_filter.h
@@ -0,0 +1,210 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_GESTURE_EVENT_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_GESTURE_EVENT_FILTER_H_
+
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/gfx/transform.h"
+
+namespace content {
+class InputRouter;
+class MockRenderWidgetHost;
+class TouchpadTapSuppressionController;
+class TouchscreenTapSuppressionController;
+
+// Maintains WebGestureEvents in a queue before forwarding them to the renderer
+// to apply a sequence of filters on them:
+// 1. Zero-velocity fling-starts from touchpad are filtered.
+// 2. The sequence is filtered for bounces. A bounce is when the finger lifts
+// from the screen briefly during an in-progress scroll. If this happens,
+// non-GestureScrollUpdate events are queued until the de-bounce interval
+// passes or another GestureScrollUpdate event occurs.
+// 3. Unnecessary GestureFlingCancel events are filtered. These are
+// GestureFlingCancels that have no corresponding GestureFlingStart in the
+// queue.
+// 4. Taps immediately after a GestureFlingCancel (caused by the same tap) are
+// filtered.
+// 5. Whenever possible, events in the queue are coalesced to have as few events
+// as possible and therefore maximize the chance that the event stream can be
+// handled entirely by the compositor thread.
+// Events in the queue are forwarded to the renderer one by one; i.e., each
+// event is sent after receiving the ACK for previous one. The only exception is
+// that if a GestureScrollUpdate is followed by a GesturePinchUpdate, they are
+// sent together.
+// TODO(rjkroege): Possibly refactor into a filter chain:
+// http://crbug.com/148443.
+class GestureEventFilter {
+ public:
+ // The |input_router| must outlive the GestureEventFilter.
+ explicit GestureEventFilter(InputRouter* input_router);
+ ~GestureEventFilter();
+
+ // Returns |true| if the caller should immediately forward the provided
+ // |GestureEventWithLatencyInfo| argument to the renderer.
+ bool ShouldForward(const GestureEventWithLatencyInfo&);
+
+ // Indicates that the caller has received an acknowledgement from the renderer
+ // with state |processed| and event |type|. May send events if the queue is
+ // not empty.
+ void ProcessGestureAck(bool processed, int type);
+
+ // Sets the state of the |fling_in_progress_| field to indicate that a fling
+ // is definitely not in progress.
+ void FlingHasBeenHalted();
+
+ // Returns the |TouchpadTapSuppressionController| instance.
+ TouchpadTapSuppressionController* GetTouchpadTapSuppressionController();
+
+ // Returns whether there are any gesture event in the queue.
+ bool HasQueuedGestureEvents() const;
+
+ // Returns the last gesture event that was sent to the renderer.
+ const WebKit::WebGestureEvent& GetGestureEventAwaitingAck() const;
+
+ // Tries forwarding the event to the tap deferral sub-filter.
+ void ForwardGestureEventForDeferral(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Tries forwarding the event, skipping the tap deferral sub-filter.
+ void ForwardGestureEventSkipDeferral(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ private:
+ friend class MockRenderWidgetHost;
+ friend class ImmediateInputRouterTest;
+
+ // TODO(mohsen): There are a bunch of ShouldForward.../ShouldDiscard...
+ // methods that are getting confusing. This should be somehow fixed. Maybe
+ // while refactoring GEF: http://crbug.com/148443.
+
+ // Invoked on the expiration of the timer to release a deferred
+ // GestureTapDown to the renderer.
+ void SendGestureTapDownNow();
+
+ // Inovked on the expiration of the debounce interval to release
+ // deferred events.
+ void SendScrollEndingEventsNow();
+
+ // Returns |true| if the given GestureFlingCancel should be discarded
+ // as unnecessary.
+ bool ShouldDiscardFlingCancelEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const;
+
+ // Returns |true| if the only event in the queue is the current event and
+ // hence that event should be handled now.
+ bool ShouldHandleEventNow() const;
+
+ // Merge or append a GestureScrollUpdate or GesturePinchUpdate into
+ // the coalescing queue.
+ void MergeOrInsertScrollAndPinchEvent(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Sub-filter for removing zero-velocity fling-starts from touchpad.
+ bool ShouldForwardForZeroVelocityFlingStart(
+ const GestureEventWithLatencyInfo& gesture_event) const;
+
+ // Sub-filter for removing bounces from in-progress scrolls.
+ bool ShouldForwardForBounceReduction(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Sub-filter for removing unnecessary GestureFlingCancels.
+ bool ShouldForwardForGFCFiltering(
+ const GestureEventWithLatencyInfo& gesture_event) const;
+
+ // Sub-filter for suppressing taps immediately after a GestureFlingCancel.
+ bool ShouldForwardForTapSuppression(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Sub-filter for deferring GestureTapDowns.
+ bool ShouldForwardForTapDeferral(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Puts the events in a queue to forward them one by one; i.e., forward them
+ // whenever ACK for previous event is received. This queue also tries to
+ // coalesce events as much as possible.
+ bool ShouldForwardForCoalescing(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ // Whether the event_in_queue is GesturePinchUpdate or
+ // GestureScrollUpdate and it has the same modifiers as the
+ // new event.
+ bool ShouldTryMerging(
+ const GestureEventWithLatencyInfo& new_event,
+ const GestureEventWithLatencyInfo& event_in_queue)const;
+
+ // Returns the transform matrix corresponding to the gesture event.
+ // Assumes the gesture event sent is either GestureScrollUpdate or
+ // GesturePinchUpdate. Returns the identity matrix otherwise.
+ gfx::Transform GetTransformForEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const;
+
+ // The receiver of all forwarded gesture events.
+ InputRouter* input_router_;
+
+ // True if a GestureFlingStart is in progress on the renderer or
+ // queued without a subsequent queued GestureFlingCancel event.
+ bool fling_in_progress_;
+
+ // True if a GestureScrollUpdate sequence is in progress.
+ bool scrolling_in_progress_;
+
+ // True if two related gesture events were sent before without waiting
+ // for an ACK, so the next gesture ACK should be ignored.
+ bool ignore_next_ack_;
+
+ // Transform that holds the combined transform matrix for the current
+ // scroll-pinch sequence at the end of the queue.
+ gfx::Transform combined_scroll_pinch_;
+
+ // Timer to release a previously deferred GestureTapDown event.
+ base::OneShotTimer<GestureEventFilter> send_gtd_timer_;
+
+ // An object tracking the state of touchpad on the delivery of mouse events to
+ // the renderer to filter mouse immediately after a touchpad fling canceling
+ // tap.
+ // TODO(mohsen): Move touchpad tap suppression out of GestureEventFilter since
+ // GEF is meant to only be used for touchscreen gesture events.
+ scoped_ptr<TouchpadTapSuppressionController>
+ touchpad_tap_suppression_controller_;
+
+ // An object tracking the state of touchscreen on the delivery of gesture tap
+ // events to the renderer to filter taps immediately after a touchscreen fling
+ // canceling tap.
+ scoped_ptr<TouchscreenTapSuppressionController>
+ touchscreen_tap_suppression_controller_;
+
+ typedef std::deque<GestureEventWithLatencyInfo> GestureEventQueue;
+
+ // Queue of coalesced gesture events not yet sent to the renderer.
+ GestureEventQueue coalesced_gesture_events_;
+
+ // Tap gesture event currently subject to deferral.
+ GestureEventWithLatencyInfo deferred_tap_down_event_;
+
+ // Timer to release a previously deferred GestureTapDown event.
+ base::OneShotTimer<GestureEventFilter> debounce_deferring_timer_;
+
+ // Queue of events that have been deferred for debounce.
+ GestureEventQueue debouncing_deferral_queue_;
+
+ // Time window in which to defer a GestureTapDown.
+ int maximum_tap_gap_time_ms_;
+
+ // Time window in which to debounce scroll/fling ends.
+ // TODO(rjkroege): Make this dynamically configurable.
+ int debounce_interval_time_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureEventFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_GESTURE_EVENT_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/input/immediate_input_router.cc b/chromium/content/browser/renderer_host/input/immediate_input_router.cc
new file mode 100644
index 00000000000..368619cc1e6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/immediate_input_router.cc
@@ -0,0 +1,579 @@
+// 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 "content/browser/renderer_host/input/immediate_input_router.h"
+
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "content/browser/renderer_host/input/gesture_event_filter.h"
+#include "content/browser/renderer_host/input/input_router_client.h"
+#include "content/browser/renderer_host/input/touch_event_queue.h"
+#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/common/content_constants_internal.h"
+#include "content/common/edit_command.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/events/event.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+using WebKit::WebKeyboardEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+
+namespace content {
+namespace {
+
+// Returns |true| if the two wheel events should be coalesced.
+bool ShouldCoalesceMouseWheelEvents(const WebMouseWheelEvent& last_event,
+ const WebMouseWheelEvent& new_event) {
+ return last_event.modifiers == new_event.modifiers &&
+ last_event.scrollByPage == new_event.scrollByPage &&
+ last_event.hasPreciseScrollingDeltas
+ == new_event.hasPreciseScrollingDeltas &&
+ last_event.phase == new_event.phase &&
+ last_event.momentumPhase == new_event.momentumPhase;
+}
+
+float GetUnacceleratedDelta(float accelerated_delta, float acceleration_ratio) {
+ return accelerated_delta * acceleration_ratio;
+}
+
+float GetAccelerationRatio(float accelerated_delta, float unaccelerated_delta) {
+ if (unaccelerated_delta == 0.f || accelerated_delta == 0.f)
+ return 1.f;
+ return unaccelerated_delta / accelerated_delta;
+}
+
+const char* GetEventAckName(InputEventAckState ack_result) {
+ switch(ack_result) {
+ case INPUT_EVENT_ACK_STATE_UNKNOWN: return "UNKNOWN";
+ case INPUT_EVENT_ACK_STATE_CONSUMED: return "CONSUMED";
+ case INPUT_EVENT_ACK_STATE_NOT_CONSUMED: return "NOT_CONSUMED";
+ case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: return "NO_CONSUMER_EXISTS";
+ default:
+ DLOG(WARNING) << "Unhandled InputEventAckState in GetEventAckName.\n";
+ break;
+ }
+ return "";
+}
+
+} // namespace
+
+ImmediateInputRouter::ImmediateInputRouter(
+ RenderProcessHost* process,
+ InputRouterClient* client,
+ int routing_id)
+ : process_(process),
+ client_(client),
+ routing_id_(routing_id),
+ select_range_pending_(false),
+ move_caret_pending_(false),
+ mouse_move_pending_(false),
+ mouse_wheel_pending_(false),
+ has_touch_handler_(false),
+ touch_event_queue_(new TouchEventQueue(this)),
+ gesture_event_filter_(new GestureEventFilter(this)) {
+ enable_no_touch_to_renderer_while_scrolling_ =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kNoTouchToRendererWhileScrolling) == "1";
+ DCHECK(process);
+ DCHECK(client);
+}
+
+ImmediateInputRouter::~ImmediateInputRouter() {
+}
+
+bool ImmediateInputRouter::SendInput(IPC::Message* message) {
+ DCHECK(IPC_MESSAGE_ID_CLASS(message->type()) == InputMsgStart);
+ scoped_ptr<IPC::Message> scoped_message(message);
+ switch (scoped_message->type()) {
+ // Check for types that require an ACK.
+ case InputMsg_SelectRange::ID:
+ return SendSelectRange(scoped_message.release());
+ case InputMsg_MoveCaret::ID:
+ return SendMoveCaret(scoped_message.release());
+ case InputMsg_HandleInputEvent::ID:
+ NOTREACHED() << "WebInputEvents should never be sent via SendInput.";
+ return false;
+ default:
+ return Send(scoped_message.release());
+ }
+}
+
+void ImmediateInputRouter::SendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) {
+ if (!client_->OnSendMouseEvent(mouse_event))
+ return;
+
+ if (mouse_event.event.type == WebInputEvent::MouseDown &&
+ gesture_event_filter_->GetTouchpadTapSuppressionController()->
+ ShouldDeferMouseDown(mouse_event))
+ return;
+ if (mouse_event.event.type == WebInputEvent::MouseUp &&
+ gesture_event_filter_->GetTouchpadTapSuppressionController()->
+ ShouldSuppressMouseUp())
+ return;
+
+ SendMouseEventImmediately(mouse_event);
+}
+
+void ImmediateInputRouter::SendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) {
+ if (!client_->OnSendWheelEvent(wheel_event))
+ return;
+
+ // If there's already a mouse wheel event waiting to be sent to the renderer,
+ // add the new deltas to that event. Not doing so (e.g., by dropping the old
+ // event, as for mouse moves) results in very slow scrolling on the Mac (on
+ // which many, very small wheel events are sent).
+ if (mouse_wheel_pending_) {
+ if (coalesced_mouse_wheel_events_.empty() ||
+ !ShouldCoalesceMouseWheelEvents(
+ coalesced_mouse_wheel_events_.back().event, wheel_event.event)) {
+ coalesced_mouse_wheel_events_.push_back(wheel_event);
+ } else {
+ MouseWheelEventWithLatencyInfo* last_wheel_event =
+ &coalesced_mouse_wheel_events_.back();
+ float unaccelerated_x =
+ GetUnacceleratedDelta(last_wheel_event->event.deltaX,
+ last_wheel_event->event.accelerationRatioX) +
+ GetUnacceleratedDelta(wheel_event.event.deltaX,
+ wheel_event.event.accelerationRatioX);
+ float unaccelerated_y =
+ GetUnacceleratedDelta(last_wheel_event->event.deltaY,
+ last_wheel_event->event.accelerationRatioY) +
+ GetUnacceleratedDelta(wheel_event.event.deltaY,
+ wheel_event.event.accelerationRatioY);
+ last_wheel_event->event.deltaX += wheel_event.event.deltaX;
+ last_wheel_event->event.deltaY += wheel_event.event.deltaY;
+ last_wheel_event->event.wheelTicksX += wheel_event.event.wheelTicksX;
+ last_wheel_event->event.wheelTicksY += wheel_event.event.wheelTicksY;
+ last_wheel_event->event.accelerationRatioX =
+ GetAccelerationRatio(last_wheel_event->event.deltaX, unaccelerated_x);
+ last_wheel_event->event.accelerationRatioY =
+ GetAccelerationRatio(last_wheel_event->event.deltaY, unaccelerated_y);
+ DCHECK_GE(wheel_event.event.timeStampSeconds,
+ last_wheel_event->event.timeStampSeconds);
+ last_wheel_event->event.timeStampSeconds =
+ wheel_event.event.timeStampSeconds;
+ last_wheel_event->latency.MergeWith(wheel_event.latency);
+ }
+ return;
+ }
+ mouse_wheel_pending_ = true;
+ current_wheel_event_ = wheel_event;
+
+ HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize",
+ coalesced_mouse_wheel_events_.size());
+
+ FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency, false);
+}
+
+void ImmediateInputRouter::SendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info) {
+ bool is_shortcut = false;
+ if (!client_->OnSendKeyboardEvent(key_event, latency_info, &is_shortcut))
+ return;
+
+ // Put all WebKeyboardEvent objects in a queue since we can't trust the
+ // renderer and we need to give something to the HandleKeyboardEvent
+ // handler.
+ key_queue_.push_back(key_event);
+ HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size());
+
+ gesture_event_filter_->FlingHasBeenHalted();
+
+ // Only forward the non-native portions of our event.
+ FilterAndSendWebInputEvent(key_event, latency_info, is_shortcut);
+}
+
+void ImmediateInputRouter::SendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ HandleGestureScroll(gesture_event);
+ if (!client_->OnSendGestureEvent(gesture_event))
+ return;
+ FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false);
+}
+
+void ImmediateInputRouter::SendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) {
+ // Always queue TouchEvents, even if the client request they be dropped.
+ client_->OnSendTouchEvent(touch_event);
+ touch_event_queue_->QueueEvent(touch_event);
+}
+
+// Forwards MouseEvent without passing it through
+// TouchpadTapSuppressionController.
+void ImmediateInputRouter::SendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) {
+ if (!client_->OnSendMouseEventImmediately(mouse_event))
+ return;
+
+ // Avoid spamming the renderer with mouse move events. It is important
+ // to note that WM_MOUSEMOVE events are anyways synthetic, but since our
+ // thread is able to rapidly consume WM_MOUSEMOVE events, we may get way
+ // more WM_MOUSEMOVE events than we wish to send to the renderer.
+ if (mouse_event.event.type == WebInputEvent::MouseMove) {
+ if (mouse_move_pending_) {
+ if (!next_mouse_move_) {
+ next_mouse_move_.reset(new MouseEventWithLatencyInfo(mouse_event));
+ } else {
+ // Accumulate movement deltas.
+ int x = next_mouse_move_->event.movementX;
+ int y = next_mouse_move_->event.movementY;
+ next_mouse_move_->event = mouse_event.event;
+ next_mouse_move_->event.movementX += x;
+ next_mouse_move_->event.movementY += y;
+ next_mouse_move_->latency.MergeWith(mouse_event.latency);
+ }
+ return;
+ }
+ mouse_move_pending_ = true;
+ }
+
+ FilterAndSendWebInputEvent(mouse_event.event, mouse_event.latency, false);
+}
+
+void ImmediateInputRouter::SendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) {
+ if (!client_->OnSendTouchEventImmediately(touch_event))
+ return;
+ FilterAndSendWebInputEvent(touch_event.event, touch_event.latency, false);
+}
+
+void ImmediateInputRouter::SendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ HandleGestureScroll(gesture_event);
+ if (!client_->OnSendGestureEventImmediately(gesture_event))
+ return;
+ FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false);
+}
+
+const NativeWebKeyboardEvent*
+ ImmediateInputRouter::GetLastKeyboardEvent() const {
+ if (key_queue_.empty())
+ return NULL;
+ return &key_queue_.front();
+}
+
+bool ImmediateInputRouter::ShouldForwardTouchEvent() const {
+ // Always send a touch event if the renderer has a touch-event handler. It is
+ // possible that a renderer stops listening to touch-events while there are
+ // still events in the touch-queue. In such cases, the new events should still
+ // get into the queue.
+ return has_touch_handler_ || !touch_event_queue_->empty();
+}
+
+bool ImmediateInputRouter::ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& touch_event) const {
+ return gesture_event_filter_->ShouldForward(touch_event);
+}
+
+bool ImmediateInputRouter::HasQueuedGestureEvents() const {
+ return gesture_event_filter_->HasQueuedGestureEvents();
+}
+
+bool ImmediateInputRouter::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ bool message_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ImmediateInputRouter, message, message_is_ok)
+ IPC_MESSAGE_HANDLER(InputHostMsg_HandleInputEvent_ACK, OnInputEventAck)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_MoveCaret_ACK, OnMsgMoveCaretAck)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SelectRange_ACK, OnSelectRangeAck)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers,
+ OnHasTouchEventHandlers)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ if (!message_is_ok)
+ client_->OnUnexpectedEventAck(true);
+
+ return handled;
+}
+
+void ImmediateInputRouter::OnTouchEventAck(
+ const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) {
+ client_->OnTouchEventAck(event, ack_result);
+}
+
+bool ImmediateInputRouter::SendSelectRange(IPC::Message* message) {
+ DCHECK(message->type() == InputMsg_SelectRange::ID);
+ if (select_range_pending_) {
+ next_selection_range_.reset(message);
+ return true;
+ }
+
+ select_range_pending_ = true;
+ return Send(message);
+}
+
+bool ImmediateInputRouter::SendMoveCaret(IPC::Message* message) {
+ DCHECK(message->type() == InputMsg_MoveCaret::ID);
+ if (move_caret_pending_) {
+ next_move_caret_.reset(message);
+ return true;
+ }
+
+ move_caret_pending_ = true;
+ return Send(message);
+}
+
+bool ImmediateInputRouter::Send(IPC::Message* message) {
+ return process_->Send(message);
+}
+
+void ImmediateInputRouter::SendWebInputEvent(
+ const WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info,
+ bool is_keyboard_shortcut) {
+ input_event_start_time_ = TimeTicks::Now();
+ Send(new InputMsg_HandleInputEvent(
+ routing_id(), &input_event, latency_info, is_keyboard_shortcut));
+ client_->IncrementInFlightEventCount();
+}
+
+void ImmediateInputRouter::FilterAndSendWebInputEvent(
+ const WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info,
+ bool is_keyboard_shortcut) {
+ TRACE_EVENT0("input", "ImmediateInputRouter::FilterAndSendWebInputEvent");
+
+ if (!process_->HasConnection())
+ return;
+
+ DCHECK(!process_->IgnoreInputEvents());
+
+ // Perform optional, synchronous event handling, sending ACK messages for
+ // processed events, or proceeding as usual.
+ InputEventAckState filter_ack = client_->FilterInputEvent(input_event,
+ latency_info);
+ switch (filter_ack) {
+ // Send the ACK and early exit.
+ case INPUT_EVENT_ACK_STATE_CONSUMED:
+ case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
+ next_mouse_move_.reset();
+ ProcessInputEventAck(input_event.type, filter_ack, latency_info);
+ // WARNING: |this| may be deleted at this point.
+ return;
+
+ case INPUT_EVENT_ACK_STATE_UNKNOWN: {
+ if (input_event.type == WebKit::WebInputEvent::MouseMove) {
+ // Since this mouse-move event has been consumed, there will be no ACKs.
+ // So reset the state here so that future mouse-move events do reach the
+ // renderer.
+ mouse_move_pending_ = false;
+ } else if (input_event.type == WebKit::WebInputEvent::MouseWheel) {
+ // Reset the wheel-event state when appropriate.
+ mouse_wheel_pending_ = false;
+ } else if (WebInputEvent::isGestureEventType(input_event.type) &&
+ gesture_event_filter_->HasQueuedGestureEvents()) {
+ // If the gesture-event filter has queued gesture events, that implies
+ // it's awaiting an ack for the event. Since the event is being dropped,
+ // it is never sent to the renderer, and so it won't receive any ACKs.
+ // So send the ACK to the gesture event filter immediately, and mark it
+ // as having been processed.
+ gesture_event_filter_->ProcessGestureAck(true, input_event.type);
+ } else if (WebInputEvent::isTouchEventType(input_event.type)) {
+ // During an overscroll gesture initiated by touch-scrolling, the
+ // touch-events do not reset or contribute to the overscroll gesture.
+ // However, the touch-events are not sent to the renderer. So send an
+ // ACK to the touch-event queue immediately. Mark the event as not
+ // processed, to make sure that the touch-scroll gesture that initiated
+ // the overscroll is updated properly.
+ touch_event_queue_->ProcessTouchAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
+ latency_info);
+ }
+ return;
+ }
+
+ // Proceed as normal.
+ case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
+ break;
+ };
+
+ // Transmit any pending wheel events on a non-wheel event. This ensures that
+ // the renderer receives the final PhaseEnded wheel event, which is necessary
+ // to terminate rubber-banding, for example.
+ if (input_event.type != WebInputEvent::MouseWheel) {
+ for (size_t i = 0; i < coalesced_mouse_wheel_events_.size(); ++i) {
+ SendWebInputEvent(coalesced_mouse_wheel_events_[i].event,
+ coalesced_mouse_wheel_events_[i].latency,
+ false);
+ }
+ coalesced_mouse_wheel_events_.clear();
+ }
+
+ SendWebInputEvent(input_event, latency_info, is_keyboard_shortcut);
+
+ // Any input event cancels a pending mouse move event.
+ next_mouse_move_.reset();
+}
+
+void ImmediateInputRouter::OnInputEventAck(
+ WebInputEvent::Type event_type,
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info) {
+ // Log the time delta for processing an input event.
+ TimeDelta delta = TimeTicks::Now() - input_event_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.IIR_InputEventDelta", delta);
+
+ client_->DecrementInFlightEventCount();
+
+ ProcessInputEventAck(event_type, ack_result, latency_info);
+}
+
+void ImmediateInputRouter::OnMsgMoveCaretAck() {
+ move_caret_pending_ = false;
+ if (next_move_caret_)
+ SendMoveCaret(next_move_caret_.release());
+}
+
+void ImmediateInputRouter::OnSelectRangeAck() {
+ select_range_pending_ = false;
+ if (next_selection_range_)
+ SendSelectRange(next_selection_range_.release());
+}
+
+void ImmediateInputRouter::OnHasTouchEventHandlers(bool has_handlers) {
+ if (has_touch_handler_ == has_handlers)
+ return;
+ has_touch_handler_ = has_handlers;
+ if (!has_handlers)
+ touch_event_queue_->FlushQueue();
+ client_->OnHasTouchEventHandlers(has_handlers);
+}
+
+void ImmediateInputRouter::ProcessInputEventAck(
+ WebInputEvent::Type event_type,
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info) {
+ TRACE_EVENT1("input", "ImmediateInputRouter::ProcessInputEventAck",
+ "ack", GetEventAckName(ack_result));
+
+ int type = static_cast<int>(event_type);
+ if (type < WebInputEvent::Undefined) {
+ client_->OnUnexpectedEventAck(true);
+ } else if (type == WebInputEvent::MouseMove) {
+ mouse_move_pending_ = false;
+
+ // now, we can send the next mouse move event
+ if (next_mouse_move_) {
+ DCHECK(next_mouse_move_->event.type == WebInputEvent::MouseMove);
+ scoped_ptr<MouseEventWithLatencyInfo> next_mouse_move
+ = next_mouse_move_.Pass();
+ SendMouseEvent(*next_mouse_move);
+ }
+ } else if (WebInputEvent::isKeyboardEventType(type)) {
+ ProcessKeyboardAck(type, ack_result);
+ } else if (type == WebInputEvent::MouseWheel) {
+ ProcessWheelAck(ack_result);
+ } else if (WebInputEvent::isTouchEventType(type)) {
+ ProcessTouchAck(ack_result, latency_info);
+ } else if (WebInputEvent::isGestureEventType(type)) {
+ ProcessGestureAck(type, ack_result);
+ }
+
+ // WARNING: |this| may be deleted at this point.
+
+ // This is used only for testing, and the other end does not use the
+ // source object. On linux, specifying
+ // Source<RenderWidgetHost> results in a very strange
+ // runtime error in the epilogue of the enclosing
+ // (ProcessInputEventAck) method, but not on other platforms; using
+ // 'void' instead is just as safe (since NotificationSource
+ // is not actually typesafe) and avoids this error.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_INPUT_EVENT_ACK,
+ Source<void>(this),
+ Details<int>(&type));
+}
+
+void ImmediateInputRouter::ProcessKeyboardAck(
+ int type,
+ InputEventAckState ack_result) {
+ if (key_queue_.empty()) {
+ LOG(ERROR) << "Got a KeyEvent back from the renderer but we "
+ << "don't seem to have sent it to the renderer!";
+ } else if (key_queue_.front().type != type) {
+ LOG(ERROR) << "We seem to have a different key type sent from "
+ << "the renderer. (" << key_queue_.front().type << " vs. "
+ << type << "). Ignoring event.";
+
+ // Something must be wrong. Clear the |key_queue_| and char event
+ // suppression so that we can resume from the error.
+ key_queue_.clear();
+ client_->OnUnexpectedEventAck(false);
+ } else {
+ NativeWebKeyboardEvent front_item = key_queue_.front();
+ key_queue_.pop_front();
+
+ client_->OnKeyboardEventAck(front_item, ack_result);
+
+ // WARNING: This ImmediateInputRouter can be deallocated at this point
+ // (i.e. in the case of Ctrl+W, where the call to
+ // HandleKeyboardEvent destroys this ImmediateInputRouter).
+ }
+}
+
+void ImmediateInputRouter::ProcessWheelAck(InputEventAckState ack_result) {
+ mouse_wheel_pending_ = false;
+
+ // Process the unhandled wheel event here before calling
+ // ForwardWheelEventWithLatencyInfo() since it will mutate
+ // current_wheel_event_.
+ client_->OnWheelEventAck(current_wheel_event_.event, ack_result);
+
+ // Now send the next (coalesced) mouse wheel event.
+ if (!coalesced_mouse_wheel_events_.empty()) {
+ MouseWheelEventWithLatencyInfo next_wheel_event =
+ coalesced_mouse_wheel_events_.front();
+ coalesced_mouse_wheel_events_.pop_front();
+ SendWheelEvent(next_wheel_event);
+ }
+}
+
+void ImmediateInputRouter::ProcessGestureAck(int type,
+ InputEventAckState ack_result) {
+ const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
+ client_->OnGestureEventAck(
+ gesture_event_filter_->GetGestureEventAwaitingAck(), ack_result);
+ gesture_event_filter_->ProcessGestureAck(processed, type);
+}
+
+void ImmediateInputRouter::ProcessTouchAck(
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info) {
+ // |touch_event_queue_| will forward to OnTouchEventAck when appropriate.
+ touch_event_queue_->ProcessTouchAck(ack_result, latency_info);
+}
+
+void ImmediateInputRouter::HandleGestureScroll(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (!enable_no_touch_to_renderer_while_scrolling_)
+ return;
+
+ // Once scrolling is started stop forwarding touch move events to renderer.
+ if (gesture_event.event.type == WebInputEvent::GestureScrollBegin)
+ touch_event_queue_->set_no_touch_move_to_renderer(true);
+
+ if (gesture_event.event.type == WebInputEvent::GestureScrollEnd ||
+ gesture_event.event.type == WebInputEvent::GestureFlingStart) {
+ touch_event_queue_->set_no_touch_move_to_renderer(false);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/immediate_input_router.h b/chromium/content/browser/renderer_host/input/immediate_input_router.h
new file mode 100644
index 00000000000..0bfa19f111e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/immediate_input_router.h
@@ -0,0 +1,206 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_IMMEDIATE_INPUT_ROUTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_IMMEDIATE_INPUT_ROUTER_H_
+
+#include <queue>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/input/input_router.h"
+#include "content/browser/renderer_host/input/touch_event_queue.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+
+namespace ui {
+struct LatencyInfo;
+}
+
+namespace content {
+
+class GestureEventFilter;
+class InputRouterClient;
+class RenderProcessHost;
+class RenderWidgetHostImpl;
+
+// A default implementation for browser input event routing. Input commands are
+// forwarded to the renderer immediately upon receipt.
+class CONTENT_EXPORT ImmediateInputRouter
+ : public NON_EXPORTED_BASE(InputRouter),
+ public NON_EXPORTED_BASE(TouchEventQueueClient) {
+ public:
+ ImmediateInputRouter(RenderProcessHost* process,
+ InputRouterClient* client,
+ int routing_id);
+ virtual ~ImmediateInputRouter();
+
+ // InputRouter
+ virtual bool SendInput(IPC::Message* message) OVERRIDE;
+ virtual void SendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE;
+ virtual void SendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) OVERRIDE;
+ virtual void SendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void SendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE;
+ virtual void SendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE;
+ virtual void SendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE;
+ virtual void SendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE;
+ virtual void SendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE;
+ virtual const NativeWebKeyboardEvent* GetLastKeyboardEvent() const OVERRIDE;
+ virtual bool ShouldForwardTouchEvent() const OVERRIDE;
+ virtual bool ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const OVERRIDE;
+ virtual bool HasQueuedGestureEvents() const OVERRIDE;
+
+ // IPC::Listener
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ GestureEventFilter* gesture_event_filter() {
+ return gesture_event_filter_.get();
+ }
+
+ TouchEventQueue* touch_event_queue() {
+ return touch_event_queue_.get();
+ }
+
+private:
+ friend class ImmediateInputRouterTest;
+
+ // TouchEventQueueClient
+ virtual void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) OVERRIDE;
+
+ bool SendMoveCaret(IPC::Message* message);
+ bool SendSelectRange(IPC::Message* message);
+ bool Send(IPC::Message* message);
+
+ // Transmits the given input event an as an IPC::Message. This is an internal
+ // helper for |FilterAndSendInputEvent()| and should not be used otherwise.
+ void SendWebInputEvent(const WebKit::WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info,
+ bool is_keyboard_shortcut);
+
+ // Filters and forwards the given WebInputEvent to |SendWebInputEvent()|. This
+ // is an internal helper for |Send*Event()| and should not be used otherwise.
+ void FilterAndSendWebInputEvent(const WebKit::WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info,
+ bool is_keyboard_shortcut);
+
+ // IPC message handlers
+ void OnInputEventAck(WebKit::WebInputEvent::Type event_type,
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info);
+ void OnMsgMoveCaretAck();
+ void OnSelectRangeAck();
+ void OnHasTouchEventHandlers(bool has_handlers);
+
+ // Handle the event ack. Triggered via |OnInputEventAck()| if the event was
+ // processed in the renderer, or synchonously from |FilterAndSendInputevent()|
+ // if the event was filtered by the |client_| prior to sending.
+ void ProcessInputEventAck(WebKit::WebInputEvent::Type event_type,
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info);
+
+ // Called by ProcessInputEventAck() to process a keyboard event ack message.
+ void ProcessKeyboardAck(int type, InputEventAckState ack_result);
+
+ // Called by ProcessInputEventAck() to process a wheel event ack message.
+ // This could result in a task being posted to allow additional wheel
+ // input messages to be coalesced.
+ void ProcessWheelAck(InputEventAckState ack_result);
+
+ // Called by ProcessInputEventAck() to process a gesture event ack message.
+ // This validates the gesture for suppression of touchpad taps and sends one
+ // previously queued coalesced gesture if it exists.
+ void ProcessGestureAck(int type, InputEventAckState ack_result);
+
+ // Called on ProcessInputEventAck() to process a touch event ack message.
+ // This can result in a gesture event being generated and sent back to the
+ // renderer.
+ void ProcessTouchAck(InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info);
+
+ void HandleGestureScroll(
+ const GestureEventWithLatencyInfo& gesture_event);
+
+ int routing_id() const { return routing_id_; }
+
+
+ RenderProcessHost* process_;
+ InputRouterClient* client_;
+ int routing_id_;
+
+ // (Similar to |mouse_move_pending_|.) True while waiting for SelectRange_ACK.
+ bool select_range_pending_;
+
+ // (Similar to |next_mouse_move_|.) The next SelectRange to send, if any.
+ scoped_ptr<IPC::Message> next_selection_range_;
+
+ // (Similar to |mouse_move_pending_|.) True while waiting for MoveCaret_ACK.
+ bool move_caret_pending_;
+
+ // (Similar to |next_mouse_move_|.) The next MoveCaret to send, if any.
+ scoped_ptr<IPC::Message> next_move_caret_;
+
+ // True if a mouse move event was sent to the render view and we are waiting
+ // for a corresponding InputHostMsg_HandleInputEvent_ACK message.
+ bool mouse_move_pending_;
+
+ // The next mouse move event to send (only non-null while mouse_move_pending_
+ // is true).
+ scoped_ptr<MouseEventWithLatencyInfo> next_mouse_move_;
+
+ // (Similar to |mouse_move_pending_|.) True if a mouse wheel event was sent
+ // and we are waiting for a corresponding ack.
+ bool mouse_wheel_pending_;
+ MouseWheelEventWithLatencyInfo current_wheel_event_;
+
+ typedef std::deque<MouseWheelEventWithLatencyInfo> WheelEventQueue;
+
+ // (Similar to |next_mouse_move_|.) The next mouse wheel events to send.
+ // Unlike mouse moves, mouse wheel events received while one is pending are
+ // coalesced (by accumulating deltas) if they match the previous event in
+ // modifiers. On the Mac, in particular, mouse wheel events are received at a
+ // high rate; not waiting for the ack results in jankiness, and using the same
+ // mechanism as for mouse moves (just dropping old events when multiple ones
+ // would be queued) results in very slow scrolling.
+ WheelEventQueue coalesced_mouse_wheel_events_;
+
+ // The time when an input event was sent to the RenderWidget.
+ base::TimeTicks input_event_start_time_;
+
+ // Queue of keyboard events that we need to track.
+ typedef std::deque<NativeWebKeyboardEvent> KeyQueue;
+
+ // A queue of keyboard events. We can't trust data from the renderer so we
+ // stuff key events into a queue and pop them out on ACK, feeding our copy
+ // back to whatever unhandled handler instead of the returned version.
+ KeyQueue key_queue_;
+
+ // Keeps track of whether the webpage has any touch event handler. If it does,
+ // then touch events are sent to the renderer. Otherwise, the touch events are
+ // not sent to the renderer.
+ bool has_touch_handler_;
+
+ // Whether enabling the optimization that sending no touch move events to
+ // renderer while scrolling.
+ bool enable_no_touch_to_renderer_while_scrolling_;
+
+ scoped_ptr<TouchEventQueue> touch_event_queue_;
+ scoped_ptr<GestureEventFilter> gesture_event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImmediateInputRouter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_IMMEDIATE_INPUT_ROUTER_H_
diff --git a/chromium/content/browser/renderer_host/input/immediate_input_router_unittest.cc b/chromium/content/browser/renderer_host/input/immediate_input_router_unittest.cc
new file mode 100644
index 00000000000..371e02e01b3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/immediate_input_router_unittest.cc
@@ -0,0 +1,2265 @@
+// 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/input/gesture_event_filter.h"
+#include "content/browser/renderer_host/input/immediate_input_router.h"
+#include "content/browser/renderer_host/input/input_router_client.h"
+#include "content/common/content_constants_internal.h"
+#include "content/common/edit_command.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+#if defined(OS_WIN) || defined(USE_AURA)
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "ui/base/events/event.h"
+#endif
+
+using base::TimeDelta;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebTouchEvent;
+using WebKit::WebTouchPoint;
+
+namespace content {
+
+namespace {
+
+const WebInputEvent* GetInputEventFromMessage(const IPC::Message& message) {
+ PickleIterator iter(message);
+ const char* data;
+ int data_length;
+ if (!message.ReadData(&iter, &data, &data_length))
+ return NULL;
+ return reinterpret_cast<const WebInputEvent*>(data);
+}
+
+bool GetIsShortcutFromHandleInputEventMessage(const IPC::Message* msg) {
+ InputMsg_HandleInputEvent::Schema::Param param;
+ InputMsg_HandleInputEvent::Read(msg, &param);
+ return param.c;
+}
+
+template<typename MSG_T, typename ARG_T1>
+void ExpectIPCMessageWithArg1(const IPC::Message* msg, const ARG_T1& arg1) {
+ ASSERT_EQ(MSG_T::ID, msg->type());
+ typename MSG_T::Schema::Param param;
+ ASSERT_TRUE(MSG_T::Read(msg, &param));
+ EXPECT_EQ(arg1, param.a);
+}
+
+template<typename MSG_T, typename ARG_T1, typename ARG_T2>
+void ExpectIPCMessageWithArg2(const IPC::Message* msg,
+ const ARG_T1& arg1,
+ const ARG_T2& arg2) {
+ ASSERT_EQ(MSG_T::ID, msg->type());
+ typename MSG_T::Schema::Param param;
+ ASSERT_TRUE(MSG_T::Read(msg, &param));
+ EXPECT_EQ(arg1, param.a);
+ EXPECT_EQ(arg2, param.b);
+}
+
+#if defined(OS_WIN) || defined(USE_AURA)
+bool TouchEventsAreEquivalent(const ui::TouchEvent& first,
+ const ui::TouchEvent& second) {
+ if (first.type() != second.type())
+ return false;
+ if (first.location() != second.location())
+ return false;
+ if (first.touch_id() != second.touch_id())
+ return false;
+ if (second.time_stamp().InSeconds() != first.time_stamp().InSeconds())
+ return false;
+ return true;
+}
+
+bool EventListIsSubset(const ScopedVector<ui::TouchEvent>& subset,
+ const ScopedVector<ui::TouchEvent>& set) {
+ if (subset.size() > set.size())
+ return false;
+ for (size_t i = 0; i < subset.size(); ++i) {
+ const ui::TouchEvent* first = subset[i];
+ const ui::TouchEvent* second = set[i];
+ bool equivalent = TouchEventsAreEquivalent(*first, *second);
+ if (!equivalent)
+ return false;
+ }
+
+ return true;
+}
+#endif // defined(OS_WIN) || defined(USE_AURA)
+
+} // namespace
+
+class MockInputRouterClient : public InputRouterClient {
+ public:
+ MockInputRouterClient()
+ : input_router_(NULL),
+ in_flight_event_count_(0),
+ has_touch_handler_(false),
+ ack_count_(0),
+ unexpected_event_ack_called_(false),
+ ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN),
+ filter_state_(INPUT_EVENT_ACK_STATE_NOT_CONSUMED),
+ is_shortcut_(false),
+ allow_send_key_event_(true),
+ send_called_(false),
+ send_immediately_called_(false) {
+ }
+ virtual ~MockInputRouterClient() {
+ }
+
+ // InputRouterClient
+ virtual InputEventAckState FilterInputEvent(
+ const WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info) OVERRIDE {
+ return filter_state_;
+ }
+
+ // Called each time a WebInputEvent IPC is sent.
+ virtual void IncrementInFlightEventCount() OVERRIDE {
+ ++in_flight_event_count_;
+ }
+
+ // Called each time a WebInputEvent ACK IPC is received.
+ virtual void DecrementInFlightEventCount() OVERRIDE {
+ --in_flight_event_count_;
+ }
+
+ // Called when the renderer notifies that it has touch event handlers.
+ virtual void OnHasTouchEventHandlers(bool has_handlers) OVERRIDE {
+ has_touch_handler_ = has_handlers;
+ }
+
+ virtual bool OnSendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info,
+ bool* is_shortcut) OVERRIDE {
+ send_called_ = true;
+ sent_key_event_ = key_event;
+ *is_shortcut = is_shortcut_;
+
+ return allow_send_key_event_;
+ }
+
+ virtual bool OnSendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) OVERRIDE {
+ send_called_ = true;
+ sent_wheel_event_ = wheel_event;
+
+ return true;
+ }
+
+ virtual bool OnSendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE {
+ send_called_ = true;
+ sent_mouse_event_ = mouse_event;
+
+ return true;
+ }
+
+ virtual bool OnSendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE {
+ send_called_ = true;
+ sent_touch_event_ = touch_event;
+
+ return true;
+ }
+
+ virtual bool OnSendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE {
+ send_called_ = true;
+ sent_gesture_event_ = gesture_event;
+
+ return input_router_->ShouldForwardGestureEvent(gesture_event);
+ }
+
+ virtual bool OnSendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE {
+ send_immediately_called_ = true;
+ immediately_sent_mouse_event_ = mouse_event;
+
+ return true;
+ }
+
+ virtual bool OnSendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE {
+ send_immediately_called_ = true;
+ immediately_sent_touch_event_ = touch_event;
+
+ return true;
+ }
+
+ virtual bool OnSendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE {
+ send_immediately_called_ = true;
+ immediately_sent_gesture_event_ = gesture_event;
+
+ return true;
+ }
+
+ // Called upon event ack receipt from the renderer.
+ virtual void OnKeyboardEventAck(const NativeWebKeyboardEvent& event,
+ InputEventAckState ack_result) OVERRIDE {
+ VLOG(1) << __FUNCTION__ << " called!";
+ acked_key_event_ = event;
+ RecordAckCalled(ack_result);
+ }
+ virtual void OnWheelEventAck(const WebMouseWheelEvent& event,
+ InputEventAckState ack_result) OVERRIDE {
+ VLOG(1) << __FUNCTION__ << " called!";
+ acked_wheel_event_ = event;
+ RecordAckCalled(ack_result);
+ }
+ virtual void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) OVERRIDE {
+ VLOG(1) << __FUNCTION__ << " called!";
+ acked_touch_event_ = event;
+ RecordAckCalled(ack_result);
+ }
+ virtual void OnGestureEventAck(const WebGestureEvent& event,
+ InputEventAckState ack_result) OVERRIDE {
+ VLOG(1) << __FUNCTION__ << " called!";
+ RecordAckCalled(ack_result);
+ }
+ virtual void OnUnexpectedEventAck(bool bad_message) OVERRIDE {
+ VLOG(1) << __FUNCTION__ << " called!";
+ unexpected_event_ack_called_ = true;
+ }
+
+ void ExpectSendCalled(bool called) {
+ EXPECT_EQ(called, send_called_);
+ send_called_ = false;
+ }
+ void ExpectSendImmediatelyCalled(bool called) {
+ EXPECT_EQ(called, send_immediately_called_);
+ send_immediately_called_ = false;
+ }
+ void ExpectAckCalled(int times) {
+ EXPECT_EQ(times, ack_count_);
+ ack_count_ = 0;
+ }
+
+ void set_input_router(InputRouter* input_router) {
+ input_router_ = input_router;
+ }
+ bool has_touch_handler() const { return has_touch_handler_; }
+ InputEventAckState ack_state() const { return ack_state_; }
+ void set_filter_state(InputEventAckState filter_state) {
+ filter_state_ = filter_state;
+ }
+ bool unexpected_event_ack_called() const {
+ return unexpected_event_ack_called_;
+ }
+ const NativeWebKeyboardEvent& acked_keyboard_event() {
+ return acked_key_event_;
+ }
+ const WebMouseWheelEvent& acked_wheel_event() {
+ return acked_wheel_event_;
+ }
+ const TouchEventWithLatencyInfo& acked_touch_event() {
+ return acked_touch_event_;
+ }
+ void set_is_shortcut(bool is_shortcut) {
+ is_shortcut_ = is_shortcut;
+ }
+ void set_allow_send_key_event(bool allow) {
+ allow_send_key_event_ = allow;
+ }
+ const NativeWebKeyboardEvent& sent_key_event() {
+ return sent_key_event_;
+ }
+ const MouseWheelEventWithLatencyInfo& sent_wheel_event() {
+ return sent_wheel_event_;
+ }
+ const MouseEventWithLatencyInfo& sent_mouse_event() {
+ return sent_mouse_event_;
+ }
+ const GestureEventWithLatencyInfo& sent_gesture_event() {
+ return sent_gesture_event_;
+ }
+ const MouseEventWithLatencyInfo& immediately_sent_mouse_event() {
+ return immediately_sent_mouse_event_;
+ }
+ const TouchEventWithLatencyInfo& immediately_sent_touch_event() {
+ return immediately_sent_touch_event_;
+ }
+ const GestureEventWithLatencyInfo& immediately_sent_gesture_event() {
+ return immediately_sent_gesture_event_;
+ }
+
+ private:
+ void RecordAckCalled(InputEventAckState ack_result) {
+ ++ack_count_;
+ ack_state_ = ack_result;
+ }
+
+ InputRouter* input_router_;
+ int in_flight_event_count_;
+ bool has_touch_handler_;
+
+ int ack_count_;
+ bool unexpected_event_ack_called_;
+ InputEventAckState ack_state_;
+ InputEventAckState filter_state_;
+ NativeWebKeyboardEvent acked_key_event_;
+ WebMouseWheelEvent acked_wheel_event_;
+ TouchEventWithLatencyInfo acked_touch_event_;
+
+ bool is_shortcut_;
+ bool allow_send_key_event_;
+ bool send_called_;
+ NativeWebKeyboardEvent sent_key_event_;
+ MouseWheelEventWithLatencyInfo sent_wheel_event_;
+ MouseEventWithLatencyInfo sent_mouse_event_;
+ TouchEventWithLatencyInfo sent_touch_event_;
+ GestureEventWithLatencyInfo sent_gesture_event_;
+
+ bool send_immediately_called_;
+ MouseEventWithLatencyInfo immediately_sent_mouse_event_;
+ TouchEventWithLatencyInfo immediately_sent_touch_event_;
+ GestureEventWithLatencyInfo immediately_sent_gesture_event_;
+};
+
+class ImmediateInputRouterTest : public testing::Test {
+ public:
+ ImmediateInputRouterTest() {
+ }
+ virtual ~ImmediateInputRouterTest() {
+ }
+
+ protected:
+ // testing::Test
+ virtual void SetUp() {
+ browser_context_.reset(new TestBrowserContext());
+ process_.reset(new MockRenderProcessHost(browser_context_.get()));
+ client_.reset(new MockInputRouterClient());
+ input_router_.reset(new ImmediateInputRouter(
+ process_.get(),
+ client_.get(),
+ MSG_ROUTING_NONE));
+ client_->set_input_router(input_router_.get());
+ }
+ virtual void TearDown() {
+ // Process all pending tasks to avoid leaks.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ input_router_.reset();
+ client_.reset();
+ process_.reset();
+ browser_context_.reset();
+ }
+
+ void SendInputEventACK(WebInputEvent::Type type,
+ InputEventAckState ack_result) {
+ scoped_ptr<IPC::Message> response(
+ new InputHostMsg_HandleInputEvent_ACK(0, type, ack_result,
+ ui::LatencyInfo()));
+ input_router_->OnMessageReceived(*response);
+ }
+
+ void SimulateKeyboardEvent(WebInputEvent::Type type) {
+ NativeWebKeyboardEvent key_event;
+ key_event.type = type;
+ key_event.windowsKeyCode = ui::VKEY_L; // non-null made up value.
+ input_router_->SendKeyboardEvent(key_event, ui::LatencyInfo());
+ client_->ExpectSendCalled(true);
+ EXPECT_EQ(type, client_->sent_key_event().type);
+ EXPECT_EQ(key_event.windowsKeyCode,
+ client_->sent_key_event().windowsKeyCode);
+ }
+
+ void SimulateWheelEvent(float dX, float dY, int modifiers, bool precise) {
+ WebMouseWheelEvent wheel_event;
+ wheel_event.type = WebInputEvent::MouseWheel;
+ wheel_event.deltaX = dX;
+ wheel_event.deltaY = dY;
+ wheel_event.modifiers = modifiers;
+ wheel_event.hasPreciseScrollingDeltas = precise;
+ input_router_->SendWheelEvent(
+ MouseWheelEventWithLatencyInfo(wheel_event, ui::LatencyInfo()));
+ client_->ExpectSendCalled(true);
+ EXPECT_EQ(wheel_event.type, client_->sent_wheel_event().event.type);
+ EXPECT_EQ(dX, client_->sent_wheel_event().event.deltaX);
+ }
+
+ void SimulateMouseMove(int x, int y, int modifiers) {
+ WebMouseEvent mouse_event;
+ mouse_event.type = WebInputEvent::MouseMove;
+ mouse_event.x = mouse_event.windowX = x;
+ mouse_event.y = mouse_event.windowY = y;
+ mouse_event.modifiers = modifiers;
+ input_router_->SendMouseEvent(
+ MouseEventWithLatencyInfo(mouse_event, ui::LatencyInfo()));
+ client_->ExpectSendCalled(true);
+ client_->ExpectSendImmediatelyCalled(true);
+ EXPECT_EQ(mouse_event.type, client_->sent_mouse_event().event.type);
+ EXPECT_EQ(x, client_->sent_mouse_event().event.x);
+ EXPECT_EQ(mouse_event.type,
+ client_->immediately_sent_mouse_event().event.type);
+ EXPECT_EQ(x, client_->immediately_sent_mouse_event().event.x);
+ }
+
+ void SimulateWheelEventWithPhase(WebMouseWheelEvent::Phase phase) {
+ WebMouseWheelEvent wheel_event;
+ wheel_event.type = WebInputEvent::MouseWheel;
+ wheel_event.phase = phase;
+ input_router_->SendWheelEvent(
+ MouseWheelEventWithLatencyInfo(wheel_event, ui::LatencyInfo()));
+ client_->ExpectSendCalled(true);
+ EXPECT_EQ(wheel_event.type, client_->sent_wheel_event().event.type);
+ EXPECT_EQ(phase, client_->sent_wheel_event().event.phase);
+ }
+
+ // Inject provided synthetic WebGestureEvent instance.
+ void SimulateGestureEventCore(WebInputEvent::Type type,
+ WebGestureEvent::SourceDevice sourceDevice,
+ WebGestureEvent* gesture_event) {
+ gesture_event->type = type;
+ gesture_event->sourceDevice = sourceDevice;
+ GestureEventWithLatencyInfo gesture_with_latency(
+ *gesture_event, ui::LatencyInfo());
+ input_router_->SendGestureEvent(gesture_with_latency);
+ client_->ExpectSendCalled(true);
+ EXPECT_EQ(type, client_->sent_gesture_event().event.type);
+ EXPECT_EQ(sourceDevice, client_->sent_gesture_event().event.sourceDevice);
+ }
+
+ // Inject simple synthetic WebGestureEvent instances.
+ void SimulateGestureEvent(WebInputEvent::Type type,
+ WebGestureEvent::SourceDevice sourceDevice) {
+ WebGestureEvent gesture_event;
+ SimulateGestureEventCore(type, sourceDevice, &gesture_event);
+ }
+
+ void SimulateGestureScrollUpdateEvent(float dX, float dY, int modifiers) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.scrollUpdate.deltaX = dX;
+ gesture_event.data.scrollUpdate.deltaY = dY;
+ gesture_event.modifiers = modifiers;
+ SimulateGestureEventCore(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen, &gesture_event);
+ }
+
+ void SimulateGesturePinchUpdateEvent(float scale,
+ float anchorX,
+ float anchorY,
+ int modifiers) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.pinchUpdate.scale = scale;
+ gesture_event.x = anchorX;
+ gesture_event.y = anchorY;
+ gesture_event.modifiers = modifiers;
+ SimulateGestureEventCore(WebInputEvent::GesturePinchUpdate,
+ WebGestureEvent::Touchscreen, &gesture_event);
+ }
+
+ // Inject synthetic GestureFlingStart events.
+ void SimulateGestureFlingStartEvent(
+ float velocityX,
+ float velocityY,
+ WebGestureEvent::SourceDevice sourceDevice) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.flingStart.velocityX = velocityX;
+ gesture_event.data.flingStart.velocityY = velocityY;
+ SimulateGestureEventCore(WebInputEvent::GestureFlingStart, sourceDevice,
+ &gesture_event);
+ }
+
+ // Set the timestamp for the touch-event.
+ void SetTouchTimestamp(base::TimeDelta timestamp) {
+ touch_event_.timeStampSeconds = timestamp.InSecondsF();
+ }
+
+ // Sends a touch event (irrespective of whether the page has a touch-event
+ // handler or not).
+ void SendTouchEvent() {
+ input_router_->SendTouchEvent(
+ TouchEventWithLatencyInfo(touch_event_, ui::LatencyInfo()));
+
+ // Mark all the points as stationary. And remove the points that have been
+ // released.
+ int point = 0;
+ for (unsigned int i = 0; i < touch_event_.touchesLength; ++i) {
+ if (touch_event_.touches[i].state == WebTouchPoint::StateReleased)
+ continue;
+
+ touch_event_.touches[point] = touch_event_.touches[i];
+ touch_event_.touches[point].state =
+ WebTouchPoint::StateStationary;
+ ++point;
+ }
+ touch_event_.touchesLength = point;
+ touch_event_.type = WebInputEvent::Undefined;
+ }
+
+ int PressTouchPoint(int x, int y) {
+ if (touch_event_.touchesLength == touch_event_.touchesLengthCap)
+ return -1;
+ WebTouchPoint& point =
+ touch_event_.touches[touch_event_.touchesLength];
+ point.id = touch_event_.touchesLength;
+ point.position.x = point.screenPosition.x = x;
+ point.position.y = point.screenPosition.y = y;
+ point.state = WebTouchPoint::StatePressed;
+ point.radiusX = point.radiusY = 1.f;
+ ++touch_event_.touchesLength;
+ touch_event_.type = WebInputEvent::TouchStart;
+ return point.id;
+ }
+
+ void MoveTouchPoint(int index, int x, int y) {
+ CHECK(index >= 0 && index < touch_event_.touchesLengthCap);
+ WebTouchPoint& point = touch_event_.touches[index];
+ point.position.x = point.screenPosition.x = x;
+ point.position.y = point.screenPosition.y = y;
+ touch_event_.touches[index].state = WebTouchPoint::StateMoved;
+ touch_event_.type = WebInputEvent::TouchMove;
+ }
+
+ void ReleaseTouchPoint(int index) {
+ CHECK(index >= 0 && index < touch_event_.touchesLengthCap);
+ touch_event_.touches[index].state = WebTouchPoint::StateReleased;
+ touch_event_.type = WebInputEvent::TouchEnd;
+ }
+
+ void set_debounce_interval_time_ms(int ms) {
+ input_router_->gesture_event_filter()->debounce_interval_time_ms_ = ms;
+ }
+
+ void set_maximum_tap_gap_time_ms(int delay_ms) {
+ input_router_->gesture_event_filter()->maximum_tap_gap_time_ms_ = delay_ms;
+ }
+
+ size_t TouchEventQueueSize() {
+ return touch_event_queue()->GetQueueSize();
+ }
+
+ const WebTouchEvent& latest_event() const {
+ return touch_event_queue()->GetLatestEvent().event;
+ }
+
+ void EnableNoTouchToRendererWhileScrolling() {
+ input_router_->enable_no_touch_to_renderer_while_scrolling_ = true;
+ }
+
+ bool no_touch_move_to_renderer() {
+ return touch_event_queue()->no_touch_move_to_renderer_;
+ }
+
+ TouchEventQueue* touch_event_queue() const {
+ return input_router_->touch_event_queue();
+ }
+
+ unsigned GestureEventLastQueueEventSize() {
+ return gesture_event_filter()->coalesced_gesture_events_.size();
+ }
+
+ WebGestureEvent GestureEventSecondFromLastQueueEvent() {
+ return gesture_event_filter()->coalesced_gesture_events_.at(
+ GestureEventLastQueueEventSize() - 2).event;
+ }
+
+ WebGestureEvent GestureEventLastQueueEvent() {
+ return gesture_event_filter()->coalesced_gesture_events_.back().event;
+ }
+
+ unsigned GestureEventDebouncingQueueSize() {
+ return gesture_event_filter()->debouncing_deferral_queue_.size();
+ }
+
+ WebGestureEvent GestureEventQueueEventAt(int i) {
+ return gesture_event_filter()->coalesced_gesture_events_.at(i).event;
+ }
+
+ bool shouldDeferTapDownEvents() {
+ return gesture_event_filter()->maximum_tap_gap_time_ms_ != 0;
+ }
+
+ bool ScrollingInProgress() {
+ return gesture_event_filter()->scrolling_in_progress_;
+ }
+
+ bool FlingInProgress() {
+ return gesture_event_filter()->fling_in_progress_;
+ }
+
+ bool WillIgnoreNextACK() {
+ return gesture_event_filter()->ignore_next_ack_;
+ }
+
+ GestureEventFilter* gesture_event_filter() const {
+ return input_router_->gesture_event_filter();
+ }
+
+ scoped_ptr<MockRenderProcessHost> process_;
+ scoped_ptr<MockInputRouterClient> client_;
+ scoped_ptr<ImmediateInputRouter> input_router_;
+
+ private:
+ base::MessageLoopForUI message_loop_;
+ WebTouchEvent touch_event_;
+
+ scoped_ptr<TestBrowserContext> browser_context_;
+};
+
+#if GTEST_HAS_PARAM_TEST
+// This is for tests that are to be run for all source devices.
+class ImmediateInputRouterWithSourceTest
+ : public ImmediateInputRouterTest,
+ public testing::WithParamInterface<WebGestureEvent::SourceDevice> {
+};
+#endif // GTEST_HAS_PARAM_TEST
+
+TEST_F(ImmediateInputRouterTest, CoalescesRangeSelection) {
+ input_router_->SendInput(
+ new InputMsg_SelectRange(0, gfx::Point(1, 2), gfx::Point(3, 4)));
+ EXPECT_EQ(1u, process_->sink().message_count());
+ ExpectIPCMessageWithArg2<InputMsg_SelectRange>(
+ process_->sink().GetMessageAt(0),
+ gfx::Point(1, 2),
+ gfx::Point(3, 4));
+ process_->sink().ClearMessages();
+
+ // Send two more messages without acking.
+ input_router_->SendInput(
+ new InputMsg_SelectRange(0, gfx::Point(5, 6), gfx::Point(7, 8)));
+ EXPECT_EQ(0u, process_->sink().message_count());
+
+ input_router_->SendInput(
+ new InputMsg_SelectRange(0, gfx::Point(9, 10), gfx::Point(11, 12)));
+ EXPECT_EQ(0u, process_->sink().message_count());
+
+ // Now ack the first message.
+ {
+ scoped_ptr<IPC::Message> response(new ViewHostMsg_SelectRange_ACK(0));
+ input_router_->OnMessageReceived(*response);
+ }
+
+ // Verify that the two messages are coalesced into one message.
+ EXPECT_EQ(1u, process_->sink().message_count());
+ ExpectIPCMessageWithArg2<InputMsg_SelectRange>(
+ process_->sink().GetMessageAt(0),
+ gfx::Point(9, 10),
+ gfx::Point(11, 12));
+ process_->sink().ClearMessages();
+
+ // Acking the coalesced msg should not send any more msg.
+ {
+ scoped_ptr<IPC::Message> response(new ViewHostMsg_SelectRange_ACK(0));
+ input_router_->OnMessageReceived(*response);
+ }
+ EXPECT_EQ(0u, process_->sink().message_count());
+}
+
+TEST_F(ImmediateInputRouterTest, CoalescesCaretMove) {
+ input_router_->SendInput(new InputMsg_MoveCaret(0, gfx::Point(1, 2)));
+ EXPECT_EQ(1u, process_->sink().message_count());
+ ExpectIPCMessageWithArg1<InputMsg_MoveCaret>(
+ process_->sink().GetMessageAt(0), gfx::Point(1, 2));
+ process_->sink().ClearMessages();
+
+ // Send two more messages without acking.
+ input_router_->SendInput(new InputMsg_MoveCaret(0, gfx::Point(5, 6)));
+ EXPECT_EQ(0u, process_->sink().message_count());
+
+ input_router_->SendInput(new InputMsg_MoveCaret(0, gfx::Point(9, 10)));
+ EXPECT_EQ(0u, process_->sink().message_count());
+
+ // Now ack the first message.
+ {
+ scoped_ptr<IPC::Message> response(new ViewHostMsg_MoveCaret_ACK(0));
+ input_router_->OnMessageReceived(*response);
+ }
+
+ // Verify that the two messages are coalesced into one message.
+ EXPECT_EQ(1u, process_->sink().message_count());
+ ExpectIPCMessageWithArg1<InputMsg_MoveCaret>(
+ process_->sink().GetMessageAt(0), gfx::Point(9, 10));
+ process_->sink().ClearMessages();
+
+ // Acking the coalesced msg should not send any more msg.
+ {
+ scoped_ptr<IPC::Message> response(new ViewHostMsg_MoveCaret_ACK(0));
+ input_router_->OnMessageReceived(*response);
+ }
+ EXPECT_EQ(0u, process_->sink().message_count());
+}
+
+TEST_F(ImmediateInputRouterTest, HandledInputEvent) {
+ client_->set_filter_state(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Simulate a keyboard event.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ // Make sure no input event is sent to the renderer.
+ EXPECT_EQ(0u, process_->sink().message_count());
+
+ // OnKeyboardEventAck should be triggered without actual ack.
+ client_->ExpectAckCalled(1);
+
+ // As the event was acked already, keyboard event queue should be
+ // empty.
+ ASSERT_EQ(NULL, input_router_->GetLastKeyboardEvent());
+}
+
+TEST_F(ImmediateInputRouterTest, ClientCanceledKeyboardEvent) {
+ client_->set_allow_send_key_event(false);
+
+ // Simulate a keyboard event.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ // Make sure no input event is sent to the renderer.
+ EXPECT_EQ(0u, process_->sink().message_count());
+ client_->ExpectAckCalled(0);
+}
+
+TEST_F(ImmediateInputRouterTest, ShortcutKeyboardEvent) {
+ client_->set_is_shortcut(true);
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ EXPECT_TRUE(GetIsShortcutFromHandleInputEventMessage(
+ process_->sink().GetMessageAt(0)));
+
+ process_->sink().ClearMessages();
+
+ client_->set_is_shortcut(false);
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ EXPECT_FALSE(GetIsShortcutFromHandleInputEventMessage(
+ process_->sink().GetMessageAt(0)));
+}
+
+TEST_F(ImmediateInputRouterTest, NoncorrespondingKeyEvents) {
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ SendInputEventACK(WebInputEvent::KeyUp,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_TRUE(client_->unexpected_event_ack_called());
+}
+
+// Tests ported from RenderWidgetHostTest --------------------------------------
+
+TEST_F(ImmediateInputRouterTest, HandleKeyEventsWeSent) {
+ // Simulate a keyboard event.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ ASSERT_TRUE(input_router_->GetLastKeyboardEvent());
+ EXPECT_EQ(WebInputEvent::RawKeyDown,
+ input_router_->GetLastKeyboardEvent()->type);
+
+ // Make sure we sent the input event to the renderer.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Send the simulated response from the renderer back.
+ SendInputEventACK(WebInputEvent::RawKeyDown,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(WebInputEvent::RawKeyDown, client_->acked_keyboard_event().type);
+}
+
+TEST_F(ImmediateInputRouterTest, IgnoreKeyEventsWeDidntSend) {
+ // Send a simulated, unrequested key response. We should ignore this.
+ SendInputEventACK(WebInputEvent::RawKeyDown,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ client_->ExpectAckCalled(0);
+}
+
+TEST_F(ImmediateInputRouterTest, CoalescesWheelEvents) {
+ // Simulate wheel events.
+ SimulateWheelEvent(0, -5, 0, false); // sent directly
+ SimulateWheelEvent(0, -10, 0, false); // enqueued
+ SimulateWheelEvent(8, -6, 0, false); // coalesced into previous event
+ SimulateWheelEvent(9, -7, 1, false); // enqueued, different modifiers
+
+ // Check that only the first event was sent.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the ACK sends the second message via ImmediateInputForwarder
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ // The coalesced events can queue up a delayed ack
+ // so that additional input events can be processed before
+ // we turn off coalescing.
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // One more time.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // After the final ack, the queue should be empty.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // FIXME(kouhei): Below is testing gesture event filter. Maybe separate test?
+ {
+ WebGestureEvent gesture_event;
+ gesture_event.type = WebInputEvent::GestureFlingStart;
+ gesture_event.sourceDevice = WebGestureEvent::Touchpad;
+ gesture_event.data.flingStart.velocityX = 0.f;
+ gesture_event.data.flingStart.velocityY = 0.f;
+ EXPECT_FALSE(input_router_->ShouldForwardGestureEvent(
+ GestureEventWithLatencyInfo(gesture_event, ui::LatencyInfo())));
+ }
+}
+
+TEST_F(ImmediateInputRouterTest,
+ CoalescesWheelEventsQueuedPhaseEndIsNotDropped) {
+ // Send an initial gesture begin and ACK it.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchpad);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Send a wheel event, should get sent directly.
+ SimulateWheelEvent(0, -5, 0, false);
+ EXPECT_EQ(2U, process_->sink().message_count());
+
+ // Send a wheel phase end event before an ACK is received for the previous
+ // wheel event, which should get queued.
+ SimulateWheelEventWithPhase(WebMouseWheelEvent::PhaseEnded);
+ EXPECT_EQ(2U, process_->sink().message_count());
+
+ // A gesture event should now result in the queued phase ended event being
+ // transmitted before it.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchpad);
+ ASSERT_EQ(4U, process_->sink().message_count());
+
+ // Verify the events that were sent.
+ const WebInputEvent* input_event =
+ GetInputEventFromMessage(*process_->sink().GetMessageAt(2));
+ ASSERT_EQ(WebInputEvent::MouseWheel, input_event->type);
+ const WebMouseWheelEvent* wheel_event =
+ static_cast<const WebMouseWheelEvent*>(input_event);
+ ASSERT_EQ(WebMouseWheelEvent::PhaseEnded, wheel_event->phase);
+
+ input_event = GetInputEventFromMessage(*process_->sink().GetMessageAt(3));
+ EXPECT_EQ(WebInputEvent::GestureScrollEnd, input_event->type);
+}
+
+TEST_F(ImmediateInputRouterTest, CoalescesScrollGestureEvents) {
+ // Turn off debounce handling for test isolation.
+ set_debounce_interval_time_ms(0);
+
+ // Test coalescing of only GestureScrollUpdate events.
+ // Simulate gesture events.
+
+ // Sent.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(8, -5, 0);
+
+ // Make sure that the queue contains what we think it should.
+ WebGestureEvent merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+
+ // Coalesced.
+ SimulateGestureScrollUpdateEvent(8, -6, 0);
+
+ // Check that coalescing updated the correct values.
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(0, merged_event.modifiers);
+ EXPECT_EQ(16, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-11, merged_event.data.scrollUpdate.deltaY);
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(8, -7, 1);
+
+ // Check that we didn't wrongly coalesce.
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Different.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+
+ // Check that only the first event was sent.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the ACK sends the second message.
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Ack for queued coalesced event.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Ack for queued uncoalesced event.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // After the final ack, the queue should be empty.
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(0U, process_->sink().message_count());
+}
+
+TEST_F(ImmediateInputRouterTest, CoalescesScrollAndPinchEvents) {
+ // Turn off debounce handling for test isolation.
+ set_debounce_interval_time_ms(0);
+
+ // Test coalescing of only GestureScrollUpdate events.
+ // Simulate gesture events.
+
+ // Sent.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+
+ // Sent.
+ SimulateGestureEvent(WebInputEvent::GesturePinchBegin,
+ WebGestureEvent::Touchscreen);
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(8, -4, 1);
+
+ // Make sure that the queue contains what we think it should.
+ WebGestureEvent merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+
+ // Coalesced without changing event order. Note anchor at (60, 60). Anchoring
+ // from a poinht that is not the origin should still give us the wight scroll.
+ SimulateGesturePinchUpdateEvent(1.5, 60, 60, 1);
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(1.5, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(8, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-4, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(6, -3, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(1.5, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(12, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-6, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Enqueued.
+ SimulateGesturePinchUpdateEvent(2, 60, 60, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(3, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(12, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-6, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Enqueued.
+ SimulateGesturePinchUpdateEvent(2, 60, 60, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(6, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(12, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-6, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Check that only the first event was sent.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the ACK sends the second message.
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(6, -6, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(6, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(13, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-7, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // At this point ACKs shouldn't be getting ignored.
+ EXPECT_FALSE(WillIgnoreNextACK());
+
+ // Check that the ACK sends both scroll and pinch updates.
+ SendInputEventACK(WebInputEvent::GesturePinchBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetFirstMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // The next ACK should be getting ignored.
+ EXPECT_TRUE(WillIgnoreNextACK());
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(1, -1, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(1, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-1, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(6, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(2, -2, 1);
+
+ // Coalescing scrolls should still work.
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(3, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-3, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(6, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Enqueued.
+ SimulateGesturePinchUpdateEvent(0.5, 60, 60, 1);
+
+ // Check whether coalesced correctly.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(0.5, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(3, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-3, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Check that the ACK gets ignored.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ // The flag should have been flipped back to false.
+ EXPECT_FALSE(WillIgnoreNextACK());
+
+ // Enqueued.
+ SimulateGestureScrollUpdateEvent(2, -2, 2);
+
+ // Shouldn't coalesce with different modifiers.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureScrollUpdate, merged_event.type);
+ EXPECT_EQ(2, merged_event.data.scrollUpdate.deltaX);
+ EXPECT_EQ(-2, merged_event.data.scrollUpdate.deltaY);
+ EXPECT_EQ(2, merged_event.modifiers);
+ merged_event = GestureEventSecondFromLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GesturePinchUpdate, merged_event.type);
+ EXPECT_EQ(0.5, merged_event.data.pinchUpdate.scale);
+ EXPECT_EQ(1, merged_event.modifiers);
+
+ // Check that the ACK sends the next scroll pinch pair.
+ SendInputEventACK(WebInputEvent::GesturePinchUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetFirstMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the ACK sends the second message.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Check that the ACK sends the second message.
+ SendInputEventACK(WebInputEvent::GesturePinchUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the queue is empty after ACK and no messages get sent.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+}
+
+#if GTEST_HAS_PARAM_TEST
+TEST_P(ImmediateInputRouterWithSourceTest, GestureFlingCancelsFiltered) {
+ WebGestureEvent::SourceDevice source_device = GetParam();
+
+ // Turn off debounce handling for test isolation.
+ set_debounce_interval_time_ms(0);
+ // GFC without previous GFS is dropped.
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ // GFC after previous GFS is dispatched and acked.
+ process_->sink().ClearMessages();
+ SimulateGestureFlingStartEvent(0, -10, source_device);
+ EXPECT_TRUE(FlingInProgress());
+ SendInputEventACK(WebInputEvent::GestureFlingStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(2U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::GestureFlingCancel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ // GFC before previous GFS is acked.
+ process_->sink().ClearMessages();
+ SimulateGestureFlingStartEvent(0, -10, source_device);
+ EXPECT_TRUE(FlingInProgress());
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+
+ // Advance state realistically.
+ SendInputEventACK(WebInputEvent::GestureFlingStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ SendInputEventACK(WebInputEvent::GestureFlingCancel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ base::MessageLoop::current()->RunUntilIdle();
+ client_->ExpectAckCalled(2);
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ // GFS is added to the queue if another event is pending
+ process_->sink().ClearMessages();
+ SimulateGestureScrollUpdateEvent(8, -7, 0);
+ SimulateGestureFlingStartEvent(0, -10, source_device);
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ WebGestureEvent merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureFlingStart, merged_event.type);
+ EXPECT_TRUE(FlingInProgress());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+
+ // GFS in queue means that a GFC is added to the queue
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ merged_event =GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureFlingCancel, merged_event.type);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+
+ // Adding a second GFC is dropped.
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(3U, GestureEventLastQueueEventSize());
+
+ // Adding another GFS will add it to the queue.
+ SimulateGestureFlingStartEvent(0, -10, source_device);
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureFlingStart, merged_event.type);
+ EXPECT_TRUE(FlingInProgress());
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+
+ // GFS in queue means that a GFC is added to the queue
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureFlingCancel, merged_event.type);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(5U, GestureEventLastQueueEventSize());
+
+ // Adding another GFC with a GFC already there is dropped.
+ SimulateGestureEvent(WebInputEvent::GestureFlingCancel, source_device);
+ merged_event = GestureEventLastQueueEvent();
+ EXPECT_EQ(WebInputEvent::GestureFlingCancel, merged_event.type);
+ EXPECT_FALSE(FlingInProgress());
+ EXPECT_EQ(5U, GestureEventLastQueueEventSize());
+}
+
+INSTANTIATE_TEST_CASE_P(AllSources,
+ ImmediateInputRouterWithSourceTest,
+ testing::Values(WebGestureEvent::Touchscreen,
+ WebGestureEvent::Touchpad));
+#endif // GTEST_HAS_PARAM_TEST
+
+// Test that GestureTapDown events are deferred.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDown) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureTapDown,
+ GestureEventLastQueueEvent().type);
+}
+
+// Test that GestureTapDown events are sent immediately on GestureTap.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDownSentOnTap) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureTap,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureTap,
+ GestureEventLastQueueEvent().type);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(WebInputEvent::GestureTapDown,
+ client_->immediately_sent_gesture_event().event.type);
+
+ // If the deferral timer incorrectly fired, it sent an extra message.
+ EXPECT_EQ(1U, process_->sink().message_count());
+}
+
+// Test that only a single GestureTapDown event is sent when tap occurs after
+// the timeout.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDownOnlyOnce) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ // Wait long enough for the timeout and verify it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureTapDown,
+ GestureEventLastQueueEvent().type);
+
+ // Now send the tap gesture and verify we didn't get an extra TapDown.
+ SimulateGestureEvent(WebInputEvent::GestureTap,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureTap,
+ GestureEventLastQueueEvent().type);
+}
+
+// Test that scroll events during the deferral interval drop the GestureTapDown.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDownAnulledOnScroll) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(WebInputEvent::GestureScrollBegin,
+ GestureEventLastQueueEvent().type);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ // If the deferral timer incorrectly fired, it will send an extra message.
+ EXPECT_EQ(1U, process_->sink().message_count());
+}
+
+// Test that a tap cancel event during the deferral interval drops the
+// GestureTapDown.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDownAnulledOnTapCancel) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureTapCancel,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ // If the deferral timer incorrectly fired, it will send an extra message.
+ EXPECT_EQ(0U, process_->sink().message_count());
+}
+
+// Test that if a GestureTapDown gets sent, any corresponding GestureTapCancel
+// is also sent.
+TEST_F(ImmediateInputRouterTest, DeferredGestureTapDownTapCancel) {
+ // Set some sort of short deferral timeout
+ set_maximum_tap_gap_time_ms(5);
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, GestureEventLastQueueEventSize());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureTapCancel,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+}
+
+// Test that a GestureScrollEnd | GestureFlingStart are deferred during the
+// debounce interval, that Scrolls are not and that the deferred events are
+// sent after that timer fires.
+TEST_F(ImmediateInputRouterTest, DebounceDefersFollowingGestureEvents) {
+ set_debounce_interval_time_ms(3);
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(ScrollingInProgress());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(ScrollingInProgress());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, GestureEventDebouncingQueueSize());
+
+ SimulateGestureFlingStartEvent(0, 10, WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(2U, GestureEventDebouncingQueueSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureTapDown,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(3U, GestureEventDebouncingQueueSize());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(5));
+ base::MessageLoop::current()->Run();
+
+ // The deferred events are correctly queued in coalescing queue.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ if (shouldDeferTapDownEvents())
+ // NOTE: The TapDown is still deferred hence not queued.
+ EXPECT_EQ(4U, GestureEventLastQueueEventSize());
+ else
+ EXPECT_EQ(5U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, GestureEventDebouncingQueueSize());
+ EXPECT_FALSE(ScrollingInProgress());
+
+ // Verify that the coalescing queue contains the correct events.
+ WebInputEvent::Type expected[] = {
+ WebInputEvent::GestureScrollUpdate,
+ WebInputEvent::GestureScrollUpdate,
+ WebInputEvent::GestureScrollEnd,
+ WebInputEvent::GestureFlingStart};
+
+ for (unsigned i = 0; i < sizeof(expected) / sizeof(WebInputEvent::Type);
+ i++) {
+ WebGestureEvent merged_event = GestureEventQueueEventAt(i);
+ EXPECT_EQ(expected[i], merged_event.type);
+ }
+}
+
+// Test that non-scroll events are deferred while scrolling during the debounce
+// interval and are discarded if a GestureScrollUpdate event arrives before the
+// interval end.
+TEST_F(ImmediateInputRouterTest, DebounceDropsDeferredEvents) {
+ set_debounce_interval_time_ms(3);
+ EXPECT_FALSE(ScrollingInProgress());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(ScrollingInProgress());
+
+ // This event should get discarded.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, GestureEventDebouncingQueueSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(ScrollingInProgress());
+
+ // Verify that the coalescing queue contains the correct events.
+ WebInputEvent::Type expected[] = {
+ WebInputEvent::GestureScrollUpdate,
+ WebInputEvent::GestureScrollUpdate};
+
+ for (unsigned i = 0; i < sizeof(expected) / sizeof(WebInputEvent::Type);
+ i++) {
+ WebGestureEvent merged_event = GestureEventQueueEventAt(i);
+ EXPECT_EQ(expected[i], merged_event.type);
+ }
+}
+
+// Tests that touch-events are queued properly.
+TEST_F(ImmediateInputRouterTest, TouchEventQueue) {
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ client_->ExpectSendImmediatelyCalled(true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // The second touch should not be sent since one is already in queue.
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+ client_->ExpectSendImmediatelyCalled(false);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ // Receive an ACK for the first touch-event.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(WebInputEvent::TouchStart,
+ client_->acked_touch_event().event.type);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->acked_touch_event().event.type);
+ EXPECT_EQ(0U, process_->sink().message_count());
+}
+
+// Tests that the touch-queue is emptied if a page stops listening for touch
+// events.
+TEST_F(ImmediateInputRouterTest, TouchEventQueueFlush) {
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_TRUE(client_->has_touch_handler());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ // Send a touch-press event.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+
+ for (int i = 5; i < 15; ++i) {
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ MoveTouchPoint(0, i, i);
+ SendTouchEvent();
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ }
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(32U, TouchEventQueueSize());
+
+ // Receive an ACK for the first touch-event. One of the queued touch-event
+ // should be forwarded.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(31U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchStart,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // The page stops listening for touch-events. The touch-event queue should now
+ // be emptied, but none of the queued touch-events should be sent to the
+ // renderer.
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false));
+ EXPECT_FALSE(client_->has_touch_handler());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_FALSE(input_router_->ShouldForwardTouchEvent());
+}
+
+// Tests that touch-events are coalesced properly in the queue.
+TEST_F(ImmediateInputRouterTest, TouchEventQueueCoalesce) {
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ // Send a touch-press event.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ client_->ExpectSendImmediatelyCalled(true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Send a few touch-move events, followed by a touch-release event. All the
+ // touch-move events should be coalesced into a single event.
+ for (int i = 5; i < 15; ++i) {
+ MoveTouchPoint(0, i, i);
+ SendTouchEvent();
+ }
+ client_->ExpectSendImmediatelyCalled(false);
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(3U, TouchEventQueueSize());
+ client_->ExpectSendImmediatelyCalled(false);
+
+ // ACK the press.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchStart,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(1);
+ process_->sink().ClearMessages();
+
+ // Coalesced touch-move events should be sent.
+ client_->ExpectSendImmediatelyCalled(true);
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->immediately_sent_touch_event().event.type);
+
+ // ACK the moves.
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(10);
+ process_->sink().ClearMessages();
+
+ // ACK the release.
+ SendInputEventACK(WebInputEvent::TouchEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchEnd,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(1);
+}
+
+// Tests that an event that has already been sent but hasn't been ack'ed yet
+// doesn't get coalesced with newer events.
+TEST_F(ImmediateInputRouterTest, SentTouchEventDoesNotCoalesce) {
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ // Send a touch-press event.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Send a few touch-move events, followed by a touch-release event. All the
+ // touch-move events should be coalesced into a single event.
+ for (int i = 5; i < 15; ++i) {
+ MoveTouchPoint(0, i, i);
+ SendTouchEvent();
+ }
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ // The coalesced touch-move event has been sent to the renderer. Any new
+ // touch-move event should not be coalesced with the sent event.
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ MoveTouchPoint(0, 7, 7);
+ SendTouchEvent();
+ EXPECT_EQ(2U, TouchEventQueueSize());
+}
+
+// Tests that coalescing works correctly for multi-touch events.
+TEST_F(ImmediateInputRouterTest, TouchEventQueueMultiTouch) {
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ // Press the first finger.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Move the finger.
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ // Now press a second finger.
+ PressTouchPoint(2, 2);
+ SendTouchEvent();
+ EXPECT_EQ(3U, TouchEventQueueSize());
+
+ // Move both fingers.
+ MoveTouchPoint(0, 10, 10);
+ MoveTouchPoint(1, 20, 20);
+ SendTouchEvent();
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ // Move only one finger now.
+ MoveTouchPoint(0, 15, 15);
+ SendTouchEvent();
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ // Move the other finger.
+ MoveTouchPoint(1, 25, 25);
+ SendTouchEvent();
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ // Make sure both fingers are marked as having been moved in the coalesced
+ // event.
+ const WebTouchEvent& event = latest_event();
+ EXPECT_EQ(WebTouchPoint::StateMoved, event.touches[0].state);
+ EXPECT_EQ(WebTouchPoint::StateMoved, event.touches[1].state);
+}
+
+// Tests that if a touch-event queue is destroyed in response to a touch-event
+// in the renderer, then there is no crash when the ACK for that touch-event
+// comes back.
+TEST_F(ImmediateInputRouterTest, TouchEventAckAfterQueueFlushed) {
+ // First, install a touch-event handler and send some touch-events to the
+ // renderer.
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ MoveTouchPoint(0, 10, 10);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ // Receive an ACK for the press. This should cause the queued touch-move to
+ // be sent to the renderer.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ // Uninstall the touch-event handler. This will cause the queue to be flushed.
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ // Now receive an ACK for the move.
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+}
+
+// Tests that touch-move events are not sent to the renderer if the preceding
+// touch-press event did not have a consumer (and consequently, did not hit the
+// main thread in the renderer). Also tests that all queued/coalesced touch
+// events are flushed immediately when the ACK for the touch-press comes back
+// with NO_CONSUMER status.
+TEST_F(ImmediateInputRouterTest, TouchEventQueueNoConsumer) {
+ // The first touch-press should reach the renderer.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // The second touch should not be sent since one is already in queue.
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ // Receive an ACK for the first touch-event. This should release the queued
+ // touch-event, but it should not be sent to the renderer.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(2);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Send a release event. This should not reach the renderer.
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(WebInputEvent::TouchEnd,
+ client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(1);
+
+ // Send a press-event, followed by move and release events, and another press
+ // event, before the ACK for the first press event comes back. All of the
+ // events should be queued first. After the NO_CONSUMER ack for the first
+ // touch-press, all events upto the second touch-press should be flushed.
+ PressTouchPoint(10, 10);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+ MoveTouchPoint(0, 6, 5);
+ SendTouchEvent();
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+
+ PressTouchPoint(6, 5);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ // The queue should hold the first sent touch-press event, the coalesced
+ // touch-move event, the touch-end event and the second touch-press event.
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(WebInputEvent::TouchEnd, client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(4);
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ // ACK the second press event as NO_CONSUMER too.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(WebInputEvent::TouchStart, client_->acked_touch_event().event.type);
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ // Send a second press event. Even though the first touch had NO_CONSUMER,
+ // this press event should reach the renderer.
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+}
+
+TEST_F(ImmediateInputRouterTest, TouchEventQueueConsumerIgnoreMultiFinger) {
+ // Press two touch points and move them around a bit. The renderer consumes
+ // the events for the first touch point, but returns NO_CONSUMER_EXISTS for
+ // the second touch point.
+
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ MoveTouchPoint(0, 5, 5);
+ SendTouchEvent();
+
+ PressTouchPoint(10, 10);
+ SendTouchEvent();
+
+ MoveTouchPoint(0, 2, 2);
+ SendTouchEvent();
+
+ MoveTouchPoint(1, 4, 10);
+ SendTouchEvent();
+
+ MoveTouchPoint(0, 10, 10);
+ MoveTouchPoint(1, 20, 20);
+ SendTouchEvent();
+
+ // Since the first touch-press is still pending ACK, no other event should
+ // have been sent to the renderer.
+ EXPECT_EQ(0U, process_->sink().message_count());
+ // The queue includes the two presses, the first touch-move of the first
+ // point, and a coalesced touch-move of both points.
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ // ACK the first press as CONSUMED. This should cause the first touch-move of
+ // the first touch-point to be dispatched.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(3U, TouchEventQueueSize());
+
+ // ACK the first move as CONSUMED.
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(2U, TouchEventQueueSize());
+
+ // ACK the second press as NO_CONSUMER_EXISTS. This will dequeue the coalesced
+ // touch-move event (which contains both touch points). Although the second
+ // touch-point does not need to be sent to the renderer, the first touch-point
+ // did move, and so the coalesced touch-event will be sent to the renderer.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(1U, TouchEventQueueSize());
+
+ // ACK the coalesced move as NOT_CONSUMED.
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ // Move just the second touch point. Because the first touch point did not
+ // move, this event should not reach the renderer.
+ MoveTouchPoint(1, 30, 30);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ // Move just the first touch point. This should reach the renderer.
+ MoveTouchPoint(0, 10, 10);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ // Move both fingers. This event should reach the renderer (after the ACK of
+ // the previous move event is received), because the first touch point did
+ // move.
+ MoveTouchPoint(0, 15, 15);
+ MoveTouchPoint(1, 25, 25);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+
+ // Release the first finger. Then move the second finger around some, then
+ // press another finger. Once the release event is ACKed, the move events of
+ // the second finger should be immediately released to the view, and the
+ // touch-press event should be dispatched to the renderer.
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ MoveTouchPoint(1, 40, 40);
+ SendTouchEvent();
+
+ MoveTouchPoint(1, 50, 50);
+ SendTouchEvent();
+
+ PressTouchPoint(1, 1);
+ SendTouchEvent();
+
+ MoveTouchPoint(1, 30, 30);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(4U, TouchEventQueueSize());
+
+ SendInputEventACK(WebInputEvent::TouchEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(2U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->acked_touch_event().event.type);
+ process_->sink().ClearMessages();
+
+ // ACK the press with NO_CONSUMED_EXISTS. This should release the queued
+ // touch-move events to the view.
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_EQ(WebInputEvent::TouchMove,
+ client_->acked_touch_event().event.type);
+
+ ReleaseTouchPoint(2);
+ ReleaseTouchPoint(1);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+}
+
+#if defined(OS_WIN) || defined(USE_AURA)
+// Tests that the acked events have correct state. (ui::Events are used only on
+// windows and aura)
+TEST_F(ImmediateInputRouterTest, AckedTouchEventState) {
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, TouchEventQueueSize());
+ EXPECT_TRUE(input_router_->ShouldForwardTouchEvent());
+
+ // Send a bunch of events, and make sure the ACKed events are correct.
+ ScopedVector<ui::TouchEvent> expected_events;
+
+ // Use a custom timestamp for all the events to test that the acked events
+ // have the same timestamp;
+ base::TimeDelta timestamp = base::Time::NowFromSystemTime() - base::Time();
+ timestamp -= base::TimeDelta::FromSeconds(600);
+
+ // Press the first finger.
+ PressTouchPoint(1, 1);
+ SetTouchTimestamp(timestamp);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ expected_events.push_back(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
+ gfx::Point(1, 1), 0, timestamp));
+
+ // Move the finger.
+ timestamp += base::TimeDelta::FromSeconds(10);
+ MoveTouchPoint(0, 5, 5);
+ SetTouchTimestamp(timestamp);
+ SendTouchEvent();
+ EXPECT_EQ(2U, TouchEventQueueSize());
+ expected_events.push_back(new ui::TouchEvent(ui::ET_TOUCH_MOVED,
+ gfx::Point(5, 5), 0, timestamp));
+
+ // Now press a second finger.
+ timestamp += base::TimeDelta::FromSeconds(10);
+ PressTouchPoint(2, 2);
+ SetTouchTimestamp(timestamp);
+ SendTouchEvent();
+ EXPECT_EQ(3U, TouchEventQueueSize());
+ expected_events.push_back(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
+ gfx::Point(2, 2), 1, timestamp));
+
+ // Move both fingers.
+ timestamp += base::TimeDelta::FromSeconds(10);
+ MoveTouchPoint(0, 10, 10);
+ MoveTouchPoint(1, 20, 20);
+ SetTouchTimestamp(timestamp);
+ SendTouchEvent();
+ EXPECT_EQ(4U, TouchEventQueueSize());
+ expected_events.push_back(new ui::TouchEvent(ui::ET_TOUCH_MOVED,
+ gfx::Point(10, 10), 0, timestamp));
+ expected_events.push_back(new ui::TouchEvent(ui::ET_TOUCH_MOVED,
+ gfx::Point(20, 20), 1, timestamp));
+
+ // Receive the ACKs and make sure the generated events from the acked events
+ // are correct.
+ WebInputEvent::Type acks[] = { WebInputEvent::TouchStart,
+ WebInputEvent::TouchMove,
+ WebInputEvent::TouchStart,
+ WebInputEvent::TouchMove };
+
+ TouchEventCoordinateSystem coordinate_system = LOCAL_COORDINATES;
+#if !defined(OS_WIN)
+ coordinate_system = SCREEN_COORDINATES;
+#endif
+ for (size_t i = 0; i < arraysize(acks); ++i) {
+ SendInputEventACK(acks[i],
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(acks[i], client_->acked_touch_event().event.type);
+ ScopedVector<ui::TouchEvent> acked;
+
+ MakeUITouchEventsFromWebTouchEvents(
+ client_->acked_touch_event(), &acked, coordinate_system);
+ bool success = EventListIsSubset(acked, expected_events);
+ EXPECT_TRUE(success) << "Failed on step: " << i;
+ if (!success)
+ break;
+ expected_events.erase(expected_events.begin(),
+ expected_events.begin() + acked.size());
+ }
+
+ EXPECT_EQ(0U, expected_events.size());
+}
+#endif // defined(OS_WIN) || defined(USE_AURA)
+
+TEST_F(ImmediateInputRouterTest, UnhandledWheelEvent) {
+ // Simulate wheel events.
+ SimulateWheelEvent(0, -5, 0, false); // sent directly
+ SimulateWheelEvent(0, -10, 0, false); // enqueued
+
+ // Check that only the first event was sent.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Indicate that the wheel event was unhandled.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Check that the correct unhandled wheel event was received.
+ client_->ExpectAckCalled(1);
+ EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, client_->ack_state());
+ EXPECT_EQ(client_->acked_wheel_event().deltaY, -5);
+
+ // Check that the second event was sent.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Check that the correct unhandled wheel event was received.
+ EXPECT_EQ(client_->acked_wheel_event().deltaY, -5);
+}
+
+// Tests that no touch move events are sent to renderer during scrolling.
+TEST_F(ImmediateInputRouterTest, NoTouchMoveWhileScroll) {
+ EnableNoTouchToRendererWhileScrolling();
+ set_debounce_interval_time_ms(0);
+ input_router_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ process_->sink().ClearMessages();
+
+ // First touch press.
+ PressTouchPoint(0, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Touch move will trigger scroll.
+ MoveTouchPoint(0, 20, 5);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(no_touch_move_to_renderer());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Touch move should not be sent to renderer.
+ MoveTouchPoint(0, 30, 5);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Touch moves become ScrollUpdate.
+ SimulateGestureScrollUpdateEvent(20, 4, 0);
+ EXPECT_TRUE(no_touch_move_to_renderer());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Touch move should not be sent to renderer.
+ MoveTouchPoint(0, 65, 10);
+ SendTouchEvent();
+ EXPECT_EQ(0U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Touch end should still be sent to renderer.
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // On GestureScrollEnd, resume sending touch moves to renderer.
+ SimulateGestureEvent(WebKit::WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_FALSE(no_touch_move_to_renderer());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Now touch events should come through to renderer.
+ PressTouchPoint(80, 10);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ MoveTouchPoint(0, 80, 20);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+}
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/input_router.h b/chromium/content/browser/renderer_host/input/input_router.h
new file mode 100644
index 00000000000..2d8a4c6dd5d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/input_router.h
@@ -0,0 +1,70 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_H_
+
+#include "base/basictypes.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "ipc/ipc_listener.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class InputRouterClient;
+
+// The InputRouter allows the embedder to customize how input events are
+// sent to the renderer, and how responses are dispatched to the browser.
+// While the router should respect the relative order in which events are
+// received, it is free to customize when those events are dispatched.
+class InputRouter : public IPC::Listener {
+ public:
+ virtual ~InputRouter() {}
+
+ // Send and take ownership of the the given InputMsg_*. This should be used
+ // only for event types not associated with a WebInputEvent. Returns true on
+ // success and false otherwise.
+ virtual bool SendInput(IPC::Message* message) = 0;
+
+ // WebInputEvents
+ virtual void SendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) = 0;
+ virtual void SendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) = 0;
+ virtual void SendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info) = 0;
+ virtual void SendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) = 0;
+ virtual void SendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) = 0;
+ virtual void SendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) = 0;
+ virtual void SendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) = 0;
+ virtual void SendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) = 0;
+
+ // Returns the oldest queued or in-flight keyboard event sent to the router.
+ virtual const NativeWebKeyboardEvent* GetLastKeyboardEvent() const = 0;
+
+ // Returns |true| if the caller should immediately forward touch events to the
+ // router. When |false|, the caller can forego sending touch events, and
+ // instead consume them directly.
+ virtual bool ShouldForwardTouchEvent() const = 0;
+
+ // Returns |true| if the caller should immediately forward the provided
+ // |gesture_event| to the router.
+ virtual bool ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const = 0;
+
+ // Returns |true| if the router has any queued or in-flight gesture events.
+ virtual bool HasQueuedGestureEvents() const = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_H_
diff --git a/chromium/content/browser/renderer_host/input/input_router_client.h b/chromium/content/browser/renderer_host/input/input_router_client.h
new file mode 100644
index 00000000000..9c854224b52
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/input_router_client.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_CLIENT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_CLIENT_H_
+
+#include "content/common/content_export.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace ui {
+struct LatencyInfo;
+}
+
+namespace content {
+
+class CONTENT_EXPORT InputRouterClient {
+ public:
+ virtual ~InputRouterClient() {}
+
+ // Called just prior to events being sent to the renderer, giving the client
+ // a chance to perform in-process event filtering.
+ // The returned disposition will yield the following behavior:
+ // * |NOT_CONSUMED| will result in |input_event| being sent as usual.
+ // * |CONSUMED| or |NO_CONSUMER_EXISTS| will trigger the appropriate ack.
+ // * |UNKNOWN| will result in |input_event| being dropped.
+ virtual InputEventAckState FilterInputEvent(
+ const WebKit::WebInputEvent& input_event,
+ const ui::LatencyInfo& latency_info) = 0;
+
+ // Called each time a WebInputEvent IPC is sent.
+ virtual void IncrementInFlightEventCount() = 0;
+
+ // Called each time a WebInputEvent ACK IPC is received.
+ virtual void DecrementInFlightEventCount() = 0;
+
+ // Called when the renderer notifies that it has touch event handlers.
+ virtual void OnHasTouchEventHandlers(bool has_handlers) = 0;
+
+ // Called upon Send*Event. Should return true if the event should be sent, and
+ // false if the event should be dropped.
+ virtual bool OnSendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info,
+ bool* is_shortcut) = 0;
+ virtual bool OnSendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) = 0;
+ virtual bool OnSendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) = 0;
+ virtual bool OnSendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) = 0;
+ virtual bool OnSendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) = 0;
+ virtual bool OnSendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) = 0;
+ virtual bool OnSendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) = 0;
+ virtual bool OnSendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) = 0;
+
+ // Called upon event ack receipt from the renderer.
+ virtual void OnKeyboardEventAck(const NativeWebKeyboardEvent& event,
+ InputEventAckState ack_result) = 0;
+ virtual void OnWheelEventAck(const WebKit::WebMouseWheelEvent& event,
+ InputEventAckState ack_result) = 0;
+ virtual void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) = 0;
+ virtual void OnGestureEventAck(const WebKit::WebGestureEvent& event,
+ InputEventAckState ack_result) = 0;
+ virtual void OnUnexpectedEventAck(bool bad_message) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_ROUTER_CLIENT_H_
diff --git a/chromium/content/browser/renderer_host/input/tap_suppression_controller.cc b/chromium/content/browser/renderer_host/input/tap_suppression_controller.cc
new file mode 100644
index 00000000000..387576c2c2a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/tap_suppression_controller.cc
@@ -0,0 +1,149 @@
+// 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 "content/browser/renderer_host/input/tap_suppression_controller.h"
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+
+namespace content {
+
+TapSuppressionController::TapSuppressionController(
+ TapSuppressionControllerClient* client)
+ : client_(client),
+ state_(TapSuppressionController::NOTHING) {
+}
+
+TapSuppressionController::~TapSuppressionController() {}
+
+void TapSuppressionController::GestureFlingCancel() {
+ switch (state_) {
+ case NOTHING:
+ case GFC_IN_PROGRESS:
+ case LAST_CANCEL_STOPPED_FLING:
+ state_ = GFC_IN_PROGRESS;
+ break;
+ case TAP_DOWN_STASHED:
+ break;
+ }
+}
+
+void TapSuppressionController::GestureFlingCancelAck(bool processed) {
+ base::TimeTicks event_time = Now();
+ switch (state_) {
+ case NOTHING:
+ break;
+ case GFC_IN_PROGRESS:
+ if (processed)
+ fling_cancel_time_ = event_time;
+ state_ = LAST_CANCEL_STOPPED_FLING;
+ break;
+ case TAP_DOWN_STASHED:
+ if (!processed) {
+ TRACE_EVENT0("browser",
+ "TapSuppressionController::GestureFlingCancelAck");
+ StopTapDownTimer();
+ client_->ForwardStashedTapDownForDeferral();
+ state_ = NOTHING;
+ } // Else waiting for the timer to release the stashed tap down.
+ break;
+ case LAST_CANCEL_STOPPED_FLING:
+ break;
+ }
+}
+
+bool TapSuppressionController::ShouldDeferTapDown() {
+ base::TimeTicks event_time = Now();
+ switch (state_) {
+ case NOTHING:
+ return false;
+ case GFC_IN_PROGRESS:
+ state_ = TAP_DOWN_STASHED;
+ StartTapDownTimer(
+ base::TimeDelta::FromMilliseconds(client_->MaxTapGapTimeInMs()));
+ return true;
+ case TAP_DOWN_STASHED:
+ NOTREACHED() << "TapDown on TAP_DOWN_STASHED state";
+ state_ = NOTHING;
+ return false;
+ case LAST_CANCEL_STOPPED_FLING:
+ if ((event_time - fling_cancel_time_).InMilliseconds()
+ < client_->MaxCancelToDownTimeInMs()) {
+ state_ = TAP_DOWN_STASHED;
+ StartTapDownTimer(
+ base::TimeDelta::FromMilliseconds(client_->MaxTapGapTimeInMs()));
+ return true;
+ } else {
+ state_ = NOTHING;
+ return false;
+ }
+ }
+ NOTREACHED() << "Invalid state";
+ return false;
+}
+
+bool TapSuppressionController::ShouldSuppressTapUp() {
+ switch (state_) {
+ case NOTHING:
+ case GFC_IN_PROGRESS:
+ return false;
+ case TAP_DOWN_STASHED:
+ state_ = NOTHING;
+ StopTapDownTimer();
+ client_->DropStashedTapDown();
+ return true;
+ case LAST_CANCEL_STOPPED_FLING:
+ NOTREACHED() << "Invalid TapUp on LAST_CANCEL_STOPPED_FLING state";
+ }
+ return false;
+}
+
+bool TapSuppressionController::ShouldSuppressTapCancel() {
+ switch (state_) {
+ case NOTHING:
+ case GFC_IN_PROGRESS:
+ return false;
+ case TAP_DOWN_STASHED:
+ state_ = NOTHING;
+ StopTapDownTimer();
+ client_->DropStashedTapDown();
+ return true;
+ case LAST_CANCEL_STOPPED_FLING:
+ NOTREACHED() << "Invalid TapCancel on LAST_CANCEL_STOPPED_FLING state";
+ }
+ return false;
+}
+
+base::TimeTicks TapSuppressionController::Now() {
+ return base::TimeTicks::Now();
+}
+
+void TapSuppressionController::StartTapDownTimer(const base::TimeDelta& delay) {
+ tap_down_timer_.Start(FROM_HERE, delay, this,
+ &TapSuppressionController::TapDownTimerExpired);
+}
+
+void TapSuppressionController::StopTapDownTimer() {
+ tap_down_timer_.Stop();
+}
+
+void TapSuppressionController::TapDownTimerExpired() {
+ switch (state_) {
+ case NOTHING:
+ case GFC_IN_PROGRESS:
+ case LAST_CANCEL_STOPPED_FLING:
+ NOTREACHED() << "Timer fired on invalid state.";
+ state_ = NOTHING;
+ break;
+ case TAP_DOWN_STASHED:
+ TRACE_EVENT0("browser",
+ "TapSuppressionController::TapDownTimerExpired");
+ client_->ForwardStashedTapDownSkipDeferral();
+ state_ = NOTHING;
+ break;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/tap_suppression_controller.h b/chromium/content/browser/renderer_host/input/tap_suppression_controller.h
new file mode 100644
index 00000000000..b355a0dda2d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/tap_suppression_controller.h
@@ -0,0 +1,77 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_H_
+
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class TapSuppressionControllerClient;
+
+// The core controller for suppression of taps (touchpad or touchscreen)
+// immediately following a GestureFlingCancel event (caused by the same tap).
+// Only taps of sufficient speed and within a specified time window after a
+// GestureFlingCancel are suppressed.
+class CONTENT_EXPORT TapSuppressionController {
+ public:
+ explicit TapSuppressionController(TapSuppressionControllerClient* client);
+ virtual ~TapSuppressionController();
+
+ // Should be called whenever a GestureFlingCancel event is received.
+ void GestureFlingCancel();
+
+ // Should be called whenever an ACK for a GestureFlingCancel event is
+ // received. |processed| is true when the GestureFlingCancel actually stopped
+ // a fling and therefore should suppress the forwarding of the following tap.
+ void GestureFlingCancelAck(bool processed);
+
+ // Should be called whenever a tap down (touchpad or touchscreen) is received.
+ // Returns true if the tap down should be deferred. The caller is responsible
+ // for keeping the event for later release, if needed.
+ bool ShouldDeferTapDown();
+
+ // Should be called whenever a tap up (touchpad or touchscreen) is received.
+ // Returns true if the tap up should be suppressed.
+ bool ShouldSuppressTapUp();
+
+ // Should be called whenever a tap cancel is received. Returns true if the tap
+ // cancel should be suppressed.
+ bool ShouldSuppressTapCancel();
+
+ protected:
+ virtual base::TimeTicks Now();
+ virtual void StartTapDownTimer(const base::TimeDelta& delay);
+ virtual void StopTapDownTimer();
+ void TapDownTimerExpired();
+
+ private:
+ friend class MockTapSuppressionController;
+
+ enum State {
+ NOTHING,
+ GFC_IN_PROGRESS,
+ TAP_DOWN_STASHED,
+ LAST_CANCEL_STOPPED_FLING,
+ };
+
+
+ TapSuppressionControllerClient* client_;
+ base::OneShotTimer<TapSuppressionController> tap_down_timer_;
+ State state_;
+
+ // TODO(rjkroege): During debugging, the event times did not prove reliable.
+ // Replace the use of base::TimeTicks with an accurate event time when they
+ // become available post http://crbug.com/119556.
+ base::TimeTicks fling_cancel_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(TapSuppressionController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/input/tap_suppression_controller_client.h b/chromium/content/browser/renderer_host/input/tap_suppression_controller_client.h
new file mode 100644
index 00000000000..821f5467920
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/tap_suppression_controller_client.h
@@ -0,0 +1,46 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_CLIENT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_CLIENT_H_
+
+namespace content {
+
+// This class provides an interface for callbacks made by
+// TapSuppressionController.
+class TapSuppressionControllerClient {
+ public:
+ virtual ~TapSuppressionControllerClient() {}
+
+ // Derived classes should implement this function to return the maximum time
+ // they allow between a GestureFlingCancel and its corresponding tap down.
+ virtual int MaxCancelToDownTimeInMs() = 0;
+
+ // Derived classes should implement this function to return the maximum time
+ // they allow between a single tap's down and up events.
+ virtual int MaxTapGapTimeInMs() = 0;
+
+ // Called whenever the deferred tap down (if saved) should be dropped totally.
+ virtual void DropStashedTapDown() = 0;
+
+ // Called whenever the deferred tap down (if saved) should be forwarded to the
+ // renderer. In this case, the tap down should go back to normal path it was
+ // on before being deferred.
+ virtual void ForwardStashedTapDownForDeferral() = 0;
+
+ // Called whenever the deferred tap down (if saved) should be forwarded to the
+ // renderer. In this case, the tap down should skip deferral filter, because
+ // it is handled here, and there is no need to delay it more.
+ virtual void ForwardStashedTapDownSkipDeferral() = 0;
+
+ protected:
+ TapSuppressionControllerClient() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TapSuppressionControllerClient);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TAP_SUPPRESSION_CONTROLLER_CLIENT_H_
diff --git a/chromium/content/browser/renderer_host/input/tap_suppression_controller_unittest.cc b/chromium/content/browser/renderer_host/input/tap_suppression_controller_unittest.cc
new file mode 100644
index 00000000000..a31471eaef9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/tap_suppression_controller_unittest.cc
@@ -0,0 +1,546 @@
+// 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/tap_suppression_controller.h"
+#include "content/browser/renderer_host/tap_suppression_controller_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace content {
+
+class MockTapSuppressionController : public TapSuppressionController,
+ public TapSuppressionControllerClient {
+ public:
+ using TapSuppressionController::NOTHING;
+ using TapSuppressionController::GFC_IN_PROGRESS;
+ using TapSuppressionController::TAP_DOWN_STASHED;
+ using TapSuppressionController::LAST_CANCEL_STOPPED_FLING;
+
+ enum Action {
+ NONE = 0,
+ TAP_DOWN_DEFERRED = 1 << 0,
+ TAP_DOWN_FORWARDED = 1 << 1,
+ TAP_DOWN_DROPPED = 1 << 2,
+ TAP_UP_SUPPRESSED = 1 << 3,
+ TAP_UP_FORWARDED = 1 << 4,
+ TAP_CANCEL_SUPPRESSED = 1 << 5,
+ TAP_CANCEL_FORWARDED = 1 << 6,
+ TAP_DOWN_FORWARDED_FOR_DEFERRAL = 1 << 7,
+ TAP_DOWN_FORWARDED_SKIPPING_DEFERRAL = 1 << 8,
+ };
+
+ MockTapSuppressionController()
+ : TapSuppressionController(this),
+ max_cancel_to_down_time_in_ms_(1),
+ max_tap_gap_time_in_ms_(1),
+ last_actions_(NONE),
+ time_(),
+ timer_started_(false) {
+ }
+
+ virtual ~MockTapSuppressionController() {}
+
+ void SendGestureFlingCancel() {
+ last_actions_ = NONE;
+ GestureFlingCancel();
+ }
+
+ void SendGestureFlingCancelAck(bool processed) {
+ last_actions_ = NONE;
+ GestureFlingCancelAck(processed);
+ }
+
+ void SendTapDown() {
+ last_actions_ = NONE;
+ if (ShouldDeferTapDown())
+ last_actions_ |= TAP_DOWN_DEFERRED;
+ else
+ last_actions_ |= TAP_DOWN_FORWARDED;
+ }
+
+ void SendTapUp() {
+ last_actions_ = NONE;
+ if (ShouldSuppressTapUp())
+ last_actions_ |= TAP_UP_SUPPRESSED;
+ else
+ last_actions_ |= TAP_UP_FORWARDED;
+ }
+
+ void SendTapCancel() {
+ last_actions_ = NONE;
+ if (ShouldSuppressTapCancel())
+ last_actions_ |= TAP_CANCEL_SUPPRESSED;
+ else
+ last_actions_ |= TAP_CANCEL_FORWARDED;
+ }
+
+ void AdvanceTime(const base::TimeDelta& delta) {
+ last_actions_ = NONE;
+ time_ += delta;
+ if (timer_started_ && time_ >= timer_expiry_time_) {
+ timer_started_ = false;
+ TapDownTimerExpired();
+ }
+ }
+
+ void set_max_cancel_to_down_time_in_ms(int val) {
+ max_cancel_to_down_time_in_ms_ = val;
+ }
+
+ void set_max_tap_gap_time_in_ms(int val) {
+ max_tap_gap_time_in_ms_ = val;
+ }
+
+ State state() { return state_; }
+
+ int last_actions() { return last_actions_; }
+
+ protected:
+ virtual base::TimeTicks Now() OVERRIDE {
+ return time_;
+ }
+
+ virtual void StartTapDownTimer(const base::TimeDelta& delay) OVERRIDE {
+ timer_expiry_time_ = time_ + delay;
+ timer_started_ = true;
+ }
+
+ virtual void StopTapDownTimer() OVERRIDE {
+ timer_started_ = false;
+ }
+
+ private:
+ // TapSuppressionControllerClient implementation
+ virtual int MaxCancelToDownTimeInMs() OVERRIDE {
+ return max_cancel_to_down_time_in_ms_;
+ }
+
+ virtual int MaxTapGapTimeInMs() OVERRIDE {
+ return max_tap_gap_time_in_ms_;
+ }
+
+ virtual void DropStashedTapDown() OVERRIDE {
+ last_actions_ |= TAP_DOWN_DROPPED;
+ }
+
+ virtual void ForwardStashedTapDownForDeferral() OVERRIDE {
+ last_actions_ |= TAP_DOWN_FORWARDED_FOR_DEFERRAL;
+ }
+
+ virtual void ForwardStashedTapDownSkipDeferral() OVERRIDE {
+ last_actions_ |= TAP_DOWN_FORWARDED_SKIPPING_DEFERRAL;
+ }
+
+ // Hiding some derived public methods
+ using TapSuppressionController::GestureFlingCancel;
+ using TapSuppressionController::GestureFlingCancelAck;
+ using TapSuppressionController::ShouldDeferTapDown;
+ using TapSuppressionController::ShouldSuppressTapUp;
+ using TapSuppressionController::ShouldSuppressTapCancel;
+
+ int max_cancel_to_down_time_in_ms_;
+ int max_tap_gap_time_in_ms_;
+
+ int last_actions_;
+
+ base::TimeTicks time_;
+ bool timer_started_;
+ base::TimeTicks timer_expiry_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTapSuppressionController);
+};
+
+class TapSuppressionControllerTest : public testing::Test {
+ public:
+ TapSuppressionControllerTest() {
+ }
+ virtual ~TapSuppressionControllerTest() {
+ }
+
+ protected:
+ // testing::Test
+ virtual void SetUp() {
+ tap_suppression_controller_.reset(new MockTapSuppressionController());
+ }
+
+ virtual void TearDown() {
+ tap_suppression_controller_.reset();
+ }
+
+ scoped_ptr<MockTapSuppressionController> tap_suppression_controller_;
+};
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes before
+// TapDown and everything happens without any delays.
+TEST_F(TapSuppressionControllerTest, GFCAckBeforeTapFast) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_SUPPRESSED |
+ MockTapSuppressionController::TAP_DOWN_DROPPED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes before
+// TapDown, but there is a small delay between TapDown and TapUp.
+TEST_F(TapSuppressionControllerTest, GFCAckBeforeTapInsufficientlyLateTapUp) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Wait less than allowed delay between TapDown and TapUp, so they are still
+ // considered a tap.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(7));
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_SUPPRESSED |
+ MockTapSuppressionController::TAP_DOWN_DROPPED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes before
+// TapDown, but there is a long delay between TapDown and TapUp.
+TEST_F(TapSuppressionControllerTest, GFCAckBeforeTapSufficientlyLateTapUp) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send processed GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Send MouseDown. This MouseDown should be suppressed, for now.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Wait more than allowed delay between TapDown and TapUp, so they are not
+ // considered a tap. This should release the previously suppressed TapDown.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(13));
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_FORWARDED_SKIPPING_DEFERRAL,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should not be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_FORWARDED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes before
+// TapDown, but there is a small delay between the Ack and TapDown.
+TEST_F(TapSuppressionControllerTest, GFCAckBeforeTapInsufficientlyLateTapDown) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Wait less than allowed delay between GestureFlingCancel and TapDown, so the
+ // TapDown is still considered associated with the GestureFlingCancel.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(7));
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_SUPPRESSED |
+ MockTapSuppressionController::TAP_DOWN_DROPPED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes before
+// TapDown, but there is a long delay between the Ack and TapDown.
+TEST_F(TapSuppressionControllerTest, GFCAckBeforeTapSufficientlyLateTapDown) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Wait more than allowed delay between GestureFlingCancel and TapDown, so the
+ // TapDown is not considered associated with the GestureFlingCancel.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(13));
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::LAST_CANCEL_STOPPED_FLING,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should not be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_FORWARDED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+
+ // Send MouseUp. This MouseUp should not be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_FORWARDED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when unprocessed GestureFlingCancel Ack
+// comes after TapDown and everything happens without any delay.
+TEST_F(TapSuppressionControllerTest, GFCAckUnprocessedAfterTapFast) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed, for now.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send unprocessed GestureFlingCancel Ack. This should release the
+ // previously suppressed TapDown.
+ tap_suppression_controller_->SendGestureFlingCancelAck(false);
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_FORWARDED_FOR_DEFERRAL,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should not be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_FORWARDED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when processed GestureFlingCancel Ack comes
+// after TapDown and everything happens without any delay.
+TEST_F(TapSuppressionControllerTest, GFCAckProcessedAfterTapFast) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send processed GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_SUPPRESSED |
+ MockTapSuppressionController::TAP_DOWN_DROPPED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes after
+// TapDown and there is a small delay between the Ack and TapUp.
+TEST_F(TapSuppressionControllerTest, GFCAckAfterTapInsufficientlyLateTapUp) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Wait less than allowed delay between TapDown and TapUp, so they are still
+ // considered as a tap.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(7));
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_SUPPRESSED |
+ MockTapSuppressionController::TAP_DOWN_DROPPED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+// Test TapSuppressionController for when GestureFlingCancel Ack comes after
+// TapDown and there is a long delay between the Ack and TapUp.
+TEST_F(TapSuppressionControllerTest, GFCAckAfterTapSufficientlyLateTapUp) {
+ tap_suppression_controller_->set_max_cancel_to_down_time_in_ms(10);
+ tap_suppression_controller_->set_max_tap_gap_time_in_ms(10);
+
+ // Send GestureFlingCancel.
+ tap_suppression_controller_->SendGestureFlingCancel();
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::GFC_IN_PROGRESS,
+ tap_suppression_controller_->state());
+
+ // Send TapDown. This TapDown should be suppressed, for now.
+ tap_suppression_controller_->SendTapDown();
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_DEFERRED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Send GestureFlingCancel Ack.
+ tap_suppression_controller_->SendGestureFlingCancelAck(true);
+ EXPECT_EQ(MockTapSuppressionController::NONE,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_STASHED,
+ tap_suppression_controller_->state());
+
+ // Wait more than allowed delay between TapDown and TapUp, so they are not
+ // considered as a tap. This should release the previously suppressed TapDown.
+ tap_suppression_controller_->AdvanceTime(TimeDelta::FromMilliseconds(13));
+ EXPECT_EQ(MockTapSuppressionController::TAP_DOWN_FORWARDED_SKIPPING_DEFERRAL,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+
+ // Send TapUp. This TapUp should not be suppressed.
+ tap_suppression_controller_->SendTapUp();
+ EXPECT_EQ(MockTapSuppressionController::TAP_UP_FORWARDED,
+ tap_suppression_controller_->last_actions());
+ EXPECT_EQ(MockTapSuppressionController::NOTHING,
+ tap_suppression_controller_->state());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/touch_event_queue.cc b/chromium/content/browser/renderer_host/input/touch_event_queue.cc
new file mode 100644
index 00000000000..e22c05d0f80
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touch_event_queue.cc
@@ -0,0 +1,240 @@
+// 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 "content/browser/renderer_host/input/touch_event_queue.h"
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "base/stl_util.h"
+
+namespace content {
+
+typedef std::vector<TouchEventWithLatencyInfo> WebTouchEventWithLatencyList;
+
+// This class represents a single coalesced touch event. However, it also keeps
+// track of all the original touch-events that were coalesced into a single
+// event. The coalesced event is forwarded to the renderer, while the original
+// touch-events are sent to the Client (on ACK for the coalesced event) so that
+// the Client receives the event with their original timestamp.
+class CoalescedWebTouchEvent {
+ public:
+ explicit CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event)
+ : coalesced_event_(event) {
+ events_.push_back(event);
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "input", "TouchEventQueue::QueueEvent", this);
+ }
+
+ ~CoalescedWebTouchEvent() {
+ TRACE_EVENT_ASYNC_END0(
+ "input", "TouchEventQueue::QueueEvent", this);
+ }
+
+ // Coalesces the event with the existing event if possible. Returns whether
+ // the event was coalesced.
+ bool CoalesceEventIfPossible(
+ const TouchEventWithLatencyInfo& event_with_latency) {
+ if (coalesced_event_.event.type == WebKit::WebInputEvent::TouchMove &&
+ event_with_latency.event.type == WebKit::WebInputEvent::TouchMove &&
+ coalesced_event_.event.modifiers ==
+ event_with_latency.event.modifiers &&
+ coalesced_event_.event.touchesLength ==
+ event_with_latency.event.touchesLength) {
+ TRACE_EVENT_INSTANT0(
+ "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
+ events_.push_back(event_with_latency);
+ // The WebTouchPoints include absolute position information. So it is
+ // sufficient to simply replace the previous event with the new event.
+ // However, it is necessary to make sure that all the points have the
+ // correct state, i.e. the touch-points that moved in the last event, but
+ // didn't change in the current event, will have Stationary state. It is
+ // necessary to change them back to Moved state.
+ const WebKit::WebTouchEvent last_event = coalesced_event_.event;
+ const ui::LatencyInfo last_latency = coalesced_event_.latency;
+ coalesced_event_ = event_with_latency;
+ coalesced_event_.latency.MergeWith(last_latency);
+ for (unsigned i = 0; i < last_event.touchesLength; ++i) {
+ if (last_event.touches[i].state == WebKit::WebTouchPoint::StateMoved)
+ coalesced_event_.event.touches[i].state =
+ WebKit::WebTouchPoint::StateMoved;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ const TouchEventWithLatencyInfo& coalesced_event() const {
+ return coalesced_event_;
+ }
+
+ WebTouchEventWithLatencyList::const_iterator begin() const {
+ return events_.begin();
+ }
+
+ WebTouchEventWithLatencyList::const_iterator end() const {
+ return events_.end();
+ }
+
+ size_t size() const { return events_.size(); }
+
+ private:
+ // This is the event that is forwarded to the renderer.
+ TouchEventWithLatencyInfo coalesced_event_;
+
+ // This is the list of the original events that were coalesced.
+ WebTouchEventWithLatencyList events_;
+
+ DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
+};
+
+TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
+ : client_(client),
+ dispatching_touch_ack_(false),
+ no_touch_move_to_renderer_(false) {
+ DCHECK(client);
+}
+
+TouchEventQueue::~TouchEventQueue() {
+ if (!touch_queue_.empty())
+ STLDeleteElements(&touch_queue_);
+}
+
+void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
+ // If the queueing of |event| was triggered by an ack dispatch, defer
+ // processing the event until the dispatch has finished.
+ if (touch_queue_.empty() && !dispatching_touch_ack_) {
+ // There is no touch event in the queue. Forward it to the renderer
+ // immediately.
+ touch_queue_.push_back(new CoalescedWebTouchEvent(event));
+ if (ShouldForwardToRenderer(event.event))
+ client_->SendTouchEventImmediately(event);
+ else
+ PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
+ ui::LatencyInfo());
+ return;
+ }
+
+ // If the last queued touch-event was a touch-move, and the current event is
+ // also a touch-move, then the events can be coalesced into a single event.
+ if (touch_queue_.size() > 1) {
+ CoalescedWebTouchEvent* last_event = touch_queue_.back();
+ if (last_event->CoalesceEventIfPossible(event))
+ return;
+ }
+ touch_queue_.push_back(new CoalescedWebTouchEvent(event));
+}
+
+void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info) {
+ DCHECK(!dispatching_touch_ack_);
+ if (touch_queue_.empty())
+ return;
+
+ // Update the ACK status for each touch point in the ACKed event.
+ const WebKit::WebTouchEvent& event =
+ touch_queue_.front()->coalesced_event().event;
+ if (event.type == WebKit::WebInputEvent::TouchEnd ||
+ event.type == WebKit::WebInputEvent::TouchCancel) {
+ // The points have been released. Erase the ACK states.
+ for (unsigned i = 0; i < event.touchesLength; ++i) {
+ const WebKit::WebTouchPoint& point = event.touches[i];
+ if (point.state == WebKit::WebTouchPoint::StateReleased ||
+ point.state == WebKit::WebTouchPoint::StateCancelled)
+ touch_ack_states_.erase(point.id);
+ }
+ } else if (event.type == WebKit::WebInputEvent::TouchStart) {
+ for (unsigned i = 0; i < event.touchesLength; ++i) {
+ const WebKit::WebTouchPoint& point = event.touches[i];
+ if (point.state == WebKit::WebTouchPoint::StatePressed)
+ touch_ack_states_[point.id] = ack_result;
+ }
+ }
+
+ PopTouchEventToClient(ack_result, latency_info);
+
+ // If there are queued touch events, then try to forward them to the renderer
+ // immediately, or ACK the events back to the client if appropriate.
+ while (!touch_queue_.empty()) {
+ const TouchEventWithLatencyInfo& touch =
+ touch_queue_.front()->coalesced_event();
+ if (ShouldForwardToRenderer(touch.event)) {
+ client_->SendTouchEventImmediately(touch);
+ break;
+ }
+ PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
+ ui::LatencyInfo());
+ }
+}
+
+void TouchEventQueue::FlushQueue() {
+ DCHECK(!dispatching_touch_ack_);
+ while (!touch_queue_.empty())
+ PopTouchEventToClient(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
+ ui::LatencyInfo());
+}
+
+size_t TouchEventQueue::GetQueueSize() const {
+ return touch_queue_.size();
+}
+
+const TouchEventWithLatencyInfo& TouchEventQueue::GetLatestEvent() const {
+ return touch_queue_.back()->coalesced_event();
+}
+
+void TouchEventQueue::PopTouchEventToClient(
+ InputEventAckState ack_result,
+ const ui::LatencyInfo& renderer_latency_info) {
+ if (touch_queue_.empty())
+ return;
+ scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
+ touch_queue_.pop_front();
+
+ // Note that acking the touch-event may result in multiple gestures being sent
+ // to the renderer, or touch-events being queued.
+ base::AutoReset<bool> dispatching_touch_ack(&dispatching_touch_ack_, true);
+
+ base::TimeTicks now = base::TimeTicks::HighResNow();
+ for (WebTouchEventWithLatencyList::const_iterator iter = acked_event->begin(),
+ end = acked_event->end();
+ iter != end; ++iter) {
+ ui::LatencyInfo* latency = const_cast<ui::LatencyInfo*>(&(iter->latency));
+ latency->AddNewLatencyFrom(renderer_latency_info);
+ latency->AddLatencyNumberWithTimestamp(
+ ui::INPUT_EVENT_LATENCY_ACKED_COMPONENT, 0, 0, now, 1);
+ client_->OnTouchEventAck((*iter), ack_result);
+ }
+}
+
+bool TouchEventQueue::ShouldForwardToRenderer(
+ const WebKit::WebTouchEvent& event) const {
+ // Touch press events should always be forwarded to the renderer.
+ if (event.type == WebKit::WebInputEvent::TouchStart)
+ return true;
+
+ if (event.type == WebKit::WebInputEvent::TouchMove &&
+ no_touch_move_to_renderer_)
+ return false;
+
+ for (unsigned int i = 0; i < event.touchesLength; ++i) {
+ const WebKit::WebTouchPoint& point = event.touches[i];
+ // If a point has been stationary, then don't take it into account.
+ if (point.state == WebKit::WebTouchPoint::StateStationary)
+ continue;
+
+ if (touch_ack_states_.count(point.id) > 0) {
+ if (touch_ack_states_.find(point.id)->second !=
+ INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
+ return true;
+ } else {
+ // If the ACK status of a point is unknown, then the event should be
+ // forwarded to the renderer.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/touch_event_queue.h b/chromium/content/browser/renderer_host/input/touch_event_queue.h
new file mode 100644
index 00000000000..358fae9c50d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touch_event_queue.h
@@ -0,0 +1,105 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EVENT_QUEUE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EVENT_QUEUE_H_
+
+#include <deque>
+#include <map>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class CoalescedWebTouchEvent;
+
+// Interface with which TouchEventQueue can forward touch events, and dispatch
+// touch event responses.
+class TouchEventQueueClient {
+ public:
+ virtual ~TouchEventQueueClient() {}
+
+ virtual void SendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& event) = 0;
+
+ virtual void OnTouchEventAck(
+ const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) = 0;
+};
+
+// A queue for throttling and coalescing touch-events.
+class TouchEventQueue {
+ public:
+
+ // The |client| must outlive the TouchEventQueue.
+ explicit TouchEventQueue(TouchEventQueueClient* client);
+ virtual ~TouchEventQueue();
+
+ // Adds an event to the queue. The event may be coalesced with previously
+ // queued events (e.g. consecutive touch-move events can be coalesced into a
+ // single touch-move event). The event may also be immediately forwarded to
+ // the renderer (e.g. when there are no other queued touch event).
+ void QueueEvent(const TouchEventWithLatencyInfo& event);
+
+ // Notifies the queue that a touch-event has been processed by the renderer.
+ // At this point, the queue may send one or more gesture events and/or
+ // additional queued touch-events to the renderer.
+ void ProcessTouchAck(InputEventAckState ack_result,
+ const ui::LatencyInfo& latency_info);
+
+ // Empties the queue of touch events. This may result in any number of gesture
+ // events being sent to the renderer.
+ void FlushQueue();
+
+ // Returns whether the event-queue is empty.
+ bool empty() const WARN_UNUSED_RESULT {
+ return touch_queue_.empty();
+ }
+
+ void set_no_touch_move_to_renderer(bool value) {
+ no_touch_move_to_renderer_ = value;
+ }
+
+ private:
+ friend class MockRenderWidgetHost;
+ friend class ImmediateInputRouterTest;
+
+ CONTENT_EXPORT size_t GetQueueSize() const;
+ CONTENT_EXPORT const TouchEventWithLatencyInfo& GetLatestEvent() const;
+
+ // Pops the touch-event from the top of the queue and sends it to the
+ // TouchEventQueueClient. This reduces the size of the queue by one.
+ void PopTouchEventToClient(InputEventAckState ack_result,
+ const ui::LatencyInfo& renderer_latency_info);
+
+ bool ShouldForwardToRenderer(const WebKit::WebTouchEvent& event) const;
+
+ // Handles touch event forwarding and ack'ed event dispatch.
+ TouchEventQueueClient* client_;
+
+ typedef std::deque<CoalescedWebTouchEvent*> TouchQueue;
+ TouchQueue touch_queue_;
+
+ // Maintain the ACK status for each touch point.
+ typedef std::map<int, InputEventAckState> TouchPointAckStates;
+ TouchPointAckStates touch_ack_states_;
+
+ // Used to defer touch forwarding when ack dispatch triggers |QueueEvent()|.
+ bool dispatching_touch_ack_;
+
+ // Don't send touch move events to renderer. This is enabled when the page
+ // is scrolling. This behaviour is currently enabled only on aura behind a
+ // flag.
+ bool no_touch_move_to_renderer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEventQueue);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EVENT_QUEUE_H_
diff --git a/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.cc b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.cc
new file mode 100644
index 00000000000..ab89f3151fc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.cc
@@ -0,0 +1,51 @@
+// 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 "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
+
+#include "content/browser/renderer_host/input/tap_suppression_controller.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+
+// The default implementation of the TouchpadTapSuppressionController does not
+// suppress taps. Touchpad tap suppression is needed only on CrOS.
+
+namespace content {
+
+TouchpadTapSuppressionController::TouchpadTapSuppressionController(
+ InputRouter* /*input_router*/)
+ : input_router_(NULL) {}
+
+TouchpadTapSuppressionController::~TouchpadTapSuppressionController() {}
+
+void TouchpadTapSuppressionController::GestureFlingCancel() {}
+
+void TouchpadTapSuppressionController::GestureFlingCancelAck(
+ bool /*processed*/) {
+}
+
+bool TouchpadTapSuppressionController::ShouldDeferMouseDown(
+ const MouseEventWithLatencyInfo& /*event*/) {
+ return false;
+}
+
+bool TouchpadTapSuppressionController::ShouldSuppressMouseUp() { return false; }
+
+int TouchpadTapSuppressionController::MaxCancelToDownTimeInMs() {
+ return 0;
+}
+
+int TouchpadTapSuppressionController::MaxTapGapTimeInMs() {
+ return 0;
+}
+
+void TouchpadTapSuppressionController::DropStashedTapDown() {
+}
+
+void TouchpadTapSuppressionController::ForwardStashedTapDownForDeferral() {
+}
+
+void TouchpadTapSuppressionController::ForwardStashedTapDownSkipDeferral() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.h b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.h
new file mode 100644
index 00000000000..3350350750e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller.h
@@ -0,0 +1,63 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHPAD_TAP_SUPPRESSION_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHPAD_TAP_SUPPRESSION_CONTROLLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class InputRouter;
+class TapSuppressionController;
+
+// Controls the suppression of touchpad taps immediately following the dispatch
+// of a GestureFlingCancel event.
+class TouchpadTapSuppressionController : public TapSuppressionControllerClient {
+ public:
+ // The |input_router| must outlive the TouchpadTapSupressionController.
+ explicit TouchpadTapSuppressionController(InputRouter* input_router);
+ virtual ~TouchpadTapSuppressionController();
+
+ // Should be called on arrival of GestureFlingCancel events.
+ void GestureFlingCancel();
+
+ // Should be called on arrival of ACK for a GestureFlingCancel event.
+ // |processed| is true if the GestureFlingCancel successfully stopped a fling.
+ void GestureFlingCancelAck(bool processed);
+
+ // Should be called on arrival of MouseDown events. Returns true if the caller
+ // should stop normal handling of the MouseDown. In this case, the caller is
+ // responsible for saving the event for later use, if needed.
+ bool ShouldDeferMouseDown(const MouseEventWithLatencyInfo& event);
+
+ // Should be called on arrival of MouseUp events. Returns true if the caller
+ // should stop normal handling of the MouseUp.
+ bool ShouldSuppressMouseUp();
+
+ private:
+ friend class MockRenderWidgetHost;
+
+ // TapSuppressionControllerClient implementation.
+ virtual int MaxCancelToDownTimeInMs() OVERRIDE;
+ virtual int MaxTapGapTimeInMs() OVERRIDE;
+ virtual void DropStashedTapDown() OVERRIDE;
+ virtual void ForwardStashedTapDownForDeferral() OVERRIDE;
+ virtual void ForwardStashedTapDownSkipDeferral() OVERRIDE;
+
+ InputRouter* input_router_;
+ MouseEventWithLatencyInfo stashed_mouse_down_;
+
+ // The core controller of tap suppression.
+ scoped_ptr<TapSuppressionController> controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchpadTapSuppressionController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHPAD_TAP_SUPPRESSION_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller_aura.cc b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller_aura.cc
new file mode 100644
index 00000000000..4c0cf3432c8
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchpad_tap_suppression_controller_aura.cc
@@ -0,0 +1,65 @@
+// 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 "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
+
+#include "content/browser/renderer_host/input/input_router.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+#include "ui/base/gestures/gesture_configuration.h"
+
+namespace content {
+
+TouchpadTapSuppressionController::TouchpadTapSuppressionController(
+ InputRouter* input_router)
+ : input_router_(input_router),
+ controller_(new TapSuppressionController(this)) {
+}
+
+TouchpadTapSuppressionController::~TouchpadTapSuppressionController() {}
+
+void TouchpadTapSuppressionController::GestureFlingCancel() {
+ controller_->GestureFlingCancel();
+}
+
+void TouchpadTapSuppressionController::GestureFlingCancelAck(bool processed) {
+ controller_->GestureFlingCancelAck(processed);
+}
+
+bool TouchpadTapSuppressionController::ShouldDeferMouseDown(
+ const MouseEventWithLatencyInfo& event) {
+ bool should_defer = controller_->ShouldDeferTapDown();
+ if (should_defer)
+ stashed_mouse_down_ = event;
+ return should_defer;
+}
+
+bool TouchpadTapSuppressionController::ShouldSuppressMouseUp() {
+ return controller_->ShouldSuppressTapUp();
+}
+
+int TouchpadTapSuppressionController::MaxCancelToDownTimeInMs() {
+ return ui::GestureConfiguration::fling_max_cancel_to_down_time_in_ms();
+}
+
+int TouchpadTapSuppressionController::MaxTapGapTimeInMs() {
+ return ui::GestureConfiguration::fling_max_tap_gap_time_in_ms();
+}
+
+void TouchpadTapSuppressionController::DropStashedTapDown() {
+}
+
+void TouchpadTapSuppressionController::ForwardStashedTapDownForDeferral() {
+ // Mouse downs are not handled by gesture event filter; so, they are
+ // immediately forwarded to the renderer.
+ input_router_->SendMouseEventImmediately(stashed_mouse_down_);
+}
+
+void TouchpadTapSuppressionController::ForwardStashedTapDownSkipDeferral() {
+ // Mouse downs are not handled by gesture event filter; so, they are
+ // immediately forwarded to the renderer.
+ input_router_->SendMouseEventImmediately(stashed_mouse_down_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.cc b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.cc
new file mode 100644
index 00000000000..ce7fb907c86
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.cc
@@ -0,0 +1,66 @@
+// 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 "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
+
+#include "content/browser/renderer_host/input/gesture_event_filter.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller.h"
+#include "ui/base/gestures/gesture_configuration.h"
+
+namespace content {
+
+TouchscreenTapSuppressionController::TouchscreenTapSuppressionController(
+ GestureEventFilter* gef)
+ : gesture_event_filter_(gef),
+ controller_(new TapSuppressionController(this)) {
+}
+
+TouchscreenTapSuppressionController::~TouchscreenTapSuppressionController() {}
+
+void TouchscreenTapSuppressionController::GestureFlingCancel() {
+ controller_->GestureFlingCancel();
+}
+
+void TouchscreenTapSuppressionController::GestureFlingCancelAck(
+ bool processed) {
+ controller_->GestureFlingCancelAck(processed);
+}
+
+bool TouchscreenTapSuppressionController::ShouldDeferGestureTapDown(
+ const GestureEventWithLatencyInfo& event) {
+ bool should_defer = controller_->ShouldDeferTapDown();
+ if (should_defer)
+ stashed_tap_down_ = event;
+ return should_defer;
+}
+
+bool TouchscreenTapSuppressionController::ShouldSuppressGestureTap() {
+ return controller_->ShouldSuppressTapUp();
+}
+
+bool TouchscreenTapSuppressionController::ShouldSuppressGestureTapCancel() {
+ return controller_->ShouldSuppressTapCancel();
+}
+
+int TouchscreenTapSuppressionController::MaxCancelToDownTimeInMs() {
+ return ui::GestureConfiguration::fling_max_cancel_to_down_time_in_ms();
+}
+
+int TouchscreenTapSuppressionController::MaxTapGapTimeInMs() {
+ return static_cast<int>(
+ ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000);
+}
+
+void TouchscreenTapSuppressionController::DropStashedTapDown() {
+}
+
+void TouchscreenTapSuppressionController::ForwardStashedTapDownForDeferral() {
+ gesture_event_filter_->ForwardGestureEventForDeferral(stashed_tap_down_);
+}
+
+void TouchscreenTapSuppressionController::ForwardStashedTapDownSkipDeferral() {
+ gesture_event_filter_->ForwardGestureEventSkipDeferral(stashed_tap_down_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h
new file mode 100644
index 00000000000..b974d9c08ba
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h
@@ -0,0 +1,64 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHSCREEN_TAP_SUPPRESSION_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHSCREEN_TAP_SUPPRESSION_CONTROLLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/input/gesture_event_filter.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+
+namespace content {
+
+class GestureEventFilter;
+class TapSuppressionController;
+
+// Controls the suppression of touchscreen taps immediately following the
+// dispatch of a GestureFlingCancel event.
+class TouchscreenTapSuppressionController
+ : public TapSuppressionControllerClient {
+ public:
+ explicit TouchscreenTapSuppressionController(GestureEventFilter* gef);
+ virtual ~TouchscreenTapSuppressionController();
+
+ // Should be called on arrival of GestureFlingCancel events.
+ void GestureFlingCancel();
+
+ // Should be called on arrival of ACK for a GestureFlingCancel event.
+ // |processed| is true if the GestureFlingCancel successfully stopped a fling.
+ void GestureFlingCancelAck(bool processed);
+
+ // Should be called on arrival of GestureTapDown events. Returns true if the
+ // caller should stop normal handling of the GestureTapDown. In this case, the
+ // caller is responsible for saving the event for later use, if needed.
+ bool ShouldDeferGestureTapDown(const GestureEventWithLatencyInfo& event);
+
+ // Should be called on arrival of GestureTap events. Returns true if the
+ // caller should stop normal handling of the GestureTap.
+ bool ShouldSuppressGestureTap();
+
+ // Should be called on arrival of GestureTapCancel events. Returns true if the
+ // caller should stop normal handling of the GestureTapCancel.
+ bool ShouldSuppressGestureTapCancel();
+
+ private:
+ // TapSuppressionControllerClient implementation.
+ virtual int MaxCancelToDownTimeInMs() OVERRIDE;
+ virtual int MaxTapGapTimeInMs() OVERRIDE;
+ virtual void DropStashedTapDown() OVERRIDE;
+ virtual void ForwardStashedTapDownForDeferral() OVERRIDE;
+ virtual void ForwardStashedTapDownSkipDeferral() OVERRIDE;
+
+ GestureEventFilter* gesture_event_filter_;
+ GestureEventWithLatencyInfo stashed_tap_down_;
+
+ // The core controller of tap suppression.
+ scoped_ptr<TapSuppressionController> controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchscreenTapSuppressionController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCHSCREEN_TAP_SUPPRESSION_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller_stub.cc b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller_stub.cc
new file mode 100644
index 00000000000..d6cf6c8a6a7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/touchscreen_tap_suppression_controller_stub.cc
@@ -0,0 +1,53 @@
+// 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 "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
+
+#include "content/browser/renderer_host/input/tap_suppression_controller.h"
+
+// This is the stub implementation of TouchscreenTapSuppressionController which
+// is used on platforms that do not need tap suppression for touchscreen.
+
+namespace content {
+
+TouchscreenTapSuppressionController::TouchscreenTapSuppressionController(
+ GestureEventFilter* /*gef*/)
+ : gesture_event_filter_(NULL) {}
+
+TouchscreenTapSuppressionController::~TouchscreenTapSuppressionController() {}
+
+void TouchscreenTapSuppressionController::GestureFlingCancel() {}
+
+void TouchscreenTapSuppressionController::GestureFlingCancelAck(
+ bool /*processed*/) {
+}
+
+bool TouchscreenTapSuppressionController::ShouldDeferGestureTapDown(
+ const GestureEventWithLatencyInfo& /*event*/) {
+ return false;
+}
+
+bool TouchscreenTapSuppressionController::ShouldSuppressGestureTap() {
+ return false;
+}
+
+bool TouchscreenTapSuppressionController::ShouldSuppressGestureTapCancel() {
+ return false;
+}
+
+int TouchscreenTapSuppressionController::MaxCancelToDownTimeInMs() {
+ return 0;
+}
+
+int TouchscreenTapSuppressionController::MaxTapGapTimeInMs() {
+ return 0;
+}
+
+void TouchscreenTapSuppressionController::DropStashedTapDown() {}
+
+void TouchscreenTapSuppressionController::ForwardStashedTapDownForDeferral() {}
+
+void TouchscreenTapSuppressionController::ForwardStashedTapDownSkipDeferral() {}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_builders_android.cc b/chromium/content/browser/renderer_host/input/web_input_event_builders_android.cc
new file mode 100644
index 00000000000..eca820bb28b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_builders_android.cc
@@ -0,0 +1,133 @@
+// 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 "content/browser/renderer_host/input/web_input_event_builders_android.h"
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/input/web_input_event_util.h"
+#include "content/browser/renderer_host/input/web_input_event_util_posix.h"
+#include "ui/base/keycodes/keyboard_code_conversion_android.h"
+#include "ui/base/keycodes/keyboard_codes_posix.h"
+
+namespace content {
+
+using WebKit::WebInputEvent;
+using WebKit::WebKeyboardEvent;
+using WebKit::WebGestureEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+
+WebKeyboardEvent WebKeyboardEventBuilder::Build(WebInputEvent::Type type,
+ int modifiers,
+ double time_sec,
+ int keycode,
+ int unicode_character,
+ bool is_system_key) {
+ DCHECK(WebInputEvent::isKeyboardEventType(type));
+ WebKeyboardEvent result;
+
+ result.type = type;
+ result.modifiers = modifiers;
+ result.timeStampSeconds = time_sec;
+ ui::KeyboardCode windows_key_code =
+ ui::KeyboardCodeFromAndroidKeyCode(keycode);
+ UpdateWindowsKeyCodeAndKeyIdentifier(&result, windows_key_code);
+ result.modifiers |= GetLocationModifiersFromWindowsKeyCode(windows_key_code);
+ result.nativeKeyCode = keycode;
+ result.unmodifiedText[0] = unicode_character;
+ if (result.windowsKeyCode == ui::VKEY_RETURN) {
+ // This is the same behavior as GTK:
+ // We need to treat the enter key as a key press of character \r. This
+ // is apparently just how webkit handles it and what it expects.
+ result.unmodifiedText[0] = '\r';
+ }
+ result.text[0] = result.unmodifiedText[0];
+ result.isSystemKey = is_system_key;
+
+ return result;
+}
+
+WebMouseEvent WebMouseEventBuilder::Build(WebKit::WebInputEvent::Type type,
+ WebMouseEvent::Button button,
+ double time_sec,
+ int window_x,
+ int window_y,
+ int modifiers,
+ int click_count) {
+ DCHECK(WebInputEvent::isMouseEventType(type));
+ WebMouseEvent result;
+
+ result.type = type;
+ result.x = window_x;
+ result.y = window_y;
+ result.windowX = window_x;
+ result.windowY = window_y;
+ result.timeStampSeconds = time_sec;
+ result.clickCount = click_count;
+ result.modifiers = modifiers;
+
+ if (type == WebInputEvent::MouseDown || type == WebInputEvent::MouseUp)
+ result.button = button;
+ else
+ result.button = WebMouseEvent::ButtonNone;
+
+ return result;
+}
+
+WebMouseWheelEvent WebMouseWheelEventBuilder::Build(Direction direction,
+ double time_sec,
+ int window_x,
+ int window_y) {
+ WebMouseWheelEvent result;
+
+ result.type = WebInputEvent::MouseWheel;
+ result.x = window_x;
+ result.y = window_y;
+ result.windowX = window_x;
+ result.windowY = window_y;
+ result.timeStampSeconds = time_sec;
+ result.button = WebMouseEvent::ButtonNone;
+
+ // The below choices are matched from GTK.
+ const float scrollbar_pixels_per_tick = 160.0f / 3.0f;
+
+ switch (direction) {
+ case DIRECTION_UP:
+ result.deltaY = scrollbar_pixels_per_tick;
+ result.wheelTicksY = 1;
+ break;
+ case DIRECTION_DOWN:
+ result.deltaY = -scrollbar_pixels_per_tick;
+ result.wheelTicksY = -1;
+ break;
+ case DIRECTION_LEFT:
+ result.deltaX = scrollbar_pixels_per_tick;
+ result.wheelTicksX = 1;
+ break;
+ case DIRECTION_RIGHT:
+ result.deltaX = -scrollbar_pixels_per_tick;
+ result.wheelTicksX = -1;
+ break;
+ }
+
+ return result;
+}
+
+WebGestureEvent WebGestureEventBuilder::Build(WebInputEvent::Type type,
+ double time_sec,
+ int x,
+ int y) {
+ DCHECK(WebInputEvent::isGestureEventType(type));
+ WebGestureEvent result;
+
+ result.type = type;
+ result.x = x;
+ result.y = y;
+ result.timeStampSeconds = time_sec;
+ result.sourceDevice = WebGestureEvent::Touchscreen;
+
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_builders_android.h b/chromium/content/browser/renderer_host/input/web_input_event_builders_android.h
new file mode 100644
index 00000000000..4488375aa5f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_builders_android.h
@@ -0,0 +1,58 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_BUILDERS_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_BUILDERS_ANDROID_H_
+
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class WebMouseEventBuilder {
+ public:
+ static WebKit::WebMouseEvent Build(WebKit::WebInputEvent::Type type,
+ WebKit::WebMouseEvent::Button button,
+ double time_sec,
+ int window_x,
+ int window_y,
+ int modifiers,
+ int click_count);
+};
+
+class WebMouseWheelEventBuilder {
+ public:
+ enum Direction {
+ DIRECTION_UP,
+ DIRECTION_DOWN,
+ DIRECTION_LEFT,
+ DIRECTION_RIGHT,
+ };
+
+ static WebKit::WebMouseWheelEvent Build(Direction direction,
+ double time_sec,
+ int window_x,
+ int window_y);
+};
+
+class WebKeyboardEventBuilder {
+ public:
+ static WebKit::WebKeyboardEvent Build(WebKit::WebInputEvent::Type type,
+ int modifiers,
+ double time_sec,
+ int keycode,
+ int unicode_character,
+ bool is_system_key);
+};
+
+class WebGestureEventBuilder {
+ public:
+ static WebKit::WebGestureEvent Build(WebKit::WebInputEvent::Type type,
+ double time_sec,
+ int x,
+ int y);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_BUILDERS_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_builders_win.cc b/chromium/content/browser/renderer_host/input/web_input_event_builders_win.cc
new file mode 100644
index 00000000000..a5a9b20f615
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_builders_win.cc
@@ -0,0 +1,460 @@
+// 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 "content/browser/renderer_host/input/web_input_event_builders_win.h"
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/input/web_input_event_util.h"
+
+using WebKit::WebInputEvent;
+using WebKit::WebKeyboardEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+
+namespace content {
+
+static const unsigned long kDefaultScrollLinesPerWheelDelta = 3;
+static const unsigned long kDefaultScrollCharsPerWheelDelta = 1;
+
+static bool IsKeyDown(WPARAM wparam) {
+ return (GetKeyState(wparam) & 0x8000) != 0;
+}
+
+static int GetLocationModifier(WPARAM wparam, LPARAM lparam) {
+ int modifier = 0;
+ switch (wparam) {
+ case VK_RETURN:
+ if ((lparam >> 16) & KF_EXTENDED)
+ modifier = WebInputEvent::IsKeyPad;
+ break;
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_HOME:
+ case VK_END:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_UP:
+ case VK_DOWN:
+ case VK_LEFT:
+ case VK_RIGHT:
+ if (!((lparam >> 16) & KF_EXTENDED))
+ modifier = WebInputEvent::IsKeyPad;
+ break;
+ case VK_NUMLOCK:
+ case VK_NUMPAD0:
+ case VK_NUMPAD1:
+ case VK_NUMPAD2:
+ case VK_NUMPAD3:
+ case VK_NUMPAD4:
+ case VK_NUMPAD5:
+ case VK_NUMPAD6:
+ case VK_NUMPAD7:
+ case VK_NUMPAD8:
+ case VK_NUMPAD9:
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ case VK_ADD:
+ case VK_DECIMAL:
+ case VK_CLEAR:
+ modifier = WebInputEvent::IsKeyPad;
+ break;
+ case VK_SHIFT:
+ if (IsKeyDown(VK_LSHIFT))
+ modifier = WebInputEvent::IsLeft;
+ else if (IsKeyDown(VK_RSHIFT))
+ modifier = WebInputEvent::IsRight;
+ break;
+ case VK_CONTROL:
+ if (IsKeyDown(VK_LCONTROL))
+ modifier = WebInputEvent::IsLeft;
+ else if (IsKeyDown(VK_RCONTROL))
+ modifier = WebInputEvent::IsRight;
+ break;
+ case VK_MENU:
+ if (IsKeyDown(VK_LMENU))
+ modifier = WebInputEvent::IsLeft;
+ else if (IsKeyDown(VK_RMENU))
+ modifier = WebInputEvent::IsRight;
+ break;
+ case VK_LWIN:
+ modifier = WebInputEvent::IsLeft;
+ break;
+ case VK_RWIN:
+ modifier = WebInputEvent::IsRight;
+ break;
+ }
+
+ DCHECK(!modifier
+ || modifier == WebInputEvent::IsKeyPad
+ || modifier == WebInputEvent::IsLeft
+ || modifier == WebInputEvent::IsRight);
+ return modifier;
+}
+
+// Loads the state for toggle keys into the event.
+static void SetToggleKeyState(WebInputEvent* event) {
+ // Low bit set from GetKeyState indicates "toggled".
+ if (::GetKeyState(VK_NUMLOCK) & 1)
+ event->modifiers |= WebInputEvent::NumLockOn;
+ if (::GetKeyState(VK_CAPITAL) & 1)
+ event->modifiers |= WebInputEvent::CapsLockOn;
+}
+
+WebKeyboardEvent WebKeyboardEventBuilder::Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ WebKeyboardEvent result;
+
+ // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
+ // GetMessageTime() refers to is the same one that we're passed in? Perhaps
+ // one of the construction parameters should be the time passed by the
+ // caller, who would know for sure.
+ result.timeStampSeconds = ::GetMessageTime() / 1000.0;
+
+ result.windowsKeyCode = static_cast<int>(wparam);
+ // Record the scan code (along with other context bits) for this key event.
+ result.nativeKeyCode = static_cast<int>(lparam);
+
+ switch (message) {
+ case WM_SYSKEYDOWN:
+ result.isSystemKey = true;
+ case WM_KEYDOWN:
+ result.type = WebInputEvent::RawKeyDown;
+ break;
+ case WM_SYSKEYUP:
+ result.isSystemKey = true;
+ case WM_KEYUP:
+ result.type = WebInputEvent::KeyUp;
+ break;
+ case WM_IME_CHAR:
+ result.type = WebInputEvent::Char;
+ break;
+ case WM_SYSCHAR:
+ result.isSystemKey = true;
+ result.type = WebInputEvent::Char;
+ case WM_CHAR:
+ result.type = WebInputEvent::Char;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (result.type == WebInputEvent::Char
+ || result.type == WebInputEvent::RawKeyDown) {
+ result.text[0] = result.windowsKeyCode;
+ result.unmodifiedText[0] = result.windowsKeyCode;
+ }
+ if (result.type != WebInputEvent::Char) {
+ UpdateWindowsKeyCodeAndKeyIdentifier(
+ &result,
+ static_cast<ui::KeyboardCode>(result.windowsKeyCode));
+ }
+
+ if (::GetKeyState(VK_SHIFT) & 0x8000)
+ result.modifiers |= WebInputEvent::ShiftKey;
+ if (::GetKeyState(VK_CONTROL) & 0x8000)
+ result.modifiers |= WebInputEvent::ControlKey;
+ if (::GetKeyState(VK_MENU) & 0x8000)
+ result.modifiers |= WebInputEvent::AltKey;
+ // NOTE: There doesn't seem to be a way to query the mouse button state in
+ // this case.
+
+ if (LOWORD(lparam) > 1)
+ result.modifiers |= WebInputEvent::IsAutoRepeat;
+
+ result.modifiers |= GetLocationModifier(wparam, lparam);
+
+ SetToggleKeyState(&result);
+ return result;
+}
+
+// WebMouseEvent --------------------------------------------------------------
+
+static int g_last_click_count = 0;
+static double g_last_click_time = 0;
+
+static LPARAM GetRelativeCursorPos(HWND hwnd) {
+ POINT pos = {-1, -1};
+ GetCursorPos(&pos);
+ ScreenToClient(hwnd, &pos);
+ return MAKELPARAM(pos.x, pos.y);
+}
+
+WebMouseEvent WebMouseEventBuilder::Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ WebMouseEvent result;
+
+ switch (message) {
+ case WM_MOUSEMOVE:
+ result.type = WebInputEvent::MouseMove;
+ if (wparam & MK_LBUTTON)
+ result.button = WebMouseEvent::ButtonLeft;
+ else if (wparam & MK_MBUTTON)
+ result.button = WebMouseEvent::ButtonMiddle;
+ else if (wparam & MK_RBUTTON)
+ result.button = WebMouseEvent::ButtonRight;
+ else
+ result.button = WebMouseEvent::ButtonNone;
+ break;
+ case WM_MOUSELEAVE:
+ result.type = WebInputEvent::MouseLeave;
+ result.button = WebMouseEvent::ButtonNone;
+ // set the current mouse position (relative to the client area of the
+ // current window) since none is specified for this event
+ lparam = GetRelativeCursorPos(hwnd);
+ break;
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONDBLCLK:
+ result.type = WebInputEvent::MouseDown;
+ result.button = WebMouseEvent::ButtonLeft;
+ break;
+ case WM_MBUTTONDOWN:
+ case WM_MBUTTONDBLCLK:
+ result.type = WebInputEvent::MouseDown;
+ result.button = WebMouseEvent::ButtonMiddle;
+ break;
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONDBLCLK:
+ result.type = WebInputEvent::MouseDown;
+ result.button = WebMouseEvent::ButtonRight;
+ break;
+ case WM_LBUTTONUP:
+ result.type = WebInputEvent::MouseUp;
+ result.button = WebMouseEvent::ButtonLeft;
+ break;
+ case WM_MBUTTONUP:
+ result.type = WebInputEvent::MouseUp;
+ result.button = WebMouseEvent::ButtonMiddle;
+ break;
+ case WM_RBUTTONUP:
+ result.type = WebInputEvent::MouseUp;
+ result.button = WebMouseEvent::ButtonRight;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
+ // GetMessageTime() refers to is the same one that we're passed in? Perhaps
+ // one of the construction parameters should be the time passed by the
+ // caller, who would know for sure.
+ result.timeStampSeconds = ::GetMessageTime() / 1000.0;
+
+ // set position fields:
+
+ result.x = static_cast<short>(LOWORD(lparam));
+ result.y = static_cast<short>(HIWORD(lparam));
+ result.windowX = result.x;
+ result.windowY = result.y;
+
+ POINT global_point = { result.x, result.y };
+ ClientToScreen(hwnd, &global_point);
+
+ result.globalX = global_point.x;
+ result.globalY = global_point.y;
+
+ // calculate number of clicks:
+
+ // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
+ // where their original code looks buggy.
+ static int last_click_position_x;
+ static int last_click_position_y;
+ static WebMouseEvent::Button last_click_button = WebMouseEvent::ButtonLeft;
+
+ double current_time = result.timeStampSeconds;
+ bool cancel_previous_click =
+ (abs(last_click_position_x - result.x) >
+ (::GetSystemMetrics(SM_CXDOUBLECLK) / 2))
+ || (abs(last_click_position_y - result.y) >
+ (::GetSystemMetrics(SM_CYDOUBLECLK) / 2))
+ || ((current_time - g_last_click_time) * 1000.0 > ::GetDoubleClickTime());
+
+ if (result.type == WebInputEvent::MouseDown) {
+ if (!cancel_previous_click && (result.button == last_click_button)) {
+ ++g_last_click_count;
+ } else {
+ g_last_click_count = 1;
+ last_click_position_x = result.x;
+ last_click_position_y = result.y;
+ }
+ g_last_click_time = current_time;
+ last_click_button = result.button;
+ } else if (result.type == WebInputEvent::MouseMove
+ || result.type == WebInputEvent::MouseLeave) {
+ if (cancel_previous_click) {
+ g_last_click_count = 0;
+ last_click_position_x = 0;
+ last_click_position_y = 0;
+ g_last_click_time = 0;
+ }
+ }
+ result.clickCount = g_last_click_count;
+
+ // set modifiers:
+
+ if (wparam & MK_CONTROL)
+ result.modifiers |= WebInputEvent::ControlKey;
+ if (wparam & MK_SHIFT)
+ result.modifiers |= WebInputEvent::ShiftKey;
+ if (::GetKeyState(VK_MENU) & 0x8000)
+ result.modifiers |= WebInputEvent::AltKey;
+ if (wparam & MK_LBUTTON)
+ result.modifiers |= WebInputEvent::LeftButtonDown;
+ if (wparam & MK_MBUTTON)
+ result.modifiers |= WebInputEvent::MiddleButtonDown;
+ if (wparam & MK_RBUTTON)
+ result.modifiers |= WebInputEvent::RightButtonDown;
+
+ SetToggleKeyState(&result);
+ return result;
+}
+
+// WebMouseWheelEvent ---------------------------------------------------------
+
+WebMouseWheelEvent
+WebMouseWheelEventBuilder::Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ WebMouseWheelEvent result;
+
+ result.type = WebInputEvent::MouseWheel;
+
+ // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
+ // GetMessageTime() refers to is the same one that we're passed in? Perhaps
+ // one of the construction parameters should be the time passed by the
+ // caller, who would know for sure.
+ result.timeStampSeconds = ::GetMessageTime() / 1000.0;
+
+ result.button = WebMouseEvent::ButtonNone;
+
+ // Get key state, coordinates, and wheel delta from event.
+ typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
+ GetKeyStateFunction get_key_state_func;
+ UINT key_state;
+ float wheel_delta;
+ bool horizontal_scroll = false;
+ if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
+ // Synthesize mousewheel event from a scroll event. This is needed to
+ // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState
+ // for key state since we are synthesizing the input event.
+ get_key_state_func = GetAsyncKeyState;
+ key_state = 0;
+ if (get_key_state_func(VK_SHIFT))
+ key_state |= MK_SHIFT;
+ if (get_key_state_func(VK_CONTROL))
+ key_state |= MK_CONTROL;
+ // NOTE: There doesn't seem to be a way to query the mouse button state
+ // in this case.
+
+ POINT cursor_position = {0};
+ GetCursorPos(&cursor_position);
+ result.globalX = cursor_position.x;
+ result.globalY = cursor_position.y;
+
+ switch (LOWORD(wparam)) {
+ case SB_LINEUP: // == SB_LINELEFT
+ wheel_delta = WHEEL_DELTA;
+ break;
+ case SB_LINEDOWN: // == SB_LINERIGHT
+ wheel_delta = -WHEEL_DELTA;
+ break;
+ case SB_PAGEUP:
+ wheel_delta = 1;
+ result.scrollByPage = true;
+ break;
+ case SB_PAGEDOWN:
+ wheel_delta = -1;
+ result.scrollByPage = true;
+ break;
+ default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
+ wheel_delta = 0;
+ break;
+ }
+
+ if (message == WM_HSCROLL)
+ horizontal_scroll = true;
+ } else {
+ // Non-synthesized event; we can just read data off the event.
+ get_key_state_func = ::GetKeyState;
+ key_state = GET_KEYSTATE_WPARAM(wparam);
+
+ result.globalX = static_cast<short>(LOWORD(lparam));
+ result.globalY = static_cast<short>(HIWORD(lparam));
+
+ wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
+ if (message == WM_MOUSEHWHEEL) {
+ horizontal_scroll = true;
+ wheel_delta = -wheel_delta; // Windows is <- -/+ ->, WebKit <- +/- ->.
+ }
+ }
+ if (key_state & MK_SHIFT)
+ horizontal_scroll = true;
+
+ // Set modifiers based on key state.
+ if (key_state & MK_SHIFT)
+ result.modifiers |= WebInputEvent::ShiftKey;
+ if (key_state & MK_CONTROL)
+ result.modifiers |= WebInputEvent::ControlKey;
+ if (get_key_state_func(VK_MENU) & 0x8000)
+ result.modifiers |= WebInputEvent::AltKey;
+ if (key_state & MK_LBUTTON)
+ result.modifiers |= WebInputEvent::LeftButtonDown;
+ if (key_state & MK_MBUTTON)
+ result.modifiers |= WebInputEvent::MiddleButtonDown;
+ if (key_state & MK_RBUTTON)
+ result.modifiers |= WebInputEvent::RightButtonDown;
+
+ SetToggleKeyState(&result);
+
+ // Set coordinates by translating event coordinates from screen to client.
+ POINT client_point = { result.globalX, result.globalY };
+ MapWindowPoints(0, hwnd, &client_point, 1);
+ result.x = client_point.x;
+ result.y = client_point.y;
+ result.windowX = result.x;
+ result.windowY = result.y;
+
+ // Convert wheel delta amount to a number of pixels to scroll.
+ //
+ // How many pixels should we scroll per line? Gecko uses the height of the
+ // current line, which means scroll distance changes as you go through the
+ // page or go to different pages. IE 8 is ~60 px/line, although the value
+ // seems to vary slightly by page and zoom level. Also, IE defaults to
+ // smooth scrolling while Firefox doesn't, so it can get away with somewhat
+ // larger scroll values without feeling as jerky. Here we use 100 px per
+ // three lines (the default scroll amount is three lines per wheel tick).
+ // Even though we have smooth scrolling, we don't make this as large as IE
+ // because subjectively IE feels like it scrolls farther than you want while
+ // reading articles.
+ static const float kScrollbarPixelsPerLine = 100.0f / 3.0f;
+ wheel_delta /= WHEEL_DELTA;
+ float scroll_delta = wheel_delta * kScrollbarPixelsPerLine;
+ if (horizontal_scroll) {
+ unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta;
+ SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0);
+ // TODO(pkasting): Should probably have a different multiplier
+ // scrollbarPixelsPerChar here.
+ scroll_delta *= static_cast<float>(scroll_chars);
+ } else {
+ unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta;
+ SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0);
+ if (scroll_lines == WHEEL_PAGESCROLL)
+ result.scrollByPage = true;
+ if (!result.scrollByPage)
+ scroll_delta *= static_cast<float>(scroll_lines);
+ }
+
+ // Set scroll amount based on above calculations. WebKit expects positive
+ // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
+ if (horizontal_scroll) {
+ result.deltaX = scroll_delta;
+ result.wheelTicksX = wheel_delta;
+ } else {
+ result.deltaY = scroll_delta;
+ result.wheelTicksY = wheel_delta;
+ }
+
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_builders_win.h b/chromium/content/browser/renderer_host/input/web_input_event_builders_win.h
new file mode 100644
index 00000000000..f01cd29fd5c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_builders_win.h
@@ -0,0 +1,34 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_BUILDERS_WIN_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_BUILDERS_WIN_H_
+
+#include <windows.h>
+
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class WebKeyboardEventBuilder {
+ public:
+ static WebKit::WebKeyboardEvent Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam);
+};
+
+class WebMouseEventBuilder {
+ public:
+ static WebKit::WebMouseEvent Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam);
+};
+
+class WebMouseWheelEventBuilder {
+ public:
+ static WebKit::WebMouseWheelEvent Build(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_WEB_INPUT_EVENT_BUILDERS_WIN_H_
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_util.cc b/chromium/content/browser/renderer_host/input/web_input_event_util.cc
new file mode 100644
index 00000000000..e3c28990ffd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_util.cc
@@ -0,0 +1,145 @@
+// 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 "content/browser/renderer_host/input/web_input_event_util.h"
+
+#include "base/strings/string_util.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace {
+
+const char* GetKeyIdentifier(ui::KeyboardCode key_code) {
+ switch (key_code) {
+ case ui::VKEY_MENU:
+ return "Alt";
+ case ui::VKEY_CONTROL:
+ return "Control";
+ case ui::VKEY_SHIFT:
+ return "Shift";
+ case ui::VKEY_CAPITAL:
+ return "CapsLock";
+ case ui::VKEY_LWIN:
+ case ui::VKEY_RWIN:
+ return "Win";
+ case ui::VKEY_CLEAR:
+ return "Clear";
+ case ui::VKEY_DOWN:
+ return "Down";
+ case ui::VKEY_END:
+ return "End";
+ case ui::VKEY_RETURN:
+ return "Enter";
+ case ui::VKEY_EXECUTE:
+ return "Execute";
+ case ui::VKEY_F1:
+ return "F1";
+ case ui::VKEY_F2:
+ return "F2";
+ case ui::VKEY_F3:
+ return "F3";
+ case ui::VKEY_F4:
+ return "F4";
+ case ui::VKEY_F5:
+ return "F5";
+ case ui::VKEY_F6:
+ return "F6";
+ case ui::VKEY_F7:
+ return "F7";
+ case ui::VKEY_F8:
+ return "F8";
+ case ui::VKEY_F9:
+ return "F9";
+ case ui::VKEY_F10:
+ return "F10";
+ case ui::VKEY_F11:
+ return "F11";
+ case ui::VKEY_F12:
+ return "F12";
+ case ui::VKEY_F13:
+ return "F13";
+ case ui::VKEY_F14:
+ return "F14";
+ case ui::VKEY_F15:
+ return "F15";
+ case ui::VKEY_F16:
+ return "F16";
+ case ui::VKEY_F17:
+ return "F17";
+ case ui::VKEY_F18:
+ return "F18";
+ case ui::VKEY_F19:
+ return "F19";
+ case ui::VKEY_F20:
+ return "F20";
+ case ui::VKEY_F21:
+ return "F21";
+ case ui::VKEY_F22:
+ return "F22";
+ case ui::VKEY_F23:
+ return "F23";
+ case ui::VKEY_F24:
+ return "F24";
+ case ui::VKEY_HELP:
+ return "Help";
+ case ui::VKEY_HOME:
+ return "Home";
+ case ui::VKEY_INSERT:
+ return "Insert";
+ case ui::VKEY_LEFT:
+ return "Left";
+ case ui::VKEY_NEXT:
+ return "PageDown";
+ case ui::VKEY_PRIOR:
+ return "PageUp";
+ case ui::VKEY_PAUSE:
+ return "Pause";
+ case ui::VKEY_SNAPSHOT:
+ return "PrintScreen";
+ case ui::VKEY_RIGHT:
+ return "Right";
+ case ui::VKEY_SCROLL:
+ return "Scroll";
+ case ui::VKEY_SELECT:
+ return "Select";
+ case ui::VKEY_UP:
+ return "Up";
+ case ui::VKEY_DELETE:
+ return "U+007F"; // Standard says that DEL becomes U+007F.
+ case ui::VKEY_MEDIA_NEXT_TRACK:
+ return "MediaNextTrack";
+ case ui::VKEY_MEDIA_PREV_TRACK:
+ return "MediaPreviousTrack";
+ case ui::VKEY_MEDIA_STOP:
+ return "MediaStop";
+ case ui::VKEY_MEDIA_PLAY_PAUSE:
+ return "MediaPlayPause";
+ case ui::VKEY_VOLUME_MUTE:
+ return "VolumeMute";
+ case ui::VKEY_VOLUME_DOWN:
+ return "VolumeDown";
+ case ui::VKEY_VOLUME_UP:
+ return "VolumeUp";
+ default:
+ return NULL;
+ };
+}
+
+} // namespace
+
+namespace content {
+
+void UpdateWindowsKeyCodeAndKeyIdentifier(WebKit::WebKeyboardEvent* event,
+ ui::KeyboardCode windows_key_code) {
+ event->windowsKeyCode = windows_key_code;
+
+ const char* id = GetKeyIdentifier(windows_key_code);
+ if (id) {
+ base::strlcpy(event->keyIdentifier, id, sizeof(event->keyIdentifier) - 1);
+ } else {
+ base::snprintf(event->keyIdentifier, sizeof(event->keyIdentifier), "U+%04X",
+ base::ToUpperASCII(static_cast<int>(windows_key_code)));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_util.h b/chromium/content/browser/renderer_host/input/web_input_event_util.h
new file mode 100644
index 00000000000..75870ebc7cc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_util.h
@@ -0,0 +1,25 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_H_
+
+#include "content/common/content_export.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace WebKit {
+class WebKeyboardEvent;
+}
+
+namespace content {
+
+// Update |event|'s windowsKeyCode and keyIdentifer properties using the
+// provided |windows_key_code|.
+CONTENT_EXPORT void UpdateWindowsKeyCodeAndKeyIdentifier(
+ WebKit::WebKeyboardEvent* event,
+ ui::KeyboardCode windows_key_code);
+
+}
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_H_
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_util_posix.cc b/chromium/content/browser/renderer_host/input/web_input_event_util_posix.cc
new file mode 100644
index 00000000000..72786c84058
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_util_posix.cc
@@ -0,0 +1,43 @@
+// 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 "content/browser/renderer_host/input/web_input_event_util_posix.h"
+
+namespace content {
+
+ui::KeyboardCode GetWindowsKeyCodeWithoutLocation(ui::KeyboardCode key_code) {
+ switch (key_code) {
+ case ui::VKEY_LCONTROL:
+ case ui::VKEY_RCONTROL:
+ return ui::VKEY_CONTROL;
+ case ui::VKEY_LSHIFT:
+ case ui::VKEY_RSHIFT:
+ return ui::VKEY_SHIFT;
+ case ui::VKEY_LMENU:
+ case ui::VKEY_RMENU:
+ return ui::VKEY_MENU;
+ default:
+ return key_code;
+ }
+}
+
+WebKit::WebInputEvent::Modifiers GetLocationModifiersFromWindowsKeyCode(
+ ui::KeyboardCode key_code) {
+ switch (key_code) {
+ case ui::VKEY_LCONTROL:
+ case ui::VKEY_LSHIFT:
+ case ui::VKEY_LMENU:
+ case ui::VKEY_LWIN:
+ return WebKit::WebKeyboardEvent::IsLeft;
+ case ui::VKEY_RCONTROL:
+ case ui::VKEY_RSHIFT:
+ case ui::VKEY_RMENU:
+ case ui::VKEY_RWIN:
+ return WebKit::WebKeyboardEvent::IsRight;
+ default:
+ return static_cast<WebKit::WebInputEvent::Modifiers>(0);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/input/web_input_event_util_posix.h b/chromium/content/browser/renderer_host/input/web_input_event_util_posix.h
new file mode 100644
index 00000000000..34e4ee96165
--- /dev/null
+++ b/chromium/content/browser/renderer_host/input/web_input_event_util_posix.h
@@ -0,0 +1,19 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_POSIX_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_POSIX_H_
+
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace content {
+
+ui::KeyboardCode GetWindowsKeyCodeWithoutLocation(ui::KeyboardCode key_code);
+WebKit::WebInputEvent::Modifiers GetLocationModifiersFromWindowsKeyCode(
+ ui::KeyboardCode key_code);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_WEB_INPUT_EVENT_UTIL_POSIX_H_
diff --git a/chromium/content/browser/renderer_host/java/DEPS b/chromium/content/browser/renderer_host/java/DEPS
new file mode 100644
index 00000000000..05512435883
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content/child", # For java bridge bindings
+ "+third_party/WebKit/public/web/WebBindings.h", # For java bridge bindings
+]
diff --git a/chromium/content/browser/renderer_host/java/OWNERS b/chromium/content/browser/renderer_host/java/OWNERS
new file mode 100644
index 00000000000..4b297c43ee6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/OWNERS
@@ -0,0 +1,2 @@
+joth@chromium.org
+steveblock@chromium.org
diff --git a/chromium/content/browser/renderer_host/java/java_bound_object.cc b/chromium/content/browser/renderer_host/java/java_bound_object.cc
new file mode 100644
index 00000000000..4199cca77b8
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bound_object.cc
@@ -0,0 +1,938 @@
+// 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 "content/browser/renderer_host/java/java_bound_object.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
+#include "content/browser/renderer_host/java/java_type.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/WebKit/public/web/WebBindings.h"
+
+using base::StringPrintf;
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetClass;
+using base::android::GetMethodIDFromClassName;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using WebKit::WebBindings;
+
+// The conversion between JavaScript and Java types is based on the Live
+// Connect 2 spec. See
+// http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+
+// Note that in some cases, we differ from from the spec in order to maintain
+// existing behavior. These areas are marked LIVECONNECT_COMPLIANCE. We may
+// revisit this decision in the future.
+
+namespace content {
+namespace {
+
+const char kJavaLangClass[] = "java/lang/Class";
+const char kJavaLangObject[] = "java/lang/Object";
+const char kJavaLangReflectMethod[] = "java/lang/reflect/Method";
+const char kGetClass[] = "getClass";
+const char kGetMethods[] = "getMethods";
+const char kIsAnnotationPresent[] = "isAnnotationPresent";
+const char kReturningJavaLangClass[] = "()Ljava/lang/Class;";
+const char kReturningJavaLangReflectMethodArray[] =
+ "()[Ljava/lang/reflect/Method;";
+const char kTakesJavaLangClassReturningBoolean[] = "(Ljava/lang/Class;)Z";
+
+// Our special NPObject type. We extend an NPObject with a pointer to a
+// JavaBoundObject. We also add static methods for each of the NPObject
+// callbacks, which are registered by our NPClass. These methods simply
+// delegate to the private implementation methods of JavaBoundObject.
+struct JavaNPObject : public NPObject {
+ JavaBoundObject* bound_object;
+
+ static const NPClass kNPClass;
+
+ static NPObject* Allocate(NPP npp, NPClass* np_class);
+ static void Deallocate(NPObject* np_object);
+ static bool HasMethod(NPObject* np_object, NPIdentifier np_identifier);
+ static bool Invoke(NPObject* np_object, NPIdentifier np_identifier,
+ const NPVariant *args, uint32_t arg_count,
+ NPVariant *result);
+ static bool HasProperty(NPObject* np_object, NPIdentifier np_identifier);
+ static bool GetProperty(NPObject* np_object, NPIdentifier np_identifier,
+ NPVariant *result);
+};
+
+const NPClass JavaNPObject::kNPClass = {
+ NP_CLASS_STRUCT_VERSION,
+ JavaNPObject::Allocate,
+ JavaNPObject::Deallocate,
+ NULL, // NPInvalidate
+ JavaNPObject::HasMethod,
+ JavaNPObject::Invoke,
+ NULL, // NPInvokeDefault
+ JavaNPObject::HasProperty,
+ JavaNPObject::GetProperty,
+ NULL, // NPSetProperty,
+ NULL, // NPRemoveProperty
+};
+
+NPObject* JavaNPObject::Allocate(NPP npp, NPClass* np_class) {
+ JavaNPObject* obj = new JavaNPObject();
+ return obj;
+}
+
+void JavaNPObject::Deallocate(NPObject* np_object) {
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ delete obj->bound_object;
+ delete obj;
+}
+
+bool JavaNPObject::HasMethod(NPObject* np_object, NPIdentifier np_identifier) {
+ std::string name(WebBindings::utf8FromIdentifier(np_identifier));
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ return obj->bound_object->HasMethod(name);
+}
+
+bool JavaNPObject::Invoke(NPObject* np_object, NPIdentifier np_identifier,
+ const NPVariant* args, uint32_t arg_count,
+ NPVariant* result) {
+ std::string name(WebBindings::utf8FromIdentifier(np_identifier));
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ return obj->bound_object->Invoke(name, args, arg_count, result);
+}
+
+bool JavaNPObject::HasProperty(NPObject* np_object,
+ NPIdentifier np_identifier) {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate
+ // that the property is not present. Spec requires supporting this correctly.
+ return false;
+}
+
+bool JavaNPObject::GetProperty(NPObject* np_object,
+ NPIdentifier np_identifier,
+ NPVariant* result) {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate
+ // that the property is undefined. Spec requires supporting this correctly.
+ return false;
+}
+
+// Calls a Java method through JNI. If the Java method raises an uncaught
+// exception, it is cleared and this method returns false. Otherwise, this
+// method returns true and the Java method's return value is provided as an
+// NPVariant. Note that this method does not do any type coercion. The Java
+// return value is simply converted to the corresponding NPAPI type.
+bool CallJNIMethod(
+ jobject object,
+ const JavaType& return_type,
+ jmethodID id,
+ jvalue* parameters,
+ NPVariant* result,
+ const JavaRef<jclass>& safe_annotation_clazz,
+ const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager) {
+ JNIEnv* env = AttachCurrentThread();
+ switch (return_type.type) {
+ case JavaType::TypeBoolean:
+ BOOLEAN_TO_NPVARIANT(env->CallBooleanMethodA(object, id, parameters),
+ *result);
+ break;
+ case JavaType::TypeByte:
+ INT32_TO_NPVARIANT(env->CallByteMethodA(object, id, parameters), *result);
+ break;
+ case JavaType::TypeChar:
+ INT32_TO_NPVARIANT(env->CallCharMethodA(object, id, parameters), *result);
+ break;
+ case JavaType::TypeShort:
+ INT32_TO_NPVARIANT(env->CallShortMethodA(object, id, parameters),
+ *result);
+ break;
+ case JavaType::TypeInt:
+ INT32_TO_NPVARIANT(env->CallIntMethodA(object, id, parameters), *result);
+ break;
+ case JavaType::TypeLong:
+ DOUBLE_TO_NPVARIANT(env->CallLongMethodA(object, id, parameters),
+ *result);
+ break;
+ case JavaType::TypeFloat:
+ DOUBLE_TO_NPVARIANT(env->CallFloatMethodA(object, id, parameters),
+ *result);
+ break;
+ case JavaType::TypeDouble:
+ DOUBLE_TO_NPVARIANT(env->CallDoubleMethodA(object, id, parameters),
+ *result);
+ break;
+ case JavaType::TypeVoid:
+ env->CallVoidMethodA(object, id, parameters);
+ VOID_TO_NPVARIANT(*result);
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that
+ // return arrays. Spec requires calling the method and converting the
+ // result to a JavaScript array.
+ VOID_TO_NPVARIANT(*result);
+ break;
+ case JavaType::TypeString: {
+ jstring java_string = static_cast<jstring>(
+ env->CallObjectMethodA(object, id, parameters));
+ // If an exception was raised, we must clear it before calling most JNI
+ // methods. ScopedJavaLocalRef is liable to make such calls, so we test
+ // first.
+ if (base::android::ClearException(env)) {
+ return false;
+ }
+ ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string);
+ if (!scoped_java_string.obj()) {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined.
+ // Spec requires returning a null string.
+ VOID_TO_NPVARIANT(*result);
+ break;
+ }
+ std::string str =
+ base::android::ConvertJavaStringToUTF8(scoped_java_string);
+ size_t length = str.length();
+ // This pointer is freed in _NPN_ReleaseVariantValue in
+ // third_party/WebKit/Source/WebCore/bindings/v8/npruntime.cpp.
+ char* buffer = static_cast<char*>(malloc(length));
+ str.copy(buffer, length, 0);
+ STRINGN_TO_NPVARIANT(buffer, length, *result);
+ break;
+ }
+ case JavaType::TypeObject: {
+ // If an exception was raised, we must clear it before calling most JNI
+ // methods. ScopedJavaLocalRef is liable to make such calls, so we test
+ // first.
+ jobject java_object = env->CallObjectMethodA(object, id, parameters);
+ if (base::android::ClearException(env)) {
+ return false;
+ }
+ ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object);
+ if (!scoped_java_object.obj()) {
+ NULL_TO_NPVARIANT(*result);
+ break;
+ }
+ OBJECT_TO_NPVARIANT(JavaBoundObject::Create(scoped_java_object,
+ safe_annotation_clazz,
+ manager),
+ *result);
+ break;
+ }
+ }
+ return !base::android::ClearException(env);
+}
+
+double RoundDoubleTowardsZero(const double& x) {
+ if (std::isnan(x)) {
+ return 0.0;
+ }
+ return x > 0.0 ? floor(x) : ceil(x);
+}
+
+// Rounds to jlong using Java's type conversion rules.
+jlong RoundDoubleToLong(const double& x) {
+ double intermediate = RoundDoubleTowardsZero(x);
+ // The int64 limits can not be converted exactly to double values, so we
+ // compare to custom constants. kint64max is 2^63 - 1, but the spacing
+ // between double values in the the range 2^62 to 2^63 is 2^10. The cast is
+ // required to silence a spurious gcc warning for integer overflow.
+ const int64 limit = (GG_INT64_C(1) << 63) - static_cast<uint64>(1 << 10);
+ DCHECK(limit > 0);
+ const double kLargestDoubleLessThanInt64Max = limit;
+ const double kSmallestDoubleGreaterThanInt64Min = -limit;
+ if (intermediate > kLargestDoubleLessThanInt64Max) {
+ return kint64max;
+ }
+ if (intermediate < kSmallestDoubleGreaterThanInt64Min) {
+ return kint64min;
+ }
+ return static_cast<jlong>(intermediate);
+}
+
+// Rounds to jint using Java's type conversion rules.
+jint RoundDoubleToInt(const double& x) {
+ double intermediate = RoundDoubleTowardsZero(x);
+ // The int32 limits cast exactly to double values.
+ intermediate = std::min(intermediate, static_cast<double>(kint32max));
+ intermediate = std::max(intermediate, static_cast<double>(kint32min));
+ return static_cast<jint>(intermediate);
+}
+
+jvalue CoerceJavaScriptNumberToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES.
+
+ // For conversion to numeric types, we need to replicate Java's type
+ // conversion rules. This requires that for integer values, we simply discard
+ // all but the lowest n buts, where n is the number of bits in the target
+ // type. For double values, the logic is more involved.
+ jvalue result;
+ DCHECK(variant.type == NPVariantType_Int32 ||
+ variant.type == NPVariantType_Double);
+ bool is_double = variant.type == NPVariantType_Double;
+ switch (target_type.type) {
+ case JavaType::TypeByte:
+ result.b = is_double ?
+ static_cast<jbyte>(RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant))) :
+ static_cast<jbyte>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeChar:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0.
+ // Spec requires converting doubles similarly to how we convert doubles to
+ // other numeric types.
+ result.c = is_double ? 0 :
+ static_cast<jchar>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeShort:
+ result.s = is_double ?
+ static_cast<jshort>(RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant))) :
+ static_cast<jshort>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeInt:
+ result.i = is_double ? RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeLong:
+ result.j = is_double ? RoundDoubleToLong(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeFloat:
+ result.f = is_double ? static_cast<jfloat>(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeDouble:
+ result.d = is_double ? NPVARIANT_TO_DOUBLE(variant) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
+ // requires handling object equivalents of primitive types.
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ result.l = coerce_to_string ?
+ ConvertUTF8ToJavaString(
+ AttachCurrentThread(),
+ is_double ?
+ base::StringPrintf("%.6lg", NPVARIANT_TO_DOUBLE(variant)) :
+ base::Int64ToString(NPVARIANT_TO_INT32(variant))).Release() :
+ NULL;
+ break;
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
+ // requires converting to false for 0 or NaN, true otherwise.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
+ // requires raising a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptBooleanToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES.
+ DCHECK_EQ(NPVariantType_Bool, variant.type);
+ bool boolean_value = NPVARIANT_TO_BOOLEAN(variant);
+ jvalue result;
+ switch (target_type.type) {
+ case JavaType::TypeBoolean:
+ result.z = boolean_value ? JNI_TRUE : JNI_FALSE;
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires handling java.lang.Boolean and java.lang.Object.
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ result.l = coerce_to_string ?
+ ConvertUTF8ToJavaString(AttachCurrentThread(),
+ boolean_value ? "true" : "false").Release() :
+ NULL;
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeChar:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble: {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
+ // requires converting to 0 or 1.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires raising a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptStringToJavaValue(const NPVariant& variant,
+ const JavaType& target_type) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES.
+ DCHECK_EQ(NPVariantType_String, variant.type);
+ jvalue result;
+ switch (target_type.type) {
+ case JavaType::TypeString:
+ result.l = ConvertUTF8ToJavaString(
+ AttachCurrentThread(),
+ base::StringPiece(NPVARIANT_TO_STRING(variant).UTF8Characters,
+ NPVARIANT_TO_STRING(variant).UTF8Length)).Release();
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires handling java.lang.Object.
+ result.l = NULL;
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble: {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
+ // requires using valueOf() method of corresponding object type.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeChar:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
+ // requires using java.lang.Short.decode().
+ result.c = 0;
+ break;
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
+ // requires converting the empty string to false, otherwise true.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires raising a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+// Note that this only handles primitive types and strings.
+jobject CreateJavaArray(const JavaType& type, jsize length) {
+ JNIEnv* env = AttachCurrentThread();
+ switch (type.type) {
+ case JavaType::TypeBoolean:
+ return env->NewBooleanArray(length);
+ case JavaType::TypeByte:
+ return env->NewByteArray(length);
+ case JavaType::TypeChar:
+ return env->NewCharArray(length);
+ case JavaType::TypeShort:
+ return env->NewShortArray(length);
+ case JavaType::TypeInt:
+ return env->NewIntArray(length);
+ case JavaType::TypeLong:
+ return env->NewLongArray(length);
+ case JavaType::TypeFloat:
+ return env->NewFloatArray(length);
+ case JavaType::TypeDouble:
+ return env->NewDoubleArray(length);
+ case JavaType::TypeString: {
+ ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/String"));
+ return env->NewObjectArray(length, clazz.obj(), NULL);
+ }
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ case JavaType::TypeArray:
+ case JavaType::TypeObject:
+ // Not handled.
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+// Sets the specified element of the supplied array to the value of the
+// supplied jvalue. Requires that the type of the array matches that of the
+// jvalue. Handles only primitive types and strings. Note that in the case of a
+// string, the array takes a new reference to the string object.
+void SetArrayElement(jobject array,
+ const JavaType& type,
+ jsize index,
+ const jvalue& value) {
+ JNIEnv* env = AttachCurrentThread();
+ switch (type.type) {
+ case JavaType::TypeBoolean:
+ env->SetBooleanArrayRegion(static_cast<jbooleanArray>(array), index, 1,
+ &value.z);
+ break;
+ case JavaType::TypeByte:
+ env->SetByteArrayRegion(static_cast<jbyteArray>(array), index, 1,
+ &value.b);
+ break;
+ case JavaType::TypeChar:
+ env->SetCharArrayRegion(static_cast<jcharArray>(array), index, 1,
+ &value.c);
+ break;
+ case JavaType::TypeShort:
+ env->SetShortArrayRegion(static_cast<jshortArray>(array), index, 1,
+ &value.s);
+ break;
+ case JavaType::TypeInt:
+ env->SetIntArrayRegion(static_cast<jintArray>(array), index, 1,
+ &value.i);
+ break;
+ case JavaType::TypeLong:
+ env->SetLongArrayRegion(static_cast<jlongArray>(array), index, 1,
+ &value.j);
+ break;
+ case JavaType::TypeFloat:
+ env->SetFloatArrayRegion(static_cast<jfloatArray>(array), index, 1,
+ &value.f);
+ break;
+ case JavaType::TypeDouble:
+ env->SetDoubleArrayRegion(static_cast<jdoubleArray>(array), index, 1,
+ &value.d);
+ break;
+ case JavaType::TypeString:
+ env->SetObjectArrayElement(static_cast<jobjectArray>(array), index,
+ value.l);
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ case JavaType::TypeArray:
+ case JavaType::TypeObject:
+ // Not handled.
+ NOTREACHED();
+ }
+ base::android::CheckException(env);
+}
+
+void ReleaseJavaValueIfRequired(JNIEnv* env,
+ jvalue* value,
+ const JavaType& type) {
+ if (type.type == JavaType::TypeString ||
+ type.type == JavaType::TypeObject ||
+ type.type == JavaType::TypeArray) {
+ env->DeleteLocalRef(value->l);
+ value->l = NULL;
+ }
+}
+
+jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string);
+
+// Returns a new local reference to a Java array.
+jobject CoerceJavaScriptObjectToArray(const NPVariant& variant,
+ const JavaType& target_type) {
+ DCHECK_EQ(JavaType::TypeArray, target_type.type);
+ NPObject* object = NPVARIANT_TO_OBJECT(variant);
+ DCHECK_NE(&JavaNPObject::kNPClass, object->_class);
+
+ const JavaType& target_inner_type = *target_type.inner_type.get();
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for
+ // multi-dimensional arrays. Spec requires handling multi-demensional arrays.
+ if (target_inner_type.type == JavaType::TypeArray) {
+ return NULL;
+ }
+
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object
+ // arrays. Spec requires handling object arrays.
+ if (target_inner_type.type == JavaType::TypeObject) {
+ return NULL;
+ }
+
+ // If the object does not have a length property, return null.
+ NPVariant length_variant;
+ if (!WebBindings::getProperty(0, object,
+ WebBindings::getStringIdentifier("length"),
+ &length_variant)) {
+ WebBindings::releaseVariantValue(&length_variant);
+ return NULL;
+ }
+
+ // If the length property does not have numeric type, or is outside the valid
+ // range for a Java array length, return null.
+ jsize length = -1;
+ if (NPVARIANT_IS_INT32(length_variant)
+ && NPVARIANT_TO_INT32(length_variant) >= 0) {
+ length = NPVARIANT_TO_INT32(length_variant);
+ } else if (NPVARIANT_IS_DOUBLE(length_variant)
+ && NPVARIANT_TO_DOUBLE(length_variant) >= 0.0
+ && NPVARIANT_TO_DOUBLE(length_variant) <= kint32max) {
+ length = static_cast<jsize>(NPVARIANT_TO_DOUBLE(length_variant));
+ }
+ WebBindings::releaseVariantValue(&length_variant);
+ if (length == -1) {
+ return NULL;
+ }
+
+ // Create the Java array.
+ // TODO(steveblock): Handle failure to create the array.
+ jobject result = CreateJavaArray(target_inner_type, length);
+ NPVariant value_variant;
+ JNIEnv* env = AttachCurrentThread();
+ for (jsize i = 0; i < length; ++i) {
+ // It seems that getProperty() will set the variant to type void on failure,
+ // but this doesn't seem to be documented, so do it explicitly here for
+ // safety.
+ VOID_TO_NPVARIANT(value_variant);
+ // If this fails, for example due to a missing element, we simply treat the
+ // value as JavaScript undefined.
+ WebBindings::getProperty(0, object, WebBindings::getIntIdentifier(i),
+ &value_variant);
+ jvalue element = CoerceJavaScriptValueToJavaValue(value_variant,
+ target_inner_type,
+ false);
+ SetArrayElement(result, target_inner_type, i, element);
+ // CoerceJavaScriptValueToJavaValue() creates new local references to
+ // strings, objects and arrays. Of these, only strings can occur here.
+ // SetArrayElement() causes the array to take its own reference to the
+ // string, so we can now release the local reference.
+ DCHECK_NE(JavaType::TypeObject, target_inner_type.type);
+ DCHECK_NE(JavaType::TypeArray, target_inner_type.type);
+ ReleaseJavaValueIfRequired(env, &element, target_inner_type);
+ WebBindings::releaseVariantValue(&value_variant);
+ }
+
+ return result;
+}
+
+jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string) {
+ // This covers both JavaScript objects (including arrays) and Java objects.
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_OTHER_OBJECTS,
+ // http://jdk6.java.net/plugin2/liveconnect/#JS_ARRAY_VALUES and
+ // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS
+ DCHECK_EQ(NPVariantType_Object, variant.type);
+
+ NPObject* object = NPVARIANT_TO_OBJECT(variant);
+ bool is_java_object = &JavaNPObject::kNPClass == object->_class;
+
+ jvalue result;
+ switch (target_type.type) {
+ case JavaType::TypeObject:
+ if (is_java_object) {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to pass all Java
+ // objects. Spec requires passing only Java objects which are
+ // assignment-compatibile.
+ result.l = AttachCurrentThread()->NewLocalRef(
+ JavaBoundObject::GetJavaObject(object).obj());
+ } else {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec
+ // requires converting if the target type is
+ // netscape.javascript.JSObject, otherwise raising a JavaScript
+ // exception.
+ result.l = NULL;
+ }
+ break;
+ case JavaType::TypeString:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to
+ // "undefined". Spec requires calling toString() on the Java object.
+ result.l = coerce_to_string ?
+ ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined").
+ Release() :
+ NULL;
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble:
+ case JavaType::TypeChar: {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
+ // requires raising a JavaScript exception.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
+ // requires raising a JavaScript exception.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ if (is_java_object) {
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires raising a JavaScript exception.
+ result.l = NULL;
+ } else {
+ result.l = CoerceJavaScriptObjectToArray(variant, target_type);
+ }
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptNullOrUndefinedToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_NULL.
+ DCHECK(variant.type == NPVariantType_Null ||
+ variant.type == NPVariantType_Void);
+ jvalue result;
+ switch (target_type.type) {
+ case JavaType::TypeObject:
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to
+ // "undefined". Spec requires converting undefined to NULL.
+ result.l = (coerce_to_string && variant.type == NPVariantType_Void) ?
+ ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined").
+ Release() :
+ NULL;
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeChar:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble: {
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeBoolean:
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
+ // requires raising a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+// coerce_to_string means that we should try to coerce all JavaScript values to
+// strings when required, rather than simply converting to NULL. This is used
+// to maintain current behaviour, which differs slightly depending upon whether
+// or not the coercion in question is for an array element.
+//
+// Note that the jvalue returned by this method may contain a new local
+// reference to an object (string, object or array). This must be released by
+// the caller.
+jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant,
+ const JavaType& target_type,
+ bool coerce_to_string) {
+ // Note that in all these conversions, the relevant field of the jvalue must
+ // always be explicitly set, as jvalue does not initialize its fields.
+
+ switch (variant.type) {
+ case NPVariantType_Int32:
+ case NPVariantType_Double:
+ return CoerceJavaScriptNumberToJavaValue(variant, target_type,
+ coerce_to_string);
+ case NPVariantType_Bool:
+ return CoerceJavaScriptBooleanToJavaValue(variant, target_type,
+ coerce_to_string);
+ case NPVariantType_String:
+ return CoerceJavaScriptStringToJavaValue(variant, target_type);
+ case NPVariantType_Object:
+ return CoerceJavaScriptObjectToJavaValue(variant, target_type,
+ coerce_to_string);
+ case NPVariantType_Null:
+ case NPVariantType_Void:
+ return CoerceJavaScriptNullOrUndefinedToJavaValue(variant, target_type,
+ coerce_to_string);
+ }
+ NOTREACHED();
+ return jvalue();
+}
+
+} // namespace
+
+NPObject* JavaBoundObject::Create(
+ const JavaRef<jobject>& object,
+ const JavaRef<jclass>& safe_annotation_clazz,
+ const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager) {
+ // The first argument (a plugin's instance handle) is passed through to the
+ // allocate function directly, and we don't use it, so it's ok to be 0.
+ // The object is created with a ref count of one.
+ NPObject* np_object = WebBindings::createObject(0, const_cast<NPClass*>(
+ &JavaNPObject::kNPClass));
+ // The NPObject takes ownership of the JavaBoundObject.
+ reinterpret_cast<JavaNPObject*>(np_object)->bound_object =
+ new JavaBoundObject(object, safe_annotation_clazz, manager);
+ return np_object;
+}
+
+JavaBoundObject::JavaBoundObject(
+ const JavaRef<jobject>& object,
+ const JavaRef<jclass>& safe_annotation_clazz,
+ const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager)
+ : java_object_(AttachCurrentThread(), object.obj()),
+ manager_(manager),
+ are_methods_set_up_(false),
+ safe_annotation_clazz_(safe_annotation_clazz) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectCreated,
+ manager_,
+ base::android::ScopedJavaGlobalRef<jobject>(object)));
+ // Other than informing the JavaBridgeDispatcherHostManager that a java bound
+ // object has been created (above), we don't do anything else with our Java
+ // object when first created. We do it all lazily when a method is first
+ // invoked.
+}
+
+JavaBoundObject::~JavaBoundObject() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed,
+ manager_,
+ base::android::ScopedJavaGlobalRef<jobject>(
+ java_object_.get(AttachCurrentThread()))));
+}
+
+ScopedJavaLocalRef<jobject> JavaBoundObject::GetJavaObject(NPObject* object) {
+ DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
+ JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object;
+ return jbo->java_object_.get(AttachCurrentThread());
+}
+
+bool JavaBoundObject::HasMethod(const std::string& name) const {
+ EnsureMethodsAreSetUp();
+ return methods_.find(name) != methods_.end();
+}
+
+bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args,
+ size_t arg_count, NPVariant* result) {
+ EnsureMethodsAreSetUp();
+
+ // Get all methods with the correct name.
+ std::pair<JavaMethodMap::const_iterator, JavaMethodMap::const_iterator>
+ iters = methods_.equal_range(name);
+ if (iters.first == iters.second) {
+ return false;
+ }
+
+ // Take the first method with the correct number of arguments.
+ JavaMethod* method = NULL;
+ for (JavaMethodMap::const_iterator iter = iters.first; iter != iters.second;
+ ++iter) {
+ if (iter->second->num_parameters() == arg_count) {
+ method = iter->second.get();
+ break;
+ }
+ }
+ if (!method) {
+ return false;
+ }
+
+ // Coerce
+ std::vector<jvalue> parameters(arg_count);
+ for (size_t i = 0; i < arg_count; ++i) {
+ parameters[i] = CoerceJavaScriptValueToJavaValue(args[i],
+ method->parameter_type(i),
+ true);
+ }
+
+ ScopedJavaLocalRef<jobject> obj = java_object_.get(AttachCurrentThread());
+
+ bool ok = false;
+ if (!obj.is_null()) {
+ // Call
+ ok = CallJNIMethod(obj.obj(), method->return_type(),
+ method->id(), &parameters[0], result,
+ safe_annotation_clazz_,
+ manager_);
+ }
+
+ // Now that we're done with the jvalue, release any local references created
+ // by CoerceJavaScriptValueToJavaValue().
+ JNIEnv* env = AttachCurrentThread();
+ for (size_t i = 0; i < arg_count; ++i) {
+ ReleaseJavaValueIfRequired(env, &parameters[i], method->parameter_type(i));
+ }
+
+ return ok;
+}
+
+void JavaBoundObject::EnsureMethodsAreSetUp() const {
+ if (are_methods_set_up_)
+ return;
+ are_methods_set_up_ = true;
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_object_.get(env);
+
+ if (obj.is_null()) {
+ return;
+ }
+
+ ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
+ env->CallObjectMethod(obj.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangObject,
+ kGetClass,
+ kReturningJavaLangClass))));
+
+ ScopedJavaLocalRef<jobjectArray> methods(env, static_cast<jobjectArray>(
+ env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangClass,
+ kGetMethods,
+ kReturningJavaLangReflectMethodArray))));
+
+ size_t num_methods = env->GetArrayLength(methods.obj());
+ // Java objects always have public methods.
+ DCHECK(num_methods);
+
+ for (size_t i = 0; i < num_methods; ++i) {
+ ScopedJavaLocalRef<jobject> java_method(
+ env,
+ env->GetObjectArrayElement(methods.obj(), i));
+
+ if (!safe_annotation_clazz_.is_null()) {
+ jboolean safe = env->CallBooleanMethod(java_method.obj(),
+ GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kIsAnnotationPresent,
+ kTakesJavaLangClassReturningBoolean),
+ safe_annotation_clazz_.obj());
+
+ if (!safe)
+ continue;
+ }
+
+ JavaMethod* method = new JavaMethod(java_method);
+ methods_.insert(std::make_pair(method->name(), method));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_bound_object.h b/chromium/content/browser/renderer_host/java/java_bound_object.h
new file mode 100644
index 00000000000..ff97fdb2b6c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bound_object.h
@@ -0,0 +1,89 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_
+
+#include <jni.h>
+#include <map>
+#include <string>
+
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/java/java_method.h"
+#include "third_party/npapi/bindings/npruntime.h"
+
+namespace content {
+
+class JavaBridgeDispatcherHostManager;
+
+// Wrapper around a Java object.
+//
+// Represents a Java object for use in the Java bridge. Holds a global ref to
+// the Java object and provides the ability to invoke methods on it.
+// Interrogation of the Java object for its methods is done lazily. This class
+// is not generally threadsafe. However, it does allow for instances to be
+// created and destroyed on different threads.
+class JavaBoundObject {
+ public:
+ // Takes a Java object and creates a JavaBoundObject around it. The
+ // |require_annotation| flag specifies whether or not only methods with the
+ // JavascriptInterface annotation are exposed to JavaScript. This property
+ // propagates to all Objects that get implicitly exposed as return values as
+ // well. Returns an NPObject with a ref count of one which owns the
+ // JavaBoundObject.
+ // See also comment below for |manager_|.
+ static NPObject* Create(
+ const base::android::JavaRef<jobject>& object,
+ const base::android::JavaRef<jclass>& safe_annotation_clazz,
+ const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager);
+
+ virtual ~JavaBoundObject();
+
+ // Gets a local ref to the underlying JavaObject from a JavaBoundObject
+ // wrapped as an NPObject. May return null if the underlying object has
+ // been garbage collected.
+ static base::android::ScopedJavaLocalRef<jobject> GetJavaObject(
+ NPObject* object);
+
+ // Methods to implement the NPObject callbacks.
+ bool HasMethod(const std::string& name) const;
+ bool Invoke(const std::string& name, const NPVariant* args, size_t arg_count,
+ NPVariant* result);
+
+ private:
+ explicit JavaBoundObject(
+ const base::android::JavaRef<jobject>& object,
+ const base::android::JavaRef<jclass>& safe_annotation_clazz,
+ const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager_);
+
+ void EnsureMethodsAreSetUp() const;
+
+ // The weak ref to the underlying Java object that this JavaBoundObject
+ // instance represents.
+ JavaObjectWeakGlobalRef java_object_;
+
+ // Keep a pointer back to the JavaBridgeDispatcherHostManager so that we
+ // can notify it when this JavaBoundObject is destroyed. JavaBoundObjects
+ // may outlive the manager so keep a WeakPtr. Note the WeakPtr may only be
+ // dereferenced on the UI thread.
+ base::WeakPtr<JavaBridgeDispatcherHostManager> manager_;
+
+ // Map of public methods, from method name to Method instance. Multiple
+ // entries will be present for overloaded methods. Note that we can't use
+ // scoped_ptr in STL containers as we can't copy it.
+ typedef std::multimap<std::string, linked_ptr<JavaMethod> > JavaMethodMap;
+ mutable JavaMethodMap methods_;
+ mutable bool are_methods_set_up_;
+
+ base::android::ScopedJavaGlobalRef<jclass> safe_annotation_clazz_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JavaBoundObject);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc
new file mode 100644
index 00000000000..8f53b9d99a8
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/java/java_bridge_channel_host.h"
+
+#include "base/atomicops.h"
+#include "base/lazy_instance.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/common/java_bridge_messages.h"
+
+using base::WaitableEvent;
+
+namespace content {
+namespace {
+struct WaitableEventLazyInstanceTraits
+ : public base::DefaultLazyInstanceTraits<WaitableEvent> {
+ static WaitableEvent* New(void* instance) {
+ // Use placement new to initialize our instance in our preallocated space.
+ // The parenthesis is very important here to force POD type initialization.
+ return new (instance) WaitableEvent(false, false);
+ }
+};
+base::LazyInstance<WaitableEvent, WaitableEventLazyInstanceTraits> dummy_event =
+ LAZY_INSTANCE_INITIALIZER;
+
+base::subtle::AtomicWord g_last_id = 0;
+}
+
+JavaBridgeChannelHost::~JavaBridgeChannelHost() {
+#if defined(OS_POSIX)
+ if (channel_handle_.socket.fd > 0) {
+ close(channel_handle_.socket.fd);
+ }
+#endif
+}
+
+JavaBridgeChannelHost* JavaBridgeChannelHost::GetJavaBridgeChannelHost(
+ int renderer_id,
+ base::MessageLoopProxy* ipc_message_loop) {
+ std::string channel_name(base::StringPrintf("r%d.javabridge", renderer_id));
+ // There's no need for a shutdown event here. If the browser is terminated
+ // while the JavaBridgeChannelHost is blocked on a synchronous IPC call, the
+ // renderer's shutdown event will cause the underlying channel to shut down,
+ // thus terminating the IPC call.
+ return static_cast<JavaBridgeChannelHost*>(NPChannelBase::GetChannel(
+ channel_name,
+ IPC::Channel::MODE_SERVER,
+ ClassFactory,
+ ipc_message_loop,
+ true,
+ dummy_event.Pointer()));
+}
+
+int JavaBridgeChannelHost::ThreadsafeGenerateRouteID() {
+ return base::subtle::NoBarrier_AtomicIncrement(&g_last_id, 1);
+}
+
+int JavaBridgeChannelHost::GenerateRouteID() {
+ return ThreadsafeGenerateRouteID();
+}
+
+bool JavaBridgeChannelHost::Init(base::MessageLoopProxy* ipc_message_loop,
+ bool create_pipe_now,
+ WaitableEvent* shutdown_event) {
+ if (!NPChannelBase::Init(ipc_message_loop, create_pipe_now, shutdown_event)) {
+ return false;
+ }
+
+ // Finish populating our ChannelHandle.
+#if defined(OS_POSIX)
+ // We take control of the FD for all session between this host and
+ // the corresponding renderers. We keep it open until this object
+ // is deleted.
+ channel_handle_.socket.fd = channel_->TakeClientFileDescriptor();
+#endif
+
+ return true;
+}
+
+bool JavaBridgeChannelHost::OnControlMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(JavaBridgeChannelHost, message)
+ IPC_MESSAGE_HANDLER(JavaBridgeMsg_GenerateRouteID, OnGenerateRouteID)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void JavaBridgeChannelHost::OnGenerateRouteID(int* route_id) {
+ *route_id = GenerateRouteID();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h
new file mode 100644
index 00000000000..3f3f1468e9b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h
@@ -0,0 +1,52 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_CHANNEL_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_CHANNEL_HOST_H_
+
+#include "content/child/npapi/np_channel_base.h"
+
+namespace content {
+
+class JavaBridgeChannelHost : public NPChannelBase {
+ public:
+ static JavaBridgeChannelHost* GetJavaBridgeChannelHost(
+ int renderer_id,
+ base::MessageLoopProxy* ipc_message_loop);
+
+ // A threadsafe function to generate a unique route ID. Used by the
+ // JavaBridgeDispatcherHost on the UI thread and this class on the Java
+ // Bridge's background thread.
+ static int ThreadsafeGenerateRouteID();
+
+ // NPChannelBase implementation:
+ virtual int GenerateRouteID() OVERRIDE;
+
+ // NPChannelBase override:
+ virtual bool Init(base::MessageLoopProxy* ipc_message_loop,
+ bool create_pipe_now,
+ base::WaitableEvent* shutdown_event) OVERRIDE;
+
+ protected:
+ // NPChannelBase override:
+ virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ private:
+ JavaBridgeChannelHost() {}
+ friend class base::RefCountedThreadSafe<JavaBridgeChannelHost>;
+ virtual ~JavaBridgeChannelHost();
+
+ static NPChannelBase* ClassFactory() {
+ return new JavaBridgeChannelHost();
+ }
+
+ // Message handlers
+ void OnGenerateRouteID(int* route_id);
+
+ DISALLOW_COPY_AND_ASSIGN(JavaBridgeChannelHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_CHANNEL_HOST_H_
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc
new file mode 100644
index 00000000000..95a1c6aefe4
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc
@@ -0,0 +1,173 @@
+// 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 "content/browser/renderer_host/java/java_bridge_dispatcher_host.h"
+
+#include "base/android/java_handler_thread.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "content/browser/renderer_host/java/java_bridge_channel_host.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/child/child_process.h"
+#include "content/child/npapi/npobject_stub.h"
+#include "content/child/npapi/npobject_util.h" // For CreateNPVariantParam()
+#include "content/common/java_bridge_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "third_party/WebKit/public/web/WebBindings.h"
+
+#if !defined(OS_ANDROID)
+#error "JavaBridge currently only supports OS_ANDROID"
+#endif
+
+namespace content {
+
+namespace {
+// The JavaBridge needs to use a Java thread so the callback
+// will happen on a thread with a prepared Looper.
+class JavaBridgeThread : public base::android::JavaHandlerThread {
+ public:
+ JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
+ Start();
+ }
+ virtual ~JavaBridgeThread() {
+ Stop();
+ }
+};
+
+void CleanUpStubs(const std::vector<base::WeakPtr<NPObjectStub> > & stubs) {
+ for (size_t i = 0; i < stubs.size(); ++i) {
+ if (stubs[i]) {
+ stubs[i]->DeleteSoon();
+ }
+ }
+}
+
+base::LazyInstance<JavaBridgeThread> g_background_thread =
+ LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+JavaBridgeDispatcherHost::JavaBridgeDispatcherHost(
+ RenderViewHost* render_view_host)
+ : RenderViewHostObserver(render_view_host),
+ is_renderer_initialized_(false) {
+}
+
+JavaBridgeDispatcherHost::~JavaBridgeDispatcherHost() {
+ g_background_thread.Get().message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&CleanUpStubs, stubs_));
+}
+
+void JavaBridgeDispatcherHost::AddNamedObject(const string16& name,
+ NPObject* object) {
+ NPVariant_Param variant_param;
+ CreateNPVariantParam(object, &variant_param);
+
+ if (!is_renderer_initialized_) {
+ is_renderer_initialized_ = true;
+ Send(new JavaBridgeMsg_Init(routing_id()));
+ }
+ Send(new JavaBridgeMsg_AddNamedObject(routing_id(), name, variant_param));
+}
+
+void JavaBridgeDispatcherHost::RemoveNamedObject(const string16& name) {
+ // On receipt of this message, the JavaBridgeDispatcher will drop its
+ // reference to the corresponding proxy object. When the last reference is
+ // removed, the proxy object will delete its NPObjectProxy, which will cause
+ // the NPObjectStub to be deleted, which will drop its reference to the
+ // original NPObject.
+ Send(new JavaBridgeMsg_RemoveNamedObject(routing_id(), name));
+}
+
+bool JavaBridgeDispatcherHost::Send(IPC::Message* msg) {
+ return RenderViewHostObserver::Send(msg);
+}
+
+void JavaBridgeDispatcherHost::RenderViewHostDestroyed(
+ RenderViewHost* render_view_host) {
+ // Base implementation deletes the object. This class is ref counted, with
+ // refs held by the JavaBridgeDispatcherHostManager and base::Bind, so that
+ // behavior is unwanted.
+}
+
+bool JavaBridgeDispatcherHost::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(JavaBridgeDispatcherHost, msg)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(JavaBridgeHostMsg_GetChannelHandle,
+ OnGetChannelHandle)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void JavaBridgeDispatcherHost::OnGetChannelHandle(IPC::Message* reply_msg) {
+ g_background_thread.Get().message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&JavaBridgeDispatcherHost::GetChannelHandle, this, reply_msg));
+}
+
+void JavaBridgeDispatcherHost::GetChannelHandle(IPC::Message* reply_msg) {
+ // The channel creates the channel handle based on the renderer ID we passed
+ // to GetJavaBridgeChannelHost() and, on POSIX, the file descriptor used by
+ // the underlying channel.
+ JavaBridgeHostMsg_GetChannelHandle::WriteReplyParams(
+ reply_msg,
+ channel_->channel_handle());
+ Send(reply_msg);
+}
+
+void JavaBridgeDispatcherHost::CreateNPVariantParam(NPObject* object,
+ NPVariant_Param* param) {
+ // The JavaBridgeChannelHost needs to be created on the background thread, as
+ // that is where Java objects will live, and CreateNPVariantParam() needs the
+ // channel to create the NPObjectStub. To avoid blocking here until the
+ // channel is ready, create the NPVariant_Param by hand, then post a message
+ // to the background thread to set up the channel and create the corresponding
+ // NPObjectStub. Post that message before doing any IPC, to make sure that
+ // the channel and object proxies are ready before responses are received
+ // from the renderer.
+
+ // Create an NPVariantParam suitable for serialization over IPC from our
+ // NPVariant. See CreateNPVariantParam() in npobject_utils.
+ param->type = NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID;
+ int route_id = JavaBridgeChannelHost::ThreadsafeGenerateRouteID();
+ param->npobject_routing_id = route_id;
+
+ WebKit::WebBindings::retainObject(object);
+ g_background_thread.Get().message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&JavaBridgeDispatcherHost::CreateObjectStub, this, object,
+ route_id));
+}
+
+void JavaBridgeDispatcherHost::CreateObjectStub(NPObject* object,
+ int route_id) {
+ DCHECK_EQ(g_background_thread.Get().message_loop(),
+ base::MessageLoop::current());
+ if (!channel_.get()) {
+ channel_ = JavaBridgeChannelHost::GetJavaBridgeChannelHost(
+ render_view_host()->GetProcess()->GetID(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO));
+ }
+
+ // In a typical scenario, the lifetime of each NPObjectStub is governed by
+ // that of the NPObjectProxy in the renderer, via the channel. However,
+ // we cannot guaranteed that the renderer always terminates cleanly
+ // (crashes / sometimes just unavoidable). We keep a weak reference to
+ // it now and schedule a delete on it when this host is getting deleted.
+
+ // Pass 0 for the containing window, as it's only used by plugins to pump the
+ // window message queue when a method on a renderer-side object causes a
+ // dialog to be displayed, and the Java Bridge does not need this
+ // functionality. The page URL is also not required.
+ stubs_.push_back((new NPObjectStub(
+ object, channel_.get(), route_id, 0, GURL()))->AsWeakPtr());
+
+ // The NPObjectStub takes a reference to the NPObject. Release the ref added
+ // in CreateNPVariantParam().
+ WebKit::WebBindings::releaseObject(object);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h
new file mode 100644
index 00000000000..6d44c39d22a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h
@@ -0,0 +1,75 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_H_
+
+#include <vector>
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "content/child/npapi/npobject_stub.h"
+#include "content/public/browser/render_view_host_observer.h"
+
+class RouteIDGenerator;
+struct NPObject;
+
+namespace content {
+class NPChannelBase;
+class RenderViewHost;
+struct NPVariant_Param;
+
+// This class handles injecting Java objects into a single RenderView. The Java
+// object itself lives in the browser process on a background thread, while a
+// proxy object is created in the renderer. An instance of this class exists
+// for each RenderViewHost.
+class JavaBridgeDispatcherHost
+ : public base::RefCountedThreadSafe<JavaBridgeDispatcherHost>,
+ public RenderViewHostObserver {
+ public:
+ // We hold a weak pointer to the RenderViewhost. It must outlive this object.
+ JavaBridgeDispatcherHost(RenderViewHost* render_view_host);
+
+ // Injects |object| into the main frame of the corresponding RenderView. A
+ // proxy object is created in the renderer and when the main frame's window
+ // object is next cleared, this proxy object is bound to the window object
+ // using |name|. The proxy object remains bound until the next time the
+ // window object is cleared after a call to RemoveNamedObject() or
+ // AddNamedObject() with the same name. The proxy object proxies calls back
+ // to |object|, which is manipulated on the background thread. This class
+ // holds a reference to |object| for the time that the proxy object is bound
+ // to the window object.
+ void AddNamedObject(const string16& name, NPObject* object);
+ void RemoveNamedObject(const string16& name);
+
+ // RenderViewHostObserver overrides:
+ // The IPC macros require this to be public.
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+ virtual void RenderViewHostDestroyed(
+ RenderViewHost* render_view_host) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<JavaBridgeDispatcherHost>;
+ virtual ~JavaBridgeDispatcherHost();
+
+ // RenderViewHostObserver override:
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // Message handlers
+ void OnGetChannelHandle(IPC::Message* reply_msg);
+
+ void GetChannelHandle(IPC::Message* reply_msg);
+ void CreateNPVariantParam(NPObject* object, NPVariant_Param* param);
+ void CreateObjectStub(NPObject* object, int route_id);
+
+ scoped_refptr<NPChannelBase> channel_;
+ bool is_renderer_initialized_;
+ std::vector<base::WeakPtr<NPObjectStub> > stubs_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaBridgeDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc
new file mode 100644
index 00000000000..bac38564e79
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc
@@ -0,0 +1,156 @@
+// 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 "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/java/java_bound_object.h"
+#include "content/browser/renderer_host/java/java_bridge_dispatcher_host.h"
+#include "content/common/android/hash_set.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/WebKit/public/web/WebBindings.h"
+
+namespace content {
+
+JavaBridgeDispatcherHostManager::JavaBridgeDispatcherHostManager(
+ WebContents* web_contents)
+ : WebContentsObserver(web_contents) {
+}
+
+JavaBridgeDispatcherHostManager::~JavaBridgeDispatcherHostManager() {
+ for (ObjectMap::iterator iter = objects_.begin(); iter != objects_.end();
+ ++iter) {
+ WebKit::WebBindings::releaseObject(iter->second);
+ }
+ DCHECK_EQ(0U, instances_.size());
+}
+
+void JavaBridgeDispatcherHostManager::AddNamedObject(const string16& name,
+ NPObject* object) {
+ // Record this object in a map so that we can add it into RenderViewHosts
+ // created later. The JavaBridgeDispatcherHost instances will take a
+ // reference to the object, but we take one too, because this method can be
+ // called before there are any such instances.
+ WebKit::WebBindings::retainObject(object);
+ objects_[name] = object;
+
+ for (InstanceMap::iterator iter = instances_.begin();
+ iter != instances_.end(); ++iter) {
+ iter->second->AddNamedObject(name, object);
+ }
+}
+
+void JavaBridgeDispatcherHostManager::SetRetainedObjectSet(
+ const JavaObjectWeakGlobalRef& retained_object_set) {
+ // It's an error to replace the retained_object_set_ after it's been set,
+ // so we check that it hasn't already been here.
+ // TODO(benm): It'd be better to pass the set in the constructor to avoid
+ // the chance of this happening; but that's tricky as this get's constructed
+ // before ContentViewCore (which owns the set). Best solution may be to move
+ // ownership of the JavaBridgerDispatchHostManager from WebContents to
+ // ContentViewCore?
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> new_retained_object_set =
+ retained_object_set.get(env);
+ base::android::ScopedJavaLocalRef<jobject> current_retained_object_set =
+ retained_object_set_.get(env);
+ if (!env->IsSameObject(new_retained_object_set.obj(),
+ current_retained_object_set.obj())) {
+ DCHECK(current_retained_object_set.is_null());
+ retained_object_set_ = retained_object_set;
+ }
+}
+
+void JavaBridgeDispatcherHostManager::RemoveNamedObject(const string16& name) {
+ ObjectMap::iterator iter = objects_.find(name);
+ if (iter == objects_.end()) {
+ return;
+ }
+
+ WebKit::WebBindings::releaseObject(iter->second);
+ objects_.erase(iter);
+
+ for (InstanceMap::iterator iter = instances_.begin();
+ iter != instances_.end(); ++iter) {
+ iter->second->RemoveNamedObject(name);
+ }
+}
+
+void JavaBridgeDispatcherHostManager::RenderViewCreated(
+ RenderViewHost* render_view_host) {
+ // Creates a JavaBridgeDispatcherHost for the specified RenderViewHost and
+ // adds all currently registered named objects to the new instance.
+ scoped_refptr<JavaBridgeDispatcherHost> instance =
+ new JavaBridgeDispatcherHost(render_view_host);
+
+ for (ObjectMap::const_iterator iter = objects_.begin();
+ iter != objects_.end(); ++iter) {
+ instance->AddNamedObject(iter->first, iter->second);
+ }
+
+ instances_[render_view_host] = instance;
+}
+
+void JavaBridgeDispatcherHostManager::RenderViewDeleted(
+ RenderViewHost* render_view_host) {
+ instances_.erase(render_view_host);
+}
+
+void JavaBridgeDispatcherHostManager::WebContentsDestroyed(
+ WebContents* web_contents) {
+ // When a WebContents is shutting down, it clears its observers before
+ // it kills all of its RenderViewHosts, so we won't get a call to
+ // RenderViewDeleted() for all RenderViewHosts.
+ instances_.clear();
+}
+
+void JavaBridgeDispatcherHostManager::DocumentAvailableInMainFrame() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Called when the window object has been cleared in the main frame.
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> retained_object_set =
+ retained_object_set_.get(env);
+ if (!retained_object_set.is_null()) {
+ JNI_Java_HashSet_clear(env, retained_object_set);
+
+ // We also need to add back the named objects we have so far as they
+ // should survive navigations.
+ ObjectMap::iterator it = objects_.begin();
+ for (; it != objects_.end(); ++it) {
+ JNI_Java_HashSet_add(env, retained_object_set,
+ JavaBoundObject::GetJavaObject(it->second));
+ }
+ }
+}
+
+void JavaBridgeDispatcherHostManager::JavaBoundObjectCreated(
+ const base::android::JavaRef<jobject>& object) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> retained_object_set =
+ retained_object_set_.get(env);
+ if (!retained_object_set.is_null()) {
+ JNI_Java_HashSet_add(env, retained_object_set, object);
+ }
+}
+
+void JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed(
+ const base::android::JavaRef<jobject>& object) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> retained_object_set =
+ retained_object_set_.get(env);
+ if (!retained_object_set.is_null()) {
+ JNI_Java_HashSet_remove(env, retained_object_set, object);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h
new file mode 100644
index 00000000000..6fb7aab7cfb
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_MANAGER_H_
+
+#include <map>
+
+#include "base/android/jni_helper.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "content/public/browser/web_contents_observer.h"
+
+struct NPObject;
+
+namespace content {
+class JavaBridgeDispatcherHost;
+class RenderViewHost;
+
+// This class handles injecting Java objects into all of the RenderViews
+// associated with a WebContents. It manages a set of JavaBridgeDispatcherHost
+// objects, one per RenderViewHost.
+class JavaBridgeDispatcherHostManager
+ : public WebContentsObserver,
+ public base::SupportsWeakPtr<JavaBridgeDispatcherHostManager> {
+ public:
+ explicit JavaBridgeDispatcherHostManager(WebContents* web_contents);
+ virtual ~JavaBridgeDispatcherHostManager();
+
+ // These methods add or remove the object to each JavaBridgeDispatcherHost.
+ // Each one holds a reference to the NPObject while the object is bound to
+ // the corresponding RenderView. See JavaBridgeDispatcherHost for details.
+ void AddNamedObject(const string16& name, NPObject* object);
+ void RemoveNamedObject(const string16& name);
+
+ // Every time a JavaBoundObject backed by a real Java object is
+ // created/destroyed, we insert/remove a strong ref to that Java object into
+ // this set so that it doesn't get garbage collected while it's still
+ // potentially in use. Although the set is managed native side, it's owned
+ // and defined in Java so that pushing refs into it does not create new GC
+ // roots that would prevent ContentViewCore from being garbage collected.
+ void SetRetainedObjectSet(const JavaObjectWeakGlobalRef& retained_object_set);
+
+ // WebContentsObserver overrides
+ virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+ virtual void DocumentAvailableInMainFrame() OVERRIDE;
+
+ void JavaBoundObjectCreated(const base::android::JavaRef<jobject>& object);
+ void JavaBoundObjectDestroyed(const base::android::JavaRef<jobject>& object);
+
+ private:
+ typedef std::map<RenderViewHost*, scoped_refptr<JavaBridgeDispatcherHost> >
+ InstanceMap;
+ InstanceMap instances_;
+ typedef std::map<string16, NPObject*> ObjectMap;
+ ObjectMap objects_;
+ JavaObjectWeakGlobalRef retained_object_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaBridgeDispatcherHostManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/java/java_method.cc b/chromium/content/browser/renderer_host/java/java_method.cc
new file mode 100644
index 00000000000..3cc569fbcae
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_method.cc
@@ -0,0 +1,237 @@
+// 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 "content/browser/renderer_host/java/java_method.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/lazy_instance.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_util.h" // For ReplaceSubstringsAfterOffset
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::GetClass;
+using base::android::GetMethodIDFromClassName;
+using base::android::MethodID;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+namespace {
+
+const char kGetName[] = "getName";
+const char kGetDeclaringClass[] = "getDeclaringClass";
+const char kGetModifiers[] = "getModifiers";
+const char kGetParameterTypes[] = "getParameterTypes";
+const char kGetReturnType[] = "getReturnType";
+const char kIntegerReturningBoolean[] = "(I)Z";
+const char kIsStatic[] = "isStatic";
+const char kJavaLangClass[] = "java/lang/Class";
+const char kJavaLangReflectMethod[] = "java/lang/reflect/Method";
+const char kJavaLangReflectModifier[] = "java/lang/reflect/Modifier";
+const char kReturningInteger[] = "()I";
+const char kReturningJavaLangClass[] = "()Ljava/lang/Class;";
+const char kReturningJavaLangClassArray[] = "()[Ljava/lang/Class;";
+const char kReturningJavaLangString[] = "()Ljava/lang/String;";
+
+struct ModifierClassTraits :
+ public base::internal::LeakyLazyInstanceTraits<ScopedJavaGlobalRef<
+ jclass> > {
+ static ScopedJavaGlobalRef<jclass>* New(void* instance) {
+ JNIEnv* env = AttachCurrentThread();
+ // Use placement new to initialize our instance in our preallocated space.
+ return new (instance) ScopedJavaGlobalRef<jclass>(
+ GetClass(env, kJavaLangReflectModifier));
+ }
+};
+
+base::LazyInstance<ScopedJavaGlobalRef<jclass>, ModifierClassTraits>
+ g_java_lang_reflect_modifier_class = LAZY_INSTANCE_INITIALIZER;
+
+std::string BinaryNameToJNIName(const std::string& binary_name,
+ JavaType* type) {
+ DCHECK(type);
+ *type = JavaType::CreateFromBinaryName(binary_name);
+ switch (type->type) {
+ case JavaType::TypeBoolean:
+ return "Z";
+ case JavaType::TypeByte:
+ return "B";
+ case JavaType::TypeChar:
+ return "C";
+ case JavaType::TypeShort:
+ return "S";
+ case JavaType::TypeInt:
+ return "I";
+ case JavaType::TypeLong:
+ return "J";
+ case JavaType::TypeFloat:
+ return "F";
+ case JavaType::TypeDouble:
+ return "D";
+ case JavaType::TypeVoid:
+ return "V";
+ case JavaType::TypeArray: {
+ // For array types, the binary name uses the JNI name encodings.
+ std::string jni_name = binary_name;
+ ReplaceSubstringsAfterOffset(&jni_name, 0, ".", "/");
+ return jni_name;
+ }
+ case JavaType::TypeString:
+ case JavaType::TypeObject:
+ std::string jni_name = "L" + binary_name + ";";
+ ReplaceSubstringsAfterOffset(&jni_name, 0, ".", "/");
+ return jni_name;
+ }
+ NOTREACHED();
+ return EmptyString();
+}
+
+} // namespace
+
+JavaMethod::JavaMethod(const base::android::JavaRef<jobject>& method)
+ : java_method_(method),
+ have_calculated_num_parameters_(false),
+ id_(NULL) {
+ JNIEnv* env = AttachCurrentThread();
+ // On construction, we do nothing except get the name. Everything else is
+ // done lazily.
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kGetName,
+ kReturningJavaLangString))));
+ name_ = ConvertJavaStringToUTF8(name);
+}
+
+JavaMethod::~JavaMethod() {
+}
+
+size_t JavaMethod::num_parameters() const {
+ EnsureNumParametersIsSetUp();
+ return num_parameters_;
+}
+
+const JavaType& JavaMethod::parameter_type(size_t index) const {
+ EnsureTypesAndIDAreSetUp();
+ return parameter_types_[index];
+}
+
+const JavaType& JavaMethod::return_type() const {
+ EnsureTypesAndIDAreSetUp();
+ return return_type_;
+}
+
+jmethodID JavaMethod::id() const {
+ EnsureTypesAndIDAreSetUp();
+ return id_;
+}
+
+void JavaMethod::EnsureNumParametersIsSetUp() const {
+ if (have_calculated_num_parameters_) {
+ return;
+ }
+ have_calculated_num_parameters_ = true;
+
+ // The number of parameters will be used frequently when determining
+ // whether to call this method. We don't get the ID etc until actually
+ // required.
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jarray> parameters(env, static_cast<jarray>(
+ env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kGetParameterTypes,
+ kReturningJavaLangClassArray))));
+ num_parameters_ = env->GetArrayLength(parameters.obj());
+}
+
+void JavaMethod::EnsureTypesAndIDAreSetUp() const {
+ if (id_) {
+ return;
+ }
+
+ // Get the parameters
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> parameters(env, static_cast<jobjectArray>(
+ env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kGetParameterTypes,
+ kReturningJavaLangClassArray))));
+ // Usually, this will already have been called.
+ EnsureNumParametersIsSetUp();
+ DCHECK_EQ(num_parameters_,
+ static_cast<size_t>(env->GetArrayLength(parameters.obj())));
+
+ // Java gives us the argument type using an extended version of the 'binary
+ // name'. See
+ // http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#getName().
+ // If we build the signature now, there's no need to store the binary name
+ // of the arguments. We just store the simple type.
+ std::string signature("(");
+
+ // Form the signature and record the parameter types.
+ parameter_types_.resize(num_parameters_);
+ for (size_t i = 0; i < num_parameters_; ++i) {
+ ScopedJavaLocalRef<jobject> parameter(env, env->GetObjectArrayElement(
+ parameters.obj(), i));
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(parameter.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangClass,
+ kGetName,
+ kReturningJavaLangString))));
+ std::string name_utf8 = ConvertJavaStringToUTF8(name);
+ signature += BinaryNameToJNIName(name_utf8, &parameter_types_[i]);
+ }
+ signature += ")";
+
+ // Get the return type
+ ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
+ env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kGetReturnType,
+ kReturningJavaLangClass))));
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangClass,
+ kGetName,
+ kReturningJavaLangString))));
+ signature += BinaryNameToJNIName(ConvertJavaStringToUTF8(name),
+ &return_type_);
+
+ // Determine whether the method is static.
+ jint modifiers = env->CallIntMethod(
+ java_method_.obj(), GetMethodIDFromClassName(env,
+ kJavaLangReflectMethod,
+ kGetModifiers,
+ kReturningInteger));
+ bool is_static = env->CallStaticBooleanMethod(
+ g_java_lang_reflect_modifier_class.Get().obj(),
+ MethodID::Get<MethodID::TYPE_STATIC>(
+ env, g_java_lang_reflect_modifier_class.Get().obj(), kIsStatic,
+ kIntegerReturningBoolean),
+ modifiers);
+
+ // Get the ID for this method.
+ ScopedJavaLocalRef<jclass> declaring_class(env, static_cast<jclass>(
+ env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
+ env,
+ kJavaLangReflectMethod,
+ kGetDeclaringClass,
+ kReturningJavaLangClass))));
+ id_ = is_static ?
+ MethodID::Get<MethodID::TYPE_STATIC>(
+ env, declaring_class.obj(), name_.c_str(), signature.c_str()) :
+ MethodID::Get<MethodID::TYPE_INSTANCE>(
+ env, declaring_class.obj(), name_.c_str(), signature.c_str());
+ java_method_.Reset();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_method.h b/chromium/content/browser/renderer_host/java/java_method.h
new file mode 100644
index 00000000000..6356f70069d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_method.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_
+
+#include <jni.h>
+#include <string>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/renderer_host/java/java_type.h"
+
+namespace content {
+
+// Wrapper around java.lang.reflect.Method. This class must be used on a single
+// thread only.
+class JavaMethod {
+ public:
+ explicit JavaMethod(const base::android::JavaRef<jobject>& method);
+ ~JavaMethod();
+
+ const std::string& name() const { return name_; }
+ size_t num_parameters() const;
+ const JavaType& parameter_type(size_t index) const;
+ const JavaType& return_type() const;
+ jmethodID id() const;
+
+ private:
+ void EnsureNumParametersIsSetUp() const;
+ void EnsureTypesAndIDAreSetUp() const;
+
+ std::string name_;
+ mutable base::android::ScopedJavaGlobalRef<jobject> java_method_;
+ mutable bool have_calculated_num_parameters_;
+ mutable size_t num_parameters_;
+ mutable std::vector<JavaType> parameter_types_;
+ mutable JavaType return_type_;
+ mutable jmethodID id_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JavaMethod);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_
diff --git a/chromium/content/browser/renderer_host/java/java_type.cc b/chromium/content/browser/renderer_host/java/java_type.cc
new file mode 100644
index 00000000000..b590e7733c0
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_type.cc
@@ -0,0 +1,114 @@
+// 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 "content/browser/renderer_host/java/java_type.h"
+
+#include "base/logging.h"
+
+namespace content {
+namespace {
+
+JavaType JavaTypeFromJNIName(const std::string& jni_name) {
+ JavaType result;
+ DCHECK(!jni_name.empty());
+ switch (jni_name[0]) {
+ case 'Z':
+ result.type = JavaType::TypeBoolean;
+ break;
+ case 'B':
+ result.type = JavaType::TypeByte;
+ break;
+ case 'C':
+ result.type = JavaType::TypeChar;
+ break;
+ case 'S':
+ result.type = JavaType::TypeShort;
+ break;
+ case 'I':
+ result.type = JavaType::TypeInt;
+ break;
+ case 'J':
+ result.type = JavaType::TypeLong;
+ break;
+ case 'F':
+ result.type = JavaType::TypeFloat;
+ break;
+ case 'D':
+ result.type = JavaType::TypeDouble;
+ break;
+ case '[':
+ result.type = JavaType::TypeArray;
+ // LIVECONNECT_COMPLIANCE: We don't support multi-dimensional arrays, so
+ // there's no need to populate the inner types.
+ break;
+ case 'L':
+ result.type = jni_name == "Ljava.lang.String;" ?
+ JavaType::TypeString :
+ JavaType::TypeObject;
+ break;
+ default:
+ // Includes void (V).
+ NOTREACHED();
+ }
+ return result;
+}
+
+} // namespace
+
+JavaType::JavaType() {
+}
+
+JavaType::JavaType(const JavaType& other) {
+ *this = other;
+}
+
+JavaType::~JavaType() {
+}
+
+JavaType& JavaType::operator=(const JavaType& other) {
+ type = other.type;
+ if (other.inner_type) {
+ DCHECK_EQ(JavaType::TypeArray, type);
+ inner_type.reset(new JavaType(*other.inner_type));
+ } else {
+ inner_type.reset();
+ }
+ return *this;
+}
+
+JavaType JavaType::CreateFromBinaryName(const std::string& binary_name) {
+ JavaType result;
+ DCHECK(!binary_name.empty());
+ if (binary_name == "boolean") {
+ result.type = JavaType::TypeBoolean;
+ } else if (binary_name == "byte") {
+ result.type = JavaType::TypeByte;
+ } else if (binary_name == "char") {
+ result.type = JavaType::TypeChar;
+ } else if (binary_name == "short") {
+ result.type = JavaType::TypeShort;
+ } else if (binary_name == "int") {
+ result.type = JavaType::TypeInt;
+ } else if (binary_name == "long") {
+ result.type = JavaType::TypeLong;
+ } else if (binary_name == "float") {
+ result.type = JavaType::TypeFloat;
+ } else if (binary_name == "double") {
+ result.type = JavaType::TypeDouble;
+ } else if (binary_name == "void") {
+ result.type = JavaType::TypeVoid;
+ } else if (binary_name[0] == '[') {
+ result.type = JavaType::TypeArray;
+ // The inner type of an array is represented in JNI format.
+ result.inner_type.reset(new JavaType(JavaTypeFromJNIName(
+ binary_name.substr(1))));
+ } else if (binary_name == "java.lang.String") {
+ result.type = JavaType::TypeString;
+ } else {
+ result.type = JavaType::TypeObject;
+ }
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/java/java_type.h b/chromium/content/browser/renderer_host/java/java_type.h
new file mode 100644
index 00000000000..fe7845eeaa6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/java/java_type.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_TYPE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_TYPE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace content {
+
+// The type of a Java value. A light-weight enum-like structure intended for
+// use by value and in STL containers.
+struct JavaType {
+ JavaType();
+ JavaType(const JavaType& other);
+ ~JavaType();
+ JavaType& operator=(const JavaType& other);
+
+ // Java's reflection API represents types as a string using an extended
+ // 'binary name'.
+ static JavaType CreateFromBinaryName(const std::string& binary_name);
+
+ enum Type {
+ TypeBoolean,
+ TypeByte,
+ TypeChar,
+ TypeShort,
+ TypeInt,
+ TypeLong,
+ TypeFloat,
+ TypeDouble,
+ // This is only used as a return type, so we should never convert from
+ // JavaScript with this type.
+ TypeVoid,
+ TypeArray,
+ // We special-case strings, as they get special handling when coercing.
+ TypeString,
+ TypeObject,
+ };
+
+ Type type;
+ scoped_ptr<JavaType> inner_type; // Used for TypeArray only.
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_TYPE_H_
diff --git a/chromium/content/browser/renderer_host/media/DEPS b/chromium/content/browser/renderer_host/media/DEPS
new file mode 100644
index 00000000000..6f4140f1e51
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+media",
+
+ # TODO: this is temporary, this directory doesn't belong under renderer_host
+ # since it depends on web_contents.
+ "+content/browser/web_contents",
+ "+content/public/browser/web_contents.h",
+ "+content/public/browser/web_contents_view.h",
+]
diff --git a/chromium/content/browser/renderer_host/media/OWNERS b/chromium/content/browser/renderer_host/media/OWNERS
new file mode 100644
index 00000000000..45ac6611129
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/OWNERS
@@ -0,0 +1,26 @@
+acolwell@chromium.org
+dalecurtis@chromium.org
+ddorwin@chromium.org
+fischman@chromium.org
+scherkus@chromium.org
+shadi@chromium.org
+tommi@chromium.org
+vrk@chromium.org
+wjia@chromium.org
+xhwang@chromium.org
+xians@chromium.org
+
+# Tab capture OWNERS.
+per-file audio*=miu@chromium.org
+per-file web_contents*=hclam@chromium.org
+per-file web_contents*=justinlin@chromium.org
+per-file web_contents*=miu@chromium.org
+per-file web_contents*=nick@chromium.org
+per-file video_capture_oracle*=hclam@chromium.org
+per-file video_capture_oracle*=justinlin@chromium.org
+per-file video_capture_oracle*=miu@chromium.org
+per-file video_capture_oracle*=nick@chromium.org
+
+# Screen capture OWNERS.
+per-file desktop_capture_*=sergeyu@chromium.org
+per-file desktop_capture_*=wez@chromium.org
diff --git a/chromium/content/browser/renderer_host/media/audio_input_device_manager.cc b/chromium/content/browser/renderer_host/media/audio_input_device_manager.cc
new file mode 100644
index 00000000000..b4959567a31
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_device_manager.cc
@@ -0,0 +1,237 @@
+// 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 "content/browser/renderer_host/media/audio_input_device_manager.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/media_stream_request.h"
+#include "media/audio/audio_device_name.h"
+#include "media/audio/audio_input_ipc.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/channel_layout.h"
+#include "media/base/scoped_histogram_timer.h"
+
+namespace content {
+
+const int AudioInputDeviceManager::kFakeOpenSessionId = 1;
+
+namespace {
+// Starting id for the first capture session.
+const int kFirstSessionId = AudioInputDeviceManager::kFakeOpenSessionId + 1;
+}
+
+AudioInputDeviceManager::AudioInputDeviceManager(
+ media::AudioManager* audio_manager)
+ : listener_(NULL),
+ next_capture_session_id_(kFirstSessionId),
+ use_fake_device_(false),
+ audio_manager_(audio_manager) {
+ // TODO(xians): Remove this fake_device after the unittests do not need it.
+ StreamDeviceInfo fake_device(MEDIA_DEVICE_AUDIO_CAPTURE,
+ media::AudioManagerBase::kDefaultDeviceName,
+ media::AudioManagerBase::kDefaultDeviceId,
+ 44100, media::CHANNEL_LAYOUT_STEREO, false);
+ fake_device.session_id = kFakeOpenSessionId;
+ devices_.push_back(fake_device);
+}
+
+AudioInputDeviceManager::~AudioInputDeviceManager() {
+}
+
+const StreamDeviceInfo* AudioInputDeviceManager::GetOpenedDeviceInfoById(
+ int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ StreamDeviceList::iterator device = GetDevice(session_id);
+ if (device == devices_.end())
+ return NULL;
+
+ return &(*device);
+}
+
+void AudioInputDeviceManager::Register(
+ MediaStreamProviderListener* listener,
+ base::MessageLoopProxy* device_thread_loop) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!listener_);
+ DCHECK(!device_loop_.get());
+ listener_ = listener;
+ device_loop_ = device_thread_loop;
+}
+
+void AudioInputDeviceManager::Unregister() {
+ DCHECK(listener_);
+ listener_ = NULL;
+}
+
+void AudioInputDeviceManager::EnumerateDevices(MediaStreamType stream_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(listener_);
+
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&AudioInputDeviceManager::EnumerateOnDeviceThread,
+ this, stream_type));
+}
+
+int AudioInputDeviceManager::Open(const StreamDeviceInfo& device) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Generate a new id for this device.
+ int session_id = next_capture_session_id_++;
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&AudioInputDeviceManager::OpenOnDeviceThread,
+ this, session_id, device));
+
+ return session_id;
+}
+
+void AudioInputDeviceManager::Close(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(listener_);
+ StreamDeviceList::iterator device = GetDevice(session_id);
+ if (device == devices_.end())
+ return;
+ const MediaStreamType stream_type = device->device.type;
+ if (session_id != kFakeOpenSessionId)
+ devices_.erase(device);
+
+ // Post a callback through the listener on IO thread since
+ // MediaStreamManager is expecting the callback asynchronously.
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioInputDeviceManager::ClosedOnIOThread,
+ this, stream_type, session_id));
+}
+
+void AudioInputDeviceManager::UseFakeDevice() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ use_fake_device_ = true;
+}
+
+bool AudioInputDeviceManager::ShouldUseFakeDevice() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return use_fake_device_;
+}
+
+void AudioInputDeviceManager::EnumerateOnDeviceThread(
+ MediaStreamType stream_type) {
+ SCOPED_UMA_HISTOGRAM_TIMER(
+ "Media.AudioInputDeviceManager.EnumerateOnDeviceThreadTime");
+ DCHECK(IsOnDeviceThread());
+
+ media::AudioDeviceNames device_names;
+
+ switch (stream_type) {
+ case MEDIA_DEVICE_AUDIO_CAPTURE:
+ // AudioManager is guaranteed to outlive MediaStreamManager in
+ // BrowserMainloop.
+ audio_manager_->GetAudioInputDeviceNames(&device_names);
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
+ for (media::AudioDeviceNames::iterator it = device_names.begin();
+ it != device_names.end(); ++it) {
+ // Add device information to device vector.
+ devices->push_back(StreamDeviceInfo(
+ stream_type, it->device_name, it->unique_id, false));
+ }
+
+ // If the |use_fake_device_| flag is on, inject the fake device if there is
+ // no available device on the OS.
+ if (use_fake_device_ && devices->empty()) {
+ devices->push_back(StreamDeviceInfo(
+ stream_type, media::AudioManagerBase::kDefaultDeviceName,
+ media::AudioManagerBase::kDefaultDeviceId, false));
+ }
+
+ // Return the device list through the listener by posting a task on
+ // IO thread since MediaStreamManager handles the callback asynchronously.
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioInputDeviceManager::DevicesEnumeratedOnIOThread,
+ this, stream_type, base::Passed(&devices)));
+}
+
+void AudioInputDeviceManager::OpenOnDeviceThread(
+ int session_id, const StreamDeviceInfo& info) {
+ SCOPED_UMA_HISTOGRAM_TIMER(
+ "Media.AudioInputDeviceManager.OpenOnDeviceThreadTime");
+ DCHECK(IsOnDeviceThread());
+
+ StreamDeviceInfo out(info.device.type, info.device.name, info.device.id,
+ 0, 0, false);
+ out.session_id = session_id;
+ if (use_fake_device_) {
+ // Don't need to query the hardware information if using fake device.
+ out.device.sample_rate = 44100;
+ out.device.channel_layout = media::CHANNEL_LAYOUT_STEREO;
+ } else {
+ // Get the preferred sample rate and channel configuration for the
+ // audio device.
+ media::AudioParameters params =
+ audio_manager_->GetInputStreamParameters(info.device.id);
+ out.device.sample_rate = params.sample_rate();
+ out.device.channel_layout = params.channel_layout();
+ }
+
+ // Return the |session_id| through the listener by posting a task on
+ // IO thread since MediaStreamManager handles the callback asynchronously.
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioInputDeviceManager::OpenedOnIOThread,
+ this, session_id, out));
+}
+
+void AudioInputDeviceManager::DevicesEnumeratedOnIOThread(
+ MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Ensure that |devices| gets deleted on exit.
+ if (listener_)
+ listener_->DevicesEnumerated(stream_type, *devices);
+}
+
+void AudioInputDeviceManager::OpenedOnIOThread(int session_id,
+ const StreamDeviceInfo& info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(session_id, info.session_id);
+ DCHECK(GetDevice(session_id) == devices_.end());
+ devices_.push_back(info);
+
+ if (listener_)
+ listener_->Opened(info.device.type, session_id);
+}
+
+void AudioInputDeviceManager::ClosedOnIOThread(MediaStreamType stream_type,
+ int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (listener_)
+ listener_->Closed(stream_type, session_id);
+}
+
+bool AudioInputDeviceManager::IsOnDeviceThread() const {
+ return device_loop_->BelongsToCurrentThread();
+}
+
+AudioInputDeviceManager::StreamDeviceList::iterator
+AudioInputDeviceManager::GetDevice(int session_id) {
+ for (StreamDeviceList::iterator i(devices_.begin()); i != devices_.end();
+ ++i) {
+ if (i->session_id == session_id)
+ return i;
+ }
+
+ return devices_.end();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_input_device_manager.h b/chromium/content/browser/renderer_host/media/audio_input_device_manager.h
new file mode 100644
index 00000000000..133673f2a5f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_device_manager.h
@@ -0,0 +1,100 @@
+// 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.
+//
+// AudioInputDeviceManager manages the audio input devices. In particular it
+// communicates with MediaStreamManager and AudioInputRendererHost on the
+// browser IO thread, handles queries like
+// enumerate/open/close/GetOpenedDeviceInfoById from MediaStreamManager and
+// GetOpenedDeviceInfoById from AudioInputRendererHost.
+// The work for enumerate/open/close is handled asynchronously on Media Stream
+// device thread, while GetOpenedDeviceInfoById is synchronous on the IO thread.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_DEVICE_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_DEVICE_MANAGER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace media {
+class AudioManager;
+}
+
+namespace content {
+
+class CONTENT_EXPORT AudioInputDeviceManager : public MediaStreamProvider {
+ public:
+ // Calling Start() with this kFakeOpenSessionId will open the default device,
+ // even though Open() has not been called. This is used to be able to use the
+ // AudioInputDeviceManager before MediaStream is implemented.
+ // TODO(xians): Remove it when the webrtc unittest does not need it any more.
+ static const int kFakeOpenSessionId;
+
+ explicit AudioInputDeviceManager(media::AudioManager* audio_manager);
+
+ // Gets the opened device info by |session_id|. Returns NULL if the device
+ // is not opened, otherwise the opened device. Called on IO thread.
+ const StreamDeviceInfo* GetOpenedDeviceInfoById(int session_id);
+
+ // MediaStreamProvider implementation, called on IO thread.
+ virtual void Register(MediaStreamProviderListener* listener,
+ base::MessageLoopProxy* device_thread_loop) OVERRIDE;
+ virtual void Unregister() OVERRIDE;
+ virtual void EnumerateDevices(MediaStreamType stream_type) OVERRIDE;
+ virtual int Open(const StreamDeviceInfo& device) OVERRIDE;
+ virtual void Close(int session_id) OVERRIDE;
+
+ void UseFakeDevice();
+ bool ShouldUseFakeDevice() const;
+
+ private:
+ typedef std::vector<StreamDeviceInfo> StreamDeviceList;
+ virtual ~AudioInputDeviceManager();
+
+ // Enumerates audio input devices on media stream device thread.
+ void EnumerateOnDeviceThread(MediaStreamType stream_type);
+ // Opens the device on media stream device thread.
+ void OpenOnDeviceThread(int session_id, const StreamDeviceInfo& info);
+
+ // Callback used by EnumerateOnDeviceThread(), called with a list of
+ // enumerated devices on IO thread.
+ void DevicesEnumeratedOnIOThread(MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices);
+ // Callback used by OpenOnDeviceThread(), called with the session_id
+ // referencing the opened device on IO thread.
+ void OpenedOnIOThread(int session_id, const StreamDeviceInfo& info);
+ // Callback used by CloseOnDeviceThread(), called with the session_id
+ // referencing the closed device on IO thread.
+ void ClosedOnIOThread(MediaStreamType type, int session_id);
+
+ // Verifies that the calling thread is media stream device thread.
+ bool IsOnDeviceThread() const;
+
+ // Helper to return iterator to the device referenced by |session_id|. If no
+ // device is found, it will return devices_.end().
+ StreamDeviceList::iterator GetDevice(int session_id);
+
+ // Only accessed on Browser::IO thread.
+ MediaStreamProviderListener* listener_;
+ int next_capture_session_id_;
+ bool use_fake_device_;
+ StreamDeviceList devices_;
+
+ media::AudioManager* const audio_manager_; // Weak.
+
+ // The message loop of media stream device thread that this object runs on.
+ scoped_refptr<base::MessageLoopProxy> device_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioInputDeviceManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_DEVICE_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc b/chromium/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc
new file mode 100644
index 00000000000..03b31d2845e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc
@@ -0,0 +1,291 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/audio_input_device_manager.h"
+#include "content/public/common/media_stream_request.h"
+#include "media/audio/audio_manager_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::InSequence;
+using testing::SaveArg;
+using testing::Return;
+
+namespace content {
+
+class MockAudioInputDeviceManagerListener
+ : public MediaStreamProviderListener {
+ public:
+ MockAudioInputDeviceManagerListener() {}
+ virtual ~MockAudioInputDeviceManagerListener() {}
+
+ MOCK_METHOD2(Opened, void(MediaStreamType, const int));
+ MOCK_METHOD2(Closed, void(MediaStreamType, const int));
+ MOCK_METHOD2(DevicesEnumerated, void(MediaStreamType,
+ const StreamDeviceInfoArray&));
+ MOCK_METHOD3(Error, void(MediaStreamType, int, MediaStreamProviderError));
+
+ StreamDeviceInfoArray devices_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioInputDeviceManagerListener);
+};
+
+class AudioInputDeviceManagerTest : public testing::Test {
+ public:
+ AudioInputDeviceManagerTest() {}
+
+ // Returns true iff machine has an audio input device.
+ bool CanRunAudioInputDeviceTests() {
+ return audio_manager_->HasAudioInputDevices();
+ }
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ // The test must run on Browser::IO.
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+ io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
+ message_loop_.get()));
+ audio_manager_.reset(media::AudioManager::Create());
+ manager_ = new AudioInputDeviceManager(audio_manager_.get());
+ audio_input_listener_.reset(new MockAudioInputDeviceManagerListener());
+ manager_->Register(audio_input_listener_.get(),
+ message_loop_->message_loop_proxy().get());
+
+ // Gets the enumerated device list from the AudioInputDeviceManager.
+ manager_->EnumerateDevices(MEDIA_DEVICE_AUDIO_CAPTURE);
+ EXPECT_CALL(*audio_input_listener_,
+ DevicesEnumerated(MEDIA_DEVICE_AUDIO_CAPTURE, _))
+ .Times(1)
+ .WillOnce(SaveArg<1>(&devices_));
+
+ // Wait until we get the list.
+ message_loop_->RunUntilIdle();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ manager_->Unregister();
+ io_thread_.reset();
+ }
+
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<BrowserThreadImpl> io_thread_;
+ scoped_refptr<AudioInputDeviceManager> manager_;
+ scoped_ptr<MockAudioInputDeviceManagerListener> audio_input_listener_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+ StreamDeviceInfoArray devices_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AudioInputDeviceManagerTest);
+};
+
+// Opens and closes the devices.
+TEST_F(AudioInputDeviceManagerTest, OpenAndCloseDevice) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+
+ ASSERT_FALSE(devices_.empty());
+
+ InSequence s;
+
+ for (StreamDeviceInfoArray::const_iterator iter = devices_.begin();
+ iter != devices_.end(); ++iter) {
+ // Opens/closes the devices.
+ int session_id = manager_->Open(*iter);
+
+ // Expected mock call with expected return value.
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
+ .Times(1);
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+
+ manager_->Close(session_id);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
+ .Times(1);
+
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+ }
+}
+
+// Opens multiple devices at one time and closes them later.
+TEST_F(AudioInputDeviceManagerTest, OpenMultipleDevices) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+
+ ASSERT_FALSE(devices_.empty());
+
+ InSequence s;
+
+ int index = 0;
+ scoped_ptr<int[]> session_id(new int[devices_.size()]);
+
+ // Opens the devices in a loop.
+ for (StreamDeviceInfoArray::const_iterator iter = devices_.begin();
+ iter != devices_.end(); ++iter, ++index) {
+ // Opens the devices.
+ session_id[index] = manager_->Open(*iter);
+
+ // Expected mock call with expected returned value.
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id[index]))
+ .Times(1);
+
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+ }
+
+ // Checks if the session_ids are unique.
+ for (size_t i = 0; i < devices_.size() - 1; ++i) {
+ for (size_t k = i + 1; k < devices_.size(); ++k) {
+ EXPECT_TRUE(session_id[i] != session_id[k]);
+ }
+ }
+
+ for (size_t i = 0; i < devices_.size(); ++i) {
+ // Closes the devices.
+ manager_->Close(session_id[i]);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, session_id[i]))
+ .Times(1);
+
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+ }
+}
+
+// Opens a non-existing device.
+TEST_F(AudioInputDeviceManagerTest, OpenNotExistingDevice) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+ InSequence s;
+
+ MediaStreamType stream_type = MEDIA_DEVICE_AUDIO_CAPTURE;
+ std::string device_name("device_doesnt_exist");
+ std::string device_id("id_doesnt_exist");
+ int sample_rate(0);
+ int channel_config(0);
+ StreamDeviceInfo dummy_device(
+ stream_type, device_name, device_id, sample_rate, channel_config, false);
+
+ int session_id = manager_->Open(dummy_device);
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
+ .Times(1);
+
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+}
+
+// Opens default device twice.
+TEST_F(AudioInputDeviceManagerTest, OpenDeviceTwice) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+
+ ASSERT_FALSE(devices_.empty());
+
+ InSequence s;
+
+ // Opens and closes the default device twice.
+ int first_session_id = manager_->Open(devices_.front());
+ int second_session_id = manager_->Open(devices_.front());
+
+ // Expected mock calls with expected returned values.
+ EXPECT_NE(first_session_id, second_session_id);
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, first_session_id))
+ .Times(1);
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, second_session_id))
+ .Times(1);
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+
+ manager_->Close(first_session_id);
+ manager_->Close(second_session_id);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, first_session_id))
+ .Times(1);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, second_session_id))
+ .Times(1);
+ // Waits for the callback.
+ message_loop_->RunUntilIdle();
+}
+
+// Accesses then closes the sessions after opening the devices.
+TEST_F(AudioInputDeviceManagerTest, AccessAndCloseSession) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+
+ ASSERT_FALSE(devices_.empty());
+
+ InSequence s;
+
+ int index = 0;
+ scoped_ptr<int[]> session_id(new int[devices_.size()]);
+
+ // Loops through the devices and calls Open()/Close()/GetOpenedDeviceInfoById
+ // for each device.
+ for (StreamDeviceInfoArray::const_iterator iter = devices_.begin();
+ iter != devices_.end(); ++iter, ++index) {
+ // Note that no DeviceStopped() notification for Event Handler as we have
+ // stopped the device before calling close.
+ session_id[index] = manager_->Open(*iter);
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id[index]))
+ .Times(1);
+ message_loop_->RunUntilIdle();
+
+ const StreamDeviceInfo* info = manager_->GetOpenedDeviceInfoById(
+ session_id[index]);
+ DCHECK(info);
+ EXPECT_EQ(iter->device.id, info->device.id);
+ manager_->Close(session_id[index]);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, session_id[index]))
+ .Times(1);
+ message_loop_->RunUntilIdle();
+ }
+}
+
+// Access an invalid session.
+TEST_F(AudioInputDeviceManagerTest, AccessInvalidSession) {
+ if (!CanRunAudioInputDeviceTests())
+ return;
+ InSequence s;
+
+ // Opens the first device.
+ StreamDeviceInfoArray::const_iterator iter = devices_.begin();
+ int session_id = manager_->Open(*iter);
+ EXPECT_CALL(*audio_input_listener_,
+ Opened(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
+ .Times(1);
+ message_loop_->RunUntilIdle();
+
+ // Access a non-opened device.
+ // This should fail and return an empty StreamDeviceInfo.
+ int invalid_session_id = session_id + 1;
+ const StreamDeviceInfo* info =
+ manager_->GetOpenedDeviceInfoById(invalid_session_id);
+ DCHECK(!info);
+
+ manager_->Close(session_id);
+ EXPECT_CALL(*audio_input_listener_,
+ Closed(MEDIA_DEVICE_AUDIO_CAPTURE, session_id))
+ .Times(1);
+ message_loop_->RunUntilIdle();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_input_renderer_host.cc b/chromium/content/browser/renderer_host/media/audio_input_renderer_host.cc
new file mode 100644
index 00000000000..4a2f731a81c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_renderer_host.cc
@@ -0,0 +1,407 @@
+// 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 "content/browser/renderer_host/media/audio_input_renderer_host.h"
+
+#include "base/bind.h"
+#include "base/memory/shared_memory.h"
+#include "base/metrics/histogram.h"
+#include "base/process/process.h"
+#include "content/browser/renderer_host/media/audio_input_device_manager.h"
+#include "content/browser/renderer_host/media/audio_input_sync_writer.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/web_contents_audio_input_stream.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace content {
+
+struct AudioInputRendererHost::AudioEntry {
+ AudioEntry();
+ ~AudioEntry();
+
+ // The AudioInputController that manages the audio input stream.
+ scoped_refptr<media::AudioInputController> controller;
+
+ // The audio input stream ID in the render view.
+ int stream_id;
+
+ // Shared memory for transmission of the audio data. It has
+ // |shared_memory_segment_count| equal lengthed segments.
+ base::SharedMemory shared_memory;
+ int shared_memory_segment_count;
+
+ // The synchronous writer to be used by the controller. We have the
+ // ownership of the writer.
+ scoped_ptr<media::AudioInputController::SyncWriter> writer;
+
+ // Set to true after we called Close() for the controller.
+ bool pending_close;
+};
+
+AudioInputRendererHost::AudioEntry::AudioEntry()
+ : stream_id(0),
+ shared_memory_segment_count(0),
+ pending_close(false) {
+}
+
+AudioInputRendererHost::AudioEntry::~AudioEntry() {}
+
+AudioInputRendererHost::AudioInputRendererHost(
+ media::AudioManager* audio_manager,
+ MediaStreamManager* media_stream_manager,
+ AudioMirroringManager* audio_mirroring_manager)
+ : audio_manager_(audio_manager),
+ media_stream_manager_(media_stream_manager),
+ audio_mirroring_manager_(audio_mirroring_manager) {
+}
+
+AudioInputRendererHost::~AudioInputRendererHost() {
+ DCHECK(audio_entries_.empty());
+}
+
+void AudioInputRendererHost::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Since the IPC channel is gone, close all requested audio streams.
+ DeleteEntries();
+}
+
+void AudioInputRendererHost::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+void AudioInputRendererHost::OnCreated(
+ media::AudioInputController* controller) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &AudioInputRendererHost::DoCompleteCreation,
+ this,
+ make_scoped_refptr(controller)));
+}
+
+void AudioInputRendererHost::OnRecording(
+ media::AudioInputController* controller) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &AudioInputRendererHost::DoSendRecordingMessage,
+ this,
+ make_scoped_refptr(controller)));
+}
+
+void AudioInputRendererHost::OnError(media::AudioInputController* controller) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &AudioInputRendererHost::DoHandleError,
+ this,
+ make_scoped_refptr(controller)));
+}
+
+void AudioInputRendererHost::OnData(media::AudioInputController* controller,
+ const uint8* data,
+ uint32 size) {
+ NOTREACHED() << "Only low-latency mode is supported.";
+}
+
+void AudioInputRendererHost::DoCompleteCreation(
+ media::AudioInputController* controller) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupByController(controller);
+ if (!entry)
+ return;
+
+ if (!PeerHandle()) {
+ NOTREACHED() << "Renderer process handle is invalid.";
+ DeleteEntryOnError(entry);
+ return;
+ }
+
+ if (!entry->controller->LowLatencyMode()) {
+ NOTREACHED() << "Only low-latency mode is supported.";
+ DeleteEntryOnError(entry);
+ return;
+ }
+
+ // Once the audio stream is created then complete the creation process by
+ // mapping shared memory and sharing with the renderer process.
+ base::SharedMemoryHandle foreign_memory_handle;
+ if (!entry->shared_memory.ShareToProcess(PeerHandle(),
+ &foreign_memory_handle)) {
+ // If we failed to map and share the shared memory then close the audio
+ // stream and send an error message.
+ DeleteEntryOnError(entry);
+ return;
+ }
+
+ AudioInputSyncWriter* writer =
+ static_cast<AudioInputSyncWriter*>(entry->writer.get());
+
+#if defined(OS_WIN)
+ base::SyncSocket::Handle foreign_socket_handle;
+#else
+ base::FileDescriptor foreign_socket_handle;
+#endif
+
+ // If we failed to prepare the sync socket for the renderer then we fail
+ // the construction of audio input stream.
+ if (!writer->PrepareForeignSocketHandle(PeerHandle(),
+ &foreign_socket_handle)) {
+ DeleteEntryOnError(entry);
+ return;
+ }
+
+ Send(new AudioInputMsg_NotifyStreamCreated(entry->stream_id,
+ foreign_memory_handle, foreign_socket_handle,
+ entry->shared_memory.requested_size(),
+ entry->shared_memory_segment_count));
+}
+
+void AudioInputRendererHost::DoSendRecordingMessage(
+ media::AudioInputController* controller) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // TODO(henrika): See crbug.com/115262 for details on why this method
+ // should be implemented.
+}
+
+void AudioInputRendererHost::DoHandleError(
+ media::AudioInputController* controller) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupByController(controller);
+ if (!entry)
+ return;
+
+ DeleteEntryOnError(entry);
+}
+
+bool AudioInputRendererHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(AudioInputRendererHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(AudioInputHostMsg_CreateStream, OnCreateStream)
+ IPC_MESSAGE_HANDLER(AudioInputHostMsg_RecordStream, OnRecordStream)
+ IPC_MESSAGE_HANDLER(AudioInputHostMsg_CloseStream, OnCloseStream)
+ IPC_MESSAGE_HANDLER(AudioInputHostMsg_SetVolume, OnSetVolume)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void AudioInputRendererHost::OnCreateStream(
+ int stream_id,
+ int render_view_id,
+ int session_id,
+ const AudioInputHostMsg_CreateStream_Config& config) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DVLOG(1) << "AudioInputRendererHost@" << this
+ << "::OnCreateStream(stream_id=" << stream_id
+ << ", render_view_id=" << render_view_id
+ << ", session_id=" << session_id << ")";
+ DCHECK_GT(render_view_id, 0);
+
+ // media::AudioParameters is validated in the deserializer.
+ if (LookupById(stream_id) != NULL) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ media::AudioParameters audio_params(config.params);
+ if (media_stream_manager_->audio_input_device_manager()->
+ ShouldUseFakeDevice()) {
+ audio_params.Reset(
+ media::AudioParameters::AUDIO_FAKE,
+ config.params.channel_layout(), config.params.channels(), 0,
+ config.params.sample_rate(), config.params.bits_per_sample(),
+ config.params.frames_per_buffer());
+ }
+
+ // Check if we have the permission to open the device and which device to use.
+ std::string device_id = media::AudioManagerBase::kDefaultDeviceId;
+ if (audio_params.format() != media::AudioParameters::AUDIO_FAKE) {
+ const StreamDeviceInfo* info = media_stream_manager_->
+ audio_input_device_manager()->GetOpenedDeviceInfoById(session_id);
+ if (!info) {
+ SendErrorMessage(stream_id);
+ DLOG(WARNING) << "No permission has been granted to input stream with "
+ << "session_id=" << session_id;
+ return;
+ }
+
+ device_id = info->device.id;
+ }
+
+ // Create a new AudioEntry structure.
+ scoped_ptr<AudioEntry> entry(new AudioEntry());
+
+ const uint32 segment_size = (sizeof(media::AudioInputBufferParameters) +
+ audio_params.GetBytesPerBuffer());
+ entry->shared_memory_segment_count = config.shared_memory_count;
+
+ // Create the shared memory and share it with the renderer process
+ // using a new SyncWriter object.
+ if (!entry->shared_memory.CreateAndMapAnonymous(
+ segment_size * entry->shared_memory_segment_count)) {
+ // If creation of shared memory failed then send an error message.
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ scoped_ptr<AudioInputSyncWriter> writer(
+ new AudioInputSyncWriter(&entry->shared_memory,
+ entry->shared_memory_segment_count));
+
+ if (!writer->Init()) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ // If we have successfully created the SyncWriter then assign it to the
+ // entry and construct an AudioInputController.
+ entry->writer.reset(writer.release());
+ if (WebContentsCaptureUtil::IsWebContentsDeviceId(device_id)) {
+ entry->controller = media::AudioInputController::CreateForStream(
+ audio_manager_->GetMessageLoop(),
+ this,
+ WebContentsAudioInputStream::Create(
+ device_id, audio_params, audio_manager_->GetWorkerLoop(),
+ audio_mirroring_manager_),
+ entry->writer.get());
+ } else {
+ // TODO(henrika): replace CreateLowLatency() with Create() as soon
+ // as satish has ensured that Speech Input also uses the default low-
+ // latency path. See crbug.com/112472 for details.
+ entry->controller = media::AudioInputController::CreateLowLatency(
+ audio_manager_,
+ this,
+ audio_params,
+ device_id,
+ entry->writer.get());
+ }
+
+ if (!entry->controller.get()) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ // Set the initial AGC state for the audio input stream. Note that, the AGC
+ // is only supported in AUDIO_PCM_LOW_LATENCY mode.
+ if (config.params.format() == media::AudioParameters::AUDIO_PCM_LOW_LATENCY)
+ entry->controller->SetAutomaticGainControl(config.automatic_gain_control);
+
+ // Since the controller was created successfully, create an entry and add it
+ // to the map.
+ entry->stream_id = stream_id;
+ audio_entries_.insert(std::make_pair(stream_id, entry.release()));
+}
+
+void AudioInputRendererHost::OnRecordStream(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+ if (!entry) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ entry->controller->Record();
+}
+
+void AudioInputRendererHost::OnCloseStream(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+
+ if (entry)
+ CloseAndDeleteStream(entry);
+}
+
+void AudioInputRendererHost::OnSetVolume(int stream_id, double volume) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+ if (!entry) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ entry->controller->SetVolume(volume);
+}
+
+void AudioInputRendererHost::SendErrorMessage(int stream_id) {
+ Send(new AudioInputMsg_NotifyStreamStateChanged(
+ stream_id, media::AudioInputIPCDelegate::kError));
+}
+
+void AudioInputRendererHost::DeleteEntries() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (AudioEntryMap::iterator i = audio_entries_.begin();
+ i != audio_entries_.end(); ++i) {
+ CloseAndDeleteStream(i->second);
+ }
+}
+
+void AudioInputRendererHost::CloseAndDeleteStream(AudioEntry* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!entry->pending_close) {
+ entry->controller->Close(base::Bind(&AudioInputRendererHost::DeleteEntry,
+ this, entry));
+ entry->pending_close = true;
+ }
+}
+
+void AudioInputRendererHost::DeleteEntry(AudioEntry* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Delete the entry when this method goes out of scope.
+ scoped_ptr<AudioEntry> entry_deleter(entry);
+
+ // Erase the entry from the map.
+ audio_entries_.erase(entry->stream_id);
+}
+
+void AudioInputRendererHost::DeleteEntryOnError(AudioEntry* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Sends the error message first before we close the stream because
+ // |entry| is destroyed in DeleteEntry().
+ SendErrorMessage(entry->stream_id);
+ CloseAndDeleteStream(entry);
+}
+
+AudioInputRendererHost::AudioEntry* AudioInputRendererHost::LookupById(
+ int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntryMap::iterator i = audio_entries_.find(stream_id);
+ if (i != audio_entries_.end())
+ return i->second;
+ return NULL;
+}
+
+AudioInputRendererHost::AudioEntry* AudioInputRendererHost::LookupByController(
+ media::AudioInputController* controller) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Iterate the map of entries.
+ // TODO(hclam): Implement a faster look up method.
+ for (AudioEntryMap::iterator i = audio_entries_.begin();
+ i != audio_entries_.end(); ++i) {
+ if (controller == i->second->controller.get())
+ return i->second;
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_input_renderer_host.h b/chromium/content/browser/renderer_host/media/audio_input_renderer_host.h
new file mode 100644
index 00000000000..d16ebfad86a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_renderer_host.h
@@ -0,0 +1,162 @@
+// 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.
+//
+// AudioInputRendererHost serves audio related requests from audio capturer
+// which lives inside the render process and provide access to audio hardware.
+//
+// Create stream sequence (AudioInputController = AIC):
+//
+// AudioInputHostMsg_CreateStream -> OnCreateStream -> AIC::CreateLowLatency ->
+// <- AudioInputMsg_NotifyStreamCreated <- DoCompleteCreation <- OnCreated <-
+//
+// Close stream sequence:
+//
+// AudioInputHostMsg_CloseStream -> OnCloseStream -> AIC::Close ->
+//
+// This class is owned by BrowserRenderProcessHost and instantiated on UI
+// thread. All other operations and method calls happen on IO thread, so we
+// need to be extra careful about the lifetime of this object.
+//
+// To ensure low latency audio, a SyncSocket pair is used to signal buffer
+// readiness without having to route messages using the IO thread.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_RENDERER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_RENDERER_HOST_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/process/process.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/media/audio_messages.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/audio_input_controller.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/simple_sources.h"
+
+namespace media {
+class AudioManager;
+class AudioParameters;
+}
+
+namespace content {
+class AudioMirroringManager;
+class MediaStreamManager;
+
+class CONTENT_EXPORT AudioInputRendererHost
+ : public BrowserMessageFilter,
+ public media::AudioInputController::EventHandler {
+ public:
+ // Called from UI thread from the owner of this object.
+ AudioInputRendererHost(
+ media::AudioManager* audio_manager,
+ MediaStreamManager* media_stream_manager,
+ AudioMirroringManager* audio_mirroring_manager);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // AudioInputController::EventHandler implementation.
+ virtual void OnCreated(media::AudioInputController* controller) OVERRIDE;
+ virtual void OnRecording(media::AudioInputController* controller) OVERRIDE;
+ virtual void OnError(media::AudioInputController* controller) OVERRIDE;
+ virtual void OnData(media::AudioInputController* controller,
+ const uint8* data,
+ uint32 size) OVERRIDE;
+
+ private:
+ // TODO(henrika): extend test suite (compare AudioRenderHost)
+ friend class BrowserThread;
+ friend class base::DeleteHelper<AudioInputRendererHost>;
+
+ struct AudioEntry;
+ typedef std::map<int, AudioEntry*> AudioEntryMap;
+
+ virtual ~AudioInputRendererHost();
+
+ // Methods called on IO thread ----------------------------------------------
+
+ // Audio related IPC message handlers.
+
+ // Creates an audio input stream with the specified format whose data is
+ // consumed by an entity in the render view referenced by |render_view_id|.
+ // |session_id| is used to find out which device to be used for the stream.
+ // Upon success/failure, the peer is notified via the
+ // NotifyStreamCreated message.
+ void OnCreateStream(int stream_id,
+ int render_view_id,
+ int session_id,
+ const AudioInputHostMsg_CreateStream_Config& config);
+
+ // Record the audio input stream referenced by |stream_id|.
+ void OnRecordStream(int stream_id);
+
+ // Close the audio stream referenced by |stream_id|.
+ void OnCloseStream(int stream_id);
+
+ // Set the volume of the audio stream referenced by |stream_id|.
+ void OnSetVolume(int stream_id, double volume);
+
+ // Complete the process of creating an audio input stream. This will set up
+ // the shared memory or shared socket in low latency mode and send the
+ // NotifyStreamCreated message to the peer.
+ void DoCompleteCreation(media::AudioInputController* controller);
+
+ // Send a state change message to the renderer.
+ void DoSendRecordingMessage(media::AudioInputController* controller);
+
+ // Handle error coming from audio stream.
+ void DoHandleError(media::AudioInputController* controller);
+
+ // Send an error message to the renderer.
+ void SendErrorMessage(int stream_id);
+
+ // Delete all audio entry and all audio streams
+ void DeleteEntries();
+
+ // Closes the stream. The stream is then deleted in DeleteEntry() after it
+ // is closed.
+ void CloseAndDeleteStream(AudioEntry* entry);
+
+ // Delete an audio entry and close the related audio stream.
+ void DeleteEntry(AudioEntry* entry);
+
+ // Delete audio entry and close the related audio input stream.
+ void DeleteEntryOnError(AudioEntry* entry);
+
+ // A helper method to look up a AudioEntry identified by |stream_id|.
+ // Returns NULL if not found.
+ AudioEntry* LookupById(int stream_id);
+
+ // Search for a AudioEntry having the reference to |controller|.
+ // This method is used to look up an AudioEntry after a controller
+ // event is received.
+ AudioEntry* LookupByController(media::AudioInputController* controller);
+
+ // Used to create an AudioInputController.
+ media::AudioManager* audio_manager_;
+
+ // Used to access to AudioInputDeviceManager.
+ MediaStreamManager* media_stream_manager_;
+
+ AudioMirroringManager* audio_mirroring_manager_;
+
+ // A map of stream IDs to audio sources.
+ AudioEntryMap audio_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioInputRendererHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_RENDERER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/audio_input_sync_writer.cc b/chromium/content/browser/renderer_host/media/audio_input_sync_writer.cc
new file mode 100644
index 00000000000..572abf3e1c6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_sync_writer.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 "content/browser/renderer_host/media/audio_input_sync_writer.h"
+
+#include <algorithm>
+
+#include "base/memory/shared_memory.h"
+
+namespace content {
+
+AudioInputSyncWriter::AudioInputSyncWriter(
+ base::SharedMemory* shared_memory,
+ int shared_memory_segment_count)
+ : shared_memory_(shared_memory),
+ shared_memory_segment_count_(shared_memory_segment_count),
+ current_segment_id_(0) {
+ DCHECK_GT(shared_memory_segment_count, 0);
+ DCHECK_EQ(shared_memory->requested_size() % shared_memory_segment_count, 0u);
+ shared_memory_segment_size_ =
+ shared_memory->requested_size() / shared_memory_segment_count;
+}
+
+AudioInputSyncWriter::~AudioInputSyncWriter() {}
+
+// TODO(henrika): Combine into one method (including Write).
+void AudioInputSyncWriter::UpdateRecordedBytes(uint32 bytes) {
+ socket_->Send(&bytes, sizeof(bytes));
+}
+
+uint32 AudioInputSyncWriter::Write(
+ const void* data, uint32 size, double volume) {
+ uint8* ptr = static_cast<uint8*>(shared_memory_->memory());
+ ptr += current_segment_id_ * shared_memory_segment_size_;
+ media::AudioInputBuffer* buffer =
+ reinterpret_cast<media::AudioInputBuffer*>(ptr);
+ buffer->params.volume = volume;
+ buffer->params.size = size;
+ memcpy(buffer->audio, data, size);
+
+ if (++current_segment_id_ >= shared_memory_segment_count_)
+ current_segment_id_ = 0;
+
+ return size;
+}
+
+void AudioInputSyncWriter::Close() {
+ socket_->Close();
+}
+
+bool AudioInputSyncWriter::Init() {
+ socket_.reset(new base::CancelableSyncSocket());
+ foreign_socket_.reset(new base::CancelableSyncSocket());
+ return base::CancelableSyncSocket::CreatePair(socket_.get(),
+ foreign_socket_.get());
+}
+
+#if defined(OS_WIN)
+
+bool AudioInputSyncWriter::PrepareForeignSocketHandle(
+ base::ProcessHandle process_handle,
+ base::SyncSocket::Handle* foreign_handle) {
+ ::DuplicateHandle(GetCurrentProcess(), foreign_socket_->handle(),
+ process_handle, foreign_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ return (*foreign_handle != 0);
+}
+
+#else
+
+bool AudioInputSyncWriter::PrepareForeignSocketHandle(
+ base::ProcessHandle process_handle,
+ base::FileDescriptor* foreign_handle) {
+ foreign_handle->fd = foreign_socket_->handle();
+ foreign_handle->auto_close = false;
+ return (foreign_handle->fd != -1);
+}
+
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_input_sync_writer.h b/chromium/content/browser/renderer_host/media/audio_input_sync_writer.h
new file mode 100644
index 00000000000..4cfe9e3f396
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_input_sync_writer.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_SYNC_WRITER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_SYNC_WRITER_H_
+
+#include "base/file_descriptor_posix.h"
+#include "base/process/process.h"
+#include "base/sync_socket.h"
+#include "media/audio/audio_input_controller.h"
+
+namespace base {
+class SharedMemory;
+}
+
+namespace content {
+// A AudioInputController::SyncWriter implementation using SyncSocket. This
+// is used by AudioInputController to provide a low latency data source for
+// transmitting audio packets between the browser process and the renderer
+// process.
+class AudioInputSyncWriter : public media::AudioInputController::SyncWriter {
+ public:
+ explicit AudioInputSyncWriter(base::SharedMemory* shared_memory,
+ int shared_memory_segment_count);
+
+ virtual ~AudioInputSyncWriter();
+
+ // media::AudioOutputController::SyncWriter implementation.
+ virtual void UpdateRecordedBytes(uint32 bytes) OVERRIDE;
+ virtual uint32 Write(const void* data, uint32 size, double volume) OVERRIDE;
+ virtual void Close() OVERRIDE;
+
+ bool Init();
+ bool PrepareForeignSocketHandle(base::ProcessHandle process_handle,
+#if defined(OS_WIN)
+ base::SyncSocket::Handle* foreign_handle);
+#else
+ base::FileDescriptor* foreign_handle);
+#endif
+
+ private:
+ base::SharedMemory* shared_memory_;
+ uint32 shared_memory_segment_size_;
+ uint32 shared_memory_segment_count_;
+ uint32 current_segment_id_;
+
+ // Socket for transmitting audio data.
+ scoped_ptr<base::CancelableSyncSocket> socket_;
+
+ // Socket to be used by the renderer. The reference is released after
+ // PrepareForeignSocketHandle() is called and ran successfully.
+ scoped_ptr<base::CancelableSyncSocket> foreign_socket_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AudioInputSyncWriter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_INPUT_SYNC_WRITER_H_
diff --git a/chromium/content/browser/renderer_host/media/audio_mirroring_manager.cc b/chromium/content/browser/renderer_host/media/audio_mirroring_manager.cc
new file mode 100644
index 00000000000..8a2bc4b0b5c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_mirroring_manager.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/audio_mirroring_manager.h"
+
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+namespace {
+
+// Debug utility to make sure methods of AudioMirroringManager are not invoked
+// more than once in a single call stack. In release builds, this compiles to
+// nothing and gets completely optimized out.
+class ReentrancyGuard {
+ public:
+#ifdef NDEBUG
+ ReentrancyGuard() {}
+ ~ReentrancyGuard() {}
+#else
+ ReentrancyGuard() {
+ DCHECK(!inside_a_method_);
+ inside_a_method_ = true;
+ }
+ ~ReentrancyGuard() {
+ inside_a_method_ = false;
+ }
+
+ static bool inside_a_method_; // Safe to be static, since AMM is a singleton.
+#endif
+};
+
+#ifndef NDEBUG
+bool ReentrancyGuard::inside_a_method_ = false;
+#endif
+
+} // namespace
+
+AudioMirroringManager::AudioMirroringManager() {}
+
+AudioMirroringManager::~AudioMirroringManager() {
+ DCHECK(diverters_.empty());
+ DCHECK(sessions_.empty());
+}
+
+void AudioMirroringManager::AddDiverter(
+ int render_process_id, int render_view_id, Diverter* diverter) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ReentrancyGuard guard;
+ DCHECK(diverter);
+
+ // DCHECK(diverter not already in diverters_ under any key)
+#ifndef NDEBUG
+ for (DiverterMap::const_iterator it = diverters_.begin();
+ it != diverters_.end(); ++it) {
+ DCHECK_NE(diverter, it->second);
+ }
+#endif
+
+ // Add the diverter to the set of active diverters.
+ const Target target(render_process_id, render_view_id);
+ diverters_.insert(std::make_pair(target, diverter));
+
+ // If a mirroring session is active, start diverting the audio stream
+ // immediately.
+ SessionMap::iterator session_it = sessions_.find(target);
+ if (session_it != sessions_.end()) {
+ diverter->StartDiverting(
+ session_it->second->AddInput(diverter->GetAudioParameters()));
+ }
+}
+
+void AudioMirroringManager::RemoveDiverter(
+ int render_process_id, int render_view_id, Diverter* diverter) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ReentrancyGuard guard;
+
+ // Stop diverting the audio stream if a mirroring session is active.
+ const Target target(render_process_id, render_view_id);
+ SessionMap::iterator session_it = sessions_.find(target);
+ if (session_it != sessions_.end())
+ diverter->StopDiverting();
+
+ // Remove the diverter from the set of active diverters.
+ for (DiverterMap::iterator it = diverters_.lower_bound(target);
+ it != diverters_.end() && it->first == target; ++it) {
+ if (it->second == diverter) {
+ diverters_.erase(it);
+ break;
+ }
+ }
+}
+
+void AudioMirroringManager::StartMirroring(
+ int render_process_id, int render_view_id,
+ MirroringDestination* destination) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ReentrancyGuard guard;
+ DCHECK(destination);
+
+ // Insert an entry into the set of active mirroring sessions. If a mirroring
+ // session is already active for |render_process_id| + |render_view_id|,
+ // replace the entry.
+ const Target target(render_process_id, render_view_id);
+ SessionMap::iterator session_it = sessions_.find(target);
+ MirroringDestination* old_destination;
+ if (session_it == sessions_.end()) {
+ old_destination = NULL;
+ sessions_.insert(std::make_pair(target, destination));
+
+ DVLOG(1) << "Start mirroring render_process_id:render_view_id="
+ << render_process_id << ':' << render_view_id
+ << " --> MirroringDestination@" << destination;
+ } else {
+ old_destination = session_it->second;
+ session_it->second = destination;
+
+ DVLOG(1) << "Switch mirroring of render_process_id:render_view_id="
+ << render_process_id << ':' << render_view_id
+ << " MirroringDestination@" << old_destination
+ << " --> MirroringDestination@" << destination;
+ }
+
+ // Divert audio streams coming from |target| to |destination|. If streams
+ // were already diverted to the |old_destination|, remove them.
+ for (DiverterMap::iterator it = diverters_.lower_bound(target);
+ it != diverters_.end() && it->first == target; ++it) {
+ Diverter* const diverter = it->second;
+ if (old_destination)
+ diverter->StopDiverting();
+ diverter->StartDiverting(
+ destination->AddInput(diverter->GetAudioParameters()));
+ }
+}
+
+void AudioMirroringManager::StopMirroring(
+ int render_process_id, int render_view_id,
+ MirroringDestination* destination) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ReentrancyGuard guard;
+
+ // Stop mirroring if there is an active session *and* the destination
+ // matches.
+ const Target target(render_process_id, render_view_id);
+ SessionMap::iterator session_it = sessions_.find(target);
+ if (session_it == sessions_.end() || destination != session_it->second)
+ return;
+
+ DVLOG(1) << "Stop mirroring render_process_id:render_view_id="
+ << render_process_id << ':' << render_view_id
+ << " --> MirroringDestination@" << destination;
+
+ // Stop diverting each audio stream in the mirroring session being stopped.
+ for (DiverterMap::iterator it = diverters_.lower_bound(target);
+ it != diverters_.end() && it->first == target; ++it) {
+ it->second->StopDiverting();
+ }
+
+ // Remove the entry from the set of active mirroring sessions.
+ sessions_.erase(session_it);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_mirroring_manager.h b/chromium/content/browser/renderer_host/media/audio_mirroring_manager.h
new file mode 100644
index 00000000000..0db4f17539f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_mirroring_manager.h
@@ -0,0 +1,108 @@
+// Copyright (c) 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.
+//
+// AudioMirroringManager is a singleton object that maintains a set of active
+// audio mirroring destinations and auto-connects/disconnects audio streams
+// to/from those destinations. It is meant to be used exclusively on the IO
+// BrowserThread.
+//
+// How it works:
+//
+// 1. AudioRendererHost gets a CreateStream message from the render process
+// and, among other things, creates an AudioOutputController to control the
+// audio data flow between the render and browser processes.
+// 2. At some point, AudioRendererHost receives an "associate with render
+// view" message. Among other actions, it registers the
+// AudioOutputController with AudioMirroringManager (as a Diverter).
+// 3. A user request to mirror all the audio for a single RenderView is made.
+// A MirroringDestination is created, and StartMirroring() is called to
+// begin the mirroring session. This causes AudioMirroringManager to
+// instruct any matching Diverters to divert their audio data to the
+// MirroringDestination.
+//
+// #2 and #3 above may occur in any order, as it is the job of
+// AudioMirroringManager to realize when the players can be "matched up."
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_
+
+#include <map>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "media/audio/audio_source_diverter.h"
+
+namespace media {
+class AudioOutputStream;
+}
+
+namespace content {
+
+class CONTENT_EXPORT AudioMirroringManager {
+ public:
+ // Interface for diverting audio data to an alternative AudioOutputStream.
+ typedef media::AudioSourceDiverter Diverter;
+
+ // Interface to be implemented by audio mirroring destinations. See comments
+ // for StartMirroring() and StopMirroring() below.
+ class MirroringDestination {
+ public:
+ // Create a consumer of audio data in the format specified by |params|, and
+ // connect it as an input to mirroring. When Close() is called on the
+ // returned AudioOutputStream, the input is disconnected and the object
+ // becomes invalid.
+ virtual media::AudioOutputStream* AddInput(
+ const media::AudioParameters& params) = 0;
+
+ protected:
+ virtual ~MirroringDestination() {}
+ };
+
+ AudioMirroringManager();
+
+ virtual ~AudioMirroringManager();
+
+ // Add/Remove a diverter for an audio stream with a known RenderView target
+ // (represented by |render_process_id| + |render_view_id|). Multiple
+ // diverters may be added for the same target. |diverter| must live until
+ // after RemoveDiverter() is called.
+ //
+ // Re-entrancy warning: These methods should not be called by a Diverter
+ // during a Start/StopDiverting() invocation.
+ virtual void AddDiverter(int render_process_id, int render_view_id,
+ Diverter* diverter);
+ virtual void RemoveDiverter(int render_process_id, int render_view_id,
+ Diverter* diverter);
+
+ // Start/stop mirroring all audio output streams associated with a RenderView
+ // target (represented by |render_process_id| + |render_view_id|) to
+ // |destination|. |destination| must live until after StopMirroring() is
+ // called.
+ virtual void StartMirroring(int render_process_id, int render_view_id,
+ MirroringDestination* destination);
+ virtual void StopMirroring(int render_process_id, int render_view_id,
+ MirroringDestination* destination);
+
+ private:
+ // A mirroring target is a RenderView identified by a
+ // <render_process_id, render_view_id> pair.
+ typedef std::pair<int, int> Target;
+
+ // Note: Objects in these maps are not owned.
+ typedef std::multimap<Target, Diverter*> DiverterMap;
+ typedef std::map<Target, MirroringDestination*> SessionMap;
+
+ // Currently-active divertable audio streams.
+ DiverterMap diverters_;
+
+ // Currently-active mirroring sessions.
+ SessionMap sessions_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioMirroringManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc b/chromium/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc
new file mode 100644
index 00000000000..2468b2c48c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/audio_mirroring_manager.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/browser/browser_thread_impl.h"
+#include "media/audio/audio_parameters.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using media::AudioOutputStream;
+using media::AudioParameters;
+using testing::_;
+using testing::NotNull;
+using testing::Ref;
+using testing::Return;
+using testing::ReturnRef;
+
+namespace content {
+
+namespace {
+
+class MockDiverter : public AudioMirroringManager::Diverter {
+ public:
+ MOCK_METHOD0(GetAudioParameters, const AudioParameters&());
+ MOCK_METHOD1(StartDiverting, void(AudioOutputStream*));
+ MOCK_METHOD0(StopDiverting, void());
+};
+
+class MockMirroringDestination
+ : public AudioMirroringManager::MirroringDestination {
+ public:
+ MOCK_METHOD1(AddInput,
+ media::AudioOutputStream*(const media::AudioParameters& params));
+};
+
+} // namespace
+
+class AudioMirroringManagerTest : public testing::Test {
+ public:
+ AudioMirroringManagerTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ io_thread_(BrowserThread::IO, &message_loop_),
+ params_(AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO,
+ AudioParameters::kAudioCDSampleRate, 16,
+ AudioParameters::kAudioCDSampleRate / 10) {}
+
+ MockDiverter* CreateStream(
+ int render_process_id, int render_view_id, int expected_times_diverted) {
+ MockDiverter* const diverter = new MockDiverter();
+ if (expected_times_diverted > 0) {
+ EXPECT_CALL(*diverter, GetAudioParameters())
+ .Times(expected_times_diverted)
+ .WillRepeatedly(ReturnRef(params_));
+ EXPECT_CALL(*diverter, StartDiverting(NotNull()))
+ .Times(expected_times_diverted);
+ EXPECT_CALL(*diverter, StopDiverting())
+ .Times(expected_times_diverted);
+ }
+
+ mirroring_manager_.AddDiverter(render_process_id, render_view_id, diverter);
+
+ return diverter;
+ }
+
+ void KillStream(
+ int render_process_id, int render_view_id, MockDiverter* diverter) {
+ mirroring_manager_.RemoveDiverter(
+ render_process_id, render_view_id, diverter);
+
+ delete diverter;
+ }
+
+ MockMirroringDestination* StartMirroringTo(
+ int render_process_id, int render_view_id, int expected_inputs_added) {
+ MockMirroringDestination* const dest = new MockMirroringDestination();
+ if (expected_inputs_added > 0) {
+ static AudioOutputStream* const kNonNullPointer =
+ reinterpret_cast<AudioOutputStream*>(0x11111110);
+ EXPECT_CALL(*dest, AddInput(Ref(params_)))
+ .Times(expected_inputs_added)
+ .WillRepeatedly(Return(kNonNullPointer));
+ }
+
+ mirroring_manager_.StartMirroring(render_process_id, render_view_id, dest);
+
+ return dest;
+ }
+
+ void StopMirroringTo(int render_process_id, int render_view_id,
+ MockMirroringDestination* dest) {
+ mirroring_manager_.StopMirroring(render_process_id, render_view_id, dest);
+
+ delete dest;
+}
+
+ private:
+ base::MessageLoop message_loop_;
+ BrowserThreadImpl io_thread_;
+ AudioParameters params_;
+ AudioMirroringManager mirroring_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioMirroringManagerTest);
+};
+
+namespace {
+const int kRenderProcessId = 123;
+const int kRenderViewId = 456;
+const int kAnotherRenderProcessId = 789;
+const int kAnotherRenderViewId = 1234;
+const int kYetAnotherRenderProcessId = 4560;
+const int kYetAnotherRenderViewId = 7890;
+}
+
+TEST_F(AudioMirroringManagerTest, MirroringSessionOfNothing) {
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 0);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+}
+
+TEST_F(AudioMirroringManagerTest, TwoMirroringSessionsOfNothing) {
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 0);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+
+ MockMirroringDestination* const another_destination =
+ StartMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, 0);
+ StopMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId,
+ another_destination);
+}
+
+TEST_F(AudioMirroringManagerTest, SwitchMirroringDestinationNoStreams) {
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 0);
+ MockMirroringDestination* const new_destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 0);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination);
+}
+
+TEST_F(AudioMirroringManagerTest, StreamLifetimeAroundMirroringSession) {
+ MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 1);
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+ KillStream(kRenderProcessId, kRenderViewId, stream);
+}
+
+TEST_F(AudioMirroringManagerTest, StreamLifetimeWithinMirroringSession) {
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 1);
+ KillStream(kRenderProcessId, kRenderViewId, stream);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+}
+
+TEST_F(AudioMirroringManagerTest, StreamLifetimeAroundTwoMirroringSessions) {
+ MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 2);
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+ MockMirroringDestination* const new_destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination);
+ KillStream(kRenderProcessId, kRenderViewId, stream);
+}
+
+TEST_F(AudioMirroringManagerTest, StreamLifetimeWithinTwoMirroringSessions) {
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 2);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+ MockMirroringDestination* const new_destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+ KillStream(kRenderProcessId, kRenderViewId, stream);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination);
+}
+
+TEST_F(AudioMirroringManagerTest, MultipleStreamsInOneMirroringSession) {
+ MockDiverter* const stream1 =
+ CreateStream(kRenderProcessId, kRenderViewId, 1);
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 3);
+ MockDiverter* const stream2 =
+ CreateStream(kRenderProcessId, kRenderViewId, 1);
+ MockDiverter* const stream3 =
+ CreateStream(kRenderProcessId, kRenderViewId, 1);
+ KillStream(kRenderProcessId, kRenderViewId, stream2);
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+ KillStream(kRenderProcessId, kRenderViewId, stream3);
+ KillStream(kRenderProcessId, kRenderViewId, stream1);
+}
+
+// A random interleaving of operations for three separate targets, each of which
+// has one stream mirrored to one destination.
+TEST_F(AudioMirroringManagerTest, ThreeSeparateMirroringSessions) {
+ MockDiverter* const stream =
+ CreateStream(kRenderProcessId, kRenderViewId, 1);
+ MockMirroringDestination* const destination =
+ StartMirroringTo(kRenderProcessId, kRenderViewId, 1);
+
+ MockMirroringDestination* const another_destination =
+ StartMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, 1);
+ MockDiverter* const another_stream =
+ CreateStream(kAnotherRenderProcessId, kAnotherRenderViewId, 1);
+
+ KillStream(kRenderProcessId, kRenderViewId, stream);
+
+ MockDiverter* const yet_another_stream =
+ CreateStream(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, 1);
+ MockMirroringDestination* const yet_another_destination =
+ StartMirroringTo(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, 1);
+
+ StopMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId,
+ another_destination);
+
+ StopMirroringTo(kYetAnotherRenderProcessId, kYetAnotherRenderViewId,
+ yet_another_destination);
+
+ StopMirroringTo(kRenderProcessId, kRenderViewId, destination);
+
+ KillStream(kAnotherRenderProcessId, kAnotherRenderViewId, another_stream);
+ KillStream(kYetAnotherRenderProcessId, kYetAnotherRenderViewId,
+ yet_another_stream);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_renderer_host.cc b/chromium/content/browser/renderer_host/media/audio_renderer_host.cc
new file mode 100644
index 00000000000..53f2eb2ae90
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_renderer_host.cc
@@ -0,0 +1,477 @@
+// 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 "content/browser/renderer_host/media/audio_renderer_host.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/memory/shared_memory.h"
+#include "base/metrics/histogram.h"
+#include "base/process/process.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/media/media_internals.h"
+#include "content/browser/renderer_host/media/audio_input_device_manager.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/audio_sync_reader.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/common/media/audio_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/media_observer.h"
+#include "content/public/common/content_switches.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/shared_memory_util.h"
+#include "media/base/audio_bus.h"
+#include "media/base/limits.h"
+
+using media::AudioBus;
+
+namespace content {
+
+class AudioRendererHost::AudioEntry
+ : public media::AudioOutputController::EventHandler {
+ public:
+ AudioEntry(AudioRendererHost* host,
+ int stream_id,
+ int render_view_id,
+ const media::AudioParameters& params,
+ const std::string& input_device_id,
+ scoped_ptr<base::SharedMemory> shared_memory,
+ scoped_ptr<media::AudioOutputController::SyncReader> reader);
+ virtual ~AudioEntry();
+
+ int stream_id() const {
+ return stream_id_;
+ }
+
+ int render_view_id() const {
+ return render_view_id_;
+ }
+
+ media::AudioOutputController* controller() const { return controller_.get(); }
+
+ base::SharedMemory* shared_memory() {
+ return shared_memory_.get();
+ }
+
+ media::AudioOutputController::SyncReader* reader() const {
+ return reader_.get();
+ }
+
+ private:
+ // media::AudioOutputController::EventHandler implementation.
+ virtual void OnCreated() OVERRIDE;
+ virtual void OnPlaying() OVERRIDE;
+ virtual void OnPowerMeasured(float power_dbfs, bool clipped) OVERRIDE;
+ virtual void OnPaused() OVERRIDE;
+ virtual void OnError() OVERRIDE;
+ virtual void OnDeviceChange(int new_buffer_size, int new_sample_rate)
+ OVERRIDE;
+
+ AudioRendererHost* const host_;
+ const int stream_id_;
+
+ // The routing ID of the source render view.
+ const int render_view_id_;
+
+ // The AudioOutputController that manages the audio stream.
+ const scoped_refptr<media::AudioOutputController> controller_;
+
+ // Shared memory for transmission of the audio data.
+ const scoped_ptr<base::SharedMemory> shared_memory_;
+
+ // The synchronous reader to be used by the controller.
+ const scoped_ptr<media::AudioOutputController::SyncReader> reader_;
+};
+
+AudioRendererHost::AudioEntry::AudioEntry(
+ AudioRendererHost* host, int stream_id, int render_view_id,
+ const media::AudioParameters& params,
+ const std::string& input_device_id,
+ scoped_ptr<base::SharedMemory> shared_memory,
+ scoped_ptr<media::AudioOutputController::SyncReader> reader)
+ : host_(host),
+ stream_id_(stream_id),
+ render_view_id_(render_view_id),
+ controller_(media::AudioOutputController::Create(
+ host->audio_manager_, this, params, input_device_id, reader.get())),
+ shared_memory_(shared_memory.Pass()),
+ reader_(reader.Pass()) {
+ DCHECK(controller_.get());
+}
+
+AudioRendererHost::AudioEntry::~AudioEntry() {}
+
+///////////////////////////////////////////////////////////////////////////////
+// AudioRendererHost implementations.
+AudioRendererHost::AudioRendererHost(
+ int render_process_id,
+ media::AudioManager* audio_manager,
+ AudioMirroringManager* mirroring_manager,
+ MediaInternals* media_internals,
+ MediaStreamManager* media_stream_manager)
+ : render_process_id_(render_process_id),
+ audio_manager_(audio_manager),
+ mirroring_manager_(mirroring_manager),
+ media_internals_(media_internals),
+ media_stream_manager_(media_stream_manager) {
+ DCHECK(audio_manager_);
+ DCHECK(media_stream_manager_);
+}
+
+AudioRendererHost::~AudioRendererHost() {
+ DCHECK(audio_entries_.empty());
+}
+
+void AudioRendererHost::OnChannelClosing() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Since the IPC channel is gone, close all requested audio streams.
+ while (!audio_entries_.empty()) {
+ // Note: OnCloseStream() removes the entries from audio_entries_.
+ OnCloseStream(audio_entries_.begin()->first);
+ }
+}
+
+void AudioRendererHost::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+void AudioRendererHost::AudioEntry::OnCreated() {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioRendererHost::DoCompleteCreation, host_, stream_id_));
+}
+
+void AudioRendererHost::AudioEntry::OnPlaying() {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ base::IgnoreResult(&AudioRendererHost::Send), host_,
+ new AudioMsg_NotifyStreamStateChanged(
+ stream_id_, media::AudioOutputIPCDelegate::kPlaying)));
+}
+
+void AudioRendererHost::AudioEntry::OnPowerMeasured(float power_dbfs,
+ bool clipped) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioRendererHost::DoNotifyAudioPowerLevel, host_,
+ stream_id_, power_dbfs, clipped));
+}
+
+void AudioRendererHost::AudioEntry::OnPaused() {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ base::IgnoreResult(&AudioRendererHost::Send), host_,
+ new AudioMsg_NotifyStreamStateChanged(
+ stream_id_, media::AudioOutputIPCDelegate::kPaused)));
+}
+
+void AudioRendererHost::AudioEntry::OnError() {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioRendererHost::ReportErrorAndClose, host_, stream_id_));
+}
+
+void AudioRendererHost::AudioEntry::OnDeviceChange(int new_buffer_size,
+ int new_sample_rate) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&AudioRendererHost::Send), host_,
+ new AudioMsg_NotifyDeviceChanged(
+ stream_id_, new_buffer_size, new_sample_rate)));
+}
+
+void AudioRendererHost::DoCompleteCreation(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!PeerHandle()) {
+ NOTREACHED() << "Renderer process handle is invalid.";
+ ReportErrorAndClose(stream_id);
+ return;
+ }
+
+ AudioEntry* const entry = LookupById(stream_id);
+ if (!entry) {
+ ReportErrorAndClose(stream_id);
+ return;
+ }
+
+ // Once the audio stream is created then complete the creation process by
+ // mapping shared memory and sharing with the renderer process.
+ base::SharedMemoryHandle foreign_memory_handle;
+ if (!entry->shared_memory()->ShareToProcess(PeerHandle(),
+ &foreign_memory_handle)) {
+ // If we failed to map and share the shared memory then close the audio
+ // stream and send an error message.
+ ReportErrorAndClose(entry->stream_id());
+ return;
+ }
+
+ AudioSyncReader* reader = static_cast<AudioSyncReader*>(entry->reader());
+
+#if defined(OS_WIN)
+ base::SyncSocket::Handle foreign_socket_handle;
+#else
+ base::FileDescriptor foreign_socket_handle;
+#endif
+
+ // If we failed to prepare the sync socket for the renderer then we fail
+ // the construction of audio stream.
+ if (!reader->PrepareForeignSocketHandle(PeerHandle(),
+ &foreign_socket_handle)) {
+ ReportErrorAndClose(entry->stream_id());
+ return;
+ }
+
+ Send(new AudioMsg_NotifyStreamCreated(
+ entry->stream_id(),
+ foreign_memory_handle,
+ foreign_socket_handle,
+ media::PacketSizeInBytes(entry->shared_memory()->requested_size())));
+}
+
+void AudioRendererHost::DoNotifyAudioPowerLevel(int stream_id,
+ float power_dbfs,
+ bool clipped) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ MediaObserver* const media_observer =
+ GetContentClient()->browser()->GetMediaObserver();
+ if (media_observer) {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAudibleNotifications)) {
+ AudioEntry* const entry = LookupById(stream_id);
+ if (entry) {
+ media_observer->OnAudioStreamPlayingChanged(
+ render_process_id_, entry->render_view_id(), entry->stream_id(),
+ true, power_dbfs, clipped);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// IPC Messages handler
+bool AudioRendererHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(AudioHostMsg_CreateStream, OnCreateStream)
+ IPC_MESSAGE_HANDLER(AudioHostMsg_PlayStream, OnPlayStream)
+ IPC_MESSAGE_HANDLER(AudioHostMsg_PauseStream, OnPauseStream)
+ IPC_MESSAGE_HANDLER(AudioHostMsg_CloseStream, OnCloseStream)
+ IPC_MESSAGE_HANDLER(AudioHostMsg_SetVolume, OnSetVolume)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void AudioRendererHost::OnCreateStream(
+ int stream_id, int render_view_id, int session_id,
+ const media::AudioParameters& params) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DVLOG(1) << "AudioRendererHost@" << this
+ << "::OnCreateStream(stream_id=" << stream_id
+ << ", render_view_id=" << render_view_id
+ << ", session_id=" << session_id << ")";
+ DCHECK_GT(render_view_id, 0);
+
+ // media::AudioParameters is validated in the deserializer.
+ int input_channels = params.input_channels();
+ if (input_channels < 0 ||
+ input_channels > media::limits::kMaxChannels ||
+ LookupById(stream_id) != NULL) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ // When the |input_channels| is valid, clients are trying to create a unified
+ // IO stream which opens an input device mapping to the |session_id|.
+ std::string input_device_id;
+ if (input_channels > 0) {
+ const StreamDeviceInfo* info = media_stream_manager_->
+ audio_input_device_manager()->GetOpenedDeviceInfoById(session_id);
+ if (!info) {
+ SendErrorMessage(stream_id);
+ DLOG(WARNING) << "No permission has been granted to input stream with "
+ << "session_id=" << session_id;
+ return;
+ }
+
+ input_device_id = info->device.id;
+ }
+
+ // Calculate output and input memory size.
+ int output_memory_size = AudioBus::CalculateMemorySize(params);
+ int frames = params.frames_per_buffer();
+ int input_memory_size =
+ AudioBus::CalculateMemorySize(input_channels, frames);
+
+ // Create the shared memory and share with the renderer process.
+ // For synchronized I/O (if input_channels > 0) then we allocate
+ // extra memory after the output data for the input data.
+ uint32 io_buffer_size = output_memory_size + input_memory_size;
+ uint32 shared_memory_size =
+ media::TotalSharedMemorySizeInBytes(io_buffer_size);
+ scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
+ if (!shared_memory->CreateAndMapAnonymous(shared_memory_size)) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ scoped_ptr<AudioSyncReader> reader(
+ new AudioSyncReader(shared_memory.get(), params, input_channels));
+ if (!reader->Init()) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ MediaObserver* const media_observer =
+ GetContentClient()->browser()->GetMediaObserver();
+ if (media_observer)
+ media_observer->OnCreatingAudioStream(render_process_id_, render_view_id);
+
+ scoped_ptr<AudioEntry> entry(new AudioEntry(
+ this, stream_id, render_view_id, params, input_device_id,
+ shared_memory.Pass(),
+ reader.PassAs<media::AudioOutputController::SyncReader>()));
+ if (mirroring_manager_) {
+ mirroring_manager_->AddDiverter(
+ render_process_id_, entry->render_view_id(), entry->controller());
+ }
+ audio_entries_.insert(std::make_pair(stream_id, entry.release()));
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamStatus(this, stream_id, "created");
+}
+
+void AudioRendererHost::OnPlayStream(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+ if (!entry) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ entry->controller()->Play();
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamPlaying(this, stream_id, true);
+}
+
+void AudioRendererHost::OnPauseStream(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+ if (!entry) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ entry->controller()->Pause();
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamPlaying(this, stream_id, false);
+}
+
+void AudioRendererHost::OnSetVolume(int stream_id, double volume) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntry* entry = LookupById(stream_id);
+ if (!entry) {
+ SendErrorMessage(stream_id);
+ return;
+ }
+
+ // Make sure the volume is valid.
+ if (volume < 0 || volume > 1.0)
+ return;
+ entry->controller()->SetVolume(volume);
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamVolume(this, stream_id, volume);
+}
+
+void AudioRendererHost::SendErrorMessage(int stream_id) {
+ Send(new AudioMsg_NotifyStreamStateChanged(
+ stream_id, media::AudioOutputIPCDelegate::kError));
+}
+
+void AudioRendererHost::OnCloseStream(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Prevent oustanding callbacks from attempting to close/delete the same
+ // AudioEntry twice.
+ AudioEntryMap::iterator i = audio_entries_.find(stream_id);
+ if (i == audio_entries_.end())
+ return;
+ scoped_ptr<AudioEntry> entry(i->second);
+ audio_entries_.erase(i);
+
+ media::AudioOutputController* const controller = entry->controller();
+ if (mirroring_manager_) {
+ mirroring_manager_->RemoveDiverter(
+ render_process_id_, entry->render_view_id(), controller);
+ }
+ controller->Close(
+ base::Bind(&AudioRendererHost::DeleteEntry, this, base::Passed(&entry)));
+
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamStatus(this, stream_id, "closed");
+}
+
+void AudioRendererHost::DeleteEntry(scoped_ptr<AudioEntry> entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // At this point, make the final "say" in audio playback state.
+ MediaObserver* const media_observer =
+ GetContentClient()->browser()->GetMediaObserver();
+ if (media_observer) {
+ media_observer->OnAudioStreamPlayingChanged(
+ render_process_id_, entry->render_view_id(), entry->stream_id(),
+ false, -std::numeric_limits<float>::infinity(), false);
+ }
+
+ // Notify the media observer.
+ if (media_internals_)
+ media_internals_->OnDeleteAudioStream(this, entry->stream_id());
+
+ // Note: |entry| will be deleted upon leaving this scope.
+}
+
+void AudioRendererHost::ReportErrorAndClose(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Make sure this isn't a stray callback executing after the stream has been
+ // closed, so error notifications aren't sent after clients believe the stream
+ // is closed.
+ if (!LookupById(stream_id))
+ return;
+
+ SendErrorMessage(stream_id);
+
+ if (media_internals_)
+ media_internals_->OnSetAudioStreamStatus(this, stream_id, "error");
+
+ OnCloseStream(stream_id);
+}
+
+AudioRendererHost::AudioEntry* AudioRendererHost::LookupById(int stream_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ AudioEntryMap::const_iterator i = audio_entries_.find(stream_id);
+ return i != audio_entries_.end() ? i->second : NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_renderer_host.h b/chromium/content/browser/renderer_host/media/audio_renderer_host.h
new file mode 100644
index 00000000000..47dbee9c0c1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_renderer_host.h
@@ -0,0 +1,162 @@
+// 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.
+//
+// AudioRendererHost serves audio related requests from AudioRenderer which
+// lives inside the render process and provide access to audio hardware.
+//
+// This class is owned by BrowserRenderProcessHost, and instantiated on UI
+// thread, but all other operations and method calls happen on IO thread, so we
+// need to be extra careful about the lifetime of this object. AudioManager is a
+// singleton and created in IO thread, audio output streams are also created in
+// the IO thread, so we need to destroy them also in IO thread. After this class
+// is created, a task of OnInitialized() is posted on IO thread in which
+// singleton of AudioManager is created.
+//
+// Here's an example of a typical IPC dialog for audio:
+//
+// Renderer AudioRendererHost
+// | |
+// | CreateStream > |
+// | < NotifyStreamCreated |
+// | |
+// | PlayStream > |
+// | < NotifyStreamStateChanged | kAudioStreamPlaying
+// | |
+// | PauseStream > |
+// | < NotifyStreamStateChanged | kAudioStreamPaused
+// | |
+// | PlayStream > |
+// | < NotifyStreamStateChanged | kAudioStreamPlaying
+// | ... |
+// | CloseStream > |
+// v v
+
+// A SyncSocket pair is used to signal buffer readiness between processes.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_RENDERER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_RENDERER_HOST_H_
+
+#include <map>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_output_controller.h"
+#include "media/audio/simple_sources.h"
+
+namespace media {
+class AudioManager;
+class AudioParameters;
+}
+
+namespace content {
+
+class AudioMirroringManager;
+class MediaInternals;
+class MediaStreamManager;
+class ResourceContext;
+
+class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter {
+ public:
+ // Called from UI thread from the owner of this object.
+ AudioRendererHost(int render_process_id,
+ media::AudioManager* audio_manager,
+ AudioMirroringManager* mirroring_manager,
+ MediaInternals* media_internals,
+ MediaStreamManager* media_stream_manager);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ friend class AudioRendererHostTest;
+ friend class BrowserThread;
+ friend class base::DeleteHelper<AudioRendererHost>;
+ friend class MockAudioRendererHost;
+ FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, CreateMockStream);
+ FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, MockStreamDataConversation);
+
+ class AudioEntry;
+ typedef std::map<int, AudioEntry*> AudioEntryMap;
+
+ virtual ~AudioRendererHost();
+
+ // Methods called on IO thread ----------------------------------------------
+
+ // Audio related IPC message handlers.
+
+ // Creates an audio output stream with the specified format whose data is
+ // produced by an entity in the render view referenced by |render_view_id|.
+ // |session_id| is used for unified IO to find out which input device to be
+ // opened for the stream. For clients that do not use unified IO,
+ // |session_id| will be ignored.
+ // Upon success/failure, the peer is notified via the NotifyStreamCreated
+ // message.
+ void OnCreateStream(int stream_id,
+ int render_view_id,
+ int session_id,
+ const media::AudioParameters& params);
+
+ // Play the audio stream referenced by |stream_id|.
+ void OnPlayStream(int stream_id);
+
+ // Pause the audio stream referenced by |stream_id|.
+ void OnPauseStream(int stream_id);
+
+ // Close the audio stream referenced by |stream_id|.
+ void OnCloseStream(int stream_id);
+
+ // Set the volume of the audio stream referenced by |stream_id|.
+ void OnSetVolume(int stream_id, double volume);
+
+ // Complete the process of creating an audio stream. This will set up the
+ // shared memory or shared socket in low latency mode and send the
+ // NotifyStreamCreated message to the peer.
+ void DoCompleteCreation(int stream_id);
+
+ // Propagate measured power level of the audio signal to MediaObserver.
+ void DoNotifyAudioPowerLevel(int stream_id, float power_dbfs, bool clipped);
+
+ // Send an error message to the renderer.
+ void SendErrorMessage(int stream_id);
+
+ // Delete an audio entry, notifying observers first. This is called by
+ // AudioOutputController after it has closed.
+ void DeleteEntry(scoped_ptr<AudioEntry> entry);
+
+ // Send an error message to the renderer, then close the stream.
+ void ReportErrorAndClose(int stream_id);
+
+ // A helper method to look up a AudioEntry identified by |stream_id|.
+ // Returns NULL if not found.
+ AudioEntry* LookupById(int stream_id);
+
+ // ID of the RenderProcessHost that owns this instance.
+ const int render_process_id_;
+
+ media::AudioManager* const audio_manager_;
+ AudioMirroringManager* const mirroring_manager_;
+ MediaInternals* const media_internals_;
+
+ // Used to access to AudioInputDeviceManager.
+ MediaStreamManager* media_stream_manager_;
+
+ // A map of stream IDs to audio sources.
+ AudioEntryMap audio_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioRendererHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_RENDERER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/audio_renderer_host_unittest.cc b/chromium/content/browser/renderer_host/media/audio_renderer_host_unittest.cc
new file mode 100644
index 00000000000..42fecc07a96
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_renderer_host_unittest.cc
@@ -0,0 +1,423 @@
+// 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 "base/bind.h"
+#include "base/environment.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sync_socket.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/audio_input_device_manager.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/audio_renderer_host.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/mock_media_observer.h"
+#include "content/common/media/audio_messages.h"
+#include "content/common/media/media_stream_options.h"
+#include "ipc/ipc_message_utils.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/fake_audio_output_stream.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::NotNull;
+
+namespace content {
+
+static const int kRenderProcessId = 1;
+static const int kRenderViewId = 4;
+static const int kStreamId = 50;
+
+class MockAudioMirroringManager : public AudioMirroringManager {
+ public:
+ MockAudioMirroringManager() {}
+ virtual ~MockAudioMirroringManager() {}
+
+ MOCK_METHOD3(AddDiverter,
+ void(int render_process_id, int render_view_id,
+ Diverter* diverter));
+ MOCK_METHOD3(RemoveDiverter,
+ void(int render_process_id, int render_view_id,
+ Diverter* diverter));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager);
+};
+
+class MockAudioRendererHost : public AudioRendererHost {
+ public:
+ explicit MockAudioRendererHost(
+ media::AudioManager* audio_manager,
+ AudioMirroringManager* mirroring_manager,
+ MediaInternals* media_internals,
+ MediaStreamManager* media_stream_manager)
+ : AudioRendererHost(kRenderProcessId,
+ audio_manager,
+ mirroring_manager,
+ media_internals,
+ media_stream_manager),
+ shared_memory_length_(0) {
+ }
+
+ // A list of mock methods.
+ MOCK_METHOD2(OnStreamCreated,
+ void(int stream_id, int length));
+ MOCK_METHOD1(OnStreamPlaying, void(int stream_id));
+ MOCK_METHOD1(OnStreamPaused, void(int stream_id));
+ MOCK_METHOD1(OnStreamError, void(int stream_id));
+
+ private:
+ virtual ~MockAudioRendererHost() {
+ // Make sure all audio streams have been deleted.
+ EXPECT_TRUE(audio_entries_.empty());
+ }
+
+ // This method is used to dispatch IPC messages to the renderer. We intercept
+ // these messages here and dispatch to our mock methods to verify the
+ // conversation between this object and the renderer.
+ virtual bool Send(IPC::Message* message) {
+ CHECK(message);
+
+ // In this method we dispatch the messages to the according handlers as if
+ // we are the renderer.
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message)
+ IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamCreated,
+ OnStreamCreated)
+ IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamStateChanged,
+ OnStreamStateChanged)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ EXPECT_TRUE(handled);
+
+ delete message;
+ return true;
+ }
+
+ void OnStreamCreated(const IPC::Message& msg, int stream_id,
+ base::SharedMemoryHandle handle,
+#if defined(OS_WIN)
+ base::SyncSocket::Handle socket_handle,
+#else
+ base::FileDescriptor socket_descriptor,
+#endif
+ uint32 length) {
+ // Maps the shared memory.
+ shared_memory_.reset(new base::SharedMemory(handle, false));
+ CHECK(shared_memory_->Map(length));
+ CHECK(shared_memory_->memory());
+ shared_memory_length_ = length;
+
+ // Create the SyncSocket using the handle.
+ base::SyncSocket::Handle sync_socket_handle;
+#if defined(OS_WIN)
+ sync_socket_handle = socket_handle;
+#else
+ sync_socket_handle = socket_descriptor.fd;
+#endif
+ sync_socket_.reset(new base::SyncSocket(sync_socket_handle));
+
+ // And then delegate the call to the mock method.
+ OnStreamCreated(stream_id, length);
+ }
+
+ void OnStreamStateChanged(const IPC::Message& msg, int stream_id,
+ media::AudioOutputIPCDelegate::State state) {
+ switch (state) {
+ case media::AudioOutputIPCDelegate::kPlaying:
+ OnStreamPlaying(stream_id);
+ break;
+ case media::AudioOutputIPCDelegate::kPaused:
+ OnStreamPaused(stream_id);
+ break;
+ case media::AudioOutputIPCDelegate::kError:
+ OnStreamError(stream_id);
+ break;
+ default:
+ FAIL() << "Unknown stream state";
+ break;
+ }
+ }
+
+ scoped_ptr<base::SharedMemory> shared_memory_;
+ scoped_ptr<base::SyncSocket> sync_socket_;
+ uint32 shared_memory_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost);
+};
+
+ACTION_P(QuitMessageLoop, message_loop) {
+ message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+class AudioRendererHostTest : public testing::Test {
+ public:
+ AudioRendererHostTest() : is_stream_active_(false) {}
+
+ protected:
+ virtual void SetUp() {
+ // Create a message loop so AudioRendererHost can use it.
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+
+ // Claim to be on both the UI and IO threads to pass all the DCHECKS.
+ io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
+ message_loop_.get()));
+ ui_thread_.reset(new BrowserThreadImpl(BrowserThread::UI,
+ message_loop_.get()));
+ audio_manager_.reset(media::AudioManager::Create());
+ media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
+ media_stream_manager_->UseFakeDevice();
+ observer_.reset(new MockMediaInternals());
+ host_ = new MockAudioRendererHost(
+ audio_manager_.get(), &mirroring_manager_, observer_.get(),
+ media_stream_manager_.get());
+
+ // Simulate IPC channel connected.
+ host_->OnChannelConnected(base::GetCurrentProcId());
+ }
+
+ virtual void TearDown() {
+ // Simulate closing the IPC channel.
+ host_->OnChannelClosing();
+
+ // Release the reference to the mock object. The object will be destructed
+ // on message_loop_.
+ host_ = NULL;
+
+ // We need to continue running message_loop_ to complete all destructions.
+ SyncWithAudioThread();
+ audio_manager_.reset();
+
+ // Make sure the stream has been deleted before continuing.
+ while (is_stream_active_)
+ message_loop_->Run();
+
+ io_thread_.reset();
+ ui_thread_.reset();
+
+ // Delete the IO message loop. This will cause the MediaStreamManager to be
+ // notified so it will stop its device thread and device managers.
+ message_loop_.reset();
+ }
+
+ void Create(bool unified_stream) {
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamStatus(_, kStreamId, "created"));
+ EXPECT_CALL(*host_.get(), OnStreamCreated(kStreamId, _))
+ .WillOnce(DoAll(Assign(&is_stream_active_, true),
+ QuitMessageLoop(message_loop_.get())));
+ EXPECT_CALL(mirroring_manager_,
+ AddDiverter(kRenderProcessId, kRenderViewId, NotNull()))
+ .RetiresOnSaturation();
+
+ // Send a create stream message to the audio output stream and wait until
+ // we receive the created message.
+ int session_id;
+ media::AudioParameters params;
+ if (unified_stream) {
+ // Use AudioInputDeviceManager::kFakeOpenSessionId as the session id to
+ // pass the permission check.
+ session_id = AudioInputDeviceManager::kFakeOpenSessionId;
+ params = media::AudioParameters(
+ media::AudioParameters::AUDIO_FAKE,
+ media::CHANNEL_LAYOUT_STEREO,
+ 2,
+ media::AudioParameters::kAudioCDSampleRate, 16,
+ media::AudioParameters::kAudioCDSampleRate / 10);
+ } else {
+ session_id = 0;
+ params = media::AudioParameters(
+ media::AudioParameters::AUDIO_FAKE,
+ media::CHANNEL_LAYOUT_STEREO,
+ media::AudioParameters::kAudioCDSampleRate, 16,
+ media::AudioParameters::kAudioCDSampleRate / 10);
+ }
+ host_->OnCreateStream(kStreamId, kRenderViewId, session_id, params);
+ message_loop_->Run();
+
+ // At some point in the future, a corresponding RemoveDiverter() call must
+ // be made.
+ EXPECT_CALL(mirroring_manager_,
+ RemoveDiverter(kRenderProcessId, kRenderViewId, NotNull()))
+ .RetiresOnSaturation();
+
+ // All created streams should ultimately be closed.
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamStatus(_, kStreamId, "closed"));
+
+ // Expect the audio stream will be deleted at some later point.
+ EXPECT_CALL(*observer_, OnDeleteAudioStream(_, kStreamId))
+ .WillOnce(DoAll(Assign(&is_stream_active_, false),
+ QuitMessageLoop(message_loop_.get())));
+ }
+
+ void Close() {
+ // Send a message to AudioRendererHost to tell it we want to close the
+ // stream.
+ host_->OnCloseStream(kStreamId);
+ if (is_stream_active_)
+ message_loop_->Run();
+ else
+ message_loop_->RunUntilIdle();
+ }
+
+ void Play() {
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamPlaying(_, kStreamId, true));
+ EXPECT_CALL(*host_.get(), OnStreamPlaying(kStreamId))
+ .WillOnce(QuitMessageLoop(message_loop_.get()));
+
+ host_->OnPlayStream(kStreamId);
+ message_loop_->Run();
+ }
+
+ void Pause() {
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamPlaying(_, kStreamId, false));
+ EXPECT_CALL(*host_.get(), OnStreamPaused(kStreamId))
+ .WillOnce(QuitMessageLoop(message_loop_.get()));
+
+ host_->OnPauseStream(kStreamId);
+ message_loop_->Run();
+ }
+
+ void SetVolume(double volume) {
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamVolume(_, kStreamId, volume));
+
+ host_->OnSetVolume(kStreamId, volume);
+ message_loop_->RunUntilIdle();
+ }
+
+ void SimulateError() {
+ EXPECT_CALL(*observer_,
+ OnSetAudioStreamStatus(_, kStreamId, "error"));
+ EXPECT_EQ(1u, host_->audio_entries_.size())
+ << "Calls Create() before calling this method";
+
+ // Expect an error signal sent through IPC.
+ EXPECT_CALL(*host_.get(), OnStreamError(kStreamId));
+
+ // Simulate an error sent from the audio device.
+ host_->ReportErrorAndClose(kStreamId);
+ SyncWithAudioThread();
+
+ // Expect the audio stream record is removed.
+ EXPECT_EQ(0u, host_->audio_entries_.size());
+ }
+
+ // Called on the audio thread.
+ static void PostQuitMessageLoop(base::MessageLoop* message_loop) {
+ message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+ }
+
+ // Called on the main thread.
+ static void PostQuitOnAudioThread(media::AudioManager* audio_manager,
+ base::MessageLoop* message_loop) {
+ audio_manager->GetMessageLoop()->PostTask(FROM_HERE,
+ base::Bind(&PostQuitMessageLoop, message_loop));
+ }
+
+ // SyncWithAudioThread() waits until all pending tasks on the audio thread
+ // are executed while also processing pending task in message_loop_ on the
+ // current thread. It is used to synchronize with the audio thread when we are
+ // closing an audio stream.
+ void SyncWithAudioThread() {
+ // Don't use scoped_refptr to addref the media::AudioManager when posting
+ // to the thread that itself owns.
+ message_loop_->PostTask(
+ FROM_HERE, base::Bind(&PostQuitOnAudioThread,
+ base::Unretained(audio_manager_.get()),
+ message_loop_.get()));
+ message_loop_->Run();
+ }
+
+ private:
+ scoped_ptr<MockMediaInternals> observer_;
+ MockAudioMirroringManager mirroring_manager_;
+ scoped_refptr<MockAudioRendererHost> host_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<BrowserThreadImpl> io_thread_;
+ scoped_ptr<BrowserThreadImpl> ui_thread_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+ scoped_ptr<MediaStreamManager> media_stream_manager_;
+
+ bool is_stream_active_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest);
+};
+
+TEST_F(AudioRendererHostTest, CreateAndClose) {
+ Create(false);
+ Close();
+}
+
+// Simulate the case where a stream is not properly closed.
+TEST_F(AudioRendererHostTest, CreateAndShutdown) {
+ Create(false);
+}
+
+TEST_F(AudioRendererHostTest, CreatePlayAndClose) {
+ Create(false);
+ Play();
+ Close();
+}
+
+TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) {
+ Create(false);
+ Play();
+ Pause();
+ Close();
+}
+
+TEST_F(AudioRendererHostTest, SetVolume) {
+ Create(false);
+ SetVolume(0.5);
+ Play();
+ Pause();
+ Close();
+}
+
+// Simulate the case where a stream is not properly closed.
+TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) {
+ Create(false);
+ Play();
+}
+
+// Simulate the case where a stream is not properly closed.
+TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) {
+ Create(false);
+ Play();
+ Pause();
+}
+
+TEST_F(AudioRendererHostTest, SimulateError) {
+ Create(false);
+ Play();
+ SimulateError();
+}
+
+// Simulate the case when an error is generated on the browser process,
+// the audio device is closed but the render process try to close the
+// audio stream again.
+TEST_F(AudioRendererHostTest, SimulateErrorAndClose) {
+ Create(false);
+ Play();
+ SimulateError();
+ Close();
+}
+
+TEST_F(AudioRendererHostTest, CreateUnifiedStreamAndClose) {
+ Create(true);
+ Close();
+}
+
+// TODO(hclam): Add tests for data conversation in low latency mode.
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_sync_reader.cc b/chromium/content/browser/renderer_host/media/audio_sync_reader.cc
new file mode 100644
index 00000000000..dea8ae2a207
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_sync_reader.cc
@@ -0,0 +1,200 @@
+// 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 "content/browser/renderer_host/media/audio_sync_reader.h"
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/memory/shared_memory.h"
+#include "base/metrics/histogram.h"
+#include "content/public/common/content_switches.h"
+#include "media/audio/audio_buffers_state.h"
+#include "media/audio/audio_parameters.h"
+#include "media/audio/shared_memory_util.h"
+
+using media::AudioBus;
+
+namespace content {
+
+AudioSyncReader::AudioSyncReader(base::SharedMemory* shared_memory,
+ const media::AudioParameters& params,
+ int input_channels)
+ : shared_memory_(shared_memory),
+ input_channels_(input_channels),
+ mute_audio_(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kMuteAudio)),
+ renderer_callback_count_(0),
+ renderer_missed_callback_count_(0) {
+ packet_size_ = media::PacketSizeInBytes(shared_memory_->requested_size());
+ int input_memory_size = 0;
+ int output_memory_size = AudioBus::CalculateMemorySize(params);
+ if (input_channels_ > 0) {
+ // The input storage is after the output storage.
+ int frames = params.frames_per_buffer();
+ input_memory_size = AudioBus::CalculateMemorySize(input_channels_, frames);
+ char* input_data =
+ static_cast<char*>(shared_memory_->memory()) + output_memory_size;
+ input_bus_ = AudioBus::WrapMemory(input_channels_, frames, input_data);
+ }
+ DCHECK_EQ(packet_size_, output_memory_size + input_memory_size);
+ output_bus_ = AudioBus::WrapMemory(params, shared_memory->memory());
+}
+
+AudioSyncReader::~AudioSyncReader() {
+ if (!renderer_callback_count_)
+ return;
+
+ // Recording the percentage of deadline misses gives us a rough overview of
+ // how many users might be running into audio glitches.
+ int percentage_missed =
+ 100.0 * renderer_missed_callback_count_ / renderer_callback_count_;
+ UMA_HISTOGRAM_PERCENTAGE(
+ "Media.AudioRendererMissedDeadline", percentage_missed);
+}
+
+bool AudioSyncReader::DataReady() {
+ return !media::IsUnknownDataSize(shared_memory_, packet_size_);
+}
+
+// media::AudioOutputController::SyncReader implementations.
+void AudioSyncReader::UpdatePendingBytes(uint32 bytes) {
+ if (bytes != static_cast<uint32>(media::kPauseMark)) {
+ // Store unknown length of data into buffer, so we later
+ // can find out if data became available.
+ media::SetUnknownDataSize(shared_memory_, packet_size_);
+ }
+
+ if (socket_) {
+ socket_->Send(&bytes, sizeof(bytes));
+ }
+}
+
+int AudioSyncReader::Read(bool block, const AudioBus* source, AudioBus* dest) {
+ ++renderer_callback_count_;
+ if (!DataReady()) {
+ ++renderer_missed_callback_count_;
+
+ if (block)
+ WaitTillDataReady();
+ }
+
+ // Copy optional synchronized live audio input for consumption by renderer
+ // process.
+ if (source && input_bus_) {
+ DCHECK_EQ(source->channels(), input_bus_->channels());
+ // TODO(crogers): In some cases with device and sample-rate changes
+ // it's possible for an AOR to insert a resampler in the path.
+ // Because this is used with the Web Audio API, it'd be better
+ // to bypass the device change handling in AOR and instead let
+ // the renderer-side Web Audio code deal with this.
+ if (source->frames() == input_bus_->frames() &&
+ source->channels() == input_bus_->channels())
+ source->CopyTo(input_bus_.get());
+ else
+ input_bus_->Zero();
+ }
+
+ // Retrieve the actual number of bytes available from the shared memory. If
+ // the renderer has not completed rendering this value will be invalid (still
+ // the marker stored in UpdatePendingBytes() above) and must be sanitized.
+ // TODO(dalecurtis): Technically this is not the exact size. Due to channel
+ // padding for alignment, there may be more data available than this; AudioBus
+ // will automatically do the right thing during CopyTo(). Rename this method
+ // to GetActualFrameCount().
+ uint32 size = media::GetActualDataSizeInBytes(shared_memory_, packet_size_);
+
+ // Compute the actual number of frames read. It's important to sanitize this
+ // value for a couple reasons. One, it might still be the unknown data size
+ // marker. Two, shared memory comes from a potentially untrusted source.
+ int frames =
+ size / (sizeof(*output_bus_->channel(0)) * output_bus_->channels());
+ if (frames < 0)
+ frames = 0;
+ else if (frames > output_bus_->frames())
+ frames = output_bus_->frames();
+
+ if (mute_audio_) {
+ dest->Zero();
+ } else {
+ // Copy data from the shared memory into the caller's AudioBus.
+ output_bus_->CopyTo(dest);
+
+ // Zero out any unfilled frames in the destination bus.
+ dest->ZeroFramesPartial(frames, dest->frames() - frames);
+ }
+
+ // Zero out the entire output buffer to avoid stuttering/repeating-buffers
+ // in the anomalous case if the renderer is unable to keep up with real-time.
+ output_bus_->Zero();
+
+ // Store unknown length of data into buffer, in case renderer does not store
+ // the length itself. It also helps in decision if we need to yield.
+ media::SetUnknownDataSize(shared_memory_, packet_size_);
+
+ // Return the actual number of frames read.
+ return frames;
+}
+
+void AudioSyncReader::Close() {
+ if (socket_) {
+ socket_->Close();
+ }
+}
+
+bool AudioSyncReader::Init() {
+ socket_.reset(new base::CancelableSyncSocket());
+ foreign_socket_.reset(new base::CancelableSyncSocket());
+ return base::CancelableSyncSocket::CreatePair(socket_.get(),
+ foreign_socket_.get());
+}
+
+#if defined(OS_WIN)
+bool AudioSyncReader::PrepareForeignSocketHandle(
+ base::ProcessHandle process_handle,
+ base::SyncSocket::Handle* foreign_handle) {
+ ::DuplicateHandle(GetCurrentProcess(), foreign_socket_->handle(),
+ process_handle, foreign_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ if (*foreign_handle != 0)
+ return true;
+ return false;
+}
+#else
+bool AudioSyncReader::PrepareForeignSocketHandle(
+ base::ProcessHandle process_handle,
+ base::FileDescriptor* foreign_handle) {
+ foreign_handle->fd = foreign_socket_->handle();
+ foreign_handle->auto_close = false;
+ if (foreign_handle->fd != -1)
+ return true;
+ return false;
+}
+#endif
+
+void AudioSyncReader::WaitTillDataReady() {
+ base::TimeTicks start = base::TimeTicks::Now();
+ const base::TimeDelta kMaxWait = base::TimeDelta::FromMilliseconds(20);
+#if defined(OS_WIN)
+ // Sleep(0) on Windows lets the other threads run.
+ const base::TimeDelta kSleep = base::TimeDelta::FromMilliseconds(0);
+#else
+ // We want to sleep for a bit here, as otherwise a backgrounded renderer won't
+ // get enough cpu to send the data and the high priority thread in the browser
+ // will use up a core causing even more skips.
+ const base::TimeDelta kSleep = base::TimeDelta::FromMilliseconds(2);
+#endif
+ base::TimeDelta time_since_start;
+ do {
+ base::PlatformThread::Sleep(kSleep);
+ time_since_start = base::TimeTicks::Now() - start;
+ } while (!DataReady() && time_since_start < kMaxWait);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Media.AudioOutputControllerDataNotReady",
+ time_since_start,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMilliseconds(1000),
+ 50);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/audio_sync_reader.h b/chromium/content/browser/renderer_host/media/audio_sync_reader.h
new file mode 100644
index 00000000000..fdfbf81a601
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/audio_sync_reader.h
@@ -0,0 +1,91 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_SYNC_READER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_SYNC_READER_H_
+
+#include "base/file_descriptor_posix.h"
+#include "base/process/process.h"
+#include "base/sync_socket.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "media/audio/audio_output_controller.h"
+#include "media/base/audio_bus.h"
+
+namespace base {
+class SharedMemory;
+}
+
+namespace content {
+
+// A AudioOutputController::SyncReader implementation using SyncSocket. This
+// is used by AudioOutputController to provide a low latency data source for
+// transmitting audio packets between the browser process and the renderer
+// process.
+class AudioSyncReader : public media::AudioOutputController::SyncReader {
+ public:
+ AudioSyncReader(base::SharedMemory* shared_memory,
+ const media::AudioParameters& params,
+ int input_channels);
+
+ virtual ~AudioSyncReader();
+
+ // media::AudioOutputController::SyncReader implementations.
+ virtual void UpdatePendingBytes(uint32 bytes) OVERRIDE;
+ virtual int Read(bool block,
+ const media::AudioBus* source,
+ media::AudioBus* dest) OVERRIDE;
+ virtual void Close() OVERRIDE;
+
+ bool Init();
+ bool PrepareForeignSocketHandle(base::ProcessHandle process_handle,
+#if defined(OS_WIN)
+ base::SyncSocket::Handle* foreign_handle);
+#else
+ base::FileDescriptor* foreign_handle);
+#endif
+
+ private:
+ // Indicates whether the renderer has data available for reading.
+ bool DataReady();
+
+ // Blocks until DataReady() is true or a timeout expires.
+ void WaitTillDataReady();
+
+ base::SharedMemory* shared_memory_;
+
+ // Number of input channels for synchronized I/O.
+ int input_channels_;
+
+ // Mutes all incoming samples. This is used to prevent audible sound
+ // during automated testing.
+ bool mute_audio_;
+
+ // Socket for transmitting audio data.
+ scoped_ptr<base::CancelableSyncSocket> socket_;
+
+ // Socket to be used by the renderer. The reference is released after
+ // PrepareForeignSocketHandle() is called and ran successfully.
+ scoped_ptr<base::CancelableSyncSocket> foreign_socket_;
+
+ // Shared memory wrapper used for transferring audio data to Read() callers.
+ scoped_ptr<media::AudioBus> output_bus_;
+
+ // Shared memory wrapper used for transferring audio data from Read() callers.
+ scoped_ptr<media::AudioBus> input_bus_;
+
+ // Maximum amount of audio data which can be transferred in one Read() call.
+ int packet_size_;
+
+ // Track the number of times the renderer missed its real-time deadline and
+ // report a UMA stat during destruction.
+ size_t renderer_callback_count_;
+ size_t renderer_missed_callback_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioSyncReader);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_SYNC_READER_H_
diff --git a/chromium/content/browser/renderer_host/media/desktop_capture_device.cc b/chromium/content/browser/renderer_host/media/desktop_capture_device.cc
new file mode 100644
index 00000000000..9549633e80d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/desktop_capture_device.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/desktop_capture_device.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/desktop_media_id.h"
+#include "media/base/video_util.h"
+#include "third_party/libyuv/include/libyuv/scale_argb.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
+#include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
+
+namespace content {
+
+namespace {
+
+const int kBytesPerPixel = 4;
+
+webrtc::DesktopRect ComputeLetterboxRect(
+ const webrtc::DesktopSize& max_size,
+ const webrtc::DesktopSize& source_size) {
+ gfx::Rect result = media::ComputeLetterboxRegion(
+ gfx::Rect(0, 0, max_size.width(), max_size.height()),
+ gfx::Size(source_size.width(), source_size.height()));
+ return webrtc::DesktopRect::MakeLTRB(
+ result.x(), result.y(), result.right(), result.bottom());
+}
+
+} // namespace
+
+class DesktopCaptureDevice::Core
+ : public base::RefCountedThreadSafe<Core>,
+ public webrtc::DesktopCapturer::Callback {
+ public:
+ Core(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_ptr<webrtc::DesktopCapturer> capturer);
+
+ // Implementation of VideoCaptureDevice methods.
+ void Allocate(const media::VideoCaptureCapability& capture_format,
+ EventHandler* event_handler);
+ void Start();
+ void Stop();
+ void DeAllocate();
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ virtual ~Core();
+
+ // webrtc::DesktopCapturer::Callback interface
+ virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
+ virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE;
+
+ // Helper methods that run on the |task_runner_|. Posted from the
+ // corresponding public methods.
+ void DoAllocate(const media::VideoCaptureCapability& capture_format);
+ void DoStart();
+ void DoStop();
+ void DoDeAllocate();
+
+ // Chooses new output properties based on the supplied source size and the
+ // properties requested to Allocate(), and dispatches OnFrameInfo[Changed]
+ // notifications.
+ void RefreshCaptureFormat(const webrtc::DesktopSize& frame_size);
+
+ // Helper to schedule capture tasks.
+ void ScheduleCaptureTimer();
+
+ // Method that is scheduled on |task_runner_| to be called on regular interval
+ // to capture a frame.
+ void OnCaptureTimer();
+
+ // Captures a single frame.
+ void DoCapture();
+
+ // Task runner used for capturing operations.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // The underlying DesktopCapturer instance used to capture frames.
+ scoped_ptr<webrtc::DesktopCapturer> desktop_capturer_;
+
+ // |event_handler_lock_| must be locked whenever |event_handler_| is used.
+ // It's necessary because DeAllocate() needs to reset it on the calling thread
+ // to ensure that the event handler is not called once DeAllocate() returns.
+ base::Lock event_handler_lock_;
+ EventHandler* event_handler_;
+
+ // Requested video capture format (width, height, frame rate, etc).
+ media::VideoCaptureCapability requested_format_;
+
+ // Actual video capture format being generated.
+ media::VideoCaptureCapability capture_format_;
+
+ // Size of frame most recently captured from the source.
+ webrtc::DesktopSize previous_frame_size_;
+
+ // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
+ // depending upon the caller's requested capture capabilities. If frames can
+ // be returned to the caller directly then this is NULL.
+ scoped_ptr<webrtc::DesktopFrame> output_frame_;
+
+ // Sub-rectangle of |output_frame_| into which the source will be scaled
+ // and/or letterboxed.
+ webrtc::DesktopRect output_rect_;
+
+ // True between DoStart() and DoStop(). Can't just check |event_handler_|
+ // because |event_handler_| is used on the caller thread.
+ bool started_;
+
+ // True when we have delayed OnCaptureTimer() task posted on
+ // |task_runner_|.
+ bool capture_task_posted_;
+
+ // True when waiting for |desktop_capturer_| to capture current frame.
+ bool capture_in_progress_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+DesktopCaptureDevice::Core::Core(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_ptr<webrtc::DesktopCapturer> capturer)
+ : task_runner_(task_runner),
+ desktop_capturer_(capturer.Pass()),
+ event_handler_(NULL),
+ started_(false),
+ capture_task_posted_(false),
+ capture_in_progress_(false) {
+}
+
+DesktopCaptureDevice::Core::~Core() {
+}
+
+void DesktopCaptureDevice::Core::Allocate(
+ const media::VideoCaptureCapability& capture_format,
+ EventHandler* event_handler) {
+ DCHECK_GT(capture_format.width, 0);
+ DCHECK_GT(capture_format.height, 0);
+ DCHECK_GT(capture_format.frame_rate, 0);
+
+ {
+ base::AutoLock auto_lock(event_handler_lock_);
+ event_handler_ = event_handler;
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::DoAllocate, this, capture_format));
+}
+
+void DesktopCaptureDevice::Core::Start() {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::DoStart, this));
+}
+
+void DesktopCaptureDevice::Core::Stop() {
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::DoStop, this));
+}
+
+void DesktopCaptureDevice::Core::DeAllocate() {
+ {
+ base::AutoLock auto_lock(event_handler_lock_);
+ event_handler_ = NULL;
+ }
+ task_runner_->PostTask(FROM_HERE, base::Bind(&Core::DoDeAllocate, this));
+}
+
+webrtc::SharedMemory*
+DesktopCaptureDevice::Core::CreateSharedMemory(size_t size) {
+ return NULL;
+}
+
+void DesktopCaptureDevice::Core::OnCaptureCompleted(
+ webrtc::DesktopFrame* frame) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(capture_in_progress_);
+
+ capture_in_progress_ = false;
+
+ if (!frame) {
+ LOG(ERROR) << "Failed to capture a frame.";
+ event_handler_->OnError();
+ return;
+ }
+
+ scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
+
+ // Handle initial frame size and size changes.
+ RefreshCaptureFormat(frame->size());
+
+ if (!started_)
+ return;
+
+ webrtc::DesktopSize output_size(capture_format_.width,
+ capture_format_.height);
+ size_t output_bytes = output_size.width() * output_size.height() *
+ webrtc::DesktopFrame::kBytesPerPixel;
+ const uint8_t* output_data = NULL;
+
+ if (frame->size().equals(output_size)) {
+ // If the captured frame matches the output size, we can return the pixel
+ // data directly, without scaling.
+ output_data = frame->data();
+ } else {
+ // Otherwise we need to down-scale and/or letterbox to the target format.
+
+ // Allocate a buffer of the correct size to scale the frame into.
+ // |output_frame_| is cleared whenever |output_rect_| changes, so we don't
+ // need to worry about clearing out stale pixel data in letterboxed areas.
+ if (!output_frame_) {
+ output_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
+ memset(output_frame_->data(), 0, output_bytes);
+ }
+ DCHECK(output_frame_->size().equals(output_size));
+
+ // TODO(wez): Optimize this to scale only changed portions of the output,
+ // using ARGBScaleClip().
+ uint8_t* output_rect_data = output_frame_->data() +
+ output_frame_->stride() * output_rect_.top() +
+ webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left();
+ libyuv::ARGBScale(frame->data(), frame->stride(),
+ frame->size().width(), frame->size().height(),
+ output_rect_data, output_frame_->stride(),
+ output_rect_.width(), output_rect_.height(),
+ libyuv::kFilterBilinear);
+ output_data = output_frame_->data();
+ }
+
+ base::AutoLock auto_lock(event_handler_lock_);
+ if (event_handler_) {
+ event_handler_->OnIncomingCapturedFrame(output_data, output_bytes,
+ base::Time::Now(), 0, false, false);
+ }
+}
+
+void DesktopCaptureDevice::Core::DoAllocate(
+ const media::VideoCaptureCapability& capture_format) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(desktop_capturer_);
+
+ requested_format_ = capture_format;
+
+ // Store requested frame rate and calculate expected delay.
+ capture_format_.frame_rate = requested_format_.frame_rate;
+ capture_format_.expected_capture_delay =
+ base::Time::kMillisecondsPerSecond / requested_format_.frame_rate;
+
+ // Support dynamic changes in resolution only if requester also does.
+ if (requested_format_.frame_size_type ==
+ media::VariableResolutionVideoCaptureDevice) {
+ capture_format_.frame_size_type =
+ media::VariableResolutionVideoCaptureDevice;
+ }
+
+ // This capturer always outputs ARGB, non-interlaced.
+ capture_format_.color = media::VideoCaptureCapability::kARGB;
+ capture_format_.interlaced = false;
+
+ desktop_capturer_->Start(this);
+
+ // Capture first frame, so that we can call OnFrameInfo() callback.
+ DoCapture();
+}
+
+void DesktopCaptureDevice::Core::DoStart() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ started_ = true;
+ if (!capture_task_posted_) {
+ ScheduleCaptureTimer();
+ DoCapture();
+ }
+}
+
+void DesktopCaptureDevice::Core::DoStop() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ started_ = false;
+ output_frame_.reset();
+ previous_frame_size_.set(0, 0);
+}
+
+void DesktopCaptureDevice::Core::DoDeAllocate() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DoStop();
+ desktop_capturer_.reset();
+}
+
+void DesktopCaptureDevice::Core::RefreshCaptureFormat(
+ const webrtc::DesktopSize& frame_size) {
+ if (previous_frame_size_.equals(frame_size))
+ return;
+
+ // Clear the output frame, if any, since it will either need resizing, or
+ // clearing of stale data in letterbox areas, anyway.
+ output_frame_.reset();
+
+ if (previous_frame_size_.is_empty() ||
+ requested_format_.frame_size_type ==
+ media::VariableResolutionVideoCaptureDevice) {
+ // If this is the first frame, or the receiver supports variable resolution
+ // then determine the output size by treating the requested width & height
+ // as maxima.
+ if (frame_size.width() > requested_format_.width ||
+ frame_size.height() > requested_format_.height) {
+ output_rect_ = ComputeLetterboxRect(
+ webrtc::DesktopSize(requested_format_.width,
+ requested_format_.height),
+ frame_size);
+ output_rect_.Translate(-output_rect_.left(), -output_rect_.top());
+ } else {
+ output_rect_ = webrtc::DesktopRect::MakeSize(frame_size);
+ }
+ capture_format_.width = output_rect_.width();
+ capture_format_.height = output_rect_.height();
+
+ {
+ base::AutoLock auto_lock(event_handler_lock_);
+ if (event_handler_) {
+ if (previous_frame_size_.is_empty()) {
+ event_handler_->OnFrameInfo(capture_format_);
+ } else {
+ event_handler_->OnFrameInfoChanged(capture_format_);
+ }
+ }
+ }
+ } else {
+ // Otherwise the output frame size cannot change, so just scale and
+ // letterbox.
+ output_rect_ = ComputeLetterboxRect(
+ webrtc::DesktopSize(capture_format_.width, capture_format_.height),
+ frame_size);
+ }
+
+ previous_frame_size_ = frame_size;
+}
+
+void DesktopCaptureDevice::Core::ScheduleCaptureTimer() {
+ DCHECK(!capture_task_posted_);
+ capture_task_posted_ = true;
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(&Core::OnCaptureTimer, this),
+ base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
+}
+
+void DesktopCaptureDevice::Core::OnCaptureTimer() {
+ DCHECK(capture_task_posted_);
+ capture_task_posted_ = false;
+
+ if (!started_)
+ return;
+
+ // Schedule a task for the next frame.
+ ScheduleCaptureTimer();
+ DoCapture();
+}
+
+void DesktopCaptureDevice::Core::DoCapture() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!capture_in_progress_);
+
+ capture_in_progress_ = true;
+ desktop_capturer_->Capture(webrtc::DesktopRegion());
+
+ // Currently only synchronous implementations of DesktopCapturer are
+ // supported.
+ DCHECK(!capture_in_progress_);
+}
+
+// static
+scoped_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create(
+ const DesktopMediaID& source) {
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool =
+ BrowserThread::GetBlockingPool();
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ blocking_pool->GetSequencedTaskRunner(
+ blocking_pool->GetSequenceToken());
+
+ switch (source.type) {
+ case DesktopMediaID::TYPE_SCREEN: {
+ scoped_ptr<webrtc::DesktopCapturer> capturer;
+
+#if defined(OS_CHROMEOS) && !defined(ARCH_CPU_ARMEL) && defined(USE_X11)
+ // ScreenCapturerX11 polls by default, due to poor driver support for
+ // DAMAGE. ChromeOS' drivers [can be patched to] support DAMAGE properly,
+ // so use it. However ARM driver seems to not support this properly, so
+ // disable it for ARM. See http://crbug.com/230105 .
+ capturer.reset(webrtc::ScreenCapturer::CreateWithXDamage(true));
+#elif defined(OS_WIN)
+ // ScreenCapturerWin disables Aero by default. We don't want it disabled
+ // for WebRTC screen capture, though.
+ capturer.reset(
+ webrtc::ScreenCapturer::CreateWithDisableAero(false));
+#else
+ capturer.reset(webrtc::ScreenCapturer::Create());
+#endif
+
+ return scoped_ptr<media::VideoCaptureDevice>(new DesktopCaptureDevice(
+ task_runner, capturer.Pass()));
+ }
+
+ case DesktopMediaID::TYPE_WINDOW: {
+ scoped_ptr<webrtc::WindowCapturer> capturer(
+ webrtc::WindowCapturer::Create());
+
+ if (!capturer || !capturer->SelectWindow(source.id)) {
+ return scoped_ptr<media::VideoCaptureDevice>();
+ }
+
+ return scoped_ptr<media::VideoCaptureDevice>(new DesktopCaptureDevice(
+ task_runner, capturer.PassAs<webrtc::DesktopCapturer>()));
+ }
+
+ default: {
+ NOTREACHED();
+ return scoped_ptr<media::VideoCaptureDevice>();
+ }
+ }
+}
+
+DesktopCaptureDevice::DesktopCaptureDevice(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_ptr<webrtc::DesktopCapturer> capturer)
+ : core_(new Core(task_runner, capturer.Pass())),
+ name_("", "") {
+}
+
+DesktopCaptureDevice::~DesktopCaptureDevice() {
+ DeAllocate();
+}
+
+void DesktopCaptureDevice::Allocate(
+ const media::VideoCaptureCapability& capture_format,
+ EventHandler* event_handler) {
+ core_->Allocate(capture_format, event_handler);
+}
+
+void DesktopCaptureDevice::Start() {
+ core_->Start();
+}
+
+void DesktopCaptureDevice::Stop() {
+ core_->Stop();
+}
+
+void DesktopCaptureDevice::DeAllocate() {
+ core_->DeAllocate();
+}
+
+const media::VideoCaptureDevice::Name& DesktopCaptureDevice::device_name() {
+ return name_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/desktop_capture_device.h b/chromium/content/browser/renderer_host/media/desktop_capture_device.h
new file mode 100644
index 00000000000..84422d3c5c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/desktop_capture_device.h
@@ -0,0 +1,58 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "media/video/capture/video_capture_device.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace webrtc {
+class DesktopCapturer;
+} // namespace webrtc
+
+namespace content {
+
+struct DesktopMediaID;
+
+// DesktopCaptureDevice implements VideoCaptureDevice for screens and windows.
+// It's essentially an adapter between webrtc::DesktopCapturer and
+// VideoCaptureDevice.
+class CONTENT_EXPORT DesktopCaptureDevice : public media::VideoCaptureDevice {
+ public:
+ // Creates capturer for the specified |source| and then creates
+ // DesktopCaptureDevice for it. May return NULL in case of a failure (e.g. if
+ // requested window was destroyed).
+ static scoped_ptr<media::VideoCaptureDevice> Create(
+ const DesktopMediaID& source);
+
+ DesktopCaptureDevice(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_ptr<webrtc::DesktopCapturer> desktop_capturer);
+ virtual ~DesktopCaptureDevice();
+
+ // VideoCaptureDevice interface.
+ virtual void Allocate(const media::VideoCaptureCapability& capture_format,
+ EventHandler* observer) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void DeAllocate() OVERRIDE;
+ virtual const Name& device_name() OVERRIDE;
+
+ private:
+ class Core;
+ scoped_refptr<Core> core_;
+ Name name_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopCaptureDevice);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_DESKTOP_CAPTURE_DEVICE_H_
diff --git a/chromium/content/browser/renderer_host/media/desktop_capture_device_unittest.cc b/chromium/content/browser/renderer_host/media/desktop_capture_device_unittest.cc
new file mode 100644
index 00000000000..cf050f5a210
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/desktop_capture_device_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/desktop_capture_device.h"
+
+#include "base/basictypes.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::Expectation;
+using ::testing::InvokeWithoutArgs;
+using ::testing::SaveArg;
+
+namespace content {
+
+namespace {
+
+MATCHER_P2(EqualsCaptureCapability, width, height, "") {
+ return arg.width == width && arg.height == height;
+}
+
+const int kTestFrameWidth1 = 100;
+const int kTestFrameHeight1 = 100;
+const int kTestFrameWidth2 = 200;
+const int kTestFrameHeight2 = 150;
+const int kBufferSize = kTestFrameWidth2 * kTestFrameHeight2 * 4;
+
+const int kFrameRate = 30;
+
+class MockFrameObserver : public media::VideoCaptureDevice::EventHandler {
+ public:
+ MOCK_METHOD0(ReserveOutputBuffer, scoped_refptr<media::VideoFrame>());
+ MOCK_METHOD0(OnError, void());
+ MOCK_METHOD1(OnFrameInfo, void(const media::VideoCaptureCapability& info));
+ MOCK_METHOD1(OnFrameInfoChanged,
+ void(const media::VideoCaptureCapability& info));
+ MOCK_METHOD6(OnIncomingCapturedFrame, void(const uint8* data,
+ int length,
+ base::Time timestamp,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz));
+ MOCK_METHOD2(OnIncomingCapturedVideoFrame,
+ void(const scoped_refptr<media::VideoFrame>& frame,
+ base::Time timestamp));
+};
+
+// TODO(sergeyu): Move this to a separate file where it can be reused.
+class FakeScreenCapturer : public webrtc::ScreenCapturer {
+ public:
+ FakeScreenCapturer()
+ : callback_(NULL),
+ frame_index_(0) {
+ }
+ virtual ~FakeScreenCapturer() {}
+
+ // VideoFrameCapturer interface.
+ virtual void Start(Callback* callback) OVERRIDE {
+ callback_ = callback;
+ }
+
+ virtual void Capture(const webrtc::DesktopRegion& region) OVERRIDE {
+ webrtc::DesktopSize size;
+ if (frame_index_ % 2 == 0) {
+ size = webrtc::DesktopSize(kTestFrameWidth1, kTestFrameHeight1);
+ } else {
+ size = webrtc::DesktopSize(kTestFrameWidth2, kTestFrameHeight2);
+ }
+ frame_index_++;
+ callback_->OnCaptureCompleted(new webrtc::BasicDesktopFrame(size));
+ }
+
+ virtual void SetMouseShapeObserver(
+ MouseShapeObserver* mouse_shape_observer) OVERRIDE {
+ }
+
+ private:
+ Callback* callback_;
+ int frame_index_;
+};
+
+class DesktopCaptureDeviceTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ worker_pool_ = new base::SequencedWorkerPool(3, "TestCaptureThread");
+ }
+
+ protected:
+ scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+};
+
+} // namespace
+
+// There is currently no screen capturer implementation for ozone. So disable
+// the test that uses a real screen-capturer instead of FakeScreenCapturer.
+// http://crbug.com/260318
+#if defined(USE_OZONE)
+#define MAYBE_Capture DISABLED_Capture
+#else
+#define MAYBE_Capture Capture
+#endif
+TEST_F(DesktopCaptureDeviceTest, MAYBE_Capture) {
+ scoped_ptr<webrtc::DesktopCapturer> capturer(
+ webrtc::ScreenCapturer::Create());
+ DesktopCaptureDevice capture_device(
+ worker_pool_->GetSequencedTaskRunner(worker_pool_->GetSequenceToken()),
+ capturer.Pass());
+ media::VideoCaptureCapability caps;
+ base::WaitableEvent done_event(false, false);
+ int frame_size;
+
+ MockFrameObserver frame_observer;
+ EXPECT_CALL(frame_observer, OnFrameInfo(_))
+ .WillOnce(SaveArg<0>(&caps));
+ EXPECT_CALL(frame_observer, OnError())
+ .Times(0);
+ EXPECT_CALL(frame_observer, OnIncomingCapturedFrame(_, _, _, _, _, _))
+ .WillRepeatedly(DoAll(
+ SaveArg<1>(&frame_size),
+ InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
+
+ media::VideoCaptureCapability capture_format(
+ 640, 480, kFrameRate, media::VideoCaptureCapability::kI420, 0, false,
+ media::ConstantResolutionVideoCaptureDevice);
+ capture_device.Allocate(capture_format, &frame_observer);
+ capture_device.Start();
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ capture_device.Stop();
+ capture_device.DeAllocate();
+
+ EXPECT_GT(caps.width, 0);
+ EXPECT_GT(caps.height, 0);
+ EXPECT_EQ(kFrameRate, caps.frame_rate);
+ EXPECT_EQ(media::VideoCaptureCapability::kARGB, caps.color);
+ EXPECT_FALSE(caps.interlaced);
+
+ EXPECT_EQ(caps.width * caps.height * 4, frame_size);
+}
+
+// Test that screen capturer behaves correctly if the source frame size changes
+// but the caller cannot cope with variable resolution output.
+TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) {
+ FakeScreenCapturer* mock_capturer = new FakeScreenCapturer();
+
+ DesktopCaptureDevice capture_device(
+ worker_pool_->GetSequencedTaskRunner(worker_pool_->GetSequenceToken()),
+ scoped_ptr<webrtc::DesktopCapturer>(mock_capturer));
+
+ media::VideoCaptureCapability caps;
+ base::WaitableEvent done_event(false, false);
+ int frame_size;
+
+ MockFrameObserver frame_observer;
+ Expectation frame_info_called = EXPECT_CALL(frame_observer, OnFrameInfo(_))
+ .WillOnce(SaveArg<0>(&caps));
+ EXPECT_CALL(frame_observer, OnFrameInfoChanged(_))
+ .Times(0);
+ EXPECT_CALL(frame_observer, OnError())
+ .Times(0);
+ EXPECT_CALL(frame_observer, OnIncomingCapturedFrame(_, _, _, _, _, _))
+ .After(frame_info_called)
+ .WillRepeatedly(DoAll(
+ SaveArg<1>(&frame_size),
+ InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
+
+ media::VideoCaptureCapability capture_format(
+ kTestFrameWidth1,
+ kTestFrameHeight1,
+ kFrameRate,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+
+ capture_device.Allocate(capture_format, &frame_observer);
+ capture_device.Start();
+
+ // Capture at least two frames, to ensure that the source frame size has
+ // changed while capturing.
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+
+ capture_device.Stop();
+ capture_device.DeAllocate();
+
+ EXPECT_EQ(kTestFrameWidth1, caps.width);
+ EXPECT_EQ(kTestFrameHeight1, caps.height);
+ EXPECT_EQ(kFrameRate, caps.frame_rate);
+ EXPECT_EQ(media::VideoCaptureCapability::kARGB, caps.color);
+ EXPECT_FALSE(caps.interlaced);
+
+ EXPECT_EQ(caps.width * caps.height * 4, frame_size);
+}
+
+// Test that screen capturer behaves correctly if the source frame size changes
+// and the caller can cope with variable resolution output.
+TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeVariableResolution) {
+ FakeScreenCapturer* mock_capturer = new FakeScreenCapturer();
+
+ DesktopCaptureDevice capture_device(
+ worker_pool_->GetSequencedTaskRunner(worker_pool_->GetSequenceToken()),
+ scoped_ptr<webrtc::DesktopCapturer>(mock_capturer));
+
+ media::VideoCaptureCapability caps;
+ base::WaitableEvent done_event(false, false);
+
+ MockFrameObserver frame_observer;
+ Expectation frame_info_called = EXPECT_CALL(frame_observer, OnFrameInfo(_))
+ .WillOnce(SaveArg<0>(&caps));
+ Expectation first_info_changed = EXPECT_CALL(frame_observer,
+ OnFrameInfoChanged(EqualsCaptureCapability(kTestFrameWidth2,
+ kTestFrameHeight2)))
+ .After(frame_info_called);
+ Expectation second_info_changed = EXPECT_CALL(frame_observer,
+ OnFrameInfoChanged(EqualsCaptureCapability(kTestFrameWidth1,
+ kTestFrameHeight1)))
+ .After(first_info_changed);
+ EXPECT_CALL(frame_observer, OnFrameInfoChanged(_))
+ .Times(AnyNumber())
+ .After(second_info_changed);
+ EXPECT_CALL(frame_observer, OnError())
+ .Times(0);
+ EXPECT_CALL(frame_observer, OnIncomingCapturedFrame(_, _, _, _, _, _))
+ .After(frame_info_called)
+ .WillRepeatedly(
+ InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal));
+
+ media::VideoCaptureCapability capture_format(
+ kTestFrameWidth2,
+ kTestFrameHeight2,
+ kFrameRate,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::VariableResolutionVideoCaptureDevice);
+
+ capture_device.Allocate(capture_format, &frame_observer);
+ capture_device.Start();
+
+ // Capture at least three frames, to ensure that the source frame size has
+ // changed at least twice while capturing.
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+
+ capture_device.Stop();
+ capture_device.DeAllocate();
+
+ EXPECT_EQ(kTestFrameWidth1, caps.width);
+ EXPECT_EQ(kTestFrameHeight1, caps.height);
+ EXPECT_EQ(kFrameRate, caps.frame_rate);
+ EXPECT_EQ(media::VideoCaptureCapability::kARGB, caps.color);
+ EXPECT_FALSE(caps.interlaced);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/device_request_message_filter.cc b/chromium/content/browser/renderer_host/media/device_request_message_filter.cc
new file mode 100644
index 00000000000..770e800d402
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/device_request_message_filter.cc
@@ -0,0 +1,219 @@
+// 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 "content/browser/renderer_host/media/device_request_message_filter.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/common/media/media_stream_messages.h"
+#include "content/public/browser/resource_context.h"
+#include "crypto/hmac.h"
+
+// Clears the MediaStreamDevice.name from all devices in |device_list|.
+static void ClearDeviceLabels(content::StreamDeviceInfoArray* devices) {
+ for (content::StreamDeviceInfoArray::iterator device_itr = devices->begin();
+ device_itr != devices->end();
+ ++device_itr) {
+ device_itr->device.name.clear();
+ }
+}
+
+namespace content {
+
+DeviceRequestMessageFilter::DeviceRequestMessageFilter(
+ ResourceContext* resource_context,
+ MediaStreamManager* media_stream_manager)
+ : resource_context_(resource_context),
+ media_stream_manager_(media_stream_manager) {
+ DCHECK(resource_context);
+ DCHECK(media_stream_manager);
+}
+
+DeviceRequestMessageFilter::~DeviceRequestMessageFilter() {
+ DCHECK(requests_.empty());
+}
+
+struct DeviceRequestMessageFilter::DeviceRequest {
+ DeviceRequest(int request_id,
+ const GURL& origin,
+ const std::string& audio_devices_label,
+ const std::string& video_devices_label)
+ : request_id(request_id),
+ origin(origin),
+ has_audio_returned(false),
+ has_video_returned(false),
+ audio_devices_label(audio_devices_label),
+ video_devices_label(video_devices_label) {}
+
+ int request_id;
+ GURL origin;
+ bool has_audio_returned;
+ bool has_video_returned;
+ std::string audio_devices_label;
+ std::string video_devices_label;
+ StreamDeviceInfoArray audio_devices;
+ StreamDeviceInfoArray video_devices;
+};
+
+void DeviceRequestMessageFilter::StreamGenerated(
+ const std::string& label,
+ const StreamDeviceInfoArray& audio_devices,
+ const StreamDeviceInfoArray& video_devices) {
+ NOTIMPLEMENTED();
+}
+
+void DeviceRequestMessageFilter::StreamGenerationFailed(
+ const std::string& label) {
+ NOTIMPLEMENTED();
+}
+
+void DeviceRequestMessageFilter::StopGeneratedStream(
+ const std::string& label) {
+ NOTIMPLEMENTED();
+}
+
+void DeviceRequestMessageFilter::DevicesEnumerated(
+ const std::string& label,
+ const StreamDeviceInfoArray& new_devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Look up the DeviceRequest by id.
+ DeviceRequestList::iterator request_it = requests_.begin();
+ for (; request_it != requests_.end(); ++request_it) {
+ if (label == request_it->audio_devices_label ||
+ label == request_it->video_devices_label) {
+ break;
+ }
+ }
+ DCHECK(request_it != requests_.end());
+
+ StreamDeviceInfoArray* audio_devices = &request_it->audio_devices;
+ StreamDeviceInfoArray* video_devices = &request_it->video_devices;
+
+ // Store hmac'd device ids instead of raw device ids.
+ if (label == request_it->audio_devices_label) {
+ request_it->has_audio_returned = true;
+ DCHECK(audio_devices->empty());
+ HmacDeviceIds(request_it->origin, new_devices, audio_devices);
+ } else {
+ DCHECK(label == request_it->video_devices_label);
+ request_it->has_video_returned = true;
+ DCHECK(video_devices->empty());
+ HmacDeviceIds(request_it->origin, new_devices, video_devices);
+ }
+
+ if (!request_it->has_audio_returned || !request_it->has_video_returned) {
+ // Wait for the rest of the devices to complete.
+ return;
+ }
+
+ // Query for mic and camera permissions.
+ if (!resource_context_->AllowMicAccess(request_it->origin))
+ ClearDeviceLabels(audio_devices);
+ if (!resource_context_->AllowCameraAccess(request_it->origin))
+ ClearDeviceLabels(video_devices);
+
+ // Both audio and video devices are ready for copying.
+ StreamDeviceInfoArray all_devices = *audio_devices;
+ all_devices.insert(
+ all_devices.end(), video_devices->begin(), video_devices->end());
+
+ Send(new MediaStreamMsg_GetSourcesACK(request_it->request_id, all_devices));
+
+ // TODO(vrk): Rename StopGeneratedStream() to CancelDeviceRequest().
+ media_stream_manager_->StopGeneratedStream(request_it->audio_devices_label);
+ media_stream_manager_->StopGeneratedStream(request_it->video_devices_label);
+ requests_.erase(request_it);
+}
+
+void DeviceRequestMessageFilter::DeviceOpened(
+ const std::string& label,
+ const StreamDeviceInfo& video_device) {
+ NOTIMPLEMENTED();
+}
+
+bool DeviceRequestMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(DeviceRequestMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_GetSources, OnGetSources)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void DeviceRequestMessageFilter::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Since the IPC channel is gone, cancel outstanding device requests.
+ for (DeviceRequestList::iterator it = requests_.begin();
+ it != requests_.end();
+ ++it) {
+ // TODO(vrk): Rename StopGeneratedStream() to CancelDeviceRequest().
+ media_stream_manager_->StopGeneratedStream(it->audio_devices_label);
+ media_stream_manager_->StopGeneratedStream(it->video_devices_label);
+ }
+ requests_.clear();
+}
+
+void DeviceRequestMessageFilter::HmacDeviceIds(
+ const GURL& origin,
+ const StreamDeviceInfoArray& raw_devices,
+ StreamDeviceInfoArray* devices_with_guids) {
+ DCHECK(devices_with_guids);
+
+ // Replace raw ids with hmac'd ids before returning to renderer process.
+ for (StreamDeviceInfoArray::const_iterator device_itr = raw_devices.begin();
+ device_itr != raw_devices.end();
+ ++device_itr) {
+ crypto::HMAC hmac(crypto::HMAC::SHA256);
+ const size_t digest_length = hmac.DigestLength();
+ std::vector<uint8> digest(digest_length);
+ bool result = hmac.Init(origin.spec()) &&
+ hmac.Sign(device_itr->device.id, &digest[0], digest.size());
+ DCHECK(result);
+ if (result) {
+ StreamDeviceInfo current_device_info = *device_itr;
+ current_device_info.device.id =
+ StringToLowerASCII(base::HexEncode(&digest[0], digest.size()));
+ devices_with_guids->push_back(current_device_info);
+ }
+ }
+}
+
+bool DeviceRequestMessageFilter::DoesRawIdMatchGuid(
+ const GURL& security_origin,
+ const std::string& device_guid,
+ const std::string& raw_device_id) {
+ crypto::HMAC hmac(crypto::HMAC::SHA256);
+ bool result = hmac.Init(security_origin.spec());
+ DCHECK(result);
+ std::vector<uint8> converted_guid;
+ base::HexStringToBytes(device_guid, &converted_guid);
+ return hmac.Verify(
+ raw_device_id,
+ base::StringPiece(reinterpret_cast<const char*>(&converted_guid[0]),
+ converted_guid.size()));
+}
+
+void DeviceRequestMessageFilter::OnGetSources(int request_id,
+ const GURL& security_origin) {
+ // Make request to get audio devices.
+ const std::string& audio_label = media_stream_manager_->EnumerateDevices(
+ this, -1, -1, -1, MEDIA_DEVICE_AUDIO_CAPTURE, security_origin);
+ DCHECK(!audio_label.empty());
+
+ // Make request for video devices.
+ const std::string& video_label = media_stream_manager_->EnumerateDevices(
+ this, -1, -1, -1, MEDIA_DEVICE_VIDEO_CAPTURE, security_origin);
+ DCHECK(!video_label.empty());
+
+ requests_.push_back(DeviceRequest(
+ request_id, security_origin, audio_label, video_label));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/device_request_message_filter.h b/chromium/content/browser/renderer_host/media/device_request_message_filter.h
new file mode 100644
index 00000000000..048e2157871
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/device_request_message_filter.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_
+
+#include <map>
+#include <string>
+
+#include "base/synchronization/lock.h"
+#include "content/browser/renderer_host/media/media_stream_requester.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class MediaStreamManager;
+class ResourceContext;
+
+// DeviceRequestMessageFilter used to delegate requests from the
+// MediaStreamCenter.
+class CONTENT_EXPORT DeviceRequestMessageFilter : public BrowserMessageFilter,
+ public MediaStreamRequester {
+ public:
+ DeviceRequestMessageFilter(ResourceContext* resource_context,
+ MediaStreamManager* media_stream_manager);
+
+ // MediaStreamRequester implementation.
+ // TODO(vrk): Replace MediaStreamRequester interface with a single callback so
+ // we don't have to override all these callbacks we don't care about.
+ // (crbug.com/249476)
+ virtual void StreamGenerated(
+ const std::string& label, const StreamDeviceInfoArray& audio_devices,
+ const StreamDeviceInfoArray& video_devices) OVERRIDE;
+ virtual void StreamGenerationFailed(const std::string& label) OVERRIDE;
+ virtual void StopGeneratedStream(const std::string& label) OVERRIDE;
+ virtual void DeviceOpened(const std::string& label,
+ const StreamDeviceInfo& video_device) OVERRIDE;
+ // DevicesEnumerated() is the only callback we're interested in.
+ virtual void DevicesEnumerated(const std::string& label,
+ const StreamDeviceInfoArray& devices) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OnChannelClosing() OVERRIDE;
+
+ // Helper method that checks whether the GUID generated by
+ // DeviceRequestMessageFilter matches the given |raw_device_id|.
+ static bool DoesRawIdMatchGuid(const GURL& security_origin,
+ const std::string& device_guid,
+ const std::string& raw_device_id);
+
+ protected:
+ virtual ~DeviceRequestMessageFilter();
+
+ private:
+ void OnGetSources(int request_id, const GURL& security_origin);
+ void HmacDeviceIds(const GURL& origin,
+ const StreamDeviceInfoArray& raw_devices,
+ StreamDeviceInfoArray* devices_with_guids);
+
+ // Owned by ProfileIOData which is guaranteed to outlive DRMF.
+ ResourceContext* resource_context_;
+ MediaStreamManager* media_stream_manager_;
+
+ struct DeviceRequest;
+ typedef std::vector<DeviceRequest> DeviceRequestList;
+ // List of all requests.
+ DeviceRequestList requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceRequestMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/device_request_message_filter_unittest.cc b/chromium/content/browser/renderer_host/media/device_request_message_filter_unittest.cc
new file mode 100644
index 00000000000..04a7bb69eea
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/device_request_message_filter_unittest.cc
@@ -0,0 +1,304 @@
+// 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 "base/strings/string_number_conversions.h"
+#include "content/browser/renderer_host/media/device_request_message_filter.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/common/media/media_stream_messages.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+
+namespace content {
+
+static const std::string kAudioLabel = "audio_label";
+static const std::string kVideoLabel = "video_label";
+
+class MockMediaStreamManager : public MediaStreamManager {
+ public:
+ MockMediaStreamManager() {}
+
+ virtual ~MockMediaStreamManager() {}
+
+ MOCK_METHOD6(EnumerateDevices,
+ std::string(MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin));
+ MOCK_METHOD1(StopGeneratedStream, void(const std::string& label));
+
+ std::string DoEnumerateDevices(MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin) {
+ if (type == MEDIA_DEVICE_AUDIO_CAPTURE) {
+ return kAudioLabel;
+ } else {
+ return kVideoLabel;
+ }
+ }
+};
+
+class MockDeviceRequestMessageFilter : public DeviceRequestMessageFilter {
+ public:
+ MockDeviceRequestMessageFilter(MockResourceContext* context,
+ MockMediaStreamManager* manager)
+ : DeviceRequestMessageFilter(context, manager), received_id_(-1) {}
+ StreamDeviceInfoArray requested_devices() { return requested_devices_; }
+ int received_id() { return received_id_; }
+
+ private:
+ virtual ~MockDeviceRequestMessageFilter() {}
+
+ // Override the Send() method to intercept the message that we're sending to
+ // the renderer.
+ virtual bool Send(IPC::Message* reply_msg) OVERRIDE {
+ CHECK(reply_msg);
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MockDeviceRequestMessageFilter, *reply_msg)
+ IPC_MESSAGE_HANDLER(MediaStreamMsg_GetSourcesACK, SaveDevices)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ EXPECT_TRUE(handled);
+
+ delete reply_msg;
+ return true;
+ }
+
+ void SaveDevices(int request_id, const StreamDeviceInfoArray& devices) {
+ received_id_ = request_id;
+ requested_devices_ = devices;
+ }
+
+ int received_id_;
+ StreamDeviceInfoArray requested_devices_;
+};
+
+class DeviceRequestMessageFilterTest : public testing::Test {
+ public:
+ DeviceRequestMessageFilterTest() : next_device_id_(0) {}
+
+ void RunTest(int number_audio_devices, int number_video_devices) {
+ AddAudioDevices(number_audio_devices);
+ AddVideoDevices(number_video_devices);
+ GURL origin("https://test.com");
+ EXPECT_CALL(*media_stream_manager_,
+ EnumerateDevices(_, _, _, _, MEDIA_DEVICE_AUDIO_CAPTURE, _))
+ .Times(1);
+ EXPECT_CALL(*media_stream_manager_,
+ EnumerateDevices(_, _, _, _, MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1);
+ // Send message to get devices. Should trigger 2 EnumerateDevice() requests.
+ const int kRequestId = 123;
+ SendGetSourcesMessage(kRequestId, origin);
+
+ // Run audio callback. Because there's still an outstanding video request,
+ // this should not populate |message|.
+ FireAudioDeviceCallback();
+ EXPECT_EQ(0u, host_->requested_devices().size());
+
+ // After the video device callback is fired, |message| should be populated.
+ EXPECT_CALL(*media_stream_manager_, StopGeneratedStream(kAudioLabel))
+ .Times(1);
+ EXPECT_CALL(*media_stream_manager_, StopGeneratedStream(kVideoLabel))
+ .Times(1);
+ FireVideoDeviceCallback();
+ EXPECT_EQ(static_cast<size_t>(number_audio_devices + number_video_devices),
+ host_->requested_devices().size());
+
+ EXPECT_EQ(kRequestId, host_->received_id());
+ // Check to make sure no devices have raw ids.
+ EXPECT_FALSE(DoesContainRawIds(host_->requested_devices()));
+
+ // Check to make sure every GUID produced matches a raw device id.
+ EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->requested_devices(), origin));
+ }
+
+ bool AreLabelsPresent(MediaStreamType type) {
+ const StreamDeviceInfoArray& devices = host_->requested_devices();
+ for (size_t i = 0; i < devices.size(); i++) {
+ if (devices[i].device.type == type && !devices[i].device.name.empty())
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ virtual ~DeviceRequestMessageFilterTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+ io_thread_.reset(
+ new TestBrowserThread(BrowserThread::IO, message_loop_.get()));
+
+ media_stream_manager_.reset(new MockMediaStreamManager());
+ ON_CALL(*media_stream_manager_, EnumerateDevices(_, _, _, _, _, _))
+ .WillByDefault(Invoke(media_stream_manager_.get(),
+ &MockMediaStreamManager::DoEnumerateDevices));
+
+ resource_context_.reset(new MockResourceContext(NULL));
+ host_ = new MockDeviceRequestMessageFilter(resource_context_.get(),
+ media_stream_manager_.get());
+ }
+
+ scoped_refptr<MockDeviceRequestMessageFilter> host_;
+ scoped_ptr<MockMediaStreamManager> media_stream_manager_;
+ scoped_ptr<MockResourceContext> resource_context_;
+ StreamDeviceInfoArray physical_audio_devices_;
+ StreamDeviceInfoArray physical_video_devices_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<TestBrowserThread> io_thread_;
+
+ private:
+ void AddAudioDevices(int number_of_devices) {
+ for (int i = 0; i < number_of_devices; i++) {
+ physical_audio_devices_.push_back(
+ StreamDeviceInfo(MEDIA_DEVICE_AUDIO_CAPTURE,
+ "/dev/audio/" + base::IntToString(next_device_id_),
+ "Audio Device" + base::IntToString(next_device_id_),
+ false));
+ next_device_id_++;
+ }
+ }
+
+ void AddVideoDevices(int number_of_devices) {
+ for (int i = 0; i < number_of_devices; i++) {
+ physical_video_devices_.push_back(
+ StreamDeviceInfo(MEDIA_DEVICE_VIDEO_CAPTURE,
+ "/dev/video/" + base::IntToString(next_device_id_),
+ "Video Device" + base::IntToString(next_device_id_),
+ false));
+ next_device_id_++;
+ }
+ }
+
+ void SendGetSourcesMessage(int request_id, const GURL& origin) {
+ // Since we're not actually sending IPC messages, this is a throw-away
+ // value.
+ bool message_was_ok;
+ host_->OnMessageReceived(MediaStreamHostMsg_GetSources(request_id, origin),
+ &message_was_ok);
+ }
+
+ void FireAudioDeviceCallback() {
+ host_->DevicesEnumerated(kAudioLabel, physical_audio_devices_);
+ }
+
+ void FireVideoDeviceCallback() {
+ host_->DevicesEnumerated(kVideoLabel, physical_video_devices_);
+ }
+
+ bool DoesContainRawIds(const StreamDeviceInfoArray& devices) {
+ for (size_t i = 0; i < devices.size(); i++) {
+ for (size_t j = 0; j < physical_audio_devices_.size(); ++j) {
+ if (physical_audio_devices_[j].device.id == devices[i].device.id)
+ return true;
+ }
+ for (size_t j = 0; j < physical_video_devices_.size(); ++j) {
+ if (physical_video_devices_[j].device.id == devices[i].device.id)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool DoesEveryDeviceMapToRawId(const StreamDeviceInfoArray& devices,
+ const GURL& origin) {
+ for (size_t i = 0; i < devices.size(); i++) {
+ bool found_match = false;
+ for (size_t j = 0; j < physical_audio_devices_.size(); ++j) {
+ if (DeviceRequestMessageFilter::DoesRawIdMatchGuid(
+ origin,
+ devices[i].device.id,
+ physical_audio_devices_[j].device.id)) {
+ EXPECT_FALSE(found_match);
+ found_match = true;
+ }
+ }
+ for (size_t j = 0; j < physical_video_devices_.size(); ++j) {
+ if (DeviceRequestMessageFilter::DoesRawIdMatchGuid(
+ origin,
+ devices[i].device.id,
+ physical_video_devices_[j].device.id)) {
+ EXPECT_FALSE(found_match);
+ found_match = true;
+ }
+ }
+ if (!found_match)
+ return false;
+ }
+ return true;
+ }
+
+ int next_device_id_;
+};
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_AudioAndVideoDevices) {
+ // Runs through test with 1 audio and 1 video device.
+ RunTest(1, 1);
+}
+
+TEST_F(DeviceRequestMessageFilterTest,
+ TestGetSources_MultipleAudioAndVideoDevices) {
+ // Runs through test with 3 audio devices and 2 video devices.
+ RunTest(3, 2);
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoVideoDevices) {
+ // Runs through test with 4 audio devices and 0 video devices.
+ RunTest(4, 0);
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoAudioDevices) {
+ // Runs through test with 0 audio devices and 3 video devices.
+ RunTest(0, 3);
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoDevices) {
+ // Runs through test with no devices.
+ RunTest(0, 0);
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_DenyMicDenyCamera) {
+ resource_context_->set_mic_access(false);
+ resource_context_->set_camera_access(false);
+ RunTest(3, 3);
+ EXPECT_FALSE(AreLabelsPresent(MEDIA_DEVICE_AUDIO_CAPTURE));
+ EXPECT_FALSE(AreLabelsPresent(MEDIA_DEVICE_VIDEO_CAPTURE));
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_AllowMicDenyCamera) {
+ resource_context_->set_mic_access(true);
+ resource_context_->set_camera_access(false);
+ RunTest(3, 3);
+ EXPECT_TRUE(AreLabelsPresent(MEDIA_DEVICE_AUDIO_CAPTURE));
+ EXPECT_FALSE(AreLabelsPresent(MEDIA_DEVICE_VIDEO_CAPTURE));
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_DenyMicAllowCamera) {
+ resource_context_->set_mic_access(false);
+ resource_context_->set_camera_access(true);
+ RunTest(3, 3);
+ EXPECT_FALSE(AreLabelsPresent(MEDIA_DEVICE_AUDIO_CAPTURE));
+ EXPECT_TRUE(AreLabelsPresent(MEDIA_DEVICE_VIDEO_CAPTURE));
+}
+
+TEST_F(DeviceRequestMessageFilterTest, TestGetSources_AllowMicAllowCamera) {
+ resource_context_->set_mic_access(true);
+ resource_context_->set_camera_access(true);
+ RunTest(3, 3);
+ EXPECT_TRUE(AreLabelsPresent(MEDIA_DEVICE_AUDIO_CAPTURE));
+ EXPECT_TRUE(AreLabelsPresent(MEDIA_DEVICE_VIDEO_CAPTURE));
+}
+
+}; // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
new file mode 100644
index 00000000000..9135b719960
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
@@ -0,0 +1,224 @@
+// 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 "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
+
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "content/common/media/media_stream_messages.h"
+#include "content/common/media/media_stream_options.h"
+#include "url/gurl.h"
+
+namespace content {
+
+MediaStreamDispatcherHost::MediaStreamDispatcherHost(
+ int render_process_id,
+ MediaStreamManager* media_stream_manager)
+ : render_process_id_(render_process_id),
+ media_stream_manager_(media_stream_manager) {
+}
+
+void MediaStreamDispatcherHost::StreamGenerated(
+ const std::string& label,
+ const StreamDeviceInfoArray& audio_devices,
+ const StreamDeviceInfoArray& video_devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "MediaStreamDispatcherHost::StreamGenerated("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ DCHECK(it != streams_.end());
+ StreamRequest request = it->second;
+
+ Send(new MediaStreamMsg_StreamGenerated(
+ request.render_view_id, request.page_request_id, label, audio_devices,
+ video_devices));
+}
+
+void MediaStreamDispatcherHost::StreamGenerationFailed(
+ const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "MediaStreamDispatcherHost::StreamGenerationFailed("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ DCHECK(it != streams_.end());
+ StreamRequest request = it->second;
+ streams_.erase(it);
+
+ Send(new MediaStreamMsg_StreamGenerationFailed(request.render_view_id,
+ request.page_request_id));
+}
+
+void MediaStreamDispatcherHost::StopGeneratedStream(
+ const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "MediaStreamDispatcherHost::StopGeneratedStream("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ DCHECK(it != streams_.end());
+ StreamRequest request = it->second;
+ streams_.erase(it);
+
+ Send(new MediaStreamMsg_StopGeneratedStream(request.render_view_id, label));
+}
+
+void MediaStreamDispatcherHost::DevicesEnumerated(
+ const std::string& label,
+ const StreamDeviceInfoArray& devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "MediaStreamDispatcherHost::DevicesEnumerated("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ DCHECK(it != streams_.end());
+ StreamRequest request = it->second;
+
+ Send(new MediaStreamMsg_DevicesEnumerated(
+ request.render_view_id, request.page_request_id, label, devices));
+}
+
+void MediaStreamDispatcherHost::DeviceOpened(
+ const std::string& label,
+ const StreamDeviceInfo& video_device) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "MediaStreamDispatcherHost::DeviceOpened("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ DCHECK(it != streams_.end());
+ StreamRequest request = it->second;
+
+ Send(new MediaStreamMsg_DeviceOpened(
+ request.render_view_id, request.page_request_id, label, video_device));
+}
+
+bool MediaStreamDispatcherHost::OnMessageReceived(
+ const IPC::Message& message, bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MediaStreamDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_GenerateStream, OnGenerateStream)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_CancelGenerateStream,
+ OnCancelGenerateStream)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_StopGeneratedStream,
+ OnStopGeneratedStream)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_EnumerateDevices,
+ OnEnumerateDevices)
+ IPC_MESSAGE_HANDLER(MediaStreamHostMsg_OpenDevice,
+ OnOpenDevice)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void MediaStreamDispatcherHost::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+ DVLOG(1) << "MediaStreamDispatcherHost::OnChannelClosing";
+
+ // Since the IPC channel is gone, close all requesting/requested streams.
+ for (StreamMap::iterator it = streams_.begin();
+ it != streams_.end();
+ ++it) {
+ std::string label = it->first;
+ media_stream_manager_->StopGeneratedStream(label);
+ }
+ // Clear the map after we have stopped all the streams.
+ streams_.clear();
+}
+
+MediaStreamDispatcherHost::~MediaStreamDispatcherHost() {
+ DCHECK(streams_.empty());
+}
+
+void MediaStreamDispatcherHost::OnGenerateStream(
+ int render_view_id,
+ int page_request_id,
+ const StreamOptions& components,
+ const GURL& security_origin) {
+ DVLOG(1) << "MediaStreamDispatcherHost::OnGenerateStream("
+ << render_view_id << ", "
+ << page_request_id << ", ["
+ << " audio:" << components.audio_type
+ << " video:" << components.video_type
+ << " ], "
+ << security_origin.spec() << ")";
+
+ const std::string& label = media_stream_manager_->GenerateStream(
+ this, render_process_id_, render_view_id, page_request_id,
+ components, security_origin);
+ if (label.empty()) {
+ Send(new MediaStreamMsg_StreamGenerationFailed(render_view_id,
+ page_request_id));
+ } else {
+ streams_[label] = StreamRequest(render_view_id, page_request_id);
+ }
+}
+
+void MediaStreamDispatcherHost::OnCancelGenerateStream(int render_view_id,
+ int page_request_id) {
+ DVLOG(1) << "MediaStreamDispatcherHost::OnCancelGenerateStream("
+ << render_view_id << ", "
+ << page_request_id << ")";
+
+ for (StreamMap::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ if (it->second.render_view_id == render_view_id &&
+ it->second.page_request_id == page_request_id) {
+ media_stream_manager_->CancelRequest(it->first);
+ }
+ }
+}
+
+void MediaStreamDispatcherHost::OnStopGeneratedStream(
+ int render_view_id, const std::string& label) {
+ DVLOG(1) << "MediaStreamDispatcherHost::OnStopGeneratedStream("
+ << ", {label = " << label << "})";
+
+ StreamMap::iterator it = streams_.find(label);
+ if (it == streams_.end())
+ return;
+
+ media_stream_manager_->StopGeneratedStream(label);
+ streams_.erase(it);
+}
+
+void MediaStreamDispatcherHost::OnEnumerateDevices(
+ int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin) {
+ DVLOG(1) << "MediaStreamDispatcherHost::OnEnumerateDevices("
+ << render_view_id << ", "
+ << page_request_id << ", "
+ << type << ", "
+ << security_origin.spec() << ")";
+
+ const std::string& label = media_stream_manager_->EnumerateDevices(
+ this, render_process_id_, render_view_id, page_request_id,
+ type, security_origin);
+ DCHECK(!label.empty());
+ streams_[label] = StreamRequest(render_view_id, page_request_id);
+}
+
+void MediaStreamDispatcherHost::OnOpenDevice(
+ int render_view_id,
+ int page_request_id,
+ const std::string& device_id,
+ MediaStreamType type,
+ const GURL& security_origin) {
+ DVLOG(1) << "MediaStreamDispatcherHost::OnOpenDevice("
+ << render_view_id << ", "
+ << page_request_id << ", device_id: "
+ << device_id.c_str() << ", type: "
+ << type << ", "
+ << security_origin.spec() << ")";
+
+ const std::string& label = media_stream_manager_->OpenDevice(
+ this, render_process_id_, render_view_id, page_request_id,
+ device_id, type, security_origin);
+ DCHECK(!label.empty());
+ streams_[label] = StreamRequest(render_view_id, page_request_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.h b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.h
new file mode 100644
index 00000000000..cfc69137a39
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.h
@@ -0,0 +1,95 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_DISPATCHER_HOST_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/media_stream_requester.h"
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+class MediaStreamManager;
+
+// MediaStreamDispatcherHost is a delegate for Media Stream API messages used by
+// MediaStreamImpl. It's the complement of MediaStreamDispatcher
+// (owned by RenderView).
+class CONTENT_EXPORT MediaStreamDispatcherHost : public BrowserMessageFilter,
+ public MediaStreamRequester {
+ public:
+ MediaStreamDispatcherHost(int render_process_id,
+ MediaStreamManager* media_stream_manager);
+
+ // MediaStreamRequester implementation.
+ virtual void StreamGenerated(
+ const std::string& label,
+ const StreamDeviceInfoArray& audio_devices,
+ const StreamDeviceInfoArray& video_devices) OVERRIDE;
+ virtual void StreamGenerationFailed(const std::string& label) OVERRIDE;
+ virtual void StopGeneratedStream(const std::string& label) OVERRIDE;
+ virtual void DevicesEnumerated(const std::string& label,
+ const StreamDeviceInfoArray& devices) OVERRIDE;
+ virtual void DeviceOpened(const std::string& label,
+ const StreamDeviceInfo& video_device) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OnChannelClosing() OVERRIDE;
+
+ protected:
+ virtual ~MediaStreamDispatcherHost();
+
+ private:
+ friend class MockMediaStreamDispatcherHost;
+
+ void OnGenerateStream(int render_view_id,
+ int page_request_id,
+ const StreamOptions& components,
+ const GURL& security_origin);
+ void OnCancelGenerateStream(int render_view_id,
+ int page_request_id);
+ void OnStopGeneratedStream(int render_view_id, const std::string& label);
+
+ void OnEnumerateDevices(int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin);
+
+ void OnOpenDevice(int render_view_id,
+ int page_request_id,
+ const std::string& device_id,
+ MediaStreamType type,
+ const GURL& security_origin);
+
+ int render_process_id_;
+ MediaStreamManager* media_stream_manager_;
+
+ struct StreamRequest {
+ StreamRequest() : render_view_id(0), page_request_id(0) {}
+ StreamRequest(int render_view_id, int page_request_id)
+ : render_view_id(render_view_id),
+ page_request_id(page_request_id ) {
+ }
+ int render_view_id;
+ // Id of the request generated by MediaStreamDispatcher.
+ int page_request_id;
+ };
+
+ typedef std::map<std::string, StreamRequest> StreamMap;
+ // Streams generated for this host.
+ StreamMap streams_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
new file mode 100644
index 00000000000..05804266035
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
@@ -0,0 +1,353 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/common/media/media_stream_messages.h"
+#include "content/common/media/media_stream_options.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
+#include "ipc/ipc_message_macros.h"
+#include "media/audio/audio_manager.h"
+#include "media/video/capture/fake_video_capture_device.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DeleteArg;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+const int kProcessId = 5;
+const int kRenderId = 6;
+const int kPageRequestId = 7;
+
+namespace content {
+
+class MockMediaStreamDispatcherHost : public MediaStreamDispatcherHost,
+ public TestContentBrowserClient {
+ public:
+ MockMediaStreamDispatcherHost(
+ const scoped_refptr<base::MessageLoopProxy>& message_loop,
+ MediaStreamManager* manager)
+ : MediaStreamDispatcherHost(kProcessId, manager),
+ message_loop_(message_loop) {}
+
+ // A list of mock methods.
+ MOCK_METHOD4(OnStreamGenerated,
+ void(int routing_id, int request_id, int audio_array_size,
+ int video_array_size));
+ MOCK_METHOD2(OnStreamGenerationFailed, void(int routing_id, int request_id));
+ MOCK_METHOD1(OnStopGeneratedStreamFromBrowser,
+ void(int routing_id));
+
+ // Accessor to private functions.
+ void OnGenerateStream(int page_request_id,
+ const StreamOptions& components,
+ const base::Closure& quit_closure) {
+ quit_closure_ = quit_closure;
+ MediaStreamDispatcherHost::OnGenerateStream(
+ kRenderId, page_request_id, components, GURL());
+ }
+
+ void OnStopGeneratedStream(const std::string& label) {
+ MediaStreamDispatcherHost::OnStopGeneratedStream(kRenderId, label);
+ }
+
+ // Return the number of streams that have been opened or is being open.
+ size_t NumberOfStreams() {
+ return streams_.size();
+ }
+
+ std::string label_;
+ StreamDeviceInfoArray audio_devices_;
+ StreamDeviceInfoArray video_devices_;
+
+ private:
+ virtual ~MockMediaStreamDispatcherHost() {}
+
+ // This method is used to dispatch IPC messages to the renderer. We intercept
+ // these messages here and dispatch to our mock methods to verify the
+ // conversation between this object and the renderer.
+ virtual bool Send(IPC::Message* message) OVERRIDE {
+ CHECK(message);
+
+ // In this method we dispatch the messages to the according handlers as if
+ // we are the renderer.
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MockMediaStreamDispatcherHost, *message)
+ IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerated, OnStreamGenerated)
+ IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerationFailed,
+ OnStreamGenerationFailed)
+ IPC_MESSAGE_HANDLER(MediaStreamMsg_StopGeneratedStream,
+ OnStopGeneratedStreamFromBrowser)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ EXPECT_TRUE(handled);
+
+ delete message;
+ return true;
+ }
+
+ // These handler methods do minimal things and delegate to the mock methods.
+ void OnStreamGenerated(
+ const IPC::Message& msg,
+ int request_id,
+ std::string label,
+ StreamDeviceInfoArray audio_device_list,
+ StreamDeviceInfoArray video_device_list) {
+ OnStreamGenerated(msg.routing_id(), request_id, audio_device_list.size(),
+ video_device_list.size());
+ // Notify that the event have occured.
+ message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure_));
+ label_ = label;
+ audio_devices_ = audio_device_list;
+ video_devices_ = video_device_list;
+ }
+
+ void OnStreamGenerationFailed(const IPC::Message& msg, int request_id) {
+ OnStreamGenerationFailed(msg.routing_id(), request_id);
+ if (!quit_closure_.is_null())
+ message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure_));
+ label_= "";
+ }
+
+ void OnStopGeneratedStreamFromBrowser(const IPC::Message& msg,
+ const std::string& label) {
+ OnStopGeneratedStreamFromBrowser(msg.routing_id());
+ // Notify that the event have occured.
+ if (!quit_closure_.is_null())
+ message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure_));
+ label_ = "";
+ }
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+ base::Closure quit_closure_;
+};
+
+class MockMediaStreamUIProxy : public FakeMediaStreamUIProxy {
+ public:
+ MOCK_METHOD1(OnStarted, void(const base::Closure& stop));
+};
+
+class MediaStreamDispatcherHostTest : public testing::Test {
+ public:
+ MediaStreamDispatcherHostTest()
+ : old_browser_client_(NULL),
+ thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+ // Create our own MediaStreamManager.
+ audio_manager_.reset(media::AudioManager::Create());
+ media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
+ // Make sure we use fake devices to avoid long delays.
+ media_stream_manager_->UseFakeDevice();
+
+ host_ = new MockMediaStreamDispatcherHost(base::MessageLoopProxy::current(),
+ media_stream_manager_.get());
+
+ // Use the fake content client and browser.
+ content_client_.reset(new TestContentClient());
+ SetContentClient(content_client_.get());
+ old_browser_client_ = SetBrowserClientForTesting(host_.get());
+ }
+
+ virtual ~MediaStreamDispatcherHostTest() {
+ // Recover the old browser client and content client.
+ SetBrowserClientForTesting(old_browser_client_);
+ content_client_.reset();
+ media_stream_manager_->WillDestroyCurrentMessageLoop();
+ }
+
+ protected:
+ virtual void SetupFakeUI(bool expect_started) {
+ scoped_ptr<MockMediaStreamUIProxy> stream_ui(new MockMediaStreamUIProxy());
+ if (expect_started) {
+ EXPECT_CALL(*stream_ui, OnStarted(_));
+ }
+ media_stream_manager_->UseFakeUI(
+ stream_ui.PassAs<FakeMediaStreamUIProxy>());
+ }
+
+ void GenerateStreamAndWaitForResult(int page_request_id,
+ const StreamOptions& options) {
+ base::RunLoop run_loop;
+ host_->OnGenerateStream(page_request_id, options, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ scoped_refptr<MockMediaStreamDispatcherHost> host_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+ scoped_ptr<MediaStreamManager> media_stream_manager_;
+ ContentBrowserClient* old_browser_client_;
+ scoped_ptr<ContentClient> content_client_;
+ content::TestBrowserThreadBundle thread_bundle_;
+};
+
+TEST_F(MediaStreamDispatcherHostTest, GenerateStream) {
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ SetupFakeUI(true);
+ EXPECT_CALL(*host_.get(), OnStreamGenerated(kRenderId, kPageRequestId, 0, 1));
+ GenerateStreamAndWaitForResult(kPageRequestId, options);
+
+ std::string label = host_->label_;
+
+ EXPECT_EQ(host_->audio_devices_.size(), 0u);
+ EXPECT_EQ(host_->video_devices_.size(), 1u);
+ EXPECT_EQ(host_->NumberOfStreams(), 1u);
+
+ host_->OnStopGeneratedStream(label);
+ EXPECT_EQ(host_->NumberOfStreams(), 0u);
+}
+
+TEST_F(MediaStreamDispatcherHostTest, GenerateThreeStreams) {
+ // This test opens three video capture devices. Two fake devices exists and it
+ // is expected the last call to |Open()| will open the first device again, but
+ // with a different label.
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Generate first stream.
+ SetupFakeUI(true);
+ EXPECT_CALL(*host_.get(), OnStreamGenerated(kRenderId, kPageRequestId, 0, 1));
+ GenerateStreamAndWaitForResult(kPageRequestId, options);
+
+ // Check the latest generated stream.
+ EXPECT_EQ(host_->audio_devices_.size(), 0u);
+ EXPECT_EQ(host_->video_devices_.size(), 1u);
+ std::string label1 = host_->label_;
+ std::string device_id1 = host_->video_devices_.front().device.id;
+
+ // Check that we now have one opened streams.
+ EXPECT_EQ(host_->NumberOfStreams(), 1u);
+
+ // Generate second stream.
+ SetupFakeUI(true);
+ EXPECT_CALL(*host_.get(),
+ OnStreamGenerated(kRenderId, kPageRequestId + 1, 0, 1));
+ GenerateStreamAndWaitForResult(kPageRequestId + 1, options);
+
+ // Check the latest generated stream.
+ EXPECT_EQ(host_->audio_devices_.size(), 0u);
+ EXPECT_EQ(host_->video_devices_.size(), 1u);
+ std::string label2 = host_->label_;
+ std::string device_id2 = host_->video_devices_.front().device.id;
+ EXPECT_EQ(device_id1, device_id2);
+ EXPECT_NE(label1, label2);
+
+ // Check that we now have two opened streams.
+ EXPECT_EQ(2u, host_->NumberOfStreams());
+
+ // Generate third stream.
+ SetupFakeUI(true);
+ EXPECT_CALL(*host_.get(),
+ OnStreamGenerated(kRenderId, kPageRequestId + 2, 0, 1));
+ GenerateStreamAndWaitForResult(kPageRequestId + 2, options);
+
+ // Check the latest generated stream.
+ EXPECT_EQ(host_->audio_devices_.size(), 0u);
+ EXPECT_EQ(host_->video_devices_.size(), 1u);
+ std::string label3 = host_->label_;
+ std::string device_id3 = host_->video_devices_.front().device.id;
+ EXPECT_EQ(device_id1, device_id3);
+ EXPECT_NE(label1, label3);
+ EXPECT_NE(label2, label3);
+
+ // Check that we now have three opened streams.
+ EXPECT_EQ(host_->NumberOfStreams(), 3u);
+
+ host_->OnStopGeneratedStream(label1);
+ host_->OnStopGeneratedStream(label2);
+ host_->OnStopGeneratedStream(label3);
+ EXPECT_EQ(host_->NumberOfStreams(), 0u);
+}
+
+TEST_F(MediaStreamDispatcherHostTest, FailOpenVideoDevice) {
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ media::FakeVideoCaptureDevice::SetFailNextCreate();
+ SetupFakeUI(false);
+ EXPECT_CALL(*host_.get(),
+ OnStreamGenerationFailed(kRenderId, kPageRequestId));
+ GenerateStreamAndWaitForResult(kPageRequestId, options);
+}
+
+TEST_F(MediaStreamDispatcherHostTest, CancelPendingStreamsOnChannelClosing) {
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ base::RunLoop run_loop;
+
+ // Create multiple GenerateStream requests.
+ size_t streams = 5;
+ for (size_t i = 1; i <= streams; ++i) {
+ host_->OnGenerateStream(
+ kPageRequestId + i, options, run_loop.QuitClosure());
+ EXPECT_EQ(host_->NumberOfStreams(), i);
+ }
+
+ // Calling OnChannelClosing() to cancel all the pending requests.
+ host_->OnChannelClosing();
+ run_loop.RunUntilIdle();
+
+ // Streams should have been cleaned up.
+ EXPECT_EQ(host_->NumberOfStreams(), 0u);
+}
+
+TEST_F(MediaStreamDispatcherHostTest, StopGeneratedStreamsOnChannelClosing) {
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Create first group of streams.
+ size_t generated_streams = 3;
+ for (size_t i = 0; i < generated_streams; ++i) {
+ SetupFakeUI(true);
+ EXPECT_CALL(*host_.get(),
+ OnStreamGenerated(kRenderId, kPageRequestId + i, 0, 1));
+ GenerateStreamAndWaitForResult(kPageRequestId + i, options);
+ }
+ EXPECT_EQ(host_->NumberOfStreams(), generated_streams);
+
+ // Calling OnChannelClosing() to cancel all the pending/generated streams.
+ host_->OnChannelClosing();
+ base::RunLoop().RunUntilIdle();
+
+ // Streams should have been cleaned up.
+ EXPECT_EQ(host_->NumberOfStreams(), 0u);
+}
+
+TEST_F(MediaStreamDispatcherHostTest, CloseFromUI) {
+ StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ base::Closure close_callback;
+ scoped_ptr<MockMediaStreamUIProxy> stream_ui(new MockMediaStreamUIProxy());
+ EXPECT_CALL(*stream_ui, OnStarted(_))
+ .WillOnce(SaveArg<0>(&close_callback));
+ media_stream_manager_->UseFakeUI(stream_ui.PassAs<FakeMediaStreamUIProxy>());
+
+ EXPECT_CALL(*host_.get(), OnStreamGenerated(kRenderId, kPageRequestId, 0, 1));
+ EXPECT_CALL(*host_.get(), OnStopGeneratedStreamFromBrowser(kRenderId));
+ GenerateStreamAndWaitForResult(kPageRequestId, options);
+
+ EXPECT_EQ(host_->audio_devices_.size(), 0u);
+ EXPECT_EQ(host_->video_devices_.size(), 1u);
+ EXPECT_EQ(host_->NumberOfStreams(), 1u);
+
+ ASSERT_FALSE(close_callback.is_null());
+ close_callback.Run();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(host_->NumberOfStreams(), 0u);
+}
+
+}; // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_manager.cc b/chromium/content/browser/renderer_host/media/media_stream_manager.cc
new file mode 100644
index 00000000000..449331198bd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_manager.cc
@@ -0,0 +1,1115 @@
+// 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 "content/browser/renderer_host/media/media_stream_manager.h"
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/threading/thread.h"
+#include "content/browser/renderer_host/media/audio_input_device_manager.h"
+#include "content/browser/renderer_host/media/device_request_message_filter.h"
+#include "content/browser/renderer_host/media/media_stream_requester.h"
+#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/media_observer.h"
+#include "content/public/browser/media_request_state.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/media_stream_request.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/channel_layout.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
+namespace content {
+
+// Creates a random label used to identify requests.
+static std::string RandomLabel() {
+ // An earlier PeerConnection spec,
+ // http://dev.w3.org/2011/webrtc/editor/webrtc.html, specified the
+ // MediaStream::label alphabet as containing 36 characters from
+ // range: U+0021, U+0023 to U+0027, U+002A to U+002B, U+002D to U+002E,
+ // U+0030 to U+0039, U+0041 to U+005A, U+005E to U+007E.
+ // Here we use a safe subset.
+ static const char kAlphabet[] = "0123456789"
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ std::string label(36, ' ');
+ for (size_t i = 0; i < label.size(); ++i) {
+ int random_char = base::RandGenerator(sizeof(kAlphabet) - 1);
+ label[i] = kAlphabet[random_char];
+ }
+ return label;
+}
+
+// Helper to verify if a media stream type is part of options or not.
+static bool Requested(const MediaStreamRequest& request,
+ MediaStreamType stream_type) {
+ return (request.audio_type == stream_type ||
+ request.video_type == stream_type);
+}
+
+// TODO(xians): Merge DeviceRequest with MediaStreamRequest.
+class MediaStreamManager::DeviceRequest {
+ public:
+ DeviceRequest(MediaStreamRequester* requester,
+ const MediaStreamRequest& request)
+ : requester(requester),
+ request(request),
+ state_(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_NOT_REQUESTED) {
+ }
+
+ ~DeviceRequest() {}
+
+ // Update the request state and notify observers.
+ void SetState(MediaStreamType stream_type, MediaRequestState new_state) {
+ if (stream_type == NUM_MEDIA_TYPES) {
+ for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
+ const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
+ state_[stream_type] = new_state;
+ }
+ } else {
+ state_[stream_type] = new_state;
+ }
+
+ if (request.video_type != MEDIA_TAB_VIDEO_CAPTURE &&
+ request.audio_type != MEDIA_TAB_AUDIO_CAPTURE &&
+ new_state != MEDIA_REQUEST_STATE_CLOSING) {
+ return;
+ }
+
+ MediaObserver* media_observer =
+ GetContentClient()->browser()->GetMediaObserver();
+ if (media_observer == NULL)
+ return;
+
+ // If we appended a device_id scheme, we want to remove it when notifying
+ // observers which may be in different modules since this scheme is only
+ // used internally within the content module.
+ std::string device_id =
+ WebContentsCaptureUtil::StripWebContentsDeviceScheme(
+ request.tab_capture_device_id);
+
+ media_observer->OnMediaRequestStateChanged(
+ request.render_process_id, request.render_view_id,
+ request.page_request_id,
+ MediaStreamDevice(stream_type, device_id, device_id), new_state);
+ }
+
+ MediaRequestState state(MediaStreamType stream_type) const {
+ return state_[stream_type];
+ }
+
+ MediaStreamRequester* const requester; // Can be NULL.
+ MediaStreamRequest request;
+
+ StreamDeviceInfoArray devices;
+
+ // Callback to the requester which audio/video devices have been selected.
+ // It can be null if the requester has no interest to know the result.
+ // Currently it is only used by |DEVICE_ACCESS| type.
+ MediaStreamManager::MediaRequestResponseCallback callback;
+
+ scoped_ptr<MediaStreamUIProxy> ui_proxy;
+
+ private:
+ std::vector<MediaRequestState> state_;
+};
+
+MediaStreamManager::EnumerationCache::EnumerationCache()
+ : valid(false) {
+}
+
+MediaStreamManager::EnumerationCache::~EnumerationCache() {
+}
+
+MediaStreamManager::MediaStreamManager()
+ : audio_manager_(NULL),
+ monitoring_started_(false),
+ io_loop_(NULL),
+ use_fake_ui_(false) {}
+
+MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager)
+ : audio_manager_(audio_manager),
+ monitoring_started_(false),
+ io_loop_(NULL),
+ use_fake_ui_(false) {
+ DCHECK(audio_manager_);
+ memset(active_enumeration_ref_count_, 0,
+ sizeof(active_enumeration_ref_count_));
+
+ // Some unit tests create the MSM in the IO thread and assumes the
+ // initialization is done synchronously.
+ if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ InitializeDeviceManagersOnIOThread();
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaStreamManager::InitializeDeviceManagersOnIOThread,
+ base::Unretained(this)));
+ }
+}
+
+MediaStreamManager::~MediaStreamManager() {
+ DCHECK(requests_.empty());
+ DCHECK(!device_thread_.get());
+}
+
+VideoCaptureManager* MediaStreamManager::video_capture_manager() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(video_capture_manager_.get());
+ return video_capture_manager_.get();
+}
+
+AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(audio_input_device_manager_.get());
+ return audio_input_device_manager_.get();
+}
+
+std::string MediaStreamManager::MakeMediaAccessRequest(
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const StreamOptions& options,
+ const GURL& security_origin,
+ const MediaRequestResponseCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Create a new request based on options.
+ MediaStreamRequest stream_request(
+ render_process_id, render_view_id, page_request_id, std::string(),
+ security_origin, MEDIA_DEVICE_ACCESS, std::string(), std::string(),
+ options.audio_type, options.video_type);
+ DeviceRequest* request = new DeviceRequest(NULL, stream_request);
+ const std::string& label = AddRequest(request);
+
+ request->callback = callback;
+
+ HandleRequest(label);
+
+ return label;
+}
+
+std::string MediaStreamManager::GenerateStream(
+ MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const StreamOptions& options,
+ const GURL& security_origin) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeDeviceForMediaStream)) {
+ UseFakeDevice();
+ }
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFakeUIForMediaStream)) {
+ UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy>());
+ }
+
+ int target_render_process_id = render_process_id;
+ int target_render_view_id = render_view_id;
+ std::string tab_capture_device_id;
+
+ // Customize options for a WebContents based capture.
+ if (options.audio_type == MEDIA_TAB_AUDIO_CAPTURE ||
+ options.video_type == MEDIA_TAB_VIDEO_CAPTURE) {
+ // TODO(justinlin): Can't plumb audio mirroring using stream type right
+ // now, so plumbing by device_id. Will revisit once it's refactored.
+ // http://crbug.com/163100
+ tab_capture_device_id =
+ WebContentsCaptureUtil::AppendWebContentsDeviceScheme(
+ !options.video_device_id.empty() ?
+ options.video_device_id : options.audio_device_id);
+
+ bool has_valid_device_id = WebContentsCaptureUtil::ExtractTabCaptureTarget(
+ tab_capture_device_id, &target_render_process_id,
+ &target_render_view_id);
+ if (!has_valid_device_id ||
+ (options.audio_type != MEDIA_TAB_AUDIO_CAPTURE &&
+ options.audio_type != MEDIA_NO_SERVICE) ||
+ (options.video_type != MEDIA_TAB_VIDEO_CAPTURE &&
+ options.video_type != MEDIA_NO_SERVICE)) {
+ LOG(ERROR) << "Invalid request.";
+ return std::string();
+ }
+ }
+
+ std::string translated_audio_device_id;
+ std::string translated_video_device_id;
+ if (options.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE) {
+ bool found_match = TranslateGUIDToRawId(
+ MEDIA_DEVICE_AUDIO_CAPTURE, security_origin, options.audio_device_id,
+ &translated_audio_device_id);
+ DCHECK(found_match || translated_audio_device_id.empty());
+ }
+
+ if (options.video_type == MEDIA_DEVICE_VIDEO_CAPTURE) {
+ bool found_match = TranslateGUIDToRawId(
+ MEDIA_DEVICE_VIDEO_CAPTURE, security_origin, options.video_device_id,
+ &translated_video_device_id);
+ DCHECK(found_match || translated_video_device_id.empty());
+ }
+
+ if (options.video_type == MEDIA_DESKTOP_VIDEO_CAPTURE ||
+ options.audio_type == MEDIA_SYSTEM_AUDIO_CAPTURE) {
+ // For screen capture we only support two valid combinations:
+ // (1) screen video capture only, or
+ // (2) screen video capture with system audio capture.
+ if (options.video_type != MEDIA_DESKTOP_VIDEO_CAPTURE ||
+ (options.audio_type != MEDIA_NO_SERVICE &&
+ options.audio_type != MEDIA_SYSTEM_AUDIO_CAPTURE)) {
+ // TODO(sergeyu): Surface error message to the calling JS code.
+ LOG(ERROR) << "Invalid screen capture request.";
+ return std::string();
+ }
+ translated_video_device_id = options.video_device_id;
+ }
+
+ // Create a new request based on options.
+ MediaStreamRequest stream_request(
+ target_render_process_id, target_render_view_id, page_request_id,
+ tab_capture_device_id, security_origin, MEDIA_GENERATE_STREAM,
+ translated_audio_device_id, translated_video_device_id,
+ options.audio_type, options.video_type);
+ DeviceRequest* request = new DeviceRequest(requester, stream_request);
+ const std::string& label = AddRequest(request);
+ HandleRequest(label);
+ return label;
+}
+
+void MediaStreamManager::CancelRequest(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DeviceRequests::iterator it = requests_.find(label);
+ if (it != requests_.end()) {
+ if (!RequestDone(*it->second)) {
+ // TODO(xians): update the |state| to STATE_DONE to trigger a state
+ // changed notification to UI before deleting the request?
+ scoped_ptr<DeviceRequest> request(it->second);
+ RemoveRequest(it);
+ for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
+ const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
+ MediaStreamProvider* device_manager = GetDeviceManager(stream_type);
+ if (!device_manager)
+ continue;
+ if (request->state(stream_type) != MEDIA_REQUEST_STATE_OPENING &&
+ request->state(stream_type) != MEDIA_REQUEST_STATE_DONE) {
+ continue;
+ }
+ for (StreamDeviceInfoArray::const_iterator device_it =
+ request->devices.begin();
+ device_it != request->devices.end(); ++device_it) {
+ if (device_it->device.type == stream_type) {
+ device_manager->Close(device_it->session_id);
+ }
+ }
+ }
+ // Cancel the request if still pending at UI side.
+ request->SetState(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_CLOSING);
+ } else {
+ StopGeneratedStream(label);
+ }
+ }
+}
+
+void MediaStreamManager::StopGeneratedStream(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Find the request and close all open devices for the request.
+ DeviceRequests::iterator it = requests_.find(label);
+ if (it != requests_.end()) {
+ if (it->second->request.request_type == MEDIA_ENUMERATE_DEVICES) {
+ StopEnumerateDevices(label);
+ return;
+ }
+
+ scoped_ptr<DeviceRequest> request(it->second);
+ RemoveRequest(it);
+ for (StreamDeviceInfoArray::const_iterator device_it =
+ request->devices.begin();
+ device_it != request->devices.end(); ++device_it) {
+ GetDeviceManager(device_it->device.type)->Close(device_it->session_id);
+ }
+ if (request->request.request_type == MEDIA_GENERATE_STREAM &&
+ RequestDone(*request)) {
+ // Notify observers that this device is being closed.
+ for (int i = MEDIA_NO_SERVICE + 1; i != NUM_MEDIA_TYPES; ++i) {
+ if (request->state(static_cast<MediaStreamType>(i)) !=
+ MEDIA_REQUEST_STATE_NOT_REQUESTED) {
+ request->SetState(static_cast<MediaStreamType>(i),
+ MEDIA_REQUEST_STATE_CLOSING);
+ }
+ }
+ }
+ }
+}
+
+std::string MediaStreamManager::EnumerateDevices(
+ MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE ||
+ type == MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // When the requester is NULL, the request is made by the UI to ensure MSM
+ // starts monitoring devices.
+ if (!requester) {
+ if (!monitoring_started_)
+ StartMonitoring();
+
+ return std::string();
+ }
+
+ // Create a new request.
+ StreamOptions options;
+ EnumerationCache* cache = NULL;
+ if (type == MEDIA_DEVICE_AUDIO_CAPTURE) {
+ options.audio_type = type;
+ cache = &audio_enumeration_cache_;
+ } else if (type == MEDIA_DEVICE_VIDEO_CAPTURE) {
+ options.video_type = type;
+ cache = &video_enumeration_cache_;
+ } else {
+ NOTREACHED();
+ return std::string();
+ }
+
+ MediaStreamRequest stream_request(
+ render_process_id, render_view_id, page_request_id, std::string(),
+ security_origin, MEDIA_ENUMERATE_DEVICES, std::string(), std::string(),
+ options.audio_type, options.video_type);
+ DeviceRequest* request = new DeviceRequest(requester, stream_request);
+ const std::string& label = AddRequest(request);
+
+ if (cache->valid) {
+ // Cached device list of this type exists. Just send it out.
+ request->SetState(type, MEDIA_REQUEST_STATE_REQUESTED);
+
+ // Need to post a task since the requester won't have label till
+ // this function returns.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaStreamManager::SendCachedDeviceList,
+ base::Unretained(this), cache, label));
+ } else {
+ StartEnumeration(request);
+ }
+
+ return label;
+}
+
+void MediaStreamManager::StopEnumerateDevices(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DeviceRequests::iterator it = requests_.find(label);
+ if (it != requests_.end()) {
+ DCHECK_EQ(it->second->request.request_type, MEDIA_ENUMERATE_DEVICES);
+ // Delete the DeviceRequest.
+ scoped_ptr<DeviceRequest> request(it->second);
+ RemoveRequest(it);
+ }
+}
+
+std::string MediaStreamManager::OpenDevice(
+ MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const std::string& device_id,
+ MediaStreamType type,
+ const GURL& security_origin) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE ||
+ type == MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Create a new request.
+ StreamOptions options;
+ if (IsAudioMediaType(type)) {
+ options.audio_type = type;
+ options.audio_device_id = device_id;
+ } else if (IsVideoMediaType(type)) {
+ options.video_type = type;
+ options.video_device_id = device_id;
+ } else {
+ NOTREACHED();
+ return std::string();
+ }
+
+ MediaStreamRequest stream_request(
+ render_process_id, render_view_id, page_request_id, std::string(),
+ security_origin, MEDIA_OPEN_DEVICE, options.audio_device_id,
+ options.video_device_id, options.audio_type, options.video_type);
+ DeviceRequest* request = new DeviceRequest(requester, stream_request);
+ const std::string& label = AddRequest(request);
+ StartEnumeration(request);
+
+ return label;
+}
+
+void MediaStreamManager::SendCachedDeviceList(
+ EnumerationCache* cache,
+ const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (cache->valid) {
+ DeviceRequests::iterator it = requests_.find(label);
+ if (it != requests_.end()) {
+ it->second->requester->DevicesEnumerated(label, cache->devices);
+ }
+ }
+}
+
+void MediaStreamManager::StartMonitoring() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!base::SystemMonitor::Get())
+ return;
+
+ if (!monitoring_started_) {
+ monitoring_started_ = true;
+ base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
+
+ // Enumerate both the audio and video devices to cache the device lists
+ // and send them to media observer.
+ ++active_enumeration_ref_count_[MEDIA_DEVICE_AUDIO_CAPTURE];
+ audio_input_device_manager_->EnumerateDevices(MEDIA_DEVICE_AUDIO_CAPTURE);
+ ++active_enumeration_ref_count_[MEDIA_DEVICE_VIDEO_CAPTURE];
+ video_capture_manager_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+ }
+}
+
+void MediaStreamManager::StopMonitoring() {
+ DCHECK_EQ(base::MessageLoop::current(), io_loop_);
+ if (monitoring_started_) {
+ base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
+ monitoring_started_ = false;
+ ClearEnumerationCache(&audio_enumeration_cache_);
+ ClearEnumerationCache(&video_enumeration_cache_);
+ }
+}
+
+bool MediaStreamManager::TranslateGUIDToRawId(MediaStreamType stream_type,
+ const GURL& security_origin,
+ const std::string& device_guid,
+ std::string* raw_device_id) {
+ DCHECK(stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ||
+ stream_type == MEDIA_DEVICE_VIDEO_CAPTURE);
+ if (device_guid.empty())
+ return false;
+
+ EnumerationCache* cache =
+ stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ?
+ &audio_enumeration_cache_ : &video_enumeration_cache_;
+
+ // If device monitoring hasn't started, the |device_guid| is not valid.
+ if (!cache->valid)
+ return false;
+
+ for (StreamDeviceInfoArray::const_iterator it = cache->devices.begin();
+ it != cache->devices.end();
+ ++it) {
+ if (DeviceRequestMessageFilter::DoesRawIdMatchGuid(
+ security_origin, device_guid, it->device.id)) {
+ *raw_device_id = it->device.id;
+ return true;
+ }
+ }
+ return false;
+}
+
+void MediaStreamManager::ClearEnumerationCache(EnumerationCache* cache) {
+ DCHECK_EQ(base::MessageLoop::current(), io_loop_);
+ cache->valid = false;
+}
+
+void MediaStreamManager::StartEnumeration(DeviceRequest* request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Start monitoring the devices when doing the first enumeration.
+ if (!monitoring_started_ && base::SystemMonitor::Get()) {
+ StartMonitoring();
+ }
+
+ // Start enumeration for devices of all requested device types.
+ for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
+ const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
+ if (Requested(request->request, stream_type)) {
+ request->SetState(stream_type, MEDIA_REQUEST_STATE_REQUESTED);
+ DCHECK_GE(active_enumeration_ref_count_[stream_type], 0);
+ if (active_enumeration_ref_count_[stream_type] == 0) {
+ ++active_enumeration_ref_count_[stream_type];
+ GetDeviceManager(stream_type)->EnumerateDevices(stream_type);
+ }
+ }
+ }
+}
+
+std::string MediaStreamManager::AddRequest(DeviceRequest* request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Create a label for this request and verify it is unique.
+ std::string unique_label;
+ do {
+ unique_label = RandomLabel();
+ } while (requests_.find(unique_label) != requests_.end());
+
+ requests_.insert(std::make_pair(unique_label, request));
+
+ return unique_label;
+}
+
+void MediaStreamManager::RemoveRequest(DeviceRequests::iterator it) {
+ requests_.erase(it);
+}
+
+void MediaStreamManager::PostRequestToUI(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DeviceRequest* request = requests_[label];
+
+ if (use_fake_ui_) {
+ if (!fake_ui_)
+ fake_ui_.reset(new FakeMediaStreamUIProxy());
+
+ MediaStreamDevices devices;
+ if (audio_enumeration_cache_.valid) {
+ for (StreamDeviceInfoArray::const_iterator it =
+ audio_enumeration_cache_.devices.begin();
+ it != audio_enumeration_cache_.devices.end(); ++it) {
+ devices.push_back(it->device);
+ }
+ }
+ if (video_enumeration_cache_.valid) {
+ for (StreamDeviceInfoArray::const_iterator it =
+ video_enumeration_cache_.devices.begin();
+ it != video_enumeration_cache_.devices.end(); ++it) {
+ devices.push_back(it->device);
+ }
+ }
+
+ fake_ui_->SetAvailableDevices(devices);
+
+ request->ui_proxy = fake_ui_.Pass();
+ } else {
+ request->ui_proxy = MediaStreamUIProxy::Create();
+ }
+
+ request->ui_proxy->RequestAccess(
+ request->request,
+ base::Bind(&MediaStreamManager::HandleAccessRequestResponse,
+ base::Unretained(this), label));
+}
+
+void MediaStreamManager::HandleRequest(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DeviceRequest* request = requests_[label];
+
+ const MediaStreamType audio_type = request->request.audio_type;
+ const MediaStreamType video_type = request->request.video_type;
+
+ bool is_web_contents_capture =
+ audio_type == MEDIA_TAB_AUDIO_CAPTURE ||
+ video_type == MEDIA_TAB_VIDEO_CAPTURE;
+
+ bool is_screen_capture =
+ video_type == MEDIA_DESKTOP_VIDEO_CAPTURE;
+
+ if (!is_web_contents_capture &&
+ !is_screen_capture &&
+ ((IsAudioMediaType(audio_type) && !audio_enumeration_cache_.valid) ||
+ (IsVideoMediaType(video_type) && !video_enumeration_cache_.valid))) {
+ // Enumerate the devices if there is no valid device lists to be used.
+ StartEnumeration(request);
+ return;
+ }
+
+ // No need to do new device enumerations, post the request to UI
+ // immediately.
+ if (IsAudioMediaType(audio_type))
+ request->SetState(audio_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
+ if (IsVideoMediaType(video_type))
+ request->SetState(video_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
+
+ PostRequestToUI(label);
+}
+
+void MediaStreamManager::InitializeDeviceManagersOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (device_thread_)
+ return;
+
+ device_thread_.reset(new base::Thread("MediaStreamDeviceThread"));
+#if defined(OS_WIN)
+ device_thread_->init_com_with_mta(true);
+#endif
+ CHECK(device_thread_->Start());
+
+ audio_input_device_manager_ = new AudioInputDeviceManager(audio_manager_);
+ audio_input_device_manager_->Register(
+ this, device_thread_->message_loop_proxy().get());
+
+ video_capture_manager_ = new VideoCaptureManager();
+ video_capture_manager_->Register(this,
+ device_thread_->message_loop_proxy().get());
+
+ // We want to be notified of IO message loop destruction to delete the thread
+ // and the device managers.
+ io_loop_ = base::MessageLoop::current();
+ io_loop_->AddDestructionObserver(this);
+}
+
+void MediaStreamManager::Opened(MediaStreamType stream_type,
+ int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Find the request containing this device and mark it as used.
+ DeviceRequest* request = NULL;
+ StreamDeviceInfoArray* devices = NULL;
+ std::string label;
+ for (DeviceRequests::iterator request_it = requests_.begin();
+ request_it != requests_.end() && request == NULL; ++request_it) {
+ devices = &(request_it->second->devices);
+ for (StreamDeviceInfoArray::iterator device_it = devices->begin();
+ device_it != devices->end(); ++device_it) {
+ if (device_it->device.type == stream_type &&
+ device_it->session_id == capture_session_id) {
+ // We've found the request.
+ device_it->in_use = true;
+ label = request_it->first;
+ request = request_it->second;
+ break;
+ }
+ }
+ }
+ if (request == NULL) {
+ // The request doesn't exist.
+ return;
+ }
+
+ DCHECK_NE(request->state(stream_type), MEDIA_REQUEST_STATE_REQUESTED);
+
+ // Check if all devices for this stream type are opened. Update the state if
+ // they are.
+ for (StreamDeviceInfoArray::iterator device_it = devices->begin();
+ device_it != devices->end(); ++device_it) {
+ if (device_it->device.type != stream_type) {
+ continue;
+ }
+ if (device_it->in_use == false) {
+ // Wait for more devices to be opened before we're done.
+ return;
+ }
+ }
+
+ request->SetState(stream_type, MEDIA_REQUEST_STATE_DONE);
+
+ if (!RequestDone(*request)) {
+ // This stream_type is done, but not the other type.
+ return;
+ }
+
+ switch (request->request.request_type) {
+ case MEDIA_OPEN_DEVICE:
+ request->requester->DeviceOpened(label, devices->front());
+ break;
+ case MEDIA_GENERATE_STREAM: {
+ // Partition the array of devices into audio vs video.
+ StreamDeviceInfoArray audio_devices, video_devices;
+ for (StreamDeviceInfoArray::iterator device_it = devices->begin();
+ device_it != devices->end(); ++device_it) {
+ if (IsAudioMediaType(device_it->device.type)) {
+ // Store the native audio parameters in the device struct.
+ // TODO(xians): Handle the tab capture sample rate/channel layout
+ // in AudioInputDeviceManager::Open().
+ if (device_it->device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
+ const StreamDeviceInfo* info =
+ audio_input_device_manager_->GetOpenedDeviceInfoById(
+ device_it->session_id);
+ DCHECK_EQ(info->device.id, device_it->device.id);
+ device_it->device.sample_rate = info->device.sample_rate;
+ device_it->device.channel_layout = info->device.channel_layout;
+ }
+ audio_devices.push_back(*device_it);
+ } else if (IsVideoMediaType(device_it->device.type)) {
+ video_devices.push_back(*device_it);
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ request->requester->StreamGenerated(label, audio_devices, video_devices);
+ request->ui_proxy->OnStarted(
+ base::Bind(&MediaStreamManager::StopStreamFromUI,
+ base::Unretained(this), label));
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void MediaStreamManager::Closed(MediaStreamType stream_type,
+ int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+void MediaStreamManager::DevicesEnumerated(
+ MediaStreamType stream_type, const StreamDeviceInfoArray& devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Only cache the device list when the device list has been changed.
+ bool need_update_clients = false;
+ EnumerationCache* cache =
+ stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ?
+ &audio_enumeration_cache_ : &video_enumeration_cache_;
+ if (!cache->valid ||
+ devices.size() != cache->devices.size() ||
+ !std::equal(devices.begin(), devices.end(), cache->devices.begin(),
+ StreamDeviceInfo::IsEqual)) {
+ cache->valid = true;
+ cache->devices = devices;
+ need_update_clients = true;
+ }
+
+ if (need_update_clients && monitoring_started_)
+ NotifyDevicesChanged(stream_type, devices);
+
+ // Publish the result for all requests waiting for device list(s).
+ // Find the requests waiting for this device list, store their labels and
+ // release the iterator before calling device settings. We might get a call
+ // back from device_settings that will need to iterate through devices.
+ std::list<std::string> label_list;
+ for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end();
+ ++it) {
+ if (it->second->state(stream_type) == MEDIA_REQUEST_STATE_REQUESTED &&
+ Requested(it->second->request, stream_type)) {
+ if (it->second->request.request_type != MEDIA_ENUMERATE_DEVICES)
+ it->second->SetState(stream_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
+ label_list.push_back(it->first);
+ }
+ }
+ for (std::list<std::string>::iterator it = label_list.begin();
+ it != label_list.end(); ++it) {
+ DeviceRequest* request = requests_[*it];
+ switch (request->request.request_type) {
+ case MEDIA_ENUMERATE_DEVICES:
+ if (need_update_clients && request->requester)
+ request->requester->DevicesEnumerated(*it, devices);
+ break;
+ default:
+ if (request->state(request->request.audio_type) ==
+ MEDIA_REQUEST_STATE_REQUESTED ||
+ request->state(request->request.video_type) ==
+ MEDIA_REQUEST_STATE_REQUESTED) {
+ // We are doing enumeration for other type of media, wait until it is
+ // all done before posting the request to UI because UI needs
+ // the device lists to handle the request.
+ break;
+ }
+
+ // Post the request to UI for permission approval.
+ PostRequestToUI(*it);
+ break;
+ }
+ }
+ label_list.clear();
+ --active_enumeration_ref_count_[stream_type];
+ DCHECK_GE(active_enumeration_ref_count_[stream_type], 0);
+}
+
+void MediaStreamManager::Error(MediaStreamType stream_type,
+ int capture_session_id,
+ MediaStreamProviderError error) {
+ // Find the device for the error call.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end();
+ ++it) {
+ StreamDeviceInfoArray& devices = it->second->devices;
+
+ // TODO(miu): BUG. It's possible for the audio (or video) device array in
+ // the "requester" to become out-of-sync with the order of devices we have
+ // here. See http://crbug.com/147650
+ int audio_device_idx = -1;
+ int video_device_idx = -1;
+ for (StreamDeviceInfoArray::iterator device_it = devices.begin();
+ device_it != devices.end(); ++device_it) {
+ if (IsAudioMediaType(device_it->device.type)) {
+ ++audio_device_idx;
+ } else if (IsVideoMediaType(device_it->device.type)) {
+ ++video_device_idx;
+ } else {
+ NOTREACHED();
+ continue;
+ }
+ if (device_it->device.type != stream_type ||
+ device_it->session_id != capture_session_id) {
+ continue;
+ }
+ // We've found the failing device. Find the error case:
+ // An error should only be reported to the MediaStreamManager if
+ // the request has not been fulfilled yet.
+ DCHECK(it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE);
+ if (it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE) {
+ // Request is not done, devices are not opened in this case.
+ if (devices.size() <= 1) {
+ scoped_ptr<DeviceRequest> request(it->second);
+ // 1. Device not opened and no other devices for this request ->
+ // signal stream error and remove the request.
+ if (request->requester)
+ request->requester->StreamGenerationFailed(it->first);
+
+ RemoveRequest(it);
+ } else {
+ // 2. Not opened but other devices exists for this request -> remove
+ // device from list, but don't signal an error.
+ devices.erase(device_it); // NOTE: This invalidates device_it!
+ }
+ }
+ return;
+ }
+ }
+}
+
+void MediaStreamManager::HandleAccessRequestResponse(
+ const std::string& label,
+ const MediaStreamDevices& devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DeviceRequests::iterator request_it = requests_.find(label);
+ if (request_it == requests_.end()) {
+ return;
+ }
+
+ // Handle the case when the request was denied.
+ if (devices.empty()) {
+ // Notify the users about the request result.
+ scoped_ptr<DeviceRequest> request(request_it->second);
+ if (request->requester)
+ request->requester->StreamGenerationFailed(label);
+
+ if (request->request.request_type == MEDIA_DEVICE_ACCESS &&
+ !request->callback.is_null()) {
+ request->callback.Run(MediaStreamDevices(), request->ui_proxy.Pass());
+ }
+
+ RemoveRequest(request_it);
+ return;
+ }
+
+ if (request_it->second->request.request_type == MEDIA_DEVICE_ACCESS) {
+ scoped_ptr<DeviceRequest> request(request_it->second);
+ if (!request->callback.is_null())
+ request->callback.Run(devices, request->ui_proxy.Pass());
+
+ // Delete the request since it is done.
+ RemoveRequest(request_it);
+ return;
+ }
+
+ // Process all newly-accepted devices for this request.
+ DeviceRequest* request = request_it->second;
+ bool found_audio = false;
+ bool found_video = false;
+ for (MediaStreamDevices::const_iterator device_it = devices.begin();
+ device_it != devices.end(); ++device_it) {
+ StreamDeviceInfo device_info;
+ device_info.device = *device_it;
+
+ // TODO(justinlin): Nicer way to do this?
+ // Re-append the device's id since we lost it when posting request to UI.
+ if (device_info.device.type == content::MEDIA_TAB_VIDEO_CAPTURE ||
+ device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) {
+ device_info.device.id = request->request.tab_capture_device_id;
+
+ // Initialize the sample_rate and channel_layout here since for audio
+ // mirroring, we don't go through EnumerateDevices where these are usually
+ // initialized.
+ if (device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) {
+ const media::AudioParameters parameters =
+ audio_manager_->GetDefaultOutputStreamParameters();
+ int sample_rate = parameters.sample_rate();
+ // If we weren't able to get the native sampling rate or the sample_rate
+ // is outside the valid range for input devices set reasonable defaults.
+ if (sample_rate <= 0 || sample_rate > 96000)
+ sample_rate = 44100;
+
+ device_info.device.sample_rate = sample_rate;
+ device_info.device.channel_layout = media::CHANNEL_LAYOUT_STEREO;
+ }
+ }
+
+ // Set in_use to false to be able to track if this device has been
+ // opened. in_use might be true if the device type can be used in more
+ // than one session.
+ device_info.in_use = false;
+
+ device_info.session_id =
+ GetDeviceManager(device_info.device.type)->Open(device_info);
+ request->SetState(device_info.device.type, MEDIA_REQUEST_STATE_OPENING);
+ request->devices.push_back(device_info);
+
+ if (device_info.device.type == request->request.audio_type) {
+ found_audio = true;
+ } else if (device_info.device.type == request->request.video_type) {
+ found_video = true;
+ }
+ }
+
+ // Check whether we've received all stream types requested.
+ if (!found_audio && IsAudioMediaType(request->request.audio_type))
+ request->SetState(request->request.audio_type, MEDIA_REQUEST_STATE_ERROR);
+
+ if (!found_video && IsVideoMediaType(request->request.video_type))
+ request->SetState(request->request.video_type, MEDIA_REQUEST_STATE_ERROR);
+}
+
+void MediaStreamManager::StopStreamFromUI(const std::string& label) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DeviceRequests::iterator it = requests_.find(label);
+ if (it == requests_.end())
+ return;
+
+ // Notify renderers that the stream has been stopped.
+ if (it->second->requester)
+ it->second->requester->StopGeneratedStream(label);
+
+ StopGeneratedStream(label);
+}
+
+void MediaStreamManager::UseFakeDevice() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ video_capture_manager()->UseFakeDevice();
+ audio_input_device_manager()->UseFakeDevice();
+}
+
+void MediaStreamManager::UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy> fake_ui) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ use_fake_ui_ = true;
+ fake_ui_ = fake_ui.Pass();
+}
+
+void MediaStreamManager::WillDestroyCurrentMessageLoop() {
+ DCHECK_EQ(base::MessageLoop::current(), io_loop_);
+ DCHECK(requests_.empty());
+ if (device_thread_) {
+ StopMonitoring();
+
+ video_capture_manager_->Unregister();
+ audio_input_device_manager_->Unregister();
+ device_thread_.reset();
+ }
+
+ audio_input_device_manager_ = NULL;
+ video_capture_manager_ = NULL;
+}
+
+void MediaStreamManager::NotifyDevicesChanged(
+ MediaStreamType stream_type,
+ const StreamDeviceInfoArray& devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ MediaObserver* media_observer =
+ GetContentClient()->browser()->GetMediaObserver();
+ if (media_observer == NULL)
+ return;
+
+ // Map the devices to MediaStreamDevices.
+ MediaStreamDevices new_devices;
+ for (StreamDeviceInfoArray::const_iterator it = devices.begin();
+ it != devices.end(); ++it) {
+ new_devices.push_back(it->device);
+ }
+
+ if (IsAudioMediaType(stream_type)) {
+ media_observer->OnAudioCaptureDevicesChanged(new_devices);
+ } else if (IsVideoMediaType(stream_type)) {
+ media_observer->OnVideoCaptureDevicesChanged(new_devices);
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool MediaStreamManager::RequestDone(const DeviceRequest& request) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ const bool requested_audio = IsAudioMediaType(request.request.audio_type);
+ const bool requested_video = IsVideoMediaType(request.request.video_type);
+
+ const bool audio_done =
+ !requested_audio ||
+ request.state(request.request.audio_type) ==
+ MEDIA_REQUEST_STATE_DONE ||
+ request.state(request.request.audio_type) ==
+ MEDIA_REQUEST_STATE_ERROR;
+ if (!audio_done)
+ return false;
+
+ const bool video_done =
+ !requested_video ||
+ request.state(request.request.video_type) ==
+ MEDIA_REQUEST_STATE_DONE ||
+ request.state(request.request.video_type) ==
+ MEDIA_REQUEST_STATE_ERROR;
+ if (!video_done)
+ return false;
+
+ for (StreamDeviceInfoArray::const_iterator it = request.devices.begin();
+ it != request.devices.end(); ++it) {
+ if (it->in_use == false)
+ return false;
+ }
+
+ return true;
+}
+
+MediaStreamProvider* MediaStreamManager::GetDeviceManager(
+ MediaStreamType stream_type) {
+ if (IsVideoMediaType(stream_type)) {
+ return video_capture_manager();
+ } else if (IsAudioMediaType(stream_type)) {
+ return audio_input_device_manager();
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+void MediaStreamManager::OnDevicesChanged(
+ base::SystemMonitor::DeviceType device_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // NOTE: This method is only called in response to physical audio/video device
+ // changes (from the operating system).
+
+ MediaStreamType stream_type;
+ if (device_type == base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE) {
+ stream_type = MEDIA_DEVICE_AUDIO_CAPTURE;
+ } else if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE) {
+ stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
+ } else {
+ return; // Uninteresting device change.
+ }
+
+ // Always do enumeration even though some enumeration is in progress,
+ // because those enumeration commands could be sent before these devices
+ // change.
+ ++active_enumeration_ref_count_[stream_type];
+ GetDeviceManager(stream_type)->EnumerateDevices(stream_type);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_manager.h b/chromium/content/browser/renderer_host/media/media_stream_manager.h
new file mode 100644
index 00000000000..5a444ef4403
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_manager.h
@@ -0,0 +1,265 @@
+// 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.
+
+// MediaStreamManager is used to open/enumerate media capture devices (video
+// supported now). Call flow:
+// 1. GenerateStream is called when a render process wants to use a capture
+// device.
+// 2. MediaStreamManager will ask MediaStreamUIController for permission to
+// use devices and for which device to use.
+// 3. MediaStreamManager will request the corresponding media device manager(s)
+// to enumerate available devices. The result will be given to
+// MediaStreamUIController.
+// 4. MediaStreamUIController will, by posting the request to UI, let the
+// users to select which devices to use and send callback to
+// MediaStreamManager with the result.
+// 5. MediaStreamManager will call the proper media device manager to open the
+// device and let the MediaStreamRequester know it has been done.
+
+// When enumeration and open are done in separate operations,
+// MediaStreamUIController is not involved as in steps.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/system_monitor/system_monitor.h"
+#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+
+namespace base {
+class Thread;
+}
+
+namespace media {
+class AudioManager;
+}
+
+namespace content {
+
+class AudioInputDeviceManager;
+class FakeMediaStreamUIProxy;
+class MediaStreamDeviceSettings;
+class MediaStreamRequester;
+class MediaStreamUIProxy;
+class VideoCaptureManager;
+
+// MediaStreamManager is used to generate and close new media devices, not to
+// start the media flow.
+// The classes requesting new media streams are answered using
+// MediaStreamManager::Listener.
+class CONTENT_EXPORT MediaStreamManager
+ : public MediaStreamProviderListener,
+ public base::MessageLoop::DestructionObserver,
+ public base::SystemMonitor::DevicesChangedObserver {
+ public:
+ // Callback to deliver the result of a media request. |label| is the string
+ // to identify the request,
+ typedef base::Callback<void(const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUIProxy> ui)>
+ MediaRequestResponseCallback;
+
+ explicit MediaStreamManager(media::AudioManager* audio_manager);
+ virtual ~MediaStreamManager();
+
+ // Used to access VideoCaptureManager.
+ VideoCaptureManager* video_capture_manager();
+
+ // Used to access AudioInputDeviceManager.
+ AudioInputDeviceManager* audio_input_device_manager();
+
+ // Creates a new media access request which is identified by a unique string
+ // that's returned to the caller. This will trigger the infobar and ask users
+ // for access to the device. |render_process_id| and |render_view_id| refer
+ // to the view where the infobar will appear to the user. |callback| is
+ // used to send the selected device to the clients. An empty list of device
+ // will be returned if the users deny the access.
+ std::string MakeMediaAccessRequest(
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const StreamOptions& components,
+ const GURL& security_origin,
+ const MediaRequestResponseCallback& callback);
+
+ // GenerateStream opens new media devices according to |components|. It
+ // creates a new request which is identified by a unique string that's
+ // returned to the caller. |render_process_id| and |render_view_id| refer to
+ // the view where the infobar will appear to the user.
+ std::string GenerateStream(MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const StreamOptions& components,
+ const GURL& security_origin);
+
+ void CancelRequest(const std::string& label);
+
+ // Closes generated stream.
+ virtual void StopGeneratedStream(const std::string& label);
+
+ // Gets a list of devices of |type|, which must be MEDIA_DEVICE_AUDIO_CAPTURE
+ // or MEDIA_DEVICE_VIDEO_CAPTURE.
+ // The request is identified using the string returned to the caller.
+ // When the |requester| is NULL, MediaStreamManager will enumerate both audio
+ // and video devices and also start monitoring device changes, such as
+ // plug/unplug. The new device lists will be delivered via media observer to
+ // MediaCaptureDevicesDispatcher.
+ virtual std::string EnumerateDevices(MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ MediaStreamType type,
+ const GURL& security_origin);
+
+ // Open a device identified by |device_id|. |type| must be either
+ // MEDIA_DEVICE_AUDIO_CAPTURE or MEDIA_DEVICE_VIDEO_CAPTURE.
+ // The request is identified using string returned to the caller.
+ std::string OpenDevice(MediaStreamRequester* requester,
+ int render_process_id,
+ int render_view_id,
+ int page_request_id,
+ const std::string& device_id,
+ MediaStreamType type,
+ const GURL& security_origin);
+
+ // Implements MediaStreamProviderListener.
+ virtual void Opened(MediaStreamType stream_type,
+ int capture_session_id) OVERRIDE;
+ virtual void Closed(MediaStreamType stream_type,
+ int capture_session_id) OVERRIDE;
+ virtual void DevicesEnumerated(MediaStreamType stream_type,
+ const StreamDeviceInfoArray& devices) OVERRIDE;
+ virtual void Error(MediaStreamType stream_type,
+ int capture_session_id,
+ MediaStreamProviderError error) OVERRIDE;
+
+ // Implements base::SystemMonitor::DevicesChangedObserver.
+ virtual void OnDevicesChanged(
+ base::SystemMonitor::DeviceType device_type) OVERRIDE;
+
+ // Used by unit test to make sure fake devices are used instead of a real
+ // devices, which is needed for server based testing or certain tests (which
+ // can pass --use-fake-device-for-media-stream).
+ void UseFakeDevice();
+
+ // Called by the tests to specify a fake UI that should be used for next
+ // generated stream (or when using --use-fake-ui-for-media-stream).
+ void UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy> fake_ui);
+
+ // This object gets deleted on the UI thread after the IO thread has been
+ // destroyed. So we need to know when IO thread is being destroyed so that
+ // we can delete VideoCaptureManager and AudioInputDeviceManager.
+ // We also must call this function explicitly in tests which use
+ // TestBrowserThreadBundle, because the notification happens too late in that
+ // case (see http://crbug.com/247525#c14).
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+ protected:
+ // Used for testing.
+ MediaStreamManager();
+
+ private:
+ // Contains all data needed to keep track of requests.
+ class DeviceRequest;
+
+ // Cache enumerated device list.
+ struct EnumerationCache {
+ EnumerationCache();
+ ~EnumerationCache();
+
+ bool valid;
+ StreamDeviceInfoArray devices;
+ };
+
+ typedef std::map<std::string, DeviceRequest*> DeviceRequests;
+
+ // Initializes the device managers on IO thread. Auto-starts the device
+ // thread and registers this as a listener with the device managers.
+ void InitializeDeviceManagersOnIOThread();
+
+ // Helper for sending up-to-date device lists to media observer when a
+ // capture device is plugged in or unplugged.
+ void NotifyDevicesChanged(MediaStreamType stream_type,
+ const StreamDeviceInfoArray& devices);
+
+
+ void HandleAccessRequestResponse(const std::string& label,
+ const MediaStreamDevices& devices);
+ void StopStreamFromUI(const std::string& label);
+
+ // Helpers.
+ bool RequestDone(const DeviceRequest& request) const;
+ MediaStreamProvider* GetDeviceManager(MediaStreamType stream_type);
+ void StartEnumeration(DeviceRequest* request);
+ std::string AddRequest(DeviceRequest* request);
+ void RemoveRequest(DeviceRequests::iterator it);
+ void ClearEnumerationCache(EnumerationCache* cache);
+ void PostRequestToUI(const std::string& label);
+ void HandleRequest(const std::string& label);
+
+ // Sends cached device list to a client corresponding to the request
+ // identified by |label|.
+ void SendCachedDeviceList(EnumerationCache* cache, const std::string& label);
+
+ // Stop the request of enumerating devices indentified by |label|.
+ void StopEnumerateDevices(const std::string& label);
+
+ // Helpers to start and stop monitoring devices.
+ void StartMonitoring();
+ void StopMonitoring();
+
+ // Finds and returns the raw device id corresponding to the given
+ // |device_guid|. Returns true if there was a raw device id that matched the
+ // given |device_guid|, false if nothing matched it.
+ bool TranslateGUIDToRawId(
+ MediaStreamType stream_type,
+ const GURL& security_origin,
+ const std::string& device_guid,
+ std::string* raw_device_id);
+
+ // Device thread shared by VideoCaptureManager and AudioInputDeviceManager.
+ scoped_ptr<base::Thread> device_thread_;
+
+ media::AudioManager* const audio_manager_; // not owned
+ scoped_refptr<AudioInputDeviceManager> audio_input_device_manager_;
+ scoped_refptr<VideoCaptureManager> video_capture_manager_;
+
+ // Indicator of device monitoring state.
+ bool monitoring_started_;
+
+ // Stores most recently enumerated device lists. The cache is cleared when
+ // monitoring is stopped or there is no request for that type of device.
+ EnumerationCache audio_enumeration_cache_;
+ EnumerationCache video_enumeration_cache_;
+
+ // Keeps track of live enumeration commands sent to VideoCaptureManager or
+ // AudioInputDeviceManager, in order to only enumerate when necessary.
+ int active_enumeration_ref_count_[NUM_MEDIA_TYPES];
+
+ // All non-closed request.
+ DeviceRequests requests_;
+
+ // Hold a pointer to the IO loop to check we delete the device thread and
+ // managers on the right thread.
+ base::MessageLoop* io_loop_;
+
+ bool screen_capture_active_;
+
+ bool use_fake_ui_;
+ scoped_ptr<FakeMediaStreamUIProxy> fake_ui_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/media/media_stream_manager_unittest.cc b/chromium/content/browser/renderer_host/media/media_stream_manager_unittest.cc
new file mode 100644
index 00000000000..91413710020
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_manager_unittest.cc
@@ -0,0 +1,174 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+#include "content/common/media/media_stream_options.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/audio/audio_manager_base.h"
+#if defined(OS_ANDROID)
+#include "media/audio/android/audio_manager_android.h"
+#elif defined(OS_LINUX) || defined(OS_OPENBSD)
+#include "media/audio/linux/audio_manager_linux.h"
+#elif defined(OS_MACOSX)
+#include "media/audio/mac/audio_manager_mac.h"
+#elif defined(OS_WIN)
+#include "media/audio/win/audio_manager_win.h"
+#endif
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace content {
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+typedef media::AudioManagerLinux AudioManagerPlatform;
+#elif defined(OS_MACOSX)
+typedef media::AudioManagerMac AudioManagerPlatform;
+#elif defined(OS_WIN)
+typedef media::AudioManagerWin AudioManagerPlatform;
+#elif defined(OS_ANDROID)
+typedef media::AudioManagerAndroid AudioManagerPlatform;
+#endif
+
+
+// This class mocks the audio manager and overrides the
+// GetAudioInputDeviceNames() method to ensure that we can run our tests on
+// the buildbots. media::AudioManagerBase
+class MockAudioManager : public AudioManagerPlatform {
+ public:
+ MockAudioManager() {}
+ virtual ~MockAudioManager() {}
+
+ virtual void GetAudioInputDeviceNames(
+ media::AudioDeviceNames* device_names) OVERRIDE {
+ if (HasAudioInputDevices()) {
+ AudioManagerBase::GetAudioInputDeviceNames(device_names);
+ } else {
+ device_names->push_back(media::AudioDeviceName("fake_device_name",
+ "fake_device_id"));
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioManager);
+};
+
+class MediaStreamManagerTest : public ::testing::Test {
+ public:
+ MediaStreamManagerTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+ message_loop_(base::MessageLoopProxy::current()) {
+ // Create our own MediaStreamManager.
+ audio_manager_.reset(new MockAudioManager());
+ media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
+
+ // Use fake devices in order to run on the bots.
+ media_stream_manager_->UseFakeDevice();
+ }
+
+ virtual ~MediaStreamManagerTest() {
+ media_stream_manager_->WillDestroyCurrentMessageLoop();
+ }
+
+ MOCK_METHOD1(Response, void(int index));
+ void ResponseCallback(int index,
+ const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUIProxy> ui_proxy) {
+ Response(index);
+ message_loop_->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ }
+
+ protected:
+ std::string MakeMediaAccessRequest(int index) {
+ const int render_process_id = 1;
+ const int render_view_id = 1;
+ const int page_request_id = 1;
+ StreamOptions components(MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ const GURL security_origin;
+ MediaStreamManager::MediaRequestResponseCallback callback =
+ base::Bind(&MediaStreamManagerTest::ResponseCallback,
+ base::Unretained(this), index);
+ return media_stream_manager_->MakeMediaAccessRequest(render_process_id,
+ render_view_id,
+ page_request_id,
+ components,
+ security_origin,
+ callback);
+ }
+
+ scoped_ptr<media::AudioManager> audio_manager_;
+ scoped_ptr<MediaStreamManager> media_stream_manager_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+ base::RunLoop run_loop_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamManagerTest);
+};
+
+TEST_F(MediaStreamManagerTest, MakeMediaAccessRequest) {
+ MakeMediaAccessRequest(0);
+
+ // Expecting the callback will be triggered and quit the test.
+ EXPECT_CALL(*this, Response(0));
+ run_loop_.Run();
+}
+
+TEST_F(MediaStreamManagerTest, MakeAndCancelMediaAccessRequest) {
+ std::string label = MakeMediaAccessRequest(0);
+ // No callback is expected.
+ media_stream_manager_->CancelRequest(label);
+}
+
+TEST_F(MediaStreamManagerTest, MakeMultipleRequests) {
+ // First request.
+ std::string label1 = MakeMediaAccessRequest(0);
+
+ // Second request.
+ int render_process_id = 2;
+ int render_view_id = 2;
+ int page_request_id = 2;
+ StreamOptions components(MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ GURL security_origin;
+ MediaStreamManager::MediaRequestResponseCallback callback =
+ base::Bind(&MediaStreamManagerTest::ResponseCallback,
+ base::Unretained(this), 1);
+ std::string label2 = media_stream_manager_->MakeMediaAccessRequest(
+ render_process_id,
+ render_view_id,
+ page_request_id,
+ components,
+ security_origin,
+ callback);
+
+ // Expecting the callbackS from requests will be triggered and quit the test.
+ // Note, the callbacks might come in a different order depending on the
+ // value of labels.
+ EXPECT_CALL(*this, Response(0));
+ EXPECT_CALL(*this, Response(1));
+ run_loop_.Run();
+}
+
+TEST_F(MediaStreamManagerTest, MakeAndCancelMultipleRequests) {
+ std::string label1 = MakeMediaAccessRequest(0);
+ std::string label2 = MakeMediaAccessRequest(1);
+ media_stream_manager_->CancelRequest(label1);
+
+ // Expecting the callback from the second request will be triggered and
+ // quit the test.
+ EXPECT_CALL(*this, Response(1));
+ run_loop_.Run();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_provider.h b/chromium/content/browser/renderer_host/media/media_stream_provider.h
new file mode 100644
index 00000000000..7f0ff17eb34
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_provider.h
@@ -0,0 +1,94 @@
+// 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.
+
+// MediaStreamProvider is used to capture media of the types defined in
+// MediaStreamType. There is only one MediaStreamProvider instance per media
+// type and a MediaStreamProvider instance can have only one registered
+// listener.
+// The MediaStreamManager is expected to be called on Browser::IO thread and
+// the listener will be called on the same thread.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_PROVIDER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_PROVIDER_H_
+
+#include <list>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace content {
+
+enum MediaStreamProviderError {
+ kMediaStreamOk = 0,
+ kInvalidMediaStreamType,
+ kInvalidSession,
+ kUnknownSession,
+ kDeviceNotAvailable,
+ kDeviceAlreadyInUse,
+ kUnknownError
+};
+
+enum { kInvalidMediaCaptureSessionId = 0xFFFFFFFF };
+
+// Callback class used by MediaStreamProvider.
+class CONTENT_EXPORT MediaStreamProviderListener {
+ public:
+ // Called by a MediaStreamProvider when a stream has been opened.
+ virtual void Opened(MediaStreamType stream_type,
+ int capture_session_id) = 0;
+
+ // Called by a MediaStreamProvider when a stream has been closed.
+ virtual void Closed(MediaStreamType stream_type,
+ int capture_session_id) = 0;
+
+ // Called by a MediaStreamProvider when available devices has been enumerated.
+ virtual void DevicesEnumerated(MediaStreamType stream_type,
+ const StreamDeviceInfoArray& devices) = 0;
+
+ // Called by a MediaStreamProvider when an error has occured.
+ virtual void Error(MediaStreamType stream_type,
+ int capture_session_id,
+ MediaStreamProviderError error) = 0;
+
+ protected:
+ virtual ~MediaStreamProviderListener() {}
+};
+
+// Implemented by a manager class providing captured media.
+class CONTENT_EXPORT MediaStreamProvider
+ : public base::RefCountedThreadSafe<MediaStreamProvider> {
+ public:
+ // Registers a listener and a device message loop.
+ virtual void Register(MediaStreamProviderListener* listener,
+ base::MessageLoopProxy* device_thread_loop) = 0;
+
+ // Unregisters the previously registered listener.
+ virtual void Unregister() = 0;
+
+ // Enumerates existing capture devices and calls |DevicesEnumerated|.
+ virtual void EnumerateDevices(MediaStreamType stream_type) = 0;
+
+ // Opens the specified device. The device is not started and it is still
+ // possible for other applications to open the device before the device is
+ // started. |Opened| is called when the device is opened.
+ // kInvalidMediaCaptureSessionId is returned on error.
+ virtual int Open(const StreamDeviceInfo& device) = 0;
+
+ // Closes the specified device and calls |Closed| when done.
+ virtual void Close(int capture_session_id) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<MediaStreamProvider>;
+ virtual ~MediaStreamProvider() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_PROVIDER_H_
diff --git a/chromium/content/browser/renderer_host/media/media_stream_requester.h b/chromium/content/browser/renderer_host/media/media_stream_requester.h
new file mode 100644
index 00000000000..50858fa3393
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_requester.h
@@ -0,0 +1,44 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_REQUESTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_REQUESTER_H_
+
+#include <string>
+
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+
+namespace content {
+
+// MediaStreamRequester must be implemented by the class requesting a new media
+// stream to be opened. MediaStreamManager will use this interface to signal
+// success and error for a request.
+class CONTENT_EXPORT MediaStreamRequester {
+ public:
+ // Called as a reply of a successful call to GenerateStream.
+ virtual void StreamGenerated(const std::string& label,
+ const StreamDeviceInfoArray& audio_devices,
+ const StreamDeviceInfoArray& video_devices) = 0;
+ // Called if GenerateStream failed.
+ virtual void StreamGenerationFailed(const std::string& label) = 0;
+
+ // Called if stream has been stopped by user request.
+ virtual void StopGeneratedStream(const std::string& label) = 0;
+
+ // Called as a reply of a successful call to EnumerateDevices.
+ virtual void DevicesEnumerated(const std::string& label,
+ const StreamDeviceInfoArray& devices) = 0;
+ // Called as a reply of a successful call to OpenDevice.
+ virtual void DeviceOpened(const std::string& label,
+ const StreamDeviceInfo& device_info) = 0;
+
+ protected:
+ virtual ~MediaStreamRequester() {
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_REQUESTER_H_
diff --git a/chromium/content/browser/renderer_host/media/media_stream_ui_controller_unittest.cc b/chromium/content/browser/renderer_host/media/media_stream_ui_controller_unittest.cc
new file mode 100644
index 00000000000..00a2cc25270
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_ui_controller_unittest.cc
@@ -0,0 +1,170 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_settings_requester.h"
+#include "content/browser/renderer_host/media/media_stream_ui_controller.h"
+#include "content/common/media/media_stream_options.h"
+#include "content/public/common/media_stream_request.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace content {
+
+class MediaStreamDeviceUIControllerTest
+ : public ::testing::Test,
+ public SettingsRequester {
+ public:
+ MediaStreamDeviceUIControllerTest() {}
+
+ // Mock implementation of SettingsRequester.
+ // TODO(sergeyu): Move mock SettingsRequester to a separate class.
+ MOCK_METHOD2(DevicesAccepted, void(
+ const std::string&, const StreamDeviceInfoArray&));
+ MOCK_METHOD1(SettingsError, void(const std::string&));
+ MOCK_METHOD1(StopStreamFromUI, void(const std::string&));
+ void GetAvailableDevices(MediaStreamDevices* devices) OVERRIDE {
+ devices->push_back(MediaStreamDevice(MEDIA_DEVICE_AUDIO_CAPTURE,
+ "mic",
+ "mic_id",
+ 0,
+ 0));
+ devices->push_back(MediaStreamDevice(MEDIA_DEVICE_VIDEO_CAPTURE,
+ "camera",
+ "camera_id"));
+ }
+
+ protected:
+ virtual void SetUp() {
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+ ui_thread_.reset(new BrowserThreadImpl(BrowserThread::UI,
+ message_loop_.get()));
+ io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
+ message_loop_.get()));
+ ui_controller_.reset(new MediaStreamUIController(this));
+ }
+
+ virtual void TearDown() {
+ message_loop_->RunUntilIdle();
+ }
+
+ void CreateDummyRequest(const std::string& label, bool audio, bool video) {
+ int dummy_render_process_id = 1;
+ int dummy_render_view_id = 1;
+ StreamOptions components(
+ audio ? MEDIA_DEVICE_AUDIO_CAPTURE : MEDIA_NO_SERVICE,
+ video ? MEDIA_DEVICE_VIDEO_CAPTURE : MEDIA_NO_SERVICE);
+ GURL security_origin;
+ ui_controller_->MakeUIRequest(label,
+ dummy_render_process_id,
+ dummy_render_view_id,
+ components,
+ security_origin,
+ MEDIA_GENERATE_STREAM,
+ std::string());
+ }
+
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<BrowserThreadImpl> ui_thread_;
+ scoped_ptr<BrowserThreadImpl> io_thread_;
+ scoped_ptr<MediaStreamUIController> ui_controller_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamDeviceUIControllerTest);
+};
+
+TEST_F(MediaStreamDeviceUIControllerTest, GenerateRequest) {
+ const std::string label = "dummy_label";
+ CreateDummyRequest(label, true, false);
+
+ // Expecting an error callback triggered by the non-existing
+ // RenderViewHostImpl.
+ EXPECT_CALL(*this, SettingsError(label));
+}
+
+TEST_F(MediaStreamDeviceUIControllerTest, GenerateAndRemoveRequest) {
+ const std::string label = "label";
+ CreateDummyRequest(label, true, false);
+
+ // Remove the current request, it should not crash.
+ ui_controller_->CancelUIRequest(label);
+}
+
+TEST_F(MediaStreamDeviceUIControllerTest, HandleRequestUsingFakeUI) {
+ ui_controller_->UseFakeUI(scoped_ptr<MediaStreamUI>());
+
+ const std::string label = "label";
+ CreateDummyRequest(label, true, true);
+
+ // Remove the current request, it should not crash.
+ EXPECT_CALL(*this, DevicesAccepted(label, _));
+
+ message_loop_->RunUntilIdle();
+
+ ui_controller_->NotifyUIIndicatorDevicesClosed(label);
+}
+
+TEST_F(MediaStreamDeviceUIControllerTest, CreateRequestsAndCancelTheFirst) {
+ ui_controller_->UseFakeUI(scoped_ptr<MediaStreamUI>());
+
+ // Create the first audio request.
+ const std::string label_1 = "label_1";
+ CreateDummyRequest(label_1, true, false);
+
+ // Create the second video request.
+ const std::string label_2 = "label_2";
+ CreateDummyRequest(label_2, false, true);
+
+ // Create the third audio and video request.
+ const std::string label_3 = "label_3";
+ CreateDummyRequest(label_3, true, true);
+
+ // Remove the first request which has been brought to the UI.
+ ui_controller_->CancelUIRequest(label_1);
+
+ // We should get callbacks from the rest of the requests.
+ EXPECT_CALL(*this, DevicesAccepted(label_2, _));
+ EXPECT_CALL(*this, DevicesAccepted(label_3, _));
+
+ message_loop_->RunUntilIdle();
+
+ ui_controller_->NotifyUIIndicatorDevicesClosed(label_2);
+ ui_controller_->NotifyUIIndicatorDevicesClosed(label_3);
+}
+
+TEST_F(MediaStreamDeviceUIControllerTest, CreateRequestsAndCancelTheLast) {
+ ui_controller_->UseFakeUI(scoped_ptr<MediaStreamUI>());
+
+ // Create the first audio request.
+ const std::string label_1 = "label_1";
+ CreateDummyRequest(label_1, true, false);
+
+ // Create the second video request.
+ const std::string label_2 = "label_2";
+ CreateDummyRequest(label_2, false, true);
+
+ // Create the third audio and video request.
+ const std::string label_3 = "label_3";
+ CreateDummyRequest(label_3, true, true);
+
+ // Remove the last request which is pending in the queue.
+ ui_controller_->CancelUIRequest(label_3);
+
+ // We should get callbacks from the rest of the requests.
+ EXPECT_CALL(*this, DevicesAccepted(label_1, _));
+ EXPECT_CALL(*this, DevicesAccepted(label_2, _));
+
+ message_loop_->RunUntilIdle();
+
+ ui_controller_->NotifyUIIndicatorDevicesClosed(label_1);
+ ui_controller_->NotifyUIIndicatorDevicesClosed(label_2);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.cc b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.cc
new file mode 100644
index 00000000000..3e4edbc0ac0
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.cc
@@ -0,0 +1,216 @@
+// 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 "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/video/capture/fake_video_capture_device.h"
+
+namespace content {
+
+class MediaStreamUIProxy::Core {
+ public:
+ explicit Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
+ RenderViewHostDelegate* test_render_delegate);
+ ~Core();
+
+ void RequestAccess(const MediaStreamRequest& request);
+ void OnStarted();
+
+ private:
+ void ProcessAccessRequestResponse(const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUI> stream_ui);
+ void ProcessStopRequestFromUI();
+
+ base::WeakPtr<MediaStreamUIProxy> proxy_;
+ scoped_ptr<MediaStreamUI> ui_;
+
+ RenderViewHostDelegate* const test_render_delegate_;
+
+ // WeakPtr<> is used to RequestMediaAccessPermission() because there is no way
+ // cancel media requests.
+ base::WeakPtrFactory<Core> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+MediaStreamUIProxy::Core::Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
+ RenderViewHostDelegate* test_render_delegate)
+ : proxy_(proxy),
+ test_render_delegate_(test_render_delegate),
+ weak_factory_(this) {
+}
+
+MediaStreamUIProxy::Core::~Core() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+void MediaStreamUIProxy::Core::RequestAccess(
+ const MediaStreamRequest& request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RenderViewHostDelegate* render_delegate;
+
+ if (test_render_delegate_) {
+ render_delegate = test_render_delegate_;
+ } else {
+ RenderViewHostImpl* host = RenderViewHostImpl::FromID(
+ request.render_process_id, request.render_view_id);
+
+ // Tab may have gone away.
+ if (!host || !host->GetDelegate()) {
+ ProcessAccessRequestResponse(
+ MediaStreamDevices(), scoped_ptr<MediaStreamUI>());
+ return;
+ }
+
+ render_delegate = host->GetDelegate();
+ }
+
+ render_delegate->RequestMediaAccessPermission(
+ request, base::Bind(&Core::ProcessAccessRequestResponse,
+ weak_factory_.GetWeakPtr()));
+}
+
+void MediaStreamUIProxy::Core::OnStarted() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (ui_) {
+ ui_->OnStarted(base::Bind(&Core::ProcessStopRequestFromUI,
+ base::Unretained(this)));
+ }
+}
+
+void MediaStreamUIProxy::Core::ProcessAccessRequestResponse(
+ const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUI> stream_ui) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ ui_ = stream_ui.Pass();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaStreamUIProxy::ProcessAccessRequestResponse,
+ proxy_, devices));
+}
+
+void MediaStreamUIProxy::Core::ProcessStopRequestFromUI() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaStreamUIProxy::ProcessStopRequestFromUI, proxy_));
+}
+
+// static
+scoped_ptr<MediaStreamUIProxy> MediaStreamUIProxy::Create() {
+ return scoped_ptr<MediaStreamUIProxy>(new MediaStreamUIProxy(NULL));
+}
+
+// static
+scoped_ptr<MediaStreamUIProxy> MediaStreamUIProxy::CreateForTests(
+ RenderViewHostDelegate* render_delegate) {
+ return scoped_ptr<MediaStreamUIProxy>(
+ new MediaStreamUIProxy(render_delegate));
+}
+
+MediaStreamUIProxy::MediaStreamUIProxy(
+ RenderViewHostDelegate* test_render_delegate)
+ : weak_factory_(this) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ core_.reset(new Core(weak_factory_.GetWeakPtr(), test_render_delegate));
+}
+
+MediaStreamUIProxy::~MediaStreamUIProxy() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, core_.release());
+}
+
+void MediaStreamUIProxy::RequestAccess(
+ const MediaStreamRequest& request,
+ const ResponseCallback& response_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ response_callback_ = response_callback;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Core::RequestAccess, base::Unretained(core_.get()), request));
+}
+
+void MediaStreamUIProxy::OnStarted(const base::Closure& stop_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ stop_callback_ = stop_callback;
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&Core::OnStarted, base::Unretained(core_.get())));
+}
+
+void MediaStreamUIProxy::ProcessAccessRequestResponse(
+ const MediaStreamDevices& devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!response_callback_.is_null());
+
+ ResponseCallback cb = response_callback_;
+ response_callback_.Reset();
+ cb.Run(devices);
+}
+
+void MediaStreamUIProxy::ProcessStopRequestFromUI() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!stop_callback_.is_null());
+
+ base::Closure cb = stop_callback_;
+ stop_callback_.Reset();
+ cb.Run();
+}
+
+FakeMediaStreamUIProxy::FakeMediaStreamUIProxy()
+ : MediaStreamUIProxy(NULL) {
+}
+
+FakeMediaStreamUIProxy::~FakeMediaStreamUIProxy() {}
+
+void FakeMediaStreamUIProxy::SetAvailableDevices(
+ const MediaStreamDevices& devices) {
+ devices_ = devices;
+}
+
+void FakeMediaStreamUIProxy::RequestAccess(
+ const MediaStreamRequest& request,
+ const ResponseCallback& response_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ response_callback_ = response_callback;
+
+ MediaStreamDevices devices_to_use;
+ bool accepted_audio = false;
+ bool accepted_video = false;
+ // Use the first capture device of the same media type in the list for the
+ // fake UI.
+ for (MediaStreamDevices::const_iterator it = devices_.begin();
+ it != devices_.end(); ++it) {
+ if (!accepted_audio &&
+ IsAudioMediaType(request.audio_type) &&
+ IsAudioMediaType(it->type)) {
+ devices_to_use.push_back(*it);
+ accepted_audio = true;
+ } else if (!accepted_video &&
+ IsVideoMediaType(request.video_type) &&
+ IsVideoMediaType(it->type)) {
+ devices_to_use.push_back(*it);
+ accepted_video = true;
+ }
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&MediaStreamUIProxy::ProcessAccessRequestResponse,
+ weak_factory_.GetWeakPtr(), devices_to_use));
+}
+
+void FakeMediaStreamUIProxy::OnStarted(const base::Closure& stop_callback) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.h b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.h
new file mode 100644
index 00000000000..62bbeb84495
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy.h
@@ -0,0 +1,88 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_UI_PROXY_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_UI_PROXY_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace content {
+
+class RenderViewHostDelegate;
+
+// MediaStreamUIProxy proxies calls to media stream UI between IO thread and UI
+// thread. One instance of this class is create per MediaStream object. It must
+// be create, used and destroyed on IO thread.
+class CONTENT_EXPORT MediaStreamUIProxy {
+ public:
+ typedef base::Callback<
+ void (const MediaStreamDevices& devices)> ResponseCallback;
+
+ static scoped_ptr<MediaStreamUIProxy> Create();
+ static scoped_ptr<MediaStreamUIProxy> CreateForTests(
+ RenderViewHostDelegate* render_delegate);
+
+ virtual ~MediaStreamUIProxy();
+
+ // Requests access for the MediaStream by calling
+ // WebContentsDelegate::RequestMediaAccessPermission(). The specified
+ // |response_callback| is called when the WebContentsDelegate approves or
+ // denies request.
+ virtual void RequestAccess(const MediaStreamRequest& request,
+ const ResponseCallback& response_callback);
+
+ // Notifies the UI that the MediaStream has been started. Must be called after
+ // access has been approved using RequestAccess(). |stop_callback| is be
+ // called on the IO thread after the user has requests the stream to be
+ // stopped.
+ virtual void OnStarted(const base::Closure& stop_callback);
+
+ void SetRenderViewHostDelegateForTests(RenderViewHostDelegate* delegate);
+
+ protected:
+ MediaStreamUIProxy(RenderViewHostDelegate* test_render_delegate);
+
+ private:
+ class Core;
+ friend class Core;
+ friend class FakeMediaStreamUIProxy;
+
+ void ProcessAccessRequestResponse(const MediaStreamDevices& devices);
+ void ProcessStopRequestFromUI();
+
+ scoped_ptr<Core> core_;
+ ResponseCallback response_callback_;
+ base::Closure stop_callback_;
+
+ base::WeakPtrFactory<MediaStreamUIProxy> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaStreamUIProxy);
+};
+
+class CONTENT_EXPORT FakeMediaStreamUIProxy : public MediaStreamUIProxy {
+ public:
+ explicit FakeMediaStreamUIProxy();
+ virtual ~FakeMediaStreamUIProxy();
+
+ void SetAvailableDevices(const MediaStreamDevices& devices);
+
+ // MediaStreamUIProxy overrides.
+ virtual void RequestAccess(
+ const MediaStreamRequest& request,
+ const ResponseCallback& response_callback) OVERRIDE;
+ virtual void OnStarted(const base::Closure& stop_callback) OVERRIDE;
+
+ private:
+ MediaStreamDevices devices_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeMediaStreamUIProxy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_UI_PROXY_H_
diff --git a/chromium/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
new file mode 100644
index 00000000000..82219a6e872
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
@@ -0,0 +1,219 @@
+// 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 "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/public/common/renderer_preferences.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+
+using testing::_;
+using testing::SaveArg;
+
+namespace content {
+namespace {
+
+class MockRenderViewHostDelegate : public RenderViewHostDelegate {
+ public:
+ MOCK_METHOD2(RequestMediaAccessPermission,
+ void(const MediaStreamRequest& request,
+ const MediaResponseCallback& callback));
+
+ // Stubs for pure virtual methods we don't care about.
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE {
+ NOTREACHED();
+ return gfx::Rect();
+ }
+ virtual RendererPreferences GetRendererPrefs(
+ BrowserContext* browser_context) const OVERRIDE {
+ NOTREACHED();
+ return RendererPreferences();
+ }
+};
+
+class MockResponseCallback {
+ public:
+ MOCK_METHOD1(OnAccessRequestResponse,
+ void(const MediaStreamDevices& devices));
+};
+
+class MockMediaStreamUI : public MediaStreamUI {
+ public:
+ MOCK_METHOD1(OnStarted, void(const base::Closure& stop));
+};
+
+class MockStopStreamHandler {
+ public:
+ MOCK_METHOD0(OnStop, void());
+};
+
+
+} // namespace
+
+class MediaStreamUIProxyTest : public testing::Test {
+ public:
+ MediaStreamUIProxyTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_) {
+ proxy_ = MediaStreamUIProxy::CreateForTests(&delegate_);
+ }
+
+ virtual ~MediaStreamUIProxyTest() {
+ proxy_.reset();
+ message_loop_.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ TestBrowserThread ui_thread_;
+ TestBrowserThread io_thread_;
+
+ MockRenderViewHostDelegate delegate_;
+ MockResponseCallback response_callback_;
+ scoped_ptr<MediaStreamUIProxy> proxy_;
+};
+
+MATCHER_P(SameRequest, expected, "") {
+ return
+ expected.render_process_id == arg.render_process_id &&
+ expected.render_view_id == arg.render_view_id &&
+ expected.tab_capture_device_id == arg.tab_capture_device_id &&
+ expected.security_origin == arg.security_origin &&
+ expected.request_type == arg.request_type &&
+ expected.requested_audio_device_id == arg.requested_audio_device_id &&
+ expected.requested_video_device_id == arg.requested_video_device_id &&
+ expected.audio_type == arg.audio_type &&
+ expected.video_type == arg.video_type;
+}
+
+TEST_F(MediaStreamUIProxyTest, Deny) {
+ MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"),
+ MEDIA_GENERATE_STREAM, std::string(),
+ std::string(),
+ MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ proxy_->RequestAccess(
+ request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
+ base::Unretained(&response_callback_)));
+ MediaResponseCallback callback;
+ EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
+ .WillOnce(SaveArg<1>(&callback));
+ message_loop_.RunUntilIdle();
+ ASSERT_FALSE(callback.is_null());
+
+ MediaStreamDevices devices;
+ callback.Run(devices, scoped_ptr<MediaStreamUI>());
+
+ MediaStreamDevices response;
+ EXPECT_CALL(response_callback_, OnAccessRequestResponse(_))
+ .WillOnce(SaveArg<0>(&response));
+ message_loop_.RunUntilIdle();
+
+ EXPECT_TRUE(response.empty());
+}
+
+TEST_F(MediaStreamUIProxyTest, AcceptAndStart) {
+ MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"),
+ MEDIA_GENERATE_STREAM, std::string(),
+ std::string(),
+ MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ proxy_->RequestAccess(
+ request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
+ base::Unretained(&response_callback_)));
+ MediaResponseCallback callback;
+ EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
+ .WillOnce(SaveArg<1>(&callback));
+ message_loop_.RunUntilIdle();
+ ASSERT_FALSE(callback.is_null());
+
+ MediaStreamDevices devices;
+ devices.push_back(
+ MediaStreamDevice(MEDIA_DEVICE_AUDIO_CAPTURE, "Mic", "Mic"));
+ scoped_ptr<MockMediaStreamUI> ui(new MockMediaStreamUI());
+ EXPECT_CALL(*ui, OnStarted(_));
+ callback.Run(devices, ui.PassAs<MediaStreamUI>());
+
+ MediaStreamDevices response;
+ EXPECT_CALL(response_callback_, OnAccessRequestResponse(_))
+ .WillOnce(SaveArg<0>(&response));
+ message_loop_.RunUntilIdle();
+
+ EXPECT_FALSE(response.empty());
+
+ proxy_->OnStarted(base::Closure());
+ message_loop_.RunUntilIdle();
+}
+
+// Verify that the proxy can be deleted before the request is processed.
+TEST_F(MediaStreamUIProxyTest, DeleteBeforeAccepted) {
+ MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"),
+ MEDIA_GENERATE_STREAM, std::string(),
+ std::string(),
+ MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ proxy_->RequestAccess(
+ request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
+ base::Unretained(&response_callback_)));
+ MediaResponseCallback callback;
+ EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
+ .WillOnce(SaveArg<1>(&callback));
+ message_loop_.RunUntilIdle();
+ ASSERT_FALSE(callback.is_null());
+
+ proxy_.reset();
+
+ MediaStreamDevices devices;
+ scoped_ptr<MediaStreamUI> ui;
+ callback.Run(devices, ui.Pass());
+}
+
+TEST_F(MediaStreamUIProxyTest, StopFromUI) {
+ MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"),
+ MEDIA_GENERATE_STREAM, std::string(),
+ std::string(),
+ MEDIA_DEVICE_AUDIO_CAPTURE,
+ MEDIA_DEVICE_VIDEO_CAPTURE);
+ proxy_->RequestAccess(
+ request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
+ base::Unretained(&response_callback_)));
+ MediaResponseCallback callback;
+ EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
+ .WillOnce(SaveArg<1>(&callback));
+ message_loop_.RunUntilIdle();
+ ASSERT_FALSE(callback.is_null());
+
+ base::Closure stop_callback;
+
+ MediaStreamDevices devices;
+ devices.push_back(
+ MediaStreamDevice(MEDIA_DEVICE_AUDIO_CAPTURE, "Mic", "Mic"));
+ scoped_ptr<MockMediaStreamUI> ui(new MockMediaStreamUI());
+ EXPECT_CALL(*ui, OnStarted(_))
+ .WillOnce(SaveArg<0>(&stop_callback));
+ callback.Run(devices, ui.PassAs<MediaStreamUI>());
+
+ MediaStreamDevices response;
+ EXPECT_CALL(response_callback_, OnAccessRequestResponse(_))
+ .WillOnce(SaveArg<0>(&response));
+ message_loop_.RunUntilIdle();
+
+ EXPECT_FALSE(response.empty());
+
+ MockStopStreamHandler stop_handler;
+ proxy_->OnStarted(base::Bind(&MockStopStreamHandler::OnStop,
+ base::Unretained(&stop_handler)));
+ message_loop_.RunUntilIdle();
+
+ ASSERT_FALSE(stop_callback.is_null());
+ EXPECT_CALL(stop_handler, OnStop());
+ stop_callback.Run();
+ message_loop_.RunUntilIdle();
+}
+
+} // content
diff --git a/chromium/content/browser/renderer_host/media/midi_dispatcher_host.cc b/chromium/content/browser/renderer_host/media/midi_dispatcher_host.cc
new file mode 100644
index 00000000000..8ecbabcd47d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/midi_dispatcher_host.cc
@@ -0,0 +1,63 @@
+// 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 "content/browser/renderer_host/media/midi_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/media/midi_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "url/gurl.h"
+
+namespace content {
+
+MIDIDispatcherHost::MIDIDispatcherHost(int render_process_id,
+ BrowserContext* browser_context)
+ : render_process_id_(render_process_id),
+ browser_context_(browser_context) {
+}
+
+MIDIDispatcherHost::~MIDIDispatcherHost() {
+}
+
+bool MIDIDispatcherHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MIDIDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(MIDIHostMsg_RequestSysExPermission,
+ OnRequestSysExPermission)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void MIDIDispatcherHost::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+ if (message.type() == MIDIHostMsg_RequestSysExPermission::ID)
+ *thread = BrowserThread::UI;
+}
+
+void MIDIDispatcherHost::OnRequestSysExPermission(int render_view_id,
+ int client_id,
+ const GURL& origin) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ browser_context_->RequestMIDISysExPermission(
+ render_process_id_,
+ render_view_id,
+ origin,
+ base::Bind(&MIDIDispatcherHost::WasSysExPermissionGranted,
+ base::Unretained(this),
+ render_view_id,
+ client_id));
+}
+
+void MIDIDispatcherHost::WasSysExPermissionGranted(int render_view_id,
+ int client_id,
+ bool success) {
+ Send(new MIDIMsg_SysExPermissionApproved(render_view_id, client_id, success));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/midi_dispatcher_host.h b/chromium/content/browser/renderer_host/media/midi_dispatcher_host.h
new file mode 100644
index 00000000000..ee861551b44
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/midi_dispatcher_host.h
@@ -0,0 +1,48 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_DISPATCHER_HOST_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+class GURL;
+
+namespace content {
+
+class BrowserContext;
+
+// MIDIDispatcherHost handles permissions for using system exclusive messages.
+// It works as BrowserMessageFilter to handle IPC messages between
+// MIDIDispatcher running as a RenderViewObserver.
+class MIDIDispatcherHost : public BrowserMessageFilter {
+ public:
+ MIDIDispatcherHost(int render_process_id, BrowserContext* browser_context);
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) OVERRIDE;
+
+ protected:
+ virtual ~MIDIDispatcherHost();
+
+ private:
+ void OnRequestSysExPermission(int render_view_id,
+ int client_id,
+ const GURL& origin);
+ void WasSysExPermissionGranted(int render_view_id,
+ int client_id,
+ bool success);
+
+ int render_process_id_;
+ BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(MIDIDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/midi_host.cc b/chromium/content/browser/renderer_host/media/midi_host.cc
new file mode 100644
index 00000000000..6ed473afeff
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/midi_host.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/midi_host.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/debug/trace_event.h"
+#include "base/process/process.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/media/media_internals.h"
+#include "content/common/media/midi_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/media_observer.h"
+#include "media/midi/midi_manager.h"
+
+using media::MIDIManager;
+using media::MIDIPortInfoList;
+
+// The total number of bytes which we're allowed to send to the OS
+// before knowing that they have been successfully sent.
+static const size_t kMaxInFlightBytes = 10 * 1024 * 1024; // 10 MB.
+
+// We keep track of the number of bytes successfully sent to
+// the hardware. Every once in a while we report back to the renderer
+// the number of bytes sent since the last report. This threshold determines
+// how many bytes will be sent before reporting back to the renderer.
+static const size_t kAcknowledgementThresholdBytes = 1024 * 1024; // 1 MB.
+
+static const uint8 kSysExMessage = 0xf0;
+
+namespace content {
+
+MIDIHost::MIDIHost(media::MIDIManager* midi_manager)
+ : midi_manager_(midi_manager),
+ sent_bytes_in_flight_(0),
+ bytes_sent_since_last_acknowledgement_(0) {
+}
+
+MIDIHost::~MIDIHost() {
+ if (midi_manager_)
+ midi_manager_->EndSession(this);
+}
+
+void MIDIHost::OnChannelClosing() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ BrowserMessageFilter::OnChannelClosing();
+}
+
+void MIDIHost::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// IPC Messages handler
+bool MIDIHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MIDIHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(MIDIHostMsg_StartSession, OnStartSession)
+ IPC_MESSAGE_HANDLER(MIDIHostMsg_SendData, OnSendData)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void MIDIHost::OnStartSession(int client_id) {
+ MIDIPortInfoList input_ports;
+ MIDIPortInfoList output_ports;
+
+ // Initialize devices and register to receive MIDI data.
+ bool success = false;
+ if (midi_manager_) {
+ success = midi_manager_->StartSession(this);
+ if (success) {
+ input_ports = midi_manager_->input_ports();
+ output_ports = midi_manager_->output_ports();
+ }
+ }
+
+ Send(new MIDIMsg_SessionStarted(
+ client_id,
+ success,
+ input_ports,
+ output_ports));
+}
+
+void MIDIHost::OnSendData(int port,
+ const std::vector<uint8>& data,
+ double timestamp) {
+ if (!midi_manager_)
+ return;
+
+ base::AutoLock auto_lock(in_flight_lock_);
+
+ // Sanity check that we won't send too much.
+ if (sent_bytes_in_flight_ > kMaxInFlightBytes ||
+ data.size() > kMaxInFlightBytes ||
+ data.size() + sent_bytes_in_flight_ > kMaxInFlightBytes)
+ return;
+
+ // For now disallow all System Exclusive messages even if we
+ // have permission.
+ // TODO(toyoshim): allow System Exclusive if browser has granted
+ // this client access. We'll likely need to pass a GURL
+ // here to compare against our permissions.
+ if (data.size() > 0 && data[0] >= kSysExMessage)
+ return;
+
+#if defined(OS_ANDROID)
+ // TODO(toyoshim): figure out why data() method does not compile on Android.
+ NOTIMPLEMENTED();
+#else
+ midi_manager_->DispatchSendMIDIData(
+ this,
+ port,
+ data.data(),
+ data.size(),
+ timestamp);
+#endif
+
+ sent_bytes_in_flight_ += data.size();
+}
+
+void MIDIHost::ReceiveMIDIData(
+ int port_index,
+ const uint8* data,
+ size_t length,
+ double timestamp) {
+ TRACE_EVENT0("midi", "MIDIHost::ReceiveMIDIData");
+
+ // For now disallow all System Exclusive messages even if we
+ // have permission.
+ // TODO(toyoshim): allow System Exclusive if browser has granted
+ // this client access. We'll likely need to pass a GURL
+ // here to compare against our permissions.
+ if (length > 0 && data[0] >= kSysExMessage)
+ return;
+
+ // Send to the renderer.
+ std::vector<uint8> v(data, data + length);
+ Send(new MIDIMsg_DataReceived(port_index, v, timestamp));
+}
+
+void MIDIHost::AccumulateMIDIBytesSent(size_t n) {
+ {
+ base::AutoLock auto_lock(in_flight_lock_);
+ if (n <= sent_bytes_in_flight_)
+ sent_bytes_in_flight_ -= n;
+ }
+
+ if (bytes_sent_since_last_acknowledgement_ + n >=
+ bytes_sent_since_last_acknowledgement_)
+ bytes_sent_since_last_acknowledgement_ += n;
+
+ if (bytes_sent_since_last_acknowledgement_ >=
+ kAcknowledgementThresholdBytes) {
+ Send(new MIDIMsg_AcknowledgeSentData(
+ bytes_sent_since_last_acknowledgement_));
+ bytes_sent_since_last_acknowledgement_ = 0;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/midi_host.h b/chromium/content/browser/renderer_host/media/midi_host.h
new file mode 100644
index 00000000000..f6b2813264e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/midi_host.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_HOST_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/midi/midi_manager.h"
+
+namespace media {
+class MIDIManager;
+}
+
+namespace content {
+
+class CONTENT_EXPORT MIDIHost
+ : public BrowserMessageFilter,
+ public media::MIDIManagerClient {
+ public:
+ // Called from UI thread from the owner of this object.
+ MIDIHost(media::MIDIManager* midi_manager);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // MIDIManagerClient implementation.
+ virtual void ReceiveMIDIData(
+ int port_index,
+ const uint8* data,
+ size_t length,
+ double timestamp) OVERRIDE;
+ virtual void AccumulateMIDIBytesSent(size_t n) OVERRIDE;
+
+ // Start session to access MIDI hardware.
+ void OnStartSession(int client_id);
+
+ // Data to be sent to a MIDI output port.
+ void OnSendData(int port,
+ const std::vector<uint8>& data,
+ double timestamp);
+
+ private:
+ friend class base::DeleteHelper<MIDIHost>;
+ friend class BrowserThread;
+
+ virtual ~MIDIHost();
+
+ // |midi_manager_| talks to the platform-specific MIDI APIs.
+ // It can be NULL if the platform (or our current implementation)
+ // does not support MIDI. If not supported then a call to
+ // OnRequestAccess() will always refuse access and a call to
+ // OnSendData() will do nothing.
+ media::MIDIManager* const midi_manager_;
+
+ // The number of bytes sent to the platform-specific MIDI sending
+ // system, but not yet completed.
+ size_t sent_bytes_in_flight_;
+
+ // The number of bytes successfully sent since the last time
+ // we've acknowledged back to the renderer.
+ size_t bytes_sent_since_last_acknowledgement_;
+
+ // Protects access to |sent_bytes_in_flight_|.
+ base::Lock in_flight_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MIDIHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MIDI_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/mock_media_observer.cc b/chromium/content/browser/renderer_host/media/mock_media_observer.cc
new file mode 100644
index 00000000000..f6d4e56cbab
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/mock_media_observer.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/media/mock_media_observer.h"
+
+namespace content {
+
+MockMediaObserver::MockMediaObserver() {}
+
+MockMediaObserver::~MockMediaObserver() {}
+
+MockMediaInternals::MockMediaInternals() {}
+
+MockMediaInternals::~MockMediaInternals() {}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/mock_media_observer.h b/chromium/content/browser/renderer_host/media/mock_media_observer.h
new file mode 100644
index 00000000000..fc3734744ea
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/mock_media_observer.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_MOCK_MEDIA_OBSERVER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MOCK_MEDIA_OBSERVER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "content/browser/media/media_internals.h"
+#include "content/public/browser/media_observer.h"
+#include "media/base/media_log_event.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+
+class MockMediaObserver : public MediaObserver {
+ public:
+ MockMediaObserver();
+ virtual ~MockMediaObserver();
+
+ MOCK_METHOD1(OnAudioCaptureDevicesChanged,
+ void(const MediaStreamDevices& devices));
+ MOCK_METHOD1(OnVideoCaptureDevicesChanged,
+ void(const MediaStreamDevices& devices));
+ MOCK_METHOD5(OnMediaRequestStateChanged,
+ void(int render_process_id, int render_view_id,
+ int page_request_id,
+ const MediaStreamDevice& device,
+ const MediaRequestState state));
+ MOCK_METHOD6(OnAudioStreamPlayingChanged,
+ void(int render_process_id,
+ int render_view_id,
+ int stream_id,
+ bool is_playing,
+ float power_dbfs,
+ bool clipped));
+};
+
+class MockMediaInternals : public MediaInternals {
+ public:
+ MockMediaInternals();
+ virtual ~MockMediaInternals();
+
+ MOCK_METHOD2(OnDeleteAudioStream,
+ void(void* host, int stream_id));
+ MOCK_METHOD3(OnSetAudioStreamPlaying,
+ void(void* host, int stream_id, bool playing));
+ MOCK_METHOD3(OnSetAudioStreamStatus,
+ void(void* host, int stream_id, const std::string& status));
+ MOCK_METHOD3(OnSetAudioStreamVolume,
+ void(void* host, int stream_id, double volume));
+ MOCK_METHOD2(OnMediaEvent,
+ void(int source, const media::MediaLogEvent& event));
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MOCK_MEDIA_OBSERVER_H_
diff --git a/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.cc b/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.cc
new file mode 100644
index 00000000000..b986055e700
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/peer_connection_tracker_host.h"
+
+#include "content/browser/media/webrtc_internals.h"
+#include "content/common/media/peer_connection_tracker_messages.h"
+
+namespace content {
+
+PeerConnectionTrackerHost::PeerConnectionTrackerHost(int render_process_id)
+ : render_process_id_(render_process_id) {}
+
+bool PeerConnectionTrackerHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+
+ IPC_BEGIN_MESSAGE_MAP_EX(PeerConnectionTrackerHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(PeerConnectionTrackerHost_AddPeerConnection,
+ OnAddPeerConnection)
+ IPC_MESSAGE_HANDLER(PeerConnectionTrackerHost_RemovePeerConnection,
+ OnRemovePeerConnection)
+ IPC_MESSAGE_HANDLER(PeerConnectionTrackerHost_UpdatePeerConnection,
+ OnUpdatePeerConnection)
+ IPC_MESSAGE_HANDLER(PeerConnectionTrackerHost_AddStats, OnAddStats)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void PeerConnectionTrackerHost::OverrideThreadForMessage(
+ const IPC::Message& message, BrowserThread::ID* thread) {
+ if (IPC_MESSAGE_CLASS(message) == PeerConnectionTrackerMsgStart)
+ *thread = BrowserThread::UI;
+}
+
+PeerConnectionTrackerHost::~PeerConnectionTrackerHost() {
+}
+
+void PeerConnectionTrackerHost::OnAddPeerConnection(
+ const PeerConnectionInfo& info) {
+ WebRTCInternals::GetInstance()->OnAddPeerConnection(
+ render_process_id_,
+ peer_pid(),
+ info.lid,
+ info.url,
+ info.servers,
+ info.constraints);
+}
+
+void PeerConnectionTrackerHost::OnRemovePeerConnection(int lid) {
+ WebRTCInternals::GetInstance()->OnRemovePeerConnection(peer_pid(), lid);
+}
+
+void PeerConnectionTrackerHost::OnUpdatePeerConnection(
+ int lid, const std::string& type, const std::string& value) {
+ WebRTCInternals::GetInstance()->OnUpdatePeerConnection(
+ peer_pid(),
+ lid,
+ type,
+ value);
+}
+
+void PeerConnectionTrackerHost::OnAddStats(int lid,
+ const base::ListValue& value) {
+ WebRTCInternals::GetInstance()->OnAddStats(peer_pid(), lid, value);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.h b/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.h
new file mode 100644
index 00000000000..2803e7be602
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/peer_connection_tracker_host.h
@@ -0,0 +1,49 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_PEER_CONNECTION_TRACKER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_PEER_CONNECTION_TRACKER_HOST_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+struct PeerConnectionInfo;
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace content {
+
+// This class is the host for PeerConnectionTracker in the browser process
+// managed by RenderProcessHostImpl. It passes IPC messages between
+// WebRTCInternals and PeerConnectionTracker.
+class PeerConnectionTrackerHost : public BrowserMessageFilter {
+ public:
+ PeerConnectionTrackerHost(int render_process_id);
+
+ // content::BrowserMessageFilter override.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OverrideThreadForMessage(const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+
+ protected:
+ virtual ~PeerConnectionTrackerHost();
+
+ private:
+ // Handlers for peer connection messages coming from the renderer.
+ void OnAddPeerConnection(const PeerConnectionInfo& info);
+ void OnRemovePeerConnection(int lid);
+ void OnUpdatePeerConnection(
+ int lid, const std::string& type, const std::string& value);
+ void OnAddStats(int lid, const base::ListValue& value);
+
+ int render_process_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeerConnectionTrackerHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_PEER_CONNECTION_TRACKER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.cc b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.cc
new file mode 100644
index 00000000000..207f86a99f3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/video_capture_buffer_pool.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+
+namespace content {
+
+VideoCaptureBufferPool::VideoCaptureBufferPool(size_t size, int count)
+ : size_(size),
+ count_(count) {
+}
+
+VideoCaptureBufferPool::~VideoCaptureBufferPool() {
+}
+
+bool VideoCaptureBufferPool::Allocate() {
+ base::AutoLock lock(lock_);
+ DCHECK(!IsAllocated());
+ buffers_.resize(count_);
+ for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
+ Buffer* buffer = new Buffer();
+ buffers_[buffer_id] = buffer;
+ if (!buffer->shared_memory.CreateAndMapAnonymous(GetMemorySize()))
+ return false;
+ }
+ return true;
+}
+
+base::SharedMemoryHandle VideoCaptureBufferPool::ShareToProcess(
+ int buffer_id,
+ base::ProcessHandle process_handle) {
+ base::AutoLock lock(lock_);
+ DCHECK(IsAllocated());
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count_);
+ Buffer* buffer = buffers_[buffer_id];
+ base::SharedMemoryHandle remote_handle;
+ buffer->shared_memory.ShareToProcess(process_handle, &remote_handle);
+ return remote_handle;
+}
+
+base::SharedMemoryHandle VideoCaptureBufferPool::GetHandle(int buffer_id) {
+ base::AutoLock lock(lock_);
+ DCHECK(IsAllocated());
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count_);
+ return buffers_[buffer_id]->shared_memory.handle();
+}
+
+void* VideoCaptureBufferPool::GetMemory(int buffer_id) {
+ base::AutoLock lock(lock_);
+ DCHECK(IsAllocated());
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count_);
+ return buffers_[buffer_id]->shared_memory.memory();
+}
+
+int VideoCaptureBufferPool::ReserveForProducer() {
+ base::AutoLock lock(lock_);
+ return ReserveForProducerInternal();
+}
+
+void VideoCaptureBufferPool::RelinquishProducerReservation(int buffer_id) {
+ base::AutoLock lock(lock_);
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count());
+ Buffer* buffer = buffers_[buffer_id];
+ DCHECK(buffer->held_by_producer);
+ buffer->held_by_producer = false;
+}
+
+void VideoCaptureBufferPool::HoldForConsumers(
+ int buffer_id,
+ int num_clients) {
+ base::AutoLock lock(lock_);
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count());
+ DCHECK(IsAllocated());
+ Buffer* buffer = buffers_[buffer_id];
+ DCHECK(buffer->held_by_producer);
+ DCHECK(!buffer->consumer_hold_count);
+
+ buffer->consumer_hold_count = num_clients;
+ // Note: |held_by_producer| will stay true until
+ // RelinquishProducerReservation() (usually called by destructor of the object
+ // wrapping this buffer, e.g. a media::VideoFrame
+}
+
+void VideoCaptureBufferPool::RelinquishConsumerHold(int buffer_id,
+ int num_clients) {
+ base::AutoLock lock(lock_);
+ DCHECK(buffer_id >= 0);
+ DCHECK(buffer_id < count());
+ DCHECK_GT(num_clients, 0);
+ DCHECK(IsAllocated());
+ Buffer* buffer = buffers_[buffer_id];
+ DCHECK_GE(buffer->consumer_hold_count, num_clients);
+
+ buffer->consumer_hold_count -= num_clients;
+}
+
+// State query functions.
+size_t VideoCaptureBufferPool::GetMemorySize() const {
+ // No need to take |lock_| currently.
+ return size_;
+}
+
+int VideoCaptureBufferPool::RecognizeReservedBuffer(
+ base::SharedMemoryHandle maybe_belongs_to_pool) {
+ base::AutoLock lock(lock_);
+ for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
+ Buffer* buffer = buffers_[buffer_id];
+ if (buffer->shared_memory.handle() == maybe_belongs_to_pool) {
+ DCHECK(buffer->held_by_producer);
+ return buffer_id;
+ }
+ }
+ return -1; // Buffer is not from our pool.
+}
+
+scoped_refptr<media::VideoFrame> VideoCaptureBufferPool::ReserveI420VideoFrame(
+ const gfx::Size& size,
+ int rotation) {
+ if (static_cast<size_t>(size.GetArea() * 3 / 2) != GetMemorySize())
+ return NULL;
+
+ base::AutoLock lock(lock_);
+
+ int buffer_id = ReserveForProducerInternal();
+ if (buffer_id < 0)
+ return NULL;
+
+ base::Closure disposal_handler = base::Bind(
+ &VideoCaptureBufferPool::RelinquishProducerReservation,
+ this,
+ buffer_id);
+
+ Buffer* buffer = buffers_[buffer_id];
+ // Wrap the buffer in a VideoFrame container.
+ scoped_refptr<media::VideoFrame> frame =
+ media::VideoFrame::WrapExternalSharedMemory(
+ media::VideoFrame::I420,
+ size,
+ gfx::Rect(size),
+ size,
+ static_cast<uint8*>(buffer->shared_memory.memory()),
+ buffer->shared_memory.handle(),
+ base::TimeDelta(),
+ disposal_handler);
+
+ if (buffer->rotation != rotation) {
+ // TODO(nick): Generalize the |rotation| mechanism.
+ media::FillYUV(frame.get(), 0, 128, 128);
+ buffer->rotation = rotation;
+ }
+
+ return frame;
+}
+
+bool VideoCaptureBufferPool::IsAnyBufferHeldForConsumers() {
+ base::AutoLock lock(lock_);
+ for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
+ Buffer* buffer = buffers_[buffer_id];
+ if (buffer->consumer_hold_count > 0)
+ return true;
+ }
+ return false;
+}
+
+VideoCaptureBufferPool::Buffer::Buffer()
+ : rotation(0),
+ held_by_producer(false),
+ consumer_hold_count(0) {}
+
+int VideoCaptureBufferPool::ReserveForProducerInternal() {
+ lock_.AssertAcquired();
+ DCHECK(IsAllocated());
+
+ int buffer_id = -1;
+ for (int candidate_id = 0; candidate_id < count(); ++candidate_id) {
+ Buffer* candidate = buffers_[candidate_id];
+ if (!candidate->consumer_hold_count && !candidate->held_by_producer) {
+ buffer_id = candidate_id;
+ break;
+ }
+ }
+ if (buffer_id == -1)
+ return -1;
+
+ Buffer* buffer = buffers_[buffer_id];
+ CHECK_GE(buffer->shared_memory.requested_size(), size_);
+ buffer->held_by_producer = true;
+ return buffer_id;
+}
+
+bool VideoCaptureBufferPool::IsAllocated() const {
+ lock_.AssertAcquired();
+ return !buffers_.empty();
+}
+
+} // namespace content
+
diff --git a/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.h b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.h
new file mode 100644
index 00000000000..6d9607737dc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool.h
@@ -0,0 +1,136 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_BUFFER_POOL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_BUFFER_POOL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/shared_memory.h"
+#include "base/process/process.h"
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+#include "ui/gfx/size.h"
+
+namespace media {
+
+class VideoFrame;
+
+} // namespace media
+
+namespace content {
+
+// A thread-safe class that does the bookkeeping and lifetime management for a
+// pool of shared-memory pixel buffers cycled between an in-process producer
+// (e.g. a VideoCaptureDevice) and a set of out-of-process consumers. The pool
+// is intended to be allocated and orchestrated by a VideoCaptureController, but
+// is designed to outlive the controller if necessary.
+//
+// Buffers are identified by an int value called |buffer_id|. Callers may depend
+// on the buffer IDs being dense in the range [0, count()), so long as the
+// Allocate() step succeeded. -1 is never a valid ID, and is returned by some
+// methods to indicate failure. Producers get a buffer by calling
+// ReserveForProducer(), and may pass on their ownership to the consumer by
+// calling HoldForConsumers(), or drop the buffer (without further processing)
+// by calling ReserveForProducer(). Consumers signal that they are done with the
+// buffer by calling RelinquishConsumerHold().
+class CONTENT_EXPORT VideoCaptureBufferPool
+ : public base::RefCountedThreadSafe<VideoCaptureBufferPool> {
+ public:
+ VideoCaptureBufferPool(size_t size, int count);
+
+ // One-time initialization to allocate the shared memory buffers. Returns true
+ // on success.
+ bool Allocate();
+
+ // One-time (per client/per-buffer) initialization to share a particular
+ // buffer to a process.
+ base::SharedMemoryHandle ShareToProcess(int buffer_id,
+ base::ProcessHandle process_handle);
+
+ // Get the shared memory handle for a particular buffer index.
+ base::SharedMemoryHandle GetHandle(int buffer_id);
+
+ // Get the mapped buffer memory for a particular buffer index.
+ void* GetMemory(int buffer_id);
+
+ // Locate the index of a buffer (if any) that's not in use by the producer or
+ // consumers, and reserve it. The buffer remains reserved (and writable by the
+ // producer) until ownership is transferred either to the consumer via
+ // HoldForConsumers(), or back to the pool with
+ // RelinquishProducerReservation().
+ int ReserveForProducer();
+
+ // Indicate that a buffer held for the producer should be returned back to the
+ // pool without passing on to the consumer. This effectively is the opposite
+ // of ReserveForProducer().
+ void RelinquishProducerReservation(int buffer_id);
+
+ // Transfer a buffer from producer to consumer ownership.
+ // |buffer_id| must be a buffer index previously returned by
+ // ReserveForProducer(), and not already passed to HoldForConsumers().
+ void HoldForConsumers(int buffer_id, int num_clients);
+
+ // Indicate that one or more consumers are done with a particular buffer. This
+ // effectively is the opposite of HoldForConsumers(). Once the consumers are
+ // done, a buffer is returned to the pool for reuse.
+ void RelinquishConsumerHold(int buffer_id, int num_clients);
+
+ // Detect whether a particular SharedMemoryHandle is exported by a buffer that
+ // belongs to this pool -- that is, whether it was allocated by an earlier
+ // call to ReserveForProducer(). If so, return its buffer_id (a value on the
+ // range [0, count())). If not, return -1, indicating the buffer is not
+ // recognized (it may be a valid frame, but we didn't allocate it).
+ int RecognizeReservedBuffer(base::SharedMemoryHandle maybe_belongs_to_pool);
+
+ // Utility functions to return a buffer wrapped in a useful type.
+ scoped_refptr<media::VideoFrame> ReserveI420VideoFrame(const gfx::Size& size,
+ int rotation);
+
+ int count() const { return count_; }
+ size_t GetMemorySize() const;
+ bool IsAnyBufferHeldForConsumers();
+
+ private:
+ friend class base::RefCountedThreadSafe<VideoCaptureBufferPool>;
+
+ // Per-buffer state.
+ struct Buffer {
+ Buffer();
+
+ // The memory created to be shared with renderer processes.
+ base::SharedMemory shared_memory;
+
+ // Rotation in degrees of the buffer.
+ int rotation;
+
+ // Tracks whether this buffer is currently referenced by the producer.
+ bool held_by_producer;
+
+ // Number of consumer processes which hold this shared memory.
+ int consumer_hold_count;
+ };
+
+ virtual ~VideoCaptureBufferPool();
+
+ int ReserveForProducerInternal();
+
+ bool IsAllocated() const;
+
+ // Protects |buffers_| and contents thereof.
+ base::Lock lock_;
+
+ // The buffers, indexed by |buffer_id|. Element 0 is always NULL.
+ ScopedVector<Buffer> buffers_;
+
+ const size_t size_;
+ const int count_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureBufferPool);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_BUFFER_POOL_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
new file mode 100644
index 00000000000..67a8be4c761
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 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.
+
+// Unit test for VideoCaptureBufferPool.
+
+#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/media/video_capture_controller.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(VideoCaptureBufferPoolTest, BufferPool) {
+ const gfx::Size size = gfx::Size(640, 480);
+ scoped_refptr<media::VideoFrame> non_pool_frame =
+ media::VideoFrame::CreateFrame(media::VideoFrame::YV12, size,
+ gfx::Rect(size), size, base::TimeDelta());
+ scoped_refptr<VideoCaptureBufferPool> pool =
+ new VideoCaptureBufferPool(size.GetArea() * 3 / 2, 3);
+
+ ASSERT_EQ(460800u, pool->GetMemorySize());
+ ASSERT_TRUE(pool->Allocate());
+
+ scoped_refptr<media::VideoFrame> frame1 =
+ pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame1.get());
+ ASSERT_EQ(size, frame1->coded_size());
+ scoped_refptr<media::VideoFrame> frame2 =
+ pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame2.get());
+ ASSERT_EQ(size, frame2->coded_size());
+ scoped_refptr<media::VideoFrame> frame3 =
+ pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame3.get());
+
+ // Touch the memory.
+ media::FillYUV(frame1.get(), 0x11, 0x22, 0x33);
+ media::FillYUV(frame2.get(), 0x44, 0x55, 0x66);
+ media::FillYUV(frame3.get(), 0x77, 0x88, 0x99);
+
+ // Fourth frame should fail.
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+
+ // Release 1st frame and retry; this should succeed.
+ frame1 = NULL;
+ scoped_refptr<media::VideoFrame> frame4 =
+ pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame4.get());
+
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+
+ // Validate the IDs
+ int buffer_id2 =
+ pool->RecognizeReservedBuffer(frame2->shared_memory_handle());
+ ASSERT_LE(0, buffer_id2);
+ int buffer_id3 =
+ pool->RecognizeReservedBuffer(frame3->shared_memory_handle());
+ ASSERT_LE(0, buffer_id3);
+ int buffer_id4 =
+ pool->RecognizeReservedBuffer(frame4->shared_memory_handle());
+ ASSERT_LE(0, buffer_id4);
+ int buffer_id_non_pool =
+ pool->RecognizeReservedBuffer(non_pool_frame->shared_memory_handle());
+ ASSERT_GT(0, buffer_id_non_pool);
+
+ ASSERT_FALSE(pool->IsAnyBufferHeldForConsumers());
+
+ // Deliver a frame.
+ pool->HoldForConsumers(buffer_id3, 2);
+
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+ frame3 = NULL; // Old producer releases frame. Should be a noop.
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+ frame2 = NULL; // Active producer releases frame. Should free a frame.
+ buffer_id2 = 0;
+
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+ frame1 = pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame1.get());
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+
+ // First consumer finishes.
+ pool->RelinquishConsumerHold(buffer_id3, 1);
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+
+ // Second consumer finishes. This should free that frame.
+ pool->RelinquishConsumerHold(buffer_id3, 1);
+ ASSERT_FALSE(pool->IsAnyBufferHeldForConsumers());
+ frame3 = pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame3.get());
+ ASSERT_FALSE(pool->IsAnyBufferHeldForConsumers());
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+
+ // Now deliver & consume frame1, but don't release the VideoFrame.
+ int buffer_id1 =
+ pool->RecognizeReservedBuffer(frame1->shared_memory_handle());
+ ASSERT_LE(0, buffer_id1);
+ pool->HoldForConsumers(buffer_id1, 5);
+ ASSERT_TRUE(pool->IsAnyBufferHeldForConsumers());
+ pool->RelinquishConsumerHold(buffer_id1, 5);
+ ASSERT_FALSE(pool->IsAnyBufferHeldForConsumers());
+
+ // Even though the consumer is done with the buffer at |buffer_id1|, it cannot
+ // be re-allocated to the producer, because |frame1| still references it. But
+ // when |frame1| goes away, we should be able to re-reserve the buffer (and
+ // the ID ought to be the same).
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+ frame1 = NULL; // Should free the frame.
+ frame2 = pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame2.get());
+ ASSERT_EQ(buffer_id1,
+ pool->RecognizeReservedBuffer(frame2->shared_memory_handle()));
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+
+ // For good measure, do one more cycle of free/realloc without delivery, now
+ // that this buffer has been through the consumer-hold cycle.
+ frame2 = NULL;
+ frame1 = pool->ReserveI420VideoFrame(size, 0);
+ ASSERT_TRUE(NULL != frame1.get());
+ ASSERT_EQ(buffer_id1,
+ pool->RecognizeReservedBuffer(frame1->shared_memory_handle()));
+ ASSERT_EQ(NULL, pool->ReserveI420VideoFrame(size, 0).get())
+ << "Pool should be empty";
+
+ // Tear down the pool, writing into the frames. The VideoFrame should
+ // preserve the lifetime of the underlying memory.
+ frame3 = NULL;
+ pool = NULL;
+
+ // Touch the memory.
+ media::FillYUV(frame1.get(), 0x11, 0x22, 0x33);
+ media::FillYUV(frame4.get(), 0x44, 0x55, 0x66);
+
+ frame1 = NULL;
+
+ media::FillYUV(frame4.get(), 0x44, 0x55, 0x66);
+ frame4 = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_controller.cc b/chromium/content/browser/renderer_host/media/video_capture_controller.cc
new file mode 100644
index 00000000000..bac43289a21
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_controller.cc
@@ -0,0 +1,732 @@
+// 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 "content/browser/renderer_host/media/video_capture_controller.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "media/base/yuv_convert.h"
+
+#if !defined(OS_IOS) && !defined(OS_ANDROID)
+#include "third_party/libyuv/include/libyuv.h"
+#endif
+
+namespace {
+
+// TODO(wjia): Support stride.
+void RotatePackedYV12Frame(
+ const uint8* src,
+ uint8* dest_yplane,
+ uint8* dest_uplane,
+ uint8* dest_vplane,
+ int width,
+ int height,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) {
+ media::RotatePlaneByPixels(
+ src, dest_yplane, width, height, rotation, flip_vert, flip_horiz);
+ int y_size = width * height;
+ src += y_size;
+ media::RotatePlaneByPixels(
+ src, dest_uplane, width/2, height/2, rotation, flip_vert, flip_horiz);
+ src += y_size/4;
+ media::RotatePlaneByPixels(
+ src, dest_vplane, width/2, height/2, rotation, flip_vert, flip_horiz);
+}
+
+} // namespace
+
+namespace content {
+
+// The number of buffers that VideoCaptureBufferPool should allocate.
+static const int kNoOfBuffers = 3;
+
+struct VideoCaptureController::ControllerClient {
+ ControllerClient(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* handler,
+ base::ProcessHandle render_process,
+ const media::VideoCaptureParams& params)
+ : controller_id(id),
+ event_handler(handler),
+ render_process_handle(render_process),
+ parameters(params),
+ session_closed(false) {
+ }
+
+ ~ControllerClient() {}
+
+ // ID used for identifying this object.
+ VideoCaptureControllerID controller_id;
+ VideoCaptureControllerEventHandler* event_handler;
+
+ // Handle to the render process that will receive the capture buffers.
+ base::ProcessHandle render_process_handle;
+ media::VideoCaptureParams parameters;
+
+ // Buffers used by this client.
+ std::set<int> buffers;
+
+ // State of capture session, controlled by VideoCaptureManager directly.
+ bool session_closed;
+};
+
+VideoCaptureController::VideoCaptureController(
+ VideoCaptureManager* video_capture_manager)
+ : chopped_width_(0),
+ chopped_height_(0),
+ frame_info_available_(false),
+ video_capture_manager_(video_capture_manager),
+ device_in_use_(false),
+ state_(VIDEO_CAPTURE_STATE_STOPPED) {
+ memset(&current_params_, 0, sizeof(current_params_));
+}
+
+void VideoCaptureController::StartCapture(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler,
+ base::ProcessHandle render_process,
+ const media::VideoCaptureParams& params) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureController::StartCapture, id " << id.device_id
+ << ", (" << params.width
+ << ", " << params.height
+ << ", " << params.frame_per_second
+ << ", " << params.session_id
+ << ")";
+
+ // Signal error in case device is already in error state.
+ if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
+ event_handler->OnError(id);
+ return;
+ }
+
+ // Do nothing if this client has called StartCapture before.
+ if (FindClient(id, event_handler, controller_clients_) ||
+ FindClient(id, event_handler, pending_clients_))
+ return;
+
+ ControllerClient* client = new ControllerClient(id, event_handler,
+ render_process, params);
+ // In case capture has been started, need to check different conditions.
+ if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
+ // TODO(wjia): Temporarily disable restarting till client supports resampling.
+#if 0
+ // This client has higher resolution than what is currently requested.
+ // Need restart capturing.
+ if (params.width > current_params_.width ||
+ params.height > current_params_.height) {
+ video_capture_manager_->Stop(current_params_.session_id,
+ base::Bind(&VideoCaptureController::OnDeviceStopped, this));
+ frame_info_available_ = false;
+ state_ = VIDEO_CAPTURE_STATE_STOPPING;
+ pending_clients_.push_back(client);
+ return;
+ }
+#endif
+
+ // This client's resolution is no larger than what's currently requested.
+ // When frame_info has been returned by device, send them to client.
+ if (frame_info_available_) {
+ SendFrameInfoAndBuffers(client);
+ }
+ controller_clients_.push_back(client);
+ return;
+ }
+
+ // In case the device is in the middle of stopping, put the client in
+ // pending queue.
+ if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
+ pending_clients_.push_back(client);
+ return;
+ }
+
+ // Fresh start.
+ controller_clients_.push_back(client);
+ current_params_ = params;
+ // Order the manager to start the actual capture.
+ video_capture_manager_->Start(params, this);
+ state_ = VIDEO_CAPTURE_STATE_STARTED;
+ device_in_use_ = true;
+}
+
+void VideoCaptureController::StopCapture(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureController::StopCapture, id " << id.device_id;
+
+ ControllerClient* client = FindClient(id, event_handler, pending_clients_);
+ // If the client is still in pending queue, just remove it.
+ if (client) {
+ pending_clients_.remove(client);
+ return;
+ }
+
+ client = FindClient(id, event_handler, controller_clients_);
+ if (!client)
+ return;
+
+ // Take back all buffers held by the |client|.
+ if (buffer_pool_.get()) {
+ for (std::set<int>::iterator buffer_it = client->buffers.begin();
+ buffer_it != client->buffers.end();
+ ++buffer_it) {
+ int buffer_id = *buffer_it;
+ buffer_pool_->RelinquishConsumerHold(buffer_id, 1);
+ }
+ }
+ client->buffers.clear();
+
+ int session_id = client->parameters.session_id;
+ delete client;
+ controller_clients_.remove(client);
+
+ // No more clients. Stop device.
+ if (controller_clients_.empty() &&
+ (state_ == VIDEO_CAPTURE_STATE_STARTED ||
+ state_ == VIDEO_CAPTURE_STATE_ERROR)) {
+ video_capture_manager_->Stop(session_id,
+ base::Bind(&VideoCaptureController::OnDeviceStopped, this));
+ frame_info_available_ = false;
+ state_ = VIDEO_CAPTURE_STATE_STOPPING;
+ }
+}
+
+void VideoCaptureController::StopSession(
+ int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id;
+
+ ControllerClient* client = FindClient(session_id, pending_clients_);
+ if (!client)
+ client = FindClient(session_id, controller_clients_);
+
+ if (client) {
+ client->session_closed = true;
+ client->event_handler->OnEnded(client->controller_id);
+ }
+}
+
+void VideoCaptureController::ReturnBuffer(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler,
+ int buffer_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ ControllerClient* client = FindClient(id, event_handler,
+ controller_clients_);
+
+ // If this buffer is not held by this client, or this client doesn't exist
+ // in controller, do nothing.
+ if (!client ||
+ client->buffers.find(buffer_id) == client->buffers.end())
+ return;
+
+ client->buffers.erase(buffer_id);
+ buffer_pool_->RelinquishConsumerHold(buffer_id, 1);
+
+ // When all buffers have been returned by clients and device has been
+ // called to stop, check if restart is needed. This could happen when
+ // capture needs to be restarted due to resolution change.
+ if (!buffer_pool_->IsAnyBufferHeldForConsumers() &&
+ state_ == VIDEO_CAPTURE_STATE_STOPPING) {
+ PostStopping();
+ }
+}
+
+scoped_refptr<media::VideoFrame> VideoCaptureController::ReserveOutputBuffer() {
+ base::AutoLock lock(buffer_pool_lock_);
+ if (!buffer_pool_.get())
+ return NULL;
+ return buffer_pool_->ReserveI420VideoFrame(gfx::Size(frame_info_.width,
+ frame_info_.height),
+ 0);
+}
+
+// Implements VideoCaptureDevice::EventHandler.
+// OnIncomingCapturedFrame is called the thread running the capture device.
+// I.e.- DirectShow thread on windows and v4l2_thread on Linux.
+void VideoCaptureController::OnIncomingCapturedFrame(
+ const uint8* data,
+ int length,
+ base::Time timestamp,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) {
+ DCHECK(frame_info_.color == media::VideoCaptureCapability::kI420 ||
+ frame_info_.color == media::VideoCaptureCapability::kYV12 ||
+ (rotation == 0 && !flip_vert && !flip_horiz));
+
+ scoped_refptr<media::VideoFrame> dst;
+ {
+ base::AutoLock lock(buffer_pool_lock_);
+ if (!buffer_pool_.get())
+ return;
+ dst = buffer_pool_->ReserveI420VideoFrame(gfx::Size(frame_info_.width,
+ frame_info_.height),
+ rotation);
+ }
+
+ if (!dst.get())
+ return;
+
+ uint8* yplane = dst->data(media::VideoFrame::kYPlane);
+ uint8* uplane = dst->data(media::VideoFrame::kUPlane);
+ uint8* vplane = dst->data(media::VideoFrame::kVPlane);
+
+ // Do color conversion from the camera format to I420.
+ switch (frame_info_.color) {
+ case media::VideoCaptureCapability::kColorUnknown: // Color format not set.
+ break;
+ case media::VideoCaptureCapability::kI420:
+ DCHECK(!chopped_width_ && !chopped_height_);
+ RotatePackedYV12Frame(
+ data, yplane, uplane, vplane, frame_info_.width, frame_info_.height,
+ rotation, flip_vert, flip_horiz);
+ break;
+ case media::VideoCaptureCapability::kYV12:
+ DCHECK(!chopped_width_ && !chopped_height_);
+ RotatePackedYV12Frame(
+ data, yplane, vplane, uplane, frame_info_.width, frame_info_.height,
+ rotation, flip_vert, flip_horiz);
+ break;
+ case media::VideoCaptureCapability::kNV21:
+ DCHECK(!chopped_width_ && !chopped_height_);
+ media::ConvertNV21ToYUV(data, yplane, uplane, vplane, frame_info_.width,
+ frame_info_.height);
+ break;
+ case media::VideoCaptureCapability::kYUY2:
+ DCHECK(!chopped_width_ && !chopped_height_);
+ if (frame_info_.width * frame_info_.height * 2 != length) {
+ // If |length| of |data| does not match the expected width and height
+ // we can't convert the frame to I420. YUY2 is 2 bytes per pixel.
+ break;
+ }
+
+ media::ConvertYUY2ToYUV(data, yplane, uplane, vplane, frame_info_.width,
+ frame_info_.height);
+ break;
+ case media::VideoCaptureCapability::kRGB24: {
+ int ystride = frame_info_.width;
+ int uvstride = frame_info_.width / 2;
+#if defined(OS_WIN) // RGB on Windows start at the bottom line.
+ int rgb_stride = -3 * (frame_info_.width + chopped_width_);
+ const uint8* rgb_src = data + 3 * (frame_info_.width + chopped_width_) *
+ (frame_info_.height -1 + chopped_height_);
+#else
+ int rgb_stride = 3 * (frame_info_.width + chopped_width_);
+ const uint8* rgb_src = data;
+#endif
+ media::ConvertRGB24ToYUV(rgb_src, yplane, uplane, vplane,
+ frame_info_.width, frame_info_.height,
+ rgb_stride, ystride, uvstride);
+ break;
+ }
+ case media::VideoCaptureCapability::kARGB:
+ media::ConvertRGB32ToYUV(data, yplane, uplane, vplane, frame_info_.width,
+ frame_info_.height,
+ (frame_info_.width + chopped_width_) * 4,
+ frame_info_.width, frame_info_.width / 2);
+ break;
+#if !defined(OS_IOS) && !defined(OS_ANDROID)
+ case media::VideoCaptureCapability::kMJPEG: {
+ int yplane_stride = frame_info_.width;
+ int uv_plane_stride = (frame_info_.width + 1) / 2;
+ int crop_x = 0;
+ int crop_y = 0;
+ libyuv::ConvertToI420(data, length, yplane, yplane_stride, uplane,
+ uv_plane_stride, vplane, uv_plane_stride, crop_x,
+ crop_y, frame_info_.width, frame_info_.height,
+ frame_info_.width, frame_info_.height,
+ libyuv::kRotate0, libyuv::FOURCC_MJPG);
+ break;
+ }
+#endif
+ default:
+ NOTREACHED();
+ }
+
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoIncomingCapturedFrameOnIOThread,
+ this, dst, timestamp));
+}
+
+// OnIncomingCapturedVideoFrame is called the thread running the capture device.
+void VideoCaptureController::OnIncomingCapturedVideoFrame(
+ const scoped_refptr<media::VideoFrame>& frame,
+ base::Time timestamp) {
+
+ scoped_refptr<media::VideoFrame> target;
+ {
+ base::AutoLock lock(buffer_pool_lock_);
+
+ if (!buffer_pool_.get())
+ return;
+
+ // If this is a frame that belongs to the buffer pool, we can forward it
+ // directly to the IO thread and be done.
+ if (buffer_pool_->RecognizeReservedBuffer(
+ frame->shared_memory_handle()) >= 0) {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoIncomingCapturedFrameOnIOThread,
+ this, frame, timestamp));
+ return;
+ }
+ // Otherwise, this is a frame that belongs to the caller, and we must copy
+ // it to a frame from the buffer pool.
+ target = buffer_pool_->ReserveI420VideoFrame(gfx::Size(frame_info_.width,
+ frame_info_.height),
+ 0);
+ }
+
+ if (!target.get())
+ return;
+
+ // Validate the inputs.
+ if (frame->coded_size() != target->coded_size())
+ return; // Only exact copies are supported.
+ if (!(frame->format() == media::VideoFrame::I420 ||
+ frame->format() == media::VideoFrame::YV12 ||
+ frame->format() == media::VideoFrame::RGB32)) {
+ NOTREACHED() << "Unsupported format passed to OnIncomingCapturedVideoFrame";
+ return;
+ }
+
+ const int kYPlane = media::VideoFrame::kYPlane;
+ const int kUPlane = media::VideoFrame::kUPlane;
+ const int kVPlane = media::VideoFrame::kVPlane;
+ const int kAPlane = media::VideoFrame::kAPlane;
+ const int kRGBPlane = media::VideoFrame::kRGBPlane;
+
+ // Do color conversion from the camera format to I420.
+ switch (frame->format()) {
+#if defined(GOOGLE_TV)
+ case media::VideoFrame::HOLE:
+ // Fall-through to NOTREACHED() block.
+#endif
+ case media::VideoFrame::INVALID:
+ case media::VideoFrame::YV16:
+ case media::VideoFrame::EMPTY:
+ case media::VideoFrame::NATIVE_TEXTURE: {
+ NOTREACHED();
+ break;
+ }
+ case media::VideoFrame::I420:
+ case media::VideoFrame::YV12: {
+ DCHECK(!chopped_width_ && !chopped_height_);
+ media::CopyYPlane(frame->data(kYPlane),
+ frame->stride(kYPlane),
+ frame->rows(kYPlane),
+ target.get());
+ media::CopyUPlane(frame->data(kUPlane),
+ frame->stride(kUPlane),
+ frame->rows(kUPlane),
+ target.get());
+ media::CopyVPlane(frame->data(kVPlane),
+ frame->stride(kVPlane),
+ frame->rows(kVPlane),
+ target.get());
+ break;
+ }
+ case media::VideoFrame::YV12A: {
+ DCHECK(!chopped_width_ && !chopped_height_);
+ media::CopyYPlane(frame->data(kYPlane),
+ frame->stride(kYPlane),
+ frame->rows(kYPlane),
+ target.get());
+ media::CopyUPlane(frame->data(kUPlane),
+ frame->stride(kUPlane),
+ frame->rows(kUPlane),
+ target.get());
+ media::CopyVPlane(frame->data(kVPlane),
+ frame->stride(kVPlane),
+ frame->rows(kVPlane),
+ target.get());
+ media::CopyAPlane(frame->data(kAPlane),
+ frame->stride(kAPlane),
+ frame->rows(kAPlane),
+ target.get());
+ break;
+ }
+ case media::VideoFrame::RGB32: {
+ media::ConvertRGB32ToYUV(frame->data(kRGBPlane),
+ target->data(kYPlane),
+ target->data(kUPlane),
+ target->data(kVPlane),
+ target->coded_size().width(),
+ target->coded_size().height(),
+ frame->stride(kRGBPlane),
+ target->stride(kYPlane),
+ target->stride(kUPlane));
+ break;
+ }
+ }
+
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoIncomingCapturedFrameOnIOThread,
+ this, target, timestamp));
+}
+
+void VideoCaptureController::OnError() {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoErrorOnIOThread, this));
+}
+
+void VideoCaptureController::OnFrameInfo(
+ const media::VideoCaptureCapability& info) {
+ frame_info_= info;
+ // Handle cases when |info| has odd numbers for width/height.
+ if (info.width & 1) {
+ --frame_info_.width;
+ chopped_width_ = 1;
+ } else {
+ chopped_width_ = 0;
+ }
+ if (info.height & 1) {
+ --frame_info_.height;
+ chopped_height_ = 1;
+ } else {
+ chopped_height_ = 0;
+ }
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoFrameInfoOnIOThread, this));
+}
+
+void VideoCaptureController::OnFrameInfoChanged(
+ const media::VideoCaptureCapability& info) {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoFrameInfoChangedOnIOThread,
+ this, info));
+}
+
+VideoCaptureController::~VideoCaptureController() {
+ buffer_pool_ = NULL; // Release all buffers.
+ STLDeleteContainerPointers(controller_clients_.begin(),
+ controller_clients_.end());
+ STLDeleteContainerPointers(pending_clients_.begin(),
+ pending_clients_.end());
+}
+
+// Called by VideoCaptureManager when a device have been stopped.
+void VideoCaptureController::OnDeviceStopped() {
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureController::DoDeviceStoppedOnIOThread, this));
+}
+
+void VideoCaptureController::DoIncomingCapturedFrameOnIOThread(
+ const scoped_refptr<media::VideoFrame>& reserved_frame,
+ base::Time timestamp) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!buffer_pool_.get())
+ return;
+
+ int buffer_id = buffer_pool_->RecognizeReservedBuffer(
+ reserved_frame->shared_memory_handle());
+ if (buffer_id < 0) {
+ NOTREACHED();
+ return;
+ }
+
+ int count = 0;
+ if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
+ for (ControllerClients::iterator client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ if ((*client_it)->session_closed)
+ continue;
+
+ (*client_it)->event_handler->OnBufferReady((*client_it)->controller_id,
+ buffer_id, timestamp);
+ (*client_it)->buffers.insert(buffer_id);
+ count++;
+ }
+ }
+
+ buffer_pool_->HoldForConsumers(buffer_id, count);
+}
+
+void VideoCaptureController::DoFrameInfoOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!buffer_pool_.get())
+ << "Device is restarted without releasing shared memory.";
+
+ // Allocate memory only when device has been started.
+ if (state_ != VIDEO_CAPTURE_STATE_STARTED)
+ return;
+
+ scoped_refptr<VideoCaptureBufferPool> buffer_pool =
+ new VideoCaptureBufferPool(frame_info_.width * frame_info_.height * 3 / 2,
+ kNoOfBuffers);
+
+ // Check whether all buffers were created successfully.
+ if (!buffer_pool->Allocate()) {
+ state_ = VIDEO_CAPTURE_STATE_ERROR;
+ for (ControllerClients::iterator client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ (*client_it)->event_handler->OnError((*client_it)->controller_id);
+ }
+ return;
+ }
+
+ {
+ base::AutoLock lock(buffer_pool_lock_);
+ buffer_pool_ = buffer_pool;
+ }
+ frame_info_available_ = true;
+
+ for (ControllerClients::iterator client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ SendFrameInfoAndBuffers(*client_it);
+ }
+}
+
+void VideoCaptureController::DoFrameInfoChangedOnIOThread(
+ const media::VideoCaptureCapability& info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // TODO(mcasas): Here we should reallocate the VideoCaptureBufferPool, if
+ // needed, to support the new video capture format. See crbug.com/266082.
+ for (ControllerClients::iterator client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ (*client_it)->event_handler->OnFrameInfoChanged(
+ (*client_it)->controller_id,
+ info.width,
+ info.height,
+ info.frame_rate);
+ }
+}
+
+void VideoCaptureController::DoErrorOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state_ = VIDEO_CAPTURE_STATE_ERROR;
+ ControllerClients::iterator client_it;
+ for (client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ (*client_it)->event_handler->OnError((*client_it)->controller_id);
+ }
+ for (client_it = pending_clients_.begin();
+ client_it != pending_clients_.end(); ++client_it) {
+ (*client_it)->event_handler->OnError((*client_it)->controller_id);
+ }
+}
+
+void VideoCaptureController::DoDeviceStoppedOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ device_in_use_ = false;
+ if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
+ PostStopping();
+ }
+}
+
+void VideoCaptureController::SendFrameInfoAndBuffers(ControllerClient* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(frame_info_available_);
+ client->event_handler->OnFrameInfo(client->controller_id,
+ frame_info_);
+ for (int buffer_id = 0; buffer_id < buffer_pool_->count(); ++buffer_id) {
+ base::SharedMemoryHandle remote_handle =
+ buffer_pool_->ShareToProcess(buffer_id, client->render_process_handle);
+
+ client->event_handler->OnBufferCreated(client->controller_id,
+ remote_handle,
+ buffer_pool_->GetMemorySize(),
+ buffer_id);
+ }
+}
+
+VideoCaptureController::ControllerClient*
+VideoCaptureController::FindClient(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* handler,
+ const ControllerClients& clients) {
+ for (ControllerClients::const_iterator client_it = clients.begin();
+ client_it != clients.end(); ++client_it) {
+ if ((*client_it)->controller_id == id &&
+ (*client_it)->event_handler == handler) {
+ return *client_it;
+ }
+ }
+ return NULL;
+}
+
+VideoCaptureController::ControllerClient*
+VideoCaptureController::FindClient(
+ int session_id,
+ const ControllerClients& clients) {
+ for (ControllerClients::const_iterator client_it = clients.begin();
+ client_it != clients.end(); ++client_it) {
+ if ((*client_it)->parameters.session_id == session_id) {
+ return *client_it;
+ }
+ }
+ return NULL;
+}
+
+// This function is called when all buffers have been returned to controller,
+// or when device is stopped. It decides whether the device needs to be
+// restarted.
+void VideoCaptureController::PostStopping() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPING);
+
+ // When clients still have some buffers, or device has not been stopped yet,
+ // do nothing.
+ if ((buffer_pool_.get() && buffer_pool_->IsAnyBufferHeldForConsumers()) ||
+ device_in_use_)
+ return;
+
+ {
+ base::AutoLock lock(buffer_pool_lock_);
+ buffer_pool_ = NULL;
+ }
+
+ // No more client. Therefore the controller is stopped.
+ if (controller_clients_.empty() && pending_clients_.empty()) {
+ state_ = VIDEO_CAPTURE_STATE_STOPPED;
+ return;
+ }
+
+ // Restart the device.
+ current_params_.width = 0;
+ current_params_.height = 0;
+ ControllerClients::iterator client_it;
+ for (client_it = controller_clients_.begin();
+ client_it != controller_clients_.end(); ++client_it) {
+ if (current_params_.width < (*client_it)->parameters.width)
+ current_params_.width = (*client_it)->parameters.width;
+ if (current_params_.height < (*client_it)->parameters.height)
+ current_params_.height = (*client_it)->parameters.height;
+ }
+ for (client_it = pending_clients_.begin();
+ client_it != pending_clients_.end(); ) {
+ if (current_params_.width < (*client_it)->parameters.width)
+ current_params_.width = (*client_it)->parameters.width;
+ if (current_params_.height < (*client_it)->parameters.height)
+ current_params_.height = (*client_it)->parameters.height;
+ controller_clients_.push_back((*client_it));
+ pending_clients_.erase(client_it++);
+ }
+ // Request the manager to start the actual capture.
+ video_capture_manager_->Start(current_params_, this);
+ state_ = VIDEO_CAPTURE_STATE_STARTED;
+ device_in_use_ = true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_controller.h b/chromium/content/browser/renderer_host/media/video_capture_controller.h
new file mode 100644
index 00000000000..5d33d01163c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_controller.h
@@ -0,0 +1,164 @@
+// 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.
+
+// VideoCaptureController is the glue between VideoCaptureHost,
+// VideoCaptureManager and VideoCaptureDevice.
+// It provides functions for VideoCaptureHost to start a VideoCaptureDevice and
+// is responsible for keeping track of shared DIBs and filling them with I420
+// video frames for IPC communication between VideoCaptureHost and
+// VideoCaptureMessageFilter.
+// It implements media::VideoCaptureDevice::EventHandler to get video frames
+// from a VideoCaptureDevice object and do color conversion straight into the
+// shared DIBs to avoid a memory copy.
+// It serves multiple VideoCaptureControllerEventHandlers.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_H_
+
+#include <list>
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
+#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
+#include "content/common/content_export.h"
+#include "content/common/media/video_capture.h"
+#include "media/video/capture/video_capture.h"
+#include "media/video/capture/video_capture_device.h"
+#include "media/video/capture/video_capture_types.h"
+
+namespace content {
+class VideoCaptureManager;
+class VideoCaptureBufferPool;
+
+class CONTENT_EXPORT VideoCaptureController
+ : public base::RefCountedThreadSafe<VideoCaptureController>,
+ public media::VideoCaptureDevice::EventHandler {
+ public:
+ VideoCaptureController(VideoCaptureManager* video_capture_manager);
+
+ // Start video capturing and try to use the resolution specified in
+ // |params|.
+ // When capturing has started, the |event_handler| receives a call OnFrameInfo
+ // with resolution that best matches the requested that the video
+ // capture device support.
+ void StartCapture(const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler,
+ base::ProcessHandle render_process,
+ const media::VideoCaptureParams& params);
+
+ // Stop video capture.
+ // This will take back all buffers held by by |event_handler|, and
+ // |event_handler| shouldn't use those buffers any more.
+ void StopCapture(const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler);
+
+ // API called directly by VideoCaptureManager in case the device is
+ // prematurely closed.
+ void StopSession(int session_id);
+
+ // Return a buffer previously given in
+ // VideoCaptureControllerEventHandler::OnBufferReady.
+ void ReturnBuffer(const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler,
+ int buffer_id);
+
+ // Implement media::VideoCaptureDevice::EventHandler.
+ virtual scoped_refptr<media::VideoFrame> ReserveOutputBuffer() OVERRIDE;
+ virtual void OnIncomingCapturedFrame(const uint8* data,
+ int length,
+ base::Time timestamp,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) OVERRIDE;
+ virtual void OnIncomingCapturedVideoFrame(
+ const scoped_refptr<media::VideoFrame>& frame,
+ base::Time timestamp) OVERRIDE;
+ virtual void OnError() OVERRIDE;
+ virtual void OnFrameInfo(const media::VideoCaptureCapability& info) OVERRIDE;
+ virtual void OnFrameInfoChanged(
+ const media::VideoCaptureCapability& info) OVERRIDE;
+
+ protected:
+ virtual ~VideoCaptureController();
+
+ private:
+ friend class base::RefCountedThreadSafe<VideoCaptureController>;
+
+ struct ControllerClient;
+ typedef std::list<ControllerClient*> ControllerClients;
+
+ // Callback when manager has stopped device.
+ void OnDeviceStopped();
+
+ // Worker functions on IO thread.
+ void DoIncomingCapturedFrameOnIOThread(
+ const scoped_refptr<media::VideoFrame>& captured_frame,
+ base::Time timestamp);
+ void DoFrameInfoOnIOThread();
+ void DoFrameInfoChangedOnIOThread(const media::VideoCaptureCapability& info);
+ void DoErrorOnIOThread();
+ void DoDeviceStoppedOnIOThread();
+
+ // Send frame info and init buffers to |client|.
+ void SendFrameInfoAndBuffers(ControllerClient* client);
+
+ // Find a client of |id| and |handler| in |clients|.
+ ControllerClient* FindClient(
+ const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* handler,
+ const ControllerClients& clients);
+
+ // Find a client of |session_id| in |clients|.
+ ControllerClient* FindClient(
+ int session_id,
+ const ControllerClients& clients);
+
+ // Decide what to do after kStopping state. Dependent on events, controller
+ // can stay in kStopping state, or go to kStopped, or restart capture.
+ void PostStopping();
+
+ // Protects access to the |buffer_pool_| pointer on non-IO threads. IO thread
+ // must hold this lock when modifying the |buffer_pool_| pointer itself.
+ // TODO(nick): Make it so that this lock isn't required.
+ base::Lock buffer_pool_lock_;
+
+ // The pool of shared-memory buffers used for capturing.
+ scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
+
+ // All clients served by this controller.
+ ControllerClients controller_clients_;
+
+ // All clients waiting for service.
+ ControllerClients pending_clients_;
+
+ // The parameter that currently used for the capturing.
+ media::VideoCaptureParams current_params_;
+
+ // It's modified on caller thread, assuming there is only one OnFrameInfo()
+ // call per StartCapture().
+ media::VideoCaptureCapability frame_info_;
+
+ // Chopped pixels in width/height in case video capture device has odd numbers
+ // for width/height.
+ int chopped_width_;
+ int chopped_height_;
+
+ // It's accessed only on IO thread.
+ bool frame_info_available_;
+
+ VideoCaptureManager* video_capture_manager_;
+
+ bool device_in_use_;
+ VideoCaptureState state_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.cc b/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.cc
new file mode 100644
index 00000000000..7d2d0be3283
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
+
+namespace content {
+
+VideoCaptureControllerID::VideoCaptureControllerID(int did)
+ : device_id(did) {
+}
+
+bool VideoCaptureControllerID::operator<(
+ const VideoCaptureControllerID& vc) const {
+ return this->device_id < vc.device_id;
+}
+
+bool VideoCaptureControllerID::operator==(
+ const VideoCaptureControllerID& vc) const {
+ return this->device_id == vc.device_id;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.h b/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.h
new file mode 100644
index 00000000000..c4844af2f73
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_controller_event_handler.h
@@ -0,0 +1,65 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_EVENT_HANDLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_EVENT_HANDLER_H_
+
+#include "base/memory/shared_memory.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+namespace media {
+struct VideoCaptureCapability;
+}
+
+namespace content {
+
+// ID used for identifying an object of VideoCaptureController.
+struct CONTENT_EXPORT VideoCaptureControllerID {
+ explicit VideoCaptureControllerID(int device_id);
+
+ bool operator<(const VideoCaptureControllerID& vc) const;
+ bool operator==(const VideoCaptureControllerID& vc) const;
+
+ int device_id;
+};
+
+// VideoCaptureControllerEventHandler is the interface for
+// VideoCaptureController to notify clients about the events such as
+// BufferReady, FrameInfo, Error, etc.
+class CONTENT_EXPORT VideoCaptureControllerEventHandler {
+ public:
+ // An Error has occurred in the VideoCaptureDevice.
+ virtual void OnError(const VideoCaptureControllerID& id) = 0;
+
+ // A buffer has been newly created.
+ virtual void OnBufferCreated(const VideoCaptureControllerID& id,
+ base::SharedMemoryHandle handle,
+ int length, int buffer_id) = 0;
+
+ // A buffer has been filled with I420 video.
+ virtual void OnBufferReady(const VideoCaptureControllerID& id,
+ int buffer_id,
+ base::Time timestamp) = 0;
+
+ // The frame resolution the VideoCaptureDevice capture video in.
+ virtual void OnFrameInfo(const VideoCaptureControllerID& id,
+ const media::VideoCaptureCapability& format) = 0;
+
+ // The frame resolution the VideoCaptureDevice capture video in.
+ virtual void OnFrameInfoChanged(const VideoCaptureControllerID& id,
+ int width,
+ int height,
+ int frame_rate) {};
+
+ // The capture session has ended and no more frames will be sent.
+ virtual void OnEnded(const VideoCaptureControllerID& id) = 0;
+
+ protected:
+ virtual ~VideoCaptureControllerEventHandler() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_EVENT_HANDLER_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_controller_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_controller_unittest.cc
new file mode 100644
index 00000000000..c4b716d2e33
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_controller_unittest.cc
@@ -0,0 +1,265 @@
+// 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.
+
+// Unit test for VideoCaptureController.
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/browser/renderer_host/media/video_capture_controller.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/common/media/media_stream_options.h"
+#include "media/video/capture/fake_video_capture_device.h"
+#include "media/video/capture/video_capture_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::InSequence;
+using ::testing::Return;
+
+namespace content {
+
+enum { kDeviceId = 1 };
+
+ACTION_P4(StopCapture, controller, controller_id, controller_handler,
+ message_loop) {
+ message_loop->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureController::StopCapture,
+ controller, controller_id, controller_handler));
+ message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+ACTION_P3(StopSession, controller, session_id, message_loop) {
+ message_loop->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureController::StopSession,
+ controller, session_id));
+ message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+class MockVideoCaptureControllerEventHandler
+ : public VideoCaptureControllerEventHandler {
+ public:
+ MockVideoCaptureControllerEventHandler(VideoCaptureController* controller,
+ base::MessageLoop* message_loop)
+ : controller_(controller),
+ message_loop_(message_loop),
+ controller_id_(kDeviceId),
+ process_handle_(base::kNullProcessHandle) {
+ }
+ virtual ~MockVideoCaptureControllerEventHandler() {}
+
+ MOCK_METHOD1(DoBufferCreated, void(const VideoCaptureControllerID&));
+ MOCK_METHOD1(DoBufferReady, void(const VideoCaptureControllerID&));
+ MOCK_METHOD1(DoFrameInfo, void(const VideoCaptureControllerID&));
+ MOCK_METHOD1(DoEnded, void(const VideoCaptureControllerID&));
+
+ virtual void OnError(const VideoCaptureControllerID& id) OVERRIDE {}
+ virtual void OnBufferCreated(const VideoCaptureControllerID& id,
+ base::SharedMemoryHandle handle,
+ int length, int buffer_id) OVERRIDE {
+ EXPECT_EQ(id, controller_id_);
+ DoBufferCreated(id);
+ }
+ virtual void OnBufferReady(const VideoCaptureControllerID& id,
+ int buffer_id,
+ base::Time timestamp) OVERRIDE {
+ EXPECT_EQ(id, controller_id_);
+ DoBufferReady(id);
+ message_loop_->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureController::ReturnBuffer,
+ controller_, controller_id_, this, buffer_id));
+ }
+ virtual void OnFrameInfo(
+ const VideoCaptureControllerID& id,
+ const media::VideoCaptureCapability& format) OVERRIDE {
+ EXPECT_EQ(id, controller_id_);
+ DoFrameInfo(id);
+ }
+ virtual void OnEnded(const VideoCaptureControllerID& id) OVERRIDE {
+ EXPECT_EQ(id, controller_id_);
+ DoEnded(id);
+ }
+
+ scoped_refptr<VideoCaptureController> controller_;
+ base::MessageLoop* message_loop_;
+ VideoCaptureControllerID controller_id_;
+ base::ProcessHandle process_handle_;
+};
+
+class MockVideoCaptureManager : public VideoCaptureManager {
+ public:
+ MockVideoCaptureManager()
+ : video_session_id_(kStartOpenSessionId),
+ device_name_("fake_device_0", "/dev/video0") {}
+
+ void Init() {
+ video_capture_device_.reset(
+ media::FakeVideoCaptureDevice::Create(device_name_));
+ ASSERT_TRUE(video_capture_device_.get() != NULL);
+ }
+
+ MOCK_METHOD3(StartCapture, void(int, int,
+ media::VideoCaptureDevice::EventHandler*));
+ MOCK_METHOD1(StopCapture, void(const media::VideoCaptureSessionId&));
+
+ void Start(const media::VideoCaptureParams& capture_params,
+ media::VideoCaptureDevice::EventHandler* vc_receiver) OVERRIDE {
+ StartCapture(capture_params.width, capture_params.height, vc_receiver);
+ // TODO(mcasas): Add testing for variable resolution video capture devices,
+ // supported by FakeVideoCaptureDevice. See crbug.com/261410, second part.
+ media::VideoCaptureCapability capture_format(
+ capture_params.width,
+ capture_params.height,
+ capture_params.frame_per_second,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ video_capture_device_->Allocate(capture_format, vc_receiver);
+ video_capture_device_->Start();
+ }
+
+ void Stop(const media::VideoCaptureSessionId& capture_session_id,
+ base::Closure stopped_cb) OVERRIDE {
+ StopCapture(capture_session_id);
+ video_capture_device_->Stop();
+ video_capture_device_->DeAllocate();
+ }
+
+ int video_session_id_;
+ media::VideoCaptureDevice::Name device_name_;
+ scoped_ptr<media::VideoCaptureDevice> video_capture_device_;
+
+ private:
+ virtual ~MockVideoCaptureManager() {}
+ DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureManager);
+};
+
+// Test class.
+class VideoCaptureControllerTest : public testing::Test {
+ public:
+ VideoCaptureControllerTest() {}
+ virtual ~VideoCaptureControllerTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+ file_thread_.reset(new BrowserThreadImpl(BrowserThread::FILE,
+ message_loop_.get()));
+ io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
+ message_loop_.get()));
+
+ vcm_ = new MockVideoCaptureManager();
+ vcm_->Init();
+ controller_ = new VideoCaptureController(vcm_.get());
+ controller_handler_.reset(new MockVideoCaptureControllerEventHandler(
+ controller_.get(), message_loop_.get()));
+ }
+
+ virtual void TearDown() OVERRIDE {}
+
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<BrowserThreadImpl> file_thread_;
+ scoped_ptr<BrowserThreadImpl> io_thread_;
+ scoped_refptr<MockVideoCaptureManager> vcm_;
+ scoped_ptr<MockVideoCaptureControllerEventHandler> controller_handler_;
+ scoped_refptr<VideoCaptureController> controller_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureControllerTest);
+};
+
+// Try to start and stop capture.
+TEST_F(VideoCaptureControllerTest, StartAndStop) {
+ media::VideoCaptureParams capture_params;
+ capture_params.session_id = vcm_->video_session_id_;
+ capture_params.width = 320;
+ capture_params.height = 240;
+ capture_params.frame_per_second = 30;
+
+ InSequence s;
+ EXPECT_CALL(*vcm_.get(),
+ StartCapture(capture_params.width,
+ capture_params.height,
+ controller_.get())).Times(1);
+ EXPECT_CALL(*controller_handler_,
+ DoFrameInfo(controller_handler_->controller_id_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*controller_handler_,
+ DoBufferCreated(controller_handler_->controller_id_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*controller_handler_,
+ DoBufferReady(controller_handler_->controller_id_))
+ .Times(AtLeast(1))
+ .WillOnce(StopCapture(controller_.get(),
+ controller_handler_->controller_id_,
+ controller_handler_.get(),
+ message_loop_.get()));
+ EXPECT_CALL(*vcm_.get(), StopCapture(vcm_->video_session_id_)).Times(1);
+
+ controller_->StartCapture(controller_handler_->controller_id_,
+ controller_handler_.get(),
+ controller_handler_->process_handle_,
+ capture_params);
+ message_loop_->Run();
+}
+
+// Try to stop session before stopping capture.
+TEST_F(VideoCaptureControllerTest, StopSession) {
+ media::VideoCaptureParams capture_params;
+ capture_params.session_id = vcm_->video_session_id_;
+ capture_params.width = 320;
+ capture_params.height = 240;
+ capture_params.frame_per_second = 30;
+
+ InSequence s;
+ EXPECT_CALL(*vcm_.get(),
+ StartCapture(capture_params.width,
+ capture_params.height,
+ controller_.get())).Times(1);
+ EXPECT_CALL(*controller_handler_,
+ DoFrameInfo(controller_handler_->controller_id_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*controller_handler_,
+ DoBufferCreated(controller_handler_->controller_id_))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*controller_handler_,
+ DoBufferReady(controller_handler_->controller_id_))
+ .Times(AtLeast(1))
+ .WillOnce(StopSession(controller_.get(),
+ vcm_->video_session_id_,
+ message_loop_.get()));
+ EXPECT_CALL(*controller_handler_,
+ DoEnded(controller_handler_->controller_id_))
+ .Times(1);
+
+ controller_->StartCapture(controller_handler_->controller_id_,
+ controller_handler_.get(),
+ controller_handler_->process_handle_,
+ capture_params);
+ message_loop_->Run();
+
+ // The session is stopped now. There should be no buffer coming from
+ // controller.
+ EXPECT_CALL(*controller_handler_,
+ DoBufferReady(controller_handler_->controller_id_))
+ .Times(0);
+ message_loop_->PostDelayedTask(FROM_HERE,
+ base::MessageLoop::QuitClosure(), base::TimeDelta::FromSeconds(1));
+ message_loop_->Run();
+
+ EXPECT_CALL(*vcm_.get(), StopCapture(vcm_->video_session_id_)).Times(1);
+ controller_->StopCapture(controller_handler_->controller_id_,
+ controller_handler_.get());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_host.cc b/chromium/content/browser/renderer_host/media/video_capture_host.cc
new file mode 100644
index 00000000000..bc7c8c19d7d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_host.cc
@@ -0,0 +1,315 @@
+// 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 "content/browser/renderer_host/media/video_capture_host.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/common/media/video_capture_messages.h"
+
+namespace content {
+
+struct VideoCaptureHost::Entry {
+ Entry(VideoCaptureController* controller)
+ : controller(controller) {}
+
+ ~Entry() {}
+
+ scoped_refptr<VideoCaptureController> controller;
+};
+
+VideoCaptureHost::VideoCaptureHost(MediaStreamManager* media_stream_manager)
+ : media_stream_manager_(media_stream_manager) {
+}
+
+VideoCaptureHost::~VideoCaptureHost() {}
+
+void VideoCaptureHost::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Since the IPC channel is gone, close all requested VideCaptureDevices.
+ for (EntryMap::iterator it = entries_.begin(); it != entries_.end(); it++) {
+ VideoCaptureController* controller = it->second->controller.get();
+ if (controller) {
+ VideoCaptureControllerID controller_id(it->first);
+ controller->StopCapture(controller_id, this);
+ media_stream_manager_->video_capture_manager()->RemoveController(
+ controller, this);
+ }
+ }
+ STLDeleteValues(&entries_);
+}
+
+void VideoCaptureHost::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements VideoCaptureControllerEventHandler.
+void VideoCaptureHost::OnError(const VideoCaptureControllerID& controller_id) {
+ DVLOG(1) << "VideoCaptureHost::OnError";
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoHandleErrorOnIOThread,
+ this, controller_id));
+}
+
+void VideoCaptureHost::OnBufferCreated(
+ const VideoCaptureControllerID& controller_id,
+ base::SharedMemoryHandle handle,
+ int length,
+ int buffer_id) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoSendNewBufferOnIOThread,
+ this, controller_id, handle, length, buffer_id));
+}
+
+void VideoCaptureHost::OnBufferReady(
+ const VideoCaptureControllerID& controller_id,
+ int buffer_id,
+ base::Time timestamp) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoSendFilledBufferOnIOThread,
+ this, controller_id, buffer_id, timestamp));
+}
+
+void VideoCaptureHost::OnFrameInfo(
+ const VideoCaptureControllerID& controller_id,
+ const media::VideoCaptureCapability& format) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoSendFrameInfoOnIOThread,
+ this, controller_id, format));
+}
+
+void VideoCaptureHost::OnFrameInfoChanged(
+ const VideoCaptureControllerID& controller_id,
+ int width,
+ int height,
+ int frame_per_second) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoSendFrameInfoChangedOnIOThread,
+ this, controller_id, width, height, frame_per_second));
+}
+
+void VideoCaptureHost::OnEnded(const VideoCaptureControllerID& controller_id) {
+ DVLOG(1) << "VideoCaptureHost::OnEnded";
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoEndedOnIOThread, this, controller_id));
+}
+
+void VideoCaptureHost::DoSendNewBufferOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ base::SharedMemoryHandle handle,
+ int length,
+ int buffer_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ Send(new VideoCaptureMsg_NewBuffer(controller_id.device_id, handle,
+ length, buffer_id));
+}
+
+void VideoCaptureHost::DoSendFilledBufferOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ int buffer_id, base::Time timestamp) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ Send(new VideoCaptureMsg_BufferReady(controller_id.device_id, buffer_id,
+ timestamp));
+}
+
+void VideoCaptureHost::DoHandleErrorOnIOThread(
+ const VideoCaptureControllerID& controller_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ Send(new VideoCaptureMsg_StateChanged(controller_id.device_id,
+ VIDEO_CAPTURE_STATE_ERROR));
+ DeleteVideoCaptureControllerOnIOThread(controller_id);
+}
+
+void VideoCaptureHost::DoEndedOnIOThread(
+ const VideoCaptureControllerID& controller_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureHost::DoEndedOnIOThread";
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ Send(new VideoCaptureMsg_StateChanged(controller_id.device_id,
+ VIDEO_CAPTURE_STATE_ENDED));
+ DeleteVideoCaptureControllerOnIOThread(controller_id);
+}
+
+void VideoCaptureHost::DoSendFrameInfoOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ const media::VideoCaptureCapability& format) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ media::VideoCaptureParams params;
+ params.width = format.width;
+ params.height = format.height;
+ params.frame_per_second = format.frame_rate;
+ params.frame_size_type = format.frame_size_type;
+ Send(new VideoCaptureMsg_DeviceInfo(controller_id.device_id, params));
+ Send(new VideoCaptureMsg_StateChanged(controller_id.device_id,
+ VIDEO_CAPTURE_STATE_STARTED));
+}
+
+void VideoCaptureHost::DoSendFrameInfoChangedOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ int width,
+ int height,
+ int frame_per_second) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (entries_.find(controller_id) == entries_.end())
+ return;
+
+ media::VideoCaptureParams params;
+ params.width = width;
+ params.height = height;
+ params.frame_per_second = frame_per_second;
+ Send(new VideoCaptureMsg_DeviceInfoChanged(controller_id.device_id, params));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// IPC Messages handler.
+bool VideoCaptureHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(VideoCaptureHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Start, OnStartCapture)
+ IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Pause, OnPauseCapture)
+ IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Stop, OnStopCapture)
+ IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_BufferReady, OnReceiveEmptyBuffer)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void VideoCaptureHost::OnStartCapture(int device_id,
+ const media::VideoCaptureParams& params) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureHost::OnStartCapture, device_id " << device_id
+ << ", (" << params.width << ", " << params.height << ", "
+ << params.frame_per_second << ", " << params.session_id
+ << ", variable resolution device:"
+ << ((params.frame_size_type ==
+ media::VariableResolutionVideoCaptureDevice) ? "yes" : "no")
+ << ")";
+ VideoCaptureControllerID controller_id(device_id);
+ DCHECK(entries_.find(controller_id) == entries_.end());
+
+ entries_[controller_id] = new Entry(NULL);
+ media_stream_manager_->video_capture_manager()->AddController(
+ params, this, base::Bind(&VideoCaptureHost::OnControllerAdded, this,
+ device_id, params));
+}
+
+void VideoCaptureHost::OnControllerAdded(
+ int device_id, const media::VideoCaptureParams& params,
+ VideoCaptureController* controller) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureHost::DoControllerAddedOnIOThread,
+ this, device_id, params, make_scoped_refptr(controller)));
+}
+
+void VideoCaptureHost::DoControllerAddedOnIOThread(
+ int device_id, const media::VideoCaptureParams params,
+ VideoCaptureController* controller) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ VideoCaptureControllerID controller_id(device_id);
+ EntryMap::iterator it = entries_.find(controller_id);
+ if (it == entries_.end()) {
+ if (controller) {
+ media_stream_manager_->video_capture_manager()->RemoveController(
+ controller, this);
+ }
+ return;
+ }
+
+ if (controller == NULL) {
+ Send(new VideoCaptureMsg_StateChanged(device_id,
+ VIDEO_CAPTURE_STATE_ERROR));
+ delete it->second;
+ entries_.erase(controller_id);
+ return;
+ }
+
+ it->second->controller = controller;
+ controller->StartCapture(controller_id, this, PeerHandle(), params);
+}
+
+void VideoCaptureHost::OnStopCapture(int device_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureHost::OnStopCapture, device_id " << device_id;
+
+ VideoCaptureControllerID controller_id(device_id);
+
+ Send(new VideoCaptureMsg_StateChanged(device_id,
+ VIDEO_CAPTURE_STATE_STOPPED));
+ DeleteVideoCaptureControllerOnIOThread(controller_id);
+}
+
+void VideoCaptureHost::OnPauseCapture(int device_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureHost::OnPauseCapture, device_id " << device_id;
+ // Not used.
+ Send(new VideoCaptureMsg_StateChanged(device_id, VIDEO_CAPTURE_STATE_ERROR));
+}
+
+void VideoCaptureHost::OnReceiveEmptyBuffer(int device_id, int buffer_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ VideoCaptureControllerID controller_id(device_id);
+ EntryMap::iterator it = entries_.find(controller_id);
+ if (it != entries_.end()) {
+ scoped_refptr<VideoCaptureController> controller = it->second->controller;
+ if (controller.get())
+ controller->ReturnBuffer(controller_id, this, buffer_id);
+ }
+}
+
+void VideoCaptureHost::DeleteVideoCaptureControllerOnIOThread(
+ const VideoCaptureControllerID& controller_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ EntryMap::iterator it = entries_.find(controller_id);
+ if (it == entries_.end())
+ return;
+
+ VideoCaptureController* controller = it->second->controller.get();
+ if (controller) {
+ controller->StopCapture(controller_id, this);
+ media_stream_manager_->video_capture_manager()->RemoveController(
+ controller, this);
+ }
+ delete it->second;
+ entries_.erase(controller_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_host.h b/chromium/content/browser/renderer_host/media/video_capture_host.h
new file mode 100644
index 00000000000..025b849de94
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_host.h
@@ -0,0 +1,161 @@
+// 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.
+//
+// VideoCaptureHost serves video capture related messages from
+// VideoCaptureMessageFilter which lives inside the render process.
+//
+// This class is owned by BrowserRenderProcessHost, and instantiated on UI
+// thread, but all other operations and method calls happen on IO thread.
+//
+// Here's an example of a typical IPC dialog for video capture:
+//
+// Renderer VideoCaptureHost
+// | |
+// | VideoCaptureHostMsg_Start > |
+// | < VideoCaptureMsg_DeviceInfo |
+// | |
+// | < VideoCaptureMsg_StateChanged |
+// | (kStarted) |
+// | < VideoCaptureMsg_BufferReady |
+// | ... |
+// | < VideoCaptureMsg_BufferReady |
+// | ... |
+// | VideoCaptureHostMsg_BufferReady > |
+// | VideoCaptureHostMsg_BufferReady > |
+// | |
+// | ... |
+// | |
+// | < VideoCaptureMsg_BufferReady |
+// | VideoCaptureHostMsg_Stop > |
+// | VideoCaptureHostMsg_BufferReady > |
+// | < VideoCaptureMsg_StateChanged |
+// | (kStopped) |
+// v v
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_HOST_H_
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/browser/renderer_host/media/video_capture_controller.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "ipc/ipc_message.h"
+
+namespace media {
+struct VideoCaptureCapability;
+}
+
+namespace content {
+class MediaStreamManager;
+
+class CONTENT_EXPORT VideoCaptureHost
+ : public BrowserMessageFilter,
+ public VideoCaptureControllerEventHandler {
+ public:
+ explicit VideoCaptureHost(MediaStreamManager* media_stream_manager);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // VideoCaptureControllerEventHandler implementation.
+ virtual void OnError(const VideoCaptureControllerID& id) OVERRIDE;
+ virtual void OnBufferCreated(const VideoCaptureControllerID& id,
+ base::SharedMemoryHandle handle,
+ int length, int buffer_id) OVERRIDE;
+ virtual void OnBufferReady(const VideoCaptureControllerID& id,
+ int buffer_id,
+ base::Time timestamp) OVERRIDE;
+ virtual void OnFrameInfo(
+ const VideoCaptureControllerID& id,
+ const media::VideoCaptureCapability& format) OVERRIDE;
+ virtual void OnFrameInfoChanged(const VideoCaptureControllerID& id,
+ int width,
+ int height,
+ int frame_per_second) OVERRIDE;
+ virtual void OnEnded(const VideoCaptureControllerID& id) OVERRIDE;
+
+ private:
+ friend class BrowserThread;
+ friend class base::DeleteHelper<VideoCaptureHost>;
+ friend class MockVideoCaptureHost;
+ friend class VideoCaptureHostTest;
+
+ virtual ~VideoCaptureHost();
+
+ // IPC message: Start capture on the VideoCaptureDevice referenced by
+ // VideoCaptureParams::session_id. |device_id| is an id created by
+ // VideoCaptureMessageFilter to identify a session
+ // between a VideoCaptureMessageFilter and a VideoCaptureHost.
+ void OnStartCapture(int device_id,
+ const media::VideoCaptureParams& params);
+ void OnControllerAdded(
+ int device_id, const media::VideoCaptureParams& params,
+ VideoCaptureController* controller);
+ void DoControllerAddedOnIOThread(
+ int device_id, const media::VideoCaptureParams params,
+ VideoCaptureController* controller);
+
+ // IPC message: Stop capture on device referenced by |device_id|.
+ void OnStopCapture(int device_id);
+
+ // IPC message: Pause capture on device referenced by |device_id|.
+ void OnPauseCapture(int device_id);
+
+ // IPC message: Receive an empty buffer from renderer. Send it to device
+ // referenced by |device_id|.
+ void OnReceiveEmptyBuffer(int device_id, int buffer_id);
+
+ // Send a newly created buffer to the VideoCaptureMessageFilter.
+ void DoSendNewBufferOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ base::SharedMemoryHandle handle,
+ int length,
+ int buffer_id);
+
+ // Send a filled buffer to the VideoCaptureMessageFilter.
+ void DoSendFilledBufferOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ int buffer_id,
+ base::Time timestamp);
+
+ // Send information about the capture parameters (resolution, frame rate etc)
+ // to the VideoCaptureMessageFilter.
+ void DoSendFrameInfoOnIOThread(const VideoCaptureControllerID& controller_id,
+ const media::VideoCaptureCapability& format);
+
+ // Send newly changed information about frame resolution and frame rate
+ // to the VideoCaptureMessageFilter.
+ void DoSendFrameInfoChangedOnIOThread(
+ const VideoCaptureControllerID& controller_id,
+ int width,
+ int height,
+ int frame_per_second);
+
+ // Handle error coming from VideoCaptureDevice.
+ void DoHandleErrorOnIOThread(const VideoCaptureControllerID& controller_id);
+
+ void DoEndedOnIOThread(const VideoCaptureControllerID& controller_id);
+
+ void DeleteVideoCaptureControllerOnIOThread(
+ const VideoCaptureControllerID& controller_id);
+
+ MediaStreamManager* media_stream_manager_;
+
+ struct Entry;
+ typedef std::map<VideoCaptureControllerID, Entry*> EntryMap;
+ // A map of VideoCaptureControllerID to its state and VideoCaptureController.
+ EntryMap entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_host_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_host_unittest.cc
new file mode 100644
index 00000000000..762148c86a2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_host_unittest.cc
@@ -0,0 +1,371 @@
+// 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 <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/video_capture_host.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/common/media/video_capture_messages.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/audio/audio_manager.h"
+#include "media/video/capture/video_capture_types.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::Return;
+
+namespace content {
+
+// Id used to identify the capture session between renderer and
+// video_capture_host.
+static const int kDeviceId = 1;
+// Id of a video capture device
+static const media::VideoCaptureSessionId kTestFakeDeviceId =
+ VideoCaptureManager::kStartOpenSessionId;
+
+// Define to enable test where video is dumped to file.
+// #define DUMP_VIDEO
+
+// Define to use a real video capture device.
+// #define TEST_REAL_CAPTURE_DEVICE
+
+// Simple class used for dumping video to a file. This can be used for
+// verifying the output.
+class DumpVideo {
+ public:
+ DumpVideo() : expected_size_(0) {}
+ void StartDump(int width, int height) {
+ base::FilePath file_name = base::FilePath(base::StringPrintf(
+ FILE_PATH_LITERAL("dump_w%d_h%d.yuv"), width, height));
+ file_.reset(file_util::OpenFile(file_name, "wb"));
+ expected_size_ = width * height * 3 / 2;
+ }
+ void NewVideoFrame(const void* buffer) {
+ if (file_.get() != NULL) {
+ fwrite(buffer, expected_size_, 1, file_.get());
+ }
+ }
+
+ private:
+ file_util::ScopedFILE file_;
+ int expected_size_;
+};
+
+class MockVideoCaptureHost : public VideoCaptureHost {
+ public:
+ MockVideoCaptureHost(MediaStreamManager* manager)
+ : VideoCaptureHost(manager),
+ return_buffers_(false),
+ dump_video_(false) {}
+
+ // A list of mock methods.
+ MOCK_METHOD4(OnNewBufferCreated,
+ void(int device_id, base::SharedMemoryHandle handle,
+ int length, int buffer_id));
+ MOCK_METHOD3(OnBufferFilled,
+ void(int device_id, int buffer_id, base::Time timestamp));
+ MOCK_METHOD2(OnStateChanged, void(int device_id, VideoCaptureState state));
+ MOCK_METHOD1(OnDeviceInfo, void(int device_id));
+
+ // Use class DumpVideo to write I420 video to file.
+ void SetDumpVideo(bool enable) {
+ dump_video_ = enable;
+ }
+
+ void SetReturnReceviedDibs(bool enable) {
+ return_buffers_ = enable;
+ }
+
+ // Return Dibs we currently have received.
+ void ReturnReceivedDibs(int device_id) {
+ int handle = GetReceivedDib();
+ while (handle) {
+ this->OnReceiveEmptyBuffer(device_id, handle);
+ handle = GetReceivedDib();
+ }
+ }
+
+ int GetReceivedDib() {
+ if (filled_dib_.empty())
+ return 0;
+ std::map<int, base::SharedMemory*>::iterator it = filled_dib_.begin();
+ int h = it->first;
+ delete it->second;
+ filled_dib_.erase(it);
+
+ return h;
+ }
+
+ private:
+ virtual ~MockVideoCaptureHost() {
+ STLDeleteContainerPairSecondPointers(filled_dib_.begin(),
+ filled_dib_.end());
+ }
+
+ // This method is used to dispatch IPC messages to the renderer. We intercept
+ // these messages here and dispatch to our mock methods to verify the
+ // conversation between this object and the renderer.
+ virtual bool Send(IPC::Message* message) OVERRIDE {
+ CHECK(message);
+
+ // In this method we dispatch the messages to the according handlers as if
+ // we are the renderer.
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message)
+ IPC_MESSAGE_HANDLER(VideoCaptureMsg_NewBuffer, OnNewBufferCreatedDispatch)
+ IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch)
+ IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChangedDispatch)
+ IPC_MESSAGE_HANDLER(VideoCaptureMsg_DeviceInfo, OnDeviceInfoDispatch)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ EXPECT_TRUE(handled);
+
+ delete message;
+ return true;
+ }
+
+ // These handler methods do minimal things and delegate to the mock methods.
+ void OnNewBufferCreatedDispatch(int device_id,
+ base::SharedMemoryHandle handle,
+ int length, int buffer_id) {
+ OnNewBufferCreated(device_id, handle, length, buffer_id);
+ base::SharedMemory* dib = new base::SharedMemory(handle, false);
+ dib->Map(length);
+ filled_dib_[buffer_id] = dib;
+ }
+
+ void OnBufferFilledDispatch(int device_id, int buffer_id,
+ base::Time timestamp) {
+ if (dump_video_) {
+ base::SharedMemory* dib = filled_dib_[buffer_id];
+ ASSERT_TRUE(dib != NULL);
+ dumper_.NewVideoFrame(dib->memory());
+ }
+
+ OnBufferFilled(device_id, buffer_id, timestamp);
+ if (return_buffers_) {
+ VideoCaptureHost::OnReceiveEmptyBuffer(device_id, buffer_id);
+ }
+ }
+
+ void OnStateChangedDispatch(int device_id, VideoCaptureState state) {
+ OnStateChanged(device_id, state);
+ }
+
+ void OnDeviceInfoDispatch(int device_id,
+ media::VideoCaptureParams params) {
+ if (dump_video_) {
+ dumper_.StartDump(params.width, params.height);
+ }
+ OnDeviceInfo(device_id);
+ }
+
+ std::map<int, base::SharedMemory*> filled_dib_;
+ bool return_buffers_;
+ bool dump_video_;
+ DumpVideo dumper_;
+};
+
+ACTION_P2(ExitMessageLoop, message_loop, quit_closure) {
+ message_loop->PostTask(FROM_HERE, quit_closure);
+}
+
+class VideoCaptureHostTest : public testing::Test {
+ public:
+ VideoCaptureHostTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+ message_loop_(base::MessageLoopProxy::current()) {
+ // Create our own MediaStreamManager.
+ audio_manager_.reset(media::AudioManager::Create());
+ media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
+#ifndef TEST_REAL_CAPTURE_DEVICE
+ media_stream_manager_->UseFakeDevice();
+#endif
+
+ host_ = new MockVideoCaptureHost(media_stream_manager_.get());
+
+ // Simulate IPC channel connected.
+ host_->OnChannelConnected(base::GetCurrentProcId());
+ }
+
+ virtual ~VideoCaptureHostTest() {
+ // Verifies and removes the expectations on host_ and
+ // returns true iff successful.
+ Mock::VerifyAndClearExpectations(host_.get());
+
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED))
+ .Times(AnyNumber());
+
+ // Simulate closing the IPC channel.
+ host_->OnChannelClosing();
+
+ // Release the reference to the mock object. The object will be destructed
+ // on the current message loop.
+ host_ = NULL;
+
+ media_stream_manager_->WillDestroyCurrentMessageLoop();
+ }
+
+ protected:
+ void StartCapture() {
+ InSequence s;
+ // 1. First - get info about the new resolution
+ EXPECT_CALL(*host_.get(), OnDeviceInfo(kDeviceId));
+
+ // 2. Change state to started
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STARTED));
+
+ // 3. Newly created buffers will arrive.
+ EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _))
+ .Times(AnyNumber()).WillRepeatedly(Return());
+
+ // 4. First filled buffer will arrive.
+ base::RunLoop run_loop;
+ EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _))
+ .Times(AnyNumber()).WillOnce(ExitMessageLoop(
+ message_loop_, run_loop.QuitClosure()));
+
+ media::VideoCaptureParams params;
+ params.width = 352;
+ params.height = 288;
+ params.frame_per_second = 30;
+ params.session_id = kTestFakeDeviceId;
+ host_->OnStartCapture(kDeviceId, params);
+ run_loop.Run();
+ }
+
+#ifdef DUMP_VIDEO
+ void CaptureAndDumpVideo(int width, int heigt, int frame_rate) {
+ InSequence s;
+ // 1. First - get info about the new resolution
+ EXPECT_CALL(*host_, OnDeviceInfo(kDeviceId));
+
+ // 2. Change state to started
+ EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STARTED));
+
+ // 3. First filled buffer will arrive.
+ base::RunLoop run_loop;
+ EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _))
+ .Times(AnyNumber())
+ .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()));
+
+ media::VideoCaptureParams params;
+ params.width = width;
+ params.height = heigt;
+ params.frame_per_second = frame_rate;
+ params.session_id = kTestFakeDeviceId;
+ host_->SetDumpVideo(true);
+ host_->OnStartCapture(kDeviceId, params);
+ run_loop.Run();
+ }
+#endif
+
+ void StopCapture() {
+ base::RunLoop run_loop;
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED))
+ .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()));
+
+ host_->OnStopCapture(kDeviceId);
+ host_->SetReturnReceviedDibs(true);
+ host_->ReturnReceivedDibs(kDeviceId);
+
+ run_loop.Run();
+
+ host_->SetReturnReceviedDibs(false);
+ // Expect the VideoCaptureDevice has been stopped
+ EXPECT_EQ(0u, host_->entries_.size());
+ }
+
+ void NotifyPacketReady() {
+ base::RunLoop run_loop;
+ EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _))
+ .Times(AnyNumber()).WillOnce(ExitMessageLoop(
+ message_loop_, run_loop.QuitClosure()))
+ .RetiresOnSaturation();
+ run_loop.Run();
+ }
+
+ void ReturnReceivedPackets() {
+ host_->ReturnReceivedDibs(kDeviceId);
+ }
+
+ void SimulateError() {
+ // Expect a change state to error state sent through IPC.
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR)).Times(1);
+ VideoCaptureControllerID id(kDeviceId);
+ host_->OnError(id);
+ // Wait for the error callback.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ scoped_refptr<MockVideoCaptureHost> host_;
+
+ private:
+ scoped_ptr<media::AudioManager> audio_manager_;
+ scoped_ptr<MediaStreamManager> media_stream_manager_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest);
+};
+
+TEST_F(VideoCaptureHostTest, StartCapture) {
+ StartCapture();
+}
+
+TEST_F(VideoCaptureHostTest, StartCapturePlayStop) {
+ StartCapture();
+ NotifyPacketReady();
+ NotifyPacketReady();
+ ReturnReceivedPackets();
+ StopCapture();
+}
+
+TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) {
+ StartCapture();
+ SimulateError();
+ StopCapture();
+}
+
+TEST_F(VideoCaptureHostTest, StartCaptureError) {
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)).Times(0);
+ StartCapture();
+ NotifyPacketReady();
+ SimulateError();
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+}
+
+#ifdef DUMP_VIDEO
+TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) {
+ CaptureAndDumpVideo(640, 480, 30);
+}
+TEST_F(VideoCaptureHostTest, CaptureAndDump720P) {
+ CaptureAndDumpVideo(1280, 720, 30);
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.cc b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
new file mode 100644
index 00000000000..79da41260a3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
@@ -0,0 +1,593 @@
+// 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 "content/browser/renderer_host/media/video_capture_manager.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/renderer_host/media/video_capture_controller.h"
+#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
+#include "content/browser/renderer_host/media/web_contents_video_capture_device.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/desktop_media_id.h"
+#include "content/public/common/media_stream_request.h"
+#include "media/base/scoped_histogram_timer.h"
+#include "media/video/capture/fake_video_capture_device.h"
+#include "media/video/capture/video_capture_device.h"
+
+#if defined(ENABLE_SCREEN_CAPTURE)
+#include "content/browser/renderer_host/media/desktop_capture_device.h"
+#endif
+
+namespace content {
+
+// Starting id for the first capture session.
+// VideoCaptureManager::kStartOpenSessionId is used as default id without
+// explicitly calling open device.
+enum { kFirstSessionId = VideoCaptureManager::kStartOpenSessionId + 1 };
+
+struct VideoCaptureManager::Controller {
+ Controller(
+ VideoCaptureController* vc_controller,
+ VideoCaptureControllerEventHandler* handler)
+ : controller(vc_controller),
+ ready_to_delete(false) {
+ handlers.push_front(handler);
+ }
+ ~Controller() {}
+
+ scoped_refptr<VideoCaptureController> controller;
+ bool ready_to_delete;
+ Handlers handlers;
+};
+
+VideoCaptureManager::VideoCaptureManager()
+ : listener_(NULL),
+ new_capture_session_id_(kFirstSessionId),
+ use_fake_device_(false) {
+}
+
+VideoCaptureManager::~VideoCaptureManager() {
+ DCHECK(devices_.empty());
+ DCHECK(controllers_.empty());
+}
+
+void VideoCaptureManager::Register(MediaStreamProviderListener* listener,
+ base::MessageLoopProxy* device_thread_loop) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(!listener_);
+ DCHECK(!device_loop_.get());
+ listener_ = listener;
+ device_loop_ = device_thread_loop;
+}
+
+void VideoCaptureManager::Unregister() {
+ DCHECK(listener_);
+ listener_ = NULL;
+}
+
+void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(listener_);
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnEnumerateDevices, this, stream_type));
+}
+
+int VideoCaptureManager::Open(const StreamDeviceInfo& device) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(listener_);
+
+ // Generate a new id for this device.
+ int video_capture_session_id = new_capture_session_id_++;
+
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnOpen, this, video_capture_session_id,
+ device));
+
+ return video_capture_session_id;
+}
+
+void VideoCaptureManager::Close(int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(listener_);
+ DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id;
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnClose, this, capture_session_id));
+}
+
+void VideoCaptureManager::Start(
+ const media::VideoCaptureParams& capture_params,
+ media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnStart, this, capture_params,
+ video_capture_receiver));
+}
+
+void VideoCaptureManager::Stop(
+ const media::VideoCaptureSessionId& capture_session_id,
+ base::Closure stopped_cb) {
+ DVLOG(1) << "VideoCaptureManager::Stop, id " << capture_session_id;
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnStop, this, capture_session_id,
+ stopped_cb));
+}
+
+void VideoCaptureManager::UseFakeDevice() {
+ use_fake_device_ = true;
+}
+
+void VideoCaptureManager::OnEnumerateDevices(MediaStreamType stream_type) {
+ SCOPED_UMA_HISTOGRAM_TIMER(
+ "Media.VideoCaptureManager.OnEnumerateDevicesTime");
+ DCHECK(IsOnDeviceThread());
+
+ media::VideoCaptureDevice::Names device_names;
+ GetAvailableDevices(stream_type, &device_names);
+
+ scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
+ for (media::VideoCaptureDevice::Names::iterator it =
+ device_names.begin(); it != device_names.end(); ++it) {
+ bool opened = DeviceOpened(*it);
+ devices->push_back(StreamDeviceInfo(
+ stream_type, it->GetNameAndModel(), it->id(), opened));
+ }
+
+ PostOnDevicesEnumerated(stream_type, devices.Pass());
+}
+
+void VideoCaptureManager::OnOpen(int capture_session_id,
+ const StreamDeviceInfo& device) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnOpenTime");
+ DCHECK(IsOnDeviceThread());
+ DCHECK(devices_.find(capture_session_id) == devices_.end());
+ DVLOG(1) << "VideoCaptureManager::OnOpen, id " << capture_session_id;
+
+ // Check if another session has already opened this device. If so, just
+ // use that opened device.
+ media::VideoCaptureDevice* opened_video_capture_device =
+ GetOpenedDevice(device);
+ if (opened_video_capture_device) {
+ DeviceEntry& new_entry = devices_[capture_session_id];
+ new_entry.stream_type = device.device.type;
+ new_entry.capture_device = opened_video_capture_device;
+ PostOnOpened(device.device.type, capture_session_id);
+ return;
+ }
+
+ scoped_ptr<media::VideoCaptureDevice> video_capture_device;
+
+ // Open the device.
+ switch (device.device.type) {
+ case MEDIA_DEVICE_VIDEO_CAPTURE: {
+ // We look up the device id from the renderer in our local enumeration
+ // since the renderer does not have all the information that might be
+ // held in the browser-side VideoCaptureDevice::Name structure.
+ media::VideoCaptureDevice::Name* found =
+ video_capture_devices_.FindById(device.device.id);
+ if (found) {
+ video_capture_device.reset(use_fake_device_ ?
+ media::FakeVideoCaptureDevice::Create(*found) :
+ media::VideoCaptureDevice::Create(*found));
+ }
+ break;
+ }
+ case MEDIA_TAB_VIDEO_CAPTURE: {
+ video_capture_device.reset(
+ WebContentsVideoCaptureDevice::Create(device.device.id));
+ break;
+ }
+ case MEDIA_DESKTOP_VIDEO_CAPTURE: {
+#if defined(ENABLE_SCREEN_CAPTURE)
+ DesktopMediaID id = DesktopMediaID::Parse(device.device.id);
+ if (id.type != DesktopMediaID::TYPE_NONE) {
+ video_capture_device = DesktopCaptureDevice::Create(id);
+ }
+#endif // defined(ENABLE_SCREEN_CAPTURE)
+ break;
+ }
+ default: {
+ NOTIMPLEMENTED();
+ break;
+ }
+ }
+
+ if (!video_capture_device) {
+ PostOnError(capture_session_id, kDeviceNotAvailable);
+ return;
+ }
+
+ DeviceEntry& new_entry = devices_[capture_session_id];
+ new_entry.stream_type = device.device.type;
+ new_entry.capture_device = video_capture_device.release();
+ PostOnOpened(device.device.type, capture_session_id);
+}
+
+void VideoCaptureManager::OnClose(int capture_session_id) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnCloseTime");
+ DCHECK(IsOnDeviceThread());
+ DVLOG(1) << "VideoCaptureManager::OnClose, id " << capture_session_id;
+
+ VideoCaptureDevices::iterator device_it = devices_.find(capture_session_id);
+ if (device_it == devices_.end()) {
+ return;
+ }
+ const DeviceEntry removed_entry = device_it->second;
+ devices_.erase(device_it);
+
+ Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
+ if (cit != controllers_.end()) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureController::StopSession,
+ cit->second->controller, capture_session_id));
+ }
+
+ if (!DeviceInUse(removed_entry.capture_device)) {
+ // No other users of this device, deallocate (if not done already) and
+ // delete the device. No need to take care of the controller, that is done
+ // by |OnStop|.
+ removed_entry.capture_device->DeAllocate();
+ Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
+ if (cit != controllers_.end()) {
+ delete cit->second;
+ controllers_.erase(cit);
+ }
+ delete removed_entry.capture_device;
+ }
+
+ PostOnClosed(removed_entry.stream_type, capture_session_id);
+}
+
+void VideoCaptureManager::OnStart(
+ const media::VideoCaptureParams capture_params,
+ media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStartTime");
+ DCHECK(IsOnDeviceThread());
+ DCHECK(video_capture_receiver != NULL);
+ DVLOG(1) << "VideoCaptureManager::OnStart, (" << capture_params.width
+ << ", " << capture_params.height
+ << ", " << capture_params.frame_per_second
+ << ", " << capture_params.session_id
+ << ")";
+
+ media::VideoCaptureDevice* video_capture_device =
+ GetDeviceInternal(capture_params.session_id);
+ if (!video_capture_device) {
+ // Invalid session id.
+ video_capture_receiver->OnError();
+ return;
+ }
+ // TODO(mcasas): Variable resolution video capture devices, are not yet
+ // fully supported, see crbug.com/261410, second part, and crbug.com/266082 .
+ if (capture_params.frame_size_type !=
+ media::ConstantResolutionVideoCaptureDevice) {
+ LOG(DFATAL) << "Only constant Video Capture resolution device supported.";
+ video_capture_receiver->OnError();
+ return;
+ }
+ Controllers::iterator cit = controllers_.find(video_capture_device);
+ if (cit != controllers_.end()) {
+ cit->second->ready_to_delete = false;
+ }
+
+ // Possible errors are signaled to video_capture_receiver by
+ // video_capture_device. video_capture_receiver to perform actions.
+ media::VideoCaptureCapability params_as_capability_copy;
+ params_as_capability_copy.width = capture_params.width;
+ params_as_capability_copy.height = capture_params.height;
+ params_as_capability_copy.frame_rate = capture_params.frame_per_second;
+ params_as_capability_copy.session_id = capture_params.session_id;
+ params_as_capability_copy.frame_size_type = capture_params.frame_size_type;
+ video_capture_device->Allocate(params_as_capability_copy,
+ video_capture_receiver);
+ video_capture_device->Start();
+}
+
+void VideoCaptureManager::OnStop(
+ const media::VideoCaptureSessionId capture_session_id,
+ base::Closure stopped_cb) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStopTime");
+ DCHECK(IsOnDeviceThread());
+ DVLOG(1) << "VideoCaptureManager::OnStop, id " << capture_session_id;
+
+ VideoCaptureDevices::iterator it = devices_.find(capture_session_id);
+ if (it != devices_.end()) {
+ media::VideoCaptureDevice* video_capture_device = it->second.capture_device;
+ // Possible errors are signaled to video_capture_receiver by
+ // video_capture_device. video_capture_receiver to perform actions.
+ video_capture_device->Stop();
+ video_capture_device->DeAllocate();
+ Controllers::iterator cit = controllers_.find(video_capture_device);
+ if (cit != controllers_.end()) {
+ cit->second->ready_to_delete = true;
+ if (cit->second->handlers.empty()) {
+ delete cit->second;
+ controllers_.erase(cit);
+ }
+ }
+ }
+
+ if (!stopped_cb.is_null())
+ stopped_cb.Run();
+
+ if (capture_session_id == kStartOpenSessionId) {
+ // This device was opened from Start(), not Open(). Close it!
+ OnClose(capture_session_id);
+ }
+}
+
+void VideoCaptureManager::OnOpened(MediaStreamType stream_type,
+ int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!listener_) {
+ // Listener has been removed.
+ return;
+ }
+ listener_->Opened(stream_type, capture_session_id);
+}
+
+void VideoCaptureManager::OnClosed(MediaStreamType stream_type,
+ int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!listener_) {
+ // Listener has been removed.
+ return;
+ }
+ listener_->Closed(stream_type, capture_session_id);
+}
+
+void VideoCaptureManager::OnDevicesEnumerated(
+ MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!listener_) {
+ // Listener has been removed.
+ return;
+ }
+ listener_->DevicesEnumerated(stream_type, *devices);
+}
+
+void VideoCaptureManager::OnError(MediaStreamType stream_type,
+ int capture_session_id,
+ MediaStreamProviderError error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!listener_) {
+ // Listener has been removed.
+ return;
+ }
+ listener_->Error(stream_type, capture_session_id, error);
+}
+
+void VideoCaptureManager::PostOnOpened(
+ MediaStreamType stream_type, int capture_session_id) {
+ DCHECK(IsOnDeviceThread());
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnOpened, this,
+ stream_type, capture_session_id));
+}
+
+void VideoCaptureManager::PostOnClosed(
+ MediaStreamType stream_type, int capture_session_id) {
+ DCHECK(IsOnDeviceThread());
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnClosed, this,
+ stream_type, capture_session_id));
+}
+
+void VideoCaptureManager::PostOnDevicesEnumerated(
+ MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices) {
+ DCHECK(IsOnDeviceThread());
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnDevicesEnumerated,
+ this, stream_type, base::Passed(&devices)));
+}
+
+void VideoCaptureManager::PostOnError(int capture_session_id,
+ MediaStreamProviderError error) {
+ DCHECK(IsOnDeviceThread());
+ MediaStreamType stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
+ VideoCaptureDevices::const_iterator it = devices_.find(capture_session_id);
+ if (it != devices_.end())
+ stream_type = it->second.stream_type;
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnError, this,
+ stream_type, capture_session_id, error));
+}
+
+bool VideoCaptureManager::IsOnDeviceThread() const {
+ return device_loop_->BelongsToCurrentThread();
+}
+
+void VideoCaptureManager::GetAvailableDevices(
+ MediaStreamType stream_type,
+ media::VideoCaptureDevice::Names* device_names) {
+ DCHECK(IsOnDeviceThread());
+
+ switch (stream_type) {
+ case MEDIA_DEVICE_VIDEO_CAPTURE:
+ // Cache the latest enumeration of video capture devices.
+ // We'll refer to this list again in OnOpen to avoid having to
+ // enumerate the devices again.
+ video_capture_devices_.clear();
+ if (!use_fake_device_) {
+ media::VideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
+ } else {
+ media::FakeVideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
+ }
+ *device_names = video_capture_devices_;
+ break;
+
+ case MEDIA_DESKTOP_VIDEO_CAPTURE:
+ device_names->clear();
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool VideoCaptureManager::DeviceOpened(
+ const media::VideoCaptureDevice::Name& device_name) {
+ DCHECK(IsOnDeviceThread());
+
+ for (VideoCaptureDevices::iterator it = devices_.begin();
+ it != devices_.end(); ++it) {
+ if (device_name.id() == it->second.capture_device->device_name().id()) {
+ // We've found the device!
+ return true;
+ }
+ }
+ return false;
+}
+
+media::VideoCaptureDevice* VideoCaptureManager::GetOpenedDevice(
+ const StreamDeviceInfo& device_info) {
+ DCHECK(IsOnDeviceThread());
+
+ for (VideoCaptureDevices::iterator it = devices_.begin();
+ it != devices_.end(); it++) {
+ if (device_info.device.id ==
+ it->second.capture_device->device_name().id()) {
+ return it->second.capture_device;
+ }
+ }
+ return NULL;
+}
+
+bool VideoCaptureManager::DeviceInUse(
+ const media::VideoCaptureDevice* video_capture_device) {
+ DCHECK(IsOnDeviceThread());
+
+ for (VideoCaptureDevices::iterator it = devices_.begin();
+ it != devices_.end(); ++it) {
+ if (video_capture_device == it->second.capture_device) {
+ // We've found the device!
+ return true;
+ }
+ }
+ return false;
+}
+
+void VideoCaptureManager::AddController(
+ const media::VideoCaptureParams& capture_params,
+ VideoCaptureControllerEventHandler* handler,
+ base::Callback<void(VideoCaptureController*)> added_cb) {
+ DCHECK(handler);
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::DoAddControllerOnDeviceThread,
+ this, capture_params, handler, added_cb));
+}
+
+void VideoCaptureManager::DoAddControllerOnDeviceThread(
+ const media::VideoCaptureParams capture_params,
+ VideoCaptureControllerEventHandler* handler,
+ base::Callback<void(VideoCaptureController*)> added_cb) {
+ DCHECK(IsOnDeviceThread());
+
+ media::VideoCaptureDevice* video_capture_device =
+ GetDeviceInternal(capture_params.session_id);
+ scoped_refptr<VideoCaptureController> controller;
+ if (video_capture_device) {
+ Controllers::iterator cit = controllers_.find(video_capture_device);
+ if (cit == controllers_.end()) {
+ controller = new VideoCaptureController(this);
+ controllers_[video_capture_device] =
+ new Controller(controller.get(), handler);
+ } else {
+ controllers_[video_capture_device]->handlers.push_front(handler);
+ controller = controllers_[video_capture_device]->controller;
+ }
+ }
+ added_cb.Run(controller.get());
+}
+
+void VideoCaptureManager::RemoveController(
+ VideoCaptureController* controller,
+ VideoCaptureControllerEventHandler* handler) {
+ DCHECK(handler);
+ device_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::DoRemoveControllerOnDeviceThread, this,
+ make_scoped_refptr(controller), handler));
+}
+
+void VideoCaptureManager::DoRemoveControllerOnDeviceThread(
+ VideoCaptureController* controller,
+ VideoCaptureControllerEventHandler* handler) {
+ DCHECK(IsOnDeviceThread());
+
+ for (Controllers::iterator cit = controllers_.begin();
+ cit != controllers_.end(); ++cit) {
+ if (controller == cit->second->controller.get()) {
+ Handlers& handlers = cit->second->handlers;
+ for (Handlers::iterator hit = handlers.begin();
+ hit != handlers.end(); ++hit) {
+ if ((*hit) == handler) {
+ handlers.erase(hit);
+ break;
+ }
+ }
+ if (handlers.empty() && cit->second->ready_to_delete) {
+ delete cit->second;
+ controllers_.erase(cit);
+ }
+ return;
+ }
+ }
+}
+
+media::VideoCaptureDevice* VideoCaptureManager::GetDeviceInternal(
+ int capture_session_id) {
+ DCHECK(IsOnDeviceThread());
+ VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
+ if (dit != devices_.end()) {
+ return dit->second.capture_device;
+ }
+
+ // Solution for not using MediaStreamManager.
+ // This session id won't be returned by Open().
+ if (capture_session_id == kStartOpenSessionId) {
+ media::VideoCaptureDevice::Names device_names;
+ GetAvailableDevices(MEDIA_DEVICE_VIDEO_CAPTURE, &device_names);
+ if (device_names.empty()) {
+ // No devices available.
+ return NULL;
+ }
+ StreamDeviceInfo device(MEDIA_DEVICE_VIDEO_CAPTURE,
+ device_names.front().GetNameAndModel(),
+ device_names.front().id(),
+ false);
+
+ // Call OnOpen to open using the first device in the list.
+ OnOpen(capture_session_id, device);
+
+ VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
+ if (dit != devices_.end()) {
+ return dit->second.capture_device;
+ }
+ }
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.h b/chromium/content/browser/renderer_host/media/video_capture_manager.h
new file mode 100644
index 00000000000..34d6e626413
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.h
@@ -0,0 +1,170 @@
+// 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.
+
+// VideoCaptureManager is used to open/close, start/stop, enumerate available
+// video capture devices, and manage VideoCaptureController's.
+// All functions are expected to be called from Browser::IO thread.
+// VideoCaptureManager will open OS dependent instances of VideoCaptureDevice.
+// A device can only be opened once.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_MANAGER_H_
+
+#include <list>
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/common/content_export.h"
+#include "content/common/media/media_stream_options.h"
+#include "media/video/capture/video_capture_device.h"
+#include "media/video/capture/video_capture_types.h"
+
+namespace content {
+class MockVideoCaptureManager;
+class VideoCaptureController;
+class VideoCaptureControllerEventHandler;
+
+// VideoCaptureManager opens/closes and start/stops video capture devices.
+class CONTENT_EXPORT VideoCaptureManager : public MediaStreamProvider {
+ public:
+ // Calling |Start| of this id will open the first device, even though open has
+ // not been called. This is used to be able to use video capture devices
+ // before MediaStream is implemented in Chrome and WebKit.
+ enum { kStartOpenSessionId = 1 };
+
+ VideoCaptureManager();
+
+ // Implements MediaStreamProvider.
+ virtual void Register(MediaStreamProviderListener* listener,
+ base::MessageLoopProxy* device_thread_loop) OVERRIDE;
+
+ virtual void Unregister() OVERRIDE;
+
+ virtual void EnumerateDevices(MediaStreamType stream_type) OVERRIDE;
+
+ virtual int Open(const StreamDeviceInfo& device) OVERRIDE;
+
+ virtual void Close(int capture_session_id) OVERRIDE;
+
+ // Functions used to start and stop media flow.
+ // Start allocates the device and no other application can use the device
+ // before Stop is called. Captured video frames will be delivered to
+ // video_capture_receiver.
+ virtual void Start(const media::VideoCaptureParams& capture_params,
+ media::VideoCaptureDevice::EventHandler* video_capture_receiver);
+
+ // Stops capture device referenced by |capture_session_id|. No more frames
+ // will be delivered to the frame receiver, and |stopped_cb| will be called.
+ // |stopped_cb| can be NULL.
+ virtual void Stop(const media::VideoCaptureSessionId& capture_session_id,
+ base::Closure stopped_cb);
+
+ // Used by unit test to make sure a fake device is used instead of a real
+ // video capture device. Due to timing requirements, the function must be
+ // called before EnumerateDevices and Open.
+ void UseFakeDevice();
+
+ // Called by VideoCaptureHost to get a controller for |capture_params|.
+ // The controller is returned via calling |added_cb|.
+ void AddController(
+ const media::VideoCaptureParams& capture_params,
+ VideoCaptureControllerEventHandler* handler,
+ base::Callback<void(VideoCaptureController*)> added_cb);
+ // Called by VideoCaptureHost to remove the |controller|.
+ void RemoveController(
+ VideoCaptureController* controller,
+ VideoCaptureControllerEventHandler* handler);
+
+ private:
+ friend class MockVideoCaptureManager;
+
+ virtual ~VideoCaptureManager();
+
+ typedef std::list<VideoCaptureControllerEventHandler*> Handlers;
+ struct Controller;
+
+ // Called by the public functions, executed on device thread.
+ void OnEnumerateDevices(MediaStreamType stream_type);
+ void OnOpen(int capture_session_id, const StreamDeviceInfo& device);
+ void OnClose(int capture_session_id);
+ void OnStart(const media::VideoCaptureParams capture_params,
+ media::VideoCaptureDevice::EventHandler* video_capture_receiver);
+ void OnStop(const media::VideoCaptureSessionId capture_session_id,
+ base::Closure stopped_cb);
+ void DoAddControllerOnDeviceThread(
+ const media::VideoCaptureParams capture_params,
+ VideoCaptureControllerEventHandler* handler,
+ base::Callback<void(VideoCaptureController*)> added_cb);
+ void DoRemoveControllerOnDeviceThread(
+ VideoCaptureController* controller,
+ VideoCaptureControllerEventHandler* handler);
+
+ // Executed on Browser::IO thread to call Listener.
+ void OnOpened(MediaStreamType type, int capture_session_id);
+ void OnClosed(MediaStreamType type, int capture_session_id);
+ void OnDevicesEnumerated(MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices);
+ void OnError(MediaStreamType type, int capture_session_id,
+ MediaStreamProviderError error);
+
+ // Executed on device thread to make sure Listener is called from
+ // Browser::IO thread.
+ void PostOnOpened(MediaStreamType type, int capture_session_id);
+ void PostOnClosed(MediaStreamType type, int capture_session_id);
+ void PostOnDevicesEnumerated(MediaStreamType stream_type,
+ scoped_ptr<StreamDeviceInfoArray> devices);
+ void PostOnError(int capture_session_id, MediaStreamProviderError error);
+
+ // Helpers
+ void GetAvailableDevices(MediaStreamType stream_type,
+ media::VideoCaptureDevice::Names* device_names);
+ bool DeviceOpened(const media::VideoCaptureDevice::Name& device_name);
+ bool DeviceInUse(const media::VideoCaptureDevice* video_capture_device);
+ media::VideoCaptureDevice* GetOpenedDevice(
+ const StreamDeviceInfo& device_info);
+ bool IsOnDeviceThread() const;
+ media::VideoCaptureDevice* GetDeviceInternal(int capture_session_id);
+
+ // The message loop of media stream device thread that this object runs on.
+ scoped_refptr<base::MessageLoopProxy> device_loop_;
+
+ // Only accessed on Browser::IO thread.
+ MediaStreamProviderListener* listener_;
+ int new_capture_session_id_;
+
+ // Only accessed from device thread.
+ // VideoCaptureManager owns all VideoCaptureDevices and is responsible for
+ // deleting the instances when they are not used any longer.
+ struct DeviceEntry {
+ MediaStreamType stream_type;
+ media::VideoCaptureDevice* capture_device; // Maybe shared across sessions.
+ };
+ typedef std::map<int, DeviceEntry> VideoCaptureDevices;
+ VideoCaptureDevices devices_; // Maps capture_session_id to DeviceEntry.
+
+ // Set to true if using fake video capture devices for testing,
+ // false by default. This is only used for the MEDIA_DEVICE_VIDEO_CAPTURE
+ // device type.
+ bool use_fake_device_;
+
+ // Only accessed from device thread.
+ // VideoCaptureManager owns all VideoCaptureController's and is responsible
+ // for deleting the instances when they are not used any longer.
+ // VideoCaptureDevice is one-to-one mapped to VideoCaptureController.
+ typedef std::map<media::VideoCaptureDevice*, Controller*> Controllers;
+ Controllers controllers_;
+
+ // We cache the enumerated video capture devices in GetAvailableDevices
+ // (e.g. called by OnEnumerateDevices) and then look up the requested ID when
+ // a device is opened (see OnOpen).
+ // Used only on the device thread.
+ media::VideoCaptureDevice::Names video_capture_devices_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc
new file mode 100644
index 00000000000..83c064655ed
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc
@@ -0,0 +1,272 @@
+// 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.
+
+// Unit test for VideoCaptureManager.
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/browser/renderer_host/media/video_capture_manager.h"
+#include "content/common/media/media_stream_options.h"
+#include "media/video/capture/video_capture_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::InSequence;
+using testing::SaveArg;
+using ::testing::Return;
+
+namespace content {
+
+// Listener class used to track progress of VideoCaptureManager test.
+class MockMediaStreamProviderListener : public MediaStreamProviderListener {
+ public:
+ MockMediaStreamProviderListener() {}
+ ~MockMediaStreamProviderListener() {}
+
+ MOCK_METHOD2(Opened, void(MediaStreamType, int));
+ MOCK_METHOD2(Closed, void(MediaStreamType, int));
+ MOCK_METHOD2(DevicesEnumerated, void(MediaStreamType,
+ const StreamDeviceInfoArray&));
+ MOCK_METHOD3(Error, void(MediaStreamType, int,
+ MediaStreamProviderError));
+}; // class MockMediaStreamProviderListener
+
+// Needed as an input argument to Start().
+class MockFrameObserver : public media::VideoCaptureDevice::EventHandler {
+ public:
+ virtual scoped_refptr<media::VideoFrame> ReserveOutputBuffer() OVERRIDE {
+ return NULL;
+ }
+ virtual void OnError() OVERRIDE {}
+ virtual void OnFrameInfo(
+ const media::VideoCaptureCapability& info) OVERRIDE {}
+ virtual void OnIncomingCapturedFrame(const uint8* data,
+ int length,
+ base::Time timestamp,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) OVERRIDE {}
+ virtual void OnIncomingCapturedVideoFrame(
+ const scoped_refptr<media::VideoFrame>& frame,
+ base::Time timestamp) OVERRIDE {}
+};
+
+// Test class
+class VideoCaptureManagerTest : public testing::Test {
+ public:
+ VideoCaptureManagerTest() {}
+ virtual ~VideoCaptureManagerTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ listener_.reset(new MockMediaStreamProviderListener());
+ message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
+ io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
+ message_loop_.get()));
+ vcm_ = new VideoCaptureManager();
+ vcm_->UseFakeDevice();
+ vcm_->Register(listener_.get(), message_loop_->message_loop_proxy().get());
+ frame_observer_.reset(new MockFrameObserver());
+ }
+
+ virtual void TearDown() OVERRIDE {}
+
+ scoped_refptr<VideoCaptureManager> vcm_;
+ scoped_ptr<MockMediaStreamProviderListener> listener_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<BrowserThreadImpl> io_thread_;
+ scoped_ptr<MockFrameObserver> frame_observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureManagerTest);
+};
+
+// Test cases
+
+// Try to open, start, stop and close a device.
+TEST_F(VideoCaptureManagerTest, CreateAndClose) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1).WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ int video_session_id = vcm_->Open(devices.front());
+
+ media::VideoCaptureParams capture_params;
+ capture_params.session_id = video_session_id;
+ capture_params.width = 320;
+ capture_params.height = 240;
+ capture_params.frame_per_second = 30;
+ vcm_->Start(capture_params, frame_observer_.get());
+
+ vcm_->Stop(video_session_id, base::Closure());
+ vcm_->Close(video_session_id);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Open the same device twice.
+TEST_F(VideoCaptureManagerTest, OpenTwice) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1).WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(2);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(2);
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ int video_session_id_first = vcm_->Open(devices.front());
+
+ // This should trigger an error callback with error code
+ // 'kDeviceAlreadyInUse'.
+ int video_session_id_second = vcm_->Open(devices.front());
+ EXPECT_NE(video_session_id_first, video_session_id_second);
+
+ vcm_->Close(video_session_id_first);
+ vcm_->Close(video_session_id_second);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Open two different devices.
+TEST_F(VideoCaptureManagerTest, OpenTwo) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1).WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(2);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(2);
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ StreamDeviceInfoArray::iterator it = devices.begin();
+
+ int video_session_id_first = vcm_->Open(*it);
+ ++it;
+ int video_session_id_second = vcm_->Open(*it);
+
+ vcm_->Close(video_session_id_first);
+ vcm_->Close(video_session_id_second);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Try open a non-existing device.
+TEST_F(VideoCaptureManagerTest, OpenNotExisting) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1).WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Error(MEDIA_DEVICE_VIDEO_CAPTURE,
+ _, kDeviceNotAvailable))
+ .Times(1);
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ MediaStreamType stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
+ std::string device_name("device_doesnt_exist");
+ std::string device_id("id_doesnt_exist");
+ StreamDeviceInfo dummy_device(stream_type, device_name, device_id, false);
+
+ // This should fail with error code 'kDeviceNotAvailable'.
+ vcm_->Open(dummy_device);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Start a device using "magic" id, i.e. call Start without calling Open.
+TEST_F(VideoCaptureManagerTest, StartUsingId) {
+ InSequence s;
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+
+ media::VideoCaptureParams capture_params;
+ capture_params.session_id = VideoCaptureManager::kStartOpenSessionId;
+ capture_params.width = 320;
+ capture_params.height = 240;
+ capture_params.frame_per_second = 30;
+
+ // Start shall trigger the Open callback.
+ vcm_->Start(capture_params, frame_observer_.get());
+
+ // Stop shall trigger the Close callback
+ vcm_->Stop(VideoCaptureManager::kStartOpenSessionId, base::Closure());
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Open and start a device, close it before calling Stop.
+TEST_F(VideoCaptureManagerTest, CloseWithoutStop) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .Times(1).WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ int video_session_id = vcm_->Open(devices.front());
+
+ media::VideoCaptureParams capture_params;
+ capture_params.session_id = video_session_id;
+ capture_params.width = 320;
+ capture_params.height = 240;
+ capture_params.frame_per_second = 30;
+ vcm_->Start(capture_params, frame_observer_.get());
+
+ // Close will stop the running device, an assert will be triggered in
+ // VideoCaptureManager destructor otherwise.
+ vcm_->Close(video_session_id);
+ vcm_->Stop(video_session_id, base::Closure());
+
+ // Wait to check callbacks before removing the listener
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_oracle.cc b/chromium/content/browser/renderer_host/media/video_capture_oracle.cc
new file mode 100644
index 00000000000..c05ac0f8a37
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_oracle.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/video_capture_oracle.h"
+
+#include "base/debug/trace_event.h"
+
+namespace content {
+
+namespace {
+
+// This value controls how many redundant, timer-base captures occur when the
+// content is static. Redundantly capturing the same frame allows iterative
+// quality enhancement, and also allows the buffer to fill in "buffered mode".
+//
+// TODO(nick): Controlling this here is a hack and a layering violation, since
+// it's a strategy specific to the WebRTC consumer, and probably just papers
+// over some frame dropping and quality bugs. It should either be controlled at
+// a higher level, or else redundant frame generation should be pushed down
+// further into the WebRTC encoding stack.
+const int kNumRedundantCapturesOfStaticContent = 200;
+
+} // anonymous namespace
+
+VideoCaptureOracle::VideoCaptureOracle(base::TimeDelta capture_period,
+ bool events_are_reliable)
+ : capture_period_(capture_period),
+ frame_number_(0),
+ last_delivered_frame_number_(0),
+ sampler_(capture_period_,
+ events_are_reliable,
+ kNumRedundantCapturesOfStaticContent) {}
+
+bool VideoCaptureOracle::ObserveEventAndDecideCapture(
+ Event event,
+ base::Time event_time) {
+ // Record |event| and decide whether it's a good time to capture.
+ const bool content_is_dirty = (event == kCompositorUpdate ||
+ event == kSoftwarePaint);
+ bool should_sample;
+ if (content_is_dirty) {
+ frame_number_++;
+ should_sample = sampler_.AddEventAndConsiderSampling(event_time);
+ } else {
+ should_sample = sampler_.IsOverdueForSamplingAt(event_time);
+ }
+ return should_sample;
+}
+
+int VideoCaptureOracle::RecordCapture() {
+ sampler_.RecordSample();
+ return frame_number_;
+}
+
+bool VideoCaptureOracle::CompleteCapture(int frame_number,
+ base::Time timestamp) {
+ // Drop frame if previous frame number is higher or we're trying to deliver
+ // a frame with the same timestamp.
+ if (last_delivered_frame_number_ > frame_number ||
+ last_delivered_frame_timestamp_ == timestamp) {
+ LOG(ERROR) << "Frame with same timestamp or out of order delivery. "
+ << "Dropping frame.";
+ return false;
+ }
+
+ if (last_delivered_frame_timestamp_ > timestamp) {
+ // We should not get here unless time was adjusted backwards.
+ LOG(ERROR) << "Frame with past timestamp (" << timestamp.ToInternalValue()
+ << ") was delivered";
+ }
+
+ last_delivered_frame_number_ = frame_number;
+ last_delivered_frame_timestamp_ = timestamp;
+
+ return true;
+}
+
+SmoothEventSampler::SmoothEventSampler(base::TimeDelta capture_period,
+ bool events_are_reliable,
+ int redundant_capture_goal)
+ : events_are_reliable_(events_are_reliable),
+ capture_period_(capture_period),
+ redundant_capture_goal_(redundant_capture_goal),
+ token_bucket_capacity_(capture_period + capture_period / 2),
+ overdue_sample_count_(0),
+ token_bucket_(token_bucket_capacity_) {
+ DCHECK_GT(capture_period_.InMicroseconds(), 0);
+}
+
+bool SmoothEventSampler::AddEventAndConsiderSampling(base::Time event_time) {
+ DCHECK(!event_time.is_null());
+
+ // Add tokens to the bucket based on advancement in time. Then, re-bound the
+ // number of tokens in the bucket. Overflow occurs when there is too much
+ // time between events (a common case), or when RecordSample() is not being
+ // called often enough (a bug). On the other hand, if RecordSample() is being
+ // called too often (e.g., as a reaction to IsOverdueForSamplingAt()), the
+ // bucket will underflow.
+ if (!current_event_.is_null()) {
+ if (current_event_ < event_time) {
+ token_bucket_ += event_time - current_event_;
+ if (token_bucket_ > token_bucket_capacity_)
+ token_bucket_ = token_bucket_capacity_;
+ }
+ // Side note: If the system clock is reset, causing |current_event_| to be
+ // greater than |event_time|, everything here will simply gracefully adjust.
+ if (token_bucket_ < base::TimeDelta())
+ token_bucket_ = base::TimeDelta();
+ TRACE_COUNTER1("mirroring",
+ "MirroringTokenBucketUsec", token_bucket_.InMicroseconds());
+ }
+ current_event_ = event_time;
+
+ // Return true if one capture period's worth of tokens are in the bucket.
+ return token_bucket_ >= capture_period_;
+}
+
+void SmoothEventSampler::RecordSample() {
+ token_bucket_ -= capture_period_;
+ TRACE_COUNTER1("mirroring",
+ "MirroringTokenBucketUsec", token_bucket_.InMicroseconds());
+
+ bool was_paused = overdue_sample_count_ == redundant_capture_goal_;
+ if (HasUnrecordedEvent()) {
+ last_sample_ = current_event_;
+ overdue_sample_count_ = 0;
+ } else {
+ ++overdue_sample_count_;
+ }
+ bool is_paused = overdue_sample_count_ == redundant_capture_goal_;
+
+ LOG_IF(INFO, !was_paused && is_paused)
+ << "Tab content unchanged for " << redundant_capture_goal_
+ << " frames; capture will halt until content changes.";
+ LOG_IF(INFO, was_paused && !is_paused)
+ << "Content changed; capture will resume.";
+}
+
+bool SmoothEventSampler::IsOverdueForSamplingAt(base::Time event_time) const {
+ DCHECK(!event_time.is_null());
+
+ // If we don't get events on compositor updates on this platform, then we
+ // don't reliably know whether we're dirty.
+ if (events_are_reliable_) {
+ if (!HasUnrecordedEvent() &&
+ overdue_sample_count_ >= redundant_capture_goal_) {
+ return false; // Not dirty.
+ }
+ }
+
+ // If we're dirty but not yet old, then we've recently gotten updates, so we
+ // won't request a sample just yet.
+ base::TimeDelta dirty_interval = event_time - last_sample_;
+ if (dirty_interval < capture_period_ * 4)
+ return false;
+ else
+ return true;
+}
+
+bool SmoothEventSampler::HasUnrecordedEvent() const {
+ return !current_event_.is_null() && current_event_ != last_sample_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/video_capture_oracle.h b/chromium/content/browser/renderer_host/media/video_capture_oracle.h
new file mode 100644
index 00000000000..739b56971a5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_oracle.h
@@ -0,0 +1,107 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_ORACLE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_ORACLE_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Filters a sequence of events to achieve a target frequency.
+class CONTENT_EXPORT SmoothEventSampler {
+ public:
+ explicit SmoothEventSampler(base::TimeDelta capture_period,
+ bool events_are_reliable,
+ int redundant_capture_goal);
+
+ // Add a new event to the event history, and return whether it ought to be
+ // sampled based on the desired |capture_period|. The event is not recorded as
+ // a sample until RecordSample() is called.
+ bool AddEventAndConsiderSampling(base::Time event_time);
+
+ // Operates on the last event added by AddEventAndConsiderSampling(), marking
+ // it as sampled. After this point we are current in the stream of events, as
+ // we have sampled the most recent event.
+ void RecordSample();
+
+ // Returns true if, at time |event_time|, sampling should occur because too
+ // much time will have passed relative to the last event and/or sample.
+ bool IsOverdueForSamplingAt(base::Time event_time) const;
+
+ // Returns true if AddEventAndConsiderSampling() has been called since the
+ // last call to RecordSample().
+ bool HasUnrecordedEvent() const;
+
+ private:
+ const bool events_are_reliable_;
+ const base::TimeDelta capture_period_;
+ const int redundant_capture_goal_;
+ const base::TimeDelta token_bucket_capacity_;
+
+ base::Time current_event_;
+ base::Time last_sample_;
+ int overdue_sample_count_;
+ base::TimeDelta token_bucket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SmoothEventSampler);
+};
+
+// VideoCaptureOracle manages the producer-side throttling of captured frames
+// from a video capture device. It is informed of every update by the device;
+// this empowers it to look into the future and decide if a particular frame
+// ought to be captured in order to achieve its target frame rate.
+class CONTENT_EXPORT VideoCaptureOracle {
+ public:
+ enum Event {
+ kTimerPoll,
+ kCompositorUpdate,
+ kSoftwarePaint,
+ };
+
+ VideoCaptureOracle(base::TimeDelta capture_period,
+ bool events_are_reliable);
+ virtual ~VideoCaptureOracle() {}
+
+ // Record an event of type |event|, and decide whether the caller should do a
+ // frame capture immediately. Decisions of the oracle are final: the caller
+ // must do what it is told.
+ bool ObserveEventAndDecideCapture(
+ Event event,
+ base::Time event_time);
+
+ // Record the start of a capture. Returns a frame_number to be used with
+ // CompleteCapture().
+ int RecordCapture();
+
+ // Record the completion of a capture. Returns true iff the captured frame
+ // should be delivered.
+ bool CompleteCapture(int frame_number, base::Time timestamp);
+
+ base::TimeDelta capture_period() const { return capture_period_; }
+
+ private:
+
+ // Time between frames.
+ const base::TimeDelta capture_period_;
+
+ // Incremented every time a paint or update event occurs.
+ int frame_number_;
+
+ // Stores the frame number from the last delivered frame.
+ int last_delivered_frame_number_;
+
+ // Stores the timestamp of the last delivered frame.
+ base::Time last_delivered_frame_timestamp_;
+
+ // Tracks present/paint history.
+ SmoothEventSampler sampler_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_ORACLE_H_
diff --git a/chromium/content/browser/renderer_host/media/video_capture_oracle_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_oracle_unittest.cc
new file mode 100644
index 00000000000..40c1826d957
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/video_capture_oracle_unittest.cc
@@ -0,0 +1,478 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/video_capture_oracle.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+void SteadyStateSampleAndAdvance(base::TimeDelta vsync,
+ SmoothEventSampler* sampler, base::Time* t) {
+ ASSERT_TRUE(sampler->AddEventAndConsiderSampling(*t));
+ ASSERT_TRUE(sampler->HasUnrecordedEvent());
+ sampler->RecordSample();
+ ASSERT_FALSE(sampler->HasUnrecordedEvent());
+ ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t));
+ *t += vsync;
+ ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t));
+}
+
+void SteadyStateNoSampleAndAdvance(base::TimeDelta vsync,
+ SmoothEventSampler* sampler, base::Time* t) {
+ ASSERT_FALSE(sampler->AddEventAndConsiderSampling(*t));
+ ASSERT_TRUE(sampler->HasUnrecordedEvent());
+ ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t));
+ *t += vsync;
+ ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t));
+}
+
+void TestRedundantCaptureStrategy(base::TimeDelta capture_period,
+ int redundant_capture_goal,
+ SmoothEventSampler* sampler, base::Time* t) {
+ // Before any events have been considered, we're overdue for sampling.
+ ASSERT_TRUE(sampler->IsOverdueForSamplingAt(*t));
+
+ // Consider the first event. We want to sample that.
+ ASSERT_FALSE(sampler->HasUnrecordedEvent());
+ ASSERT_TRUE(sampler->AddEventAndConsiderSampling(*t));
+ ASSERT_TRUE(sampler->HasUnrecordedEvent());
+ sampler->RecordSample();
+ ASSERT_FALSE(sampler->HasUnrecordedEvent());
+
+ // After more than one capture period has passed without considering an event,
+ // we should repeatedly be overdue for sampling. However, once the redundant
+ // capture goal is achieved, we should no longer be overdue for sampling.
+ *t += capture_period * 4;
+ for (int i = 0; i < redundant_capture_goal; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_FALSE(sampler->HasUnrecordedEvent());
+ ASSERT_TRUE(sampler->IsOverdueForSamplingAt(*t))
+ << "Should sample until redundant capture goal is hit";
+ sampler->RecordSample();
+ *t += capture_period; // Timer fires once every capture period.
+ }
+ ASSERT_FALSE(sampler->IsOverdueForSamplingAt(*t))
+ << "Should not be overdue once redundant capture goal achieved.";
+}
+
+// 60Hz sampled at 30Hz should produce 30Hz. In addition, this test contains
+// much more comprehensive before/after/edge-case scenarios than the others.
+TEST(SmoothEventSamplerTest, Sample60HertzAt30Hertz) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const int redundant_capture_goal = 200;
+ const base::TimeDelta vsync = base::TimeDelta::FromSeconds(1) / 60;
+
+ SmoothEventSampler sampler(capture_period, true, redundant_capture_goal);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ TestRedundantCaptureStrategy(capture_period, redundant_capture_goal,
+ &sampler, &t);
+
+ // Steady state, we should capture every other vsync, indefinitely.
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+
+ // Now pretend we're limited by backpressure in the pipeline. In this scenario
+ // case we are adding events but not sampling them.
+ for (int i = 0; i < 20; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_EQ(i >= 7, sampler.IsOverdueForSamplingAt(t));
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ ASSERT_TRUE(sampler.HasUnrecordedEvent());
+ t += vsync;
+ }
+
+ // Now suppose we can sample again. We should be back in the steady state,
+ // but at a different phase.
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t));
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+}
+
+// 50Hz sampled at 30Hz should produce a sequence where some frames are skipped.
+TEST(SmoothEventSamplerTest, Sample50HertzAt30Hertz) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const int redundant_capture_goal = 2;
+ const base::TimeDelta vsync = base::TimeDelta::FromSeconds(1) / 50;
+
+ SmoothEventSampler sampler(capture_period, true, redundant_capture_goal);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ TestRedundantCaptureStrategy(capture_period, redundant_capture_goal,
+ &sampler, &t);
+
+ // Steady state, we should capture 1st, 2nd and 4th frames out of every five
+ // frames, indefinitely.
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+
+ // Now pretend we're limited by backpressure in the pipeline. In this scenario
+ // case we are adding events but not sampling them.
+ for (int i = 0; i < 12; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_EQ(i >= 5, sampler.IsOverdueForSamplingAt(t));
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ t += vsync;
+ }
+
+ // Now suppose we can sample again. We should be back in the steady state
+ // again.
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t));
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+}
+
+// 75Hz sampled at 30Hz should produce a sequence where some frames are skipped.
+TEST(SmoothEventSamplerTest, Sample75HertzAt30Hertz) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const int redundant_capture_goal = 32;
+ const base::TimeDelta vsync = base::TimeDelta::FromSeconds(1) / 75;
+
+ SmoothEventSampler sampler(capture_period, true, redundant_capture_goal);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ TestRedundantCaptureStrategy(capture_period, redundant_capture_goal,
+ &sampler, &t);
+
+ // Steady state, we should capture 1st and 3rd frames out of every five
+ // frames, indefinitely.
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+
+ // Now pretend we're limited by backpressure in the pipeline. In this scenario
+ // case we are adding events but not sampling them.
+ for (int i = 0; i < 20; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_EQ(i >= 8, sampler.IsOverdueForSamplingAt(t));
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ t += vsync;
+ }
+
+ // Now suppose we can sample again. We capture the next frame, and not the one
+ // after that, and then we're back in the steady state again.
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ SteadyStateNoSampleAndAdvance(vsync, &sampler, &t);
+ }
+}
+
+// 30Hz sampled at 30Hz should produce 30Hz.
+TEST(SmoothEventSamplerTest, Sample30HertzAt30Hertz) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const int redundant_capture_goal = 1;
+ const base::TimeDelta vsync = base::TimeDelta::FromSeconds(1) / 30;
+
+ SmoothEventSampler sampler(capture_period, true, redundant_capture_goal);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ TestRedundantCaptureStrategy(capture_period, redundant_capture_goal,
+ &sampler, &t);
+
+ // Steady state, we should capture every vsync, indefinitely.
+ for (int i = 0; i < 200; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ }
+
+ // Now pretend we're limited by backpressure in the pipeline. In this scenario
+ // case we are adding events but not sampling them.
+ for (int i = 0; i < 7; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_EQ(i >= 3, sampler.IsOverdueForSamplingAt(t));
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ t += vsync;
+ }
+
+ // Now suppose we can sample again. We should be back in the steady state.
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t));
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ }
+}
+
+// 24Hz sampled at 30Hz should produce 24Hz.
+TEST(SmoothEventSamplerTest, Sample24HertzAt30Hertz) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const int redundant_capture_goal = 333;
+ const base::TimeDelta vsync = base::TimeDelta::FromSeconds(1) / 24;
+
+ SmoothEventSampler sampler(capture_period, true, redundant_capture_goal);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ TestRedundantCaptureStrategy(capture_period, redundant_capture_goal,
+ &sampler, &t);
+
+ // Steady state, we should capture every vsync, indefinitely.
+ for (int i = 0; i < 200; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ }
+
+ // Now pretend we're limited by backpressure in the pipeline. In this scenario
+ // case we are adding events but not sampling them.
+ for (int i = 0; i < 7; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ ASSERT_EQ(i >= 3, sampler.IsOverdueForSamplingAt(t));
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ t += vsync;
+ }
+
+ // Now suppose we can sample again. We should be back in the steady state.
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t));
+ for (int i = 0; i < 100; i++) {
+ SCOPED_TRACE(base::StringPrintf("Iteration %d", i));
+ SteadyStateSampleAndAdvance(vsync, &sampler, &t);
+ }
+}
+
+TEST(SmoothEventSamplerTest, DoubleDrawAtOneTimeStillDirties) {
+ const base::TimeDelta capture_period = base::TimeDelta::FromSeconds(1) / 30;
+ const base::TimeDelta overdue_period = base::TimeDelta::FromSeconds(1);
+
+ SmoothEventSampler sampler(capture_period, true, 1);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ sampler.RecordSample();
+ ASSERT_FALSE(sampler.IsOverdueForSamplingAt(t))
+ << "Sampled last event; should not be dirty.";
+ t += overdue_period;
+
+ // Now simulate 2 events with the same clock value.
+ ASSERT_TRUE(sampler.AddEventAndConsiderSampling(t));
+ sampler.RecordSample();
+ ASSERT_FALSE(sampler.AddEventAndConsiderSampling(t))
+ << "Two events at same time -- expected second not to be sampled.";
+ ASSERT_TRUE(sampler.IsOverdueForSamplingAt(t + overdue_period))
+ << "Second event should dirty the capture state.";
+ sampler.RecordSample();
+ ASSERT_FALSE(sampler.IsOverdueForSamplingAt(t + overdue_period));
+}
+
+TEST(SmoothEventSamplerTest, FallbackToPollingIfUpdatesUnreliable) {
+ const base::TimeDelta timer_interval = base::TimeDelta::FromSeconds(1) / 30;
+
+ SmoothEventSampler should_not_poll(timer_interval, true, 1);
+ SmoothEventSampler should_poll(timer_interval, false, 1);
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+
+ // Do one round of the "happy case" where an event was received and
+ // RecordSample() was called by the client.
+ ASSERT_TRUE(should_not_poll.AddEventAndConsiderSampling(t));
+ ASSERT_TRUE(should_poll.AddEventAndConsiderSampling(t));
+ should_not_poll.RecordSample();
+ should_poll.RecordSample();
+
+ // One time period ahead, neither sampler says we're overdue.
+ for (int i = 0; i < 3; i++) {
+ t += timer_interval;
+ ASSERT_FALSE(should_not_poll.IsOverdueForSamplingAt(t))
+ << "Sampled last event; should not be dirty.";
+ ASSERT_FALSE(should_poll.IsOverdueForSamplingAt(t))
+ << "Dirty interval has not elapsed yet.";
+ }
+
+ // Next time period ahead, both samplers say we're overdue. The non-polling
+ // sampler is returning true here because it has been configured to allow one
+ // redundant capture.
+ t += timer_interval;
+ ASSERT_TRUE(should_not_poll.IsOverdueForSamplingAt(t))
+ << "Sampled last event; is dirty one time only to meet redundancy goal.";
+ ASSERT_TRUE(should_poll.IsOverdueForSamplingAt(t))
+ << "If updates are unreliable, must fall back to polling when idle.";
+ should_not_poll.RecordSample();
+ should_poll.RecordSample();
+
+ // Forever more, the non-polling sampler returns false while the polling one
+ // returns true.
+ for (int i = 0; i < 100; ++i) {
+ t += timer_interval;
+ ASSERT_FALSE(should_not_poll.IsOverdueForSamplingAt(t))
+ << "Sampled last event; should not be dirty.";
+ ASSERT_TRUE(should_poll.IsOverdueForSamplingAt(t))
+ << "If updates are unreliable, must fall back to polling when idle.";
+ should_poll.RecordSample();
+ }
+ t += timer_interval / 3;
+ ASSERT_FALSE(should_not_poll.IsOverdueForSamplingAt(t))
+ << "Sampled last event; should not be dirty.";
+ ASSERT_TRUE(should_poll.IsOverdueForSamplingAt(t))
+ << "If updates are unreliable, must fall back to polling when idle.";
+ should_poll.RecordSample();
+}
+
+struct DataPoint {
+ bool should_capture;
+ double increment_ms;
+};
+
+void ReplayCheckingSamplerDecisions(const DataPoint* data_points,
+ size_t num_data_points,
+ SmoothEventSampler* sampler) {
+ base::Time t;
+ ASSERT_TRUE(base::Time::FromString("Sat, 23 Mar 2013 1:21:08 GMT", &t));
+ for (size_t i = 0; i < num_data_points; ++i) {
+ t += base::TimeDelta::FromMicroseconds(
+ static_cast<int64>(data_points[i].increment_ms * 1000));
+ ASSERT_EQ(data_points[i].should_capture,
+ sampler->AddEventAndConsiderSampling(t))
+ << "at data_points[" << i << ']';
+ if (data_points[i].should_capture)
+ sampler->RecordSample();
+ }
+}
+
+TEST(SmoothEventSamplerTest, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz) {
+ // Actual capturing of timing data: Initial instability as a 24 FPS video was
+ // started from a still screen, then clearly followed by steady-state.
+ static const DataPoint data_points[] = {
+ { true, 1437.93 }, { true, 150.484 }, { true, 217.362 }, { true, 50.161 },
+ { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 66.88 },
+ { true, 50.161 }, { false, 0 }, { false, 0 }, { true, 50.16 },
+ { true, 33.441 }, { true, 16.72 }, { false, 16.72 }, { true, 117.041 },
+ { true, 16.72 }, { false, 16.72 }, { true, 50.161 }, { true, 50.16 },
+ { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 16.72 },
+ { false, 0 }, { true, 50.161 }, { false, 0 }, { true, 33.44 },
+ { true, 16.72 }, { false, 16.721 }, { true, 66.881 }, { false, 0 },
+ { true, 33.441 }, { true, 16.72 }, { true, 50.16 }, { true, 16.72 },
+ { false, 16.721 }, { true, 50.161 }, { true, 50.16 }, { false, 0 },
+ { true, 33.441 }, { true, 50.337 }, { true, 50.183 }, { true, 16.722 },
+ { true, 50.161 }, { true, 33.441 }, { true, 50.16 }, { true, 33.441 },
+ { true, 50.16 }, { true, 33.441 }, { true, 50.16 }, { true, 33.44 },
+ { true, 50.161 }, { true, 50.16 }, { true, 33.44 }, { true, 33.441 },
+ { true, 50.16 }, { true, 50.161 }, { true, 33.44 }, { true, 33.441 },
+ { true, 50.16 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
+ { true, 50.161 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
+ { true, 83.601 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }
+ };
+
+ SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
+ ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler);
+}
+
+TEST(SmoothEventSamplerTest, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz) {
+ // Actual capturing of timing data: Initial instability as a 30 FPS video was
+ // started from a still screen, then followed by steady-state. Drawing
+ // framerate from the video rendering was a bit volatile, but averaged 30 FPS.
+ static const DataPoint data_points[] = {
+ { true, 2407.69 }, { true, 16.733 }, { true, 217.362 }, { true, 33.441 },
+ { true, 33.44 }, { true, 33.44 }, { true, 33.441 }, { true, 33.44 },
+ { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 },
+ { true, 16.721 }, { true, 33.44 }, { false, 0 }, { true, 50.161 },
+ { true, 50.16 }, { false, 0 }, { true, 50.161 }, { true, 33.44 },
+ { true, 16.72 }, { false, 0 }, { false, 16.72 }, { true, 66.881 },
+ { false, 0 }, { true, 33.44 }, { true, 16.72 }, { true, 50.161 },
+ { false, 0 }, { true, 33.538 }, { true, 33.526 }, { true, 33.447 },
+ { true, 33.445 }, { true, 33.441 }, { true, 16.721 }, { true, 33.44 },
+ { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, { true, 33.44 },
+ { true, 33.441 }, { true, 33.44 }, { false, 0 }, { false, 16.72 },
+ { true, 66.881 }, { true, 16.72 }, { false, 16.72 }, { true, 50.16 },
+ { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 33.44 },
+ { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { false, 0 },
+ { true, 33.44 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
+ { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 66.88 },
+ { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
+ { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
+ { true, 16.72 }, { true, 50.161 }, { false, 0 }, { true, 50.16 },
+ { false, 0.001 }, { true, 16.721 }, { true, 66.88 }, { true, 33.44 },
+ { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
+ { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 66.881 },
+ { true, 33.44 }, { true, 16.72 }, { true, 33.441 }, { false, 16.72 },
+ { true, 66.88 }, { true, 16.721 }, { true, 50.16 }, { true, 33.44 },
+ { true, 16.72 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 }
+ };
+
+ SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
+ ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler);
+}
+
+TEST(SmoothEventSamplerTest, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz) {
+ // Actual capturing of timing data: WebGL Acquarium demo
+ // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran
+ // between 55-60 FPS in the steady-state.
+ static const DataPoint data_points[] = {
+ { true, 16.72 }, { true, 16.72 }, { true, 4163.29 }, { true, 50.193 },
+ { true, 117.041 }, { true, 50.161 }, { true, 50.16 }, { true, 33.441 },
+ { true, 50.16 }, { true, 33.44 }, { false, 0 }, { false, 0 },
+ { true, 50.161 }, { true, 83.601 }, { true, 50.16 }, { true, 16.72 },
+ { true, 33.441 }, { false, 16.72 }, { true, 50.16 }, { true, 16.72 },
+ { false, 0.001 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
+ { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
+ { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
+ { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
+ { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
+ { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
+ { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 33.44 },
+ { false, 0 }, { true, 16.721 }, { true, 50.161 }, { false, 0 },
+ { true, 33.44 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
+ { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
+ { true, 50.16 }, { false, 0 }, { true, 16.721 }, { true, 33.44 },
+ { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
+ { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
+ { false, 0 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
+ { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
+ { true, 33.44 }, { false, 0 }, { true, 33.44 }, { true, 33.441 },
+ { false, 0 }, { true, 33.44 }, { true, 33.441 }, { false, 0 },
+ { true, 33.44 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
+ { true, 16.721 }, { true, 50.161 }, { false, 0 }, { true, 16.72 },
+ { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 33.44 },
+ { true, 33.44 }, { false, 0 }, { true, 33.441 }, { false, 16.72 },
+ { true, 16.72 }, { true, 50.16 }, { false, 0 }, { true, 16.72 },
+ { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
+ { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 },
+ { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 },
+ { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 }
+ };
+
+ SmoothEventSampler sampler(base::TimeDelta::FromSeconds(1) / 30, true, 3);
+ ReplayCheckingSamplerDecisions(data_points, arraysize(data_points), &sampler);
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.cc b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.cc
new file mode 100644
index 00000000000..8336eb2c195
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.cc
@@ -0,0 +1,349 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/web_contents_audio_input_stream.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "content/browser/renderer_host/media/web_contents_tracker.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/virtual_audio_input_stream.h"
+#include "media/audio/virtual_audio_output_stream.h"
+
+namespace content {
+
+class WebContentsAudioInputStream::Impl
+ : public base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>,
+ public AudioMirroringManager::MirroringDestination {
+ public:
+ // Takes ownership of |mixer_stream|. The rest outlive this instance.
+ Impl(int render_process_id, int render_view_id,
+ AudioMirroringManager* mirroring_manager,
+ const scoped_refptr<WebContentsTracker>& tracker,
+ media::VirtualAudioInputStream* mixer_stream);
+
+ // Open underlying VirtualAudioInputStream and start tracker.
+ bool Open();
+
+ // Start the underlying VirtualAudioInputStream and instruct
+ // AudioMirroringManager to begin a mirroring session.
+ void Start(AudioInputCallback* callback);
+
+ // Stop the underlying VirtualAudioInputStream and instruct
+ // AudioMirroringManager to shutdown a mirroring session.
+ void Stop();
+
+ // Close the underlying VirtualAudioInputStream and stop the tracker.
+ void Close();
+
+ // Accessor to underlying VirtualAudioInputStream.
+ media::VirtualAudioInputStream* mixer_stream() const {
+ return mixer_stream_.get();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>;
+
+ enum State {
+ CONSTRUCTED,
+ OPENED,
+ MIRRORING,
+ CLOSED
+ };
+
+ virtual ~Impl();
+
+ // Returns true if the mirroring target has been permanently lost.
+ bool IsTargetLost() const;
+
+ // Notifies the consumer callback that the stream is now dead.
+ void ReportError();
+
+ // Start/Stop mirroring by posting a call to AudioMirroringManager on the IO
+ // BrowserThread.
+ void StartMirroring();
+ void StopMirroring();
+
+ // AudioMirroringManager::MirroringDestination implementation
+ virtual media::AudioOutputStream* AddInput(
+ const media::AudioParameters& params) OVERRIDE;
+
+ // Callback which is run when |stream| is closed. Deletes |stream|.
+ void ReleaseInput(media::VirtualAudioOutputStream* stream);
+
+ // Called by WebContentsTracker when the target of the audio mirroring has
+ // changed.
+ void OnTargetChanged(int render_process_id, int render_view_id);
+
+ // Injected dependencies.
+ AudioMirroringManager* const mirroring_manager_;
+ const scoped_refptr<WebContentsTracker> tracker_;
+ // The AudioInputStream implementation that handles the audio conversion and
+ // mixing details.
+ const scoped_ptr<media::VirtualAudioInputStream> mixer_stream_;
+
+ State state_;
+
+ // Current audio mirroring target.
+ int target_render_process_id_;
+ int target_render_view_id_;
+
+ // Current callback used to consume the resulting mixed audio data.
+ AudioInputCallback* callback_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(Impl);
+};
+
+WebContentsAudioInputStream::Impl::Impl(
+ int render_process_id, int render_view_id,
+ AudioMirroringManager* mirroring_manager,
+ const scoped_refptr<WebContentsTracker>& tracker,
+ media::VirtualAudioInputStream* mixer_stream)
+ : mirroring_manager_(mirroring_manager),
+ tracker_(tracker), mixer_stream_(mixer_stream), state_(CONSTRUCTED),
+ target_render_process_id_(render_process_id),
+ target_render_view_id_(render_view_id),
+ callback_(NULL) {
+ DCHECK(mirroring_manager_);
+ DCHECK(tracker_.get());
+ DCHECK(mixer_stream_.get());
+
+ // WAIS::Impl can be constructed on any thread, but will DCHECK that all
+ // its methods from here on are called from the same thread.
+ thread_checker_.DetachFromThread();
+}
+
+WebContentsAudioInputStream::Impl::~Impl() {
+ DCHECK(state_ == CONSTRUCTED || state_ == CLOSED);
+}
+
+bool WebContentsAudioInputStream::Impl::Open() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK_EQ(CONSTRUCTED, state_) << "Illegal to Open more than once.";
+
+ if (!mixer_stream_->Open())
+ return false;
+
+ state_ = OPENED;
+
+ tracker_->Start(
+ target_render_process_id_, target_render_view_id_,
+ base::Bind(&Impl::OnTargetChanged, this));
+
+ return true;
+}
+
+void WebContentsAudioInputStream::Impl::Start(AudioInputCallback* callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(callback);
+
+ if (state_ != OPENED)
+ return;
+
+ callback_ = callback;
+ if (IsTargetLost()) {
+ ReportError();
+ callback_ = NULL;
+ return;
+ }
+
+ state_ = MIRRORING;
+ mixer_stream_->Start(callback);
+
+ StartMirroring();
+}
+
+void WebContentsAudioInputStream::Impl::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != MIRRORING)
+ return;
+
+ state_ = OPENED;
+
+ mixer_stream_->Stop();
+ callback_ = NULL;
+
+ if (!IsTargetLost())
+ StopMirroring();
+}
+
+void WebContentsAudioInputStream::Impl::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ Stop();
+
+ if (state_ == OPENED) {
+ state_ = CONSTRUCTED;
+ tracker_->Stop();
+ mixer_stream_->Close();
+ }
+
+ DCHECK_EQ(CONSTRUCTED, state_);
+ state_ = CLOSED;
+}
+
+bool WebContentsAudioInputStream::Impl::IsTargetLost() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ return target_render_process_id_ <= 0 || target_render_view_id_ <= 0;
+}
+
+void WebContentsAudioInputStream::Impl::ReportError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // TODO(miu): Need clean-up of AudioInputCallback interface in a future
+ // change, since its only implementation ignores the first argument entirely
+ callback_->OnError(NULL);
+}
+
+void WebContentsAudioInputStream::Impl::StartMirroring() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioMirroringManager::StartMirroring,
+ base::Unretained(mirroring_manager_),
+ target_render_process_id_, target_render_view_id_,
+ make_scoped_refptr(this)));
+}
+
+void WebContentsAudioInputStream::Impl::StopMirroring() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&AudioMirroringManager::StopMirroring,
+ base::Unretained(mirroring_manager_),
+ target_render_process_id_, target_render_view_id_,
+ make_scoped_refptr(this)));
+}
+
+media::AudioOutputStream* WebContentsAudioInputStream::Impl::AddInput(
+ const media::AudioParameters& params) {
+ // Note: The closure created here holds a reference to "this," which will
+ // guarantee the VirtualAudioInputStream (mixer_stream_) outlives the
+ // VirtualAudioOutputStream.
+ return new media::VirtualAudioOutputStream(
+ params,
+ mixer_stream_.get(),
+ base::Bind(&Impl::ReleaseInput, this));
+}
+
+void WebContentsAudioInputStream::Impl::ReleaseInput(
+ media::VirtualAudioOutputStream* stream) {
+ delete stream;
+}
+
+void WebContentsAudioInputStream::Impl::OnTargetChanged(int render_process_id,
+ int render_view_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (target_render_process_id_ == render_process_id &&
+ target_render_view_id_ == render_view_id) {
+ return;
+ }
+
+ DVLOG(1) << "Target RenderView has changed from "
+ << target_render_process_id_ << ':' << target_render_view_id_
+ << " to " << render_process_id << ':' << render_view_id;
+
+ if (state_ == MIRRORING)
+ StopMirroring();
+
+ target_render_process_id_ = render_process_id;
+ target_render_view_id_ = render_view_id;
+
+ if (state_ == MIRRORING) {
+ if (IsTargetLost()) {
+ ReportError();
+ Stop();
+ } else {
+ StartMirroring();
+ }
+ }
+}
+
+// static
+WebContentsAudioInputStream* WebContentsAudioInputStream::Create(
+ const std::string& device_id,
+ const media::AudioParameters& params,
+ const scoped_refptr<base::MessageLoopProxy>& worker_loop,
+ AudioMirroringManager* audio_mirroring_manager) {
+ int render_process_id;
+ int render_view_id;
+ if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
+ device_id, &render_process_id, &render_view_id)) {
+ return NULL;
+ }
+
+ return new WebContentsAudioInputStream(
+ render_process_id, render_view_id,
+ audio_mirroring_manager,
+ new WebContentsTracker(),
+ new media::VirtualAudioInputStream(
+ params, worker_loop,
+ media::VirtualAudioInputStream::AfterCloseCallback()));
+}
+
+WebContentsAudioInputStream::WebContentsAudioInputStream(
+ int render_process_id, int render_view_id,
+ AudioMirroringManager* mirroring_manager,
+ const scoped_refptr<WebContentsTracker>& tracker,
+ media::VirtualAudioInputStream* mixer_stream)
+ : impl_(new Impl(render_process_id, render_view_id,
+ mirroring_manager, tracker, mixer_stream)) {}
+
+WebContentsAudioInputStream::~WebContentsAudioInputStream() {}
+
+bool WebContentsAudioInputStream::Open() {
+ return impl_->Open();
+}
+
+void WebContentsAudioInputStream::Start(AudioInputCallback* callback) {
+ impl_->Start(callback);
+}
+
+void WebContentsAudioInputStream::Stop() {
+ impl_->Stop();
+}
+
+void WebContentsAudioInputStream::Close() {
+ impl_->Close();
+ delete this;
+}
+
+double WebContentsAudioInputStream::GetMaxVolume() {
+ return impl_->mixer_stream()->GetMaxVolume();
+}
+
+void WebContentsAudioInputStream::SetVolume(double volume) {
+ impl_->mixer_stream()->SetVolume(volume);
+}
+
+double WebContentsAudioInputStream::GetVolume() {
+ return impl_->mixer_stream()->GetVolume();
+}
+
+void WebContentsAudioInputStream::SetAutomaticGainControl(bool enabled) {
+ impl_->mixer_stream()->SetAutomaticGainControl(enabled);
+}
+
+bool WebContentsAudioInputStream::GetAutomaticGainControl() {
+ return impl_->mixer_stream()->GetAutomaticGainControl();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.h b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.h
new file mode 100644
index 00000000000..486547ecaa2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream.h
@@ -0,0 +1,92 @@
+// Copyright (c) 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.
+//
+// An AudioInputStream which provides a loop-back of all audio output generated
+// by the RenderView associated with a WebContents instance. The single stream
+// of data is produced by format-converting and mixing all audio output from a
+// RenderView. In other words, WebContentsAudioInputStream provides tab-level
+// audio mirroring.
+//
+// The implementation observes a WebContents instance (which represents a
+// browser tab) so that it can track the replacement of RenderViews due to
+// navigation, crash/reload, etc. events; and take appropriate actions to
+// provide a seamless, uninterrupted mirroring experience.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_AUDIO_INPUT_STREAM_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_AUDIO_INPUT_STREAM_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "media/audio/audio_io.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace media {
+class AudioParameters;
+class VirtualAudioInputStream;
+}
+
+namespace content {
+
+class AudioMirroringManager;
+class WebContentsTracker;
+
+class CONTENT_EXPORT WebContentsAudioInputStream
+ : NON_EXPORTED_BASE(public media::AudioInputStream) {
+ public:
+ // media::AudioInputStream implementation
+ virtual bool Open() OVERRIDE;
+ virtual void Start(AudioInputCallback* callback) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual double GetMaxVolume() OVERRIDE;
+ virtual void SetVolume(double volume) OVERRIDE;
+ virtual double GetVolume() OVERRIDE;
+ virtual void SetAutomaticGainControl(bool enabled) OVERRIDE;
+ virtual bool GetAutomaticGainControl() OVERRIDE;
+
+ // Create a new audio mirroring session, or return NULL on error. |device_id|
+ // should be in the format accepted by
+ // WebContentsCaptureUtil::ExtractTabCaptureTarget(). The caller must
+ // guarantee Close() is called on the returned object so that it may
+ // self-destruct.
+ // |worker_loop| is the loop on which AudioInputCallback methods are called
+ // and may or may not be the single thread that invokes the AudioInputStream
+ // methods.
+ static WebContentsAudioInputStream* Create(
+ const std::string& device_id,
+ const media::AudioParameters& params,
+ const scoped_refptr<base::MessageLoopProxy>& worker_loop,
+ AudioMirroringManager* audio_mirroring_manager);
+
+ private:
+ friend class WebContentsAudioInputStreamTest;
+
+ // Maintain most state and functionality in an internal ref-counted
+ // implementation class. This object must outlive a call to Close(), until
+ // the shutdown tasks running on other threads complete: The
+ // AudioMirroringManager on the IO thread, the WebContentsTracker on the UI
+ // thread, and the VirtualAudioOuputStreams on the audio thread.
+ class Impl;
+
+ WebContentsAudioInputStream(
+ int render_process_id, int render_view_id,
+ AudioMirroringManager* mirroring_manager,
+ const scoped_refptr<WebContentsTracker>& tracker,
+ media::VirtualAudioInputStream* mixer_stream);
+
+ virtual ~WebContentsAudioInputStream();
+
+ scoped_refptr<Impl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsAudioInputStream);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_AUDIO_INPUT_STREAM_H_
diff --git a/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc
new file mode 100644
index 00000000000..3ce336d39c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc
@@ -0,0 +1,513 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/web_contents_audio_input_stream.h"
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/web_contents_tracker.h"
+#include "media/audio/simple_sources.h"
+#include "media/audio/virtual_audio_input_stream.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NotNull;
+using ::testing::SaveArg;
+using ::testing::WithArgs;
+
+using media::AudioInputStream;
+using media::AudioOutputStream;
+using media::AudioParameters;
+using media::SineWaveAudioSource;
+using media::VirtualAudioInputStream;
+using media::VirtualAudioOutputStream;
+
+namespace content {
+
+namespace {
+
+const int kRenderProcessId = 123;
+const int kRenderViewId = 456;
+const int kAnotherRenderProcessId = 789;
+const int kAnotherRenderViewId = 1;
+
+const AudioParameters& TestAudioParameters() {
+ static const AudioParameters params(
+ AudioParameters::AUDIO_FAKE,
+ media::CHANNEL_LAYOUT_STEREO,
+ AudioParameters::kAudioCDSampleRate, 16,
+ AudioParameters::kAudioCDSampleRate / 100);
+ return params;
+}
+
+class MockAudioMirroringManager : public AudioMirroringManager {
+ public:
+ MockAudioMirroringManager() : AudioMirroringManager() {}
+ virtual ~MockAudioMirroringManager() {}
+
+ MOCK_METHOD3(StartMirroring,
+ void(int render_process_id, int render_view_id,
+ MirroringDestination* destination));
+ MOCK_METHOD3(StopMirroring,
+ void(int render_process_id, int render_view_id,
+ MirroringDestination* destination));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager);
+};
+
+class MockWebContentsTracker : public WebContentsTracker {
+ public:
+ MockWebContentsTracker() : WebContentsTracker() {}
+
+ MOCK_METHOD3(Start,
+ void(int render_process_id, int render_view_id,
+ const ChangeCallback& callback));
+ MOCK_METHOD0(Stop, void());
+
+ private:
+ virtual ~MockWebContentsTracker() {}
+
+ DISALLOW_COPY_AND_ASSIGN(MockWebContentsTracker);
+};
+
+// A fully-functional VirtualAudioInputStream, but methods are mocked to allow
+// tests to check how/when they are invoked.
+class MockVirtualAudioInputStream : public VirtualAudioInputStream {
+ public:
+ explicit MockVirtualAudioInputStream(
+ const scoped_refptr<base::MessageLoopProxy>& worker_loop)
+ : VirtualAudioInputStream(TestAudioParameters(), worker_loop,
+ VirtualAudioInputStream::AfterCloseCallback()),
+ real_(TestAudioParameters(), worker_loop,
+ base::Bind(&MockVirtualAudioInputStream::OnRealStreamHasClosed,
+ base::Unretained(this))),
+ real_stream_is_closed_(false) {
+ // Set default actions of mocked methods to delegate to the concrete
+ // implementation.
+ ON_CALL(*this, Open())
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Open));
+ ON_CALL(*this, Start(_))
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Start));
+ ON_CALL(*this, Stop())
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Stop));
+ ON_CALL(*this, Close())
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Close));
+ ON_CALL(*this, GetMaxVolume())
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetMaxVolume));
+ ON_CALL(*this, SetVolume(_))
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::SetVolume));
+ ON_CALL(*this, GetVolume())
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetVolume));
+ ON_CALL(*this, SetAutomaticGainControl(_))
+ .WillByDefault(
+ Invoke(&real_, &VirtualAudioInputStream::SetAutomaticGainControl));
+ ON_CALL(*this, GetAutomaticGainControl())
+ .WillByDefault(
+ Invoke(&real_, &VirtualAudioInputStream::GetAutomaticGainControl));
+ ON_CALL(*this, AddOutputStream(NotNull(), _))
+ .WillByDefault(
+ Invoke(&real_, &VirtualAudioInputStream::AddOutputStream));
+ ON_CALL(*this, RemoveOutputStream(NotNull(), _))
+ .WillByDefault(
+ Invoke(&real_, &VirtualAudioInputStream::RemoveOutputStream));
+ }
+
+ ~MockVirtualAudioInputStream() {
+ DCHECK(real_stream_is_closed_);
+ }
+
+ MOCK_METHOD0(Open, bool());
+ MOCK_METHOD1(Start, void(AudioInputStream::AudioInputCallback*));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD0(Close, void());
+ MOCK_METHOD0(GetMaxVolume, double());
+ MOCK_METHOD1(SetVolume, void(double));
+ MOCK_METHOD0(GetVolume, double());
+ MOCK_METHOD1(SetAutomaticGainControl, void(bool));
+ MOCK_METHOD0(GetAutomaticGainControl, bool());
+ MOCK_METHOD2(AddOutputStream, void(VirtualAudioOutputStream*,
+ const AudioParameters&));
+ MOCK_METHOD2(RemoveOutputStream, void(VirtualAudioOutputStream*,
+ const AudioParameters&));
+
+ private:
+ void OnRealStreamHasClosed(VirtualAudioInputStream* stream) {
+ DCHECK_EQ(&real_, stream);
+ DCHECK(!real_stream_is_closed_);
+ real_stream_is_closed_ = true;
+ }
+
+ VirtualAudioInputStream real_;
+ bool real_stream_is_closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockVirtualAudioInputStream);
+};
+
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MockAudioInputCallback() {}
+
+ MOCK_METHOD5(OnData, void(AudioInputStream* stream, const uint8* src,
+ uint32 size, uint32 hardware_delay_bytes,
+ double volume));
+ MOCK_METHOD1(OnClose, void(AudioInputStream* stream));
+ MOCK_METHOD1(OnError, void(AudioInputStream* stream));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioInputCallback);
+};
+
+} // namespace
+
+class WebContentsAudioInputStreamTest : public testing::Test {
+ public:
+ WebContentsAudioInputStreamTest()
+ : audio_thread_("Audio thread"),
+ io_thread_(BrowserThread::IO),
+ mock_mirroring_manager_(new MockAudioMirroringManager()),
+ mock_tracker_(new MockWebContentsTracker()),
+ mock_vais_(NULL),
+ wcais_(NULL),
+ destination_(NULL),
+ current_render_process_id_(kRenderProcessId),
+ current_render_view_id_(kRenderViewId),
+ on_data_event_(false, false) {
+ audio_thread_.Start();
+ io_thread_.Start();
+ }
+
+ virtual ~WebContentsAudioInputStreamTest() {
+ audio_thread_.Stop();
+ io_thread_.Stop();
+
+ DCHECK(!mock_vais_);
+ DCHECK(!wcais_);
+ EXPECT_FALSE(destination_);
+ DCHECK(streams_.empty());
+ DCHECK(sources_.empty());
+ }
+
+ void Open() {
+ mock_vais_ =
+ new MockVirtualAudioInputStream(audio_thread_.message_loop_proxy());
+ EXPECT_CALL(*mock_vais_, Open());
+ EXPECT_CALL(*mock_vais_, Close()); // At Close() time.
+
+ ASSERT_EQ(kRenderProcessId, current_render_process_id_);
+ ASSERT_EQ(kRenderViewId, current_render_view_id_);
+ EXPECT_CALL(*mock_tracker_.get(), Start(kRenderProcessId, kRenderViewId, _))
+ .WillOnce(DoAll(
+ SaveArg<2>(&change_callback_),
+ WithArgs<0, 1>(Invoke(&change_callback_,
+ &WebContentsTracker::ChangeCallback::Run))));
+ EXPECT_CALL(*mock_tracker_.get(), Stop()); // At Close() time.
+
+ wcais_ = new WebContentsAudioInputStream(
+ current_render_process_id_, current_render_view_id_,
+ mock_mirroring_manager_.get(),
+ mock_tracker_, mock_vais_);
+ wcais_->Open();
+ }
+
+ void Start() {
+ EXPECT_CALL(*mock_vais_, Start(&mock_input_callback_));
+ EXPECT_CALL(*mock_vais_, Stop()); // At Stop() time.
+
+ EXPECT_CALL(*mock_mirroring_manager_,
+ StartMirroring(kRenderProcessId, kRenderViewId, NotNull()))
+ .WillOnce(SaveArg<2>(&destination_))
+ .RetiresOnSaturation();
+ // At Stop() time, or when the mirroring target changes:
+ EXPECT_CALL(*mock_mirroring_manager_,
+ StopMirroring(kRenderProcessId, kRenderViewId, NotNull()))
+ .WillOnce(Assign(
+ &destination_,
+ static_cast<AudioMirroringManager::MirroringDestination*>(NULL)))
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(mock_input_callback_, OnData(NotNull(), NotNull(), _, _, _))
+ .WillRepeatedly(
+ InvokeWithoutArgs(&on_data_event_, &base::WaitableEvent::Signal));
+ EXPECT_CALL(mock_input_callback_, OnClose(_)); // At Stop() time.
+
+ wcais_->Start(&mock_input_callback_);
+
+ // Test plumbing of volume controls and automatic gain controls. Calls to
+ // wcais_ methods should delegate directly to mock_vais_.
+ EXPECT_CALL(*mock_vais_, GetVolume());
+ double volume = wcais_->GetVolume();
+ EXPECT_CALL(*mock_vais_, GetMaxVolume());
+ const double max_volume = wcais_->GetMaxVolume();
+ volume *= 2.0;
+ if (volume < max_volume) {
+ volume = max_volume;
+ }
+ EXPECT_CALL(*mock_vais_, SetVolume(volume));
+ wcais_->SetVolume(volume);
+ EXPECT_CALL(*mock_vais_, GetAutomaticGainControl());
+ bool auto_gain = wcais_->GetAutomaticGainControl();
+ auto_gain = !auto_gain;
+ EXPECT_CALL(*mock_vais_, SetAutomaticGainControl(auto_gain));
+ wcais_->SetAutomaticGainControl(auto_gain);
+ }
+
+ void AddAnotherInput() {
+ // Note: WCAIS posts a task to invoke
+ // MockAudioMirroringManager::StartMirroring() on the IO thread, which
+ // causes our mock to set |destination_|. Block until that has happened.
+ base::WaitableEvent done(false, false);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(
+ &base::WaitableEvent::Signal, base::Unretained(&done)));
+ done.Wait();
+ ASSERT_TRUE(destination_);
+
+ EXPECT_CALL(*mock_vais_, AddOutputStream(NotNull(), _))
+ .RetiresOnSaturation();
+ // Later, when stream is closed:
+ EXPECT_CALL(*mock_vais_, RemoveOutputStream(NotNull(), _))
+ .RetiresOnSaturation();
+
+ const AudioParameters& params = TestAudioParameters();
+ AudioOutputStream* const out = destination_->AddInput(params);
+ ASSERT_TRUE(out);
+ streams_.push_back(out);
+ EXPECT_TRUE(out->Open());
+ SineWaveAudioSource* const source = new SineWaveAudioSource(
+ params.channel_layout(), 200.0, params.sample_rate());
+ sources_.push_back(source);
+ out->Start(source);
+ }
+
+ void RemoveOneInputInFIFOOrder() {
+ ASSERT_FALSE(streams_.empty());
+ AudioOutputStream* const out = streams_.front();
+ streams_.pop_front();
+ out->Stop();
+ out->Close(); // Self-deletes.
+ ASSERT_TRUE(!sources_.empty());
+ delete sources_.front();
+ sources_.pop_front();
+ }
+
+ void ChangeMirroringTarget() {
+ const int next_render_process_id =
+ current_render_process_id_ == kRenderProcessId ?
+ kAnotherRenderProcessId : kRenderProcessId;
+ const int next_render_view_id =
+ current_render_view_id_ == kRenderViewId ?
+ kAnotherRenderViewId : kRenderViewId;
+
+ EXPECT_CALL(*mock_mirroring_manager_,
+ StartMirroring(next_render_process_id, next_render_view_id,
+ NotNull()))
+ .WillOnce(SaveArg<2>(&destination_))
+ .RetiresOnSaturation();
+ // At Stop() time, or when the mirroring target changes:
+ EXPECT_CALL(*mock_mirroring_manager_,
+ StopMirroring(next_render_process_id, next_render_view_id,
+ NotNull()))
+ .WillOnce(Assign(
+ &destination_,
+ static_cast<AudioMirroringManager::MirroringDestination*>(NULL)))
+ .RetiresOnSaturation();
+
+ // Simulate OnTargetChange() callback from WebContentsTracker.
+ EXPECT_FALSE(change_callback_.is_null());
+ change_callback_.Run(next_render_process_id, next_render_view_id);
+
+ current_render_process_id_ = next_render_process_id;
+ current_render_view_id_ = next_render_view_id;
+ }
+
+ void LoseMirroringTarget() {
+ EXPECT_CALL(mock_input_callback_, OnError(_));
+
+ // Simulate OnTargetChange() callback from WebContentsTracker.
+ EXPECT_FALSE(change_callback_.is_null());
+ change_callback_.Run(-1, -1);
+ }
+
+ void Stop() {
+ wcais_->Stop();
+ }
+
+ void Close() {
+ // WebContentsAudioInputStream self-destructs on Close(). Its internal
+ // objects hang around until they are no longer referred to (e.g., as tasks
+ // on other threads shut things down).
+ wcais_->Close();
+ wcais_ = NULL;
+ mock_vais_ = NULL;
+ }
+
+ void RunOnAudioThread(const base::Closure& closure) {
+ audio_thread_.message_loop()->PostTask(FROM_HERE, closure);
+ }
+
+ // Block the calling thread until OnData() callbacks are being made.
+ void WaitForData() {
+ // Note: Arbitrarily chosen, but more iterations causes tests to take
+ // significantly more time.
+ static const int kNumIterations = 3;
+ for (int i = 0; i < kNumIterations; ++i)
+ on_data_event_.Wait();
+ }
+
+ private:
+ base::Thread audio_thread_;
+ BrowserThreadImpl io_thread_;
+
+ scoped_ptr<MockAudioMirroringManager> mock_mirroring_manager_;
+ scoped_refptr<MockWebContentsTracker> mock_tracker_;
+
+ MockVirtualAudioInputStream* mock_vais_; // Owned by wcais_.
+ WebContentsAudioInputStream* wcais_; // Self-destructs on Close().
+
+ // Mock consumer of audio data.
+ MockAudioInputCallback mock_input_callback_;
+
+ // Provided by WebContentsAudioInputStream to the mock WebContentsTracker.
+ // This callback is saved here, and test code will invoke it to simulate
+ // target change events.
+ WebContentsTracker::ChangeCallback change_callback_;
+
+ // Provided by WebContentsAudioInputStream to the mock AudioMirroringManager.
+ // A pointer to the implementation is saved here, and test code will invoke it
+ // to simulate: 1) calls to AddInput(); and 2) diverting audio data.
+ AudioMirroringManager::MirroringDestination* destination_;
+
+ // Current target RenderView. These get flipped in ChangedMirroringTarget().
+ int current_render_process_id_;
+ int current_render_view_id_;
+
+ // Streams provided by calls to WebContentsAudioInputStream::AddInput(). Each
+ // is started with a simulated source of audio data.
+ std::list<AudioOutputStream*> streams_;
+ std::list<SineWaveAudioSource*> sources_; // 1:1 with elements in streams_.
+
+ base::WaitableEvent on_data_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsAudioInputStreamTest);
+};
+
+#define RUN_ON_AUDIO_THREAD(method) \
+ RunOnAudioThread(base::Bind(&WebContentsAudioInputStreamTest::method, \
+ base::Unretained(this)))
+
+TEST_F(WebContentsAudioInputStreamTest, OpenedButNeverStarted) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringNothing) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringOutputOutlivesSession) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringOutputWithinSession) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringNothingWithTargetChange) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget);
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamAfterTargetChange) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamWithTargetChange) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringLostTarget) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(LoseMirroringTarget);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+TEST_F(WebContentsAudioInputStreamTest, MirroringMultipleStreamsAndTargets) {
+ RUN_ON_AUDIO_THREAD(Open);
+ RUN_ON_AUDIO_THREAD(Start);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(AddAnotherInput);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ WaitForData();
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget);
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder);
+ RUN_ON_AUDIO_THREAD(Stop);
+ RUN_ON_AUDIO_THREAD(Close);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_capture_util.cc b/chromium/content/browser/renderer_host/media/web_contents_capture_util.cc
new file mode 100644
index 00000000000..28b0fa6c8dc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_capture_util.cc
@@ -0,0 +1,59 @@
+// 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 "content/browser/renderer_host/media/web_contents_capture_util.h"
+
+#include "base/basictypes.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+
+namespace {
+
+const char kVirtualDeviceScheme[] = "virtual-media-stream://";
+
+} // namespace
+
+namespace content {
+
+std::string WebContentsCaptureUtil::AppendWebContentsDeviceScheme(
+ const std::string& device_id) {
+ return kVirtualDeviceScheme + device_id;
+}
+
+std::string WebContentsCaptureUtil::StripWebContentsDeviceScheme(
+ const std::string& device_id) {
+ return (IsWebContentsDeviceId(device_id) ?
+ device_id.substr(arraysize(kVirtualDeviceScheme) - 1) :
+ device_id);
+}
+
+bool WebContentsCaptureUtil::IsWebContentsDeviceId(
+ const std::string& device_id) {
+ return StartsWithASCII(device_id, kVirtualDeviceScheme, true);
+}
+
+bool WebContentsCaptureUtil::ExtractTabCaptureTarget(
+ const std::string& device_id_param,
+ int* render_process_id,
+ int* render_view_id) {
+ if (!IsWebContentsDeviceId(device_id_param))
+ return false;
+
+ const std::string device_id = device_id_param.substr(
+ arraysize(kVirtualDeviceScheme) - 1);
+
+ const size_t sep_pos = device_id.find(':');
+ if (sep_pos == std::string::npos)
+ return false;
+
+ const base::StringPiece component1(device_id.data(), sep_pos);
+ const base::StringPiece component2(device_id.data() + sep_pos + 1,
+ device_id.length() - sep_pos - 1);
+
+ return (base::StringToInt(component1, render_process_id) &&
+ base::StringToInt(component2, render_view_id));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_capture_util.h b/chromium/content/browser/renderer_host/media/web_contents_capture_util.h
new file mode 100644
index 00000000000..8f376afdeec
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_capture_util.h
@@ -0,0 +1,35 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_CAPTURE_UTIL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_CAPTURE_UTIL_H_
+
+#include <string>
+
+#include "content/common/content_export.h"
+
+namespace content {
+
+class CONTENT_EXPORT WebContentsCaptureUtil {
+ public:
+ // Returns a new id after appending the device id scheme for virtual streams.
+ static std::string AppendWebContentsDeviceScheme(
+ const std::string& device_id_param);
+
+ static std::string StripWebContentsDeviceScheme(
+ const std::string& device_id_param);
+
+ // Check whether the device id indicates that this is a web contents stream.
+ static bool IsWebContentsDeviceId(const std::string& device_id);
+
+ // Function to extract the target renderer id's from a tab media stream
+ // request's device id.
+ static bool ExtractTabCaptureTarget(const std::string& device_id,
+ int* render_process_id,
+ int* render_view_id);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_CAPTURE_UTIL_H_
diff --git a/chromium/content/browser/renderer_host/media/web_contents_tracker.cc b/chromium/content/browser/renderer_host/media/web_contents_tracker.cc
new file mode 100644
index 00000000000..3a75080cb46
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_tracker.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 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 "content/browser/renderer_host/media/web_contents_tracker.h"
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace content {
+
+WebContentsTracker::WebContentsTracker() {}
+
+WebContentsTracker::~WebContentsTracker() {
+ DCHECK(!web_contents()) << "BUG: Still observering!";
+}
+
+void WebContentsTracker::Start(int render_process_id, int render_view_id,
+ const ChangeCallback& callback) {
+ DCHECK(!message_loop_.get() || message_loop_->BelongsToCurrentThread());
+
+ message_loop_ = base::MessageLoopProxy::current();
+ DCHECK(message_loop_.get());
+ callback_ = callback;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebContentsTracker::LookUpAndObserveWebContents, this,
+ render_process_id, render_view_id));
+}
+
+void WebContentsTracker::Stop() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+
+ callback_.Reset();
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebContentsTracker::Observe, this,
+ static_cast<WebContents*>(NULL)));
+}
+
+void WebContentsTracker::OnWebContentsChangeEvent() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ WebContents* const wc = web_contents();
+ RenderViewHost* const rvh = wc ? wc->GetRenderViewHost() : NULL;
+ RenderProcessHost* const rph = rvh ? rvh->GetProcess() : NULL;
+
+ const int render_process_id = rph ? rph->GetID() : MSG_ROUTING_NONE;
+ const int render_view_id = rvh ? rvh->GetRoutingID() : MSG_ROUTING_NONE;
+
+ message_loop_->PostTask(FROM_HERE,
+ base::Bind(&WebContentsTracker::MaybeDoCallback, this,
+ render_process_id, render_view_id));
+}
+
+void WebContentsTracker::MaybeDoCallback(int render_process_id,
+ int render_view_id) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+
+ if (!callback_.is_null())
+ callback_.Run(render_process_id, render_view_id);
+}
+
+void WebContentsTracker::LookUpAndObserveWebContents(int render_process_id,
+ int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RenderViewHost* const rvh =
+ RenderViewHost::FromID(render_process_id, render_view_id);
+ DVLOG_IF(1, !rvh) << "RenderViewHost::FromID("
+ << render_process_id << ", " << render_view_id
+ << ") returned NULL.";
+ Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL);
+ DVLOG_IF(1, !web_contents())
+ << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL.";
+
+ OnWebContentsChangeEvent();
+}
+
+void WebContentsTracker::RenderViewReady() {
+ OnWebContentsChangeEvent();
+}
+
+void WebContentsTracker::AboutToNavigateRenderView(RenderViewHost* rvh) {
+ OnWebContentsChangeEvent();
+}
+
+void WebContentsTracker::DidNavigateMainFrame(
+ const LoadCommittedDetails& details, const FrameNavigateParams& params) {
+ OnWebContentsChangeEvent();
+}
+
+void WebContentsTracker::WebContentsDestroyed(WebContents* web_contents) {
+ OnWebContentsChangeEvent();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_tracker.h b/chromium/content/browser/renderer_host/media/web_contents_tracker.h
new file mode 100644
index 00000000000..632ced28be7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_tracker.h
@@ -0,0 +1,86 @@
+// Copyright (c) 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.
+//
+// Given a starting render_process_id and render_view_id, the WebContentsTracker
+// tracks RenderViewHost instance swapping during the lifetime of a WebContents
+// instance. This is used when mirroring tab video and audio so that user
+// navigations, crashes, etc., during a tab's lifetime allow the capturing code
+// to remain active on the current/latest RenderView.
+//
+// Threading issues: Start(), Stop() and the ChangeCallback are invoked on the
+// same thread. This can be any thread, and the decision is locked-in by
+// WebContentsTracker when Start() is called.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_TRACKER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_TRACKER_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace content {
+
+class CONTENT_EXPORT WebContentsTracker
+ : public base::RefCountedThreadSafe<WebContentsTracker>,
+ public WebContentsObserver {
+ public:
+ WebContentsTracker();
+
+ // Callback for whenever the target is swapped. The callback is also invoked
+ // with both arguments set to MSG_ROUTING_NONE to indicate tracking will not
+ // continue (i.e., the WebContents instance was not found or has been
+ // destroyed).
+ typedef base::Callback<void(int render_process_id, int render_view_id)>
+ ChangeCallback;
+
+ // Start tracking. The last-known |render_process_id| and |render_view_id|
+ // are provided, and the given callback is invoked asynchronously one or more
+ // times. The callback will be invoked on the same thread calling Start().
+ virtual void Start(int render_process_id, int render_view_id,
+ const ChangeCallback& callback);
+
+ // Stop tracking. Once this method returns, the callback is guaranteed not to
+ // be invoked again.
+ virtual void Stop();
+
+ protected:
+ friend class base::RefCountedThreadSafe<WebContentsTracker>;
+ virtual ~WebContentsTracker();
+
+ private:
+ // Reads the render_process_id/render_view_id from the current WebContents
+ // instance and then invokes the callback.
+ void OnWebContentsChangeEvent();
+
+ // Called on the thread that Start()/Stop() are called on, check whether the
+ // callback is still valid and, if so, invoke it.
+ void MaybeDoCallback(int render_process_id, int render_view_id);
+
+ // Look-up the current WebContents instance associated with the given
+ // |render_process_id| and |render_view_id| and begin observing it.
+ void LookUpAndObserveWebContents(int render_process_id,
+ int render_view_id);
+
+ // WebContentsObserver overrides to react to events of interest.
+ virtual void RenderViewReady() OVERRIDE;
+ virtual void AboutToNavigateRenderView(RenderViewHost* render_view_host)
+ OVERRIDE;
+ virtual void DidNavigateMainFrame(const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE;
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+ ChangeCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsTracker);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_TRACKER_H_
diff --git a/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.cc b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.cc
new file mode 100644
index 00000000000..e27b703081e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.cc
@@ -0,0 +1,1278 @@
+// 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.
+//
+// Implementation notes: This needs to work on a variety of hardware
+// configurations where the speed of the CPU and GPU greatly affect overall
+// performance. Spanning several threads, the process of capturing has been
+// split up into four conceptual stages:
+//
+// 1. Reserve Buffer: Before a frame can be captured, a slot in the consumer's
+// shared-memory IPC buffer is reserved. There are only a few of these;
+// when they run out, it indicates that the downstream consumer -- likely a
+// video encoder -- is the performance bottleneck, and that the rate of
+// frame capture should be throttled back.
+//
+// 2. Capture: A bitmap is snapshotted/copied from the RenderView's backing
+// store. This is initiated on the UI BrowserThread, and often occurs
+// asynchronously. Where supported, the GPU scales and color converts
+// frames to our desired size, and the readback happens directly into the
+// shared-memory buffer. But this is not always possible, particularly when
+// accelerated compositing is disabled.
+//
+// 3. Render (if needed): If the web contents cannot be captured directly into
+// our target size and color format, scaling and colorspace conversion must
+// be done on the CPU. A dedicated thread is used for this operation, to
+// avoid blocking the UI thread. The Render stage always reads from a
+// bitmap returned by Capture, and writes into the reserved slot in the
+// shared-memory buffer.
+//
+// 4. Deliver: The rendered video frame is returned to the consumer (which
+// implements the VideoCaptureDevice::EventHandler interface). Because
+// all paths have written the frame into the IPC buffer, this step should
+// never need to do an additional copy of the pixel data.
+//
+// In the best-performing case, the Render step is bypassed: Capture produces
+// ready-to-Deliver frames. But when accelerated readback is not possible, the
+// system is designed so that Capture and Render may run concurrently. A timing
+// diagram helps illustrate this point (@30 FPS):
+//
+// Time: 0ms 33ms 66ms 99ms
+// thread1: |-Capture-f1------v |-Capture-f2------v |-Capture-f3----v |-Capt
+// thread2: |-Render-f1-----v |-Render-f2-----v |-Render-f3
+//
+// In the above example, both capturing and rendering *each* take almost the
+// full 33 ms available between frames, yet we see that the required throughput
+// is obtained.
+//
+// Turning on verbose logging will cause the effective frame rate to be logged
+// at 5-second intervals.
+
+#include "content/browser/renderer_host/media/web_contents_video_capture_device.h"
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_forward.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/media/video_capture_oracle.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "media/base/bind_to_loop.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "media/base/yuv_convert.h"
+#include "media/video/capture/video_capture_types.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/skia_util.h"
+
+namespace content {
+
+namespace {
+
+const int kMinFrameWidth = 2;
+const int kMinFrameHeight = 2;
+const int kMaxFramesInFlight = 2;
+const int kMaxSnapshotsInFlight = 1;
+
+// TODO(nick): Remove this once frame subscription is supported on Aura and
+// Linux.
+#if (defined(OS_WIN) || defined(OS_MACOSX)) || defined(USE_AURA)
+const bool kAcceleratedSubscriberIsSupported = true;
+#else
+const bool kAcceleratedSubscriberIsSupported = false;
+#endif
+
+// Returns the nearest even integer closer to zero.
+template<typename IntType>
+IntType MakeEven(IntType x) {
+ return x & static_cast<IntType>(-2);
+}
+
+// Compute a letterbox region, aligned to even coordinates.
+gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size,
+ const gfx::Size& content_size) {
+
+ gfx::Rect result = media::ComputeLetterboxRegion(gfx::Rect(frame_size),
+ content_size);
+
+ result.set_x(MakeEven(result.x()));
+ result.set_y(MakeEven(result.y()));
+ result.set_width(std::max(kMinFrameWidth, MakeEven(result.width())));
+ result.set_height(std::max(kMinFrameHeight, MakeEven(result.height())));
+
+ return result;
+}
+
+// Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps
+// the VideoCaptureOracle, which decides which frames to capture, and a
+// VideoCaptureDevice::EventHandler, which allocates and receives the captured
+// frames, in a lock to synchronize state between the two.
+class ThreadSafeCaptureOracle
+ : public base::RefCountedThreadSafe<ThreadSafeCaptureOracle> {
+ public:
+ ThreadSafeCaptureOracle(media::VideoCaptureDevice::EventHandler* consumer,
+ scoped_ptr<VideoCaptureOracle> oracle);
+
+ bool ObserveEventAndDecideCapture(
+ VideoCaptureOracle::Event event,
+ base::Time event_time,
+ scoped_refptr<media::VideoFrame>* storage,
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* callback);
+
+ base::TimeDelta capture_period() const {
+ return oracle_->capture_period();
+ }
+
+ // Allow new captures to start occurring.
+ void Start();
+
+ // Stop new captures from happening (but doesn't forget the consumer).
+ void Stop();
+
+ // Signal an error to the consumer.
+ void ReportError();
+
+ // Permanently stop capturing. Immediately cease all activity on the
+ // VCD::EventHandler.
+ void InvalidateConsumer();
+
+ private:
+ friend class base::RefCountedThreadSafe<ThreadSafeCaptureOracle>;
+ virtual ~ThreadSafeCaptureOracle() {}
+
+ // Callback invoked on completion of all captures.
+ void DidCaptureFrame(const scoped_refptr<media::VideoFrame>& frame,
+ int frame_number,
+ base::Time timestamp,
+ bool success);
+
+ // Protects everything below it.
+ base::Lock lock_;
+
+ // Recipient of our capture activity. Becomes null after it is invalidated.
+ media::VideoCaptureDevice::EventHandler* consumer_;
+
+ // Makes the decision to capture a frame.
+ const scoped_ptr<VideoCaptureOracle> oracle_;
+
+ // Whether capturing is currently allowed. Can toggle back and forth.
+ bool is_started_;
+};
+
+// FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
+// with RenderWidgetHostViewFrameSubscriber. We create one per event type.
+class FrameSubscriber : public RenderWidgetHostViewFrameSubscriber {
+ public:
+ FrameSubscriber(VideoCaptureOracle::Event event_type,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle)
+ : event_type_(event_type),
+ oracle_proxy_(oracle) {}
+
+ virtual bool ShouldCaptureFrame(
+ base::Time present_time,
+ scoped_refptr<media::VideoFrame>* storage,
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback*
+ deliver_frame_cb) OVERRIDE;
+
+ private:
+ const VideoCaptureOracle::Event event_type_;
+ scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
+};
+
+// ContentCaptureSubscription is the relationship between a RenderWidgetHost
+// whose content is updating, a subscriber that is deciding which of these
+// updates to capture (and where to deliver them to), and a callback that
+// knows how to do the capture and prepare the result for delivery.
+//
+// In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in
+// the RenderWidgetHostView, to process updates that occur via accelerated
+// compositing, (b) installing itself as an observer of updates to the
+// RenderWidgetHost's backing store, to hook updates that occur via software
+// rendering, and (c) running a timer to possibly initiate non-event-driven
+// captures that the subscriber might request.
+//
+// All of this happens on the UI thread, although the
+// RenderWidgetHostViewFrameSubscriber we install may be dispatching updates
+// autonomously on some other thread.
+class ContentCaptureSubscription : public content::NotificationObserver {
+ public:
+ typedef base::Callback<void(
+ const base::Time&,
+ const scoped_refptr<media::VideoFrame>&,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)>
+ CaptureCallback;
+
+ // Create a subscription. Whenever a manual capture is required, the
+ // subscription will invoke |capture_callback| on the UI thread to do the
+ // work.
+ ContentCaptureSubscription(
+ const RenderWidgetHost& source,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
+ const CaptureCallback& capture_callback);
+ virtual ~ContentCaptureSubscription();
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ void OnTimer();
+
+ const int render_process_id_;
+ const int render_view_id_;
+
+ FrameSubscriber paint_subscriber_;
+ FrameSubscriber timer_subscriber_;
+ content::NotificationRegistrar registrar_;
+ CaptureCallback capture_callback_;
+ base::Timer timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription);
+};
+
+// Render the SkBitmap |input| into the given VideoFrame buffer |output|, then
+// invoke |done_cb| to indicate success or failure. |input| is expected to be
+// ARGB. |output| must be YV12 or I420. Colorspace conversion is always done.
+// Scaling and letterboxing will be done as needed.
+//
+// This software implementation should be used only when GPU acceleration of
+// these activities is not possible. This operation may be expensive (tens to
+// hundreds of milliseconds), so the caller should ensure that it runs on a
+// thread where such a pause would cause UI jank.
+void RenderVideoFrame(const SkBitmap& input,
+ const scoped_refptr<media::VideoFrame>& output,
+ const base::Callback<void(bool)>& done_cb);
+
+// Keeps track of the RenderView to be sourced, and executes copying of the
+// backing store on the UI BrowserThread.
+//
+// TODO(nick): It would be nice to merge this with WebContentsTracker, but its
+// implementation is currently asynchronous -- in our case, the "rvh changed"
+// notification would get posted back to the UI thread and processed later, and
+// this seems disadvantageous.
+class CaptureMachine : public WebContentsObserver,
+ public base::SupportsWeakPtr<CaptureMachine> {
+ public:
+ virtual ~CaptureMachine();
+
+ // Creates a CaptureMachine. Must be run on the UI BrowserThread. Returns
+ // NULL if the indicated render view cannot be found.
+ static scoped_ptr<CaptureMachine> Create(
+ int render_process_id,
+ int render_view_id,
+ const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy);
+
+ // Starts a copy from the backing store or the composited surface. Must be run
+ // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
+ // completes. The copy will occur to |target|.
+ //
+ // This may be used as a ContentCaptureSubscription::CaptureCallback.
+ void Capture(
+ const base::Time& start_time,
+ const scoped_refptr<media::VideoFrame>& target,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb);
+
+ // content::WebContentsObserver implementation.
+ virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
+ fullscreen_widget_id_ = routing_id;
+ RenewFrameSubscription();
+ }
+
+ virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
+ DCHECK_EQ(fullscreen_widget_id_, routing_id);
+ fullscreen_widget_id_ = MSG_ROUTING_NONE;
+ RenewFrameSubscription();
+ }
+
+ virtual void RenderViewReady() OVERRIDE {
+ RenewFrameSubscription();
+ }
+
+ virtual void AboutToNavigateRenderView(RenderViewHost* rvh) OVERRIDE {
+ RenewFrameSubscription();
+ }
+
+ virtual void DidNavigateMainFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE {
+ RenewFrameSubscription();
+ }
+
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+
+ private:
+ CaptureMachine(
+ const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy);
+
+ // Starts observing the web contents, returning false if lookup fails.
+ bool StartObservingWebContents(int initial_render_process_id,
+ int initial_render_view_id);
+
+ // Helper function to determine the view that we are currently tracking.
+ RenderWidgetHost* GetTarget();
+
+ // Response callback for RenderWidgetHost::CopyFromBackingStore().
+ void DidCopyFromBackingStore(
+ const base::Time& start_time,
+ const scoped_refptr<media::VideoFrame>& target,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb,
+ bool success,
+ const SkBitmap& bitmap);
+
+ // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame().
+ void DidCopyFromCompositingSurfaceToVideoFrame(
+ const base::Time& start_time,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb,
+ bool success);
+
+ // Remove the old subscription, and start a new one. This should be called
+ // after any change to the WebContents that affects the RenderWidgetHost or
+ // attached views.
+ void RenewFrameSubscription();
+
+ // The task runner of the thread on which SkBitmap->VideoFrame conversion will
+ // occur. Only used when this activity cannot be done on the GPU.
+ const scoped_refptr<base::SequencedTaskRunner> render_task_runner_;
+
+ // Makes all the decisions about which frames to copy, and how.
+ const scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
+
+ // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE
+ // otherwise.
+ int fullscreen_widget_id_;
+
+ // Last known RenderView size.
+ gfx::Size last_view_size_;
+
+ // Responsible for forwarding events from the active RenderWidgetHost to the
+ // oracle, and initiating captures accordingly.
+ scoped_ptr<ContentCaptureSubscription> subscription_;
+
+ DISALLOW_COPY_AND_ASSIGN(CaptureMachine);
+};
+
+// Responsible for logging the effective frame rate.
+// TODO(nick): Make this compatible with the push model and hook it back up.
+class VideoFrameDeliveryLog {
+ public:
+ VideoFrameDeliveryLog();
+
+ // Treat |frame_number| as having been delivered, and update the
+ // frame rate statistics accordingly.
+ void ChronicleFrameDelivery(int frame_number);
+
+ private:
+ // The following keep track of and log the effective frame rate whenever
+ // verbose logging is turned on.
+ base::Time last_frame_rate_log_time_;
+ int count_frames_rendered_;
+ int last_frame_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog);
+};
+
+ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
+ media::VideoCaptureDevice::EventHandler* consumer,
+ scoped_ptr<VideoCaptureOracle> oracle)
+ : consumer_(consumer),
+ oracle_(oracle.Pass()),
+ is_started_(false) {
+}
+
+bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
+ VideoCaptureOracle::Event event,
+ base::Time event_time,
+ scoped_refptr<media::VideoFrame>* storage,
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* callback) {
+ base::AutoLock guard(lock_);
+
+ if (!consumer_ || !is_started_)
+ return false; // Capture is stopped.
+
+ scoped_refptr<media::VideoFrame> output_buffer =
+ consumer_->ReserveOutputBuffer();
+ const bool should_capture =
+ oracle_->ObserveEventAndDecideCapture(event, event_time);
+ const bool content_is_dirty =
+ (event == VideoCaptureOracle::kCompositorUpdate ||
+ event == VideoCaptureOracle::kSoftwarePaint);
+ const char* event_name =
+ (event == VideoCaptureOracle::kTimerPoll ? "poll" :
+ (event == VideoCaptureOracle::kCompositorUpdate ? "gpu" :
+ "paint"));
+
+ // Consider the various reasons not to initiate a capture.
+ if (should_capture && !output_buffer.get()) {
+ TRACE_EVENT_INSTANT1("mirroring",
+ "EncodeLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger",
+ event_name);
+ return false;
+ } else if (!should_capture && output_buffer.get()) {
+ if (content_is_dirty) {
+ // This is a normal and acceptable way to drop a frame. We've hit our
+ // capture rate limit: for example, the content is animating at 60fps but
+ // we're capturing at 30fps.
+ TRACE_EVENT_INSTANT1("mirroring", "FpsRateLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger", event_name);
+ }
+ return false;
+ } else if (!should_capture && !output_buffer.get()) {
+ // We decided not to capture, but we wouldn't have been able to if we wanted
+ // to because no output buffer was available.
+ TRACE_EVENT_INSTANT1("mirroring", "NearlyEncodeLimited",
+ TRACE_EVENT_SCOPE_THREAD,
+ "trigger", event_name);
+ return false;
+ }
+ int frame_number = oracle_->RecordCapture();
+ TRACE_EVENT_ASYNC_BEGIN2("mirroring", "Capture", output_buffer.get(),
+ "frame_number", frame_number,
+ "trigger", event_name);
+ *storage = output_buffer;
+ *callback = base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame,
+ this, output_buffer, frame_number);
+ return true;
+}
+
+void ThreadSafeCaptureOracle::Start() {
+ base::AutoLock guard(lock_);
+ is_started_ = true;
+}
+
+void ThreadSafeCaptureOracle::Stop() {
+ base::AutoLock guard(lock_);
+ is_started_ = false;
+}
+
+void ThreadSafeCaptureOracle::ReportError() {
+ base::AutoLock guard(lock_);
+ if (consumer_)
+ consumer_->OnError();
+}
+
+void ThreadSafeCaptureOracle::InvalidateConsumer() {
+ base::AutoLock guard(lock_);
+
+ TRACE_EVENT_INSTANT0("mirroring", "InvalidateConsumer",
+ TRACE_EVENT_SCOPE_THREAD);
+
+ is_started_ = false;
+ consumer_ = NULL;
+}
+
+void ThreadSafeCaptureOracle::DidCaptureFrame(
+ const scoped_refptr<media::VideoFrame>& frame,
+ int frame_number,
+ base::Time timestamp,
+ bool success) {
+ base::AutoLock guard(lock_);
+
+ TRACE_EVENT_ASYNC_END2("mirroring", "Capture", frame.get(),
+ "success", success,
+ "timestamp", timestamp.ToInternalValue());
+
+ if (!consumer_ || !is_started_)
+ return; // Capture is stopped.
+
+ if (success)
+ if (oracle_->CompleteCapture(frame_number, timestamp))
+ consumer_->OnIncomingCapturedVideoFrame(frame, timestamp);
+}
+
+bool FrameSubscriber::ShouldCaptureFrame(
+ base::Time present_time,
+ scoped_refptr<media::VideoFrame>* storage,
+ DeliverFrameCallback* deliver_frame_cb) {
+ TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
+ "instance", this);
+
+ return oracle_proxy_->ObserveEventAndDecideCapture(event_type_, present_time,
+ storage, deliver_frame_cb);
+}
+
+ContentCaptureSubscription::ContentCaptureSubscription(
+ const RenderWidgetHost& source,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
+ const CaptureCallback& capture_callback)
+ : render_process_id_(source.GetProcess()->GetID()),
+ render_view_id_(source.GetRoutingID()),
+ paint_subscriber_(VideoCaptureOracle::kSoftwarePaint, oracle_proxy),
+ timer_subscriber_(VideoCaptureOracle::kTimerPoll, oracle_proxy),
+ capture_callback_(capture_callback),
+ timer_(true, true) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RenderWidgetHostViewPort* view =
+ RenderWidgetHostViewPort::FromRWHV(source.GetView());
+
+ // Subscribe to accelerated presents. These will be serviced directly by the
+ // oracle.
+ if (view && kAcceleratedSubscriberIsSupported) {
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber(
+ new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate,
+ oracle_proxy));
+ view->BeginFrameSubscription(subscriber.Pass());
+ }
+
+ // Subscribe to software paint events. This instance will service these by
+ // reflecting them back to the CaptureMachine via |capture_callback|.
+ registrar_.Add(
+ this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
+ Source<RenderWidgetHost>(&source));
+
+ // Subscribe to timer events. This instance will service these as well.
+ timer_.Start(FROM_HERE, oracle_proxy->capture_period(),
+ base::Bind(&ContentCaptureSubscription::OnTimer,
+ base::Unretained(this)));
+}
+
+ContentCaptureSubscription::~ContentCaptureSubscription() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (kAcceleratedSubscriberIsSupported) {
+ RenderViewHost* source = RenderViewHost::FromID(render_process_id_,
+ render_view_id_);
+ if (source) {
+ RenderWidgetHostViewPort* view =
+ RenderWidgetHostViewPort::FromRWHV(source->GetView());
+ if (view)
+ view->EndFrameSubscription();
+ }
+ }
+}
+
+void ContentCaptureSubscription::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, type);
+
+ RenderWidgetHostImpl* rwh =
+ RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr());
+
+ // This message occurs on window resizes and visibility changes even when
+ // accelerated compositing is active, so we need to filter out these cases.
+ if (!rwh || !rwh->GetView() || (rwh->is_accelerated_compositing_active() &&
+ rwh->GetView()->IsSurfaceAvailableForCopy()))
+ return;
+
+ TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe",
+ "instance", this);
+
+ base::Closure copy_done_callback;
+ scoped_refptr<media::VideoFrame> frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
+ const base::Time start_time = base::Time::Now();
+ if (paint_subscriber_.ShouldCaptureFrame(start_time,
+ &frame,
+ &deliver_frame_cb)) {
+ // This message happens just before paint. If we post a task to do the copy,
+ // it should run soon after the paint.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(capture_callback_, start_time, frame, deliver_frame_cb));
+ }
+}
+
+void ContentCaptureSubscription::OnTimer() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer");
+
+ scoped_refptr<media::VideoFrame> frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
+
+ const base::Time start_time = base::Time::Now();
+ if (timer_subscriber_.ShouldCaptureFrame(start_time,
+ &frame,
+ &deliver_frame_cb)) {
+ capture_callback_.Run(start_time, frame, deliver_frame_cb);
+ }
+}
+
+void RenderVideoFrame(const SkBitmap& input,
+ const scoped_refptr<media::VideoFrame>& output,
+ const base::Callback<void(bool)>& done_cb) {
+ base::ScopedClosureRunner failure_handler(base::Bind(done_cb, false));
+
+ SkAutoLockPixels locker(input);
+
+ // Sanity-check the captured bitmap.
+ if (input.empty() ||
+ !input.readyToDraw() ||
+ input.config() != SkBitmap::kARGB_8888_Config ||
+ input.width() < 2 || input.height() < 2) {
+ DVLOG(1) << "input unacceptable (size="
+ << input.getSize()
+ << ", ready=" << input.readyToDraw()
+ << ", config=" << input.config() << ')';
+ return;
+ }
+
+ // Sanity-check the output buffer.
+ if (output->format() != media::VideoFrame::I420) {
+ NOTREACHED();
+ return;
+ }
+
+ // Calculate the width and height of the content region in the |output|, based
+ // on the aspect ratio of |input|.
+ gfx::Rect region_in_frame = ComputeYV12LetterboxRegion(
+ output->coded_size(), gfx::Size(input.width(), input.height()));
+
+ // Scale the bitmap to the required size, if necessary.
+ SkBitmap scaled_bitmap;
+ if (input.width() != region_in_frame.width() ||
+ input.height() != region_in_frame.height()) {
+
+ skia::ImageOperations::ResizeMethod method;
+ if (input.width() < region_in_frame.width() ||
+ input.height() < region_in_frame.height()) {
+ // Avoid box filtering when magnifying, because it's actually
+ // nearest-neighbor.
+ method = skia::ImageOperations::RESIZE_HAMMING1;
+ } else {
+ method = skia::ImageOperations::RESIZE_BOX;
+ }
+
+ TRACE_EVENT_ASYNC_STEP0("mirroring", "Capture", output.get(), "Scale");
+ scaled_bitmap = skia::ImageOperations::Resize(input, method,
+ region_in_frame.width(),
+ region_in_frame.height());
+ } else {
+ scaled_bitmap = input;
+ }
+
+ TRACE_EVENT_ASYNC_STEP0("mirroring", "Capture", output.get(), "YUV");
+ {
+ SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
+
+ media::CopyRGBToVideoFrame(
+ reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
+ scaled_bitmap.rowBytes(),
+ region_in_frame,
+ output.get());
+ }
+
+ // The result is now ready.
+ failure_handler.Release();
+ done_cb.Run(true);
+}
+
+VideoFrameDeliveryLog::VideoFrameDeliveryLog()
+ : last_frame_rate_log_time_(),
+ count_frames_rendered_(0),
+ last_frame_number_(0) {
+}
+
+void VideoFrameDeliveryLog::ChronicleFrameDelivery(int frame_number) {
+ // Log frame rate, if verbose logging is turned on.
+ static const base::TimeDelta kFrameRateLogInterval =
+ base::TimeDelta::FromSeconds(10);
+ const base::Time now = base::Time::Now();
+ if (last_frame_rate_log_time_.is_null()) {
+ last_frame_rate_log_time_ = now;
+ count_frames_rendered_ = 0;
+ last_frame_number_ = frame_number;
+ } else {
+ ++count_frames_rendered_;
+ const base::TimeDelta elapsed = now - last_frame_rate_log_time_;
+ if (elapsed >= kFrameRateLogInterval) {
+ const double measured_fps =
+ count_frames_rendered_ / elapsed.InSecondsF();
+ const int frames_elapsed = frame_number - last_frame_number_;
+ const int count_frames_dropped = frames_elapsed - count_frames_rendered_;
+ DCHECK_LE(0, count_frames_dropped);
+ UMA_HISTOGRAM_PERCENTAGE(
+ "TabCapture.FrameDropPercentage",
+ (count_frames_dropped * 100 + frames_elapsed / 2) / frames_elapsed);
+ UMA_HISTOGRAM_COUNTS(
+ "TabCapture.FrameRate",
+ static_cast<int>(measured_fps));
+ VLOG(1) << "Current measured frame rate for "
+ << "WebContentsVideoCaptureDevice is " << measured_fps << " FPS.";
+ last_frame_rate_log_time_ = now;
+ count_frames_rendered_ = 0;
+ last_frame_number_ = frame_number;
+ }
+ }
+}
+
+// static
+scoped_ptr<CaptureMachine> CaptureMachine::Create(
+ int render_process_id,
+ int render_view_id,
+ const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(render_task_runner.get());
+ DCHECK(oracle_proxy.get());
+ scoped_ptr<CaptureMachine> machine(
+ new CaptureMachine(render_task_runner, oracle_proxy));
+
+ if (!machine->StartObservingWebContents(render_process_id, render_view_id))
+ machine.reset();
+
+ return machine.Pass();
+}
+
+CaptureMachine::CaptureMachine(
+ const scoped_refptr<base::SequencedTaskRunner>& render_task_runner,
+ const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy)
+ : render_task_runner_(render_task_runner),
+ oracle_proxy_(oracle_proxy),
+ fullscreen_widget_id_(MSG_ROUTING_NONE) {}
+
+CaptureMachine::~CaptureMachine() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
+
+ // Stop observing the web contents.
+ subscription_.reset();
+ if (web_contents()) {
+ web_contents()->DecrementCapturerCount();
+ Observe(NULL);
+ }
+}
+
+void CaptureMachine::Capture(
+ const base::Time& start_time,
+ const scoped_refptr<media::VideoFrame>& target,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ RenderWidgetHost* rwh = GetTarget();
+ RenderWidgetHostViewPort* view =
+ rwh ? RenderWidgetHostViewPort::FromRWHV(rwh->GetView()) : NULL;
+ if (!view || !rwh) {
+ deliver_frame_cb.Run(base::Time(), false);
+ return;
+ }
+
+ gfx::Size video_size = target->coded_size();
+ gfx::Size view_size = view->GetViewBounds().size();
+ gfx::Size fitted_size;
+ if (!view_size.IsEmpty()) {
+ fitted_size = ComputeYV12LetterboxRegion(video_size, view_size).size();
+ }
+ if (view_size != last_view_size_) {
+ last_view_size_ = view_size;
+
+ // Measure the number of kilopixels.
+ UMA_HISTOGRAM_COUNTS_10000(
+ "TabCapture.ViewChangeKiloPixels",
+ view_size.width() * view_size.height() / 1024);
+ }
+
+ if (!view->IsSurfaceAvailableForCopy()) {
+ // Fallback to the more expensive renderer-side copy if the surface and
+ // backing store are not accessible.
+ rwh->GetSnapshotFromRenderer(
+ gfx::Rect(),
+ base::Bind(&CaptureMachine::DidCopyFromBackingStore, this->AsWeakPtr(),
+ start_time, target, deliver_frame_cb));
+ } else if (view->CanCopyToVideoFrame()) {
+ view->CopyFromCompositingSurfaceToVideoFrame(
+ gfx::Rect(view_size),
+ target,
+ base::Bind(&CaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame,
+ this->AsWeakPtr(), start_time, deliver_frame_cb));
+ } else {
+ rwh->CopyFromBackingStore(
+ gfx::Rect(),
+ fitted_size, // Size here is a request not always honored.
+ base::Bind(&CaptureMachine::DidCopyFromBackingStore, this->AsWeakPtr(),
+ start_time, target, deliver_frame_cb));
+ }
+}
+
+bool CaptureMachine::StartObservingWebContents(int initial_render_process_id,
+ int initial_render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Look-up the RenderViewHost and, from that, the WebContents that wraps it.
+ // If successful, begin observing the WebContents instance.
+ //
+ // Why this can be unsuccessful: The request for mirroring originates in a
+ // render process, and this request is based on the current RenderView
+ // associated with a tab. However, by the time we get up-and-running here,
+ // there have been multiple back-and-forth IPCs between processes, as well as
+ // a bit of indirection across threads. It's easily possible that, in the
+ // meantime, the original RenderView may have gone away.
+ RenderViewHost* const rvh =
+ RenderViewHost::FromID(initial_render_process_id,
+ initial_render_view_id);
+ DVLOG_IF(1, !rvh) << "RenderViewHost::FromID("
+ << initial_render_process_id << ", "
+ << initial_render_view_id << ") returned NULL.";
+ Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL);
+
+ WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
+ if (contents) {
+ contents->IncrementCapturerCount();
+ fullscreen_widget_id_ = contents->GetFullscreenWidgetRoutingID();
+ RenewFrameSubscription();
+ return true;
+ }
+
+ DVLOG(1) << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL.";
+ return false;
+}
+
+void CaptureMachine::WebContentsDestroyed(WebContents* web_contents) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ subscription_.reset();
+ web_contents->DecrementCapturerCount();
+ oracle_proxy_->ReportError();
+}
+
+RenderWidgetHost* CaptureMachine::GetTarget() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!web_contents())
+ return NULL;
+
+ RenderWidgetHost* rwh = NULL;
+ if (fullscreen_widget_id_ != MSG_ROUTING_NONE) {
+ RenderProcessHost* process = web_contents()->GetRenderProcessHost();
+ if (process)
+ rwh = RenderWidgetHost::FromID(process->GetID(), fullscreen_widget_id_);
+ } else {
+ rwh = web_contents()->GetRenderViewHost();
+ }
+
+ return rwh;
+}
+
+void CaptureMachine::DidCopyFromBackingStore(
+ const base::Time& start_time,
+ const scoped_refptr<media::VideoFrame>& target,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb,
+ bool success,
+ const SkBitmap& bitmap) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ base::Time now = base::Time::Now();
+ if (success) {
+ UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time);
+ TRACE_EVENT_ASYNC_STEP0("mirroring", "Capture", target.get(), "Render");
+ render_task_runner_->PostTask(FROM_HERE, base::Bind(
+ &RenderVideoFrame, bitmap, target,
+ base::Bind(deliver_frame_cb, start_time)));
+ } else {
+ // Capture can fail due to transient issues, so just skip this frame.
+ DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
+ deliver_frame_cb.Run(start_time, false);
+ }
+}
+
+void CaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
+ const base::Time& start_time,
+ const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
+ deliver_frame_cb,
+ bool success) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ base::Time now = base::Time::Now();
+
+ if (success) {
+ UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time);
+ } else {
+ // Capture can fail due to transient issues, so just skip this frame.
+ DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
+ }
+ deliver_frame_cb.Run(start_time, success);
+}
+
+void CaptureMachine::RenewFrameSubscription() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Always destroy the old subscription before creating a new one.
+ subscription_.reset();
+
+ RenderWidgetHost* rwh = GetTarget();
+ if (!rwh || !rwh->GetView())
+ return;
+
+ subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_,
+ base::Bind(&CaptureMachine::Capture, this->AsWeakPtr())));
+}
+
+void DeleteCaptureMachineOnUIThread(
+ scoped_ptr<CaptureMachine> capture_machine) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ capture_machine.reset();
+}
+
+} // namespace
+
+// The "meat" of the video capture implementation, which is a ref-counted class.
+// Separating this from the "shell class" WebContentsVideoCaptureDevice allows
+// safe destruction without needing to block any threads (e.g., the IO
+// BrowserThread).
+//
+// WebContentsVideoCaptureDevice::Impl manages a simple state machine and the
+// pipeline (see notes at top of this file). It times the start of successive
+// captures and facilitates the processing of each through the stages of the
+// pipeline.
+class WebContentsVideoCaptureDevice::Impl : public base::SupportsWeakPtr<Impl> {
+ public:
+
+ Impl(int render_process_id, int render_view_id);
+ virtual ~Impl();
+
+ // Asynchronous requests to change WebContentsVideoCaptureDevice::Impl state.
+ void Allocate(int width,
+ int height,
+ int frame_rate,
+ media::VideoCaptureDevice::EventHandler* consumer);
+ void Start();
+ void Stop();
+ void DeAllocate();
+
+ private:
+
+ // Flag indicating current state.
+ enum State {
+ kIdle,
+ kAllocated,
+ kCapturing,
+ kError
+ };
+
+ void TransitionStateTo(State next_state);
+
+ // Stops capturing and notifies consumer_ of an error state.
+ void Error();
+
+ // Called in response to CaptureMachine::Create that runs on the UI thread.
+ // It will assign the capture machine to the Impl class if it still exists
+ // otherwise it will post a task to delete CaptureMachine on the UI thread.
+ static void AssignCaptureMachine(
+ base::WeakPtr<WebContentsVideoCaptureDevice::Impl> impl,
+ scoped_ptr<CaptureMachine> capture_machine);
+
+ // Tracks that all activity occurs on the media stream manager's thread.
+ base::ThreadChecker thread_checker_;
+
+ // These values identify the starting view that will be captured. After
+ // capture starts, the target view IDs will change as navigation occurs, and
+ // so these values are not relevant after the initial bootstrapping.
+ const int initial_render_process_id_;
+ const int initial_render_view_id_;
+
+ // Our event handler, which gobbles the frames we capture.
+ VideoCaptureDevice::EventHandler* consumer_;
+
+ // Current lifecycle state.
+ State state_;
+
+ // A dedicated worker thread for doing image operations. Started/joined here,
+ // but used by the CaptureMachine.
+ base::Thread render_thread_;
+
+ // Tracks the CaptureMachine that's doing work on our behalf on the UI thread.
+ // This value should never be dereferenced by this class, other than to
+ // create and destroy it on the UI thread.
+ scoped_ptr<CaptureMachine> capture_machine_;
+
+ // Our thread-safe capture oracle which serves as the gateway to the video
+ // capture pipeline. Besides the WCVCD itself, it is the only component of the
+ // system with direct access to |consumer_|.
+ scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(Impl);
+};
+
+WebContentsVideoCaptureDevice::Impl::Impl(int render_process_id,
+ int render_view_id)
+ : initial_render_process_id_(render_process_id),
+ initial_render_view_id_(render_view_id),
+ consumer_(NULL),
+ state_(kIdle),
+ render_thread_("WebContentsVideo_RenderThread") {
+}
+
+void WebContentsVideoCaptureDevice::Impl::Allocate(
+ int width,
+ int height,
+ int frame_rate,
+ VideoCaptureDevice::EventHandler* consumer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != kIdle) {
+ DVLOG(1) << "Allocate() invoked when not in state Idle.";
+ return;
+ }
+
+ if (frame_rate <= 0) {
+ DVLOG(1) << "invalid frame_rate: " << frame_rate;
+ consumer->OnError();
+ return;
+ }
+
+ if (!render_thread_.Start()) {
+ DVLOG(1) << "Failed to spawn render thread.";
+ consumer->OnError();
+ return;
+ }
+
+ // Frame dimensions must each be a positive, even integer, since the consumer
+ // wants (or will convert to) YUV420.
+ width = MakeEven(width);
+ height = MakeEven(height);
+ if (width < kMinFrameWidth || height < kMinFrameHeight) {
+ DVLOG(1) << "invalid width (" << width << ") and/or height ("
+ << height << ")";
+ consumer->OnError();
+ return;
+ }
+
+ // Initialize capture settings which will be consistent for the
+ // duration of the capture.
+ media::VideoCaptureCapability settings;
+
+ settings.width = width;
+ settings.height = height;
+ settings.frame_rate = frame_rate;
+ // Note: the value of |settings.color| doesn't matter if we use only the
+ // VideoFrame based methods on |consumer|.
+ settings.color = media::VideoCaptureCapability::kI420;
+ settings.expected_capture_delay = 0;
+ settings.interlaced = false;
+
+ base::TimeDelta capture_period = base::TimeDelta::FromMicroseconds(
+ 1000000.0 / settings.frame_rate + 0.5);
+
+ consumer_ = consumer;
+ consumer_->OnFrameInfo(settings);
+ scoped_ptr<VideoCaptureOracle> oracle(
+ new VideoCaptureOracle(capture_period,
+ kAcceleratedSubscriberIsSupported));
+ oracle_proxy_ = new ThreadSafeCaptureOracle(
+ consumer_,
+ oracle.Pass());
+
+ // Allocates the CaptureMachine. The CaptureMachine will be tracking render
+ // view swapping over its lifetime, and we don't want to lose our reference to
+ // the current render view by starting over with the stale
+ // |initial_render_view_id_|.
+ DCHECK(!capture_machine_.get());
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&CaptureMachine::Create,
+ initial_render_process_id_,
+ initial_render_view_id_,
+ render_thread_.message_loop_proxy(), oracle_proxy_),
+ base::Bind(&Impl::AssignCaptureMachine, AsWeakPtr()));
+
+ TransitionStateTo(kAllocated);
+}
+
+void WebContentsVideoCaptureDevice::Impl::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != kAllocated) {
+ return;
+ }
+
+ TransitionStateTo(kCapturing);
+
+ oracle_proxy_->Start();
+}
+
+// static
+void WebContentsVideoCaptureDevice::Impl::AssignCaptureMachine(
+ base::WeakPtr<WebContentsVideoCaptureDevice::Impl> impl,
+ scoped_ptr<CaptureMachine> capture_machine) {
+ DCHECK(!impl.get() || impl->thread_checker_.CalledOnValidThread());
+
+ if (!impl.get()) {
+ // If WCVD::Impl was destroyed before we got back on it's thread and
+ // capture_machine is not NULL, then we need to return to the UI thread to
+ // safely cleanup the CaptureMachine.
+ if (capture_machine) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(
+ &DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine)));
+ return;
+ }
+ } else if (!capture_machine) {
+ impl->Error();
+ } else {
+ impl->capture_machine_ = capture_machine.Pass();
+ }
+}
+
+void WebContentsVideoCaptureDevice::Impl::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ != kCapturing) {
+ return;
+ }
+ oracle_proxy_->Stop();
+
+ TransitionStateTo(kAllocated);
+}
+
+void WebContentsVideoCaptureDevice::Impl::DeAllocate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (state_ == kCapturing) {
+ Stop();
+ }
+ if (state_ == kAllocated) {
+ // |consumer_| is about to be deleted, so we mustn't use it anymore.
+ oracle_proxy_->InvalidateConsumer();
+ consumer_ = NULL;
+ oracle_proxy_ = NULL;
+ render_thread_.Stop();
+
+ TransitionStateTo(kIdle);
+ }
+}
+
+WebContentsVideoCaptureDevice::Impl::~Impl() {
+ // There is still a capture pipeline running that is checking in with the
+ // oracle, and processing captures that are already started in flight. That
+ // pipeline must be shut down asynchronously, on the UI thread.
+ if (capture_machine_) {
+ // The task that is posted to the UI thread might not run if we are shutting
+ // down, so we transfer ownership of CaptureMachine to the closure so that
+ // it is still cleaned up when the closure is deleted.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE, base::Bind(
+ &DeleteCaptureMachineOnUIThread, base::Passed(&capture_machine_)));
+ }
+
+ DCHECK(!capture_machine_) << "Cleanup on UI thread did not happen.";
+ DCHECK(!consumer_) << "Device not DeAllocated -- possible data race.";
+ DVLOG(1) << "WebContentsVideoCaptureDevice::Impl@" << this << " destroying.";
+}
+
+void WebContentsVideoCaptureDevice::Impl::TransitionStateTo(State next_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+#ifndef NDEBUG
+ static const char* kStateNames[] = {
+ "Idle", "Allocated", "Capturing", "Error"
+ };
+ DVLOG(1) << "State change: " << kStateNames[state_]
+ << " --> " << kStateNames[next_state];
+#endif
+
+ state_ = next_state;
+}
+
+void WebContentsVideoCaptureDevice::Impl::Error() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ == kIdle)
+ return;
+
+ if (consumer_)
+ consumer_->OnError();
+
+ DeAllocate();
+ TransitionStateTo(kError);
+}
+
+WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
+ const media::VideoCaptureDevice::Name& name,
+ int render_process_id,
+ int render_view_id)
+ : device_name_(name),
+ impl_(new WebContentsVideoCaptureDevice::Impl(render_process_id,
+ render_view_id)) {}
+
+WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
+ DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
+}
+
+// static
+media::VideoCaptureDevice* WebContentsVideoCaptureDevice::Create(
+ const std::string& device_id) {
+ // Parse device_id into render_process_id and render_view_id.
+ int render_process_id = -1;
+ int render_view_id = -1;
+ if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(device_id,
+ &render_process_id,
+ &render_view_id))
+ return NULL;
+
+ std::string device_name;
+ base::SStringPrintf(&device_name,
+ "WebContents[%.*s]",
+ static_cast<int>(device_id.size()), device_id.data());
+ return new WebContentsVideoCaptureDevice(
+ media::VideoCaptureDevice::Name(device_name, device_id),
+ render_process_id, render_view_id);
+}
+
+void WebContentsVideoCaptureDevice::Allocate(
+ const media::VideoCaptureCapability& capture_format,
+ VideoCaptureDevice::EventHandler* observer) {
+ DVLOG(1) << "Allocating " << capture_format.width << "x"
+ << capture_format.height;
+ impl_->Allocate(capture_format.width,
+ capture_format.height,
+ capture_format.frame_rate,
+ observer);
+}
+
+void WebContentsVideoCaptureDevice::Start() {
+ impl_->Start();
+}
+
+void WebContentsVideoCaptureDevice::Stop() {
+ impl_->Stop();
+}
+
+void WebContentsVideoCaptureDevice::DeAllocate() {
+ impl_->DeAllocate();
+}
+
+const media::VideoCaptureDevice::Name&
+WebContentsVideoCaptureDevice::device_name() {
+ return device_name_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.h b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.h
new file mode 100644
index 00000000000..94ac0680f6f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device.h
@@ -0,0 +1,73 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_VIDEO_CAPTURE_DEVICE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_VIDEO_CAPTURE_DEVICE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "media/video/capture/video_capture_device.h"
+
+namespace content {
+
+class RenderWidgetHost;
+
+// A virtualized VideoCaptureDevice that mirrors the displayed contents of a
+// tab (accessed via its associated WebContents instance), producing a stream of
+// video frames.
+//
+// An instance is created by providing a device_id. The device_id contains the
+// routing ID for a RenderViewHost, and from the RenderViewHost instance, a
+// reference to its associated WebContents instance is acquired. From then on,
+// WebContentsVideoCaptureDevice will capture from whatever render view is
+// currently associated with that WebContents instance. This allows the
+// underlying render view to be swapped out (e.g., due to navigation or
+// crashes/reloads), without any interruption in capturing.
+class CONTENT_EXPORT WebContentsVideoCaptureDevice
+ : public media::VideoCaptureDevice {
+ public:
+ // Construct from a |device_id| string of the form:
+ // "virtual-media-stream://render_process_id:render_view_id", where
+ // |render_process_id| and |render_view_id| are decimal integers.
+ // |destroy_cb| is invoked on an outside thread once all outstanding objects
+ // are completely destroyed -- this will be some time after the
+ // WebContentsVideoCaptureDevice is itself deleted.
+ // TODO(miu): Passing a destroy callback suggests needing to revisit the
+ // design philosophy of an asynchronous DeAllocate(). http://crbug.com/158641
+ static media::VideoCaptureDevice* Create(const std::string& device_id);
+
+ virtual ~WebContentsVideoCaptureDevice();
+
+ // VideoCaptureDevice implementation.
+ virtual void Allocate(const media::VideoCaptureCapability& capture_format,
+ VideoCaptureDevice::EventHandler* observer) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void DeAllocate() OVERRIDE;
+
+ // Note: The following is just a pass-through of the device_id provided to the
+ // constructor. It does not change when the content of the page changes
+ // (e.g., due to navigation), or when the underlying RenderView is
+ // swapped-out.
+ virtual const Name& device_name() OVERRIDE;
+
+ private:
+ class Impl;
+
+ WebContentsVideoCaptureDevice(const Name& name,
+ int render_process_id,
+ int render_view_id);
+
+ Name device_name_;
+ const scoped_ptr<Impl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsVideoCaptureDevice);
+};
+
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEB_CONTENTS_VIDEO_CAPTURE_DEVICE_H_
diff --git a/chromium/content/browser/renderer_host/media/web_contents_video_capture_device_unittest.cc b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device_unittest.cc
new file mode 100644
index 00000000000..d88d553fa73
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/web_contents_video_capture_device_unittest.cc
@@ -0,0 +1,797 @@
+// 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 "content/browser/renderer_host/media/web_contents_video_capture_device.h"
+
+#include "base/bind_helpers.h"
+#include "base/debug/debugger.h"
+#include "base/run_loop.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
+#include "content/browser/renderer_host/media/video_capture_oracle.h"
+#include "content/browser/renderer_host/media/web_contents_capture_util.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "content/test/test_web_contents.h"
+#include "media/base/video_util.h"
+#include "media/base/yuv_convert.h"
+#include "media/video/capture/video_capture_types.h"
+#include "skia/ext/platform_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace content {
+namespace {
+
+const int kTestWidth = 320;
+const int kTestHeight = 240;
+const int kTestFramesPerSecond = 20;
+const SkColor kNothingYet = 0xdeadbeef;
+const SkColor kNotInterested = ~kNothingYet;
+
+void DeadlineExceeded(base::Closure quit_closure) {
+ if (!base::debug::BeingDebugged()) {
+ quit_closure.Run();
+ FAIL() << "Deadline exceeded while waiting, quitting";
+ } else {
+ LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't "
+ << "attached.";
+ }
+}
+
+void RunCurrentLoopWithDeadline() {
+ base::Timer deadline(false, false);
+ deadline.Start(FROM_HERE, TestTimeouts::action_max_timeout(), base::Bind(
+ &DeadlineExceeded, base::MessageLoop::current()->QuitClosure()));
+ base::MessageLoop::current()->Run();
+ deadline.Stop();
+}
+
+SkColor ConvertRgbToYuv(SkColor rgb) {
+ uint8 yuv[3];
+ media::ConvertRGB32ToYUV(reinterpret_cast<uint8*>(&rgb),
+ yuv, yuv + 1, yuv + 2, 1, 1, 1, 1, 1);
+ return SkColorSetRGB(yuv[0], yuv[1], yuv[2]);
+}
+
+// Thread-safe class that controls the source pattern to be captured by the
+// system under test. The lifetime of this class is greater than the lifetime
+// of all objects that reference it, so it does not need to be reference
+// counted.
+class CaptureTestSourceController {
+ public:
+
+ CaptureTestSourceController()
+ : color_(SK_ColorMAGENTA),
+ copy_result_size_(kTestWidth, kTestHeight),
+ can_copy_to_video_frame_(false),
+ use_frame_subscriber_(false) {}
+
+ void SetSolidColor(SkColor color) {
+ base::AutoLock guard(lock_);
+ color_ = color;
+ }
+
+ SkColor GetSolidColor() {
+ base::AutoLock guard(lock_);
+ return color_;
+ }
+
+ void SetCopyResultSize(int width, int height) {
+ base::AutoLock guard(lock_);
+ copy_result_size_ = gfx::Size(width, height);
+ }
+
+ gfx::Size GetCopyResultSize() {
+ base::AutoLock guard(lock_);
+ return copy_result_size_;
+ }
+
+ void SignalCopy() {
+ // TODO(nick): This actually should always be happening on the UI thread.
+ base::AutoLock guard(lock_);
+ if (!copy_done_.is_null()) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_);
+ copy_done_.Reset();
+ }
+ }
+
+ void SetCanCopyToVideoFrame(bool value) {
+ base::AutoLock guard(lock_);
+ can_copy_to_video_frame_ = value;
+ }
+
+ bool CanCopyToVideoFrame() {
+ base::AutoLock guard(lock_);
+ return can_copy_to_video_frame_;
+ }
+
+ void SetUseFrameSubscriber(bool value) {
+ base::AutoLock guard(lock_);
+ use_frame_subscriber_ = value;
+ }
+
+ bool CanUseFrameSubscriber() {
+ base::AutoLock guard(lock_);
+ return use_frame_subscriber_;
+ }
+
+ void WaitForNextCopy() {
+ {
+ base::AutoLock guard(lock_);
+ copy_done_ = base::MessageLoop::current()->QuitClosure();
+ }
+
+ RunCurrentLoopWithDeadline();
+ }
+
+ private:
+ base::Lock lock_; // Guards changes to all members.
+ SkColor color_;
+ gfx::Size copy_result_size_;
+ bool can_copy_to_video_frame_;
+ bool use_frame_subscriber_;
+ base::Closure copy_done_;
+
+ DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController);
+};
+
+// A stub implementation which returns solid-color bitmaps in calls to
+// CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame
+// readback path to be switched on and off. The behavior is controlled by a
+// CaptureTestSourceController.
+class CaptureTestView : public TestRenderWidgetHostView {
+ public:
+ explicit CaptureTestView(RenderWidgetHostImpl* rwh,
+ CaptureTestSourceController* controller)
+ : TestRenderWidgetHostView(rwh),
+ controller_(controller) {}
+
+ virtual ~CaptureTestView() {}
+
+ // TestRenderWidgetHostView overrides.
+ virtual gfx::Rect GetViewBounds() const OVERRIDE {
+ return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight);
+ }
+
+ virtual bool CanCopyToVideoFrame() const OVERRIDE {
+ return controller_->CanCopyToVideoFrame();
+ }
+
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE {
+ SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
+ media::FillYUV(
+ target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
+ callback.Run(true);
+ controller_->SignalCopy();
+ }
+
+ virtual void BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) OVERRIDE {
+ subscriber_.reset(subscriber.release());
+ }
+
+ virtual void EndFrameSubscription() OVERRIDE {
+ subscriber_.reset();
+ }
+
+ // Simulate a compositor paint event for our subscriber.
+ void SimulateUpdate() {
+ const base::Time present_time = base::Time::Now();
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
+ scoped_refptr<media::VideoFrame> target;
+ if (subscriber_ && subscriber_->ShouldCaptureFrame(present_time,
+ &target, &callback)) {
+ SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
+ media::FillYUV(
+ target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(callback, present_time, true));
+ controller_->SignalCopy();
+ }
+ }
+
+ private:
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_;
+ CaptureTestSourceController* const controller_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView);
+};
+
+#if defined(COMPILER_MSVC)
+// MSVC warns on diamond inheritance. See comment for same warning on
+// RenderViewHostImpl.
+#pragma warning(push)
+#pragma warning(disable: 4250)
+#endif
+
+// A stub implementation which returns solid-color bitmaps in calls to
+// CopyFromBackingStore(). The behavior is controlled by a
+// CaptureTestSourceController.
+class CaptureTestRenderViewHost : public TestRenderViewHost {
+ public:
+ CaptureTestRenderViewHost(SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out,
+ CaptureTestSourceController* controller)
+ : TestRenderViewHost(instance, delegate, widget_delegate, routing_id,
+ main_frame_routing_id, swapped_out),
+ controller_(controller) {
+ // Override the default view installed by TestRenderViewHost; we need
+ // our special subclass which has mocked-out tab capture support.
+ RenderWidgetHostView* old_view = GetView();
+ SetView(new CaptureTestView(this, controller));
+ delete old_view;
+ }
+
+ // TestRenderViewHost overrides.
+ virtual void CopyFromBackingStore(
+ const gfx::Rect& src_rect,
+ const gfx::Size& accelerated_dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE {
+ gfx::Size size = controller_->GetCopyResultSize();
+ SkColor color = controller_->GetSolidColor();
+
+ // Although it's not necessary, use a PlatformBitmap here (instead of a
+ // regular SkBitmap) to exercise possible threading issues.
+ skia::PlatformBitmap output;
+ EXPECT_TRUE(output.Allocate(size.width(), size.height(), false));
+ {
+ SkAutoLockPixels locker(output.GetBitmap());
+ output.GetBitmap().eraseColor(color);
+ }
+ callback.Run(true, output.GetBitmap());
+ controller_->SignalCopy();
+ }
+
+ private:
+ CaptureTestSourceController* controller_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost);
+};
+
+#if defined(COMPILER_MSVC)
+// Re-enable warning 4250
+#pragma warning(pop)
+#endif
+
+class CaptureTestRenderViewHostFactory : public RenderViewHostFactory {
+ public:
+ explicit CaptureTestRenderViewHostFactory(
+ CaptureTestSourceController* controller) : controller_(controller) {
+ RegisterFactory(this);
+ }
+
+ virtual ~CaptureTestRenderViewHostFactory() {
+ UnregisterFactory();
+ }
+
+ // RenderViewHostFactory implementation.
+ virtual RenderViewHost* CreateRenderViewHost(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out) OVERRIDE {
+ return new CaptureTestRenderViewHost(instance, delegate, widget_delegate,
+ routing_id, main_frame_routing_id,
+ swapped_out, controller_);
+ }
+ private:
+ CaptureTestSourceController* controller_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory);
+};
+
+// A stub consumer of captured video frames, which checks the output of
+// WebContentsVideoCaptureDevice.
+class StubConsumer : public media::VideoCaptureDevice::EventHandler {
+ public:
+ StubConsumer()
+ : error_encountered_(false),
+ wait_color_yuv_(0xcafe1950) {
+ buffer_pool_ =
+ new VideoCaptureBufferPool(kTestWidth * kTestHeight * 3 / 2, 2);
+ EXPECT_TRUE(buffer_pool_->Allocate());
+ }
+ virtual ~StubConsumer() {}
+
+ void QuitIfConditionMet(SkColor color) {
+ base::AutoLock guard(lock_);
+
+ if (wait_color_yuv_ == color || error_encountered_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ void WaitForNextColor(SkColor expected_color) {
+ {
+ base::AutoLock guard(lock_);
+ wait_color_yuv_ = ConvertRgbToYuv(expected_color);
+ error_encountered_ = false;
+ }
+ RunCurrentLoopWithDeadline();
+ {
+ base::AutoLock guard(lock_);
+ ASSERT_FALSE(error_encountered_);
+ }
+ }
+
+ void WaitForError() {
+ {
+ base::AutoLock guard(lock_);
+ wait_color_yuv_ = kNotInterested;
+ error_encountered_ = false;
+ }
+ RunCurrentLoopWithDeadline();
+ {
+ base::AutoLock guard(lock_);
+ ASSERT_TRUE(error_encountered_);
+ }
+ }
+
+ bool HasError() {
+ base::AutoLock guard(lock_);
+ return error_encountered_;
+ }
+
+ virtual scoped_refptr<media::VideoFrame> ReserveOutputBuffer() OVERRIDE {
+ return buffer_pool_->ReserveI420VideoFrame(gfx::Size(kTestWidth,
+ kTestHeight),
+ 0);
+ }
+
+ virtual void OnIncomingCapturedFrame(
+ const uint8* data,
+ int length,
+ base::Time timestamp,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) OVERRIDE {
+ FAIL();
+ }
+
+ virtual void OnIncomingCapturedVideoFrame(
+ const scoped_refptr<media::VideoFrame>& frame,
+ base::Time timestamp) OVERRIDE {
+ EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), frame->coded_size());
+ EXPECT_EQ(media::VideoFrame::I420, frame->format());
+ EXPECT_LE(
+ 0,
+ buffer_pool_->RecognizeReservedBuffer(frame->shared_memory_handle()));
+ uint8 yuv[3];
+ for (int plane = 0; plane < 3; ++plane) {
+ yuv[plane] = frame->data(plane)[0];
+ }
+ // TODO(nick): We just look at the first pixel presently, because if
+ // the analysis is too slow, the backlog of frames will grow without bound
+ // and trouble erupts. http://crbug.com/174519
+ PostColorOrError(SkColorSetRGB(yuv[0], yuv[1], yuv[2]));
+ }
+
+ void PostColorOrError(SkColor new_color) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &StubConsumer::QuitIfConditionMet, base::Unretained(this), new_color));
+ }
+
+ virtual void OnError() OVERRIDE {
+ {
+ base::AutoLock guard(lock_);
+ error_encountered_ = true;
+ }
+ PostColorOrError(kNothingYet);
+ }
+
+ virtual void OnFrameInfo(const media::VideoCaptureCapability& info) OVERRIDE {
+ EXPECT_EQ(kTestWidth, info.width);
+ EXPECT_EQ(kTestHeight, info.height);
+ EXPECT_EQ(kTestFramesPerSecond, info.frame_rate);
+ EXPECT_EQ(media::VideoCaptureCapability::kI420, info.color);
+ }
+
+ private:
+ base::Lock lock_;
+ bool error_encountered_;
+ SkColor wait_color_yuv_;
+ scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(StubConsumer);
+};
+
+// Test harness that sets up a minimal environment with necessary stubs.
+class WebContentsVideoCaptureDeviceTest : public testing::Test {
+ public:
+ // This is public because C++ method pointer scoping rules are silly and make
+ // this hard to use with Bind().
+ void ResetWebContents() {
+ web_contents_.reset();
+ }
+
+ protected:
+ virtual void SetUp() {
+ // TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be
+ // eliminated here, if only we could use RenderViewHostTestHarness. The
+ // catch is that we need our TestRenderViewHost to support a
+ // CopyFromBackingStore operation that we control. To accomplish that,
+ // either RenderViewHostTestHarness would have to support installing a
+ // custom RenderViewHostFactory, or else we implant some kind of delegated
+ // CopyFromBackingStore functionality into TestRenderViewHost itself.
+
+ render_process_host_factory_.reset(new MockRenderProcessHostFactory());
+ // Create our (self-registering) RVH factory, so that when we create a
+ // WebContents, it in turn creates CaptureTestRenderViewHosts.
+ render_view_host_factory_.reset(
+ new CaptureTestRenderViewHostFactory(&controller_));
+
+ browser_context_.reset(new TestBrowserContext());
+
+ scoped_refptr<SiteInstance> site_instance =
+ SiteInstance::Create(browser_context_.get());
+ SiteInstanceImpl::set_render_process_host_factory(
+ render_process_host_factory_.get());
+ web_contents_.reset(
+ TestWebContents::Create(browser_context_.get(), site_instance.get()));
+
+ // This is actually a CaptureTestRenderViewHost.
+ RenderWidgetHostImpl* rwh =
+ RenderWidgetHostImpl::From(web_contents_->GetRenderViewHost());
+
+ std::string device_id =
+ WebContentsCaptureUtil::AppendWebContentsDeviceScheme(
+ base::StringPrintf("%d:%d", rwh->GetProcess()->GetID(),
+ rwh->GetRoutingID()));
+
+ device_.reset(WebContentsVideoCaptureDevice::Create(device_id));
+
+ base::RunLoop().RunUntilIdle();
+ }
+
+ virtual void TearDown() {
+ // Tear down in opposite order of set-up.
+
+ // The device is destroyed asynchronously, and will notify the
+ // CaptureTestSourceController when it finishes destruction.
+ // Trigger this, and wait.
+ if (device_) {
+ device_->DeAllocate();
+ device_.reset();
+ }
+
+ base::RunLoop().RunUntilIdle();
+
+ // Destroy the browser objects.
+ web_contents_.reset();
+ browser_context_.reset();
+
+ base::RunLoop().RunUntilIdle();
+
+ SiteInstanceImpl::set_render_process_host_factory(NULL);
+ render_view_host_factory_.reset();
+ render_process_host_factory_.reset();
+ }
+
+ // Accessors.
+ CaptureTestSourceController* source() { return &controller_; }
+ media::VideoCaptureDevice* device() { return device_.get(); }
+ StubConsumer* consumer() { return &consumer_; }
+
+ void SimulateDrawEvent() {
+ if (source()->CanUseFrameSubscriber()) {
+ // Print
+ CaptureTestView* test_view = static_cast<CaptureTestView*>(
+ web_contents_->GetRenderViewHost()->GetView());
+ test_view->SimulateUpdate();
+ } else {
+ // Simulate a non-accelerated paint.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
+ Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()),
+ NotificationService::NoDetails());
+ }
+ }
+
+ void DestroyVideoCaptureDevice() { device_.reset(); }
+
+ private:
+ // The consumer is the ultimate recipient of captured pixel data.
+ StubConsumer consumer_;
+
+ // The controller controls which pixel patterns to produce.
+ CaptureTestSourceController controller_;
+
+ // Self-registering RenderProcessHostFactory.
+ scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
+
+ // Creates capture-capable RenderViewHosts whose pixel content production is
+ // under the control of |controller_|.
+ scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_;
+
+ // A mocked-out browser and tab.
+ scoped_ptr<TestBrowserContext> browser_context_;
+ scoped_ptr<WebContents> web_contents_;
+
+ // Finally, the WebContentsVideoCaptureDevice under test.
+ scoped_ptr<media::VideoCaptureDevice> device_;
+
+ TestBrowserThreadBundle thread_bundle_;
+};
+
+TEST_F(WebContentsVideoCaptureDeviceTest, InvalidInitialWebContentsError) {
+ // Before the installs itself on the UI thread up to start capturing, we'll
+ // delete the web contents. This should trigger an error which can happen in
+ // practice; we should be able to recover gracefully.
+ ResetWebContents();
+
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+ device()->Start();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForError());
+ device()->DeAllocate();
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest, WebContentsDestroyed) {
+ // We'll simulate the tab being closed after the capture pipeline is up and
+ // running.
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+ device()->Start();
+
+ // Do one capture to prove
+ source()->SetSolidColor(SK_ColorRED);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED));
+
+ base::RunLoop().RunUntilIdle();
+
+ // Post a task to close the tab. We should see an error reported to the
+ // consumer.
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebContentsVideoCaptureDeviceTest::ResetWebContents,
+ base::Unretained(this)));
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForError());
+ device()->DeAllocate();
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest,
+ StopDeviceBeforeCaptureMachineCreation) {
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+ device()->Start();
+ // Make a point of not running the UI messageloop here.
+ device()->Stop();
+ device()->DeAllocate();
+ DestroyVideoCaptureDevice();
+
+ // Currently, there should be CreateCaptureMachineOnUIThread() and
+ // DestroyCaptureMachineOnUIThread() tasks pending on the current (UI) message
+ // loop. These should both succeed without crashing, and the machine should
+ // wind up in the idle state.
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest, StopWithRendererWorkToDo) {
+ // Set up the test to use RGB copies and an normal
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(false);
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+
+ device()->Start();
+ // Make a point of not running the UI messageloop here.
+ // TODO(ajwong): Why do we care?
+ base::RunLoop().RunUntilIdle();
+
+ for (int i = 0; i < 10; ++i)
+ SimulateDrawEvent();
+
+ device()->Stop();
+ device()->DeAllocate();
+ // Currently, there should be CreateCaptureMachineOnUIThread() and
+ // DestroyCaptureMachineOnUIThread() tasks pending on the current message
+ // loop. These should both succeed without crashing, and the machine should
+ // wind up in the idle state.
+ ASSERT_FALSE(consumer()->HasError());
+ base::RunLoop().RunUntilIdle();
+ ASSERT_FALSE(consumer()->HasError());
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest, DeviceRestart) {
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+ device()->Start();
+ base::RunLoop().RunUntilIdle();
+ source()->SetSolidColor(SK_ColorRED);
+ SimulateDrawEvent();
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED));
+ SimulateDrawEvent();
+ SimulateDrawEvent();
+ source()->SetSolidColor(SK_ColorGREEN);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorGREEN));
+ device()->Stop();
+
+ // Device is stopped, but content can still be animating.
+ SimulateDrawEvent();
+ SimulateDrawEvent();
+ base::RunLoop().RunUntilIdle();
+
+ device()->Start();
+ source()->SetSolidColor(SK_ColorBLUE);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorBLUE));
+ source()->SetSolidColor(SK_ColorYELLOW);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorYELLOW));
+ device()->DeAllocate();
+}
+
+// The "happy case" test. No scaling is needed, so we should be able to change
+// the picture emitted from the source and expect to see each delivered to the
+// consumer. The test will alternate between the three capture paths, simulating
+// falling in and out of accelerated compositing.
+TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) {
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+
+ device()->Start();
+
+ for (int i = 0; i < 6; i++) {
+ const char* name = NULL;
+ switch (i % 3) {
+ case 0:
+ source()->SetCanCopyToVideoFrame(true);
+ source()->SetUseFrameSubscriber(false);
+ name = "VideoFrame";
+ break;
+ case 1:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(true);
+ name = "Subscriber";
+ break;
+ case 2:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(false);
+ name = "SkBitmap";
+ break;
+ default:
+ FAIL();
+ }
+
+ SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
+
+ source()->SetSolidColor(SK_ColorRED);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED));
+
+ source()->SetSolidColor(SK_ColorGREEN);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorGREEN));
+
+ source()->SetSolidColor(SK_ColorBLUE);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorBLUE));
+
+ source()->SetSolidColor(SK_ColorBLACK);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorBLACK));
+ }
+ device()->DeAllocate();
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest, RejectsInvalidAllocateParams) {
+ media::VideoCaptureCapability capture_format(
+ 1280,
+ 720,
+ -2,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&media::VideoCaptureDevice::Allocate,
+ base::Unretained(device()),
+ capture_format,
+ consumer()));
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForError());
+}
+
+TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) {
+ media::VideoCaptureCapability capture_format(
+ kTestWidth,
+ kTestHeight,
+ kTestFramesPerSecond,
+ media::VideoCaptureCapability::kI420,
+ 0,
+ false,
+ media::ConstantResolutionVideoCaptureDevice);
+ device()->Allocate(capture_format, consumer());
+
+ // 1x1 is too small to process; we intend for this to result in an error.
+ source()->SetCopyResultSize(1, 1);
+ source()->SetSolidColor(SK_ColorRED);
+ device()->Start();
+
+ // These frames ought to be dropped during the Render stage. Let
+ // several captures to happen.
+ ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
+ ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
+ ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
+ ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
+ ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
+
+ // Now push some good frames through; they should be processed normally.
+ source()->SetCopyResultSize(kTestWidth, kTestHeight);
+ source()->SetSolidColor(SK_ColorGREEN);
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorGREEN));
+ source()->SetSolidColor(SK_ColorRED);
+ ASSERT_NO_FATAL_FAILURE(consumer()->WaitForNextColor(SK_ColorRED));
+
+ device()->Stop();
+ device()->DeAllocate();
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.cc b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.cc
new file mode 100644
index 00000000000..0230b26eeaa
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.cc
@@ -0,0 +1,87 @@
+// 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 "content/browser/renderer_host/media/webrtc_identity_service_host.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/media/webrtc_identity_store.h"
+#include "content/common/media/webrtc_identity_messages.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+WebRTCIdentityServiceHost::WebRTCIdentityServiceHost(
+ int renderer_process_id,
+ WebRTCIdentityStore* identity_store)
+ : renderer_process_id_(renderer_process_id),
+ identity_store_(identity_store) {}
+
+WebRTCIdentityServiceHost::~WebRTCIdentityServiceHost() {
+ if (!cancel_callback_.is_null())
+ cancel_callback_.Run();
+}
+
+bool WebRTCIdentityServiceHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(WebRTCIdentityServiceHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(WebRTCIdentityMsg_RequestIdentity, OnRequestIdentity)
+ IPC_MESSAGE_HANDLER(WebRTCIdentityMsg_CancelRequest, OnCancelRequest)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void WebRTCIdentityServiceHost::OnRequestIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name) {
+ if (!cancel_callback_.is_null()) {
+ DLOG(WARNING)
+ << "Request rejected because the previous request has not finished.";
+ SendErrorMessage(net::ERR_INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanAccessCookiesForOrigin(renderer_process_id_, origin)) {
+ DLOG(WARNING) << "Request rejected because origin access is denied.";
+ SendErrorMessage(net::ERR_ACCESS_DENIED);
+ return;
+ }
+
+ cancel_callback_ = identity_store_->RequestIdentity(
+ origin,
+ identity_name,
+ common_name,
+ base::Bind(&WebRTCIdentityServiceHost::OnComplete,
+ base::Unretained(this)));
+ if (cancel_callback_.is_null()) {
+ SendErrorMessage(net::ERR_UNEXPECTED);
+ }
+}
+
+void WebRTCIdentityServiceHost::OnCancelRequest() {
+ base::ResetAndReturn(&cancel_callback_).Run();
+}
+
+void WebRTCIdentityServiceHost::OnComplete(int status,
+ const std::string& certificate,
+ const std::string& private_key) {
+ cancel_callback_.Reset();
+ if (status == net::OK) {
+ Send(new WebRTCIdentityHostMsg_IdentityReady(certificate, private_key));
+ } else {
+ SendErrorMessage(status);
+ }
+}
+
+void WebRTCIdentityServiceHost::SendErrorMessage(int error) {
+ Send(new WebRTCIdentityHostMsg_RequestFailed(error));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.h b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.h
new file mode 100644
index 00000000000..3676223fe86
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host.h
@@ -0,0 +1,63 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEBRTC_IDENTITY_SERVICE_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEBRTC_IDENTITY_SERVICE_HOST_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+
+class GURL;
+
+namespace content {
+
+class WebRTCIdentityStore;
+
+// This class is the host for WebRTCIdentityService in the browser process.
+// It converts the IPC messages for requesting a WebRTC DTLS identity and
+// cancelling a pending request into calls of WebRTCIdentityStore. It also sends
+// the request result back to the renderer through IPC.
+// Only one outstanding request is allowed per renderer at a time. If a second
+// request is made before the first one completes, an IPC with error
+// ERR_INSUFFICIENT_RESOURCES will be sent back to the renderer.
+class CONTENT_EXPORT WebRTCIdentityServiceHost : public BrowserMessageFilter {
+ public:
+ explicit WebRTCIdentityServiceHost(int renderer_process_id,
+ WebRTCIdentityStore* identity_store);
+
+ protected:
+ virtual ~WebRTCIdentityServiceHost();
+
+ // content::BrowserMessageFilter override.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ // See WebRTCIdentityStore for the meaning of the parameters.
+ void OnComplete(int status,
+ const std::string& certificate,
+ const std::string& private_key);
+
+ // See WebRTCIdentityStore for the meaning of the parameters.
+ void OnRequestIdentity(const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name);
+
+ void OnCancelRequest();
+
+ void SendErrorMessage(int error);
+
+ int renderer_process_id_;
+ base::Closure cancel_callback_;
+ WebRTCIdentityStore* identity_store_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityServiceHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_WEBRTC_IDENTITY_SERVICE_HOST_H_
diff --git a/chromium/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc
new file mode 100644
index 00000000000..341378d3c7b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc
@@ -0,0 +1,187 @@
+// 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 <deque>
+
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/media/webrtc_identity_store.h"
+#include "content/browser/renderer_host/media/webrtc_identity_service_host.h"
+#include "content/common/media/webrtc_identity_messages.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "ipc/ipc_message.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+static const char FAKE_ORIGIN[] = "http://fake.com";
+static const char FAKE_IDENTITY_NAME[] = "fake identity";
+static const char FAKE_COMMON_NAME[] = "fake common name";
+static const char FAKE_CERTIFICATE[] = "fake cert";
+static const char FAKE_PRIVATE_KEY[] = "fake private key";
+static const int FAKE_ERROR = 100;
+static const int FAKE_RENDERER_ID = 10;
+
+class MockWebRTCIdentityStore : public WebRTCIdentityStore {
+ public:
+ MockWebRTCIdentityStore() : WebRTCIdentityStore(base::FilePath(), NULL) {}
+
+ virtual base::Closure RequestIdentity(
+ const GURL& origin,
+ const std::string& identity_name,
+ const std::string& common_name,
+ const CompletionCallback& callback) OVERRIDE {
+ EXPECT_TRUE(callback_.is_null());
+
+ callback_ = callback;
+ return base::Bind(&MockWebRTCIdentityStore::OnCancel,
+ base::Unretained(this));
+ }
+
+ bool HasPendingRequest() const { return !callback_.is_null(); }
+
+ void RunCompletionCallback(int error,
+ const std::string& cert,
+ const std::string& key) {
+ callback_.Run(error, cert, key);
+ callback_.Reset();
+ }
+
+ private:
+ virtual ~MockWebRTCIdentityStore() {}
+
+ void OnCancel() { callback_.Reset(); }
+
+ CompletionCallback callback_;
+};
+
+class WebRTCIdentityServiceHostForTest : public WebRTCIdentityServiceHost {
+ public:
+ explicit WebRTCIdentityServiceHostForTest(WebRTCIdentityStore* identity_store)
+ : WebRTCIdentityServiceHost(FAKE_RENDERER_ID, identity_store) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ policy->Add(FAKE_RENDERER_ID);
+ }
+
+ virtual bool Send(IPC::Message* message) OVERRIDE {
+ messages_.push_back(*message);
+ delete message;
+ return true;
+ }
+
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE {
+ return WebRTCIdentityServiceHost::OnMessageReceived(message,
+ message_was_ok);
+ }
+
+ IPC::Message GetLastMessage() { return messages_.back(); }
+
+ int GetNumberOfMessages() { return messages_.size(); }
+
+ void ClearMessages() { messages_.clear(); }
+
+ private:
+ virtual ~WebRTCIdentityServiceHostForTest() {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ policy->Remove(FAKE_RENDERER_ID);
+ }
+
+ std::deque<IPC::Message> messages_;
+};
+
+class WebRTCIdentityServiceHostTest : public ::testing::Test {
+ public:
+ WebRTCIdentityServiceHostTest()
+ : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
+ store_(new MockWebRTCIdentityStore()),
+ host_(new WebRTCIdentityServiceHostForTest(store_.get())) {}
+
+ void SendRequestToHost() {
+ bool ok;
+ host_->OnMessageReceived(
+ WebRTCIdentityMsg_RequestIdentity(
+ GURL(FAKE_ORIGIN), FAKE_IDENTITY_NAME, FAKE_COMMON_NAME),
+ &ok);
+ ASSERT_TRUE(ok);
+ }
+
+ void SendCancelRequestToHost() {
+ bool ok;
+ host_->OnMessageReceived(WebRTCIdentityMsg_CancelRequest(), &ok);
+ ASSERT_TRUE(ok);
+ }
+
+ void VerifyRequestFailedMessage(int error) {
+ EXPECT_EQ(1, host_->GetNumberOfMessages());
+ IPC::Message ipc = host_->GetLastMessage();
+ EXPECT_EQ(ipc.type(), WebRTCIdentityHostMsg_RequestFailed::ID);
+
+ Tuple1<int> error_in_message;
+ WebRTCIdentityHostMsg_RequestFailed::Read(&ipc, &error_in_message);
+ EXPECT_EQ(error, error_in_message.a);
+ }
+
+ void VerifyIdentityReadyMessage(const std::string& cert,
+ const std::string& key) {
+ EXPECT_EQ(1, host_->GetNumberOfMessages());
+ IPC::Message ipc = host_->GetLastMessage();
+ EXPECT_EQ(ipc.type(), WebRTCIdentityHostMsg_IdentityReady::ID);
+
+ Tuple2<std::string, std::string> identity_in_message;
+ WebRTCIdentityHostMsg_IdentityReady::Read(&ipc, &identity_in_message);
+ EXPECT_EQ(cert, identity_in_message.a);
+ EXPECT_EQ(key, identity_in_message.b);
+ }
+
+ protected:
+ TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_refptr<MockWebRTCIdentityStore> store_;
+ scoped_refptr<WebRTCIdentityServiceHostForTest> host_;
+};
+
+} // namespace
+
+TEST_F(WebRTCIdentityServiceHostTest, TestSendAndCancelRequest) {
+ SendRequestToHost();
+ EXPECT_TRUE(store_->HasPendingRequest());
+ SendCancelRequestToHost();
+ EXPECT_FALSE(store_->HasPendingRequest());
+}
+
+TEST_F(WebRTCIdentityServiceHostTest, TestOnlyOneRequestAllowed) {
+ SendRequestToHost();
+ EXPECT_TRUE(store_->HasPendingRequest());
+ EXPECT_EQ(0, host_->GetNumberOfMessages());
+ SendRequestToHost();
+
+ VerifyRequestFailedMessage(net::ERR_INSUFFICIENT_RESOURCES);
+}
+
+TEST_F(WebRTCIdentityServiceHostTest, TestOnIdentityReady) {
+ SendRequestToHost();
+ store_->RunCompletionCallback(net::OK, FAKE_CERTIFICATE, FAKE_PRIVATE_KEY);
+ VerifyIdentityReadyMessage(FAKE_CERTIFICATE, FAKE_PRIVATE_KEY);
+}
+
+TEST_F(WebRTCIdentityServiceHostTest, TestOnRequestFailed) {
+ SendRequestToHost();
+ store_->RunCompletionCallback(net::ERR_KEY_GENERATION_FAILED, "", "");
+ VerifyRequestFailedMessage(net::ERR_KEY_GENERATION_FAILED);
+}
+
+TEST_F(WebRTCIdentityServiceHostTest, TestOriginAccessDenied) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ policy->Remove(FAKE_RENDERER_ID);
+
+ SendRequestToHost();
+ VerifyRequestFailedMessage(net::ERR_ACCESS_DENIED);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/memory_benchmark_message_filter.cc b/chromium/content/browser/renderer_host/memory_benchmark_message_filter.cc
new file mode 100644
index 00000000000..d5598528940
--- /dev/null
+++ b/chromium/content/browser/renderer_host/memory_benchmark_message_filter.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 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 "content/browser/renderer_host/memory_benchmark_message_filter.h"
+
+#include "content/common/memory_benchmark_messages.h"
+
+#if defined(USE_TCMALLOC) && (defined(OS_LINUX) || defined(OS_ANDROID))
+
+#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
+
+namespace content {
+
+MemoryBenchmarkMessageFilter::MemoryBenchmarkMessageFilter() {
+}
+
+bool MemoryBenchmarkMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(MemoryBenchmarkMessageFilter,
+ message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(MemoryBenchmarkHostMsg_HeapProfilerDump,
+ OnHeapProfilerDump)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+MemoryBenchmarkMessageFilter::~MemoryBenchmarkMessageFilter() {
+}
+
+void MemoryBenchmarkMessageFilter::OnHeapProfilerDump(
+ const std::string& reason) {
+ ::HeapProfilerDump(reason.c_str());
+}
+
+} // namespace content
+
+#endif // defined(USE_TCMALLOC) && (defined(OS_LINUX) || defined(OS_ANDROID))
diff --git a/chromium/content/browser/renderer_host/memory_benchmark_message_filter.h b/chromium/content/browser/renderer_host/memory_benchmark_message_filter.h
new file mode 100644
index 00000000000..da5a6aa4b5d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/memory_benchmark_message_filter.h
@@ -0,0 +1,30 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_MEMORY_BENCHMARK_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_MEMORY_BENCHMARK_MESSAGE_FILTER_H_
+
+#include <string>
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+class MemoryBenchmarkMessageFilter : public BrowserMessageFilter {
+ public:
+ MemoryBenchmarkMessageFilter();
+
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ private:
+ virtual ~MemoryBenchmarkMessageFilter();
+
+ void OnHeapProfilerDump(const std::string& reason);
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryBenchmarkMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_MEMORY_BENCHMARK_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event.cc b/chromium/content/browser/renderer_host/native_web_keyboard_event.cc
new file mode 100644
index 00000000000..3cb2af1397b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event.cc
@@ -0,0 +1,27 @@
+// 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 "content/public/browser/native_web_keyboard_event.h"
+
+#include "ui/base/events/event_constants.h"
+
+namespace content {
+
+int GetModifiersFromNativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ int modifiers = ui::EF_NONE;
+ if (event.modifiers & NativeWebKeyboardEvent::ShiftKey)
+ modifiers |= ui::EF_SHIFT_DOWN;
+ if (event.modifiers & NativeWebKeyboardEvent::ControlKey)
+ modifiers |= ui::EF_CONTROL_DOWN;
+ if (event.modifiers & NativeWebKeyboardEvent::AltKey)
+ modifiers |= ui::EF_ALT_DOWN;
+#if defined(OS_MACOSX)
+ if (event.modifiers & NativeWebKeyboardEvent::MetaKey)
+ modifiers |= ui::EF_COMMAND_DOWN;
+#endif
+ return modifiers;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event_android.cc b/chromium/content/browser/renderer_host/native_web_keyboard_event_android.cc
new file mode 100644
index 00000000000..ad5f1f176eb
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event_android.cc
@@ -0,0 +1,75 @@
+// 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 "content/public/browser/native_web_keyboard_event.h"
+
+#include "base/android/jni_android.h"
+#include "content/browser/renderer_host/input/web_input_event_builders_android.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace {
+
+jobject NewGlobalRefForKeyEvent(jobject key_event) {
+ if (key_event == NULL) return NULL;
+ return base::android::AttachCurrentThread()->NewGlobalRef(key_event);
+}
+
+void DeleteGlobalRefForKeyEvent(jobject key_event) {
+ if (key_event != NULL)
+ base::android::AttachCurrentThread()->DeleteGlobalRef(key_event);
+}
+
+}
+
+namespace content {
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent()
+ : os_event(NULL),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ WebKit::WebInputEvent::Type type,
+ int modifiers, double time_secs, int keycode, int unicode_character,
+ bool is_system_key)
+ : WebKeyboardEvent(WebKeyboardEventBuilder::Build(
+ type, modifiers, time_secs, keycode, unicode_character,
+ is_system_key)) {
+ os_event = NULL;
+ skip_in_browser = false;
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ jobject android_key_event, WebKit::WebInputEvent::Type type,
+ int modifiers, double time_secs, int keycode, int unicode_character,
+ bool is_system_key)
+ : WebKeyboardEvent(WebKeyboardEventBuilder::Build(
+ type, modifiers, time_secs, keycode, unicode_character,
+ is_system_key)) {
+ os_event = NewGlobalRefForKeyEvent(android_key_event);
+ skip_in_browser = false;
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& other)
+ : WebKeyboardEvent(other),
+ os_event(NewGlobalRefForKeyEvent(other.os_event)),
+ skip_in_browser(other.skip_in_browser) {
+}
+
+NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(
+ const NativeWebKeyboardEvent& other) {
+ WebKeyboardEvent::operator=(other);
+
+ os_event = NewGlobalRefForKeyEvent(other.os_event);
+ skip_in_browser = other.skip_in_browser;
+
+ return *this;
+}
+
+NativeWebKeyboardEvent::~NativeWebKeyboardEvent() {
+ DeleteGlobalRefForKeyEvent(os_event);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event_aura.cc b/chromium/content/browser/renderer_host/native_web_keyboard_event_aura.cc
new file mode 100644
index 00000000000..1658d8d3dc2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event_aura.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/native_web_keyboard_event.h"
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/web_input_event_aura.h"
+#include "ui/base/events/event.h"
+
+namespace {
+
+// We need to copy |os_event| in NativeWebKeyboardEvent because it is
+// queued in RenderWidgetHost and may be passed and used
+// RenderViewHostDelegate::HandledKeybardEvent after the original aura
+// event is destroyed.
+ui::Event* CopyEvent(ui::Event* event) {
+ return event ? static_cast<ui::KeyEvent*>(event)->Copy() : NULL;
+}
+
+int EventFlagsToWebInputEventModifiers(int flags) {
+ return
+ (flags & ui::EF_SHIFT_DOWN ? WebKit::WebInputEvent::ShiftKey : 0) |
+ (flags & ui::EF_CONTROL_DOWN ? WebKit::WebInputEvent::ControlKey : 0) |
+ (flags & ui::EF_CAPS_LOCK_DOWN ? WebKit::WebInputEvent::CapsLockOn : 0) |
+ (flags & ui::EF_ALT_DOWN ? WebKit::WebInputEvent::AltKey : 0);
+}
+
+} // namespace
+
+using WebKit::WebKeyboardEvent;
+
+namespace content {
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent()
+ : os_event(NULL),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(gfx::NativeEvent native_event)
+ : WebKeyboardEvent(MakeWebKeyboardEvent(
+ static_cast<ui::KeyEvent*>(native_event))),
+ os_event(CopyEvent(native_event)),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& other)
+ : WebKeyboardEvent(other),
+ os_event(CopyEvent(other.os_event)),
+ skip_in_browser(other.skip_in_browser) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ ui::EventType key_event_type,
+ bool is_char,
+ wchar_t character,
+ int state,
+ double time_stamp_seconds)
+ : os_event(NULL),
+ skip_in_browser(false) {
+ switch (key_event_type) {
+ case ui::ET_KEY_PRESSED:
+ type = is_char ? WebKit::WebInputEvent::Char :
+ WebKit::WebInputEvent::RawKeyDown;
+ break;
+ case ui::ET_KEY_RELEASED:
+ type = WebKit::WebInputEvent::KeyUp;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ modifiers = EventFlagsToWebInputEventModifiers(state);
+ timeStampSeconds = time_stamp_seconds;
+ windowsKeyCode = character;
+ nativeKeyCode = character;
+ text[0] = character;
+ unmodifiedText[0] = character;
+ isSystemKey = (state & ui::EF_ALT_DOWN) != 0;
+ setKeyIdentifierFromWindowsKeyCode();
+}
+
+NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(
+ const NativeWebKeyboardEvent& other) {
+ WebKeyboardEvent::operator=(other);
+ delete os_event;
+ os_event = CopyEvent(other.os_event);
+ skip_in_browser = other.skip_in_browser;
+
+ return *this;
+}
+
+NativeWebKeyboardEvent::~NativeWebKeyboardEvent() {
+ delete os_event;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event_gtk.cc b/chromium/content/browser/renderer_host/native_web_keyboard_event_gtk.cc
new file mode 100644
index 00000000000..f7631945fec
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event_gtk.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/native_web_keyboard_event.h"
+
+#include <gdk/gdk.h>
+
+#include "third_party/WebKit/public/web/gtk/WebInputEventFactory.h"
+
+using WebKit::WebInputEventFactory;
+
+namespace {
+
+void CopyEventTo(gfx::NativeEvent in, gfx::NativeEvent* out) {
+ *out = in ? gdk_event_copy(in) : NULL;
+}
+
+void FreeEvent(gfx::NativeEvent event) {
+ if (event)
+ gdk_event_free(event);
+}
+
+} // namespace
+
+namespace content {
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent()
+ : os_event(NULL),
+ skip_in_browser(false),
+ match_edit_command(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(gfx::NativeEvent native_event)
+ : WebKeyboardEvent(WebInputEventFactory::keyboardEvent(&native_event->key)),
+ skip_in_browser(false),
+ match_edit_command(false) {
+ CopyEventTo(native_event, &os_event);
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(wchar_t character,
+ int state,
+ double time_stamp_seconds)
+ : WebKeyboardEvent(WebInputEventFactory::keyboardEvent(character,
+ state,
+ time_stamp_seconds)),
+ os_event(NULL),
+ skip_in_browser(false),
+ match_edit_command(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& other)
+ : WebKeyboardEvent(other),
+ skip_in_browser(other.skip_in_browser),
+ match_edit_command(other.match_edit_command) {
+ CopyEventTo(other.os_event, &os_event);
+}
+
+NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(
+ const NativeWebKeyboardEvent& other) {
+ WebKeyboardEvent::operator=(other);
+
+ FreeEvent(os_event);
+ CopyEventTo(other.os_event, &os_event);
+
+ skip_in_browser = other.skip_in_browser;
+ match_edit_command = other.match_edit_command;
+
+ return *this;
+}
+
+NativeWebKeyboardEvent::~NativeWebKeyboardEvent() {
+ FreeEvent(os_event);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event_mac.mm b/chromium/content/browser/renderer_host/native_web_keyboard_event_mac.mm
new file mode 100644
index 00000000000..5b4f38ca11a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event_mac.mm
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/native_web_keyboard_event.h"
+
+#import <AppKit/AppKit.h>
+
+#include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
+
+using WebKit::WebInputEventFactory;
+
+namespace content {
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent()
+ : os_event(NULL),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(gfx::NativeEvent native_event)
+ : WebKeyboardEvent(WebInputEventFactory::keyboardEvent(native_event)),
+ os_event([native_event retain]),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(wchar_t character,
+ int modifiers,
+ double time_stamp_seconds)
+ : WebKeyboardEvent(WebInputEventFactory::keyboardEvent(character,
+ modifiers,
+ time_stamp_seconds)),
+ os_event(NULL),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& other)
+ : WebKeyboardEvent(other),
+ os_event([other.os_event retain]),
+ skip_in_browser(other.skip_in_browser) {
+}
+
+NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(
+ const NativeWebKeyboardEvent& other) {
+ WebKeyboardEvent::operator=(other);
+
+ NSObject* previous = os_event;
+ os_event = [other.os_event retain];
+ [previous release];
+
+ skip_in_browser = other.skip_in_browser;
+
+ return *this;
+}
+
+NativeWebKeyboardEvent::~NativeWebKeyboardEvent() {
+ [os_event release];
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/native_web_keyboard_event_win.cc b/chromium/content/browser/renderer_host/native_web_keyboard_event_win.cc
new file mode 100644
index 00000000000..42d3cce10fe
--- /dev/null
+++ b/chromium/content/browser/renderer_host/native_web_keyboard_event_win.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/native_web_keyboard_event.h"
+
+#include "content/browser/renderer_host/input/web_input_event_builders_win.h"
+
+using WebKit::WebKeyboardEvent;
+
+namespace content {
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent()
+ : skip_in_browser(false) {
+ memset(&os_event, 0, sizeof(os_event));
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(gfx::NativeEvent native_event)
+ : WebKeyboardEvent(
+ WebKeyboardEventBuilder::Build(native_event.hwnd,
+ native_event.message,
+ native_event.wParam,
+ native_event.lParam)),
+ os_event(native_event),
+ skip_in_browser(false) {
+}
+
+NativeWebKeyboardEvent::NativeWebKeyboardEvent(
+ const NativeWebKeyboardEvent& other)
+ : WebKeyboardEvent(other),
+ os_event(other.os_event),
+ skip_in_browser(other.skip_in_browser) {
+}
+
+NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(
+ const NativeWebKeyboardEvent& other) {
+ WebKeyboardEvent::operator=(other);
+
+ os_event = other.os_event;
+ skip_in_browser = other.skip_in_browser;
+
+ return *this;
+}
+
+NativeWebKeyboardEvent::~NativeWebKeyboardEvent() {
+ // Noop under windows
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/overscroll_configuration.cc b/chromium/content/browser/renderer_host/overscroll_configuration.cc
new file mode 100644
index 00000000000..77585e5fb02
--- /dev/null
+++ b/chromium/content/browser/renderer_host/overscroll_configuration.cc
@@ -0,0 +1,84 @@
+// 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 "content/public/browser/overscroll_configuration.h"
+
+#include "base/logging.h"
+
+namespace {
+
+float g_horiz_threshold_complete = 0.25f;
+float g_vert_threshold_complete = 0.20f;
+
+float g_horiz_threshold_start = 50.f;
+float g_vert_threshold_start = 0.f;
+
+float g_horiz_resist_after = 30.f;
+float g_vert_resist_after = 30.f;
+
+}
+
+namespace content {
+
+void SetOverscrollConfig(OverscrollConfig config, float value) {
+ switch (config) {
+ case OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE:
+ g_horiz_threshold_complete = value;
+ break;
+
+ case OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE:
+ g_vert_threshold_complete = value;
+ break;
+
+ case OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START:
+ g_horiz_threshold_start = value;
+ break;
+
+ case OVERSCROLL_CONFIG_VERT_THRESHOLD_START:
+ g_vert_threshold_start = value;
+ break;
+
+ case OVERSCROLL_CONFIG_HORIZ_RESIST_AFTER:
+ g_horiz_resist_after = value;
+ break;
+
+ case OVERSCROLL_CONFIG_VERT_RESIST_AFTER:
+ g_vert_resist_after = value;
+ break;
+
+ case OVERSCROLL_CONFIG_NONE:
+ case OVERSCROLL_CONFIG_COUNT:
+ NOTREACHED();
+ }
+}
+
+float GetOverscrollConfig(OverscrollConfig config) {
+ switch (config) {
+ case OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE:
+ return g_horiz_threshold_complete;
+
+ case OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE:
+ return g_vert_threshold_complete;
+
+ case OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START:
+ return g_horiz_threshold_start;
+
+ case OVERSCROLL_CONFIG_VERT_THRESHOLD_START:
+ return g_vert_threshold_start;
+
+ case OVERSCROLL_CONFIG_HORIZ_RESIST_AFTER:
+ return g_horiz_resist_after;
+
+ case OVERSCROLL_CONFIG_VERT_RESIST_AFTER:
+ return g_vert_resist_after;
+
+ case OVERSCROLL_CONFIG_NONE:
+ case OVERSCROLL_CONFIG_COUNT:
+ NOTREACHED();
+ }
+
+ return -1.f;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/overscroll_controller.cc b/chromium/content/browser/renderer_host/overscroll_controller.cc
new file mode 100644
index 00000000000..3fed4c332a7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/overscroll_controller.cc
@@ -0,0 +1,372 @@
+// 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 "content/browser/renderer_host/overscroll_controller.h"
+
+#include "content/browser/renderer_host/overscroll_controller_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/browser/overscroll_configuration.h"
+#include "content/public/browser/render_widget_host_view.h"
+
+namespace content {
+
+OverscrollController::OverscrollController(
+ RenderWidgetHostImpl* render_widget_host)
+ : render_widget_host_(render_widget_host),
+ overscroll_mode_(OVERSCROLL_NONE),
+ scroll_state_(STATE_UNKNOWN),
+ overscroll_delta_x_(0.f),
+ overscroll_delta_y_(0.f),
+ delegate_(NULL) {
+}
+
+OverscrollController::~OverscrollController() {
+}
+
+bool OverscrollController::WillDispatchEvent(
+ const WebKit::WebInputEvent& event,
+ const ui::LatencyInfo& latency_info) {
+ if (scroll_state_ != STATE_UNKNOWN) {
+ switch (event.type) {
+ case WebKit::WebInputEvent::GestureScrollEnd:
+ case WebKit::WebInputEvent::GestureFlingStart:
+ scroll_state_ = STATE_UNKNOWN;
+ break;
+
+ case WebKit::WebInputEvent::MouseWheel: {
+ const WebKit::WebMouseWheelEvent& wheel =
+ static_cast<const WebKit::WebMouseWheelEvent&>(event);
+ if (!wheel.hasPreciseScrollingDeltas ||
+ wheel.phase == WebKit::WebMouseWheelEvent::PhaseEnded ||
+ wheel.phase == WebKit::WebMouseWheelEvent::PhaseCancelled) {
+ scroll_state_ = STATE_UNKNOWN;
+ }
+ break;
+ }
+
+ default:
+ if (WebKit::WebInputEvent::isMouseEventType(event.type) ||
+ WebKit::WebInputEvent::isKeyboardEventType(event.type)) {
+ scroll_state_ = STATE_UNKNOWN;
+ }
+ break;
+ }
+ }
+
+ if (DispatchEventCompletesAction(event)) {
+ CompleteAction();
+
+ // If the overscroll was caused by touch-scrolling, then the gesture event
+ // that completes the action needs to be sent to the renderer, because the
+ // touch-scrolls maintain state in the renderer side (in the compositor, for
+ // example), and the event that completes this action needs to be sent to
+ // the renderer so that those states can be updated/reset appropriately.
+ // Send the event through the host when appropriate.
+ if (ShouldForwardToHost(event)) {
+ const WebKit::WebGestureEvent& gevent =
+ static_cast<const WebKit::WebGestureEvent&>(event);
+ return render_widget_host_->ShouldForwardGestureEvent(
+ GestureEventWithLatencyInfo(gevent, latency_info));
+ }
+
+ return false;
+ }
+
+ if (overscroll_mode_ != OVERSCROLL_NONE && DispatchEventResetsState(event)) {
+ SetOverscrollMode(OVERSCROLL_NONE);
+ // The overscroll gesture status is being reset. If the event is a
+ // gesture event (from either touchscreen or trackpad), then make sure the
+ // host gets the event first (if it didn't already process it).
+ if (ShouldForwardToHost(event)) {
+ const WebKit::WebGestureEvent& gevent =
+ static_cast<const WebKit::WebGestureEvent&>(event);
+ return render_widget_host_->ShouldForwardGestureEvent(
+ GestureEventWithLatencyInfo(gevent, latency_info));
+ }
+
+ // Let the event be dispatched to the renderer.
+ return true;
+ }
+
+ if (overscroll_mode_ != OVERSCROLL_NONE) {
+ // Consume the event and update overscroll state when in the middle of the
+ // overscroll gesture.
+ ProcessEventForOverscroll(event);
+
+ if (event.type == WebKit::WebInputEvent::TouchEnd ||
+ event.type == WebKit::WebInputEvent::TouchCancel ||
+ event.type == WebKit::WebInputEvent::TouchMove) {
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void OverscrollController::ReceivedEventACK(const WebKit::WebInputEvent& event,
+ bool processed) {
+ if (processed) {
+ // If a scroll event is consumed by the page, i.e. some content on the page
+ // has been scrolled, then there is not going to be an overscroll gesture,
+ // until the current scroll ends, and a new scroll gesture starts.
+ if (scroll_state_ == STATE_UNKNOWN &&
+ (event.type == WebKit::WebInputEvent::MouseWheel ||
+ event.type == WebKit::WebInputEvent::GestureScrollUpdate)) {
+ scroll_state_ = STATE_CONTENT_SCROLLING;
+ }
+ return;
+ }
+ ProcessEventForOverscroll(event);
+}
+
+void OverscrollController::DiscardingGestureEvent(
+ const WebKit::WebGestureEvent& gesture) {
+ if (scroll_state_ != STATE_UNKNOWN &&
+ (gesture.type == WebKit::WebInputEvent::GestureScrollEnd ||
+ gesture.type == WebKit::WebInputEvent::GestureFlingStart)) {
+ scroll_state_ = STATE_UNKNOWN;
+ }
+}
+
+void OverscrollController::Reset() {
+ overscroll_mode_ = OVERSCROLL_NONE;
+ overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
+ scroll_state_ = STATE_UNKNOWN;
+}
+
+void OverscrollController::Cancel() {
+ SetOverscrollMode(OVERSCROLL_NONE);
+ overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
+ scroll_state_ = STATE_UNKNOWN;
+}
+
+bool OverscrollController::DispatchEventCompletesAction (
+ const WebKit::WebInputEvent& event) const {
+ if (overscroll_mode_ == OVERSCROLL_NONE)
+ return false;
+
+ // Complete the overscroll gesture if there was a mouse move or a scroll-end
+ // after the threshold.
+ if (event.type != WebKit::WebInputEvent::MouseMove &&
+ event.type != WebKit::WebInputEvent::GestureScrollEnd &&
+ event.type != WebKit::WebInputEvent::GestureFlingStart)
+ return false;
+
+ RenderWidgetHostView* view = render_widget_host_->GetView();
+ if (!view->IsShowing())
+ return false;
+
+ const gfx::Rect& bounds = view->GetViewBounds();
+ if (bounds.IsEmpty())
+ return false;
+
+ if (event.type == WebKit::WebInputEvent::GestureFlingStart) {
+ // Check to see if the fling is in the same direction of the overscroll.
+ const WebKit::WebGestureEvent gesture =
+ static_cast<const WebKit::WebGestureEvent&>(event);
+ switch (overscroll_mode_) {
+ case OVERSCROLL_EAST:
+ if (gesture.data.flingStart.velocityX < 0)
+ return false;
+ break;
+ case OVERSCROLL_WEST:
+ if (gesture.data.flingStart.velocityX > 0)
+ return false;
+ break;
+ case OVERSCROLL_NORTH:
+ if (gesture.data.flingStart.velocityY > 0)
+ return false;
+ break;
+ case OVERSCROLL_SOUTH:
+ if (gesture.data.flingStart.velocityY < 0)
+ return false;
+ break;
+ case OVERSCROLL_NONE:
+ case OVERSCROLL_COUNT:
+ NOTREACHED();
+ }
+ }
+
+ float ratio, threshold;
+ if (overscroll_mode_ == OVERSCROLL_WEST ||
+ overscroll_mode_ == OVERSCROLL_EAST) {
+ ratio = fabs(overscroll_delta_x_) / bounds.width();
+ threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
+ } else {
+ ratio = fabs(overscroll_delta_y_) / bounds.height();
+ threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE);
+ }
+
+ return ratio >= threshold;
+}
+
+bool OverscrollController::DispatchEventResetsState(
+ const WebKit::WebInputEvent& event) const {
+ switch (event.type) {
+ case WebKit::WebInputEvent::MouseWheel: {
+ // Only wheel events with precise deltas (i.e. from trackpad) contribute
+ // to the overscroll gesture.
+ const WebKit::WebMouseWheelEvent& wheel =
+ static_cast<const WebKit::WebMouseWheelEvent&>(event);
+ return !wheel.hasPreciseScrollingDeltas;
+ }
+
+ case WebKit::WebInputEvent::GestureScrollUpdate:
+ case WebKit::WebInputEvent::GestureFlingCancel:
+ return false;
+
+ default:
+ // Touch events can arrive during an overscroll gesture initiated by
+ // touch-scrolling. These events should not reset the overscroll state.
+ return !WebKit::WebInputEvent::isTouchEventType(event.type);
+ }
+}
+
+void OverscrollController::ProcessEventForOverscroll(
+ const WebKit::WebInputEvent& event) {
+ switch (event.type) {
+ case WebKit::WebInputEvent::MouseWheel: {
+ const WebKit::WebMouseWheelEvent& wheel =
+ static_cast<const WebKit::WebMouseWheelEvent&>(event);
+ if (!wheel.hasPreciseScrollingDeltas)
+ return;
+
+ ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX,
+ wheel.deltaY * wheel.accelerationRatioY);
+ break;
+ }
+ case WebKit::WebInputEvent::GestureScrollUpdate: {
+ const WebKit::WebGestureEvent& gesture =
+ static_cast<const WebKit::WebGestureEvent&>(event);
+ ProcessOverscroll(gesture.data.scrollUpdate.deltaX,
+ gesture.data.scrollUpdate.deltaY);
+ break;
+ }
+ case WebKit::WebInputEvent::GestureFlingStart: {
+ const float kFlingVelocityThreshold = 1100.f;
+ const WebKit::WebGestureEvent& gesture =
+ static_cast<const WebKit::WebGestureEvent&>(event);
+ float velocity_x = gesture.data.flingStart.velocityX;
+ float velocity_y = gesture.data.flingStart.velocityY;
+ if (fabs(velocity_x) > kFlingVelocityThreshold) {
+ if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) ||
+ (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) {
+ CompleteAction();
+ break;
+ }
+ } else if (fabs(velocity_y) > kFlingVelocityThreshold) {
+ if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) ||
+ (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) {
+ CompleteAction();
+ break;
+ }
+ }
+
+ // Reset overscroll state if fling didn't complete the overscroll gesture.
+ SetOverscrollMode(OVERSCROLL_NONE);
+ break;
+ }
+
+ default:
+ DCHECK(WebKit::WebInputEvent::isGestureEventType(event.type) ||
+ WebKit::WebInputEvent::isTouchEventType(event.type))
+ << "Received unexpected event: " << event.type;
+ }
+}
+
+void OverscrollController::ProcessOverscroll(float delta_x, float delta_y) {
+ if (scroll_state_ != STATE_CONTENT_SCROLLING)
+ overscroll_delta_x_ += delta_x;
+ overscroll_delta_y_ += delta_y;
+
+ float horiz_threshold = GetOverscrollConfig(
+ OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START);
+ float vert_threshold = GetOverscrollConfig(
+ OVERSCROLL_CONFIG_VERT_THRESHOLD_START);
+ if (fabs(overscroll_delta_x_) <= horiz_threshold &&
+ fabs(overscroll_delta_y_) <= vert_threshold) {
+ SetOverscrollMode(OVERSCROLL_NONE);
+ return;
+ }
+
+ // Compute the current overscroll direction. If the direction is different
+ // from the current direction, then always switch to no-overscroll mode first
+ // to make sure that subsequent scroll events go through to the page first.
+ OverscrollMode new_mode = OVERSCROLL_NONE;
+ const float kMinRatio = 2.5;
+ if (fabs(overscroll_delta_x_) > horiz_threshold &&
+ fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
+ new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
+ else if (fabs(overscroll_delta_y_) > vert_threshold &&
+ fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
+ new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
+
+ if (overscroll_mode_ == OVERSCROLL_NONE)
+ SetOverscrollMode(new_mode);
+ else if (new_mode != overscroll_mode_)
+ SetOverscrollMode(OVERSCROLL_NONE);
+
+ if (overscroll_mode_ == OVERSCROLL_NONE)
+ return;
+
+ // Tell the delegate about the overscroll update so that it can update
+ // the display accordingly (e.g. show history preview etc.).
+ if (delegate_) {
+ // Do not include the threshold amount when sending the deltas to the
+ // delegate.
+ float delegate_delta_x = overscroll_delta_x_;
+ if (fabs(delegate_delta_x) > horiz_threshold) {
+ if (delegate_delta_x < 0)
+ delegate_delta_x += horiz_threshold;
+ else
+ delegate_delta_x -= horiz_threshold;
+ } else {
+ delegate_delta_x = 0.f;
+ }
+
+ float delegate_delta_y = overscroll_delta_y_;
+ if (fabs(delegate_delta_y) > vert_threshold) {
+ if (delegate_delta_y < 0)
+ delegate_delta_y += vert_threshold;
+ else
+ delegate_delta_y -= vert_threshold;
+ } else {
+ delegate_delta_y = 0.f;
+ }
+ delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
+ }
+}
+
+void OverscrollController::CompleteAction() {
+ if (delegate_)
+ delegate_->OnOverscrollComplete(overscroll_mode_);
+ overscroll_mode_ = OVERSCROLL_NONE;
+ overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
+}
+
+void OverscrollController::SetOverscrollMode(OverscrollMode mode) {
+ if (overscroll_mode_ == mode)
+ return;
+ OverscrollMode old_mode = overscroll_mode_;
+ overscroll_mode_ = mode;
+ if (overscroll_mode_ == OVERSCROLL_NONE)
+ overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
+ else
+ scroll_state_ = STATE_OVERSCROLLING;
+ if (delegate_)
+ delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_);
+}
+
+bool OverscrollController::ShouldForwardToHost(
+ const WebKit::WebInputEvent& event) const {
+ if (!WebKit::WebInputEvent::isGestureEventType(event.type))
+ return false;
+
+ // If the RenderWidgetHost already processed this event, then the event must
+ // not be sent again.
+ return !render_widget_host_->HasQueuedGestureEvents();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/overscroll_controller.h b/chromium/content/browser/renderer_host/overscroll_controller.h
new file mode 100644
index 00000000000..fc1fc5eb47f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/overscroll_controller.h
@@ -0,0 +1,138 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace ui {
+struct LatencyInfo;
+}
+
+namespace content {
+
+class MockRenderWidgetHost;
+class OverscrollControllerDelegate;
+class RenderWidgetHostImpl;
+
+// Indicates the direction that the scroll is heading in relative to the screen,
+// with the top being NORTH.
+enum OverscrollMode {
+ OVERSCROLL_NONE,
+ OVERSCROLL_NORTH,
+ OVERSCROLL_SOUTH,
+ OVERSCROLL_WEST,
+ OVERSCROLL_EAST,
+ OVERSCROLL_COUNT
+};
+
+// When a page is scrolled beyond the scrollable region, it will trigger an
+// overscroll gesture. This controller receives the events that are dispatched
+// to the renderer, and the ACKs of events, and updates the overscroll gesture
+// status accordingly.
+class OverscrollController {
+ public:
+ // Creates an overscroll controller for the specified RenderWidgetHost.
+ // The RenderWidgetHost owns this overscroll controller.
+ explicit OverscrollController(RenderWidgetHostImpl* widget_host);
+ virtual ~OverscrollController();
+
+ // This must be called when dispatching any event from the
+ // RenderWidgetHostView so that the state of the overscroll gesture can be
+ // updated properly.
+ // Returns true if the event should be dispatched, false otherwise.
+ bool WillDispatchEvent(const WebKit::WebInputEvent& event,
+ const ui::LatencyInfo& latency_info);
+
+ // This must be called when the ACK for any event comes in. This updates the
+ // overscroll gesture status as appropriate.
+ void ReceivedEventACK(const WebKit::WebInputEvent& event, bool processed);
+
+ // This must be called when a gesture event is filtered out and not sent to
+ // the renderer.
+ void DiscardingGestureEvent(const WebKit::WebGestureEvent& event);
+
+ OverscrollMode overscroll_mode() const { return overscroll_mode_; }
+
+ void set_delegate(OverscrollControllerDelegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ // Resets internal states.
+ void Reset();
+
+ // Cancels any in-progress overscroll (and calls OnOverscrollModeChange on the
+ // delegate if necessary), and resets internal states.
+ void Cancel();
+
+ private:
+ friend class MockRenderWidgetHost;
+
+ // Different scrolling states.
+ enum ScrollState {
+ STATE_UNKNOWN,
+ STATE_PENDING,
+ STATE_CONTENT_SCROLLING,
+ STATE_OVERSCROLLING,
+ };
+
+ // Returns true if the event indicates that the in-progress overscroll gesture
+ // can now be completed.
+ bool DispatchEventCompletesAction(
+ const WebKit::WebInputEvent& event) const;
+
+ // Returns true to indicate that dispatching the event should reset the
+ // overscroll gesture status.
+ bool DispatchEventResetsState(const WebKit::WebInputEvent& event) const;
+
+ // Processes an event and updates internal state for overscroll.
+ void ProcessEventForOverscroll(const WebKit::WebInputEvent& event);
+
+ // Processes horizontal overscroll. This can update both the overscroll mode
+ // and the over scroll amount (i.e. |overscroll_mode_|, |overscroll_delta_x_|
+ // and |overscroll_delta_y_|).
+ void ProcessOverscroll(float delta_x, float delta_y);
+
+ // Completes the desired action from the current gesture.
+ void CompleteAction();
+
+ // Sets the overscroll mode (and triggers callback in the delegate when
+ // appropriate).
+ void SetOverscrollMode(OverscrollMode new_mode);
+
+ // Returns whether the input event should be forwarded to the
+ // RenderWidgetHost.
+ bool ShouldForwardToHost(const WebKit::WebInputEvent& event) const;
+
+ // The RenderWidgetHost that owns this overscroll controller.
+ RenderWidgetHostImpl* render_widget_host_;
+
+ // The current state of overscroll gesture.
+ OverscrollMode overscroll_mode_;
+
+ // Used to keep track of the scrolling state.
+ // If scrolling starts, and some scroll events are consumed at the beginning
+ // of the scroll (i.e. some content on the web-page was scrolled), then do not
+ // process any of the subsequent scroll events for generating overscroll
+ // gestures.
+ ScrollState scroll_state_;
+
+ // The amount of overscroll in progress. These values are invalid when
+ // |overscroll_mode_| is set to OVERSCROLL_NONE.
+ float overscroll_delta_x_;
+ float overscroll_delta_y_;
+
+ // The delegate that receives the overscroll updates. The delegate is not
+ // owned by this controller.
+ OverscrollControllerDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverscrollController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/overscroll_controller_delegate.h b/chromium/content/browser/renderer_host/overscroll_controller_delegate.h
new file mode 100644
index 00000000000..6d40ed12095
--- /dev/null
+++ b/chromium/content/browser/renderer_host/overscroll_controller_delegate.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_DELEGATE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_DELEGATE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/renderer_host/overscroll_controller.h"
+
+namespace content {
+
+// The delegate receives overscroll gesture updates from the controller and
+// should perform appropriate actions.
+class OverscrollControllerDelegate {
+ public:
+ OverscrollControllerDelegate() {}
+ virtual ~OverscrollControllerDelegate() {}
+
+ // This is called for each update in the overscroll amount.
+ virtual void OnOverscrollUpdate(float delta_x, float delta_y) = 0;
+
+ // This is called when the overscroll completes.
+ virtual void OnOverscrollComplete(OverscrollMode overscroll_mode) = 0;
+
+ // This is called when the direction of the overscroll changes.
+ virtual void OnOverscrollModeChange(OverscrollMode old_mode,
+ OverscrollMode new_mode) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OverscrollControllerDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_OVERSCROLL_CONTROLLER_DELEGATE_H_
diff --git a/chromium/content/browser/renderer_host/p2p/DEPS b/chromium/content/browser/renderer_host/p2p/DEPS
new file mode 100644
index 00000000000..93e27aa264f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+jingle/glue",
+]
diff --git a/chromium/content/browser/renderer_host/p2p/OWNERS b/chromium/content/browser/renderer_host/p2p/OWNERS
new file mode 100644
index 00000000000..f12c2d60e83
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/OWNERS
@@ -0,0 +1,3 @@
+hclam@chromium.org
+mallinath@chromium.org
+sergeyu@chromium.org
diff --git a/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.cc b/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.cc
new file mode 100644
index 00000000000..f1249d51e89
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.cc
@@ -0,0 +1,265 @@
+// 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 "content/browser/renderer_host/p2p/socket_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "content/browser/renderer_host/p2p/socket_host.h"
+#include "content/common/p2p_messages.h"
+#include "content/public/browser/resource_context.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/sys_addrinfo.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using content::BrowserMessageFilter;
+using content::BrowserThread;
+
+namespace content {
+
+class P2PSocketDispatcherHost::DnsRequest {
+ public:
+ typedef base::Callback<void(const net::IPAddressNumber&)> DoneCallback;
+
+ DnsRequest(int32 request_id, net::HostResolver* host_resolver)
+ : request_id_(request_id),
+ resolver_(host_resolver) {
+ }
+
+ void Resolve(const std::string& host_name,
+ const DoneCallback& done_callback) {
+ DCHECK(!done_callback.is_null());
+
+ host_name_ = host_name;
+ done_callback_ = done_callback;
+
+ // Return an error if it's an empty string.
+ if (host_name_.empty()) {
+ done_callback_.Run(net::IPAddressNumber());
+ return;
+ }
+
+ // Add period at the end to make sure that we only resolve
+ // fully-qualified names.
+ if (host_name_.at(host_name_.size() - 1) != '.')
+ host_name_ = host_name_ + '.';
+
+ net::HostResolver::RequestInfo info(net::HostPortPair(host_name_, 0));
+ int result = resolver_.Resolve(
+ info, &addresses_,
+ base::Bind(&P2PSocketDispatcherHost::DnsRequest::OnDone,
+ base::Unretained(this)),
+ net::BoundNetLog());
+ if (result != net::ERR_IO_PENDING)
+ OnDone(result);
+ }
+
+ int32 request_id() { return request_id_; }
+
+ private:
+ void OnDone(int result) {
+ if (result != net::OK) {
+ LOG(ERROR) << "Failed to resolve address for " << host_name_
+ << ", errorcode: " << result;
+ done_callback_.Run(net::IPAddressNumber());
+ return;
+ }
+
+ DCHECK(!addresses_.empty());
+ done_callback_.Run(addresses_.front().address());
+ }
+
+ int32 request_id_;
+ net::AddressList addresses_;
+
+ std::string host_name_;
+ net::SingleRequestHostResolver resolver_;
+
+ DoneCallback done_callback_;
+};
+
+P2PSocketDispatcherHost::P2PSocketDispatcherHost(
+ content::ResourceContext* resource_context,
+ net::URLRequestContextGetter* url_context)
+ : resource_context_(resource_context),
+ url_context_(url_context),
+ monitoring_networks_(false) {
+}
+
+void P2PSocketDispatcherHost::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ // Since the IPC channel is gone, close pending connections.
+ STLDeleteContainerPairSecondPointers(sockets_.begin(), sockets_.end());
+ sockets_.clear();
+
+ STLDeleteContainerPointers(dns_requests_.begin(), dns_requests_.end());
+ dns_requests_.clear();
+
+ if (monitoring_networks_) {
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ monitoring_networks_ = false;
+ }
+}
+
+void P2PSocketDispatcherHost::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+bool P2PSocketDispatcherHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(P2PSocketDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_StartNetworkNotifications,
+ OnStartNetworkNotifications)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_StopNetworkNotifications,
+ OnStopNetworkNotifications)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_GetHostAddress, OnGetHostAddress)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_CreateSocket, OnCreateSocket)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_AcceptIncomingTcpConnection,
+ OnAcceptIncomingTcpConnection)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_Send, OnSend)
+ IPC_MESSAGE_HANDLER(P2PHostMsg_DestroySocket, OnDestroySocket)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void P2PSocketDispatcherHost::OnIPAddressChanged() {
+ // Notify the renderer about changes to list of network interfaces.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE, base::Bind(
+ &P2PSocketDispatcherHost::DoGetNetworkList, this));
+}
+
+P2PSocketDispatcherHost::~P2PSocketDispatcherHost() {
+ DCHECK(sockets_.empty());
+ DCHECK(dns_requests_.empty());
+
+ if (monitoring_networks_)
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+}
+
+P2PSocketHost* P2PSocketDispatcherHost::LookupSocket(int socket_id) {
+ SocketsMap::iterator it = sockets_.find(socket_id);
+ return (it == sockets_.end()) ? NULL : it->second;
+}
+
+void P2PSocketDispatcherHost::OnStartNetworkNotifications(
+ const IPC::Message& msg) {
+ if (!monitoring_networks_) {
+ net::NetworkChangeNotifier::AddIPAddressObserver(this);
+ monitoring_networks_ = true;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE, base::Bind(
+ &P2PSocketDispatcherHost::DoGetNetworkList, this));
+}
+
+void P2PSocketDispatcherHost::OnStopNetworkNotifications(
+ const IPC::Message& msg) {
+ if (monitoring_networks_) {
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ monitoring_networks_ = false;
+ }
+}
+
+void P2PSocketDispatcherHost::OnGetHostAddress(const std::string& host_name,
+ int32 request_id) {
+ DnsRequest* request = new DnsRequest(request_id,
+ resource_context_->GetHostResolver());
+ dns_requests_.insert(request);
+ request->Resolve(host_name, base::Bind(
+ &P2PSocketDispatcherHost::OnAddressResolved,
+ base::Unretained(this), request));
+}
+
+void P2PSocketDispatcherHost::OnCreateSocket(
+ P2PSocketType type, int socket_id,
+ const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) {
+ if (LookupSocket(socket_id)) {
+ LOG(ERROR) << "Received P2PHostMsg_CreateSocket for socket "
+ "that already exists.";
+ return;
+ }
+
+ scoped_ptr<P2PSocketHost> socket(
+ P2PSocketHost::Create(this, socket_id, type, url_context_.get()));
+
+ if (!socket) {
+ Send(new P2PMsg_OnError(socket_id));
+ return;
+ }
+
+ if (socket->Init(local_address, remote_address)) {
+ sockets_[socket_id] = socket.release();
+ }
+}
+
+void P2PSocketDispatcherHost::OnAcceptIncomingTcpConnection(
+ int listen_socket_id, const net::IPEndPoint& remote_address,
+ int connected_socket_id) {
+ P2PSocketHost* socket = LookupSocket(listen_socket_id);
+ if (!socket) {
+ LOG(ERROR) << "Received P2PHostMsg_AcceptIncomingTcpConnection "
+ "for invalid socket_id.";
+ return;
+ }
+ P2PSocketHost* accepted_connection =
+ socket->AcceptIncomingTcpConnection(remote_address, connected_socket_id);
+ if (accepted_connection) {
+ sockets_[connected_socket_id] = accepted_connection;
+ }
+}
+
+void P2PSocketDispatcherHost::OnSend(int socket_id,
+ const net::IPEndPoint& socket_address,
+ const std::vector<char>& data) {
+ P2PSocketHost* socket = LookupSocket(socket_id);
+ if (!socket) {
+ LOG(ERROR) << "Received P2PHostMsg_Send for invalid socket_id.";
+ return;
+ }
+ socket->Send(socket_address, data);
+}
+
+void P2PSocketDispatcherHost::OnDestroySocket(int socket_id) {
+ SocketsMap::iterator it = sockets_.find(socket_id);
+ if (it != sockets_.end()) {
+ delete it->second;
+ sockets_.erase(it);
+ } else {
+ LOG(ERROR) << "Received P2PHostMsg_DestroySocket for invalid socket_id.";
+ }
+}
+
+void P2PSocketDispatcherHost::DoGetNetworkList() {
+ net::NetworkInterfaceList list;
+ net::GetNetworkList(&list);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(
+ &P2PSocketDispatcherHost::SendNetworkList, this, list));
+}
+
+void P2PSocketDispatcherHost::SendNetworkList(
+ const net::NetworkInterfaceList& list) {
+ Send(new P2PMsg_NetworkListChanged(list));
+}
+
+void P2PSocketDispatcherHost::OnAddressResolved(
+ DnsRequest* request,
+ const net::IPAddressNumber& result) {
+ Send(new P2PMsg_GetHostAddressResult(request->request_id(), result));
+
+ dns_requests_.erase(request);
+ delete request;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.h b/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.h
new file mode 100644
index 00000000000..8a31ae558b1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_dispatcher_host.h
@@ -0,0 +1,93 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_DISPATCHER_HOST_H_
+
+#include <map>
+
+#include "content/common/p2p_sockets.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+class P2PSocketHost;
+class ResourceContext;
+
+class P2PSocketDispatcherHost
+ : public content::BrowserMessageFilter,
+ public net::NetworkChangeNotifier::IPAddressObserver {
+ public:
+ P2PSocketDispatcherHost(content::ResourceContext* resource_context,
+ net::URLRequestContextGetter* url_context);
+
+ // content::BrowserMessageFilter overrides.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // net::NetworkChangeNotifier::IPAddressObserver interface.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ protected:
+ virtual ~P2PSocketDispatcherHost();
+
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
+ friend class base::DeleteHelper<P2PSocketDispatcherHost>;
+
+ typedef std::map<int, P2PSocketHost*> SocketsMap;
+
+ class DnsRequest;
+
+ P2PSocketHost* LookupSocket(int socket_id);
+
+ // Handlers for the messages coming from the renderer.
+ void OnStartNetworkNotifications(const IPC::Message& msg);
+ void OnStopNetworkNotifications(const IPC::Message& msg);
+
+ void OnGetHostAddress(const std::string& host_name,
+ int32 request_id);
+
+ void OnCreateSocket(P2PSocketType type,
+ int socket_id,
+ const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address);
+ void OnAcceptIncomingTcpConnection(int listen_socket_id,
+ const net::IPEndPoint& remote_address,
+ int connected_socket_id);
+ void OnSend(int socket_id,
+ const net::IPEndPoint& socket_address,
+ const std::vector<char>& data);
+ void OnDestroySocket(int socket_id);
+
+ void DoGetNetworkList();
+ void SendNetworkList(const net::NetworkInterfaceList& list);
+
+ void OnAddressResolved(DnsRequest* request,
+ const net::IPAddressNumber& result);
+
+ content::ResourceContext* resource_context_;
+ scoped_refptr<net::URLRequestContextGetter> url_context_;
+
+ SocketsMap sockets_;
+
+ bool monitoring_networks_;
+
+ std::set<DnsRequest*> dns_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host.cc b/chromium/content/browser/renderer_host/p2p/socket_host.cc
new file mode 100644
index 00000000000..027bf2a608b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host.cc
@@ -0,0 +1,104 @@
+// 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 "content/browser/renderer_host/p2p/socket_host.h"
+
+#include "base/sys_byteorder.h"
+#include "content/browser/renderer_host/p2p/socket_host_tcp.h"
+#include "content/browser/renderer_host/p2p/socket_host_tcp_server.h"
+#include "content/browser/renderer_host/p2p/socket_host_udp.h"
+
+namespace {
+const uint32 kStunMagicCookie = 0x2112A442;
+} // namespace
+
+namespace content {
+
+P2PSocketHost::P2PSocketHost(IPC::Sender* message_sender, int id)
+ : message_sender_(message_sender),
+ id_(id),
+ state_(STATE_UNINITIALIZED) {
+}
+
+P2PSocketHost::~P2PSocketHost() { }
+
+// Verifies that the packet |data| has a valid STUN header.
+// static
+bool P2PSocketHost::GetStunPacketType(
+ const char* data, int data_size, StunMessageType* type) {
+
+ if (data_size < kStunHeaderSize)
+ return false;
+
+ uint32 cookie = base::NetToHost32(*reinterpret_cast<const uint32*>(data + 4));
+ if (cookie != kStunMagicCookie)
+ return false;
+
+ uint16 length = base::NetToHost16(*reinterpret_cast<const uint16*>(data + 2));
+ if (length != data_size - kStunHeaderSize)
+ return false;
+
+ int message_type = base::NetToHost16(*reinterpret_cast<const uint16*>(data));
+
+ // Verify that the type is known:
+ switch (message_type) {
+ case STUN_BINDING_REQUEST:
+ case STUN_BINDING_RESPONSE:
+ case STUN_BINDING_ERROR_RESPONSE:
+ case STUN_SHARED_SECRET_REQUEST:
+ case STUN_SHARED_SECRET_RESPONSE:
+ case STUN_SHARED_SECRET_ERROR_RESPONSE:
+ case STUN_ALLOCATE_REQUEST:
+ case STUN_ALLOCATE_RESPONSE:
+ case STUN_ALLOCATE_ERROR_RESPONSE:
+ case STUN_SEND_REQUEST:
+ case STUN_SEND_RESPONSE:
+ case STUN_SEND_ERROR_RESPONSE:
+ case STUN_DATA_INDICATION:
+ *type = static_cast<StunMessageType>(message_type);
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+// static
+bool P2PSocketHost::IsRequestOrResponse(StunMessageType type) {
+ return type == STUN_BINDING_REQUEST || type == STUN_BINDING_RESPONSE ||
+ type == STUN_ALLOCATE_REQUEST || type == STUN_ALLOCATE_RESPONSE;
+}
+
+// static
+P2PSocketHost* P2PSocketHost::Create(
+ IPC::Sender* message_sender, int id, P2PSocketType type,
+ net::URLRequestContextGetter* url_context) {
+ switch (type) {
+ case P2P_SOCKET_UDP:
+ return new P2PSocketHostUdp(message_sender, id);
+
+ case P2P_SOCKET_TCP_SERVER:
+ return new P2PSocketHostTcpServer(
+ message_sender, id, P2P_SOCKET_TCP_CLIENT);
+
+ case P2P_SOCKET_STUN_TCP_SERVER:
+ return new P2PSocketHostTcpServer(
+ message_sender, id, P2P_SOCKET_STUN_TCP_CLIENT);
+
+ case P2P_SOCKET_TCP_CLIENT:
+ case P2P_SOCKET_SSLTCP_CLIENT:
+ case P2P_SOCKET_TLS_CLIENT:
+ return new P2PSocketHostTcp(message_sender, id, type, url_context);
+
+ case P2P_SOCKET_STUN_TCP_CLIENT:
+ case P2P_SOCKET_STUN_SSLTCP_CLIENT:
+ case P2P_SOCKET_STUN_TLS_CLIENT:
+ return new P2PSocketHostStunTcp(message_sender, id, type, url_context);
+ }
+
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host.h b/chromium/content/browser/renderer_host/p2p/socket_host.h
new file mode 100644
index 00000000000..8228300408e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_H_
+
+#include "content/common/content_export.h"
+#include "content/common/p2p_sockets.h"
+
+#include "net/base/ip_endpoint.h"
+
+namespace IPC {
+class Sender;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+// Base class for P2P sockets.
+class CONTENT_EXPORT P2PSocketHost {
+ public:
+ static const int kStunHeaderSize = 20;
+ // Creates P2PSocketHost of the specific type.
+ static P2PSocketHost* Create(IPC::Sender* message_sender,
+ int id, P2PSocketType type,
+ net::URLRequestContextGetter* url_context);
+
+ virtual ~P2PSocketHost();
+
+ // Initalizes the socket. Returns false when initiazations fails.
+ virtual bool Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) = 0;
+
+ // Sends |data| on the socket to |to|.
+ virtual void Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) = 0;
+
+ virtual P2PSocketHost* AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) = 0;
+
+ protected:
+ friend class P2PSocketHostTcpTestBase;
+
+ enum StunMessageType {
+ STUN_BINDING_REQUEST = 0x0001,
+ STUN_BINDING_RESPONSE = 0x0101,
+ STUN_BINDING_ERROR_RESPONSE = 0x0111,
+ STUN_SHARED_SECRET_REQUEST = 0x0002,
+ STUN_SHARED_SECRET_RESPONSE = 0x0102,
+ STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112,
+ STUN_ALLOCATE_REQUEST = 0x0003,
+ STUN_ALLOCATE_RESPONSE = 0x0103,
+ STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+ STUN_SEND_REQUEST = 0x0004,
+ STUN_SEND_RESPONSE = 0x0104,
+ STUN_SEND_ERROR_RESPONSE = 0x0114,
+ STUN_DATA_INDICATION = 0x0115
+ };
+
+ enum State {
+ STATE_UNINITIALIZED,
+ STATE_CONNECTING,
+ STATE_TLS_CONNECTING,
+ STATE_OPEN,
+ STATE_ERROR,
+ };
+
+ P2PSocketHost(IPC::Sender* message_sender, int id);
+
+ // Verifies that the packet |data| has a valid STUN header. In case
+ // of success stores type of the message in |type|.
+ static bool GetStunPacketType(const char* data, int data_size,
+ StunMessageType* type);
+ static bool IsRequestOrResponse(StunMessageType type);
+
+ IPC::Sender* message_sender_;
+ int id_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp.cc b/chromium/content/browser/renderer_host/p2p/socket_host_tcp.cc
new file mode 100644
index 00000000000..314026e2438
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp.cc
@@ -0,0 +1,514 @@
+// 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 "content/browser/renderer_host/p2p/socket_host_tcp.h"
+
+#include "base/sys_byteorder.h"
+#include "content/common/p2p_messages.h"
+#include "ipc/ipc_sender.h"
+#include "jingle/glue/fake_ssl_client_socket.h"
+#include "jingle/glue/proxy_resolving_client_socket.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+
+typedef uint16 PacketLength;
+const int kPacketHeaderSize = sizeof(PacketLength);
+const int kReadBufferSize = 4096;
+const int kPacketLengthOffset = 2;
+const int kTurnChannelDataHeaderSize = 4;
+
+bool IsTlsClientSocket(content::P2PSocketType type) {
+ return (type == content::P2P_SOCKET_STUN_TLS_CLIENT ||
+ type == content::P2P_SOCKET_TLS_CLIENT);
+}
+
+bool IsPseudoTlsClientSocket(content::P2PSocketType type) {
+ return (type == content::P2P_SOCKET_SSLTCP_CLIENT ||
+ type == content::P2P_SOCKET_STUN_SSLTCP_CLIENT);
+}
+
+} // namespace
+
+namespace content {
+
+P2PSocketHostTcpBase::P2PSocketHostTcpBase(
+ IPC::Sender* message_sender, int id,
+ P2PSocketType type, net::URLRequestContextGetter* url_context)
+ : P2PSocketHost(message_sender, id),
+ write_pending_(false),
+ connected_(false),
+ type_(type),
+ url_context_(url_context) {
+}
+
+P2PSocketHostTcpBase::~P2PSocketHostTcpBase() {
+ if (state_ == STATE_OPEN) {
+ DCHECK(socket_.get());
+ socket_.reset();
+ }
+}
+
+bool P2PSocketHostTcpBase::InitAccepted(const net::IPEndPoint& remote_address,
+ net::StreamSocket* socket) {
+ DCHECK(socket);
+ DCHECK_EQ(state_, STATE_UNINITIALIZED);
+
+ remote_address_ = remote_address;
+ // TODO(ronghuawu): Add FakeSSLServerSocket.
+ socket_.reset(socket);
+ state_ = STATE_OPEN;
+ DoRead();
+ return state_ != STATE_ERROR;
+}
+
+bool P2PSocketHostTcpBase::Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) {
+ DCHECK_EQ(state_, STATE_UNINITIALIZED);
+
+ remote_address_ = remote_address;
+ state_ = STATE_CONNECTING;
+
+ net::HostPortPair dest_host_port_pair =
+ net::HostPortPair::FromIPEndPoint(remote_address);
+ // TODO(mallinath) - We are ignoring local_address altogether. We should
+ // find a way to inject this into ProxyResolvingClientSocket. This could be
+ // a problem on multi-homed host.
+
+ // The default SSLConfig is good enough for us for now.
+ const net::SSLConfig ssl_config;
+ socket_.reset(new jingle_glue::ProxyResolvingClientSocket(
+ NULL, // Default socket pool provided by the net::Proxy.
+ url_context_,
+ ssl_config,
+ dest_host_port_pair));
+
+ int status = socket_->Connect(
+ base::Bind(&P2PSocketHostTcpBase::OnConnected,
+ base::Unretained(this)));
+ if (status != net::ERR_IO_PENDING) {
+ // We defer execution of ProcessConnectDone instead of calling it
+ // directly here as the caller may not expect an error/close to
+ // happen here. This is okay, as from the caller's point of view,
+ // the connect always happens asynchronously.
+ base::MessageLoop* message_loop = base::MessageLoop::current();
+ CHECK(message_loop);
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&P2PSocketHostTcpBase::OnConnected,
+ base::Unretained(this), status));
+ }
+
+ return state_ != STATE_ERROR;
+}
+
+void P2PSocketHostTcpBase::OnError() {
+ socket_.reset();
+
+ if (state_ == STATE_UNINITIALIZED || state_ == STATE_CONNECTING ||
+ state_ == STATE_TLS_CONNECTING || state_ == STATE_OPEN) {
+ message_sender_->Send(new P2PMsg_OnError(id_));
+ }
+
+ state_ = STATE_ERROR;
+}
+
+void P2PSocketHostTcpBase::OnConnected(int result) {
+ DCHECK_EQ(state_, STATE_CONNECTING);
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+
+ if (result != net::OK) {
+ OnError();
+ return;
+ }
+
+ if (IsTlsClientSocket(type_)) {
+ state_ = STATE_TLS_CONNECTING;
+ StartTls();
+ } else {
+ if (IsPseudoTlsClientSocket(type_)) {
+ scoped_ptr<net::StreamSocket> transport_socket = socket_.Pass();
+ socket_.reset(
+ new jingle_glue::FakeSSLClientSocket(transport_socket.Pass()));
+ }
+
+ // If we are not doing TLS, we are ready to send data now.
+ // In case of TLS, SignalConnect will be sent only after TLS handshake is
+ // successfull. So no buffering will be done at socket handlers if any
+ // packets sent before that by the application.
+ state_ = STATE_OPEN;
+ DoSendSocketCreateMsg();
+ DoRead();
+ }
+}
+
+void P2PSocketHostTcpBase::StartTls() {
+ DCHECK_EQ(state_, STATE_TLS_CONNECTING);
+ DCHECK(socket_.get());
+
+ scoped_ptr<net::ClientSocketHandle> socket_handle(
+ new net::ClientSocketHandle());
+ socket_handle->SetSocket(socket_.Pass());
+
+ net::SSLClientSocketContext context;
+ context.cert_verifier = url_context_->GetURLRequestContext()->cert_verifier();
+ context.transport_security_state =
+ url_context_->GetURLRequestContext()->transport_security_state();
+ DCHECK(context.transport_security_state);
+
+ // Default ssl config.
+ const net::SSLConfig ssl_config;
+ net::HostPortPair dest_host_port_pair =
+ net::HostPortPair::FromIPEndPoint(remote_address_);
+ net::ClientSocketFactory* socket_factory =
+ net::ClientSocketFactory::GetDefaultFactory();
+ DCHECK(socket_factory);
+
+ socket_ = socket_factory->CreateSSLClientSocket(
+ socket_handle.Pass(), dest_host_port_pair, ssl_config, context);
+ int status = socket_->Connect(
+ base::Bind(&P2PSocketHostTcpBase::ProcessTlsConnectDone,
+ base::Unretained(this)));
+ if (status != net::ERR_IO_PENDING) {
+ ProcessTlsConnectDone(status);
+ }
+}
+
+void P2PSocketHostTcpBase::ProcessTlsConnectDone(int status) {
+ DCHECK_NE(status, net::ERR_IO_PENDING);
+ DCHECK_EQ(state_, STATE_TLS_CONNECTING);
+ if (status != net::OK) {
+ OnError();
+ return;
+ }
+
+ state_ = STATE_OPEN;
+ DoSendSocketCreateMsg();
+ DoRead();
+}
+
+void P2PSocketHostTcpBase::DoSendSocketCreateMsg() {
+ DCHECK(socket_.get());
+
+ net::IPEndPoint address;
+ int result = socket_->GetLocalAddress(&address);
+ if (result < 0) {
+ LOG(ERROR) << "P2PSocketHostTcpBase::OnConnected: unable to get local"
+ << " address: " << result;
+ OnError();
+ return;
+ }
+
+ VLOG(1) << "Local address: " << address.ToString();
+
+ // If we are not doing TLS, we are ready to send data now.
+ // In case of TLS SignalConnect will be sent only after TLS handshake is
+ // successfull. So no buffering will be done at socket handlers if any
+ // packets sent before that by the application.
+ message_sender_->Send(new P2PMsg_OnSocketCreated(id_, address));
+}
+
+void P2PSocketHostTcpBase::DoRead() {
+ int result;
+ do {
+ if (!read_buffer_.get()) {
+ read_buffer_ = new net::GrowableIOBuffer();
+ read_buffer_->SetCapacity(kReadBufferSize);
+ } else if (read_buffer_->RemainingCapacity() < kReadBufferSize) {
+ // Make sure that we always have at least kReadBufferSize of
+ // remaining capacity in the read buffer. Normally all packets
+ // are smaller than kReadBufferSize, so this is not really
+ // required.
+ read_buffer_->SetCapacity(read_buffer_->capacity() + kReadBufferSize -
+ read_buffer_->RemainingCapacity());
+ }
+ result = socket_->Read(
+ read_buffer_.get(),
+ read_buffer_->RemainingCapacity(),
+ base::Bind(&P2PSocketHostTcp::OnRead, base::Unretained(this)));
+ DidCompleteRead(result);
+ } while (result > 0);
+}
+
+void P2PSocketHostTcpBase::OnRead(int result) {
+ DidCompleteRead(result);
+ if (state_ == STATE_OPEN) {
+ DoRead();
+ }
+}
+
+void P2PSocketHostTcpBase::OnPacket(const std::vector<char>& data) {
+ if (!connected_) {
+ P2PSocketHost::StunMessageType type;
+ bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
+ if (stun && IsRequestOrResponse(type)) {
+ connected_ = true;
+ } else if (!stun || type == STUN_DATA_INDICATION) {
+ LOG(ERROR) << "Received unexpected data packet from "
+ << remote_address_.ToString()
+ << " before STUN binding is finished. "
+ << "Terminating connection.";
+ OnError();
+ return;
+ }
+ }
+
+ message_sender_->Send(new P2PMsg_OnDataReceived(id_, remote_address_, data));
+}
+
+void P2PSocketHostTcpBase::Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) {
+ if (!socket_) {
+ // The Send message may be sent after the an OnError message was
+ // sent by hasn't been processed the renderer.
+ return;
+ }
+
+ if (!(to == remote_address_)) {
+ // Renderer should use this socket only to send data to |remote_address_|.
+ NOTREACHED();
+ OnError();
+ return;
+ }
+
+ if (!connected_) {
+ P2PSocketHost::StunMessageType type;
+ bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
+ if (!stun || type == STUN_DATA_INDICATION) {
+ LOG(ERROR) << "Page tried to send a data packet to " << to.ToString()
+ << " before STUN binding is finished.";
+ OnError();
+ return;
+ }
+ }
+
+ DoSend(to, data);
+}
+
+void P2PSocketHostTcpBase::WriteOrQueue(
+ scoped_refptr<net::DrainableIOBuffer>& buffer) {
+ if (write_buffer_.get()) {
+ write_queue_.push(buffer);
+ return;
+ }
+
+ write_buffer_ = buffer;
+ DoWrite();
+}
+
+void P2PSocketHostTcpBase::DoWrite() {
+ while (write_buffer_.get() && state_ == STATE_OPEN && !write_pending_) {
+ int result = socket_->Write(
+ write_buffer_.get(),
+ write_buffer_->BytesRemaining(),
+ base::Bind(&P2PSocketHostTcp::OnWritten, base::Unretained(this)));
+ HandleWriteResult(result);
+ }
+}
+
+void P2PSocketHostTcpBase::OnWritten(int result) {
+ DCHECK(write_pending_);
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+
+ write_pending_ = false;
+ HandleWriteResult(result);
+ DoWrite();
+}
+
+void P2PSocketHostTcpBase::HandleWriteResult(int result) {
+ DCHECK(write_buffer_.get());
+ if (result >= 0) {
+ write_buffer_->DidConsume(result);
+ if (write_buffer_->BytesRemaining() == 0) {
+ message_sender_->Send(new P2PMsg_OnSendComplete(id_));
+ if (write_queue_.empty()) {
+ write_buffer_ = NULL;
+ } else {
+ write_buffer_ = write_queue_.front();
+ write_queue_.pop();
+ }
+ }
+ } else if (result == net::ERR_IO_PENDING) {
+ write_pending_ = true;
+ } else {
+ LOG(ERROR) << "Error when sending data in TCP socket: " << result;
+ OnError();
+ }
+}
+
+P2PSocketHost* P2PSocketHostTcpBase::AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) {
+ NOTREACHED();
+ OnError();
+ return NULL;
+}
+
+void P2PSocketHostTcpBase::DidCompleteRead(int result) {
+ DCHECK_EQ(state_, STATE_OPEN);
+
+ if (result == net::ERR_IO_PENDING) {
+ return;
+ } else if (result < 0) {
+ LOG(ERROR) << "Error when reading from TCP socket: " << result;
+ OnError();
+ return;
+ }
+
+ read_buffer_->set_offset(read_buffer_->offset() + result);
+ char* head = read_buffer_->StartOfBuffer(); // Purely a convenience.
+ int pos = 0;
+ while (pos <= read_buffer_->offset() && state_ == STATE_OPEN) {
+ int consumed = ProcessInput(head + pos, read_buffer_->offset() - pos);
+ if (!consumed)
+ break;
+ pos += consumed;
+ }
+ // We've consumed all complete packets from the buffer; now move any remaining
+ // bytes to the head of the buffer and set offset to reflect this.
+ if (pos && pos <= read_buffer_->offset()) {
+ memmove(head, head + pos, read_buffer_->offset() - pos);
+ read_buffer_->set_offset(read_buffer_->offset() - pos);
+ }
+}
+
+P2PSocketHostTcp::P2PSocketHostTcp(
+ IPC::Sender* message_sender, int id,
+ P2PSocketType type, net::URLRequestContextGetter* url_context)
+ : P2PSocketHostTcpBase(message_sender, id, type, url_context) {
+ DCHECK(type == P2P_SOCKET_TCP_CLIENT ||
+ type == P2P_SOCKET_SSLTCP_CLIENT ||
+ type == P2P_SOCKET_TLS_CLIENT);
+}
+
+P2PSocketHostTcp::~P2PSocketHostTcp() {
+}
+
+int P2PSocketHostTcp::ProcessInput(char* input, int input_len) {
+ if (input_len < kPacketHeaderSize)
+ return 0;
+ int packet_size = base::NetToHost16(*reinterpret_cast<uint16*>(input));
+ if (input_len < packet_size + kPacketHeaderSize)
+ return 0;
+
+ int consumed = kPacketHeaderSize;
+ char* cur = input + consumed;
+ std::vector<char> data(cur, cur + packet_size);
+ OnPacket(data);
+ consumed += packet_size;
+ return consumed;
+}
+
+void P2PSocketHostTcp::DoSend(const net::IPEndPoint& to,
+ const std::vector<char>& data) {
+ int size = kPacketHeaderSize + data.size();
+ scoped_refptr<net::DrainableIOBuffer> buffer =
+ new net::DrainableIOBuffer(new net::IOBuffer(size), size);
+ *reinterpret_cast<uint16*>(buffer->data()) = base::HostToNet16(data.size());
+ memcpy(buffer->data() + kPacketHeaderSize, &data[0], data.size());
+
+ WriteOrQueue(buffer);
+}
+
+// P2PSocketHostStunTcp
+P2PSocketHostStunTcp::P2PSocketHostStunTcp(
+ IPC::Sender* message_sender, int id,
+ P2PSocketType type, net::URLRequestContextGetter* url_context)
+ : P2PSocketHostTcpBase(message_sender, id, type, url_context) {
+ DCHECK(type == P2P_SOCKET_STUN_TCP_CLIENT ||
+ type == P2P_SOCKET_STUN_SSLTCP_CLIENT ||
+ type == P2P_SOCKET_STUN_TLS_CLIENT);
+}
+
+P2PSocketHostStunTcp::~P2PSocketHostStunTcp() {
+}
+
+int P2PSocketHostStunTcp::ProcessInput(char* input, int input_len) {
+ if (input_len < kPacketHeaderSize + kPacketLengthOffset)
+ return 0;
+
+ int pad_bytes;
+ int packet_size = GetExpectedPacketSize(
+ input, input_len, &pad_bytes);
+
+ if (input_len < packet_size + pad_bytes)
+ return 0;
+
+ // We have a complete packet. Read through it.
+ int consumed = 0;
+ char* cur = input;
+ std::vector<char> data(cur, cur + packet_size);
+ OnPacket(data);
+ consumed += packet_size;
+ consumed += pad_bytes;
+ return consumed;
+}
+
+void P2PSocketHostStunTcp::DoSend(const net::IPEndPoint& to,
+ const std::vector<char>& data) {
+ // Each packet is expected to have header (STUN/TURN ChannelData), where
+ // header contains message type and and length of message.
+ if (data.size() < kPacketHeaderSize + kPacketLengthOffset) {
+ NOTREACHED();
+ OnError();
+ return;
+ }
+
+ int pad_bytes;
+ size_t expected_len = GetExpectedPacketSize(
+ &data[0], data.size(), &pad_bytes);
+
+ // Accepts only complete STUN/TURN packets.
+ if (data.size() != expected_len) {
+ NOTREACHED();
+ OnError();
+ return;
+ }
+
+ // Add any pad bytes to the total size.
+ int size = data.size() + pad_bytes;
+
+ scoped_refptr<net::DrainableIOBuffer> buffer =
+ new net::DrainableIOBuffer(new net::IOBuffer(size), size);
+ memcpy(buffer->data(), &data[0], data.size());
+
+ if (pad_bytes) {
+ char padding[4] = {0};
+ DCHECK_LE(pad_bytes, 4);
+ memcpy(buffer->data() + data.size(), padding, pad_bytes);
+ }
+ WriteOrQueue(buffer);
+}
+
+int P2PSocketHostStunTcp::GetExpectedPacketSize(
+ const char* data, int len, int* pad_bytes) {
+ DCHECK_LE(kTurnChannelDataHeaderSize, len);
+ // Both stun and turn had length at offset 2.
+ int packet_size = base::NetToHost16(*reinterpret_cast<const uint16*>(
+ data + kPacketLengthOffset));
+
+ // Get packet type (STUN or TURN).
+ uint16 msg_type = base::NetToHost16(*reinterpret_cast<const uint16*>(data));
+
+ *pad_bytes = 0;
+ // Add heder length to packet length.
+ if ((msg_type & 0xC000) == 0) {
+ packet_size += kStunHeaderSize;
+ } else {
+ packet_size += kTurnChannelDataHeaderSize;
+ // Calculate any padding if present.
+ if (packet_size % 4)
+ *pad_bytes = 4 - packet_size % 4;
+ }
+ return packet_size;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp.h b/chromium/content/browser/renderer_host/p2p/socket_host_tcp.h
new file mode 100644
index 00000000000..e5fa08ff93f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_H_
+
+#include <queue>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/p2p/socket_host.h"
+#include "content/common/p2p_sockets.h"
+#include "net/base/completion_callback.h"
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+class DrainableIOBuffer;
+class GrowableIOBuffer;
+class StreamSocket;
+class URLRequestContextGetter;
+} // namespace net
+
+namespace content {
+
+class CONTENT_EXPORT P2PSocketHostTcpBase : public P2PSocketHost {
+ public:
+ P2PSocketHostTcpBase(IPC::Sender* message_sender,
+ int id,
+ P2PSocketType type,
+ net::URLRequestContextGetter* url_context);
+ virtual ~P2PSocketHostTcpBase();
+
+ bool InitAccepted(const net::IPEndPoint& remote_address,
+ net::StreamSocket* socket);
+
+ // P2PSocketHost overrides.
+ virtual bool Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) OVERRIDE;
+ virtual void Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) OVERRIDE;
+ virtual P2PSocketHost* AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) OVERRIDE;
+
+ protected:
+ // Derived classes will provide the implementation.
+ virtual int ProcessInput(char* input, int input_len) = 0;
+ virtual void DoSend(const net::IPEndPoint& to,
+ const std::vector<char>& data) = 0;
+
+ void WriteOrQueue(scoped_refptr<net::DrainableIOBuffer>& buffer);
+ void OnPacket(const std::vector<char>& data);
+ void OnError();
+
+ private:
+ friend class P2PSocketHostTcpTestBase;
+ friend class P2PSocketHostTcpServerTest;
+
+ // SSL/TLS connection functions.
+ void StartTls();
+ void ProcessTlsConnectDone(int status);
+
+ void DidCompleteRead(int result);
+ void DoRead();
+
+ void DoWrite();
+ void HandleWriteResult(int result);
+
+ // Callbacks for Connect(), Read() and Write().
+ void OnConnected(int result);
+ void OnRead(int result);
+ void OnWritten(int result);
+
+ void DoSendSocketCreateMsg();
+
+ net::IPEndPoint remote_address_;
+
+ scoped_ptr<net::StreamSocket> socket_;
+ scoped_refptr<net::GrowableIOBuffer> read_buffer_;
+ std::queue<scoped_refptr<net::DrainableIOBuffer> > write_queue_;
+ scoped_refptr<net::DrainableIOBuffer> write_buffer_;
+
+ bool write_pending_;
+
+ bool connected_;
+ P2PSocketType type_;
+ scoped_refptr<net::URLRequestContextGetter> url_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHostTcpBase);
+};
+
+class CONTENT_EXPORT P2PSocketHostTcp : public P2PSocketHostTcpBase {
+ public:
+ P2PSocketHostTcp(IPC::Sender* message_sender,
+ int id,
+ P2PSocketType type,
+ net::URLRequestContextGetter* url_context);
+ virtual ~P2PSocketHostTcp();
+
+ protected:
+ virtual int ProcessInput(char* input, int input_len) OVERRIDE;
+ virtual void DoSend(const net::IPEndPoint& to,
+ const std::vector<char>& data) OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHostTcp);
+};
+
+// P2PSocketHostStunTcp class provides the framing of STUN messages when used
+// with TURN. These messages will not have length at front of the packet and
+// are padded to multiple of 4 bytes.
+// Formatting of messages is defined in RFC5766.
+class CONTENT_EXPORT P2PSocketHostStunTcp : public P2PSocketHostTcpBase {
+ public:
+ P2PSocketHostStunTcp(IPC::Sender* message_sender,
+ int id,
+ P2PSocketType type,
+ net::URLRequestContextGetter* url_context);
+
+ virtual ~P2PSocketHostStunTcp();
+
+ protected:
+ virtual int ProcessInput(char* input, int input_len) OVERRIDE;
+ virtual void DoSend(const net::IPEndPoint& to,
+ const std::vector<char>& data) OVERRIDE;
+ private:
+ int GetExpectedPacketSize(const char* data, int len, int* pad_bytes);
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHostStunTcp);
+};
+
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.cc b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.cc
new file mode 100644
index 00000000000..146e5c81c52
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.cc
@@ -0,0 +1,144 @@
+// 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 "content/browser/renderer_host/p2p/socket_host_tcp_server.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/stl_util.h"
+#include "content/browser/renderer_host/p2p/socket_host_tcp.h"
+#include "content/common/p2p_messages.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/socket/stream_socket.h"
+
+namespace {
+const int kListenBacklog = 5;
+} // namespace
+
+namespace content {
+
+P2PSocketHostTcpServer::P2PSocketHostTcpServer(
+ IPC::Sender* message_sender, int id, P2PSocketType client_type)
+ : P2PSocketHost(message_sender, id),
+ client_type_(client_type),
+ socket_(new net::TCPServerSocket(NULL, net::NetLog::Source())),
+ accept_callback_(
+ base::Bind(&P2PSocketHostTcpServer::OnAccepted,
+ base::Unretained(this))) {
+}
+
+P2PSocketHostTcpServer::~P2PSocketHostTcpServer() {
+ STLDeleteContainerPairSecondPointers(accepted_sockets_.begin(),
+ accepted_sockets_.end());
+
+ if (state_ == STATE_OPEN) {
+ DCHECK(socket_.get());
+ socket_.reset();
+ }
+}
+
+bool P2PSocketHostTcpServer::Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) {
+ DCHECK_EQ(state_, STATE_UNINITIALIZED);
+
+ int result = socket_->Listen(local_address, kListenBacklog);
+ if (result < 0) {
+ LOG(ERROR) << "Listen() failed: " << result;
+ OnError();
+ return false;
+ }
+
+ result = socket_->GetLocalAddress(&local_address_);
+ if (result < 0) {
+ LOG(ERROR) << "P2PSocketHostTcpServer::Init(): can't to get local address: "
+ << result;
+ OnError();
+ return false;
+ }
+ VLOG(1) << "Local address: " << local_address_.ToString();
+
+ state_ = STATE_OPEN;
+ message_sender_->Send(new P2PMsg_OnSocketCreated(id_, local_address_));
+ DoAccept();
+ return true;
+}
+
+void P2PSocketHostTcpServer::OnError() {
+ socket_.reset();
+
+ if (state_ == STATE_UNINITIALIZED || state_ == STATE_OPEN)
+ message_sender_->Send(new P2PMsg_OnError(id_));
+
+ state_ = STATE_ERROR;
+}
+
+void P2PSocketHostTcpServer::DoAccept() {
+ while (true) {
+ int result = socket_->Accept(&accept_socket_, accept_callback_);
+ if (result == net::ERR_IO_PENDING) {
+ break;
+ } else {
+ HandleAcceptResult(result);
+ }
+ }
+}
+
+void P2PSocketHostTcpServer::HandleAcceptResult(int result) {
+ if (result < 0) {
+ if (result != net::ERR_IO_PENDING)
+ OnError();
+ return;
+ }
+
+ net::IPEndPoint address;
+ if (accept_socket_->GetPeerAddress(&address) != net::OK) {
+ LOG(ERROR) << "Failed to get address of an accepted socket.";
+ accept_socket_.reset();
+ return;
+ }
+ AcceptedSocketsMap::iterator it = accepted_sockets_.find(address);
+ if (it != accepted_sockets_.end())
+ delete it->second;
+
+ accepted_sockets_[address] = accept_socket_.release();
+ message_sender_->Send(
+ new P2PMsg_OnIncomingTcpConnection(id_, address));
+}
+
+void P2PSocketHostTcpServer::OnAccepted(int result) {
+ HandleAcceptResult(result);
+ if (result == net::OK)
+ DoAccept();
+}
+
+void P2PSocketHostTcpServer::Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) {
+ NOTREACHED();
+ OnError();
+}
+
+P2PSocketHost* P2PSocketHostTcpServer::AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) {
+ AcceptedSocketsMap::iterator it = accepted_sockets_.find(remote_address);
+ if (it == accepted_sockets_.end())
+ return NULL;
+
+ net::StreamSocket* socket = it->second;
+ accepted_sockets_.erase(it);
+
+ scoped_ptr<P2PSocketHostTcpBase> result;
+ if (client_type_ == P2P_SOCKET_TCP_CLIENT) {
+ result.reset(new P2PSocketHostTcp(message_sender_, id, client_type_, NULL));
+ } else {
+ result.reset(new P2PSocketHostStunTcp(
+ message_sender_, id, client_type_, NULL));
+ }
+ if (!result->InitAccepted(remote_address, socket))
+ return NULL;
+ return result.release();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.h b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.h
new file mode 100644
index 00000000000..c9aa8eb92b5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_SERVER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_SERVER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/p2p/socket_host.h"
+#include "content/common/content_export.h"
+#include "content/common/p2p_sockets.h"
+#include "ipc/ipc_sender.h"
+#include "net/base/completion_callback.h"
+#include "net/socket/tcp_server_socket.h"
+
+namespace net {
+class StreamSocket;
+} // namespace net
+
+namespace content {
+
+class CONTENT_EXPORT P2PSocketHostTcpServer : public P2PSocketHost {
+ public:
+ typedef std::map<net::IPEndPoint, net::StreamSocket*> AcceptedSocketsMap;
+
+ P2PSocketHostTcpServer(IPC::Sender* message_sender, int id,
+ P2PSocketType client_type);
+ virtual ~P2PSocketHostTcpServer();
+
+ // P2PSocketHost overrides.
+ virtual bool Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) OVERRIDE;
+ virtual void Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) OVERRIDE;
+ virtual P2PSocketHost* AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) OVERRIDE;
+
+ private:
+ friend class P2PSocketHostTcpServerTest;
+
+ void OnError();
+
+ void DoAccept();
+ void HandleAcceptResult(int result);
+
+ // Callback for Accept().
+ void OnAccepted(int result);
+
+ const P2PSocketType client_type_;
+ scoped_ptr<net::ServerSocket> socket_;
+ net::IPEndPoint local_address_;
+
+ scoped_ptr<net::StreamSocket> accept_socket_;
+ AcceptedSocketsMap accepted_sockets_;
+
+ net::CompletionCallback accept_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHostTcpServer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TCP_SERVER_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server_unittest.cc b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server_unittest.cc
new file mode 100644
index 00000000000..9eba7412236
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_server_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/renderer_host/p2p/socket_host_tcp_server.h"
+
+#include "content/browser/renderer_host/p2p/socket_host_tcp.h"
+#include "content/browser/renderer_host/p2p/socket_host_test_utils.h"
+#include "net/base/completion_callback.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DeleteArg;
+using ::testing::DoAll;
+using ::testing::Return;
+
+namespace {
+
+class FakeServerSocket : public net::ServerSocket {
+ public:
+ FakeServerSocket()
+ : listening_(false),
+ accept_socket_(NULL) {
+ }
+
+ virtual ~FakeServerSocket() {}
+
+ bool listening() { return listening_; }
+
+ void AddIncoming(net::StreamSocket* socket) {
+ if (!accept_callback_.is_null()) {
+ DCHECK(incoming_sockets_.empty());
+ accept_socket_->reset(socket);
+ accept_socket_ = NULL;
+
+ // This copy is necessary because this implementation of ServerSocket
+ // bases logic on the null-ness of |accept_callback_| in the bound
+ // callback.
+ net::CompletionCallback cb = accept_callback_;
+ accept_callback_.Reset();
+ cb.Run(net::OK);
+ } else {
+ incoming_sockets_.push_back(socket);
+ }
+ }
+
+ virtual int Listen(const net::IPEndPoint& address, int backlog) OVERRIDE {
+ local_address_ = address;
+ listening_ = true;
+ return net::OK;
+ }
+
+ virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE {
+ *address = local_address_;
+ return net::OK;
+ }
+
+ virtual int Accept(scoped_ptr<net::StreamSocket>* socket,
+ const net::CompletionCallback& callback) OVERRIDE {
+ DCHECK(socket);
+ if (!incoming_sockets_.empty()) {
+ socket->reset(incoming_sockets_.front());
+ incoming_sockets_.pop_front();
+ return net::OK;
+ } else {
+ accept_socket_ = socket;
+ accept_callback_ = callback;
+ return net::ERR_IO_PENDING;
+ }
+ }
+
+ private:
+ bool listening_;
+
+ net::IPEndPoint local_address_;
+
+ scoped_ptr<net::StreamSocket>* accept_socket_;
+ net::CompletionCallback accept_callback_;
+
+ std::list<net::StreamSocket*> incoming_sockets_;
+};
+
+} // namespace
+
+namespace content {
+
+class P2PSocketHostTcpServerTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ socket_ = new FakeServerSocket();
+ socket_host_.reset(new P2PSocketHostTcpServer(
+ &sender_, 0, P2P_SOCKET_TCP_CLIENT));
+ socket_host_->socket_.reset(socket_);
+
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSocketCreated::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ socket_host_->Init(ParseAddress(kTestLocalIpAddress, 0),
+ ParseAddress(kTestIpAddress1, kTestPort1));
+ EXPECT_TRUE(socket_->listening());
+ }
+
+ // Needed by the chilt classes because only this class is a friend
+ // of P2PSocketHostTcp.
+ net::StreamSocket* GetSocketFormTcpSocketHost(P2PSocketHostTcp* host) {
+ return host->socket_.get();
+ }
+
+ MockIPCSender sender_;
+ FakeServerSocket* socket_; // Owned by |socket_host_|.
+ scoped_ptr<P2PSocketHostTcpServer> socket_host_;
+};
+
+// Accept incoming connection.
+TEST_F(P2PSocketHostTcpServerTest, Accept) {
+ FakeSocket* incoming = new FakeSocket(NULL);
+ incoming->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
+ net::IPEndPoint addr = ParseAddress(kTestIpAddress1, kTestPort1);
+ incoming->SetPeerAddress(addr);
+
+ EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->AddIncoming(incoming);
+
+ const int kAcceptedSocketId = 1;
+
+ scoped_ptr<P2PSocketHost> new_host(
+ socket_host_->AcceptIncomingTcpConnection(addr, kAcceptedSocketId));
+ ASSERT_TRUE(new_host.get() != NULL);
+ EXPECT_EQ(incoming, GetSocketFormTcpSocketHost(
+ reinterpret_cast<P2PSocketHostTcp*>(new_host.get())));
+}
+
+// Accept 2 simultaneous connections.
+TEST_F(P2PSocketHostTcpServerTest, Accept2) {
+ FakeSocket* incoming1 = new FakeSocket(NULL);
+ incoming1->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
+ net::IPEndPoint addr1 = ParseAddress(kTestIpAddress1, kTestPort1);
+ incoming1->SetPeerAddress(addr1);
+ FakeSocket* incoming2 = new FakeSocket(NULL);
+ incoming2->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
+ net::IPEndPoint addr2 = ParseAddress(kTestIpAddress2, kTestPort2);
+ incoming2->SetPeerAddress(addr2);
+
+ EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr1)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr2)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->AddIncoming(incoming1);
+ socket_->AddIncoming(incoming2);
+
+ const int kAcceptedSocketId1 = 1;
+ const int kAcceptedSocketId2 = 2;
+
+ scoped_ptr<P2PSocketHost> new_host1(
+ socket_host_->AcceptIncomingTcpConnection(addr1, kAcceptedSocketId1));
+ ASSERT_TRUE(new_host1.get() != NULL);
+ EXPECT_EQ(incoming1, GetSocketFormTcpSocketHost(
+ reinterpret_cast<P2PSocketHostTcp*>(new_host1.get())));
+ scoped_ptr<P2PSocketHost> new_host2(
+ socket_host_->AcceptIncomingTcpConnection(addr2, kAcceptedSocketId2));
+ ASSERT_TRUE(new_host2.get() != NULL);
+ EXPECT_EQ(incoming2, GetSocketFormTcpSocketHost(
+ reinterpret_cast<P2PSocketHostTcp*>(new_host2.get())));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_tcp_unittest.cc b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_unittest.cc
new file mode 100644
index 00000000000..340c6b57a5e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_tcp_unittest.cc
@@ -0,0 +1,353 @@
+// 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 "content/browser/renderer_host/p2p/socket_host_tcp.h"
+
+#include <deque>
+
+#include "base/sys_byteorder.h"
+#include "content/browser/renderer_host/p2p/socket_host_test_utils.h"
+#include "net/socket/stream_socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DeleteArg;
+using ::testing::DoAll;
+using ::testing::Return;
+
+namespace content {
+
+class P2PSocketHostTcpTestBase : public testing::Test {
+ protected:
+ explicit P2PSocketHostTcpTestBase(P2PSocketType type)
+ : socket_type_(type) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSocketCreated::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ if (socket_type_ == P2P_SOCKET_TCP_CLIENT) {
+ socket_host_.reset(new P2PSocketHostTcp(
+ &sender_, 0, P2P_SOCKET_TCP_CLIENT, NULL));
+ } else {
+ socket_host_.reset(new P2PSocketHostStunTcp(
+ &sender_, 0, P2P_SOCKET_STUN_TCP_CLIENT, NULL));
+ }
+
+ socket_ = new FakeSocket(&sent_data_);
+ socket_->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
+ socket_host_->socket_.reset(socket_);
+
+ dest_ = ParseAddress(kTestIpAddress1, kTestPort1);
+
+ local_address_ = ParseAddress(kTestLocalIpAddress, kTestPort1);
+
+ socket_host_->remote_address_ = dest_;
+ socket_host_->state_ = P2PSocketHost::STATE_CONNECTING;
+ socket_host_->OnConnected(net::OK);
+ }
+
+ std::string IntToSize(int size) {
+ std::string result;
+ uint16 size16 = base::HostToNet16(size);
+ result.resize(sizeof(size16));
+ memcpy(&result[0], &size16, sizeof(size16));
+ return result;
+ }
+
+ std::string sent_data_;
+ FakeSocket* socket_; // Owned by |socket_host_|.
+ scoped_ptr<P2PSocketHostTcpBase> socket_host_;
+ MockIPCSender sender_;
+
+ net::IPEndPoint local_address_;
+
+ net::IPEndPoint dest_;
+ net::IPEndPoint dest2_;
+
+ P2PSocketType socket_type_;
+};
+
+class P2PSocketHostTcpTest : public P2PSocketHostTcpTestBase {
+ protected:
+ P2PSocketHostTcpTest() : P2PSocketHostTcpTestBase(P2P_SOCKET_TCP_CLIENT) { }
+};
+
+class P2PSocketHostStunTcpTest : public P2PSocketHostTcpTestBase {
+ protected:
+ P2PSocketHostStunTcpTest()
+ : P2PSocketHostTcpTestBase(P2P_SOCKET_STUN_TCP_CLIENT) {
+ }
+};
+
+// Verify that we can send STUN message and that they are formatted
+// properly.
+TEST_F(P2PSocketHostTcpTest, SendStunNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(3)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ std::vector<char> packet3;
+ CreateStunError(&packet3);
+ socket_host_->Send(dest_, packet3);
+
+ std::string expected_data;
+ expected_data.append(IntToSize(packet1.size()));
+ expected_data.append(packet1.begin(), packet1.end());
+ expected_data.append(IntToSize(packet2.size()));
+ expected_data.append(packet2.begin(), packet2.end());
+ expected_data.append(IntToSize(packet3.size()));
+ expected_data.append(packet3.begin(), packet3.end());
+
+ EXPECT_EQ(expected_data, sent_data_);
+}
+
+// Verify that we can receive STUN messages from the socket, and that
+// the messages are parsed properly.
+TEST_F(P2PSocketHostTcpTest, ReceiveStun) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(3)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ std::vector<char> packet3;
+ CreateStunError(&packet3);
+ socket_host_->Send(dest_, packet3);
+
+ std::string received_data;
+ received_data.append(IntToSize(packet1.size()));
+ received_data.append(packet1.begin(), packet1.end());
+ received_data.append(IntToSize(packet2.size()));
+ received_data.append(packet2.begin(), packet2.end());
+ received_data.append(IntToSize(packet3.size()));
+ received_data.append(packet3.begin(), packet3.end());
+
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet1)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet2)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet3)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ size_t pos = 0;
+ size_t step_sizes[] = {3, 2, 1};
+ size_t step = 0;
+ while (pos < received_data.size()) {
+ size_t step_size = std::min(step_sizes[step], received_data.size() - pos);
+ socket_->AppendInputData(&received_data[pos], step_size);
+ pos += step_size;
+ if (++step >= arraysize(step_sizes))
+ step = 0;
+ }
+}
+
+// Verify that we can't send data before we've received STUN response
+// from the other side.
+TEST_F(P2PSocketHostTcpTest, SendDataNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnError::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest_, packet);
+
+ EXPECT_EQ(0U, sent_data_.size());
+}
+
+// Verify that we can send data after we've received STUN response
+// from the other side.
+TEST_F(P2PSocketHostTcpTest, SendAfterStunRequest) {
+ // Receive packet from |dest_|.
+ std::vector<char> request_packet;
+ CreateStunRequest(&request_packet);
+
+ std::string received_data;
+ received_data.append(IntToSize(request_packet.size()));
+ received_data.append(request_packet.begin(), request_packet.end());
+
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(request_packet)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->AppendInputData(&received_data[0], received_data.size());
+
+ // Now we should be able to send any data to |dest_|.
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest_, packet);
+
+ std::string expected_data;
+ expected_data.append(IntToSize(packet.size()));
+ expected_data.append(packet.begin(), packet.end());
+
+ EXPECT_EQ(expected_data, sent_data_);
+}
+
+// Verify that asynchronous writes are handled correctly.
+TEST_F(P2PSocketHostTcpTest, AsyncWrites) {
+ base::MessageLoop message_loop;
+
+ socket_->set_async_write(true);
+
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(2)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ message_loop.RunUntilIdle();
+
+ std::string expected_data;
+ expected_data.append(IntToSize(packet1.size()));
+ expected_data.append(packet1.begin(), packet1.end());
+ expected_data.append(IntToSize(packet2.size()));
+ expected_data.append(packet2.begin(), packet2.end());
+
+ EXPECT_EQ(expected_data, sent_data_);
+}
+
+// Verify that we can send STUN message and that they are formatted
+// properly.
+TEST_F(P2PSocketHostStunTcpTest, SendStunNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(3)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ std::vector<char> packet3;
+ CreateStunError(&packet3);
+ socket_host_->Send(dest_, packet3);
+
+ std::string expected_data;
+ expected_data.append(packet1.begin(), packet1.end());
+ expected_data.append(packet2.begin(), packet2.end());
+ expected_data.append(packet3.begin(), packet3.end());
+
+ EXPECT_EQ(expected_data, sent_data_);
+}
+
+// Verify that we can receive STUN messages from the socket, and that
+// the messages are parsed properly.
+TEST_F(P2PSocketHostStunTcpTest, ReceiveStun) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(3)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ std::vector<char> packet3;
+ CreateStunError(&packet3);
+ socket_host_->Send(dest_, packet3);
+
+ std::string received_data;
+ received_data.append(packet1.begin(), packet1.end());
+ received_data.append(packet2.begin(), packet2.end());
+ received_data.append(packet3.begin(), packet3.end());
+
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet1)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet2)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(packet3)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ size_t pos = 0;
+ size_t step_sizes[] = {3, 2, 1};
+ size_t step = 0;
+ while (pos < received_data.size()) {
+ size_t step_size = std::min(step_sizes[step], received_data.size() - pos);
+ socket_->AppendInputData(&received_data[pos], step_size);
+ pos += step_size;
+ if (++step >= arraysize(step_sizes))
+ step = 0;
+ }
+}
+
+// Verify that we can't send data before we've received STUN response
+// from the other side.
+TEST_F(P2PSocketHostStunTcpTest, SendDataNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnError::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest_, packet);
+
+ EXPECT_EQ(0U, sent_data_.size());
+}
+
+// Verify that asynchronous writes are handled correctly.
+TEST_F(P2PSocketHostStunTcpTest, AsyncWrites) {
+ base::MessageLoop message_loop;
+
+ socket_->set_async_write(true);
+
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(2)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest_, packet2);
+
+ message_loop.RunUntilIdle();
+
+ std::string expected_data;
+ expected_data.append(packet1.begin(), packet1.end());
+ expected_data.append(packet2.begin(), packet2.end());
+
+ EXPECT_EQ(expected_data, sent_data_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_test_utils.h b/chromium/content/browser/renderer_host/p2p/socket_host_test_utils.h
new file mode 100644
index 00000000000..77da0cdb792
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_test_utils.h
@@ -0,0 +1,320 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TEST_UTILS_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TEST_UTILS_H_
+
+#include <vector>
+
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "base/sys_byteorder.h"
+#include "base/thread_task_runner_handle.h"
+#include "content/common/p2p_messages.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_sender.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/socket/stream_socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestLocalIpAddress[] = "123.44.22.4";
+const char kTestIpAddress1[] = "123.44.22.31";
+const int kTestPort1 = 234;
+const char kTestIpAddress2[] = "133.11.22.33";
+const int kTestPort2 = 543;
+
+const int kStunHeaderSize = 20;
+const uint16 kStunBindingRequest = 0x0001;
+const uint16 kStunBindingResponse = 0x0102;
+const uint16 kStunBindingError = 0x0111;
+const uint32 kStunMagicCookie = 0x2112A442;
+
+class MockIPCSender : public IPC::Sender {
+ public:
+ MockIPCSender();
+ virtual ~MockIPCSender();
+
+ MOCK_METHOD1(Send, bool(IPC::Message* msg));
+};
+
+MockIPCSender::MockIPCSender() { }
+MockIPCSender::~MockIPCSender() { }
+
+class FakeSocket : public net::StreamSocket {
+ public:
+ FakeSocket(std::string* written_data);
+ virtual ~FakeSocket();
+
+ void set_async_write(bool async_write) { async_write_ = async_write; }
+ void AppendInputData(const char* data, int data_size);
+ int input_pos() const { return input_pos_; }
+ bool read_pending() const { return read_pending_; }
+ void SetPeerAddress(const net::IPEndPoint& peer_address);
+ void SetLocalAddress(const net::IPEndPoint& local_address);
+
+ // net::Socket implementation.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual int Connect(const net::CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(net::IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE;
+ virtual const net::BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual net::NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(net::SSLInfo* ssl_info) OVERRIDE;
+
+ private:
+ void DoAsyncWrite(scoped_refptr<net::IOBuffer> buf, int buf_len,
+ const net::CompletionCallback& callback);
+
+ bool read_pending_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ int read_buffer_size_;
+ net::CompletionCallback read_callback_;
+
+ std::string input_data_;
+ int input_pos_;
+
+ std::string* written_data_;
+ bool async_write_;
+ bool write_pending_;
+
+ net::IPEndPoint peer_address_;
+ net::IPEndPoint local_address_;
+
+ net::BoundNetLog net_log_;
+};
+
+FakeSocket::FakeSocket(std::string* written_data)
+ : read_pending_(false),
+ input_pos_(0),
+ written_data_(written_data),
+ async_write_(false),
+ write_pending_(false) {
+}
+
+FakeSocket::~FakeSocket() { }
+
+void FakeSocket::AppendInputData(const char* data, int data_size) {
+ input_data_.insert(input_data_.end(), data, data + data_size);
+ // Complete pending read if any.
+ if (read_pending_) {
+ read_pending_ = false;
+ int result = std::min(read_buffer_size_,
+ static_cast<int>(input_data_.size() - input_pos_));
+ CHECK(result > 0);
+ memcpy(read_buffer_->data(), &input_data_[0] + input_pos_, result);
+ input_pos_ += result;
+ read_buffer_ = NULL;
+ net::CompletionCallback cb = read_callback_;
+ read_callback_.Reset();
+ cb.Run(result);
+ }
+}
+
+void FakeSocket::SetPeerAddress(const net::IPEndPoint& peer_address) {
+ peer_address_ = peer_address;
+}
+
+void FakeSocket::SetLocalAddress(const net::IPEndPoint& local_address) {
+ local_address_ = local_address;
+}
+
+int FakeSocket::Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(buf);
+ if (input_pos_ < static_cast<int>(input_data_.size())){
+ int result = std::min(buf_len,
+ static_cast<int>(input_data_.size()) - input_pos_);
+ memcpy(buf->data(), &(*input_data_.begin()) + input_pos_, result);
+ input_pos_ += result;
+ return result;
+ } else {
+ read_pending_ = true;
+ read_buffer_ = buf;
+ read_buffer_size_ = buf_len;
+ read_callback_ = callback;
+ return net::ERR_IO_PENDING;
+ }
+}
+
+int FakeSocket::Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK(!write_pending_);
+
+ if (async_write_) {
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::Bind(
+ &FakeSocket::DoAsyncWrite, base::Unretained(this),
+ scoped_refptr<net::IOBuffer>(buf), buf_len, callback));
+ write_pending_ = true;
+ return net::ERR_IO_PENDING;
+ }
+
+ if (written_data_) {
+ written_data_->insert(written_data_->end(),
+ buf->data(), buf->data() + buf_len);
+ }
+ return buf_len;
+}
+
+void FakeSocket::DoAsyncWrite(scoped_refptr<net::IOBuffer> buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ write_pending_ = false;
+
+ if (written_data_) {
+ written_data_->insert(written_data_->end(),
+ buf->data(), buf->data() + buf_len);
+ }
+ callback.Run(buf_len);
+}
+
+bool FakeSocket::SetReceiveBufferSize(int32 size) {
+ NOTIMPLEMENTED();
+ return false;
+}
+bool FakeSocket::SetSendBufferSize(int32 size) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+int FakeSocket::Connect(const net::CompletionCallback& callback) {
+ return 0;
+}
+
+void FakeSocket::Disconnect() {
+ NOTREACHED();
+}
+
+bool FakeSocket::IsConnected() const {
+ return true;
+}
+
+bool FakeSocket::IsConnectedAndIdle() const {
+ return false;
+}
+
+int FakeSocket::GetPeerAddress(net::IPEndPoint* address) const {
+ *address = peer_address_;
+ return net::OK;
+}
+
+int FakeSocket::GetLocalAddress(net::IPEndPoint* address) const {
+ *address = local_address_;
+ return net::OK;
+}
+
+const net::BoundNetLog& FakeSocket::NetLog() const {
+ NOTREACHED();
+ return net_log_;
+}
+
+void FakeSocket::SetSubresourceSpeculation() {
+ NOTREACHED();
+}
+
+void FakeSocket::SetOmniboxSpeculation() {
+ NOTREACHED();
+}
+
+bool FakeSocket::WasEverUsed() const {
+ return true;
+}
+
+bool FakeSocket::UsingTCPFastOpen() const {
+ return false;
+}
+
+bool FakeSocket::WasNpnNegotiated() const {
+ return false;
+}
+
+net::NextProto FakeSocket::GetNegotiatedProtocol() const {
+ return net::kProtoUnknown;
+}
+
+bool FakeSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
+ return false;
+}
+
+void CreateRandomPacket(std::vector<char>* packet) {
+ size_t size = kStunHeaderSize + rand() % 1000;
+ packet->resize(size);
+ for (size_t i = 0; i < size; i++) {
+ (*packet)[i] = rand() % 256;
+ }
+ // Always set the first bit to ensure that generated packet is not
+ // valid STUN packet.
+ (*packet)[0] = (*packet)[0] | 0x80;
+}
+
+void CreateStunPacket(std::vector<char>* packet, uint16 type) {
+ CreateRandomPacket(packet);
+ *reinterpret_cast<uint16*>(&*packet->begin()) = base::HostToNet16(type);
+ *reinterpret_cast<uint16*>(&*packet->begin() + 2) =
+ base::HostToNet16(packet->size() - kStunHeaderSize);
+ *reinterpret_cast<uint32*>(&*packet->begin() + 4) =
+ base::HostToNet32(kStunMagicCookie);
+}
+
+void CreateStunRequest(std::vector<char>* packet) {
+ CreateStunPacket(packet, kStunBindingRequest);
+}
+
+void CreateStunResponse(std::vector<char>* packet) {
+ CreateStunPacket(packet, kStunBindingResponse);
+}
+
+void CreateStunError(std::vector<char>* packet) {
+ CreateStunPacket(packet, kStunBindingError);
+}
+
+net::IPEndPoint ParseAddress(const std::string ip_str, int port) {
+ net::IPAddressNumber ip;
+ EXPECT_TRUE(net::ParseIPLiteralToNumber(ip_str, &ip));
+ return net::IPEndPoint(ip, port);
+}
+
+MATCHER_P(MatchMessage, type, "") {
+ return arg->type() == type;
+}
+
+MATCHER_P(MatchPacketMessage, packet_content, "") {
+ if (arg->type() != P2PMsg_OnDataReceived::ID)
+ return false;
+ P2PMsg_OnDataReceived::Param params;
+ P2PMsg_OnDataReceived::Read(arg, &params);
+ return params.c == packet_content;
+}
+
+MATCHER_P(MatchIncomingSocketMessage, address, "") {
+ if (arg->type() != P2PMsg_OnIncomingTcpConnection::ID)
+ return false;
+ P2PMsg_OnIncomingTcpConnection::Param params;
+ P2PMsg_OnIncomingTcpConnection::Read(
+ arg, &params);
+ return params.b == address;
+}
+
+} // namespace
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_TEST_UTILS_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_udp.cc b/chromium/content/browser/renderer_host/p2p/socket_host_udp.cc
new file mode 100644
index 00000000000..bcfb282a2c4
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_udp.cc
@@ -0,0 +1,260 @@
+// 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 "content/browser/renderer_host/p2p/socket_host_udp.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "content/common/p2p_messages.h"
+#include "ipc/ipc_sender.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+// UDP packets cannot be bigger than 64k.
+const int kReadBufferSize = 65536;
+
+// Defines set of transient errors. These errors are ignored when we get them
+// from sendto() or recvfrom() calls.
+//
+// net::ERR_OUT_OF_MEMORY
+//
+// This is caused by ENOBUFS which means the buffer of the network interface
+// is full.
+//
+// net::ERR_CONNECTION_RESET
+//
+// This is caused by WSAENETRESET or WSAECONNRESET which means the
+// last send resulted in an "ICMP Port Unreachable" message.
+bool IsTransientError(int error) {
+ return error == net::ERR_ADDRESS_UNREACHABLE ||
+ error == net::ERR_ADDRESS_INVALID ||
+ error == net::ERR_ACCESS_DENIED ||
+ error == net::ERR_CONNECTION_RESET ||
+ error == net::ERR_OUT_OF_MEMORY;
+}
+
+uint64 GetUniqueEventId(const content::P2PSocketHostUdp* obj,
+ uint64 packet_id) {
+ uint64 uid = reinterpret_cast<uintptr_t>(obj);
+ uid <<= 32;
+ uid |= packet_id;
+ return uid;
+}
+
+} // namespace
+
+namespace content {
+
+P2PSocketHostUdp::PendingPacket::PendingPacket(
+ const net::IPEndPoint& to, const std::vector<char>& content, uint64 id)
+ : to(to),
+ data(new net::IOBuffer(content.size())),
+ size(content.size()),
+ id(id) {
+ memcpy(data->data(), &content[0], size);
+}
+
+P2PSocketHostUdp::PendingPacket::~PendingPacket() {
+}
+
+P2PSocketHostUdp::P2PSocketHostUdp(IPC::Sender* message_sender, int id)
+ : P2PSocketHost(message_sender, id),
+ socket_(new net::UDPServerSocket(NULL, net::NetLog::Source())),
+ send_pending_(false),
+ send_packet_count_(0) {
+}
+
+P2PSocketHostUdp::~P2PSocketHostUdp() {
+ if (state_ == STATE_OPEN) {
+ DCHECK(socket_.get());
+ socket_.reset();
+ }
+}
+
+bool P2PSocketHostUdp::Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) {
+ DCHECK_EQ(state_, STATE_UNINITIALIZED);
+
+ int result = socket_->Listen(local_address);
+ if (result < 0) {
+ LOG(ERROR) << "bind() failed: " << result;
+ OnError();
+ return false;
+ }
+
+ net::IPEndPoint address;
+ result = socket_->GetLocalAddress(&address);
+ if (result < 0) {
+ LOG(ERROR) << "P2PSocketHostUdp::Init(): unable to get local address: "
+ << result;
+ OnError();
+ return false;
+ }
+ VLOG(1) << "Local address: " << address.ToString();
+
+ state_ = STATE_OPEN;
+
+ message_sender_->Send(new P2PMsg_OnSocketCreated(id_, address));
+
+ recv_buffer_ = new net::IOBuffer(kReadBufferSize);
+ DoRead();
+
+ return true;
+}
+
+void P2PSocketHostUdp::OnError() {
+ socket_.reset();
+ send_queue_.clear();
+
+ if (state_ == STATE_UNINITIALIZED || state_ == STATE_OPEN)
+ message_sender_->Send(new P2PMsg_OnError(id_));
+
+ state_ = STATE_ERROR;
+}
+
+void P2PSocketHostUdp::DoRead() {
+ int result;
+ do {
+ result = socket_->RecvFrom(
+ recv_buffer_.get(),
+ kReadBufferSize,
+ &recv_address_,
+ base::Bind(&P2PSocketHostUdp::OnRecv, base::Unretained(this)));
+ if (result == net::ERR_IO_PENDING)
+ return;
+ HandleReadResult(result);
+ } while (state_ == STATE_OPEN);
+}
+
+void P2PSocketHostUdp::OnRecv(int result) {
+ HandleReadResult(result);
+ if (state_ == STATE_OPEN) {
+ DoRead();
+ }
+}
+
+void P2PSocketHostUdp::HandleReadResult(int result) {
+ DCHECK_EQ(state_, STATE_OPEN);
+
+ if (result > 0) {
+ std::vector<char> data(recv_buffer_->data(), recv_buffer_->data() + result);
+
+ if (connected_peers_.find(recv_address_) == connected_peers_.end()) {
+ P2PSocketHost::StunMessageType type;
+ bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
+ if (stun && IsRequestOrResponse(type)) {
+ connected_peers_.insert(recv_address_);
+ } else if (!stun || type == STUN_DATA_INDICATION) {
+ LOG(ERROR) << "Received unexpected data packet from "
+ << recv_address_.ToString()
+ << " before STUN binding is finished.";
+ return;
+ }
+ }
+
+ message_sender_->Send(new P2PMsg_OnDataReceived(id_, recv_address_, data));
+ } else if (result < 0 && !IsTransientError(result)) {
+ LOG(ERROR) << "Error when reading from UDP socket: " << result;
+ OnError();
+ }
+}
+
+void P2PSocketHostUdp::Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) {
+ if (!socket_) {
+ // The Send message may be sent after the an OnError message was
+ // sent by hasn't been processed the renderer.
+ return;
+ }
+
+ if (connected_peers_.find(to) == connected_peers_.end()) {
+ P2PSocketHost::StunMessageType type;
+ bool stun = GetStunPacketType(&*data.begin(), data.size(), &type);
+ if (!stun || type == STUN_DATA_INDICATION) {
+ LOG(ERROR) << "Page tried to send a data packet to " << to.ToString()
+ << " before STUN binding is finished.";
+ OnError();
+ return;
+ }
+ }
+
+ if (send_pending_) {
+ send_queue_.push_back(PendingPacket(to, data, send_packet_count_));
+ } else {
+ PendingPacket packet(to, data, send_packet_count_);
+ DoSend(packet);
+ }
+ ++send_packet_count_;
+}
+
+void P2PSocketHostUdp::DoSend(const PendingPacket& packet) {
+ TRACE_EVENT_ASYNC_BEGIN1("p2p", "Udp::DoSend",
+ GetUniqueEventId(this, packet.id),
+ "size", packet.size);
+ int result = socket_->SendTo(
+ packet.data.get(),
+ packet.size,
+ packet.to,
+ base::Bind(&P2PSocketHostUdp::OnSend, base::Unretained(this), packet.id));
+
+ // sendto() may return an error, e.g. if we've received an ICMP Destination
+ // Unreachable message. When this happens try sending the same packet again,
+ // and just drop it if it fails again.
+ if (IsTransientError(result)) {
+ result = socket_->SendTo(
+ packet.data.get(),
+ packet.size,
+ packet.to,
+ base::Bind(&P2PSocketHostUdp::OnSend, base::Unretained(this),
+ packet.id));
+ }
+
+ if (result == net::ERR_IO_PENDING) {
+ send_pending_ = true;
+ } else {
+ HandleSendResult(packet.id, result);
+ }
+}
+
+void P2PSocketHostUdp::OnSend(uint64 packet_id, int result) {
+ DCHECK(send_pending_);
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+
+ send_pending_ = false;
+
+ HandleSendResult(packet_id, result);
+
+ // Send next packets if we have them waiting in the buffer.
+ while (state_ == STATE_OPEN && !send_queue_.empty() && !send_pending_) {
+ DoSend(send_queue_.front());
+ send_queue_.pop_front();
+ }
+}
+
+void P2PSocketHostUdp::HandleSendResult(uint64 packet_id, int result) {
+ TRACE_EVENT_ASYNC_END1("p2p", "Udp::DoSend",
+ GetUniqueEventId(this, packet_id),
+ "result", result);
+ if (result > 0) {
+ message_sender_->Send(new P2PMsg_OnSendComplete(id_));
+ } else if (IsTransientError(result)) {
+ LOG(INFO) << "sendto() has failed twice returning a "
+ " transient error. Dropping the packet.";
+ } else if (result < 0) {
+ LOG(ERROR) << "Error when sending data in UDP socket: " << result;
+ OnError();
+ }
+}
+
+P2PSocketHost* P2PSocketHostUdp::AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) {
+ NOTREACHED();
+ OnError();
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_udp.h b/chromium/content/browser/renderer_host/p2p/socket_host_udp.h
new file mode 100644
index 00000000000..e9eb36393f4
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_udp.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_UDP_H_
+#define CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_UDP_H_
+
+#include <deque>
+#include <set>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/p2p/socket_host.h"
+#include "content/common/content_export.h"
+#include "content/common/p2p_sockets.h"
+#include "net/base/ip_endpoint.h"
+#include "net/udp/udp_server_socket.h"
+
+namespace content {
+
+class CONTENT_EXPORT P2PSocketHostUdp : public P2PSocketHost {
+ public:
+ P2PSocketHostUdp(IPC::Sender* message_sender, int id);
+ virtual ~P2PSocketHostUdp();
+
+ // P2PSocketHost overrides.
+ virtual bool Init(const net::IPEndPoint& local_address,
+ const net::IPEndPoint& remote_address) OVERRIDE;
+ virtual void Send(const net::IPEndPoint& to,
+ const std::vector<char>& data) OVERRIDE;
+ virtual P2PSocketHost* AcceptIncomingTcpConnection(
+ const net::IPEndPoint& remote_address, int id) OVERRIDE;
+
+ private:
+ friend class P2PSocketHostUdpTest;
+
+ typedef std::set<net::IPEndPoint> ConnectedPeerSet;
+
+ struct PendingPacket {
+ PendingPacket(const net::IPEndPoint& to,
+ const std::vector<char>& content,
+ uint64 id);
+ ~PendingPacket();
+ net::IPEndPoint to;
+ scoped_refptr<net::IOBuffer> data;
+ int size;
+ uint64 id;
+ };
+
+ void OnError();
+
+ void DoRead();
+ void OnRecv(int result);
+ void HandleReadResult(int result);
+
+ void DoSend(const PendingPacket& packet);
+ void OnSend(uint64 packet_id, int result);
+ void HandleSendResult(uint64 packet_id, int result);
+
+ scoped_ptr<net::DatagramServerSocket> socket_;
+ scoped_refptr<net::IOBuffer> recv_buffer_;
+ net::IPEndPoint recv_address_;
+
+ std::deque<PendingPacket> send_queue_;
+ bool send_pending_;
+ uint64 send_packet_count_;
+
+ // Set of peer for which we have received STUN binding request or
+ // response or relay allocation request or response.
+ ConnectedPeerSet connected_peers_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PSocketHostUdp);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_P2P_SOCKET_HOST_UDP_H_
diff --git a/chromium/content/browser/renderer_host/p2p/socket_host_udp_unittest.cc b/chromium/content/browser/renderer_host/p2p/socket_host_udp_unittest.cc
new file mode 100644
index 00000000000..9ecd56abd54
--- /dev/null
+++ b/chromium/content/browser/renderer_host/p2p/socket_host_udp_unittest.cc
@@ -0,0 +1,292 @@
+// 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 "content/browser/renderer_host/p2p/socket_host_udp.h"
+
+#include <deque>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "content/browser/renderer_host/p2p/socket_host_test_utils.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/udp/datagram_server_socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DeleteArg;
+using ::testing::DoAll;
+using ::testing::Return;
+
+namespace {
+
+class FakeDatagramServerSocket : public net::DatagramServerSocket {
+ public:
+ typedef std::pair<net::IPEndPoint, std::vector<char> > UDPPacket;
+
+ // P2PSocketHostUdp destroyes a socket on errors so sent packets
+ // need to be stored outside of this object.
+ explicit FakeDatagramServerSocket(std::deque<UDPPacket>* sent_packets)
+ : sent_packets_(sent_packets) {
+ }
+
+ virtual void Close() OVERRIDE {
+ }
+
+ virtual int GetPeerAddress(net::IPEndPoint* address) const OVERRIDE {
+ NOTREACHED();
+ return net::ERR_SOCKET_NOT_CONNECTED;
+ }
+
+ virtual int GetLocalAddress(net::IPEndPoint* address) const OVERRIDE {
+ *address = address_;
+ return 0;
+ }
+
+ virtual int Listen(const net::IPEndPoint& address) OVERRIDE {
+ address_ = address;
+ return 0;
+ }
+
+ virtual int RecvFrom(net::IOBuffer* buf, int buf_len,
+ net::IPEndPoint* address,
+ const net::CompletionCallback& callback) OVERRIDE {
+ CHECK(recv_callback_.is_null());
+ if (incoming_packets_.size() > 0) {
+ scoped_refptr<net::IOBuffer> buffer(buf);
+ int size = std::min(
+ static_cast<int>(incoming_packets_.front().second.size()), buf_len);
+ memcpy(buffer->data(), &*incoming_packets_.front().second.begin(), size);
+ *address = incoming_packets_.front().first;
+ incoming_packets_.pop_front();
+ return size;
+ } else {
+ recv_callback_ = callback;
+ recv_buffer_ = buf;
+ recv_size_ = buf_len;
+ recv_address_ = address;
+ return net::ERR_IO_PENDING;
+ }
+ }
+
+ virtual int SendTo(net::IOBuffer* buf, int buf_len,
+ const net::IPEndPoint& address,
+ const net::CompletionCallback& callback) OVERRIDE {
+ scoped_refptr<net::IOBuffer> buffer(buf);
+ std::vector<char> data_vector(buffer->data(), buffer->data() + buf_len);
+ sent_packets_->push_back(UDPPacket(address, data_vector));
+ return buf_len;
+ }
+
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE {
+ return true;
+ }
+
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE {
+ return true;
+ }
+
+ void ReceivePacket(const net::IPEndPoint& address, std::vector<char> data) {
+ if (!recv_callback_.is_null()) {
+ int size = std::min(recv_size_, static_cast<int>(data.size()));
+ memcpy(recv_buffer_->data(), &*data.begin(), size);
+ *recv_address_ = address;
+ net::CompletionCallback cb = recv_callback_;
+ recv_callback_.Reset();
+ recv_buffer_ = NULL;
+ cb.Run(size);
+ } else {
+ incoming_packets_.push_back(UDPPacket(address, data));
+ }
+ }
+
+ virtual const net::BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void AllowAddressReuse() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void AllowBroadcast() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual int JoinGroup(
+ const net::IPAddressNumber& group_address) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ virtual int LeaveGroup(
+ const net::IPAddressNumber& group_address) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ virtual int SetMulticastTimeToLive(int time_to_live) OVERRIDE {
+ NOTIMPLEMENTED();
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ virtual int SetMulticastLoopbackMode(bool loopback) OVERRIDE {
+ NOTIMPLEMENTED();
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ private:
+ net::IPEndPoint address_;
+ std::deque<UDPPacket>* sent_packets_;
+ std::deque<UDPPacket> incoming_packets_;
+ net::BoundNetLog net_log_;
+
+ scoped_refptr<net::IOBuffer> recv_buffer_;
+ net::IPEndPoint* recv_address_;
+ int recv_size_;
+ net::CompletionCallback recv_callback_;
+};
+
+} // namespace
+
+namespace content {
+
+class P2PSocketHostUdpTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSocketCreated::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ socket_host_.reset(new P2PSocketHostUdp(&sender_, 0));
+ socket_ = new FakeDatagramServerSocket(&sent_packets_);
+ socket_host_->socket_.reset(socket_);
+
+ local_address_ = ParseAddress(kTestLocalIpAddress, kTestPort1);
+ socket_host_->Init(local_address_, net::IPEndPoint());
+
+ dest1_ = ParseAddress(kTestIpAddress1, kTestPort1);
+ dest2_ = ParseAddress(kTestIpAddress2, kTestPort2);
+ }
+
+ std::deque<FakeDatagramServerSocket::UDPPacket> sent_packets_;
+ FakeDatagramServerSocket* socket_; // Owned by |socket_host_|.
+ scoped_ptr<P2PSocketHostUdp> socket_host_;
+ MockIPCSender sender_;
+
+ net::IPEndPoint local_address_;
+
+ net::IPEndPoint dest1_;
+ net::IPEndPoint dest2_;
+};
+
+// Verify that we can send STUN messages before we receive anything
+// from the other side.
+TEST_F(P2PSocketHostUdpTest, SendStunNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .Times(3)
+ .WillRepeatedly(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet1;
+ CreateStunRequest(&packet1);
+ socket_host_->Send(dest1_, packet1);
+
+ std::vector<char> packet2;
+ CreateStunResponse(&packet2);
+ socket_host_->Send(dest1_, packet2);
+
+ std::vector<char> packet3;
+ CreateStunError(&packet3);
+ socket_host_->Send(dest1_, packet3);
+
+ ASSERT_EQ(sent_packets_.size(), 3U);
+ ASSERT_EQ(sent_packets_[0].second, packet1);
+ ASSERT_EQ(sent_packets_[1].second, packet2);
+ ASSERT_EQ(sent_packets_[2].second, packet3);
+}
+
+// Verify that no data packets can be sent before STUN binding has
+// finished.
+TEST_F(P2PSocketHostUdpTest, SendDataNoAuth) {
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnError::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest1_, packet);
+
+ ASSERT_EQ(sent_packets_.size(), 0U);
+}
+
+// Verify that we can send data after we've received STUN request
+// from the other side.
+TEST_F(P2PSocketHostUdpTest, SendAfterStunRequest) {
+ // Receive packet from |dest1_|.
+ std::vector<char> request_packet;
+ CreateStunRequest(&request_packet);
+
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(request_packet)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->ReceivePacket(dest1_, request_packet);
+
+ // Now we should be able to send any data to |dest1_|.
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest1_, packet);
+
+ ASSERT_EQ(1U, sent_packets_.size());
+ ASSERT_EQ(dest1_, sent_packets_[0].first);
+}
+
+// Verify that we can send data after we've received STUN response
+// from the other side.
+TEST_F(P2PSocketHostUdpTest, SendAfterStunResponse) {
+ // Receive packet from |dest1_|.
+ std::vector<char> request_packet;
+ CreateStunRequest(&request_packet);
+
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(request_packet)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->ReceivePacket(dest1_, request_packet);
+
+ // Now we should be able to send any data to |dest1_|.
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnSendComplete::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ socket_host_->Send(dest1_, packet);
+
+ ASSERT_EQ(1U, sent_packets_.size());
+ ASSERT_EQ(dest1_, sent_packets_[0].first);
+}
+
+// Verify messages still cannot be sent to an unathorized host after
+// successful binding with different host.
+TEST_F(P2PSocketHostUdpTest, SendAfterStunResponseDifferentHost) {
+ // Receive packet from |dest1_|.
+ std::vector<char> request_packet;
+ CreateStunRequest(&request_packet);
+
+ EXPECT_CALL(sender_, Send(MatchPacketMessage(request_packet)))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_->ReceivePacket(dest1_, request_packet);
+
+ // Should fail when trying to send the same packet to |dest2_|.
+ std::vector<char> packet;
+ CreateRandomPacket(&packet);
+ EXPECT_CALL(sender_, Send(
+ MatchMessage(static_cast<uint32>(P2PMsg_OnError::ID))))
+ .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
+ socket_host_->Send(dest2_, packet);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/OWNERS b/chromium/content/browser/renderer_host/pepper/OWNERS
new file mode 100644
index 00000000000..dc19a03cb21
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/OWNERS
@@ -0,0 +1,3 @@
+dmichael@chromium.org
+raymes@chromium.org
+yzshen@chromium.org
diff --git a/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc
new file mode 100644
index 00000000000..10eef8b5515
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc
@@ -0,0 +1,168 @@
+// 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 "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+
+#include "content/browser/tracing/trace_message_filter.h"
+#include "content/common/pepper_renderer_instance_data.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace content {
+
+// static
+BrowserPpapiHost* BrowserPpapiHost::CreateExternalPluginProcess(
+ IPC::Sender* sender,
+ ppapi::PpapiPermissions permissions,
+ base::ProcessHandle plugin_child_process,
+ IPC::ChannelProxy* channel,
+ net::HostResolver* host_resolver,
+ int render_process_id,
+ int render_view_id,
+ const base::FilePath& profile_directory) {
+ scoped_refptr<PepperMessageFilter> pepper_message_filter(
+ new PepperMessageFilter(permissions,
+ host_resolver,
+ render_process_id,
+ render_view_id));
+
+ // The plugin name and path shouldn't be needed for external plugins.
+ BrowserPpapiHostImpl* browser_ppapi_host =
+ new BrowserPpapiHostImpl(sender, permissions, std::string(),
+ base::FilePath(), profile_directory, true,
+ pepper_message_filter);
+ browser_ppapi_host->set_plugin_process_handle(plugin_child_process);
+
+ channel->AddFilter(pepper_message_filter);
+ channel->AddFilter(browser_ppapi_host->message_filter().get());
+ channel->AddFilter(new TraceMessageFilter());
+
+ return browser_ppapi_host;
+}
+
+BrowserPpapiHostImpl::BrowserPpapiHostImpl(
+ IPC::Sender* sender,
+ const ppapi::PpapiPermissions& permissions,
+ const std::string& plugin_name,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ bool external_plugin,
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter)
+ : ppapi_host_(new ppapi::host::PpapiHost(sender, permissions)),
+ plugin_process_handle_(base::kNullProcessHandle),
+ plugin_name_(plugin_name),
+ plugin_path_(plugin_path),
+ profile_data_directory_(profile_data_directory),
+ external_plugin_(external_plugin) {
+ message_filter_ = new HostMessageFilter(ppapi_host_.get());
+ ppapi_host_->AddHostFactoryFilter(scoped_ptr<ppapi::host::HostFactory>(
+ new ContentBrowserPepperHostFactory(this, pepper_message_filter)));
+}
+
+BrowserPpapiHostImpl::~BrowserPpapiHostImpl() {
+ // Notify the filter so it won't foward messages to us.
+ message_filter_->OnHostDestroyed();
+
+ // Delete the host explicitly first. This shutdown will destroy the
+ // resources, which may want to do cleanup in their destructors and expect
+ // their pointers to us to be valid.
+ ppapi_host_.reset();
+}
+
+ppapi::host::PpapiHost* BrowserPpapiHostImpl::GetPpapiHost() {
+ return ppapi_host_.get();
+}
+
+base::ProcessHandle BrowserPpapiHostImpl::GetPluginProcessHandle() const {
+ // Handle should previously have been set before use.
+ DCHECK(plugin_process_handle_ != base::kNullProcessHandle);
+ return plugin_process_handle_;
+}
+
+bool BrowserPpapiHostImpl::IsValidInstance(PP_Instance instance) const {
+ return instance_map_.find(instance) != instance_map_.end();
+}
+
+bool BrowserPpapiHostImpl::GetRenderViewIDsForInstance(
+ PP_Instance instance,
+ int* render_process_id,
+ int* render_view_id) const {
+ InstanceMap::const_iterator found = instance_map_.find(instance);
+ if (found == instance_map_.end()) {
+ *render_process_id = 0;
+ *render_view_id = 0;
+ return false;
+ }
+
+ *render_process_id = found->second.render_process_id;
+ *render_view_id = found->second.render_view_id;
+ return true;
+}
+
+const std::string& BrowserPpapiHostImpl::GetPluginName() {
+ return plugin_name_;
+}
+
+const base::FilePath& BrowserPpapiHostImpl::GetPluginPath() {
+ return plugin_path_;
+}
+
+const base::FilePath& BrowserPpapiHostImpl::GetProfileDataDirectory() {
+ return profile_data_directory_;
+}
+
+GURL BrowserPpapiHostImpl::GetDocumentURLForInstance(PP_Instance instance) {
+ InstanceMap::const_iterator found = instance_map_.find(instance);
+ if (found == instance_map_.end())
+ return GURL();
+ return found->second.document_url;
+}
+
+GURL BrowserPpapiHostImpl::GetPluginURLForInstance(PP_Instance instance) {
+ InstanceMap::const_iterator found = instance_map_.find(instance);
+ if (found == instance_map_.end())
+ return GURL();
+ return found->second.plugin_url;
+}
+
+void BrowserPpapiHostImpl::AddInstance(
+ PP_Instance instance,
+ const PepperRendererInstanceData& instance_data) {
+ DCHECK(instance_map_.find(instance) == instance_map_.end());
+ instance_map_[instance] = instance_data;
+}
+
+void BrowserPpapiHostImpl::DeleteInstance(PP_Instance instance) {
+ InstanceMap::iterator found = instance_map_.find(instance);
+ if (found == instance_map_.end()) {
+ NOTREACHED();
+ return;
+ }
+ instance_map_.erase(found);
+}
+
+bool BrowserPpapiHostImpl::HostMessageFilter::OnMessageReceived(
+ const IPC::Message& msg) {
+ // Don't forward messages if our owner object has been destroyed.
+ if (!ppapi_host_)
+ return false;
+
+ /* TODO(brettw) when we add messages, here, the code should look like this:
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(BrowserPpapiHostImpl, msg)
+ // Add necessary message handlers here.
+ IPC_MESSAGE_UNHANDLED(handled = ppapi_host_->OnMessageReceived(msg))
+ IPC_END_MESSAGE_MAP();
+ return handled;
+ */
+ return ppapi_host_->OnMessageReceived(msg);
+}
+
+void BrowserPpapiHostImpl::HostMessageFilter::OnHostDestroyed() {
+ DCHECK(ppapi_host_);
+ ppapi_host_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h
new file mode 100644
index 00000000000..c46ab13b220
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h
@@ -0,0 +1,118 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_IMPL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "content/common/content_export.h"
+#include "content/common/pepper_renderer_instance_data.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ppapi/host/ppapi_host.h"
+
+namespace content {
+
+class CONTENT_EXPORT BrowserPpapiHostImpl : public BrowserPpapiHost {
+ public:
+ // The creator is responsible for calling set_plugin_process_handle as soon
+ // as it is known (we start the process asynchronously so it won't be known
+ // when this object is created).
+ // |external_plugin| signfies that this is a proxy created for an embedder's
+ // plugin, i.e. using BrowserPpapiHost::CreateExternalPluginProcess.
+ BrowserPpapiHostImpl(
+ IPC::Sender* sender,
+ const ppapi::PpapiPermissions& permissions,
+ const std::string& plugin_name,
+ const base::FilePath& plugin_path,
+ const base::FilePath& profile_data_directory,
+ bool external_plugin,
+ // TODO (ygorshenin@): remove this once TCP sockets are
+ // converted to the new design.
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter);
+ virtual ~BrowserPpapiHostImpl();
+
+ // BrowserPpapiHost.
+ virtual ppapi::host::PpapiHost* GetPpapiHost() OVERRIDE;
+ virtual base::ProcessHandle GetPluginProcessHandle() const OVERRIDE;
+ virtual bool IsValidInstance(PP_Instance instance) const OVERRIDE;
+ virtual bool GetRenderViewIDsForInstance(PP_Instance instance,
+ int* render_process_id,
+ int* render_view_id) const OVERRIDE;
+ virtual const std::string& GetPluginName() OVERRIDE;
+ virtual const base::FilePath& GetPluginPath() OVERRIDE;
+ virtual const base::FilePath& GetProfileDataDirectory() OVERRIDE;
+ virtual GURL GetDocumentURLForInstance(PP_Instance instance) OVERRIDE;
+ virtual GURL GetPluginURLForInstance(PP_Instance instance) OVERRIDE;
+
+ void set_plugin_process_handle(base::ProcessHandle handle) {
+ plugin_process_handle_ = handle;
+ }
+
+ bool external_plugin() { return external_plugin_; }
+
+ // These two functions are notifications that an instance has been created
+ // or destroyed. They allow us to maintain a mapping of PP_Instance to data
+ // associated with the instance including view IDs in the browser process.
+ void AddInstance(PP_Instance instance,
+ const PepperRendererInstanceData& instance_data);
+ void DeleteInstance(PP_Instance instance);
+
+ scoped_refptr<IPC::ChannelProxy::MessageFilter> message_filter() {
+ return message_filter_;
+ }
+
+ private:
+ friend class BrowserPpapiHostTest;
+
+ // Implementing MessageFilter on BrowserPpapiHostImpl makes it ref-counted,
+ // preventing us from returning these to embedders without holding a
+ // reference. To avoid that, define a message filter object.
+ class HostMessageFilter : public IPC::ChannelProxy::MessageFilter {
+ public:
+ explicit HostMessageFilter(ppapi::host::PpapiHost* ppapi_host)
+ : ppapi_host_(ppapi_host) {}
+ // IPC::ChannelProxy::MessageFilter.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+
+ void OnHostDestroyed();
+
+ private:
+ virtual ~HostMessageFilter() {}
+
+ ppapi::host::PpapiHost* ppapi_host_;
+ };
+
+ scoped_ptr<ppapi::host::PpapiHost> ppapi_host_;
+ base::ProcessHandle plugin_process_handle_;
+ std::string plugin_name_;
+ base::FilePath plugin_path_;
+ base::FilePath profile_data_directory_;
+
+ // If true, this is an external plugin, i.e. created by the embedder using
+ // BrowserPpapiHost::CreateExternalPluginProcess.
+ bool external_plugin_;
+
+ // Tracks all PP_Instances in this plugin and associated renderer-related
+ // data.
+ typedef std::map<PP_Instance, PepperRendererInstanceData> InstanceMap;
+ InstanceMap instance_map_;
+
+ scoped_refptr<HostMessageFilter> message_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPpapiHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_IMPL_H_
diff --git a/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.cc b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.cc
new file mode 100644
index 00000000000..a8c257abacd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.cc
@@ -0,0 +1,31 @@
+// 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 "content/browser/renderer_host/pepper/browser_ppapi_host_test.h"
+
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+
+namespace content {
+
+BrowserPpapiHostTest::BrowserPpapiHostTest()
+ : sink_() {
+ ppapi_host_.reset(new BrowserPpapiHostImpl(
+ &sink_,
+ ppapi::PpapiPermissions::AllPermissions(),
+ std::string(),
+ base::FilePath(),
+ base::FilePath(),
+ false,
+ NULL));
+ ppapi_host_->set_plugin_process_handle(base::GetCurrentProcessHandle());
+}
+
+BrowserPpapiHostTest::~BrowserPpapiHostTest() {
+}
+
+BrowserPpapiHost* BrowserPpapiHostTest::GetBrowserPpapiHost() {
+ return ppapi_host_.get();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.h b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.h
new file mode 100644
index 00000000000..3f375eea107
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/browser_ppapi_host_test.h
@@ -0,0 +1,37 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_TEST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_TEST_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "ppapi/proxy/resource_message_test_sink.h"
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+
+// Test harness for testing Pepper resource hosts in the browser. This will
+// construct a BrowserPpapiHost connected to a test sink for testing messages.
+class BrowserPpapiHostTest {
+ public:
+ BrowserPpapiHostTest();
+ virtual ~BrowserPpapiHostTest();
+
+ ppapi::proxy::ResourceMessageTestSink& sink() { return sink_; }
+ BrowserPpapiHost* GetBrowserPpapiHost();
+
+ private:
+ ppapi::proxy::ResourceMessageTestSink sink_;
+
+ scoped_ptr<BrowserPpapiHostImpl> ppapi_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserPpapiHostTest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_BROWSER_PPAPI_HOST_TEST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc b/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
new file mode 100644
index 00000000000..f0cefc40396
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
@@ -0,0 +1,199 @@
+// 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 "content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h"
+
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h"
+#include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
+#include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
+#include "content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h"
+#include "content/browser/renderer_host/pepper/pepper_gamepad_host.h"
+#include "content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h"
+#include "content/browser/renderer_host/pepper/pepper_network_proxy_host.h"
+#include "content/browser/renderer_host/pepper/pepper_print_settings_manager.h"
+#include "content/browser/renderer_host/pepper/pepper_printing_host.h"
+#include "content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h"
+#include "content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h"
+#include "content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h"
+#include "ppapi/host/message_filter_host.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/host/resource_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+
+using ppapi::host::MessageFilterHost;
+using ppapi::host::ResourceHost;
+using ppapi::host::ResourceMessageFilter;
+using ppapi::UnpackMessage;
+
+namespace content {
+
+namespace {
+
+const size_t kMaxSocketsAllowed = 1024;
+
+bool CanCreateSocket() {
+ return
+ PepperUDPSocketMessageFilter::GetNumInstances() +
+ PepperTCPServerSocketMessageFilter::GetNumInstances() <
+ kMaxSocketsAllowed;
+}
+
+} // namespace
+
+ContentBrowserPepperHostFactory::ContentBrowserPepperHostFactory(
+ BrowserPpapiHostImpl* host,
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter)
+ : host_(host),
+ pepper_message_filter_(pepper_message_filter) {
+}
+
+ContentBrowserPepperHostFactory::~ContentBrowserPepperHostFactory() {
+}
+
+scoped_ptr<ResourceHost> ContentBrowserPepperHostFactory::CreateResourceHost(
+ ppapi::host::PpapiHost* host,
+ const ppapi::proxy::ResourceMessageCallParams& params,
+ PP_Instance instance,
+ const IPC::Message& message) {
+ DCHECK(host == host_->GetPpapiHost());
+
+ // Make sure the plugin is giving us a valid instance for this resource.
+ if (!host_->IsValidInstance(instance))
+ return scoped_ptr<ResourceHost>();
+
+ // Public interfaces.
+ switch (message.type()) {
+ case PpapiHostMsg_FileSystem_Create::ID: {
+ PP_FileSystemType file_system_type;
+ if (!ppapi::UnpackMessage<PpapiHostMsg_FileSystem_Create>(message,
+ &file_system_type)) {
+ NOTREACHED();
+ return scoped_ptr<ResourceHost>();
+ }
+ return scoped_ptr<ResourceHost>(new PepperFileSystemBrowserHost(
+ host_, instance, params.pp_resource(), file_system_type));
+ }
+ case PpapiHostMsg_Gamepad_Create::ID: {
+ return scoped_ptr<ResourceHost>(new PepperGamepadHost(
+ host_, instance, params.pp_resource()));
+ }
+ case PpapiHostMsg_NetworkProxy_Create::ID: {
+ return scoped_ptr<ResourceHost>(new PepperNetworkProxyHost(
+ host_, instance, params.pp_resource()));
+ }
+ case PpapiHostMsg_HostResolver_Create::ID: {
+ scoped_refptr<ResourceMessageFilter> host_resolver(
+ new PepperHostResolverMessageFilter(host_, instance, false));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(),
+ host_resolver));
+ }
+ case PpapiHostMsg_FileRef_CreateInternal::ID: {
+ PP_Resource file_system;
+ std::string internal_path;
+ if (!UnpackMessage<PpapiHostMsg_FileRef_CreateInternal>(
+ message, &file_system, &internal_path)) {
+ NOTREACHED();
+ return scoped_ptr<ResourceHost>();
+ }
+ return scoped_ptr<ResourceHost>(new PepperFileRefHost(
+ host_, instance, params.pp_resource(), file_system, internal_path));
+ }
+ case PpapiHostMsg_UDPSocket_Create::ID: {
+ if (CanCreateSocket()) {
+ scoped_refptr<ResourceMessageFilter> udp_socket(
+ new PepperUDPSocketMessageFilter(host_, instance, false));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(), udp_socket));
+ } else {
+ return scoped_ptr<ResourceHost>();
+ }
+ }
+ }
+
+ // Dev interfaces.
+ if (GetPermissions().HasPermission(ppapi::PERMISSION_DEV)) {
+ switch (message.type()) {
+ case PpapiHostMsg_Printing_Create::ID: {
+ scoped_ptr<PepperPrintSettingsManager> manager(
+ new PepperPrintSettingsManagerImpl());
+ return scoped_ptr<ResourceHost>(new PepperPrintingHost(
+ host_->GetPpapiHost(), instance,
+ params.pp_resource(), manager.Pass()));
+ }
+ case PpapiHostMsg_TrueTypeFontSingleton_Create::ID: {
+ return scoped_ptr<ResourceHost>(new PepperTrueTypeFontListHost(
+ host_, instance, params.pp_resource()));
+ }
+ }
+ }
+
+ // Private interfaces.
+ if (GetPermissions().HasPermission(ppapi::PERMISSION_PRIVATE)) {
+ switch (message.type()) {
+ case PpapiHostMsg_BrowserFontSingleton_Create::ID:
+ return scoped_ptr<ResourceHost>(new PepperBrowserFontSingletonHost(
+ host_, instance, params.pp_resource()));
+ }
+ }
+
+ // Permissions for the following interfaces will be checked at the
+ // time of the corresponding instance's methods calls (because
+ // permission check can be performed only on the UI
+ // thread). Currently these interfaces are available only for
+ // whitelisted apps which may not have access to the other private
+ // interfaces.
+ if (message.type() == PpapiHostMsg_HostResolver_CreatePrivate::ID) {
+ scoped_refptr<ResourceMessageFilter> host_resolver(
+ new PepperHostResolverMessageFilter(host_, instance, true));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(), host_resolver));
+ }
+ if (message.type() == PpapiHostMsg_TCPServerSocket_CreatePrivate::ID) {
+ if (CanCreateSocket()) {
+ scoped_refptr<ResourceMessageFilter> tcp_server_socket(
+ new PepperTCPServerSocketMessageFilter(host_, instance, true,
+ pepper_message_filter_));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(),
+ tcp_server_socket));
+ } else {
+ return scoped_ptr<ResourceHost>();
+ }
+ }
+ if (message.type() == PpapiHostMsg_UDPSocket_CreatePrivate::ID) {
+ if (CanCreateSocket()) {
+ scoped_refptr<ResourceMessageFilter> udp_socket(
+ new PepperUDPSocketMessageFilter(host_, instance, true));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(), udp_socket));
+ } else {
+ return scoped_ptr<ResourceHost>();
+ }
+ }
+
+ // Flash interfaces.
+ if (GetPermissions().HasPermission(ppapi::PERMISSION_FLASH)) {
+ switch (message.type()) {
+ case PpapiHostMsg_FlashFile_Create::ID: {
+ scoped_refptr<ResourceMessageFilter> file_filter(
+ new PepperFlashFileMessageFilter(instance, host_));
+ return scoped_ptr<ResourceHost>(new MessageFilterHost(
+ host_->GetPpapiHost(), instance, params.pp_resource(),
+ file_filter));
+ }
+ }
+ }
+
+ return scoped_ptr<ResourceHost>();
+}
+
+const ppapi::PpapiPermissions&
+ContentBrowserPepperHostFactory::GetPermissions() const {
+ return host_->GetPpapiHost()->permissions();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h b/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h
new file mode 100644
index 00000000000..e39ec35d858
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_PEPPER_CONTENT_BROWSER_PEPPER_HOST_FACTORY_H_
+#define CONTENT_BROWSER_PEPPER_CONTENT_BROWSER_PEPPER_HOST_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "ppapi/host/host_factory.h"
+
+namespace ppapi {
+class PpapiPermissions;
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+
+class ContentBrowserPepperHostFactory : public ppapi::host::HostFactory {
+ public:
+ // Non-owning pointer to the filter must outlive this class.
+ ContentBrowserPepperHostFactory(
+ BrowserPpapiHostImpl* host,
+ // TODO (ygorshenin@): remove this once TCP sockets are
+ // converted to the new design.
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter);
+ virtual ~ContentBrowserPepperHostFactory();
+
+ virtual scoped_ptr<ppapi::host::ResourceHost> CreateResourceHost(
+ ppapi::host::PpapiHost* host,
+ const ppapi::proxy::ResourceMessageCallParams& params,
+ PP_Instance instance,
+ const IPC::Message& message) OVERRIDE;
+
+ private:
+ const ppapi::PpapiPermissions& GetPermissions() const;
+
+ // Non-owning pointer.
+ BrowserPpapiHostImpl* host_;
+
+ scoped_refptr<PepperMessageFilter> pepper_message_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentBrowserPepperHostFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PEPPER_CONTENT_BROWSER_PEPPER_HOST_FACTORY_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.cc
new file mode 100644
index 00000000000..99a8e04fc70
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.cc
@@ -0,0 +1,111 @@
+// 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 "content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/values.h"
+#include "content/common/font_list.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/resource_message_filter.h"
+#include "ppapi/proxy/ppapi_messages.h"
+
+namespace content {
+
+namespace {
+
+// Handles the font list request on the blocking pool.
+class FontMessageFilter : public ppapi::host::ResourceMessageFilter {
+ public:
+ FontMessageFilter();
+
+ // ppapi::host::ResourceMessageFilter implementation.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ private:
+ virtual ~FontMessageFilter();
+
+ // Message handler.
+ int32_t OnHostMsgGetFontFamilies(ppapi::host::HostMessageContext* context);
+
+ DISALLOW_COPY_AND_ASSIGN(FontMessageFilter);
+};
+
+FontMessageFilter::FontMessageFilter() {
+}
+
+FontMessageFilter::~FontMessageFilter() {
+}
+
+scoped_refptr<base::TaskRunner> FontMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) {
+ // Use the blocking pool to get the font list (currently the only message)
+ // Since getting the font list is non-threadsafe on Linux (for versions of
+ // Pango predating 2013), use a sequenced task runner.
+ base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
+ return pool->GetSequencedTaskRunner(
+ pool->GetNamedSequenceToken(kFontListSequenceToken));
+}
+
+int32_t FontMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(FontMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_BrowserFontSingleton_GetFontFamilies,
+ OnHostMsgGetFontFamilies)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t FontMessageFilter::OnHostMsgGetFontFamilies(
+ ppapi::host::HostMessageContext* context) {
+ // OK to use "slow blocking" version since we're on the blocking pool.
+ scoped_ptr<base::ListValue> list(GetFontList_SlowBlocking());
+
+ std::string output;
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ base::ListValue* cur_font;
+ if (!list->GetList(i, &cur_font))
+ continue;
+
+ // Each entry is actually a list of (font name, localized name).
+ // We only care about the regular name.
+ std::string font_name;
+ if (!cur_font->GetString(0, &font_name))
+ continue;
+
+ // Font names are separated with nulls. We also want an explicit null at
+ // the end of the string (Pepper strings aren't null terminated so since
+ // we specify there will be a null, it should actually be in the string).
+ output.append(font_name);
+ output.push_back(0);
+ }
+
+ context->reply_msg =
+ PpapiPluginMsg_BrowserFontSingleton_GetFontFamiliesReply(output);
+ return PP_OK;
+}
+
+} // namespace
+
+PepperBrowserFontSingletonHost::PepperBrowserFontSingletonHost(
+ BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource) {
+ AddFilter(scoped_refptr<ppapi::host::ResourceMessageFilter>(
+ new FontMessageFilter()));
+}
+
+PepperBrowserFontSingletonHost::~PepperBrowserFontSingletonHost() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h b/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h
new file mode 100644
index 00000000000..c002e634c91
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_browser_font_singleton_host.h
@@ -0,0 +1,28 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_BROWSER_FONT_SINGLETON_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_BROWSER_FONT_SINGLETON_HOST_H_
+
+#include "base/basictypes.h"
+#include "ppapi/host/resource_host.h"
+
+namespace content {
+
+class BrowserPpapiHost;
+
+class PepperBrowserFontSingletonHost : public ppapi::host::ResourceHost {
+ public:
+ PepperBrowserFontSingletonHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource);
+ virtual ~PepperBrowserFontSingletonHost();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PepperBrowserFontSingletonHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_BROWSER_FONT_SINGLETON_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.cc b/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.cc
new file mode 100644
index 00000000000..27ba37dfcca
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.cc
@@ -0,0 +1,168 @@
+// 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 "content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_time.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/file_type_conversion.h"
+#include "ppapi/shared_impl/time_conversion.h"
+
+namespace content {
+
+PepperExternalFileRefBackend::PepperExternalFileRefBackend(
+ ppapi::host::PpapiHost* host,
+ int render_process_id,
+ const base::FilePath& path) : host_(host),
+ path_(path),
+ render_process_id_(render_process_id),
+ weak_factory_(this) {
+ task_runner_ =
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
+}
+
+PepperExternalFileRefBackend::~PepperExternalFileRefBackend() {
+}
+
+int32_t PepperExternalFileRefBackend::MakeDirectory(
+ ppapi::host::ReplyMessageContext reply_context,
+ bool make_ancestors) {
+ // This operation isn't supported for external filesystems.
+ return PP_ERROR_NOACCESS;
+}
+
+int32_t PepperExternalFileRefBackend::Touch(
+ ppapi::host::ReplyMessageContext reply_context,
+ PP_Time last_access_time,
+ PP_Time last_modified_time) {
+ IPC::Message reply_msg = PpapiPluginMsg_FileRef_TouchReply();
+ base::FileUtilProxy::Touch(
+ task_runner_.get(),
+ path_,
+ ppapi::PPTimeToTime(last_access_time),
+ ppapi::PPTimeToTime(last_modified_time),
+ base::Bind(&PepperExternalFileRefBackend::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ reply_context,
+ reply_msg));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperExternalFileRefBackend::Delete(
+ ppapi::host::ReplyMessageContext reply_context) {
+ // This operation isn't supported for external filesystems.
+ return PP_ERROR_NOACCESS;
+}
+
+int32_t PepperExternalFileRefBackend::Rename(
+ ppapi::host::ReplyMessageContext reply_context,
+ PepperFileRefHost* new_file_ref) {
+ // This operation isn't supported for external filesystems.
+ return PP_ERROR_NOACCESS;
+}
+
+int32_t PepperExternalFileRefBackend::Query(
+ ppapi::host::ReplyMessageContext reply_context) {
+ bool ok = base::FileUtilProxy::GetFileInfo(
+ task_runner_.get(),
+ path_,
+ base::Bind(&PepperExternalFileRefBackend::GetMetadataComplete,
+ weak_factory_.GetWeakPtr(),
+ reply_context));
+ DCHECK(ok);
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperExternalFileRefBackend::ReadDirectoryEntries(
+ ppapi::host::ReplyMessageContext context) {
+ // This operation isn't supported for external filesystems.
+ return PP_ERROR_NOACCESS;
+}
+
+int32_t PepperExternalFileRefBackend::GetAbsolutePath(
+ ppapi::host::ReplyMessageContext reply_context) {
+ host_->SendReply(reply_context,
+ PpapiPluginMsg_FileRef_GetAbsolutePathReply(path_.AsUTF8Unsafe()));
+ return PP_OK;
+}
+
+fileapi::FileSystemURL PepperExternalFileRefBackend::GetFileSystemURL() const {
+ return fileapi::FileSystemURL();
+}
+
+std::string PepperExternalFileRefBackend::GetFileSystemURLSpec() const {
+ return std::string();
+}
+
+base::FilePath PepperExternalFileRefBackend::GetExternalPath() const {
+ return path_;
+}
+
+int32_t PepperExternalFileRefBackend::CanRead() const {
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanReadFile(render_process_id_, path_)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperExternalFileRefBackend::CanWrite() const {
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanWriteFile(render_process_id_, path_)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperExternalFileRefBackend::CanCreate() const {
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanCreateFile(render_process_id_, path_)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperExternalFileRefBackend::CanReadWrite() const {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanReadFile(render_process_id_, path_) ||
+ !policy->CanWriteFile(render_process_id_, path_)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+void PepperExternalFileRefBackend::DidFinish(
+ ppapi::host::ReplyMessageContext reply_context,
+ const IPC::Message& msg,
+ base::PlatformFileError error) {
+ reply_context.params.set_result(ppapi::PlatformFileErrorToPepperError(error));
+ host_->SendReply(reply_context, msg);
+}
+
+void PepperExternalFileRefBackend::GetMetadataComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ const base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info) {
+ reply_context.params.set_result(ppapi::PlatformFileErrorToPepperError(error));
+
+ PP_FileInfo pp_file_info;
+ if (error == base::PLATFORM_FILE_OK) {
+ ppapi::PlatformFileInfoToPepperFileInfo(
+ file_info, PP_FILESYSTEMTYPE_EXTERNAL, &pp_file_info);
+ } else {
+ memset(&pp_file_info, 0, sizeof(pp_file_info));
+ }
+
+ host_->SendReply(reply_context,
+ PpapiPluginMsg_FileRef_QueryReply(pp_file_info));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h b/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h
new file mode 100644
index 00000000000..994e9c291cd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h
@@ -0,0 +1,75 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_EXTERNAL_FILE_REF_BACKEND_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_EXTERNAL_FILE_REF_BACKEND_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/c/pp_time.h"
+#include "ppapi/host/ppapi_host.h"
+
+namespace content {
+
+// Implementations of FileRef operations for external filesystems.
+class PepperExternalFileRefBackend : public PepperFileRefBackend {
+ public:
+ PepperExternalFileRefBackend(ppapi::host::PpapiHost* host,
+ int render_process_id,
+ const base::FilePath& path);
+ virtual ~PepperExternalFileRefBackend();
+
+ // PepperFileRefBackend overrides.
+ virtual int32_t MakeDirectory(ppapi::host::ReplyMessageContext context,
+ bool make_ancestors) OVERRIDE;
+ virtual int32_t Touch(ppapi::host::ReplyMessageContext context,
+ PP_Time last_accessed_time,
+ PP_Time last_modified_time) OVERRIDE;
+ virtual int32_t Delete(ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t Rename(ppapi::host::ReplyMessageContext context,
+ PepperFileRefHost* new_file_ref) OVERRIDE;
+ virtual int32_t Query(ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t ReadDirectoryEntries(
+ ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t GetAbsolutePath(ppapi::host::ReplyMessageContext context)
+ OVERRIDE;
+ virtual fileapi::FileSystemURL GetFileSystemURL() const OVERRIDE;
+ virtual std::string GetFileSystemURLSpec() const OVERRIDE;
+ virtual base::FilePath GetExternalPath() const OVERRIDE;
+
+ virtual int32_t CanRead() const OVERRIDE;
+ virtual int32_t CanWrite() const OVERRIDE;
+ virtual int32_t CanCreate() const OVERRIDE;
+ virtual int32_t CanReadWrite() const OVERRIDE;
+
+ private:
+ // Generic reply callback.
+ void DidFinish(ppapi::host::ReplyMessageContext reply_context,
+ const IPC::Message& msg,
+ base::PlatformFileError error);
+
+ // Operation specific callbacks.
+ void GetMetadataComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info);
+
+ ppapi::host::PpapiHost* host_;
+ base::FilePath path_;
+ int render_process_id_;
+
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ base::WeakPtrFactory<PepperExternalFileRefBackend> weak_factory_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_EXTERNAL_FILE_REF_BACKEND_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.cc
new file mode 100644
index 00000000000..5370af9d853
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.cc
@@ -0,0 +1,260 @@
+// 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 "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
+
+#include <string>
+
+#include "content/browser/renderer_host/pepper/pepper_external_file_ref_backend.h"
+#include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
+#include "content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/file_ref_util.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+
+using ppapi::host::ResourceHost;
+
+namespace content {
+
+PepperFileRefBackend::~PepperFileRefBackend() {
+}
+
+PepperFileRefHost::PepperFileRefHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ PP_Resource file_system,
+ const std::string& path)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ host_(host),
+ fs_type_(PP_FILESYSTEMTYPE_INVALID) {
+ if (!ppapi::IsValidInternalPath(path))
+ return;
+
+ int render_process_id;
+ int unused;
+ if (!host->GetRenderViewIDsForInstance(instance,
+ &render_process_id,
+ &unused)) {
+ return;
+ }
+
+ ResourceHost* fs_resource_host =
+ host->GetPpapiHost()->GetResourceHost(file_system);
+ if (fs_resource_host == NULL) {
+ DLOG(ERROR) << "Couldn't find FileSystem host: " << resource
+ << " path: " << path;
+ return;
+ }
+
+ PepperFileSystemBrowserHost* fs_host = NULL;
+ if (fs_resource_host->IsFileSystemHost())
+ fs_host = static_cast<PepperFileSystemBrowserHost*>(fs_resource_host);
+ if (fs_host == NULL) {
+ DLOG(ERROR) << "Filesystem PP_Resource is not PepperFileSystemBrowserHost";
+ return;
+ }
+
+ fs_type_ = fs_host->GetType();
+ // TODO(teravest): Add support for isolated filesystems.
+ if ((fs_type_ != PP_FILESYSTEMTYPE_LOCALPERSISTENT) &&
+ (fs_type_ != PP_FILESYSTEMTYPE_LOCALTEMPORARY)) {
+ DLOG(ERROR) << "Unsupported filesystem type: " << fs_type_;
+ return;
+ }
+
+ backend_.reset(new PepperInternalFileRefBackend(
+ host->GetPpapiHost(),
+ render_process_id,
+ base::AsWeakPtr(fs_host),
+ path));
+}
+
+PepperFileRefHost::PepperFileRefHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ const base::FilePath& external_path)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ host_(host),
+ fs_type_(PP_FILESYSTEMTYPE_EXTERNAL) {
+ if (!ppapi::IsValidExternalPath(external_path))
+ return;
+
+ int render_process_id;
+ int unused;
+ if (!host->GetRenderViewIDsForInstance(instance,
+ &render_process_id,
+ &unused)) {
+ return;
+ }
+
+ backend_.reset(new PepperExternalFileRefBackend(host->GetPpapiHost(),
+ render_process_id,
+ external_path));
+}
+
+PepperFileRefHost::~PepperFileRefHost() {
+}
+
+bool PepperFileRefHost::IsFileRefHost() {
+ return true;
+}
+
+PP_FileSystemType PepperFileRefHost::GetFileSystemType() const {
+ return fs_type_;
+}
+
+fileapi::FileSystemURL PepperFileRefHost::GetFileSystemURL() const {
+ if (backend_)
+ return backend_->GetFileSystemURL();
+ return fileapi::FileSystemURL();
+}
+
+std::string PepperFileRefHost::GetFileSystemURLSpec() const {
+ if (backend_)
+ return backend_->GetFileSystemURLSpec();
+ return std::string();
+}
+
+base::FilePath PepperFileRefHost::GetExternalPath() const {
+ if (backend_)
+ return backend_->GetExternalPath();
+ return base::FilePath();
+}
+
+int32_t PepperFileRefHost::CanRead() const {
+ if (backend_)
+ return backend_->CanRead();
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFileRefHost::CanWrite() const {
+ if (backend_)
+ return backend_->CanWrite();
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFileRefHost::CanCreate() const {
+ if (backend_)
+ return backend_->CanCreate();
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFileRefHost::CanReadWrite() const {
+ if (backend_)
+ return backend_->CanReadWrite();
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFileRefHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ if (!backend_)
+ return PP_ERROR_FAILED;
+
+ IPC_BEGIN_MESSAGE_MAP(PepperFileRefHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileRef_MakeDirectory,
+ OnMakeDirectory);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileRef_Touch,
+ OnTouch);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileRef_Delete,
+ OnDelete);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileRef_Rename,
+ OnRename);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileRef_Query,
+ OnQuery);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_FileRef_ReadDirectoryEntries,
+ OnReadDirectoryEntries);
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileRef_GetAbsolutePath,
+ OnGetAbsolutePath);
+
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFileRefHost::OnMakeDirectory(
+ ppapi::host::HostMessageContext* context,
+ bool make_ancestors) {
+ int32_t rv = CanCreate();
+ if (rv != PP_OK)
+ return rv;
+ return backend_->MakeDirectory(context->MakeReplyMessageContext(),
+ make_ancestors);
+}
+
+int32_t PepperFileRefHost::OnTouch(ppapi::host::HostMessageContext* context,
+ PP_Time last_access_time,
+ PP_Time last_modified_time) {
+ // TODO(teravest): Change this to be kWriteFilePermissions here and in
+ // fileapi_message_filter.
+ int32_t rv = CanCreate();
+ if (rv != PP_OK)
+ return rv;
+ return backend_->Touch(context->MakeReplyMessageContext(),
+ last_access_time,
+ last_modified_time);
+}
+
+int32_t PepperFileRefHost::OnDelete(ppapi::host::HostMessageContext* context) {
+ int32_t rv = CanWrite();
+ if (rv != PP_OK)
+ return rv;
+ return backend_->Delete(context->MakeReplyMessageContext());
+}
+
+int32_t PepperFileRefHost::OnRename(ppapi::host::HostMessageContext* context,
+ PP_Resource new_file_ref) {
+ int32_t rv = CanReadWrite();
+ if (rv != PP_OK)
+ return rv;
+
+ ResourceHost* resource_host =
+ host_->GetPpapiHost()->GetResourceHost(new_file_ref);
+ if (!resource_host)
+ return PP_ERROR_BADRESOURCE;
+
+ PepperFileRefHost* file_ref_host = NULL;
+ if (resource_host->IsFileRefHost())
+ file_ref_host = static_cast<PepperFileRefHost*>(resource_host);
+ if (!file_ref_host)
+ return PP_ERROR_BADRESOURCE;
+
+ rv = file_ref_host->CanCreate();
+ if (rv != PP_OK)
+ return rv;
+
+ return backend_->Rename(context->MakeReplyMessageContext(),
+ file_ref_host);
+}
+
+int32_t PepperFileRefHost::OnQuery(ppapi::host::HostMessageContext* context) {
+ int32_t rv = CanRead();
+ if (rv != PP_OK)
+ return rv;
+ return backend_->Query(context->MakeReplyMessageContext());
+}
+
+int32_t PepperFileRefHost::OnReadDirectoryEntries(
+ ppapi::host::HostMessageContext* context) {
+ int32_t rv = CanRead();
+ if (rv != PP_OK)
+ return rv;
+ return backend_->ReadDirectoryEntries(context->MakeReplyMessageContext());
+}
+
+int32_t PepperFileRefHost::OnGetAbsolutePath(
+ ppapi::host::HostMessageContext* context) {
+ if (!host_->GetPpapiHost()->permissions().HasPermission(
+ ppapi::PERMISSION_PRIVATE))
+ return PP_ERROR_NOACCESS;
+ return backend_->GetAbsolutePath(context->MakeReplyMessageContext());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.h b/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.h
new file mode 100644
index 00000000000..5d765764326
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_file_ref_host.h
@@ -0,0 +1,113 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_REF_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_REF_HOST_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/c/pp_time.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_host.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace content {
+
+class PepperFileRefHost;
+
+// Internal and external filesystems have very different codepaths for
+// performing FileRef operations. The logic is split into separate classes
+// to make it easier to read.
+class PepperFileRefBackend {
+ public:
+ virtual ~PepperFileRefBackend();
+
+ virtual int32_t MakeDirectory(ppapi::host::ReplyMessageContext context,
+ bool make_ancestors) = 0;
+ virtual int32_t Touch(ppapi::host::ReplyMessageContext context,
+ PP_Time last_accessed_time,
+ PP_Time last_modified_time) = 0;
+ virtual int32_t Delete(ppapi::host::ReplyMessageContext context) = 0;
+ virtual int32_t Rename(ppapi::host::ReplyMessageContext context,
+ PepperFileRefHost* new_file_ref) = 0;
+ virtual int32_t Query(ppapi::host::ReplyMessageContext context) = 0;
+ virtual int32_t ReadDirectoryEntries(
+ ppapi::host::ReplyMessageContext context) = 0;
+ virtual int32_t GetAbsolutePath(
+ ppapi::host::ReplyMessageContext context) = 0;
+ virtual fileapi::FileSystemURL GetFileSystemURL() const = 0;
+ virtual std::string GetFileSystemURLSpec() const = 0;
+ virtual base::FilePath GetExternalPath() const = 0;
+
+ // Returns an error from the pp_errors.h enum.
+ virtual int32_t CanRead() const = 0;
+ virtual int32_t CanWrite() const = 0;
+ virtual int32_t CanCreate() const = 0;
+ virtual int32_t CanReadWrite() const = 0;
+};
+
+class CONTENT_EXPORT PepperFileRefHost
+ : public ppapi::host::ResourceHost,
+ public base::SupportsWeakPtr<PepperFileRefHost> {
+ public:
+ PepperFileRefHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ PP_Resource file_system,
+ const std::string& internal_path);
+
+ PepperFileRefHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ const base::FilePath& external_path);
+
+ virtual ~PepperFileRefHost();
+
+ // ResourceHost overrides.
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+ virtual bool IsFileRefHost() OVERRIDE;
+
+ // Required to support Rename().
+ PP_FileSystemType GetFileSystemType() const;
+ fileapi::FileSystemURL GetFileSystemURL() const;
+
+ // Required to support FileIO.
+ std::string GetFileSystemURLSpec() const;
+ base::FilePath GetExternalPath() const;
+
+ int32_t CanRead() const;
+ int32_t CanWrite() const;
+ int32_t CanCreate() const;
+ int32_t CanReadWrite() const;
+
+ private:
+ int32_t OnMakeDirectory(ppapi::host::HostMessageContext* context,
+ bool make_ancestors);
+ int32_t OnTouch(ppapi::host::HostMessageContext* context,
+ PP_Time last_access_time,
+ PP_Time last_modified_time);
+ int32_t OnDelete(ppapi::host::HostMessageContext* context);
+ int32_t OnRename(ppapi::host::HostMessageContext* context,
+ PP_Resource new_file_ref);
+ int32_t OnQuery(ppapi::host::HostMessageContext* context);
+ int32_t OnReadDirectoryEntries(ppapi::host::HostMessageContext* context);
+ int32_t OnGetAbsolutePath(ppapi::host::HostMessageContext* context);
+
+ BrowserPpapiHost* host_;
+ scoped_ptr<PepperFileRefBackend> backend_;
+ PP_FileSystemType fs_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperFileRefHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_REF_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc
new file mode 100644
index 00000000000..1a10da77b83
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc
@@ -0,0 +1,183 @@
+// 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 "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/file_type_conversion.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace content {
+
+namespace {
+
+// TODO(teravest): Move this function to be shared and public in fileapi.
+bool LooksLikeAGuid(const std::string& fsid) {
+ const size_t kExpectedFsIdSize = 32;
+ if (fsid.size() != kExpectedFsIdSize)
+ return false;
+ for (std::string::const_iterator it = fsid.begin(); it != fsid.end(); ++it) {
+ if (('A' <= *it && *it <= 'F') || ('0' <= *it && *it <= '9'))
+ continue;
+ return false;
+ }
+ return true;
+}
+
+scoped_refptr<fileapi::FileSystemContext>
+GetFileSystemContextFromRenderId(int render_process_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderProcessHost* render_process_host =
+ RenderProcessHost::FromID(render_process_id);
+ if (!render_process_host)
+ return NULL;
+ StoragePartition* storage_partition =
+ render_process_host->GetStoragePartition();
+ if (!storage_partition)
+ return NULL;
+ return storage_partition->GetFileSystemContext();
+}
+
+} // namespace
+
+PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ PP_FileSystemType type)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ browser_ppapi_host_(host),
+ weak_factory_(this),
+ type_(type),
+ opened_(false),
+ fs_context_(NULL),
+ called_open_(false) {
+}
+
+PepperFileSystemBrowserHost::~PepperFileSystemBrowserHost() {
+ if (fs_context_.get())
+ fs_context_->operation_runner()->Shutdown();
+}
+
+int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperFileSystemBrowserHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_FileSystem_Open,
+ OnHostMsgOpen)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_FileSystem_InitIsolatedFileSystem,
+ OnHostMsgInitIsolatedFileSystem)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+bool PepperFileSystemBrowserHost::IsFileSystemHost() {
+ return true;
+}
+
+int32_t PepperFileSystemBrowserHost::OnHostMsgOpen(
+ ppapi::host::HostMessageContext* context,
+ int64_t /* unused */) {
+ // TODO(raymes): The file system size is now unused by FileSystemDispatcher.
+ // Figure out why. Why is the file system size signed?
+
+ // Not allow multiple opens.
+ if (called_open_)
+ return PP_ERROR_INPROGRESS;
+ called_open_ = true;
+
+ fileapi::FileSystemType file_system_type;
+ switch (type_) {
+ case PP_FILESYSTEMTYPE_LOCALTEMPORARY:
+ file_system_type = fileapi::kFileSystemTypeTemporary;
+ break;
+ case PP_FILESYSTEMTYPE_LOCALPERSISTENT:
+ file_system_type = fileapi::kFileSystemTypePersistent;
+ break;
+ case PP_FILESYSTEMTYPE_EXTERNAL:
+ file_system_type = fileapi::kFileSystemTypeExternal;
+ break;
+ default:
+ return PP_ERROR_FAILED;
+ }
+
+ int render_process_id = 0;
+ int unused;
+ if (!browser_ppapi_host_->GetRenderViewIDsForInstance(pp_instance(),
+ &render_process_id,
+ &unused)) {
+ return PP_ERROR_FAILED;
+ }
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
+ base::Bind(&PepperFileSystemBrowserHost::GotFileSystemContext,
+ weak_factory_.GetWeakPtr(),
+ context->MakeReplyMessageContext(),
+ file_system_type));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperFileSystemBrowserHost::GotFileSystemContext(
+ ppapi::host::ReplyMessageContext reply_context,
+ fileapi::FileSystemType file_system_type,
+ scoped_refptr<fileapi::FileSystemContext> fs_context) {
+ if (!fs_context.get()) {
+ OpenFileSystemComplete(
+ reply_context, base::PLATFORM_FILE_ERROR_FAILED, std::string(), GURL());
+ return;
+ }
+ GURL origin = browser_ppapi_host_->GetDocumentURLForInstance(
+ pp_instance()).GetOrigin();
+ fs_context->OpenFileSystem(origin, file_system_type,
+ fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&PepperFileSystemBrowserHost::OpenFileSystemComplete,
+ weak_factory_.GetWeakPtr(),
+ reply_context));
+ fs_context_ = fs_context;
+}
+
+void PepperFileSystemBrowserHost::OpenFileSystemComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ base::PlatformFileError error,
+ const std::string& /* unused */,
+ const GURL& root) {
+ int32 pp_error = ppapi::PlatformFileErrorToPepperError(error);
+ if (pp_error == PP_OK) {
+ opened_ = true;
+ root_url_ = root;
+ }
+ reply_context.params.set_result(pp_error);
+ host()->SendReply(reply_context, PpapiPluginMsg_FileSystem_OpenReply());
+}
+
+int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem(
+ ppapi::host::HostMessageContext* context,
+ const std::string& fsid) {
+ called_open_ = true;
+ // Do a sanity check.
+ if (!LooksLikeAGuid(fsid))
+ return PP_ERROR_BADARGUMENT;
+ const GURL& url =
+ browser_ppapi_host_->GetDocumentURLForInstance(pp_instance());
+ root_url_ = GURL(fileapi::GetIsolatedFileSystemRootURIString(
+ url.GetOrigin(), fsid, "crxfs"));
+ opened_ = true;
+ return PP_OK;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h b/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h
new file mode 100644
index 00000000000..3eb146e5b7e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_file_system_browser_host.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_SYSTEM_BROWSER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_SYSTEM_BROWSER_HOST_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_host.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace content {
+
+class BrowserPpapiHost;
+
+class PepperFileSystemBrowserHost :
+ public ppapi::host::ResourceHost,
+ public base::SupportsWeakPtr<PepperFileSystemBrowserHost> {
+ public:
+ PepperFileSystemBrowserHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ PP_FileSystemType type);
+ virtual ~PepperFileSystemBrowserHost();
+
+ // ppapi::host::ResourceHost override.
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+ virtual bool IsFileSystemHost() OVERRIDE;
+
+ // Supports FileRefs direct access on the host side.
+ PP_FileSystemType GetType() const { return type_; }
+ bool IsOpened() const { return opened_; }
+ GURL GetRootUrl() const { return root_url_; }
+ scoped_refptr<fileapi::FileSystemContext> GetFileSystemContext() const {
+ return fs_context_;
+ }
+
+ private:
+ void GotFileSystemContext(
+ ppapi::host::ReplyMessageContext reply_context,
+ fileapi::FileSystemType file_system_type,
+ scoped_refptr<fileapi::FileSystemContext> fs_context);
+ void OpenFileSystemComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ base::PlatformFileError error,
+ const std::string& name,
+ const GURL& root);
+
+ int32_t OnHostMsgOpen(ppapi::host::HostMessageContext* context,
+ int64_t expected_size);
+ int32_t OnHostMsgInitIsolatedFileSystem(
+ ppapi::host::HostMessageContext* context,
+ const std::string& fsid);
+
+ BrowserPpapiHost* browser_ppapi_host_;
+ base::WeakPtrFactory<PepperFileSystemBrowserHost> weak_factory_;
+
+ PP_FileSystemType type_;
+ bool opened_; // whether open is successful.
+ GURL root_url_;
+ scoped_refptr<fileapi::FileSystemContext> fs_context_;
+ bool called_open_; // whether open has been called.
+
+ DISALLOW_COPY_AND_ASSIGN(PepperFileSystemBrowserHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FILE_SYSTEM_BROWSER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.cc b/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.cc
new file mode 100644
index 00000000000..ef740625bd3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.cc
@@ -0,0 +1,323 @@
+// 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 "content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_security_helper.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_constants.h"
+#include "ipc/ipc_platform_file.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/file_path.h"
+#include "ppapi/shared_impl/file_type_conversion.h"
+
+namespace content {
+
+namespace {
+
+bool CanRead(int process_id, const base::FilePath& path) {
+ return ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanReadFile(process_id, path);
+}
+
+bool CanWrite(int process_id, const base::FilePath& path) {
+ return ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanWriteFile(process_id, path);
+}
+
+bool CanReadWrite(int process_id, const base::FilePath& path) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ return policy->CanReadFile(process_id, path) &&
+ policy->CanWriteFile(process_id, path);
+}
+
+} // namespace
+
+PepperFlashFileMessageFilter::PepperFlashFileMessageFilter(
+ PP_Instance instance,
+ BrowserPpapiHost* host)
+ : plugin_process_handle_(host->GetPluginProcessHandle()) {
+ int unused;
+ host->GetRenderViewIDsForInstance(instance, &render_process_id_, &unused);
+ base::FilePath profile_data_directory = host->GetProfileDataDirectory();
+ std::string plugin_name = host->GetPluginName();
+
+ if (profile_data_directory.empty() || plugin_name.empty()) {
+ // These are used to construct the path. If they are not set it means we
+ // will construct a bad path and could provide access to the wrong files.
+ // In this case, |plugin_data_directory_| will remain unset and
+ // |ValidateAndConvertPepperFilePath| will fail.
+ NOTREACHED();
+ } else {
+ plugin_data_directory_ = GetDataDirName(profile_data_directory).Append(
+ base::FilePath::FromUTF8Unsafe(plugin_name));
+ }
+}
+
+PepperFlashFileMessageFilter::~PepperFlashFileMessageFilter() {
+}
+
+// static
+base::FilePath PepperFlashFileMessageFilter::GetDataDirName(
+ const base::FilePath& profile_path) {
+ return profile_path.Append(kPepperDataDirname);
+}
+
+scoped_refptr<base::TaskRunner>
+PepperFlashFileMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) {
+ // The blocking pool provides a pool of threads to run file
+ // operations, instead of a single thread which might require
+ // queuing time. Since these messages are synchronous as sent from
+ // the plugin, the sending thread cannot send a new message until
+ // this one returns, so there is no need to sequence tasks here. If
+ // the plugin has multiple threads, it cannot make assumptions about
+ // ordering of IPC message sends, so it cannot make assumptions
+ // about ordering of operations caused by those IPC messages.
+ return scoped_refptr<base::TaskRunner>(BrowserThread::GetBlockingPool());
+}
+
+int32_t PepperFlashFileMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperFlashFileMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_OpenFile,
+ OnOpenFile)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_RenameFile,
+ OnRenameFile)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_DeleteFileOrDir,
+ OnDeleteFileOrDir)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_CreateDir,
+ OnCreateDir)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_QueryFile,
+ OnQueryFile)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_GetDirContents,
+ OnGetDirContents)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_FlashFile_CreateTemporaryFile,
+ OnCreateTemporaryFile)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperFlashFileMessageFilter::OnOpenFile(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path,
+ int pp_open_flags) {
+ base::FilePath full_path = ValidateAndConvertPepperFilePath(
+ path,
+ base::Bind(&CanOpenWithPepperFlags, pp_open_flags));
+ if (full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ int platform_file_flags = 0;
+ if (!ppapi::PepperFileOpenFlagsToPlatformFileFlags(
+ pp_open_flags, &platform_file_flags)) {
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file_handle = base::CreatePlatformFile(
+ full_path, platform_file_flags, NULL, &error);
+ if (error != base::PLATFORM_FILE_OK) {
+ DCHECK_EQ(file_handle, base::kInvalidPlatformFileValue);
+ return ppapi::PlatformFileErrorToPepperError(error);
+ }
+
+ // Make sure we didn't try to open a directory: directory fd shouldn't be
+ // passed to untrusted processes because they open security holes.
+ base::PlatformFileInfo info;
+ if (!base::GetPlatformFileInfo(file_handle, &info) || info.is_directory) {
+ // When in doubt, throw it out.
+ base::ClosePlatformFile(file_handle);
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ IPC::PlatformFileForTransit file = IPC::GetFileHandleForProcess(file_handle,
+ plugin_process_handle_, true);
+ ppapi::host::ReplyMessageContext reply_context =
+ context->MakeReplyMessageContext();
+ reply_context.params.AppendHandle(ppapi::proxy::SerializedHandle(
+ ppapi::proxy::SerializedHandle::FILE, file));
+ SendReply(reply_context, IPC::Message());
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperFlashFileMessageFilter::OnRenameFile(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& from_path,
+ const ppapi::PepperFilePath& to_path) {
+ base::FilePath from_full_path = ValidateAndConvertPepperFilePath(
+ from_path, base::Bind(&CanWrite));
+ base::FilePath to_full_path = ValidateAndConvertPepperFilePath(
+ to_path, base::Bind(&CanWrite));
+ if (from_full_path.empty() || to_full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ bool result = base::Move(from_full_path, to_full_path);
+ return ppapi::PlatformFileErrorToPepperError(result ?
+ base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+}
+
+int32_t PepperFlashFileMessageFilter::OnDeleteFileOrDir(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path,
+ bool recursive) {
+ base::FilePath full_path = ValidateAndConvertPepperFilePath(
+ path, base::Bind(&CanWrite));
+ if (full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ bool result = base::DeleteFile(full_path, recursive);
+ return ppapi::PlatformFileErrorToPepperError(result ?
+ base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+}
+int32_t PepperFlashFileMessageFilter::OnCreateDir(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path) {
+ base::FilePath full_path = ValidateAndConvertPepperFilePath(
+ path, base::Bind(&CanWrite));
+ if (full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ bool result = file_util::CreateDirectory(full_path);
+ return ppapi::PlatformFileErrorToPepperError(result ?
+ base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+}
+
+int32_t PepperFlashFileMessageFilter::OnQueryFile(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path) {
+ base::FilePath full_path = ValidateAndConvertPepperFilePath(
+ path, base::Bind(&CanRead));
+ if (full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ base::PlatformFileInfo info;
+ bool result = file_util::GetFileInfo(full_path, &info);
+ context->reply_msg = PpapiPluginMsg_FlashFile_QueryFileReply(info);
+ return ppapi::PlatformFileErrorToPepperError(result ?
+ base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+}
+
+int32_t PepperFlashFileMessageFilter::OnGetDirContents(
+ ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path) {
+ base::FilePath full_path = ValidateAndConvertPepperFilePath(
+ path, base::Bind(&CanRead));
+ if (full_path.empty()) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ ppapi::DirContents contents;
+ base::FileEnumerator enumerator(full_path, false,
+ base::FileEnumerator::FILES |
+ base::FileEnumerator::DIRECTORIES |
+ base::FileEnumerator::INCLUDE_DOT_DOT);
+
+ while (!enumerator.Next().empty()) {
+ base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ ppapi::DirEntry entry = {
+ info.GetName(),
+ info.IsDirectory()
+ };
+ contents.push_back(entry);
+ }
+
+ context->reply_msg = PpapiPluginMsg_FlashFile_GetDirContentsReply(contents);
+ return PP_OK;
+}
+
+int32_t PepperFlashFileMessageFilter::OnCreateTemporaryFile(
+ ppapi::host::HostMessageContext* context) {
+ ppapi::PepperFilePath dir_path(
+ ppapi::PepperFilePath::DOMAIN_MODULE_LOCAL, base::FilePath());
+ base::FilePath validated_dir_path = ValidateAndConvertPepperFilePath(
+ dir_path, base::Bind(&CanReadWrite));
+ if (validated_dir_path.empty() ||
+ (!base::DirectoryExists(validated_dir_path) &&
+ !file_util::CreateDirectory(validated_dir_path))) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_ACCESS_DENIED);
+ }
+
+ base::FilePath file_path;
+ if (!file_util::CreateTemporaryFileInDir(validated_dir_path, &file_path)) {
+ return ppapi::PlatformFileErrorToPepperError(
+ base::PLATFORM_FILE_ERROR_FAILED);
+ }
+
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file_handle = base::CreatePlatformFile(
+ file_path,
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY |
+ base::PLATFORM_FILE_DELETE_ON_CLOSE,
+ NULL, &error);
+
+ if (error != base::PLATFORM_FILE_OK) {
+ DCHECK_EQ(file_handle, base::kInvalidPlatformFileValue);
+ return ppapi::PlatformFileErrorToPepperError(error);
+ }
+
+ IPC::PlatformFileForTransit file = IPC::GetFileHandleForProcess(file_handle,
+ plugin_process_handle_, true);
+ ppapi::host::ReplyMessageContext reply_context =
+ context->MakeReplyMessageContext();
+ reply_context.params.AppendHandle(ppapi::proxy::SerializedHandle(
+ ppapi::proxy::SerializedHandle::FILE, file));
+ SendReply(reply_context, IPC::Message());
+ return PP_OK_COMPLETIONPENDING;
+}
+
+base::FilePath PepperFlashFileMessageFilter::ValidateAndConvertPepperFilePath(
+ const ppapi::PepperFilePath& pepper_path,
+ const CheckPermissionsCallback& check_permissions_callback) const {
+ base::FilePath file_path; // Empty path returned on error.
+ switch (pepper_path.domain()) {
+ case ppapi::PepperFilePath::DOMAIN_ABSOLUTE:
+ if (pepper_path.path().IsAbsolute() &&
+ check_permissions_callback.Run(render_process_id_,
+ pepper_path.path()))
+ file_path = pepper_path.path();
+ break;
+ case ppapi::PepperFilePath::DOMAIN_MODULE_LOCAL:
+ // This filter provides the module name portion of the path to prevent
+ // plugins from accessing each other's data.
+ if (!plugin_data_directory_.empty() &&
+ !pepper_path.path().IsAbsolute() &&
+ !pepper_path.path().ReferencesParent())
+ file_path = plugin_data_directory_.Append(pepper_path.path());
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return file_path;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h b/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h
new file mode 100644
index 00000000000..f89d7cf5907
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h
@@ -0,0 +1,86 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_FILE_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_FILE_MESSAGE_FILTER_H_
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/host/resource_host.h"
+#include "ppapi/host/resource_message_filter.h"
+
+namespace content {
+class BrowserPpapiHost;
+}
+
+namespace ppapi {
+class PepperFilePath;
+}
+
+namespace ppapi {
+namespace host {
+struct HostMessageContext;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHost;
+
+// All file messages are handled by BrowserThread's blocking pool.
+class PepperFlashFileMessageFilter : public ppapi::host::ResourceMessageFilter {
+ public:
+ PepperFlashFileMessageFilter(PP_Instance instance,
+ BrowserPpapiHost* host);
+
+ static base::FilePath GetDataDirName(const base::FilePath& profile_path);
+
+ private:
+ typedef base::Callback<bool(int, const base::FilePath&)>
+ CheckPermissionsCallback;
+
+ virtual ~PepperFlashFileMessageFilter();
+
+ // ppapi::host::ResourceMessageFilter overrides.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ int32_t OnOpenFile(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path,
+ int pp_open_flags);
+ int32_t OnRenameFile(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& from_path,
+ const ppapi::PepperFilePath& to_path);
+ int32_t OnDeleteFileOrDir(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path,
+ bool recursive);
+ int32_t OnCreateDir(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path);
+ int32_t OnQueryFile(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path);
+ int32_t OnGetDirContents(ppapi::host::HostMessageContext* context,
+ const ppapi::PepperFilePath& path);
+ int32_t OnCreateTemporaryFile(ppapi::host::HostMessageContext* context);
+
+ base::FilePath ValidateAndConvertPepperFilePath(
+ const ppapi::PepperFilePath& pepper_path,
+ const CheckPermissionsCallback& check_permissions_callback) const;
+
+ base::FilePath plugin_data_directory_;
+ int render_process_id_;
+ base::ProcessHandle plugin_process_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperFlashFileMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_FILE_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.cc
new file mode 100644
index 00000000000..818bf24a274
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.cc
@@ -0,0 +1,84 @@
+// 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 "content/browser/renderer_host/pepper/pepper_gamepad_host.h"
+
+#include "base/bind.h"
+#include "content/browser/gamepad/gamepad_service.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/ppb_gamepad_shared.h"
+
+namespace content {
+
+PepperGamepadHost::PepperGamepadHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ browser_ppapi_host_(host),
+ gamepad_service_(GamepadService::GetInstance()),
+ is_started_(false),
+ weak_factory_(this) {
+}
+
+PepperGamepadHost::PepperGamepadHost(GamepadService* gamepad_service,
+ BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ browser_ppapi_host_(host),
+ gamepad_service_(gamepad_service),
+ is_started_(false),
+ weak_factory_(this) {
+}
+
+PepperGamepadHost::~PepperGamepadHost() {
+ if (is_started_)
+ gamepad_service_->RemoveConsumer();
+}
+
+int32_t PepperGamepadHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperGamepadHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_Gamepad_RequestMemory,
+ OnRequestMemory)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperGamepadHost::OnRequestMemory(
+ ppapi::host::HostMessageContext* context) {
+ if (is_started_)
+ return PP_ERROR_FAILED;
+
+ gamepad_service_->AddConsumer();
+ is_started_ = true;
+
+ // Don't send the shared memory back until the user has interacted with the
+ // gamepad. This is to prevent fingerprinting and matches what the web
+ // platform does.
+ gamepad_service_->RegisterForUserGesture(
+ base::Bind(&PepperGamepadHost::GotUserGesture,
+ weak_factory_.GetWeakPtr(),
+ context->MakeReplyMessageContext()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperGamepadHost::GotUserGesture(
+ const ppapi::host::ReplyMessageContext& context) {
+ base::SharedMemoryHandle handle =
+ gamepad_service_->GetSharedMemoryHandleForProcess(
+ browser_ppapi_host_->GetPluginProcessHandle());
+
+ context.params.AppendHandle(ppapi::proxy::SerializedHandle(
+ handle, sizeof(ppapi::ContentGamepadHardwareBuffer)));
+ host()->SendReply(context, PpapiPluginMsg_Gamepad_SendMemory());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.h b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.h
new file mode 100644
index 00000000000..1db73aa694a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host.h
@@ -0,0 +1,61 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_GAMEPAD_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_GAMEPAD_HOST_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "ppapi/host/resource_host.h"
+
+namespace ppapi {
+namespace host {
+struct ReplyMessageContext;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHost;
+class GamepadService;
+
+class CONTENT_EXPORT PepperGamepadHost : public ppapi::host::ResourceHost {
+ public:
+ PepperGamepadHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource);
+
+ // Allows tests to specify a gamepad service to use rather than the global
+ // singleton. The caller owns the gamepad_service pointer.
+ PepperGamepadHost(GamepadService* gamepad_service,
+ BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource);
+
+ virtual ~PepperGamepadHost();
+
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ private:
+ int32_t OnRequestMemory(ppapi::host::HostMessageContext* context);
+
+ void GotUserGesture(const ppapi::host::ReplyMessageContext& in_context);
+
+ BrowserPpapiHost* browser_ppapi_host_;
+
+ GamepadService* gamepad_service_;
+
+ bool is_started_;
+
+ base::WeakPtrFactory<PepperGamepadHost> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperGamepadHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_GAMEPAD_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host_unittest.cc b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host_unittest.cc
new file mode 100644
index 00000000000..3dfbe2bb198
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_gamepad_host_unittest.cc
@@ -0,0 +1,204 @@
+// 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 <string.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/gamepad/gamepad_test_helpers.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_test.h"
+#include "content/browser/renderer_host/pepper/pepper_gamepad_host.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/proxy/gamepad_resource.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/resource_message_params.h"
+#include "ppapi/shared_impl/ppb_gamepad_shared.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+class PepperGamepadHostTest
+ : public testing::Test,
+ public BrowserPpapiHostTest {
+ public:
+ PepperGamepadHostTest() {
+ }
+ virtual ~PepperGamepadHostTest() {
+ }
+
+ void ConstructService(const WebKit::WebGamepads& test_data) {
+ service_.reset(new GamepadServiceTestConstructor(test_data));
+ }
+
+ GamepadService* gamepad_service() { return service_->gamepad_service(); }
+
+ protected:
+ scoped_ptr<GamepadServiceTestConstructor> service_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperGamepadHostTest);
+};
+
+inline ptrdiff_t AddressDiff(const void* a, const void* b) {
+ return static_cast<const char*>(a) - static_cast<const char*>(b);
+}
+
+} // namespace
+
+// Validate the memory layout of the Pepper proxy struct matches the content
+// one. The proxy can't depend on content so has a duplicate definition. This
+// code can see both definitions so we do the validation here.
+TEST_F(PepperGamepadHostTest, ValidateHardwareBuffersMatch) {
+ // Hardware buffer.
+ COMPILE_ASSERT(sizeof(ppapi::ContentGamepadHardwareBuffer) ==
+ sizeof(GamepadHardwareBuffer),
+ gamepad_hardware_buffers_must_match);
+ ppapi::ContentGamepadHardwareBuffer ppapi_buf;
+ GamepadHardwareBuffer content_buf;
+ EXPECT_EQ(AddressDiff(&content_buf.sequence, &content_buf),
+ AddressDiff(&ppapi_buf.sequence, &ppapi_buf));
+ EXPECT_EQ(AddressDiff(&content_buf.buffer, &content_buf),
+ AddressDiff(&ppapi_buf.buffer, &ppapi_buf));
+}
+
+TEST_F(PepperGamepadHostTest, ValidateGamepadsMatch) {
+ // Gamepads.
+ COMPILE_ASSERT(sizeof(ppapi::WebKitGamepads) ==
+ sizeof(WebKit::WebGamepads),
+ gamepads_data_must_match);
+ ppapi::WebKitGamepads ppapi_gamepads;
+ WebKit::WebGamepads web_gamepads;
+ EXPECT_EQ(AddressDiff(&web_gamepads.length, &web_gamepads),
+ AddressDiff(&ppapi_gamepads.length, &ppapi_gamepads));
+
+ // See comment below on storage & the EXPECT macro.
+ size_t webkit_items_length_cap = WebKit::WebGamepads::itemsLengthCap;
+ size_t ppapi_items_length_cap = ppapi::WebKitGamepads::kItemsLengthCap;
+ EXPECT_EQ(webkit_items_length_cap, ppapi_items_length_cap);
+
+ for (size_t i = 0; i < web_gamepads.itemsLengthCap; i++) {
+ EXPECT_EQ(AddressDiff(&web_gamepads.items[0], &web_gamepads),
+ AddressDiff(&ppapi_gamepads.items[0], &ppapi_gamepads));
+ }
+}
+
+TEST_F(PepperGamepadHostTest, ValidateGamepadMatch) {
+ // Gamepad.
+ COMPILE_ASSERT(sizeof(ppapi::WebKitGamepad) ==
+ sizeof(WebKit::WebGamepad),
+ gamepad_data_must_match);
+ ppapi::WebKitGamepad ppapi_gamepad;
+ WebKit::WebGamepad web_gamepad;
+
+ // Using EXPECT seems to force storage for the parameter, which the constants
+ // in the WebKit/PPAPI headers don't have. So we have to use temporaries
+ // before comparing them.
+ size_t webkit_id_length_cap = WebKit::WebGamepad::idLengthCap;
+ size_t ppapi_id_length_cap = ppapi::WebKitGamepad::kIdLengthCap;
+ EXPECT_EQ(webkit_id_length_cap, ppapi_id_length_cap);
+
+ size_t webkit_axes_length_cap = WebKit::WebGamepad::axesLengthCap;
+ size_t ppapi_axes_length_cap = ppapi::WebKitGamepad::kAxesLengthCap;
+ EXPECT_EQ(webkit_axes_length_cap, ppapi_axes_length_cap);
+
+ size_t webkit_buttons_length_cap = WebKit::WebGamepad::buttonsLengthCap;
+ size_t ppapi_buttons_length_cap = ppapi::WebKitGamepad::kButtonsLengthCap;
+ EXPECT_EQ(webkit_buttons_length_cap, ppapi_buttons_length_cap);
+
+ EXPECT_EQ(AddressDiff(&web_gamepad.connected, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.connected, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.id, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.id, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.timestamp, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.timestamp, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.axesLength, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.axes_length, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.axes, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.axes, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.buttonsLength, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.buttons_length, &ppapi_gamepad));
+ EXPECT_EQ(AddressDiff(&web_gamepad.buttons, &web_gamepad),
+ AddressDiff(&ppapi_gamepad.buttons, &ppapi_gamepad));
+}
+
+TEST_F(PepperGamepadHostTest, WaitForReply) {
+ WebKit::WebGamepads default_data;
+ memset(&default_data, 0, sizeof(WebKit::WebGamepads));
+ default_data.length = 1;
+ default_data.items[0].connected = true;
+ default_data.items[0].buttonsLength = 1;
+ ConstructService(default_data);
+
+ PP_Instance pp_instance = 12345;
+ PP_Resource pp_resource = 67890;
+ PepperGamepadHost gamepad_host(gamepad_service(), GetBrowserPpapiHost(),
+ pp_instance, pp_resource);
+
+ // Synthesize a request for gamepad data.
+ ppapi::host::HostMessageContext context(
+ ppapi::proxy::ResourceMessageCallParams(pp_resource, 1));
+ EXPECT_EQ(PP_OK_COMPLETIONPENDING,
+ gamepad_host.OnResourceMessageReceived(
+ PpapiHostMsg_Gamepad_RequestMemory(),
+ &context));
+
+ // Wait for the gamepad background thread to read twice to make sure we
+ // don't get a message yet (see below for why).
+ MockGamepadDataFetcher* fetcher = service_->data_fetcher();
+ fetcher->WaitForDataRead();
+ fetcher->WaitForDataRead();
+
+ // It should not have sent the callback message.
+ service_->message_loop().RunUntilIdle();
+ EXPECT_EQ(0u, sink().message_count());
+
+ // Set a button down and wait for it to be read twice.
+ //
+ // We wait for two reads before calling RunAllPending because the provider
+ // will read the data on the background thread (setting the event) and *then*
+ // will issue the callback on our thread. Waiting for it to read twice
+ // ensures that it was able to issue callbacks for the first read (if it
+ // issued one) before we try to check for it.
+ WebKit::WebGamepads button_down_data = default_data;
+ button_down_data.items[0].buttons[0] = 1.f;
+ fetcher->SetTestData(button_down_data);
+ fetcher->WaitForDataRead();
+ fetcher->WaitForDataRead();
+
+ // It should have sent a callback.
+ service_->message_loop().RunUntilIdle();
+ ppapi::proxy::ResourceMessageReplyParams reply_params;
+ IPC::Message reply_msg;
+ ASSERT_TRUE(sink().GetFirstResourceReplyMatching(
+ PpapiPluginMsg_Gamepad_SendMemory::ID, &reply_params, &reply_msg));
+
+ // Extract the shared memory handle.
+ base::SharedMemoryHandle reply_handle;
+ EXPECT_TRUE(reply_params.TakeSharedMemoryHandleAtIndex(0, &reply_handle));
+
+ // Validate the shared memory.
+ base::SharedMemory shared_memory(reply_handle, true);
+ EXPECT_TRUE(shared_memory.Map(sizeof(ppapi::ContentGamepadHardwareBuffer)));
+ const ppapi::ContentGamepadHardwareBuffer* buffer =
+ static_cast<const ppapi::ContentGamepadHardwareBuffer*>(
+ shared_memory.memory());
+ EXPECT_EQ(button_down_data.length, buffer->buffer.length);
+ EXPECT_EQ(button_down_data.items[0].buttonsLength,
+ buffer->buffer.items[0].buttons_length);
+ for (size_t i = 0; i < ppapi::WebKitGamepad::kButtonsLengthCap; i++) {
+ EXPECT_EQ(button_down_data.items[0].buttons[i],
+ buffer->buffer.items[0].buttons[i]);
+ }
+
+ // Duplicate requests should be denied.
+ EXPECT_EQ(PP_ERROR_FAILED,
+ gamepad_host.OnResourceMessageReceived(
+ PpapiHostMsg_Gamepad_RequestMemory(),
+ &context));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc b/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc
new file mode 100644
index 00000000000..e0e17b367ed
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc
@@ -0,0 +1,222 @@
+// 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 "content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_lookup_request.h"
+#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/common/socket_permission_request.h"
+#include "net/base/address_list.h"
+#include "net/dns/host_resolver.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/private/ppb_host_resolver_private.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/error_conversion.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+
+using ppapi::host::NetErrorToPepperError;
+using ppapi::host::ReplyMessageContext;
+
+namespace content {
+
+namespace {
+
+void PrepareRequestInfo(const PP_HostResolver_Private_Hint& hint,
+ net::HostResolver::RequestInfo* request_info) {
+ DCHECK(request_info);
+
+ net::AddressFamily address_family;
+ switch (hint.family) {
+ case PP_NETADDRESSFAMILY_PRIVATE_IPV4:
+ address_family = net::ADDRESS_FAMILY_IPV4;
+ break;
+ case PP_NETADDRESSFAMILY_PRIVATE_IPV6:
+ address_family = net::ADDRESS_FAMILY_IPV6;
+ break;
+ default:
+ address_family = net::ADDRESS_FAMILY_UNSPECIFIED;
+ }
+ request_info->set_address_family(address_family);
+
+ net::HostResolverFlags host_resolver_flags = 0;
+ if (hint.flags & PP_HOST_RESOLVER_PRIVATE_FLAGS_CANONNAME)
+ host_resolver_flags |= net::HOST_RESOLVER_CANONNAME;
+ if (hint.flags & PP_HOST_RESOLVER_PRIVATE_FLAGS_LOOPBACK_ONLY)
+ host_resolver_flags |= net::HOST_RESOLVER_LOOPBACK_ONLY;
+ request_info->set_host_resolver_flags(host_resolver_flags);
+}
+
+void CreateNetAddressListFromAddressList(
+ const net::AddressList& list,
+ std::vector<PP_NetAddress_Private>* net_address_list) {
+ DCHECK(net_address_list);
+
+ net_address_list->clear();
+ net_address_list->reserve(list.size());
+
+ PP_NetAddress_Private address;
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (!ppapi::NetAddressPrivateImpl::IPEndPointToNetAddress(list[i].address(),
+ list[i].port(),
+ &address)) {
+ net_address_list->clear();
+ return;
+ }
+ net_address_list->push_back(address);
+ }
+}
+
+} // namespace
+
+PepperHostResolverMessageFilter::PepperHostResolverMessageFilter(
+ BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api)
+ : external_plugin_(host->external_plugin()),
+ private_api_(private_api),
+ render_process_id_(0),
+ render_view_id_(0) {
+ DCHECK(host);
+
+ if (!host->GetRenderViewIDsForInstance(
+ instance,
+ &render_process_id_,
+ &render_view_id_)) {
+ NOTREACHED();
+ }
+}
+
+PepperHostResolverMessageFilter::~PepperHostResolverMessageFilter() {
+}
+
+scoped_refptr<base::TaskRunner>
+PepperHostResolverMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ if (message.type() == PpapiHostMsg_HostResolver_Resolve::ID)
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
+ return NULL;
+}
+
+int32_t PepperHostResolverMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperHostResolverMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_HostResolver_Resolve, OnMsgResolve)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperHostResolverMessageFilter::OnMsgResolve(
+ const ppapi::host::HostMessageContext* context,
+ const ppapi::HostPortPair& host_port,
+ const PP_HostResolver_Private_Hint& hint) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Check plugin permissions.
+ SocketPermissionRequest request(
+ SocketPermissionRequest::RESOLVE_HOST, host_port.host, host_port.port);
+ RenderViewHost* render_view_host =
+ RenderViewHost::FromID(render_process_id_, render_view_id_);
+ if (!render_view_host ||
+ !pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
+ private_api_,
+ request,
+ render_view_host)) {
+ return PP_ERROR_NOACCESS;
+ }
+
+ RenderProcessHost* render_process_host =
+ RenderProcessHost::FromID(render_process_id_);
+ if (!render_process_host)
+ return PP_ERROR_FAILED;
+ BrowserContext* browser_context = render_process_host->GetBrowserContext();
+ if (!browser_context || !browser_context->GetResourceContext())
+ return PP_ERROR_FAILED;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PepperHostResolverMessageFilter::DoResolve, this,
+ context->MakeReplyMessageContext(),
+ host_port,
+ hint,
+ browser_context->GetResourceContext()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperHostResolverMessageFilter::DoResolve(
+ const ReplyMessageContext& context,
+ const ppapi::HostPortPair& host_port,
+ const PP_HostResolver_Private_Hint& hint,
+ ResourceContext* resource_context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::HostResolver* host_resolver = resource_context->GetHostResolver();
+ if (!host_resolver) {
+ SendResolveError(PP_ERROR_FAILED, context);
+ return;
+ }
+
+ net::HostResolver::RequestInfo request_info(
+ net::HostPortPair(host_port.host, host_port.port));
+ PrepareRequestInfo(hint, &request_info);
+
+ scoped_ptr<ReplyMessageContext> bound_info(new ReplyMessageContext(context));
+
+ // The lookup request will delete itself on completion.
+ PepperLookupRequest<ReplyMessageContext>* lookup_request =
+ new PepperLookupRequest<ReplyMessageContext>(
+ host_resolver,
+ request_info,
+ bound_info.release(),
+ base::Bind(&PepperHostResolverMessageFilter::OnLookupFinished, this));
+ lookup_request->Start();
+}
+
+void PepperHostResolverMessageFilter::OnLookupFinished(
+ int net_result,
+ const net::AddressList& addresses,
+ const ReplyMessageContext& context) {
+ if (net_result != net::OK) {
+ SendResolveError(NetErrorToPepperError(net_result), context);
+ } else {
+ const std::string& canonical_name = addresses.canonical_name();
+ NetAddressList net_address_list;
+ CreateNetAddressListFromAddressList(addresses, &net_address_list);
+ if (net_address_list.empty())
+ SendResolveError(PP_ERROR_FAILED, context);
+ else
+ SendResolveReply(PP_OK, canonical_name, net_address_list, context);
+ }
+}
+
+void PepperHostResolverMessageFilter::SendResolveReply(
+ int32_t result,
+ const std::string& canonical_name,
+ const NetAddressList& net_address_list,
+ const ReplyMessageContext& context) {
+ ReplyMessageContext reply_context = context;
+ reply_context.params.set_result(result);
+ SendReply(reply_context,
+ PpapiPluginMsg_HostResolver_ResolveReply(
+ canonical_name, net_address_list));
+}
+
+void PepperHostResolverMessageFilter::SendResolveError(
+ int32_t error,
+ const ReplyMessageContext& context) {
+ SendResolveReply(error, std::string(), NetAddressList(), context);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h b/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
new file mode 100644
index 00000000000..cc936036d3e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
@@ -0,0 +1,89 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_HOST_RESOLVER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_HOST_RESOLVER_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/common/content_export.h"
+#include "content/public/common/process_type.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/host/resource_message_filter.h"
+
+struct PP_HostResolver_Private_Hint;
+struct PP_NetAddress_Private;
+
+namespace net {
+class AddressList;
+}
+
+namespace ppapi {
+struct HostPortPair;
+
+namespace host {
+struct HostMessageContext;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+class ResourceContext;
+
+class CONTENT_EXPORT PepperHostResolverMessageFilter
+ : public ppapi::host::ResourceMessageFilter {
+ public:
+ PepperHostResolverMessageFilter(BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api);
+
+ protected:
+ virtual ~PepperHostResolverMessageFilter();
+
+ private:
+ typedef std::vector<PP_NetAddress_Private> NetAddressList;
+
+ // ppapi::host::ResourceMessageFilter overrides.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ int32_t OnMsgResolve(const ppapi::host::HostMessageContext* context,
+ const ppapi::HostPortPair& host_port,
+ const PP_HostResolver_Private_Hint& hint);
+
+ // Backend for OnMsgResolve(). Delegates host resolution to the
+ // Browser's HostResolver. Must be called on the IO thread.
+ void DoResolve(const ppapi::host::ReplyMessageContext& context,
+ const ppapi::HostPortPair& host_port,
+ const PP_HostResolver_Private_Hint& hint,
+ ResourceContext* resource_context);
+
+ void OnLookupFinished(int net_result,
+ const net::AddressList& addresses,
+ const ppapi::host::ReplyMessageContext& bound_info);
+ void SendResolveReply(int32_t result,
+ const std::string& canonical_name,
+ const NetAddressList& net_address_list,
+ const ppapi::host::ReplyMessageContext& context);
+ void SendResolveError(int32_t error,
+ const ppapi::host::ReplyMessageContext& context);
+
+ bool external_plugin_;
+ bool private_api_;
+ int render_process_id_;
+ int render_view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperHostResolverMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_HOST_RESOLVER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.cc b/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.cc
new file mode 100644
index 00000000000..337807d34e1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.cc
@@ -0,0 +1,306 @@
+// 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 "content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/file_util_proxy.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/base/escape.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/file_ref_create_info.h"
+#include "ppapi/shared_impl/file_ref_util.h"
+#include "ppapi/shared_impl/file_type_conversion.h"
+#include "ppapi/shared_impl/scoped_pp_var.h"
+#include "ppapi/shared_impl/time_conversion.h"
+#include "ppapi/shared_impl/var.h"
+#include "ppapi/thunk/enter.h"
+#include "ppapi/thunk/ppb_file_ref_api.h"
+#include "ppapi/thunk/ppb_file_system_api.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using ppapi::host::PpapiHost;
+using ppapi::host::ResourceHost;
+
+namespace content {
+
+PepperInternalFileRefBackend::PepperInternalFileRefBackend(
+ PpapiHost* host,
+ int render_process_id,
+ base::WeakPtr<PepperFileSystemBrowserHost> fs_host,
+ const std::string& path) : host_(host),
+ render_process_id_(render_process_id),
+ fs_host_(fs_host),
+ fs_type_(fs_host->GetType()),
+ path_(path),
+ weak_factory_(this) {
+ ppapi::NormalizeInternalPath(&path_);
+}
+
+PepperInternalFileRefBackend::~PepperInternalFileRefBackend() {
+}
+
+fileapi::FileSystemURL PepperInternalFileRefBackend::GetFileSystemURL() const {
+ if (!fs_url_.is_valid() && fs_host_.get() && fs_host_->IsOpened()) {
+ GURL fs_path = fs_host_->GetRootUrl().Resolve(
+ net::EscapePath(path_.substr(1)));
+ fs_url_ = GetFileSystemContext()->CrackURL(fs_path);
+ }
+ return fs_url_;
+}
+
+std::string PepperInternalFileRefBackend::GetFileSystemURLSpec() const {
+ if (fs_host_.get() && fs_host_->IsOpened() &&
+ fs_host_->GetRootUrl().is_valid()) {
+ return fs_host_->GetRootUrl().Resolve(
+ net::EscapePath(path_.substr(1))).spec();
+ }
+ return std::string();
+}
+
+base::FilePath PepperInternalFileRefBackend::GetExternalPath() const {
+ return base::FilePath();
+}
+
+scoped_refptr<fileapi::FileSystemContext>
+PepperInternalFileRefBackend::GetFileSystemContext() const {
+ // TODO(teravest): Make this work for CRX file systems.
+ if (!fs_host_.get())
+ return NULL;
+ return fs_host_->GetFileSystemContext();
+}
+
+void PepperInternalFileRefBackend::DidFinish(
+ ppapi::host::ReplyMessageContext context,
+ const IPC::Message& msg,
+ base::PlatformFileError error) {
+ context.params.set_result(ppapi::PlatformFileErrorToPepperError(error));
+ host_->SendReply(context, msg);
+}
+
+int32_t PepperInternalFileRefBackend::MakeDirectory(
+ ppapi::host::ReplyMessageContext reply_context,
+ bool make_ancestors) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->CreateDirectory(
+ GetFileSystemURL(),
+ false,
+ make_ancestors,
+ base::Bind(&PepperInternalFileRefBackend::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ reply_context,
+ PpapiPluginMsg_FileRef_MakeDirectoryReply()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperInternalFileRefBackend::Touch(
+ ppapi::host::ReplyMessageContext reply_context,
+ PP_Time last_access_time,
+ PP_Time last_modified_time) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->TouchFile(
+ GetFileSystemURL(),
+ ppapi::PPTimeToTime(last_access_time),
+ ppapi::PPTimeToTime(last_modified_time),
+ base::Bind(&PepperInternalFileRefBackend::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ reply_context,
+ PpapiPluginMsg_FileRef_TouchReply()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperInternalFileRefBackend::Delete(
+ ppapi::host::ReplyMessageContext reply_context) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->Remove(
+ GetFileSystemURL(),
+ false,
+ base::Bind(&PepperInternalFileRefBackend::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ reply_context,
+ PpapiPluginMsg_FileRef_DeleteReply()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperInternalFileRefBackend::Rename(
+ ppapi::host::ReplyMessageContext reply_context,
+ PepperFileRefHost* new_file_ref) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ fileapi::FileSystemURL new_url = new_file_ref->GetFileSystemURL();
+ if (!new_url.is_valid())
+ return PP_ERROR_FAILED;
+ if (!new_url.IsInSameFileSystem(GetFileSystemURL()))
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->Move(
+ GetFileSystemURL(),
+ new_url,
+ base::Bind(&PepperInternalFileRefBackend::DidFinish,
+ weak_factory_.GetWeakPtr(),
+ reply_context,
+ PpapiPluginMsg_FileRef_RenameReply()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperInternalFileRefBackend::Query(
+ ppapi::host::ReplyMessageContext reply_context) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->GetMetadata(
+ GetFileSystemURL(),
+ base::Bind(&PepperInternalFileRefBackend::GetMetadataComplete,
+ weak_factory_.GetWeakPtr(),
+ reply_context));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperInternalFileRefBackend::GetMetadataComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info) {
+ reply_context.params.set_result(ppapi::PlatformFileErrorToPepperError(error));
+
+ PP_FileInfo pp_file_info;
+ if (error == base::PLATFORM_FILE_OK)
+ ppapi::PlatformFileInfoToPepperFileInfo(file_info, fs_type_, &pp_file_info);
+ else
+ memset(&pp_file_info, 0, sizeof(pp_file_info));
+
+ host_->SendReply(reply_context,
+ PpapiPluginMsg_FileRef_QueryReply(pp_file_info));
+}
+
+int32_t PepperInternalFileRefBackend::ReadDirectoryEntries(
+ ppapi::host::ReplyMessageContext reply_context) {
+ if (!GetFileSystemURL().is_valid())
+ return PP_ERROR_FAILED;
+
+ GetFileSystemContext()->operation_runner()->ReadDirectory(
+ GetFileSystemURL(),
+ base::Bind(&PepperInternalFileRefBackend::ReadDirectoryComplete,
+ weak_factory_.GetWeakPtr(),
+ reply_context));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperInternalFileRefBackend::ReadDirectoryComplete(
+ ppapi::host::ReplyMessageContext context,
+ base::PlatformFileError error,
+ const fileapi::FileSystemOperation::FileEntryList& file_list,
+ bool has_more) {
+ // The current filesystem backend always returns false.
+ DCHECK(!has_more);
+
+ context.params.set_result(ppapi::PlatformFileErrorToPepperError(error));
+
+ std::vector<ppapi::FileRef_CreateInfo> infos;
+ std::vector<PP_FileType> file_types;
+ if (error == base::PLATFORM_FILE_OK && fs_host_.get()) {
+ std::string dir_path = path_;
+ if (dir_path.empty() || dir_path[dir_path.size() - 1] != '/')
+ dir_path += '/';
+
+ for (fileapi::FileSystemOperation::FileEntryList::const_iterator it =
+ file_list.begin(); it != file_list.end(); ++it) {
+ if (it->is_directory)
+ file_types.push_back(PP_FILETYPE_DIRECTORY);
+ else
+ file_types.push_back(PP_FILETYPE_REGULAR);
+
+ ppapi::FileRef_CreateInfo info;
+ info.file_system_type = fs_type_;
+ info.file_system_plugin_resource = fs_host_->pp_resource();
+ std::string path =
+ dir_path + fileapi::FilePathToString(base::FilePath(it->name));
+ info.internal_path = path;
+ info.display_name = ppapi::GetNameForInternalFilePath(path);
+ infos.push_back(info);
+ }
+ }
+
+ host_->SendReply(context,
+ PpapiPluginMsg_FileRef_ReadDirectoryEntriesReply(infos, file_types));
+}
+
+int32_t PepperInternalFileRefBackend::GetAbsolutePath(
+ ppapi::host::ReplyMessageContext reply_context) {
+ host_->SendReply(reply_context,
+ PpapiPluginMsg_FileRef_GetAbsolutePathReply(path_));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperInternalFileRefBackend::CanRead() const {
+ fileapi::FileSystemURL url = GetFileSystemURL();
+ if (!FileSystemURLIsValid(GetFileSystemContext().get(), url))
+ return PP_ERROR_FAILED;
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanReadFileSystemFile(render_process_id_, url)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperInternalFileRefBackend::CanWrite() const {
+ fileapi::FileSystemURL url = GetFileSystemURL();
+ if (!FileSystemURLIsValid(GetFileSystemContext().get(), url))
+ return PP_ERROR_FAILED;
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanWriteFileSystemFile(render_process_id_, url)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperInternalFileRefBackend::CanCreate() const {
+ fileapi::FileSystemURL url = GetFileSystemURL();
+ if (!FileSystemURLIsValid(GetFileSystemContext().get(), url))
+ return PP_ERROR_FAILED;
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ CanCreateFileSystemFile(render_process_id_, url)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+int32_t PepperInternalFileRefBackend::CanReadWrite() const {
+ fileapi::FileSystemURL url = GetFileSystemURL();
+ if (!FileSystemURLIsValid(GetFileSystemContext().get(), url))
+ return PP_ERROR_FAILED;
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanReadFileSystemFile(render_process_id_, url) ||
+ !policy->CanWriteFileSystemFile(render_process_id_, url)) {
+ return PP_ERROR_NOACCESS;
+ }
+ return PP_OK;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h b/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h
new file mode 100644
index 00000000000..fb8a6cf7603
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_internal_file_ref_backend.h
@@ -0,0 +1,89 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_INTERNAL_FILE_REF_BACKEND_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_INTERNAL_FILE_REF_BACKEND_H_
+
+#include <string>
+
+#include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/c/pp_time.h"
+#include "ppapi/host/ppapi_host.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace content {
+
+class PepperFileSystemBrowserHost;
+
+// Implementations of FileRef operations for internal filesystems.
+class PepperInternalFileRefBackend : public PepperFileRefBackend {
+ public:
+ PepperInternalFileRefBackend(
+ ppapi::host::PpapiHost* host,
+ int render_process_id,
+ base::WeakPtr<PepperFileSystemBrowserHost> fs_host,
+ const std::string& path);
+ virtual ~PepperInternalFileRefBackend();
+
+ // PepperFileRefBackend overrides.
+ virtual int32_t MakeDirectory(ppapi::host::ReplyMessageContext context,
+ bool make_ancestors) OVERRIDE;
+ virtual int32_t Touch(ppapi::host::ReplyMessageContext context,
+ PP_Time last_accessed_time,
+ PP_Time last_modified_time) OVERRIDE;
+ virtual int32_t Delete(ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t Rename(ppapi::host::ReplyMessageContext context,
+ PepperFileRefHost* new_file_ref) OVERRIDE;
+ virtual int32_t Query(ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t ReadDirectoryEntries(
+ ppapi::host::ReplyMessageContext context) OVERRIDE;
+ virtual int32_t GetAbsolutePath(ppapi::host::ReplyMessageContext context)
+ OVERRIDE;
+
+ virtual fileapi::FileSystemURL GetFileSystemURL() const OVERRIDE;
+ virtual std::string GetFileSystemURLSpec() const OVERRIDE;
+ virtual base::FilePath GetExternalPath() const OVERRIDE;
+
+ virtual int32_t CanRead() const OVERRIDE;
+ virtual int32_t CanWrite() const OVERRIDE;
+ virtual int32_t CanCreate() const OVERRIDE;
+ virtual int32_t CanReadWrite() const OVERRIDE;
+
+ private:
+ // Generic reply callback.
+ void DidFinish(ppapi::host::ReplyMessageContext reply_context,
+ const IPC::Message& msg,
+ base::PlatformFileError error);
+
+ // Operation specific callbacks.
+ void GetMetadataComplete(
+ ppapi::host::ReplyMessageContext reply_context,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info);
+ void ReadDirectoryComplete(
+ ppapi::host::ReplyMessageContext context,
+ base::PlatformFileError error,
+ const fileapi::FileSystemOperation::FileEntryList& file_list,
+ bool has_more);
+
+ scoped_refptr<fileapi::FileSystemContext> GetFileSystemContext() const;
+
+ ppapi::host::PpapiHost* host_;
+ int render_process_id_;
+ base::WeakPtr<PepperFileSystemBrowserHost> fs_host_;
+ PP_FileSystemType fs_type_;
+ std::string path_;
+
+ mutable fileapi::FileSystemURL fs_url_;
+
+ base::WeakPtrFactory<PepperInternalFileRefBackend> weak_factory_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_INTERNAL_FILE_REF_BACKEND_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_lookup_request.h b/chromium/content/browser/renderer_host/pepper/pepper_lookup_request.h
new file mode 100644
index 00000000000..bcb9a60203f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_lookup_request.h
@@ -0,0 +1,63 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_LOOKUP_REQUEST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_LOOKUP_REQUEST_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+
+namespace content {
+
+template<class T>
+class PepperLookupRequest {
+ public:
+ typedef base::Callback<void(int, const net::AddressList&, const T&)>
+ LookupRequestCallback;
+
+ // Takes ownership over |bound_info|. |bound_info| will be passed to
+ // callback, when lookup will finish.
+ PepperLookupRequest(net::HostResolver* resolver,
+ const net::HostResolver::RequestInfo& request_info,
+ T* bound_info,
+ const LookupRequestCallback& callback)
+ : resolver_(resolver),
+ request_info_(request_info),
+ bound_info_(bound_info),
+ callback_(callback) {
+ }
+
+ void Start() {
+ int result = resolver_.Resolve(
+ request_info_, &addresses_,
+ base::Bind(&PepperLookupRequest<T>::OnLookupFinished,
+ base::Unretained(this)),
+ net::BoundNetLog());
+ if (result != net::ERR_IO_PENDING)
+ OnLookupFinished(result);
+ }
+
+ private:
+ void OnLookupFinished(int result) {
+ callback_.Run(result, addresses_, *bound_info_);
+ delete this;
+ }
+
+ net::SingleRequestHostResolver resolver_;
+ net::HostResolver::RequestInfo request_info_;
+ scoped_ptr<T> bound_info_;
+ LookupRequestCallback callback_;
+
+ net::AddressList addresses_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperLookupRequest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_LOOKUP_REQUEST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_message_filter.cc b/chromium/content/browser/renderer_host/pepper/pepper_message_filter.cc
new file mode 100644
index 00000000000..717de350dc7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_message_filter.cc
@@ -0,0 +1,491 @@
+// 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 "content/browser/renderer_host/pepper/pepper_message_filter.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/worker_pool.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+#include "content/browser/renderer_host/pepper/pepper_tcp_socket.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/pepper_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/common/content_client.h"
+#include "net/base/address_family.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/sys_addrinfo.h"
+#include "net/cert/cert_verifier.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/api_id.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+#include "ppapi/shared_impl/socket_option_data.h"
+
+using ppapi::NetAddressPrivateImpl;
+
+namespace content {
+namespace {
+
+const size_t kMaxSocketsAllowed = 1024;
+const uint32 kInvalidSocketID = 0;
+
+} // namespace
+
+PepperMessageFilter::PepperMessageFilter(int process_id,
+ BrowserContext* browser_context)
+ : plugin_type_(PLUGIN_TYPE_IN_PROCESS),
+ permissions_(),
+ process_id_(process_id),
+ external_plugin_render_view_id_(0),
+ resource_context_(browser_context->GetResourceContext()),
+ host_resolver_(NULL),
+ next_socket_id_(1) {
+ DCHECK(browser_context);
+ // Keep BrowserContext data in FILE-thread friendly storage.
+ browser_path_ = browser_context->GetPath();
+ incognito_ = browser_context->IsOffTheRecord();
+ DCHECK(resource_context_);
+}
+
+PepperMessageFilter::PepperMessageFilter(
+ const ppapi::PpapiPermissions& permissions,
+ net::HostResolver* host_resolver)
+ : plugin_type_(PLUGIN_TYPE_OUT_OF_PROCESS),
+ permissions_(permissions),
+ process_id_(0),
+ external_plugin_render_view_id_(0),
+ resource_context_(NULL),
+ host_resolver_(host_resolver),
+ next_socket_id_(1),
+ incognito_(false) {
+ DCHECK(host_resolver);
+}
+
+PepperMessageFilter::PepperMessageFilter(
+ const ppapi::PpapiPermissions& permissions,
+ net::HostResolver* host_resolver,
+ int process_id,
+ int render_view_id)
+ : plugin_type_(PLUGIN_TYPE_EXTERNAL_PLUGIN),
+ permissions_(permissions),
+ process_id_(process_id),
+ external_plugin_render_view_id_(render_view_id),
+ resource_context_(NULL),
+ host_resolver_(host_resolver),
+ next_socket_id_(1) {
+ DCHECK(host_resolver);
+}
+
+bool PepperMessageFilter::OnMessageReceived(const IPC::Message& msg,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(PepperMessageFilter, msg, *message_was_ok)
+ // TCP messages.
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_Create, OnTCPCreate)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_CreatePrivate,
+ OnTCPCreatePrivate)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_Connect, OnTCPConnect)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_ConnectWithNetAddress,
+ OnTCPConnectWithNetAddress)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_SSLHandshake,
+ OnTCPSSLHandshake)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_Read, OnTCPRead)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_Write, OnTCPWrite)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_Disconnect, OnTCPDisconnect)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBTCPSocket_SetOption, OnTCPSetOption)
+
+ // NetworkMonitor messages.
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBNetworkMonitor_Start,
+ OnNetworkMonitorStart)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBNetworkMonitor_Stop,
+ OnNetworkMonitorStop)
+
+ // X509 certificate messages.
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBX509Certificate_ParseDER,
+ OnX509CertificateParseDER);
+
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void PepperMessageFilter::OnIPAddressChanged() {
+ GetAndSendNetworkList();
+}
+
+net::HostResolver* PepperMessageFilter::GetHostResolver() {
+ return resource_context_ ?
+ resource_context_->GetHostResolver() : host_resolver_;
+}
+
+net::CertVerifier* PepperMessageFilter::GetCertVerifier() {
+ if (!cert_verifier_)
+ cert_verifier_.reset(net::CertVerifier::CreateDefault());
+
+ return cert_verifier_.get();
+}
+
+net::TransportSecurityState* PepperMessageFilter::GetTransportSecurityState() {
+ if (!transport_security_state_)
+ transport_security_state_.reset(new net::TransportSecurityState);
+
+ return transport_security_state_.get();
+}
+
+uint32 PepperMessageFilter::AddAcceptedTCPSocket(
+ int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ net::StreamSocket* socket) {
+ scoped_ptr<net::StreamSocket> s(socket);
+
+ uint32 tcp_socket_id = GenerateSocketID();
+ if (tcp_socket_id != kInvalidSocketID) {
+ // Currently all TCP sockets created this way correspond to
+ // PPB_TCPSocket_Private.
+ tcp_sockets_[tcp_socket_id] = linked_ptr<PepperTCPSocket>(
+ new PepperTCPSocket(this,
+ routing_id,
+ plugin_dispatcher_id,
+ tcp_socket_id,
+ s.release(),
+ true /* private_api */));
+ }
+ return tcp_socket_id;
+}
+
+PepperMessageFilter::~PepperMessageFilter() {
+ if (!network_monitor_ids_.empty())
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+}
+
+void PepperMessageFilter::OnTCPCreate(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32* socket_id) {
+ CreateTCPSocket(routing_id, plugin_dispatcher_id, false, socket_id);
+}
+
+void PepperMessageFilter::OnTCPCreatePrivate(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32* socket_id) {
+ CreateTCPSocket(routing_id, plugin_dispatcher_id, true, socket_id);
+}
+
+void PepperMessageFilter::OnTCPConnect(int32 routing_id,
+ uint32 socket_id,
+ const std::string& host,
+ uint16_t port) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ // This is only supported by PPB_TCPSocket_Private.
+ if (!iter->second->private_api()) {
+ NOTREACHED();
+ return;
+ }
+
+ content::SocketPermissionRequest params(
+ content::SocketPermissionRequest::TCP_CONNECT, host, port);
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PepperMessageFilter::CanUseSocketAPIs, this,
+ routing_id, params, true /* private_api */),
+ base::Bind(&PepperMessageFilter::DoTCPConnect, this,
+ routing_id, socket_id, host, port));
+}
+
+void PepperMessageFilter::DoTCPConnect(int32 routing_id,
+ uint32 socket_id,
+ const std::string& host,
+ uint16_t port,
+ bool allowed) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ // Due to current permission check process (IO -> UI -> IO) some
+ // calls to the TCP socket interface can be intermixed (like
+ // Connect and Close). So, NOTREACHED() is not appropriate here.
+ return;
+ }
+
+ if (routing_id == iter->second->routing_id() && allowed)
+ iter->second->Connect(host, port);
+ else
+ iter->second->SendConnectACKError(PP_ERROR_NOACCESS);
+}
+
+void PepperMessageFilter::OnTCPConnectWithNetAddress(
+ int32 routing_id,
+ uint32 socket_id,
+ const PP_NetAddress_Private& net_addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ content::SocketPermissionRequest params =
+ pepper_socket_utils::CreateSocketPermissionRequest(
+ content::SocketPermissionRequest::TCP_CONNECT, net_addr);
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PepperMessageFilter::CanUseSocketAPIs, this,
+ routing_id, params, iter->second->private_api()),
+ base::Bind(&PepperMessageFilter::DoTCPConnectWithNetAddress, this,
+ routing_id, socket_id, net_addr));
+}
+
+void PepperMessageFilter::DoTCPConnectWithNetAddress(
+ int32 routing_id,
+ uint32 socket_id,
+ const PP_NetAddress_Private& net_addr,
+ bool allowed) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ // Due to current permission check process (IO -> UI -> IO) some
+ // calls to the TCP socket interface can be intermixed (like
+ // ConnectWithNetAddress and Close). So, NOTREACHED() is not
+ // appropriate here.
+ return;
+ }
+
+ if (routing_id == iter->second->routing_id() && allowed)
+ iter->second->ConnectWithNetAddress(net_addr);
+ else
+ iter->second->SendConnectACKError(PP_ERROR_NOACCESS);
+}
+
+void PepperMessageFilter::OnTCPSSLHandshake(
+ uint32 socket_id,
+ const std::string& server_name,
+ uint16_t server_port,
+ const std::vector<std::vector<char> >& trusted_certs,
+ const std::vector<std::vector<char> >& untrusted_certs) {
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ // This is only supported by PPB_TCPSocket_Private.
+ if (!iter->second->private_api()) {
+ NOTREACHED();
+ return;
+ }
+
+ iter->second->SSLHandshake(server_name, server_port, trusted_certs,
+ untrusted_certs);
+}
+
+void PepperMessageFilter::OnTCPRead(uint32 socket_id, int32_t bytes_to_read) {
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ iter->second->Read(bytes_to_read);
+}
+
+void PepperMessageFilter::OnTCPWrite(uint32 socket_id,
+ const std::string& data) {
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ iter->second->Write(data);
+}
+
+void PepperMessageFilter::OnTCPDisconnect(uint32 socket_id) {
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Destroying the TCPSocket instance will cancel any pending completion
+ // callback. From this point on, there won't be any messages associated with
+ // this socket sent to the plugin side.
+ tcp_sockets_.erase(iter);
+}
+
+void PepperMessageFilter::OnTCPSetOption(uint32 socket_id,
+ PP_TCPSocket_Option name,
+ const ppapi::SocketOptionData& value) {
+ TCPSocketMap::iterator iter = tcp_sockets_.find(socket_id);
+ if (iter == tcp_sockets_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ iter->second->SetOption(name, value);
+}
+
+void PepperMessageFilter::OnNetworkMonitorStart(uint32 plugin_dispatcher_id) {
+ // Support all in-process plugins, and ones with "private" permissions.
+ if (plugin_type_ != PLUGIN_TYPE_IN_PROCESS &&
+ !permissions_.HasPermission(ppapi::PERMISSION_PRIVATE)) {
+ return;
+ }
+
+ if (network_monitor_ids_.empty())
+ net::NetworkChangeNotifier::AddIPAddressObserver(this);
+
+ network_monitor_ids_.insert(plugin_dispatcher_id);
+ GetAndSendNetworkList();
+}
+
+void PepperMessageFilter::OnNetworkMonitorStop(uint32 plugin_dispatcher_id) {
+ // Support all in-process plugins, and ones with "private" permissions.
+ if (plugin_type_ != PLUGIN_TYPE_IN_PROCESS &&
+ !permissions_.HasPermission(ppapi::PERMISSION_PRIVATE)) {
+ return;
+ }
+
+ network_monitor_ids_.erase(plugin_dispatcher_id);
+ if (network_monitor_ids_.empty())
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+}
+
+void PepperMessageFilter::OnX509CertificateParseDER(
+ const std::vector<char>& der,
+ bool* succeeded,
+ ppapi::PPB_X509Certificate_Fields* result) {
+ if (der.size() == 0)
+ *succeeded = false;
+ *succeeded = PepperTCPSocket::GetCertificateFields(&der[0], der.size(),
+ result);
+}
+
+uint32 PepperMessageFilter::GenerateSocketID() {
+ // TODO(yzshen): Change to use Pepper resource ID as socket ID.
+ //
+ // Generate a socket ID. For each process which sends us socket requests, IDs
+ // of living sockets must be unique, to each socket type.
+ //
+ // However, it is safe to generate IDs based on the internal state of a single
+ // PepperSocketMessageHandler object, because for each plugin or renderer
+ // process, there is at most one PepperMessageFilter (in other words, at most
+ // one PepperSocketMessageHandler) talking to it.
+ if (tcp_sockets_.size() >= kMaxSocketsAllowed)
+ return kInvalidSocketID;
+
+ uint32 socket_id = kInvalidSocketID;
+ do {
+ // Although it is unlikely, make sure that we won't cause any trouble when
+ // the counter overflows.
+ socket_id = next_socket_id_++;
+ } while (socket_id == kInvalidSocketID ||
+ tcp_sockets_.find(socket_id) != tcp_sockets_.end());
+
+ return socket_id;
+}
+
+bool PepperMessageFilter::CanUseSocketAPIs(
+ int32 render_id,
+ const content::SocketPermissionRequest& params,
+ bool private_api) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // External plugins always get their own PepperMessageFilter, initialized with
+ // a render view id. Use this instead of the one that came with the message,
+ // which is actually an API ID.
+ bool external_plugin = false;
+ if (plugin_type_ == PLUGIN_TYPE_EXTERNAL_PLUGIN) {
+ external_plugin = true;
+ render_id = external_plugin_render_view_id_;
+ }
+
+ RenderViewHostImpl* render_view_host =
+ RenderViewHostImpl::FromID(process_id_, render_id);
+
+ return pepper_socket_utils::CanUseSocketAPIs(external_plugin,
+ private_api,
+ params,
+ render_view_host);
+}
+
+void PepperMessageFilter::GetAndSendNetworkList() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE, base::Bind(&PepperMessageFilter::DoGetNetworkList, this));
+}
+
+void PepperMessageFilter::DoGetNetworkList() {
+ scoped_ptr<net::NetworkInterfaceList> list(new net::NetworkInterfaceList());
+ net::GetNetworkList(list.get());
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PepperMessageFilter::SendNetworkList,
+ this, base::Passed(&list)));
+}
+
+void PepperMessageFilter::SendNetworkList(
+ scoped_ptr<net::NetworkInterfaceList> list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ scoped_ptr< ::ppapi::NetworkList> list_copy(
+ new ::ppapi::NetworkList(list->size()));
+ for (size_t i = 0; i < list->size(); ++i) {
+ const net::NetworkInterface& network = list->at(i);
+ ::ppapi::NetworkInfo& network_copy = list_copy->at(i);
+ network_copy.name = network.name;
+
+ network_copy.addresses.resize(1, NetAddressPrivateImpl::kInvalidNetAddress);
+ bool result = NetAddressPrivateImpl::IPEndPointToNetAddress(
+ network.address, 0, &(network_copy.addresses[0]));
+ DCHECK(result);
+
+ // TODO(sergeyu): Currently net::NetworkInterfaceList provides
+ // only name and one IP address. Add all other fields and copy
+ // them here.
+ network_copy.type = PP_NETWORKLIST_UNKNOWN;
+ network_copy.state = PP_NETWORKLIST_UP;
+ network_copy.display_name = network.name;
+ network_copy.mtu = 0;
+ }
+ for (NetworkMonitorIdSet::iterator it = network_monitor_ids_.begin();
+ it != network_monitor_ids_.end(); ++it) {
+ Send(new PpapiMsg_PPBNetworkMonitor_NetworkList(
+ ppapi::API_ID_PPB_NETWORKMANAGER_PRIVATE, *it, *list_copy));
+ }
+}
+
+void PepperMessageFilter::CreateTCPSocket(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ bool private_api,
+ uint32* socket_id) {
+ *socket_id = GenerateSocketID();
+ if (*socket_id == kInvalidSocketID)
+ return;
+
+ tcp_sockets_[*socket_id] = linked_ptr<PepperTCPSocket>(
+ new PepperTCPSocket(this, routing_id, plugin_dispatcher_id, *socket_id,
+ private_api));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_message_filter.h b/chromium/content/browser/renderer_host/pepper/pepper_message_filter.h
new file mode 100644
index 00000000000..11c8fbd4902
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_message_filter.h
@@ -0,0 +1,231 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_MESSAGE_FILTER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/process_type.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/ssl_config_service.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/ppb_tcp_socket.h"
+#include "ppapi/c/private/ppb_flash.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+
+struct PP_NetAddress_Private;
+
+namespace base {
+class ListValue;
+}
+
+namespace net {
+class CertVerifier;
+class HostResolver;
+}
+
+namespace ppapi {
+class PPB_X509Certificate_Fields;
+class SocketOptionData;
+}
+
+namespace content {
+class BrowserContext;
+class PepperTCPSocket;
+class ResourceContext;
+
+// This class is used in two contexts, both supporting PPAPI plugins. The first
+// is on the renderer->browser channel, to handle requests from in-process
+// PPAPI plugins and any requests that the PPAPI implementation code in the
+// renderer needs to make. The second is on the plugin->browser channel to
+// handle requests that out-of-process plugins send directly to the browser.
+class PepperMessageFilter
+ : public BrowserMessageFilter,
+ public net::NetworkChangeNotifier::IPAddressObserver {
+ public:
+ // Constructor when used in the context of a render process.
+ PepperMessageFilter(int process_id,
+ BrowserContext* browser_context);
+
+ // Constructor when used in the context of a PPAPI process..
+ PepperMessageFilter(const ppapi::PpapiPermissions& permissions,
+ net::HostResolver* host_resolver);
+
+ // Constructor when used in the context of an external plugin, i.e. created by
+ // the embedder using BrowserPpapiHost::CreateExternalPluginProcess.
+ PepperMessageFilter(const ppapi::PpapiPermissions& permissions,
+ net::HostResolver* host_resolver,
+ int process_id,
+ int render_view_id);
+
+ // BrowserMessageFilter methods.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // net::NetworkChangeNotifier::IPAddressObserver interface.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // Returns the host resolver (it may come from the resource context or the
+ // host_resolver_ member).
+ net::HostResolver* GetHostResolver();
+
+ net::CertVerifier* GetCertVerifier();
+ net::TransportSecurityState* GetTransportSecurityState();
+
+ // Adds already accepted socket to the internal TCP sockets table. Takes
+ // ownership over |socket|. In the case of failure (full socket table)
+ // returns 0 and deletes |socket|. Otherwise, returns generated ID for
+ // |socket|.
+ uint32 AddAcceptedTCPSocket(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ net::StreamSocket* socket);
+
+ const net::SSLConfig& ssl_config() { return ssl_config_; }
+
+ protected:
+ virtual ~PepperMessageFilter();
+
+ private:
+ struct OnConnectTcpBoundInfo {
+ int routing_id;
+ int request_id;
+ };
+
+ // Containers for sockets keyed by socked_id.
+ typedef std::map<uint32, linked_ptr<PepperTCPSocket> > TCPSocketMap;
+
+ // Set of disptachers ID's that have subscribed for NetworkMonitor
+ // notifications.
+ typedef std::set<uint32> NetworkMonitorIdSet;
+
+ void OnGetLocalTimeZoneOffset(base::Time t, double* result);
+
+ void OnTCPCreate(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32* socket_id);
+ void OnTCPCreatePrivate(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32* socket_id);
+ void OnTCPConnect(int32 routing_id,
+ uint32 socket_id,
+ const std::string& host,
+ uint16_t port);
+ void OnTCPConnectWithNetAddress(int32 routing_id,
+ uint32 socket_id,
+ const PP_NetAddress_Private& net_addr);
+ void OnTCPSSLHandshake(
+ uint32 socket_id,
+ const std::string& server_name,
+ uint16_t server_port,
+ const std::vector<std::vector<char> >& trusted_certs,
+ const std::vector<std::vector<char> >& untrusted_certs);
+ void OnTCPRead(uint32 socket_id, int32_t bytes_to_read);
+ void OnTCPWrite(uint32 socket_id, const std::string& data);
+ void OnTCPDisconnect(uint32 socket_id);
+ void OnTCPSetOption(uint32 socket_id,
+ PP_TCPSocket_Option name,
+ const ppapi::SocketOptionData& value);
+
+ void OnNetworkMonitorStart(uint32 plugin_dispatcher_id);
+ void OnNetworkMonitorStop(uint32 plugin_dispatcher_id);
+
+ void DoTCPConnect(int32 routing_id,
+ uint32 socket_id,
+ const std::string& host,
+ uint16_t port,
+ bool allowed);
+ void DoTCPConnectWithNetAddress(int32 routing_id,
+ uint32 socket_id,
+ const PP_NetAddress_Private& net_addr,
+ bool allowed);
+ void OnX509CertificateParseDER(const std::vector<char>& der,
+ bool* succeeded,
+ ppapi::PPB_X509Certificate_Fields* result);
+ void OnUpdateActivity();
+
+ uint32 GenerateSocketID();
+
+ // Return true if render with given ID can use socket APIs.
+ bool CanUseSocketAPIs(int32 render_id,
+ const content::SocketPermissionRequest& params,
+ bool private_api);
+
+ void GetAndSendNetworkList();
+ void DoGetNetworkList();
+ void SendNetworkList(scoped_ptr<net::NetworkInterfaceList> list);
+ void CreateTCPSocket(int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ bool private_api,
+ uint32* socket_id);
+ enum PluginType {
+ PLUGIN_TYPE_IN_PROCESS,
+ PLUGIN_TYPE_OUT_OF_PROCESS,
+ // External plugin means it was created through
+ // BrowserPpapiHost::CreateExternalPluginProcess.
+ PLUGIN_TYPE_EXTERNAL_PLUGIN,
+ };
+
+ PluginType plugin_type_;
+
+ // When attached to an out-of-process plugin (be it native or NaCl) this
+ // will have the Pepper permissions for the plugin. When attached to the
+ // renderer channel, this will have no permissions listed (since there may
+ // be many plugins sharing this channel).
+ ppapi::PpapiPermissions permissions_;
+
+ // Render process ID.
+ int process_id_;
+
+ // External plugin RenderView id to determine private API access. Normally, we
+ // handle messages coming from multiple RenderViews, but external plugins
+ // always creates a new PepperMessageFilter for each RenderView.
+ int external_plugin_render_view_id_;
+
+ // When non-NULL, this should be used instead of the host_resolver_.
+ ResourceContext* const resource_context_;
+
+ // When non-NULL, this should be used instead of the resource_context_. Use
+ // GetHostResolver instead of accessing directly.
+ net::HostResolver* host_resolver_;
+
+ // The default SSL configuration settings are used, as opposed to Chrome's SSL
+ // settings.
+ net::SSLConfig ssl_config_;
+ // This is lazily created. Users should use GetCertVerifier to retrieve it.
+ scoped_ptr<net::CertVerifier> cert_verifier_;
+ // This is lazily created. Users should use GetTransportSecurityState to
+ // retrieve it.
+ scoped_ptr<net::TransportSecurityState> transport_security_state_;
+
+ uint32 next_socket_id_;
+
+ TCPSocketMap tcp_sockets_;
+
+ NetworkMonitorIdSet network_monitor_ids_;
+
+ base::FilePath browser_path_;
+ bool incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc
new file mode 100644
index 00000000000..6042badfc92
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 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 "content/browser/renderer_host/pepper/pepper_network_proxy_host.h"
+
+#include "base/bind.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/socket_permission_request.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_info.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+
+namespace content {
+
+PepperNetworkProxyHost::PepperNetworkProxyHost(BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ proxy_service_(NULL),
+ is_allowed_(false),
+ waiting_for_ui_thread_data_(true),
+ weak_factory_(this) {
+ int render_process_id(0), render_view_id(0);
+ host->GetRenderViewIDsForInstance(instance,
+ &render_process_id,
+ &render_view_id);
+ BrowserThread::PostTaskAndReplyWithResult(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&GetUIThreadDataOnUIThread,
+ render_process_id,
+ render_view_id,
+ host->external_plugin()),
+ base::Bind(&PepperNetworkProxyHost::DidGetUIThreadData,
+ weak_factory_.GetWeakPtr()));
+}
+
+PepperNetworkProxyHost::~PepperNetworkProxyHost() {
+ while (!pending_requests_.empty()) {
+ // If the proxy_service_ is NULL, we shouldn't have any outstanding
+ // requests.
+ DCHECK(proxy_service_);
+ net::ProxyService::PacRequest* request = pending_requests_.front();
+ proxy_service_->CancelPacRequest(request);
+ pending_requests_.pop();
+ }
+}
+
+PepperNetworkProxyHost::UIThreadData::UIThreadData()
+ : is_allowed(false) {
+}
+
+PepperNetworkProxyHost::UIThreadData::~UIThreadData() {
+}
+
+// static
+PepperNetworkProxyHost::UIThreadData
+PepperNetworkProxyHost::GetUIThreadDataOnUIThread(int render_process_id,
+ int render_view_id,
+ bool is_external_plugin) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ PepperNetworkProxyHost::UIThreadData result;
+ RenderProcessHost* render_process_host =
+ RenderProcessHost::FromID(render_process_id);
+ if (render_process_host && render_process_host->GetBrowserContext()) {
+ result.context_getter = render_process_host->GetBrowserContext()->
+ GetRequestContextForRenderProcess(render_process_id);
+ }
+
+ RenderViewHost* render_view_host =
+ RenderViewHost::FromID(render_process_id, render_view_id);
+ if (render_view_host) {
+ SocketPermissionRequest request(
+ content::SocketPermissionRequest::RESOLVE_PROXY, std::string(), 0);
+ result.is_allowed = pepper_socket_utils::CanUseSocketAPIs(
+ is_external_plugin,
+ false /* is_private_api */,
+ request,
+ render_view_host);
+ }
+ return result;
+}
+
+void PepperNetworkProxyHost::DidGetUIThreadData(
+ const UIThreadData& ui_thread_data) {
+ is_allowed_ = ui_thread_data.is_allowed;
+ if (ui_thread_data.context_getter.get() &&
+ ui_thread_data.context_getter->GetURLRequestContext()) {
+ proxy_service_ =
+ ui_thread_data.context_getter->GetURLRequestContext()->proxy_service();
+ }
+ waiting_for_ui_thread_data_ = false;
+ if (!proxy_service_) {
+ DLOG_IF(WARNING, proxy_service_)
+ << "Failed to find a ProxyService for Pepper plugin.";
+ }
+ TryToSendUnsentRequests();
+}
+
+int32_t PepperNetworkProxyHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperNetworkProxyHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_NetworkProxy_GetProxyForURL, OnMsgGetProxyForURL)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperNetworkProxyHost::OnMsgGetProxyForURL(
+ ppapi::host::HostMessageContext* context,
+ const std::string& url) {
+ GURL gurl(url);
+ if (gurl.is_valid()) {
+ UnsentRequest request = { gurl, context->MakeReplyMessageContext() };
+ unsent_requests_.push(request);
+ TryToSendUnsentRequests();
+ } else {
+ SendFailureReply(PP_ERROR_BADARGUMENT,
+ context->MakeReplyMessageContext());
+ }
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperNetworkProxyHost::TryToSendUnsentRequests() {
+ if (waiting_for_ui_thread_data_)
+ return;
+
+ while (!unsent_requests_.empty()) {
+ const UnsentRequest& request = unsent_requests_.front();
+ if (!proxy_service_) {
+ SendFailureReply(PP_ERROR_FAILED, request.reply_context);
+ } else if (!is_allowed_) {
+ SendFailureReply(PP_ERROR_NOACCESS, request.reply_context);
+ } else {
+ // Everything looks valid, so try to resolve the proxy.
+ net::ProxyInfo* proxy_info = new net::ProxyInfo;
+ net::ProxyService::PacRequest* pending_request = NULL;
+ base::Callback<void (int)> callback =
+ base::Bind(&PepperNetworkProxyHost::OnResolveProxyCompleted,
+ weak_factory_.GetWeakPtr(),
+ request.reply_context,
+ base::Owned(proxy_info));
+ int result = proxy_service_->ResolveProxy(request.url,
+ proxy_info,
+ callback,
+ &pending_request,
+ net::BoundNetLog());
+ pending_requests_.push(pending_request);
+ // If it was handled synchronously, we must run the callback now;
+ // proxy_service_ won't run it for us in this case.
+ if (result != net::ERR_IO_PENDING)
+ callback.Run(result);
+ }
+ unsent_requests_.pop();
+ }
+}
+
+void PepperNetworkProxyHost::OnResolveProxyCompleted(
+ ppapi::host::ReplyMessageContext context,
+ net::ProxyInfo* proxy_info,
+ int result) {
+ pending_requests_.pop();
+
+ if (result != net::OK) {
+ // Currently, the only proxy-specific error we could get is
+ // MANDATORY_PROXY_CONFIGURATION_FAILED. There's really no action a plugin
+ // can take in this case, so there's no need to distinguish it from other
+ // failures.
+ context.params.set_result(PP_ERROR_FAILED);
+ }
+ host()->SendReply(context,
+ PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(
+ proxy_info->ToPacString()));
+}
+
+void PepperNetworkProxyHost::SendFailureReply(
+ int32_t error,
+ ppapi::host::ReplyMessageContext context) {
+ context.params.set_result(error);
+ host()->SendReply(context,
+ PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(
+ std::string()));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.h b/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
new file mode 100644
index 00000000000..38e36cb13e6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
@@ -0,0 +1,104 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_NETWORK_PROXY_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_NETWORK_PROXY_HOST_H_
+
+#include <queue>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "net/proxy/proxy_service.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_host.h"
+
+namespace net {
+class ProxyInfo;
+class URLRequestContextGetter;
+}
+
+namespace ppapi {
+namespace host {
+struct ReplyMessageContext;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+
+// The host for PPB_NetworkProxy. This class lives on the IO thread.
+class CONTENT_EXPORT PepperNetworkProxyHost : public ppapi::host::ResourceHost {
+ public:
+ PepperNetworkProxyHost(BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ PP_Resource resource);
+
+ virtual ~PepperNetworkProxyHost();
+
+ private:
+ // We retrieve the appropriate URLRequestContextGetter and whether this API
+ // is allowed for the instance on the UI thread and pass those to
+ // DidGetUIThreadData, which sets allowed_ and proxy_service_.
+ struct UIThreadData {
+ UIThreadData();
+ ~UIThreadData();
+ bool is_allowed;
+ scoped_refptr<net::URLRequestContextGetter> context_getter;
+ };
+ static UIThreadData GetUIThreadDataOnUIThread(int render_process_id,
+ int render_view_id,
+ bool is_external_plugin);
+ void DidGetUIThreadData(const UIThreadData&);
+
+ // ResourceHost implementation.
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ int32_t OnMsgGetProxyForURL(ppapi::host::HostMessageContext* context,
+ const std::string& url);
+
+ // If we have a valid proxy_service_, send all messages in unsent_requests_.
+ void TryToSendUnsentRequests();
+
+ void OnResolveProxyCompleted(ppapi::host::ReplyMessageContext context,
+ net::ProxyInfo* proxy_info,
+ int result);
+ void SendFailureReply(int32_t error,
+ ppapi::host::ReplyMessageContext context);
+
+ // The following two members are invalid until we get some information from
+ // the UI thread. However, these are only ever set or accessed on the IO
+ // thread.
+ net::ProxyService* proxy_service_;
+ bool is_allowed_;
+
+ // True initially, but set to false once the values for proxy_service_ and
+ // is_allowed_ have been set.
+ bool waiting_for_ui_thread_data_;
+
+ // We have to get the URLRequestContextGetter from the UI thread before we
+ // can retrieve proxy_service_. If we receive any calls for GetProxyForURL
+ // before proxy_service_ is available, we save them in unsent_requests_.
+ struct UnsentRequest {
+ GURL url;
+ ppapi::host::ReplyMessageContext reply_context;
+ };
+ std::queue<UnsentRequest> unsent_requests_;
+
+ // Requests awaiting a response from ProxyService. We need to store these so
+ // that we can cancel them if we get destroyed.
+ std::queue<net::ProxyService::PacRequest*> pending_requests_;
+
+ base::WeakPtrFactory<PepperNetworkProxyHost> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperNetworkProxyHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_NETWORK_PROXY_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.cc b/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.cc
new file mode 100644
index 00000000000..84aaddfd2f7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.cc
@@ -0,0 +1,101 @@
+// 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 "content/browser/renderer_host/pepper/pepper_print_settings_manager.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "ppapi/c/pp_errors.h"
+#include "printing/printing_context.h"
+#include "printing/units.h"
+
+namespace content {
+
+namespace {
+
+#if defined(ENABLE_FULL_PRINTING)
+// Print units conversion functions.
+int32_t DeviceUnitsInPoints(int32_t device_units,
+ int32_t device_units_per_inch) {
+ return printing::ConvertUnit(device_units, device_units_per_inch,
+ printing::kPointsPerInch);
+}
+
+PP_Size PrintSizeToPPPrintSize(const gfx::Size& print_size,
+ int32_t device_units_per_inch) {
+ PP_Size result;
+ result.width = DeviceUnitsInPoints(print_size.width(), device_units_per_inch);
+ result.height = DeviceUnitsInPoints(print_size.height(),
+ device_units_per_inch);
+ return result;
+}
+
+PP_Rect PrintAreaToPPPrintArea(const gfx::Rect& print_area,
+ int32_t device_units_per_inch) {
+ PP_Rect result;
+ result.point.x = DeviceUnitsInPoints(print_area.origin().x(),
+ device_units_per_inch);
+ result.point.y = DeviceUnitsInPoints(print_area.origin().y(),
+ device_units_per_inch);
+ result.size = PrintSizeToPPPrintSize(print_area.size(),
+ device_units_per_inch);
+ return result;
+}
+
+PepperPrintSettingsManager::Result ComputeDefaultPrintSettings() {
+ // This function should run on the UI thread because |PrintingContext| methods
+ // call into platform APIs.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<printing::PrintingContext> context(
+ printing::PrintingContext::Create(std::string()));
+ if (!context.get() ||
+ context->UseDefaultSettings() != printing::PrintingContext::OK) {
+ return PepperPrintSettingsManager::Result(PP_PrintSettings_Dev(),
+ PP_ERROR_FAILED);
+ }
+ const printing::PrintSettings& print_settings = context->settings();
+ const printing::PageSetup& page_setup =
+ print_settings.page_setup_device_units();
+ int device_units_per_inch = print_settings.device_units_per_inch();
+ if (device_units_per_inch <= 0) {
+ return PepperPrintSettingsManager::Result(PP_PrintSettings_Dev(),
+ PP_ERROR_FAILED);
+ }
+ PP_PrintSettings_Dev settings;
+ settings.printable_area = PrintAreaToPPPrintArea(
+ page_setup.printable_area(), device_units_per_inch);
+ settings.content_area = PrintAreaToPPPrintArea(
+ page_setup.content_area(), device_units_per_inch);
+ settings.paper_size = PrintSizeToPPPrintSize(
+ page_setup.physical_size(), device_units_per_inch);
+ settings.dpi = print_settings.dpi();
+
+ // The remainder of the attributes are hard-coded to the defaults as set
+ // elsewhere.
+ settings.orientation = PP_PRINTORIENTATION_NORMAL;
+ settings.grayscale = PP_FALSE;
+ settings.print_scaling_option = PP_PRINTSCALINGOPTION_SOURCE_SIZE;
+
+ // TODO(raymes): Should be computed in the same way as
+ // |PluginInstance::GetPreferredPrintOutputFormat|.
+ // |PP_PRINTOUTPUTFORMAT_PDF| is currently the only supported format though,
+ // so just make it the default.
+ settings.format = PP_PRINTOUTPUTFORMAT_PDF;
+ return PepperPrintSettingsManager::Result(settings, PP_OK);
+}
+#else
+PepperPrintSettingsManager::Result ComputeDefaultPrintSettings() {
+ return PepperPrintSettingsManager::Result(PP_PrintSettings_Dev(),
+ PP_ERROR_NOTSUPPORTED);
+}
+#endif
+
+} // namespace
+
+void PepperPrintSettingsManagerImpl::GetDefaultPrintSettings(
+ PepperPrintSettingsManager::Callback callback) {
+ BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE,
+ base::Bind(ComputeDefaultPrintSettings), callback);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.h b/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.h
new file mode 100644
index 00000000000..48a8e9f100e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_print_settings_manager.h
@@ -0,0 +1,47 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINT_SETTINGS_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINT_SETTINGS_MANAGER_H_
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "content/common/content_export.h"
+#include "ppapi/c/dev/pp_print_settings_dev.h"
+
+namespace content {
+
+// A class for getting the default print settings for the default printer.
+class CONTENT_EXPORT PepperPrintSettingsManager {
+ public:
+ typedef std::pair<PP_PrintSettings_Dev, int32_t> Result;
+ typedef base::Callback<void(Result)> Callback;
+
+ // The default print settings are obtained asynchronously and |callback|
+ // is called with the the print settings when they are available. |callback|
+ // will always be called on the same thread from which
+ // |GetDefaultPrintSettings| was issued.
+ virtual void GetDefaultPrintSettings(Callback callback) = 0;
+
+ virtual ~PepperPrintSettingsManager() {}
+};
+
+// Real implementation for getting the default print settings.
+class CONTENT_EXPORT PepperPrintSettingsManagerImpl
+ : public PepperPrintSettingsManager {
+ public:
+ PepperPrintSettingsManagerImpl() {}
+ virtual ~PepperPrintSettingsManagerImpl() {}
+
+ // PepperPrintSettingsManager implementation.
+ virtual void GetDefaultPrintSettings(
+ PepperPrintSettingsManager::Callback callback) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PepperPrintSettingsManagerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINT_SETTINGS_MANAGER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_printing_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_printing_host.cc
new file mode 100644
index 00000000000..c11939c34a8
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_printing_host.cc
@@ -0,0 +1,58 @@
+// 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 "content/browser/renderer_host/pepper/pepper_printing_host.h"
+
+#include "ppapi/c/dev/pp_print_settings_dev.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+
+namespace content {
+
+PepperPrintingHost::PepperPrintingHost(
+ ppapi::host::PpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ scoped_ptr<PepperPrintSettingsManager> print_settings_manager)
+ : ResourceHost(host, instance, resource),
+ print_settings_manager_(print_settings_manager.Pass()),
+ weak_factory_(this) {
+}
+
+PepperPrintingHost::~PepperPrintingHost() {
+}
+
+int32_t PepperPrintingHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperPrintingHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_Printing_GetDefaultPrintSettings,
+ OnGetDefaultPrintSettings)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperPrintingHost::OnGetDefaultPrintSettings(
+ ppapi::host::HostMessageContext* context) {
+ print_settings_manager_->GetDefaultPrintSettings(
+ base::Bind(&PepperPrintingHost::PrintSettingsCallback,
+ weak_factory_.GetWeakPtr(),
+ context->MakeReplyMessageContext()));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperPrintingHost::PrintSettingsCallback(
+ ppapi::host::ReplyMessageContext reply_context,
+ PepperPrintSettingsManager::Result result) {
+ reply_context.params.set_result(result.second);
+ host()->SendReply(reply_context,
+ PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply(result.first));
+}
+
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_printing_host.h b/chromium/content/browser/renderer_host/pepper/pepper_printing_host.h
new file mode 100644
index 00000000000..4c302ee7f5a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_printing_host.h
@@ -0,0 +1,49 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINTING_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINTING_HOST_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/pepper/pepper_print_settings_manager.h"
+#include "content/common/content_export.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_host.h"
+
+namespace content {
+
+class CONTENT_EXPORT PepperPrintingHost : public ppapi::host::ResourceHost {
+ public:
+ PepperPrintingHost(
+ ppapi::host::PpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource,
+ scoped_ptr<PepperPrintSettingsManager> print_settings_manager);
+ virtual ~PepperPrintingHost();
+
+ // ppapi::host::ResourceHost implementation.
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ private:
+ int32_t OnGetDefaultPrintSettings(
+ ppapi::host::HostMessageContext* context);
+
+ void PrintSettingsCallback(
+ ppapi::host::ReplyMessageContext reply_context,
+ PepperPrintSettingsManager::Result result);
+
+ scoped_ptr<PepperPrintSettingsManager> print_settings_manager_;
+
+ base::WeakPtrFactory<PepperPrintingHost> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperPrintingHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PRINTING_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_printing_host_unittest.cc b/chromium/content/browser/renderer_host/pepper/pepper_printing_host_unittest.cc
new file mode 100644
index 00000000000..a46db10b384
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_printing_host_unittest.cc
@@ -0,0 +1,128 @@
+// 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 "content/browser/renderer_host/pepper/browser_ppapi_host_test.h"
+#include "content/browser/renderer_host/pepper/pepper_print_settings_manager.h"
+#include "content/browser/renderer_host/pepper/pepper_printing_host.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/resource_message_params.h"
+#include "ppapi/proxy/resource_message_test_sink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// Mock implementation of |PepperPrintSettingsManager| for test purposes.
+class MockPepperPrintSettingsManager
+ : public PepperPrintSettingsManager {
+ public:
+ MockPepperPrintSettingsManager(const PP_PrintSettings_Dev& settings);
+ virtual ~MockPepperPrintSettingsManager() {}
+
+ // PepperPrintSettingsManager implementation.
+ virtual void GetDefaultPrintSettings(
+ PepperPrintSettingsManager::Callback callback) OVERRIDE;
+ private:
+ PP_PrintSettings_Dev settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPepperPrintSettingsManager);
+};
+
+MockPepperPrintSettingsManager::MockPepperPrintSettingsManager(
+ const PP_PrintSettings_Dev& settings)
+ : settings_(settings) {
+}
+
+void MockPepperPrintSettingsManager::GetDefaultPrintSettings(
+ PepperPrintSettingsManager::Callback callback) {
+ callback.Run(PepperPrintSettingsManager::Result(settings_, PP_OK));
+}
+
+class PepperPrintingHostTest
+ : public testing::Test,
+ public BrowserPpapiHostTest {
+ public:
+ PepperPrintingHostTest() {
+ }
+
+ virtual ~PepperPrintingHostTest() {
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(PepperPrintingHostTest);
+};
+
+bool PP_SizeEqual(const PP_Size& lhs, const PP_Size& rhs) {
+ return lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+bool PP_RectEqual(const PP_Rect& lhs, const PP_Rect& rhs) {
+ return lhs.point.x == rhs.point.x &&
+ lhs.point.y == rhs.point.y &&
+ PP_SizeEqual(lhs.size, rhs.size);
+}
+
+} // namespace
+
+TEST_F(PepperPrintingHostTest, GetDefaultPrintSettings) {
+ PP_Instance pp_instance = 12345;
+ PP_Resource pp_resource = 67890;
+ PP_PrintSettings_Dev expected_settings = {
+ { { 0, 0 }, { 500, 515 } },
+ { { 25, 35 }, { 300, 720 } },
+ { 600, 700 },
+ 200,
+ PP_PRINTORIENTATION_NORMAL,
+ PP_PRINTSCALINGOPTION_NONE,
+ PP_FALSE,
+ PP_PRINTOUTPUTFORMAT_PDF
+ };
+
+ // Construct the resource host.
+ scoped_ptr<PepperPrintSettingsManager> manager(
+ new MockPepperPrintSettingsManager(expected_settings));
+ PepperPrintingHost printing(GetBrowserPpapiHost()->GetPpapiHost(),
+ pp_instance, pp_resource, manager.Pass());
+
+ // Simulate a message being received.
+ ppapi::proxy::ResourceMessageCallParams call_params(pp_resource, 1);
+ ppapi::host::HostMessageContext context(call_params);
+ int32 result = printing.OnResourceMessageReceived(
+ PpapiHostMsg_Printing_GetDefaultPrintSettings(), &context);
+ EXPECT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ // This should have sent the Pepper reply to our test sink.
+ ppapi::proxy::ResourceMessageReplyParams reply_params;
+ IPC::Message reply_msg;
+ ASSERT_TRUE(sink().GetFirstResourceReplyMatching(
+ PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply::ID, &reply_params,
+ &reply_msg));
+
+ // Validation of reply.
+ EXPECT_EQ(call_params.sequence(), reply_params.sequence());
+ EXPECT_EQ(PP_OK, reply_params.result());
+ PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply::Schema::Param
+ reply_msg_param;
+ ASSERT_TRUE(PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply::Read(
+ &reply_msg, &reply_msg_param));
+ PP_PrintSettings_Dev actual_settings = reply_msg_param.a;
+
+ EXPECT_TRUE(PP_RectEqual(expected_settings.printable_area,
+ actual_settings.printable_area));
+ EXPECT_TRUE(PP_RectEqual(expected_settings.content_area,
+ actual_settings.content_area));
+ EXPECT_TRUE(PP_SizeEqual(expected_settings.paper_size,
+ actual_settings.paper_size));
+ EXPECT_EQ(expected_settings.dpi, actual_settings.dpi);
+ EXPECT_EQ(expected_settings.orientation, actual_settings.orientation);
+ EXPECT_EQ(expected_settings.print_scaling_option,
+ actual_settings.print_scaling_option);
+ EXPECT_EQ(expected_settings.grayscale, actual_settings.grayscale);
+ EXPECT_EQ(expected_settings.format, actual_settings.format);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.cc b/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.cc
new file mode 100644
index 00000000000..a5b95ceb223
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.cc
@@ -0,0 +1,177 @@
+// 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 "content/browser/renderer_host/pepper/pepper_renderer_connection.h"
+
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/common/pepper_renderer_instance_data.h"
+#include "content/common/view_messages.h"
+#include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "ipc/ipc_message_macros.h"
+#include "ppapi/host/resource_host.h"
+#include "ppapi/proxy/ppapi_message_utils.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/resource_message_params.h"
+
+namespace content {
+
+PepperRendererConnection::PepperRendererConnection(int render_process_id)
+ : render_process_id_(render_process_id) {
+ // Only give the renderer permission for stable APIs.
+ in_process_host_.reset(new BrowserPpapiHostImpl(this,
+ ppapi::PpapiPermissions(),
+ "",
+ base::FilePath(),
+ base::FilePath(),
+ false,
+ NULL));
+}
+
+PepperRendererConnection::~PepperRendererConnection() {
+}
+
+BrowserPpapiHostImpl* PepperRendererConnection::GetHostForChildProcess(
+ int child_process_id) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Find the plugin which this message refers to. Check NaCl plugins first.
+ BrowserPpapiHostImpl* host = static_cast<BrowserPpapiHostImpl*>(
+ GetContentClient()->browser()->GetExternalBrowserPpapiHost(
+ child_process_id));
+
+ if (!host) {
+ // Check trusted pepper plugins.
+ for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->process() &&
+ iter->process()->GetData().id == child_process_id) {
+ // Found the plugin.
+ host = iter->host_impl();
+ break;
+ }
+ }
+ }
+
+ // If the message is being sent from an in-process plugin, we own the
+ // BrowserPpapiHost.
+ if (!host && child_process_id == 0) {
+ host = in_process_host_.get();
+ }
+
+ return host;
+}
+
+bool PepperRendererConnection::OnMessageReceived(const IPC::Message& msg,
+ bool* message_was_ok) {
+ if (in_process_host_->GetPpapiHost()->OnMessageReceived(msg))
+ return true;
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(PepperRendererConnection, msg, *message_was_ok)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_CreateResourceHostFromHost,
+ OnMsgCreateResourceHostFromHost)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_FileRef_GetInfoForRenderer,
+ OnMsgFileRefGetInfoForRenderer)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidCreateInProcessInstance,
+ OnMsgDidCreateInProcessInstance)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidDeleteInProcessInstance,
+ OnMsgDidDeleteInProcessInstance)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void PepperRendererConnection::OnMsgCreateResourceHostFromHost(
+ int routing_id,
+ int child_process_id,
+ const ppapi::proxy::ResourceMessageCallParams& params,
+ PP_Instance instance,
+ const IPC::Message& nested_msg) {
+ BrowserPpapiHostImpl* host = GetHostForChildProcess(child_process_id);
+
+ int pending_resource_host_id;
+ if (!host) {
+ DLOG(ERROR) << "Invalid plugin process ID.";
+ pending_resource_host_id = 0;
+ } else {
+ // FileRef_CreateExternal is only permitted from the renderer. Because of
+ // this, we handle this message here and not in
+ // content_browser_pepper_host_factory.cc.
+ scoped_ptr<ppapi::host::ResourceHost> resource_host;
+ if (host->IsValidInstance(instance)) {
+ if (nested_msg.type() == PpapiHostMsg_FileRef_CreateExternal::ID) {
+ base::FilePath external_path;
+ if (ppapi::UnpackMessage<PpapiHostMsg_FileRef_CreateExternal>(
+ nested_msg, &external_path)) {
+ resource_host.reset(new PepperFileRefHost(
+ host, instance, params.pp_resource(), external_path));
+ }
+ }
+ }
+
+ if (!resource_host.get()) {
+ resource_host = host->GetPpapiHost()->CreateResourceHost(params,
+ instance,
+ nested_msg);
+ }
+ pending_resource_host_id =
+ host->GetPpapiHost()->AddPendingResourceHost(resource_host.Pass());
+ }
+
+ Send(new PpapiHostMsg_CreateResourceHostFromHostReply(
+ routing_id, params.sequence(), pending_resource_host_id));
+}
+
+void PepperRendererConnection::OnMsgFileRefGetInfoForRenderer(
+ int routing_id,
+ int child_process_id,
+ int32_t sequence,
+ const std::vector<PP_Resource>& resources) {
+ std::vector<PP_Resource> out_resources;
+ std::vector<PP_FileSystemType> fs_types;
+ std::vector<std::string> file_system_url_specs;
+ std::vector<base::FilePath> external_paths;
+
+ BrowserPpapiHostImpl* host = GetHostForChildProcess(child_process_id);
+ if (host) {
+ for (size_t i = 0; i < resources.size(); ++i) {
+ ppapi::host::ResourceHost* resource_host =
+ host->GetPpapiHost()->GetResourceHost(resources[i]);
+ if (resource_host && resource_host->IsFileRefHost()) {
+ PepperFileRefHost* file_ref_host =
+ static_cast<PepperFileRefHost*>(resource_host);
+ out_resources.push_back(resources[i]);
+ fs_types.push_back(file_ref_host->GetFileSystemType());
+ file_system_url_specs.push_back(file_ref_host->GetFileSystemURLSpec());
+ external_paths.push_back(file_ref_host->GetExternalPath());
+ }
+ }
+ }
+ Send(new PpapiHostMsg_FileRef_GetInfoForRendererReply(
+ routing_id,
+ sequence,
+ out_resources,
+ fs_types,
+ file_system_url_specs,
+ external_paths));
+}
+
+void PepperRendererConnection::OnMsgDidCreateInProcessInstance(
+ PP_Instance instance,
+ const PepperRendererInstanceData& instance_data) {
+ PepperRendererInstanceData data = instance_data;
+ data.render_process_id = render_process_id_;
+ in_process_host_->AddInstance(instance, data);
+}
+
+void PepperRendererConnection::OnMsgDidDeleteInProcessInstance(
+ PP_Instance instance) {
+ in_process_host_->DeleteInstance(instance);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.h b/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.h
new file mode 100644
index 00000000000..43923465d0b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_renderer_connection.h
@@ -0,0 +1,79 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_RENDERER_CONNECTION_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_RENDERER_CONNECTION_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_resource.h"
+
+class GURL;
+
+namespace ppapi {
+namespace proxy {
+class ResourceMessageCallParams;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+struct PepperRendererInstanceData;
+
+// This class represents a connection from the browser to the renderer for
+// sending/receiving pepper ResourceHost related messages. When the browser
+// and renderer communicate about ResourceHosts, they should pass the plugin
+// process ID to identify which plugin they are talking about.
+class PepperRendererConnection : public BrowserMessageFilter {
+ public:
+ explicit PepperRendererConnection(int render_process_id);
+
+ // BrowserMessageFilter overrides.
+ virtual bool OnMessageReceived(const IPC::Message& msg,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~PepperRendererConnection();
+
+ // Returns the host for the child process for the given |child_process_id|.
+ // If |child_process_id| is 0, returns the host owned by this
+ // PepperRendererConnection, which serves as the host for in-process plugins.
+ BrowserPpapiHostImpl* GetHostForChildProcess(int child_process_id) const;
+
+ void OnMsgCreateResourceHostFromHost(
+ int routing_id,
+ int child_process_id,
+ const ppapi::proxy::ResourceMessageCallParams& params,
+ PP_Instance instance,
+ const IPC::Message& nested_msg);
+
+ void OnMsgFileRefGetInfoForRenderer(
+ int routing_id,
+ int child_process_id,
+ int32_t sequence_num,
+ const std::vector<PP_Resource>& resources);
+
+ void OnMsgDidCreateInProcessInstance(
+ PP_Instance instance,
+ const PepperRendererInstanceData& instance_data);
+ void OnMsgDidDeleteInProcessInstance(PP_Instance instance);
+
+ int render_process_id_;
+
+ // We have a single BrowserPpapiHost per-renderer for all in-process plugins
+ // running. This is just a work-around allowing new style resources to work
+ // with the browser when running in-process but it means that plugin-specific
+ // information (like the plugin name) won't be available.
+ scoped_ptr<BrowserPpapiHostImpl> in_process_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperRendererConnection);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_RENDERER_CONNECTION_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_security_helper.cc b/chromium/content/browser/renderer_host/pepper/pepper_security_helper.cc
new file mode 100644
index 00000000000..5402823f01e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_security_helper.cc
@@ -0,0 +1,54 @@
+// 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 "content/browser/renderer_host/pepper/pepper_security_helper.h"
+
+#include "base/logging.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "ppapi/c/ppb_file_io.h"
+
+namespace content {
+
+bool CanOpenWithPepperFlags(int pp_open_flags, int child_id,
+ const base::FilePath& file) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ bool pp_read = !!(pp_open_flags & PP_FILEOPENFLAG_READ);
+ bool pp_write = !!(pp_open_flags & PP_FILEOPENFLAG_WRITE);
+ bool pp_create = !!(pp_open_flags & PP_FILEOPENFLAG_CREATE);
+ bool pp_truncate = !!(pp_open_flags & PP_FILEOPENFLAG_TRUNCATE);
+ bool pp_exclusive = !!(pp_open_flags & PP_FILEOPENFLAG_EXCLUSIVE);
+ bool pp_append = !!(pp_open_flags & PP_FILEOPENFLAG_APPEND);
+
+ if (pp_read && !policy->CanReadFile(child_id, file))
+ return false;
+
+ if (pp_write && !policy->CanWriteFile(child_id, file))
+ return false;
+
+ if (pp_append) {
+ // Given ChildSecurityPolicyImpl's current definition of permissions,
+ // APPEND is never supported.
+ return false;
+ }
+
+ if (pp_truncate && !pp_write)
+ return false;
+
+ if (pp_create) {
+ if (pp_exclusive) {
+ return policy->CanCreateFile(child_id, file);
+ } else {
+ // Asks for too much, but this is the only grant that allows overwrite.
+ return policy->CanCreateWriteFile(child_id, file);
+ }
+ } else if (pp_truncate) {
+ return policy->CanCreateWriteFile(child_id, file);
+ }
+
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_security_helper.h b/chromium/content/browser/renderer_host/pepper/pepper_security_helper.h
new file mode 100644
index 00000000000..4a3cea5874a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_security_helper.h
@@ -0,0 +1,21 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SECURITY_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SECURITY_HELPER_H_
+
+#include "base/files/file_path.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Helper method that returns whether or not the child process is allowed to
+// open the specified |file| with the specified |pp_open_flags|.
+CONTENT_EXPORT bool CanOpenWithPepperFlags(int pp_open_flags,
+ int child_id,
+ const base::FilePath& file);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SECURITY_HELPER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.cc b/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.cc
new file mode 100644
index 00000000000..9dc585e3315
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.cc
@@ -0,0 +1,81 @@
+// 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 "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/common/content_client.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+
+namespace content {
+namespace pepper_socket_utils {
+
+SocketPermissionRequest CreateSocketPermissionRequest(
+ SocketPermissionRequest::OperationType type,
+ const PP_NetAddress_Private& net_addr) {
+ std::string host = ppapi::NetAddressPrivateImpl::DescribeNetAddress(net_addr,
+ false);
+ int port = 0;
+ std::vector<unsigned char> address;
+ ppapi::NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr,
+ &address,
+ &port);
+ return SocketPermissionRequest(type, host, port);
+}
+
+bool CanUseSocketAPIs(bool external_plugin,
+ bool private_api,
+ const SocketPermissionRequest& params,
+ int render_process_id,
+ int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderViewHost* render_view_host = RenderViewHost::FromID(render_process_id,
+ render_view_id);
+ return render_view_host && CanUseSocketAPIs(external_plugin,
+ private_api,
+ params,
+ render_view_host);
+}
+
+bool CanUseSocketAPIs(bool external_plugin,
+ bool private_api,
+ const SocketPermissionRequest& params,
+ RenderViewHost* render_view_host) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!external_plugin) {
+ // Always allow socket APIs for out-process plugins (other than external
+ // plugins instantiated by the embeeder through
+ // BrowserPpapiHost::CreateExternalPluginProcess).
+ return true;
+ }
+
+ if (!render_view_host)
+ return false;
+ SiteInstance* site_instance = render_view_host->GetSiteInstance();
+ if (!site_instance)
+ return false;
+ if (!GetContentClient()->browser()->AllowPepperSocketAPI(
+ site_instance->GetBrowserContext(),
+ site_instance->GetSiteURL(),
+ private_api,
+ params)) {
+ LOG(ERROR) << "Host " << site_instance->GetSiteURL().host()
+ << " cannot use socket API or destination is not allowed";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace pepper_socket_utils
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.h b/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.h
new file mode 100644
index 00000000000..7a0cef56a5c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_socket_utils.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SOCKET_UTILS_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SOCKET_UTILS_H_
+
+#include "content/public/common/socket_permission_request.h"
+
+struct PP_NetAddress_Private;
+
+namespace content {
+
+class RenderViewHost;
+
+namespace pepper_socket_utils {
+
+SocketPermissionRequest CreateSocketPermissionRequest(
+ SocketPermissionRequest::OperationType type,
+ const PP_NetAddress_Private& net_addr);
+
+bool CanUseSocketAPIs(bool external_plugin,
+ bool private_api,
+ const SocketPermissionRequest& params,
+ int render_process_id,
+ int render_view_id);
+
+// TODO (ygorshenin@): remove this method.
+bool CanUseSocketAPIs(bool external_plugin,
+ bool private_api,
+ const SocketPermissionRequest& params,
+ RenderViewHost* render_view_host);
+
+} // namespace pepper_socket_utils
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SOCKET_UTILS_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc b/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc
new file mode 100644
index 00000000000..48c8291cab9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc
@@ -0,0 +1,318 @@
+// 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 "content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/socket_permission_request.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/tcp_server_socket.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/error_conversion.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/api_id.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+
+using ppapi::NetAddressPrivateImpl;
+using ppapi::host::NetErrorToPepperError;
+
+namespace {
+
+size_t g_num_instances = 0;
+
+} // namespace
+
+namespace content {
+
+PepperTCPServerSocketMessageFilter::PepperTCPServerSocketMessageFilter(
+ BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api,
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter)
+ : state_(STATE_BEFORE_LISTENING),
+ pepper_message_filter_(pepper_message_filter),
+ external_plugin_(host->external_plugin()),
+ private_api_(private_api),
+ render_process_id_(0),
+ render_view_id_(0) {
+ ++g_num_instances;
+ DCHECK(host);
+ if (!host->GetRenderViewIDsForInstance(instance,
+ &render_process_id_,
+ &render_view_id_)) {
+ NOTREACHED();
+ }
+}
+
+PepperTCPServerSocketMessageFilter::~PepperTCPServerSocketMessageFilter() {
+ --g_num_instances;
+}
+
+// static
+size_t PepperTCPServerSocketMessageFilter::GetNumInstances() {
+ return g_num_instances;
+}
+
+scoped_refptr<base::TaskRunner>
+PepperTCPServerSocketMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ switch (message.type()) {
+ case PpapiHostMsg_TCPServerSocket_Listen::ID:
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
+ case PpapiHostMsg_TCPServerSocket_Accept::ID:
+ case PpapiHostMsg_TCPServerSocket_StopListening::ID:
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
+ }
+ return NULL;
+}
+
+int32_t PepperTCPServerSocketMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperTCPServerSocketMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_TCPServerSocket_Listen, OnMsgListen)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_TCPServerSocket_Accept, OnMsgAccept)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_TCPServerSocket_StopListening, OnMsgStopListening)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperTCPServerSocketMessageFilter::OnMsgListen(
+ const ppapi::host::HostMessageContext* context,
+ const PP_NetAddress_Private& addr,
+ int32_t backlog) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(context);
+
+ SocketPermissionRequest request =
+ pepper_socket_utils::CreateSocketPermissionRequest(
+ content::SocketPermissionRequest::TCP_LISTEN, addr);
+ if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
+ private_api_,
+ request,
+ render_process_id_,
+ render_view_id_)) {
+ return PP_ERROR_NOACCESS;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PepperTCPServerSocketMessageFilter::DoListen, this,
+ context->MakeReplyMessageContext(), addr, backlog));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperTCPServerSocketMessageFilter::OnMsgAccept(
+ const ppapi::host::HostMessageContext* context,
+ uint32 plugin_dispatcher_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(context);
+
+ if (state_ != STATE_LISTENING)
+ return PP_ERROR_FAILED;
+
+ state_ = STATE_ACCEPT_IN_PROGRESS;
+ ppapi::host::ReplyMessageContext reply_context(
+ context->MakeReplyMessageContext());
+ int net_result = socket_->Accept(
+ &socket_buffer_,
+ base::Bind(&PepperTCPServerSocketMessageFilter::OnAcceptCompleted,
+ base::Unretained(this),
+ reply_context, plugin_dispatcher_id));
+ if (net_result != net::ERR_IO_PENDING)
+ OnAcceptCompleted(reply_context, plugin_dispatcher_id, net_result);
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperTCPServerSocketMessageFilter::OnMsgStopListening(
+ const ppapi::host::HostMessageContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(context);
+
+ state_ = STATE_CLOSED;
+ socket_.reset();
+ return PP_OK;
+}
+
+void PepperTCPServerSocketMessageFilter::DoListen(
+ const ppapi::host::ReplyMessageContext& context,
+ const PP_NetAddress_Private& addr,
+ int32_t backlog) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::IPAddressNumber address;
+ int port;
+ if (state_ != STATE_BEFORE_LISTENING ||
+ !NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
+ SendListenError(context, PP_ERROR_FAILED);
+ state_ = STATE_CLOSED;
+ return;
+ }
+
+ state_ = STATE_LISTEN_IN_PROGRESS;
+
+ socket_.reset(new net::TCPServerSocket(NULL, net::NetLog::Source()));
+ int net_result = socket_->Listen(net::IPEndPoint(address, port), backlog);
+ if (net_result != net::ERR_IO_PENDING)
+ OnListenCompleted(context, net_result);
+}
+
+void PepperTCPServerSocketMessageFilter::OnListenCompleted(
+ const ppapi::host::ReplyMessageContext& context,
+ int net_result) {
+ if (state_ != STATE_LISTEN_IN_PROGRESS) {
+ SendListenError(context, PP_ERROR_FAILED);
+ state_ = STATE_CLOSED;
+ return;
+ }
+ if (net_result != net::OK) {
+ SendListenError(context, NetErrorToPepperError(net_result));
+ state_ = STATE_BEFORE_LISTENING;
+ return;
+ }
+
+ DCHECK(socket_.get());
+
+ net::IPEndPoint end_point;
+ PP_NetAddress_Private addr;
+
+ int32_t pp_result =
+ NetErrorToPepperError(socket_->GetLocalAddress(&end_point));
+ if (pp_result != PP_OK) {
+ SendListenError(context, pp_result);
+ state_ = STATE_BEFORE_LISTENING;
+ return;
+ }
+ if (!NetAddressPrivateImpl::IPEndPointToNetAddress(end_point.address(),
+ end_point.port(),
+ &addr)) {
+ SendListenError(context, PP_ERROR_FAILED);
+ state_ = STATE_BEFORE_LISTENING;
+ return;
+ }
+
+ SendListenReply(context, PP_OK, addr);
+ state_ = STATE_LISTENING;
+}
+
+void PepperTCPServerSocketMessageFilter::OnAcceptCompleted(
+ const ppapi::host::ReplyMessageContext& context,
+ uint32 plugin_dispatcher_id,
+ int net_result) {
+ if (state_ != STATE_ACCEPT_IN_PROGRESS) {
+ SendAcceptError(context, PP_ERROR_FAILED);
+ state_ = STATE_CLOSED;
+ return;
+ }
+
+ state_ = STATE_LISTENING;
+
+ if (net_result != net::OK) {
+ SendAcceptError(context, NetErrorToPepperError(net_result));
+ return;
+ }
+
+ DCHECK(socket_buffer_.get());
+
+ scoped_ptr<net::StreamSocket> socket(socket_buffer_.release());
+ net::IPEndPoint ip_end_point_local;
+ net::IPEndPoint ip_end_point_remote;
+ PP_NetAddress_Private local_addr = NetAddressPrivateImpl::kInvalidNetAddress;
+ PP_NetAddress_Private remote_addr = NetAddressPrivateImpl::kInvalidNetAddress;
+
+ int32_t pp_result =
+ NetErrorToPepperError(socket->GetLocalAddress(&ip_end_point_local));
+ if (pp_result != PP_OK) {
+ SendAcceptError(context, pp_result);
+ return;
+ }
+ if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
+ ip_end_point_local.address(),
+ ip_end_point_local.port(),
+ &local_addr)) {
+ SendAcceptError(context, PP_ERROR_FAILED);
+ return;
+ }
+ pp_result =
+ NetErrorToPepperError(socket->GetPeerAddress(&ip_end_point_remote));
+ if (pp_result != PP_OK) {
+ SendAcceptError(context, pp_result);
+ return;
+ }
+ if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
+ ip_end_point_remote.address(),
+ ip_end_point_remote.port(),
+ &remote_addr)) {
+ SendAcceptError(context, PP_ERROR_FAILED);
+ return;
+ }
+ if (!pepper_message_filter_.get() || plugin_dispatcher_id == 0) {
+ SendAcceptError(context, PP_ERROR_FAILED);
+ return;
+ }
+ uint32 accepted_socket_id = pepper_message_filter_->AddAcceptedTCPSocket(
+ ppapi::API_ID_PPB_TCPSOCKET_PRIVATE,
+ plugin_dispatcher_id,
+ socket.release());
+ if (accepted_socket_id != 0) {
+ SendAcceptReply(context, PP_OK, accepted_socket_id, local_addr,
+ remote_addr);
+ } else {
+ SendAcceptError(context, PP_ERROR_NOSPACE);
+ }
+}
+
+void PepperTCPServerSocketMessageFilter::SendListenReply(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result,
+ const PP_NetAddress_Private& local_addr) {
+ ppapi::host::ReplyMessageContext reply_context(context);
+ reply_context.params.set_result(pp_result);
+ SendReply(reply_context,
+ PpapiPluginMsg_TCPServerSocket_ListenReply(local_addr));
+}
+
+void PepperTCPServerSocketMessageFilter::SendListenError(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result) {
+ SendListenReply(context, pp_result,
+ NetAddressPrivateImpl::kInvalidNetAddress);
+}
+
+void PepperTCPServerSocketMessageFilter::SendAcceptReply(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result,
+ uint32 accepted_socket_id,
+ const PP_NetAddress_Private& local_addr,
+ const PP_NetAddress_Private& remote_addr) {
+ ppapi::host::ReplyMessageContext reply_context(context);
+ reply_context.params.set_result(pp_result);
+ SendReply(reply_context, PpapiPluginMsg_TCPServerSocket_AcceptReply(
+ accepted_socket_id, local_addr, remote_addr));
+}
+
+void PepperTCPServerSocketMessageFilter::SendAcceptError(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result) {
+ SendAcceptReply(context,
+ pp_result,
+ 0,
+ NetAddressPrivateImpl::kInvalidNetAddress,
+ NetAddressPrivateImpl::kInvalidNetAddress);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h b/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h
new file mode 100644
index 00000000000..ad8cf19f287
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h
@@ -0,0 +1,106 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SERVER_SOCKET_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SERVER_SOCKET_MESSAGE_FILTER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "content/common/content_export.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/host/resource_message_filter.h"
+
+struct PP_NetAddress_Private;
+
+namespace net {
+class ServerSocket;
+class StreamSocket;
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+
+class CONTENT_EXPORT PepperTCPServerSocketMessageFilter
+ : public ppapi::host::ResourceMessageFilter {
+ public:
+ PepperTCPServerSocketMessageFilter(
+ BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api,
+ const scoped_refptr<PepperMessageFilter>& pepper_message_filter);
+
+ static size_t GetNumInstances();
+
+ protected:
+ virtual ~PepperTCPServerSocketMessageFilter();
+
+ private:
+ enum State {
+ STATE_BEFORE_LISTENING,
+ STATE_LISTEN_IN_PROGRESS,
+ STATE_LISTENING,
+ STATE_ACCEPT_IN_PROGRESS,
+ STATE_CLOSED
+ };
+
+ // ppapi::host::ResourceMessageFilter overrides.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ int32_t OnMsgListen(const ppapi::host::HostMessageContext* context,
+ const PP_NetAddress_Private& addr,
+ int32_t backlog);
+ int32_t OnMsgAccept(const ppapi::host::HostMessageContext* context,
+ uint32 plugin_dispatcher_id);
+ int32_t OnMsgStopListening(const ppapi::host::HostMessageContext* context);
+
+ void DoListen(const ppapi::host::ReplyMessageContext& context,
+ const PP_NetAddress_Private& addr,
+ int32_t backlog);
+
+ void OnListenCompleted(const ppapi::host::ReplyMessageContext& context,
+ int net_result);
+ void OnAcceptCompleted(const ppapi::host::ReplyMessageContext& context,
+ uint32 plugin_dispatcher_id,
+ int net_result);
+
+ void SendListenReply(const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result,
+ const PP_NetAddress_Private& local_addr);
+ void SendListenError(const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result);
+ void SendAcceptReply(const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result,
+ uint32 accepted_socket_id,
+ const PP_NetAddress_Private& local_addr,
+ const PP_NetAddress_Private& remote_addr);
+ void SendAcceptError(const ppapi::host::ReplyMessageContext& context,
+ int32_t pp_result);
+
+ // Following fields are initialized and used only on the IO thread.
+ State state_;
+ scoped_ptr<net::ServerSocket> socket_;
+ scoped_ptr<net::StreamSocket> socket_buffer_;
+ scoped_refptr<PepperMessageFilter> pepper_message_filter_;
+
+ // Following fields are initialized on the IO thread but used only
+ // on the UI thread.
+ const bool external_plugin_;
+ const bool private_api_;
+ int render_process_id_;
+ int render_view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperTCPServerSocketMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SERVER_SOCKET_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.cc b/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.cc
new file mode 100644
index 00000000000..05b2e09993a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.cc
@@ -0,0 +1,525 @@
+// 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 "content/browser/renderer_host/pepper/pepper_tcp_socket.h"
+
+#include <string.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/x509_certificate.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "ppapi/host/error_conversion.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+#include "ppapi/shared_impl/private/ppb_x509_certificate_private_shared.h"
+#include "ppapi/shared_impl/socket_option_data.h"
+#include "ppapi/shared_impl/tcp_socket_shared.h"
+
+using ppapi::host::NetErrorToPepperError;
+using ppapi::NetAddressPrivateImpl;
+
+namespace content {
+
+PepperTCPSocket::PepperTCPSocket(
+ PepperMessageFilter* manager,
+ int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32 socket_id,
+ bool private_api)
+ : manager_(manager),
+ routing_id_(routing_id),
+ plugin_dispatcher_id_(plugin_dispatcher_id),
+ socket_id_(socket_id),
+ private_api_(private_api),
+ connection_state_(BEFORE_CONNECT),
+ end_of_file_reached_(false) {
+ DCHECK(manager);
+}
+
+PepperTCPSocket::PepperTCPSocket(
+ PepperMessageFilter* manager,
+ int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32 socket_id,
+ net::StreamSocket* socket,
+ bool private_api)
+ : manager_(manager),
+ routing_id_(routing_id),
+ plugin_dispatcher_id_(plugin_dispatcher_id),
+ socket_id_(socket_id),
+ private_api_(private_api),
+ connection_state_(CONNECTED),
+ end_of_file_reached_(false),
+ socket_(socket) {
+ DCHECK(manager);
+}
+
+PepperTCPSocket::~PepperTCPSocket() {
+ // Make sure no further callbacks from socket_.
+ if (socket_)
+ socket_->Disconnect();
+}
+
+void PepperTCPSocket::Connect(const std::string& host, uint16_t port) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (connection_state_ != BEFORE_CONNECT) {
+ SendConnectACKError(PP_ERROR_FAILED);
+ return;
+ }
+
+ connection_state_ = CONNECT_IN_PROGRESS;
+ net::HostResolver::RequestInfo request_info(net::HostPortPair(host, port));
+ resolver_.reset(new net::SingleRequestHostResolver(
+ manager_->GetHostResolver()));
+ int net_result = resolver_->Resolve(
+ request_info, &address_list_,
+ base::Bind(&PepperTCPSocket::OnResolveCompleted, base::Unretained(this)),
+ net::BoundNetLog());
+ if (net_result != net::ERR_IO_PENDING)
+ OnResolveCompleted(net_result);
+}
+
+void PepperTCPSocket::ConnectWithNetAddress(
+ const PP_NetAddress_Private& net_addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (connection_state_ != BEFORE_CONNECT) {
+ SendConnectACKError(PP_ERROR_FAILED);
+ return;
+ }
+
+ net::IPAddressNumber address;
+ int port;
+ if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr, &address,
+ &port)) {
+ SendConnectACKError(PP_ERROR_ADDRESS_INVALID);
+ return;
+ }
+
+ // Copy the single IPEndPoint to address_list_.
+ address_list_.clear();
+ address_list_.push_back(net::IPEndPoint(address, port));
+ connection_state_ = CONNECT_IN_PROGRESS;
+ StartConnect(address_list_);
+}
+
+void PepperTCPSocket::SSLHandshake(
+ const std::string& server_name,
+ uint16_t server_port,
+ const std::vector<std::vector<char> >& trusted_certs,
+ const std::vector<std::vector<char> >& untrusted_certs) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Allow to do SSL handshake only if currently the socket has been connected
+ // and there isn't pending read or write.
+ // IsConnected() includes the state that SSL handshake has been finished and
+ // therefore isn't suitable here.
+ if (connection_state_ != CONNECTED || read_buffer_.get() ||
+ write_buffer_base_.get() || write_buffer_.get()) {
+ SendSSLHandshakeACK(false);
+ return;
+ }
+
+ connection_state_ = SSL_HANDSHAKE_IN_PROGRESS;
+ // TODO(raymes,rsleevi): Use trusted/untrusted certificates when connecting.
+
+ scoped_ptr<net::ClientSocketHandle> handle(new net::ClientSocketHandle());
+ handle->SetSocket(socket_.Pass());
+ net::ClientSocketFactory* factory =
+ net::ClientSocketFactory::GetDefaultFactory();
+ net::HostPortPair host_port_pair(server_name, server_port);
+ net::SSLClientSocketContext ssl_context;
+ ssl_context.cert_verifier = manager_->GetCertVerifier();
+ ssl_context.transport_security_state = manager_->GetTransportSecurityState();
+ socket_ = factory->CreateSSLClientSocket(
+ handle.Pass(), host_port_pair, manager_->ssl_config(), ssl_context);
+ if (!socket_) {
+ LOG(WARNING) << "Failed to create an SSL client socket.";
+ OnSSLHandshakeCompleted(net::ERR_UNEXPECTED);
+ return;
+ }
+
+ int net_result = socket_->Connect(
+ base::Bind(&PepperTCPSocket::OnSSLHandshakeCompleted,
+ base::Unretained(this)));
+ if (net_result != net::ERR_IO_PENDING)
+ OnSSLHandshakeCompleted(net_result);
+}
+
+void PepperTCPSocket::Read(int32 bytes_to_read) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!IsConnected() || end_of_file_reached_) {
+ SendReadACKError(PP_ERROR_FAILED);
+ return;
+ }
+
+ if (read_buffer_.get()) {
+ SendReadACKError(PP_ERROR_INPROGRESS);
+ return;
+ }
+
+ if (bytes_to_read <= 0 ||
+ bytes_to_read > ppapi::TCPSocketShared::kMaxReadSize) {
+ SendReadACKError(PP_ERROR_BADARGUMENT);
+ return;
+ }
+
+ read_buffer_ = new net::IOBuffer(bytes_to_read);
+ int net_result = socket_->Read(
+ read_buffer_.get(),
+ bytes_to_read,
+ base::Bind(&PepperTCPSocket::OnReadCompleted, base::Unretained(this)));
+ if (net_result != net::ERR_IO_PENDING)
+ OnReadCompleted(net_result);
+}
+
+void PepperTCPSocket::Write(const std::string& data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!IsConnected()) {
+ SendWriteACKError(PP_ERROR_FAILED);
+ return;
+ }
+
+ if (write_buffer_base_.get() || write_buffer_.get()) {
+ SendWriteACKError(PP_ERROR_INPROGRESS);
+ return;
+ }
+
+ size_t data_size = data.size();
+ if (data_size == 0 ||
+ data_size > static_cast<size_t>(ppapi::TCPSocketShared::kMaxWriteSize)) {
+ SendWriteACKError(PP_ERROR_BADARGUMENT);
+ return;
+ }
+
+ write_buffer_base_ = new net::IOBuffer(data_size);
+ memcpy(write_buffer_base_->data(), data.data(), data_size);
+ write_buffer_ =
+ new net::DrainableIOBuffer(write_buffer_base_.get(), data_size);
+ DoWrite();
+}
+
+void PepperTCPSocket::SetOption(PP_TCPSocket_Option name,
+ const ppapi::SocketOptionData& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!IsConnected() || IsSsl()) {
+ SendSetOptionACK(PP_ERROR_FAILED);
+ return;
+ }
+
+ net::TCPClientSocket* tcp_socket =
+ static_cast<net::TCPClientSocket*>(socket_.get());
+ DCHECK(tcp_socket);
+
+ switch (name) {
+ case PP_TCPSOCKET_OPTION_NO_DELAY: {
+ bool boolean_value = false;
+ if (!value.GetBool(&boolean_value)) {
+ SendSetOptionACK(PP_ERROR_BADARGUMENT);
+ return;
+ }
+
+ SendSetOptionACK(
+ tcp_socket->SetNoDelay(boolean_value) ? PP_OK : PP_ERROR_FAILED);
+ return;
+ }
+ case PP_TCPSOCKET_OPTION_SEND_BUFFER_SIZE:
+ case PP_TCPSOCKET_OPTION_RECV_BUFFER_SIZE: {
+ int32_t integer_value = 0;
+ if (!value.GetInt32(&integer_value) || integer_value <= 0) {
+ SendSetOptionACK(PP_ERROR_BADARGUMENT);
+ return;
+ }
+
+ bool result = false;
+ if (name == PP_TCPSOCKET_OPTION_SEND_BUFFER_SIZE) {
+ if (integer_value > ppapi::TCPSocketShared::kMaxSendBufferSize) {
+ SendSetOptionACK(PP_ERROR_BADARGUMENT);
+ return;
+ }
+ result = tcp_socket->SetSendBufferSize(integer_value);
+ } else {
+ if (integer_value > ppapi::TCPSocketShared::kMaxReceiveBufferSize) {
+ SendSetOptionACK(PP_ERROR_BADARGUMENT);
+ return;
+ }
+ result = tcp_socket->SetReceiveBufferSize(integer_value);
+ }
+ SendSetOptionACK(result ? PP_OK : PP_ERROR_FAILED);
+ return;
+ }
+ default: {
+ NOTREACHED();
+ SendSetOptionACK(PP_ERROR_BADARGUMENT);
+ return;
+ }
+ }
+}
+
+void PepperTCPSocket::StartConnect(const net::AddressList& addresses) {
+ DCHECK(connection_state_ == CONNECT_IN_PROGRESS);
+
+ socket_.reset(new net::TCPClientSocket(addresses, NULL,
+ net::NetLog::Source()));
+ int net_result = socket_->Connect(
+ base::Bind(&PepperTCPSocket::OnConnectCompleted,
+ base::Unretained(this)));
+ if (net_result != net::ERR_IO_PENDING)
+ OnConnectCompleted(net_result);
+}
+
+void PepperTCPSocket::SendConnectACKError(int32_t error) {
+ manager_->Send(new PpapiMsg_PPBTCPSocket_ConnectACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, error,
+ NetAddressPrivateImpl::kInvalidNetAddress,
+ NetAddressPrivateImpl::kInvalidNetAddress));
+}
+
+// static
+bool PepperTCPSocket::GetCertificateFields(
+ const net::X509Certificate& cert,
+ ppapi::PPB_X509Certificate_Fields* fields) {
+ const net::CertPrincipal& issuer = cert.issuer();
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_COMMON_NAME,
+ new base::StringValue(issuer.common_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_LOCALITY_NAME,
+ new base::StringValue(issuer.locality_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_STATE_OR_PROVINCE_NAME,
+ new base::StringValue(issuer.state_or_province_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_COUNTRY_NAME,
+ new base::StringValue(issuer.country_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_ORGANIZATION_NAME,
+ new base::StringValue(JoinString(issuer.organization_names, '\n')));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_ISSUER_ORGANIZATION_UNIT_NAME,
+ new base::StringValue(JoinString(issuer.organization_unit_names, '\n')));
+
+ const net::CertPrincipal& subject = cert.subject();
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_COMMON_NAME,
+ new base::StringValue(subject.common_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_LOCALITY_NAME,
+ new base::StringValue(subject.locality_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_STATE_OR_PROVINCE_NAME,
+ new base::StringValue(subject.state_or_province_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_COUNTRY_NAME,
+ new base::StringValue(subject.country_name));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_ORGANIZATION_NAME,
+ new base::StringValue(JoinString(subject.organization_names, '\n')));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SUBJECT_ORGANIZATION_UNIT_NAME,
+ new base::StringValue(JoinString(subject.organization_unit_names, '\n')));
+
+ const std::string& serial_number = cert.serial_number();
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_SERIAL_NUMBER,
+ base::BinaryValue::CreateWithCopiedBuffer(serial_number.data(),
+ serial_number.length()));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_VALIDITY_NOT_BEFORE,
+ new base::FundamentalValue(cert.valid_start().ToDoubleT()));
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_VALIDITY_NOT_AFTER,
+ new base::FundamentalValue(cert.valid_expiry().ToDoubleT()));
+ std::string der;
+ net::X509Certificate::GetDEREncoded(cert.os_cert_handle(), &der);
+ fields->SetField(PP_X509CERTIFICATE_PRIVATE_RAW,
+ base::BinaryValue::CreateWithCopiedBuffer(der.data(), der.length()));
+ return true;
+}
+
+// static
+bool PepperTCPSocket::GetCertificateFields(
+ const char* der,
+ uint32_t length,
+ ppapi::PPB_X509Certificate_Fields* fields) {
+ scoped_refptr<net::X509Certificate> cert =
+ net::X509Certificate::CreateFromBytes(der, length);
+ if (!cert.get())
+ return false;
+ return GetCertificateFields(*cert.get(), fields);
+}
+
+void PepperTCPSocket::SendReadACKError(int32_t error) {
+ manager_->Send(new PpapiMsg_PPBTCPSocket_ReadACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, error, std::string()));
+}
+
+void PepperTCPSocket::SendWriteACKError(int32_t error) {
+ DCHECK_GT(0, error);
+ manager_->Send(new PpapiMsg_PPBTCPSocket_WriteACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, error));
+}
+
+void PepperTCPSocket::SendSSLHandshakeACK(bool succeeded) {
+ ppapi::PPB_X509Certificate_Fields certificate_fields;
+ if (succeeded) {
+ // Our socket is guaranteed to be an SSL socket if we get here.
+ net::SSLClientSocket* ssl_socket =
+ static_cast<net::SSLClientSocket*>(socket_.get());
+ net::SSLInfo ssl_info;
+ ssl_socket->GetSSLInfo(&ssl_info);
+ if (ssl_info.cert.get())
+ GetCertificateFields(*ssl_info.cert.get(), &certificate_fields);
+ }
+ manager_->Send(new PpapiMsg_PPBTCPSocket_SSLHandshakeACK(
+ routing_id_,
+ plugin_dispatcher_id_,
+ socket_id_,
+ succeeded,
+ certificate_fields));
+}
+
+void PepperTCPSocket::SendSetOptionACK(int32_t result) {
+ manager_->Send(new PpapiMsg_PPBTCPSocket_SetOptionACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, result));
+}
+
+void PepperTCPSocket::OnResolveCompleted(int net_result) {
+ DCHECK(connection_state_ == CONNECT_IN_PROGRESS);
+
+ if (net_result != net::OK) {
+ SendConnectACKError(NetErrorToPepperError(net_result));
+ connection_state_ = BEFORE_CONNECT;
+ return;
+ }
+
+ StartConnect(address_list_);
+}
+
+void PepperTCPSocket::OnConnectCompleted(int net_result) {
+ DCHECK(connection_state_ == CONNECT_IN_PROGRESS && socket_.get());
+
+ int32_t pp_result = NetErrorToPepperError(net_result);
+ do {
+ if (pp_result != PP_OK)
+ break;
+
+ net::IPEndPoint ip_end_point_local;
+ net::IPEndPoint ip_end_point_remote;
+ pp_result = NetErrorToPepperError(
+ socket_->GetLocalAddress(&ip_end_point_local));
+ if (pp_result != PP_OK)
+ break;
+ pp_result = NetErrorToPepperError(
+ socket_->GetPeerAddress(&ip_end_point_remote));
+ if (pp_result != PP_OK)
+ break;
+
+ PP_NetAddress_Private local_addr =
+ NetAddressPrivateImpl::kInvalidNetAddress;
+ PP_NetAddress_Private remote_addr =
+ NetAddressPrivateImpl::kInvalidNetAddress;
+ if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
+ ip_end_point_local.address(),
+ ip_end_point_local.port(),
+ &local_addr) ||
+ !NetAddressPrivateImpl::IPEndPointToNetAddress(
+ ip_end_point_remote.address(),
+ ip_end_point_remote.port(),
+ &remote_addr)) {
+ pp_result = PP_ERROR_ADDRESS_INVALID;
+ break;
+ }
+
+ manager_->Send(new PpapiMsg_PPBTCPSocket_ConnectACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, PP_OK,
+ local_addr, remote_addr));
+ connection_state_ = CONNECTED;
+ return;
+ } while (false);
+
+ SendConnectACKError(pp_result);
+ connection_state_ = BEFORE_CONNECT;
+}
+
+void PepperTCPSocket::OnSSLHandshakeCompleted(int net_result) {
+ DCHECK(connection_state_ == SSL_HANDSHAKE_IN_PROGRESS);
+
+ bool succeeded = net_result == net::OK;
+ SendSSLHandshakeACK(succeeded);
+ connection_state_ = succeeded ? SSL_CONNECTED : SSL_HANDSHAKE_FAILED;
+}
+
+void PepperTCPSocket::OnReadCompleted(int net_result) {
+ DCHECK(read_buffer_.get());
+
+ if (net_result > 0) {
+ manager_->Send(new PpapiMsg_PPBTCPSocket_ReadACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, PP_OK,
+ std::string(read_buffer_->data(), net_result)));
+ } else if (net_result == 0) {
+ end_of_file_reached_ = true;
+ manager_->Send(new PpapiMsg_PPBTCPSocket_ReadACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_, PP_OK, std::string()));
+ } else {
+ SendReadACKError(NetErrorToPepperError(net_result));
+ }
+ read_buffer_ = NULL;
+}
+
+void PepperTCPSocket::OnWriteCompleted(int net_result) {
+ DCHECK(write_buffer_base_.get());
+ DCHECK(write_buffer_.get());
+
+ // Note: For partial writes of 0 bytes, don't continue writing to avoid a
+ // likely infinite loop.
+ if (net_result > 0) {
+ write_buffer_->DidConsume(net_result);
+ if (write_buffer_->BytesRemaining() > 0) {
+ DoWrite();
+ return;
+ }
+ }
+
+ if (net_result >= 0) {
+ manager_->Send(new PpapiMsg_PPBTCPSocket_WriteACK(
+ routing_id_, plugin_dispatcher_id_, socket_id_,
+ write_buffer_->BytesConsumed()));
+ } else {
+ SendWriteACKError(NetErrorToPepperError(net_result));
+ }
+
+ write_buffer_ = NULL;
+ write_buffer_base_ = NULL;
+}
+
+bool PepperTCPSocket::IsConnected() const {
+ return connection_state_ == CONNECTED || connection_state_ == SSL_CONNECTED;
+}
+
+bool PepperTCPSocket::IsSsl() const {
+ return connection_state_ == SSL_HANDSHAKE_IN_PROGRESS ||
+ connection_state_ == SSL_CONNECTED ||
+ connection_state_ == SSL_HANDSHAKE_FAILED;
+}
+
+void PepperTCPSocket::DoWrite() {
+ DCHECK(write_buffer_base_.get());
+ DCHECK(write_buffer_.get());
+ DCHECK_GT(write_buffer_->BytesRemaining(), 0);
+
+ int net_result = socket_->Write(
+ write_buffer_.get(),
+ write_buffer_->BytesRemaining(),
+ base::Bind(&PepperTCPSocket::OnWriteCompleted, base::Unretained(this)));
+ if (net_result != net::ERR_IO_PENDING)
+ OnWriteCompleted(net_result);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.h b/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.h
new file mode 100644
index 00000000000..986afb015bb
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_tcp_socket.h
@@ -0,0 +1,148 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SOCKET_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/ppb_tcp_socket.h"
+
+struct PP_NetAddress_Private;
+
+namespace ppapi {
+class PPB_X509Certificate_Fields;
+class SocketOptionData;
+}
+
+namespace net {
+class DrainableIOBuffer;
+class IOBuffer;
+class SingleRequestHostResolver;
+class StreamSocket;
+class X509Certificate;
+}
+
+namespace content {
+class PepperMessageFilter;
+
+// PepperTCPSocket is used by PepperMessageFilter to handle requests from
+// the Pepper TCP socket API (PPB_TCPSocket and PPB_TCPSocket_Private).
+class PepperTCPSocket {
+ public:
+ PepperTCPSocket(PepperMessageFilter* manager,
+ int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32 socket_id,
+ bool private_api);
+
+ // Used for creation already connected sockets. Takes ownership of
+ // |socket|.
+ PepperTCPSocket(PepperMessageFilter* manager,
+ int32 routing_id,
+ uint32 plugin_dispatcher_id,
+ uint32 socket_id,
+ net::StreamSocket* socket,
+ bool private_api);
+ ~PepperTCPSocket();
+
+ int routing_id() { return routing_id_; }
+ bool private_api() const { return private_api_; }
+
+ void Connect(const std::string& host, uint16_t port);
+ void ConnectWithNetAddress(const PP_NetAddress_Private& net_addr);
+ void SSLHandshake(
+ const std::string& server_name,
+ uint16_t server_port,
+ const std::vector<std::vector<char> >& trusted_certs,
+ const std::vector<std::vector<char> >& untrusted_certs);
+ void Read(int32 bytes_to_read);
+ void Write(const std::string& data);
+ void SetOption(PP_TCPSocket_Option name,
+ const ppapi::SocketOptionData& value);
+
+ void SendConnectACKError(int32_t error);
+
+ // Extracts the certificate field data from a |net::X509Certificate| into
+ // |PPB_X509Certificate_Fields|.
+ static bool GetCertificateFields(const net::X509Certificate& cert,
+ ppapi::PPB_X509Certificate_Fields* fields);
+ // Extracts the certificate field data from the DER representation of a
+ // certificate into |PPB_X509Certificate_Fields|.
+ static bool GetCertificateFields(const char* der,
+ uint32_t length,
+ ppapi::PPB_X509Certificate_Fields* fields);
+
+ private:
+ enum ConnectionState {
+ // Before a connection is successfully established (including a previous
+ // connect request failed).
+ BEFORE_CONNECT,
+ // There is a connect request that is pending.
+ CONNECT_IN_PROGRESS,
+ // A connection has been successfully established.
+ CONNECTED,
+ // There is an SSL handshake request that is pending.
+ SSL_HANDSHAKE_IN_PROGRESS,
+ // An SSL connection has been successfully established.
+ SSL_CONNECTED,
+ // An SSL handshake has failed.
+ SSL_HANDSHAKE_FAILED
+ };
+
+ void StartConnect(const net::AddressList& addresses);
+
+ void SendReadACKError(int32_t error);
+ void SendWriteACKError(int32_t error);
+ void SendSSLHandshakeACK(bool succeeded);
+ void SendSetOptionACK(int32_t result);
+
+ void OnResolveCompleted(int net_result);
+ void OnConnectCompleted(int net_result);
+ void OnSSLHandshakeCompleted(int net_result);
+ void OnReadCompleted(int net_result);
+ void OnWriteCompleted(int net_result);
+
+ bool IsConnected() const;
+ bool IsSsl() const;
+
+ // Actually does a write from |write_buffer_|; possibly called many times for
+ // each |Write()|.
+ void DoWrite();
+
+ PepperMessageFilter* manager_;
+ int32 routing_id_;
+ uint32 plugin_dispatcher_id_;
+ uint32 socket_id_;
+ bool private_api_;
+
+ ConnectionState connection_state_;
+ bool end_of_file_reached_;
+
+ scoped_ptr<net::SingleRequestHostResolver> resolver_;
+ net::AddressList address_list_;
+
+ scoped_ptr<net::StreamSocket> socket_;
+
+ scoped_refptr<net::IOBuffer> read_buffer_;
+
+ // |StreamSocket::Write()| may not always write the full buffer, but we would
+ // rather have our |Write()| do so whenever possible. To do this, we may have
+ // to call the former multiple times for each of the latter. This entails
+ // using a |DrainableIOBuffer|, which requires an underlying base |IOBuffer|.
+ scoped_refptr<net::IOBuffer> write_buffer_base_;
+ scoped_refptr<net::DrainableIOBuffer> write_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperTCPSocket);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TCP_SOCKET_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list.h b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list.h
new file mode 100644
index 00000000000..da944c50071
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list.h
@@ -0,0 +1,36 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_H_
+
+#include <string>
+#include <vector>
+
+namespace ppapi {
+namespace proxy {
+struct SerializedTrueTypeFontDesc;
+}
+}
+
+namespace content {
+
+// Adds font family names on the host platform to the vector of strings.
+//
+// This function is potentially slow (the system may do a bunch of I/O) so be
+// sure not to call this on a time-critical thread like the UI or I/O threads.
+void GetFontFamilies_SlowBlocking(std::vector<std::string>* font_families);
+
+// Adds font descriptors for fonts on the host platform in the given family to
+// the vector of descriptors.
+//
+// This function is potentially slow (the system may do a bunch of I/O) so be
+// sure not to call this on a time-critical thread like the UI or I/O threads.
+void GetFontsInFamily_SlowBlocking(
+ const std::string& family,
+ std::vector<ppapi::proxy::SerializedTrueTypeFontDesc>* fonts_in_family);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_android.cc b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_android.cc
new file mode 100644
index 00000000000..28db24f7ffa
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_android.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 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 "base/logging.h"
+#include "content/browser/renderer_host/pepper/pepper_truetype_font_list.h"
+
+namespace content {
+
+void GetFontFamilies_SlowBlocking(std::vector<std::string>* font_families) {
+ NOTIMPLEMENTED(); // Font API isn't implemented on Android.
+}
+
+void GetFontsInFamily_SlowBlocking(
+ const std::string& family,
+ std::vector<ppapi::proxy::SerializedTrueTypeFontDesc>* fonts_in_family) {
+ NOTIMPLEMENTED(); // Font API isn't implemented on Android.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.cc b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.cc
new file mode 100644
index 00000000000..1b2aa668f04
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 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 "content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h"
+
+#include <algorithm>
+
+#include "base/safe_numerics.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/renderer_host/pepper/pepper_truetype_font_list.h"
+#include "content/common/font_list.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_message_filter.h"
+#include "ppapi/proxy/ppapi_messages.h"
+
+namespace content {
+
+namespace {
+
+// Handles the font list request on the blocking pool.
+class FontMessageFilter : public ppapi::host::ResourceMessageFilter {
+ public:
+ FontMessageFilter();
+
+ // ppapi::host::ResourceMessageFilter implementation.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ private:
+ virtual ~FontMessageFilter();
+
+ // Message handlers.
+ int32_t OnHostMsgGetFontFamilies(ppapi::host::HostMessageContext* context);
+ int32_t OnHostMsgGetFontsInFamily(ppapi::host::HostMessageContext* context,
+ const std::string& family);
+
+ DISALLOW_COPY_AND_ASSIGN(FontMessageFilter);
+};
+
+FontMessageFilter::FontMessageFilter() {
+}
+
+FontMessageFilter::~FontMessageFilter() {
+}
+
+scoped_refptr<base::TaskRunner> FontMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& msg) {
+ // Use the blocking pool to get the font list (currently the only message)
+ // Since getting the font list is non-threadsafe on Linux (for versions of
+ // Pango predating 2013), use a sequenced task runner.
+ base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
+ return pool->GetSequencedTaskRunner(
+ pool->GetNamedSequenceToken(kFontListSequenceToken));
+}
+
+int32_t FontMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(FontMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_TrueTypeFontSingleton_GetFontFamilies,
+ OnHostMsgGetFontFamilies)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_TrueTypeFontSingleton_GetFontsInFamily,
+ OnHostMsgGetFontsInFamily)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t FontMessageFilter::OnHostMsgGetFontFamilies(
+ ppapi::host::HostMessageContext* context) {
+ // OK to use "slow blocking" version since we're on the blocking pool.
+ std::vector<std::string> font_families;
+ GetFontFamilies_SlowBlocking(&font_families);
+ // Sort the names in case the host platform returns them out of order.
+ std::sort(font_families.begin(), font_families.end());
+
+ int32_t result = base::checked_numeric_cast<int32_t>(font_families.size());
+ ppapi::host::ReplyMessageContext reply_context =
+ context->MakeReplyMessageContext();
+ reply_context.params.set_result(result);
+ context->reply_msg =
+ PpapiPluginMsg_TrueTypeFontSingleton_GetFontFamiliesReply(font_families);
+ return result;
+}
+
+int32_t FontMessageFilter::OnHostMsgGetFontsInFamily(
+ ppapi::host::HostMessageContext* context,
+ const std::string& family) {
+ // OK to use "slow blocking" version since we're on the blocking pool.
+ std::vector<ppapi::proxy::SerializedTrueTypeFontDesc> fonts_in_family;
+ GetFontsInFamily_SlowBlocking(family, &fonts_in_family);
+
+ int32_t result = base::checked_numeric_cast<int32_t>(fonts_in_family.size());
+ ppapi::host::ReplyMessageContext reply_context =
+ context->MakeReplyMessageContext();
+ reply_context.params.set_result(result);
+ context->reply_msg =
+ PpapiPluginMsg_TrueTypeFontSingleton_GetFontsInFamilyReply(
+ fonts_in_family);
+ return result;
+}
+
+} // namespace
+
+PepperTrueTypeFontListHost::PepperTrueTypeFontListHost(
+ BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource) {
+ AddFilter(scoped_refptr<ppapi::host::ResourceMessageFilter>(
+ new FontMessageFilter()));
+}
+
+PepperTrueTypeFontListHost::~PepperTrueTypeFontListHost() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h
new file mode 100644
index 00000000000..69a471bb33e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_host.h
@@ -0,0 +1,28 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_HOST_H_
+
+#include "base/basictypes.h"
+#include "ppapi/host/resource_host.h"
+
+namespace content {
+
+class BrowserPpapiHost;
+
+class PepperTrueTypeFontListHost : public ppapi::host::ResourceHost {
+ public:
+ PepperTrueTypeFontListHost(BrowserPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource);
+ virtual ~PepperTrueTypeFontListHost();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PepperTrueTypeFontListHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_TRUETYPE_FONT_LIST_HOST_H_
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_linux.cc b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_linux.cc
new file mode 100644
index 00000000000..24c3f5df2f5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_linux.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 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 "content/browser/renderer_host/pepper/pepper_truetype_font_list.h"
+
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+
+#include <string>
+
+#include "ppapi/proxy/serialized_structs.h"
+
+namespace content {
+
+void GetFontFamilies_SlowBlocking(std::vector<std::string>* font_families) {
+ PangoFontMap* font_map = ::pango_cairo_font_map_get_default();
+ PangoFontFamily** families = NULL;
+ int num_families = 0;
+ ::pango_font_map_list_families(font_map, &families, &num_families);
+
+ for (int i = 0; i < num_families; ++i)
+ font_families->push_back(::pango_font_family_get_name(families[i]));
+ g_free(families);
+}
+
+void GetFontsInFamily_SlowBlocking(
+ const std::string& family,
+ std::vector<ppapi::proxy::SerializedTrueTypeFontDesc>* fonts_in_family) {
+ PangoFontMap* font_map = ::pango_cairo_font_map_get_default();
+ PangoFontFamily** font_families = NULL;
+ int num_families = 0;
+ ::pango_font_map_list_families(font_map, &font_families, &num_families);
+
+ for (int i = 0; i < num_families; ++i) {
+ PangoFontFamily* font_family = font_families[i];
+ if (family.compare(::pango_font_family_get_name(font_family)) == 0) {
+ PangoFontFace** font_faces = NULL;
+ int num_faces = 0;
+ ::pango_font_family_list_faces(font_family, &font_faces, &num_faces);
+
+ for (int j = 0; j < num_faces; ++j) {
+ PangoFontFace* font_face = font_faces[j];
+ PangoFontDescription* font_desc = ::pango_font_face_describe(font_face);
+ ppapi::proxy::SerializedTrueTypeFontDesc desc;
+ desc.family = family;
+ if (::pango_font_description_get_style(font_desc) == PANGO_STYLE_ITALIC)
+ desc.style = PP_TRUETYPEFONTSTYLE_ITALIC;
+ desc.weight = static_cast<PP_TrueTypeFontWeight_Dev>(
+ ::pango_font_description_get_weight(font_desc));
+ desc.width = static_cast<PP_TrueTypeFontWidth_Dev>(
+ ::pango_font_description_get_stretch(font_desc));
+ // Character set is not part of Pango font description.
+ desc.charset = PP_TRUETYPEFONTCHARSET_DEFAULT;
+
+ fonts_in_family->push_back(desc);
+ }
+ g_free(font_faces);
+ }
+ }
+ g_free(font_families);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm
new file mode 100644
index 00000000000..761572305ab
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm
@@ -0,0 +1,82 @@
+// Copyright (c) 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 "content/browser/renderer_host/pepper/pepper_truetype_font_list.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/proxy/serialized_structs.h"
+
+namespace content {
+
+namespace {
+
+// Table to map AppKit weights to Pepper ones.
+const PP_TrueTypeFontWeight_Dev kPepperFontWeights[] = {
+ PP_TRUETYPEFONTWEIGHT_THIN, // 0 is the minimum AppKit weight.
+ PP_TRUETYPEFONTWEIGHT_ULTRALIGHT,
+ PP_TRUETYPEFONTWEIGHT_ULTRALIGHT,
+ PP_TRUETYPEFONTWEIGHT_LIGHT,
+ PP_TRUETYPEFONTWEIGHT_LIGHT,
+ PP_TRUETYPEFONTWEIGHT_NORMAL, // 5 is a 'normal' AppKit weight.
+ PP_TRUETYPEFONTWEIGHT_MEDIUM,
+ PP_TRUETYPEFONTWEIGHT_MEDIUM,
+ PP_TRUETYPEFONTWEIGHT_SEMIBOLD,
+ PP_TRUETYPEFONTWEIGHT_BOLD, // 9 is a 'bold' AppKit weight.
+ PP_TRUETYPEFONTWEIGHT_ULTRABOLD,
+ PP_TRUETYPEFONTWEIGHT_HEAVY,
+};
+const NSInteger kPepperFontWeightsLength = arraysize(kPepperFontWeights);
+
+} // namespace
+
+void GetFontFamilies_SlowBlocking(std::vector<std::string>* font_families) {
+ base::mac::ScopedNSAutoreleasePool autorelease_pool;
+ NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
+ NSArray* fonts = [fontManager availableFontFamilies];
+ font_families->reserve([fonts count]);
+ for (NSString* family_name in fonts)
+ font_families->push_back(base::SysNSStringToUTF8(family_name));
+}
+
+void GetFontsInFamily_SlowBlocking(
+ const std::string& family,
+ std::vector<ppapi::proxy::SerializedTrueTypeFontDesc>* fonts_in_family) {
+ base::mac::ScopedNSAutoreleasePool autorelease_pool;
+ NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
+ NSString* ns_family = base::SysUTF8ToNSString(family);
+ NSArray* ns_fonts_in_family =
+ [fontManager availableMembersOfFontFamily:ns_family];
+
+ for (NSArray* font_info in ns_fonts_in_family) {
+ ppapi::proxy::SerializedTrueTypeFontDesc desc;
+ desc.family = family;
+ NSInteger font_weight = [[font_info objectAtIndex:2] intValue];
+ font_weight = std::max(static_cast<NSInteger>(0), font_weight);
+ font_weight = std::min(kPepperFontWeightsLength - 1, font_weight);
+ desc.weight = kPepperFontWeights[font_weight];
+
+ NSFontTraitMask font_traits =
+ [[font_info objectAtIndex:3] unsignedIntValue];
+ desc.style = PP_TRUETYPEFONTSTYLE_NORMAL;
+ if (font_traits & NSItalicFontMask)
+ desc.style = PP_TRUETYPEFONTSTYLE_ITALIC;
+
+ desc.width = PP_TRUETYPEFONTWIDTH_NORMAL;
+ if (font_traits & NSCondensedFontMask)
+ desc.width = PP_TRUETYPEFONTWIDTH_CONDENSED;
+ else if (font_traits & NSExpandedFontMask)
+ desc.width = PP_TRUETYPEFONTWIDTH_EXPANDED;
+
+ // Mac doesn't support requesting non-default character sets.
+ desc.charset = PP_TRUETYPEFONTCHARSET_DEFAULT;
+
+ fonts_in_family->push_back(desc);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_win.cc b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_win.cc
new file mode 100644
index 00000000000..e5cdcc6b01f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_truetype_font_list_win.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 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 "content/browser/renderer_host/pepper/pepper_truetype_font_list.h"
+
+#include <windows.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_hdc.h"
+#include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/proxy/serialized_structs.h"
+
+namespace content {
+
+namespace {
+
+typedef std::vector<std::string> FontFamilyList;
+typedef std::vector<ppapi::proxy::SerializedTrueTypeFontDesc> FontDescList;
+
+static int CALLBACK EnumFontFamiliesProc(ENUMLOGFONTEXW* logical_font,
+ NEWTEXTMETRICEXW* physical_font,
+ DWORD font_type,
+ LPARAM lparam) {
+ FontFamilyList* font_families = reinterpret_cast<FontFamilyList*>(lparam);
+ if (font_families) {
+ const LOGFONTW& lf = logical_font->elfLogFont;
+ if (lf.lfFaceName[0] && lf.lfFaceName[0] != '@' &&
+ lf.lfOutPrecision == OUT_STROKE_PRECIS) { // Outline fonts only.
+ std::string face_name(UTF16ToUTF8(lf.lfFaceName));
+ font_families->push_back(face_name);
+ }
+ }
+ return 1;
+}
+
+static int CALLBACK EnumFontsInFamilyProc(ENUMLOGFONTEXW* logical_font,
+ NEWTEXTMETRICEXW* physical_font,
+ DWORD font_type,
+ LPARAM lparam) {
+ FontDescList* fonts_in_family = reinterpret_cast<FontDescList*>(lparam);
+ if (fonts_in_family) {
+ const LOGFONTW& lf = logical_font->elfLogFont;
+ if (lf.lfFaceName[0] && lf.lfFaceName[0] != '@' &&
+ lf.lfOutPrecision == OUT_STROKE_PRECIS) { // Outline fonts only.
+ ppapi::proxy::SerializedTrueTypeFontDesc desc;
+ desc.family = UTF16ToUTF8(lf.lfFaceName);
+ if (lf.lfItalic)
+ desc.style = PP_TRUETYPEFONTSTYLE_ITALIC;
+ desc.weight = static_cast<PP_TrueTypeFontWeight_Dev>(lf.lfWeight);
+ desc.width = PP_TRUETYPEFONTWIDTH_NORMAL; // TODO(bbudge) support widths.
+ desc.charset =
+ static_cast<PP_TrueTypeFontCharset_Dev>(lf.lfCharSet);
+ fonts_in_family->push_back(desc);
+ }
+ }
+ return 1;
+}
+
+} // namespace
+
+void GetFontFamilies_SlowBlocking(FontFamilyList* font_families) {
+ LOGFONTW logfont;
+ memset(&logfont, 0, sizeof(logfont));
+ logfont.lfCharSet = DEFAULT_CHARSET;
+ base::win::ScopedCreateDC hdc(::CreateCompatibleDC(NULL));
+ ::EnumFontFamiliesExW(hdc, &logfont, (FONTENUMPROCW)&EnumFontFamiliesProc,
+ (LPARAM)font_families, 0);
+}
+
+void GetFontsInFamily_SlowBlocking(const std::string& family,
+ FontDescList* fonts_in_family) {
+ LOGFONTW logfont;
+ memset(&logfont, 0, sizeof(logfont));
+ logfont.lfCharSet = DEFAULT_CHARSET;
+ string16 family16 = UTF8ToUTF16(family);
+ memcpy(&logfont.lfFaceName, &family16[0], sizeof(logfont.lfFaceName));
+ base::win::ScopedCreateDC hdc(::CreateCompatibleDC(NULL));
+ ::EnumFontFamiliesExW(hdc, &logfont, (FONTENUMPROCW)&EnumFontsInFamilyProc,
+ (LPARAM)fonts_in_family, 0);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.cc b/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.cc
new file mode 100644
index 00000000000..7453fd2886a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.cc
@@ -0,0 +1,461 @@
+// 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 "content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h"
+
+#include <cstring>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/socket_permission_request.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/udp/udp_server_socket.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/error_conversion.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/udp_socket_resource_base.h"
+#include "ppapi/shared_impl/private/net_address_private_impl.h"
+#include "ppapi/shared_impl/socket_option_data.h"
+
+using ppapi::host::NetErrorToPepperError;
+using ppapi::NetAddressPrivateImpl;
+
+namespace {
+
+size_t g_num_instances = 0;
+
+} // namespace
+
+namespace content {
+
+PepperUDPSocketMessageFilter::PepperUDPSocketMessageFilter(
+ BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api)
+ : allow_address_reuse_(false),
+ allow_broadcast_(false),
+ closed_(false),
+ external_plugin_(host->external_plugin()),
+ private_api_(private_api),
+ render_process_id_(0),
+ render_view_id_(0) {
+ ++g_num_instances;
+ DCHECK(host);
+
+ if (!host->GetRenderViewIDsForInstance(instance,
+ &render_process_id_,
+ &render_view_id_)) {
+ NOTREACHED();
+ }
+}
+
+PepperUDPSocketMessageFilter::~PepperUDPSocketMessageFilter() {
+ Close();
+ --g_num_instances;
+}
+
+// static
+size_t PepperUDPSocketMessageFilter::GetNumInstances() {
+ return g_num_instances;
+}
+
+scoped_refptr<base::TaskRunner>
+PepperUDPSocketMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ switch (message.type()) {
+ case PpapiHostMsg_UDPSocket_SetOption::ID:
+ case PpapiHostMsg_UDPSocket_RecvFrom::ID:
+ case PpapiHostMsg_UDPSocket_Close::ID:
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
+ case PpapiHostMsg_UDPSocket_Bind::ID:
+ case PpapiHostMsg_UDPSocket_SendTo::ID:
+ return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
+ }
+ return NULL;
+}
+
+int32_t PepperUDPSocketMessageFilter::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ IPC_BEGIN_MESSAGE_MAP(PepperUDPSocketMessageFilter, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_UDPSocket_SetOption, OnMsgSetOption)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_UDPSocket_Bind, OnMsgBind)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_UDPSocket_RecvFrom, OnMsgRecvFrom)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_UDPSocket_SendTo, OnMsgSendTo)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_UDPSocket_Close, OnMsgClose)
+ IPC_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperUDPSocketMessageFilter::OnMsgSetOption(
+ const ppapi::host::HostMessageContext* context,
+ PP_UDPSocket_Option name,
+ const ppapi::SocketOptionData& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (closed_)
+ return PP_ERROR_FAILED;
+
+ switch (name) {
+ case PP_UDPSOCKET_OPTION_ADDRESS_REUSE:
+ case PP_UDPSOCKET_OPTION_BROADCAST: {
+ if (socket_.get()) {
+ // They only take effect before the socket is bound.
+ return PP_ERROR_FAILED;
+ }
+
+ bool boolean_value = false;
+ if (!value.GetBool(&boolean_value))
+ return PP_ERROR_BADARGUMENT;
+
+ if (name == PP_UDPSOCKET_OPTION_ADDRESS_REUSE)
+ allow_address_reuse_ = boolean_value;
+ else
+ allow_broadcast_ = boolean_value;
+ return PP_OK;
+ }
+ case PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE:
+ case PP_UDPSOCKET_OPTION_RECV_BUFFER_SIZE: {
+ if (!socket_.get()) {
+ // They only take effect after the socket is bound.
+ return PP_ERROR_FAILED;
+ }
+ int32_t integer_value = 0;
+ if (!value.GetInt32(&integer_value) || integer_value <= 0)
+ return PP_ERROR_BADARGUMENT;
+
+ bool result = false;
+ if (name == PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE) {
+ if (integer_value >
+ ppapi::proxy::UDPSocketResourceBase::kMaxSendBufferSize) {
+ return PP_ERROR_BADARGUMENT;
+ }
+ result = socket_->SetSendBufferSize(integer_value);
+ } else {
+ if (integer_value >
+ ppapi::proxy::UDPSocketResourceBase::kMaxReceiveBufferSize) {
+ return PP_ERROR_BADARGUMENT;
+ }
+ result = socket_->SetReceiveBufferSize(integer_value);
+ }
+ return result ? PP_OK : PP_ERROR_FAILED;
+ }
+ default: {
+ NOTREACHED();
+ return PP_ERROR_BADARGUMENT;
+ }
+ }
+}
+
+int32_t PepperUDPSocketMessageFilter::OnMsgBind(
+ const ppapi::host::HostMessageContext* context,
+ const PP_NetAddress_Private& addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(context);
+
+ SocketPermissionRequest request =
+ pepper_socket_utils::CreateSocketPermissionRequest(
+ SocketPermissionRequest::UDP_BIND, addr);
+ if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_, private_api_,
+ request, render_process_id_,
+ render_view_id_)) {
+ return PP_ERROR_NOACCESS;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PepperUDPSocketMessageFilter::DoBind, this,
+ context->MakeReplyMessageContext(),
+ addr));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperUDPSocketMessageFilter::OnMsgRecvFrom(
+ const ppapi::host::HostMessageContext* context,
+ int32_t num_bytes) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(context);
+ DCHECK(socket_.get());
+
+ if (closed_ || !socket_.get())
+ return PP_ERROR_FAILED;
+
+ if (recvfrom_buffer_.get())
+ return PP_ERROR_INPROGRESS;
+
+ if (num_bytes <= 0 ||
+ num_bytes > ppapi::proxy::UDPSocketResourceBase::kMaxReadSize) {
+ // |num_bytes| value is checked on the plugin side.
+ NOTREACHED();
+ return PP_ERROR_BADARGUMENT;
+ }
+
+ recvfrom_buffer_ = new net::IOBuffer(num_bytes);
+
+ // Use base::Unretained(this), so that the lifespan of this object doesn't
+ // have to last until the callback is called.
+ // It is safe to do so because |socket_| is owned by this object. If this
+ // object gets destroyed (and so does |socket_|), the callback won't be
+ // called.
+ int net_result = socket_->RecvFrom(
+ recvfrom_buffer_.get(),
+ num_bytes,
+ &recvfrom_address_,
+ base::Bind(&PepperUDPSocketMessageFilter::OnRecvFromCompleted,
+ base::Unretained(this),
+ context->MakeReplyMessageContext()));
+ if (net_result != net::ERR_IO_PENDING)
+ OnRecvFromCompleted(context->MakeReplyMessageContext(), net_result);
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperUDPSocketMessageFilter::OnMsgSendTo(
+ const ppapi::host::HostMessageContext* context,
+ const std::string& data,
+ const PP_NetAddress_Private& addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(context);
+
+ SocketPermissionRequest request =
+ pepper_socket_utils::CreateSocketPermissionRequest(
+ SocketPermissionRequest::UDP_SEND_TO, addr);
+ if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_, private_api_,
+ request, render_process_id_,
+ render_view_id_)) {
+ return PP_ERROR_NOACCESS;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&PepperUDPSocketMessageFilter::DoSendTo, this,
+ context->MakeReplyMessageContext(), data, addr));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperUDPSocketMessageFilter::OnMsgClose(
+ const ppapi::host::HostMessageContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ Close();
+ return PP_OK;
+}
+
+void PepperUDPSocketMessageFilter::DoBind(
+ const ppapi::host::ReplyMessageContext& context,
+ const PP_NetAddress_Private& addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (closed_ || socket_.get()) {
+ SendBindError(context, PP_ERROR_FAILED);
+ return;
+ }
+
+ scoped_ptr<net::UDPServerSocket> socket(new net::UDPServerSocket(
+ NULL, net::NetLog::Source()));
+
+ net::IPAddressNumber address;
+ int port;
+ if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
+ SendBindError(context, PP_ERROR_ADDRESS_INVALID);
+ return;
+ }
+
+ if (allow_address_reuse_)
+ socket->AllowAddressReuse();
+ if (allow_broadcast_)
+ socket->AllowBroadcast();
+
+ int32_t pp_result = NetErrorToPepperError(
+ socket->Listen(net::IPEndPoint(address, port)));
+ if (pp_result != PP_OK) {
+ SendBindError(context, pp_result);
+ return;
+ }
+
+ net::IPEndPoint bound_address;
+ pp_result = NetErrorToPepperError(socket->GetLocalAddress(&bound_address));
+ if (pp_result != PP_OK) {
+ SendBindError(context, pp_result);
+ return;
+ }
+
+ PP_NetAddress_Private net_address =
+ NetAddressPrivateImpl::kInvalidNetAddress;
+ if (!NetAddressPrivateImpl::IPEndPointToNetAddress(bound_address.address(),
+ bound_address.port(),
+ &net_address)) {
+ SendBindError(context, PP_ERROR_ADDRESS_INVALID);
+ return;
+ }
+
+ allow_address_reuse_ = false;
+ allow_broadcast_ = false;
+ socket_.swap(socket);
+ SendBindReply(context, PP_OK, net_address);
+}
+
+void PepperUDPSocketMessageFilter::DoSendTo(
+ const ppapi::host::ReplyMessageContext& context,
+ const std::string& data,
+ const PP_NetAddress_Private& addr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(socket_.get());
+
+ if (closed_ || !socket_.get()) {
+ SendSendToError(context, PP_ERROR_FAILED);
+ return;
+ }
+
+ if (sendto_buffer_.get()) {
+ SendSendToError(context, PP_ERROR_INPROGRESS);
+ return;
+ }
+
+ size_t num_bytes = data.size();
+ if (num_bytes == 0 ||
+ num_bytes > static_cast<size_t>(
+ ppapi::proxy::UDPSocketResourceBase::kMaxWriteSize)) {
+ // Size of |data| is checked on the plugin side.
+ NOTREACHED();
+ SendSendToError(context, PP_ERROR_BADARGUMENT);
+ return;
+ }
+
+ sendto_buffer_ = new net::IOBufferWithSize(num_bytes);
+ memcpy(sendto_buffer_->data(), data.data(), num_bytes);
+
+ net::IPAddressNumber address;
+ int port;
+ if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
+ SendSendToError(context, PP_ERROR_ADDRESS_INVALID);
+ return;
+ }
+
+ // Please see OnMsgRecvFrom() for the reason why we use base::Unretained(this)
+ // when calling |socket_| methods.
+ int net_result = socket_->SendTo(
+ sendto_buffer_.get(),
+ sendto_buffer_->size(),
+ net::IPEndPoint(address, port),
+ base::Bind(&PepperUDPSocketMessageFilter::OnSendToCompleted,
+ base::Unretained(this),
+ context));
+ if (net_result != net::ERR_IO_PENDING)
+ OnSendToCompleted(context, net_result);
+}
+
+void PepperUDPSocketMessageFilter::Close() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (socket_.get() && !closed_)
+ socket_->Close();
+ closed_ = true;
+}
+
+void PepperUDPSocketMessageFilter::OnRecvFromCompleted(
+ const ppapi::host::ReplyMessageContext& context,
+ int net_result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(recvfrom_buffer_.get());
+
+ int32_t pp_result = NetErrorToPepperError(net_result);
+
+ // Convert IPEndPoint we get back from RecvFrom to a PP_NetAddress_Private
+ // to send back.
+ PP_NetAddress_Private addr = NetAddressPrivateImpl::kInvalidNetAddress;
+ if (pp_result >= 0 &&
+ !NetAddressPrivateImpl::IPEndPointToNetAddress(
+ recvfrom_address_.address(), recvfrom_address_.port(), &addr)) {
+ pp_result = PP_ERROR_ADDRESS_INVALID;
+ }
+
+ if (pp_result >= 0) {
+ SendRecvFromReply(context, PP_OK,
+ std::string(recvfrom_buffer_->data(), pp_result), addr);
+ } else {
+ SendRecvFromError(context, pp_result);
+ }
+
+ recvfrom_buffer_ = NULL;
+}
+
+void PepperUDPSocketMessageFilter::OnSendToCompleted(
+ const ppapi::host::ReplyMessageContext& context,
+ int net_result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(sendto_buffer_.get());
+
+ int32_t pp_result = NetErrorToPepperError(net_result);
+ if (pp_result < 0)
+ SendSendToError(context, pp_result);
+ else
+ SendSendToReply(context, PP_OK, pp_result);
+ sendto_buffer_ = NULL;
+}
+
+void PepperUDPSocketMessageFilter::SendBindReply(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ const PP_NetAddress_Private& addr) {
+ ppapi::host::ReplyMessageContext reply_context(context);
+ reply_context.params.set_result(result);
+ SendReply(reply_context, PpapiPluginMsg_UDPSocket_BindReply(addr));
+}
+
+void PepperUDPSocketMessageFilter::SendRecvFromReply(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ const std::string& data,
+ const PP_NetAddress_Private& addr) {
+ ppapi::host::ReplyMessageContext reply_context(context);
+ reply_context.params.set_result(result);
+ SendReply(reply_context,
+ PpapiPluginMsg_UDPSocket_RecvFromReply(data, addr));
+}
+
+void PepperUDPSocketMessageFilter::SendSendToReply(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ int32_t bytes_written) {
+ ppapi::host::ReplyMessageContext reply_context(context);
+ reply_context.params.set_result(result);
+ SendReply(reply_context,
+ PpapiPluginMsg_UDPSocket_SendToReply(bytes_written));
+}
+
+void PepperUDPSocketMessageFilter::SendBindError(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result) {
+ SendBindReply(context, result, NetAddressPrivateImpl::kInvalidNetAddress);
+}
+
+void PepperUDPSocketMessageFilter::SendRecvFromError(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result) {
+ SendRecvFromReply(context,
+ result,
+ std::string(),
+ NetAddressPrivateImpl::kInvalidNetAddress);
+}
+
+void PepperUDPSocketMessageFilter::SendSendToError(
+ const ppapi::host::ReplyMessageContext& context,
+ int32_t result) {
+ SendSendToReply(context, result, 0);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h b/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h
new file mode 100644
index 00000000000..2df4ceec63c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h
@@ -0,0 +1,131 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_UDP_SOCKET_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_UDP_SOCKET_MESSAGE_FILTER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/common/process_type.h"
+#include "net/base/completion_callback.h"
+#include "net/base/ip_endpoint.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/ppb_udp_socket.h"
+#include "ppapi/host/resource_message_filter.h"
+
+struct PP_NetAddress_Private;
+
+namespace net {
+class IOBuffer;
+class IOBufferWithSize;
+class UDPServerSocket;
+}
+
+namespace ppapi {
+
+class SocketOptionData;
+
+namespace host {
+struct ReplyMessageContext;
+}
+}
+
+namespace content {
+
+class BrowserPpapiHostImpl;
+struct SocketPermissionRequest;
+
+class CONTENT_EXPORT PepperUDPSocketMessageFilter
+ : public ppapi::host::ResourceMessageFilter {
+ public:
+ PepperUDPSocketMessageFilter(BrowserPpapiHostImpl* host,
+ PP_Instance instance,
+ bool private_api);
+
+ static size_t GetNumInstances();
+
+ protected:
+ virtual ~PepperUDPSocketMessageFilter();
+
+ private:
+ // ppapi::host::ResourceMessageFilter overrides.
+ virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+ virtual int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) OVERRIDE;
+
+ int32_t OnMsgSetOption(
+ const ppapi::host::HostMessageContext* context,
+ PP_UDPSocket_Option name,
+ const ppapi::SocketOptionData& value);
+ int32_t OnMsgBind(const ppapi::host::HostMessageContext* context,
+ const PP_NetAddress_Private& addr);
+ int32_t OnMsgRecvFrom(const ppapi::host::HostMessageContext* context,
+ int32_t num_bytes);
+ int32_t OnMsgSendTo(const ppapi::host::HostMessageContext* context,
+ const std::string& data,
+ const PP_NetAddress_Private& addr);
+ int32_t OnMsgClose(const ppapi::host::HostMessageContext* context);
+
+ void DoBind(const ppapi::host::ReplyMessageContext& context,
+ const PP_NetAddress_Private& addr);
+ void DoSendTo(const ppapi::host::ReplyMessageContext& context,
+ const std::string& data,
+ const PP_NetAddress_Private& addr);
+ void Close();
+
+ void OnRecvFromCompleted(const ppapi::host::ReplyMessageContext& context,
+ int net_result);
+ void OnSendToCompleted(const ppapi::host::ReplyMessageContext& context,
+ int net_result);
+
+ void SendBindReply(const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ const PP_NetAddress_Private& addr);
+ void SendRecvFromReply(const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ const std::string& data,
+ const PP_NetAddress_Private& addr);
+ void SendSendToReply(const ppapi::host::ReplyMessageContext& context,
+ int32_t result,
+ int32_t bytes_written);
+
+ void SendBindError(const ppapi::host::ReplyMessageContext& context,
+ int32_t result);
+ void SendRecvFromError(const ppapi::host::ReplyMessageContext& context,
+ int32_t result);
+ void SendSendToError(const ppapi::host::ReplyMessageContext& context,
+ int32_t result);
+
+ bool allow_address_reuse_;
+ bool allow_broadcast_;
+
+ scoped_ptr<net::UDPServerSocket> socket_;
+ bool closed_;
+
+ scoped_refptr<net::IOBuffer> recvfrom_buffer_;
+ scoped_refptr<net::IOBufferWithSize> sendto_buffer_;
+
+ net::IPEndPoint recvfrom_address_;
+
+ bool external_plugin_;
+ bool private_api_;
+
+ int render_process_id_;
+ int render_view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperUDPSocketMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_UDP_SOCKET_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/popup_menu_helper_mac.h b/chromium/content/browser/renderer_host/popup_menu_helper_mac.h
new file mode 100644
index 00000000000..332fecdc54f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/popup_menu_helper_mac.h
@@ -0,0 +1,59 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_POPUP_MENU_HELPER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_POPUP_MENU_HELPER_MAC_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+class RenderViewHost;
+class RenderViewHostImpl;
+class RenderWidgetHostViewMac;
+struct MenuItem;
+
+class PopupMenuHelper : public NotificationObserver {
+ public:
+ // Creates a PopupMenuHelper that will notify |render_view_host| when a user
+ // selects or cancels the popup.
+ explicit PopupMenuHelper(RenderViewHost* render_view_host);
+
+ // Shows the popup menu and notifies the RenderViewHost of the selection/
+ // cancel.
+ // This call is blocking.
+ void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection);
+
+ // Immediately return from ShowPopupMenu.
+ CONTENT_EXPORT static void DontShowPopupMenuForTesting();
+
+ protected:
+ virtual RenderWidgetHostViewMac* GetRenderWidgetHostView() const;
+
+ // NotificationObserver implementation:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ NotificationRegistrar notification_registrar_;
+
+ RenderViewHostImpl* render_view_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(PopupMenuHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_POPUP_MENU_HELPER_MAC_H_
diff --git a/chromium/content/browser/renderer_host/popup_menu_helper_mac.mm b/chromium/content/browser/renderer_host/popup_menu_helper_mac.mm
new file mode 100644
index 00000000000..d5b9ed876d5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/popup_menu_helper_mac.mm
@@ -0,0 +1,112 @@
+// 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.
+
+#import <Carbon/Carbon.h>
+
+#include "content/browser/renderer_host/popup_menu_helper_mac.h"
+
+#include "base/mac/scoped_nsobject.h"
+#import "base/mac/scoped_sending_event.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/browser/renderer_host/webmenurunner_mac.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#import "ui/base/cocoa/base_view.h"
+
+namespace content {
+
+namespace {
+
+bool g_allow_showing_popup_menus = true;
+
+} // namespace
+
+PopupMenuHelper::PopupMenuHelper(RenderViewHost* render_view_host)
+ : render_view_host_(static_cast<RenderViewHostImpl*>(render_view_host)) {
+ notification_registrar_.Add(
+ this, NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(render_view_host));
+}
+
+void PopupMenuHelper::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ // Only single selection list boxes show a popup on Mac.
+ DCHECK(!allow_multiple_selection);
+
+ if (!g_allow_showing_popup_menus)
+ return;
+
+ // Retain the Cocoa view for the duration of the pop-up so that it can't be
+ // dealloced if my Destroy() method is called while the pop-up's up (which
+ // would in turn delete me, causing a crash once the -runMenuInView
+ // call returns. That's what was happening in <http://crbug.com/33250>).
+ RenderWidgetHostViewMac* rwhvm =
+ static_cast<RenderWidgetHostViewMac*>(GetRenderWidgetHostView());
+ base::scoped_nsobject<RenderWidgetHostViewCocoa> cocoa_view(
+ [rwhvm->cocoa_view() retain]);
+
+ // Display the menu.
+ base::scoped_nsobject<WebMenuRunner> menu_runner;
+ menu_runner.reset([[WebMenuRunner alloc] initWithItems:items
+ fontSize:item_font_size
+ rightAligned:right_aligned]);
+
+ {
+ // Make sure events can be pumped while the menu is up.
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+
+ // One of the events that could be pumped is |window.close()|.
+ // User-initiated event-tracking loops protect against this by
+ // setting flags in -[CrApplication sendEvent:], but since
+ // web-content menus are initiated by IPC message the setup has to
+ // be done manually.
+ base::mac::ScopedSendingEvent sending_event_scoper;
+
+ // Now run a SYNCHRONOUS NESTED EVENT LOOP until the pop-up is finished.
+ [menu_runner runMenuInView:cocoa_view
+ withBounds:[cocoa_view flipRectToNSRect:bounds]
+ initialIndex:selected_item];
+ }
+
+ if (!render_view_host_) {
+ // Bad news, the RenderViewHost got deleted while we were off running the
+ // menu. Nothing to do.
+ return;
+ }
+
+ if ([menu_runner menuItemWasChosen]) {
+ render_view_host_->DidSelectPopupMenuItem(
+ [menu_runner indexOfSelectedItem]);
+ } else {
+ render_view_host_->DidCancelPopupMenu();
+ }
+}
+
+// static
+void PopupMenuHelper::DontShowPopupMenuForTesting() {
+ g_allow_showing_popup_menus = false;
+}
+
+RenderWidgetHostViewMac* PopupMenuHelper::GetRenderWidgetHostView() const {
+ return static_cast<RenderWidgetHostViewMac*>(render_view_host_->GetView());
+}
+
+void PopupMenuHelper::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED);
+ DCHECK(Source<RenderWidgetHost>(source).ptr() == render_view_host_);
+ render_view_host_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_frame_host_impl.cc b/chromium/content/browser/renderer_host/render_frame_host_impl.cc
new file mode 100644
index 00000000000..ea511d0b8c3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_frame_host_impl.cc
@@ -0,0 +1,34 @@
+// 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 "content/browser/renderer_host/render_frame_host_impl.h"
+
+#include "content/browser/renderer_host/render_view_host_impl.h"
+
+namespace content {
+
+RenderFrameHostImpl::RenderFrameHostImpl(
+ RenderViewHostImpl* render_view_host,
+ int routing_id,
+ bool swapped_out)
+ : render_view_host_(render_view_host),
+ routing_id_(routing_id) {
+}
+
+RenderFrameHostImpl::~RenderFrameHostImpl() {
+}
+
+bool RenderFrameHostImpl::Send(IPC::Message* message) {
+ // Use the RenderViewHost object to send the message. It inherits it from
+ // RenderWidgetHost, which ultimately uses the current process's |Send|.
+ return render_view_host_->Send(message);
+}
+
+bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message &msg) {
+ // Pass the message up to the RenderViewHost, until we have enough
+ // infrastructure to start processing messages in this object.
+ return render_view_host_->OnMessageReceived(msg);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_frame_host_impl.h b/chromium/content/browser/renderer_host/render_frame_host_impl.h
new file mode 100644
index 00000000000..bb94c8e63c2
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_frame_host_impl.h
@@ -0,0 +1,41 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace content {
+
+class RenderViewHostImpl;
+
+class CONTENT_EXPORT RenderFrameHostImpl : public RenderFrameHost {
+ public:
+ RenderFrameHostImpl(
+ RenderViewHostImpl* render_view_host,
+ int routing_id,
+ bool swapped_out);
+ virtual ~RenderFrameHostImpl();
+
+ // IPC::Sender
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // IPC::Listener
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+
+ int routing_id() { return routing_id_; }
+
+ private:
+ RenderViewHostImpl* render_view_host_;
+
+ int routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_
diff --git a/chromium/content/browser/renderer_host/render_message_filter.cc b/chromium/content/browser/renderer_host/render_message_filter.cc
new file mode 100644
index 00000000000..fc84eabf2bd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_message_filter.cc
@@ -0,0 +1,1170 @@
+// 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 "content/browser/renderer_host/render_message_filter.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/worker_pool.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/media/media_internals.h"
+#include "content/browser/plugin_process_host.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/browser/renderer_host/pepper/pepper_security_helper.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/child_process_messages.h"
+#include "content/common/cookie_data.h"
+#include "content/common/desktop_notification_messages.h"
+#include "content/common/media/media_param_traits.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/download_save_info.h"
+#include "content/public/browser/plugin_service_filter.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/context_menu_params.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/common/webplugininfo.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_platform_file.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/media_log_event.h"
+#include "net/base/io_buffer.h"
+#include "net/base/keygen_handler.h"
+#include "net/base/mime_util.h"
+#include "net/base/request_priority.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_cache.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ppapi/shared_impl/file_type_conversion.h"
+#include "third_party/WebKit/public/web/WebNotificationPresenter.h"
+#include "ui/gfx/color_profile.h"
+
+#if defined(OS_MACOSX)
+#include "content/common/mac/font_descriptor.h"
+#else
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#endif
+#if defined(OS_POSIX)
+#include "base/file_descriptor_posix.h"
+#endif
+#if defined(OS_WIN)
+#include "content/browser/renderer_host/backing_store_win.h"
+#include "content/common/font_cache_dispatcher_win.h"
+#endif
+#if defined(OS_ANDROID)
+#include "media/base/android/webaudio_media_codec_bridge.h"
+#endif
+
+using net::CookieStore;
+
+namespace content {
+namespace {
+
+const int kPluginsRefreshThresholdInSeconds = 3;
+
+// When two CPU usage queries arrive within this interval, we sample the CPU
+// usage only once and send it as a response for both queries.
+static const int64 kCPUUsageSampleIntervalMs = 900;
+
+// On Windows, |g_color_profile| can run on an arbitrary background thread.
+// We avoid races by using LazyInstance's constructor lock to initialize the
+// object.
+base::LazyInstance<gfx::ColorProfile>::Leaky g_color_profile =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Common functionality for converting a sync renderer message to a callback
+// function in the browser. Derive from this, create it on the heap when
+// issuing your callback. When done, write your reply parameters into
+// reply_msg(), and then call SendReplyAndDeleteThis().
+class RenderMessageCompletionCallback {
+ public:
+ RenderMessageCompletionCallback(RenderMessageFilter* filter,
+ IPC::Message* reply_msg)
+ : filter_(filter),
+ reply_msg_(reply_msg) {
+ }
+
+ virtual ~RenderMessageCompletionCallback() {
+ }
+
+ RenderMessageFilter* filter() { return filter_.get(); }
+ IPC::Message* reply_msg() { return reply_msg_; }
+
+ void SendReplyAndDeleteThis() {
+ filter_->Send(reply_msg_);
+ delete this;
+ }
+
+ private:
+ scoped_refptr<RenderMessageFilter> filter_;
+ IPC::Message* reply_msg_;
+};
+
+class OpenChannelToPpapiPluginCallback
+ : public RenderMessageCompletionCallback,
+ public PpapiPluginProcessHost::PluginClient {
+ public:
+ OpenChannelToPpapiPluginCallback(RenderMessageFilter* filter,
+ ResourceContext* context,
+ IPC::Message* reply_msg)
+ : RenderMessageCompletionCallback(filter, reply_msg),
+ context_(context) {
+ }
+
+ virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
+ int* renderer_id) OVERRIDE {
+ *renderer_handle = filter()->PeerHandle();
+ *renderer_id = filter()->render_process_id();
+ }
+
+ virtual void OnPpapiChannelOpened(const IPC::ChannelHandle& channel_handle,
+ base::ProcessId plugin_pid,
+ int plugin_child_id) OVERRIDE {
+ ViewHostMsg_OpenChannelToPepperPlugin::WriteReplyParams(
+ reply_msg(), channel_handle, plugin_pid, plugin_child_id);
+ SendReplyAndDeleteThis();
+ }
+
+ virtual bool OffTheRecord() OVERRIDE {
+ return filter()->OffTheRecord();
+ }
+
+ virtual ResourceContext* GetResourceContext() OVERRIDE {
+ return context_;
+ }
+
+ private:
+ ResourceContext* context_;
+};
+
+class OpenChannelToPpapiBrokerCallback
+ : public PpapiPluginProcessHost::BrokerClient {
+ public:
+ OpenChannelToPpapiBrokerCallback(RenderMessageFilter* filter,
+ int routing_id)
+ : filter_(filter),
+ routing_id_(routing_id) {
+ }
+
+ virtual ~OpenChannelToPpapiBrokerCallback() {}
+
+ virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
+ int* renderer_id) OVERRIDE {
+ *renderer_handle = filter_->PeerHandle();
+ *renderer_id = filter_->render_process_id();
+ }
+
+ virtual void OnPpapiChannelOpened(const IPC::ChannelHandle& channel_handle,
+ base::ProcessId plugin_pid,
+ int /* plugin_child_id */) OVERRIDE {
+ filter_->Send(new ViewMsg_PpapiBrokerChannelCreated(routing_id_,
+ plugin_pid,
+ channel_handle));
+ delete this;
+ }
+
+ virtual bool OffTheRecord() OVERRIDE {
+ return filter_->OffTheRecord();
+ }
+
+ private:
+ scoped_refptr<RenderMessageFilter> filter_;
+ int routing_id_;
+};
+
+} // namespace
+
+class RenderMessageFilter::OpenChannelToNpapiPluginCallback
+ : public RenderMessageCompletionCallback,
+ public PluginProcessHost::Client {
+ public:
+ OpenChannelToNpapiPluginCallback(RenderMessageFilter* filter,
+ ResourceContext* context,
+ IPC::Message* reply_msg)
+ : RenderMessageCompletionCallback(filter, reply_msg),
+ context_(context),
+ host_(NULL),
+ sent_plugin_channel_request_(false) {
+ }
+
+ virtual int ID() OVERRIDE {
+ return filter()->render_process_id();
+ }
+
+ virtual ResourceContext* GetResourceContext() OVERRIDE {
+ return context_;
+ }
+
+ virtual bool OffTheRecord() OVERRIDE {
+ if (filter()->OffTheRecord())
+ return true;
+ if (GetContentClient()->browser()->AllowSaveLocalState(context_))
+ return false;
+
+ // For now, only disallow storing data for Flash <http://crbug.com/97319>.
+ for (size_t i = 0; i < info_.mime_types.size(); ++i) {
+ if (info_.mime_types[i].mime_type == kFlashPluginSwfMimeType)
+ return true;
+ }
+ return false;
+ }
+
+ virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
+ info_ = info;
+ }
+
+ virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
+ DCHECK(host);
+ host_ = host;
+ }
+
+ virtual void OnSentPluginChannelRequest() OVERRIDE {
+ sent_plugin_channel_request_ = true;
+ }
+
+ virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
+ WriteReplyAndDeleteThis(handle);
+ }
+
+ virtual void OnError() OVERRIDE {
+ WriteReplyAndDeleteThis(IPC::ChannelHandle());
+ }
+
+ PluginProcessHost* host() const {
+ return host_;
+ }
+
+ bool sent_plugin_channel_request() const {
+ return sent_plugin_channel_request_;
+ }
+
+ void Cancel() {
+ delete this;
+ }
+
+ private:
+ void WriteReplyAndDeleteThis(const IPC::ChannelHandle& handle) {
+ ViewHostMsg_OpenChannelToPlugin::WriteReplyParams(reply_msg(),
+ handle, info_);
+ filter()->OnCompletedOpenChannelToNpapiPlugin(this);
+ SendReplyAndDeleteThis();
+ }
+
+ ResourceContext* context_;
+ WebPluginInfo info_;
+ PluginProcessHost* host_;
+ bool sent_plugin_channel_request_;
+};
+
+RenderMessageFilter::RenderMessageFilter(
+ int render_process_id,
+ bool is_guest,
+ PluginServiceImpl* plugin_service,
+ BrowserContext* browser_context,
+ net::URLRequestContextGetter* request_context,
+ RenderWidgetHelper* render_widget_helper,
+ media::AudioManager* audio_manager,
+ MediaInternals* media_internals,
+ DOMStorageContextWrapper* dom_storage_context)
+ : resource_dispatcher_host_(ResourceDispatcherHostImpl::Get()),
+ plugin_service_(plugin_service),
+ profile_data_directory_(browser_context->GetPath()),
+ request_context_(request_context),
+ resource_context_(browser_context->GetResourceContext()),
+ render_widget_helper_(render_widget_helper),
+ incognito_(browser_context->IsOffTheRecord()),
+ dom_storage_context_(dom_storage_context),
+ render_process_id_(render_process_id),
+ is_guest_(is_guest),
+ cpu_usage_(0),
+ audio_manager_(audio_manager),
+ media_internals_(media_internals) {
+ DCHECK(request_context_.get());
+
+ render_widget_helper_->Init(render_process_id_, resource_dispatcher_host_);
+}
+
+RenderMessageFilter::~RenderMessageFilter() {
+ // This function should be called on the IO thread.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(plugin_host_clients_.empty());
+}
+
+void RenderMessageFilter::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+#if defined(ENABLE_PLUGINS)
+ for (std::set<OpenChannelToNpapiPluginCallback*>::iterator it =
+ plugin_host_clients_.begin(); it != plugin_host_clients_.end(); ++it) {
+ OpenChannelToNpapiPluginCallback* client = *it;
+ if (client->host()) {
+ if (client->sent_plugin_channel_request()) {
+ client->host()->CancelSentRequest(client);
+ } else {
+ client->host()->CancelPendingRequest(client);
+ }
+ } else {
+ plugin_service_->CancelOpenChannelToNpapiPlugin(client);
+ }
+ client->Cancel();
+ }
+#endif // defined(ENABLE_PLUGINS)
+ plugin_host_clients_.clear();
+}
+
+void RenderMessageFilter::OnChannelConnected(int32 peer_id) {
+ BrowserMessageFilter::OnChannelConnected(peer_id);
+ base::ProcessHandle handle = PeerHandle();
+#if defined(OS_MACOSX)
+ process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle,
+ NULL));
+#else
+ process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
+#endif
+ cpu_usage_ = process_metrics_->GetCPUUsage(); // Initialize CPU usage counters
+ cpu_usage_sample_time_ = base::TimeTicks::Now();
+}
+
+bool RenderMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderMessageFilter, message, *message_was_ok)
+#if defined(OS_WIN)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PreCacheFontCharacters,
+ OnPreCacheFontCharacters)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetProcessMemorySizes,
+ OnGetProcessMemorySizes)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GenerateRoutingID, OnGenerateRoutingID)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWindow, OnCreateWindow)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnCreateWidget)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreateFullscreenWidget,
+ OnCreateFullscreenWidget)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetCookie, OnSetCookie)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetCookies, OnGetCookies)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetRawCookies, OnGetRawCookies)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DeleteCookie, OnDeleteCookie)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CookiesEnabled, OnCookiesEnabled)
+#if defined(OS_MACOSX)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_LoadFont, OnLoadFont)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DownloadUrl, OnDownloadUrl)
+#if defined(ENABLE_PLUGINS)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetPlugins, OnGetPlugins)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetPluginInfo, OnGetPluginInfo)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPlugin,
+ OnOpenChannelToPlugin)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPepperPlugin,
+ OnOpenChannelToPepperPlugin)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidCreateOutOfProcessPepperInstance,
+ OnDidCreateOutOfProcessPepperInstance)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidDeleteOutOfProcessPepperInstance,
+ OnDidDeleteOutOfProcessPepperInstance)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_OpenChannelToPpapiBroker,
+ OnOpenChannelToPpapiBroker)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_AsyncOpenPepperFile, OnAsyncOpenPepperFile)
+#endif
+ IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_UpdateRect,
+ render_widget_helper_->DidReceiveBackingStoreMsg(message))
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateIsDelayed, OnUpdateIsDelayed)
+ IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_CheckPermission,
+ OnCheckNotificationPermission)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_SyncAllocateSharedMemory,
+ OnAllocateSharedMemory)
+#if defined(OS_POSIX) && !defined(TOOLKIT_GTK) && !defined(OS_ANDROID)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_AllocTransportDIB, OnAllocTransportDIB)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FreeTransportDIB, OnFreeTransportDIB)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidGenerateCacheableMetadata,
+ OnCacheableMetadataAvailable)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_Keygen, OnKeygen)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetCPUUsage, OnGetCPUUsage)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetAudioHardwareConfig,
+ OnGetAudioHardwareConfig)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetMonitorColorProfile,
+ OnGetMonitorColorProfile)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_MediaLogEvents, OnMediaLogEvents)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Are3DAPIsBlocked, OnAre3DAPIsBlocked)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidLose3DContext, OnDidLose3DContext)
+#if defined(OS_ANDROID)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RunWebAudioMediaCodec, OnWebAudioMediaCodec)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void RenderMessageFilter::OnDestruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+base::TaskRunner* RenderMessageFilter::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+#if defined(OS_WIN)
+ // Windows monitor profile must be read from a file.
+ if (message.type() == ViewHostMsg_GetMonitorColorProfile::ID)
+ return BrowserThread::GetBlockingPool();
+#endif
+#if defined(OS_MACOSX)
+ // OSX CoreAudio calls must all happen on the main thread.
+ if (message.type() == ViewHostMsg_GetAudioHardwareConfig::ID)
+ return audio_manager_->GetMessageLoop().get();
+#endif
+ if (message.type() == ViewHostMsg_AsyncOpenPepperFile::ID)
+ return BrowserThread::GetBlockingPool();
+ return NULL;
+}
+
+bool RenderMessageFilter::OffTheRecord() const {
+ return incognito_;
+}
+
+void RenderMessageFilter::OnCreateWindow(
+ const ViewHostMsg_CreateWindow_Params& params,
+ int* route_id,
+ int* main_frame_route_id,
+ int* surface_id,
+ int64* cloned_session_storage_namespace_id) {
+ bool no_javascript_access;
+ bool can_create_window =
+ GetContentClient()->browser()->CanCreateWindow(
+ params.opener_url,
+ params.opener_security_origin,
+ params.window_container_type,
+ params.target_url,
+ params.referrer,
+ params.disposition,
+ params.features,
+ params.user_gesture,
+ params.opener_suppressed,
+ resource_context_,
+ render_process_id_,
+ is_guest_,
+ params.opener_id,
+ &no_javascript_access);
+
+ if (!can_create_window) {
+ *route_id = MSG_ROUTING_NONE;
+ *main_frame_route_id = MSG_ROUTING_NONE;
+ *surface_id = 0;
+ return;
+ }
+
+ // This will clone the sessionStorage for namespace_id_to_clone.
+ scoped_refptr<SessionStorageNamespaceImpl> cloned_namespace =
+ new SessionStorageNamespaceImpl(dom_storage_context_.get(),
+ params.session_storage_namespace_id);
+ *cloned_session_storage_namespace_id = cloned_namespace->id();
+
+ render_widget_helper_->CreateNewWindow(params,
+ no_javascript_access,
+ PeerHandle(),
+ route_id,
+ main_frame_route_id,
+ surface_id,
+ cloned_namespace.get());
+}
+
+void RenderMessageFilter::OnCreateWidget(int opener_id,
+ WebKit::WebPopupType popup_type,
+ int* route_id,
+ int* surface_id) {
+ render_widget_helper_->CreateNewWidget(
+ opener_id, popup_type, route_id, surface_id);
+}
+
+void RenderMessageFilter::OnCreateFullscreenWidget(int opener_id,
+ int* route_id,
+ int* surface_id) {
+ render_widget_helper_->CreateNewFullscreenWidget(
+ opener_id, route_id, surface_id);
+}
+
+void RenderMessageFilter::OnGetProcessMemorySizes(size_t* private_bytes,
+ size_t* shared_bytes) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ using base::ProcessMetrics;
+#if !defined(OS_MACOSX) || defined(OS_IOS)
+ scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics(
+ PeerHandle()));
+#else
+ scoped_ptr<ProcessMetrics> metrics(ProcessMetrics::CreateProcessMetrics(
+ PeerHandle(), content::BrowserChildProcessHost::GetPortProvider()));
+#endif
+ if (!metrics->GetMemoryBytes(private_bytes, shared_bytes)) {
+ *private_bytes = 0;
+ *shared_bytes = 0;
+ }
+}
+
+void RenderMessageFilter::OnSetCookie(const IPC::Message& message,
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ const std::string& cookie) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanAccessCookiesForOrigin(render_process_id_, url))
+ return;
+
+ net::CookieOptions options;
+ if (GetContentClient()->browser()->AllowSetCookie(
+ url, first_party_for_cookies, cookie,
+ resource_context_, render_process_id_, message.routing_id(),
+ &options)) {
+ net::URLRequestContext* context = GetRequestContextForURL(url);
+ // Pass a null callback since we don't care about when the 'set' completes.
+ context->cookie_store()->SetCookieWithOptionsAsync(
+ url, cookie, options, net::CookieMonster::SetCookiesCallback());
+ }
+}
+
+void RenderMessageFilter::OnGetCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanAccessCookiesForOrigin(render_process_id_, url)) {
+ SendGetCookiesResponse(reply_msg, std::string());
+ return;
+ }
+
+ // If we crash here, figure out what URL the renderer was requesting.
+ // http://crbug.com/99242
+ char url_buf[128];
+ base::strlcpy(url_buf, url.spec().c_str(), arraysize(url_buf));
+ base::debug::Alias(url_buf);
+
+ net::URLRequestContext* context = GetRequestContextForURL(url);
+ net::CookieMonster* cookie_monster =
+ context->cookie_store()->GetCookieMonster();
+ cookie_monster->GetAllCookiesForURLAsync(
+ url, base::Bind(&RenderMessageFilter::CheckPolicyForCookies, this, url,
+ first_party_for_cookies, reply_msg));
+}
+
+void RenderMessageFilter::OnGetRawCookies(
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ // Only return raw cookies to trusted renderers or if this request is
+ // not targeted to an an external host like ChromeFrame.
+ // TODO(ananta) We need to support retreiving raw cookies from external
+ // hosts.
+ if (!policy->CanReadRawCookies(render_process_id_) ||
+ !policy->CanAccessCookiesForOrigin(render_process_id_, url)) {
+ SendGetRawCookiesResponse(reply_msg, net::CookieList());
+ return;
+ }
+
+ // We check policy here to avoid sending back cookies that would not normally
+ // be applied to outbound requests for the given URL. Since this cookie info
+ // is visible in the developer tools, it is helpful to make it match reality.
+ net::URLRequestContext* context = GetRequestContextForURL(url);
+ net::CookieMonster* cookie_monster =
+ context->cookie_store()->GetCookieMonster();
+ cookie_monster->GetAllCookiesForURLAsync(
+ url, base::Bind(&RenderMessageFilter::SendGetRawCookiesResponse,
+ this, reply_msg));
+}
+
+void RenderMessageFilter::OnDeleteCookie(const GURL& url,
+ const std::string& cookie_name) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanAccessCookiesForOrigin(render_process_id_, url))
+ return;
+
+ net::URLRequestContext* context = GetRequestContextForURL(url);
+ context->cookie_store()->DeleteCookieAsync(url, cookie_name, base::Closure());
+}
+
+void RenderMessageFilter::OnCookiesEnabled(
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ bool* cookies_enabled) {
+ // TODO(ananta): If this render view is associated with an automation channel,
+ // aka ChromeFrame then we need to retrieve cookie settings from the external
+ // host.
+ *cookies_enabled = GetContentClient()->browser()->AllowGetCookie(
+ url, first_party_for_cookies, net::CookieList(), resource_context_,
+ render_process_id_, MSG_ROUTING_CONTROL);
+}
+
+#if defined(OS_MACOSX)
+void RenderMessageFilter::OnLoadFont(const FontDescriptor& font,
+ IPC::Message* reply_msg) {
+ FontLoader::Result* result = new FontLoader::Result;
+
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&FontLoader::LoadFont, font, result),
+ base::Bind(&RenderMessageFilter::SendLoadFontReply, this, reply_msg,
+ base::Owned(result)));
+}
+
+void RenderMessageFilter::SendLoadFontReply(IPC::Message* reply,
+ FontLoader::Result* result) {
+ base::SharedMemoryHandle handle;
+ if (result->font_data_size == 0 || result->font_id == 0) {
+ result->font_data_size = 0;
+ result->font_id = 0;
+ handle = base::SharedMemory::NULLHandle();
+ } else {
+ result->font_data.GiveToProcess(base::GetCurrentProcessHandle(), &handle);
+ }
+ ViewHostMsg_LoadFont::WriteReplyParams(
+ reply, result->font_data_size, handle, result->font_id);
+ Send(reply);
+}
+#endif // OS_MACOSX
+
+void RenderMessageFilter::OnGetPlugins(
+ bool refresh,
+ IPC::Message* reply_msg) {
+ // Don't refresh if the specified threshold has not been passed. Note that
+ // this check is performed before off-loading to the file thread. The reason
+ // we do this is that some pages tend to request that the list of plugins be
+ // refreshed at an excessive rate. This instigates disk scanning, as the list
+ // is accumulated by doing multiple reads from disk. This effect is
+ // multiplied when we have several pages requesting this operation.
+ if (refresh) {
+ const base::TimeDelta threshold = base::TimeDelta::FromSeconds(
+ kPluginsRefreshThresholdInSeconds);
+ const base::TimeTicks now = base::TimeTicks::Now();
+ if (now - last_plugin_refresh_time_ >= threshold) {
+ // Only refresh if the threshold hasn't been exceeded yet.
+ PluginServiceImpl::GetInstance()->RefreshPlugins();
+ last_plugin_refresh_time_ = now;
+ }
+ }
+
+ PluginServiceImpl::GetInstance()->GetPlugins(
+ base::Bind(&RenderMessageFilter::GetPluginsCallback, this, reply_msg));
+}
+
+void RenderMessageFilter::GetPluginsCallback(
+ IPC::Message* reply_msg,
+ const std::vector<WebPluginInfo>& all_plugins) {
+ // Filter the plugin list.
+ PluginServiceFilter* filter = PluginServiceImpl::GetInstance()->GetFilter();
+ std::vector<WebPluginInfo> plugins;
+
+ int child_process_id = -1;
+ int routing_id = MSG_ROUTING_NONE;
+ for (size_t i = 0; i < all_plugins.size(); ++i) {
+ // Copy because the filter can mutate.
+ WebPluginInfo plugin(all_plugins[i]);
+ if (!filter || filter->IsPluginAvailable(child_process_id,
+ routing_id,
+ resource_context_,
+ GURL(),
+ GURL(),
+ &plugin)) {
+ plugins.push_back(plugin);
+ }
+ }
+
+ ViewHostMsg_GetPlugins::WriteReplyParams(reply_msg, plugins);
+ Send(reply_msg);
+}
+
+void RenderMessageFilter::OnGetPluginInfo(
+ int routing_id,
+ const GURL& url,
+ const GURL& page_url,
+ const std::string& mime_type,
+ bool* found,
+ WebPluginInfo* info,
+ std::string* actual_mime_type) {
+ bool allow_wildcard = true;
+ *found = plugin_service_->GetPluginInfo(
+ render_process_id_, routing_id, resource_context_,
+ url, page_url, mime_type, allow_wildcard,
+ NULL, info, actual_mime_type);
+}
+
+void RenderMessageFilter::OnOpenChannelToPlugin(int routing_id,
+ const GURL& url,
+ const GURL& policy_url,
+ const std::string& mime_type,
+ IPC::Message* reply_msg) {
+ OpenChannelToNpapiPluginCallback* client =
+ new OpenChannelToNpapiPluginCallback(this, resource_context_, reply_msg);
+ DCHECK(!ContainsKey(plugin_host_clients_, client));
+ plugin_host_clients_.insert(client);
+ plugin_service_->OpenChannelToNpapiPlugin(
+ render_process_id_, routing_id,
+ url, policy_url, mime_type, client);
+}
+
+void RenderMessageFilter::OnOpenChannelToPepperPlugin(
+ const base::FilePath& path,
+ IPC::Message* reply_msg) {
+ plugin_service_->OpenChannelToPpapiPlugin(
+ render_process_id_,
+ path,
+ profile_data_directory_,
+ new OpenChannelToPpapiPluginCallback(this, resource_context_, reply_msg));
+}
+
+void RenderMessageFilter::OnDidCreateOutOfProcessPepperInstance(
+ int plugin_child_id,
+ int32 pp_instance,
+ PepperRendererInstanceData instance_data,
+ bool is_external) {
+ // It's important that we supply the render process ID ourselves based on the
+ // channel the message arrived on. We use the
+ // PP_Instance -> (process id, view id)
+ // mapping to decide how to handle messages received from the (untrusted)
+ // plugin, so an exploited renderer must not be able to insert fake mappings
+ // that may allow it access to other render processes.
+ DCHECK_EQ(0, instance_data.render_process_id);
+ instance_data.render_process_id = render_process_id_;
+ if (is_external) {
+ // We provide the BrowserPpapiHost to the embedder, so it's safe to cast.
+ BrowserPpapiHostImpl* host = static_cast<BrowserPpapiHostImpl*>(
+ GetContentClient()->browser()->GetExternalBrowserPpapiHost(
+ plugin_child_id));
+ if (host)
+ host->AddInstance(pp_instance, instance_data);
+ } else {
+ PpapiPluginProcessHost::DidCreateOutOfProcessInstance(
+ plugin_child_id, pp_instance, instance_data);
+ }
+}
+
+void RenderMessageFilter::OnDidDeleteOutOfProcessPepperInstance(
+ int plugin_child_id,
+ int32 pp_instance,
+ bool is_external) {
+ if (is_external) {
+ // We provide the BrowserPpapiHost to the embedder, so it's safe to cast.
+ BrowserPpapiHostImpl* host = static_cast<BrowserPpapiHostImpl*>(
+ GetContentClient()->browser()->GetExternalBrowserPpapiHost(
+ plugin_child_id));
+ if (host)
+ host->DeleteInstance(pp_instance);
+ } else {
+ PpapiPluginProcessHost::DidDeleteOutOfProcessInstance(
+ plugin_child_id, pp_instance);
+ }
+}
+
+void RenderMessageFilter::OnOpenChannelToPpapiBroker(
+ int routing_id,
+ const base::FilePath& path) {
+ plugin_service_->OpenChannelToPpapiBroker(
+ render_process_id_,
+ path,
+ new OpenChannelToPpapiBrokerCallback(this, routing_id));
+}
+
+void RenderMessageFilter::OnGenerateRoutingID(int* route_id) {
+ *route_id = render_widget_helper_->GetNextRoutingID();
+}
+
+void RenderMessageFilter::OnGetCPUUsage(int* cpu_usage) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ int64 since_last_sample_ms = (now - cpu_usage_sample_time_).InMilliseconds();
+ if (since_last_sample_ms > kCPUUsageSampleIntervalMs) {
+ cpu_usage_sample_time_ = now;
+ cpu_usage_ = static_cast<int>(process_metrics_->GetCPUUsage());
+ }
+ *cpu_usage = cpu_usage_;
+}
+
+void RenderMessageFilter::OnGetAudioHardwareConfig(
+ media::AudioParameters* input_params,
+ media::AudioParameters* output_params) {
+ DCHECK(input_params);
+ DCHECK(output_params);
+ *output_params = audio_manager_->GetDefaultOutputStreamParameters();
+
+ // TODO(henrika): add support for all available input devices.
+ *input_params = audio_manager_->GetInputStreamParameters(
+ media::AudioManagerBase::kDefaultDeviceId);
+}
+
+void RenderMessageFilter::OnGetMonitorColorProfile(std::vector<char>* profile) {
+#if defined(OS_WIN)
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (BackingStoreWin::ColorManagementEnabled())
+ return;
+#endif
+ *profile = g_color_profile.Get().profile();
+}
+
+void RenderMessageFilter::OnDownloadUrl(const IPC::Message& message,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& suggested_name) {
+ scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo());
+ save_info->suggested_name = suggested_name;
+ scoped_ptr<net::URLRequest> request(
+ resource_context_->GetRequestContext()->CreateRequest(url, NULL));
+ RecordDownloadSource(INITIATED_BY_RENDERER);
+ resource_dispatcher_host_->BeginDownload(
+ request.Pass(),
+ referrer,
+ true, // is_content_initiated
+ resource_context_,
+ render_process_id_,
+ message.routing_id(),
+ false,
+ save_info.Pass(),
+ content::DownloadItem::kInvalidId,
+ ResourceDispatcherHostImpl::DownloadStartedCallback());
+}
+
+void RenderMessageFilter::OnCheckNotificationPermission(
+ const GURL& source_origin, int* result) {
+#if defined(ENABLE_NOTIFICATIONS)
+ *result = GetContentClient()->browser()->
+ CheckDesktopNotificationPermission(source_origin, resource_context_,
+ render_process_id_);
+#else
+ *result = WebKit::WebNotificationPresenter::PermissionAllowed;
+#endif
+}
+
+void RenderMessageFilter::OnAllocateSharedMemory(
+ uint32 buffer_size,
+ base::SharedMemoryHandle* handle) {
+ ChildProcessHostImpl::AllocateSharedMemory(
+ buffer_size, PeerHandle(), handle);
+}
+
+net::URLRequestContext* RenderMessageFilter::GetRequestContextForURL(
+ const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ net::URLRequestContext* context =
+ GetContentClient()->browser()->OverrideRequestContextForURL(
+ url, resource_context_);
+ if (!context)
+ context = request_context_->GetURLRequestContext();
+
+ return context;
+}
+
+#if defined(OS_POSIX) && !defined(TOOLKIT_GTK) && !defined(OS_ANDROID)
+void RenderMessageFilter::OnAllocTransportDIB(
+ uint32 size, bool cache_in_browser, TransportDIB::Handle* handle) {
+ render_widget_helper_->AllocTransportDIB(size, cache_in_browser, handle);
+}
+
+void RenderMessageFilter::OnFreeTransportDIB(
+ TransportDIB::Id dib_id) {
+ render_widget_helper_->FreeTransportDIB(dib_id);
+}
+#endif
+
+bool RenderMessageFilter::CheckPreparsedJsCachingEnabled() const {
+ static bool checked = false;
+ static bool result = false;
+ if (!checked) {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ result = command_line.HasSwitch(switches::kEnablePreparsedJsCaching);
+ checked = true;
+ }
+ return result;
+}
+
+void RenderMessageFilter::OnCacheableMetadataAvailable(
+ const GURL& url,
+ double expected_response_time,
+ const std::vector<char>& data) {
+ if (!CheckPreparsedJsCachingEnabled())
+ return;
+
+ net::HttpCache* cache = request_context_->GetURLRequestContext()->
+ http_transaction_factory()->GetCache();
+ DCHECK(cache);
+
+ // Use the same priority for the metadata write as for script
+ // resources (see defaultPriorityForResourceType() in WebKit's
+ // CachedResource.cpp). Note that WebURLRequest::PriorityMedium
+ // corresponds to net::LOW (see ConvertWebKitPriorityToNetPriority()
+ // in weburlloader_impl.cc).
+ const net::RequestPriority kPriority = net::LOW;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(data.size()));
+ memcpy(buf->data(), &data.front(), data.size());
+ cache->WriteMetadata(url,
+ kPriority,
+ base::Time::FromDoubleT(expected_response_time),
+ buf.get(),
+ data.size());
+}
+
+void RenderMessageFilter::OnKeygen(uint32 key_size_index,
+ const std::string& challenge_string,
+ const GURL& url,
+ IPC::Message* reply_msg) {
+ // Map displayed strings indicating level of keysecurity in the <keygen>
+ // menu to the key size in bits. (See SSLKeyGeneratorChromium.cpp in WebCore.)
+ int key_size_in_bits;
+ switch (key_size_index) {
+ case 0:
+ key_size_in_bits = 2048;
+ break;
+ case 1:
+ key_size_in_bits = 1024;
+ break;
+ default:
+ DCHECK(false) << "Illegal key_size_index " << key_size_index;
+ ViewHostMsg_Keygen::WriteReplyParams(reply_msg, std::string());
+ Send(reply_msg);
+ return;
+ }
+
+ VLOG(1) << "Dispatching keygen task to worker pool.";
+ // Dispatch to worker pool, so we do not block the IO thread.
+ if (!base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(
+ &RenderMessageFilter::OnKeygenOnWorkerThread, this,
+ key_size_in_bits, challenge_string, url, reply_msg),
+ true)) {
+ NOTREACHED() << "Failed to dispatch keygen task to worker pool";
+ ViewHostMsg_Keygen::WriteReplyParams(reply_msg, std::string());
+ Send(reply_msg);
+ return;
+ }
+}
+
+void RenderMessageFilter::OnKeygenOnWorkerThread(
+ int key_size_in_bits,
+ const std::string& challenge_string,
+ const GURL& url,
+ IPC::Message* reply_msg) {
+ DCHECK(reply_msg);
+
+ // Generate a signed public key and challenge, then send it back.
+ net::KeygenHandler keygen_handler(key_size_in_bits, challenge_string, url);
+
+#if defined(USE_NSS)
+ // Attach a password delegate so we can authenticate.
+ keygen_handler.set_crypto_module_password_delegate(
+ GetContentClient()->browser()->GetCryptoPasswordDelegate(url));
+#endif // defined(USE_NSS)
+
+ ViewHostMsg_Keygen::WriteReplyParams(
+ reply_msg,
+ keygen_handler.GenKeyAndSignChallenge());
+ Send(reply_msg);
+}
+
+void RenderMessageFilter::OnAsyncOpenPepperFile(int routing_id,
+ const base::FilePath& path,
+ int pp_open_flags,
+ int message_id) {
+ int platform_file_flags = 0;
+ if (!CanOpenWithPepperFlags(pp_open_flags, render_process_id_, path) ||
+ !ppapi::PepperFileOpenFlagsToPlatformFileFlags(
+ pp_open_flags, &platform_file_flags)) {
+ DLOG(ERROR) <<
+ "Bad pp_open_flags in ViewMsgHost_AsyncOpenPepperFile message: " <<
+ pp_open_flags;
+ RecordAction(UserMetricsAction("BadMessageTerminate_AOF"));
+ BadMessageReceived();
+ return;
+ }
+
+ base::PlatformFileError error_code = base::PLATFORM_FILE_OK;
+ base::PlatformFile file = base::CreatePlatformFile(
+ path, platform_file_flags, NULL, &error_code);
+ IPC::PlatformFileForTransit file_for_transit =
+ file != base::kInvalidPlatformFileValue ?
+ IPC::GetFileHandleForProcess(file, PeerHandle(), true) :
+ IPC::InvalidPlatformFileForTransit();
+
+ Send(new ViewMsg_AsyncOpenPepperFile_ACK(
+ routing_id, error_code, file_for_transit, message_id));
+}
+
+void RenderMessageFilter::OnMediaLogEvents(
+ const std::vector<media::MediaLogEvent>& events) {
+ if (media_internals_)
+ media_internals_->OnMediaEvents(render_process_id_, events);
+}
+
+void RenderMessageFilter::CheckPolicyForCookies(
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg,
+ const net::CookieList& cookie_list) {
+ net::URLRequestContext* context = GetRequestContextForURL(url);
+ // Check the policy for get cookies, and pass cookie_list to the
+ // TabSpecificContentSetting for logging purpose.
+ if (GetContentClient()->browser()->AllowGetCookie(
+ url, first_party_for_cookies, cookie_list, resource_context_,
+ render_process_id_, reply_msg->routing_id())) {
+ // Gets the cookies from cookie store if allowed.
+ context->cookie_store()->GetCookiesWithOptionsAsync(
+ url, net::CookieOptions(),
+ base::Bind(&RenderMessageFilter::SendGetCookiesResponse,
+ this, reply_msg));
+ } else {
+ SendGetCookiesResponse(reply_msg, std::string());
+ }
+}
+
+void RenderMessageFilter::SendGetCookiesResponse(IPC::Message* reply_msg,
+ const std::string& cookies) {
+ ViewHostMsg_GetCookies::WriteReplyParams(reply_msg, cookies);
+ Send(reply_msg);
+}
+
+void RenderMessageFilter::SendGetRawCookiesResponse(
+ IPC::Message* reply_msg,
+ const net::CookieList& cookie_list) {
+ std::vector<CookieData> cookies;
+ for (size_t i = 0; i < cookie_list.size(); ++i)
+ cookies.push_back(CookieData(cookie_list[i]));
+ ViewHostMsg_GetRawCookies::WriteReplyParams(reply_msg, cookies);
+ Send(reply_msg);
+}
+
+void RenderMessageFilter::OnCompletedOpenChannelToNpapiPlugin(
+ OpenChannelToNpapiPluginCallback* client) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(ContainsKey(plugin_host_clients_, client));
+ plugin_host_clients_.erase(client);
+}
+
+void RenderMessageFilter::OnUpdateIsDelayed(const IPC::Message& msg) {
+ // When not in accelerated compositing mode, in certain cases (e.g. waiting
+ // for a resize or if no backing store) the RenderWidgetHost is blocking the
+ // UI thread for some time, waiting for an UpdateRect from the renderer. If we
+ // are going to switch to accelerated compositing, the GPU process may need
+ // round-trips to the UI thread before finishing the frame, causing deadlocks
+ // if we delay the UpdateRect until we receive the OnSwapBuffersComplete. So
+ // the renderer sent us this message, so that we can unblock the UI thread.
+ // We will simply re-use the UpdateRect unblock mechanism, just with a
+ // different message.
+ render_widget_helper_->DidReceiveBackingStoreMsg(msg);
+}
+
+void RenderMessageFilter::OnAre3DAPIsBlocked(int render_view_id,
+ const GURL& top_origin_url,
+ ThreeDAPIType requester,
+ bool* blocked) {
+ *blocked = GpuDataManagerImpl::GetInstance()->Are3DAPIsBlocked(
+ top_origin_url, render_process_id_, render_view_id, requester);
+}
+
+void RenderMessageFilter::OnDidLose3DContext(
+ const GURL& top_origin_url,
+ ThreeDAPIType /* unused */,
+ int arb_robustness_status_code) {
+#if defined(OS_MACOSX)
+ // TODO(kbr): this file indirectly includes npapi.h, which on Mac
+ // OS pulls in the system OpenGL headers. For some
+ // not-yet-investigated reason this breaks the build with the 10.6
+ // SDK but not 10.7. For now work around this in a way compatible
+ // with the Khronos headers.
+#ifndef GL_GUILTY_CONTEXT_RESET_ARB
+#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253
+#endif
+#ifndef GL_INNOCENT_CONTEXT_RESET_ARB
+#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254
+#endif
+#ifndef GL_UNKNOWN_CONTEXT_RESET_ARB
+#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255
+#endif
+
+#endif
+ GpuDataManagerImpl::DomainGuilt guilt;
+ switch (arb_robustness_status_code) {
+ case GL_GUILTY_CONTEXT_RESET_ARB:
+ guilt = GpuDataManagerImpl::DOMAIN_GUILT_KNOWN;
+ break;
+ case GL_UNKNOWN_CONTEXT_RESET_ARB:
+ guilt = GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN;
+ break;
+ default:
+ // Ignore lost contexts known to be innocent.
+ return;
+ }
+
+ GpuDataManagerImpl::GetInstance()->BlockDomainFrom3DAPIs(
+ top_origin_url, guilt);
+}
+
+#if defined(OS_WIN)
+void RenderMessageFilter::OnPreCacheFontCharacters(const LOGFONT& font,
+ const string16& str) {
+ // First, comments from FontCacheDispatcher::OnPreCacheFont do apply here too.
+ // Except that for True Type fonts,
+ // GetTextMetrics will not load the font in memory.
+ // The only way windows seem to load properly, it is to create a similar
+ // device (like the one in which we print), then do an ExtTextOut,
+ // as we do in the printing thread, which is sandboxed.
+ HDC hdc = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
+ HFONT font_handle = CreateFontIndirect(&font);
+ DCHECK(NULL != font_handle);
+
+ HGDIOBJ old_font = SelectObject(hdc, font_handle);
+ DCHECK(NULL != old_font);
+
+ ExtTextOut(hdc, 0, 0, ETO_GLYPH_INDEX, 0, str.c_str(), str.length(), NULL);
+
+ SelectObject(hdc, old_font);
+ DeleteObject(font_handle);
+
+ HENHMETAFILE metafile = CloseEnhMetaFile(hdc);
+
+ if (metafile) {
+ DeleteEnhMetaFile(metafile);
+ }
+}
+#endif
+
+#if defined(OS_ANDROID)
+void RenderMessageFilter::OnWebAudioMediaCodec(
+ base::SharedMemoryHandle encoded_data_handle,
+ base::FileDescriptor pcm_output,
+ uint32_t data_size) {
+ // Let a WorkerPool handle this request since the WebAudio
+ // MediaCodec bridge is slow and can block while sending the data to
+ // the renderer.
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&media::WebAudioMediaCodecBridge::RunWebAudioMediaCodec,
+ encoded_data_handle, pcm_output, data_size),
+ true);
+}
+#endif
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_message_filter.h b/chromium/content/browser/renderer_host/render_message_filter.h
new file mode 100644
index 00000000000..4eb986471c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_message_filter.h
@@ -0,0 +1,310 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "content/common/pepper_renderer_instance_data.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/common/three_d_api_types.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/channel_layout.h"
+#include "net/cookies/canonical_cookie.h"
+#include "third_party/WebKit/public/web/WebPopupType.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/surface/transport_dib.h"
+
+#if defined(OS_MACOSX)
+#include "content/common/mac/font_loader.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/threading/worker_pool.h"
+#endif
+
+struct FontDescriptor;
+struct ViewHostMsg_CreateWindow_Params;
+
+namespace WebKit {
+struct WebScreenInfo;
+}
+
+namespace base {
+class ProcessMetrics;
+class SharedMemory;
+class TaskRunner;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace media {
+class AudioManager;
+struct MediaLogEvent;
+}
+
+namespace net {
+class URLRequestContext;
+class URLRequestContextGetter;
+}
+
+namespace content {
+class BrowserContext;
+class DOMStorageContextWrapper;
+class MediaInternals;
+class PluginServiceImpl;
+class RenderWidgetHelper;
+class ResourceContext;
+class ResourceDispatcherHostImpl;
+struct Referrer;
+struct WebPluginInfo;
+
+// This class filters out incoming IPC messages for the renderer process on the
+// IPC thread.
+class RenderMessageFilter : public BrowserMessageFilter {
+ public:
+ // Create the filter.
+ RenderMessageFilter(int render_process_id,
+ bool is_guest,
+ PluginServiceImpl * plugin_service,
+ BrowserContext* browser_context,
+ net::URLRequestContextGetter* request_context,
+ RenderWidgetHelper* render_widget_helper,
+ media::AudioManager* audio_manager,
+ MediaInternals* media_internals,
+ DOMStorageContextWrapper* dom_storage_context);
+
+ // IPC::ChannelProxy::MessageFilter methods:
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+
+ // BrowserMessageFilter methods:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
+
+ bool OffTheRecord() const;
+
+ int render_process_id() const { return render_process_id_; }
+
+ // Returns the correct net::URLRequestContext depending on what type of url is
+ // given.
+ // Only call on the IO thread.
+ net::URLRequestContext* GetRequestContextForURL(const GURL& url);
+
+ private:
+ friend class BrowserThread;
+ friend class base::DeleteHelper<RenderMessageFilter>;
+
+ class OpenChannelToNpapiPluginCallback;
+
+ virtual ~RenderMessageFilter();
+
+ void OnGetProcessMemorySizes(size_t* private_bytes, size_t* shared_bytes);
+ void OnCreateWindow(const ViewHostMsg_CreateWindow_Params& params,
+ int* route_id,
+ int* main_frame_route_id,
+ int* surface_id,
+ int64* cloned_session_storage_namespace_id);
+ void OnCreateWidget(int opener_id,
+ WebKit::WebPopupType popup_type,
+ int* route_id,
+ int* surface_id);
+ void OnCreateFullscreenWidget(int opener_id,
+ int* route_id,
+ int* surface_id);
+ void OnSetCookie(const IPC::Message& message,
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ const std::string& cookie);
+ void OnGetCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg);
+ void OnGetRawCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg);
+ void OnDeleteCookie(const GURL& url,
+ const std::string& cookieName);
+ void OnCookiesEnabled(const GURL& url,
+ const GURL& first_party_for_cookies,
+ bool* cookies_enabled);
+
+#if defined(OS_MACOSX)
+ // Messages for OOP font loading.
+ void OnLoadFont(const FontDescriptor& font, IPC::Message* reply_msg);
+ void SendLoadFontReply(IPC::Message* reply, FontLoader::Result* result);
+#endif
+
+#if defined(OS_WIN)
+ void OnPreCacheFontCharacters(const LOGFONT& log_font,
+ const string16& characters);
+#endif
+
+ void OnGetPlugins(bool refresh, IPC::Message* reply_msg);
+ void GetPluginsCallback(IPC::Message* reply_msg,
+ const std::vector<WebPluginInfo>& plugins);
+ void OnGetPluginInfo(int routing_id,
+ const GURL& url,
+ const GURL& policy_url,
+ const std::string& mime_type,
+ bool* found,
+ WebPluginInfo* info,
+ std::string* actual_mime_type);
+ void OnOpenChannelToPlugin(int routing_id,
+ const GURL& url,
+ const GURL& policy_url,
+ const std::string& mime_type,
+ IPC::Message* reply_msg);
+ void OnOpenChannelToPepperPlugin(const base::FilePath& path,
+ IPC::Message* reply_msg);
+ void OnDidCreateOutOfProcessPepperInstance(
+ int plugin_child_id,
+ int32 pp_instance,
+ PepperRendererInstanceData instance_data,
+ bool is_external);
+ void OnDidDeleteOutOfProcessPepperInstance(int plugin_child_id,
+ int32 pp_instance,
+ bool is_external);
+ void OnOpenChannelToPpapiBroker(int routing_id,
+ const base::FilePath& path);
+ void OnGenerateRoutingID(int* route_id);
+ void OnDownloadUrl(const IPC::Message& message,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& suggested_name);
+ void OnCheckNotificationPermission(const GURL& source_origin,
+ int* permission_level);
+
+ void OnGetCPUUsage(int* cpu_usage);
+
+ void OnGetAudioHardwareConfig(media::AudioParameters* input_params,
+ media::AudioParameters* output_params);
+
+ // Used to look up the monitor color profile.
+ void OnGetMonitorColorProfile(std::vector<char>* profile);
+
+ // Used to ask the browser to allocate a block of shared memory for the
+ // renderer to send back data in, since shared memory can't be created
+ // in the renderer on POSIX due to the sandbox.
+ void OnAllocateSharedMemory(uint32 buffer_size,
+ base::SharedMemoryHandle* handle);
+ void OnResolveProxy(const GURL& url, IPC::Message* reply_msg);
+
+ // Browser side transport DIB allocation
+ void OnAllocTransportDIB(uint32 size,
+ bool cache_in_browser,
+ TransportDIB::Handle* result);
+ void OnFreeTransportDIB(TransportDIB::Id dib_id);
+ void OnCacheableMetadataAvailable(const GURL& url,
+ double expected_response_time,
+ const std::vector<char>& data);
+ void OnKeygen(uint32 key_size_index, const std::string& challenge_string,
+ const GURL& url, IPC::Message* reply_msg);
+ void OnKeygenOnWorkerThread(
+ int key_size_in_bits,
+ const std::string& challenge_string,
+ const GURL& url,
+ IPC::Message* reply_msg);
+ void OnAsyncOpenPepperFile(int routing_id,
+ const base::FilePath& path,
+ int pp_open_flags,
+ int message_id);
+ void OnMediaLogEvents(const std::vector<media::MediaLogEvent>&);
+
+ // Check the policy for getting cookies. Gets the cookies if allowed.
+ void CheckPolicyForCookies(const GURL& url,
+ const GURL& first_party_for_cookies,
+ IPC::Message* reply_msg,
+ const net::CookieList& cookie_list);
+
+ // Writes the cookies to reply messages, and sends the message.
+ // Callback functions for getting cookies from cookie store.
+ void SendGetCookiesResponse(IPC::Message* reply_msg,
+ const std::string& cookies);
+ void SendGetRawCookiesResponse(IPC::Message* reply_msg,
+ const net::CookieList& cookie_list);
+
+ bool CheckBenchmarkingEnabled() const;
+ bool CheckPreparsedJsCachingEnabled() const;
+ void OnCompletedOpenChannelToNpapiPlugin(
+ OpenChannelToNpapiPluginCallback* client);
+
+ void OnUpdateIsDelayed(const IPC::Message& msg);
+ void OnAre3DAPIsBlocked(int render_view_id,
+ const GURL& top_origin_url,
+ ThreeDAPIType requester,
+ bool* blocked);
+ void OnDidLose3DContext(const GURL& top_origin_url,
+ ThreeDAPIType context_type,
+ int arb_robustness_status_code);
+
+#if defined(OS_ANDROID)
+ void OnWebAudioMediaCodec(base::SharedMemoryHandle encoded_data_handle,
+ base::FileDescriptor pcm_output,
+ uint32_t data_size);
+#endif
+
+ // Cached resource request dispatcher host and plugin service, guaranteed to
+ // be non-null if Init succeeds. We do not own the objects, they are managed
+ // by the BrowserProcess, which has a wider scope than we do.
+ ResourceDispatcherHostImpl* resource_dispatcher_host_;
+ PluginServiceImpl* plugin_service_;
+ base::FilePath profile_data_directory_;
+
+ // Contextual information to be used for requests created here.
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+ // The ResourceContext which is to be used on the IO thread.
+ ResourceContext* resource_context_;
+
+ scoped_refptr<RenderWidgetHelper> render_widget_helper_;
+
+ // Whether this process is used for incognito contents.
+ // This doesn't belong here; http://crbug.com/89628
+ bool incognito_;
+
+ // Initialized to 0, accessed on FILE thread only.
+ base::TimeTicks last_plugin_refresh_time_;
+
+ scoped_refptr<DOMStorageContextWrapper> dom_storage_context_;
+
+ int render_process_id_;
+
+ bool is_guest_;
+
+ std::set<OpenChannelToNpapiPluginCallback*> plugin_host_clients_;
+
+ // Records the last time we sampled CPU usage of the renderer process.
+ base::TimeTicks cpu_usage_sample_time_;
+ // Records the last sampled CPU usage in percents.
+ int cpu_usage_;
+ // Used for sampling CPU usage of the renderer process.
+ scoped_ptr<base::ProcessMetrics> process_metrics_;
+
+ media::AudioManager* audio_manager_;
+ MediaInternals* media_internals_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/render_process_host_browsertest.cc b/chromium/content/browser/renderer_host/render_process_host_browsertest.cc
new file mode 100644
index 00000000000..9ee8036e044
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -0,0 +1,50 @@
+// 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 "content/common/child_process_messages.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_notification_tracker.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace content {
+namespace {
+
+class RenderProcessHostTest : public ContentBrowserTest {};
+
+// Sometimes the renderer process's ShutdownRequest (corresponding to the
+// ViewMsg_WasSwappedOut from a previous navigation) doesn't arrive until after
+// the browser process decides to re-use the renderer for a new purpose. This
+// test makes sure the browser doesn't let the renderer die in that case. See
+// http://crbug.com/87176.
+IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
+ ShutdownRequestFromActiveTabIgnored) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
+ NavigateToURL(shell(), test_url);
+ RenderProcessHost* rph =
+ shell()->web_contents()->GetRenderViewHost()->GetProcess();
+
+ TestNotificationTracker termination_watcher;
+ termination_watcher.ListenFor(NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ Source<RenderProcessHost>(rph));
+ ChildProcessHostMsg_ShutdownRequest msg;
+ rph->OnMessageReceived(msg);
+
+ // If the RPH sends a mistaken ChildProcessMsg_Shutdown, the renderer process
+ // will take some time to die. Wait for a second tab to load in order to give
+ // that time to happen.
+ NavigateToURL(CreateBrowser(), test_url);
+
+ EXPECT_EQ(0U, termination_watcher.size());
+}
+
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_process_host_impl.cc b/chromium/content/browser/renderer_host/render_process_host_impl.cc
new file mode 100644
index 00000000000..e31aff2afc6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_process_host_impl.cc
@@ -0,0 +1,1816 @@
+// 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.
+
+// Represents the browser side of the browser <--> renderer communication
+// channel. There will be one RenderProcessHost per renderer process.
+
+#include "content/browser/renderer_host/render_process_host_impl.h"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#if defined(OS_POSIX)
+#include <utility> // for pair<>
+#endif
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/platform_file.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/supports_user_data.h"
+#include "base/sys_info.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/tracked_objects.h"
+#include "cc/base/switches.h"
+#include "content/browser/appcache/appcache_dispatcher_host.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/browser_main.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h"
+#include "content/browser/browser_plugin/browser_plugin_message_filter.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/device_orientation/device_motion_message_filter.h"
+#include "content/browser/device_orientation/device_orientation_message_filter.h"
+#include "content/browser/device_orientation/orientation_message_filter.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/dom_storage_message_filter.h"
+#include "content/browser/download/mhtml_generation_manager.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/fileapi/fileapi_message_filter.h"
+#include "content/browser/geolocation/geolocation_dispatcher_host.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/shader_disk_cache.h"
+#include "content/browser/histogram_message_filter.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/loader/resource_scheduler_filter.h"
+#include "content/browser/media/media_internals.h"
+#include "content/browser/mime_registry_message_filter.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/browser/profiler_message_filter.h"
+#include "content/browser/quota_dispatcher_host.h"
+#include "content/browser/renderer_host/clipboard_message_filter.h"
+#include "content/browser/renderer_host/database_message_filter.h"
+#include "content/browser/renderer_host/file_utilities_message_filter.h"
+#include "content/browser/renderer_host/gamepad_browser_message_filter.h"
+#include "content/browser/renderer_host/gpu_message_filter.h"
+#include "content/browser/renderer_host/media/audio_input_renderer_host.h"
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h"
+#include "content/browser/renderer_host/media/audio_renderer_host.h"
+#include "content/browser/renderer_host/media/device_request_message_filter.h"
+#include "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
+#include "content/browser/renderer_host/media/midi_dispatcher_host.h"
+#include "content/browser/renderer_host/media/midi_host.h"
+#include "content/browser/renderer_host/media/peer_connection_tracker_host.h"
+#include "content/browser/renderer_host/media/video_capture_host.h"
+#include "content/browser/renderer_host/memory_benchmark_message_filter.h"
+#include "content/browser/renderer_host/p2p/socket_dispatcher_host.h"
+#include "content/browser/renderer_host/pepper/pepper_message_filter.h"
+#include "content/browser/renderer_host/pepper/pepper_renderer_connection.h"
+#include "content/browser/renderer_host/render_message_filter.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/socket_stream_dispatcher_host.h"
+#include "content/browser/renderer_host/text_input_client_message_filter.h"
+#include "content/browser/resolve_proxy_msg_helper.h"
+#include "content/browser/speech/input_tag_speech_dispatcher_host.h"
+#include "content/browser/speech/speech_recognition_dispatcher_host.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/browser/tracing/trace_message_filter.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/browser/worker_host/worker_message_filter.h"
+#include "content/browser/worker_host/worker_storage_partition.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/child_process_messages.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/resource_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host_factory.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/url_constants.h"
+#include "content/renderer/render_process_impl.h"
+#include "content/renderer/render_thread_impl.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_platform_file.h"
+#include "ipc/ipc_switches.h"
+#include "media/base/media_switches.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ppapi/shared_impl/ppapi_switches.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gl/gl_switches.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/common/resource_type.h"
+
+#if defined(OS_ANDROID)
+#include "content/browser/android/vibration_message_filter.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#include "content/common/font_cache_dispatcher_win.h"
+#include "content/common/sandbox_win.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#endif
+
+#if defined(ENABLE_WEBRTC)
+#include "content/browser/renderer_host/media/webrtc_identity_service_host.h"
+#endif
+
+#include "third_party/skia/include/core/SkBitmap.h"
+
+extern bool g_exited_main_message_loop;
+
+static const char* kSiteProcessMapKeyName = "content_site_process_map";
+
+namespace content {
+namespace {
+
+base::MessageLoop* g_in_process_thread;
+
+void CacheShaderInfo(int32 id, base::FilePath path) {
+ ShaderCacheFactory::GetInstance()->SetCacheInfo(id, path);
+}
+
+void RemoveShaderInfo(int32 id) {
+ ShaderCacheFactory::GetInstance()->RemoveCacheInfo(id);
+}
+
+} // namespace
+
+#if !defined(CHROME_MULTIPLE_DLL)
+
+// This class creates the IO thread for the renderer when running in
+// single-process mode. It's not used in multi-process mode.
+class RendererMainThread : public base::Thread {
+ public:
+ explicit RendererMainThread(const std::string& channel_id)
+ : Thread("Chrome_InProcRendererThread"),
+ channel_id_(channel_id) {
+ }
+
+ virtual ~RendererMainThread() {
+ Stop();
+ }
+
+ protected:
+ virtual void Init() OVERRIDE {
+ render_process_.reset(new RenderProcessImpl());
+ new RenderThreadImpl(channel_id_);
+ g_in_process_thread = message_loop();
+ }
+
+ virtual void CleanUp() OVERRIDE {
+ g_in_process_thread = NULL;
+ render_process_.reset();
+
+ // It's a little lame to manually set this flag. But the single process
+ // RendererThread will receive the WM_QUIT. We don't need to assert on
+ // this thread, so just force the flag manually.
+ // If we want to avoid this, we could create the InProcRendererThread
+ // directly with _beginthreadex() rather than using the Thread class.
+ // We used to set this flag in the Init function above. However there
+ // other threads like WebThread which are created by this thread
+ // which resets this flag. Please see Thread::StartWithOptions. Setting
+ // this flag to true in Cleanup works around these problems.
+ SetThreadWasQuitProperly(true);
+ }
+
+ private:
+ std::string channel_id_;
+ scoped_ptr<RenderProcess> render_process_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererMainThread);
+};
+
+#endif
+
+namespace {
+
+// Helper class that we pass to ResourceMessageFilter so that it can find the
+// right net::URLRequestContext for a request.
+class RendererURLRequestContextSelector
+ : public ResourceMessageFilter::URLRequestContextSelector {
+ public:
+ RendererURLRequestContextSelector(BrowserContext* browser_context,
+ int render_child_id)
+ : request_context_(browser_context->GetRequestContextForRenderProcess(
+ render_child_id)),
+ media_request_context_(
+ browser_context->GetMediaRequestContextForRenderProcess(
+ render_child_id)) {
+ }
+
+ virtual net::URLRequestContext* GetRequestContext(
+ ResourceType::Type resource_type) OVERRIDE {
+ net::URLRequestContextGetter* request_context = request_context_.get();
+ // If the request has resource type of ResourceType::MEDIA, we use a request
+ // context specific to media for handling it because these resources have
+ // specific needs for caching.
+ if (resource_type == ResourceType::MEDIA)
+ request_context = media_request_context_.get();
+ return request_context->GetURLRequestContext();
+ }
+
+ private:
+ virtual ~RendererURLRequestContextSelector() {}
+
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+ scoped_refptr<net::URLRequestContextGetter> media_request_context_;
+};
+
+// the global list of all renderer processes
+base::LazyInstance<IDMap<RenderProcessHost> >::Leaky
+ g_all_hosts = LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<scoped_refptr<BrowserPluginGeolocationPermissionContext> >
+ g_browser_plugin_geolocation_context = LAZY_INSTANCE_INITIALIZER;
+
+// Map of site to process, to ensure we only have one RenderProcessHost per
+// site in process-per-site mode. Each map is specific to a BrowserContext.
+class SiteProcessMap : public base::SupportsUserData::Data {
+ public:
+ typedef base::hash_map<std::string, RenderProcessHost*> SiteToProcessMap;
+ SiteProcessMap() {}
+
+ void RegisterProcess(const std::string& site, RenderProcessHost* process) {
+ map_[site] = process;
+ }
+
+ RenderProcessHost* FindProcess(const std::string& site) {
+ SiteToProcessMap::iterator i = map_.find(site);
+ if (i != map_.end())
+ return i->second;
+ return NULL;
+ }
+
+ void RemoveProcess(RenderProcessHost* host) {
+ // Find all instances of this process in the map, then separately remove
+ // them.
+ std::set<std::string> sites;
+ for (SiteToProcessMap::const_iterator i = map_.begin();
+ i != map_.end();
+ i++) {
+ if (i->second == host)
+ sites.insert(i->first);
+ }
+ for (std::set<std::string>::iterator i = sites.begin();
+ i != sites.end();
+ i++) {
+ SiteToProcessMap::iterator iter = map_.find(*i);
+ if (iter != map_.end()) {
+ DCHECK_EQ(iter->second, host);
+ map_.erase(iter);
+ }
+ }
+ }
+
+ private:
+ SiteToProcessMap map_;
+};
+
+// Find the SiteProcessMap specific to the given context.
+SiteProcessMap* GetSiteProcessMapForBrowserContext(BrowserContext* context) {
+ DCHECK(context);
+ SiteProcessMap* map = static_cast<SiteProcessMap*>(
+ context->GetUserData(kSiteProcessMapKeyName));
+ if (!map) {
+ map = new SiteProcessMap();
+ context->SetUserData(kSiteProcessMapKeyName, map);
+ }
+ return map;
+}
+
+#if defined(OS_WIN)
+// NOTE: changes to this class need to be reviewed by the security team.
+class RendererSandboxedProcessLauncherDelegate
+ : public content::SandboxedProcessLauncherDelegate {
+ public:
+ RendererSandboxedProcessLauncherDelegate() {}
+ virtual ~RendererSandboxedProcessLauncherDelegate() {}
+
+ virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE {
+#if !defined (GOOGLE_CHROME_BUILD)
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kInProcessPlugins)) {
+ *in_sandbox = false;
+ }
+#endif
+ }
+
+ virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
+ bool* success) {
+ AddBaseHandleClosePolicy(policy);
+ GetContentClient()->browser()->PreSpawnRenderer(policy, success);
+ }
+};
+#endif // OS_WIN
+
+} // namespace
+
+// Stores the maximum number of renderer processes the content module can
+// create.
+static size_t g_max_renderer_count_override = 0;
+
+// static
+size_t RenderProcessHost::GetMaxRendererProcessCount() {
+ if (g_max_renderer_count_override)
+ return g_max_renderer_count_override;
+
+ // Defines the maximum number of renderer processes according to the
+ // amount of installed memory as reported by the OS. The calculation
+ // assumes that you want the renderers to use half of the installed
+ // RAM and assuming that each WebContents uses ~40MB.
+ // If you modify this assumption, you need to adjust the
+ // ThirtyFourTabs test to match the expected number of processes.
+ //
+ // With the given amounts of installed memory below on a 32-bit CPU,
+ // the maximum renderer count will roughly be as follows:
+ //
+ // 128 MB -> 3
+ // 512 MB -> 6
+ // 1024 MB -> 12
+ // 4096 MB -> 51
+ // 16384 MB -> 82 (kMaxRendererProcessCount)
+
+ static size_t max_count = 0;
+ if (!max_count) {
+ const size_t kEstimatedWebContentsMemoryUsage =
+#if defined(ARCH_CPU_64_BITS)
+ 60; // In MB
+#else
+ 40; // In MB
+#endif
+ max_count = base::SysInfo::AmountOfPhysicalMemoryMB() / 2;
+ max_count /= kEstimatedWebContentsMemoryUsage;
+
+ const size_t kMinRendererProcessCount = 3;
+ max_count = std::max(max_count, kMinRendererProcessCount);
+ max_count = std::min(max_count, kMaxRendererProcessCount);
+ }
+ return max_count;
+}
+
+// static
+bool g_run_renderer_in_process_ = false;
+
+// static
+void RenderProcessHost::SetMaxRendererProcessCount(size_t count) {
+ g_max_renderer_count_override = count;
+}
+
+RenderProcessHostImpl::RenderProcessHostImpl(
+ BrowserContext* browser_context,
+ StoragePartitionImpl* storage_partition_impl,
+ bool supports_browser_plugin,
+ bool is_guest)
+ : fast_shutdown_started_(false),
+ deleting_soon_(false),
+ pending_views_(0),
+ visible_widgets_(0),
+ backgrounded_(true),
+ cached_dibs_cleaner_(
+ FROM_HERE, base::TimeDelta::FromSeconds(5),
+ this, &RenderProcessHostImpl::ClearTransportDIBCache),
+ is_initialized_(false),
+ id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()),
+ browser_context_(browser_context),
+ storage_partition_impl_(storage_partition_impl),
+ sudden_termination_allowed_(true),
+ ignore_input_events_(false),
+ supports_browser_plugin_(supports_browser_plugin),
+ is_guest_(is_guest),
+ gpu_observer_registered_(false),
+ power_monitor_broadcaster_(this) {
+ widget_helper_ = new RenderWidgetHelper();
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->Add(GetID());
+
+ CHECK(!g_exited_main_message_loop);
+ RegisterHost(GetID(), this);
+ g_all_hosts.Get().set_check_on_null_data(true);
+ // Initialize |child_process_activity_time_| to a reasonable value.
+ mark_child_process_activity_time();
+
+ if (!GetBrowserContext()->IsOffTheRecord() &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableGpuShaderDiskCache)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&CacheShaderInfo, GetID(),
+ storage_partition_impl_->GetPath()));
+ }
+
+ // Note: When we create the RenderProcessHostImpl, it's technically
+ // backgrounded, because it has no visible listeners. But the process
+ // doesn't actually exist yet, so we'll Background it later, after
+ // creation.
+}
+
+RenderProcessHostImpl::~RenderProcessHostImpl() {
+ DCHECK(!run_renderer_in_process());
+ ChildProcessSecurityPolicyImpl::GetInstance()->Remove(GetID());
+
+ if (gpu_observer_registered_) {
+ GpuDataManagerImpl::GetInstance()->RemoveObserver(this);
+ gpu_observer_registered_ = false;
+ }
+
+ // We may have some unsent messages at this point, but that's OK.
+ channel_.reset();
+ while (!queued_messages_.empty()) {
+ delete queued_messages_.front();
+ queued_messages_.pop();
+ }
+
+ ClearTransportDIBCache();
+ UnregisterHost(GetID());
+
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableGpuShaderDiskCache)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&RemoveShaderInfo, GetID()));
+ }
+}
+
+void RenderProcessHostImpl::EnableSendQueue() {
+ is_initialized_ = false;
+}
+
+bool RenderProcessHostImpl::Init() {
+ // calling Init() more than once does nothing, this makes it more convenient
+ // for the view host which may not be sure in some cases
+ if (channel_)
+ return true;
+
+ CommandLine::StringType renderer_prefix;
+#if defined(OS_POSIX)
+ // A command prefix is something prepended to the command line of the spawned
+ // process. It is supported only on POSIX systems.
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ renderer_prefix =
+ browser_command_line.GetSwitchValueNative(switches::kRendererCmdPrefix);
+#endif // defined(OS_POSIX)
+
+#if defined(OS_LINUX)
+ int flags = renderer_prefix.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
+ ChildProcessHost::CHILD_NORMAL;
+#else
+ int flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ // Find the renderer before creating the channel so if this fails early we
+ // return without creating the channel.
+ base::FilePath renderer_path = ChildProcessHost::GetChildPath(flags);
+ if (renderer_path.empty())
+ return false;
+
+ // Setup the IPC channel.
+ const std::string channel_id =
+ IPC::Channel::GenerateVerifiedChannelID(std::string());
+ channel_.reset(
+ new IPC::ChannelProxy(channel_id,
+ IPC::Channel::MODE_SERVER,
+ this,
+ BrowserThread::GetMessageLoopProxyForThread(
+ BrowserThread::IO).get()));
+
+ // Call the embedder first so that their IPC filters have priority.
+ GetContentClient()->browser()->RenderProcessHostCreated(this);
+
+ CreateMessageFilters();
+
+ // Single-process mode not supported in multiple-dll mode currently.
+#if !defined(CHROME_MULTIPLE_DLL)
+ if (run_renderer_in_process()) {
+ // Crank up a thread and run the initialization there. With the way that
+ // messages flow between the browser and renderer, this thread is required
+ // to prevent a deadlock in single-process mode. Since the primordial
+ // thread in the renderer process runs the WebKit code and can sometimes
+ // make blocking calls to the UI thread (i.e. this thread), they need to run
+ // on separate threads.
+ in_process_renderer_.reset(new RendererMainThread(channel_id));
+
+ base::Thread::Options options;
+#if defined(OS_WIN) && !defined(OS_MACOSX)
+ // In-process plugins require this to be a UI message loop.
+ options.message_loop_type = base::MessageLoop::TYPE_UI;
+#else
+ // We can't have multiple UI loops on Linux and Android, so we don't support
+ // in-process plugins.
+ options.message_loop_type = base::MessageLoop::TYPE_DEFAULT;
+#endif
+ in_process_renderer_->StartWithOptions(options);
+
+ OnProcessLaunched(); // Fake a callback that the process is ready.
+ } else
+#endif // !CHROME_MULTIPLE_DLL
+ {
+ // Build command line for renderer. We call AppendRendererCommandLine()
+ // first so the process type argument will appear first.
+ CommandLine* cmd_line = new CommandLine(renderer_path);
+ if (!renderer_prefix.empty())
+ cmd_line->PrependWrapper(renderer_prefix);
+ AppendRendererCommandLine(cmd_line);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+
+ // Spawn the child process asynchronously to avoid blocking the UI thread.
+ // As long as there's no renderer prefix, we can use the zygote process
+ // at this stage.
+ child_process_launcher_.reset(new ChildProcessLauncher(
+#if defined(OS_WIN)
+ new RendererSandboxedProcessLauncherDelegate,
+#elif defined(OS_POSIX)
+ renderer_prefix.empty(),
+ base::EnvironmentVector(),
+ channel_->TakeClientFileDescriptor(),
+#endif
+ cmd_line,
+ GetID(),
+ this));
+
+ fast_shutdown_started_ = false;
+ }
+
+ if (!gpu_observer_registered_) {
+ gpu_observer_registered_ = true;
+ GpuDataManagerImpl::GetInstance()->AddObserver(this);
+ }
+
+ is_initialized_ = true;
+ return true;
+}
+
+void RenderProcessHostImpl::CreateMessageFilters() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ channel_->AddFilter(new ResourceSchedulerFilter(GetID()));
+ MediaInternals* media_internals = MediaInternals::GetInstance();;
+ media::AudioManager* audio_manager =
+ BrowserMainLoop::GetInstance()->audio_manager();
+ // Add BrowserPluginMessageFilter to ensure it gets the first stab at messages
+ // from guests.
+ if (supports_browser_plugin_) {
+ scoped_refptr<BrowserPluginMessageFilter> bp_message_filter(
+ new BrowserPluginMessageFilter(GetID(), IsGuest()));
+ channel_->AddFilter(bp_message_filter.get());
+ }
+
+ scoped_refptr<RenderMessageFilter> render_message_filter(
+ new RenderMessageFilter(
+ GetID(),
+ IsGuest(),
+#if defined(ENABLE_PLUGINS)
+ PluginServiceImpl::GetInstance(),
+#else
+ NULL,
+#endif
+ GetBrowserContext(),
+ GetBrowserContext()->GetRequestContextForRenderProcess(GetID()),
+ widget_helper_.get(),
+ audio_manager,
+ media_internals,
+ storage_partition_impl_->GetDOMStorageContext()));
+ channel_->AddFilter(render_message_filter.get());
+ BrowserContext* browser_context = GetBrowserContext();
+ ResourceContext* resource_context = browser_context->GetResourceContext();
+
+ ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter(
+ GetID(), PROCESS_TYPE_RENDERER, resource_context,
+ storage_partition_impl_->GetAppCacheService(),
+ ChromeBlobStorageContext::GetFor(browser_context),
+ storage_partition_impl_->GetFileSystemContext(),
+ new RendererURLRequestContextSelector(browser_context, GetID()));
+
+ channel_->AddFilter(resource_message_filter);
+ MediaStreamManager* media_stream_manager =
+ BrowserMainLoop::GetInstance()->media_stream_manager();
+ channel_->AddFilter(new AudioInputRendererHost(
+ audio_manager,
+ media_stream_manager,
+ BrowserMainLoop::GetInstance()->audio_mirroring_manager()));
+ channel_->AddFilter(new AudioRendererHost(
+ GetID(), audio_manager,
+ BrowserMainLoop::GetInstance()->audio_mirroring_manager(),
+ media_internals, media_stream_manager));
+ channel_->AddFilter(
+ new MIDIHost(BrowserMainLoop::GetInstance()->midi_manager()));
+ channel_->AddFilter(new MIDIDispatcherHost(GetID(), browser_context));
+ channel_->AddFilter(new VideoCaptureHost(media_stream_manager));
+ channel_->AddFilter(new AppCacheDispatcherHost(
+ storage_partition_impl_->GetAppCacheService(),
+ GetID()));
+ channel_->AddFilter(new ClipboardMessageFilter);
+ channel_->AddFilter(new DOMStorageMessageFilter(
+ GetID(),
+ storage_partition_impl_->GetDOMStorageContext()));
+ channel_->AddFilter(new IndexedDBDispatcherHost(
+ GetID(),
+ storage_partition_impl_->GetIndexedDBContext()));
+ if (IsGuest()) {
+ if (!g_browser_plugin_geolocation_context.Get().get()) {
+ g_browser_plugin_geolocation_context.Get() =
+ new BrowserPluginGeolocationPermissionContext();
+ }
+ channel_->AddFilter(GeolocationDispatcherHost::New(
+ GetID(), g_browser_plugin_geolocation_context.Get().get()));
+ } else {
+ channel_->AddFilter(GeolocationDispatcherHost::New(
+ GetID(), browser_context->GetGeolocationPermissionContext()));
+ }
+ gpu_message_filter_ = new GpuMessageFilter(GetID(), widget_helper_.get());
+ channel_->AddFilter(gpu_message_filter_);
+#if defined(ENABLE_WEBRTC)
+ channel_->AddFilter(new WebRTCIdentityServiceHost(
+ GetID(), storage_partition_impl_->GetWebRTCIdentityStore()));
+ peer_connection_tracker_host_ = new PeerConnectionTrackerHost(GetID());
+ channel_->AddFilter(peer_connection_tracker_host_.get());
+ channel_->AddFilter(new MediaStreamDispatcherHost(
+ GetID(), media_stream_manager));
+ channel_->AddFilter(
+ new DeviceRequestMessageFilter(resource_context, media_stream_manager));
+#endif
+#if defined(ENABLE_PLUGINS)
+ // TODO(raymes): PepperMessageFilter should be removed from here.
+ channel_->AddFilter(new PepperMessageFilter(GetID(), browser_context));
+ channel_->AddFilter(new PepperRendererConnection(GetID()));
+#endif
+#if defined(ENABLE_INPUT_SPEECH)
+ channel_->AddFilter(new InputTagSpeechDispatcherHost(
+ IsGuest(), GetID(), storage_partition_impl_->GetURLRequestContext()));
+#endif
+ channel_->AddFilter(new SpeechRecognitionDispatcherHost(
+ GetID(), storage_partition_impl_->GetURLRequestContext()));
+ channel_->AddFilter(new FileAPIMessageFilter(
+ GetID(),
+ storage_partition_impl_->GetURLRequestContext(),
+ storage_partition_impl_->GetFileSystemContext(),
+ ChromeBlobStorageContext::GetFor(browser_context),
+ StreamContext::GetFor(browser_context)));
+ channel_->AddFilter(new OrientationMessageFilter());
+ channel_->AddFilter(new FileUtilitiesMessageFilter(GetID()));
+ channel_->AddFilter(new MimeRegistryMessageFilter());
+ channel_->AddFilter(new DatabaseMessageFilter(
+ storage_partition_impl_->GetDatabaseTracker()));
+#if defined(OS_MACOSX)
+ channel_->AddFilter(new TextInputClientMessageFilter(GetID()));
+#elif defined(OS_WIN)
+ channel_->AddFilter(new FontCacheDispatcher());
+#endif
+
+ SocketStreamDispatcherHost* socket_stream_dispatcher_host =
+ new SocketStreamDispatcherHost(GetID(),
+ new RendererURLRequestContextSelector(browser_context, GetID()),
+ resource_context);
+ channel_->AddFilter(socket_stream_dispatcher_host);
+
+ channel_->AddFilter(new WorkerMessageFilter(
+ GetID(),
+ resource_context,
+ WorkerStoragePartition(
+ storage_partition_impl_->GetURLRequestContext(),
+ storage_partition_impl_->GetMediaURLRequestContext(),
+ storage_partition_impl_->GetAppCacheService(),
+ storage_partition_impl_->GetQuotaManager(),
+ storage_partition_impl_->GetFileSystemContext(),
+ storage_partition_impl_->GetDatabaseTracker(),
+ storage_partition_impl_->GetIndexedDBContext()),
+ base::Bind(&RenderWidgetHelper::GetNextRoutingID,
+ base::Unretained(widget_helper_.get()))));
+
+#if defined(ENABLE_WEBRTC)
+ channel_->AddFilter(new P2PSocketDispatcherHost(
+ resource_context,
+ browser_context->GetRequestContextForRenderProcess(GetID())));
+#endif
+
+ channel_->AddFilter(new TraceMessageFilter());
+ channel_->AddFilter(new ResolveProxyMsgHelper(
+ browser_context->GetRequestContextForRenderProcess(GetID())));
+ channel_->AddFilter(new QuotaDispatcherHost(
+ GetID(),
+ storage_partition_impl_->GetQuotaManager(),
+ GetContentClient()->browser()->CreateQuotaPermissionContext()));
+ channel_->AddFilter(new GamepadBrowserMessageFilter());
+ channel_->AddFilter(new DeviceMotionMessageFilter());
+ channel_->AddFilter(new DeviceOrientationMessageFilter());
+ channel_->AddFilter(new ProfilerMessageFilter(PROCESS_TYPE_RENDERER));
+ channel_->AddFilter(new HistogramMessageFilter());
+#if defined(USE_TCMALLOC) && (defined(OS_LINUX) || defined(OS_ANDROID))
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableMemoryBenchmarking))
+ channel_->AddFilter(new MemoryBenchmarkMessageFilter());
+#endif
+#if defined(OS_ANDROID)
+ channel_->AddFilter(new VibrationMessageFilter());
+#endif
+}
+
+int RenderProcessHostImpl::GetNextRoutingID() {
+ return widget_helper_->GetNextRoutingID();
+}
+
+
+void RenderProcessHostImpl::ResumeDeferredNavigation(
+ const GlobalRequestID& request_id) {
+ widget_helper_->ResumeDeferredNavigation(request_id);
+}
+
+void RenderProcessHostImpl::AddRoute(
+ int32 routing_id,
+ IPC::Listener* listener) {
+ listeners_.AddWithID(listener, routing_id);
+}
+
+void RenderProcessHostImpl::RemoveRoute(int32 routing_id) {
+ DCHECK(listeners_.Lookup(routing_id) != NULL);
+ listeners_.Remove(routing_id);
+
+#if defined(OS_WIN)
+ // Dump the handle table if handle auditing is enabled.
+ const CommandLine& browser_command_line =
+ *CommandLine::ForCurrentProcess();
+ if (browser_command_line.HasSwitch(switches::kAuditHandles) ||
+ browser_command_line.HasSwitch(switches::kAuditAllHandles)) {
+ DumpHandles();
+
+ // We wait to close the channels until the child process has finished
+ // dumping handles and sends us ChildProcessHostMsg_DumpHandlesDone.
+ return;
+ }
+#endif
+ // Keep the one renderer thread around forever in single process mode.
+ if (!run_renderer_in_process())
+ Cleanup();
+}
+
+bool RenderProcessHostImpl::WaitForBackingStoreMsg(
+ int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg) {
+ // The post task to this thread with the process id could be in queue, and we
+ // don't want to dispatch a message before then since it will need the handle.
+ if (child_process_launcher_.get() && child_process_launcher_->IsStarting())
+ return false;
+
+ return widget_helper_->WaitForBackingStoreMsg(render_widget_id,
+ max_delay, msg);
+}
+
+void RenderProcessHostImpl::ReceivedBadMessage() {
+ if (run_renderer_in_process()) {
+ // In single process mode it is better if we don't suicide but just
+ // crash.
+ CHECK(false);
+ }
+ // We kill the renderer but don't include a NOTREACHED, because we want the
+ // browser to try to survive when it gets illegal messages from the renderer.
+ base::KillProcess(GetHandle(), RESULT_CODE_KILLED_BAD_MESSAGE,
+ false);
+}
+
+void RenderProcessHostImpl::WidgetRestored() {
+ // Verify we were properly backgrounded.
+ DCHECK_EQ(backgrounded_, (visible_widgets_ == 0));
+ visible_widgets_++;
+ SetBackgrounded(false);
+}
+
+void RenderProcessHostImpl::WidgetHidden() {
+ // On startup, the browser will call Hide
+ if (backgrounded_)
+ return;
+
+ DCHECK_EQ(backgrounded_, (visible_widgets_ == 0));
+ visible_widgets_--;
+ DCHECK_GE(visible_widgets_, 0);
+ if (visible_widgets_ == 0) {
+ DCHECK(!backgrounded_);
+ SetBackgrounded(true);
+ }
+}
+
+int RenderProcessHostImpl::VisibleWidgetCount() const {
+ return visible_widgets_;
+}
+
+bool RenderProcessHostImpl::IsGuest() const {
+ return is_guest_;
+}
+
+StoragePartition* RenderProcessHostImpl::GetStoragePartition() const {
+ return storage_partition_impl_;
+}
+
+void RenderProcessHostImpl::AppendRendererCommandLine(
+ CommandLine* command_line) const {
+ // Pass the process type first, so it shows first in process listings.
+ command_line->AppendSwitchASCII(switches::kProcessType,
+ switches::kRendererProcess);
+
+ // Now send any options from our own command line we want to propagate.
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ PropagateBrowserCommandLineToRenderer(browser_command_line, command_line);
+
+ // Pass on the browser locale.
+ const std::string locale =
+ GetContentClient()->browser()->GetApplicationLocale();
+ command_line->AppendSwitchASCII(switches::kLang, locale);
+
+ // If we run base::FieldTrials, we want to pass to their state to the
+ // renderer so that it can act in accordance with each state, or record
+ // histograms relating to the base::FieldTrial states.
+ std::string field_trial_states;
+ base::FieldTrialList::StatesToString(&field_trial_states);
+ if (!field_trial_states.empty()) {
+ command_line->AppendSwitchASCII(switches::kForceFieldTrials,
+ field_trial_states);
+ }
+
+ GetContentClient()->browser()->AppendExtraCommandLineSwitches(
+ command_line, GetID());
+
+ // Appending disable-gpu-feature switches due to software rendering list.
+ GpuDataManagerImpl* gpu_data_manager = GpuDataManagerImpl::GetInstance();
+ DCHECK(gpu_data_manager);
+ gpu_data_manager->AppendRendererCommandLine(command_line);
+}
+
+void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
+ const CommandLine& browser_cmd,
+ CommandLine* renderer_cmd) const {
+ // Propagate the following switches to the renderer command line (along
+ // with any associated values) if present in the browser command line.
+ static const char* const kSwitchNames[] = {
+ switches::kAllowFiltersOverIPC,
+ switches::kAudioBufferSize,
+ switches::kAuditAllHandles,
+ switches::kAuditHandles,
+ switches::kDisable3DAPIs,
+ switches::kDisableAcceleratedCompositing,
+ switches::kDisableAcceleratedVideoDecode,
+ switches::kDisableApplicationCache,
+ switches::kDisableAudio,
+ switches::kDisableBreakpad,
+ switches::kDisableDatabases,
+ switches::kDisableDelegatedRenderer,
+ switches::kDisableDesktopNotifications,
+ switches::kDisableDeviceOrientation,
+ switches::kDisableFileSystem,
+ switches::kDisableGeolocation,
+ switches::kDisableGLMultisampling,
+ switches::kDisableGpuVsync,
+ switches::kDisableGpu,
+ switches::kDisableGpuCompositing,
+ switches::kDisableHistogramCustomizer,
+ switches::kDisableLocalStorage,
+ switches::kDisableLogging,
+ switches::kDisableNewDialogStyle,
+ switches::kDisableSeccompFilterSandbox,
+ switches::kDisableSessionStorage,
+ switches::kDisableSharedWorkers,
+ switches::kDisableSpeechInput,
+ switches::kDisableTouchDragDrop,
+ switches::kDisableTouchEditing,
+#if defined(OS_ANDROID)
+ switches::kDisableWebRTC,
+ switches::kEnableSpeechRecognition,
+#endif
+ switches::kDisableWebAudio,
+#if defined(ENABLE_WEBRTC)
+ switches::kDisableDeviceEnumeration,
+ switches::kEnableSCTPDataChannels,
+#endif
+ switches::kEnableWebAnimationsCSS,
+ switches::kEnableWebAnimationsSVG,
+ switches::kEnableWebMIDI,
+ switches::kEnableExperimentalCanvasFeatures,
+ switches::kEnableExperimentalWebSocket,
+ switches::kDomAutomationController,
+ switches::kEnableAccessibilityLogging,
+ switches::kEnableBeginFrameScheduling,
+ switches::kEnableBrowserInputController,
+ switches::kEnableBrowserPluginForAllViewTypes,
+ switches::kEnableDCHECK,
+ switches::kEnableDelegatedRenderer,
+ switches::kEnableEncryptedMedia,
+ switches::kDisableLegacyEncryptedMedia,
+ switches::kOverrideEncryptedMediaCanPlayType,
+ switches::kEnableExperimentalWebPlatformFeatures,
+ switches::kEnableFixedLayout,
+ switches::kEnableDeferredImageDecoding,
+ switches::kEnableGPUServiceLogging,
+ switches::kEnableGPUClientLogging,
+ switches::kEnableGpuClientTracing,
+ switches::kEnableGpuBenchmarking,
+ switches::kEnableMemoryBenchmarking,
+ switches::kEnableOverlayScrollbars,
+ switches::kEnableSkiaBenchmarking,
+ switches::kEnableLogging,
+ switches::kEnableSpeechSynthesis,
+ switches::kEnableTouchDragDrop,
+ switches::kEnableTouchEditing,
+#if defined(ENABLE_WEBRTC)
+ switches::kEnableWebRtcAecRecordings,
+ switches::kEnableWebRtcTcpServerSocket,
+ switches::kEnableWebRtcHWDecoding,
+ switches::kEnableWebRtcHWEncoding,
+#endif
+ switches::kDisableWebKitMediaSource,
+ switches::kEnableOverscrollNotifications,
+ switches::kEnableStrictSiteIsolation,
+ switches::kDisableFullScreen,
+ switches::kEnableNewDialogStyle,
+#if defined(ENABLE_PLUGINS)
+ switches::kEnablePepperTesting,
+ switches::kDisablePepper3d,
+#endif
+ switches::kEnablePreparsedJsCaching,
+ switches::kEnablePruneGpuCommandBuffers,
+ switches::kEnablePinch,
+ switches::kDisablePinch,
+#if defined(OS_MACOSX)
+ // Allow this to be set when invoking the browser and relayed along.
+ switches::kEnableSandboxLogging,
+#endif
+ switches::kEnableSoftwareCompositing,
+ switches::kEnableStatsTable,
+ switches::kEnableThreadedCompositing,
+ switches::kEnableCompositingForFixedPosition,
+ switches::kEnableHighDpiCompositingForFixedPosition,
+ switches::kDisableCompositingForFixedPosition,
+ switches::kEnableAcceleratedOverflowScroll,
+ switches::kEnableCompositingForTransition,
+ switches::kDisableCompositingForTransition,
+ switches::kEnableAcceleratedFixedRootBackground,
+ switches::kDisableAcceleratedFixedRootBackground,
+ switches::kDisableThreadedCompositing,
+ switches::kDisableTouchAdjustment,
+ switches::kDefaultTileWidth,
+ switches::kDefaultTileHeight,
+ switches::kMaxUntiledLayerWidth,
+ switches::kMaxUntiledLayerHeight,
+ switches::kEnableViewport,
+ switches::kEnableInbandTextTracks,
+ switches::kEnableOpusPlayback,
+ switches::kEnableVp8AlphaPlayback,
+ switches::kEnableEac3Playback,
+ switches::kForceDeviceScaleFactor,
+ switches::kFullMemoryCrashReport,
+#if defined(OS_ANDROID)
+ switches::kHideScrollbars,
+#endif
+#if !defined (GOOGLE_CHROME_BUILD)
+ // These are unsupported and not fully tested modes, so don't enable them
+ // for official Google Chrome builds.
+ switches::kInProcessPlugins,
+#endif // GOOGLE_CHROME_BUILD
+ switches::kJavaScriptFlags,
+ switches::kLoggingLevel,
+ switches::kMemoryMetrics,
+#if defined(OS_ANDROID)
+ switches::kNetworkCountryIso,
+ switches::kDisableGestureRequirementForMediaPlayback,
+#endif
+#if defined(GOOGLE_TV)
+ switches::kUseExternalVideoSurfaceThresholdInPixels,
+#endif
+ switches::kNoReferrers,
+ switches::kNoSandbox,
+ switches::kEnableVtune,
+ switches::kPpapiInProcess,
+ switches::kRegisterPepperPlugins,
+ switches::kRendererAssertTest,
+#if defined(OS_POSIX)
+ switches::kChildCleanExit,
+#endif
+ switches::kRendererStartupDialog,
+ switches::kShowPaintRects,
+ switches::kSitePerProcess,
+ switches::kStatsCollectionController,
+ switches::kTestSandbox,
+ switches::kTouchEvents,
+ switches::kTraceStartup,
+ // This flag needs to be propagated to the renderer process for
+ // --in-process-webgl.
+ switches::kUseGL,
+ switches::kUseMobileUserAgent,
+ switches::kUserAgent,
+ switches::kV,
+ switches::kVideoThreads,
+ switches::kVModule,
+ switches::kWebCoreLogChannels,
+ switches::kEnableWebGLDraftExtensions,
+ switches::kEnableHTMLImports,
+ switches::kTraceToConsole,
+ switches::kEnableDeviceMotion,
+#if defined(OS_ANDROID)
+ switches::kDisableDeviceMotion,
+#endif
+ // Please keep these in alphabetical order. Compositor switches here should
+ // also be added to chrome/browser/chromeos/login/chrome_restart_request.cc.
+ cc::switches::kBackgroundColorInsteadOfCheckerboard,
+ cc::switches::kCompositeToMailbox,
+ cc::switches::kDisableCompositedAntialiasing,
+ cc::switches::kDisableImplSidePainting,
+ cc::switches::kDisableThreadedAnimation,
+ cc::switches::kEnableImplSidePainting,
+ cc::switches::kEnablePartialSwap,
+ cc::switches::kEnablePerTilePainting,
+ cc::switches::kEnablePinchVirtualViewport,
+ cc::switches::kEnableTopControlsPositionCalculation,
+ cc::switches::kForceDirectLayerDrawing,
+ cc::switches::kLowResolutionContentsScaleFactor,
+ cc::switches::kMaxTilesForInterestArea,
+ cc::switches::kMaxUnusedResourceMemoryUsagePercentage,
+ cc::switches::kNumRasterThreads,
+ cc::switches::kShowCompositedLayerBorders,
+ cc::switches::kShowFPSCounter,
+ cc::switches::kShowNonOccludingRects,
+ cc::switches::kShowOccludingRects,
+ cc::switches::kShowPropertyChangedRects,
+ cc::switches::kShowReplicaScreenSpaceRects,
+ cc::switches::kShowScreenSpaceRects,
+ cc::switches::kShowSurfaceDamageRects,
+ cc::switches::kSlowDownRasterScaleFactor,
+ cc::switches::kStrictLayerPropertyChangeChecking,
+ cc::switches::kTopControlsHeight,
+ cc::switches::kTopControlsHideThreshold,
+ cc::switches::kTopControlsShowThreshold,
+ cc::switches::kTraceOverdraw,
+ cc::switches::kUseMapImage,
+ };
+ renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames,
+ arraysize(kSwitchNames));
+
+ // Disable databases in incognito mode.
+ if (GetBrowserContext()->IsOffTheRecord() &&
+ !browser_cmd.HasSwitch(switches::kDisableDatabases)) {
+ renderer_cmd->AppendSwitch(switches::kDisableDatabases);
+#if defined(OS_ANDROID)
+ renderer_cmd->AppendSwitch(switches::kDisableMediaHistoryLogging);
+#endif
+ }
+
+ // Enforce the extra command line flags for impl-side painting.
+ if (cc::switches::IsImplSidePaintingEnabled() &&
+ !browser_cmd.HasSwitch(switches::kEnableDeferredImageDecoding))
+ renderer_cmd->AppendSwitch(switches::kEnableDeferredImageDecoding);
+}
+
+base::ProcessHandle RenderProcessHostImpl::GetHandle() const {
+ if (run_renderer_in_process())
+ return base::Process::Current().handle();
+
+ if (!child_process_launcher_.get() || child_process_launcher_->IsStarting())
+ return base::kNullProcessHandle;
+
+ return child_process_launcher_->GetHandle();
+}
+
+bool RenderProcessHostImpl::FastShutdownIfPossible() {
+ if (run_renderer_in_process())
+ return false; // Single process mode never shutdown the renderer.
+
+ if (!GetContentClient()->browser()->IsFastShutdownPossible())
+ return false;
+
+ if (!child_process_launcher_.get() ||
+ child_process_launcher_->IsStarting() ||
+ !GetHandle())
+ return false; // Render process hasn't started or is probably crashed.
+
+ // Test if there's an unload listener.
+ // NOTE: It's possible that an onunload listener may be installed
+ // while we're shutting down, so there's a small race here. Given that
+ // the window is small, it's unlikely that the web page has much
+ // state that will be lost by not calling its unload handlers properly.
+ if (!SuddenTerminationAllowed())
+ return false;
+
+ ProcessDied(false /* already_dead */);
+ fast_shutdown_started_ = true;
+ return true;
+}
+
+void RenderProcessHostImpl::DumpHandles() {
+#if defined(OS_WIN)
+ Send(new ChildProcessMsg_DumpHandles());
+ return;
+#endif
+
+ NOTIMPLEMENTED();
+}
+
+// This is a platform specific function for mapping a transport DIB given its id
+TransportDIB* RenderProcessHostImpl::MapTransportDIB(
+ TransportDIB::Id dib_id) {
+#if defined(OS_WIN)
+ // On Windows we need to duplicate the handle from the remote process
+ HANDLE section;
+ DuplicateHandle(GetHandle(), dib_id.handle, GetCurrentProcess(), &section,
+ STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ | FILE_MAP_WRITE,
+ FALSE, 0);
+ return TransportDIB::Map(section);
+#elif defined(TOOLKIT_GTK)
+ return TransportDIB::Map(dib_id.shmkey);
+#elif defined(OS_ANDROID)
+ return TransportDIB::Map(dib_id);
+#else
+ // On POSIX, the browser allocates all DIBs and keeps a file descriptor around
+ // for each.
+ return widget_helper_->MapTransportDIB(dib_id);
+#endif
+}
+
+TransportDIB* RenderProcessHostImpl::GetTransportDIB(
+ TransportDIB::Id dib_id) {
+ if (!TransportDIB::is_valid_id(dib_id))
+ return NULL;
+
+ const std::map<TransportDIB::Id, TransportDIB*>::iterator
+ i = cached_dibs_.find(dib_id);
+ if (i != cached_dibs_.end()) {
+ cached_dibs_cleaner_.Reset();
+ return i->second;
+ }
+
+ TransportDIB* dib = MapTransportDIB(dib_id);
+ if (!dib)
+ return NULL;
+
+ if (cached_dibs_.size() >= MAX_MAPPED_TRANSPORT_DIBS) {
+ // Clean a single entry from the cache
+ std::map<TransportDIB::Id, TransportDIB*>::iterator smallest_iterator;
+ size_t smallest_size = std::numeric_limits<size_t>::max();
+
+ for (std::map<TransportDIB::Id, TransportDIB*>::iterator
+ i = cached_dibs_.begin(); i != cached_dibs_.end(); ++i) {
+ if (i->second->size() <= smallest_size) {
+ smallest_iterator = i;
+ smallest_size = i->second->size();
+ }
+ }
+
+#if defined(TOOLKIT_GTK)
+ smallest_iterator->second->Detach();
+#else
+ delete smallest_iterator->second;
+#endif
+ cached_dibs_.erase(smallest_iterator);
+ }
+
+ cached_dibs_[dib_id] = dib;
+ cached_dibs_cleaner_.Reset();
+ return dib;
+}
+
+void RenderProcessHostImpl::ClearTransportDIBCache() {
+#if defined(TOOLKIT_GTK)
+ std::map<TransportDIB::Id, TransportDIB*>::const_iterator dib =
+ cached_dibs_.begin();
+ for (; dib != cached_dibs_.end(); ++dib)
+ dib->second->Detach();
+#else
+ STLDeleteContainerPairSecondPointers(
+ cached_dibs_.begin(), cached_dibs_.end());
+#endif
+ cached_dibs_.clear();
+}
+
+bool RenderProcessHostImpl::Send(IPC::Message* msg) {
+ if (!channel_) {
+ if (!is_initialized_) {
+ queued_messages_.push(msg);
+ return true;
+ } else {
+ delete msg;
+ return false;
+ }
+ }
+
+ if (child_process_launcher_.get() && child_process_launcher_->IsStarting()) {
+ queued_messages_.push(msg);
+ return true;
+ }
+
+ return channel_->Send(msg);
+}
+
+bool RenderProcessHostImpl::OnMessageReceived(const IPC::Message& msg) {
+ // If we're about to be deleted, or have initiated the fast shutdown sequence,
+ // we ignore incoming messages.
+
+ if (deleting_soon_ || fast_shutdown_started_)
+ return false;
+
+ mark_child_process_activity_time();
+ if (msg.routing_id() == MSG_ROUTING_CONTROL) {
+ // Dispatch control messages.
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderProcessHostImpl, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_ShutdownRequest,
+ OnShutdownRequest)
+ IPC_MESSAGE_HANDLER(ChildProcessHostMsg_DumpHandlesDone,
+ OnDumpHandlesDone)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SuddenTerminationChanged,
+ SuddenTerminationChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UserMetricsRecordAction,
+ OnUserMetricsRecordAction)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SavedPageAsMHTML, OnSavedPageAsMHTML)
+ // Adding single handlers for your service here is fine, but once your
+ // service needs more than one handler, please extract them into a new
+ // message filter and add that filter to CreateMessageFilters().
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!msg_is_ok) {
+ // The message had a handler, but its de-serialization failed.
+ // We consider this a capital crime. Kill the renderer if we have one.
+ LOG(ERROR) << "bad message " << msg.type() << " terminating renderer.";
+ RecordAction(UserMetricsAction("BadMessageTerminate_BRPH"));
+ ReceivedBadMessage();
+ }
+ return true;
+ }
+
+ // Dispatch incoming messages to the appropriate IPC::Listener.
+ IPC::Listener* listener = listeners_.Lookup(msg.routing_id());
+ if (!listener) {
+ if (msg.is_sync()) {
+ // The listener has gone away, so we must respond or else the caller will
+ // hang waiting for a reply.
+ IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg);
+ reply->set_reply_error();
+ Send(reply);
+ }
+
+ // If this is a SwapBuffers, we need to ack it if we're not going to handle
+ // it so that the GPU process doesn't get stuck in unscheduled state.
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderProcessHostImpl, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CompositorSurfaceBuffersSwapped,
+ OnCompositorSurfaceBuffersSwappedNoHost)
+ IPC_END_MESSAGE_MAP_EX()
+ return true;
+ }
+ return listener->OnMessageReceived(msg);
+}
+
+void RenderProcessHostImpl::OnChannelConnected(int32 peer_pid) {
+#if defined(IPC_MESSAGE_LOG_ENABLED)
+ Send(new ChildProcessMsg_SetIPCLoggingEnabled(
+ IPC::Logging::GetInstance()->Enabled()));
+#endif
+
+ tracked_objects::ThreadData::Status status =
+ tracked_objects::ThreadData::status();
+ Send(new ChildProcessMsg_SetProfilerStatus(status));
+}
+
+void RenderProcessHostImpl::OnChannelError() {
+ ProcessDied(true /* already_dead */);
+}
+
+BrowserContext* RenderProcessHostImpl::GetBrowserContext() const {
+ return browser_context_;
+}
+
+bool RenderProcessHostImpl::InSameStoragePartition(
+ StoragePartition* partition) const {
+ return storage_partition_impl_ == partition;
+}
+
+int RenderProcessHostImpl::GetID() const {
+ return id_;
+}
+
+bool RenderProcessHostImpl::HasConnection() const {
+ return channel_.get() != NULL;
+}
+
+void RenderProcessHostImpl::SetIgnoreInputEvents(bool ignore_input_events) {
+ ignore_input_events_ = ignore_input_events;
+}
+
+bool RenderProcessHostImpl::IgnoreInputEvents() const {
+ return ignore_input_events_;
+}
+
+void RenderProcessHostImpl::Cleanup() {
+ // When no other owners of this object, we can delete ourselves
+ if (listeners_.IsEmpty()) {
+ DCHECK_EQ(0, pending_views_);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ Source<RenderProcessHost>(this),
+ NotificationService::NoDetails());
+
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+ deleting_soon_ = true;
+ // It's important not to wait for the DeleteTask to delete the channel
+ // proxy. Kill it off now. That way, in case the profile is going away, the
+ // rest of the objects attached to this RenderProcessHost start going
+ // away first, since deleting the channel proxy will post a
+ // OnChannelClosed() to IPC::ChannelProxy::Context on the IO thread.
+ channel_.reset();
+ gpu_message_filter_ = NULL;
+
+ // Remove ourself from the list of renderer processes so that we can't be
+ // reused in between now and when the Delete task runs.
+ UnregisterHost(GetID());
+ }
+}
+
+void RenderProcessHostImpl::AddPendingView() {
+ pending_views_++;
+}
+
+void RenderProcessHostImpl::RemovePendingView() {
+ DCHECK(pending_views_);
+ pending_views_--;
+}
+
+void RenderProcessHostImpl::SetSuddenTerminationAllowed(bool enabled) {
+ sudden_termination_allowed_ = enabled;
+}
+
+bool RenderProcessHostImpl::SuddenTerminationAllowed() const {
+ return sudden_termination_allowed_;
+}
+
+base::TimeDelta RenderProcessHostImpl::GetChildProcessIdleTime() const {
+ return base::TimeTicks::Now() - child_process_activity_time_;
+}
+
+void RenderProcessHostImpl::SurfaceUpdated(int32 surface_id) {
+ if (!gpu_message_filter_)
+ return;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &GpuMessageFilter::SurfaceUpdated,
+ gpu_message_filter_,
+ surface_id));
+}
+
+void RenderProcessHostImpl::ResumeRequestsForView(int route_id) {
+ widget_helper_->ResumeRequestsForView(route_id);
+}
+
+IPC::ChannelProxy* RenderProcessHostImpl::GetChannel() {
+ return channel_.get();
+}
+
+bool RenderProcessHostImpl::FastShutdownForPageCount(size_t count) {
+ if (static_cast<size_t>(GetActiveViewCount()) == count)
+ return FastShutdownIfPossible();
+ return false;
+}
+
+bool RenderProcessHostImpl::FastShutdownStarted() const {
+ return fast_shutdown_started_;
+}
+
+// static
+void RenderProcessHostImpl::RegisterHost(int host_id, RenderProcessHost* host) {
+ g_all_hosts.Get().AddWithID(host, host_id);
+}
+
+// static
+void RenderProcessHostImpl::UnregisterHost(int host_id) {
+ RenderProcessHost* host = g_all_hosts.Get().Lookup(host_id);
+ if (!host)
+ return;
+
+ g_all_hosts.Get().Remove(host_id);
+
+ // Look up the map of site to process for the given browser_context,
+ // in case we need to remove this process from it. It will be registered
+ // under any sites it rendered that use process-per-site mode.
+ SiteProcessMap* map =
+ GetSiteProcessMapForBrowserContext(host->GetBrowserContext());
+ map->RemoveProcess(host);
+}
+
+// static
+bool RenderProcessHostImpl::IsSuitableHost(
+ RenderProcessHost* host,
+ BrowserContext* browser_context,
+ const GURL& site_url) {
+ if (run_renderer_in_process())
+ return true;
+
+ if (host->GetBrowserContext() != browser_context)
+ return false;
+
+ // Check whether the given host and the intended site_url will be using the
+ // same StoragePartition, since a RenderProcessHost can only support a single
+ // StoragePartition. This is relevant for packaged apps, browser tags, and
+ // isolated sites.
+ StoragePartition* dest_partition =
+ BrowserContext::GetStoragePartitionForSite(browser_context, site_url);
+ if (!host->InSameStoragePartition(dest_partition))
+ return false;
+
+ // All URLs are suitable if this is associated with a guest renderer process.
+ // TODO(fsamuel, creis): Further validation is needed to ensure that only
+ // normal web URLs are permitted in guest processes. We need to investigate
+ // where this validation should happen.
+ if (host->IsGuest())
+ return true;
+
+ if (!host->IsGuest() && site_url.SchemeIs(chrome::kGuestScheme))
+ return false;
+
+ if (ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
+ host->GetID()) !=
+ WebUIControllerFactoryRegistry::GetInstance()->UseWebUIBindingsForURL(
+ browser_context, site_url)) {
+ return false;
+ }
+
+ return GetContentClient()->browser()->IsSuitableHost(host, site_url);
+}
+
+// static
+bool RenderProcessHost::run_renderer_in_process() {
+ return g_run_renderer_in_process_;
+}
+
+// static
+void RenderProcessHost::SetRunRendererInProcess(bool value) {
+ g_run_renderer_in_process_ = value;
+
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (value && !command_line->HasSwitch(switches::kLang)) {
+ // Modify the current process' command line to include the browser locale,
+ // as the renderer expects this flag to be set.
+ const std::string locale =
+ GetContentClient()->browser()->GetApplicationLocale();
+ command_line->AppendSwitchASCII(switches::kLang, locale);
+ }
+}
+
+RenderProcessHost::iterator RenderProcessHost::AllHostsIterator() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return iterator(g_all_hosts.Pointer());
+}
+
+// static
+RenderProcessHost* RenderProcessHost::FromID(int render_process_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return g_all_hosts.Get().Lookup(render_process_id);
+}
+
+// static
+bool RenderProcessHost::ShouldTryToUseExistingProcessHost(
+ BrowserContext* browser_context, const GURL& url) {
+ // Experimental:
+ // If --enable-strict-site-isolation or --site-per-process is enabled, do not
+ // try to reuse renderer processes when over the limit. (We could allow pages
+ // from the same site to share, if we knew what the given process was
+ // dedicated to. Allowing no sharing is simpler for now.) This may cause
+ // resource exhaustion issues if too many sites are open at once.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kEnableStrictSiteIsolation) ||
+ command_line.HasSwitch(switches::kSitePerProcess))
+ return false;
+
+ if (run_renderer_in_process())
+ return true;
+
+ // NOTE: Sometimes it's necessary to create more render processes than
+ // GetMaxRendererProcessCount(), for instance when we want to create
+ // a renderer process for a browser context that has no existing
+ // renderers. This is OK in moderation, since the
+ // GetMaxRendererProcessCount() is conservative.
+ if (g_all_hosts.Get().size() >= GetMaxRendererProcessCount())
+ return true;
+
+ return GetContentClient()->browser()->
+ ShouldTryToUseExistingProcessHost(browser_context, url);
+}
+
+// static
+RenderProcessHost* RenderProcessHost::GetExistingProcessHost(
+ BrowserContext* browser_context,
+ const GURL& site_url) {
+ // First figure out which existing renderers we can use.
+ std::vector<RenderProcessHost*> suitable_renderers;
+ suitable_renderers.reserve(g_all_hosts.Get().size());
+
+ iterator iter(AllHostsIterator());
+ while (!iter.IsAtEnd()) {
+ if (RenderProcessHostImpl::IsSuitableHost(
+ iter.GetCurrentValue(),
+ browser_context, site_url))
+ suitable_renderers.push_back(iter.GetCurrentValue());
+
+ iter.Advance();
+ }
+
+ // Now pick a random suitable renderer, if we have any.
+ if (!suitable_renderers.empty()) {
+ int suitable_count = static_cast<int>(suitable_renderers.size());
+ int random_index = base::RandInt(0, suitable_count - 1);
+ return suitable_renderers[random_index];
+ }
+
+ return NULL;
+}
+
+// static
+bool RenderProcessHost::ShouldUseProcessPerSite(
+ BrowserContext* browser_context,
+ const GURL& url) {
+ // Returns true if we should use the process-per-site model. This will be
+ // the case if the --process-per-site switch is specified, or in
+ // process-per-site-instance for particular sites (e.g., WebUI).
+ // Note that --single-process is handled in ShouldTryToUseExistingProcessHost.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kProcessPerSite))
+ return true;
+
+ // We want to consolidate particular sites like WebUI even when we are using
+ // the process-per-tab or process-per-site-instance models.
+ // Note: DevTools pages have WebUI type but should not reuse the same host.
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, url) &&
+ !url.SchemeIs(chrome::kChromeDevToolsScheme)) {
+ return true;
+ }
+
+ // Otherwise let the content client decide, defaulting to false.
+ return GetContentClient()->browser()->ShouldUseProcessPerSite(browser_context,
+ url);
+}
+
+// static
+RenderProcessHost* RenderProcessHostImpl::GetProcessHostForSite(
+ BrowserContext* browser_context,
+ const GURL& url) {
+ // Look up the map of site to process for the given browser_context.
+ SiteProcessMap* map =
+ GetSiteProcessMapForBrowserContext(browser_context);
+
+ // See if we have an existing process with appropriate bindings for this site.
+ // If not, the caller should create a new process and register it.
+ std::string site = SiteInstance::GetSiteForURL(browser_context, url)
+ .possibly_invalid_spec();
+ RenderProcessHost* host = map->FindProcess(site);
+ if (host && !IsSuitableHost(host, browser_context, url)) {
+ // The registered process does not have an appropriate set of bindings for
+ // the url. Remove it from the map so we can register a better one.
+ RecordAction(UserMetricsAction("BindingsMismatch_GetProcessHostPerSite"));
+ map->RemoveProcess(host);
+ host = NULL;
+ }
+
+ return host;
+}
+
+void RenderProcessHostImpl::RegisterProcessHostForSite(
+ BrowserContext* browser_context,
+ RenderProcessHost* process,
+ const GURL& url) {
+ // Look up the map of site to process for the given browser_context.
+ SiteProcessMap* map =
+ GetSiteProcessMapForBrowserContext(browser_context);
+
+ // Only register valid, non-empty sites. Empty or invalid sites will not
+ // use process-per-site mode. We cannot check whether the process has
+ // appropriate bindings here, because the bindings have not yet been granted.
+ std::string site = SiteInstance::GetSiteForURL(browser_context, url)
+ .possibly_invalid_spec();
+ if (!site.empty())
+ map->RegisterProcess(site, process);
+}
+
+base::MessageLoop*
+ RenderProcessHostImpl::GetInProcessRendererThreadForTesting() {
+ return g_in_process_thread;
+}
+
+void RenderProcessHostImpl::ProcessDied(bool already_dead) {
+ // Our child process has died. If we didn't expect it, it's a crash.
+ // In any case, we need to let everyone know it's gone.
+ // The OnChannelError notification can fire multiple times due to nested sync
+ // calls to a renderer. If we don't have a valid channel here it means we
+ // already handled the error.
+
+ // child_process_launcher_ can be NULL in single process mode or if fast
+ // termination happened.
+ int exit_code = 0;
+ base::TerminationStatus status =
+ child_process_launcher_.get() ?
+ child_process_launcher_->GetChildTerminationStatus(already_dead,
+ &exit_code) :
+ base::TERMINATION_STATUS_NORMAL_TERMINATION;
+
+ RendererClosedDetails details(GetHandle(), status, exit_code);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ Source<RenderProcessHost>(this),
+ Details<RendererClosedDetails>(&details));
+
+ child_process_launcher_.reset();
+ channel_.reset();
+ gpu_message_filter_ = NULL;
+
+ IDMap<IPC::Listener>::iterator iter(&listeners_);
+ while (!iter.IsAtEnd()) {
+ iter.GetCurrentValue()->OnMessageReceived(
+ ViewHostMsg_RenderProcessGone(iter.GetCurrentKey(),
+ static_cast<int>(status),
+ exit_code));
+ iter.Advance();
+ }
+
+ ClearTransportDIBCache();
+
+ // this object is not deleted at this point and may be reused later.
+ // TODO(darin): clean this up
+}
+
+int RenderProcessHostImpl::GetActiveViewCount() {
+ int num_active_views = 0;
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ // Count only RenderWidgetHosts in this process.
+ if (widgets[i]->GetProcess()->GetID() == GetID())
+ num_active_views++;
+ }
+ return num_active_views;
+}
+
+// Frame subscription API for this class is for accelerated composited path
+// only. These calls are redirected to GpuMessageFilter.
+void RenderProcessHostImpl::BeginFrameSubscription(
+ int route_id,
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ if (!gpu_message_filter_)
+ return;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &GpuMessageFilter::BeginFrameSubscription,
+ gpu_message_filter_,
+ route_id, base::Passed(&subscriber)));
+}
+
+void RenderProcessHostImpl::EndFrameSubscription(int route_id) {
+ if (!gpu_message_filter_)
+ return;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &GpuMessageFilter::EndFrameSubscription,
+ gpu_message_filter_,
+ route_id));
+}
+
+void RenderProcessHostImpl::OnShutdownRequest() {
+ // Don't shut down if there are active RenderViews, or if there are pending
+ // RenderViews being swapped back in.
+ // In single process mode, we never shutdown the renderer.
+ int num_active_views = GetActiveViewCount();
+ if (pending_views_ || num_active_views > 0 || run_renderer_in_process())
+ return;
+
+ // Notify any contents that might have swapped out renderers from this
+ // process. They should not attempt to swap them back in.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_CLOSING,
+ Source<RenderProcessHost>(this),
+ NotificationService::NoDetails());
+
+ Send(new ChildProcessMsg_Shutdown());
+}
+
+void RenderProcessHostImpl::SuddenTerminationChanged(bool enabled) {
+ SetSuddenTerminationAllowed(enabled);
+}
+
+void RenderProcessHostImpl::OnDumpHandlesDone() {
+ Cleanup();
+}
+
+void RenderProcessHostImpl::SetBackgrounded(bool backgrounded) {
+ // Note: we always set the backgrounded_ value. If the process is NULL
+ // (and hence hasn't been created yet), we will set the process priority
+ // later when we create the process.
+ backgrounded_ = backgrounded;
+ if (!child_process_launcher_.get() || child_process_launcher_->IsStarting())
+ return;
+
+#if defined(OS_WIN)
+ // The cbstext.dll loads as a global GetMessage hook in the browser process
+ // and intercepts/unintercepts the kernel32 API SetPriorityClass in a
+ // background thread. If the UI thread invokes this API just when it is
+ // intercepted the stack is messed up on return from the interceptor
+ // which causes random crashes in the browser process. Our hack for now
+ // is to not invoke the SetPriorityClass API if the dll is loaded.
+ if (GetModuleHandle(L"cbstext.dll"))
+ return;
+#endif // OS_WIN
+
+ child_process_launcher_->SetProcessBackgrounded(backgrounded);
+}
+
+void RenderProcessHostImpl::OnProcessLaunched() {
+ // No point doing anything, since this object will be destructed soon. We
+ // especially don't want to send the RENDERER_PROCESS_CREATED notification,
+ // since some clients might expect a RENDERER_PROCESS_TERMINATED afterwards to
+ // properly cleanup.
+ if (deleting_soon_)
+ return;
+
+ if (child_process_launcher_) {
+ if (!child_process_launcher_->GetHandle()) {
+ OnChannelError();
+ return;
+ }
+
+ child_process_launcher_->SetProcessBackgrounded(backgrounded_);
+ }
+
+ // NOTE: This needs to be before sending queued messages because
+ // ExtensionService uses this notification to initialize the renderer process
+ // with state that must be there before any JavaScript executes.
+ //
+ // The queued messages contain such things as "navigate". If this notification
+ // was after, we can end up executing JavaScript before the initialization
+ // happens.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_CREATED,
+ Source<RenderProcessHost>(this),
+ NotificationService::NoDetails());
+
+ while (!queued_messages_.empty()) {
+ Send(queued_messages_.front());
+ queued_messages_.pop();
+ }
+}
+
+void RenderProcessHostImpl::OnUserMetricsRecordAction(
+ const std::string& action) {
+ RecordComputedAction(action);
+}
+
+void RenderProcessHostImpl::OnSavedPageAsMHTML(int job_id, int64 data_size) {
+ MHTMLGenerationManager::GetInstance()->MHTMLGenerated(job_id, data_size);
+}
+
+void RenderProcessHostImpl::OnCompositorSurfaceBuffersSwappedNoHost(
+ const ViewHostMsg_CompositorSurfaceBuffersSwapped_Params& params) {
+ TRACE_EVENT0("renderer_host",
+ "RenderWidgetHostImpl::OnCompositorSurfaceBuffersSwappedNoHost");
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(params.route_id,
+ params.gpu_process_host_id,
+ ack_params);
+}
+
+void RenderProcessHostImpl::OnGpuSwitching() {
+ // We are updating all widgets including swapped out ones.
+ RenderWidgetHost::List widgets =
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ if (!widgets[i]->IsRenderView())
+ continue;
+
+ // Skip widgets in other processes.
+ if (widgets[i]->GetProcess()->GetID() != GetID())
+ continue;
+
+ RenderViewHost* rvh = RenderViewHost::From(widgets[i]);
+ rvh->UpdateWebkitPreferences(rvh->GetWebkitPreferences());
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_process_host_impl.h b/chromium/content/browser/renderer_host/render_process_host_impl.h
new file mode 100644
index 00000000000..72ec846303d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_process_host_impl.h
@@ -0,0 +1,332 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_IMPL_H_
+
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "base/timer/timer.h"
+#include "content/browser/child_process_launcher.h"
+#include "content/browser/power_monitor_message_broadcaster.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/gpu_data_manager_observer.h"
+#include "content/public/browser/render_process_host.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ui/surface/transport_dib.h"
+
+class CommandLine;
+struct ViewHostMsg_CompositorSurfaceBuffersSwapped_Params;
+
+namespace base {
+class MessageLoop;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace content {
+class GpuMessageFilter;
+class PeerConnectionTrackerHost;
+class RendererMainThread;
+class RenderWidgetHelper;
+class RenderWidgetHost;
+class RenderWidgetHostImpl;
+class RenderWidgetHostViewFrameSubscriber;
+class StoragePartition;
+class StoragePartitionImpl;
+
+// Implements a concrete RenderProcessHost for the browser process for talking
+// to actual renderer processes (as opposed to mocks).
+//
+// Represents the browser side of the browser <--> renderer communication
+// channel. There will be one RenderProcessHost per renderer process.
+//
+// This object is refcounted so that it can release its resources when all
+// hosts using it go away.
+//
+// This object communicates back and forth with the RenderProcess object
+// running in the renderer process. Each RenderProcessHost and RenderProcess
+// keeps a list of RenderView (renderer) and WebContentsImpl (browser) which
+// are correlated with IDs. This way, the Views and the corresponding ViewHosts
+// communicate through the two process objects.
+//
+// A RenderProcessHost is also associated with one and only one
+// StoragePartition. This allows us to implement strong storage isolation
+// because all the IPCs from the RenderViews (renderer) will only ever be able
+// to access the partition they are assigned to.
+class CONTENT_EXPORT RenderProcessHostImpl
+ : public RenderProcessHost,
+ public ChildProcessLauncher::Client,
+ public GpuDataManagerObserver {
+ public:
+ RenderProcessHostImpl(BrowserContext* browser_context,
+ StoragePartitionImpl* storage_partition_impl,
+ bool supports_browser_plugin,
+ bool is_guest);
+ virtual ~RenderProcessHostImpl();
+
+ // RenderProcessHost implementation (public portion).
+ virtual void EnableSendQueue() OVERRIDE;
+ virtual bool Init() OVERRIDE;
+ virtual int GetNextRoutingID() OVERRIDE;
+ virtual void AddRoute(int32 routing_id, IPC::Listener* listener) OVERRIDE;
+ virtual void RemoveRoute(int32 routing_id) OVERRIDE;
+ virtual bool WaitForBackingStoreMsg(int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg) OVERRIDE;
+ virtual void ReceivedBadMessage() OVERRIDE;
+ virtual void WidgetRestored() OVERRIDE;
+ virtual void WidgetHidden() OVERRIDE;
+ virtual int VisibleWidgetCount() const OVERRIDE;
+ virtual bool IsGuest() const OVERRIDE;
+ virtual StoragePartition* GetStoragePartition() const OVERRIDE;
+ virtual bool FastShutdownIfPossible() OVERRIDE;
+ virtual void DumpHandles() OVERRIDE;
+ virtual base::ProcessHandle GetHandle() const OVERRIDE;
+ virtual TransportDIB* GetTransportDIB(TransportDIB::Id dib_id) OVERRIDE;
+ virtual TransportDIB* MapTransportDIB(TransportDIB::Id dib_id) OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual bool InSameStoragePartition(
+ StoragePartition* partition) const OVERRIDE;
+ virtual int GetID() const OVERRIDE;
+ virtual bool HasConnection() const OVERRIDE;
+ virtual void SetIgnoreInputEvents(bool ignore_input_events) OVERRIDE;
+ virtual bool IgnoreInputEvents() const OVERRIDE;
+ virtual void Cleanup() OVERRIDE;
+ virtual void AddPendingView() OVERRIDE;
+ virtual void RemovePendingView() OVERRIDE;
+ virtual void SetSuddenTerminationAllowed(bool enabled) OVERRIDE;
+ virtual bool SuddenTerminationAllowed() const OVERRIDE;
+ virtual IPC::ChannelProxy* GetChannel() OVERRIDE;
+ virtual bool FastShutdownForPageCount(size_t count) OVERRIDE;
+ virtual bool FastShutdownStarted() const OVERRIDE;
+ virtual base::TimeDelta GetChildProcessIdleTime() const OVERRIDE;
+ virtual void SurfaceUpdated(int32 surface_id) OVERRIDE;
+ virtual void ResumeRequestsForView(int route_id) OVERRIDE;
+
+ // IPC::Sender via RenderProcessHost.
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // IPC::Listener via RenderProcessHost.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+
+ // ChildProcessLauncher::Client implementation.
+ virtual void OnProcessLaunched() OVERRIDE;
+
+ // Tells the ResourceDispatcherHost to resume a deferred navigation without
+ // transferring it to a new renderer process.
+ void ResumeDeferredNavigation(const GlobalRequestID& request_id);
+
+ // Call this function when it is evident that the child process is actively
+ // performing some operation, for example if we just received an IPC message.
+ void mark_child_process_activity_time() {
+ child_process_activity_time_ = base::TimeTicks::Now();
+ }
+
+ // Returns the current number of active views in this process. Excludes
+ // any RenderViewHosts that are swapped out.
+ int GetActiveViewCount();
+
+ // Start and end frame subscription for a specific renderer.
+ // This API only supports subscription to accelerated composited frames.
+ void BeginFrameSubscription(
+ int route_id,
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber);
+ void EndFrameSubscription(int route_id);
+
+ // Register/unregister the host identified by the host id in the global host
+ // list.
+ static void RegisterHost(int host_id, RenderProcessHost* host);
+ static void UnregisterHost(int host_id);
+
+ // Returns true if |host| is suitable for launching a new view with |site_url|
+ // in the given |browser_context|.
+ static bool IsSuitableHost(RenderProcessHost* host,
+ BrowserContext* browser_context,
+ const GURL& site_url);
+
+ // Returns an existing RenderProcessHost for |url| in |browser_context|,
+ // if one exists. Otherwise a new RenderProcessHost should be created and
+ // registered using RegisterProcessHostForSite().
+ // This should only be used for process-per-site mode, which can be enabled
+ // globally with a command line flag or per-site, as determined by
+ // SiteInstanceImpl::ShouldUseProcessPerSite.
+ static RenderProcessHost* GetProcessHostForSite(
+ BrowserContext* browser_context,
+ const GURL& url);
+
+ // Registers the given |process| to be used for any instance of |url|
+ // within |browser_context|.
+ // This should only be used for process-per-site mode, which can be enabled
+ // globally with a command line flag or per-site, as determined by
+ // SiteInstanceImpl::ShouldUseProcessPerSite.
+ static void RegisterProcessHostForSite(
+ BrowserContext* browser_context,
+ RenderProcessHost* process,
+ const GURL& url);
+
+ static base::MessageLoop* GetInProcessRendererThreadForTesting();
+
+ protected:
+ // A proxy for our IPC::Channel that lives on the IO thread (see
+ // browser_process.h)
+ scoped_ptr<IPC::ChannelProxy> channel_;
+
+ // True if fast shutdown has been performed on this RPH.
+ bool fast_shutdown_started_;
+
+ // True if we've posted a DeleteTask and will be deleted soon.
+ bool deleting_soon_;
+
+ // The count of currently swapped out but pending RenderViews. We have
+ // started to swap these in, so the renderer process should not exit if
+ // this count is non-zero.
+ int32 pending_views_;
+
+ private:
+ friend class VisitRelayingRenderProcessHost;
+
+ // Creates and adds the IO thread message filters.
+ void CreateMessageFilters();
+
+ // Control message handlers.
+ void OnShutdownRequest();
+ void OnDumpHandlesDone();
+ void SuddenTerminationChanged(bool enabled);
+ void OnUserMetricsRecordAction(const std::string& action);
+ void OnSavedPageAsMHTML(int job_id, int64 mhtml_file_size);
+
+ // CompositorSurfaceBuffersSwapped handler when there's no RWH.
+ void OnCompositorSurfaceBuffersSwappedNoHost(
+ const ViewHostMsg_CompositorSurfaceBuffersSwapped_Params& params);
+
+ // Generates a command line to be used to spawn a renderer and appends the
+ // results to |*command_line|.
+ void AppendRendererCommandLine(CommandLine* command_line) const;
+
+ // Copies applicable command line switches from the given |browser_cmd| line
+ // flags to the output |renderer_cmd| line flags. Not all switches will be
+ // copied over.
+ void PropagateBrowserCommandLineToRenderer(const CommandLine& browser_cmd,
+ CommandLine* renderer_cmd) const;
+
+ // Callers can reduce the RenderProcess' priority.
+ void SetBackgrounded(bool backgrounded);
+
+ // Handle termination of our process.
+ void ProcessDied(bool already_dead);
+
+ virtual void OnGpuSwitching() OVERRIDE;
+
+ // The registered IPC listener objects. When this list is empty, we should
+ // delete ourselves.
+ IDMap<IPC::Listener> listeners_;
+
+ // The count of currently visible widgets. Since the host can be a container
+ // for multiple widgets, it uses this count to determine when it should be
+ // backgrounded.
+ int32 visible_widgets_;
+
+ // Does this process have backgrounded priority.
+ bool backgrounded_;
+
+ // Used to allow a RenderWidgetHost to intercept various messages on the
+ // IO thread.
+ scoped_refptr<RenderWidgetHelper> widget_helper_;
+
+ // The filter for GPU-related messages coming from the renderer.
+ // Thread safety note: this field is to be accessed from the UI thread.
+ // We don't keep a reference to it, to avoid it being destroyed on the UI
+ // thread, but we clear this field when we clear channel_. When channel_ goes
+ // away, it posts a task to the IO thread to destroy it there, so we know that
+ // it's valid if non-NULL.
+ GpuMessageFilter* gpu_message_filter_;
+
+ // A map of transport DIB ids to cached TransportDIBs
+ std::map<TransportDIB::Id, TransportDIB*> cached_dibs_;
+
+ enum {
+ // This is the maximum size of |cached_dibs_|
+ MAX_MAPPED_TRANSPORT_DIBS = 3,
+ };
+
+ void ClearTransportDIBCache();
+ // This is used to clear our cache five seconds after the last use.
+ base::DelayTimer<RenderProcessHostImpl> cached_dibs_cleaner_;
+
+#if !defined(CHROME_MULTIPLE_DLL)
+ // Used in single-process mode.
+ scoped_ptr<RendererMainThread> in_process_renderer_;
+#endif
+
+ // True after Init() has been called. We can't just check channel_ because we
+ // also reset that in the case of process termination.
+ bool is_initialized_;
+
+ // Used to launch and terminate the process without blocking the UI thread.
+ scoped_ptr<ChildProcessLauncher> child_process_launcher_;
+
+ // Messages we queue while waiting for the process handle. We queue them here
+ // instead of in the channel so that we ensure they're sent after init related
+ // messages that are sent once the process handle is available. This is
+ // because the queued messages may have dependencies on the init messages.
+ std::queue<IPC::Message*> queued_messages_;
+
+ // The globally-unique identifier for this RPH.
+ int id_;
+
+ BrowserContext* browser_context_;
+
+ // Owned by |browser_context_|.
+ StoragePartitionImpl* storage_partition_impl_;
+
+ // True if the process can be shut down suddenly. If this is true, then we're
+ // sure that all the RenderViews in the process can be shutdown suddenly. If
+ // it's false, then specific RenderViews might still be allowed to be shutdown
+ // suddenly by checking their SuddenTerminationAllowed() flag. This can occur
+ // if one WebContents has an unload event listener but another WebContents in
+ // the same process doesn't.
+ bool sudden_termination_allowed_;
+
+ // Set to true if we shouldn't send input events. We actually do the
+ // filtering for this at the render widget level.
+ bool ignore_input_events_;
+
+ // Records the last time we regarded the child process active.
+ base::TimeTicks child_process_activity_time_;
+
+ // Indicates whether this is a RenderProcessHost that has permission to embed
+ // Browser Plugins.
+ bool supports_browser_plugin_;
+
+ // Indicates whether this is a RenderProcessHost of a Browser Plugin guest
+ // renderer.
+ bool is_guest_;
+
+ // Forwards messages between WebRTCInternals in the browser process
+ // and PeerConnectionTracker in the renderer process.
+ scoped_refptr<PeerConnectionTrackerHost> peer_connection_tracker_host_;
+
+ // Prevents the class from being added as a GpuDataManagerImpl observer more
+ // than once.
+ bool gpu_observer_registered_;
+
+ // Forwards power state messages to the renderer process.
+ PowerMonitorMessageBroadcaster power_monitor_broadcaster_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderProcessHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_IMPL_H_
diff --git a/chromium/content/browser/renderer_host/render_sandbox_host_linux.cc b/chromium/content/browser/renderer_host/render_sandbox_host_linux.cc
new file mode 100644
index 00000000000..87d31dca88c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_sandbox_host_linux.cc
@@ -0,0 +1,721 @@
+// 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 "content/browser/renderer_host/render_sandbox_host_linux.h"
+
+#include <fcntl.h>
+#include <fontconfig/fontconfig.h>
+#include <stdint.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/linux_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/singleton.h"
+#include "base/pickle.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/posix/unix_domain_socket_linux.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "content/child/webkitplatformsupport_impl.h"
+#include "content/common/font_config_ipc_linux.h"
+#include "content/common/sandbox_linux.h"
+#include "skia/ext/skia_utils_base.h"
+#include "third_party/WebKit/public/web/WebKit.h"
+#include "third_party/WebKit/public/web/linux/WebFontInfo.h"
+#include "third_party/npapi/bindings/npapi_extensions.h"
+#include "third_party/skia/include/ports/SkFontConfigInterface.h"
+#include "ui/gfx/font_render_params_linux.h"
+
+using WebKit::WebCString;
+using WebKit::WebFontInfo;
+using WebKit::WebUChar;
+using WebKit::WebUChar32;
+
+namespace content {
+
+// http://code.google.com/p/chromium/wiki/LinuxSandboxIPC
+
+// BEWARE: code in this file run across *processes* (not just threads).
+
+// This code runs in a child process
+class SandboxIPCProcess {
+ public:
+ // lifeline_fd: this is the read end of a pipe which the browser process
+ // holds the other end of. If the browser process dies, its descriptors are
+ // closed and we will noticed an EOF on the pipe. That's our signal to exit.
+ // browser_socket: the browser's end of the sandbox IPC socketpair. From the
+ // point of view of the renderer, it's talking to the browser but this
+ // object actually services the requests.
+ // sandbox_cmd: the path of the sandbox executable
+ SandboxIPCProcess(int lifeline_fd, int browser_socket,
+ std::string sandbox_cmd)
+ : lifeline_fd_(lifeline_fd),
+ browser_socket_(browser_socket) {
+ if (!sandbox_cmd.empty()) {
+ sandbox_cmd_.push_back(sandbox_cmd);
+ sandbox_cmd_.push_back(base::kFindInodeSwitch);
+ }
+
+ // FontConfig doesn't provide a standard property to control subpixel
+ // positioning, so we pass the current setting through to WebKit.
+ WebFontInfo::setSubpixelPositioning(
+ gfx::GetDefaultWebkitSubpixelPositioning());
+ }
+
+ ~SandboxIPCProcess();
+
+ void Run() {
+ struct pollfd pfds[2];
+ pfds[0].fd = lifeline_fd_;
+ pfds[0].events = POLLIN;
+ pfds[1].fd = browser_socket_;
+ pfds[1].events = POLLIN;
+
+ int failed_polls = 0;
+ for (;;) {
+ const int r = HANDLE_EINTR(poll(pfds, 2, -1));
+ if (r < 1) {
+ LOG(WARNING) << "poll errno:" << errno;
+ if (failed_polls++ == 3) {
+ LOG(FATAL) << "poll failing. Sandbox host aborting.";
+ return;
+ }
+ continue;
+ }
+
+ failed_polls = 0;
+
+ if (pfds[0].revents) {
+ // our parent died so we should too.
+ _exit(0);
+ }
+
+ if (pfds[1].revents) {
+ HandleRequestFromRenderer(browser_socket_);
+ }
+ }
+ }
+
+ private:
+ void EnsureWebKitInitialized();
+
+ // ---------------------------------------------------------------------------
+ // Requests from the renderer...
+
+ void HandleRequestFromRenderer(int fd) {
+ std::vector<int> fds;
+
+ // A FontConfigIPC::METHOD_MATCH message could be kMaxFontFamilyLength
+ // bytes long (this is the largest message type).
+ // 128 bytes padding are necessary so recvmsg() does not return MSG_TRUNC
+ // error for a maximum length message.
+ char buf[FontConfigIPC::kMaxFontFamilyLength + 128];
+
+ const ssize_t len = UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds);
+ if (len == -1) {
+ // TODO: should send an error reply, or the sender might block forever.
+ NOTREACHED()
+ << "Sandbox host message is larger than kMaxFontFamilyLength";
+ return;
+ }
+ if (fds.empty())
+ return;
+
+ Pickle pickle(buf, len);
+ PickleIterator iter(pickle);
+
+ int kind;
+ if (!pickle.ReadInt(&iter, &kind))
+ goto error;
+
+ if (kind == FontConfigIPC::METHOD_MATCH) {
+ HandleFontMatchRequest(fd, pickle, iter, fds);
+ } else if (kind == FontConfigIPC::METHOD_OPEN) {
+ HandleFontOpenRequest(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_GET_FONT_FAMILY_FOR_CHAR) {
+ HandleGetFontFamilyForChar(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_LOCALTIME) {
+ HandleLocaltime(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_GET_CHILD_WITH_INODE) {
+ HandleGetChildWithInode(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_GET_STYLE_FOR_STRIKE) {
+ HandleGetStyleForStrike(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_MAKE_SHARED_MEMORY_SEGMENT) {
+ HandleMakeSharedMemorySegment(fd, pickle, iter, fds);
+ } else if (kind == LinuxSandbox::METHOD_MATCH_WITH_FALLBACK) {
+ HandleMatchWithFallback(fd, pickle, iter, fds);
+ }
+
+ error:
+ for (std::vector<int>::const_iterator
+ i = fds.begin(); i != fds.end(); ++i) {
+ close(*i);
+ }
+ }
+
+ int FindOrAddPath(const SkString& path) {
+ int count = paths_.count();
+ for (int i = 0; i < count; ++i) {
+ if (path == *paths_[i])
+ return i;
+ }
+ *paths_.append() = new SkString(path);
+ return count;
+ }
+
+ void HandleFontMatchRequest(int fd, const Pickle& pickle, PickleIterator iter,
+ std::vector<int>& fds) {
+ uint32_t requested_style;
+ std::string family;
+ if (!pickle.ReadString(&iter, &family) ||
+ !pickle.ReadUInt32(&iter, &requested_style))
+ return;
+
+ SkFontConfigInterface::FontIdentity result_identity;
+ SkString result_family;
+ SkTypeface::Style result_style;
+ SkFontConfigInterface* fc =
+ SkFontConfigInterface::GetSingletonDirectInterface();
+ const bool r = fc->matchFamilyName(
+ family.c_str(), static_cast<SkTypeface::Style>(requested_style),
+ &result_identity, &result_family, &result_style);
+
+ Pickle reply;
+ if (!r) {
+ reply.WriteBool(false);
+ } else {
+ // Stash away the returned path, so we can give it an ID (index)
+ // which will later be given to us in a request to open the file.
+ int index = FindOrAddPath(result_identity.fString);
+ result_identity.fID = static_cast<uint32_t>(index);
+
+ reply.WriteBool(true);
+ skia::WriteSkString(&reply, result_family);
+ skia::WriteSkFontIdentity(&reply, result_identity);
+ reply.WriteUInt32(result_style);
+ }
+ SendRendererReply(fds, reply, -1);
+ }
+
+ void HandleFontOpenRequest(int fd, const Pickle& pickle, PickleIterator iter,
+ std::vector<int>& fds) {
+ uint32_t index;
+ if (!pickle.ReadUInt32(&iter, &index))
+ return;
+ if (index >= static_cast<uint32_t>(paths_.count()))
+ return;
+ const int result_fd = open(paths_[index]->c_str(), O_RDONLY);
+
+ Pickle reply;
+ if (result_fd == -1) {
+ reply.WriteBool(false);
+ } else {
+ reply.WriteBool(true);
+ }
+
+ // The receiver will have its own access to the file, so we will close it
+ // after this send.
+ SendRendererReply(fds, reply, result_fd);
+
+ if (result_fd >= 0) {
+ int err = HANDLE_EINTR(close(result_fd));
+ DCHECK(!err);
+ }
+ }
+
+ void HandleGetFontFamilyForChar(int fd, const Pickle& pickle,
+ PickleIterator iter,
+ std::vector<int>& fds) {
+ // The other side of this call is
+ // chrome/renderer/renderer_sandbox_support_linux.cc
+
+ EnsureWebKitInitialized();
+ WebUChar32 c;
+ if (!pickle.ReadInt(&iter, &c))
+ return;
+
+ std::string preferred_locale;
+ if (!pickle.ReadString(&iter, &preferred_locale))
+ return;
+
+ WebKit::WebFontFamily family;
+ WebFontInfo::familyForChar(c, preferred_locale.c_str(), &family);
+
+ Pickle reply;
+ if (family.name.data()) {
+ reply.WriteString(family.name.data());
+ } else {
+ reply.WriteString(std::string());
+ }
+ reply.WriteBool(family.isBold);
+ reply.WriteBool(family.isItalic);
+ SendRendererReply(fds, reply, -1);
+ }
+
+ void HandleGetStyleForStrike(int fd, const Pickle& pickle,
+ PickleIterator iter,
+ std::vector<int>& fds) {
+ std::string family;
+ int sizeAndStyle;
+
+ if (!pickle.ReadString(&iter, &family) ||
+ !pickle.ReadInt(&iter, &sizeAndStyle)) {
+ return;
+ }
+
+ EnsureWebKitInitialized();
+ WebKit::WebFontRenderStyle style;
+ WebFontInfo::renderStyleForStrike(family.c_str(), sizeAndStyle, &style);
+
+ Pickle reply;
+ reply.WriteInt(style.useBitmaps);
+ reply.WriteInt(style.useAutoHint);
+ reply.WriteInt(style.useHinting);
+ reply.WriteInt(style.hintStyle);
+ reply.WriteInt(style.useAntiAlias);
+ reply.WriteInt(style.useSubpixelRendering);
+ reply.WriteInt(style.useSubpixelPositioning);
+
+ SendRendererReply(fds, reply, -1);
+ }
+
+ void HandleLocaltime(int fd, const Pickle& pickle, PickleIterator iter,
+ std::vector<int>& fds) {
+ // The other side of this call is in zygote_main_linux.cc
+
+ std::string time_string;
+ if (!pickle.ReadString(&iter, &time_string) ||
+ time_string.size() != sizeof(time_t)) {
+ return;
+ }
+
+ time_t time;
+ memcpy(&time, time_string.data(), sizeof(time));
+ // We use localtime here because we need the tm_zone field to be filled
+ // out. Since we are a single-threaded process, this is safe.
+ const struct tm* expanded_time = localtime(&time);
+
+ std::string result_string;
+ const char* time_zone_string = "";
+ if (expanded_time != NULL) {
+ result_string = std::string(reinterpret_cast<const char*>(expanded_time),
+ sizeof(struct tm));
+ time_zone_string = expanded_time->tm_zone;
+ }
+
+ Pickle reply;
+ reply.WriteString(result_string);
+ reply.WriteString(time_zone_string);
+ SendRendererReply(fds, reply, -1);
+ }
+
+ void HandleGetChildWithInode(int fd, const Pickle& pickle,
+ PickleIterator iter,
+ std::vector<int>& fds) {
+ // The other side of this call is in zygote_main_linux.cc
+ if (sandbox_cmd_.empty()) {
+ LOG(ERROR) << "Not in the sandbox, this should not be called";
+ return;
+ }
+
+ uint64_t inode;
+ if (!pickle.ReadUInt64(&iter, &inode))
+ return;
+
+ base::ProcessId pid = 0;
+ std::string inode_output;
+
+ std::vector<std::string> sandbox_cmd = sandbox_cmd_;
+ sandbox_cmd.push_back(base::Int64ToString(inode));
+ CommandLine get_inode_cmd(sandbox_cmd);
+ if (base::GetAppOutput(get_inode_cmd, &inode_output))
+ base::StringToInt(inode_output, &pid);
+
+ if (!pid) {
+ // Even though the pid is invalid, we still need to reply to the zygote
+ // and not just return here.
+ LOG(ERROR) << "Could not get pid";
+ }
+
+ Pickle reply;
+ reply.WriteInt(pid);
+ SendRendererReply(fds, reply, -1);
+ }
+
+ void HandleMakeSharedMemorySegment(int fd, const Pickle& pickle,
+ PickleIterator iter,
+ std::vector<int>& fds) {
+ base::SharedMemoryCreateOptions options;
+ uint32_t size;
+ if (!pickle.ReadUInt32(&iter, &size))
+ return;
+ options.size = size;
+ if (!pickle.ReadBool(&iter, &options.executable))
+ return;
+ int shm_fd = -1;
+ base::SharedMemory shm;
+ if (shm.Create(options))
+ shm_fd = shm.handle().fd;
+ Pickle reply;
+ SendRendererReply(fds, reply, shm_fd);
+ }
+
+ void HandleMatchWithFallback(int fd, const Pickle& pickle,
+ PickleIterator iter,
+ std::vector<int>& fds) {
+ // Unlike the other calls, for which we are an indirection in front of
+ // WebKit or Skia, this call is always made via this sandbox helper
+ // process. Therefore the fontconfig code goes in here directly.
+
+ std::string face;
+ bool is_bold, is_italic;
+ uint32 charset;
+
+ if (!pickle.ReadString(&iter, &face) ||
+ face.empty() ||
+ !pickle.ReadBool(&iter, &is_bold) ||
+ !pickle.ReadBool(&iter, &is_italic) ||
+ !pickle.ReadUInt32(&iter, &charset)) {
+ return;
+ }
+
+ FcLangSet* langset = FcLangSetCreate();
+ MSCharSetToFontconfig(langset, charset);
+
+ FcPattern* pattern = FcPatternCreate();
+ // TODO(agl): FC_FAMILy needs to change
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8*) face.c_str());
+ if (is_bold)
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if (is_italic)
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ FcPatternAddLangSet(pattern, FC_LANG, langset);
+ FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+
+ FcResult result;
+ FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
+ int font_fd = -1;
+ int good_enough_index = -1;
+ bool good_enough_index_set = false;
+
+ if (font_set) {
+ for (int i = 0; i < font_set->nfont; ++i) {
+ FcPattern* current = font_set->fonts[i];
+
+ // Older versions of fontconfig have a bug where they cannot select
+ // only scalable fonts so we have to manually filter the results.
+ FcBool is_scalable;
+ if (FcPatternGetBool(current, FC_SCALABLE, 0,
+ &is_scalable) != FcResultMatch ||
+ !is_scalable) {
+ continue;
+ }
+
+ FcChar8* c_filename;
+ if (FcPatternGetString(current, FC_FILE, 0, &c_filename) !=
+ FcResultMatch) {
+ continue;
+ }
+
+ // We only want to return sfnt (TrueType) based fonts. We don't have a
+ // very good way of detecting this so we'll filter based on the
+ // filename.
+ bool is_sfnt = false;
+ static const char kSFNTExtensions[][5] = {
+ ".ttf", ".otc", ".TTF", ".ttc", ""
+ };
+ const size_t filename_len = strlen(reinterpret_cast<char*>(c_filename));
+ for (unsigned j = 0; ; j++) {
+ if (kSFNTExtensions[j][0] == 0) {
+ // None of the extensions matched.
+ break;
+ }
+ const size_t ext_len = strlen(kSFNTExtensions[j]);
+ if (filename_len > ext_len &&
+ memcmp(c_filename + filename_len - ext_len,
+ kSFNTExtensions[j], ext_len) == 0) {
+ is_sfnt = true;
+ break;
+ }
+ }
+
+ if (!is_sfnt)
+ continue;
+
+ // This font is good enough to pass muster, but we might be able to do
+ // better with subsequent ones.
+ if (!good_enough_index_set) {
+ good_enough_index = i;
+ good_enough_index_set = true;
+ }
+
+ FcValue matrix;
+ bool have_matrix = FcPatternGet(current, FC_MATRIX, 0, &matrix) == 0;
+
+ if (is_italic && have_matrix) {
+ // we asked for an italic font, but fontconfig is giving us a
+ // non-italic font with a transformation matrix.
+ continue;
+ }
+
+ FcValue embolden;
+ const bool have_embolden =
+ FcPatternGet(current, FC_EMBOLDEN, 0, &embolden) == 0;
+
+ if (is_bold && have_embolden) {
+ // we asked for a bold font, but fontconfig gave us a non-bold font
+ // and asked us to apply fake bolding.
+ continue;
+ }
+
+ font_fd = open(reinterpret_cast<char*>(c_filename), O_RDONLY);
+ if (font_fd >= 0)
+ break;
+ }
+ }
+
+ if (font_fd == -1 && good_enough_index_set) {
+ // We didn't find a font that we liked, so we fallback to something
+ // acceptable.
+ FcPattern* current = font_set->fonts[good_enough_index];
+ FcChar8* c_filename;
+ FcPatternGetString(current, FC_FILE, 0, &c_filename);
+ font_fd = open(reinterpret_cast<char*>(c_filename), O_RDONLY);
+ }
+
+ if (font_set)
+ FcFontSetDestroy(font_set);
+ FcPatternDestroy(pattern);
+
+ Pickle reply;
+ SendRendererReply(fds, reply, font_fd);
+
+ if (font_fd >= 0) {
+ if (HANDLE_EINTR(close(font_fd)) < 0)
+ PLOG(ERROR) << "close";
+ }
+ }
+
+ // MSCharSetToFontconfig translates a Microsoft charset identifier to a
+ // fontconfig language set by appending to |langset|.
+ static void MSCharSetToFontconfig(FcLangSet* langset, unsigned fdwCharSet) {
+ // We have need to translate raw fdwCharSet values into terms that
+ // fontconfig can understand. (See the description of fdwCharSet in the MSDN
+ // documentation for CreateFont:
+ // http://msdn.microsoft.com/en-us/library/dd183499(VS.85).aspx )
+ //
+ // Although the argument is /called/ 'charset', the actual values conflate
+ // character sets (which are sets of Unicode code points) and character
+ // encodings (which are algorithms for turning a series of bits into a
+ // series of code points.) Sometimes the values will name a language,
+ // sometimes they'll name an encoding. In the latter case I'm assuming that
+ // they mean the set of code points in the domain of that encoding.
+ //
+ // fontconfig deals with ISO 639-1 language codes:
+ // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+ //
+ // So, for each of the documented fdwCharSet values I've had to take a
+ // guess at the set of ISO 639-1 languages intended.
+
+ switch (fdwCharSet) {
+ case NPCharsetAnsi:
+ // These values I don't really know what to do with, so I'm going to map
+ // them to English also.
+ case NPCharsetDefault:
+ case NPCharsetMac:
+ case NPCharsetOEM:
+ case NPCharsetSymbol:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("en"));
+ break;
+ case NPCharsetBaltic:
+ // The three baltic languages.
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("et"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("lv"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("lt"));
+ break;
+ // TODO(jungshik): Would we be better off mapping Big5 to zh-tw
+ // and GB2312 to zh-cn? Fontconfig has 4 separate orthography
+ // files (zh-{cn,tw,hk,mo}.
+ case NPCharsetChineseBIG5:
+ case NPCharsetGB2312:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("zh"));
+ break;
+ case NPCharsetEastEurope:
+ // A scattering of eastern European languages.
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("pl"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("cs"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("sk"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("hu"));
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("hr"));
+ break;
+ case NPCharsetGreek:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("el"));
+ break;
+ case NPCharsetHangul:
+ case NPCharsetJohab:
+ // Korean
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ko"));
+ break;
+ case NPCharsetRussian:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ru"));
+ break;
+ case NPCharsetShiftJIS:
+ // Japanese
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ja"));
+ break;
+ case NPCharsetTurkish:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("tr"));
+ break;
+ case NPCharsetVietnamese:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("vi"));
+ break;
+ case NPCharsetArabic:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ar"));
+ break;
+ case NPCharsetHebrew:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("he"));
+ break;
+ case NPCharsetThai:
+ FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("th"));
+ break;
+ // default:
+ // Don't add any languages in that case that we don't recognise the
+ // constant.
+ }
+ }
+
+ void SendRendererReply(const std::vector<int>& fds, const Pickle& reply,
+ int reply_fd) {
+ struct msghdr msg;
+ memset(&msg, 0, sizeof(msg));
+ struct iovec iov = {const_cast<void*>(reply.data()), reply.size()};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ char control_buffer[CMSG_SPACE(sizeof(int))];
+
+ if (reply_fd != -1) {
+ struct stat st;
+ if (fstat(reply_fd, &st) == 0 && S_ISDIR(st.st_mode)) {
+ LOG(FATAL) << "Tried to send a directory descriptor over sandbox IPC";
+ // We must never send directory descriptors to a sandboxed process
+ // because they can use openat with ".." elements in the path in order
+ // to escape the sandbox and reach the real filesystem.
+ }
+
+ struct cmsghdr *cmsg;
+ msg.msg_control = control_buffer;
+ msg.msg_controllen = sizeof(control_buffer);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &reply_fd, sizeof(reply_fd));
+ msg.msg_controllen = cmsg->cmsg_len;
+ }
+
+ if (HANDLE_EINTR(sendmsg(fds[0], &msg, MSG_DONTWAIT)) < 0)
+ PLOG(ERROR) << "sendmsg";
+ }
+
+ // ---------------------------------------------------------------------------
+
+ const int lifeline_fd_;
+ const int browser_socket_;
+ std::vector<std::string> sandbox_cmd_;
+ scoped_ptr<WebKitPlatformSupportImpl> webkit_platform_support_;
+ SkTDArray<SkString*> paths_;
+};
+
+SandboxIPCProcess::~SandboxIPCProcess() {
+ paths_.deleteAll();
+ if (webkit_platform_support_)
+ WebKit::shutdownWithoutV8();
+}
+
+void SandboxIPCProcess::EnsureWebKitInitialized() {
+ if (webkit_platform_support_)
+ return;
+ webkit_platform_support_.reset(new WebKitPlatformSupportImpl);
+ WebKit::initializeWithoutV8(webkit_platform_support_.get());
+}
+
+// -----------------------------------------------------------------------------
+
+// Runs on the main thread at startup.
+RenderSandboxHostLinux::RenderSandboxHostLinux()
+ : initialized_(false),
+ renderer_socket_(0),
+ childs_lifeline_fd_(0),
+ pid_(0) {
+}
+
+// static
+RenderSandboxHostLinux* RenderSandboxHostLinux::GetInstance() {
+ return Singleton<RenderSandboxHostLinux>::get();
+}
+
+void RenderSandboxHostLinux::Init(const std::string& sandbox_path) {
+ DCHECK(!initialized_);
+ initialized_ = true;
+
+ int fds[2];
+ // We use SOCK_SEQPACKET rather than SOCK_DGRAM to prevent the renderer from
+ // sending datagrams to other sockets on the system. The sandbox may prevent
+ // the renderer from calling socket() to create new sockets, but it'll still
+ // inherit some sockets. With PF_UNIX+SOCK_DGRAM, it can call sendmsg to send
+ // a datagram to any (abstract) socket on the same system. With
+ // SOCK_SEQPACKET, this is prevented.
+#if defined(OS_FREEBSD) || defined(OS_OPENBSD)
+ // The BSDs often don't support SOCK_SEQPACKET yet, so fall back to
+ // SOCK_DGRAM if necessary.
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) != 0)
+ CHECK(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0);
+#else
+ CHECK(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) == 0);
+#endif
+
+ renderer_socket_ = fds[0];
+ const int browser_socket = fds[1];
+
+ int pipefds[2];
+ CHECK(0 == pipe(pipefds));
+ const int child_lifeline_fd = pipefds[0];
+ childs_lifeline_fd_ = pipefds[1];
+
+ pid_ = fork();
+ if (pid_ == 0) {
+ if (HANDLE_EINTR(close(fds[0])) < 0)
+ DPLOG(ERROR) << "close";
+ if (HANDLE_EINTR(close(pipefds[1])) < 0)
+ DPLOG(ERROR) << "close";
+
+ SandboxIPCProcess handler(child_lifeline_fd, browser_socket, sandbox_path);
+ handler.Run();
+ _exit(0);
+ }
+}
+
+RenderSandboxHostLinux::~RenderSandboxHostLinux() {
+ if (initialized_) {
+ if (HANDLE_EINTR(close(renderer_socket_)) < 0)
+ PLOG(ERROR) << "close";
+ if (HANDLE_EINTR(close(childs_lifeline_fd_)) < 0)
+ PLOG(ERROR) << "close";
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_sandbox_host_linux.h b/chromium/content/browser/renderer_host/render_sandbox_host_linux.h
new file mode 100644
index 00000000000..d9f7edb39e0
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_sandbox_host_linux.h
@@ -0,0 +1,56 @@
+// 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.
+
+// http://code.google.com/p/chromium/wiki/LinuxSandboxIPC
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "content/common/content_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace content {
+
+// This is a singleton object which handles sandbox requests from the
+// renderers.
+class CONTENT_EXPORT RenderSandboxHostLinux {
+ public:
+ // Returns the singleton instance.
+ static RenderSandboxHostLinux* GetInstance();
+
+ // Get the file descriptor which renderers should be given in order to signal
+ // crashes to the browser.
+ int GetRendererSocket() const {
+ DCHECK(initialized_);
+ return renderer_socket_;
+ }
+ pid_t pid() const {
+ DCHECK(initialized_);
+ return pid_;
+ }
+ void Init(const std::string& sandbox_path);
+
+ private:
+ friend struct DefaultSingletonTraits<RenderSandboxHostLinux>;
+ // This object must be constructed on the main thread.
+ RenderSandboxHostLinux();
+ ~RenderSandboxHostLinux();
+
+ // Whether Init() has been called yet.
+ bool initialized_;
+
+ int renderer_socket_;
+ int childs_lifeline_fd_;
+ pid_t pid_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderSandboxHostLinux);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_
diff --git a/chromium/content/browser/renderer_host/render_view_host_browsertest.cc b/chromium/content/browser/renderer_host/render_view_host_browsertest.cc
new file mode 100644
index 00000000000..4255d055ce1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_browsertest.cc
@@ -0,0 +1,118 @@
+// 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 "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace content {
+
+class RenderViewHostTest : public ContentBrowserTest {
+ public:
+ RenderViewHostTest() {}
+};
+
+class RenderViewHostTestWebContentsObserver : public WebContentsObserver {
+ public:
+ explicit RenderViewHostTestWebContentsObserver(WebContents* web_contents)
+ : WebContentsObserver(web_contents),
+ navigation_count_(0) {}
+ virtual ~RenderViewHostTestWebContentsObserver() {}
+
+ virtual void DidNavigateMainFrame(
+ const LoadCommittedDetails& details,
+ const FrameNavigateParams& params) OVERRIDE {
+ observed_socket_address_ = params.socket_address;
+ base_url_ = params.base_url;
+ ++navigation_count_;
+ }
+
+ const net::HostPortPair& observed_socket_address() const {
+ return observed_socket_address_;
+ }
+
+ GURL base_url() const {
+ return base_url_;
+ }
+
+ int navigation_count() const { return navigation_count_; }
+
+ private:
+ net::HostPortPair observed_socket_address_;
+ GURL base_url_;
+ int navigation_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostTestWebContentsObserver);
+};
+
+IN_PROC_BROWSER_TEST_F(RenderViewHostTest, FrameNavigateSocketAddress) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ RenderViewHostTestWebContentsObserver observer(shell()->web_contents());
+
+ GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
+ NavigateToURL(shell(), test_url);
+
+ EXPECT_EQ(net::HostPortPair::FromURL(
+ embedded_test_server()->base_url()).ToString(),
+ observer.observed_socket_address().ToString());
+ EXPECT_EQ(1, observer.navigation_count());
+}
+
+IN_PROC_BROWSER_TEST_F(RenderViewHostTest, BaseURLParam) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ RenderViewHostTestWebContentsObserver observer(shell()->web_contents());
+
+ // Base URL is not set if it is the same as the URL.
+ GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
+ NavigateToURL(shell(), test_url);
+ EXPECT_TRUE(observer.base_url().is_empty());
+ EXPECT_EQ(1, observer.navigation_count());
+
+ // But should be set to the original page when reading MHTML.
+ base::FilePath content_test_data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &content_test_data_dir));
+ test_url = net::FilePathToFileURL(
+ content_test_data_dir.AppendASCII("google.mht"));
+ NavigateToURL(shell(), test_url);
+ EXPECT_EQ("http://www.google.com/", observer.base_url().spec());
+}
+
+// This test ensures a RenderFrameHost object is created for the top level frame
+// in each RenderViewHost.
+IN_PROC_BROWSER_TEST_F(RenderViewHostTest, BasicRenderFrameHost) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
+ NavigateToURL(shell(), test_url);
+
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ shell()->web_contents()->GetRenderViewHost());
+ EXPECT_TRUE(rvh->main_render_frame_host_.get());
+
+ ShellAddedObserver new_shell_observer;
+ EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "window.open();"));
+ Shell* new_shell = new_shell_observer.GetShell();
+ RenderViewHostImpl* new_rvh = static_cast<RenderViewHostImpl*>(
+ new_shell->web_contents()->GetRenderViewHost());
+
+ EXPECT_TRUE(new_rvh->main_render_frame_host_.get());
+ EXPECT_NE(rvh->main_render_frame_host_->routing_id(),
+ new_rvh->main_render_frame_host_->routing_id());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_view_host_delegate.cc b/chromium/content/browser/renderer_host/render_view_host_delegate.cc
new file mode 100644
index 00000000000..5ef5dc0a82c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_delegate.cc
@@ -0,0 +1,53 @@
+// 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 "content/browser/renderer_host/render_view_host_delegate.h"
+
+#include "url/gurl.h"
+#include "webkit/common/webpreferences.h"
+
+namespace content {
+
+RenderViewHostDelegateView* RenderViewHostDelegate::GetDelegateView() {
+ return NULL;
+}
+
+RenderViewHostDelegate::RendererManagement*
+RenderViewHostDelegate::GetRendererManagementDelegate() {
+ return NULL;
+}
+
+bool RenderViewHostDelegate::OnMessageReceived(RenderViewHost* render_view_host,
+ const IPC::Message& message) {
+ return false;
+}
+
+bool RenderViewHostDelegate::AddMessageToConsole(
+ int32 level, const string16& message, int32 line_no,
+ const string16& source_id) {
+ return false;
+}
+
+const GURL& RenderViewHostDelegate::GetURL() const {
+ return GURL::EmptyGURL();
+}
+
+WebContents* RenderViewHostDelegate::GetAsWebContents() {
+ return NULL;
+}
+
+WebPreferences RenderViewHostDelegate::GetWebkitPrefs() {
+ return WebPreferences();
+}
+
+bool RenderViewHostDelegate::IsFullscreenForCurrentTab() const {
+ return false;
+}
+
+SessionStorageNamespace* RenderViewHostDelegate::GetSessionStorageNamespace(
+ SiteInstance* instance) {
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_view_host_delegate.h b/chromium/content/browser/renderer_host/render_view_host_delegate.h
new file mode 100644
index 00000000000..5ce2a44d716
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_delegate.h
@@ -0,0 +1,429 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/i18n/rtl.h"
+#include "base/process/kill.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/public/common/javascript_message_type.h"
+#include "content/public/common/media_stream_request.h"
+#include "net/base/load_states.h"
+#include "third_party/WebKit/public/web/WebPopupType.h"
+#include "ui/base/window_open_disposition.h"
+
+class GURL;
+class SkBitmap;
+class WebKeyboardEvent;
+struct ViewHostMsg_CreateWindow_Params;
+struct ViewHostMsg_DidFailProvisionalLoadWithError_Params;
+struct ViewHostMsg_FrameNavigate_Params;
+struct ViewMsg_PostMessage_Params;
+struct WebPreferences;
+
+namespace base {
+class ListValue;
+class TimeTicks;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace gfx {
+class Point;
+class Rect;
+class Size;
+}
+
+namespace content {
+
+class BrowserContext;
+class PageState;
+class RenderViewHost;
+class RenderViewHostDelegateView;
+class SessionStorageNamespace;
+class WebContents;
+class WebContentsImpl;
+struct ContextMenuParams;
+struct FileChooserParams;
+struct GlobalRequestID;
+struct NativeWebKeyboardEvent;
+struct Referrer;
+struct RendererPreferences;
+class SiteInstance;
+
+//
+// RenderViewHostDelegate
+//
+// An interface implemented by an object interested in knowing about the state
+// of the RenderViewHost.
+//
+// This interface currently encompasses every type of message that was
+// previously being sent by WebContents itself. Some of these notifications
+// may not be relevant to all users of RenderViewHost and we should consider
+// exposing a more generic Send function on RenderViewHost and a response
+// listener here to serve that need.
+class CONTENT_EXPORT RenderViewHostDelegate {
+ public:
+ // RendererManagerment -------------------------------------------------------
+ // Functions for managing switching of Renderers. For WebContents, this is
+ // implemented by the RenderViewHostManager.
+
+ class CONTENT_EXPORT RendererManagement {
+ public:
+ // Notification whether we should close the page, after an explicit call to
+ // AttemptToClosePage. This is called before a cross-site request or before
+ // a tab/window is closed (as indicated by the first parameter) to allow the
+ // appropriate renderer to approve or deny the request. |proceed| indicates
+ // whether the user chose to proceed. |proceed_time| is the time when the
+ // request was allowed to proceed.
+ virtual void ShouldClosePage(
+ bool for_cross_site_transition,
+ bool proceed,
+ const base::TimeTicks& proceed_time) = 0;
+
+ // The |pending_render_view_host| is ready to commit a page. The delegate
+ // should ensure that the old RenderViewHost runs its unload handler first.
+ virtual void OnCrossSiteResponse(
+ RenderViewHost* pending_render_view_host,
+ const GlobalRequestID& global_request_id) = 0;
+
+ protected:
+ virtual ~RendererManagement() {}
+ };
+
+ // ---------------------------------------------------------------------------
+
+ // Returns the current delegate associated with a feature. May return NULL if
+ // there is no corresponding delegate.
+ virtual RenderViewHostDelegateView* GetDelegateView();
+ virtual RendererManagement* GetRendererManagementDelegate();
+
+ // This is used to give the delegate a chance to filter IPC messages.
+ virtual bool OnMessageReceived(RenderViewHost* render_view_host,
+ const IPC::Message& message);
+
+ // Gets the URL that is currently being displayed, if there is one.
+ virtual const GURL& GetURL() const;
+
+ // Return this object cast to a WebContents, if it is one. If the object is
+ // not a WebContents, returns NULL. DEPRECATED: Be sure to include brettw or
+ // jam as reviewers before you use this method. http://crbug.com/82582
+ virtual WebContents* GetAsWebContents();
+
+ // Return the rect where to display the resize corner, if any, otherwise
+ // an empty rect.
+ virtual gfx::Rect GetRootWindowResizerRect() const = 0;
+
+ // The RenderView is being constructed (message sent to the renderer process
+ // to construct a RenderView). Now is a good time to send other setup events
+ // to the RenderView. This precedes any other commands to the RenderView.
+ virtual void RenderViewCreated(RenderViewHost* render_view_host) {}
+
+ // The RenderView has been constructed.
+ virtual void RenderViewReady(RenderViewHost* render_view_host) {}
+
+ // The RenderView died somehow (crashed or was killed by the user).
+ virtual void RenderViewTerminated(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) {}
+
+ // The RenderView is going to be deleted. This is called when each
+ // RenderView is going to be destroyed
+ virtual void RenderViewDeleted(RenderViewHost* render_view_host) {}
+
+ // The RenderView started a provisional load for a given frame.
+ virtual void DidStartProvisionalLoadForFrame(
+ RenderViewHost* render_view_host,
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool main_frame,
+ const GURL& url) {}
+
+ // The RenderView processed a redirect during a provisional load.
+ //
+ // TODO(creis): Remove this method and have the pre-rendering code listen to
+ // the ResourceDispatcherHost's RESOURCE_RECEIVED_REDIRECT notification
+ // instead. See http://crbug.com/78512.
+ virtual void DidRedirectProvisionalLoad(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) {}
+
+ // A provisional load in the RenderView failed.
+ virtual void DidFailProvisionalLoadWithError(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_DidFailProvisionalLoadWithError_Params& params) {}
+
+ // The RenderView was navigated to a different page.
+ virtual void DidNavigate(RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {}
+
+ // The state for the page changed and should be updated.
+ virtual void UpdateState(RenderViewHost* render_view_host,
+ int32 page_id,
+ const PageState& state) {}
+
+ // The page's title was changed and should be updated.
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) {}
+
+ // The page's encoding was changed and should be updated.
+ virtual void UpdateEncoding(RenderViewHost* render_view_host,
+ const std::string& encoding) {}
+
+ // The destination URL has changed should be updated
+ virtual void UpdateTargetURL(int32 page_id, const GURL& url) {}
+
+ // The page is trying to close the RenderView's representation in the client.
+ virtual void Close(RenderViewHost* render_view_host) {}
+
+ // The RenderViewHost has been swapped out.
+ virtual void SwappedOut(RenderViewHost* render_view_host) {}
+
+ // The page is trying to move the RenderView's representation in the client.
+ virtual void RequestMove(const gfx::Rect& new_bounds) {}
+
+ // The RenderView began loading a new page. This corresponds to WebKit's
+ // notion of the throbber starting.
+ virtual void DidStartLoading(RenderViewHost* render_view_host) {}
+
+ // The RenderView stopped loading a page. This corresponds to WebKit's
+ // notion of the throbber stopping.
+ virtual void DidStopLoading(RenderViewHost* render_view_host) {}
+
+ // The pending page load was canceled.
+ virtual void DidCancelLoading() {}
+
+ // The RenderView made progress loading a page's top frame.
+ // |progress| is a value between 0 (nothing loaded) to 1.0 (top frame
+ // entirely loaded).
+ virtual void DidChangeLoadProgress(double progress) {}
+
+ // The RenderView set its opener to null, disowning it for the lifetime of
+ // the window.
+ virtual void DidDisownOpener(RenderViewHost* rvh) {}
+
+ // Another page accessed the initial empty document of this RenderView,
+ // which means it is no longer safe to display a pending URL without
+ // risking a URL spoof.
+ virtual void DidAccessInitialDocument() {}
+
+ // The RenderView's main frame document element is ready. This happens when
+ // the document has finished parsing.
+ virtual void DocumentAvailableInMainFrame(RenderViewHost* render_view_host) {}
+
+ // The onload handler in the RenderView's main frame has completed.
+ virtual void DocumentOnLoadCompletedInMainFrame(
+ RenderViewHost* render_view_host,
+ int32 page_id) {}
+
+ // The page wants to open a URL with the specified disposition.
+ virtual void RequestOpenURL(RenderViewHost* rvh,
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ bool is_redirect,
+ bool user_gesture) {}
+
+ // The page wants to transfer the request to a new renderer.
+ virtual void RequestTransferURL(
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ const GlobalRequestID& old_request_id,
+ bool is_redirect,
+ bool user_gesture) {}
+
+ // The page wants to close the active view in this tab.
+ virtual void RouteCloseEvent(RenderViewHost* rvh) {}
+
+ // The page wants to post a message to the active view in this tab.
+ virtual void RouteMessageEvent(
+ RenderViewHost* rvh,
+ const ViewMsg_PostMessage_Params& params) {}
+
+ // A javascript message, confirmation or prompt should be shown.
+ virtual void RunJavaScriptMessage(RenderViewHost* rvh,
+ const string16& message,
+ const string16& default_prompt,
+ const GURL& frame_url,
+ JavaScriptMessageType type,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message) {}
+
+ virtual void RunBeforeUnloadConfirm(RenderViewHost* rvh,
+ const string16& message,
+ bool is_reload,
+ IPC::Message* reply_msg) {}
+
+ // A message was added to to the console.
+ virtual bool AddMessageToConsole(int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id);
+
+ // Return a dummy RendererPreferences object that will be used by the renderer
+ // associated with the owning RenderViewHost.
+ virtual RendererPreferences GetRendererPrefs(
+ BrowserContext* browser_context) const = 0;
+
+ // Returns a WebPreferences object that will be used by the renderer
+ // associated with the owning render view host.
+ virtual WebPreferences GetWebkitPrefs();
+
+ // Notification the user has made a gesture while focus was on the
+ // page. This is used to avoid uninitiated user downloads (aka carpet
+ // bombing), see DownloadRequestLimiter for details.
+ virtual void OnUserGesture() {}
+
+ // Notification from the renderer host that blocked UI event occurred.
+ // This happens when there are tab-modal dialogs. In this case, the
+ // notification is needed to let us draw attention to the dialog (i.e.
+ // refocus on the modal dialog, flash title etc).
+ virtual void OnIgnoredUIEvent() {}
+
+ // Notification that the renderer has become unresponsive. The
+ // delegate can use this notification to show a warning to the user.
+ virtual void RendererUnresponsive(RenderViewHost* render_view_host,
+ bool is_during_unload) {}
+
+ // Notification that a previously unresponsive renderer has become
+ // responsive again. The delegate can use this notification to end the
+ // warning shown to the user.
+ virtual void RendererResponsive(RenderViewHost* render_view_host) {}
+
+ // Notification that the RenderViewHost's load state changed.
+ virtual void LoadStateChanged(const GURL& url,
+ const net::LoadStateWithParam& load_state,
+ uint64 upload_position,
+ uint64 upload_size) {}
+
+ // Notification that a worker process has crashed.
+ virtual void WorkerCrashed() {}
+
+ // The page wants the hosting window to activate/deactivate itself (it
+ // called the JavaScript window.focus()/blur() method).
+ virtual void Activate() {}
+ virtual void Deactivate() {}
+
+ // Notification that the view has lost capture.
+ virtual void LostCapture() {}
+
+ // Notifications about mouse events in this view. This is useful for
+ // implementing global 'on hover' features external to the view.
+ virtual void HandleMouseMove() {}
+ virtual void HandleMouseDown() {}
+ virtual void HandleMouseLeave() {}
+ virtual void HandleMouseUp() {}
+ virtual void HandlePointerActivate() {}
+ virtual void HandleGestureBegin() {}
+ virtual void HandleGestureEnd() {}
+
+ // Called when a file selection is to be done.
+ virtual void RunFileChooser(
+ RenderViewHost* render_view_host,
+ const FileChooserParams& params) {}
+
+ // Notification that the page wants to go into or out of fullscreen mode.
+ virtual void ToggleFullscreenMode(bool enter_fullscreen) {}
+ virtual bool IsFullscreenForCurrentTab() const;
+
+ // The contents' preferred size changed.
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
+
+ // The contents auto-resized and the container should match it.
+ virtual void ResizeDueToAutoResize(const gfx::Size& new_size) {}
+
+ // Requests to lock the mouse. Once the request is approved or rejected,
+ // GotResponseToLockMouseRequest() will be called on the requesting render
+ // view host.
+ virtual void RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) {}
+
+ // Notification that the view has lost the mouse lock.
+ virtual void LostMouseLock() {}
+
+ // The page is trying to open a new page (e.g. a popup window). The window
+ // should be created associated with the given route, but it should not be
+ // shown yet. That should happen in response to ShowCreatedWindow.
+ // |params.window_container_type| describes the type of RenderViewHost
+ // container that is requested -- in particular, the window.open call may
+ // have specified 'background' and 'persistent' in the feature string.
+ //
+ // The passed |params.frame_name| parameter is the name parameter that was
+ // passed to window.open(), and will be empty if none was passed.
+ //
+ // Note: this is not called "CreateWindow" because that will clash with
+ // the Windows function which is actually a #define.
+ virtual void CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) {}
+
+ // The page is trying to open a new widget (e.g. a select popup). The
+ // widget should be created associated with the given route, but it should
+ // not be shown yet. That should happen in response to ShowCreatedWidget.
+ // |popup_type| indicates if the widget is a popup and what kind of popup it
+ // is (select, autofill...).
+ virtual void CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {}
+
+ // Creates a full screen RenderWidget. Similar to above.
+ virtual void CreateNewFullscreenWidget(int route_id) {}
+
+ // Show a previously created page with the specified disposition and bounds.
+ // The window is identified by the route_id passed to CreateNewWindow.
+ //
+ // Note: this is not called "ShowWindow" because that will clash with
+ // the Windows function which is actually a #define.
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {}
+
+ // Show the newly created widget with the specified bounds.
+ // The widget is identified by the route_id passed to CreateNewWidget.
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {}
+
+ // Show the newly created full screen widget. Similar to above.
+ virtual void ShowCreatedFullscreenWidget(int route_id) {}
+
+ // A context menu should be shown, to be built using the context information
+ // provided in the supplied params.
+ virtual void ShowContextMenu(const ContextMenuParams& params) {}
+
+ // The render view has requested access to media devices listed in
+ // |request|, and the client should grant or deny that permission by
+ // calling |callback|.
+ virtual void RequestMediaAccessPermission(
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) {}
+
+ // Returns the SessionStorageNamespace the render view should use. Might
+ // create the SessionStorageNamespace on the fly.
+ virtual SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance);
+
+ protected:
+ virtual ~RenderViewHostDelegate() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_
diff --git a/chromium/content/browser/renderer_host/render_view_host_factory.cc b/chromium/content/browser/renderer_host/render_view_host_factory.cc
new file mode 100644
index 00000000000..20d15d38f0c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_factory.cc
@@ -0,0 +1,44 @@
+// 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 "content/browser/renderer_host/render_view_host_factory.h"
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+
+namespace content {
+
+// static
+RenderViewHostFactory* RenderViewHostFactory::factory_ = NULL;
+
+// static
+RenderViewHost* RenderViewHostFactory::Create(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out) {
+ if (factory_) {
+ return factory_->CreateRenderViewHost(instance, delegate, widget_delegate,
+ routing_id, main_frame_routing_id,
+ swapped_out);
+ }
+ return new RenderViewHostImpl(instance, delegate, widget_delegate, routing_id,
+ main_frame_routing_id, swapped_out);
+}
+
+// static
+void RenderViewHostFactory::RegisterFactory(RenderViewHostFactory* factory) {
+ DCHECK(!factory_) << "Can't register two factories at once.";
+ factory_ = factory;
+}
+
+// static
+void RenderViewHostFactory::UnregisterFactory() {
+ DCHECK(factory_) << "No factory to unregister.";
+ factory_ = NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_view_host_factory.h b/chromium/content/browser/renderer_host/render_view_host_factory.h
new file mode 100644
index 00000000000..6226187af53
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_factory.h
@@ -0,0 +1,72 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+
+namespace content {
+class RenderViewHost;
+class RenderViewHostDelegate;
+class RenderWidgetHostDelegate;
+class SessionStorageNamespace;
+class SiteInstance;
+
+// A factory for creating RenderViewHosts. There is a global factory function
+// that can be installed for the purposes of testing to provide a specialized
+// RenderViewHost class.
+class RenderViewHostFactory {
+ public:
+ // Creates a RenderViewHost using the currently registered factory, or the
+ // default one if no factory is registered. Ownership of the returned
+ // pointer will be passed to the caller.
+ static RenderViewHost* Create(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out);
+
+ // Returns true if there is currently a globally-registered factory.
+ static bool has_factory() {
+ return !!factory_;
+ }
+
+ protected:
+ RenderViewHostFactory() {}
+ virtual ~RenderViewHostFactory() {}
+
+ // You can derive from this class and specify an implementation for this
+ // function to create a different kind of RenderViewHost for testing.
+ virtual RenderViewHost* CreateRenderViewHost(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out) = 0;
+
+ // Registers your factory to be called when new RenderViewHosts are created.
+ // We have only one global factory, so there must be no factory registered
+ // before the call. This class does NOT take ownership of the pointer.
+ CONTENT_EXPORT static void RegisterFactory(RenderViewHostFactory* factory);
+
+ // Unregister the previously registered factory. With no factory registered,
+ // the default RenderViewHosts will be created.
+ CONTENT_EXPORT static void UnregisterFactory();
+
+ private:
+ // The current globally registered factory. This is NULL when we should
+ // create the default RenderViewHosts.
+ CONTENT_EXPORT static RenderViewHostFactory* factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_
diff --git a/chromium/content/browser/renderer_host/render_view_host_impl.cc b/chromium/content/browser/renderer_host/render_view_host_impl.cc
new file mode 100644
index 00000000000..56801c03c18
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_impl.cc
@@ -0,0 +1,2059 @@
+// 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 "content/browser/renderer_host/render_view_host_impl.h"
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_reader.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/cross_site_request_manager.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/host_zoom_map_impl.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/desktop_notification_messages.h"
+#include "content/common/drag_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/inter_process_time_ticks_converter.h"
+#include "content/common/speech_recognition_messages.h"
+#include "content/common/swapped_out_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_accessibility_state.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/dom_operation_notification_details.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/context_menu_params.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/common/url_utils.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/shell_dialogs/selected_file_info.h"
+#include "ui/snapshot/snapshot.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+
+#if defined(OS_MACOSX)
+#include "content/browser/renderer_host/popup_menu_helper_mac.h"
+#elif defined(OS_ANDROID)
+#include "media/base/android/media_player_manager.h"
+#endif
+
+using base::TimeDelta;
+using WebKit::WebConsoleMessage;
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationNone;
+using WebKit::WebDragOperationsMask;
+using WebKit::WebInputEvent;
+using WebKit::WebMediaPlayerAction;
+using WebKit::WebPluginAction;
+
+namespace content {
+namespace {
+
+// Delay to wait on closing the WebContents for a beforeunload/unload handler to
+// fire.
+const int kUnloadTimeoutMS = 1000;
+
+// Translate a WebKit text direction into a base::i18n one.
+base::i18n::TextDirection WebTextDirectionToChromeTextDirection(
+ WebKit::WebTextDirection dir) {
+ switch (dir) {
+ case WebKit::WebTextDirectionLeftToRight:
+ return base::i18n::LEFT_TO_RIGHT;
+ case WebKit::WebTextDirectionRightToLeft:
+ return base::i18n::RIGHT_TO_LEFT;
+ default:
+ NOTREACHED();
+ return base::i18n::UNKNOWN_DIRECTION;
+ }
+}
+
+base::LazyInstance<std::vector<RenderViewHost::CreatedCallback> >
+g_created_callbacks = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderViewHost, public:
+
+// static
+RenderViewHost* RenderViewHost::FromID(int render_process_id,
+ int render_view_id) {
+ RenderWidgetHost* widget =
+ RenderWidgetHost::FromID(render_process_id, render_view_id);
+ if (!widget || !widget->IsRenderView())
+ return NULL;
+ return static_cast<RenderViewHostImpl*>(RenderWidgetHostImpl::From(widget));
+}
+
+// static
+RenderViewHost* RenderViewHost::From(RenderWidgetHost* rwh) {
+ DCHECK(rwh->IsRenderView());
+ return static_cast<RenderViewHostImpl*>(RenderWidgetHostImpl::From(rwh));
+}
+
+// static
+void RenderViewHost::FilterURL(const RenderProcessHost* process,
+ bool empty_allowed,
+ GURL* url) {
+ RenderViewHostImpl::FilterURL(ChildProcessSecurityPolicyImpl::GetInstance(),
+ process, empty_allowed, url);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderViewHostImpl, public:
+
+// static
+RenderViewHostImpl* RenderViewHostImpl::FromID(int render_process_id,
+ int render_view_id) {
+ return static_cast<RenderViewHostImpl*>(
+ RenderViewHost::FromID(render_process_id, render_view_id));
+}
+
+RenderViewHostImpl::RenderViewHostImpl(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out)
+ : RenderWidgetHostImpl(widget_delegate, instance->GetProcess(), routing_id),
+ delegate_(delegate),
+ instance_(static_cast<SiteInstanceImpl*>(instance)),
+ waiting_for_drag_context_response_(false),
+ enabled_bindings_(0),
+ navigations_suspended_(false),
+ has_accessed_initial_document_(false),
+ is_swapped_out_(swapped_out),
+ is_subframe_(false),
+ main_frame_id_(-1),
+ run_modal_reply_msg_(NULL),
+ run_modal_opener_id_(MSG_ROUTING_NONE),
+ is_waiting_for_beforeunload_ack_(false),
+ is_waiting_for_unload_ack_(false),
+ has_timed_out_on_unload_(false),
+ unload_ack_is_for_cross_site_transition_(false),
+ are_javascript_messages_suppressed_(false),
+ sudden_termination_allowed_(false),
+ render_view_termination_status_(base::TERMINATION_STATUS_STILL_RUNNING) {
+ DCHECK(instance_.get());
+ CHECK(delegate_); // http://crbug.com/82827
+
+ if (main_frame_routing_id == MSG_ROUTING_NONE)
+ main_frame_routing_id = GetProcess()->GetNextRoutingID();
+
+ main_render_frame_host_.reset(
+ new RenderFrameHostImpl(this, main_frame_routing_id, is_swapped_out_));
+
+ GetProcess()->EnableSendQueue();
+
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); i++)
+ g_created_callbacks.Get().at(i).Run(this);
+
+ if (!swapped_out)
+ instance_->increment_active_view_count();
+
+#if defined(OS_ANDROID)
+ media_player_manager_ = media::MediaPlayerManager::Create(this);
+#endif
+}
+
+RenderViewHostImpl::~RenderViewHostImpl() {
+ FOR_EACH_OBSERVER(
+ RenderViewHostObserver, observers_, RenderViewHostDestruction());
+
+ GetDelegate()->RenderViewDeleted(this);
+
+ // Be sure to clean up any leftover state from cross-site requests.
+ CrossSiteRequestManager::GetInstance()->SetHasPendingCrossSiteRequest(
+ GetProcess()->GetID(), GetRoutingID(), false);
+
+ // If this was swapped out, it already decremented the active view
+ // count of the SiteInstance it belongs to.
+ if (!is_swapped_out_)
+ instance_->decrement_active_view_count();
+}
+
+RenderViewHostDelegate* RenderViewHostImpl::GetDelegate() const {
+ return delegate_;
+}
+
+SiteInstance* RenderViewHostImpl::GetSiteInstance() const {
+ return instance_.get();
+}
+
+bool RenderViewHostImpl::CreateRenderView(
+ const string16& frame_name,
+ int opener_route_id,
+ int32 max_page_id) {
+ TRACE_EVENT0("renderer_host", "RenderViewHostImpl::CreateRenderView");
+ DCHECK(!IsRenderViewLive()) << "Creating view twice";
+
+ // The process may (if we're sharing a process with another host that already
+ // initialized it) or may not (we have our own process or the old process
+ // crashed) have been initialized. Calling Init multiple times will be
+ // ignored, so this is safe.
+ if (!GetProcess()->Init())
+ return false;
+ DCHECK(GetProcess()->HasConnection());
+ DCHECK(GetProcess()->GetBrowserContext());
+
+ renderer_initialized_ = true;
+
+ GpuSurfaceTracker::Get()->SetSurfaceHandle(
+ surface_id(), GetCompositingSurface());
+
+ // Ensure the RenderView starts with a next_page_id larger than any existing
+ // page ID it might be asked to render.
+ int32 next_page_id = 1;
+ if (max_page_id > -1)
+ next_page_id = max_page_id + 1;
+
+ ViewMsg_New_Params params;
+ params.renderer_preferences =
+ delegate_->GetRendererPrefs(GetProcess()->GetBrowserContext());
+ params.web_preferences = delegate_->GetWebkitPrefs();
+ params.view_id = GetRoutingID();
+ params.main_frame_routing_id = main_render_frame_host_->routing_id();
+ params.surface_id = surface_id();
+ params.session_storage_namespace_id =
+ delegate_->GetSessionStorageNamespace(instance_)->id();
+ params.frame_name = frame_name;
+ // Ensure the RenderView sets its opener correctly.
+ params.opener_route_id = opener_route_id;
+ params.swapped_out = is_swapped_out_;
+ params.next_page_id = next_page_id;
+ GetWebScreenInfo(&params.screen_info);
+ params.accessibility_mode = accessibility_mode();
+ params.allow_partial_swap = !GetProcess()->IsGuest();
+
+ Send(new ViewMsg_New(params));
+
+ // If it's enabled, tell the renderer to set up the Javascript bindings for
+ // sending messages back to the browser.
+ if (GetProcess()->IsGuest())
+ DCHECK_EQ(0, enabled_bindings_);
+ Send(new ViewMsg_AllowBindings(GetRoutingID(), enabled_bindings_));
+ // Let our delegate know that we created a RenderView.
+ delegate_->RenderViewCreated(this);
+
+ FOR_EACH_OBSERVER(
+ RenderViewHostObserver, observers_, RenderViewHostInitialized());
+
+ return true;
+}
+
+bool RenderViewHostImpl::IsRenderViewLive() const {
+ return GetProcess()->HasConnection() && renderer_initialized_;
+}
+
+bool RenderViewHostImpl::IsSubframe() const {
+ return is_subframe_;
+}
+
+void RenderViewHostImpl::SyncRendererPrefs() {
+ Send(new ViewMsg_SetRendererPrefs(GetRoutingID(),
+ delegate_->GetRendererPrefs(
+ GetProcess()->GetBrowserContext())));
+}
+
+void RenderViewHostImpl::Navigate(const ViewMsg_Navigate_Params& params) {
+ TRACE_EVENT0("renderer_host", "RenderViewHostImpl::Navigate");
+ // Browser plugin guests are not allowed to navigate outside web-safe schemes,
+ // so do not grant them the ability to request additional URLs.
+ if (!GetProcess()->IsGuest()) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL(
+ GetProcess()->GetID(), params.url);
+ if (params.url.SchemeIs(chrome::kDataScheme) &&
+ params.base_url_for_data_url.SchemeIs(chrome::kFileScheme)) {
+ // If 'data:' is used, and we have a 'file:' base url, grant access to
+ // local files.
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL(
+ GetProcess()->GetID(), params.base_url_for_data_url);
+ }
+ }
+
+ // Only send the message if we aren't suspended at the start of a cross-site
+ // request.
+ if (navigations_suspended_) {
+ // Shouldn't be possible to have a second navigation while suspended, since
+ // navigations will only be suspended during a cross-site request. If a
+ // second navigation occurs, WebContentsImpl will cancel this pending RVH
+ // create a new pending RVH.
+ DCHECK(!suspended_nav_params_.get());
+ suspended_nav_params_.reset(new ViewMsg_Navigate_Params(params));
+ } else {
+ // Get back to a clean state, in case we start a new navigation without
+ // completing a RVH swap or unload handler.
+ SetSwappedOut(false);
+
+ Send(new ViewMsg_Navigate(GetRoutingID(), params));
+ }
+
+ // Force the throbber to start. We do this because WebKit's "started
+ // loading" message will be received asynchronously from the UI of the
+ // browser. But we want to keep the throbber in sync with what's happening
+ // in the UI. For example, we want to start throbbing immediately when the
+ // user naivgates even if the renderer is delayed. There is also an issue
+ // with the throbber starting because the WebUI (which controls whether the
+ // favicon is displayed) happens synchronously. If the start loading
+ // messages was asynchronous, then the default favicon would flash in.
+ //
+ // WebKit doesn't send throb notifications for JavaScript URLs, so we
+ // don't want to either.
+ if (!params.url.SchemeIs(chrome::kJavaScriptScheme))
+ delegate_->DidStartLoading(this);
+
+ FOR_EACH_OBSERVER(RenderViewHostObserver, observers_, Navigate(params.url));
+}
+
+void RenderViewHostImpl::NavigateToURL(const GURL& url) {
+ ViewMsg_Navigate_Params params;
+ params.page_id = -1;
+ params.pending_history_list_offset = -1;
+ params.current_history_list_offset = -1;
+ params.current_history_list_length = 0;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.navigation_type = ViewMsg_Navigate_Type::NORMAL;
+ Navigate(params);
+}
+
+void RenderViewHostImpl::SetNavigationsSuspended(
+ bool suspend,
+ const base::TimeTicks& proceed_time) {
+ // This should only be called to toggle the state.
+ DCHECK(navigations_suspended_ != suspend);
+
+ navigations_suspended_ = suspend;
+ if (!suspend && suspended_nav_params_) {
+ // There's navigation message params waiting to be sent. Now that we're not
+ // suspended anymore, resume navigation by sending them. If we were swapped
+ // out, we should also stop filtering out the IPC messages now.
+ SetSwappedOut(false);
+
+ DCHECK(!proceed_time.is_null());
+ suspended_nav_params_->browser_navigation_start = proceed_time;
+ Send(new ViewMsg_Navigate(GetRoutingID(), *suspended_nav_params_.get()));
+ suspended_nav_params_.reset();
+ }
+}
+
+void RenderViewHostImpl::CancelSuspendedNavigations() {
+ // Clear any state if a pending navigation is canceled or pre-empted.
+ if (suspended_nav_params_)
+ suspended_nav_params_.reset();
+ navigations_suspended_ = false;
+}
+
+void RenderViewHostImpl::FirePageBeforeUnload(bool for_cross_site_transition) {
+ if (!IsRenderViewLive()) {
+ // This RenderViewHostImpl doesn't have a live renderer, so just
+ // skip running the onbeforeunload handler.
+ is_waiting_for_beforeunload_ack_ = true; // Checked by OnShouldCloseACK.
+ unload_ack_is_for_cross_site_transition_ = for_cross_site_transition;
+ base::TimeTicks now = base::TimeTicks::Now();
+ OnShouldCloseACK(true, now, now);
+ return;
+ }
+
+ // This may be called more than once (if the user clicks the tab close button
+ // several times, or if she clicks the tab close button then the browser close
+ // button), and we only send the message once.
+ if (is_waiting_for_beforeunload_ack_) {
+ // Some of our close messages could be for the tab, others for cross-site
+ // transitions. We always want to think it's for closing the tab if any
+ // of the messages were, since otherwise it might be impossible to close
+ // (if there was a cross-site "close" request pending when the user clicked
+ // the close button). We want to keep the "for cross site" flag only if
+ // both the old and the new ones are also for cross site.
+ unload_ack_is_for_cross_site_transition_ =
+ unload_ack_is_for_cross_site_transition_ && for_cross_site_transition;
+ } else {
+ // Start the hang monitor in case the renderer hangs in the beforeunload
+ // handler.
+ is_waiting_for_beforeunload_ack_ = true;
+ unload_ack_is_for_cross_site_transition_ = for_cross_site_transition;
+ // Increment the in-flight event count, to ensure that input events won't
+ // cancel the timeout timer.
+ increment_in_flight_event_count();
+ StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS));
+ send_should_close_start_time_ = base::TimeTicks::Now();
+ Send(new ViewMsg_ShouldClose(GetRoutingID()));
+ }
+}
+
+void RenderViewHostImpl::SwapOut() {
+ // This will be set back to false in OnSwapOutACK, just before we replace
+ // this RVH with the pending RVH.
+ is_waiting_for_unload_ack_ = true;
+ // Start the hang monitor in case the renderer hangs in the unload handler.
+ // Increment the in-flight event count, to ensure that input events won't
+ // cancel the timeout timer.
+ increment_in_flight_event_count();
+ StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS));
+
+ if (IsRenderViewLive()) {
+ Send(new ViewMsg_SwapOut(GetRoutingID()));
+ } else {
+ // This RenderViewHost doesn't have a live renderer, so just skip the unload
+ // event.
+ OnSwappedOut(true);
+ }
+}
+
+void RenderViewHostImpl::OnSwapOutACK() {
+ OnSwappedOut(false);
+}
+
+void RenderViewHostImpl::OnSwappedOut(bool timed_out) {
+ // Stop the hang monitor now that the unload handler has finished.
+ decrement_in_flight_event_count();
+ StopHangMonitorTimeout();
+ is_waiting_for_unload_ack_ = false;
+ has_timed_out_on_unload_ = timed_out;
+ delegate_->SwappedOut(this);
+}
+
+void RenderViewHostImpl::WasSwappedOut() {
+ // Don't bother reporting hung state anymore.
+ StopHangMonitorTimeout();
+
+ // If we have timed out on running the unload handler, we consider
+ // the process hung and we should terminate it if there are no other tabs
+ // using the process. If there are other views using this process, the
+ // unresponsive renderer timeout will catch it.
+ bool hung = has_timed_out_on_unload_;
+
+ // Now that we're no longer the active RVH in the tab, start filtering out
+ // most IPC messages. Usually the renderer will have stopped sending
+ // messages as of OnSwapOutACK. However, we may have timed out waiting
+ // for that message, and additional IPC messages may keep streaming in.
+ // We filter them out, as long as that won't cause problems (e.g., we
+ // still allow synchronous messages through).
+ SetSwappedOut(true);
+
+ // If we are not running the renderer in process and no other tab is using
+ // the hung process, consider it eligible to be killed, assuming it is a real
+ // process (unit tests don't have real processes).
+ if (hung) {
+ base::ProcessHandle process_handle = GetProcess()->GetHandle();
+ int views = 0;
+
+ // Count the number of active widget hosts for the process, which
+ // is equivalent to views using the process as of this writing.
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ if (widgets[i]->GetProcess()->GetID() == GetProcess()->GetID())
+ ++views;
+ }
+
+ if (!RenderProcessHost::run_renderer_in_process() &&
+ process_handle && views <= 1) {
+ // The process can safely be terminated, only if WebContents sets
+ // SuddenTerminationAllowed, which indicates that the timer has expired.
+ // This is not the case if we load data URLs or about:blank. The reason
+ // is that those have no network requests and this code is hit without
+ // setting the unresponsiveness timer. This allows a corner case where a
+ // navigation to a data URL will leave a process running, if the
+ // beforeunload handler completes fine, but the unload handler hangs.
+ // At this time, the complexity to solve this edge case is not worthwhile.
+ if (SuddenTerminationAllowed()) {
+ // We should kill the process, but for now, just log the data so we can
+ // diagnose the kill rate and investigate if separate timer is needed.
+ // http://crbug.com/104346.
+
+ // Log a histogram point to help us diagnose how many of those kills
+ // we have performed. 1 is the enum value for RendererType Normal for
+ // the histogram.
+ UMA_HISTOGRAM_PERCENTAGE(
+ "BrowserRenderProcessHost.ChildKillsUnresponsive", 1);
+ }
+ }
+ }
+
+ // Inform the renderer that it can exit if no one else is using it.
+ Send(new ViewMsg_WasSwappedOut(GetRoutingID()));
+}
+
+void RenderViewHostImpl::ClosePage() {
+ // Start the hang monitor in case the renderer hangs in the unload handler.
+ is_waiting_for_unload_ack_ = true;
+ StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS));
+
+ if (IsRenderViewLive()) {
+ // Since we are sending an IPC message to the renderer, increase the event
+ // count to prevent the hang monitor timeout from being stopped by input
+ // event acknowledgements.
+ increment_in_flight_event_count();
+
+ // TODO(creis): Should this be moved to Shutdown? It may not be called for
+ // RenderViewHosts that have been swapped out.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW,
+ Source<RenderViewHost>(this),
+ NotificationService::NoDetails());
+
+ Send(new ViewMsg_ClosePage(GetRoutingID()));
+ } else {
+ // This RenderViewHost doesn't have a live renderer, so just skip the unload
+ // event and close the page.
+ ClosePageIgnoringUnloadEvents();
+ }
+}
+
+void RenderViewHostImpl::ClosePageIgnoringUnloadEvents() {
+ StopHangMonitorTimeout();
+ is_waiting_for_beforeunload_ack_ = false;
+ is_waiting_for_unload_ack_ = false;
+
+ sudden_termination_allowed_ = true;
+ delegate_->Close(this);
+}
+
+bool RenderViewHostImpl::HasPendingCrossSiteRequest() {
+ return CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
+ GetProcess()->GetID(), GetRoutingID());
+}
+
+void RenderViewHostImpl::SetHasPendingCrossSiteRequest(
+ bool has_pending_request) {
+ CrossSiteRequestManager::GetInstance()->SetHasPendingCrossSiteRequest(
+ GetProcess()->GetID(), GetRoutingID(), has_pending_request);
+}
+
+#if defined(OS_ANDROID)
+void RenderViewHostImpl::ActivateNearestFindResult(int request_id,
+ float x,
+ float y) {
+ Send(new InputMsg_ActivateNearestFindResult(GetRoutingID(),
+ request_id, x, y));
+}
+
+void RenderViewHostImpl::RequestFindMatchRects(int current_version) {
+ Send(new ViewMsg_FindMatchRects(GetRoutingID(), current_version));
+}
+#endif
+
+void RenderViewHostImpl::DragTargetDragEnter(
+ const DropData& drop_data,
+ const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ WebDragOperationsMask operations_allowed,
+ int key_modifiers) {
+ const int renderer_id = GetProcess()->GetID();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // The URL could have been cobbled together from any highlighted text string,
+ // and can't be interpreted as a capability.
+ DropData filtered_data(drop_data);
+ FilterURL(policy, GetProcess(), true, &filtered_data.url);
+
+ // The filenames vector, on the other hand, does represent a capability to
+ // access the given files.
+ fileapi::IsolatedContext::FileInfoSet files;
+ for (std::vector<DropData::FileInfo>::iterator iter(
+ filtered_data.filenames.begin());
+ iter != filtered_data.filenames.end(); ++iter) {
+ // A dragged file may wind up as the value of an input element, or it
+ // may be used as the target of a navigation instead. We don't know
+ // which will happen at this point, so generously grant both access
+ // and request permissions to the specific file to cover both cases.
+ // We do not give it the permission to request all file:// URLs.
+ base::FilePath path =
+ base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(iter->path));
+
+ // Make sure we have the same display_name as the one we register.
+ if (iter->display_name.empty()) {
+ std::string name;
+ files.AddPath(path, &name);
+ iter->display_name = UTF8ToUTF16(name);
+ } else {
+ files.AddPathWithName(path, UTF16ToUTF8(iter->display_name));
+ }
+
+ policy->GrantRequestSpecificFileURL(renderer_id,
+ net::FilePathToFileURL(path));
+
+ // If the renderer already has permission to read these paths, we don't need
+ // to re-grant them. This prevents problems with DnD for files in the CrOS
+ // file manager--the file manager already had read/write access to those
+ // directories, but dragging a file would cause the read/write access to be
+ // overwritten with read-only access, making them impossible to delete or
+ // rename until the renderer was killed.
+ if (!policy->CanReadFile(renderer_id, path)) {
+ policy->GrantReadFile(renderer_id, path);
+ // Allow dragged directories to be enumerated by the child process.
+ // Note that we can't tell a file from a directory at this point.
+ policy->GrantReadDirectory(renderer_id, path);
+ }
+ }
+
+ fileapi::IsolatedContext* isolated_context =
+ fileapi::IsolatedContext::GetInstance();
+ DCHECK(isolated_context);
+ std::string filesystem_id = isolated_context->RegisterDraggedFileSystem(
+ files);
+ if (!filesystem_id.empty()) {
+ // Grant the permission iff the ID is valid.
+ policy->GrantReadFileSystem(renderer_id, filesystem_id);
+ }
+ filtered_data.filesystem_id = UTF8ToUTF16(filesystem_id);
+
+ Send(new DragMsg_TargetDragEnter(GetRoutingID(), filtered_data, client_pt,
+ screen_pt, operations_allowed,
+ key_modifiers));
+}
+
+void RenderViewHostImpl::DragTargetDragOver(
+ const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ WebDragOperationsMask operations_allowed,
+ int key_modifiers) {
+ Send(new DragMsg_TargetDragOver(GetRoutingID(), client_pt, screen_pt,
+ operations_allowed, key_modifiers));
+}
+
+void RenderViewHostImpl::DragTargetDragLeave() {
+ Send(new DragMsg_TargetDragLeave(GetRoutingID()));
+}
+
+void RenderViewHostImpl::DragTargetDrop(
+ const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ int key_modifiers) {
+ Send(new DragMsg_TargetDrop(GetRoutingID(), client_pt, screen_pt,
+ key_modifiers));
+}
+
+void RenderViewHostImpl::DesktopNotificationPermissionRequestDone(
+ int callback_context) {
+ Send(new DesktopNotificationMsg_PermissionRequestDone(
+ GetRoutingID(), callback_context));
+}
+
+void RenderViewHostImpl::DesktopNotificationPostDisplay(int callback_context) {
+ Send(new DesktopNotificationMsg_PostDisplay(GetRoutingID(),
+ callback_context));
+}
+
+void RenderViewHostImpl::DesktopNotificationPostError(int notification_id,
+ const string16& message) {
+ Send(new DesktopNotificationMsg_PostError(
+ GetRoutingID(), notification_id, message));
+}
+
+void RenderViewHostImpl::DesktopNotificationPostClose(int notification_id,
+ bool by_user) {
+ Send(new DesktopNotificationMsg_PostClose(
+ GetRoutingID(), notification_id, by_user));
+}
+
+void RenderViewHostImpl::DesktopNotificationPostClick(int notification_id) {
+ Send(new DesktopNotificationMsg_PostClick(GetRoutingID(), notification_id));
+}
+
+void RenderViewHostImpl::ExecuteJavascriptInWebFrame(
+ const string16& frame_xpath,
+ const string16& jscript) {
+ Send(new ViewMsg_ScriptEvalRequest(GetRoutingID(), frame_xpath, jscript,
+ 0, false));
+}
+
+void RenderViewHostImpl::ExecuteJavascriptInWebFrameCallbackResult(
+ const string16& frame_xpath,
+ const string16& jscript,
+ const JavascriptResultCallback& callback) {
+ static int next_id = 1;
+ int key = next_id++;
+ Send(new ViewMsg_ScriptEvalRequest(GetRoutingID(), frame_xpath, jscript,
+ key, true));
+ javascript_callbacks_.insert(std::make_pair(key, callback));
+}
+
+void RenderViewHostImpl::JavaScriptDialogClosed(IPC::Message* reply_msg,
+ bool success,
+ const string16& user_input) {
+ GetProcess()->SetIgnoreInputEvents(false);
+ bool is_waiting =
+ is_waiting_for_beforeunload_ack_ || is_waiting_for_unload_ack_;
+
+ // If we are executing as part of (before)unload event handling, we don't
+ // want to use the regular hung_renderer_delay_ms_ if the user has agreed to
+ // leave the current page. In this case, use the regular timeout value used
+ // during the (before)unload handling.
+ if (is_waiting) {
+ StartHangMonitorTimeout(TimeDelta::FromMilliseconds(
+ success ? kUnloadTimeoutMS : hung_renderer_delay_ms_));
+ }
+
+ ViewHostMsg_RunJavaScriptMessage::WriteReplyParams(reply_msg,
+ success, user_input);
+ Send(reply_msg);
+
+ // If we are waiting for an unload or beforeunload ack and the user has
+ // suppressed messages, kill the tab immediately; a page that's spamming
+ // alerts in onbeforeunload is presumably malicious, so there's no point in
+ // continuing to run its script and dragging out the process.
+ // This must be done after sending the reply since RenderView can't close
+ // correctly while waiting for a response.
+ if (is_waiting && are_javascript_messages_suppressed_)
+ delegate_->RendererUnresponsive(this, is_waiting);
+}
+
+void RenderViewHostImpl::DragSourceEndedAt(
+ int client_x, int client_y, int screen_x, int screen_y,
+ WebDragOperation operation) {
+ Send(new DragMsg_SourceEndedOrMoved(
+ GetRoutingID(),
+ gfx::Point(client_x, client_y),
+ gfx::Point(screen_x, screen_y),
+ true, operation));
+}
+
+void RenderViewHostImpl::DragSourceMovedTo(
+ int client_x, int client_y, int screen_x, int screen_y) {
+ Send(new DragMsg_SourceEndedOrMoved(
+ GetRoutingID(),
+ gfx::Point(client_x, client_y),
+ gfx::Point(screen_x, screen_y),
+ false, WebDragOperationNone));
+}
+
+void RenderViewHostImpl::DragSourceSystemDragEnded() {
+ Send(new DragMsg_SourceSystemDragEnded(GetRoutingID()));
+}
+
+void RenderViewHostImpl::AllowBindings(int bindings_flags) {
+ // Ensure we aren't granting WebUI bindings to a process that has already
+ // been used for non-privileged views.
+ if (bindings_flags & BINDINGS_POLICY_WEB_UI &&
+ GetProcess()->HasConnection() &&
+ !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
+ GetProcess()->GetID())) {
+ // This process has no bindings yet. Make sure it does not have more
+ // than this single active view.
+ RenderProcessHostImpl* process =
+ static_cast<RenderProcessHostImpl*>(GetProcess());
+ if (process->GetActiveViewCount() > 1)
+ return;
+ }
+
+ // Never grant any bindings to browser plugin guests.
+ if (GetProcess()->IsGuest()) {
+ NOTREACHED() << "Never grant bindings to a guest process.";
+ return;
+ }
+
+ if (bindings_flags & BINDINGS_POLICY_WEB_UI) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
+ GetProcess()->GetID());
+ }
+
+ enabled_bindings_ |= bindings_flags;
+ if (renderer_initialized_)
+ Send(new ViewMsg_AllowBindings(GetRoutingID(), enabled_bindings_));
+}
+
+int RenderViewHostImpl::GetEnabledBindings() const {
+ return enabled_bindings_;
+}
+
+void RenderViewHostImpl::SetWebUIProperty(const std::string& name,
+ const std::string& value) {
+ // This is a sanity check before telling the renderer to enable the property.
+ // It could lie and send the corresponding IPC messages anyway, but we will
+ // not act on them if enabled_bindings_ doesn't agree. If we get here without
+ // WebUI bindings, kill the renderer process.
+ if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI) {
+ Send(new ViewMsg_SetWebUIProperty(GetRoutingID(), name, value));
+ } else {
+ RecordAction(UserMetricsAction("BindingsMismatchTerminate_RVH_WebUI"));
+ base::KillProcess(
+ GetProcess()->GetHandle(), content::RESULT_CODE_KILLED, false);
+ }
+}
+
+void RenderViewHostImpl::GotFocus() {
+ RenderWidgetHostImpl::GotFocus(); // Notifies the renderer it got focus.
+
+ RenderViewHostDelegateView* view = delegate_->GetDelegateView();
+ if (view)
+ view->GotFocus();
+}
+
+void RenderViewHostImpl::LostCapture() {
+ RenderWidgetHostImpl::LostCapture();
+ delegate_->LostCapture();
+}
+
+void RenderViewHostImpl::LostMouseLock() {
+ RenderWidgetHostImpl::LostMouseLock();
+ delegate_->LostMouseLock();
+}
+
+void RenderViewHostImpl::SetInitialFocus(bool reverse) {
+ Send(new ViewMsg_SetInitialFocus(GetRoutingID(), reverse));
+}
+
+void RenderViewHostImpl::FilesSelectedInChooser(
+ const std::vector<ui::SelectedFileInfo>& files,
+ FileChooserParams::Mode permissions) {
+ // Grant the security access requested to the given files.
+ for (size_t i = 0; i < files.size(); ++i) {
+ const ui::SelectedFileInfo& file = files[i];
+ if (permissions == FileChooserParams::Save) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantCreateWriteFile(
+ GetProcess()->GetID(), file.local_path);
+ } else {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ GetProcess()->GetID(), file.local_path);
+ }
+ }
+ Send(new ViewMsg_RunFileChooserResponse(GetRoutingID(), files));
+}
+
+void RenderViewHostImpl::DirectoryEnumerationFinished(
+ int request_id,
+ const std::vector<base::FilePath>& files) {
+ // Grant the security access requested to the given files.
+ for (std::vector<base::FilePath>::const_iterator file = files.begin();
+ file != files.end(); ++file) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ GetProcess()->GetID(), *file);
+ }
+ Send(new ViewMsg_EnumerateDirectoryResponse(GetRoutingID(),
+ request_id,
+ files));
+}
+
+void RenderViewHostImpl::LoadStateChanged(
+ const GURL& url,
+ const net::LoadStateWithParam& load_state,
+ uint64 upload_position,
+ uint64 upload_size) {
+ delegate_->LoadStateChanged(url, load_state, upload_position, upload_size);
+}
+
+bool RenderViewHostImpl::SuddenTerminationAllowed() const {
+ return sudden_termination_allowed_ ||
+ GetProcess()->SuddenTerminationAllowed();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderViewHostImpl, IPC message handlers:
+
+bool RenderViewHostImpl::OnMessageReceived(const IPC::Message& msg) {
+ if (!BrowserMessageFilter::CheckCanDispatchOnUI(msg, this))
+ return true;
+
+ // Filter out most IPC messages if this renderer is swapped out.
+ // We still want to handle certain ACKs to keep our state consistent.
+ if (is_swapped_out_) {
+ if (!SwappedOutMessages::CanHandleWhileSwappedOut(msg)) {
+ // If this is a synchronous message and we decided not to handle it,
+ // we must send an error reply, or else the renderer will be stuck
+ // and won't respond to future requests.
+ if (msg.is_sync()) {
+ IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg);
+ reply->set_reply_error();
+ Send(reply);
+ }
+ // Don't continue looking for someone to handle it.
+ return true;
+ }
+ }
+
+ ObserverListBase<RenderViewHostObserver>::Iterator it(observers_);
+ RenderViewHostObserver* observer;
+ while ((observer = it.GetNext()) != NULL) {
+ if (observer->OnMessageReceived(msg))
+ return true;
+ }
+
+ if (delegate_->OnMessageReceived(this, msg))
+ return true;
+
+ // TODO(jochen): Consider removing message handlers that only add a this
+ // pointer and forward the messages to the RenderViewHostDelegate. The
+ // respective delegates can handle the messages themselves in their
+ // OnMessageReceived implementation.
+ bool handled = true;
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderViewHostImpl, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowView, OnShowView)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowFullscreenWidget,
+ OnShowFullscreenWidget)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunModal, OnRunModal)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewReady, OnRenderViewReady)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RenderProcessGone, OnRenderProcessGone)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame,
+ OnDidStartProvisionalLoadForFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidRedirectProvisionalLoad,
+ OnDidRedirectProvisionalLoad)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidFailProvisionalLoadWithError,
+ OnDidFailProvisionalLoadWithError)
+ IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_FrameNavigate, OnNavigate(msg))
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateState, OnUpdateState)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateTitle, OnUpdateTitle)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateEncoding, OnUpdateEncoding)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateTargetURL, OnUpdateTargetURL)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateInspectorSetting,
+ OnUpdateInspectorSetting)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Close, OnClose)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RequestMove, OnRequestMove)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartLoading, OnDidStartLoading)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidStopLoading, OnDidStopLoading)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeLoadProgress,
+ OnDidChangeLoadProgress)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidDisownOpener, OnDidDisownOpener)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentAvailableInMainFrame,
+ OnDocumentAvailableInMainFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentOnLoadCompletedInMainFrame,
+ OnDocumentOnLoadCompletedInMainFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ContextMenu, OnContextMenu)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ToggleFullscreen, OnToggleFullscreen)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_OpenURL, OnOpenURL)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidContentsPreferredSizeChange,
+ OnDidContentsPreferredSizeChange)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeScrollOffset,
+ OnDidChangeScrollOffset)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeScrollbarsForMainFrame,
+ OnDidChangeScrollbarsForMainFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeScrollOffsetPinningForMainFrame,
+ OnDidChangeScrollOffsetPinningForMainFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeNumWheelEvents,
+ OnDidChangeNumWheelEvents)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RouteCloseEvent,
+ OnRouteCloseEvent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RouteMessageEvent, OnRouteMessageEvent)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunJavaScriptMessage,
+ OnRunJavaScriptMessage)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunBeforeUnloadConfirm,
+ OnRunBeforeUnloadConfirm)
+ IPC_MESSAGE_HANDLER(DragHostMsg_StartDragging, OnStartDragging)
+ IPC_MESSAGE_HANDLER(DragHostMsg_UpdateDragCursor, OnUpdateDragCursor)
+ IPC_MESSAGE_HANDLER(DragHostMsg_TargetDrop_ACK, OnTargetDropACK)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FocusedNodeChanged, OnFocusedNodeChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_AddMessageToConsole, OnAddMessageToConsole)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShouldClose_ACK, OnShouldCloseACK)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ClosePage_ACK, OnClosePageACK)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SwapOut_ACK, OnSwapOutACK)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SelectionChanged, OnSelectionChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SelectionBoundsChanged,
+ OnSelectionBoundsChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ScriptEvalResponse, OnScriptEvalResponse)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidZoomURL, OnDidZoomURL)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetWindowSnapshot, OnGetWindowSnapshot)
+ IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_RequestPermission,
+ OnRequestDesktopNotificationPermission)
+ IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_Show,
+ OnShowDesktopNotification)
+ IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_Cancel,
+ OnCancelDesktopNotification)
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowPopup, OnShowPopup)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidAccessInitialDocument,
+ OnDidAccessInitialDocument)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DomOperationResponse,
+ OnDomOperationResponse)
+ IPC_MESSAGE_HANDLER(AccessibilityHostMsg_Notifications,
+ OnAccessibilityNotifications)
+ // Have the super handle all other messages.
+ IPC_MESSAGE_UNHANDLED(
+ handled = RenderWidgetHostImpl::OnMessageReceived(msg))
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!msg_is_ok) {
+ // The message had a handler, but its de-serialization failed.
+ // Kill the renderer.
+ RecordAction(UserMetricsAction("BadMessageTerminate_RVH"));
+ GetProcess()->ReceivedBadMessage();
+ }
+
+ return handled;
+}
+
+void RenderViewHostImpl::Shutdown() {
+ // If we are being run modally (see RunModal), then we need to cleanup.
+ if (run_modal_reply_msg_) {
+ Send(run_modal_reply_msg_);
+ run_modal_reply_msg_ = NULL;
+ RenderViewHostImpl* opener =
+ RenderViewHostImpl::FromID(GetProcess()->GetID(), run_modal_opener_id_);
+ if (opener) {
+ opener->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(
+ hung_renderer_delay_ms_));
+ // Balance out the decrement when we got created.
+ opener->increment_in_flight_event_count();
+ }
+ run_modal_opener_id_ = MSG_ROUTING_NONE;
+ }
+
+ RenderWidgetHostImpl::Shutdown();
+}
+
+bool RenderViewHostImpl::IsRenderView() const {
+ return true;
+}
+
+void RenderViewHostImpl::CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) {
+ ViewHostMsg_CreateWindow_Params validated_params(params);
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ FilterURL(policy, GetProcess(), false, &validated_params.target_url);
+ FilterURL(policy, GetProcess(), false, &validated_params.opener_url);
+ FilterURL(policy, GetProcess(), true,
+ &validated_params.opener_security_origin);
+
+ delegate_->CreateNewWindow(route_id, main_frame_route_id,
+ validated_params, session_storage_namespace);
+}
+
+void RenderViewHostImpl::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ delegate_->CreateNewWidget(route_id, popup_type);
+}
+
+void RenderViewHostImpl::CreateNewFullscreenWidget(int route_id) {
+ delegate_->CreateNewFullscreenWidget(route_id);
+}
+
+void RenderViewHostImpl::OnShowView(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ if (!is_swapped_out_) {
+ delegate_->ShowCreatedWindow(
+ route_id, disposition, initial_pos, user_gesture);
+ }
+ Send(new ViewMsg_Move_ACK(route_id));
+}
+
+void RenderViewHostImpl::OnShowWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ if (!is_swapped_out_)
+ delegate_->ShowCreatedWidget(route_id, initial_pos);
+ Send(new ViewMsg_Move_ACK(route_id));
+}
+
+void RenderViewHostImpl::OnShowFullscreenWidget(int route_id) {
+ if (!is_swapped_out_)
+ delegate_->ShowCreatedFullscreenWidget(route_id);
+ Send(new ViewMsg_Move_ACK(route_id));
+}
+
+void RenderViewHostImpl::OnRunModal(int opener_id, IPC::Message* reply_msg) {
+ DCHECK(!run_modal_reply_msg_);
+ run_modal_reply_msg_ = reply_msg;
+ run_modal_opener_id_ = opener_id;
+
+ RecordAction(UserMetricsAction("ShowModalDialog"));
+
+ RenderViewHostImpl* opener =
+ RenderViewHostImpl::FromID(GetProcess()->GetID(), run_modal_opener_id_);
+ if (opener) {
+ opener->StopHangMonitorTimeout();
+ // The ack for the mouse down won't come until the dialog closes, so fake it
+ // so that we don't get a timeout.
+ opener->decrement_in_flight_event_count();
+ }
+
+ // TODO(darin): Bug 1107929: Need to inform our delegate to show this view in
+ // an app-modal fashion.
+}
+
+void RenderViewHostImpl::OnRenderViewReady() {
+ render_view_termination_status_ = base::TERMINATION_STATUS_STILL_RUNNING;
+ SendScreenRects();
+ WasResized();
+ delegate_->RenderViewReady(this);
+}
+
+void RenderViewHostImpl::OnRenderProcessGone(int status, int exit_code) {
+ // Keep the termination status so we can get at it later when we
+ // need to know why it died.
+ render_view_termination_status_ =
+ static_cast<base::TerminationStatus>(status);
+
+ // Reset state.
+ main_frame_id_ = -1;
+
+ // Our base class RenderWidgetHost needs to reset some stuff.
+ RendererExited(render_view_termination_status_, exit_code);
+
+ delegate_->RenderViewTerminated(this,
+ static_cast<base::TerminationStatus>(status),
+ exit_code);
+}
+
+void RenderViewHostImpl::OnDidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& url) {
+ delegate_->DidStartProvisionalLoadForFrame(
+ this, frame_id, parent_frame_id, is_main_frame, url);
+}
+
+void RenderViewHostImpl::OnDidRedirectProvisionalLoad(
+ int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) {
+ delegate_->DidRedirectProvisionalLoad(
+ this, page_id, source_url, target_url);
+}
+
+void RenderViewHostImpl::OnDidFailProvisionalLoadWithError(
+ const ViewHostMsg_DidFailProvisionalLoadWithError_Params& params) {
+ delegate_->DidFailProvisionalLoadWithError(this, params);
+}
+
+// Called when the renderer navigates. For every frame loaded, we'll get this
+// notification containing parameters identifying the navigation.
+//
+// Subframes are identified by the page transition type. For subframes loaded
+// as part of a wider page load, the page_id will be the same as for the top
+// level frame. If the user explicitly requests a subframe navigation, we will
+// get a new page_id because we need to create a new navigation entry for that
+// action.
+void RenderViewHostImpl::OnNavigate(const IPC::Message& msg) {
+ // Read the parameters out of the IPC message directly to avoid making another
+ // copy when we filter the URLs.
+ PickleIterator iter(msg);
+ ViewHostMsg_FrameNavigate_Params validated_params;
+ if (!IPC::ParamTraits<ViewHostMsg_FrameNavigate_Params>::
+ Read(&msg, &iter, &validated_params))
+ return;
+
+ // If we're waiting for a cross-site beforeunload ack from this renderer and
+ // we receive a Navigate message from the main frame, then the renderer was
+ // navigating already and sent it before hearing the ViewMsg_Stop message.
+ // We do not want to cancel the pending navigation in this case, since the
+ // old page will soon be stopped. Instead, treat this as a beforeunload ack
+ // to allow the pending navigation to continue.
+ if (is_waiting_for_beforeunload_ack_ &&
+ unload_ack_is_for_cross_site_transition_ &&
+ PageTransitionIsMainFrame(validated_params.transition)) {
+ OnShouldCloseACK(true, send_should_close_start_time_,
+ base::TimeTicks::Now());
+ return;
+ }
+
+ // If we're waiting for an unload ack from this renderer and we receive a
+ // Navigate message, then the renderer was navigating before it received the
+ // unload request. It will either respond to the unload request soon or our
+ // timer will expire. Either way, we should ignore this message, because we
+ // have already committed to closing this renderer.
+ if (is_waiting_for_unload_ack_)
+ return;
+
+ // Cache the main frame id, so we can use it for creating the frame tree
+ // root node when needed.
+ if (PageTransitionIsMainFrame(validated_params.transition)) {
+ if (main_frame_id_ == -1) {
+ main_frame_id_ = validated_params.frame_id;
+ } else {
+ // TODO(nasko): We plan to remove the usage of frame_id in navigation
+ // and move to routing ids. This is in place to ensure that a
+ // renderer is not misbehaving and sending us incorrect data.
+ DCHECK_EQ(main_frame_id_, validated_params.frame_id);
+ }
+ }
+ RenderProcessHost* process = GetProcess();
+
+ // Attempts to commit certain off-limits URL should be caught more strictly
+ // than our FilterURL checks below. If a renderer violates this policy, it
+ // should be killed.
+ if (!CanCommitURL(validated_params.url)) {
+ VLOG(1) << "Blocked URL " << validated_params.url.spec();
+ validated_params.url = GURL(kAboutBlankURL);
+ RecordAction(UserMetricsAction("CanCommitURL_BlockedAndKilled"));
+ // Kills the process.
+ process->ReceivedBadMessage();
+ }
+
+ // Now that something has committed, we don't need to track whether the
+ // initial page has been accessed.
+ has_accessed_initial_document_ = false;
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ // Without this check, an evil renderer can trick the browser into creating
+ // a navigation entry for a banned URL. If the user clicks the back button
+ // followed by the forward button (or clicks reload, or round-trips through
+ // session restore, etc), we'll think that the browser commanded the
+ // renderer to load the URL and grant the renderer the privileges to request
+ // the URL. To prevent this attack, we block the renderer from inserting
+ // banned URLs into the navigation controller in the first place.
+ FilterURL(policy, process, false, &validated_params.url);
+ FilterURL(policy, process, true, &validated_params.referrer.url);
+ for (std::vector<GURL>::iterator it(validated_params.redirects.begin());
+ it != validated_params.redirects.end(); ++it) {
+ FilterURL(policy, process, false, &(*it));
+ }
+ FilterURL(policy, process, true, &validated_params.searchable_form_url);
+ FilterURL(policy, process, true, &validated_params.password_form.origin);
+ FilterURL(policy, process, true, &validated_params.password_form.action);
+
+ // Without this check, the renderer can trick the browser into using
+ // filenames it can't access in a future session restore.
+ if (!CanAccessFilesOfPageState(validated_params.page_state)) {
+ GetProcess()->ReceivedBadMessage();
+ return;
+ }
+
+ delegate_->DidNavigate(this, validated_params);
+}
+
+void RenderViewHostImpl::OnUpdateState(int32 page_id, const PageState& state) {
+ // Without this check, the renderer can trick the browser into using
+ // filenames it can't access in a future session restore.
+ if (!CanAccessFilesOfPageState(state)) {
+ GetProcess()->ReceivedBadMessage();
+ return;
+ }
+
+ delegate_->UpdateState(this, page_id, state);
+}
+
+void RenderViewHostImpl::OnUpdateTitle(
+ int32 page_id,
+ const string16& title,
+ WebKit::WebTextDirection title_direction) {
+ if (title.length() > kMaxTitleChars) {
+ NOTREACHED() << "Renderer sent too many characters in title.";
+ return;
+ }
+
+ delegate_->UpdateTitle(this, page_id, title,
+ WebTextDirectionToChromeTextDirection(
+ title_direction));
+}
+
+void RenderViewHostImpl::OnUpdateEncoding(const std::string& encoding_name) {
+ delegate_->UpdateEncoding(this, encoding_name);
+}
+
+void RenderViewHostImpl::OnUpdateTargetURL(int32 page_id, const GURL& url) {
+ if (!is_swapped_out_)
+ delegate_->UpdateTargetURL(page_id, url);
+
+ // Send a notification back to the renderer that we are ready to
+ // receive more target urls.
+ Send(new ViewMsg_UpdateTargetURL_ACK(GetRoutingID()));
+}
+
+void RenderViewHostImpl::OnUpdateInspectorSetting(
+ const std::string& key, const std::string& value) {
+ GetContentClient()->browser()->UpdateInspectorSetting(
+ this, key, value);
+}
+
+void RenderViewHostImpl::OnClose() {
+ // If the renderer is telling us to close, it has already run the unload
+ // events, and we can take the fast path.
+ ClosePageIgnoringUnloadEvents();
+}
+
+void RenderViewHostImpl::OnRequestMove(const gfx::Rect& pos) {
+ if (!is_swapped_out_)
+ delegate_->RequestMove(pos);
+ Send(new ViewMsg_Move_ACK(GetRoutingID()));
+}
+
+void RenderViewHostImpl::OnDidStartLoading() {
+ delegate_->DidStartLoading(this);
+}
+
+void RenderViewHostImpl::OnDidStopLoading() {
+ delegate_->DidStopLoading(this);
+}
+
+void RenderViewHostImpl::OnDidChangeLoadProgress(double load_progress) {
+ delegate_->DidChangeLoadProgress(load_progress);
+}
+
+void RenderViewHostImpl::OnDidDisownOpener() {
+ delegate_->DidDisownOpener(this);
+}
+
+void RenderViewHostImpl::OnDocumentAvailableInMainFrame() {
+ delegate_->DocumentAvailableInMainFrame(this);
+}
+
+void RenderViewHostImpl::OnDocumentOnLoadCompletedInMainFrame(
+ int32 page_id) {
+ delegate_->DocumentOnLoadCompletedInMainFrame(this, page_id);
+}
+
+void RenderViewHostImpl::OnContextMenu(const ContextMenuParams& params) {
+ // Validate the URLs in |params|. If the renderer can't request the URLs
+ // directly, don't show them in the context menu.
+ ContextMenuParams validated_params(params);
+ RenderProcessHost* process = GetProcess();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // We don't validate |unfiltered_link_url| so that this field can be used
+ // when users want to copy the original link URL.
+ FilterURL(policy, process, true, &validated_params.link_url);
+ FilterURL(policy, process, true, &validated_params.src_url);
+ FilterURL(policy, process, false, &validated_params.page_url);
+ FilterURL(policy, process, true, &validated_params.frame_url);
+
+ delegate_->ShowContextMenu(validated_params);
+}
+
+void RenderViewHostImpl::OnToggleFullscreen(bool enter_fullscreen) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ delegate_->ToggleFullscreenMode(enter_fullscreen);
+ WasResized();
+}
+
+void RenderViewHostImpl::OnOpenURL(
+ const ViewHostMsg_OpenURL_Params& params) {
+ GURL validated_url(params.url);
+ FilterURL(ChildProcessSecurityPolicyImpl::GetInstance(),
+ GetProcess(), false, &validated_url);
+
+ delegate_->RequestOpenURL(
+ this, validated_url, params.referrer, params.disposition, params.frame_id,
+ params.should_replace_current_entry, params.user_gesture);
+}
+
+void RenderViewHostImpl::OnDidContentsPreferredSizeChange(
+ const gfx::Size& new_size) {
+ delegate_->UpdatePreferredSize(new_size);
+}
+
+void RenderViewHostImpl::OnRenderAutoResized(const gfx::Size& new_size) {
+ delegate_->ResizeDueToAutoResize(new_size);
+}
+
+void RenderViewHostImpl::OnDidChangeScrollOffset() {
+ if (view_)
+ view_->ScrollOffsetChanged();
+}
+
+void RenderViewHostImpl::OnDidChangeScrollbarsForMainFrame(
+ bool has_horizontal_scrollbar, bool has_vertical_scrollbar) {
+ if (view_)
+ view_->SetHasHorizontalScrollbar(has_horizontal_scrollbar);
+}
+
+void RenderViewHostImpl::OnDidChangeScrollOffsetPinningForMainFrame(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ if (view_)
+ view_->SetScrollOffsetPinning(is_pinned_to_left, is_pinned_to_right);
+}
+
+void RenderViewHostImpl::OnDidChangeNumWheelEvents(int count) {
+}
+
+void RenderViewHostImpl::OnSelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ if (view_)
+ view_->SelectionChanged(text, offset, range);
+}
+
+void RenderViewHostImpl::OnSelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ if (view_) {
+ view_->SelectionBoundsChanged(params);
+ }
+}
+
+void RenderViewHostImpl::OnRouteCloseEvent() {
+ // Have the delegate route this to the active RenderViewHost.
+ delegate_->RouteCloseEvent(this);
+}
+
+void RenderViewHostImpl::OnRouteMessageEvent(
+ const ViewMsg_PostMessage_Params& params) {
+ // Give to the delegate to route to the active RenderViewHost.
+ delegate_->RouteMessageEvent(this, params);
+}
+
+void RenderViewHostImpl::OnRunJavaScriptMessage(
+ const string16& message,
+ const string16& default_prompt,
+ const GURL& frame_url,
+ JavaScriptMessageType type,
+ IPC::Message* reply_msg) {
+ // While a JS message dialog is showing, tabs in the same process shouldn't
+ // process input events.
+ GetProcess()->SetIgnoreInputEvents(true);
+ StopHangMonitorTimeout();
+ delegate_->RunJavaScriptMessage(this, message, default_prompt, frame_url,
+ type, reply_msg,
+ &are_javascript_messages_suppressed_);
+}
+
+void RenderViewHostImpl::OnRunBeforeUnloadConfirm(const GURL& frame_url,
+ const string16& message,
+ bool is_reload,
+ IPC::Message* reply_msg) {
+ // While a JS before unload dialog is showing, tabs in the same process
+ // shouldn't process input events.
+ GetProcess()->SetIgnoreInputEvents(true);
+ StopHangMonitorTimeout();
+ delegate_->RunBeforeUnloadConfirm(this, message, is_reload, reply_msg);
+}
+
+void RenderViewHostImpl::OnStartDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask drag_operations_mask,
+ const SkBitmap& bitmap,
+ const gfx::Vector2d& bitmap_offset_in_dip,
+ const DragEventSourceInfo& event_info) {
+ RenderViewHostDelegateView* view = delegate_->GetDelegateView();
+ if (!view)
+ return;
+
+ DropData filtered_data(drop_data);
+ RenderProcessHost* process = GetProcess();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // Allow drag of Javascript URLs to enable bookmarklet drag to bookmark bar.
+ if (!filtered_data.url.SchemeIs(chrome::kJavaScriptScheme))
+ FilterURL(policy, process, true, &filtered_data.url);
+ FilterURL(policy, process, false, &filtered_data.html_base_url);
+ // Filter out any paths that the renderer didn't have access to. This prevents
+ // the following attack on a malicious renderer:
+ // 1. StartDragging IPC sent with renderer-specified filesystem paths that it
+ // doesn't have read permissions for.
+ // 2. We initiate a native DnD operation.
+ // 3. DnD operation immediately ends since mouse is not held down. DnD events
+ // still fire though, which causes read permissions to be granted to the
+ // renderer for any file paths in the drop.
+ filtered_data.filenames.clear();
+ for (std::vector<DropData::FileInfo>::const_iterator it =
+ drop_data.filenames.begin();
+ it != drop_data.filenames.end(); ++it) {
+ base::FilePath path(base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(it->path)));
+ if (policy->CanReadFile(GetProcess()->GetID(), path))
+ filtered_data.filenames.push_back(*it);
+ }
+ ui::ScaleFactor scale_factor = GetScaleFactorForView(GetView());
+ gfx::ImageSkia image(gfx::ImageSkiaRep(bitmap, scale_factor));
+ view->StartDragging(filtered_data, drag_operations_mask, image,
+ bitmap_offset_in_dip, event_info);
+}
+
+void RenderViewHostImpl::OnUpdateDragCursor(WebDragOperation current_op) {
+ RenderViewHostDelegateView* view = delegate_->GetDelegateView();
+ if (view)
+ view->UpdateDragCursor(current_op);
+}
+
+void RenderViewHostImpl::OnTargetDropACK() {
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_VIEW_HOST_DID_RECEIVE_DRAG_TARGET_DROP_ACK,
+ Source<RenderViewHost>(this),
+ NotificationService::NoDetails());
+}
+
+void RenderViewHostImpl::OnTakeFocus(bool reverse) {
+ RenderViewHostDelegateView* view = delegate_->GetDelegateView();
+ if (view)
+ view->TakeFocus(reverse);
+}
+
+void RenderViewHostImpl::OnFocusedNodeChanged(bool is_editable_node) {
+ NotificationService::current()->Notify(
+ NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
+ Source<RenderViewHost>(this),
+ Details<const bool>(&is_editable_node));
+}
+
+void RenderViewHostImpl::OnAddMessageToConsole(
+ int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id) {
+ if (delegate_->AddMessageToConsole(level, message, line_no, source_id))
+ return;
+ // Pass through log level only on WebUI pages to limit console spew.
+ int32 resolved_level = HasWebUIScheme(delegate_->GetURL()) ? level : 0;
+
+ if (resolved_level >= ::logging::GetMinLogLevel()) {
+ logging::LogMessage("CONSOLE", line_no, resolved_level).stream() << "\"" <<
+ message << "\", source: " << source_id << " (" << line_no << ")";
+ }
+}
+
+void RenderViewHostImpl::AddObserver(RenderViewHostObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void RenderViewHostImpl::RemoveObserver(RenderViewHostObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void RenderViewHostImpl::OnUserGesture() {
+ delegate_->OnUserGesture();
+}
+
+void RenderViewHostImpl::OnShouldCloseACK(
+ bool proceed,
+ const base::TimeTicks& renderer_before_unload_start_time,
+ const base::TimeTicks& renderer_before_unload_end_time) {
+ decrement_in_flight_event_count();
+ StopHangMonitorTimeout();
+ // If this renderer navigated while the beforeunload request was in flight, we
+ // may have cleared this state in OnNavigate, in which case we can ignore
+ // this message.
+ if (!is_waiting_for_beforeunload_ack_ || is_swapped_out_)
+ return;
+
+ is_waiting_for_beforeunload_ack_ = false;
+
+ RenderViewHostDelegate::RendererManagement* management_delegate =
+ delegate_->GetRendererManagementDelegate();
+ if (management_delegate) {
+ base::TimeTicks before_unload_end_time;
+ if (!send_should_close_start_time_.is_null() &&
+ !renderer_before_unload_start_time.is_null() &&
+ !renderer_before_unload_end_time.is_null()) {
+ // When passing TimeTicks across process boundaries, we need to compensate
+ // for any skew between the processes. Here we are converting the
+ // renderer's notion of before_unload_end_time to TimeTicks in the browser
+ // process. See comments in inter_process_time_ticks_converter.h for more.
+ InterProcessTimeTicksConverter converter(
+ LocalTimeTicks::FromTimeTicks(send_should_close_start_time_),
+ LocalTimeTicks::FromTimeTicks(base::TimeTicks::Now()),
+ RemoteTimeTicks::FromTimeTicks(renderer_before_unload_start_time),
+ RemoteTimeTicks::FromTimeTicks(renderer_before_unload_end_time));
+ LocalTimeTicks browser_before_unload_end_time =
+ converter.ToLocalTimeTicks(
+ RemoteTimeTicks::FromTimeTicks(renderer_before_unload_end_time));
+ before_unload_end_time = browser_before_unload_end_time.ToTimeTicks();
+ }
+ management_delegate->ShouldClosePage(
+ unload_ack_is_for_cross_site_transition_, proceed,
+ before_unload_end_time);
+ }
+
+ // If canceled, notify the delegate to cancel its pending navigation entry.
+ if (!proceed)
+ delegate_->DidCancelLoading();
+}
+
+void RenderViewHostImpl::OnClosePageACK() {
+ decrement_in_flight_event_count();
+ ClosePageIgnoringUnloadEvents();
+}
+
+void RenderViewHostImpl::NotifyRendererUnresponsive() {
+ delegate_->RendererUnresponsive(
+ this, is_waiting_for_beforeunload_ack_ || is_waiting_for_unload_ack_);
+}
+
+void RenderViewHostImpl::NotifyRendererResponsive() {
+ delegate_->RendererResponsive(this);
+}
+
+void RenderViewHostImpl::RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) {
+ delegate_->RequestToLockMouse(user_gesture, last_unlocked_by_target);
+}
+
+bool RenderViewHostImpl::IsFullscreen() const {
+ return delegate_->IsFullscreenForCurrentTab();
+}
+
+void RenderViewHostImpl::OnFocus() {
+ // Note: We allow focus and blur from swapped out RenderViewHosts, even when
+ // the active RenderViewHost is in a different BrowsingInstance (e.g., WebUI).
+ delegate_->Activate();
+}
+
+void RenderViewHostImpl::OnBlur() {
+ delegate_->Deactivate();
+}
+
+gfx::Rect RenderViewHostImpl::GetRootWindowResizerRect() const {
+ return delegate_->GetRootWindowResizerRect();
+}
+
+void RenderViewHostImpl::ForwardMouseEvent(
+ const WebKit::WebMouseEvent& mouse_event) {
+
+ // We make a copy of the mouse event because
+ // RenderWidgetHost::ForwardMouseEvent will delete |mouse_event|.
+ WebKit::WebMouseEvent event_copy(mouse_event);
+ RenderWidgetHostImpl::ForwardMouseEvent(event_copy);
+
+ switch (event_copy.type) {
+ case WebInputEvent::MouseMove:
+ delegate_->HandleMouseMove();
+ break;
+ case WebInputEvent::MouseLeave:
+ delegate_->HandleMouseLeave();
+ break;
+ case WebInputEvent::MouseDown:
+ delegate_->HandleMouseDown();
+ break;
+ case WebInputEvent::MouseWheel:
+ if (ignore_input_events())
+ delegate_->OnIgnoredUIEvent();
+ break;
+ case WebInputEvent::MouseUp:
+ delegate_->HandleMouseUp();
+ default:
+ // For now, we don't care about the rest.
+ break;
+ }
+}
+
+void RenderViewHostImpl::OnPointerEventActivate() {
+ delegate_->HandlePointerActivate();
+}
+
+void RenderViewHostImpl::ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event) {
+ if (ignore_input_events()) {
+ if (key_event.type == WebInputEvent::RawKeyDown)
+ delegate_->OnIgnoredUIEvent();
+ return;
+ }
+ RenderWidgetHostImpl::ForwardKeyboardEvent(key_event);
+}
+
+#if defined(OS_ANDROID)
+void RenderViewHostImpl::DidSelectPopupMenuItems(
+ const std::vector<int>& selected_indices) {
+ Send(new ViewMsg_SelectPopupMenuItems(GetRoutingID(), false,
+ selected_indices));
+}
+
+void RenderViewHostImpl::DidCancelPopupMenu() {
+ Send(new ViewMsg_SelectPopupMenuItems(GetRoutingID(), true,
+ std::vector<int>()));
+}
+#endif
+
+#if defined(OS_MACOSX)
+void RenderViewHostImpl::DidSelectPopupMenuItem(int selected_index) {
+ Send(new ViewMsg_SelectPopupMenuItem(GetRoutingID(), selected_index));
+}
+
+void RenderViewHostImpl::DidCancelPopupMenu() {
+ Send(new ViewMsg_SelectPopupMenuItem(GetRoutingID(), -1));
+}
+#endif
+
+void RenderViewHostImpl::SendOrientationChangeEvent(int orientation) {
+ Send(new ViewMsg_OrientationChangeEvent(GetRoutingID(), orientation));
+}
+
+void RenderViewHostImpl::ToggleSpeechInput() {
+ Send(new InputTagSpeechMsg_ToggleSpeechInput(GetRoutingID()));
+}
+
+bool RenderViewHostImpl::CanCommitURL(const GURL& url) {
+ // TODO(creis): We should also check for WebUI pages here. Also, when the
+ // out-of-process iframes implementation is ready, we should check for
+ // cross-site URLs that are not allowed to commit in this process.
+
+ // Give the client a chance to disallow URLs from committing.
+ return GetContentClient()->browser()->CanCommitURL(GetProcess(), url);
+}
+
+void RenderViewHostImpl::FilterURL(ChildProcessSecurityPolicyImpl* policy,
+ const RenderProcessHost* process,
+ bool empty_allowed,
+ GURL* url) {
+ if (empty_allowed && url->is_empty())
+ return;
+
+ // The browser process should never hear the swappedout:// URL from any
+ // of the renderer's messages. Check for this in debug builds, but don't
+ // let it crash a release browser.
+ DCHECK(GURL(kSwappedOutURL) != *url);
+
+ if (!url->is_valid()) {
+ // Have to use about:blank for the denied case, instead of an empty GURL.
+ // This is because the browser treats navigation to an empty GURL as a
+ // navigation to the home page. This is often a privileged page
+ // (chrome://newtab/) which is exactly what we don't want.
+ *url = GURL(kAboutBlankURL);
+ RecordAction(UserMetricsAction("FilterURLTermiate_Invalid"));
+ return;
+ }
+
+ if (url->SchemeIs(chrome::kAboutScheme)) {
+ // The renderer treats all URLs in the about: scheme as being about:blank.
+ // Canonicalize about: URLs to about:blank.
+ *url = GURL(kAboutBlankURL);
+ RecordAction(UserMetricsAction("FilterURLTermiate_About"));
+ }
+
+ // Do not allow browser plugin guests to navigate to non-web URLs, since they
+ // cannot swap processes or grant bindings.
+ bool non_web_url_in_guest = process->IsGuest() &&
+ !(url->is_valid() && policy->IsWebSafeScheme(url->scheme()));
+
+ if (non_web_url_in_guest || !policy->CanRequestURL(process->GetID(), *url)) {
+ // If this renderer is not permitted to request this URL, we invalidate the
+ // URL. This prevents us from storing the blocked URL and becoming confused
+ // later.
+ VLOG(1) << "Blocked URL " << url->spec();
+ *url = GURL(kAboutBlankURL);
+ RecordAction(UserMetricsAction("FilterURLTermiate_Blocked"));
+ }
+}
+
+void RenderViewHost::AddCreatedCallback(const CreatedCallback& callback) {
+ g_created_callbacks.Get().push_back(callback);
+}
+
+void RenderViewHost::RemoveCreatedCallback(const CreatedCallback& callback) {
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); ++i) {
+ if (g_created_callbacks.Get().at(i).Equals(callback)) {
+ g_created_callbacks.Get().erase(g_created_callbacks.Get().begin() + i);
+ return;
+ }
+ }
+}
+
+void RenderViewHostImpl::SetAltErrorPageURL(const GURL& url) {
+ Send(new ViewMsg_SetAltErrorPageURL(GetRoutingID(), url));
+}
+
+void RenderViewHostImpl::ExitFullscreen() {
+ RejectMouseLockOrUnlockIfNecessary();
+ // We need to notify the contents that its fullscreen state has changed. This
+ // is done as part of the resize message.
+ WasResized();
+}
+
+WebPreferences RenderViewHostImpl::GetWebkitPreferences() {
+ return delegate_->GetWebkitPrefs();
+}
+
+void RenderViewHostImpl::DisownOpener() {
+ // This should only be called when swapped out.
+ DCHECK(is_swapped_out_);
+
+ Send(new ViewMsg_DisownOpener(GetRoutingID()));
+}
+
+void RenderViewHostImpl::SetAccessibilityCallbackForTesting(
+ const base::Callback<void(AccessibilityNotification)>& callback) {
+ accessibility_testing_callback_ = callback;
+}
+
+void RenderViewHostImpl::UpdateWebkitPreferences(const WebPreferences& prefs) {
+ Send(new ViewMsg_UpdateWebPreferences(GetRoutingID(), prefs));
+}
+
+void RenderViewHostImpl::NotifyTimezoneChange() {
+ Send(new ViewMsg_TimezoneChange(GetRoutingID()));
+}
+
+void RenderViewHostImpl::ClearFocusedNode() {
+ Send(new ViewMsg_ClearFocusedNode(GetRoutingID()));
+}
+
+void RenderViewHostImpl::SetZoomLevel(double level) {
+ Send(new ViewMsg_SetZoomLevel(GetRoutingID(), level));
+}
+
+void RenderViewHostImpl::Zoom(PageZoom zoom) {
+ Send(new ViewMsg_Zoom(GetRoutingID(), zoom));
+}
+
+void RenderViewHostImpl::ReloadFrame() {
+ Send(new ViewMsg_ReloadFrame(GetRoutingID()));
+}
+
+void RenderViewHostImpl::Find(int request_id,
+ const string16& search_text,
+ const WebKit::WebFindOptions& options) {
+ Send(new ViewMsg_Find(GetRoutingID(), request_id, search_text, options));
+}
+
+void RenderViewHostImpl::InsertCSS(const string16& frame_xpath,
+ const std::string& css) {
+ Send(new ViewMsg_CSSInsertRequest(GetRoutingID(), frame_xpath, css));
+}
+
+void RenderViewHostImpl::DisableScrollbarsForThreshold(const gfx::Size& size) {
+ Send(new ViewMsg_DisableScrollbarsForSmallWindows(GetRoutingID(), size));
+}
+
+void RenderViewHostImpl::EnablePreferredSizeMode() {
+ Send(new ViewMsg_EnablePreferredSizeChangedMode(GetRoutingID()));
+}
+
+void RenderViewHostImpl::EnableAutoResize(const gfx::Size& min_size,
+ const gfx::Size& max_size) {
+ SetShouldAutoResize(true);
+ Send(new ViewMsg_EnableAutoResize(GetRoutingID(), min_size, max_size));
+}
+
+void RenderViewHostImpl::DisableAutoResize(const gfx::Size& new_size) {
+ SetShouldAutoResize(false);
+ Send(new ViewMsg_DisableAutoResize(GetRoutingID(), new_size));
+}
+
+void RenderViewHostImpl::ExecuteCustomContextMenuCommand(
+ int action, const CustomContextMenuContext& context) {
+ Send(new ViewMsg_CustomContextMenuAction(GetRoutingID(), context, action));
+}
+
+void RenderViewHostImpl::NotifyContextMenuClosed(
+ const CustomContextMenuContext& context) {
+ Send(new ViewMsg_ContextMenuClosed(GetRoutingID(), context));
+}
+
+void RenderViewHostImpl::CopyImageAt(int x, int y) {
+ Send(new ViewMsg_CopyImageAt(GetRoutingID(), x, y));
+}
+
+void RenderViewHostImpl::ExecuteMediaPlayerActionAtLocation(
+ const gfx::Point& location, const WebKit::WebMediaPlayerAction& action) {
+ Send(new ViewMsg_MediaPlayerActionAt(GetRoutingID(), location, action));
+}
+
+void RenderViewHostImpl::ExecutePluginActionAtLocation(
+ const gfx::Point& location, const WebKit::WebPluginAction& action) {
+ Send(new ViewMsg_PluginActionAt(GetRoutingID(), location, action));
+}
+
+void RenderViewHostImpl::DisassociateFromPopupCount() {
+ Send(new ViewMsg_DisassociateFromPopupCount(GetRoutingID()));
+}
+
+void RenderViewHostImpl::NotifyMoveOrResizeStarted() {
+ Send(new ViewMsg_MoveOrResizeStarted(GetRoutingID()));
+}
+
+void RenderViewHostImpl::StopFinding(StopFindAction action) {
+ Send(new ViewMsg_StopFinding(GetRoutingID(), action));
+}
+
+void RenderViewHostImpl::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ if (view_ && !is_swapped_out_)
+ view_->OnAccessibilityNotifications(params);
+
+ // Always send an ACK or the renderer can be in a bad state.
+ Send(new AccessibilityMsg_Notifications_ACK(GetRoutingID()));
+
+ // The rest of this code is just for testing; bail out if we're not
+ // in that mode.
+ if (accessibility_testing_callback_.is_null())
+ return;
+
+ for (unsigned i = 0; i < params.size(); i++) {
+ const AccessibilityHostMsg_NotificationParams& param = params[i];
+ AccessibilityNotification src_type = param.notification_type;
+ if (src_type == AccessibilityNotificationLayoutComplete ||
+ src_type == AccessibilityNotificationLoadComplete) {
+ MakeAccessibilityNodeDataTree(param.nodes, &accessibility_tree_);
+ }
+ accessibility_testing_callback_.Run(src_type);
+ }
+}
+
+void RenderViewHostImpl::OnScriptEvalResponse(int id,
+ const base::ListValue& result) {
+ const base::Value* result_value;
+ if (!result.Get(0, &result_value)) {
+ // Programming error or rogue renderer.
+ NOTREACHED() << "Got bad arguments for OnScriptEvalResponse";
+ return;
+ }
+
+ std::map<int, JavascriptResultCallback>::iterator it =
+ javascript_callbacks_.find(id);
+ if (it != javascript_callbacks_.end()) {
+ // ExecuteJavascriptInWebFrameCallbackResult was used; do callback.
+ it->second.Run(result_value);
+ javascript_callbacks_.erase(it);
+ } else {
+ NOTREACHED() << "Received script response for unknown request";
+ }
+}
+
+void RenderViewHostImpl::OnDidZoomURL(double zoom_level,
+ bool remember,
+ const GURL& url) {
+ HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(
+ HostZoomMap::GetForBrowserContext(GetProcess()->GetBrowserContext()));
+ if (remember) {
+ host_zoom_map->
+ SetZoomLevelForHost(net::GetHostOrSpecFromURL(url), zoom_level);
+ } else {
+ host_zoom_map->SetTemporaryZoomLevel(
+ GetProcess()->GetID(), GetRoutingID(), zoom_level);
+ }
+}
+
+void RenderViewHostImpl::OnRequestDesktopNotificationPermission(
+ const GURL& source_origin, int callback_context) {
+ GetContentClient()->browser()->RequestDesktopNotificationPermission(
+ source_origin, callback_context, GetProcess()->GetID(), GetRoutingID());
+}
+
+void RenderViewHostImpl::OnShowDesktopNotification(
+ const ShowDesktopNotificationHostMsgParams& params) {
+ // Disallow HTML notifications from javascript: and file: schemes as this
+ // allows unwanted cross-domain access.
+ GURL url = params.contents_url;
+ if (params.is_html &&
+ (url.SchemeIs(chrome::kJavaScriptScheme) ||
+ url.SchemeIs(chrome::kFileScheme))) {
+ return;
+ }
+
+ GetContentClient()->browser()->ShowDesktopNotification(
+ params, GetProcess()->GetID(), GetRoutingID(), false);
+}
+
+void RenderViewHostImpl::OnCancelDesktopNotification(int notification_id) {
+ GetContentClient()->browser()->CancelDesktopNotification(
+ GetProcess()->GetID(), GetRoutingID(), notification_id);
+}
+
+void RenderViewHostImpl::OnRunFileChooser(const FileChooserParams& params) {
+ delegate_->RunFileChooser(this, params);
+}
+
+void RenderViewHostImpl::OnDidAccessInitialDocument() {
+ has_accessed_initial_document_ = true;
+ delegate_->DidAccessInitialDocument();
+}
+
+void RenderViewHostImpl::OnDomOperationResponse(
+ const std::string& json_string, int automation_id) {
+ DomOperationNotificationDetails details(json_string, automation_id);
+ NotificationService::current()->Notify(
+ NOTIFICATION_DOM_OPERATION_RESPONSE,
+ Source<RenderViewHost>(this),
+ Details<DomOperationNotificationDetails>(&details));
+}
+
+void RenderViewHostImpl::OnGetWindowSnapshot(const int snapshot_id) {
+ std::vector<unsigned char> png;
+
+ // This feature is behind the kEnableGpuBenchmarking command line switch
+ // because it poses security concerns and should only be used for testing.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kEnableGpuBenchmarking)) {
+ gfx::Rect view_bounds = GetView()->GetViewBounds();
+ gfx::Rect snapshot_bounds(view_bounds.size());
+ gfx::Size snapshot_size = snapshot_bounds.size();
+
+ if (ui::GrabViewSnapshot(GetView()->GetNativeView(),
+ &png, snapshot_bounds)) {
+ Send(new ViewMsg_WindowSnapshotCompleted(
+ GetRoutingID(), snapshot_id, snapshot_size, png));
+ return;
+ }
+ }
+
+ Send(new ViewMsg_WindowSnapshotCompleted(
+ GetRoutingID(), snapshot_id, gfx::Size(), png));
+}
+
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
+void RenderViewHostImpl::OnShowPopup(
+ const ViewHostMsg_ShowPopup_Params& params) {
+ RenderViewHostDelegateView* view = delegate_->GetDelegateView();
+ if (view) {
+ view->ShowPopupMenu(params.bounds,
+ params.item_height,
+ params.item_font_size,
+ params.selected_item,
+ params.popup_items,
+ params.right_aligned,
+ params.allow_multiple_selection);
+ }
+}
+#endif
+
+void RenderViewHostImpl::SetSwappedOut(bool is_swapped_out) {
+ // We update the number of RenderViews in a SiteInstance when the
+ // swapped out status of this RenderView gets flipped.
+ if (is_swapped_out_ && !is_swapped_out)
+ instance_->increment_active_view_count();
+ else if (!is_swapped_out_ && is_swapped_out)
+ instance_->decrement_active_view_count();
+
+ is_swapped_out_ = is_swapped_out;
+
+ // Whenever we change swap out state, we should not be waiting for
+ // beforeunload or unload acks. We clear them here to be safe, since they
+ // can cause navigations to be ignored in OnNavigate.
+ is_waiting_for_beforeunload_ack_ = false;
+ is_waiting_for_unload_ack_ = false;
+ has_timed_out_on_unload_ = false;
+}
+
+bool RenderViewHostImpl::CanAccessFilesOfPageState(
+ const PageState& state) const {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ const std::vector<base::FilePath>& file_paths = state.GetReferencedFiles();
+ for (std::vector<base::FilePath>::const_iterator file = file_paths.begin();
+ file != file_paths.end(); ++file) {
+ if (!policy->CanReadFile(GetProcess()->GetID(), *file))
+ return false;
+ }
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_view_host_impl.h b/chromium/content/browser/renderer_host/render_view_host_impl.h
new file mode 100644
index 00000000000..20caaf5ea73
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_impl.h
@@ -0,0 +1,714 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_IMPL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/process/kill.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/common/accessibility_node_data.h"
+#include "content/common/accessibility_notification.h"
+#include "content/common/drag_event_source_info.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/javascript_message_type.h"
+#include "content/public/common/window_container_type.h"
+#include "net/base/load_states.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/WebKit/public/web/WebConsoleMessage.h"
+#include "third_party/WebKit/public/web/WebPopupType.h"
+#include "third_party/WebKit/public/web/WebTextDirection.h"
+#include "ui/base/window_open_disposition.h"
+
+class SkBitmap;
+class ViewMsg_Navigate;
+struct AccessibilityHostMsg_NotificationParams;
+struct MediaPlayerAction;
+struct ViewHostMsg_CreateWindow_Params;
+struct ViewHostMsg_DidFailProvisionalLoadWithError_Params;
+struct ViewHostMsg_OpenURL_Params;
+struct ViewHostMsg_SelectionBounds_Params;
+struct ViewHostMsg_ShowPopup_Params;
+struct ViewMsg_Navigate_Params;
+struct ViewMsg_PostMessage_Params;
+struct ViewMsg_StopFinding_Params;
+
+namespace base {
+class ListValue;
+}
+
+namespace ui {
+class Range;
+struct SelectedFileInfo;
+}
+
+#if defined(OS_ANDROID)
+namespace media {
+class MediaPlayerManager;
+}
+#endif
+
+namespace content {
+
+class ChildProcessSecurityPolicyImpl;
+class PageState;
+class RenderFrameHostImpl;
+class RenderViewHostObserver;
+class RenderWidgetHostDelegate;
+class SessionStorageNamespace;
+class SessionStorageNamespaceImpl;
+class TestRenderViewHost;
+struct ContextMenuParams;
+struct FileChooserParams;
+struct Referrer;
+struct ShowDesktopNotificationHostMsgParams;
+
+#if defined(COMPILER_MSVC)
+// RenderViewHostImpl is the bottom of a diamond-shaped hierarchy,
+// with RenderWidgetHost at the root. VS warns when methods from the
+// root are overridden in only one of the base classes and not both
+// (in this case, RenderWidgetHostImpl provides implementations of
+// many of the methods). This is a silly warning when dealing with
+// pure virtual methods that only have a single implementation in the
+// hierarchy above this class, and is safe to ignore in this case.
+#pragma warning(push)
+#pragma warning(disable: 4250)
+#endif
+
+// This implements the RenderViewHost interface that is exposed to
+// embedders of content, and adds things only visible to content.
+//
+// The exact API of this object needs to be more thoroughly designed. Right
+// now it mimics what WebContentsImpl exposed, which is a fairly large API and
+// may contain things that are not relevant to a common subset of views. See
+// also the comment in render_view_host_delegate.h about the size and scope of
+// the delegate API.
+//
+// Right now, the concept of page navigation (both top level and frame) exists
+// in the WebContentsImpl still, so if you instantiate one of these elsewhere,
+// you will not be able to traverse pages back and forward. We need to determine
+// if we want to bring that and other functionality down into this object so it
+// can be shared by others.
+class CONTENT_EXPORT RenderViewHostImpl
+ : public RenderViewHost,
+ public RenderWidgetHostImpl {
+ public:
+ // Convenience function, just like RenderViewHost::FromID.
+ static RenderViewHostImpl* FromID(int render_process_id, int render_view_id);
+
+ // |routing_id| could be a valid route id, or it could be MSG_ROUTING_NONE, in
+ // which case RenderWidgetHost will create a new one. |swapped_out| indicates
+ // whether the view should initially be swapped out (e.g., for an opener
+ // frame being rendered by another process).
+ //
+ // The |session_storage_namespace| parameter allows multiple render views and
+ // WebContentses to share the same session storage (part of the WebStorage
+ // spec) space. This is useful when restoring contentses, but most callers
+ // should pass in NULL which will cause a new SessionStorageNamespace to be
+ // created.
+ RenderViewHostImpl(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out);
+ virtual ~RenderViewHostImpl();
+
+ // RenderViewHost implementation.
+ virtual void AllowBindings(int binding_flags) OVERRIDE;
+ virtual void ClearFocusedNode() OVERRIDE;
+ virtual void ClosePage() OVERRIDE;
+ virtual void CopyImageAt(int x, int y) OVERRIDE;
+ virtual void DisassociateFromPopupCount() OVERRIDE;
+ virtual void DesktopNotificationPermissionRequestDone(
+ int callback_context) OVERRIDE;
+ virtual void DesktopNotificationPostDisplay(int callback_context) OVERRIDE;
+ virtual void DesktopNotificationPostError(int notification_id,
+ const string16& message) OVERRIDE;
+ virtual void DesktopNotificationPostClose(int notification_id,
+ bool by_user) OVERRIDE;
+ virtual void DesktopNotificationPostClick(int notification_id) OVERRIDE;
+ virtual void DirectoryEnumerationFinished(
+ int request_id,
+ const std::vector<base::FilePath>& files) OVERRIDE;
+ virtual void DisableScrollbarsForThreshold(const gfx::Size& size) OVERRIDE;
+ virtual void DragSourceEndedAt(
+ int client_x, int client_y, int screen_x, int screen_y,
+ WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void DragSourceMovedTo(
+ int client_x, int client_y, int screen_x, int screen_y) OVERRIDE;
+ virtual void DragSourceSystemDragEnded() OVERRIDE;
+ virtual void DragTargetDragEnter(
+ const DropData& drop_data,
+ const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ WebKit::WebDragOperationsMask operations_allowed,
+ int key_modifiers) OVERRIDE;
+ virtual void DragTargetDragOver(
+ const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ WebKit::WebDragOperationsMask operations_allowed,
+ int key_modifiers) OVERRIDE;
+ virtual void DragTargetDragLeave() OVERRIDE;
+ virtual void DragTargetDrop(const gfx::Point& client_pt,
+ const gfx::Point& screen_pt,
+ int key_modifiers) OVERRIDE;
+ virtual void EnableAutoResize(const gfx::Size& min_size,
+ const gfx::Size& max_size) OVERRIDE;
+ virtual void DisableAutoResize(const gfx::Size& new_size) OVERRIDE;
+ virtual void EnablePreferredSizeMode() OVERRIDE;
+ virtual void ExecuteCustomContextMenuCommand(
+ int action, const CustomContextMenuContext& context) OVERRIDE;
+ virtual void ExecuteMediaPlayerActionAtLocation(
+ const gfx::Point& location,
+ const WebKit::WebMediaPlayerAction& action) OVERRIDE;
+ virtual void ExecuteJavascriptInWebFrame(const string16& frame_xpath,
+ const string16& jscript) OVERRIDE;
+ virtual void ExecuteJavascriptInWebFrameCallbackResult(
+ const string16& frame_xpath,
+ const string16& jscript,
+ const JavascriptResultCallback& callback) OVERRIDE;
+ virtual void ExecutePluginActionAtLocation(
+ const gfx::Point& location,
+ const WebKit::WebPluginAction& action) OVERRIDE;
+ virtual void ExitFullscreen() OVERRIDE;
+ virtual void Find(int request_id, const string16& search_text,
+ const WebKit::WebFindOptions& options) OVERRIDE;
+ virtual void StopFinding(StopFindAction action) OVERRIDE;
+ virtual void FirePageBeforeUnload(bool for_cross_site_transition) OVERRIDE;
+ virtual void FilesSelectedInChooser(
+ const std::vector<ui::SelectedFileInfo>& files,
+ FileChooserParams::Mode permissions) OVERRIDE;
+ virtual RenderViewHostDelegate* GetDelegate() const OVERRIDE;
+ virtual int GetEnabledBindings() const OVERRIDE;
+ virtual SiteInstance* GetSiteInstance() const OVERRIDE;
+ virtual void InsertCSS(const string16& frame_xpath,
+ const std::string& css) OVERRIDE;
+ virtual bool IsRenderViewLive() const OVERRIDE;
+ virtual bool IsSubframe() const OVERRIDE;
+ virtual void NotifyContextMenuClosed(
+ const CustomContextMenuContext& context) OVERRIDE;
+ virtual void NotifyMoveOrResizeStarted() OVERRIDE;
+ virtual void ReloadFrame() OVERRIDE;
+ virtual void SetAltErrorPageURL(const GURL& url) OVERRIDE;
+ virtual void SetWebUIProperty(const std::string& name,
+ const std::string& value) OVERRIDE;
+ virtual void SetZoomLevel(double level) OVERRIDE;
+ virtual void Zoom(PageZoom zoom) OVERRIDE;
+ virtual void SyncRendererPrefs() OVERRIDE;
+ virtual void ToggleSpeechInput() OVERRIDE;
+ virtual WebPreferences GetWebkitPreferences() OVERRIDE;
+ virtual void UpdateWebkitPreferences(
+ const WebPreferences& prefs) OVERRIDE;
+ virtual void NotifyTimezoneChange() OVERRIDE;
+
+#if defined(OS_ANDROID)
+ virtual void ActivateNearestFindResult(int request_id,
+ float x,
+ float y) OVERRIDE;
+ virtual void RequestFindMatchRects(int current_version) OVERRIDE;
+#endif
+
+ void set_delegate(RenderViewHostDelegate* d) {
+ CHECK(d); // http://crbug.com/82827
+ delegate_ = d;
+ }
+
+ // Set up the RenderView child process. Virtual because it is overridden by
+ // TestRenderViewHost. If the |frame_name| parameter is non-empty, it is used
+ // as the name of the new top-level frame.
+ // The |opener_route_id| parameter indicates which RenderView created this
+ // (MSG_ROUTING_NONE if none). If |max_page_id| is larger than -1, the
+ // RenderView is told to start issuing page IDs at |max_page_id| + 1.
+ virtual bool CreateRenderView(const string16& frame_name,
+ int opener_route_id,
+ int32 max_page_id);
+
+ base::TerminationStatus render_view_termination_status() const {
+ return render_view_termination_status_;
+ }
+
+ // Sends the given navigation message. Use this rather than sending it
+ // yourself since this does the internal bookkeeping described below. This
+ // function takes ownership of the provided message pointer.
+ //
+ // If a cross-site request is in progress, we may be suspended while waiting
+ // for the onbeforeunload handler, so this function might buffer the message
+ // rather than sending it.
+ void Navigate(const ViewMsg_Navigate_Params& message);
+
+ // Load the specified URL, this is a shortcut for Navigate().
+ void NavigateToURL(const GURL& url);
+
+ // Returns whether navigation messages are currently suspended for this
+ // RenderViewHost. Only true during a cross-site navigation, while waiting
+ // for the onbeforeunload handler.
+ bool are_navigations_suspended() const { return navigations_suspended_; }
+
+ // Suspends (or unsuspends) any navigation messages from being sent from this
+ // RenderViewHost. This is called when a pending RenderViewHost is created
+ // for a cross-site navigation, because we must suspend any navigations until
+ // we hear back from the old renderer's onbeforeunload handler. Note that it
+ // is important that only one navigation event happen after calling this
+ // method with |suspend| equal to true. If |suspend| is false and there is
+ // a suspended_nav_message_, this will send the message. This function
+ // should only be called to toggle the state; callers should check
+ // are_navigations_suspended() first. If |suspend| is false, the time that the
+ // user decided the navigation should proceed should be passed as
+ // |proceed_time|.
+ void SetNavigationsSuspended(bool suspend,
+ const base::TimeTicks& proceed_time);
+
+ // Clears any suspended navigation state after a cross-site navigation is
+ // canceled or suspended. This is important if we later return to this
+ // RenderViewHost.
+ void CancelSuspendedNavigations();
+
+ // Whether the initial empty page of this view has been accessed by another
+ // page, making it unsafe to show the pending URL. Always false after the
+ // first commit.
+ bool has_accessed_initial_document() {
+ return has_accessed_initial_document_;
+ }
+
+ // Whether this RenderViewHost has been swapped out to be displayed by a
+ // different process.
+ bool is_swapped_out() const { return is_swapped_out_; }
+
+ // Tells the renderer that this RenderView is being swapped out for one in a
+ // different renderer process. It should run its unload handler and move to
+ // a blank document. The renderer should preserve the Frame object until it
+ // exits, in case we come back. The renderer can exit if it has no other
+ // active RenderViews, but not until WasSwappedOut is called (when it is no
+ // longer visible).
+ void SwapOut();
+
+ // Called when either the SwapOut request has been acknowledged or has timed
+ // out.
+ void OnSwappedOut(bool timed_out);
+
+ // Called to notify the renderer that it has been visibly swapped out and
+ // replaced by another RenderViewHost, after an earlier call to SwapOut.
+ // It is now safe for the process to exit if there are no other active
+ // RenderViews.
+ void WasSwappedOut();
+
+ // Close the page ignoring whether it has unload events registers.
+ // This is called after the beforeunload and unload events have fired
+ // and the user has agreed to continue with closing the page.
+ void ClosePageIgnoringUnloadEvents();
+
+ // Returns whether this RenderViewHost has an outstanding cross-site request.
+ // Cleared when we hear the response and start to swap out the old
+ // RenderViewHost, or if we hear a commit here without a network request.
+ bool HasPendingCrossSiteRequest();
+
+ // Sets whether this RenderViewHost has an outstanding cross-site request,
+ // for which another renderer will need to run an onunload event handler.
+ // This is called before the first navigation event for this RenderViewHost,
+ // and cleared when we hear the response or commit.
+ void SetHasPendingCrossSiteRequest(bool has_pending_request);
+
+ // Notifies the RenderView that the JavaScript message that was shown was
+ // closed by the user.
+ void JavaScriptDialogClosed(IPC::Message* reply_msg,
+ bool success,
+ const string16& user_input);
+
+ // Tells the renderer view to focus the first (last if reverse is true) node.
+ void SetInitialFocus(bool reverse);
+
+ // Get html data by serializing all frames of current page with lists
+ // which contain all resource links that have local copy.
+ // The parameter links contain original URLs of all saved links.
+ // The parameter local_paths contain corresponding local file paths of
+ // all saved links, which matched with vector:links one by one.
+ // The parameter local_directory_name is relative path of directory which
+ // contain all saved auxiliary files included all sub frames and resouces.
+ void GetSerializedHtmlDataForCurrentPageWithLocalLinks(
+ const std::vector<GURL>& links,
+ const std::vector<base::FilePath>& local_paths,
+ const base::FilePath& local_directory_name);
+
+ // Notifies the RenderViewHost that its load state changed.
+ void LoadStateChanged(const GURL& url,
+ const net::LoadStateWithParam& load_state,
+ uint64 upload_position,
+ uint64 upload_size);
+
+ bool SuddenTerminationAllowed() const;
+ void set_sudden_termination_allowed(bool enabled) {
+ sudden_termination_allowed_ = enabled;
+ }
+
+ // RenderWidgetHost public overrides.
+ virtual void Shutdown() OVERRIDE;
+ virtual bool IsRenderView() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void LostCapture() OVERRIDE;
+ virtual void LostMouseLock() OVERRIDE;
+ virtual void ForwardMouseEvent(
+ const WebKit::WebMouseEvent& mouse_event) OVERRIDE;
+ virtual void OnPointerEventActivate() OVERRIDE;
+ virtual void ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event) OVERRIDE;
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE;
+
+ // Creates a new RenderView with the given route id.
+ void CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace);
+
+ // Creates a new RenderWidget with the given route id. |popup_type| indicates
+ // if this widget is a popup and what kind of popup it is (select, autofill).
+ void CreateNewWidget(int route_id, WebKit::WebPopupType popup_type);
+
+ // Creates a full screen RenderWidget.
+ void CreateNewFullscreenWidget(int route_id);
+
+#if defined(OS_MACOSX)
+ // Select popup menu related methods (for external popup menus).
+ void DidSelectPopupMenuItem(int selected_index);
+ void DidCancelPopupMenu();
+#endif
+
+#if defined(OS_ANDROID)
+ media::MediaPlayerManager* media_player_manager() {
+ return media_player_manager_;
+ }
+
+ void DidSelectPopupMenuItems(const std::vector<int>& selected_indices);
+ void DidCancelPopupMenu();
+#endif
+
+ // User rotated the screen. Calls the "onorientationchange" Javascript hook.
+ void SendOrientationChangeEvent(int orientation);
+
+ // Sets a bit indicating whether the RenderView is responsible for displaying
+ // a subframe in a different process from its parent page.
+ void set_is_subframe(bool is_subframe) {
+ is_subframe_ = is_subframe;
+ }
+
+ int64 main_frame_id() const {
+ return main_frame_id_;
+ }
+
+ // Set the opener to null in the renderer process.
+ void DisownOpener();
+
+ // Turn on accessibility testing. The given callback will be run
+ // every time an accessibility notification is received from the
+ // renderer process, and the accessibility tree it sent can be
+ // retrieved using accessibility_tree_for_testing().
+ void SetAccessibilityCallbackForTesting(
+ const base::Callback<void(AccessibilityNotification)>& callback);
+
+ // Only valid if SetAccessibilityCallbackForTesting was called and
+ // the callback was run at least once. Returns a snapshot of the
+ // accessibility tree received from the renderer as of the last time
+ // a LoadComplete or LayoutComplete accessibility notification was received.
+ const AccessibilityNodeDataTreeNode& accessibility_tree_for_testing() {
+ return accessibility_tree_;
+ }
+
+ // Set accessibility callbacks.
+ void SetAccessibilityLayoutCompleteCallbackForTesting(
+ const base::Closure& callback);
+ void SetAccessibilityLoadCompleteCallbackForTesting(
+ const base::Closure& callback);
+ void SetAccessibilityOtherCallbackForTesting(
+ const base::Closure& callback);
+
+ bool is_waiting_for_beforeunload_ack() {
+ return is_waiting_for_beforeunload_ack_;
+ }
+
+ bool is_waiting_for_unload_ack() {
+ return is_waiting_for_unload_ack_;
+ }
+
+ // Returns whether the given URL is allowed to commit in the current process.
+ // This is a more conservative check than FilterURL, since it will be used to
+ // kill processes that commit unauthorized URLs.
+ bool CanCommitURL(const GURL& url);
+
+ // Checks that the given renderer can request |url|, if not it sets it to
+ // about:blank.
+ // empty_allowed must be set to false for navigations for security reasons.
+ static void FilterURL(ChildProcessSecurityPolicyImpl* policy,
+ const RenderProcessHost* process,
+ bool empty_allowed,
+ GURL* url);
+
+ // NOTE: Do not add functions that just send an IPC message that are called in
+ // one or two places. Have the caller send the IPC message directly (unless
+ // the caller places are in different platforms, in which case it's better
+ // to keep them consistent).
+
+ protected:
+ friend class RenderViewHostObserver;
+
+ // Add and remove observers for filtering IPC messages. Clients must be sure
+ // to remove the observer before they go away.
+ void AddObserver(RenderViewHostObserver* observer);
+ void RemoveObserver(RenderViewHostObserver* observer);
+
+ // RenderWidgetHost protected overrides.
+ virtual void OnUserGesture() OVERRIDE;
+ virtual void NotifyRendererUnresponsive() OVERRIDE;
+ virtual void NotifyRendererResponsive() OVERRIDE;
+ virtual void OnRenderAutoResized(const gfx::Size& size) OVERRIDE;
+ virtual void RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) OVERRIDE;
+ virtual bool IsFullscreen() const OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+
+ // IPC message handlers.
+ void OnShowView(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ void OnShowWidget(int route_id, const gfx::Rect& initial_pos);
+ void OnShowFullscreenWidget(int route_id);
+ void OnRunModal(int opener_id, IPC::Message* reply_msg);
+ void OnRenderViewReady();
+ void OnRenderProcessGone(int status, int error_code);
+ void OnDidStartProvisionalLoadForFrame(int64 frame_id,
+ int64 parent_frame_id,
+ bool main_frame,
+ const GURL& url);
+ void OnDidRedirectProvisionalLoad(int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url);
+ void OnDidFailProvisionalLoadWithError(
+ const ViewHostMsg_DidFailProvisionalLoadWithError_Params& params);
+ void OnNavigate(const IPC::Message& msg);
+ void OnUpdateState(int32 page_id, const PageState& state);
+ void OnUpdateTitle(int32 page_id,
+ const string16& title,
+ WebKit::WebTextDirection title_direction);
+ void OnUpdateEncoding(const std::string& encoding);
+ void OnUpdateTargetURL(int32 page_id, const GURL& url);
+ void OnClose();
+ void OnRequestMove(const gfx::Rect& pos);
+ void OnDidStartLoading();
+ void OnDidStopLoading();
+ void OnDidChangeLoadProgress(double load_progress);
+ void OnDidDisownOpener();
+ void OnDocumentAvailableInMainFrame();
+ void OnDocumentOnLoadCompletedInMainFrame(int32 page_id);
+ void OnContextMenu(const ContextMenuParams& params);
+ void OnToggleFullscreen(bool enter_fullscreen);
+ void OnOpenURL(const ViewHostMsg_OpenURL_Params& params);
+ void OnDidContentsPreferredSizeChange(const gfx::Size& new_size);
+ void OnDidChangeScrollOffset();
+ void OnDidChangeScrollbarsForMainFrame(bool has_horizontal_scrollbar,
+ bool has_vertical_scrollbar);
+ void OnDidChangeScrollOffsetPinningForMainFrame(bool is_pinned_to_left,
+ bool is_pinned_to_right);
+ void OnDidChangeNumWheelEvents(int count);
+ void OnSelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range);
+ void OnSelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params);
+ void OnPasteFromSelectionClipboard();
+ void OnRouteCloseEvent();
+ void OnRouteMessageEvent(const ViewMsg_PostMessage_Params& params);
+ void OnRunJavaScriptMessage(const string16& message,
+ const string16& default_prompt,
+ const GURL& frame_url,
+ JavaScriptMessageType type,
+ IPC::Message* reply_msg);
+ void OnRunBeforeUnloadConfirm(const GURL& frame_url,
+ const string16& message,
+ bool is_reload,
+ IPC::Message* reply_msg);
+ void OnStartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask operations_allowed,
+ const SkBitmap& bitmap,
+ const gfx::Vector2d& bitmap_offset_in_dip,
+ const DragEventSourceInfo& event_info);
+ void OnUpdateDragCursor(WebKit::WebDragOperation drag_operation);
+ void OnTargetDropACK();
+ void OnTakeFocus(bool reverse);
+ void OnFocusedNodeChanged(bool is_editable_node);
+ void OnAddMessageToConsole(int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id);
+ void OnUpdateInspectorSetting(const std::string& key,
+ const std::string& value);
+ void OnShouldCloseACK(
+ bool proceed,
+ const base::TimeTicks& renderer_before_unload_start_time,
+ const base::TimeTicks& renderer_before_unload_end_time);
+ void OnClosePageACK();
+ void OnSwapOutACK();
+ void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params);
+ void OnScriptEvalResponse(int id, const base::ListValue& result);
+ void OnDidZoomURL(double zoom_level, bool remember, const GURL& url);
+ void OnRequestDesktopNotificationPermission(const GURL& origin,
+ int callback_id);
+ void OnShowDesktopNotification(
+ const ShowDesktopNotificationHostMsgParams& params);
+ void OnCancelDesktopNotification(int notification_id);
+ void OnRunFileChooser(const FileChooserParams& params);
+ void OnDidAccessInitialDocument();
+ void OnDomOperationResponse(const std::string& json_string,
+ int automation_id);
+ void OnGetWindowSnapshot(const int snapshot_id);
+
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
+ void OnShowPopup(const ViewHostMsg_ShowPopup_Params& params);
+#endif
+
+ private:
+ friend class TestRenderViewHost;
+ FRIEND_TEST_ALL_PREFIXES(RenderViewHostTest, BasicRenderFrameHost);
+
+ // Sets whether this RenderViewHost is swapped out in favor of another,
+ // and clears any waiting state that is no longer relevant.
+ void SetSwappedOut(bool is_swapped_out);
+
+ bool CanAccessFilesOfPageState(const PageState& state) const;
+
+ // This is an RenderFrameHost object associated with the top-level frame in
+ // the page rendered by this RenderViewHost.
+ // TODO(nasko): Remove this pointer once we have enough infrastructure to
+ // move this to the top-level FrameTreeNode.
+ scoped_ptr<RenderFrameHostImpl> main_render_frame_host_;
+
+ // Our delegate, which wants to know about changes in the RenderView.
+ RenderViewHostDelegate* delegate_;
+
+ // The SiteInstance associated with this RenderViewHost. All pages drawn
+ // in this RenderViewHost are part of this SiteInstance. Should not change
+ // over time.
+ scoped_refptr<SiteInstanceImpl> instance_;
+
+ // true if we are currently waiting for a response for drag context
+ // information.
+ bool waiting_for_drag_context_response_;
+
+ // A bitwise OR of bindings types that have been enabled for this RenderView.
+ // See BindingsPolicy for details.
+ int enabled_bindings_;
+
+ // Whether we should buffer outgoing Navigate messages rather than sending
+ // them. This will be true when a RenderViewHost is created for a cross-site
+ // request, until we hear back from the onbeforeunload handler of the old
+ // RenderViewHost.
+ bool navigations_suspended_;
+
+ // We only buffer the params for a suspended navigation while we have a
+ // pending RVH for a WebContentsImpl. There will only ever be one suspended
+ // navigation, because WebContentsImpl will destroy the pending RVH and create
+ // a new one if a second navigation occurs.
+ scoped_ptr<ViewMsg_Navigate_Params> suspended_nav_params_;
+
+ // Whether the initial empty page of this view has been accessed by another
+ // page, making it unsafe to show the pending URL. Usually false unless
+ // another window tries to modify the blank page. Always false after the
+ // first commit.
+ bool has_accessed_initial_document_;
+
+ // Whether this RenderViewHost is currently swapped out, such that the view is
+ // being rendered by another process.
+ bool is_swapped_out_;
+
+ // Whether this RenderView is responsible for displaying a subframe in a
+ // different process from its parent page.
+ bool is_subframe_;
+
+ // The frame id of the main (top level) frame. This value is set on the
+ // initial navigation of a RenderView and reset when the RenderView's
+ // process is terminated (in RenderProcessGone).
+ int64 main_frame_id_;
+
+ // If we were asked to RunModal, then this will hold the reply_msg that we
+ // must return to the renderer to unblock it.
+ IPC::Message* run_modal_reply_msg_;
+ // This will hold the routing id of the RenderView that opened us.
+ int run_modal_opener_id_;
+
+ // Set to true when there is a pending ViewMsg_ShouldClose message. This
+ // ensures we don't spam the renderer with multiple beforeunload requests.
+ // When either this value or is_waiting_for_unload_ack_ is true, the value of
+ // unload_ack_is_for_cross_site_transition_ indicates whether this is for a
+ // cross-site transition or a tab close attempt.
+ bool is_waiting_for_beforeunload_ack_;
+
+ // Set to true when there is a pending ViewMsg_Close message. Also see
+ // is_waiting_for_beforeunload_ack_, unload_ack_is_for_cross_site_transition_.
+ bool is_waiting_for_unload_ack_;
+
+ // Set to true when waiting for ViewHostMsg_SwapOut_ACK has timed out.
+ bool has_timed_out_on_unload_;
+
+ // Valid only when is_waiting_for_beforeunload_ack_ or
+ // is_waiting_for_unload_ack_ is true. This tells us if the unload request
+ // is for closing the entire tab ( = false), or only this RenderViewHost in
+ // the case of a cross-site transition ( = true).
+ bool unload_ack_is_for_cross_site_transition_;
+
+ bool are_javascript_messages_suppressed_;
+
+ // The mapping of pending javascript calls created by
+ // ExecuteJavascriptInWebFrameCallbackResult and their corresponding
+ // callbacks.
+ std::map<int, JavascriptResultCallback> javascript_callbacks_;
+
+ // Accessibility callback for testing.
+ base::Callback<void(AccessibilityNotification)>
+ accessibility_testing_callback_;
+
+ // The most recently received accessibility tree - for testing only.
+ AccessibilityNodeDataTreeNode accessibility_tree_;
+
+ // True if the render view can be shut down suddenly.
+ bool sudden_termination_allowed_;
+
+ // The termination status of the last render view that terminated.
+ base::TerminationStatus render_view_termination_status_;
+
+ // A list of observers that filter messages. Weak references.
+ ObserverList<RenderViewHostObserver> observers_;
+
+ // When the last ShouldClose message was sent.
+ base::TimeTicks send_should_close_start_time_;
+
+#if defined(OS_ANDROID)
+ // Manages all the android mediaplayer objects and handling IPCs for video.
+ // This class inherits from RenderViewHostObserver.
+ media::MediaPlayerManager* media_player_manager_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostImpl);
+};
+
+#if defined(COMPILER_MSVC)
+#pragma warning(pop)
+#endif
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_IMPL_H_
diff --git a/chromium/content/browser/renderer_host/render_view_host_manager_browsertest.cc b/chromium/content/browser/renderer_host/render_view_host_manager_browsertest.cc
new file mode 100644
index 00000000000..f5ebb76fc0a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_manager_browsertest.cc
@@ -0,0 +1,1299 @@
+// 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 "base/json/json_reader.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/content_constants_internal.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/base/net_util.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+namespace content {
+
+class RenderViewHostManagerTest : public ContentBrowserTest {
+ public:
+ RenderViewHostManagerTest() {}
+
+ static bool GetFilePathWithHostAndPortReplacement(
+ const std::string& original_file_path,
+ const net::HostPortPair& host_port_pair,
+ std::string* replacement_path) {
+ std::vector<net::SpawnedTestServer::StringPair> replacement_text;
+ replacement_text.push_back(
+ make_pair("REPLACE_WITH_HOST_AND_PORT", host_port_pair.ToString()));
+ return net::SpawnedTestServer::GetFilePathWithReplacements(
+ original_file_path, replacement_text, replacement_path);
+ }
+};
+
+// Web pages should not have script access to the swapped out page.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ DISABLED_NoScriptAccessAfterSwapOut) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Open a same-site link in a new window.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new window to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, blank_site_instance);
+
+ // We should have access to the opened window's location.
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(testScriptAccessToWindow());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Now navigate the new window to a different site.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // We should no longer have script access to the opened window's location.
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(testScriptAccessToWindow());",
+ &success));
+ EXPECT_FALSE(success);
+}
+
+// Test for crbug.com/24447. Following a cross-site link with rel=noreferrer
+// and target=_blank should create a new SiteInstance.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ SwapProcessWithRelNoreferrerAndTargetBlank) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a rel=noreferrer + target=blank link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickNoRefTargetBlankLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the window to open.
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ EXPECT_EQ("/files/title2.html",
+ new_shell->web_contents()->GetVisibleURL().path());
+
+ // Wait for the cross-site transition in the new tab to finish.
+ WaitForLoadStop(new_shell->web_contents());
+ WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
+ new_shell->web_contents());
+ EXPECT_FALSE(web_contents->GetRenderManagerForTesting()->
+ pending_render_view_host());
+
+ // Should have a new SiteInstance.
+ scoped_refptr<SiteInstance> noref_blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, noref_blank_site_instance);
+}
+
+// As of crbug.com/69267, we create a new BrowsingInstance (and SiteInstance)
+// for rel=noreferrer links in new windows, even to same site pages and named
+// targets.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ SwapProcessWithSameSiteRelNoreferrer) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a same-site rel=noreferrer + target=foo link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteNoRefTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the window to open.
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Opens in new window.
+ EXPECT_EQ("/files/title2.html",
+ new_shell->web_contents()->GetVisibleURL().path());
+
+ // Wait for the cross-site transition in the new tab to finish.
+ WaitForLoadStop(new_shell->web_contents());
+ WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
+ new_shell->web_contents());
+ EXPECT_FALSE(web_contents->GetRenderManagerForTesting()->
+ pending_render_view_host());
+
+ // Should have a new SiteInstance (in a new BrowsingInstance).
+ scoped_refptr<SiteInstance> noref_blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, noref_blank_site_instance);
+}
+
+// Test for crbug.com/24447. Following a cross-site link with just
+// target=_blank should not create a new SiteInstance.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ DontSwapProcessWithOnlyTargetBlank) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a target=blank link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickTargetBlankLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the window to open.
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the cross-site transition in the new tab to finish.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/title2.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, blank_site_instance);
+}
+
+// Test for crbug.com/24447. Following a cross-site link with rel=noreferrer
+// and no target=_blank should not create a new SiteInstance.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ DontSwapProcessWithOnlyRelNoreferrer) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a rel=noreferrer link.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickNoRefLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the cross-site transition in the current tab to finish.
+ WaitForLoadStop(shell()->web_contents());
+
+ // Opens in same window.
+ EXPECT_EQ(1u, Shell::windows().size());
+ EXPECT_EQ("/files/title2.html",
+ shell()->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> noref_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, noref_site_instance);
+}
+
+namespace {
+
+class WebContentsDestroyedObserver : public WebContentsObserver {
+ public:
+ WebContentsDestroyedObserver(WebContents* web_contents,
+ const base::Closure& callback)
+ : WebContentsObserver(web_contents),
+ callback_(callback) {
+ }
+
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE {
+ callback_.Run();
+ }
+
+ private:
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsDestroyedObserver);
+};
+
+} // namespace
+
+// Test for crbug.com/116192. Targeted links should still work after the
+// named target window has swapped processes.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ AllowTargetedNavigationsAfterSwap) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a target=foo link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new tab to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, blank_site_instance);
+
+ // Now navigate the new tab to a different site.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // Clicking the original link in the first tab should cause us to swap back.
+ TestNavigationObserver navigation_observer(new_shell->web_contents());
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ navigation_observer.Wait();
+
+ // Should have swapped back and shown the new window again.
+ scoped_refptr<SiteInstance> revisit_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, revisit_site_instance);
+
+ // If it navigates away to another process, the original window should
+ // still be able to close it (using a cross-process close message).
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ EXPECT_EQ(new_site_instance,
+ new_shell->web_contents()->GetSiteInstance());
+ scoped_refptr<MessageLoopRunner> loop_runner(new MessageLoopRunner);
+ WebContentsDestroyedObserver close_observer(new_shell->web_contents(),
+ loop_runner->QuitClosure());
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(testCloseWindow());",
+ &success));
+ EXPECT_TRUE(success);
+ loop_runner->Run();
+}
+
+// Test that setting the opener to null in a window affects cross-process
+// navigations, including those to existing entries. http://crbug.com/156669.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, DisownOpener) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a target=_blank link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetBlankLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new tab to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/title2.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, blank_site_instance);
+
+ // Now navigate the new tab to a different site.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // Now disown the opener.
+ EXPECT_TRUE(ExecuteScript(new_shell->web_contents(),
+ "window.opener = null;"));
+
+ // Go back and ensure the opener is still null.
+ {
+ TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
+ new_shell->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ new_shell->web_contents(),
+ "window.domAutomationController.send(window.opener == null);",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Now navigate forward again (creating a new process) and check opener.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ new_shell->web_contents(),
+ "window.domAutomationController.send(window.opener == null);",
+ &success));
+ EXPECT_TRUE(success);
+}
+
+// Test that subframes can disown their openers. http://crbug.com/225528.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, DisownSubframeOpener) {
+ const GURL frame_url("data:text/html,<iframe name=\"foo\"></iframe>");
+ NavigateToURL(shell(), frame_url);
+
+ // Give the frame an opener using window.open.
+ EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
+ "window.open('about:blank','foo');"));
+
+ // Now disown the frame's opener. Shouldn't crash.
+ EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
+ "window.frames[0].opener = null;"));
+}
+
+// Test for crbug.com/99202. PostMessage calls should still work after
+// navigating the source and target windows to different sites.
+// Specifically:
+// 1) Create 3 windows (opener, "foo", and _blank) and send "foo" cross-process.
+// 2) Fail to post a message from "foo" to opener with the wrong target origin.
+// 3) Post a message from "foo" to opener, which replies back to "foo".
+// 4) Post a message from _blank to "foo".
+// 5) Post a message from "foo" to a subframe of opener, which replies back.
+// 6) Post a message from _blank to a subframe of "foo".
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ SupportCrossProcessPostMessage) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance and RVHM for later comparison.
+ WebContents* opener_contents = shell()->web_contents();
+ scoped_refptr<SiteInstance> orig_site_instance(
+ opener_contents->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+ RenderViewHostManager* opener_manager = static_cast<WebContentsImpl*>(
+ opener_contents)->GetRenderManagerForTesting();
+
+ // 1) Open two more windows, one named. These initially have openers but no
+ // reference to each other. We will later post a message between them.
+
+ // First, a named target=foo window.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ opener_contents,
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new window to finish, if it hasn't, then
+ // send it to post_message.html on a different site.
+ WebContents* foo_contents = new_shell->web_contents();
+ WaitForLoadStop(foo_contents);
+ EXPECT_EQ("/files/navigate_opener.html",
+ foo_contents->GetLastCommittedURL().path());
+ NavigateToURL(new_shell, https_server.GetURL("files/post_message.html"));
+ scoped_refptr<SiteInstance> foo_site_instance(
+ foo_contents->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, foo_site_instance);
+
+ // Second, a target=_blank window.
+ ShellAddedObserver new_shell_observer2;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetBlankLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the navigation in the new window to finish, if it hasn't, then
+ // send it to post_message.html on the original site.
+ Shell* new_shell2 = new_shell_observer2.GetShell();
+ WebContents* new_contents = new_shell2->web_contents();
+ WaitForLoadStop(new_contents);
+ EXPECT_EQ("/files/title2.html", new_contents->GetLastCommittedURL().path());
+ NavigateToURL(new_shell2, test_server()->GetURL("files/post_message.html"));
+ EXPECT_EQ(orig_site_instance, new_contents->GetSiteInstance());
+ RenderViewHostManager* new_manager =
+ static_cast<WebContentsImpl*>(new_contents)->GetRenderManagerForTesting();
+
+ // We now have three windows. The opener should have a swapped out RVH
+ // for the new SiteInstance, but the _blank window should not.
+ EXPECT_EQ(3u, Shell::windows().size());
+ EXPECT_TRUE(
+ opener_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
+ EXPECT_FALSE(
+ new_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
+
+ // 2) Fail to post a message from the foo window to the opener if the target
+ // origin is wrong. We won't see an error, but we can check for the right
+ // number of received messages below.
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ foo_contents,
+ "window.domAutomationController.send(postToOpener('msg',"
+ " 'http://google.com'));",
+ &success));
+ EXPECT_TRUE(success);
+ ASSERT_FALSE(
+ opener_manager->GetSwappedOutRenderViewHost(orig_site_instance.get()));
+
+ // 3) Post a message from the foo window to the opener. The opener will
+ // reply, causing the foo window to update its own title.
+ WindowedNotificationObserver title_observer(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(foo_contents));
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ foo_contents,
+ "window.domAutomationController.send(postToOpener('msg','*'));",
+ &success));
+ EXPECT_TRUE(success);
+ ASSERT_FALSE(
+ opener_manager->GetSwappedOutRenderViewHost(orig_site_instance.get()));
+ title_observer.Wait();
+
+ // We should have received only 1 message in the opener and "foo" tabs,
+ // and updated the title.
+ int opener_received_messages = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ opener_contents,
+ "window.domAutomationController.send(window.receivedMessages);",
+ &opener_received_messages));
+ int foo_received_messages = 0;
+ EXPECT_TRUE(ExecuteScriptAndExtractInt(
+ foo_contents,
+ "window.domAutomationController.send(window.receivedMessages);",
+ &foo_received_messages));
+ EXPECT_EQ(1, foo_received_messages);
+ EXPECT_EQ(1, opener_received_messages);
+ EXPECT_EQ(ASCIIToUTF16("msg"), foo_contents->GetTitle());
+
+ // 4) Now post a message from the _blank window to the foo window. The
+ // foo window will update its title and will not reply.
+ WindowedNotificationObserver title_observer2(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(foo_contents));
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ new_contents,
+ "window.domAutomationController.send(postToFoo('msg2'));",
+ &success));
+ EXPECT_TRUE(success);
+ title_observer2.Wait();
+ EXPECT_EQ(ASCIIToUTF16("msg2"), foo_contents->GetTitle());
+
+ // This postMessage should have created a swapped out RVH for the new
+ // SiteInstance in the target=_blank window.
+ EXPECT_TRUE(
+ new_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
+
+ // TODO(nasko): Test subframe targeting of postMessage once
+ // http://crbug.com/153701 is fixed.
+}
+
+// Test for crbug.com/116192. Navigations to a window's opener should
+// still work after a process swap.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ AllowTargetedNavigationsInOpenerAfterSwap) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original tab and SiteInstance for later comparison.
+ WebContents* orig_contents = shell()->web_contents();
+ scoped_refptr<SiteInstance> orig_site_instance(
+ orig_contents->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a target=foo link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ orig_contents,
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new window to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> blank_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, blank_site_instance);
+
+ // Now navigate the original (opener) tab to a different site.
+ NavigateToURL(shell(), https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // The opened tab should be able to navigate the opener back to its process.
+ TestNavigationObserver navigation_observer(orig_contents);
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ new_shell->web_contents(),
+ "window.domAutomationController.send(navigateOpener());",
+ &success));
+ EXPECT_TRUE(success);
+ navigation_observer.Wait();
+
+ // Should have swapped back into this process.
+ scoped_refptr<SiteInstance> revisit_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, revisit_site_instance);
+}
+
+// Test that opening a new window in the same SiteInstance and then navigating
+// both windows to a different SiteInstance allows the first process to exit.
+// See http://crbug.com/126333.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ ProcessExitWithSwappedOutViews) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Test clicking a target=foo link.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new window to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> opened_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, opened_site_instance);
+
+ // Now navigate the opened window to a different site.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // The original process should still be alive, since it is still used in the
+ // first window.
+ RenderProcessHost* orig_process = orig_site_instance->GetProcess();
+ EXPECT_TRUE(orig_process->HasConnection());
+
+ // Navigate the first window to a different site as well. The original
+ // process should exit, since all of its views are now swapped out.
+ WindowedNotificationObserver exit_observer(
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ Source<RenderProcessHost>(orig_process));
+ NavigateToURL(shell(), https_server.GetURL("files/title1.html"));
+ exit_observer.Wait();
+ scoped_refptr<SiteInstance> new_site_instance2(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_EQ(new_site_instance, new_site_instance2);
+}
+
+// Test for crbug.com/76666. A cross-site navigation that fails with a 204
+// error should not make us ignore future renderer-initiated navigations.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, ClickLinkAfter204Error) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ // The links will point to the HTTPS server.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance.get() != NULL);
+
+ // Load a cross-site page that fails with a 204 error.
+ NavigateToURL(shell(), https_server.GetURL("nocontent"));
+
+ // We should still be looking at the normal page. The typed URL will
+ // still be visible until the user clears it manually, but the last
+ // committed URL will be the previous page.
+ scoped_refptr<SiteInstance> post_nav_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, post_nav_site_instance);
+ EXPECT_EQ("/nocontent",
+ shell()->web_contents()->GetVisibleURL().path());
+ EXPECT_EQ("/files/click-noreferrer-links.html",
+ shell()->web_contents()->GetController().
+ GetLastCommittedEntry()->GetVirtualURL().path());
+
+ // Renderer-initiated navigations should work.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickNoRefLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the cross-site transition in the current tab to finish.
+ WaitForLoadStop(shell()->web_contents());
+
+ // Opens in same tab.
+ EXPECT_EQ(1u, Shell::windows().size());
+ EXPECT_EQ("/files/title2.html",
+ shell()->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ scoped_refptr<SiteInstance> noref_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+ EXPECT_EQ(orig_site_instance, noref_site_instance);
+}
+
+// Test for crbug.com/9682. We should show the URL for a pending renderer-
+// initiated navigation in a new tab, until the content of the initial
+// about:blank page is modified by another window. At that point, we should
+// revert to showing about:blank to prevent a URL spoof.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, ShowLoadingURLUntilSpoof) {
+ ASSERT_TRUE(test_server()->Start());
+
+ // Load a page that can open a URL that won't commit in a new window.
+ NavigateToURL(
+ shell(), test_server()->GetURL("files/click-nocontent-link.html"));
+ WebContents* orig_contents = shell()->web_contents();
+
+ // Click a /nocontent link that opens in a new window but never commits.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ orig_contents,
+ "window.domAutomationController.send(clickNoContentTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the window to open.
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Ensure the destination URL is visible, because it is considered the
+ // initial navigation.
+ WebContents* contents = new_shell->web_contents();
+ EXPECT_TRUE(contents->GetController().IsInitialNavigation());
+ EXPECT_EQ("/nocontent",
+ contents->GetController().GetVisibleEntry()->GetURL().path());
+
+ // Now modify the contents of the new window from the opener. This will also
+ // modify the title of the document to give us something to listen for.
+ WindowedNotificationObserver title_observer(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(contents));
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ orig_contents,
+ "window.domAutomationController.send(modifyNewWindow());",
+ &success));
+ EXPECT_TRUE(success);
+ title_observer.Wait();
+ EXPECT_EQ(ASCIIToUTF16("Modified Title"), contents->GetTitle());
+
+ // At this point, we should no longer be showing the destination URL.
+ // The visible entry should be null, resulting in about:blank in the address
+ // bar.
+ EXPECT_FALSE(contents->GetController().GetVisibleEntry());
+}
+
+// Test for crbug.com/9682. We should not show the URL for a pending renderer-
+// initiated navigation in a new tab if it is not the initial navigation. In
+// this case, the renderer will not notify us of a modification, so we cannot
+// show the pending URL without allowing a spoof.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ DontShowLoadingURLIfNotInitialNav) {
+ ASSERT_TRUE(test_server()->Start());
+
+ // Load a page that can open a URL that won't commit in a new window.
+ NavigateToURL(
+ shell(), test_server()->GetURL("files/click-nocontent-link.html"));
+ WebContents* orig_contents = shell()->web_contents();
+
+ // Click a /nocontent link that opens in a new window but never commits.
+ // By using an onclick handler that first creates the window, the slow
+ // navigation is not considered an initial navigation.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ orig_contents,
+ "window.domAutomationController.send("
+ "clickNoContentScriptedTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Wait for the window to open.
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Ensure the destination URL is not visible, because it is not the initial
+ // navigation.
+ WebContents* contents = new_shell->web_contents();
+ EXPECT_FALSE(contents->GetController().IsInitialNavigation());
+ EXPECT_FALSE(contents->GetController().GetVisibleEntry());
+}
+
+// Test for http://crbug.com/93427. Ensure that cross-site navigations
+// do not cause back/forward navigations to be considered stale by the
+// renderer.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, BackForwardNotStale) {
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Visit a page on first site.
+ std::string replacement_path_a1;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/title1.html",
+ test_server()->host_port_pair(),
+ &replacement_path_a1));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path_a1));
+
+ // Visit three pages on second site.
+ std::string replacement_path_b1;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/title1.html",
+ https_server.host_port_pair(),
+ &replacement_path_b1));
+ NavigateToURL(shell(), https_server.GetURL(replacement_path_b1));
+ std::string replacement_path_b2;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/title2.html",
+ https_server.host_port_pair(),
+ &replacement_path_b2));
+ NavigateToURL(shell(), https_server.GetURL(replacement_path_b2));
+ std::string replacement_path_b3;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/title3.html",
+ https_server.host_port_pair(),
+ &replacement_path_b3));
+ NavigateToURL(shell(), https_server.GetURL(replacement_path_b3));
+
+ // History is now [blank, A1, B1, B2, *B3].
+ WebContents* contents = shell()->web_contents();
+ EXPECT_EQ(5, contents->GetController().GetEntryCount());
+
+ // Open another window in same process to keep this process alive.
+ Shell* new_shell = CreateBrowser();
+ NavigateToURL(new_shell, https_server.GetURL(replacement_path_b1));
+
+ // Go back three times to first site.
+ {
+ TestNavigationObserver back_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+ {
+ TestNavigationObserver back_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+ {
+ TestNavigationObserver back_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+
+ // Now go forward twice to B2. Shouldn't be left spinning.
+ {
+ TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoForward();
+ forward_nav_load_observer.Wait();
+ }
+ {
+ TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoForward();
+ forward_nav_load_observer.Wait();
+ }
+
+ // Go back twice to first site.
+ {
+ TestNavigationObserver back_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+ {
+ TestNavigationObserver back_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+
+ // Now go forward directly to B3. Shouldn't be left spinning.
+ {
+ TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoToIndex(4);
+ forward_nav_load_observer.Wait();
+ }
+}
+
+// Test for http://crbug.com/130016.
+// Swapping out a render view should update its visiblity state.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ SwappedOutViewHasCorrectVisibilityState) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Load a page with links that open in a new window.
+ std::string replacement_path;
+ ASSERT_TRUE(GetFilePathWithHostAndPortReplacement(
+ "files/click-noreferrer-links.html",
+ https_server.host_port_pair(),
+ &replacement_path));
+ NavigateToURL(shell(), test_server()->GetURL(replacement_path));
+
+ // Open a same-site link in a new widnow.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(clickSameSiteTargetedLink());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new tab to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ RenderViewHost* rvh = new_shell->web_contents()->GetRenderViewHost();
+
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ rvh,
+ "window.domAutomationController.send("
+ " document.webkitVisibilityState == 'visible');",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Now navigate the new window to a different site. This should swap out the
+ // tab's existing RenderView, causing it become hidden.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ rvh,
+ "window.domAutomationController.send("
+ " document.webkitVisibilityState == 'hidden');",
+ &success));
+ EXPECT_TRUE(success);
+
+ // Going back should make the previously swapped-out view to become visible
+ // again.
+ {
+ TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
+ new_shell->web_contents()->GetController().GoBack();
+ back_nav_load_observer.Wait();
+ }
+
+ EXPECT_EQ("/files/navigate_opener.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ EXPECT_EQ(rvh, new_shell->web_contents()->GetRenderViewHost());
+
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ rvh,
+ "window.domAutomationController.send("
+ " document.webkitVisibilityState == 'visible');",
+ &success));
+ EXPECT_TRUE(success);
+}
+
+// This class holds onto RenderViewHostObservers for as long as their observed
+// RenderViewHosts are alive. This allows us to confirm that all hosts have
+// properly been shutdown.
+class RenderViewHostObserverArray {
+ public:
+ ~RenderViewHostObserverArray() {
+ // In case some would be left in there with a dead pointer to us.
+ for (std::list<RVHObserver*>::iterator iter = observers_.begin();
+ iter != observers_.end(); ++iter) {
+ (*iter)->ClearParent();
+ }
+ }
+ void AddObserverToRVH(RenderViewHost* rvh) {
+ observers_.push_back(new RVHObserver(this, rvh));
+ }
+ size_t GetNumObservers() const {
+ return observers_.size();
+ }
+
+ private:
+ friend class RVHObserver;
+ class RVHObserver : public RenderViewHostObserver {
+ public:
+ RVHObserver(RenderViewHostObserverArray* parent, RenderViewHost* rvh)
+ : RenderViewHostObserver(rvh),
+ parent_(parent) {
+ }
+ virtual void RenderViewHostDestroyed(RenderViewHost* rvh) OVERRIDE {
+ if (parent_)
+ parent_->RemoveObserver(this);
+ RenderViewHostObserver::RenderViewHostDestroyed(rvh);
+ };
+ void ClearParent() {
+ parent_ = NULL;
+ }
+ private:
+ RenderViewHostObserverArray* parent_;
+ };
+
+ void RemoveObserver(RVHObserver* observer) {
+ observers_.remove(observer);
+ }
+
+ std::list<RVHObserver*> observers_;
+};
+
+// Test for crbug.com/90867. Make sure we don't leak render view hosts since
+// they may cause crashes or memory corruptions when trying to call dead
+// delegate_. This test also verifies crbug.com/117420 and crbug.com/143255 to
+// ensure that a separate SiteInstance is created when navigating to view-source
+// URLs, regardless of current URL.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, LeakingRenderViewHosts) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // Observe the created render_view_host's to make sure they will not leak.
+ RenderViewHostObserverArray rvh_observers;
+
+ GURL navigated_url(test_server()->GetURL("files/title2.html"));
+ GURL view_source_url(kViewSourceScheme + std::string(":") +
+ navigated_url.spec());
+
+ // Let's ensure that when we start with a blank window, navigating away to a
+ // view-source URL, we create a new SiteInstance.
+ RenderViewHost* blank_rvh = shell()->web_contents()->GetRenderViewHost();
+ SiteInstance* blank_site_instance = blank_rvh->GetSiteInstance();
+ EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), GURL::EmptyGURL());
+ EXPECT_EQ(blank_site_instance->GetSiteURL(), GURL::EmptyGURL());
+ rvh_observers.AddObserverToRVH(blank_rvh);
+
+ // Now navigate to the view-source URL and ensure we got a different
+ // SiteInstance and RenderViewHost.
+ NavigateToURL(shell(), view_source_url);
+ EXPECT_NE(blank_rvh, shell()->web_contents()->GetRenderViewHost());
+ EXPECT_NE(blank_site_instance, shell()->web_contents()->
+ GetRenderViewHost()->GetSiteInstance());
+ rvh_observers.AddObserverToRVH(shell()->web_contents()->GetRenderViewHost());
+
+ // Load a random page and then navigate to view-source: of it.
+ // This used to cause two RVH instances for the same SiteInstance, which
+ // was a problem. This is no longer the case.
+ NavigateToURL(shell(), navigated_url);
+ SiteInstance* site_instance1 = shell()->web_contents()->
+ GetRenderViewHost()->GetSiteInstance();
+ rvh_observers.AddObserverToRVH(shell()->web_contents()->GetRenderViewHost());
+
+ NavigateToURL(shell(), view_source_url);
+ rvh_observers.AddObserverToRVH(shell()->web_contents()->GetRenderViewHost());
+ SiteInstance* site_instance2 = shell()->web_contents()->
+ GetRenderViewHost()->GetSiteInstance();
+
+ // Ensure that view-source navigations force a new SiteInstance.
+ EXPECT_NE(site_instance1, site_instance2);
+
+ // Now navigate to a different instance so that we swap out again.
+ NavigateToURL(shell(), https_server.GetURL("files/title2.html"));
+ rvh_observers.AddObserverToRVH(shell()->web_contents()->GetRenderViewHost());
+
+ // This used to leak a render view host.
+ shell()->Close();
+
+ RunAllPendingInMessageLoop(); // Needed on ChromeOS.
+
+ EXPECT_EQ(0U, rvh_observers.GetNumObservers());
+}
+
+// Test for crbug.com/143155. Frame tree updates during unload should not
+// interrupt the intended navigation and show swappedout:// instead.
+// Specifically:
+// 1) Open 2 tabs in an HTTP SiteInstance, with a subframe in the opener.
+// 2) Send the second tab to a different HTTPS SiteInstance.
+// This creates a swapped out opener for the first tab in the HTTPS process.
+// 3) Navigate the first tab to the HTTPS SiteInstance, and have the first
+// tab's unload handler remove its frame.
+// This used to cause an update to the frame tree of the swapped out RV,
+// just as it was navigating to a real page. That pre-empted the real
+// navigation and visibly sent the tab to swappedout://.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
+ DontPreemptNavigationWithFrameTreeUpdate) {
+ // Start two servers with different sites.
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ // 1. Load a page that deletes its iframe during unload.
+ NavigateToURL(shell(),
+ test_server()->GetURL("files/remove_frame_on_unload.html"));
+
+ // Get the original SiteInstance for later comparison.
+ scoped_refptr<SiteInstance> orig_site_instance(
+ shell()->web_contents()->GetSiteInstance());
+
+ // Open a same-site page in a new window.
+ ShellAddedObserver new_shell_observer;
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(openWindow());",
+ &success));
+ EXPECT_TRUE(success);
+ Shell* new_shell = new_shell_observer.GetShell();
+
+ // Wait for the navigation in the new window to finish, if it hasn't.
+ WaitForLoadStop(new_shell->web_contents());
+ EXPECT_EQ("/files/title1.html",
+ new_shell->web_contents()->GetLastCommittedURL().path());
+
+ // Should have the same SiteInstance.
+ EXPECT_EQ(orig_site_instance, new_shell->web_contents()->GetSiteInstance());
+
+ // 2. Send the second tab to a different process.
+ NavigateToURL(new_shell, https_server.GetURL("files/title1.html"));
+ scoped_refptr<SiteInstance> new_site_instance(
+ new_shell->web_contents()->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, new_site_instance);
+
+ // 3. Send the first tab to the second tab's process.
+ NavigateToURL(shell(), https_server.GetURL("files/title1.html"));
+
+ // Make sure it ends up at the right page.
+ WaitForLoadStop(shell()->web_contents());
+ EXPECT_EQ(https_server.GetURL("files/title1.html"),
+ shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_EQ(new_site_instance, shell()->web_contents()->GetSiteInstance());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_view_host_unittest.cc b/chromium/content/browser/renderer_host/render_view_host_unittest.cc
new file mode 100644
index 00000000000..a38e0446f3d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_view_host_unittest.cc
@@ -0,0 +1,291 @@
+// 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 "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_web_contents.h"
+#include "net/base/net_util.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+
+namespace content {
+
+class RenderViewHostTestBrowserClient : public TestContentBrowserClient {
+ public:
+ RenderViewHostTestBrowserClient() {}
+ virtual ~RenderViewHostTestBrowserClient() {}
+
+ virtual bool IsHandledURL(const GURL& url) OVERRIDE {
+ return url.scheme() == chrome::kFileScheme;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostTestBrowserClient);
+};
+
+class RenderViewHostTest : public RenderViewHostImplTestHarness {
+ public:
+ RenderViewHostTest() : old_browser_client_(NULL) {}
+ virtual ~RenderViewHostTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ RenderViewHostImplTestHarness::SetUp();
+ old_browser_client_ = SetBrowserClientForTesting(&test_browser_client_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ SetBrowserClientForTesting(old_browser_client_);
+ RenderViewHostImplTestHarness::TearDown();
+ }
+
+ private:
+ RenderViewHostTestBrowserClient test_browser_client_;
+ ContentBrowserClient* old_browser_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostTest);
+};
+
+// All about URLs reported by the renderer should get rewritten to about:blank.
+// See RenderViewHost::OnNavigate for a discussion.
+TEST_F(RenderViewHostTest, FilterAbout) {
+ test_rvh()->SendNavigate(1, GURL("about:cache"));
+ ASSERT_TRUE(controller().GetActiveEntry());
+ EXPECT_EQ(GURL(kAboutBlankURL), controller().GetActiveEntry()->GetURL());
+}
+
+// Create a full screen popup RenderWidgetHost and View.
+TEST_F(RenderViewHostTest, CreateFullscreenWidget) {
+ int routing_id = process()->GetNextRoutingID();
+ test_rvh()->CreateNewFullscreenWidget(routing_id);
+}
+
+// Makes sure that RenderViewHost::is_waiting_for_unload_ack_ is false when
+// reloading a page. If is_waiting_for_unload_ack_ is not false when reloading
+// the contents may get closed out even though the user pressed the reload
+// button.
+TEST_F(RenderViewHostTest, ResetUnloadOnReload) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ // This test is for a subtle timing bug. Here's the sequence that triggered
+ // the bug:
+ // . go to a page.
+ // . go to a new page, preferably one that takes a while to resolve, such
+ // as one on a site that doesn't exist.
+ // . After this step is_waiting_for_unload_ack_ has been set to true on
+ // the first RVH.
+ // . click stop before the page has been commited.
+ // . click reload.
+ // . is_waiting_for_unload_ack_ is still true, and the if the hang monitor
+ // fires the contents gets closed.
+
+ NavigateAndCommit(url1);
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ // Simulate the ClosePage call which is normally sent by the net::URLRequest.
+ rvh()->ClosePage();
+ // Needed so that navigations are not suspended on the RVH.
+ test_rvh()->SendShouldCloseACK(true);
+ contents()->Stop();
+ controller().Reload(false);
+ EXPECT_FALSE(test_rvh()->is_waiting_for_unload_ack());
+}
+
+// Ensure we do not grant bindings to a process shared with unprivileged views.
+TEST_F(RenderViewHostTest, DontGrantBindingsToSharedProcess) {
+ // Create another view in the same process.
+ scoped_ptr<TestWebContents> new_web_contents(
+ TestWebContents::Create(browser_context(), rvh()->GetSiteInstance()));
+
+ rvh()->AllowBindings(BINDINGS_POLICY_WEB_UI);
+ EXPECT_FALSE(rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+}
+
+class MockDraggingRenderViewHostDelegateView
+ : public RenderViewHostDelegateView {
+ public:
+ virtual ~MockDraggingRenderViewHostDelegateView() {}
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE {}
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE {
+ drag_url_ = drop_data.url;
+ html_base_url_ = drop_data.html_base_url;
+ }
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE {}
+ virtual void GotFocus() OVERRIDE {}
+ virtual void TakeFocus(bool reverse) OVERRIDE {}
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
+
+ GURL drag_url() {
+ return drag_url_;
+ }
+
+ GURL html_base_url() {
+ return html_base_url_;
+ }
+
+ private:
+ GURL drag_url_;
+ GURL html_base_url_;
+};
+
+TEST_F(RenderViewHostTest, StartDragging) {
+ TestWebContents* web_contents = contents();
+ MockDraggingRenderViewHostDelegateView delegate_view;
+ web_contents->set_delegate_view(&delegate_view);
+
+ DropData drop_data;
+ GURL file_url = GURL("file:///home/user/secrets.txt");
+ drop_data.url = file_url;
+ drop_data.html_base_url = file_url;
+ test_rvh()->TestOnStartDragging(drop_data);
+ EXPECT_EQ(GURL(kAboutBlankURL), delegate_view.drag_url());
+ EXPECT_EQ(GURL(kAboutBlankURL), delegate_view.html_base_url());
+
+ GURL http_url = GURL("http://www.domain.com/index.html");
+ drop_data.url = http_url;
+ drop_data.html_base_url = http_url;
+ test_rvh()->TestOnStartDragging(drop_data);
+ EXPECT_EQ(http_url, delegate_view.drag_url());
+ EXPECT_EQ(http_url, delegate_view.html_base_url());
+
+ GURL https_url = GURL("https://www.domain.com/index.html");
+ drop_data.url = https_url;
+ drop_data.html_base_url = https_url;
+ test_rvh()->TestOnStartDragging(drop_data);
+ EXPECT_EQ(https_url, delegate_view.drag_url());
+ EXPECT_EQ(https_url, delegate_view.html_base_url());
+
+ GURL javascript_url = GURL("javascript:alert('I am a bookmarklet')");
+ drop_data.url = javascript_url;
+ drop_data.html_base_url = http_url;
+ test_rvh()->TestOnStartDragging(drop_data);
+ EXPECT_EQ(javascript_url, delegate_view.drag_url());
+ EXPECT_EQ(http_url, delegate_view.html_base_url());
+}
+
+TEST_F(RenderViewHostTest, DragEnteredFileURLsStillBlocked) {
+ DropData dropped_data;
+ gfx::Point client_point;
+ gfx::Point screen_point;
+ // We use "//foo/bar" path (rather than "/foo/bar") since dragged paths are
+ // expected to be absolute on any platforms.
+ base::FilePath highlighted_file_path(FILE_PATH_LITERAL("//tmp/foo.html"));
+ base::FilePath dragged_file_path(FILE_PATH_LITERAL("//tmp/image.jpg"));
+ base::FilePath sensitive_file_path(FILE_PATH_LITERAL("//etc/passwd"));
+ GURL highlighted_file_url = net::FilePathToFileURL(highlighted_file_path);
+ GURL dragged_file_url = net::FilePathToFileURL(dragged_file_path);
+ GURL sensitive_file_url = net::FilePathToFileURL(sensitive_file_path);
+ dropped_data.url = highlighted_file_url;
+ dropped_data.filenames.push_back(DropData::FileInfo(
+ UTF8ToUTF16(dragged_file_path.AsUTF8Unsafe()), string16()));
+
+ rvh()->DragTargetDragEnter(dropped_data, client_point, screen_point,
+ WebKit::WebDragOperationNone, 0);
+
+ int id = process()->GetID();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ EXPECT_FALSE(policy->CanRequestURL(id, highlighted_file_url));
+ EXPECT_FALSE(policy->CanReadFile(id, highlighted_file_path));
+ EXPECT_TRUE(policy->CanRequestURL(id, dragged_file_url));
+ EXPECT_TRUE(policy->CanReadFile(id, dragged_file_path));
+ EXPECT_FALSE(policy->CanRequestURL(id, sensitive_file_url));
+ EXPECT_FALSE(policy->CanReadFile(id, sensitive_file_path));
+}
+
+// The test that follow trigger DCHECKS in debug build.
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+
+// Test that when we fail to de-serialize a message, RenderViewHost calls the
+// ReceivedBadMessage() handler.
+TEST_F(RenderViewHostTest, BadMessageHandlerRenderViewHost) {
+ EXPECT_EQ(0, process()->bad_msg_count());
+ // craft an incorrect ViewHostMsg_UpdateTargetURL message. The real one has
+ // two payload items but the one we construct has none.
+ IPC::Message message(0, ViewHostMsg_UpdateTargetURL::ID,
+ IPC::Message::PRIORITY_NORMAL);
+ test_rvh()->OnMessageReceived(message);
+ EXPECT_EQ(1, process()->bad_msg_count());
+}
+
+// Test that when we fail to de-serialize a message, RenderWidgetHost calls the
+// ReceivedBadMessage() handler.
+TEST_F(RenderViewHostTest, BadMessageHandlerRenderWidgetHost) {
+ EXPECT_EQ(0, process()->bad_msg_count());
+ // craft an incorrect ViewHostMsg_UpdateRect message. The real one has
+ // one payload item but the one we construct has none.
+ IPC::Message message(0, ViewHostMsg_UpdateRect::ID,
+ IPC::Message::PRIORITY_NORMAL);
+ test_rvh()->OnMessageReceived(message);
+ EXPECT_EQ(1, process()->bad_msg_count());
+}
+
+// Test that OnInputEventAck() detects bad messages.
+TEST_F(RenderViewHostTest, BadMessageHandlerInputEventAck) {
+ EXPECT_EQ(0, process()->bad_msg_count());
+ // InputHostMsg_HandleInputEvent_ACK is defined taking 0 params but
+ // the code actually expects it to have at least one int para, this this
+ // bogus message will not fail at de-serialization but should fail in
+ // OnInputEventAck() processing.
+ IPC::Message message(0, InputHostMsg_HandleInputEvent_ACK::ID,
+ IPC::Message::PRIORITY_NORMAL);
+ test_rvh()->OnMessageReceived(message);
+ EXPECT_EQ(1, process()->bad_msg_count());
+}
+
+#endif
+
+TEST_F(RenderViewHostTest, MessageWithBadHistoryItemFiles) {
+ base::FilePath file_path;
+ EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &file_path));
+ file_path = file_path.AppendASCII("foo");
+ EXPECT_EQ(0, process()->bad_msg_count());
+ test_rvh()->TestOnUpdateStateWithFile(process()->GetID(), file_path);
+ EXPECT_EQ(1, process()->bad_msg_count());
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ process()->GetID(), file_path);
+ test_rvh()->TestOnUpdateStateWithFile(process()->GetID(), file_path);
+ EXPECT_EQ(1, process()->bad_msg_count());
+}
+
+TEST_F(RenderViewHostTest, NavigationWithBadHistoryItemFiles) {
+ GURL url("http://www.google.com");
+ base::FilePath file_path;
+ EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &file_path));
+ file_path = file_path.AppendASCII("bar");
+ EXPECT_EQ(0, process()->bad_msg_count());
+ test_rvh()->SendNavigateWithFile(1, url, file_path);
+ EXPECT_EQ(1, process()->bad_msg_count());
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
+ process()->GetID(), file_path);
+ test_rvh()->SendNavigateWithFile(process()->GetID(), url, file_path);
+ EXPECT_EQ(1, process()->bad_msg_count());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_helper.cc b/chromium/content/browser/renderer_host/render_widget_helper.cc
new file mode 100644
index 00000000000..e7e25953c40
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_helper.cc
@@ -0,0 +1,403 @@
+// 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 "content/browser/renderer_host/render_widget_helper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/common/view_messages.h"
+
+namespace content {
+namespace {
+
+typedef std::map<int, RenderWidgetHelper*> WidgetHelperMap;
+base::LazyInstance<WidgetHelperMap> g_widget_helpers =
+ LAZY_INSTANCE_INITIALIZER;
+
+void AddWidgetHelper(int render_process_id,
+ const scoped_refptr<RenderWidgetHelper>& widget_helper) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // We don't care if RenderWidgetHelpers overwrite an existing process_id. Just
+ // want this to be up to date.
+ g_widget_helpers.Get()[render_process_id] = widget_helper.get();
+}
+
+} // namespace
+
+// A helper used with DidReceiveBackingStoreMsg that we hold a pointer to in
+// pending_paints_.
+class RenderWidgetHelper::BackingStoreMsgProxy {
+ public:
+ BackingStoreMsgProxy(RenderWidgetHelper* h, const IPC::Message& m);
+ ~BackingStoreMsgProxy();
+ void Run();
+ void Cancel() { cancelled_ = true; }
+
+ const IPC::Message& message() const { return message_; }
+
+ private:
+ scoped_refptr<RenderWidgetHelper> helper_;
+ IPC::Message message_;
+ bool cancelled_; // If true, then the message will not be dispatched.
+
+ DISALLOW_COPY_AND_ASSIGN(BackingStoreMsgProxy);
+};
+
+RenderWidgetHelper::BackingStoreMsgProxy::BackingStoreMsgProxy(
+ RenderWidgetHelper* h, const IPC::Message& m)
+ : helper_(h),
+ message_(m),
+ cancelled_(false) {
+}
+
+RenderWidgetHelper::BackingStoreMsgProxy::~BackingStoreMsgProxy() {
+ // If the paint message was never dispatched, then we need to let the
+ // helper know that we are going away.
+ if (!cancelled_ && helper_.get())
+ helper_->OnDiscardBackingStoreMsg(this);
+}
+
+void RenderWidgetHelper::BackingStoreMsgProxy::Run() {
+ if (!cancelled_) {
+ helper_->OnDispatchBackingStoreMsg(this);
+ helper_ = NULL;
+ }
+}
+
+RenderWidgetHelper::RenderWidgetHelper()
+ : render_process_id_(-1),
+#if defined(OS_WIN)
+ event_(CreateEvent(NULL, FALSE /* auto-reset */, FALSE, NULL)),
+#elif defined(OS_POSIX)
+ event_(false /* auto-reset */, false),
+#endif
+ resource_dispatcher_host_(NULL) {
+}
+
+RenderWidgetHelper::~RenderWidgetHelper() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // Delete this RWH from the map if it is found.
+ WidgetHelperMap& widget_map = g_widget_helpers.Get();
+ WidgetHelperMap::iterator it = widget_map.find(render_process_id_);
+ if (it != widget_map.end() && it->second == this)
+ widget_map.erase(it);
+
+ // The elements of pending_paints_ each hold an owning reference back to this
+ // object, so we should not be destroyed unless pending_paints_ is empty!
+ DCHECK(pending_paints_.empty());
+
+#if defined(OS_POSIX) && !defined(TOOLKIT_GTK) && !defined(OS_ANDROID)
+ ClearAllocatedDIBs();
+#endif
+}
+
+void RenderWidgetHelper::Init(
+ int render_process_id,
+ ResourceDispatcherHostImpl* resource_dispatcher_host) {
+ render_process_id_ = render_process_id;
+ resource_dispatcher_host_ = resource_dispatcher_host;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&AddWidgetHelper,
+ render_process_id_, make_scoped_refptr(this)));
+}
+
+int RenderWidgetHelper::GetNextRoutingID() {
+ return next_routing_id_.GetNext() + 1;
+}
+
+// static
+RenderWidgetHelper* RenderWidgetHelper::FromProcessHostID(
+ int render_process_host_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ WidgetHelperMap::const_iterator ci = g_widget_helpers.Get().find(
+ render_process_host_id);
+ return (ci == g_widget_helpers.Get().end())? NULL : ci->second;
+}
+
+void RenderWidgetHelper::ResumeDeferredNavigation(
+ const GlobalRequestID& request_id) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&RenderWidgetHelper::OnResumeDeferredNavigation,
+ this,
+ request_id));
+}
+
+bool RenderWidgetHelper::WaitForBackingStoreMsg(
+ int render_widget_id, const base::TimeDelta& max_delay, IPC::Message* msg) {
+ base::TimeTicks time_start = base::TimeTicks::Now();
+
+ for (;;) {
+ BackingStoreMsgProxy* proxy = NULL;
+ {
+ base::AutoLock lock(pending_paints_lock_);
+
+ BackingStoreMsgProxyMap::iterator it =
+ pending_paints_.find(render_widget_id);
+ if (it != pending_paints_.end()) {
+ BackingStoreMsgProxyQueue &queue = it->second;
+ DCHECK(!queue.empty());
+ proxy = queue.front();
+
+ // Flag the proxy as cancelled so that when it is run as a task it will
+ // do nothing.
+ proxy->Cancel();
+
+ queue.pop_front();
+ if (queue.empty())
+ pending_paints_.erase(it);
+ }
+ }
+
+ if (proxy) {
+ *msg = proxy->message();
+ DCHECK(msg->routing_id() == render_widget_id);
+ return true;
+ }
+
+ // Calculate the maximum amount of time that we are willing to sleep.
+ base::TimeDelta max_sleep_time =
+ max_delay - (base::TimeTicks::Now() - time_start);
+ if (max_sleep_time <= base::TimeDelta::FromMilliseconds(0))
+ break;
+
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ event_.TimedWait(max_sleep_time);
+ }
+
+ return false;
+}
+
+void RenderWidgetHelper::ResumeRequestsForView(int route_id) {
+ // We only need to resume blocked requests if we used a valid route_id.
+ // See CreateNewWindow.
+ if (route_id != MSG_ROUTING_NONE) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&RenderWidgetHelper::OnResumeRequestsForView,
+ this, route_id));
+ }
+}
+
+void RenderWidgetHelper::DidReceiveBackingStoreMsg(const IPC::Message& msg) {
+ int render_widget_id = msg.routing_id();
+
+ BackingStoreMsgProxy* proxy = new BackingStoreMsgProxy(this, msg);
+ {
+ base::AutoLock lock(pending_paints_lock_);
+
+ pending_paints_[render_widget_id].push_back(proxy);
+ }
+
+ // Notify anyone waiting on the UI thread that there is a new entry in the
+ // proxy map. If they don't find the entry they are looking for, then they
+ // will just continue waiting.
+ event_.Signal();
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&BackingStoreMsgProxy::Run, base::Owned(proxy)));
+}
+
+void RenderWidgetHelper::OnDiscardBackingStoreMsg(BackingStoreMsgProxy* proxy) {
+ const IPC::Message& msg = proxy->message();
+
+ // Remove the proxy from the map now that we are going to handle it normally.
+ {
+ base::AutoLock lock(pending_paints_lock_);
+
+ BackingStoreMsgProxyMap::iterator it =
+ pending_paints_.find(msg.routing_id());
+ DCHECK(it != pending_paints_.end());
+ BackingStoreMsgProxyQueue &queue = it->second;
+ DCHECK(queue.front() == proxy);
+
+ queue.pop_front();
+ if (queue.empty())
+ pending_paints_.erase(it);
+ }
+}
+
+void RenderWidgetHelper::OnDispatchBackingStoreMsg(
+ BackingStoreMsgProxy* proxy) {
+ OnDiscardBackingStoreMsg(proxy);
+
+ // It is reasonable for the host to no longer exist.
+ RenderProcessHost* host = RenderProcessHost::FromID(render_process_id_);
+ if (host)
+ host->OnMessageReceived(proxy->message());
+}
+
+void RenderWidgetHelper::OnResumeDeferredNavigation(
+ const GlobalRequestID& request_id) {
+ resource_dispatcher_host_->ResumeDeferredNavigation(request_id);
+}
+
+void RenderWidgetHelper::CreateNewWindow(
+ const ViewHostMsg_CreateWindow_Params& params,
+ bool no_javascript_access,
+ base::ProcessHandle render_process,
+ int* route_id,
+ int* main_frame_route_id,
+ int* surface_id,
+ SessionStorageNamespace* session_storage_namespace) {
+ if (params.opener_suppressed || no_javascript_access) {
+ // If the opener is supppressed or script access is disallowed, we should
+ // open the window in a new BrowsingInstance, and thus a new process. That
+ // means the current renderer process will not be able to route messages to
+ // it. Because of this, we will immediately show and navigate the window
+ // in OnCreateWindowOnUI, using the params provided here.
+ *route_id = MSG_ROUTING_NONE;
+ *main_frame_route_id = MSG_ROUTING_NONE;
+ *surface_id = 0;
+ } else {
+ *route_id = GetNextRoutingID();
+ *main_frame_route_id = GetNextRoutingID();
+ *surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
+ render_process_id_, *route_id);
+ // Block resource requests until the view is created, since the HWND might
+ // be needed if a response ends up creating a plugin.
+ resource_dispatcher_host_->BlockRequestsForRoute(
+ render_process_id_, *route_id);
+ resource_dispatcher_host_->BlockRequestsForRoute(
+ render_process_id_, *main_frame_route_id);
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&RenderWidgetHelper::OnCreateWindowOnUI,
+ this, params, *route_id, *main_frame_route_id,
+ make_scoped_refptr(session_storage_namespace)));
+}
+
+void RenderWidgetHelper::OnCreateWindowOnUI(
+ const ViewHostMsg_CreateWindow_Params& params,
+ int route_id,
+ int main_frame_route_id,
+ SessionStorageNamespace* session_storage_namespace) {
+ RenderViewHostImpl* host =
+ RenderViewHostImpl::FromID(render_process_id_, params.opener_id);
+ if (host)
+ host->CreateNewWindow(route_id, main_frame_route_id, params,
+ session_storage_namespace);
+}
+
+void RenderWidgetHelper::OnResumeRequestsForView(int route_id) {
+ resource_dispatcher_host_->ResumeBlockedRequestsForRoute(
+ render_process_id_, route_id);
+}
+
+void RenderWidgetHelper::CreateNewWidget(int opener_id,
+ WebKit::WebPopupType popup_type,
+ int* route_id,
+ int* surface_id) {
+ *route_id = GetNextRoutingID();
+ *surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
+ render_process_id_, *route_id);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &RenderWidgetHelper::OnCreateWidgetOnUI, this, opener_id, *route_id,
+ popup_type));
+}
+
+void RenderWidgetHelper::CreateNewFullscreenWidget(int opener_id,
+ int* route_id,
+ int* surface_id) {
+ *route_id = GetNextRoutingID();
+ *surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
+ render_process_id_, *route_id);
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &RenderWidgetHelper::OnCreateFullscreenWidgetOnUI, this,
+ opener_id, *route_id));
+}
+
+void RenderWidgetHelper::OnCreateWidgetOnUI(
+ int opener_id, int route_id, WebKit::WebPopupType popup_type) {
+ RenderViewHostImpl* host = RenderViewHostImpl::FromID(
+ render_process_id_, opener_id);
+ if (host)
+ host->CreateNewWidget(route_id, popup_type);
+}
+
+void RenderWidgetHelper::OnCreateFullscreenWidgetOnUI(int opener_id,
+ int route_id) {
+ RenderViewHostImpl* host = RenderViewHostImpl::FromID(
+ render_process_id_, opener_id);
+ if (host)
+ host->CreateNewFullscreenWidget(route_id);
+}
+
+#if defined(OS_POSIX) && !defined(TOOLKIT_GTK) && !defined(OS_ANDROID)
+TransportDIB* RenderWidgetHelper::MapTransportDIB(TransportDIB::Id dib_id) {
+ base::AutoLock locked(allocated_dibs_lock_);
+
+ const std::map<TransportDIB::Id, int>::iterator
+ i = allocated_dibs_.find(dib_id);
+ if (i == allocated_dibs_.end())
+ return NULL;
+
+ base::FileDescriptor fd(dup(i->second), true);
+ return TransportDIB::Map(fd);
+}
+
+void RenderWidgetHelper::AllocTransportDIB(uint32 size,
+ bool cache_in_browser,
+ TransportDIB::Handle* result) {
+ scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
+ if (!shared_memory->CreateAnonymous(size)) {
+ result->fd = -1;
+ result->auto_close = false;
+ return;
+ }
+
+ shared_memory->GiveToProcess(0 /* pid, not needed */, result);
+
+ if (cache_in_browser) {
+ // Keep a copy of the file descriptor around
+ base::AutoLock locked(allocated_dibs_lock_);
+ allocated_dibs_[shared_memory->id()] = dup(result->fd);
+ }
+}
+
+void RenderWidgetHelper::FreeTransportDIB(TransportDIB::Id dib_id) {
+ base::AutoLock locked(allocated_dibs_lock_);
+
+ const std::map<TransportDIB::Id, int>::iterator
+ i = allocated_dibs_.find(dib_id);
+
+ if (i != allocated_dibs_.end()) {
+ if (HANDLE_EINTR(close(i->second)) < 0)
+ PLOG(ERROR) << "close";
+ allocated_dibs_.erase(i);
+ } else {
+ DLOG(WARNING) << "Renderer asked us to free unknown transport DIB";
+ }
+}
+
+void RenderWidgetHelper::ClearAllocatedDIBs() {
+ for (std::map<TransportDIB::Id, int>::iterator
+ i = allocated_dibs_.begin(); i != allocated_dibs_.end(); ++i) {
+ if (HANDLE_EINTR(close(i->second)) < 0)
+ PLOG(ERROR) << "close: " << i->first;
+ }
+
+ allocated_dibs_.clear();
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_helper.h b/chromium/content/browser/renderer_host/render_widget_helper.h
new file mode 100644
index 00000000000..c5e80a1a296
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_helper.h
@@ -0,0 +1,251 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_
+
+#include <deque>
+#include <map>
+
+#include "base/atomic_sequence_num.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/common/window_container_type.h"
+#include "third_party/WebKit/public/web/WebPopupType.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/surface/transport_dib.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace base {
+class TimeDelta;
+}
+
+struct ViewHostMsg_CreateWindow_Params;
+struct ViewMsg_SwapOut_Params;
+
+namespace content {
+class ResourceDispatcherHostImpl;
+class SessionStorageNamespace;
+
+// Instantiated per RenderProcessHost to provide various optimizations on
+// behalf of a RenderWidgetHost. This class bridges between the IO thread
+// where the RenderProcessHost's MessageFilter lives and the UI thread where
+// the RenderWidgetHost lives.
+//
+//
+// OPTIMIZED RESIZE
+//
+// RenderWidgetHelper is used to implement optimized resize. When the
+// RenderWidgetHost is resized, it sends a Resize message to its RenderWidget
+// counterpart in the renderer process. In response to the Resize message,
+// the RenderWidget generates a new BackingStore and sends an UpdateRect
+// message (or BuffersSwapped via the GPU process in the case of accelerated
+// compositing), and it sets the IS_RESIZE_ACK flag in the UpdateRect message
+// to true. In the accelerated case, an UpdateRect is still sent from the
+// renderer to the browser with acks and plugin moves even though the GPU
+// BackingStore was sent earlier in the BuffersSwapped message. "BackingStore
+// message" is used throughout this code and documentation to mean either a
+// software UpdateRect or GPU BuffersSwapped message.
+//
+// Back in the browser process, when the RenderProcessHost's MessageFilter
+// sees an UpdateRect message (or when the GpuProcessHost sees a
+// BuffersSwapped message), it directs it to the RenderWidgetHelper by calling
+// the DidReceiveBackingStoreMsg method. That method stores the data for the
+// message in a map, where it can be directly accessed by the RenderWidgetHost
+// on the UI thread during a call to RenderWidgetHost's GetBackingStore
+// method.
+//
+// When the RenderWidgetHost's GetBackingStore method is called, it first
+// checks to see if it is waiting for a resize ack. If it is, then it calls
+// the RenderWidgetHelper's WaitForBackingStoreMsg to check if there is
+// already a resulting BackingStore message (or to wait a short amount of time
+// for one to arrive). The main goal of this mechanism is to short-cut the
+// usual way in which IPC messages are proxied over to the UI thread via
+// InvokeLater. This approach is necessary since window resize is followed up
+// immediately by a request to repaint the window.
+//
+//
+// OPTIMIZED TAB SWITCHING
+//
+// When a RenderWidgetHost is in a background tab, it is flagged as hidden.
+// This causes the corresponding RenderWidget to stop sending BackingStore
+// messages. The RenderWidgetHost also discards its backingstore when it is
+// hidden, which helps free up memory. As a result, when a RenderWidgetHost
+// is restored, it can be momentarily be without a backingstore. (Restoring
+// a RenderWidgetHost results in a WasShown message being sent to the
+// RenderWidget, which triggers a full BackingStore message.) This can lead
+// to an observed rendering glitch as the WebContentsImpl will just have to
+// fill white overtop the RenderWidgetHost until the RenderWidgetHost
+// receives a BackingStore message to refresh its backingstore.
+//
+// To avoid this 'white flash', the RenderWidgetHost again makes use of the
+// RenderWidgetHelper's WaitForBackingStoreMsg method. When the
+// RenderWidgetHost's GetBackingStore method is called, it will call
+// WaitForBackingStoreMsg if it has no backingstore.
+//
+// TRANSPORT DIB CREATION
+//
+// On some platforms (currently the Mac) the renderer cannot create transport
+// DIBs because of sandbox limitations. Thus, it has to make synchronous IPCs
+// to the browser for them. Since these requests are synchronous, they cannot
+// terminate on the UI thread. Thus, in this case, this object performs the
+// allocation and maintains the set of allocated transport DIBs which the
+// renderers can refer to.
+//
+class RenderWidgetHelper
+ : public base::RefCountedThreadSafe<RenderWidgetHelper,
+ BrowserThread::DeleteOnIOThread> {
+ public:
+ RenderWidgetHelper();
+
+ void Init(int render_process_id,
+ ResourceDispatcherHostImpl* resource_dispatcher_host);
+
+ // Gets the next available routing id. This is thread safe.
+ int GetNextRoutingID();
+
+ // IO THREAD ONLY -----------------------------------------------------------
+
+ // Lookup the RenderWidgetHelper from the render_process_host_id. Returns NULL
+ // if not found. NOTE: The raw pointer is for temporary use only. To retain,
+ // store in a scoped_refptr.
+ static RenderWidgetHelper* FromProcessHostID(int render_process_host_id);
+
+ // UI THREAD ONLY -----------------------------------------------------------
+
+ // These three functions provide the backend implementation of the
+ // corresponding functions in RenderProcessHost. See those declarations
+ // for documentation.
+ void ResumeDeferredNavigation(const GlobalRequestID& request_id);
+ bool WaitForBackingStoreMsg(int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg);
+ // Called to resume the requests for a view after it's ready. The view was
+ // created by CreateNewWindow which initially blocked the requests.
+ void ResumeRequestsForView(int route_id);
+
+#if defined(OS_POSIX) && !defined(TOOLKIT_GTK) && !defined(OS_ANDROID)
+ // Given the id of a transport DIB, return a mapping to it or NULL on error.
+ TransportDIB* MapTransportDIB(TransportDIB::Id dib_id);
+#endif
+
+ // IO THREAD ONLY -----------------------------------------------------------
+
+ // Called on the IO thread when a BackingStore message is received.
+ void DidReceiveBackingStoreMsg(const IPC::Message& msg);
+
+ void CreateNewWindow(
+ const ViewHostMsg_CreateWindow_Params& params,
+ bool no_javascript_access,
+ base::ProcessHandle render_process,
+ int* route_id,
+ int* main_frame_route_id,
+ int* surface_id,
+ SessionStorageNamespace* session_storage_namespace);
+ void CreateNewWidget(int opener_id,
+ WebKit::WebPopupType popup_type,
+ int* route_id,
+ int* surface_id);
+ void CreateNewFullscreenWidget(int opener_id, int* route_id, int* surface_id);
+
+#if defined(OS_POSIX)
+ // Called on the IO thread to handle the allocation of a TransportDIB. If
+ // |cache_in_browser| is |true|, then a copy of the shmem is kept by the
+ // browser, and it is the caller's repsonsibility to call
+ // FreeTransportDIB(). In all cases, the caller is responsible for deleting
+ // the resulting TransportDIB.
+ void AllocTransportDIB(uint32 size,
+ bool cache_in_browser,
+ TransportDIB::Handle* result);
+
+ // Called on the IO thread to handle the freeing of a transport DIB
+ void FreeTransportDIB(TransportDIB::Id dib_id);
+#endif
+
+ private:
+ // A class used to proxy a paint message. PaintMsgProxy objects are created
+ // on the IO thread and destroyed on the UI thread.
+ class BackingStoreMsgProxy;
+ friend class BackingStoreMsgProxy;
+ friend class base::RefCountedThreadSafe<RenderWidgetHelper>;
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
+ friend class base::DeleteHelper<RenderWidgetHelper>;
+
+ typedef std::deque<BackingStoreMsgProxy*> BackingStoreMsgProxyQueue;
+ // Map from render_widget_id to a queue of live PaintMsgProxy instances.
+ typedef base::hash_map<int, BackingStoreMsgProxyQueue >
+ BackingStoreMsgProxyMap;
+
+ ~RenderWidgetHelper();
+
+ // Called on the UI thread to discard a paint message.
+ void OnDiscardBackingStoreMsg(BackingStoreMsgProxy* proxy);
+
+ // Called on the UI thread to dispatch a paint message if necessary.
+ void OnDispatchBackingStoreMsg(BackingStoreMsgProxy* proxy);
+
+ // Called on the UI thread to finish creating a window.
+ void OnCreateWindowOnUI(
+ const ViewHostMsg_CreateWindow_Params& params,
+ int route_id,
+ int main_frame_route_id,
+ SessionStorageNamespace* session_storage_namespace);
+
+ // Called on the IO thread after a window was created on the UI thread.
+ void OnResumeRequestsForView(int route_id);
+
+ // Called on the UI thread to finish creating a widget.
+ void OnCreateWidgetOnUI(int opener_id,
+ int route_id,
+ WebKit::WebPopupType popup_type);
+
+ // Called on the UI thread to create a fullscreen widget.
+ void OnCreateFullscreenWidgetOnUI(int opener_id, int route_id);
+
+ // Called on the IO thread to resume a paused navigation in the network
+ // stack without transferring it to a new renderer process.
+ void OnResumeDeferredNavigation(const GlobalRequestID& request_id);
+
+#if defined(OS_POSIX)
+ // Called on destruction to release all allocated transport DIBs
+ void ClearAllocatedDIBs();
+
+ // On POSIX we keep file descriptors to all the allocated DIBs around until
+ // the renderer frees them.
+ base::Lock allocated_dibs_lock_;
+ std::map<TransportDIB::Id, int> allocated_dibs_;
+#endif
+
+ // A map of live paint messages. Must hold pending_paints_lock_ to access.
+ // The BackingStoreMsgProxy objects are not owned by this map. (See
+ // BackingStoreMsgProxy for details about how the lifetime of instances are
+ // managed.)
+ BackingStoreMsgProxyMap pending_paints_;
+ base::Lock pending_paints_lock_;
+
+ int render_process_id_;
+
+ // Event used to implement WaitForBackingStoreMsg.
+ base::WaitableEvent event_;
+
+ // The next routing id to use.
+ base::AtomicSequenceNumber next_routing_id_;
+
+ ResourceDispatcherHostImpl* resource_dispatcher_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_browsertest.cc b/chromium/content/browser/renderer_host/render_widget_host_browsertest.cc
new file mode 100644
index 00000000000..2a9ef56a7c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 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 "base/path_service.h"
+#include "base/run_loop.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/base/net_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace content {
+
+class RenderWidgetHostBrowserTest : public ContentBrowserTest {
+ public:
+ RenderWidgetHostBrowserTest() {}
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir_));
+ }
+
+ void GetSnapshotFromRendererCallback(const base::Closure& quit_closure,
+ bool* snapshot_valid,
+ bool success,
+ const SkBitmap& bitmap) {
+ quit_closure.Run();
+ EXPECT_EQ(success, true);
+
+ const int row_bytes = bitmap.rowBytesAsPixels();
+ SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
+ for (int i = 0; i < bitmap.width(); ++i) {
+ for (int j = 0; j < bitmap.height(); ++j) {
+ if (pixels[j * row_bytes + i] != SK_ColorRED) {
+ return;
+ }
+ }
+ }
+ *snapshot_valid = true;
+ }
+
+ protected:
+ base::FilePath test_dir_;
+};
+
+// Disabled on Windows and CrOS because it is flaky: crbug.com/272379.
+#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#define MAYBE_GetSnapshotFromRendererTest DISABLED_GetSnapshotFromRendererTest
+#else
+#define MAYBE_GetSnapshotFromRendererTest GetSnapshotFromRendererTest
+#endif
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostBrowserTest,
+ MAYBE_GetSnapshotFromRendererTest) {
+ base::RunLoop run_loop;
+
+ NavigateToURL(shell(), GURL(net::FilePathToFileURL(
+ test_dir_.AppendASCII("rwh_simple.html"))));
+
+ bool snapshot_valid = false;
+ RenderViewHost* const rwh = shell()->web_contents()->GetRenderViewHost();
+ rwh->GetSnapshotFromRenderer(gfx::Rect(), base::Bind(
+ &RenderWidgetHostBrowserTest::GetSnapshotFromRendererCallback,
+ base::Unretained(this),
+ run_loop.QuitClosure(),
+ &snapshot_valid));
+ run_loop.Run();
+
+ EXPECT_EQ(snapshot_valid, true);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_delegate.cc b/chromium/content/browser/renderer_host/render_widget_host_delegate.cc
new file mode 100644
index 00000000000..73f5325f5dd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_delegate.cc
@@ -0,0 +1,27 @@
+// 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 "content/browser/renderer_host/render_widget_host_delegate.h"
+
+namespace content {
+
+bool RenderWidgetHostDelegate::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+}
+
+bool RenderWidgetHostDelegate::PreHandleWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ return false;
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible
+RenderWidgetHostDelegate::GetParentNativeViewAccessible() {
+ return NULL;
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_delegate.h b/chromium/content/browser/renderer_host/render_widget_host_delegate.h
new file mode 100644
index 00000000000..9e8d36ea092
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_delegate.h
@@ -0,0 +1,63 @@
+// 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 CONTENT_BROWSER_RENDER_WIDGET_HOST_DELEGATE_H_
+#define CONTENT_BROWSER_RENDER_WIDGET_HOST_DELEGATE_H_
+
+#include "build/build_config.h"
+#include "content/common/content_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace WebKit {
+class WebMouseWheelEvent;
+}
+
+namespace content {
+
+class RenderWidgetHostImpl;
+struct NativeWebKeyboardEvent;
+
+//
+// RenderWidgetHostDelegate
+//
+// An interface implemented by an object interested in knowing about the state
+// of the RenderWidgetHost.
+class CONTENT_EXPORT RenderWidgetHostDelegate {
+ public:
+ // The RenderWidgetHost is going to be deleted.
+ virtual void RenderWidgetDeleted(RenderWidgetHostImpl* render_widget_host) {}
+
+ // Callback to give the browser a chance to handle the specified keyboard
+ // event before sending it to the renderer.
+ // Returns true if the |event| was handled. Otherwise, if the |event| would
+ // be handled in HandleKeyboardEvent() method as a normal keyboard shortcut,
+ // |*is_keyboard_shortcut| should be set to true.
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut);
+
+ // Callback to inform the browser that the renderer did not process the
+ // specified events. This gives an opportunity to the browser to process the
+ // event (used for keyboard shortcuts).
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}
+
+ // Callback to give the browser a chance to handle the specified mouse wheel
+ // event before sending it to the renderer.
+ // Returns true if the |event| was handled.
+ virtual bool PreHandleWheelEvent(const WebKit::WebMouseWheelEvent& event);
+
+ // Notifies that screen rects were sent to renderer process.
+ virtual void DidSendScreenRects(RenderWidgetHostImpl* rwh) {}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+ // Returns the widget's parent's NativeViewAccessible.
+ virtual gfx::NativeViewAccessible GetParentNativeViewAccessible();
+#endif
+
+ protected:
+ virtual ~RenderWidgetHostDelegate() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDER_WIDGET_HOST_DELEGATE_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_impl.cc b/chromium/content/browser/renderer_host/render_widget_host_impl.cc
new file mode 100644
index 00000000000..edd21fe5987
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_impl.cc
@@ -0,0 +1,2485 @@
+// 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 "content/browser/renderer_host/render_widget_host_impl.h"
+
+#include <math.h>
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/hash_tables.h"
+#include "base/debug/trace_event.h"
+#include "base/i18n/rtl.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "content/browser/accessibility/browser_accessibility_state_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/browser/renderer_host/backing_store_manager.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/input/immediate_input_router.h"
+#include "content/browser/renderer_host/overscroll_controller.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/content_constants_internal.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/compositor_util.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "skia/ext/image_operations.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "ui/base/events/event.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/gfx/vector2d_conversions.h"
+#include "webkit/common/cursors/webcursor.h"
+#include "webkit/common/webpreferences.h"
+
+#if defined(TOOLKIT_GTK)
+#include "content/browser/renderer_host/backing_store_gtk.h"
+#elif defined(OS_MACOSX)
+#include "content/browser/renderer_host/backing_store_mac.h"
+#elif defined(OS_WIN)
+#include "content/common/plugin_constants_win.h"
+#endif
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+using WebKit::WebKeyboardEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebTextDirection;
+
+namespace content {
+namespace {
+
+bool g_check_for_pending_resize_ack = true;
+
+// How long to (synchronously) wait for the renderer to respond with a
+// PaintRect message, when our backing-store is invalid, before giving up and
+// returning a null or incorrectly sized backing-store from GetBackingStore.
+// This timeout impacts the "choppiness" of our window resize perf.
+const int kPaintMsgTimeoutMS = 50;
+
+base::LazyInstance<std::vector<RenderWidgetHost::CreatedCallback> >
+g_created_callbacks = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+
+typedef std::pair<int32, int32> RenderWidgetHostID;
+typedef base::hash_map<RenderWidgetHostID, RenderWidgetHostImpl*>
+ RoutingIDWidgetMap;
+static base::LazyInstance<RoutingIDWidgetMap> g_routing_id_widget_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+void RenderWidgetHost::RemoveAllBackingStores() {
+ BackingStoreManager::RemoveAllBackingStores();
+}
+
+// static
+size_t RenderWidgetHost::BackingStoreMemorySize() {
+ return BackingStoreManager::MemorySize();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostImpl
+
+RenderWidgetHostImpl::RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id)
+ : view_(NULL),
+ renderer_initialized_(false),
+ hung_renderer_delay_ms_(kHungRendererDelayMs),
+ delegate_(delegate),
+ process_(process),
+ routing_id_(routing_id),
+ surface_id_(0),
+ is_loading_(false),
+ is_hidden_(false),
+ is_fullscreen_(false),
+ is_accelerated_compositing_active_(false),
+ repaint_ack_pending_(false),
+ resize_ack_pending_(false),
+ overdraw_bottom_height_(0.f),
+ should_auto_resize_(false),
+ waiting_for_screen_rects_ack_(false),
+ accessibility_mode_(AccessibilityModeOff),
+ needs_repainting_on_restore_(false),
+ is_unresponsive_(false),
+ in_flight_event_count_(0),
+ in_get_backing_store_(false),
+ abort_get_backing_store_(false),
+ view_being_painted_(false),
+ ignore_input_events_(false),
+ input_method_active_(false),
+ text_direction_updated_(false),
+ text_direction_(WebKit::WebTextDirectionLeftToRight),
+ text_direction_canceled_(false),
+ suppress_next_char_events_(false),
+ pending_mouse_lock_request_(false),
+ allow_privileged_mouse_lock_(false),
+ has_touch_handler_(false),
+ weak_factory_(this),
+ last_input_number_(0) {
+ CHECK(delegate_);
+ if (routing_id_ == MSG_ROUTING_NONE) {
+ routing_id_ = process_->GetNextRoutingID();
+ surface_id_ = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
+ process_->GetID(),
+ routing_id_);
+ } else {
+ // TODO(piman): This is a O(N) lookup, where we could forward the
+ // information from the RenderWidgetHelper. The problem is that doing so
+ // currently leaks outside of content all the way to chrome classes, and
+ // would be a layering violation. Since we don't expect more than a few
+ // hundreds of RWH, this seems acceptable. Revisit if performance become a
+ // problem, for example by tracking in the RenderWidgetHelper the routing id
+ // (and surface id) that have been created, but whose RWH haven't yet.
+ surface_id_ = GpuSurfaceTracker::Get()->LookupSurfaceForRenderer(
+ process_->GetID(),
+ routing_id_);
+ DCHECK(surface_id_);
+ }
+
+ is_threaded_compositing_enabled_ = IsThreadedCompositingEnabled();
+
+ g_routing_id_widget_map.Get().insert(std::make_pair(
+ RenderWidgetHostID(process->GetID(), routing_id_), this));
+ process_->AddRoute(routing_id_, this);
+ // Because the widget initializes as is_hidden_ == false,
+ // tell the process host that we're alive.
+ process_->WidgetRestored();
+
+ accessibility_mode_ =
+ BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode();
+
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); i++)
+ g_created_callbacks.Get().at(i).Run(this);
+
+ input_router_.reset(new ImmediateInputRouter(process, this, routing_id_));
+
+#if defined(USE_AURA)
+ bool overscroll_enabled = CommandLine::ForCurrentProcess()->
+ GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
+ SetOverscrollControllerEnabled(overscroll_enabled);
+#endif
+}
+
+RenderWidgetHostImpl::~RenderWidgetHostImpl() {
+ SetView(NULL);
+
+ // Clear our current or cached backing store if either remains.
+ BackingStoreManager::RemoveBackingStore(this);
+
+ GpuSurfaceTracker::Get()->RemoveSurface(surface_id_);
+ surface_id_ = 0;
+
+ process_->RemoveRoute(routing_id_);
+ g_routing_id_widget_map.Get().erase(
+ RenderWidgetHostID(process_->GetID(), routing_id_));
+
+ if (delegate_)
+ delegate_->RenderWidgetDeleted(this);
+}
+
+// static
+RenderWidgetHost* RenderWidgetHost::FromID(
+ int32 process_id,
+ int32 routing_id) {
+ return RenderWidgetHostImpl::FromID(process_id, routing_id);
+}
+
+// static
+RenderWidgetHostImpl* RenderWidgetHostImpl::FromID(
+ int32 process_id,
+ int32 routing_id) {
+ RoutingIDWidgetMap* widgets = g_routing_id_widget_map.Pointer();
+ RoutingIDWidgetMap::iterator it = widgets->find(
+ RenderWidgetHostID(process_id, routing_id));
+ return it == widgets->end() ? NULL : it->second;
+}
+
+// static
+std::vector<RenderWidgetHost*> RenderWidgetHost::GetRenderWidgetHosts() {
+ std::vector<RenderWidgetHost*> hosts;
+ RoutingIDWidgetMap* widgets = g_routing_id_widget_map.Pointer();
+ for (RoutingIDWidgetMap::const_iterator it = widgets->begin();
+ it != widgets->end();
+ ++it) {
+ RenderWidgetHost* widget = it->second;
+
+ if (!widget->IsRenderView()) {
+ hosts.push_back(widget);
+ continue;
+ }
+
+ // Add only active RenderViewHosts.
+ RenderViewHost* rvh = RenderViewHost::From(widget);
+ if (!static_cast<RenderViewHostImpl*>(rvh)->is_swapped_out())
+ hosts.push_back(widget);
+ }
+ return hosts;
+}
+
+// static
+std::vector<RenderWidgetHost*> RenderWidgetHostImpl::GetAllRenderWidgetHosts() {
+ std::vector<RenderWidgetHost*> hosts;
+ RoutingIDWidgetMap* widgets = g_routing_id_widget_map.Pointer();
+ for (RoutingIDWidgetMap::const_iterator it = widgets->begin();
+ it != widgets->end();
+ ++it) {
+ hosts.push_back(it->second);
+ }
+ return hosts;
+}
+
+// static
+RenderWidgetHostImpl* RenderWidgetHostImpl::From(RenderWidgetHost* rwh) {
+ return rwh->AsRenderWidgetHostImpl();
+}
+
+// static
+void RenderWidgetHost::AddCreatedCallback(const CreatedCallback& callback) {
+ g_created_callbacks.Get().push_back(callback);
+}
+
+// static
+void RenderWidgetHost::RemoveCreatedCallback(const CreatedCallback& callback) {
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); ++i) {
+ if (g_created_callbacks.Get().at(i).Equals(callback)) {
+ g_created_callbacks.Get().erase(g_created_callbacks.Get().begin() + i);
+ return;
+ }
+ }
+}
+
+void RenderWidgetHostImpl::SetView(RenderWidgetHostView* view) {
+ view_ = RenderWidgetHostViewPort::FromRWHV(view);
+
+ if (!view_) {
+ GpuSurfaceTracker::Get()->SetSurfaceHandle(
+ surface_id_, gfx::GLSurfaceHandle());
+ }
+}
+
+RenderProcessHost* RenderWidgetHostImpl::GetProcess() const {
+ return process_;
+}
+
+int RenderWidgetHostImpl::GetRoutingID() const {
+ return routing_id_;
+}
+
+RenderWidgetHostView* RenderWidgetHostImpl::GetView() const {
+ return view_;
+}
+
+RenderWidgetHostImpl* RenderWidgetHostImpl::AsRenderWidgetHostImpl() {
+ return this;
+}
+
+gfx::NativeViewId RenderWidgetHostImpl::GetNativeViewId() const {
+ if (view_)
+ return view_->GetNativeViewId();
+ return 0;
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostImpl::GetCompositingSurface() {
+ if (view_)
+ return view_->GetCompositingSurface();
+ return gfx::GLSurfaceHandle();
+}
+
+void RenderWidgetHostImpl::CompositingSurfaceUpdated() {
+ GpuSurfaceTracker::Get()->SetSurfaceHandle(
+ surface_id_, GetCompositingSurface());
+ process_->SurfaceUpdated(surface_id_);
+}
+
+void RenderWidgetHostImpl::ResetSizeAndRepaintPendingFlags() {
+ resize_ack_pending_ = false;
+ if (repaint_ack_pending_) {
+ TRACE_EVENT_ASYNC_END0(
+ "renderer_host", "RenderWidgetHostImpl::repaint_ack_pending_", this);
+ }
+ repaint_ack_pending_ = false;
+ last_requested_size_.SetSize(0, 0);
+}
+
+void RenderWidgetHostImpl::SendScreenRects() {
+ if (!renderer_initialized_ || waiting_for_screen_rects_ack_)
+ return;
+
+ if (is_hidden_) {
+ // On GTK, this comes in for backgrounded tabs. Ignore, to match what
+ // happens on Win & Mac, and when the view is shown it'll call this again.
+ return;
+ }
+
+ if (!view_)
+ return;
+
+ last_view_screen_rect_ = view_->GetViewBounds();
+ last_window_screen_rect_ = view_->GetBoundsInRootWindow();
+ Send(new ViewMsg_UpdateScreenRects(
+ GetRoutingID(), last_view_screen_rect_, last_window_screen_rect_));
+ if (delegate_)
+ delegate_->DidSendScreenRects(this);
+ waiting_for_screen_rects_ack_ = true;
+}
+
+base::TimeDelta
+ RenderWidgetHostImpl::GetSyntheticScrollMessageInterval() const {
+ return smooth_scroll_gesture_controller_.GetSyntheticScrollMessageInterval();
+}
+
+void RenderWidgetHostImpl::SetOverscrollControllerEnabled(bool enabled) {
+ if (!enabled)
+ overscroll_controller_.reset();
+ else if (!overscroll_controller_)
+ overscroll_controller_.reset(new OverscrollController(this));
+}
+
+void RenderWidgetHostImpl::SuppressNextCharEvents() {
+ suppress_next_char_events_ = true;
+}
+
+void RenderWidgetHostImpl::Init() {
+ DCHECK(process_->HasConnection());
+
+ renderer_initialized_ = true;
+
+ GpuSurfaceTracker::Get()->SetSurfaceHandle(
+ surface_id_, GetCompositingSurface());
+
+ // Send the ack along with the information on placement.
+ Send(new ViewMsg_CreatingNew_ACK(routing_id_));
+ GetProcess()->ResumeRequestsForView(routing_id_);
+
+ WasResized();
+}
+
+void RenderWidgetHostImpl::Shutdown() {
+ RejectMouseLockOrUnlockIfNecessary();
+
+ if (process_->HasConnection()) {
+ // Tell the renderer object to close.
+ bool rv = Send(new ViewMsg_Close(routing_id_));
+ DCHECK(rv);
+ }
+
+ Destroy();
+}
+
+bool RenderWidgetHostImpl::IsLoading() const {
+ return is_loading_;
+}
+
+bool RenderWidgetHostImpl::IsRenderView() const {
+ return false;
+}
+
+bool RenderWidgetHostImpl::OnMessageReceived(const IPC::Message &msg) {
+ bool handled = true;
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderWidgetHostImpl, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewReady, OnRenderViewReady)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RenderProcessGone, OnRenderProcessGone)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Close, OnClose)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateScreenRects_ACK,
+ OnUpdateScreenRectsAck)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RequestMove, OnRequestMove)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetTooltipText, OnSetTooltipText)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PaintAtSize_ACK, OnPaintAtSizeAck)
+#if defined(OS_MACOSX)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CompositorSurfaceBuffersSwapped,
+ OnCompositorSurfaceBuffersSwapped)
+#endif
+ IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_SwapCompositorFrame,
+ msg_is_ok = OnSwapCompositorFrame(msg))
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidOverscroll, OnOverscrolled)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnUpdateRect)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateIsDelayed, OnUpdateIsDelayed)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_BeginSmoothScroll, OnBeginSmoothScroll)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Focus, OnFocus)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Blur, OnBlur)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnSetCursor)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_TextInputTypeChanged,
+ OnTextInputTypeChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCancelComposition,
+ OnImeCancelComposition)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidActivateAcceleratedCompositing,
+ OnDidActivateAcceleratedCompositing)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_LockMouse, OnLockMouse)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UnlockMouse, OnUnlockMouse)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ShowDisambiguationPopup,
+ OnShowDisambiguationPopup)
+#if defined(OS_WIN)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_WindowlessPluginDummyWindowCreated,
+ OnWindowlessPluginDummyWindowCreated)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_WindowlessPluginDummyWindowDestroyed,
+ OnWindowlessPluginDummyWindowDestroyed)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Snapshot, OnSnapshot)
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCompositionRangeChanged,
+ OnImeCompositionRangeChanged)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!handled && input_router_ && input_router_->OnMessageReceived(msg))
+ return true;
+
+ if (!handled && view_ && view_->OnMessageReceived(msg))
+ return true;
+
+ if (!msg_is_ok) {
+ // The message de-serialization failed. Kill the renderer process.
+ RecordAction(UserMetricsAction("BadMessageTerminate_RWH"));
+ GetProcess()->ReceivedBadMessage();
+ }
+ return handled;
+}
+
+bool RenderWidgetHostImpl::Send(IPC::Message* msg) {
+ if (IPC_MESSAGE_ID_CLASS(msg->type()) == InputMsgStart)
+ return input_router_->SendInput(msg);
+
+ return process_->Send(msg);
+}
+
+void RenderWidgetHostImpl::WasHidden() {
+ is_hidden_ = true;
+
+ // Don't bother reporting hung state when we aren't active.
+ StopHangMonitorTimeout();
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ Send(new ViewMsg_WasHidden(routing_id_));
+
+ // Tell the RenderProcessHost we were hidden.
+ process_->WidgetHidden();
+
+ bool is_visible = false;
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
+ Source<RenderWidgetHost>(this),
+ Details<bool>(&is_visible));
+}
+
+void RenderWidgetHostImpl::WasShown() {
+ // When we create the widget, it is created as *not* hidden.
+ if (!is_hidden_)
+ return;
+ is_hidden_ = false;
+
+ SendScreenRects();
+
+ BackingStore* backing_store = BackingStoreManager::Lookup(this);
+ // If we already have a backing store for this widget, then we don't need to
+ // repaint on restore _unless_ we know that our backing store is invalid.
+ // When accelerated compositing is on, we must always repaint, even when
+ // the backing store exists.
+ bool needs_repainting;
+ if (needs_repainting_on_restore_ || !backing_store ||
+ is_accelerated_compositing_active()) {
+ needs_repainting = true;
+ needs_repainting_on_restore_ = false;
+ } else {
+ needs_repainting = false;
+ }
+ Send(new ViewMsg_WasShown(routing_id_, needs_repainting));
+
+ process_->WidgetRestored();
+
+ bool is_visible = true;
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
+ Source<RenderWidgetHost>(this),
+ Details<bool>(&is_visible));
+
+ // It's possible for our size to be out of sync with the renderer. The
+ // following is one case that leads to this:
+ // 1. WasResized -> Send ViewMsg_Resize to render
+ // 2. WasResized -> do nothing as resize_ack_pending_ is true
+ // 3. WasHidden
+ // 4. OnUpdateRect from (1) processed. Does NOT invoke WasResized as view
+ // is hidden. Now renderer/browser out of sync with what they think size
+ // is.
+ // By invoking WasResized the renderer is updated as necessary. WasResized
+ // does nothing if the sizes are already in sync.
+ //
+ // TODO: ideally ViewMsg_WasShown would take a size. This way, the renderer
+ // could handle both the restore and resize at once. This isn't that big a
+ // deal as RenderWidget::WasShown delays updating, so that the resize from
+ // WasResized is usually processed before the renderer is painted.
+ WasResized();
+}
+
+void RenderWidgetHostImpl::WasResized() {
+ if (resize_ack_pending_ || !process_->HasConnection() || !view_ ||
+ !renderer_initialized_ || should_auto_resize_) {
+ return;
+ }
+
+ gfx::Rect view_bounds = view_->GetViewBounds();
+ gfx::Size new_size(view_bounds.size());
+
+ gfx::Size old_physical_backing_size = physical_backing_size_;
+ physical_backing_size_ = view_->GetPhysicalBackingSize();
+ bool was_fullscreen = is_fullscreen_;
+ is_fullscreen_ = IsFullscreen();
+ float old_overdraw_bottom_height = overdraw_bottom_height_;
+ overdraw_bottom_height_ = view_->GetOverdrawBottomHeight();
+
+ bool size_changed = new_size != last_requested_size_;
+ bool side_payload_changed =
+ !screen_info_.get() ||
+ old_physical_backing_size != physical_backing_size_ ||
+ was_fullscreen != is_fullscreen_ ||
+ old_overdraw_bottom_height != overdraw_bottom_height_;
+
+ if (!size_changed && !side_payload_changed)
+ return;
+
+ if (!screen_info_) {
+ screen_info_.reset(new WebKit::WebScreenInfo);
+ GetWebScreenInfo(screen_info_.get());
+ }
+
+ // We don't expect to receive an ACK when the requested size or the physical
+ // backing size is empty, or when the main viewport size didn't change.
+ if (!new_size.IsEmpty() && !physical_backing_size_.IsEmpty() && size_changed)
+ resize_ack_pending_ = g_check_for_pending_resize_ack;
+
+ ViewMsg_Resize_Params params;
+ params.screen_info = *screen_info_;
+ params.new_size = new_size;
+ params.physical_backing_size = physical_backing_size_;
+ params.overdraw_bottom_height = overdraw_bottom_height_;
+ params.resizer_rect = GetRootWindowResizerRect();
+ params.is_fullscreen = is_fullscreen_;
+ if (!Send(new ViewMsg_Resize(routing_id_, params))) {
+ resize_ack_pending_ = false;
+ } else {
+ last_requested_size_ = new_size;
+ }
+}
+
+void RenderWidgetHostImpl::ResizeRectChanged(const gfx::Rect& new_rect) {
+ Send(new ViewMsg_ChangeResizeRect(routing_id_, new_rect));
+}
+
+void RenderWidgetHostImpl::GotFocus() {
+ Focus();
+}
+
+void RenderWidgetHostImpl::Focus() {
+ Send(new InputMsg_SetFocus(routing_id_, true));
+}
+
+void RenderWidgetHostImpl::Blur() {
+ // If there is a pending mouse lock request, we don't want to reject it at
+ // this point. The user can switch focus back to this view and approve the
+ // request later.
+ if (IsMouseLocked())
+ view_->UnlockMouse();
+
+ // If there is a pending overscroll, then that should be cancelled.
+ if (overscroll_controller_)
+ overscroll_controller_->Cancel();
+
+ Send(new InputMsg_SetFocus(routing_id_, false));
+}
+
+void RenderWidgetHostImpl::LostCapture() {
+ Send(new InputMsg_MouseCaptureLost(routing_id_));
+}
+
+void RenderWidgetHostImpl::SetActive(bool active) {
+ Send(new ViewMsg_SetActive(routing_id_, active));
+}
+
+void RenderWidgetHostImpl::LostMouseLock() {
+ Send(new ViewMsg_MouseLockLost(routing_id_));
+}
+
+void RenderWidgetHostImpl::ViewDestroyed() {
+ RejectMouseLockOrUnlockIfNecessary();
+
+ // TODO(evanm): tracking this may no longer be necessary;
+ // eliminate this function if so.
+ SetView(NULL);
+}
+
+void RenderWidgetHostImpl::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ if (!view_)
+ return;
+ view_->SetIsLoading(is_loading);
+}
+
+void RenderWidgetHostImpl::CopyFromBackingStore(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& accelerated_dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ if (view_ && is_accelerated_compositing_active_) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostImpl::CopyFromBackingStore::FromCompositingSurface");
+ gfx::Rect accelerated_copy_rect = src_subrect.IsEmpty() ?
+ gfx::Rect(view_->GetViewBounds().size()) : src_subrect;
+ view_->CopyFromCompositingSurface(accelerated_copy_rect,
+ accelerated_dst_size,
+ callback);
+ return;
+ }
+
+ BackingStore* backing_store = GetBackingStore(false);
+ if (!backing_store) {
+ callback.Run(false, SkBitmap());
+ return;
+ }
+
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostImpl::CopyFromBackingStore::FromBackingStore");
+ gfx::Rect copy_rect = src_subrect.IsEmpty() ?
+ gfx::Rect(backing_store->size()) : src_subrect;
+ // When the result size is equal to the backing store size, copy from the
+ // backing store directly to the output canvas.
+ skia::PlatformBitmap output;
+ bool result = backing_store->CopyFromBackingStore(copy_rect, &output);
+ callback.Run(result, output.GetBitmap());
+}
+
+#if defined(TOOLKIT_GTK)
+bool RenderWidgetHostImpl::CopyFromBackingStoreToGtkWindow(
+ const gfx::Rect& dest_rect, GdkWindow* target) {
+ BackingStore* backing_store = GetBackingStore(false);
+ if (!backing_store)
+ return false;
+ (static_cast<BackingStoreGtk*>(backing_store))->PaintToRect(
+ dest_rect, target);
+ return true;
+}
+#elif defined(OS_MACOSX)
+gfx::Size RenderWidgetHostImpl::GetBackingStoreSize() {
+ BackingStore* backing_store = GetBackingStore(false);
+ return backing_store ? backing_store->size() : gfx::Size();
+}
+
+bool RenderWidgetHostImpl::CopyFromBackingStoreToCGContext(
+ const CGRect& dest_rect, CGContextRef target) {
+ BackingStore* backing_store = GetBackingStore(false);
+ if (!backing_store)
+ return false;
+ (static_cast<BackingStoreMac*>(backing_store))->
+ CopyFromBackingStoreToCGContext(dest_rect, target);
+ return true;
+}
+#endif
+
+void RenderWidgetHostImpl::PaintAtSize(TransportDIB::Handle dib_handle,
+ int tag,
+ const gfx::Size& page_size,
+ const gfx::Size& desired_size) {
+ // Ask the renderer to create a bitmap regardless of whether it's
+ // hidden, being resized, redrawn, etc. It resizes the web widget
+ // to the page_size and then scales it to the desired_size.
+ Send(new ViewMsg_PaintAtSize(routing_id_, dib_handle, tag,
+ page_size, desired_size));
+}
+
+bool RenderWidgetHostImpl::TryGetBackingStore(const gfx::Size& desired_size,
+ BackingStore** backing_store) {
+ // Check if the view has an accelerated surface of the desired size.
+ if (view_->HasAcceleratedSurface(desired_size)) {
+ *backing_store = NULL;
+ return true;
+ }
+
+ // Check for a software backing store of the desired size.
+ *backing_store = BackingStoreManager::GetBackingStore(this, desired_size);
+ return !!*backing_store;
+}
+
+BackingStore* RenderWidgetHostImpl::GetBackingStore(bool force_create) {
+ if (!view_)
+ return NULL;
+
+ // The view_size will be current_size_ for auto-sized views and otherwise the
+ // size of the view_. (For auto-sized views, current_size_ is updated during
+ // UpdateRect messages.)
+ gfx::Size view_size = current_size_;
+ if (!should_auto_resize_) {
+ // Get the desired size from the current view bounds.
+ gfx::Rect view_rect = view_->GetViewBounds();
+ if (view_rect.IsEmpty())
+ return NULL;
+ view_size = view_rect.size();
+ }
+
+ TRACE_EVENT2("renderer_host", "RenderWidgetHostImpl::GetBackingStore",
+ "width", base::IntToString(view_size.width()),
+ "height", base::IntToString(view_size.height()));
+
+ // We should not be asked to paint while we are hidden. If we are hidden,
+ // then it means that our consumer failed to call WasShown. If we're not
+ // force creating the backing store, it's OK since we can feel free to give
+ // out our cached one if we have it.
+ DCHECK(!is_hidden_ || !force_create) <<
+ "GetBackingStore called while hidden!";
+
+ // We should never be called recursively; this can theoretically lead to
+ // infinite recursion and almost certainly leads to lower performance.
+ DCHECK(!in_get_backing_store_) << "GetBackingStore called recursively!";
+ base::AutoReset<bool> auto_reset_in_get_backing_store(
+ &in_get_backing_store_, true);
+
+ // We might have a cached backing store that we can reuse!
+ BackingStore* backing_store = NULL;
+ if (TryGetBackingStore(view_size, &backing_store) || !force_create)
+ return backing_store;
+
+ // We do not have a suitable backing store in the cache, so send out a
+ // request to the renderer to paint the view if required.
+ if (!repaint_ack_pending_ && !resize_ack_pending_ && !view_being_painted_) {
+ repaint_start_time_ = TimeTicks::Now();
+ repaint_ack_pending_ = true;
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "renderer_host", "RenderWidgetHostImpl::repaint_ack_pending_", this);
+ Send(new ViewMsg_Repaint(routing_id_, view_size));
+ }
+
+ TimeDelta max_delay = TimeDelta::FromMilliseconds(kPaintMsgTimeoutMS);
+ TimeTicks end_time = TimeTicks::Now() + max_delay;
+ do {
+ TRACE_EVENT0("renderer_host", "GetBackingStore::WaitForUpdate");
+
+#if defined(OS_MACOSX)
+ view_->AboutToWaitForBackingStoreMsg();
+#endif
+
+ // When we have asked the RenderWidget to resize, and we are still waiting
+ // on a response, block for a little while to see if we can't get a response
+ // before returning the old (incorrectly sized) backing store.
+ IPC::Message msg;
+ if (process_->WaitForBackingStoreMsg(routing_id_, max_delay, &msg)) {
+ OnMessageReceived(msg);
+
+ // For auto-resized views, current_size_ determines the view_size and it
+ // may have changed during the handling of an UpdateRect message.
+ if (should_auto_resize_)
+ view_size = current_size_;
+
+ // Break now if we got a backing store or accelerated surface of the
+ // correct size.
+ if (TryGetBackingStore(view_size, &backing_store) ||
+ abort_get_backing_store_) {
+ abort_get_backing_store_ = false;
+ return backing_store;
+ }
+ } else {
+ TRACE_EVENT0("renderer_host", "GetBackingStore::Timeout");
+ break;
+ }
+
+ // Loop if we still have time left and haven't gotten a properly sized
+ // BackingStore yet. This is necessary to support the GPU path which
+ // typically has multiple frames pipelined -- we may need to skip one or two
+ // BackingStore messages to get to the latest.
+ max_delay = end_time - TimeTicks::Now();
+ } while (max_delay > TimeDelta::FromSeconds(0));
+
+ // We have failed to get a backing store of view_size. Fall back on
+ // current_size_ to avoid a white flash while resizing slow pages.
+ if (view_size != current_size_)
+ TryGetBackingStore(current_size_, &backing_store);
+ return backing_store;
+}
+
+BackingStore* RenderWidgetHostImpl::AllocBackingStore(const gfx::Size& size) {
+ if (!view_)
+ return NULL;
+ return view_->AllocBackingStore(size);
+}
+
+void RenderWidgetHostImpl::DonePaintingToBackingStore() {
+ Send(new ViewMsg_UpdateRect_ACK(GetRoutingID()));
+}
+
+void RenderWidgetHostImpl::ScheduleComposite() {
+ if (is_hidden_ || !is_accelerated_compositing_active_ ||
+ current_size_.IsEmpty()) {
+ return;
+ }
+
+ // Send out a request to the renderer to paint the view if required.
+ if (!repaint_ack_pending_ && !resize_ack_pending_ && !view_being_painted_) {
+ repaint_start_time_ = TimeTicks::Now();
+ repaint_ack_pending_ = true;
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "renderer_host", "RenderWidgetHostImpl::repaint_ack_pending_", this);
+ Send(new ViewMsg_Repaint(routing_id_, current_size_));
+ }
+}
+
+void RenderWidgetHostImpl::StartHangMonitorTimeout(TimeDelta delay) {
+ if (!GetProcess()->IsGuest() && CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableHangMonitor)) {
+ return;
+ }
+
+ // Set time_when_considered_hung_ if it's null. Also, update
+ // time_when_considered_hung_ if the caller's request is sooner than the
+ // existing one. This will have the side effect that the existing timeout will
+ // be forgotten.
+ Time requested_end_time = Time::Now() + delay;
+ if (time_when_considered_hung_.is_null() ||
+ time_when_considered_hung_ > requested_end_time)
+ time_when_considered_hung_ = requested_end_time;
+
+ // If we already have a timer with the same or shorter duration, then we can
+ // wait for it to finish.
+ if (hung_renderer_timer_.IsRunning() &&
+ hung_renderer_timer_.GetCurrentDelay() <= delay) {
+ // If time_when_considered_hung_ was null, this timer may fire early.
+ // CheckRendererIsUnresponsive handles that by calling
+ // StartHangMonitorTimeout with the remaining time.
+ // If time_when_considered_hung_ was non-null, it means we still haven't
+ // heard from the renderer so we leave time_when_considered_hung_ as is.
+ return;
+ }
+
+ // Either the timer is not yet running, or we need to adjust the timer to
+ // fire sooner.
+ time_when_considered_hung_ = requested_end_time;
+ hung_renderer_timer_.Stop();
+ hung_renderer_timer_.Start(FROM_HERE, delay, this,
+ &RenderWidgetHostImpl::CheckRendererIsUnresponsive);
+}
+
+void RenderWidgetHostImpl::RestartHangMonitorTimeout() {
+ // Setting to null will cause StartHangMonitorTimeout to restart the timer.
+ time_when_considered_hung_ = Time();
+ StartHangMonitorTimeout(
+ TimeDelta::FromMilliseconds(hung_renderer_delay_ms_));
+}
+
+void RenderWidgetHostImpl::StopHangMonitorTimeout() {
+ time_when_considered_hung_ = Time();
+ RendererIsResponsive();
+ // We do not bother to stop the hung_renderer_timer_ here in case it will be
+ // started again shortly, which happens to be the common use case.
+}
+
+void RenderWidgetHostImpl::EnableFullAccessibilityMode() {
+ SetAccessibilityMode(AccessibilityModeComplete);
+}
+
+static WebGestureEvent MakeGestureEvent(WebInputEvent::Type type,
+ double timestamp_seconds,
+ int x,
+ int y,
+ int modifiers) {
+ WebGestureEvent result;
+
+ result.type = type;
+ result.x = x;
+ result.y = y;
+ result.sourceDevice = WebGestureEvent::Touchscreen;
+ result.timeStampSeconds = timestamp_seconds;
+ result.modifiers = modifiers;
+
+ return result;
+}
+
+void RenderWidgetHostImpl::SimulateTouchGestureWithMouse(
+ const WebMouseEvent& mouse_event) {
+ int x = mouse_event.x, y = mouse_event.y;
+ float dx = mouse_event.movementX, dy = mouse_event.movementY;
+ static int startX = 0, startY = 0;
+
+ switch (mouse_event.button) {
+ case WebMouseEvent::ButtonLeft:
+ if (mouse_event.type == WebInputEvent::MouseDown) {
+ startX = x;
+ startY = y;
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureScrollBegin, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ if (dx != 0 || dy != 0) {
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GestureScrollUpdate, mouse_event.timeStampSeconds,
+ x, y, 0);
+ event.data.scrollUpdate.deltaX = dx;
+ event.data.scrollUpdate.deltaY = dy;
+ ForwardGestureEvent(event);
+ }
+ if (mouse_event.type == WebInputEvent::MouseUp) {
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureScrollEnd, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ break;
+ case WebMouseEvent::ButtonMiddle:
+ if (mouse_event.type == WebInputEvent::MouseDown) {
+ startX = x;
+ startY = y;
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureTapDown, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ if (mouse_event.type == WebInputEvent::MouseUp) {
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureTap, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ break;
+ case WebMouseEvent::ButtonRight:
+ if (mouse_event.type == WebInputEvent::MouseDown) {
+ startX = x;
+ startY = y;
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureScrollBegin, mouse_event.timeStampSeconds,
+ x, y, 0));
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GesturePinchBegin, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ if (dx != 0 || dy != 0) {
+ dx = pow(dy < 0 ? 0.998f : 1.002f, fabs(dy));
+ WebGestureEvent event = MakeGestureEvent(
+ WebInputEvent::GesturePinchUpdate, mouse_event.timeStampSeconds,
+ startX, startY, 0);
+ event.data.pinchUpdate.scale = dx;
+ ForwardGestureEvent(event);
+ }
+ if (mouse_event.type == WebInputEvent::MouseUp) {
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GesturePinchEnd, mouse_event.timeStampSeconds,
+ x, y, 0));
+ ForwardGestureEvent(MakeGestureEvent(
+ WebInputEvent::GestureScrollEnd, mouse_event.timeStampSeconds,
+ x, y, 0));
+ }
+ break;
+ case WebMouseEvent::ButtonNone:
+ break;
+ }
+}
+
+void RenderWidgetHostImpl::ForwardMouseEvent(const WebMouseEvent& mouse_event) {
+ ForwardMouseEventWithLatencyInfo(
+ MouseEventWithLatencyInfo(mouse_event,
+ CreateRWHLatencyInfoIfNotExist(NULL)));
+}
+
+void RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(
+ const MouseEventWithLatencyInfo& mouse_event) {
+ TRACE_EVENT2("input", "RenderWidgetHostImpl::ForwardMouseEvent",
+ "x", mouse_event.event.x, "y", mouse_event.event.y);
+ input_router_->SendMouseEvent(mouse_event);
+}
+
+void RenderWidgetHostImpl::OnPointerEventActivate() {
+}
+
+void RenderWidgetHostImpl::ForwardWheelEvent(
+ const WebMouseWheelEvent& wheel_event) {
+ ForwardWheelEventWithLatencyInfo(
+ MouseWheelEventWithLatencyInfo(wheel_event,
+ CreateRWHLatencyInfoIfNotExist(NULL)));
+}
+
+void RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(
+ const MouseWheelEventWithLatencyInfo& wheel_event) {
+ TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardWheelEvent");
+ input_router_->SendWheelEvent(wheel_event);
+}
+
+void RenderWidgetHostImpl::ForwardGestureEvent(
+ const WebKit::WebGestureEvent& gesture_event) {
+ ForwardGestureEventWithLatencyInfo(gesture_event, ui::LatencyInfo());
+}
+
+void RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(
+ const WebKit::WebGestureEvent& gesture_event,
+ const ui::LatencyInfo& ui_latency) {
+ TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardGestureEvent");
+ // Early out if necessary, prior to performing latency logic.
+ if (IgnoreInputEvents())
+ return;
+
+ ui::LatencyInfo latency_info = CreateRWHLatencyInfoIfNotExist(&ui_latency);
+
+ if (gesture_event.type == WebKit::WebInputEvent::GestureScrollUpdate) {
+ latency_info.AddLatencyNumber(
+ ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT,
+ GetLatencyComponentId(),
+ ++last_input_number_);
+
+ // Make a copy of the INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT with a
+ // different name INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT.
+ // So we can track the latency specifically for scroll update events.
+ ui::LatencyInfo::LatencyComponent original_component;
+ if (latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 0,
+ &original_component)) {
+ latency_info.AddLatencyNumberWithTimestamp(
+ ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+ GetLatencyComponentId(),
+ original_component.sequence_number,
+ original_component.event_time,
+ original_component.event_count);
+ }
+ }
+
+ GestureEventWithLatencyInfo gesture_with_latency(gesture_event, latency_info);
+ input_router_->SendGestureEvent(gesture_with_latency);
+}
+
+void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
+ const WebKit::WebTouchEvent& touch_event,
+ const ui::LatencyInfo& ui_latency) {
+ TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardTouchEvent");
+ ui::LatencyInfo latency_info = CreateRWHLatencyInfoIfNotExist(&ui_latency);
+ TouchEventWithLatencyInfo touch_with_latency(touch_event, latency_info);
+ input_router_->SendTouchEvent(touch_with_latency);
+}
+
+void RenderWidgetHostImpl::ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event) {
+ TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardKeyboardEvent");
+ input_router_->SendKeyboardEvent(key_event,
+ CreateRWHLatencyInfoIfNotExist(NULL));
+}
+
+void RenderWidgetHostImpl::SendCursorVisibilityState(bool is_visible) {
+ Send(new InputMsg_CursorVisibilityChange(GetRoutingID(), is_visible));
+}
+
+int64 RenderWidgetHostImpl::GetLatencyComponentId() {
+ return GetRoutingID() | (static_cast<int64>(GetProcess()->GetID()) << 32);
+}
+
+// static
+void RenderWidgetHostImpl::DisableResizeAckCheckForTesting() {
+ g_check_for_pending_resize_ack = false;
+}
+
+ui::LatencyInfo RenderWidgetHostImpl::CreateRWHLatencyInfoIfNotExist(
+ const ui::LatencyInfo* original) {
+ ui::LatencyInfo info;
+ if (original)
+ info = *original;
+ // In Aura, gesture event will already carry its original touch event's
+ // INPUT_EVENT_LATENCY_RWH_COMPONENT.
+ if (!info.FindLatency(ui::INPUT_EVENT_LATENCY_RWH_COMPONENT,
+ GetLatencyComponentId(),
+ NULL)) {
+ info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_RWH_COMPONENT,
+ GetLatencyComponentId(),
+ ++last_input_number_);
+ }
+ return info;
+}
+
+
+void RenderWidgetHostImpl::AddKeyboardListener(KeyboardListener* listener) {
+ keyboard_listeners_.AddObserver(listener);
+}
+
+void RenderWidgetHostImpl::RemoveKeyboardListener(
+ KeyboardListener* listener) {
+ // Ensure that the element is actually an observer.
+ DCHECK(keyboard_listeners_.HasObserver(listener));
+ keyboard_listeners_.RemoveObserver(listener);
+}
+
+void RenderWidgetHostImpl::GetWebScreenInfo(WebKit::WebScreenInfo* result) {
+ TRACE_EVENT0("renderer_host", "RenderWidgetHostImpl::GetWebScreenInfo");
+ if (GetView())
+ static_cast<RenderWidgetHostViewPort*>(GetView())->GetScreenInfo(result);
+ else
+ RenderWidgetHostViewPort::GetDefaultScreenInfo(result);
+}
+
+const NativeWebKeyboardEvent*
+ RenderWidgetHostImpl::GetLastKeyboardEvent() const {
+ return input_router_->GetLastKeyboardEvent();
+}
+
+void RenderWidgetHostImpl::NotifyScreenInfoChanged() {
+ // The resize message (which may not happen immediately) will carry with it
+ // the screen info as well as the new size (if the screen has changed scale
+ // factor).
+ InvalidateScreenInfo();
+ WasResized();
+}
+
+void RenderWidgetHostImpl::InvalidateScreenInfo() {
+ screen_info_.reset();
+}
+
+void RenderWidgetHostImpl::GetSnapshotFromRenderer(
+ const gfx::Rect& src_subrect,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ TRACE_EVENT0("browser", "RenderWidgetHostImpl::GetSnapshotFromRenderer");
+ pending_snapshots_.push(callback);
+
+ gfx::Rect copy_rect = src_subrect.IsEmpty() ?
+ gfx::Rect(view_->GetViewBounds().size()) : src_subrect;
+
+ gfx::Rect copy_rect_in_pixel = ConvertViewRectToPixel(view_, copy_rect);
+ Send(new ViewMsg_Snapshot(GetRoutingID(), copy_rect_in_pixel));
+}
+
+void RenderWidgetHostImpl::OnSnapshot(bool success,
+ const SkBitmap& bitmap) {
+ if (pending_snapshots_.size() == 0) {
+ LOG(ERROR) << "RenderWidgetHostImpl::OnSnapshot: "
+ "Received a snapshot that was not requested.";
+ return;
+ }
+
+ base::Callback<void(bool, const SkBitmap&)> callback =
+ pending_snapshots_.front();
+ pending_snapshots_.pop();
+
+ if (!success) {
+ callback.Run(success, SkBitmap());
+ return;
+ }
+
+ callback.Run(success, bitmap);
+}
+
+void RenderWidgetHostImpl::UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ Send(new ViewMsg_UpdateVSyncParameters(GetRoutingID(), timebase, interval));
+}
+
+void RenderWidgetHostImpl::RendererExited(base::TerminationStatus status,
+ int exit_code) {
+ // Clearing this flag causes us to re-create the renderer when recovering
+ // from a crashed renderer.
+ renderer_initialized_ = false;
+
+ waiting_for_screen_rects_ack_ = false;
+
+ // Reset to ensure that input routing works with a new renderer.
+ input_router_.reset(new ImmediateInputRouter(process_, this, routing_id_));
+
+ if (overscroll_controller_)
+ overscroll_controller_->Reset();
+
+ // Must reset these to ensure that keyboard events work with a new renderer.
+ suppress_next_char_events_ = false;
+
+ // Reset some fields in preparation for recovering from a crash.
+ ResetSizeAndRepaintPendingFlags();
+ current_size_.SetSize(0, 0);
+ is_hidden_ = false;
+ is_accelerated_compositing_active_ = false;
+
+ // Reset this to ensure the hung renderer mechanism is working properly.
+ in_flight_event_count_ = 0;
+
+ if (view_) {
+ GpuSurfaceTracker::Get()->SetSurfaceHandle(surface_id_,
+ gfx::GLSurfaceHandle());
+ view_->RenderProcessGone(status, exit_code);
+ view_ = NULL; // The View should be deleted by RenderProcessGone.
+ }
+
+ BackingStoreManager::RemoveBackingStore(this);
+}
+
+void RenderWidgetHostImpl::UpdateTextDirection(WebTextDirection direction) {
+ text_direction_updated_ = true;
+ text_direction_ = direction;
+}
+
+void RenderWidgetHostImpl::CancelUpdateTextDirection() {
+ if (text_direction_updated_)
+ text_direction_canceled_ = true;
+}
+
+void RenderWidgetHostImpl::NotifyTextDirection() {
+ if (text_direction_updated_) {
+ if (!text_direction_canceled_)
+ Send(new ViewMsg_SetTextDirection(GetRoutingID(), text_direction_));
+ text_direction_updated_ = false;
+ text_direction_canceled_ = false;
+ }
+}
+
+void RenderWidgetHostImpl::SetInputMethodActive(bool activate) {
+ input_method_active_ = activate;
+ Send(new ViewMsg_SetInputMethodActive(GetRoutingID(), activate));
+}
+
+void RenderWidgetHostImpl::ImeSetComposition(
+ const string16& text,
+ const std::vector<WebKit::WebCompositionUnderline>& underlines,
+ int selection_start,
+ int selection_end) {
+ Send(new ViewMsg_ImeSetComposition(
+ GetRoutingID(), text, underlines, selection_start, selection_end));
+}
+
+void RenderWidgetHostImpl::ImeConfirmComposition(
+ const string16& text,
+ const ui::Range& replacement_range,
+ bool keep_selection) {
+ Send(new ViewMsg_ImeConfirmComposition(
+ GetRoutingID(), text, replacement_range, keep_selection));
+}
+
+void RenderWidgetHostImpl::ImeCancelComposition() {
+ Send(new ViewMsg_ImeSetComposition(GetRoutingID(), string16(),
+ std::vector<WebKit::WebCompositionUnderline>(), 0, 0));
+}
+
+void RenderWidgetHostImpl::ExtendSelectionAndDelete(
+ size_t before,
+ size_t after) {
+ Send(new ViewMsg_ExtendSelectionAndDelete(GetRoutingID(), before, after));
+}
+
+gfx::Rect RenderWidgetHostImpl::GetRootWindowResizerRect() const {
+ return gfx::Rect();
+}
+
+void RenderWidgetHostImpl::RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) {
+ // Directly reject to lock the mouse. Subclass can override this method to
+ // decide whether to allow mouse lock or not.
+ GotResponseToLockMouseRequest(false);
+}
+
+void RenderWidgetHostImpl::RejectMouseLockOrUnlockIfNecessary() {
+ DCHECK(!pending_mouse_lock_request_ || !IsMouseLocked());
+ if (pending_mouse_lock_request_) {
+ pending_mouse_lock_request_ = false;
+ Send(new ViewMsg_LockMouse_ACK(routing_id_, false));
+ } else if (IsMouseLocked()) {
+ view_->UnlockMouse();
+ }
+}
+
+bool RenderWidgetHostImpl::IsMouseLocked() const {
+ return view_ ? view_->IsMouseLocked() : false;
+}
+
+bool RenderWidgetHostImpl::IsFullscreen() const {
+ return false;
+}
+
+void RenderWidgetHostImpl::SetShouldAutoResize(bool enable) {
+ should_auto_resize_ = enable;
+}
+
+bool RenderWidgetHostImpl::IsInOverscrollGesture() const {
+ return overscroll_controller_.get() &&
+ overscroll_controller_->overscroll_mode() != OVERSCROLL_NONE;
+}
+
+void RenderWidgetHostImpl::Destroy() {
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(this),
+ NotificationService::NoDetails());
+
+ // Tell the view to die.
+ // Note that in the process of the view shutting down, it can call a ton
+ // of other messages on us. So if you do any other deinitialization here,
+ // do it after this call to view_->Destroy().
+ if (view_)
+ view_->Destroy();
+
+ delete this;
+}
+
+void RenderWidgetHostImpl::CheckRendererIsUnresponsive() {
+ // If we received a call to StopHangMonitorTimeout.
+ if (time_when_considered_hung_.is_null())
+ return;
+
+ // If we have not waited long enough, then wait some more.
+ Time now = Time::Now();
+ if (now < time_when_considered_hung_) {
+ StartHangMonitorTimeout(time_when_considered_hung_ - now);
+ return;
+ }
+
+ // OK, looks like we have a hung renderer!
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_HANG,
+ Source<RenderWidgetHost>(this),
+ NotificationService::NoDetails());
+ is_unresponsive_ = true;
+ NotifyRendererUnresponsive();
+}
+
+void RenderWidgetHostImpl::RendererIsResponsive() {
+ if (is_unresponsive_) {
+ is_unresponsive_ = false;
+ NotifyRendererResponsive();
+ }
+}
+
+void RenderWidgetHostImpl::OnRenderViewReady() {
+ SendScreenRects();
+ WasResized();
+}
+
+void RenderWidgetHostImpl::OnRenderProcessGone(int status, int exit_code) {
+ // TODO(evanm): This synchronously ends up calling "delete this".
+ // Is that really what we want in response to this message? I'm matching
+ // previous behavior of the code here.
+ Destroy();
+}
+
+void RenderWidgetHostImpl::OnClose() {
+ Shutdown();
+}
+
+void RenderWidgetHostImpl::OnSetTooltipText(
+ const string16& tooltip_text,
+ WebTextDirection text_direction_hint) {
+ // First, add directionality marks around tooltip text if necessary.
+ // A naive solution would be to simply always wrap the text. However, on
+ // windows, Unicode directional embedding characters can't be displayed on
+ // systems that lack RTL fonts and are instead displayed as empty squares.
+ //
+ // To get around this we only wrap the string when we deem it necessary i.e.
+ // when the locale direction is different than the tooltip direction hint.
+ //
+ // Currently, we use element's directionality as the tooltip direction hint.
+ // An alternate solution would be to set the overall directionality based on
+ // trying to detect the directionality from the tooltip text rather than the
+ // element direction. One could argue that would be a preferable solution
+ // but we use the current approach to match Fx & IE's behavior.
+ string16 wrapped_tooltip_text = tooltip_text;
+ if (!tooltip_text.empty()) {
+ if (text_direction_hint == WebKit::WebTextDirectionLeftToRight) {
+ // Force the tooltip to have LTR directionality.
+ wrapped_tooltip_text =
+ base::i18n::GetDisplayStringInLTRDirectionality(wrapped_tooltip_text);
+ } else if (text_direction_hint == WebKit::WebTextDirectionRightToLeft &&
+ !base::i18n::IsRTL()) {
+ // Force the tooltip to have RTL directionality.
+ base::i18n::WrapStringWithRTLFormatting(&wrapped_tooltip_text);
+ }
+ }
+ if (GetView())
+ view_->SetTooltipText(wrapped_tooltip_text);
+}
+
+void RenderWidgetHostImpl::OnUpdateScreenRectsAck() {
+ waiting_for_screen_rects_ack_ = false;
+ if (!view_)
+ return;
+
+ if (view_->GetViewBounds() == last_view_screen_rect_ &&
+ view_->GetBoundsInRootWindow() == last_window_screen_rect_) {
+ return;
+ }
+
+ SendScreenRects();
+}
+
+void RenderWidgetHostImpl::OnRequestMove(const gfx::Rect& pos) {
+ // Note that we ignore the position.
+ if (view_) {
+ view_->SetBounds(pos);
+ Send(new ViewMsg_Move_ACK(routing_id_));
+ }
+}
+
+void RenderWidgetHostImpl::OnPaintAtSizeAck(int tag, const gfx::Size& size) {
+ std::pair<int, gfx::Size> details = std::make_pair(tag, size);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK,
+ Source<RenderWidgetHost>(this),
+ Details<std::pair<int, gfx::Size> >(&details));
+}
+
+#if defined(OS_MACOSX)
+void RenderWidgetHostImpl::OnCompositorSurfaceBuffersSwapped(
+ const ViewHostMsg_CompositorSurfaceBuffersSwapped_Params& params) {
+ TRACE_EVENT0("renderer_host",
+ "RenderWidgetHostImpl::OnCompositorSurfaceBuffersSwapped");
+ if (!view_) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(params.route_id,
+ params.gpu_process_host_id,
+ ack_params);
+ return;
+ }
+ GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params gpu_params;
+ gpu_params.surface_id = params.surface_id;
+ gpu_params.surface_handle = params.surface_handle;
+ gpu_params.route_id = params.route_id;
+ gpu_params.size = params.size;
+ gpu_params.scale_factor = params.scale_factor;
+ gpu_params.latency_info = params.latency_info;
+ view_->AcceleratedSurfaceBuffersSwapped(gpu_params,
+ params.gpu_process_host_id);
+ view_->DidReceiveRendererFrame();
+}
+#endif // OS_MACOSX
+
+bool RenderWidgetHostImpl::OnSwapCompositorFrame(
+ const IPC::Message& message) {
+ ViewHostMsg_SwapCompositorFrame::Param param;
+ if (!ViewHostMsg_SwapCompositorFrame::Read(&message, &param))
+ return false;
+ scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame);
+ uint32 output_surface_id = param.a;
+ param.b.AssignTo(frame.get());
+
+ if (view_) {
+ view_->OnSwapCompositorFrame(output_surface_id, frame.Pass());
+ view_->DidReceiveRendererFrame();
+ } else {
+ cc::CompositorFrameAck ack;
+ if (frame->gl_frame_data) {
+ ack.gl_frame_data = frame->gl_frame_data.Pass();
+ ack.gl_frame_data->sync_point = 0;
+ } else if (frame->delegated_frame_data) {
+ ack.resources.swap(frame->delegated_frame_data->resource_list);
+ } else if (frame->software_frame_data) {
+ ack.last_software_frame_id = frame->software_frame_data->id;
+ }
+ SendSwapCompositorFrameAck(routing_id_, process_->GetID(),
+ output_surface_id, ack);
+ }
+ return true;
+}
+
+void RenderWidgetHostImpl::OnOverscrolled(
+ gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity) {
+ if (view_)
+ view_->OnOverscrolled(accumulated_overscroll, current_fling_velocity);
+}
+
+void RenderWidgetHostImpl::OnUpdateRect(
+ const ViewHostMsg_UpdateRect_Params& params) {
+ TRACE_EVENT0("renderer_host", "RenderWidgetHostImpl::OnUpdateRect");
+ TimeTicks paint_start = TimeTicks::Now();
+
+ // Update our knowledge of the RenderWidget's size.
+ current_size_ = params.view_size;
+ // Update our knowledge of the RenderWidget's scroll offset.
+ last_scroll_offset_ = params.scroll_offset;
+
+ bool is_resize_ack =
+ ViewHostMsg_UpdateRect_Flags::is_resize_ack(params.flags);
+
+ // resize_ack_pending_ needs to be cleared before we call DidPaintRect, since
+ // that will end up reaching GetBackingStore.
+ if (is_resize_ack) {
+ DCHECK(!g_check_for_pending_resize_ack || resize_ack_pending_);
+ resize_ack_pending_ = false;
+ }
+
+ bool is_repaint_ack =
+ ViewHostMsg_UpdateRect_Flags::is_repaint_ack(params.flags);
+ if (is_repaint_ack) {
+ DCHECK(repaint_ack_pending_);
+ TRACE_EVENT_ASYNC_END0(
+ "renderer_host", "RenderWidgetHostImpl::repaint_ack_pending_", this);
+ repaint_ack_pending_ = false;
+ TimeDelta delta = TimeTicks::Now() - repaint_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_RepaintDelta", delta);
+ }
+
+ DCHECK(!params.view_size.IsEmpty());
+
+ bool was_async = false;
+
+ // If this is a GPU UpdateRect, params.bitmap is invalid and dib will be NULL.
+ TransportDIB* dib = process_->GetTransportDIB(params.bitmap);
+
+ // If gpu process does painting, scroll_rect and copy_rects are always empty
+ // and backing store is never used.
+ if (dib) {
+ DCHECK(!params.bitmap_rect.IsEmpty());
+ gfx::Size pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(params.bitmap_rect.size(), params.scale_factor));
+ const size_t size = pixel_size.height() * pixel_size.width() * 4;
+ if (dib->size() < size) {
+ DLOG(WARNING) << "Transport DIB too small for given rectangle";
+ RecordAction(UserMetricsAction("BadMessageTerminate_RWH1"));
+ GetProcess()->ReceivedBadMessage();
+ } else {
+ // Scroll the backing store.
+ if (!params.scroll_rect.IsEmpty()) {
+ ScrollBackingStoreRect(params.scroll_delta,
+ params.scroll_rect,
+ params.view_size);
+ }
+
+ // Paint the backing store. This will update it with the
+ // renderer-supplied bits. The view will read out of the backing store
+ // later to actually draw to the screen.
+ was_async = PaintBackingStoreRect(
+ params.bitmap,
+ params.bitmap_rect,
+ params.copy_rects,
+ params.view_size,
+ params.scale_factor,
+ base::Bind(&RenderWidgetHostImpl::DidUpdateBackingStore,
+ weak_factory_.GetWeakPtr(), params, paint_start));
+ }
+ }
+
+ if (!was_async) {
+ DidUpdateBackingStore(params, paint_start);
+ }
+
+ if (should_auto_resize_) {
+ bool post_callback = new_auto_size_.IsEmpty();
+ new_auto_size_ = params.view_size;
+ if (post_callback) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RenderWidgetHostImpl::DelayedAutoResized,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+
+ // Log the time delta for processing a paint message. On platforms that don't
+ // support asynchronous painting, this is equivalent to
+ // MPArch.RWH_TotalPaintTime.
+ TimeDelta delta = TimeTicks::Now() - paint_start;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_OnMsgUpdateRect", delta);
+}
+
+void RenderWidgetHostImpl::OnUpdateIsDelayed() {
+ if (in_get_backing_store_)
+ abort_get_backing_store_ = true;
+}
+
+void RenderWidgetHostImpl::DidUpdateBackingStore(
+ const ViewHostMsg_UpdateRect_Params& params,
+ const TimeTicks& paint_start) {
+ TRACE_EVENT0("renderer_host", "RenderWidgetHostImpl::DidUpdateBackingStore");
+ TimeTicks update_start = TimeTicks::Now();
+
+ if (params.needs_ack) {
+ // ACK early so we can prefetch the next PaintRect if there is a next one.
+ // This must be done AFTER we're done painting with the bitmap supplied by
+ // the renderer. This ACK is a signal to the renderer that the backing store
+ // can be re-used, so the bitmap may be invalid after this call.
+ Send(new ViewMsg_UpdateRect_ACK(routing_id_));
+ }
+
+ // Move the plugins if the view hasn't already been destroyed. Plugin moves
+ // will not be re-issued, so must move them now, regardless of whether we
+ // paint or not. MovePluginWindows attempts to move the plugin windows and
+ // in the process could dispatch other window messages which could cause the
+ // view to be destroyed.
+ if (view_)
+ view_->MovePluginWindows(params.scroll_offset, params.plugin_window_moves);
+
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
+ Source<RenderWidgetHost>(this),
+ NotificationService::NoDetails());
+
+ // We don't need to update the view if the view is hidden. We must do this
+ // early return after the ACK is sent, however, or the renderer will not send
+ // us more data.
+ if (is_hidden_)
+ return;
+
+ // Now paint the view. Watch out: it might be destroyed already.
+ if (view_ && !is_accelerated_compositing_active_) {
+ view_being_painted_ = true;
+ view_->DidUpdateBackingStore(params.scroll_rect, params.scroll_delta,
+ params.copy_rects, params.latency_info);
+ view_->DidReceiveRendererFrame();
+ view_being_painted_ = false;
+ }
+
+ // If we got a resize ack, then perhaps we have another resize to send?
+ bool is_resize_ack =
+ ViewHostMsg_UpdateRect_Flags::is_resize_ack(params.flags);
+ if (is_resize_ack)
+ WasResized();
+
+ // Log the time delta for processing a paint message.
+ TimeTicks now = TimeTicks::Now();
+ TimeDelta delta = now - update_start;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_DidUpdateBackingStore", delta);
+
+ // Measures the time from receiving the MsgUpdateRect IPC to completing the
+ // DidUpdateBackingStore() method. On platforms which have asynchronous
+ // painting, such as Linux, this is the sum of MPArch.RWH_OnMsgUpdateRect,
+ // MPArch.RWH_DidUpdateBackingStore, and the time spent asynchronously
+ // waiting for the paint to complete.
+ //
+ // On other platforms, this will be equivalent to MPArch.RWH_OnMsgUpdateRect.
+ delta = now - paint_start;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TotalPaintTime", delta);
+}
+
+void RenderWidgetHostImpl::OnBeginSmoothScroll(
+ const ViewHostMsg_BeginSmoothScroll_Params& params) {
+ if (!view_)
+ return;
+ smooth_scroll_gesture_controller_.BeginSmoothScroll(view_, params);
+}
+
+void RenderWidgetHostImpl::OnFocus() {
+ // Only RenderViewHost can deal with that message.
+ RecordAction(UserMetricsAction("BadMessageTerminate_RWH4"));
+ GetProcess()->ReceivedBadMessage();
+}
+
+void RenderWidgetHostImpl::OnBlur() {
+ // Only RenderViewHost can deal with that message.
+ RecordAction(UserMetricsAction("BadMessageTerminate_RWH5"));
+ GetProcess()->ReceivedBadMessage();
+}
+
+void RenderWidgetHostImpl::OnSetCursor(const WebCursor& cursor) {
+ if (!view_) {
+ return;
+ }
+ view_->UpdateCursor(cursor);
+}
+
+void RenderWidgetHostImpl::OnTextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ if (view_)
+ view_->TextInputTypeChanged(type, can_compose_inline, input_mode);
+}
+
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+void RenderWidgetHostImpl::OnImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+ if (view_)
+ view_->ImeCompositionRangeChanged(range, character_bounds);
+}
+#endif
+
+void RenderWidgetHostImpl::OnImeCancelComposition() {
+ if (view_)
+ view_->ImeCancelComposition();
+}
+
+void RenderWidgetHostImpl::OnDidActivateAcceleratedCompositing(bool activated) {
+ TRACE_EVENT1("renderer_host",
+ "RenderWidgetHostImpl::OnDidActivateAcceleratedCompositing",
+ "activated", activated);
+ is_accelerated_compositing_active_ = activated;
+ if (view_)
+ view_->OnAcceleratedCompositingStateChange();
+}
+
+void RenderWidgetHostImpl::OnLockMouse(bool user_gesture,
+ bool last_unlocked_by_target,
+ bool privileged) {
+
+ if (pending_mouse_lock_request_) {
+ Send(new ViewMsg_LockMouse_ACK(routing_id_, false));
+ return;
+ } else if (IsMouseLocked()) {
+ Send(new ViewMsg_LockMouse_ACK(routing_id_, true));
+ return;
+ }
+
+ pending_mouse_lock_request_ = true;
+ if (privileged && allow_privileged_mouse_lock_) {
+ // Directly approve to lock the mouse.
+ GotResponseToLockMouseRequest(true);
+ } else {
+ RequestToLockMouse(user_gesture, last_unlocked_by_target);
+ }
+}
+
+void RenderWidgetHostImpl::OnUnlockMouse() {
+ RejectMouseLockOrUnlockIfNecessary();
+}
+
+void RenderWidgetHostImpl::OnShowDisambiguationPopup(
+ const gfx::Rect& rect,
+ const gfx::Size& size,
+ const TransportDIB::Id& id) {
+ DCHECK(!rect.IsEmpty());
+ DCHECK(!size.IsEmpty());
+
+ TransportDIB* dib = process_->GetTransportDIB(id);
+ DCHECK(dib->memory());
+ DCHECK(dib->size() == SkBitmap::ComputeSize(SkBitmap::kARGB_8888_Config,
+ size.width(), size.height()));
+
+ SkBitmap zoomed_bitmap;
+ zoomed_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ size.width(), size.height());
+ zoomed_bitmap.setPixels(dib->memory());
+
+#if defined(OS_ANDROID)
+ if (view_)
+ view_->ShowDisambiguationPopup(rect, zoomed_bitmap);
+#else
+ NOTIMPLEMENTED();
+#endif
+
+ zoomed_bitmap.setPixels(0);
+ Send(new ViewMsg_ReleaseDisambiguationPopupDIB(GetRoutingID(),
+ dib->handle()));
+}
+
+#if defined(OS_WIN)
+void RenderWidgetHostImpl::OnWindowlessPluginDummyWindowCreated(
+ gfx::NativeViewId dummy_activation_window) {
+ HWND hwnd = reinterpret_cast<HWND>(dummy_activation_window);
+
+ // This may happen as a result of a race condition when the plugin is going
+ // away.
+ wchar_t window_title[MAX_PATH + 1] = {0};
+ if (!IsWindow(hwnd) ||
+ !GetWindowText(hwnd, window_title, arraysize(window_title)) ||
+ lstrcmpiW(window_title, kDummyActivationWindowName) != 0) {
+ return;
+ }
+
+ SetParent(hwnd, reinterpret_cast<HWND>(GetNativeViewId()));
+ dummy_windows_for_activation_.push_back(hwnd);
+}
+
+void RenderWidgetHostImpl::OnWindowlessPluginDummyWindowDestroyed(
+ gfx::NativeViewId dummy_activation_window) {
+ HWND hwnd = reinterpret_cast<HWND>(dummy_activation_window);
+ std::list<HWND>::iterator i = dummy_windows_for_activation_.begin();
+ for (; i != dummy_windows_for_activation_.end(); ++i) {
+ if ((*i) == hwnd) {
+ dummy_windows_for_activation_.erase(i);
+ return;
+ }
+ }
+ NOTREACHED() << "Unknown dummy window";
+}
+#endif
+
+bool RenderWidgetHostImpl::PaintBackingStoreRect(
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ const gfx::Size& view_size,
+ float scale_factor,
+ const base::Closure& completion_callback) {
+ // The view may be destroyed already.
+ if (!view_)
+ return false;
+
+ if (is_hidden_) {
+ // Don't bother updating the backing store when we're hidden. Just mark it
+ // as being totally invalid. This will cause a complete repaint when the
+ // view is restored.
+ needs_repainting_on_restore_ = true;
+ return false;
+ }
+
+ bool needs_full_paint = false;
+ bool scheduled_completion_callback = false;
+ BackingStoreManager::PrepareBackingStore(this, view_size, bitmap, bitmap_rect,
+ copy_rects, scale_factor,
+ completion_callback,
+ &needs_full_paint,
+ &scheduled_completion_callback);
+ if (needs_full_paint) {
+ repaint_start_time_ = TimeTicks::Now();
+ DCHECK(!repaint_ack_pending_);
+ repaint_ack_pending_ = true;
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "renderer_host", "RenderWidgetHostImpl::repaint_ack_pending_", this);
+ Send(new ViewMsg_Repaint(routing_id_, view_size));
+ }
+
+ return scheduled_completion_callback;
+}
+
+void RenderWidgetHostImpl::ScrollBackingStoreRect(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+ if (is_hidden_) {
+ // Don't bother updating the backing store when we're hidden. Just mark it
+ // as being totally invalid. This will cause a complete repaint when the
+ // view is restored.
+ needs_repainting_on_restore_ = true;
+ return;
+ }
+
+ // TODO(darin): do we need to do something else if our backing store is not
+ // the same size as the advertised view? maybe we just assume there is a
+ // full paint on its way?
+ BackingStore* backing_store = BackingStoreManager::Lookup(this);
+ if (!backing_store || (backing_store->size() != view_size))
+ return;
+ backing_store->ScrollBackingStore(delta, clip_rect, view_size);
+}
+
+void RenderWidgetHostImpl::Replace(const string16& word) {
+ Send(new InputMsg_Replace(routing_id_, word));
+}
+
+void RenderWidgetHostImpl::ReplaceMisspelling(const string16& word) {
+ Send(new InputMsg_ReplaceMisspelling(routing_id_, word));
+}
+
+void RenderWidgetHostImpl::SetIgnoreInputEvents(bool ignore_input_events) {
+ ignore_input_events_ = ignore_input_events;
+}
+
+bool RenderWidgetHostImpl::KeyPressListenersHandleEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (event.skip_in_browser || event.type != WebKeyboardEvent::RawKeyDown)
+ return false;
+
+ ObserverList<KeyboardListener>::Iterator it(keyboard_listeners_);
+ KeyboardListener* listener;
+ while ((listener = it.GetNext()) != NULL) {
+ if (listener->HandleKeyPressEvent(event))
+ return true;
+ }
+
+ return false;
+}
+
+InputEventAckState RenderWidgetHostImpl::FilterInputEvent(
+ const WebKit::WebInputEvent& event, const ui::LatencyInfo& latency_info) {
+ if (overscroll_controller() &&
+ !overscroll_controller()->WillDispatchEvent(event, latency_info)) {
+ return INPUT_EVENT_ACK_STATE_UNKNOWN;
+ }
+
+ return view_ ? view_->FilterInputEvent(event)
+ : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
+}
+
+void RenderWidgetHostImpl::IncrementInFlightEventCount() {
+ StartHangMonitorTimeout(
+ TimeDelta::FromMilliseconds(hung_renderer_delay_ms_));
+ increment_in_flight_event_count();
+}
+
+void RenderWidgetHostImpl::DecrementInFlightEventCount() {
+ DCHECK(in_flight_event_count_ >= 0);
+ // Cancel pending hung renderer checks since the renderer is responsive.
+ if (decrement_in_flight_event_count() <= 0)
+ StopHangMonitorTimeout();
+}
+
+void RenderWidgetHostImpl::OnHasTouchEventHandlers(bool has_handlers) {
+ if (has_touch_handler_ == has_handlers)
+ return;
+ has_touch_handler_ = has_handlers;
+#if defined(OS_ANDROID)
+ if (view_)
+ view_->HasTouchEventHandlers(has_touch_handler_);
+#endif
+}
+
+bool RenderWidgetHostImpl::OnSendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info,
+ bool* is_shortcut) {
+ if (IgnoreInputEvents())
+ return false;
+
+ if (!process_->HasConnection())
+ return false;
+
+ // First, let keypress listeners take a shot at handling the event. If a
+ // listener handles the event, it should not be propagated to the renderer.
+ if (KeyPressListenersHandleEvent(key_event)) {
+ // Some keypresses that are accepted by the listener might have follow up
+ // char events, which should be ignored.
+ if (key_event.type == WebKeyboardEvent::RawKeyDown)
+ suppress_next_char_events_ = true;
+ return false;
+ }
+
+ if (key_event.type == WebKeyboardEvent::Char &&
+ (key_event.windowsKeyCode == ui::VKEY_RETURN ||
+ key_event.windowsKeyCode == ui::VKEY_SPACE)) {
+ OnUserGesture();
+ }
+
+ // Double check the type to make sure caller hasn't sent us nonsense that
+ // will mess up our key queue.
+ if (!WebInputEvent::isKeyboardEventType(key_event.type))
+ return false;
+
+ if (suppress_next_char_events_) {
+ // If preceding RawKeyDown event was handled by the browser, then we need
+ // suppress all Char events generated by it. Please note that, one
+ // RawKeyDown event may generate multiple Char events, so we can't reset
+ // |suppress_next_char_events_| until we get a KeyUp or a RawKeyDown.
+ if (key_event.type == WebKeyboardEvent::Char)
+ return false;
+ // We get a KeyUp or a RawKeyDown event.
+ suppress_next_char_events_ = false;
+ }
+
+ // Only pre-handle the key event if it's not handled by the input method.
+ if (delegate_ && !key_event.skip_in_browser) {
+ // We need to set |suppress_next_char_events_| to true if
+ // PreHandleKeyboardEvent() returns true, but |this| may already be
+ // destroyed at that time. So set |suppress_next_char_events_| true here,
+ // then revert it afterwards when necessary.
+ if (key_event.type == WebKeyboardEvent::RawKeyDown)
+ suppress_next_char_events_ = true;
+
+ // Tab switching/closing accelerators aren't sent to the renderer to avoid
+ // a hung/malicious renderer from interfering.
+ if (delegate_->PreHandleKeyboardEvent(key_event, is_shortcut))
+ return false;
+
+ if (key_event.type == WebKeyboardEvent::RawKeyDown)
+ suppress_next_char_events_ = false;
+ }
+
+ return true;
+}
+
+bool RenderWidgetHostImpl::OnSendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) {
+ if (IgnoreInputEvents())
+ return false;
+
+ if (delegate_->PreHandleWheelEvent(wheel_event.event))
+ return false;
+
+ return true;
+}
+
+bool RenderWidgetHostImpl::OnSendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) {
+ if (IgnoreInputEvents())
+ return false;
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSimulateTouchScreenWithMouse)) {
+ SimulateTouchGestureWithMouse(mouse_event.event);
+ return false;
+ }
+
+ return true;
+}
+
+bool RenderWidgetHostImpl::OnSendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) {
+ return !IgnoreInputEvents();
+}
+
+bool RenderWidgetHostImpl::OnSendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ if (IgnoreInputEvents())
+ return false;
+
+ if (!IsInOverscrollGesture() &&
+ !input_router_->ShouldForwardGestureEvent(gesture_event)) {
+ if (overscroll_controller_.get())
+ overscroll_controller_->DiscardingGestureEvent(gesture_event.event);
+ return false;
+ }
+
+ return true;
+}
+
+bool RenderWidgetHostImpl::OnSendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) {
+ TRACE_EVENT_INSTANT0("input",
+ "RenderWidgetHostImpl::OnSendMouseEventImmediately",
+ TRACE_EVENT_SCOPE_THREAD);
+ if (IgnoreInputEvents())
+ return false;
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSimulateTouchScreenWithMouse)) {
+ SimulateTouchGestureWithMouse(mouse_event.event);
+ return false;
+ }
+
+ if (mouse_event.event.type == WebInputEvent::MouseDown)
+ OnUserGesture();
+
+ return true;
+}
+
+bool RenderWidgetHostImpl::OnSendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) {
+ TRACE_EVENT_INSTANT0("input",
+ "RenderWidgetHostImpl::OnSendTouchEventImmediately",
+ TRACE_EVENT_SCOPE_THREAD);
+ return !IgnoreInputEvents();
+}
+
+bool RenderWidgetHostImpl::OnSendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) {
+ TRACE_EVENT_INSTANT0("input",
+ "RenderWidgetHostImpl::OnSendGestureEventImmediately",
+ TRACE_EVENT_SCOPE_THREAD);
+ return !IgnoreInputEvents();
+}
+
+void RenderWidgetHostImpl::OnKeyboardEventAck(
+ const NativeWebKeyboardEvent& event,
+ InputEventAckState ack_result) {
+#if defined(OS_MACOSX)
+ if (!is_hidden() && view_ && view_->PostProcessEventForPluginIme(event))
+ return;
+#endif
+
+ // We only send unprocessed key event upwards if we are not hidden,
+ // because the user has moved away from us and no longer expect any effect
+ // of this key event.
+ const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
+ if (delegate_ && !processed && !is_hidden() && !event.skip_in_browser) {
+ delegate_->HandleKeyboardEvent(event);
+
+ // WARNING: This RenderWidgetHostImpl can be deallocated at this point
+ // (i.e. in the case of Ctrl+W, where the call to
+ // HandleKeyboardEvent destroys this RenderWidgetHostImpl).
+ }
+}
+
+void RenderWidgetHostImpl::OnWheelEventAck(
+ const WebKit::WebMouseWheelEvent& wheel_event,
+ InputEventAckState ack_result) {
+ const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
+ if (overscroll_controller_)
+ overscroll_controller_->ReceivedEventACK(wheel_event, processed);
+
+ if (!processed && !is_hidden() && view_)
+ view_->UnhandledWheelEvent(wheel_event);
+}
+
+void RenderWidgetHostImpl::OnGestureEventAck(
+ const WebKit::WebGestureEvent& event,
+ InputEventAckState ack_result) {
+ const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
+ if (overscroll_controller_)
+ overscroll_controller_->ReceivedEventACK(event, processed);
+
+ if (view_)
+ view_->GestureEventAck(event.type, ack_result);
+}
+
+void RenderWidgetHostImpl::OnTouchEventAck(
+ const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) {
+ ComputeTouchLatency(event.latency);
+ if (view_)
+ view_->ProcessAckedTouchEvent(event, ack_result);
+}
+
+void RenderWidgetHostImpl::OnUnexpectedEventAck(bool bad_message) {
+ if (bad_message) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_RWH2"));
+ process_->ReceivedBadMessage();
+ }
+
+ suppress_next_char_events_ = false;
+}
+
+const gfx::Vector2d& RenderWidgetHostImpl::GetLastScrollOffset() const {
+ return last_scroll_offset_;
+}
+
+bool RenderWidgetHostImpl::IgnoreInputEvents() const {
+ return ignore_input_events_ || process_->IgnoreInputEvents();
+}
+
+bool RenderWidgetHostImpl::ShouldForwardTouchEvent() const {
+ return input_router_->ShouldForwardTouchEvent();
+}
+
+bool RenderWidgetHostImpl::ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const {
+ return input_router_->ShouldForwardGestureEvent(gesture_event);
+}
+
+bool RenderWidgetHostImpl::HasQueuedGestureEvents() const {
+ return input_router_->HasQueuedGestureEvents();
+}
+
+void RenderWidgetHostImpl::StartUserGesture() {
+ OnUserGesture();
+}
+
+void RenderWidgetHostImpl::Stop() {
+ Send(new ViewMsg_Stop(GetRoutingID()));
+}
+
+void RenderWidgetHostImpl::SetBackground(const SkBitmap& background) {
+ Send(new ViewMsg_SetBackground(GetRoutingID(), background));
+}
+
+void RenderWidgetHostImpl::SetEditCommandsForNextKeyEvent(
+ const std::vector<EditCommand>& commands) {
+ Send(new InputMsg_SetEditCommandsForNextKeyEvent(GetRoutingID(), commands));
+}
+
+void RenderWidgetHostImpl::SetAccessibilityMode(AccessibilityMode mode) {
+ accessibility_mode_ = mode;
+ Send(new ViewMsg_SetAccessibilityMode(GetRoutingID(), mode));
+}
+
+void RenderWidgetHostImpl::AccessibilityDoDefaultAction(int object_id) {
+ Send(new AccessibilityMsg_DoDefaultAction(GetRoutingID(), object_id));
+}
+
+void RenderWidgetHostImpl::AccessibilitySetFocus(int object_id) {
+ Send(new AccessibilityMsg_SetFocus(GetRoutingID(), object_id));
+}
+
+void RenderWidgetHostImpl::AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) {
+ Send(new AccessibilityMsg_ScrollToMakeVisible(
+ GetRoutingID(), acc_obj_id, subfocus));
+}
+
+void RenderWidgetHostImpl::AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) {
+ Send(new AccessibilityMsg_ScrollToPoint(
+ GetRoutingID(), acc_obj_id, point));
+}
+
+void RenderWidgetHostImpl::AccessibilitySetTextSelection(
+ int object_id, int start_offset, int end_offset) {
+ Send(new AccessibilityMsg_SetTextSelection(
+ GetRoutingID(), object_id, start_offset, end_offset));
+}
+
+void RenderWidgetHostImpl::FatalAccessibilityTreeError() {
+ Send(new AccessibilityMsg_FatalError(GetRoutingID()));
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+void RenderWidgetHostImpl::SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) {
+ if (view_)
+ view_->SetParentNativeViewAccessible(accessible_parent);
+}
+
+gfx::NativeViewAccessible
+RenderWidgetHostImpl::GetParentNativeViewAccessible() const {
+ return delegate_->GetParentNativeViewAccessible();
+}
+#endif
+
+void RenderWidgetHostImpl::ExecuteEditCommand(const std::string& command,
+ const std::string& value) {
+ Send(new InputMsg_ExecuteEditCommand(GetRoutingID(), command, value));
+}
+
+void RenderWidgetHostImpl::ScrollFocusedEditableNodeIntoRect(
+ const gfx::Rect& rect) {
+ Send(new InputMsg_ScrollFocusedEditableNodeIntoRect(GetRoutingID(), rect));
+}
+
+void RenderWidgetHostImpl::SelectRange(const gfx::Point& start,
+ const gfx::Point& end) {
+ Send(new InputMsg_SelectRange(GetRoutingID(), start, end));
+}
+
+void RenderWidgetHostImpl::MoveCaret(const gfx::Point& point) {
+ Send(new InputMsg_MoveCaret(GetRoutingID(), point));
+}
+
+void RenderWidgetHostImpl::Undo() {
+ Send(new InputMsg_Undo(GetRoutingID()));
+ RecordAction(UserMetricsAction("Undo"));
+}
+
+void RenderWidgetHostImpl::Redo() {
+ Send(new InputMsg_Redo(GetRoutingID()));
+ RecordAction(UserMetricsAction("Redo"));
+}
+
+void RenderWidgetHostImpl::Cut() {
+ Send(new InputMsg_Cut(GetRoutingID()));
+ RecordAction(UserMetricsAction("Cut"));
+}
+
+void RenderWidgetHostImpl::Copy() {
+ Send(new InputMsg_Copy(GetRoutingID()));
+ RecordAction(UserMetricsAction("Copy"));
+}
+
+void RenderWidgetHostImpl::CopyToFindPboard() {
+#if defined(OS_MACOSX)
+ // Windows/Linux don't have the concept of a find pasteboard.
+ Send(new InputMsg_CopyToFindPboard(GetRoutingID()));
+ RecordAction(UserMetricsAction("CopyToFindPboard"));
+#endif
+}
+
+void RenderWidgetHostImpl::Paste() {
+ Send(new InputMsg_Paste(GetRoutingID()));
+ RecordAction(UserMetricsAction("Paste"));
+}
+
+void RenderWidgetHostImpl::PasteAndMatchStyle() {
+ Send(new InputMsg_PasteAndMatchStyle(GetRoutingID()));
+ RecordAction(UserMetricsAction("PasteAndMatchStyle"));
+}
+
+void RenderWidgetHostImpl::Delete() {
+ Send(new InputMsg_Delete(GetRoutingID()));
+ RecordAction(UserMetricsAction("DeleteSelection"));
+}
+
+void RenderWidgetHostImpl::SelectAll() {
+ Send(new InputMsg_SelectAll(GetRoutingID()));
+ RecordAction(UserMetricsAction("SelectAll"));
+}
+
+void RenderWidgetHostImpl::Unselect() {
+ Send(new InputMsg_Unselect(GetRoutingID()));
+ RecordAction(UserMetricsAction("Unselect"));
+}
+
+bool RenderWidgetHostImpl::GotResponseToLockMouseRequest(bool allowed) {
+ if (!allowed) {
+ RejectMouseLockOrUnlockIfNecessary();
+ return false;
+ } else {
+ if (!pending_mouse_lock_request_) {
+ // This is possible, e.g., the plugin sends us an unlock request before
+ // the user allows to lock to mouse.
+ return false;
+ }
+
+ pending_mouse_lock_request_ = false;
+ if (!view_ || !view_->HasFocus()|| !view_->LockMouse()) {
+ Send(new ViewMsg_LockMouse_ACK(routing_id_, false));
+ return false;
+ } else {
+ Send(new ViewMsg_LockMouse_ACK(routing_id_, true));
+ return true;
+ }
+ }
+}
+
+// static
+void RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ int32 route_id, int gpu_host_id,
+ const AcceleratedSurfaceMsg_BufferPresented_Params& params) {
+ GpuProcessHostUIShim* ui_shim = GpuProcessHostUIShim::FromID(gpu_host_id);
+ if (ui_shim) {
+ ui_shim->Send(new AcceleratedSurfaceMsg_BufferPresented(route_id,
+ params));
+ }
+}
+
+// static
+void RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ int32 route_id,
+ uint32 output_surface_id,
+ int renderer_host_id,
+ const cc::CompositorFrameAck& ack) {
+ RenderProcessHost* host = RenderProcessHost::FromID(renderer_host_id);
+ if (!host)
+ return;
+ host->Send(new ViewMsg_SwapCompositorFrameAck(
+ route_id, output_surface_id, ack));
+}
+
+void RenderWidgetHostImpl::AcknowledgeSwapBuffersToRenderer() {
+ if (!is_threaded_compositing_enabled_)
+ Send(new ViewMsg_SwapBuffers_ACK(routing_id_));
+}
+
+#if defined(USE_AURA)
+
+void RenderWidgetHostImpl::ParentChanged(gfx::NativeViewId new_parent) {
+#if defined(OS_WIN)
+ HWND hwnd = reinterpret_cast<HWND>(new_parent);
+ if (!hwnd)
+ hwnd = GetDesktopWindow();
+ for (std::list<HWND>::iterator i = dummy_windows_for_activation_.begin();
+ i != dummy_windows_for_activation_.end(); ++i) {
+ SetParent(*i, hwnd);
+ }
+#endif
+}
+
+#endif
+
+void RenderWidgetHostImpl::DelayedAutoResized() {
+ gfx::Size new_size = new_auto_size_;
+ // Clear the new_auto_size_ since the empty value is used as a flag to
+ // indicate that no callback is in progress (i.e. without this line
+ // DelayedAutoResized will not get called again).
+ new_auto_size_.SetSize(0, 0);
+ if (!should_auto_resize_)
+ return;
+
+ OnRenderAutoResized(new_size);
+}
+
+void RenderWidgetHostImpl::DetachDelegate() {
+ delegate_ = NULL;
+}
+
+void RenderWidgetHostImpl::ComputeTouchLatency(
+ const ui::LatencyInfo& latency_info) {
+ ui::LatencyInfo::LatencyComponent ui_component;
+ ui::LatencyInfo::LatencyComponent rwh_component;
+ ui::LatencyInfo::LatencyComponent acked_component;
+
+ if (!latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_UI_COMPONENT,
+ 0,
+ &ui_component) ||
+ !latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_RWH_COMPONENT,
+ GetLatencyComponentId(),
+ &rwh_component))
+ return;
+
+ DCHECK(ui_component.event_count == 1);
+ DCHECK(rwh_component.event_count == 1);
+
+ base::TimeDelta ui_delta =
+ rwh_component.event_time - ui_component.event_time;
+ rendering_stats_.touch_ui_count++;
+ rendering_stats_.total_touch_ui_latency += ui_delta;
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Event.Latency.Browser.TouchUI",
+ ui_delta.InMicroseconds(),
+ 0,
+ 20000,
+ 100);
+
+ if (latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_ACKED_COMPONENT,
+ 0,
+ &acked_component)) {
+ DCHECK(acked_component.event_count == 1);
+ base::TimeDelta acked_delta =
+ acked_component.event_time - rwh_component.event_time;
+ rendering_stats_.touch_acked_count++;
+ rendering_stats_.total_touch_acked_latency += acked_delta;
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Event.Latency.Browser.TouchAcked",
+ acked_delta.InMicroseconds(),
+ 0,
+ 1000000,
+ 100);
+ }
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableGpuBenchmarking))
+ Send(new ViewMsg_SetBrowserRenderingStats(routing_id_, rendering_stats_));
+}
+
+void RenderWidgetHostImpl::FrameSwapped(const ui::LatencyInfo& latency_info) {
+ ui::LatencyInfo::LatencyComponent rwh_component;
+ if (!latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_RWH_COMPONENT,
+ GetLatencyComponentId(),
+ &rwh_component))
+ return;
+
+ rendering_stats_.input_event_count += rwh_component.event_count;
+ rendering_stats_.total_input_latency +=
+ rwh_component.event_count *
+ (latency_info.swap_timestamp - rwh_component.event_time);
+
+ ui::LatencyInfo::LatencyComponent original_component;
+ if (latency_info.FindLatency(
+ ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+ GetLatencyComponentId(),
+ &original_component)) {
+ // This UMA metric tracks the time from when the original touch event is
+ // created (averaged if there are multiple) to when the scroll gesture
+ // results in final frame swap.
+ base::TimeDelta delta =
+ latency_info.swap_timestamp - original_component.event_time;
+ for (size_t i = 0; i < original_component.event_count; i++) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Event.Latency.TouchToScrollUpdateSwap",
+ delta.InMicroseconds(),
+ 0,
+ 1000000,
+ 100);
+ }
+ rendering_stats_.scroll_update_count += original_component.event_count;
+ rendering_stats_.total_scroll_update_latency +=
+ original_component.event_count *
+ (latency_info.swap_timestamp - original_component.event_time);
+ }
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableGpuBenchmarking))
+ Send(new ViewMsg_SetBrowserRenderingStats(routing_id_, rendering_stats_));
+}
+
+void RenderWidgetHostImpl::DidReceiveRendererFrame() {
+ view_->DidReceiveRendererFrame();
+}
+
+// static
+void RenderWidgetHostImpl::CompositorFrameDrawn(
+ const ui::LatencyInfo& latency_info) {
+ for (ui::LatencyInfo::LatencyMap::const_iterator b =
+ latency_info.latency_components.begin();
+ b != latency_info.latency_components.end();
+ ++b) {
+ if (b->first.first != ui::INPUT_EVENT_LATENCY_RWH_COMPONENT)
+ continue;
+ // Matches with GetLatencyComponentId
+ int routing_id = b->first.second & 0xffffffff;
+ int process_id = (b->first.second >> 32) & 0xffffffff;
+ RenderWidgetHost* rwh =
+ RenderWidgetHost::FromID(process_id, routing_id);
+ if (!rwh)
+ continue;
+ RenderWidgetHostImpl::From(rwh)->FrameSwapped(latency_info);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_impl.h b/chromium/content/browser/renderer_host/render_widget_host_impl.h
new file mode 100644
index 00000000000..362c6d7f793
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_impl.h
@@ -0,0 +1,925 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_IMPL_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/process/kill.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/input/input_router_client.h"
+#include "content/browser/renderer_host/smooth_scroll_gesture_controller.h"
+#include "content/common/browser_rendering_stats.h"
+#include "content/common/view_message_enums.h"
+#include "content/port/browser/event_with_latency_info.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/common/page_zoom.h"
+#include "ipc/ipc_listener.h"
+#include "ui/base/ime/text_input_mode.h"
+#include "ui/base/ime/text_input_type.h"
+#include "ui/base/latency_info.h"
+#include "ui/gfx/native_widget_types.h"
+
+class WebCursor;
+struct AcceleratedSurfaceMsg_BufferPresented_Params;
+struct ViewHostMsg_CompositorSurfaceBuffersSwapped_Params;
+struct ViewHostMsg_UpdateRect_Params;
+struct ViewHostMsg_TextInputState_Params;
+struct ViewHostMsg_BeginSmoothScroll_Params;
+
+namespace base {
+class TimeTicks;
+}
+
+namespace cc {
+class CompositorFrame;
+class CompositorFrameAck;
+}
+
+namespace ui {
+class KeyEvent;
+class Range;
+}
+
+namespace WebKit {
+class WebInputEvent;
+class WebMouseEvent;
+struct WebCompositionUnderline;
+struct WebScreenInfo;
+}
+
+#if defined(OS_ANDROID)
+namespace WebKit {
+class WebLayer;
+}
+#endif
+
+namespace content {
+class BackingStore;
+class InputRouter;
+class MockRenderWidgetHost;
+class OverscrollController;
+class RenderWidgetHostDelegate;
+class RenderWidgetHostViewPort;
+class SmoothScrollGestureController;
+struct EditCommand;
+
+// This implements the RenderWidgetHost interface that is exposed to
+// embedders of content, and adds things only visible to content.
+class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
+ public InputRouterClient,
+ public IPC::Listener {
+ public:
+ // routing_id can be MSG_ROUTING_NONE, in which case the next available
+ // routing id is taken from the RenderProcessHost.
+ // If this object outlives |delegate|, DetachDelegate() must be called when
+ // |delegate| goes away.
+ RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id);
+ virtual ~RenderWidgetHostImpl();
+
+ // Similar to RenderWidgetHost::FromID, but returning the Impl object.
+ static RenderWidgetHostImpl* FromID(int32 process_id, int32 routing_id);
+
+ // Returns all RenderWidgetHosts including swapped out ones for
+ // internal use. The public interface
+ // RendgerWidgetHost::GetRenderWidgetHosts only returns active ones.
+ // Keep in mind that there may be dependencies between these
+ // widgets. If a caller indirectly causes one of the widgets to be
+ // deleted while iterating over the list, the deleted widget will
+ // stay in the list and possibly causes a use-after-free. Take care
+ // to avoid deleting widgets as you iterate (e.g., see
+ // http://crbug.com/259859). TODO(nasko): Improve this interface to
+ // better prevent UaFs.
+ static std::vector<RenderWidgetHost*> GetAllRenderWidgetHosts();
+
+ // Use RenderWidgetHostImpl::From(rwh) to downcast a
+ // RenderWidgetHost to a RenderWidgetHostImpl. Internally, this
+ // uses RenderWidgetHost::AsRenderWidgetHostImpl().
+ static RenderWidgetHostImpl* From(RenderWidgetHost* rwh);
+
+ void set_hung_renderer_delay_ms(const base::TimeDelta& timeout) {
+ hung_renderer_delay_ms_ = timeout.InMilliseconds();
+ }
+
+ // RenderWidgetHost implementation.
+ virtual void Undo() OVERRIDE;
+ virtual void Redo() OVERRIDE;
+ virtual void Cut() OVERRIDE;
+ virtual void Copy() OVERRIDE;
+ virtual void CopyToFindPboard() OVERRIDE;
+ virtual void Paste() OVERRIDE;
+ virtual void PasteAndMatchStyle() OVERRIDE;
+ virtual void Delete() OVERRIDE;
+ virtual void SelectAll() OVERRIDE;
+ virtual void Unselect() OVERRIDE;
+ virtual void UpdateTextDirection(WebKit::WebTextDirection direction) OVERRIDE;
+ virtual void NotifyTextDirection() OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void SetActive(bool active) OVERRIDE;
+ virtual void CopyFromBackingStore(
+ const gfx::Rect& src_rect,
+ const gfx::Size& accelerated_dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+#if defined(TOOLKIT_GTK)
+ virtual bool CopyFromBackingStoreToGtkWindow(const gfx::Rect& dest_rect,
+ GdkWindow* target) OVERRIDE;
+#elif defined(OS_MACOSX)
+ virtual gfx::Size GetBackingStoreSize() OVERRIDE;
+ virtual bool CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
+ CGContextRef target) OVERRIDE;
+#endif
+ virtual void EnableFullAccessibilityMode() OVERRIDE;
+ virtual void ForwardMouseEvent(
+ const WebKit::WebMouseEvent& mouse_event) OVERRIDE;
+ virtual void ForwardWheelEvent(
+ const WebKit::WebMouseWheelEvent& wheel_event) OVERRIDE;
+ virtual void ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event) OVERRIDE;
+ virtual const gfx::Vector2d& GetLastScrollOffset() const OVERRIDE;
+ virtual RenderProcessHost* GetProcess() const OVERRIDE;
+ virtual int GetRoutingID() const OVERRIDE;
+ virtual RenderWidgetHostView* GetView() const OVERRIDE;
+ virtual bool IsLoading() const OVERRIDE;
+ virtual bool IsRenderView() const OVERRIDE;
+ virtual void PaintAtSize(TransportDIB::Handle dib_handle,
+ int tag,
+ const gfx::Size& page_size,
+ const gfx::Size& desired_size) OVERRIDE;
+ virtual void Replace(const string16& word) OVERRIDE;
+ virtual void ReplaceMisspelling(const string16& word) OVERRIDE;
+ virtual void ResizeRectChanged(const gfx::Rect& new_rect) OVERRIDE;
+ virtual void RestartHangMonitorTimeout() OVERRIDE;
+ virtual void SetIgnoreInputEvents(bool ignore_input_events) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void WasResized() OVERRIDE;
+ virtual void AddKeyboardListener(KeyboardListener* listener) OVERRIDE;
+ virtual void RemoveKeyboardListener(KeyboardListener* listener) OVERRIDE;
+ virtual void GetWebScreenInfo(WebKit::WebScreenInfo* result) OVERRIDE;
+ virtual void GetSnapshotFromRenderer(
+ const gfx::Rect& src_subrect,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+
+ const NativeWebKeyboardEvent* GetLastKeyboardEvent() const;
+
+ // Notification that the screen info has changed.
+ void NotifyScreenInfoChanged();
+
+ // Invalidates the cached screen info so that next resize request
+ // will carry the up to date screen info. Unlike
+ // |NotifyScreenInfoChanged|, this doesn't send a message to the renderer.
+ void InvalidateScreenInfo();
+
+ // Sets the View of this RenderWidgetHost.
+ void SetView(RenderWidgetHostView* view);
+
+ int surface_id() const { return surface_id_; }
+
+ bool empty() const { return current_size_.IsEmpty(); }
+
+ // Called when a renderer object already been created for this host, and we
+ // just need to be attached to it. Used for window.open, <select> dropdown
+ // menus, and other times when the renderer initiates creating an object.
+ void Init();
+
+ // Tells the renderer to die and then calls Destroy().
+ virtual void Shutdown();
+
+ // IPC::Listener
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+
+ // Sends a message to the corresponding object in the renderer.
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // Called to notify the RenderWidget that it has been hidden or restored from
+ // having been hidden.
+ void WasHidden();
+ void WasShown();
+
+ // Returns true if the RenderWidget is hidden.
+ bool is_hidden() const { return is_hidden_; }
+
+ // Called to notify the RenderWidget that its associated native window
+ // got/lost focused.
+ virtual void GotFocus();
+ virtual void LostCapture();
+
+ // Called to notify the RenderWidget that it has lost the mouse lock.
+ virtual void LostMouseLock();
+
+ // Noifies the RenderWidget of the current mouse cursor visibility state.
+ void SendCursorVisibilityState(bool is_visible);
+
+ // Tells us whether the page is rendered directly via the GPU process.
+ bool is_accelerated_compositing_active() {
+ return is_accelerated_compositing_active_;
+ }
+
+ // Notifies the RenderWidgetHost that the View was destroyed.
+ void ViewDestroyed();
+
+ // Indicates if the page has finished loading.
+ void SetIsLoading(bool is_loading);
+
+ // Check for the existance of a BackingStore of the given |desired_size| and
+ // return it if it exists. If the BackingStore is GPU, true is returned and
+ // |*backing_store| is set to NULL.
+ bool TryGetBackingStore(const gfx::Size& desired_size,
+ BackingStore** backing_store);
+
+ // Get access to the widget's backing store matching the size of the widget's
+ // view. If you pass |force_create| as true, then GetBackingStore may block
+ // for the renderer to send a new frame. Otherwise, NULL will be returned if
+ // the backing store doesn't already exist. It will also return NULL if the
+ // backing store could not be created.
+ //
+ // Mac only: NULL may also be returned if the last frame was GPU accelerated.
+ // Call GetView()->HasAcceleratedSurface to determine if the last frame was
+ // accelerated.
+ BackingStore* GetBackingStore(bool force_create);
+
+ // Allocate a new backing store of the given size. Returns NULL on failure
+ // (for example, if we don't currently have a RenderWidgetHostView.)
+ BackingStore* AllocBackingStore(const gfx::Size& size);
+
+ // When a backing store does asynchronous painting, it will call this function
+ // when it is done with the DIB. We will then forward a message to the
+ // renderer to send another paint.
+ void DonePaintingToBackingStore();
+
+ // GPU accelerated version of GetBackingStore function. This will
+ // trigger a re-composite to the view. If a resize is pending, it will
+ // block briefly waiting for an ack from the renderer.
+ void ScheduleComposite();
+
+ // Starts a hang monitor timeout. If there's already a hang monitor timeout
+ // the new one will only fire if it has a shorter delay than the time
+ // left on the existing timeouts.
+ void StartHangMonitorTimeout(base::TimeDelta delay);
+
+ // Stops all existing hang monitor timeouts and assumes the renderer is
+ // responsive.
+ void StopHangMonitorTimeout();
+
+ // Forwards the given message to the renderer. These are called by the view
+ // when it has received a message.
+ void ForwardGestureEvent(const WebKit::WebGestureEvent& gesture_event);
+ void ForwardGestureEventWithLatencyInfo(
+ const WebKit::WebGestureEvent& gesture_event,
+ const ui::LatencyInfo& ui_latency);
+ void ForwardTouchEventWithLatencyInfo(
+ const WebKit::WebTouchEvent& touch_event,
+ const ui::LatencyInfo& ui_latency);
+ void ForwardMouseEventWithLatencyInfo(
+ const MouseEventWithLatencyInfo& mouse_event);
+ void ForwardWheelEventWithLatencyInfo(
+ const MouseWheelEventWithLatencyInfo& wheel_event);
+
+ void CancelUpdateTextDirection();
+
+ // Called when a mouse click/gesture tap activates the renderer.
+ virtual void OnPointerEventActivate();
+
+ // Notifies the renderer whether or not the input method attached to this
+ // process is activated.
+ // When the input method is activated, a renderer process sends IPC messages
+ // to notify the status of its composition node. (This message is mainly used
+ // for notifying the position of the input cursor so that the browser can
+ // display input method windows under the cursor.)
+ void SetInputMethodActive(bool activate);
+
+ // Update the composition node of the renderer (or WebKit).
+ // WebKit has a special node (a composition node) for input method to change
+ // its text without affecting any other DOM nodes. When the input method
+ // (attached to the browser) updates its text, the browser sends IPC messages
+ // to update the composition node of the renderer.
+ // (Read the comments of each function for its detail.)
+
+ // Sets the text of the composition node.
+ // This function can also update the cursor position and mark the specified
+ // range in the composition node.
+ // A browser should call this function:
+ // * when it receives a WM_IME_COMPOSITION message with a GCS_COMPSTR flag
+ // (on Windows);
+ // * when it receives a "preedit_changed" signal of GtkIMContext (on Linux);
+ // * when markedText of NSTextInput is called (on Mac).
+ void ImeSetComposition(
+ const string16& text,
+ const std::vector<WebKit::WebCompositionUnderline>& underlines,
+ int selection_start,
+ int selection_end);
+
+ // Finishes an ongoing composition with the specified text.
+ // A browser should call this function:
+ // * when it receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR flag
+ // (on Windows);
+ // * when it receives a "commit" signal of GtkIMContext (on Linux);
+ // * when insertText of NSTextInput is called (on Mac).
+ void ImeConfirmComposition(const string16& text,
+ const ui::Range& replacement_range,
+ bool keep_selection);
+
+ // Cancels an ongoing composition.
+ void ImeCancelComposition();
+
+ // Deletes the current selection plus the specified number of characters
+ // before and after the selection or caret.
+ void ExtendSelectionAndDelete(size_t before, size_t after);
+
+ // This is for derived classes to give us access to the resizer rect.
+ // And to also expose it to the RenderWidgetHostView.
+ virtual gfx::Rect GetRootWindowResizerRect() const;
+
+ bool ignore_input_events() const {
+ return ignore_input_events_;
+ }
+
+ bool input_method_active() const {
+ return input_method_active_;
+ }
+
+ // Whether forwarded WebInputEvents should be ignored. True if either
+ // |ignore_input_events_| or |process_->IgnoreInputEvents()| is true.
+ bool IgnoreInputEvents() const;
+
+ // Event queries delegated to the |input_router_|.
+ bool ShouldForwardTouchEvent() const;
+ bool ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const;
+ bool HasQueuedGestureEvents() const;
+
+ bool has_touch_handler() const { return has_touch_handler_; }
+
+ // Notification that the user has made some kind of input that could
+ // perform an action. See OnUserGesture for more details.
+ void StartUserGesture();
+
+ // Set the RenderView background.
+ void SetBackground(const SkBitmap& background);
+
+ // Notifies the renderer that the next key event is bound to one or more
+ // pre-defined edit commands
+ void SetEditCommandsForNextKeyEvent(
+ const std::vector<EditCommand>& commands);
+
+ // Gets the accessibility mode.
+ AccessibilityMode accessibility_mode() const {
+ return accessibility_mode_;
+ }
+
+ // Send a message to the renderer process to change the accessibility mode.
+ void SetAccessibilityMode(AccessibilityMode mode);
+
+ // Relay a request from assistive technology to perform the default action
+ // on a given node.
+ void AccessibilityDoDefaultAction(int object_id);
+
+ // Relay a request from assistive technology to set focus to a given node.
+ void AccessibilitySetFocus(int object_id);
+
+ // Relay a request from assistive technology to make a given object
+ // visible by scrolling as many scrollable containers as necessary.
+ // In addition, if it's not possible to make the entire object visible,
+ // scroll so that the |subfocus| rect is visible at least. The subfocus
+ // rect is in local coordinates of the object itself.
+ void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus);
+
+ // Relay a request from assistive technology to move a given object
+ // to a specific location, in the WebContents area coordinate space, i.e.
+ // (0, 0) is the top-left corner of the WebContents.
+ void AccessibilityScrollToPoint(int acc_obj_id, gfx::Point point);
+
+ // Relay a request from assistive technology to set text selection.
+ void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset);
+
+ // Kill the renderer because we got a fatal accessibility error.
+ void FatalAccessibilityTreeError();
+
+#if defined(OS_WIN) && defined(USE_AURA)
+ void SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent);
+ gfx::NativeViewAccessible GetParentNativeViewAccessible() const;
+#endif
+
+ // Executes the edit command on the RenderView.
+ void ExecuteEditCommand(const std::string& command,
+ const std::string& value);
+
+ // Tells the renderer to scroll the currently focused node into rect only if
+ // the currently focused node is a Text node (textfield, text area or content
+ // editable divs).
+ void ScrollFocusedEditableNodeIntoRect(const gfx::Rect& rect);
+
+ // Requests the renderer to select the region between two points.
+ void SelectRange(const gfx::Point& start, const gfx::Point& end);
+
+ // Requests the renderer to move the caret selection towards the point.
+ void MoveCaret(const gfx::Point& point);
+
+ // Called when the reponse to a pending mouse lock request has arrived.
+ // Returns true if |allowed| is true and the mouse has been successfully
+ // locked.
+ bool GotResponseToLockMouseRequest(bool allowed);
+
+ // Tells the RenderWidget about the latest vsync parameters.
+ // Note: Make sure the timebase was obtained using
+ // base::TimeTicks::HighResNow. Using the non-high res timer will result in
+ // incorrect synchronization across processes.
+ virtual void UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ // Called by the view in response to AcceleratedSurfaceBuffersSwapped or
+ // AcceleratedSurfacePostSubBuffer.
+ static void AcknowledgeBufferPresent(
+ int32 route_id,
+ int gpu_host_id,
+ const AcceleratedSurfaceMsg_BufferPresented_Params& params);
+
+ // Called by the view in response to OnSwapCompositorFrame.
+ static void SendSwapCompositorFrameAck(
+ int32 route_id,
+ uint32 output_surface_id,
+ int renderer_host_id,
+ const cc::CompositorFrameAck& ack);
+
+ // Called by the view in response to AcceleratedSurfaceBuffersSwapped for
+ // platforms that support deferred GPU process descheduling. This does
+ // nothing if the compositor thread is enabled.
+ // TODO(jbates) Once the compositor thread is always on, this can be removed.
+ void AcknowledgeSwapBuffersToRenderer();
+
+ bool is_threaded_compositing_enabled() const {
+ return is_threaded_compositing_enabled_;
+ }
+
+#if defined(USE_AURA)
+ // Called by the view when the parent changes. If a parent isn't available,
+ // NULL is used.
+ void ParentChanged(gfx::NativeViewId new_parent);
+#endif
+
+ // Signals that the compositing surface was updated, e.g. after a lost context
+ // event.
+ void CompositingSurfaceUpdated();
+
+ void set_allow_privileged_mouse_lock(bool allow) {
+ allow_privileged_mouse_lock_ = allow;
+ }
+
+ // Resets state variables related to tracking pending size and painting.
+ //
+ // We need to reset these flags when we want to repaint the contents of
+ // browser plugin in this RWH. Resetting these flags will ensure we ignore
+ // any previous pending acks that are not relevant upon repaint.
+ void ResetSizeAndRepaintPendingFlags();
+
+ void DetachDelegate();
+
+ // Update the renderer's cache of the screen rect of the view and window.
+ void SendScreenRects();
+
+ OverscrollController* overscroll_controller() const {
+ return overscroll_controller_.get();
+ }
+
+ base::TimeDelta GetSyntheticScrollMessageInterval() const;
+
+ // Sets whether the overscroll controller should be enabled for this page.
+ void SetOverscrollControllerEnabled(bool enabled);
+
+ // Suppreses future char events until a keydown. See
+ // suppress_next_char_events_.
+ void SuppressNextCharEvents();
+
+ // Indicates whether the renderer drives the RenderWidgetHosts's size or the
+ // other way around.
+ bool should_auto_resize() { return should_auto_resize_; }
+
+ void ComputeTouchLatency(const ui::LatencyInfo& latency_info);
+ void FrameSwapped(const ui::LatencyInfo& latency_info);
+ void DidReceiveRendererFrame();
+
+ // Returns the ID that uniquely describes this component to the latency
+ // subsystem.
+ int64 GetLatencyComponentId();
+
+ static void CompositorFrameDrawn(const ui::LatencyInfo& latency_info);
+
+ // Don't check whether we expected a resize ack during layout tests.
+ static void DisableResizeAckCheckForTesting();
+
+ protected:
+ virtual RenderWidgetHostImpl* AsRenderWidgetHostImpl() OVERRIDE;
+
+ // Create a LatencyInfo struct with INPUT_EVENT_LATENCY_RWH_COMPONENT
+ // component if it is not already in |original|. And if |original| is
+ // not NULL, it is also merged into the resulting LatencyInfo.
+ ui::LatencyInfo CreateRWHLatencyInfoIfNotExist(
+ const ui::LatencyInfo* original);
+
+ // Called when we receive a notification indicating that the renderer
+ // process has gone. This will reset our state so that our state will be
+ // consistent if a new renderer is created.
+ void RendererExited(base::TerminationStatus status, int exit_code);
+
+ // Retrieves an id the renderer can use to refer to its view.
+ // This is used for various IPC messages, including plugins.
+ gfx::NativeViewId GetNativeViewId() const;
+
+ // Retrieves an id for the surface that the renderer can draw to
+ // when accelerated compositing is enabled.
+ gfx::GLSurfaceHandle GetCompositingSurface();
+
+ // ---------------------------------------------------------------------------
+ // The following methods are overridden by RenderViewHost to send upwards to
+ // its delegate.
+
+ // Called when a mousewheel event was not processed by the renderer.
+ virtual void UnhandledWheelEvent(const WebKit::WebMouseWheelEvent& event) {}
+
+ // Notification that the user has made some kind of input that could
+ // perform an action. The gestures that count are 1) any mouse down
+ // event and 2) enter or space key presses.
+ virtual void OnUserGesture() {}
+
+ // Callbacks for notification when the renderer becomes unresponsive to user
+ // input events, and subsequently responsive again.
+ virtual void NotifyRendererUnresponsive() {}
+ virtual void NotifyRendererResponsive() {}
+
+ // Called when auto-resize resulted in the renderer size changing.
+ virtual void OnRenderAutoResized(const gfx::Size& new_size) {}
+
+ // ---------------------------------------------------------------------------
+
+ // RenderViewHost overrides this method to impose further restrictions on when
+ // to allow mouse lock.
+ // Once the request is approved or rejected, GotResponseToLockMouseRequest()
+ // will be called.
+ virtual void RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target);
+
+ void RejectMouseLockOrUnlockIfNecessary();
+ bool IsMouseLocked() const;
+
+ // RenderViewHost overrides this method to report when in fullscreen mode.
+ virtual bool IsFullscreen() const;
+
+ // Indicates if the render widget host should track the render widget's size
+ // as opposed to visa versa.
+ void SetShouldAutoResize(bool enable);
+
+ // Expose increment/decrement of the in-flight event count, so
+ // RenderViewHostImpl can account for in-flight beforeunload/unload events.
+ int increment_in_flight_event_count() { return ++in_flight_event_count_; }
+ int decrement_in_flight_event_count() { return --in_flight_event_count_; }
+
+ // Returns whether an overscroll gesture is in progress.
+ bool IsInOverscrollGesture() const;
+
+ // The View associated with the RenderViewHost. The lifetime of this object
+ // is associated with the lifetime of the Render process. If the Renderer
+ // crashes, its View is destroyed and this pointer becomes NULL, even though
+ // render_view_host_ lives on to load another URL (creating a new View while
+ // doing so).
+ RenderWidgetHostViewPort* view_;
+
+ // true if a renderer has once been valid. We use this flag to display a sad
+ // tab only when we lose our renderer and not if a paint occurs during
+ // initialization.
+ bool renderer_initialized_;
+
+ // This value indicates how long to wait before we consider a renderer hung.
+ int hung_renderer_delay_ms_;
+
+ private:
+ friend class MockRenderWidgetHost;
+
+ // Tell this object to destroy itself.
+ void Destroy();
+
+ // Checks whether the renderer is hung and calls NotifyRendererUnresponsive
+ // if it is.
+ void CheckRendererIsUnresponsive();
+
+ // Called if we know the renderer is responsive. When we currently think the
+ // renderer is unresponsive, this will clear that state and call
+ // NotifyRendererResponsive.
+ void RendererIsResponsive();
+
+ // IPC message handlers
+ void OnRenderViewReady();
+ void OnRenderProcessGone(int status, int error_code);
+ void OnClose();
+ void OnUpdateScreenRectsAck();
+ void OnRequestMove(const gfx::Rect& pos);
+ void OnSetTooltipText(const string16& tooltip_text,
+ WebKit::WebTextDirection text_direction_hint);
+ void OnPaintAtSizeAck(int tag, const gfx::Size& size);
+#if defined(OS_MACOSX)
+ void OnCompositorSurfaceBuffersSwapped(
+ const ViewHostMsg_CompositorSurfaceBuffersSwapped_Params& params);
+#endif
+ bool OnSwapCompositorFrame(const IPC::Message& message);
+ void OnOverscrolled(gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity);
+ void OnUpdateRect(const ViewHostMsg_UpdateRect_Params& params);
+ void OnUpdateIsDelayed();
+ void OnBeginSmoothScroll(
+ const ViewHostMsg_BeginSmoothScroll_Params& params);
+ virtual void OnFocus();
+ virtual void OnBlur();
+ void OnSetCursor(const WebCursor& cursor);
+ void OnTextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode);
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+ void OnImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds);
+#endif
+ void OnImeCancelComposition();
+ void OnDidActivateAcceleratedCompositing(bool activated);
+ void OnLockMouse(bool user_gesture,
+ bool last_unlocked_by_target,
+ bool privileged);
+ void OnUnlockMouse();
+ void OnShowDisambiguationPopup(const gfx::Rect& rect,
+ const gfx::Size& size,
+ const TransportDIB::Id& id);
+#if defined(OS_WIN)
+ void OnWindowlessPluginDummyWindowCreated(
+ gfx::NativeViewId dummy_activation_window);
+ void OnWindowlessPluginDummyWindowDestroyed(
+ gfx::NativeViewId dummy_activation_window);
+#endif
+ void OnSnapshot(bool success, const SkBitmap& bitmap);
+
+ // Called (either immediately or asynchronously) after we're done with our
+ // BackingStore and can send an ACK to the renderer so it can paint onto it
+ // again.
+ void DidUpdateBackingStore(const ViewHostMsg_UpdateRect_Params& params,
+ const base::TimeTicks& paint_start);
+
+ // Paints the given bitmap to the current backing store at the given
+ // location. Returns true if the passed callback was asynchronously
+ // scheduled in the future (and thus the caller must manually synchronously
+ // call the callback function).
+ bool PaintBackingStoreRect(TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ const gfx::Size& view_size,
+ float scale_factor,
+ const base::Closure& completion_callback);
+
+ // Scrolls the given |clip_rect| in the backing by the given dx/dy amount. The
+ // |dib| and its corresponding location |bitmap_rect| in the backing store
+ // is the newly painted pixels by the renderer.
+ void ScrollBackingStoreRect(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size);
+
+ // Give key press listeners a chance to handle this key press. This allow
+ // widgets that don't have focus to still handle key presses.
+ bool KeyPressListenersHandleEvent(const NativeWebKeyboardEvent& event);
+
+ // InputRouterClient
+ virtual InputEventAckState FilterInputEvent(
+ const WebKit::WebInputEvent& event,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void IncrementInFlightEventCount() OVERRIDE;
+ virtual void DecrementInFlightEventCount() OVERRIDE;
+ virtual void OnHasTouchEventHandlers(bool has_handlers) OVERRIDE;
+ virtual bool OnSendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info,
+ bool* is_shortcut) OVERRIDE;
+ virtual bool OnSendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) OVERRIDE;
+ virtual bool OnSendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE;
+ virtual bool OnSendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE;
+ virtual bool OnSendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE;
+ virtual bool OnSendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE;
+ virtual bool OnSendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE;
+ virtual bool OnSendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE;
+ virtual void OnKeyboardEventAck(const NativeWebKeyboardEvent& event,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void OnWheelEventAck(const WebKit::WebMouseWheelEvent& event,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void OnGestureEventAck(const WebKit::WebGestureEvent& event,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void OnUnexpectedEventAck(bool bad_message) OVERRIDE;
+
+ void SimulateTouchGestureWithMouse(const WebKit::WebMouseEvent& mouse_event);
+
+ // Called when there is a new auto resize (using a post to avoid a stack
+ // which may get in recursive loops).
+ void DelayedAutoResized();
+
+
+ // Our delegate, which wants to know mainly about keyboard events.
+ // It will remain non-NULL until DetachDelegate() is called.
+ RenderWidgetHostDelegate* delegate_;
+
+ // Created during construction but initialized during Init*(). Therefore, it
+ // is guaranteed never to be NULL, but its channel may be NULL if the
+ // renderer crashed, so you must always check that.
+ RenderProcessHost* process_;
+
+ // The ID of the corresponding object in the Renderer Instance.
+ int routing_id_;
+
+ // The ID of the surface corresponding to this render widget.
+ int surface_id_;
+
+ // Indicates whether a page is loading or not.
+ bool is_loading_;
+
+ // Indicates whether a page is hidden or not.
+ bool is_hidden_;
+
+ // Indicates whether a page is fullscreen or not.
+ bool is_fullscreen_;
+
+ // True when a page is rendered directly via the GPU process.
+ bool is_accelerated_compositing_active_;
+
+ // True if threaded compositing is enabled on this view.
+ bool is_threaded_compositing_enabled_;
+
+ // Set if we are waiting for a repaint ack for the view.
+ bool repaint_ack_pending_;
+
+ // True when waiting for RESIZE_ACK.
+ bool resize_ack_pending_;
+
+ // Cached copy of the screen info so that it doesn't need to be updated every
+ // time the window is resized.
+ scoped_ptr<WebKit::WebScreenInfo> screen_info_;
+
+ // Set if screen_info_ may have changed and should be recomputed and force a
+ // resize message.
+ bool screen_info_out_of_date_;
+
+ // The current size of the RenderWidget.
+ gfx::Size current_size_;
+
+ // The size of the view's backing surface in non-DPI-adjusted pixels.
+ gfx::Size physical_backing_size_;
+
+ // The height of the physical backing surface that is overdrawn opaquely in
+ // the browser, for example by an on-screen-keyboard (in DPI-adjusted pixels).
+ float overdraw_bottom_height_;
+
+ // The size we last sent as requested size to the renderer. |current_size_|
+ // is only updated once the resize message has been ack'd. This on the other
+ // hand is updated when the resize message is sent. This is very similar to
+ // |resize_ack_pending_|, but the latter is not set if the new size has width
+ // or height zero, which is why we need this too.
+ gfx::Size last_requested_size_;
+
+ // The next auto resize to send.
+ gfx::Size new_auto_size_;
+
+ // True if the render widget host should track the render widget's size as
+ // opposed to visa versa.
+ bool should_auto_resize_;
+
+ bool waiting_for_screen_rects_ack_;
+ gfx::Rect last_view_screen_rect_;
+ gfx::Rect last_window_screen_rect_;
+
+ AccessibilityMode accessibility_mode_;
+
+ // Keyboard event listeners.
+ ObserverList<KeyboardListener> keyboard_listeners_;
+
+ // If true, then we should repaint when restoring even if we have a
+ // backingstore. This flag is set to true if we receive a paint message
+ // while is_hidden_ to true. Even though we tell the render widget to hide
+ // itself, a paint message could already be in flight at that point.
+ bool needs_repainting_on_restore_;
+
+ // This is true if the renderer is currently unresponsive.
+ bool is_unresponsive_;
+
+ // The following value indicates a time in the future when we would consider
+ // the renderer hung if it does not generate an appropriate response message.
+ base::Time time_when_considered_hung_;
+
+ // This value denotes the number of input events yet to be acknowledged
+ // by the renderer.
+ int in_flight_event_count_;
+
+ // This timer runs to check if time_when_considered_hung_ has past.
+ base::OneShotTimer<RenderWidgetHostImpl> hung_renderer_timer_;
+
+ // Flag to detect recursive calls to GetBackingStore().
+ bool in_get_backing_store_;
+
+ // Flag to trigger the GetBackingStore method to abort early.
+ bool abort_get_backing_store_;
+
+ // Set when we call DidPaintRect/DidScrollRect on the view.
+ bool view_being_painted_;
+
+ // Used for UMA histogram logging to measure the time for a repaint view
+ // operation to finish.
+ base::TimeTicks repaint_start_time_;
+
+ // Set to true if we shouldn't send input events from the render widget.
+ bool ignore_input_events_;
+
+ // Indicates whether IME is active.
+ bool input_method_active_;
+
+ // Set when we update the text direction of the selected input element.
+ bool text_direction_updated_;
+ WebKit::WebTextDirection text_direction_;
+
+ // Set when we cancel updating the text direction.
+ // This flag also ignores succeeding update requests until we call
+ // NotifyTextDirection().
+ bool text_direction_canceled_;
+
+ // Indicates if the next sequence of Char events should be suppressed or not.
+ // System may translate a RawKeyDown event into zero or more Char events,
+ // usually we send them to the renderer directly in sequence. However, If a
+ // RawKeyDown event was not handled by the renderer but was handled by
+ // our UnhandledKeyboardEvent() method, e.g. as an accelerator key, then we
+ // shall not send the following sequence of Char events, which was generated
+ // by this RawKeyDown event, to the renderer. Otherwise the renderer may
+ // handle the Char events and cause unexpected behavior.
+ // For example, pressing alt-2 may let the browser switch to the second tab,
+ // but the Char event generated by alt-2 may also activate a HTML element
+ // if its accesskey happens to be "2", then the user may get confused when
+ // switching back to the original tab, because the content may already be
+ // changed.
+ bool suppress_next_char_events_;
+
+ // The last scroll offset of the render widget.
+ gfx::Vector2d last_scroll_offset_;
+
+ bool pending_mouse_lock_request_;
+ bool allow_privileged_mouse_lock_;
+
+ // Keeps track of whether the webpage has any touch event handler. If it does,
+ // then touch events are sent to the renderer. Otherwise, the touch events are
+ // not sent to the renderer.
+ bool has_touch_handler_;
+
+ base::WeakPtrFactory<RenderWidgetHostImpl> weak_factory_;
+
+ SmoothScrollGestureController smooth_scroll_gesture_controller_;
+
+ // Receives and handles all input events.
+ scoped_ptr<InputRouter> input_router_;
+
+ scoped_ptr<OverscrollController> overscroll_controller_;
+
+#if defined(OS_WIN)
+ std::list<HWND> dummy_windows_for_activation_;
+#endif
+
+ // List of callbacks for pending snapshot requests to the renderer.
+ std::queue<base::Callback<void(bool, const SkBitmap&)> > pending_snapshots_;
+
+ int64 last_input_number_;
+
+ BrowserRenderingStats rendering_stats_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_IMPL_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_unittest.cc b/chromium/content/browser/renderer_host/render_widget_host_unittest.cc
new file mode 100644
index 00000000000..960ce5eb92a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -0,0 +1,2577 @@
+// 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/timer/timer.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/browser/renderer_host/input/gesture_event_filter.h"
+#include "content/browser/renderer_host/input/immediate_input_router.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller.h"
+#include "content/browser/renderer_host/input/tap_suppression_controller_client.h"
+#include "content/browser/renderer_host/input/touch_event_queue.h"
+#include "content/browser/renderer_host/overscroll_controller.h"
+#include "content/browser/renderer_host/overscroll_controller_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/screen.h"
+
+#if defined(USE_AURA)
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "ui/aura/env.h"
+#include "ui/aura/test/test_screen.h"
+#endif
+
+#if defined(OS_WIN) || defined(USE_AURA)
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "ui/base/events/event.h"
+#endif
+
+using base::TimeDelta;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebTouchEvent;
+using WebKit::WebTouchPoint;
+
+namespace content {
+
+// TestOverscrollDelegate ------------------------------------------------------
+
+class TestOverscrollDelegate : public OverscrollControllerDelegate {
+ public:
+ TestOverscrollDelegate()
+ : current_mode_(OVERSCROLL_NONE),
+ completed_mode_(OVERSCROLL_NONE),
+ delta_x_(0.f),
+ delta_y_(0.f) {
+ }
+
+ virtual ~TestOverscrollDelegate() {}
+
+ OverscrollMode current_mode() const { return current_mode_; }
+ OverscrollMode completed_mode() const { return completed_mode_; }
+ float delta_x() const { return delta_x_; }
+ float delta_y() const { return delta_y_; }
+
+ void Reset() {
+ current_mode_ = OVERSCROLL_NONE;
+ completed_mode_ = OVERSCROLL_NONE;
+ delta_x_ = delta_y_ = 0.f;
+ }
+
+ private:
+ // Overridden from OverscrollControllerDelegate:
+ virtual void OnOverscrollUpdate(float delta_x, float delta_y) OVERRIDE {
+ delta_x_ = delta_x;
+ delta_y_ = delta_y;
+ }
+
+ virtual void OnOverscrollComplete(OverscrollMode overscroll_mode) OVERRIDE {
+ EXPECT_EQ(current_mode_, overscroll_mode);
+ completed_mode_ = overscroll_mode;
+ current_mode_ = OVERSCROLL_NONE;
+ }
+
+ virtual void OnOverscrollModeChange(OverscrollMode old_mode,
+ OverscrollMode new_mode) OVERRIDE {
+ EXPECT_EQ(current_mode_, old_mode);
+ current_mode_ = new_mode;
+ delta_x_ = delta_y_ = 0.f;
+ }
+
+ OverscrollMode current_mode_;
+ OverscrollMode completed_mode_;
+ float delta_x_;
+ float delta_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestOverscrollDelegate);
+};
+
+// MockKeyboardListener --------------------------------------------------------
+class MockKeyboardListener : public KeyboardListener {
+ public:
+ MockKeyboardListener()
+ : handle_key_press_event_(false) {
+ }
+ virtual ~MockKeyboardListener() {}
+
+ // KeyboardListener:
+ virtual bool HandleKeyPressEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE {
+ return handle_key_press_event_;
+ }
+
+ void set_handle_key_press_event(bool handle) {
+ handle_key_press_event_ = handle;
+ }
+
+ private:
+ bool handle_key_press_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockKeyboardListener);
+};
+
+// MockInputRouter -------------------------------------------------------------
+
+class MockInputRouter : public InputRouter {
+ public:
+ explicit MockInputRouter(InputRouterClient* client)
+ : send_event_called_(false),
+ sent_mouse_event_(false),
+ sent_wheel_event_(false),
+ sent_keyboard_event_(false),
+ sent_gesture_event_(false),
+ send_touch_event_not_cancelled_(false),
+ message_received_(false),
+ client_(client) {
+ }
+ virtual ~MockInputRouter() {}
+
+ // InputRouter
+ virtual bool SendInput(IPC::Message* message) OVERRIDE {
+ send_event_called_ = true;
+
+ // SendInput takes ownership of message
+ delete message;
+
+ return true;
+ }
+ virtual void SendMouseEvent(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE {
+ if (client_->OnSendMouseEvent(mouse_event))
+ sent_mouse_event_ = true;
+ }
+ virtual void SendWheelEvent(
+ const MouseWheelEventWithLatencyInfo& wheel_event) OVERRIDE {
+ if (client_->OnSendWheelEvent(wheel_event))
+ sent_wheel_event_ = true;
+ }
+ virtual void SendKeyboardEvent(
+ const NativeWebKeyboardEvent& key_event,
+ const ui::LatencyInfo& latency_info) OVERRIDE {
+ bool is_shortcut = false;
+ if (client_->OnSendKeyboardEvent(key_event, latency_info, &is_shortcut))
+ sent_keyboard_event_ = true;
+ }
+ virtual void SendGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE {
+ if (client_->OnSendGestureEvent(gesture_event))
+ sent_gesture_event_ = true;
+ }
+ virtual void SendTouchEvent(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE {
+ if (client_->OnSendTouchEvent(touch_event))
+ send_touch_event_not_cancelled_ = true;
+ }
+ virtual void SendMouseEventImmediately(
+ const MouseEventWithLatencyInfo& mouse_event) OVERRIDE {}
+ virtual void SendTouchEventImmediately(
+ const TouchEventWithLatencyInfo& touch_event) OVERRIDE {}
+ virtual void SendGestureEventImmediately(
+ const GestureEventWithLatencyInfo& gesture_event) OVERRIDE {}
+ virtual const NativeWebKeyboardEvent* GetLastKeyboardEvent() const OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+ virtual bool ShouldForwardTouchEvent() const OVERRIDE { return true; }
+ virtual bool ShouldForwardGestureEvent(
+ const GestureEventWithLatencyInfo& gesture_event) const OVERRIDE {
+ return true;
+ }
+ virtual bool HasQueuedGestureEvents() const OVERRIDE { return true; }
+
+ // IPC::Listener
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
+ message_received_ = true;
+ return false;
+ }
+
+ bool send_event_called_;
+ bool sent_mouse_event_;
+ bool sent_wheel_event_;
+ bool sent_keyboard_event_;
+ bool sent_gesture_event_;
+ bool send_touch_event_not_cancelled_;
+ bool message_received_;
+
+ private:
+ InputRouterClient* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockInputRouter);
+};
+
+// MockRenderWidgetHost ----------------------------------------------------
+
+class MockRenderWidgetHost : public RenderWidgetHostImpl {
+ public:
+ MockRenderWidgetHost(
+ RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id)
+ : RenderWidgetHostImpl(delegate, process, routing_id),
+ unresponsive_timer_fired_(false) {
+ immediate_input_router_ =
+ static_cast<ImmediateInputRouter*>(input_router_.get());
+ }
+
+ // Allow poking at a few private members.
+ using RenderWidgetHostImpl::OnPaintAtSizeAck;
+ using RenderWidgetHostImpl::OnUpdateRect;
+ using RenderWidgetHostImpl::RendererExited;
+ using RenderWidgetHostImpl::last_requested_size_;
+ using RenderWidgetHostImpl::is_hidden_;
+ using RenderWidgetHostImpl::resize_ack_pending_;
+ using RenderWidgetHostImpl::input_router_;
+
+ bool unresponsive_timer_fired() const {
+ return unresponsive_timer_fired_;
+ }
+
+ void set_hung_renderer_delay_ms(int delay_ms) {
+ hung_renderer_delay_ms_ = delay_ms;
+ }
+
+ unsigned GestureEventLastQueueEventSize() {
+ return gesture_event_filter()->coalesced_gesture_events_.size();
+ }
+
+ WebGestureEvent GestureEventSecondFromLastQueueEvent() {
+ return gesture_event_filter()->coalesced_gesture_events_.at(
+ GestureEventLastQueueEventSize() - 2).event;
+ }
+
+ WebGestureEvent GestureEventLastQueueEvent() {
+ return gesture_event_filter()->coalesced_gesture_events_.back().event;
+ }
+
+ unsigned GestureEventDebouncingQueueSize() {
+ return gesture_event_filter()->debouncing_deferral_queue_.size();
+ }
+
+ WebGestureEvent GestureEventQueueEventAt(int i) {
+ return gesture_event_filter()->coalesced_gesture_events_.at(i).event;
+ }
+
+ bool shouldDeferTapDownEvents() {
+ return gesture_event_filter()->maximum_tap_gap_time_ms_ != 0;
+ }
+
+ bool ScrollingInProgress() {
+ return gesture_event_filter()->scrolling_in_progress_;
+ }
+
+ bool FlingInProgress() {
+ return gesture_event_filter()->fling_in_progress_;
+ }
+
+ bool WillIgnoreNextACK() {
+ return gesture_event_filter()->ignore_next_ack_;
+ }
+
+ void SetupForOverscrollControllerTest() {
+ SetOverscrollControllerEnabled(true);
+ overscroll_delegate_.reset(new TestOverscrollDelegate);
+ overscroll_controller_->set_delegate(overscroll_delegate_.get());
+ }
+
+ void set_maximum_tap_gap_time_ms(int delay_ms) {
+ gesture_event_filter()->maximum_tap_gap_time_ms_ = delay_ms;
+ }
+
+ void set_debounce_interval_time_ms(int delay_ms) {
+ gesture_event_filter()->debounce_interval_time_ms_ = delay_ms;
+ }
+
+ size_t TouchEventQueueSize() {
+ return touch_event_queue()->GetQueueSize();
+ }
+
+ bool ScrollStateIsContentScrolling() const {
+ return scroll_state() == OverscrollController::STATE_CONTENT_SCROLLING;
+ }
+
+ bool ScrollStateIsOverscrolling() const {
+ return scroll_state() == OverscrollController::STATE_OVERSCROLLING;
+ }
+
+ bool ScrollStateIsUnknown() const {
+ return scroll_state() == OverscrollController::STATE_UNKNOWN;
+ }
+
+ OverscrollController::ScrollState scroll_state() const {
+ return overscroll_controller_->scroll_state_;
+ }
+
+ const WebTouchEvent& latest_event() const {
+ return touch_event_queue()->GetLatestEvent().event;
+ }
+
+ OverscrollMode overscroll_mode() const {
+ return overscroll_controller_->overscroll_mode_;
+ }
+
+ float overscroll_delta_x() const {
+ return overscroll_controller_->overscroll_delta_x_;
+ }
+
+ float overscroll_delta_y() const {
+ return overscroll_controller_->overscroll_delta_y_;
+ }
+
+ TestOverscrollDelegate* overscroll_delegate() {
+ return overscroll_delegate_.get();
+ }
+
+ void SetupForInputRouterTest() {
+ mock_input_router_ = new MockInputRouter(this);
+ input_router_.reset(mock_input_router_);
+ }
+
+ MockInputRouter* mock_input_router() {
+ return mock_input_router_;
+ }
+
+ protected:
+ virtual void NotifyRendererUnresponsive() OVERRIDE {
+ unresponsive_timer_fired_ = true;
+ }
+
+ TouchEventQueue* touch_event_queue() const {
+ return immediate_input_router_->touch_event_queue();
+ }
+
+ GestureEventFilter* gesture_event_filter() const {
+ return immediate_input_router_->gesture_event_filter();
+ }
+
+ private:
+ bool unresponsive_timer_fired_;
+
+ // |immediate_input_router_| and |mock_input_router_| are owned by
+ // RenderWidgetHostImpl |input_router_|. Below are provided for convenience so
+ // that we don't have to reinterpret_cast it all the time.
+ ImmediateInputRouter* immediate_input_router_;
+ MockInputRouter* mock_input_router_;
+
+ scoped_ptr<TestOverscrollDelegate> overscroll_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockRenderWidgetHost);
+};
+
+namespace {
+
+// RenderWidgetHostProcess -----------------------------------------------------
+
+class RenderWidgetHostProcess : public MockRenderProcessHost {
+ public:
+ explicit RenderWidgetHostProcess(BrowserContext* browser_context)
+ : MockRenderProcessHost(browser_context),
+ current_update_buf_(NULL),
+ update_msg_should_reply_(false),
+ update_msg_reply_flags_(0) {
+ }
+ virtual ~RenderWidgetHostProcess() {
+ delete current_update_buf_;
+ }
+
+ void set_update_msg_should_reply(bool reply) {
+ update_msg_should_reply_ = reply;
+ }
+ void set_update_msg_reply_flags(int flags) {
+ update_msg_reply_flags_ = flags;
+ }
+
+ // Fills the given update parameters with resonable default values.
+ void InitUpdateRectParams(ViewHostMsg_UpdateRect_Params* params);
+
+ virtual bool HasConnection() const OVERRIDE { return true; }
+
+ protected:
+ virtual bool WaitForBackingStoreMsg(int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg) OVERRIDE;
+
+ TransportDIB* current_update_buf_;
+
+ // Set to true when WaitForBackingStoreMsg should return a successful update
+ // message reply. False implies timeout.
+ bool update_msg_should_reply_;
+
+ // Indicates the flags that should be sent with a repaint request. This
+ // only has an effect when update_msg_should_reply_ is true.
+ int update_msg_reply_flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostProcess);
+};
+
+void RenderWidgetHostProcess::InitUpdateRectParams(
+ ViewHostMsg_UpdateRect_Params* params) {
+ // Create the shared backing store.
+ const int w = 100, h = 100;
+ const size_t pixel_size = w * h * 4;
+
+ if (!current_update_buf_)
+ current_update_buf_ = TransportDIB::Create(pixel_size, 0);
+ params->bitmap = current_update_buf_->id();
+ params->bitmap_rect = gfx::Rect(0, 0, w, h);
+ params->scroll_delta = gfx::Vector2d();
+ params->copy_rects.push_back(params->bitmap_rect);
+ params->view_size = gfx::Size(w, h);
+ params->flags = update_msg_reply_flags_;
+ params->needs_ack = true;
+ params->scale_factor = 1;
+}
+
+bool RenderWidgetHostProcess::WaitForBackingStoreMsg(
+ int render_widget_id,
+ const base::TimeDelta& max_delay,
+ IPC::Message* msg) {
+ if (!update_msg_should_reply_)
+ return false;
+
+ // Construct a fake update reply.
+ ViewHostMsg_UpdateRect_Params params;
+ InitUpdateRectParams(&params);
+
+ ViewHostMsg_UpdateRect message(render_widget_id, params);
+ *msg = message;
+ return true;
+}
+
+// TestView --------------------------------------------------------------------
+
+// This test view allows us to specify the size, and keep track of acked
+// touch-events.
+class TestView : public TestRenderWidgetHostView {
+ public:
+ explicit TestView(RenderWidgetHostImpl* rwh)
+ : TestRenderWidgetHostView(rwh),
+ acked_event_count_(0),
+ gesture_event_type_(-1),
+ use_fake_physical_backing_size_(false),
+ ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN) {
+ }
+
+ // Sets the bounds returned by GetViewBounds.
+ void set_bounds(const gfx::Rect& bounds) {
+ bounds_ = bounds;
+ }
+
+ const WebTouchEvent& acked_event() const { return acked_event_; }
+ int acked_event_count() const { return acked_event_count_; }
+ void ClearAckedEvent() {
+ acked_event_.type = WebKit::WebInputEvent::Undefined;
+ acked_event_count_ = 0;
+ }
+
+ const WebMouseWheelEvent& unhandled_wheel_event() const {
+ return unhandled_wheel_event_;
+ }
+ int gesture_event_type() const { return gesture_event_type_; }
+ InputEventAckState ack_result() const { return ack_result_; }
+
+ void SetMockPhysicalBackingSize(const gfx::Size& mock_physical_backing_size) {
+ use_fake_physical_backing_size_ = true;
+ mock_physical_backing_size_ = mock_physical_backing_size;
+ }
+ void ClearMockPhysicalBackingSize() {
+ use_fake_physical_backing_size_ = false;
+ }
+
+ // RenderWidgetHostView override.
+ virtual gfx::Rect GetViewBounds() const OVERRIDE {
+ return bounds_;
+ }
+ virtual void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE {
+ acked_event_ = touch.event;
+ ++acked_event_count_;
+ }
+ virtual void UnhandledWheelEvent(const WebMouseWheelEvent& event) OVERRIDE {
+ unhandled_wheel_event_ = event;
+ }
+ virtual void GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) OVERRIDE {
+ gesture_event_type_ = gesture_event_type;
+ ack_result_ = ack_result;
+ }
+ virtual gfx::Size GetPhysicalBackingSize() const OVERRIDE {
+ if (use_fake_physical_backing_size_)
+ return mock_physical_backing_size_;
+ return TestRenderWidgetHostView::GetPhysicalBackingSize();
+ }
+
+ protected:
+ WebMouseWheelEvent unhandled_wheel_event_;
+ WebTouchEvent acked_event_;
+ int acked_event_count_;
+ int gesture_event_type_;
+ gfx::Rect bounds_;
+ bool use_fake_physical_backing_size_;
+ gfx::Size mock_physical_backing_size_;
+ InputEventAckState ack_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestView);
+};
+
+// MockRenderWidgetHostDelegate --------------------------------------------
+
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate()
+ : prehandle_keyboard_event_(false),
+ prehandle_keyboard_event_called_(false),
+ prehandle_keyboard_event_type_(WebInputEvent::Undefined),
+ unhandled_keyboard_event_called_(false),
+ unhandled_keyboard_event_type_(WebInputEvent::Undefined) {
+ }
+ virtual ~MockRenderWidgetHostDelegate() {}
+
+ // Tests that make sure we ignore keyboard event acknowledgments to events we
+ // didn't send work by making sure we didn't call UnhandledKeyboardEvent().
+ bool unhandled_keyboard_event_called() const {
+ return unhandled_keyboard_event_called_;
+ }
+
+ WebInputEvent::Type unhandled_keyboard_event_type() const {
+ return unhandled_keyboard_event_type_;
+ }
+
+ bool prehandle_keyboard_event_called() const {
+ return prehandle_keyboard_event_called_;
+ }
+
+ WebInputEvent::Type prehandle_keyboard_event_type() const {
+ return prehandle_keyboard_event_type_;
+ }
+
+ void set_prehandle_keyboard_event(bool handle) {
+ prehandle_keyboard_event_ = handle;
+ }
+
+ protected:
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) OVERRIDE {
+ prehandle_keyboard_event_type_ = event.type;
+ prehandle_keyboard_event_called_ = true;
+ return prehandle_keyboard_event_;
+ }
+
+ virtual void HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE {
+ unhandled_keyboard_event_type_ = event.type;
+ unhandled_keyboard_event_called_ = true;
+ }
+
+ private:
+ bool prehandle_keyboard_event_;
+ bool prehandle_keyboard_event_called_;
+ WebInputEvent::Type prehandle_keyboard_event_type_;
+
+ bool unhandled_keyboard_event_called_;
+ WebInputEvent::Type unhandled_keyboard_event_type_;
+};
+
+// MockPaintingObserver --------------------------------------------------------
+
+class MockPaintingObserver : public NotificationObserver {
+ public:
+ void WidgetDidReceivePaintAtSizeAck(RenderWidgetHostImpl* host,
+ int tag,
+ const gfx::Size& size) {
+ host_ = reinterpret_cast<MockRenderWidgetHost*>(host);
+ tag_ = tag;
+ size_ = size;
+ }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ if (type == NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK) {
+ std::pair<int, gfx::Size>* size_ack_details =
+ Details<std::pair<int, gfx::Size> >(details).ptr();
+ WidgetDidReceivePaintAtSizeAck(
+ RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr()),
+ size_ack_details->first,
+ size_ack_details->second);
+ }
+ }
+
+ MockRenderWidgetHost* host() const { return host_; }
+ int tag() const { return tag_; }
+ gfx::Size size() const { return size_; }
+
+ private:
+ MockRenderWidgetHost* host_;
+ int tag_;
+ gfx::Size size_;
+};
+
+// RenderWidgetHostTest --------------------------------------------------------
+
+class RenderWidgetHostTest : public testing::Test {
+ public:
+ RenderWidgetHostTest() : process_(NULL) {
+ }
+ virtual ~RenderWidgetHostTest() {
+ }
+
+ protected:
+ // testing::Test
+ virtual void SetUp() {
+ browser_context_.reset(new TestBrowserContext());
+ delegate_.reset(new MockRenderWidgetHostDelegate());
+ process_ = new RenderWidgetHostProcess(browser_context_.get());
+#if defined(USE_AURA)
+ screen_.reset(aura::TestScreen::Create());
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get());
+#endif
+ host_.reset(
+ new MockRenderWidgetHost(delegate_.get(), process_, MSG_ROUTING_NONE));
+ view_.reset(new TestView(host_.get()));
+ host_->SetView(view_.get());
+ host_->Init();
+ }
+ virtual void TearDown() {
+ view_.reset();
+ host_.reset();
+ delegate_.reset();
+ process_ = NULL;
+ browser_context_.reset();
+
+#if defined(USE_AURA)
+ aura::Env::DeleteInstance();
+ screen_.reset();
+#endif
+
+ // Process all pending tasks to avoid leaks.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void SendInputEventACK(WebInputEvent::Type type,
+ InputEventAckState ack_result) {
+ scoped_ptr<IPC::Message> response(
+ new InputHostMsg_HandleInputEvent_ACK(0, type, ack_result,
+ ui::LatencyInfo()));
+ host_->OnMessageReceived(*response);
+ }
+
+ void SimulateKeyboardEvent(WebInputEvent::Type type) {
+ NativeWebKeyboardEvent key_event;
+ key_event.type = type;
+ key_event.windowsKeyCode = ui::VKEY_L; // non-null made up value.
+ host_->ForwardKeyboardEvent(key_event);
+ }
+
+ void SimulateMouseEvent(WebInputEvent::Type type) {
+ WebKit::WebMouseEvent mouse_event;
+ mouse_event.type = type;
+ host_->ForwardMouseEvent(mouse_event);
+ }
+
+ void SimulateWheelEvent(float dX, float dY, int modifiers, bool precise) {
+ WebMouseWheelEvent wheel_event;
+ wheel_event.type = WebInputEvent::MouseWheel;
+ wheel_event.deltaX = dX;
+ wheel_event.deltaY = dY;
+ wheel_event.modifiers = modifiers;
+ wheel_event.hasPreciseScrollingDeltas = precise;
+ host_->ForwardWheelEvent(wheel_event);
+ }
+
+ void SimulateMouseMove(int x, int y, int modifiers) {
+ WebKit::WebMouseEvent mouse_event;
+ mouse_event.type = WebInputEvent::MouseMove;
+ mouse_event.x = mouse_event.windowX = x;
+ mouse_event.y = mouse_event.windowY = y;
+ mouse_event.modifiers = modifiers;
+ host_->ForwardMouseEvent(mouse_event);
+ }
+
+ void SimulateWheelEventWithPhase(WebMouseWheelEvent::Phase phase) {
+ WebMouseWheelEvent wheel_event;
+ wheel_event.type = WebInputEvent::MouseWheel;
+ wheel_event.phase = phase;
+ host_->ForwardWheelEvent(wheel_event);
+ }
+
+ // Inject provided synthetic WebGestureEvent instance.
+ void SimulateGestureEventCore(WebInputEvent::Type type,
+ WebGestureEvent::SourceDevice sourceDevice,
+ WebGestureEvent* gesture_event) {
+ gesture_event->type = type;
+ gesture_event->sourceDevice = sourceDevice;
+ host_->ForwardGestureEvent(*gesture_event);
+ }
+
+ // Inject simple synthetic WebGestureEvent instances.
+ void SimulateGestureEvent(WebInputEvent::Type type,
+ WebGestureEvent::SourceDevice sourceDevice) {
+ WebGestureEvent gesture_event;
+ SimulateGestureEventCore(type, sourceDevice, &gesture_event);
+ }
+
+ void SimulateGestureScrollUpdateEvent(float dX, float dY, int modifiers) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.scrollUpdate.deltaX = dX;
+ gesture_event.data.scrollUpdate.deltaY = dY;
+ gesture_event.modifiers = modifiers;
+ SimulateGestureEventCore(WebInputEvent::GestureScrollUpdate,
+ WebGestureEvent::Touchscreen, &gesture_event);
+ }
+
+ void SimulateGesturePinchUpdateEvent(float scale,
+ float anchorX,
+ float anchorY,
+ int modifiers) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.pinchUpdate.scale = scale;
+ gesture_event.x = anchorX;
+ gesture_event.y = anchorY;
+ gesture_event.modifiers = modifiers;
+ SimulateGestureEventCore(WebInputEvent::GesturePinchUpdate,
+ WebGestureEvent::Touchscreen, &gesture_event);
+ }
+
+ // Inject synthetic GestureFlingStart events.
+ void SimulateGestureFlingStartEvent(
+ float velocityX,
+ float velocityY,
+ WebGestureEvent::SourceDevice sourceDevice) {
+ WebGestureEvent gesture_event;
+ gesture_event.data.flingStart.velocityX = velocityX;
+ gesture_event.data.flingStart.velocityY = velocityY;
+ SimulateGestureEventCore(WebInputEvent::GestureFlingStart, sourceDevice,
+ &gesture_event);
+ }
+
+ // Set the timestamp for the touch-event.
+ void SetTouchTimestamp(base::TimeDelta timestamp) {
+ touch_event_.timeStampSeconds = timestamp.InSecondsF();
+ }
+
+ // Sends a touch event (irrespective of whether the page has a touch-event
+ // handler or not).
+ void SendTouchEvent() {
+ host_->ForwardTouchEventWithLatencyInfo(touch_event_, ui::LatencyInfo());
+
+ // Mark all the points as stationary. And remove the points that have been
+ // released.
+ int point = 0;
+ for (unsigned int i = 0; i < touch_event_.touchesLength; ++i) {
+ if (touch_event_.touches[i].state == WebKit::WebTouchPoint::StateReleased)
+ continue;
+
+ touch_event_.touches[point] = touch_event_.touches[i];
+ touch_event_.touches[point].state =
+ WebKit::WebTouchPoint::StateStationary;
+ ++point;
+ }
+ touch_event_.touchesLength = point;
+ touch_event_.type = WebInputEvent::Undefined;
+ }
+
+ int PressTouchPoint(int x, int y) {
+ if (touch_event_.touchesLength == touch_event_.touchesLengthCap)
+ return -1;
+ WebKit::WebTouchPoint& point =
+ touch_event_.touches[touch_event_.touchesLength];
+ point.id = touch_event_.touchesLength;
+ point.position.x = point.screenPosition.x = x;
+ point.position.y = point.screenPosition.y = y;
+ point.state = WebKit::WebTouchPoint::StatePressed;
+ point.radiusX = point.radiusY = 1.f;
+ ++touch_event_.touchesLength;
+ touch_event_.type = WebInputEvent::TouchStart;
+ return point.id;
+ }
+
+ void MoveTouchPoint(int index, int x, int y) {
+ CHECK(index >= 0 && index < touch_event_.touchesLengthCap);
+ WebKit::WebTouchPoint& point = touch_event_.touches[index];
+ point.position.x = point.screenPosition.x = x;
+ point.position.y = point.screenPosition.y = y;
+ touch_event_.touches[index].state = WebKit::WebTouchPoint::StateMoved;
+ touch_event_.type = WebInputEvent::TouchMove;
+ }
+
+ void ReleaseTouchPoint(int index) {
+ CHECK(index >= 0 && index < touch_event_.touchesLengthCap);
+ touch_event_.touches[index].state = WebKit::WebTouchPoint::StateReleased;
+ touch_event_.type = WebInputEvent::TouchEnd;
+ }
+
+ const WebInputEvent* GetInputEventFromMessage(const IPC::Message& message) {
+ PickleIterator iter(message);
+ const char* data;
+ int data_length;
+ if (!message.ReadData(&iter, &data, &data_length))
+ return NULL;
+ return reinterpret_cast<const WebInputEvent*>(data);
+ }
+
+ base::MessageLoopForUI message_loop_;
+
+ scoped_ptr<TestBrowserContext> browser_context_;
+ RenderWidgetHostProcess* process_; // Deleted automatically by the widget.
+ scoped_ptr<MockRenderWidgetHostDelegate> delegate_;
+ scoped_ptr<MockRenderWidgetHost> host_;
+ scoped_ptr<TestView> view_;
+ scoped_ptr<gfx::Screen> screen_;
+
+ private:
+ WebTouchEvent touch_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostTest);
+};
+
+#if GTEST_HAS_PARAM_TEST
+// RenderWidgetHostWithSourceTest ----------------------------------------------
+
+// This is for tests that are to be run for all source devices.
+class RenderWidgetHostWithSourceTest
+ : public RenderWidgetHostTest,
+ public testing::WithParamInterface<WebGestureEvent::SourceDevice> {
+};
+#endif // GTEST_HAS_PARAM_TEST
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+TEST_F(RenderWidgetHostTest, Resize) {
+ // The initial bounds is the empty rect, but the screen info hasn't been sent
+ // yet, so setting it to the same thing should send the resize message.
+ view_->set_bounds(gfx::Rect());
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Setting the bounds to a "real" rect should send out the notification.
+ // but should not expect ack for empty physical backing size.
+ gfx::Rect original_size(0, 0, 100, 100);
+ process_->sink().ClearMessages();
+ view_->set_bounds(original_size);
+ view_->SetMockPhysicalBackingSize(gfx::Size());
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(original_size.size(), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Setting the bounds to a "real" rect should send out the notification.
+ // but should not expect ack for only physical backing size change.
+ process_->sink().ClearMessages();
+ view_->ClearMockPhysicalBackingSize();
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(original_size.size(), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Send out a update that's not a resize ack after setting resize ack pending
+ // flag. This should not clean the resize ack pending flag.
+ process_->sink().ClearMessages();
+ gfx::Rect second_size(0, 0, 110, 110);
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ view_->set_bounds(second_size);
+ host_->WasResized();
+ EXPECT_TRUE(host_->resize_ack_pending_);
+ ViewHostMsg_UpdateRect_Params params;
+ process_->InitUpdateRectParams(&params);
+ host_->OnUpdateRect(params);
+ EXPECT_TRUE(host_->resize_ack_pending_);
+ EXPECT_EQ(second_size.size(), host_->last_requested_size_);
+
+ // Sending out a new notification should NOT send out a new IPC message since
+ // a resize ACK is pending.
+ gfx::Rect third_size(0, 0, 120, 120);
+ process_->sink().ClearMessages();
+ view_->set_bounds(third_size);
+ host_->WasResized();
+ EXPECT_TRUE(host_->resize_ack_pending_);
+ EXPECT_EQ(second_size.size(), host_->last_requested_size_);
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Send a update that's a resize ack, but for the original_size we sent. Since
+ // this isn't the second_size, the message handler should immediately send
+ // a new resize message for the new size to the renderer.
+ process_->sink().ClearMessages();
+ params.flags = ViewHostMsg_UpdateRect_Flags::IS_RESIZE_ACK;
+ params.view_size = original_size.size();
+ host_->OnUpdateRect(params);
+ EXPECT_TRUE(host_->resize_ack_pending_);
+ EXPECT_EQ(third_size.size(), host_->last_requested_size_);
+ ASSERT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Send the resize ack for the latest size.
+ process_->sink().ClearMessages();
+ params.view_size = third_size.size();
+ host_->OnUpdateRect(params);
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(third_size.size(), host_->last_requested_size_);
+ ASSERT_FALSE(process_->sink().GetFirstMessageMatching(ViewMsg_Resize::ID));
+
+ // Now clearing the bounds should send out a notification but we shouldn't
+ // expect a resize ack (since the renderer won't ack empty sizes). The message
+ // should contain the new size (0x0) and not the previous one that we skipped
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect());
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Send a rect that has no area but has either width or height set.
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 0, 30));
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(0, 30), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Set the same size again. It should not be sent again.
+ process_->sink().ClearMessages();
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(0, 30), host_->last_requested_size_);
+ EXPECT_FALSE(process_->sink().GetFirstMessageMatching(ViewMsg_Resize::ID));
+
+ // A different size should be sent again, however.
+ view_->set_bounds(gfx::Rect(0, 0, 0, 31));
+ host_->WasResized();
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(0, 31), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+}
+
+// Test for crbug.com/25097. If a renderer crashes between a resize and the
+// corresponding update message, we must be sure to clear the resize ack logic.
+TEST_F(RenderWidgetHostTest, ResizeThenCrash) {
+ // Clear the first Resize message that carried screen info.
+ process_->sink().ClearMessages();
+
+ // Setting the bounds to a "real" rect should send out the notification.
+ gfx::Rect original_size(0, 0, 100, 100);
+ view_->set_bounds(original_size);
+ host_->WasResized();
+ EXPECT_TRUE(host_->resize_ack_pending_);
+ EXPECT_EQ(original_size.size(), host_->last_requested_size_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID));
+
+ // Simulate a renderer crash before the update message. Ensure all the
+ // resize ack logic is cleared. Must clear the view first so it doesn't get
+ // deleted.
+ host_->SetView(NULL);
+ host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
+ EXPECT_FALSE(host_->resize_ack_pending_);
+ EXPECT_EQ(gfx::Size(), host_->last_requested_size_);
+
+ // Reset the view so we can exit the test cleanly.
+ host_->SetView(view_.get());
+}
+
+// Tests setting custom background
+TEST_F(RenderWidgetHostTest, Background) {
+#if !defined(OS_MACOSX)
+ scoped_ptr<RenderWidgetHostView> view(
+ RenderWidgetHostView::CreateViewForWidget(host_.get()));
+#if defined(OS_LINUX) || defined(USE_AURA)
+ // TODO(derat): Call this on all platforms: http://crbug.com/102450.
+ // InitAsChild doesn't seem to work if NULL parent is passed on Windows,
+ // which leads to DCHECK failure in RenderWidgetHostView::Destroy.
+ // When you enable this for OS_WIN, enable |view.release()->Destroy()|
+ // below.
+ view->InitAsChild(NULL);
+#endif
+ host_->SetView(view.get());
+
+ // Create a checkerboard background to test with.
+ gfx::Canvas canvas(gfx::Size(4, 4), ui::SCALE_FACTOR_100P, true);
+ canvas.FillRect(gfx::Rect(0, 0, 2, 2), SK_ColorBLACK);
+ canvas.FillRect(gfx::Rect(2, 0, 2, 2), SK_ColorWHITE);
+ canvas.FillRect(gfx::Rect(0, 2, 2, 2), SK_ColorWHITE);
+ canvas.FillRect(gfx::Rect(2, 2, 2, 2), SK_ColorBLACK);
+ const SkBitmap& background =
+ canvas.sk_canvas()->getDevice()->accessBitmap(false);
+
+ // Set the background and make sure we get back a copy.
+ view->SetBackground(background);
+ EXPECT_EQ(4, view->GetBackground().width());
+ EXPECT_EQ(4, view->GetBackground().height());
+ EXPECT_EQ(background.getSize(), view->GetBackground().getSize());
+ background.lockPixels();
+ view->GetBackground().lockPixels();
+ EXPECT_TRUE(0 == memcmp(background.getPixels(),
+ view->GetBackground().getPixels(),
+ background.getSize()));
+ view->GetBackground().unlockPixels();
+ background.unlockPixels();
+
+ const IPC::Message* set_background =
+ process_->sink().GetUniqueMessageMatching(ViewMsg_SetBackground::ID);
+ ASSERT_TRUE(set_background);
+ Tuple1<SkBitmap> sent_background;
+ ViewMsg_SetBackground::Read(set_background, &sent_background);
+ EXPECT_EQ(background.getSize(), sent_background.a.getSize());
+ background.lockPixels();
+ sent_background.a.lockPixels();
+ EXPECT_TRUE(0 == memcmp(background.getPixels(),
+ sent_background.a.getPixels(),
+ background.getSize()));
+ sent_background.a.unlockPixels();
+ background.unlockPixels();
+
+#if defined(OS_LINUX) || defined(USE_AURA)
+ // See the comment above |InitAsChild(NULL)|.
+ host_->SetView(NULL);
+ static_cast<RenderWidgetHostViewPort*>(view.release())->Destroy();
+#endif
+
+#else
+ // TODO(port): Mac does not have gfx::Canvas. Maybe we can just change this
+ // test to use SkCanvas directly?
+#endif
+
+ // TODO(aa): It would be nice to factor out the painting logic so that we
+ // could test that, but it appears that would mean painting everything twice
+ // since windows HDC structures are opaque.
+}
+
+// Tests getting the backing store with the renderer not setting repaint ack
+// flags.
+TEST_F(RenderWidgetHostTest, GetBackingStore_NoRepaintAck) {
+ // First set the view size to match what the renderer is rendering.
+ ViewHostMsg_UpdateRect_Params params;
+ process_->InitUpdateRectParams(&params);
+ view_->set_bounds(gfx::Rect(params.view_size));
+
+ // We don't currently have a backing store, and if the renderer doesn't send
+ // one in time, we should get nothing.
+ process_->set_update_msg_should_reply(false);
+ BackingStore* backing = host_->GetBackingStore(true);
+ EXPECT_FALSE(backing);
+ // The widget host should have sent a request for a repaint, and there should
+ // be no paint ACK.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID));
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(
+ ViewMsg_UpdateRect_ACK::ID));
+
+ // Allowing the renderer to reply in time should give is a backing store.
+ process_->sink().ClearMessages();
+ process_->set_update_msg_should_reply(true);
+ process_->set_update_msg_reply_flags(0);
+ backing = host_->GetBackingStore(true);
+ EXPECT_TRUE(backing);
+ // The widget host should NOT have sent a request for a repaint, since there
+ // was an ACK already pending.
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID));
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ ViewMsg_UpdateRect_ACK::ID));
+}
+
+// Tests getting the backing store with the renderer sending a repaint ack.
+TEST_F(RenderWidgetHostTest, GetBackingStore_RepaintAck) {
+ // First set the view size to match what the renderer is rendering.
+ ViewHostMsg_UpdateRect_Params params;
+ process_->InitUpdateRectParams(&params);
+ view_->set_bounds(gfx::Rect(params.view_size));
+
+ // Doing a request request with the update message allowed should work and
+ // the repaint ack should work.
+ process_->set_update_msg_should_reply(true);
+ process_->set_update_msg_reply_flags(
+ ViewHostMsg_UpdateRect_Flags::IS_REPAINT_ACK);
+ BackingStore* backing = host_->GetBackingStore(true);
+ EXPECT_TRUE(backing);
+ // We still should not have sent out a repaint request since the last flags
+ // didn't have the repaint ack set, and the pending flag will still be set.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID));
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ ViewMsg_UpdateRect_ACK::ID));
+
+ // Asking again for the backing store should just re-use the existing one
+ // and not send any messagse.
+ process_->sink().ClearMessages();
+ backing = host_->GetBackingStore(true);
+ EXPECT_TRUE(backing);
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID));
+ EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(
+ ViewMsg_UpdateRect_ACK::ID));
+}
+
+// Test that we don't paint when we're hidden, but we still send the ACK. Most
+// of the rest of the painting is tested in the GetBackingStore* ones.
+TEST_F(RenderWidgetHostTest, HiddenPaint) {
+ BrowserThreadImpl ui_thread(BrowserThread::UI, base::MessageLoop::current());
+ // Hide the widget, it should have sent out a message to the renderer.
+ EXPECT_FALSE(host_->is_hidden_);
+ host_->WasHidden();
+ EXPECT_TRUE(host_->is_hidden_);
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_WasHidden::ID));
+
+ // Send it an update as from the renderer.
+ process_->sink().ClearMessages();
+ ViewHostMsg_UpdateRect_Params params;
+ process_->InitUpdateRectParams(&params);
+ host_->OnUpdateRect(params);
+
+ // It should have sent out the ACK.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ ViewMsg_UpdateRect_ACK::ID));
+
+ // Now unhide.
+ process_->sink().ClearMessages();
+ host_->WasShown();
+ EXPECT_FALSE(host_->is_hidden_);
+
+ // It should have sent out a restored message with a request to paint.
+ const IPC::Message* restored = process_->sink().GetUniqueMessageMatching(
+ ViewMsg_WasShown::ID);
+ ASSERT_TRUE(restored);
+ Tuple1<bool> needs_repaint;
+ ViewMsg_WasShown::Read(restored, &needs_repaint);
+ EXPECT_TRUE(needs_repaint.a);
+}
+
+TEST_F(RenderWidgetHostTest, PaintAtSize) {
+ const int kPaintAtSizeTag = 42;
+ host_->PaintAtSize(TransportDIB::GetFakeHandleForTest(), kPaintAtSizeTag,
+ gfx::Size(40, 60), gfx::Size(20, 30));
+ EXPECT_TRUE(
+ process_->sink().GetUniqueMessageMatching(ViewMsg_PaintAtSize::ID));
+
+ NotificationRegistrar registrar;
+ MockPaintingObserver observer;
+ registrar.Add(
+ &observer,
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK,
+ Source<RenderWidgetHost>(host_.get()));
+
+ host_->OnPaintAtSizeAck(kPaintAtSizeTag, gfx::Size(20, 30));
+ EXPECT_EQ(host_.get(), observer.host());
+ EXPECT_EQ(kPaintAtSizeTag, observer.tag());
+ EXPECT_EQ(20, observer.size().width());
+ EXPECT_EQ(30, observer.size().height());
+}
+
+TEST_F(RenderWidgetHostTest, IgnoreKeyEventsHandledByRenderer) {
+ // Simulate a keyboard event.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ // Make sure we sent the input event to the renderer.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Send the simulated response from the renderer back.
+ SendInputEventACK(WebInputEvent::RawKeyDown,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_FALSE(delegate_->unhandled_keyboard_event_called());
+}
+
+TEST_F(RenderWidgetHostTest, PreHandleRawKeyDownEvent) {
+ // Simluate the situation that the browser handled the key down event during
+ // pre-handle phrase.
+ delegate_->set_prehandle_keyboard_event(true);
+ process_->sink().ClearMessages();
+
+ // Simulate a keyboard event.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ EXPECT_TRUE(delegate_->prehandle_keyboard_event_called());
+ EXPECT_EQ(WebInputEvent::RawKeyDown,
+ delegate_->prehandle_keyboard_event_type());
+
+ // Make sure the RawKeyDown event is not sent to the renderer.
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // The browser won't pre-handle a Char event.
+ delegate_->set_prehandle_keyboard_event(false);
+
+ // Forward the Char event.
+ SimulateKeyboardEvent(WebInputEvent::Char);
+
+ // Make sure the Char event is suppressed.
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Forward the KeyUp event.
+ SimulateKeyboardEvent(WebInputEvent::KeyUp);
+
+ // Make sure only KeyUp was sent to the renderer.
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(InputMsg_HandleInputEvent::ID,
+ process_->sink().GetMessageAt(0)->type());
+ process_->sink().ClearMessages();
+
+ // Send the simulated response from the renderer back.
+ SendInputEventACK(WebInputEvent::KeyUp,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ EXPECT_TRUE(delegate_->unhandled_keyboard_event_called());
+ EXPECT_EQ(WebInputEvent::KeyUp, delegate_->unhandled_keyboard_event_type());
+}
+
+TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) {
+ SimulateWheelEvent(-5, 0, 0, true);
+
+ // Make sure we sent the input event to the renderer.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Send the simulated response from the renderer back.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(-5, view_->unhandled_wheel_event().deltaX);
+}
+
+TEST_F(RenderWidgetHostTest, UnhandledGestureEvent) {
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+
+ // Make sure we sent the input event to the renderer.
+ EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
+ InputMsg_HandleInputEvent::ID));
+ process_->sink().ClearMessages();
+
+ // Send the simulated response from the renderer back.
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(WebInputEvent::GestureScrollBegin, view_->gesture_event_type());
+ EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, view_->ack_result());
+}
+
+// Test that the hang monitor timer expires properly if a new timer is started
+// while one is in progress (see crbug.com/11007).
+TEST_F(RenderWidgetHostTest, DontPostponeHangMonitorTimeout) {
+ // Start with a short timeout.
+ host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10));
+
+ // Immediately try to add a long 30 second timeout.
+ EXPECT_FALSE(host_->unresponsive_timer_fired());
+ host_->StartHangMonitorTimeout(TimeDelta::FromSeconds(30));
+
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(host_->unresponsive_timer_fired());
+}
+
+// Test that the hang monitor timer expires properly if it is started, stopped,
+// and then started again.
+TEST_F(RenderWidgetHostTest, StopAndStartHangMonitorTimeout) {
+ // Start with a short timeout, then stop it.
+ host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10));
+ host_->StopHangMonitorTimeout();
+
+ // Start it again to ensure it still works.
+ EXPECT_FALSE(host_->unresponsive_timer_fired());
+ host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10));
+
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(40));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(host_->unresponsive_timer_fired());
+}
+
+// Test that the hang monitor timer expires properly if it is started, then
+// updated to a shorter duration.
+TEST_F(RenderWidgetHostTest, ShorterDelayHangMonitorTimeout) {
+ // Start with a timeout.
+ host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(100));
+
+ // Start it again with shorter delay.
+ EXPECT_FALSE(host_->unresponsive_timer_fired());
+ host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(20));
+
+ // Wait long enough for the second timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(25));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(host_->unresponsive_timer_fired());
+}
+
+// Test that the hang monitor catches two input events but only one ack.
+// This can happen if the second input event causes the renderer to hang.
+// This test will catch a regression of crbug.com/111185.
+TEST_F(RenderWidgetHostTest, MultipleInputEvents) {
+ // Configure the host to wait 10ms before considering
+ // the renderer hung.
+ host_->set_hung_renderer_delay_ms(10);
+
+ // Send two events but only one ack.
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ SendInputEventACK(WebInputEvent::RawKeyDown,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Wait long enough for first timeout and see if it fired.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(40));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(host_->unresponsive_timer_fired());
+}
+
+// This test is not valid for Windows because getting the shared memory
+// size doesn't work.
+#if !defined(OS_WIN)
+TEST_F(RenderWidgetHostTest, IncorrectBitmapScaleFactor) {
+ ViewHostMsg_UpdateRect_Params params;
+ process_->InitUpdateRectParams(&params);
+ params.scale_factor = params.scale_factor * 2;
+
+ EXPECT_EQ(0, process_->bad_msg_count());
+ host_->OnUpdateRect(params);
+ EXPECT_EQ(1, process_->bad_msg_count());
+}
+#endif
+
+// Tests that scroll ACKs are correctly handled by the overscroll-navigation
+// controller.
+TEST_F(RenderWidgetHostTest, WheelScrollEventOverscrolls) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+
+ // Simulate wheel events.
+ SimulateWheelEvent(-5, 0, 0, true); // sent directly
+ SimulateWheelEvent(-1, 1, 0, true); // enqueued
+ SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-20, 6, 1, true); // enqueued, different modifiers
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK the first wheel event as not processed.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK for the second (coalesced) event as not processed. This will
+ // start a back navigation. However, this will also cause the queued next
+ // event to be sent to the renderer. But since overscroll navigation has
+ // started, that event will also be included in the overscroll computation
+ // instead of being sent to the renderer. So the result will be an overscroll
+ // back navigation, and no event will be sent to the renderer.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(-81.f, host_->overscroll_delta_x());
+ EXPECT_EQ(-31.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Send a mouse-move event. This should cancel the overscroll navigation.
+ SimulateMouseMove(5, 10, 0);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+}
+
+// Tests that if some scroll events are consumed towards the start, then
+// subsequent scrolls do not horizontal overscroll.
+TEST_F(RenderWidgetHostTest, WheelScrollConsumedDoNotHorizOverscroll) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+
+ // Simulate wheel events.
+ SimulateWheelEvent(-5, 0, 0, true); // sent directly
+ SimulateWheelEvent(-1, -1, 0, true); // enqueued
+ SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-20, 6, 1, true); // enqueued, different modifiers
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK the first wheel event as processed.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK for the second (coalesced) event as not processed. This should
+ // not initiate overscroll, since the beginning of the scroll has been
+ // consumed. The queued event with different modifiers should be sent to the
+ // renderer.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+
+ // Indicate the end of the scrolling from the touchpad.
+ SimulateGestureFlingStartEvent(-1200.f, 0.f, WebGestureEvent::Touchpad);
+ EXPECT_EQ(1U, process_->sink().message_count());
+
+ // Start another scroll. This time, do not consume any scroll events.
+ process_->sink().ClearMessages();
+ SimulateWheelEvent(0, -5, 0, true); // sent directly
+ SimulateWheelEvent(0, -1, 0, true); // enqueued
+ SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-20, 6, 1, true); // enqueued, different modifiers
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK for the first wheel and the subsequent coalesced event as not
+ // processed. This should start a back-overscroll.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+}
+
+// Tests that wheel-scrolling correctly turns overscroll on and off.
+TEST_F(RenderWidgetHostTest, WheelScrollOverscrollToggle) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+
+ // Send a wheel event. ACK the event as not processed. This should not
+ // initiate an overscroll gesture since it doesn't cross the threshold yet.
+ SimulateWheelEvent(10, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more so as to not overscroll.
+ SimulateWheelEvent(10, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more to initiate an overscroll.
+ SimulateWheelEvent(40, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(60.f, host_->overscroll_delta_x());
+ EXPECT_EQ(10.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ process_->sink().ClearMessages();
+
+ // Scroll in the reverse direction enough to abort the overscroll.
+ SimulateWheelEvent(-20, 0, 0, true);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Continue to scroll in the reverse direction.
+ SimulateWheelEvent(-20, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Continue to scroll in the reverse direction enough to initiate overscroll
+ // in that direction.
+ SimulateWheelEvent(-55, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(-75.f, host_->overscroll_delta_x());
+ EXPECT_EQ(-25.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+}
+
+TEST_F(RenderWidgetHostTest, ScrollEventsOverscrollWithFling) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+
+ // Send a wheel event. ACK the event as not processed. This should not
+ // initiate an overscroll gesture since it doesn't cross the threshold yet.
+ SimulateWheelEvent(10, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more so as to not overscroll.
+ SimulateWheelEvent(20, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more to initiate an overscroll.
+ SimulateWheelEvent(30, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(60.f, host_->overscroll_delta_x());
+ EXPECT_EQ(10.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+
+ // Send a fling start, but with a small velocity, so that the overscroll is
+ // aborted. The fling should proceed to the renderer, through the gesture
+ // event filter.
+ SimulateGestureFlingStartEvent(0.f, 0.1f, WebGestureEvent::Touchpad);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, process_->sink().message_count());
+}
+
+// Same as ScrollEventsOverscrollWithFling, but with zero velocity. Checks that
+// the zero-velocity fling does not reach the renderer.
+TEST_F(RenderWidgetHostTest, ScrollEventsOverscrollWithZeroFling) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+
+ // Send a wheel event. ACK the event as not processed. This should not
+ // initiate an overscroll gesture since it doesn't cross the threshold yet.
+ SimulateWheelEvent(10, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more so as to not overscroll.
+ SimulateWheelEvent(20, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Scroll some more to initiate an overscroll.
+ SimulateWheelEvent(30, 0, 0, true);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(60.f, host_->overscroll_delta_x());
+ EXPECT_EQ(10.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+
+ // Send a fling start, but with a small velocity, so that the overscroll is
+ // aborted. The fling should proceed to the renderer, through the gesture
+ // event filter.
+ SimulateGestureFlingStartEvent(0.f, 0.f, WebGestureEvent::Touchpad);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+}
+
+// Tests that a fling in the opposite direction of the overscroll cancels the
+// overscroll nav instead of completing it.
+TEST_F(RenderWidgetHostTest, ReverseFlingCancelsOverscroll) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ {
+ // Start and end a gesture in the same direction without processing the
+ // gesture events in the renderer. This should initiate and complete an
+ // overscroll navigation.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(300, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ }
+
+ {
+ // Start over, except instead of ending the gesture with ScrollEnd, end it
+ // with a FlingStart, with velocity in the reverse direction. This should
+ // initiate an overscroll navigation, but it should be cancelled because of
+ // the fling in the opposite direction.
+ host_->overscroll_delegate()->Reset();
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(-300, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_delegate()->current_mode());
+ SimulateGestureFlingStartEvent(100, 0, WebGestureEvent::Touchscreen);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ }
+}
+
+// Tests that touch-scroll events are handled correctly by the overscroll
+// controller. This also tests that the overscroll controller and the
+// gesture-event filter play nice with each other.
+TEST_F(RenderWidgetHostTest, GestureScrollOverscrolls) {
+ // Turn off debounce handling for test isolation.
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Send another gesture event and ACK as not being processed. This should
+ // initiate the navigation gesture.
+ SimulateGestureScrollUpdateEvent(55, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(55.f, host_->overscroll_delta_x());
+ EXPECT_EQ(-5.f, host_->overscroll_delta_y());
+ EXPECT_EQ(5.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(-5.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ process_->sink().ClearMessages();
+
+ // Send another gesture update event. This event should be consumed by the
+ // controller, and not be forwarded to the renderer. The gesture-event filter
+ // should not also receive this event.
+ SimulateGestureScrollUpdateEvent(10, -5, 0);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(65.f, host_->overscroll_delta_x());
+ EXPECT_EQ(-10.f, host_->overscroll_delta_y());
+ EXPECT_EQ(15.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(-10.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+
+ // Now send a scroll end. This should cancel the overscroll gesture, and send
+ // the event to the renderer. The gesture-event filter should receive this
+ // event.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+}
+
+// Tests that if the page is scrolled because of a scroll-gesture, then that
+// particular scroll sequence never generates overscroll if the scroll direction
+// is horizontal.
+TEST_F(RenderWidgetHostTest, GestureScrollConsumedHorizontal) {
+ // Turn off debounce handling for test isolation.
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(10, 0, 0);
+
+ // Start scrolling on content. ACK both events as being processed.
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Send another gesture event and ACK as not being processed. This should
+ // not initiate overscroll because the beginning of the scroll event did
+ // scroll some content on the page.
+ SimulateGestureScrollUpdateEvent(55, 0, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+}
+
+// Tests that if the page is scrolled because of a scroll-gesture, then that
+// particular scroll sequence never generates overscroll if the scroll direction
+// is vertical.
+TEST_F(RenderWidgetHostTest, GestureScrollConsumedVertical) {
+ // Turn off debounce handling for test isolation.
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(0, -1, 0);
+
+ // Start scrolling on content. ACK both events as being processed.
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Send another gesture event and ACK as not being processed. This should
+ // initiate overscroll because the scroll was in the vertical direction even
+ // though the beginning of the scroll did scroll content.
+ SimulateGestureScrollUpdateEvent(0, -50, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NORTH, host_->overscroll_mode());
+
+ // Changing direction of scroll to be horizontal to test that this causes the
+ // vertical overscroll to stop.
+ SimulateGestureScrollUpdateEvent(500, 0, 0);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+}
+
+// Tests that the overscroll controller plays nice with touch-scrolls and the
+// gesture event filter with debounce filtering turned on.
+TEST_F(RenderWidgetHostTest, GestureScrollDebounceOverscrolls) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(100);
+ process_->sink().ClearMessages();
+
+ // Start scrolling. Receive ACK as it being processed.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Send update events.
+ SimulateGestureScrollUpdateEvent(25, 0, 0);
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Quickly end and restart the scroll gesture. These two events should get
+ // discarded.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, host_->GestureEventDebouncingQueueSize());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(2U, host_->GestureEventDebouncingQueueSize());
+
+ // Send another update event. This should get into the queue.
+ SimulateGestureScrollUpdateEvent(30, 0, 0);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(2U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+
+ // Receive an ACK for the first scroll-update event as not being processed.
+ // This will contribute to the overscroll gesture, but not enough for the
+ // overscroll controller to start consuming gesture events. This also cause
+ // the queued gesture event to be forwarded to the renderer.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Send another update event. This should get into the queue.
+ SimulateGestureScrollUpdateEvent(10, 0, 0);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(2U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+
+ // Receive an ACK for the second scroll-update event as not being processed.
+ // This will now initiate an overscroll. This will also cause the queued
+ // gesture event to be released. But instead of going to the renderer, it will
+ // be consumed by the overscroll controller.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(65.f, host_->overscroll_delta_x());
+ EXPECT_EQ(15.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+}
+
+// Tests that the gesture debounce timer plays nice with the overscroll
+// controller.
+TEST_F(RenderWidgetHostTest, GestureScrollDebounceTimerOverscroll) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(10);
+ process_->sink().ClearMessages();
+
+ // Start scrolling. Receive ACK as it being processed.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Send update events.
+ SimulateGestureScrollUpdateEvent(55, 0, 0);
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Send an end event. This should get in the debounce queue.
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, host_->GestureEventDebouncingQueueSize());
+
+ // Receive ACK for the scroll-update event.
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(55.f, host_->overscroll_delta_x());
+ EXPECT_EQ(5.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Let the timer for the debounce queue fire. That should release the queued
+ // scroll-end event. Since overscroll has started, but there hasn't been
+ // enough overscroll to complete the gesture, the overscroll controller
+ // will reset the state. The scroll-end should therefore be dispatched to the
+ // renderer, and the gesture-event-filter should await an ACK for it.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(15));
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_EQ(1U, process_->sink().message_count());
+}
+
+// Tests that when touch-events are dispatched to the renderer, the overscroll
+// gesture deals with them correctly.
+TEST_F(RenderWidgetHostTest, OverscrollWithTouchEvents) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(10);
+ host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ // The test sends an intermingled sequence of touch and gesture events.
+
+ PressTouchPoint(0, 1);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchStart,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ MoveTouchPoint(0, 20, 5);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(20, 0, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Another touch move event should reach the renderer since overscroll hasn't
+ // started yet.
+ MoveTouchPoint(0, 65, 10);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ SimulateGestureScrollUpdateEvent(45, 0, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(65.f, host_->overscroll_delta_x());
+ EXPECT_EQ(15.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ // Send another touch event. The page should get the touch-move event, even
+ // though overscroll has started.
+ MoveTouchPoint(0, 55, 5);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->TouchEventQueueSize());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(65.f, host_->overscroll_delta_x());
+ EXPECT_EQ(15.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ SimulateGestureScrollUpdateEvent(-10, 0, 0);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(55.f, host_->overscroll_delta_x());
+ EXPECT_EQ(5.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+
+ MoveTouchPoint(0, 255, 5);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->TouchEventQueueSize());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::TouchMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ SimulateGestureScrollUpdateEvent(200, 0, 0);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(255.f, host_->overscroll_delta_x());
+ EXPECT_EQ(205.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+
+ // The touch-end/cancel event should always reach the renderer if the page has
+ // touch handlers.
+ ReleaseTouchPoint(0);
+ SendTouchEvent();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->TouchEventQueueSize());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::TouchEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+
+ SimulateGestureEvent(WebKit::WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->TouchEventQueueSize());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->completed_mode());
+}
+
+// Tests that touch-gesture end is dispatched to the renderer at the end of a
+// touch-gesture initiated overscroll.
+TEST_F(RenderWidgetHostTest, TouchGestureEndDispatchedAfterOverscrollComplete) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(10);
+ host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ // Start scrolling. Receive ACK as it being processed.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Send update events.
+ SimulateGestureScrollUpdateEvent(55, -5, 0);
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(55.f, host_->overscroll_delta_x());
+ EXPECT_EQ(5.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(-5.f, host_->overscroll_delegate()->delta_y());
+
+ // Send end event.
+ SimulateGestureEvent(WebKit::WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, host_->GestureEventDebouncingQueueSize());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+
+ SendInputEventACK(WebKit::WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+
+ // Start scrolling. Receive ACK as it being processed.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ // Send update events.
+ SimulateGestureScrollUpdateEvent(235, -5, 0);
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(235.f, host_->overscroll_delta_x());
+ EXPECT_EQ(185.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(-5.f, host_->overscroll_delegate()->delta_y());
+
+ // Send end event.
+ SimulateGestureEvent(WebKit::WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(1U, host_->GestureEventDebouncingQueueSize());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+
+ SendInputEventACK(WebKit::WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(0U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+}
+
+TEST_F(RenderWidgetHostTest, OverscrollDirectionChange) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(100);
+ process_->sink().ClearMessages();
+
+ // Start scrolling. Receive ACK as it being processed.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Send update events and receive ack as not consumed.
+ SimulateGestureScrollUpdateEvent(125, -5, 0);
+ EXPECT_EQ(1U, host_->GestureEventLastQueueEventSize());
+ EXPECT_EQ(0U, host_->GestureEventDebouncingQueueSize());
+ EXPECT_TRUE(host_->ScrollingInProgress());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Send another update event, but in the reverse direction. The overscroll
+ // controller will consume the event, and reset the overscroll mode.
+ SimulateGestureScrollUpdateEvent(-260, 0, 0);
+ EXPECT_EQ(0U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+
+ // Since the overscroll mode has been reset, the next scroll update events
+ // should reach the renderer.
+ SimulateGestureScrollUpdateEvent(-20, 0, 0);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+}
+
+// Tests that if a mouse-move event completes the overscroll gesture, future
+// move events do reach the renderer.
+TEST_F(RenderWidgetHostTest, OverscrollMouseMoveCompletion) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ SimulateWheelEvent(5, 0, 0, true); // sent directly
+ SimulateWheelEvent(-1, 0, 0, true); // enqueued
+ SimulateWheelEvent(-10, -3, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-15, -1, 0, true); // coalesced into previous event
+ SimulateWheelEvent(-30, -3, 0, true); // coalesced into previous event
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK the first wheel event as not processed.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Receive ACK for the second (coalesced) event as not processed. This will
+ // start an overcroll gesture.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Send a mouse-move event. This should cancel the overscroll navigation
+ // (since the amount overscrolled is not above the threshold), and so the
+ // mouse-move should reach the renderer.
+ SimulateMouseMove(5, 10, 0);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::MouseMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Moving the mouse more should continue to send the events to the renderer.
+ SimulateMouseMove(5, 10, 0);
+ SendInputEventACK(WebInputEvent::MouseMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // Now try with gestures.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(300, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ process_->sink().ClearMessages();
+
+ // Overscroll gesture is in progress. Send a mouse-move now. This should
+ // complete the gesture (because the amount overscrolled is above the
+ // threshold), and consume the event.
+ SimulateMouseMove(5, 10, 0);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Move mouse some more. The mouse-move events should reach the renderer.
+ SimulateMouseMove(5, 10, 0);
+ EXPECT_EQ(1U, process_->sink().message_count());
+
+ SendInputEventACK(WebInputEvent::MouseMove,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ process_->sink().ClearMessages();
+}
+
+// Tests that if a page scrolled, then the overscroll controller's states are
+// reset after the end of the scroll.
+TEST_F(RenderWidgetHostTest, OverscrollStateResetsAfterScroll) {
+ host_->SetupForOverscrollControllerTest();
+ host_->set_debounce_interval_time_ms(0);
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ SimulateWheelEvent(0, 5, 0, true); // sent directly
+ SimulateWheelEvent(0, 30, 0, true); // enqueued
+ SimulateWheelEvent(0, 40, 0, true); // coalesced into previous event
+ SimulateWheelEvent(0, 10, 0, true); // coalesced into previous event
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // The first wheel event is consumed. Dispatches the queued wheel event.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_TRUE(host_->ScrollStateIsContentScrolling());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ // The second wheel event is consumed.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_TRUE(host_->ScrollStateIsContentScrolling());
+
+ // Touchpad scroll can end with a zero-velocity fling. But it is not
+ // dispatched, but it should still reset the overscroll controller state.
+ SimulateGestureFlingStartEvent(0.f, 0.f, WebGestureEvent::Touchpad);
+ EXPECT_TRUE(host_->ScrollStateIsUnknown());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateWheelEvent(-5, 0, 0, true); // sent directly
+ SimulateWheelEvent(-60, 0, 0, true); // enqueued
+ SimulateWheelEvent(-100, 0, 0, true); // coalesced into previous event
+ EXPECT_EQ(1U, process_->sink().message_count());
+ EXPECT_TRUE(host_->ScrollStateIsUnknown());
+ process_->sink().ClearMessages();
+
+ // The first wheel scroll did not scroll content. Overscroll should not start
+ // yet, since enough hasn't been scrolled.
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_TRUE(host_->ScrollStateIsUnknown());
+ EXPECT_EQ(1U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+
+ SendInputEventACK(WebInputEvent::MouseWheel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_mode());
+ EXPECT_TRUE(host_->ScrollStateIsOverscrolling());
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateGestureFlingStartEvent(0.f, 0.f, WebGestureEvent::Touchpad);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_WEST, host_->overscroll_delegate()->completed_mode());
+ EXPECT_TRUE(host_->ScrollStateIsUnknown());
+ EXPECT_EQ(0U, process_->sink().message_count());
+ process_->sink().ClearMessages();
+}
+
+TEST_F(RenderWidgetHostTest, OverscrollResetsOnBlur) {
+ host_->SetupForOverscrollControllerTest();
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ // Start an overscroll with gesture scroll. In the middle of the scroll, blur
+ // the host.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(300, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+
+ host_->Blur();
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->completed_mode());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_x());
+ EXPECT_EQ(0.f, host_->overscroll_delegate()->delta_y());
+ process_->sink().ClearMessages();
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Start a scroll gesture again. This should correctly start the overscroll
+ // after the threshold.
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ SimulateGestureScrollUpdateEvent(300, -5, 0);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->completed_mode());
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollEnd,
+ WebGestureEvent::Touchscreen);
+ EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_delegate()->current_mode());
+ EXPECT_EQ(OVERSCROLL_EAST, host_->overscroll_delegate()->completed_mode());
+ process_->sink().ClearMessages();
+}
+
+#define TEST_InputRouterRoutes_NOARGS(INPUTMSG) \
+ TEST_F(RenderWidgetHostTest, InputRouterRoutes##INPUTMSG) { \
+ host_->SetupForInputRouterTest(); \
+ host_->INPUTMSG(); \
+ EXPECT_TRUE(host_->mock_input_router()->send_event_called_); \
+ }
+
+TEST_InputRouterRoutes_NOARGS(Undo);
+TEST_InputRouterRoutes_NOARGS(Redo);
+TEST_InputRouterRoutes_NOARGS(Cut);
+TEST_InputRouterRoutes_NOARGS(Copy);
+#if defined(OS_MACOSX)
+TEST_InputRouterRoutes_NOARGS(CopyToFindPboard);
+#endif
+TEST_InputRouterRoutes_NOARGS(Paste);
+TEST_InputRouterRoutes_NOARGS(PasteAndMatchStyle);
+TEST_InputRouterRoutes_NOARGS(Delete);
+TEST_InputRouterRoutes_NOARGS(SelectAll);
+TEST_InputRouterRoutes_NOARGS(Unselect);
+TEST_InputRouterRoutes_NOARGS(Focus);
+TEST_InputRouterRoutes_NOARGS(Blur);
+TEST_InputRouterRoutes_NOARGS(LostCapture);
+
+#undef TEST_InputRouterRoutes_NOARGS
+
+TEST_F(RenderWidgetHostTest, InputRouterRoutesReplace) {
+ host_->SetupForInputRouterTest();
+ host_->Replace(EmptyString16());
+ EXPECT_TRUE(host_->mock_input_router()->send_event_called_);
+}
+
+TEST_F(RenderWidgetHostTest, InputRouterRoutesReplaceMisspelling) {
+ host_->SetupForInputRouterTest();
+ host_->ReplaceMisspelling(EmptyString16());
+ EXPECT_TRUE(host_->mock_input_router()->send_event_called_);
+}
+
+TEST_F(RenderWidgetHostTest, IgnoreInputEvent) {
+ host_->SetupForInputRouterTest();
+
+ host_->SetIgnoreInputEvents(true);
+
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_);
+
+ SimulateMouseEvent(WebInputEvent::MouseMove);
+ EXPECT_FALSE(host_->mock_input_router()->sent_mouse_event_);
+
+ SimulateWheelEvent(0, 100, 0, true);
+ EXPECT_FALSE(host_->mock_input_router()->sent_wheel_event_);
+
+ SimulateGestureEvent(WebInputEvent::GestureScrollBegin,
+ WebGestureEvent::Touchscreen);
+ EXPECT_FALSE(host_->mock_input_router()->sent_gesture_event_);
+
+ PressTouchPoint(100, 100);
+ SendTouchEvent();
+ EXPECT_FALSE(host_->mock_input_router()->send_touch_event_not_cancelled_);
+}
+
+TEST_F(RenderWidgetHostTest, KeyboardListenerIgnoresEvent) {
+ host_->SetupForInputRouterTest();
+
+ scoped_ptr<MockKeyboardListener> keyboard_listener_(new MockKeyboardListener);
+ host_->AddKeyboardListener(keyboard_listener_.get());
+
+ keyboard_listener_->set_handle_key_press_event(false);
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_);
+
+ host_->RemoveKeyboardListener(keyboard_listener_.get());
+}
+
+TEST_F(RenderWidgetHostTest, KeyboardListenerSuppressFollowingEvents) {
+ host_->SetupForInputRouterTest();
+
+ scoped_ptr<MockKeyboardListener> keyboard_listener_(new MockKeyboardListener);
+ host_->AddKeyboardListener(keyboard_listener_.get());
+
+ // KeyboardListener handles the first event
+ keyboard_listener_->set_handle_key_press_event(true);
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+
+ EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_);
+
+ // Following Char events should be suppressed
+ keyboard_listener_->set_handle_key_press_event(false);
+ SimulateKeyboardEvent(WebInputEvent::Char);
+ EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_);
+ SimulateKeyboardEvent(WebInputEvent::Char);
+ EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_);
+
+ // Sending RawKeyDown event should stop suppression
+ SimulateKeyboardEvent(WebInputEvent::RawKeyDown);
+ EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_);
+
+ host_->mock_input_router()->sent_keyboard_event_ = false;
+ SimulateKeyboardEvent(WebInputEvent::Char);
+ EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_);
+
+ host_->RemoveKeyboardListener(keyboard_listener_.get());
+}
+
+TEST_F(RenderWidgetHostTest, InputRouterReceivesHandleInputEvent_ACK) {
+ host_->SetupForInputRouterTest();
+
+ SendInputEventACK(WebInputEvent::RawKeyDown,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ EXPECT_TRUE(host_->mock_input_router()->message_received_);
+}
+
+TEST_F(RenderWidgetHostTest, InputRouterReceivesMoveCaret_ACK) {
+ host_->SetupForInputRouterTest();
+
+ host_->OnMessageReceived(ViewHostMsg_MoveCaret_ACK(0));
+
+ EXPECT_TRUE(host_->mock_input_router()->message_received_);
+}
+
+TEST_F(RenderWidgetHostTest, InputRouterReceivesSelectRange_ACK) {
+ host_->SetupForInputRouterTest();
+
+ host_->OnMessageReceived(ViewHostMsg_SelectRange_ACK(0));
+
+ EXPECT_TRUE(host_->mock_input_router()->message_received_);
+}
+
+TEST_F(RenderWidgetHostTest, InputRouterReceivesHasTouchEventHandlers) {
+ host_->SetupForInputRouterTest();
+
+ host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+
+ EXPECT_TRUE(host_->mock_input_router()->message_received_);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_android.cc b/chromium/content/browser/renderer_host/render_widget_host_view_android.cc
new file mode 100644
index 00000000000..8e885407e5c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -0,0 +1,1296 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_android.h"
+
+#include <android/bitmap.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/worker_pool.h"
+#include "cc/layers/delegated_renderer_layer.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/texture_layer.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/trees/layer_tree_host.h"
+#include "content/browser/accessibility/browser_accessibility_manager_android.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/android/in_process/synchronous_compositor_impl.h"
+#include "content/browser/android/overscroll_glow.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/compositor_impl_android.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/image_transport_factory_android.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/surface_texture_transport_client_android.h"
+#include "content/browser/renderer_host/touch_smooth_scroll_gesture_android.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/common/content_switches.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/android/device_display_info.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace content {
+
+namespace {
+
+const int kUndefinedOutputSurfaceId = -1;
+
+void InsertSyncPointAndAckForGpu(
+ int gpu_host_id, int route_id, const std::string& return_mailbox) {
+ uint32 sync_point =
+ ImageTransportFactoryAndroid::GetInstance()->InsertSyncPoint();
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.mailbox_name = return_mailbox;
+ ack_params.sync_point = sync_point;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ route_id, gpu_host_id, ack_params);
+}
+
+void InsertSyncPointAndAckForCompositor(
+ int renderer_host_id,
+ uint32 output_surface_id,
+ int route_id,
+ const gpu::Mailbox& return_mailbox,
+ const gfx::Size return_size) {
+ cc::CompositorFrameAck ack;
+ ack.gl_frame_data.reset(new cc::GLFrameData());
+ if (!return_mailbox.IsZero()) {
+ ack.gl_frame_data->mailbox = return_mailbox;
+ ack.gl_frame_data->size = return_size;
+ ack.gl_frame_data->sync_point =
+ ImageTransportFactoryAndroid::GetInstance()->InsertSyncPoint();
+ }
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ route_id, output_surface_id, renderer_host_id, ack);
+}
+
+// Sends an acknowledgement to the renderer of a processed IME event.
+void SendImeEventAck(RenderWidgetHostImpl* host) {
+ host->Send(new ViewMsg_ImeEventAck(host->GetRoutingID()));
+}
+
+void CopyFromCompositingSurfaceFinished(
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ const cc::TextureMailbox::ReleaseCallback& release_callback,
+ scoped_ptr<SkBitmap> bitmap,
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock,
+ bool result) {
+ bitmap_pixels_lock.reset();
+ release_callback.Run(0, false);
+ callback.Run(result, *bitmap);
+}
+
+} // anonymous namespace
+
+RenderWidgetHostViewAndroid::RenderWidgetHostViewAndroid(
+ RenderWidgetHostImpl* widget_host,
+ ContentViewCoreImpl* content_view_core)
+ : host_(widget_host),
+ needs_begin_frame_(false),
+ are_layers_attached_(true),
+ content_view_core_(NULL),
+ ime_adapter_android_(this),
+ cached_background_color_(SK_ColorWHITE),
+ texture_id_in_layer_(0),
+ current_mailbox_output_surface_id_(kUndefinedOutputSurfaceId),
+ weak_ptr_factory_(this),
+ overscroll_effect_enabled_(true) {
+ if (CompositorImpl::UsesDirectGL()) {
+ surface_texture_transport_.reset(new SurfaceTextureTransportClient());
+ layer_ = surface_texture_transport_->Initialize();
+ layer_->SetIsDrawable(true);
+ } else {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableDelegatedRenderer)) {
+ delegated_renderer_layer_ = cc::DelegatedRendererLayer::Create(this);
+ layer_ = delegated_renderer_layer_;
+ } else {
+ texture_layer_ = cc::TextureLayer::Create(this);
+ layer_ = texture_layer_;
+ }
+ }
+
+ layer_->SetContentsOpaque(true);
+
+ overscroll_effect_enabled_ = !CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kDisableOverscrollEdgeEffect);
+ // Don't block the main thread with effect resource loading.
+ // Actual effect creation is deferred until an overscroll event is received.
+ if (overscroll_effect_enabled_) {
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(&OverscrollGlow::EnsureResources),
+ true);
+ }
+
+ host_->SetView(this);
+ SetContentViewCore(content_view_core);
+ ImageTransportFactoryAndroid::AddObserver(this);
+}
+
+RenderWidgetHostViewAndroid::~RenderWidgetHostViewAndroid() {
+ ImageTransportFactoryAndroid::RemoveObserver(this);
+ SetContentViewCore(NULL);
+ DCHECK(ack_callbacks_.empty());
+ if (texture_id_in_layer_) {
+ ImageTransportFactoryAndroid::GetInstance()->DeleteTexture(
+ texture_id_in_layer_);
+ }
+
+ if (texture_layer_.get())
+ texture_layer_->ClearClient();
+}
+
+
+bool RenderWidgetHostViewAndroid::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewAndroid, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ImeBatchStateChanged_ACK,
+ OnProcessImeBatchStateAck)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_StartContentIntent, OnStartContentIntent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeBodyBackgroundColor,
+ OnDidChangeBodyBackgroundColor)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetNeedsBeginFrame,
+ OnSetNeedsBeginFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_TextInputStateChanged,
+ OnTextInputStateChanged)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void RenderWidgetHostViewAndroid::InitAsChild(gfx::NativeView parent_view) {
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewAndroid::InitAsPopup(
+ RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewAndroid::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ NOTIMPLEMENTED();
+}
+
+RenderWidgetHost*
+RenderWidgetHostViewAndroid::GetRenderWidgetHost() const {
+ return host_;
+}
+
+void RenderWidgetHostViewAndroid::WasShown() {
+ if (!host_->is_hidden())
+ return;
+
+ host_->WasShown();
+}
+
+void RenderWidgetHostViewAndroid::WasHidden() {
+ RunAckCallbacks();
+
+ if (host_->is_hidden())
+ return;
+
+ // Inform the renderer that we are being hidden so it can reduce its resource
+ // utilization.
+ host_->WasHidden();
+}
+
+void RenderWidgetHostViewAndroid::WasResized() {
+ if (surface_texture_transport_.get() && content_view_core_)
+ surface_texture_transport_->SetSize(
+ content_view_core_->GetPhysicalBackingSize());
+
+ host_->WasResized();
+}
+
+void RenderWidgetHostViewAndroid::SetSize(const gfx::Size& size) {
+ // Ignore the given size as only the Java code has the power to
+ // resize the view on Android.
+ WasResized();
+}
+
+void RenderWidgetHostViewAndroid::SetBounds(const gfx::Rect& rect) {
+ SetSize(rect.size());
+}
+
+WebKit::WebGLId RenderWidgetHostViewAndroid::GetScaledContentTexture(
+ float scale,
+ gfx::Size* out_size) {
+ gfx::Size size(gfx::ToCeiledSize(
+ gfx::ScaleSize(texture_size_in_layer_, scale)));
+
+ if (!CompositorImpl::IsInitialized() ||
+ texture_id_in_layer_ == 0 ||
+ texture_size_in_layer_.IsEmpty() ||
+ size.IsEmpty()) {
+ if (out_size)
+ out_size->SetSize(0, 0);
+
+ return 0;
+ }
+
+ if (out_size)
+ *out_size = size;
+
+ GLHelper* helper = ImageTransportFactoryAndroid::GetInstance()->GetGLHelper();
+ return helper->CopyAndScaleTexture(texture_id_in_layer_,
+ texture_size_in_layer_,
+ size,
+ true,
+ GLHelper::SCALER_QUALITY_FAST);
+}
+
+bool RenderWidgetHostViewAndroid::PopulateBitmapWithContents(jobject jbitmap) {
+ if (!CompositorImpl::IsInitialized() ||
+ texture_id_in_layer_ == 0 ||
+ texture_size_in_layer_.IsEmpty())
+ return false;
+
+ gfx::JavaBitmap bitmap(jbitmap);
+
+ // TODO(dtrainor): Eventually add support for multiple formats here.
+ DCHECK(bitmap.format() == ANDROID_BITMAP_FORMAT_RGBA_8888);
+
+ GLHelper* helper = ImageTransportFactoryAndroid::GetInstance()->GetGLHelper();
+
+ WebKit::WebGLId texture = helper->CopyAndScaleTexture(
+ texture_id_in_layer_,
+ texture_size_in_layer_,
+ bitmap.size(),
+ true,
+ GLHelper::SCALER_QUALITY_FAST);
+ if (texture == 0)
+ return false;
+
+ helper->ReadbackTextureSync(texture,
+ gfx::Rect(bitmap.size()),
+ static_cast<unsigned char*> (bitmap.pixels()));
+
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ context->deleteTexture(texture);
+
+ return true;
+}
+
+bool RenderWidgetHostViewAndroid::HasValidFrame() const {
+ return texture_id_in_layer_ != 0 &&
+ content_view_core_ &&
+ !texture_size_in_layer_.IsEmpty();
+}
+
+gfx::NativeView RenderWidgetHostViewAndroid::GetNativeView() const {
+ return content_view_core_->GetViewAndroid();
+}
+
+gfx::NativeViewId RenderWidgetHostViewAndroid::GetNativeViewId() const {
+ return reinterpret_cast<gfx::NativeViewId>(
+ const_cast<RenderWidgetHostViewAndroid*>(this));
+}
+
+gfx::NativeViewAccessible
+RenderWidgetHostViewAndroid::GetNativeViewAccessible() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void RenderWidgetHostViewAndroid::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) {
+ // We don't have plugin windows on Android. Do nothing. Note: this is called
+ // from RenderWidgetHost::OnUpdateRect which is itself invoked while
+ // processing the corresponding message from Renderer.
+}
+
+void RenderWidgetHostViewAndroid::Focus() {
+ host_->Focus();
+ host_->SetInputMethodActive(true);
+ ResetClipping();
+ if (overscroll_effect_)
+ overscroll_effect_->SetEnabled(true);
+}
+
+void RenderWidgetHostViewAndroid::Blur() {
+ host_->ExecuteEditCommand("Unselect", "");
+ host_->SetInputMethodActive(false);
+ host_->Blur();
+ if (overscroll_effect_)
+ overscroll_effect_->SetEnabled(false);
+}
+
+bool RenderWidgetHostViewAndroid::HasFocus() const {
+ if (!content_view_core_)
+ return false; // ContentViewCore not created yet.
+
+ return content_view_core_->HasFocus();
+}
+
+bool RenderWidgetHostViewAndroid::IsSurfaceAvailableForCopy() const {
+ return HasValidFrame();
+}
+
+void RenderWidgetHostViewAndroid::Show() {
+ if (are_layers_attached_)
+ return;
+
+ are_layers_attached_ = true;
+ AttachLayers();
+}
+
+void RenderWidgetHostViewAndroid::Hide() {
+ if (!are_layers_attached_)
+ return;
+
+ are_layers_attached_ = false;
+ RemoveLayers();
+}
+
+bool RenderWidgetHostViewAndroid::IsShowing() {
+ // ContentViewCoreImpl represents the native side of the Java
+ // ContentViewCore. It being NULL means that it is not attached
+ // to the View system yet, so we treat this RWHVA as hidden.
+ return are_layers_attached_ && content_view_core_;
+}
+
+gfx::Rect RenderWidgetHostViewAndroid::GetViewBounds() const {
+ if (!content_view_core_)
+ return gfx::Rect();
+
+ gfx::Size size = content_view_core_->GetViewportSizeDip();
+ gfx::Size offset = content_view_core_->GetViewportSizeOffsetDip();
+ size.Enlarge(-offset.width(), -offset.height());
+
+ return gfx::Rect(size);
+}
+
+gfx::Size RenderWidgetHostViewAndroid::GetPhysicalBackingSize() const {
+ if (!content_view_core_)
+ return gfx::Size();
+
+ return content_view_core_->GetPhysicalBackingSize();
+}
+
+float RenderWidgetHostViewAndroid::GetOverdrawBottomHeight() const {
+ if (!content_view_core_)
+ return 0.f;
+
+ return content_view_core_->GetOverdrawBottomHeightDip();
+}
+
+void RenderWidgetHostViewAndroid::UpdateCursor(const WebCursor& cursor) {
+ // There are no cursors on Android.
+}
+
+void RenderWidgetHostViewAndroid::SetIsLoading(bool is_loading) {
+ // Do nothing. The UI notification is handled through ContentViewClient which
+ // is TabContentsDelegate.
+}
+
+void RenderWidgetHostViewAndroid::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ // Unused on Android, which uses OnTextInputChanged instead.
+}
+
+int RenderWidgetHostViewAndroid::GetNativeImeAdapter() {
+ return reinterpret_cast<int>(&ime_adapter_android_);
+}
+
+void RenderWidgetHostViewAndroid::OnTextInputStateChanged(
+ const ViewHostMsg_TextInputState_Params& params) {
+ // If an acknowledgement is required for this event, regardless of how we exit
+ // from this method, we must acknowledge that we processed the input state
+ // change.
+ base::ScopedClosureRunner ack_caller(base::Bind(&SendImeEventAck, host_));
+ if (!params.require_ack)
+ ack_caller.Release();
+
+ if (!IsShowing())
+ return;
+
+ content_view_core_->UpdateImeAdapter(
+ GetNativeImeAdapter(),
+ static_cast<int>(params.type),
+ params.value, params.selection_start, params.selection_end,
+ params.composition_start, params.composition_end,
+ params.show_ime_if_needed);
+}
+
+void RenderWidgetHostViewAndroid::OnProcessImeBatchStateAck(bool is_begin) {
+ if (content_view_core_)
+ content_view_core_->ProcessImeBatchStateAck(is_begin);
+}
+
+void RenderWidgetHostViewAndroid::OnDidChangeBodyBackgroundColor(
+ SkColor color) {
+ if (cached_background_color_ == color)
+ return;
+
+ cached_background_color_ = color;
+ if (content_view_core_)
+ content_view_core_->OnBackgroundColorChanged(color);
+}
+
+void RenderWidgetHostViewAndroid::SendBeginFrame(
+ const cc::BeginFrameArgs& args) {
+ TRACE_EVENT0("cc", "RenderWidgetHostViewAndroid::SendBeginFrame");
+ if (host_)
+ host_->Send(new ViewMsg_BeginFrame(host_->GetRoutingID(), args));
+}
+
+void RenderWidgetHostViewAndroid::OnSetNeedsBeginFrame(
+ bool enabled) {
+ TRACE_EVENT1("cc", "RenderWidgetHostViewAndroid::OnSetNeedsBeginFrame",
+ "enabled", enabled);
+ // ContentViewCoreImpl handles multiple subscribers to the BeginFrame, so
+ // we have to make sure calls to ContentViewCoreImpl's SetNeedsBeginFrame
+ // are balanced, even if RenderWidgetHostViewAndroid's may not be.
+ if (content_view_core_ && needs_begin_frame_ != enabled) {
+ content_view_core_->SetNeedsBeginFrame(enabled);
+ needs_begin_frame_ = enabled;
+ }
+}
+
+void RenderWidgetHostViewAndroid::OnStartContentIntent(
+ const GURL& content_url) {
+ if (content_view_core_)
+ content_view_core_->StartContentIntent(content_url);
+}
+
+void RenderWidgetHostViewAndroid::ImeCancelComposition() {
+ ime_adapter_android_.CancelComposition();
+}
+
+void RenderWidgetHostViewAndroid::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewAndroid::RenderProcessGone(
+ base::TerminationStatus status, int error_code) {
+ Destroy();
+}
+
+void RenderWidgetHostViewAndroid::Destroy() {
+ RemoveLayers();
+ content_view_core_ = NULL;
+
+ // The RenderWidgetHost's destruction led here, so don't call it.
+ host_ = NULL;
+
+ delete this;
+}
+
+void RenderWidgetHostViewAndroid::SetTooltipText(
+ const string16& tooltip_text) {
+ // Tooltips don't makes sense on Android.
+}
+
+void RenderWidgetHostViewAndroid::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
+
+ if (text.empty() || range.is_empty() || !content_view_core_)
+ return;
+ size_t pos = range.GetMin() - offset;
+ size_t n = range.length();
+
+ DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
+ if (pos >= text.length()) {
+ NOTREACHED() << "The text can not cover range.";
+ return;
+ }
+
+ std::string utf8_selection = UTF16ToUTF8(text.substr(pos, n));
+
+ content_view_core_->OnSelectionChanged(utf8_selection);
+}
+
+void RenderWidgetHostViewAndroid::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ if (content_view_core_) {
+ content_view_core_->OnSelectionBoundsChanged(params);
+ }
+}
+
+void RenderWidgetHostViewAndroid::ScrollOffsetChanged() {
+}
+
+BackingStore* RenderWidgetHostViewAndroid::AllocBackingStore(
+ const gfx::Size& size) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void RenderWidgetHostViewAndroid::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostViewBase::SetBackground(background);
+ host_->Send(new ViewMsg_SetBackground(host_->GetRoutingID(), background));
+}
+
+void RenderWidgetHostViewAndroid::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ if (!IsSurfaceAvailableForCopy()) {
+ callback.Run(false, SkBitmap());
+ return;
+ }
+
+ const gfx::Display& display =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+ float device_scale_factor = display.device_scale_factor();
+
+ DCHECK_EQ(device_scale_factor,
+ ui::GetScaleFactorScale(GetScaleFactorForView(this)));
+
+ const gfx::Size& dst_size_in_pixel = ConvertViewSizeToPixel(this, dst_size);
+ gfx::Rect src_subrect_in_pixel =
+ ConvertRectToPixel(device_scale_factor, src_subrect);
+
+ scoped_ptr<cc::CopyOutputRequest> request;
+ if (src_subrect_in_pixel.size() == dst_size_in_pixel) {
+ request = cc::CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &RenderWidgetHostViewAndroid::PrepareBitmapCopyOutputResult,
+ dst_size_in_pixel,
+ callback));
+ } else {
+ request = cc::CopyOutputRequest::CreateRequest(base::Bind(
+ &RenderWidgetHostViewAndroid::PrepareTextureCopyOutputResult,
+ dst_size_in_pixel,
+ callback));
+ }
+ request->set_area(src_subrect_in_pixel);
+ layer_->RequestCopyOfOutput(request.Pass());
+}
+
+void RenderWidgetHostViewAndroid::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ NOTIMPLEMENTED();
+ callback.Run(false);
+}
+
+bool RenderWidgetHostViewAndroid::CanCopyToVideoFrame() const {
+ return false;
+}
+
+void RenderWidgetHostViewAndroid::ShowDisambiguationPopup(
+ const gfx::Rect& target_rect, const SkBitmap& zoomed_bitmap) {
+ if (!content_view_core_)
+ return;
+
+ content_view_core_->ShowDisambiguationPopup(target_rect, zoomed_bitmap);
+}
+
+SmoothScrollGesture* RenderWidgetHostViewAndroid::CreateSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll, int mouse_event_x,
+ int mouse_event_y) {
+ return new TouchSmoothScrollGestureAndroid(
+ pixels_to_scroll,
+ GetRenderWidgetHost(),
+ content_view_core_->CreateSmoothScroller(
+ scroll_down, mouse_event_x, mouse_event_y));
+}
+
+void RenderWidgetHostViewAndroid::OnAcceleratedCompositingStateChange() {
+}
+
+void RenderWidgetHostViewAndroid::SendDelegatedFrameAck(
+ uint32 output_surface_id) {
+ cc::CompositorFrameAck ack;
+ delegated_renderer_layer_->TakeUnusedResourcesForChildCompositor(
+ &ack.resources);
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ host_->GetRoutingID(), output_surface_id,
+ host_->GetProcess()->GetID(), ack);
+}
+
+void RenderWidgetHostViewAndroid::SwapDelegatedFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::DelegatedFrameData> frame_data) {
+ bool has_frame = frame_data.get() && !frame_data->render_pass_list.empty();
+
+ if (has_frame) {
+ delegated_renderer_layer_->SetFrameData(frame_data.Pass());
+ delegated_renderer_layer_->SetDisplaySize(texture_size_in_layer_);
+ layer_->SetIsDrawable(true);
+ }
+ layer_->SetBounds(content_size_in_layer_);
+ layer_->SetNeedsDisplay();
+
+ base::Closure ack_callback =
+ base::Bind(&RenderWidgetHostViewAndroid::SendDelegatedFrameAck,
+ weak_ptr_factory_.GetWeakPtr(),
+ output_surface_id);
+
+ if (host_->is_hidden())
+ ack_callback.Run();
+ else
+ ack_callbacks_.push(ack_callback);
+}
+
+void RenderWidgetHostViewAndroid::ComputeContentsSize(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ // Calculate the content size. This should be 0 if the texture_size is 0.
+ gfx::Vector2dF offset;
+ if (texture_size_in_layer_.GetArea() > 0)
+ offset = frame_metadata.location_bar_content_translation;
+ offset.set_y(offset.y() + frame_metadata.overdraw_bottom_height);
+ offset.Scale(frame_metadata.device_scale_factor);
+ content_size_in_layer_ =
+ gfx::Size(texture_size_in_layer_.width() - offset.x(),
+ texture_size_in_layer_.height() - offset.y());
+ // Content size changes should be reflected in associated animation effects.
+ UpdateAnimationSize(frame_metadata);
+}
+
+void RenderWidgetHostViewAndroid::OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) {
+ // Always let ContentViewCore know about the new frame first, so it can decide
+ // to schedule a Draw immediately when it sees the texture layer invalidation.
+ UpdateContentViewCoreFrameMetadata(frame->metadata);
+
+ if (frame->delegated_frame_data) {
+ if (!frame->delegated_frame_data->render_pass_list.empty()) {
+ texture_size_in_layer_ = frame->delegated_frame_data->render_pass_list
+ .back()->output_rect.size();
+ }
+ ComputeContentsSize(frame->metadata);
+
+ SwapDelegatedFrame(output_surface_id, frame->delegated_frame_data.Pass());
+ return;
+ }
+
+ if (!frame->gl_frame_data || frame->gl_frame_data->mailbox.IsZero())
+ return;
+
+ if (output_surface_id != current_mailbox_output_surface_id_) {
+ current_mailbox_ = gpu::Mailbox();
+ current_mailbox_output_surface_id_ = kUndefinedOutputSurfaceId;
+ }
+
+ base::Closure callback = base::Bind(&InsertSyncPointAndAckForCompositor,
+ host_->GetProcess()->GetID(),
+ output_surface_id,
+ host_->GetRoutingID(),
+ current_mailbox_,
+ texture_size_in_layer_);
+ ImageTransportFactoryAndroid::GetInstance()->WaitSyncPoint(
+ frame->gl_frame_data->sync_point);
+
+ texture_size_in_layer_ = frame->gl_frame_data->size;
+ ComputeContentsSize(frame->metadata);
+
+ if (layer_->layer_tree_host())
+ layer_->layer_tree_host()->SetLatencyInfo(frame->metadata.latency_info);
+
+ BuffersSwapped(frame->gl_frame_data->mailbox, output_surface_id, callback);
+}
+
+void RenderWidgetHostViewAndroid::SynchronousFrameMetadata(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ // This is a subset of OnSwapCompositorFrame() used in the synchronous
+ // compositor flow.
+ UpdateContentViewCoreFrameMetadata(frame_metadata);
+ ComputeContentsSize(frame_metadata);
+}
+
+void RenderWidgetHostViewAndroid::UpdateContentViewCoreFrameMetadata(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ if (content_view_core_) {
+ // All offsets and sizes are in CSS pixels.
+ content_view_core_->UpdateFrameInfo(
+ frame_metadata.root_scroll_offset,
+ frame_metadata.page_scale_factor,
+ gfx::Vector2dF(frame_metadata.min_page_scale_factor,
+ frame_metadata.max_page_scale_factor),
+ frame_metadata.root_layer_size,
+ frame_metadata.viewport_size,
+ frame_metadata.location_bar_offset,
+ frame_metadata.location_bar_content_translation,
+ frame_metadata.overdraw_bottom_height);
+ }
+}
+
+void RenderWidgetHostViewAndroid::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+ NOTREACHED() << "Deprecated. Use --composite-to-mailbox.";
+
+ if (params.mailbox_name.empty())
+ return;
+
+ std::string return_mailbox;
+ if (!current_mailbox_.IsZero()) {
+ return_mailbox.assign(
+ reinterpret_cast<const char*>(current_mailbox_.name),
+ sizeof(current_mailbox_.name));
+ }
+
+ base::Closure callback = base::Bind(&InsertSyncPointAndAckForGpu,
+ gpu_host_id, params.route_id,
+ return_mailbox);
+
+ gpu::Mailbox mailbox;
+ std::copy(params.mailbox_name.data(),
+ params.mailbox_name.data() + params.mailbox_name.length(),
+ reinterpret_cast<char*>(mailbox.name));
+
+ texture_size_in_layer_ = params.size;
+ content_size_in_layer_ = params.size;
+
+ BuffersSwapped(mailbox, kUndefinedOutputSurfaceId, callback);
+}
+
+void RenderWidgetHostViewAndroid::BuffersSwapped(
+ const gpu::Mailbox& mailbox,
+ uint32_t output_surface_id,
+ const base::Closure& ack_callback) {
+ ImageTransportFactoryAndroid* factory =
+ ImageTransportFactoryAndroid::GetInstance();
+
+ // TODO(sievers): When running the impl thread in the browser we
+ // need to delay the ACK until after commit and use more than a single
+ // texture.
+ DCHECK(!CompositorImpl::IsThreadingEnabled());
+
+ if (!texture_id_in_layer_) {
+ texture_id_in_layer_ = factory->CreateTexture();
+ texture_layer_->SetIsDrawable(true);
+ }
+
+ ImageTransportFactoryAndroid::GetInstance()->AcquireTexture(
+ texture_id_in_layer_, mailbox.name);
+
+ ResetClipping();
+
+ current_mailbox_ = mailbox;
+ current_mailbox_output_surface_id_ = output_surface_id;
+
+ if (host_->is_hidden())
+ ack_callback.Run();
+ else
+ ack_callbacks_.push(ack_callback);
+}
+
+void RenderWidgetHostViewAndroid::AttachLayers() {
+ if (!content_view_core_)
+ return;
+
+ content_view_core_->AttachLayer(layer_);
+
+ if (overscroll_effect_)
+ content_view_core_->AttachLayer(overscroll_effect_->root_layer());
+}
+
+void RenderWidgetHostViewAndroid::RemoveLayers() {
+ if (!content_view_core_)
+ return;
+
+ if (overscroll_effect_)
+ content_view_core_->RemoveLayer(overscroll_effect_->root_layer());
+
+ content_view_core_->RemoveLayer(layer_);
+}
+
+bool RenderWidgetHostViewAndroid::Animate(base::TimeTicks frame_time) {
+ if (!overscroll_effect_)
+ return false;
+ return overscroll_effect_->Animate(frame_time);
+}
+
+void RenderWidgetHostViewAndroid::CreateOverscrollEffectIfNecessary() {
+ if (!overscroll_effect_enabled_ || overscroll_effect_)
+ return;
+
+ overscroll_effect_ = OverscrollGlow::Create(true);
+
+ // Prevent future creation attempts on failure.
+ if (!overscroll_effect_)
+ overscroll_effect_enabled_ = false;
+
+ if (overscroll_effect_ && content_view_core_ && are_layers_attached_)
+ content_view_core_->AttachLayer(overscroll_effect_->root_layer());
+}
+
+void RenderWidgetHostViewAndroid::UpdateAnimationSize(
+ const cc::CompositorFrameMetadata& frame_metadata) {
+ if (!overscroll_effect_)
+ return;
+ // Disable edge effects for axes on which scrolling is impossible.
+ gfx::SizeF ceiled_viewport_size =
+ gfx::ToCeiledSize(frame_metadata.viewport_size);
+ overscroll_effect_->set_horizontal_overscroll_enabled(
+ ceiled_viewport_size.width() < frame_metadata.root_layer_size.width());
+ overscroll_effect_->set_vertical_overscroll_enabled(
+ ceiled_viewport_size.height() < frame_metadata.root_layer_size.height());
+ overscroll_effect_->set_size(content_size_in_layer_);
+}
+
+void RenderWidgetHostViewAndroid::ScheduleAnimationIfNecessary() {
+ if (!content_view_core_)
+ return;
+ if (overscroll_effect_ && overscroll_effect_->NeedsAnimate())
+ content_view_core_->SetNeedsAnimate();
+}
+
+void RenderWidgetHostViewAndroid::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewAndroid::AcceleratedSurfaceSuspend() {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewAndroid::AcceleratedSurfaceRelease() {
+ // This tells us we should free the frontbuffer.
+ if (texture_id_in_layer_) {
+ texture_layer_->SetTextureId(0);
+ texture_layer_->SetIsDrawable(false);
+ ImageTransportFactoryAndroid::GetInstance()->DeleteTexture(
+ texture_id_in_layer_);
+ texture_id_in_layer_ = 0;
+ current_mailbox_ = gpu::Mailbox();
+ current_mailbox_output_surface_id_ = kUndefinedOutputSurfaceId;
+ }
+}
+
+bool RenderWidgetHostViewAndroid::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ NOTREACHED();
+ return false;
+}
+
+void RenderWidgetHostViewAndroid::GetScreenInfo(WebKit::WebScreenInfo* result) {
+ // ScreenInfo isn't tied to the widget on Android. Always return the default.
+ RenderWidgetHostViewBase::GetDefaultScreenInfo(result);
+}
+
+// TODO(jrg): Find out the implications and answer correctly here,
+// as we are returning the WebView and not root window bounds.
+gfx::Rect RenderWidgetHostViewAndroid::GetBoundsInRootWindow() {
+ return GetViewBounds();
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostViewAndroid::GetCompositingSurface() {
+ if (surface_texture_transport_) {
+ return surface_texture_transport_->GetCompositingSurface(
+ host_->surface_id());
+ } else {
+ return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::TEXTURE_TRANSPORT);
+ }
+}
+
+void RenderWidgetHostViewAndroid::ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) {
+ if (content_view_core_)
+ content_view_core_->ConfirmTouchEvent(ack_result);
+}
+
+void RenderWidgetHostViewAndroid::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+ // intentionally empty, like RenderWidgetHostViewViews
+}
+
+void RenderWidgetHostViewAndroid::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ // intentionally empty, like RenderWidgetHostViewViews
+}
+
+void RenderWidgetHostViewAndroid::UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ // intentionally empty, like RenderWidgetHostViewViews
+}
+
+void RenderWidgetHostViewAndroid::GestureEventAck(
+ int gesture_event_type,
+ InputEventAckState ack_result) {
+ if (gesture_event_type == WebKit::WebInputEvent::GestureFlingStart &&
+ ack_result == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) {
+ content_view_core_->UnhandledFlingStartEvent();
+ }
+}
+
+InputEventAckState RenderWidgetHostViewAndroid::FilterInputEvent(
+ const WebKit::WebInputEvent& input_event) {
+ if (host_) {
+ SynchronousCompositorImpl* compositor =
+ SynchronousCompositorImpl::FromID(host_->GetProcess()->GetID(),
+ host_->GetRoutingID());
+ if (compositor)
+ return compositor->HandleInputEvent(input_event);
+ }
+ return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
+}
+
+void RenderWidgetHostViewAndroid::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ if (!host_ ||
+ host_->accessibility_mode() != AccessibilityModeComplete ||
+ !content_view_core_) {
+ return;
+ }
+
+ if (!GetBrowserAccessibilityManager()) {
+ SetBrowserAccessibilityManager(
+ new BrowserAccessibilityManagerAndroid(
+ content_view_core_->GetJavaObject(),
+ BrowserAccessibilityManagerAndroid::GetEmptyDocument(),
+ this));
+ }
+ GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params);
+}
+
+void RenderWidgetHostViewAndroid::SetAccessibilityFocus(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetFocus(acc_obj_id);
+}
+
+void RenderWidgetHostViewAndroid::AccessibilityDoDefaultAction(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityDoDefaultAction(acc_obj_id);
+}
+
+void RenderWidgetHostViewAndroid::AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToMakeVisible(acc_obj_id, subfocus);
+}
+
+void RenderWidgetHostViewAndroid::AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToPoint(acc_obj_id, point);
+}
+
+void RenderWidgetHostViewAndroid::AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetTextSelection(
+ acc_obj_id, start_offset, end_offset);
+}
+
+gfx::Point RenderWidgetHostViewAndroid::GetLastTouchEventLocation() const {
+ NOTIMPLEMENTED();
+ // Only used on Win8
+ return gfx::Point();
+}
+
+void RenderWidgetHostViewAndroid::FatalAccessibilityTreeError() {
+ if (!host_)
+ return;
+
+ host_->FatalAccessibilityTreeError();
+ SetBrowserAccessibilityManager(NULL);
+}
+
+bool RenderWidgetHostViewAndroid::LockMouse() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void RenderWidgetHostViewAndroid::UnlockMouse() {
+ NOTIMPLEMENTED();
+}
+
+// Methods called from the host to the render
+
+void RenderWidgetHostViewAndroid::SendKeyEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (host_)
+ host_->ForwardKeyboardEvent(event);
+}
+
+void RenderWidgetHostViewAndroid::SendTouchEvent(
+ const WebKit::WebTouchEvent& event) {
+ if (host_)
+ host_->ForwardTouchEventWithLatencyInfo(event, ui::LatencyInfo());
+}
+
+
+void RenderWidgetHostViewAndroid::SendMouseEvent(
+ const WebKit::WebMouseEvent& event) {
+ if (host_)
+ host_->ForwardMouseEvent(event);
+}
+
+void RenderWidgetHostViewAndroid::SendMouseWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ if (host_)
+ host_->ForwardWheelEvent(event);
+}
+
+void RenderWidgetHostViewAndroid::SendGestureEvent(
+ const WebKit::WebGestureEvent& event) {
+ // Sending a gesture that may trigger overscroll should resume the effect.
+ if (overscroll_effect_) {
+ overscroll_effect_->SetEnabled(true);
+ if (event.type == WebKit::WebInputEvent::GestureScrollEnd)
+ overscroll_effect_->Release(base::TimeTicks::Now());
+ }
+
+ if (host_)
+ host_->ForwardGestureEvent(event);
+}
+
+void RenderWidgetHostViewAndroid::SelectRange(const gfx::Point& start,
+ const gfx::Point& end) {
+ if (host_)
+ host_->SelectRange(start, end);
+}
+
+void RenderWidgetHostViewAndroid::MoveCaret(const gfx::Point& point) {
+ if (host_)
+ host_->MoveCaret(point);
+}
+
+void RenderWidgetHostViewAndroid::RequestContentClipping(
+ const gfx::Rect& clipping,
+ const gfx::Size& content_size) {
+ // A focused view provides its own clipping.
+ if (HasFocus())
+ return;
+
+ ClipContents(clipping, content_size);
+}
+
+void RenderWidgetHostViewAndroid::ResetClipping() {
+ ClipContents(gfx::Rect(gfx::Point(), content_size_in_layer_),
+ content_size_in_layer_);
+}
+
+void RenderWidgetHostViewAndroid::ClipContents(const gfx::Rect& clipping,
+ const gfx::Size& content_size) {
+ if (!texture_id_in_layer_ || content_size_in_layer_.IsEmpty())
+ return;
+
+ gfx::Size clipped_content(content_size_in_layer_);
+ clipped_content.SetToMin(clipping.size());
+ texture_layer_->SetBounds(clipped_content);
+ texture_layer_->SetNeedsDisplay();
+
+ if (texture_size_in_layer_.IsEmpty()) {
+ texture_layer_->SetUV(gfx::PointF(), gfx::PointF());
+ return;
+ }
+
+ gfx::PointF offset(
+ clipping.x() + content_size_in_layer_.width() - content_size.width(),
+ clipping.y() + content_size_in_layer_.height() - content_size.height());
+ offset.SetToMax(gfx::PointF());
+
+ gfx::Vector2dF uv_scale(1.f / texture_size_in_layer_.width(),
+ 1.f / texture_size_in_layer_.height());
+ texture_layer_->SetUV(
+ gfx::PointF(offset.x() * uv_scale.x(),
+ offset.y() * uv_scale.y()),
+ gfx::PointF((offset.x() + clipped_content.width()) * uv_scale.x(),
+ (offset.y() + clipped_content.height()) * uv_scale.y()));
+}
+
+SkColor RenderWidgetHostViewAndroid::GetCachedBackgroundColor() const {
+ return cached_background_color_;
+}
+
+void RenderWidgetHostViewAndroid::OnOverscrolled(
+ gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity) {
+ CreateOverscrollEffectIfNecessary();
+ if (!overscroll_effect_)
+ return;
+
+ overscroll_effect_->OnOverscrolled(base::TimeTicks::Now(),
+ accumulated_overscroll,
+ current_fling_velocity);
+ ScheduleAnimationIfNecessary();
+}
+
+void RenderWidgetHostViewAndroid::SetContentViewCore(
+ ContentViewCoreImpl* content_view_core) {
+ RunAckCallbacks();
+
+ if (are_layers_attached_)
+ RemoveLayers();
+
+ content_view_core_ = content_view_core;
+
+ if (are_layers_attached_)
+ AttachLayers();
+}
+
+void RenderWidgetHostViewAndroid::RunAckCallbacks() {
+ while (!ack_callbacks_.empty()) {
+ ack_callbacks_.front().Run();
+ ack_callbacks_.pop();
+ }
+}
+
+void RenderWidgetHostViewAndroid::HasTouchEventHandlers(
+ bool need_touch_events) {
+ if (content_view_core_)
+ content_view_core_->HasTouchEventHandlers(need_touch_events);
+}
+
+unsigned RenderWidgetHostViewAndroid::PrepareTexture() {
+ RunAckCallbacks();
+ return texture_id_in_layer_;
+}
+
+void RenderWidgetHostViewAndroid::DidCommitFrameData() {
+ RunAckCallbacks();
+}
+
+WebKit::WebGraphicsContext3D* RenderWidgetHostViewAndroid::Context3d() {
+ return ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+}
+
+bool RenderWidgetHostViewAndroid::PrepareTextureMailbox(
+ cc::TextureMailbox* mailbox,
+ bool use_shared_memory) {
+ return false;
+}
+
+void RenderWidgetHostViewAndroid::OnLostResources() {
+ if (texture_layer_)
+ texture_layer_->SetIsDrawable(false);
+ texture_id_in_layer_ = 0;
+ RunAckCallbacks();
+}
+
+// static
+void RenderWidgetHostViewAndroid::PrepareTextureCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ DCHECK(result->HasTexture());
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+
+ if (!result->HasTexture() || result->IsEmpty() || result->size().IsEmpty())
+ return;
+
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ dst_size_in_pixel.width(), dst_size_in_pixel.height());
+ if (!bitmap->allocPixels())
+ return;
+ bitmap->setIsOpaque(true);
+
+ ImageTransportFactoryAndroid* factory =
+ ImageTransportFactoryAndroid::GetInstance();
+ GLHelper* gl_helper = factory->GetGLHelper();
+ if (!gl_helper)
+ return;
+
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock(
+ new SkAutoLockPixels(*bitmap));
+ uint8* pixels = static_cast<uint8*>(bitmap->getPixels());
+
+ scoped_ptr<cc::TextureMailbox> texture_mailbox = result->TakeTexture();
+ DCHECK(texture_mailbox->IsTexture());
+ if (!texture_mailbox->IsTexture())
+ return;
+
+ scoped_callback_runner.Release();
+
+ gl_helper->CropScaleReadbackAndCleanMailbox(
+ texture_mailbox->name(),
+ texture_mailbox->sync_point(),
+ result->size(),
+ gfx::Rect(result->size()),
+ dst_size_in_pixel,
+ pixels,
+ base::Bind(&CopyFromCompositingSurfaceFinished,
+ callback,
+ texture_mailbox->callback(),
+ base::Passed(&bitmap),
+ base::Passed(&bitmap_pixels_lock)));
+}
+
+// static
+void RenderWidgetHostViewAndroid::PrepareBitmapCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ DCHECK(result->HasBitmap());
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+
+ if (!result->HasBitmap() || result->IsEmpty() || result->size().IsEmpty())
+ return;
+
+ scoped_ptr<SkBitmap> source = result->TakeBitmap();
+ DCHECK(source);
+ if (!source)
+ return;
+
+ DCHECK_EQ(source->width(), dst_size_in_pixel.width());
+ DCHECK_EQ(source->height(), dst_size_in_pixel.height());
+
+ scoped_callback_runner.Release();
+ callback.Run(true, *source);
+}
+
+// static
+void RenderWidgetHostViewPort::GetDefaultScreenInfo(
+ WebKit::WebScreenInfo* results) {
+ const gfx::Display& display =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+ results->rect = display.bounds();
+ // TODO(husky): Remove any system controls from availableRect.
+ results->availableRect = display.work_area();
+ results->deviceScaleFactor = display.device_scale_factor();
+ gfx::DeviceDisplayInfo info;
+ results->depth = info.GetBitsPerPixel();
+ results->depthPerComponent = info.GetBitsPerComponent();
+ results->isMonochrome = (results->depthPerComponent == 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostView, public:
+
+// static
+RenderWidgetHostView*
+RenderWidgetHostView::CreateViewForWidget(RenderWidgetHost* widget) {
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(widget);
+ return new RenderWidgetHostViewAndroid(rwhi, NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_android.h b/chromium/content/browser/renderer_host/render_widget_host_view_android.h
new file mode 100644
index 00000000000..d8ea83da7ce
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_android.h
@@ -0,0 +1,330 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_ANDROID_H_
+
+#include <map>
+#include <queue>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/i18n/rtl.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process.h"
+#include "cc/layers/delegated_renderer_layer_client.h"
+#include "cc/layers/texture_layer_client.h"
+#include "cc/output/begin_frame_args.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/renderer_host/image_transport_factory_android.h"
+#include "content/browser/renderer_host/ime_adapter_android.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d_f.h"
+
+struct ViewHostMsg_TextInputState_Params;
+
+struct GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params;
+struct GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params;
+
+namespace cc {
+class CopyOutputResult;
+class DelegatedRendererLayer;
+class Layer;
+class TextureLayer;
+}
+
+namespace WebKit {
+class WebExternalTextureLayer;
+class WebTouchEvent;
+class WebMouseEvent;
+}
+
+namespace content {
+class ContentViewCoreImpl;
+class OverscrollGlow;
+class RenderWidgetHost;
+class RenderWidgetHostImpl;
+class SurfaceTextureTransportClient;
+struct NativeWebKeyboardEvent;
+
+// -----------------------------------------------------------------------------
+// See comments in render_widget_host_view.h about this class and its members.
+// -----------------------------------------------------------------------------
+class RenderWidgetHostViewAndroid
+ : public RenderWidgetHostViewBase,
+ public BrowserAccessibilityDelegate,
+ public cc::TextureLayerClient,
+ public cc::DelegatedRendererLayerClient,
+ public ImageTransportFactoryAndroidObserver {
+ public:
+ RenderWidgetHostViewAndroid(RenderWidgetHostImpl* widget,
+ ContentViewCoreImpl* content_view_core);
+ virtual ~RenderWidgetHostViewAndroid();
+
+ // RenderWidgetHostView implementation.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual gfx::Size GetPhysicalBackingSize() const OVERRIDE;
+ virtual float GetOverdrawBottomHeight() const OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+ virtual void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual void UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+ virtual InputEventAckState FilterInputEvent(
+ const WebKit::WebInputEvent& input_event) OVERRIDE;
+ virtual void GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>&
+ params) OVERRIDE;
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void HasTouchEventHandlers(bool need_touch_events) OVERRIDE;
+ virtual void OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) OVERRIDE;
+ virtual void OnOverscrolled(gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity) OVERRIDE;
+ virtual void ShowDisambiguationPopup(const gfx::Rect& target_rect,
+ const SkBitmap& zoomed_bitmap) OVERRIDE;
+ virtual SmoothScrollGesture* CreateSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll, int mouse_event_x,
+ int mouse_event_y) OVERRIDE;
+
+ // Implementation of BrowserAccessibilityDelegate:
+ virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) OVERRIDE;
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) OVERRIDE;
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) OVERRIDE;
+ virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE;
+ virtual void FatalAccessibilityTreeError() OVERRIDE;
+
+ // cc::TextureLayerClient implementation.
+ virtual unsigned PrepareTexture() OVERRIDE;
+ virtual WebKit::WebGraphicsContext3D* Context3d() OVERRIDE;
+ virtual bool PrepareTextureMailbox(cc::TextureMailbox* mailbox,
+ bool use_shared_memory) OVERRIDE;
+
+ // cc::DelegatedRendererLayerClient implementation.
+ virtual void DidCommitFrameData() OVERRIDE;
+
+ // ImageTransportFactoryAndroidObserver implementation.
+ virtual void OnLostResources() OVERRIDE;
+
+ // Non-virtual methods
+ void SetContentViewCore(ContentViewCoreImpl* content_view_core);
+ SkColor GetCachedBackgroundColor() const;
+ void SendKeyEvent(const NativeWebKeyboardEvent& event);
+ void SendTouchEvent(const WebKit::WebTouchEvent& event);
+ void SendMouseEvent(const WebKit::WebMouseEvent& event);
+ void SendMouseWheelEvent(const WebKit::WebMouseWheelEvent& event);
+ void SendGestureEvent(const WebKit::WebGestureEvent& event);
+ void SendBeginFrame(const cc::BeginFrameArgs& args);
+
+ void OnTextInputStateChanged(const ViewHostMsg_TextInputState_Params& params);
+ void OnProcessImeBatchStateAck(bool is_begin);
+ void OnDidChangeBodyBackgroundColor(SkColor color);
+ void OnStartContentIntent(const GURL& content_url);
+ void OnSetNeedsBeginFrame(bool enabled);
+
+ int GetNativeImeAdapter();
+
+ void WasResized();
+
+ WebKit::WebGLId GetScaledContentTexture(float scale, gfx::Size* out_size);
+ bool PopulateBitmapWithContents(jobject jbitmap);
+
+ bool HasValidFrame() const;
+
+ // Select all text between the given coordinates.
+ void SelectRange(const gfx::Point& start, const gfx::Point& end);
+
+ void MoveCaret(const gfx::Point& point);
+
+ void RequestContentClipping(const gfx::Rect& clipping,
+ const gfx::Size& content_size);
+
+ // Returns true when animation ticks are still needed. This avoids a separate
+ // round-trip for requesting follow-up animation.
+ bool Animate(base::TimeTicks frame_time);
+
+ void SynchronousFrameMetadata(
+ const cc::CompositorFrameMetadata& frame_metadata);
+
+ private:
+ void BuffersSwapped(const gpu::Mailbox& mailbox,
+ uint32_t output_surface_id,
+ const base::Closure& ack_callback);
+
+ void RunAckCallbacks();
+
+ void SwapDelegatedFrame(uint32 output_surface_id,
+ scoped_ptr<cc::DelegatedFrameData> frame_data);
+ void SendDelegatedFrameAck(uint32 output_surface_id);
+
+ void UpdateContentViewCoreFrameMetadata(
+ const cc::CompositorFrameMetadata& frame_metadata);
+ void ComputeContentsSize(const cc::CompositorFrameMetadata& frame_metadata);
+ void ResetClipping();
+ void ClipContents(const gfx::Rect& clipping, const gfx::Size& content_size);
+
+ void AttachLayers();
+ void RemoveLayers();
+
+ void CreateOverscrollEffectIfNecessary();
+ void UpdateAnimationSize(const cc::CompositorFrameMetadata& frame_metadata);
+ void ScheduleAnimationIfNecessary();
+
+ // Called after async screenshot task completes. Scales and crops the result
+ // of the copy.
+ static void PrepareTextureCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+ static void PrepareBitmapCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+
+ // The model object.
+ RenderWidgetHostImpl* host_;
+
+ // Used to track whether this render widget needs a BeginFrame.
+ bool needs_begin_frame_;
+
+ // Whether or not this widget is potentially attached to the view hierarchy.
+ // This view may not actually be attached if this is true, but it should be
+ // treated as such, because as soon as a ContentViewCore is set the layer
+ // will be attached automatically.
+ bool are_layers_attached_;
+
+ // ContentViewCoreImpl is our interface to the view system.
+ ContentViewCoreImpl* content_view_core_;
+
+ ImeAdapterAndroid ime_adapter_android_;
+
+ // Body background color of the underlying document.
+ SkColor cached_background_color_;
+
+ // The texture layer for this view when using browser-side compositing.
+ scoped_refptr<cc::TextureLayer> texture_layer_;
+
+ scoped_refptr<cc::DelegatedRendererLayer> delegated_renderer_layer_;
+
+ // The layer used for rendering the contents of this view.
+ // It is either owned by texture_layer_ or surface_texture_transport_
+ // depending on the mode.
+ scoped_refptr<cc::Layer> layer_;
+
+ // The most recent texture id that was pushed to the texture layer.
+ unsigned int texture_id_in_layer_;
+
+ // The most recent texture size that was pushed to the texture layer.
+ gfx::Size texture_size_in_layer_;
+
+ // The most recent content size that was pushed to the texture layer.
+ gfx::Size content_size_in_layer_;
+
+ // Used for image transport when needing to share resources across threads.
+ scoped_ptr<SurfaceTextureTransportClient> surface_texture_transport_;
+
+ // The mailbox of the previously received frame.
+ gpu::Mailbox current_mailbox_;
+ uint32_t current_mailbox_output_surface_id_;
+
+ base::WeakPtrFactory<RenderWidgetHostViewAndroid> weak_ptr_factory_;
+
+ std::queue<base::Closure> ack_callbacks_;
+
+ // Used to render overscroll overlays.
+ bool overscroll_effect_enabled_;
+ scoped_ptr<OverscrollGlow> overscroll_effect_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_aura.cc b/chromium/content/browser/renderer_host/render_widget_host_view_aura.cc
new file mode 100644
index 00000000000..8c52017ed59
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -0,0 +1,3250 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_aura.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/resources/texture_mailbox.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/browser_accessibility_state_impl.h"
+#include "content/browser/renderer_host/backing_store_aura.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/overscroll_controller.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h"
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "content/browser/renderer_host/web_input_event_aura.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/video_util.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/client/cursor_client_observer.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/client/stacking_client.h"
+#include "ui/aura/client/tooltip_client.h"
+#include "ui/aura/client/window_types.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ime/input_method.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skia_util.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "content/browser/accessibility/browser_accessibility_manager_win.h"
+#include "content/browser/accessibility/browser_accessibility_win.h"
+#include "ui/base/win/hidden_window.h"
+#include "ui/gfx/gdi_util.h"
+#endif
+
+using gfx::RectToSkIRect;
+using gfx::SkIRectToRect;
+
+using WebKit::WebScreenInfo;
+using WebKit::WebTouchEvent;
+
+namespace content {
+
+void ReleaseMailbox(scoped_refptr<MemoryHolder> holder,
+ unsigned sync_point,
+ bool lost_resource) {}
+
+class MemoryHolder : public base::RefCounted<MemoryHolder> {
+ public:
+ MemoryHolder(scoped_ptr<base::SharedMemory> shared_memory,
+ gfx::Size frame_size,
+ base::Callback<void()> callback)
+ : shared_memory_(shared_memory.Pass()),
+ frame_size_(frame_size),
+ callback_(callback) {}
+
+ cc::TextureMailbox GetMailbox() {
+ return cc::TextureMailbox(
+ shared_memory_.get(),
+ frame_size_,
+ base::Bind(ReleaseMailbox, make_scoped_refptr(this)));
+ }
+
+ private:
+ friend class base::RefCounted<MemoryHolder>;
+ ~MemoryHolder() { callback_.Run(); }
+
+ scoped_ptr<base::SharedMemory> shared_memory_;
+ gfx::Size frame_size_;
+ base::Callback<void()> callback_;
+};
+
+namespace {
+
+// In mouse lock mode, we need to prevent the (invisible) cursor from hitting
+// the border of the view, in order to get valid movement information. However,
+// forcing the cursor back to the center of the view after each mouse move
+// doesn't work well. It reduces the frequency of useful mouse move messages
+// significantly. Therefore, we move the cursor to the center of the view only
+// if it approaches the border. |kMouseLockBorderPercentage| specifies the width
+// of the border area, in percentage of the corresponding dimension.
+const int kMouseLockBorderPercentage = 15;
+
+// When accelerated compositing is enabled and a widget resize is pending,
+// we delay further resizes of the UI. The following constant is the maximum
+// length of time that we should delay further UI resizes while waiting for a
+// resized frame from a renderer.
+const int kResizeLockTimeoutMs = 67;
+
+#if defined(OS_WIN)
+// Used to associate a plugin HWND with its RenderWidgetHostViewAura instance.
+const wchar_t kWidgetOwnerProperty[] = L"RenderWidgetHostViewAuraOwner";
+
+BOOL CALLBACK WindowDestroyingCallback(HWND window, LPARAM param) {
+ RenderWidgetHostViewAura* widget =
+ reinterpret_cast<RenderWidgetHostViewAura*>(param);
+ if (GetProp(window, kWidgetOwnerProperty) == widget) {
+ // Properties set on HWNDs must be removed to avoid leaks.
+ RemoveProp(window, kWidgetOwnerProperty);
+ RenderWidgetHostViewBase::DetachPluginWindowsCallback(window);
+ }
+ return TRUE;
+}
+
+BOOL CALLBACK HideWindowsCallback(HWND window, LPARAM param) {
+ RenderWidgetHostViewAura* widget =
+ reinterpret_cast<RenderWidgetHostViewAura*>(param);
+ if (GetProp(window, kWidgetOwnerProperty) == widget)
+ SetParent(window, ui::GetHiddenWindow());
+ return TRUE;
+}
+
+BOOL CALLBACK ShowWindowsCallback(HWND window, LPARAM param) {
+ RenderWidgetHostViewAura* widget =
+ reinterpret_cast<RenderWidgetHostViewAura*>(param);
+
+ if (GetProp(window, kWidgetOwnerProperty) == widget) {
+ HWND parent =
+ widget->GetNativeView()->GetRootWindow()->GetAcceleratedWidget();
+ SetParent(window, parent);
+ }
+ return TRUE;
+}
+
+struct CutoutRectsParams {
+ RenderWidgetHostViewAura* widget;
+ std::vector<gfx::Rect> cutout_rects;
+ std::map<HWND, WebPluginGeometry>* geometry;
+};
+
+// Used to update the region for the windowed plugin to draw in. We start with
+// the clip rect from the renderer, then remove the cutout rects from the
+// renderer, and then remove the transient windows from the root window and the
+// constrained windows from the parent window.
+BOOL CALLBACK SetCutoutRectsCallback(HWND window, LPARAM param) {
+ CutoutRectsParams* params = reinterpret_cast<CutoutRectsParams*>(param);
+
+ if (GetProp(window, kWidgetOwnerProperty) == params->widget) {
+ // First calculate the offset of this plugin from the root window, since
+ // the cutouts are relative to the root window.
+ HWND parent = params->widget->GetNativeView()->GetRootWindow()->
+ GetAcceleratedWidget();
+ POINT offset;
+ offset.x = offset.y = 0;
+ MapWindowPoints(window, parent, &offset, 1);
+
+ // Now get the cached clip rect and cutouts for this plugin window that came
+ // from the renderer.
+ std::map<HWND, WebPluginGeometry>::iterator i = params->geometry->begin();
+ while (i != params->geometry->end() &&
+ i->second.window != window &&
+ GetParent(i->second.window) != window) {
+ ++i;
+ }
+
+ if (i == params->geometry->end()) {
+ NOTREACHED();
+ return TRUE;
+ }
+
+ HRGN hrgn = CreateRectRgn(i->second.clip_rect.x(),
+ i->second.clip_rect.y(),
+ i->second.clip_rect.right(),
+ i->second.clip_rect.bottom());
+ // We start with the cutout rects that came from the renderer, then add the
+ // ones that came from transient and constrained windows.
+ std::vector<gfx::Rect> cutout_rects = i->second.cutout_rects;
+ for (size_t i = 0; i < params->cutout_rects.size(); ++i) {
+ gfx::Rect offset_cutout = params->cutout_rects[i];
+ offset_cutout.Offset(-offset.x, -offset.y);
+ cutout_rects.push_back(offset_cutout);
+ }
+ gfx::SubtractRectanglesFromRegion(hrgn, cutout_rects);
+ SetWindowRgn(window, hrgn, TRUE);
+ }
+ return TRUE;
+}
+
+// A callback function for EnumThreadWindows to enumerate and dismiss
+// any owned popup windows.
+BOOL CALLBACK DismissOwnedPopups(HWND window, LPARAM arg) {
+ const HWND toplevel_hwnd = reinterpret_cast<HWND>(arg);
+
+ if (::IsWindowVisible(window)) {
+ const HWND owner = ::GetWindow(window, GW_OWNER);
+ if (toplevel_hwnd == owner) {
+ ::PostMessage(window, WM_CANCELMODE, 0, 0);
+ }
+ }
+
+ return TRUE;
+}
+#endif
+
+void UpdateWebTouchEventAfterDispatch(WebKit::WebTouchEvent* event,
+ WebKit::WebTouchPoint* point) {
+ if (point->state != WebKit::WebTouchPoint::StateReleased &&
+ point->state != WebKit::WebTouchPoint::StateCancelled)
+ return;
+ --event->touchesLength;
+ for (unsigned i = point - event->touches;
+ i < event->touchesLength;
+ ++i) {
+ event->touches[i] = event->touches[i + 1];
+ }
+}
+
+bool CanRendererHandleEvent(const ui::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED)
+ return false;
+
+#if defined(OS_WIN)
+ // Renderer cannot handle WM_XBUTTON or NC events.
+ switch (event->native_event().message) {
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONUP:
+ case WM_XBUTTONDBLCLK:
+ case WM_NCMOUSELEAVE:
+ case WM_NCMOUSEMOVE:
+ case WM_NCXBUTTONDOWN:
+ case WM_NCXBUTTONUP:
+ case WM_NCXBUTTONDBLCLK:
+ return false;
+ default:
+ break;
+ }
+#endif
+ return true;
+}
+
+// We don't mark these as handled so that they're sent back to the
+// DefWindowProc so it can generate WM_APPCOMMAND as necessary.
+bool IsXButtonUpEvent(const ui::MouseEvent* event) {
+#if defined(OS_WIN)
+ switch (event->native_event().message) {
+ case WM_XBUTTONUP:
+ case WM_NCXBUTTONUP:
+ return true;
+ }
+#endif
+ return false;
+}
+
+void GetScreenInfoForWindow(WebScreenInfo* results, aura::Window* window) {
+ const gfx::Display display = window ?
+ gfx::Screen::GetScreenFor(window)->GetDisplayNearestWindow(window) :
+ gfx::Screen::GetScreenFor(window)->GetPrimaryDisplay();
+ results->rect = display.bounds();
+ results->availableRect = display.work_area();
+ // TODO(derat|oshima): Don't hardcode this. Get this from display object.
+ results->depth = 24;
+ results->depthPerComponent = 8;
+ results->deviceScaleFactor = display.device_scale_factor();
+}
+
+bool ShouldSendPinchGesture() {
+#if defined(OS_WIN)
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8)
+ return true;
+#endif
+ static bool pinch_allowed =
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableViewport) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePinch);
+ return pinch_allowed;
+}
+
+bool PointerEventActivates(const ui::Event& event) {
+ if (event.type() == ui::ET_MOUSE_PRESSED)
+ return true;
+
+ if (event.type() == ui::ET_GESTURE_BEGIN) {
+ const ui::GestureEvent& gesture =
+ static_cast<const ui::GestureEvent&>(event);
+ return gesture.details().touch_points() == 1;
+ }
+
+ return false;
+}
+
+// Swap ack for the renderer when kCompositeToMailbox is enabled.
+void SendCompositorFrameAck(
+ int32 route_id,
+ uint32 output_surface_id,
+ int renderer_host_id,
+ const gpu::Mailbox& received_mailbox,
+ const gfx::Size& received_size,
+ bool skip_frame,
+ const scoped_refptr<ui::Texture>& texture_to_produce) {
+ cc::CompositorFrameAck ack;
+ ack.gl_frame_data.reset(new cc::GLFrameData());
+ DCHECK(!texture_to_produce.get() || !skip_frame);
+ if (texture_to_produce.get()) {
+ std::string mailbox_name = texture_to_produce->Produce();
+ std::copy(mailbox_name.data(),
+ mailbox_name.data() + mailbox_name.length(),
+ reinterpret_cast<char*>(ack.gl_frame_data->mailbox.name));
+ ack.gl_frame_data->size = texture_to_produce->size();
+ ack.gl_frame_data->sync_point =
+ content::ImageTransportFactory::GetInstance()->InsertSyncPoint();
+ } else if (skip_frame) {
+ // Skip the frame, i.e. tell the producer to reuse the same buffer that
+ // we just received.
+ ack.gl_frame_data->size = received_size;
+ ack.gl_frame_data->mailbox = received_mailbox;
+ }
+
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ route_id, output_surface_id, renderer_host_id, ack);
+}
+
+void AcknowledgeBufferForGpu(
+ int32 route_id,
+ int gpu_host_id,
+ const std::string& received_mailbox,
+ bool skip_frame,
+ const scoped_refptr<ui::Texture>& texture_to_produce) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack;
+ uint32 sync_point = 0;
+ DCHECK(!texture_to_produce.get() || !skip_frame);
+ if (texture_to_produce.get()) {
+ ack.mailbox_name = texture_to_produce->Produce();
+ sync_point =
+ content::ImageTransportFactory::GetInstance()->InsertSyncPoint();
+ } else if (skip_frame) {
+ ack.mailbox_name = received_mailbox;
+ ack.sync_point = 0;
+ }
+
+ ack.sync_point = sync_point;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ route_id, gpu_host_id, ack);
+}
+
+} // namespace
+
+// We need to watch for mouse events outside a Web Popup or its parent
+// and dismiss the popup for certain events.
+class RenderWidgetHostViewAura::EventFilterForPopupExit :
+ public ui::EventHandler {
+ public:
+ explicit EventFilterForPopupExit(RenderWidgetHostViewAura* rwhva)
+ : rwhva_(rwhva) {
+ DCHECK(rwhva_);
+ aura::RootWindow* root_window = rwhva_->window_->GetRootWindow();
+ DCHECK(root_window);
+ root_window->AddPreTargetHandler(this);
+ }
+
+ virtual ~EventFilterForPopupExit() {
+ aura::RootWindow* root_window = rwhva_->window_->GetRootWindow();
+ DCHECK(root_window);
+ root_window->RemovePreTargetHandler(this);
+ }
+
+ // Overridden from ui::EventHandler
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ rwhva_->ApplyEventFilterForPopupExit(event);
+ }
+
+ private:
+ RenderWidgetHostViewAura* rwhva_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventFilterForPopupExit);
+};
+
+void RenderWidgetHostViewAura::ApplyEventFilterForPopupExit(
+ ui::MouseEvent* event) {
+ if (in_shutdown_ || is_fullscreen_)
+ return;
+
+ if (event->type() != ui::ET_MOUSE_PRESSED || !event->target())
+ return;
+
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (target != window_ &&
+ (!popup_parent_host_view_ ||
+ target != popup_parent_host_view_->window_)) {
+ // Note: popup_parent_host_view_ may be NULL when there are multiple
+ // popup children per view. See: RenderWidgetHostViewAura::InitAsPopup().
+ in_shutdown_ = true;
+ host_->Shutdown();
+ }
+}
+
+// We have to implement the WindowObserver interface on a separate object
+// because clang doesn't like implementing multiple interfaces that have
+// methods with the same name. This object is owned by the
+// RenderWidgetHostViewAura.
+class RenderWidgetHostViewAura::WindowObserver : public aura::WindowObserver {
+ public:
+ explicit WindowObserver(RenderWidgetHostViewAura* view)
+ : view_(view) {
+ view_->window_->AddObserver(this);
+ }
+
+ virtual ~WindowObserver() {
+ view_->window_->RemoveObserver(this);
+ }
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowAddedToRootWindow(aura::Window* window) OVERRIDE {
+ if (window == view_->window_)
+ view_->AddedToRootWindow();
+ }
+
+ virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE {
+ if (window == view_->window_)
+ view_->RemovingFromRootWindow();
+ }
+
+ private:
+ RenderWidgetHostViewAura* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowObserver);
+};
+
+#if defined(OS_WIN)
+// On Windows, we need to watch the top level window for changes to transient
+// windows because they can cover the view and we need to ensure that they're
+// rendered on top of windowed NPAPI plugins.
+class RenderWidgetHostViewAura::TransientWindowObserver
+ : public aura::WindowObserver {
+ public:
+ explicit TransientWindowObserver(RenderWidgetHostViewAura* view)
+ : view_(view), top_level_(NULL) {
+ view_->window_->AddObserver(this);
+ }
+
+ virtual ~TransientWindowObserver() {
+ view_->window_->RemoveObserver(this);
+ StopObserving();
+ }
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowHierarchyChanged(
+ const aura::WindowObserver::HierarchyChangeParams& params) OVERRIDE {
+ aura::Window* top_level = GetToplevelWindow();
+ if (top_level == top_level_)
+ return;
+
+ StopObserving();
+ top_level_ = top_level;
+ if (top_level_ && top_level_ != view_->window_)
+ top_level_->AddObserver(this);
+ }
+
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
+ if (window == top_level_)
+ StopObserving();
+ }
+
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ if (window->transient_parent())
+ SendPluginCutoutRects();
+ }
+
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE {
+ if (window->transient_parent())
+ SendPluginCutoutRects();
+ }
+
+ virtual void OnAddTransientChild(aura::Window* window,
+ aura::Window* transient) OVERRIDE {
+ transient->AddObserver(this);
+ // Just wait for the OnWindowBoundsChanged of the transient, since the size
+ // is not known now.
+ }
+
+ virtual void OnRemoveTransientChild(aura::Window* window,
+ aura::Window* transient) OVERRIDE {
+ transient->RemoveObserver(this);
+ SendPluginCutoutRects();
+ }
+
+ aura::Window* GetToplevelWindow() {
+ aura::RootWindow* root = view_->window_->GetRootWindow();
+ if (!root)
+ return NULL;
+ aura::client::ActivationClient* activation_client =
+ aura::client::GetActivationClient(root);
+ if (!activation_client)
+ return NULL;
+ return activation_client->GetToplevelWindow(view_->window_);
+ }
+
+ void StopObserving() {
+ if (!top_level_)
+ return;
+
+ const aura::Window::Windows& transients = top_level_->transient_children();
+ for (size_t i = 0; i < transients.size(); ++i)
+ transients[i]->RemoveObserver(this);
+
+ if (top_level_ != view_->window_)
+ top_level_->RemoveObserver(this);
+ top_level_ = NULL;
+ }
+
+ void SendPluginCutoutRects() {
+ std::vector<gfx::Rect> cutouts;
+ if (top_level_) {
+ const aura::Window::Windows& transients =
+ top_level_->transient_children();
+ for (size_t i = 0; i < transients.size(); ++i) {
+ if (transients[i]->IsVisible())
+ cutouts.push_back(transients[i]->GetBoundsInRootWindow());
+ }
+ }
+
+ view_->UpdateTransientRects(cutouts);
+ }
+ private:
+ RenderWidgetHostViewAura* view_;
+ aura::Window* top_level_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver);
+};
+
+#endif
+
+class RenderWidgetHostViewAura::ResizeLock {
+ public:
+ ResizeLock(aura::RootWindow* root_window,
+ const gfx::Size new_size,
+ bool defer_compositor_lock)
+ : root_window_(root_window),
+ new_size_(new_size),
+ compositor_lock_(defer_compositor_lock ?
+ NULL :
+ root_window_->compositor()->GetCompositorLock()),
+ weak_ptr_factory_(this),
+ defer_compositor_lock_(defer_compositor_lock) {
+ TRACE_EVENT_ASYNC_BEGIN2("ui", "ResizeLock", this,
+ "width", new_size_.width(),
+ "height", new_size_.height());
+ root_window_->HoldPointerMoves();
+
+ BrowserThread::PostDelayedTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&RenderWidgetHostViewAura::ResizeLock::CancelLock,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kResizeLockTimeoutMs));
+ }
+
+ ~ResizeLock() {
+ CancelLock();
+ TRACE_EVENT_ASYNC_END2("ui", "ResizeLock", this,
+ "width", new_size_.width(),
+ "height", new_size_.height());
+ }
+
+ void UnlockCompositor() {
+ defer_compositor_lock_ = false;
+ compositor_lock_ = NULL;
+ }
+
+ void CancelLock() {
+ if (!root_window_)
+ return;
+ UnlockCompositor();
+ root_window_->ReleasePointerMoves();
+ root_window_ = NULL;
+ }
+
+ const gfx::Size& expected_size() const {
+ return new_size_;
+ }
+
+ bool GrabDeferredLock() {
+ if (root_window_ && defer_compositor_lock_) {
+ compositor_lock_ = root_window_->compositor()->GetCompositorLock();
+ defer_compositor_lock_ = false;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ aura::RootWindow* root_window_;
+ gfx::Size new_size_;
+ scoped_refptr<ui::CompositorLock> compositor_lock_;
+ base::WeakPtrFactory<ResizeLock> weak_ptr_factory_;
+ bool defer_compositor_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeLock);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, public:
+
+RenderWidgetHostViewAura::RenderWidgetHostViewAura(RenderWidgetHost* host)
+ : host_(RenderWidgetHostImpl::From(host)),
+ window_(new aura::Window(this)),
+ in_shutdown_(false),
+ is_fullscreen_(false),
+ popup_parent_host_view_(NULL),
+ popup_child_host_view_(NULL),
+ is_loading_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
+ can_compose_inline_(true),
+ has_composition_text_(false),
+ last_swapped_surface_scale_factor_(1.f),
+ paint_canvas_(NULL),
+ synthetic_move_sent_(false),
+ accelerated_compositing_state_changed_(false),
+ can_lock_compositor_(YES),
+ cursor_visibility_state_in_renderer_(UNKNOWN),
+ paint_observer_(NULL),
+ touch_editing_client_(NULL) {
+ host_->SetView(this);
+ window_observer_.reset(new WindowObserver(this));
+ aura::client::SetTooltipText(window_, &tooltip_);
+ aura::client::SetActivationDelegate(window_, this);
+ aura::client::SetActivationChangeObserver(window_, this);
+ aura::client::SetFocusChangeObserver(window_, this);
+ gfx::Screen::GetScreenFor(window_)->AddObserver(this);
+#if defined(OS_WIN)
+ transient_observer_.reset(new TransientWindowObserver(this));
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, RenderWidgetHostView implementation:
+
+void RenderWidgetHostViewAura::InitAsChild(
+ gfx::NativeView parent_view) {
+ window_->Init(ui::LAYER_TEXTURED);
+ window_->SetName("RenderWidgetHostViewAura");
+}
+
+void RenderWidgetHostViewAura::InitAsPopup(
+ RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& bounds_in_screen) {
+ popup_parent_host_view_ =
+ static_cast<RenderWidgetHostViewAura*>(parent_host_view);
+
+ RenderWidgetHostViewAura* old_child =
+ popup_parent_host_view_->popup_child_host_view_;
+ if (old_child) {
+ // TODO(jhorwich): Allow multiple popup_child_host_view_ per view, or
+ // similar mechanism to ensure a second popup doesn't cause the first one
+ // to never get a chance to filter events. See crbug.com/160589.
+ DCHECK(old_child->popup_parent_host_view_ == popup_parent_host_view_);
+ old_child->popup_parent_host_view_ = NULL;
+ }
+ popup_parent_host_view_->popup_child_host_view_ = this;
+ window_->SetType(aura::client::WINDOW_TYPE_MENU);
+ window_->Init(ui::LAYER_TEXTURED);
+ window_->SetName("RenderWidgetHostViewAura");
+
+ aura::RootWindow* root = popup_parent_host_view_->window_->GetRootWindow();
+ window_->SetDefaultParentByRootWindow(root, bounds_in_screen);
+
+ // TODO(erg): While I could make sure details of the StackingClient are
+ // hidden behind aura, hiding the details of the ScreenPositionClient will
+ // take another effort.
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(root);
+ gfx::Point origin_in_parent(bounds_in_screen.origin());
+ if (screen_position_client) {
+ screen_position_client->ConvertPointFromScreen(
+ window_->parent(), &origin_in_parent);
+ }
+ SetBounds(gfx::Rect(origin_in_parent, bounds_in_screen.size()));
+ Show();
+}
+
+void RenderWidgetHostViewAura::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ is_fullscreen_ = true;
+ window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window_->Init(ui::LAYER_TEXTURED);
+ window_->SetName("RenderWidgetHostViewAura");
+ window_->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+
+ aura::RootWindow* parent = NULL;
+ gfx::Rect bounds;
+ if (reference_host_view) {
+ aura::Window* reference_window =
+ static_cast<RenderWidgetHostViewAura*>(reference_host_view)->window_;
+ if (reference_window) {
+ host_tracker_.reset(new aura::WindowTracker);
+ host_tracker_->Add(reference_window);
+ }
+ gfx::Display display = gfx::Screen::GetScreenFor(window_)->
+ GetDisplayNearestWindow(reference_window);
+ parent = reference_window->GetRootWindow();
+ bounds = display.bounds();
+ }
+ window_->SetDefaultParentByRootWindow(parent, bounds);
+ Show();
+ Focus();
+}
+
+RenderWidgetHost* RenderWidgetHostViewAura::GetRenderWidgetHost() const {
+ return host_;
+}
+
+void RenderWidgetHostViewAura::WasShown() {
+ if (!host_->is_hidden())
+ return;
+ host_->WasShown();
+
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(window_->GetRootWindow());
+ if (cursor_client)
+ NotifyRendererOfCursorVisibilityState(cursor_client->IsCursorVisible());
+
+ if (!current_surface_.get() && host_->is_accelerated_compositing_active() &&
+ !released_front_lock_.get()) {
+ released_front_lock_ = GetCompositor()->GetCompositorLock();
+ }
+
+#if defined(OS_WIN)
+ LPARAM lparam = reinterpret_cast<LPARAM>(this);
+ EnumChildWindows(ui::GetHiddenWindow(), ShowWindowsCallback, lparam);
+ transient_observer_->SendPluginCutoutRects();
+#endif
+}
+
+void RenderWidgetHostViewAura::WasHidden() {
+ if (host_->is_hidden())
+ return;
+ host_->WasHidden();
+
+ released_front_lock_ = NULL;
+
+#if defined(OS_WIN)
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (root_window) {
+ HWND parent = root_window->GetAcceleratedWidget();
+ LPARAM lparam = reinterpret_cast<LPARAM>(this);
+
+ EnumChildWindows(parent, HideWindowsCallback, lparam);
+ }
+#endif
+}
+
+void RenderWidgetHostViewAura::SetSize(const gfx::Size& size) {
+ SetBounds(gfx::Rect(window_->bounds().origin(), size));
+}
+
+void RenderWidgetHostViewAura::SetBounds(const gfx::Rect& rect) {
+ if (HasDisplayPropertyChanged(window_))
+ host_->InvalidateScreenInfo();
+
+ window_->SetBounds(rect);
+ host_->WasResized();
+ MaybeCreateResizeLock();
+ if (touch_editing_client_) {
+ touch_editing_client_->OnSelectionOrCursorChanged(selection_anchor_rect_,
+ selection_focus_rect_);
+ }
+}
+
+void RenderWidgetHostViewAura::MaybeCreateResizeLock() {
+ gfx::Size desired_size = window_->bounds().size();
+ if (!host_->should_auto_resize() &&
+ !resize_lock_.get() &&
+ desired_size != current_frame_size_ &&
+ host_->is_accelerated_compositing_active()) {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ ui::Compositor* compositor = root_window ?
+ root_window->compositor() : NULL;
+ if (root_window && compositor) {
+ // Listen to changes in the compositor lock state.
+ if (!compositor->HasObserver(this))
+ compositor->AddObserver(this);
+
+// On Windows while resizing, the the resize locks makes us mis-paint a white
+// vertical strip (including the non-client area) if the content composition is
+// lagging the UI composition. So here we disable the throttling so that the UI
+// bits can draw ahead of the content thereby reducing the amount of whiteout.
+// Because this causes the content to be drawn at wrong sizes while resizing
+// we compensate by blocking the UI thread in Compositor::Draw() by issuing a
+// FinishAllRendering() if we are resizing.
+#if !defined (OS_WIN)
+ bool defer_compositor_lock =
+ can_lock_compositor_ == NO_PENDING_RENDERER_FRAME ||
+ can_lock_compositor_ == NO_PENDING_COMMIT;
+
+ if (can_lock_compositor_ == YES)
+ can_lock_compositor_ = YES_DID_LOCK;
+
+ resize_lock_.reset(new ResizeLock(root_window, desired_size,
+ defer_compositor_lock));
+#endif
+ }
+ }
+}
+
+gfx::NativeView RenderWidgetHostViewAura::GetNativeView() const {
+ return window_;
+}
+
+gfx::NativeViewId RenderWidgetHostViewAura::GetNativeViewId() const {
+#if defined(OS_WIN)
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (root_window) {
+ HWND window = root_window->GetAcceleratedWidget();
+ return reinterpret_cast<gfx::NativeViewId>(window);
+ }
+#endif
+ return static_cast<gfx::NativeViewId>(NULL);
+}
+
+gfx::NativeViewAccessible RenderWidgetHostViewAura::GetNativeViewAccessible() {
+#if defined(OS_WIN)
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return static_cast<gfx::NativeViewAccessible>(NULL);
+ HWND hwnd = root_window->GetAcceleratedWidget();
+
+ BrowserAccessibilityManager* manager =
+ GetOrCreateBrowserAccessibilityManager();
+ if (manager)
+ return manager->GetRoot()->ToBrowserAccessibilityWin();
+#endif
+
+ NOTIMPLEMENTED();
+ return static_cast<gfx::NativeViewAccessible>(NULL);
+}
+
+BrowserAccessibilityManager*
+RenderWidgetHostViewAura::GetOrCreateBrowserAccessibilityManager() {
+ BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
+ if (manager)
+ return manager;
+
+#if defined(OS_WIN)
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return NULL;
+ HWND hwnd = root_window->GetAcceleratedWidget();
+
+ // The accessible_parent may be NULL at this point. The WebContents will pass
+ // it down to this instance (by way of the RenderViewHost and
+ // RenderWidgetHost) when it is known. This instance will then set it on its
+ // BrowserAccessibilityManager.
+ gfx::NativeViewAccessible accessible_parent =
+ host_->GetParentNativeViewAccessible();
+
+ manager = new BrowserAccessibilityManagerWin(
+ hwnd, accessible_parent,
+ BrowserAccessibilityManagerWin::GetEmptyDocument(), this);
+#else
+ manager = BrowserAccessibilityManager::Create(
+ BrowserAccessibilityManager::GetEmptyDocument(), this);
+#endif
+
+ SetBrowserAccessibilityManager(manager);
+ return manager;
+}
+
+void RenderWidgetHostViewAura::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& plugin_window_moves) {
+#if defined(OS_WIN)
+ // We need to clip the rectangle to the tab's viewport, otherwise we will draw
+ // over the browser UI.
+ if (!window_->GetRootWindow()) {
+ DCHECK(plugin_window_moves.empty());
+ return;
+ }
+ HWND parent = window_->GetRootWindow()->GetAcceleratedWidget();
+ gfx::Rect view_bounds = window_->GetBoundsInRootWindow();
+ std::vector<WebPluginGeometry> moves = plugin_window_moves;
+
+ gfx::Rect view_port(scroll_offset.x(), scroll_offset.y(), view_bounds.width(),
+ view_bounds.height());
+
+ for (size_t i = 0; i < moves.size(); ++i) {
+ gfx::Rect clip(moves[i].clip_rect);
+ gfx::Vector2d view_port_offset(
+ moves[i].window_rect.OffsetFromOrigin() + scroll_offset);
+ clip.Offset(view_port_offset);
+ clip.Intersect(view_port);
+ clip.Offset(-view_port_offset);
+ moves[i].clip_rect = clip;
+
+ moves[i].window_rect.Offset(view_bounds.OffsetFromOrigin());
+
+ plugin_window_moves_[moves[i].window] = moves[i];
+
+ // transient_rects_ and constrained_rects_ are relative to the root window.
+ // We want to convert them to be relative to the plugin window.
+ std::vector<gfx::Rect> cutout_rects;
+ cutout_rects.assign(transient_rects_.begin(), transient_rects_.end());
+ cutout_rects.insert(cutout_rects.end(), constrained_rects_.begin(),
+ constrained_rects_.end());
+ for (size_t j = 0; j < cutout_rects.size(); ++j) {
+ gfx::Rect offset_cutout = cutout_rects[j];
+ offset_cutout -= moves[i].window_rect.OffsetFromOrigin();
+ moves[i].cutout_rects.push_back(offset_cutout);
+ }
+ }
+
+ MovePluginWindowsHelper(parent, moves);
+
+ // Make sure each plugin window (or its wrapper if it exists) has a pointer to
+ // |this|.
+ for (size_t i = 0; i < moves.size(); ++i) {
+ HWND window = moves[i].window;
+ if (GetParent(window) != parent) {
+ window = GetParent(window);
+ DCHECK(GetParent(window) == parent);
+ }
+ if (!GetProp(window, kWidgetOwnerProperty))
+ SetProp(window, kWidgetOwnerProperty, this);
+ }
+#endif // defined(OS_WIN)
+}
+
+void RenderWidgetHostViewAura::Focus() {
+ // Make sure we have a FocusClient before attempting to Focus(). In some
+ // situations we may not yet be in a valid Window hierarchy (such as reloading
+ // after out of memory discarded the tab).
+ aura::client::FocusClient* client = aura::client::GetFocusClient(window_);
+ if (client)
+ window_->Focus();
+}
+
+void RenderWidgetHostViewAura::Blur() {
+ window_->Blur();
+}
+
+bool RenderWidgetHostViewAura::HasFocus() const {
+ return window_->HasFocus();
+}
+
+bool RenderWidgetHostViewAura::IsSurfaceAvailableForCopy() const {
+ return CanCopyToBitmap() || !!host_->GetBackingStore(false);
+}
+
+void RenderWidgetHostViewAura::Show() {
+ window_->Show();
+}
+
+void RenderWidgetHostViewAura::Hide() {
+ window_->Hide();
+}
+
+bool RenderWidgetHostViewAura::IsShowing() {
+ return window_->IsVisible();
+}
+
+gfx::Rect RenderWidgetHostViewAura::GetViewBounds() const {
+ // This is the size that we want the renderer to produce. While we're waiting
+ // for the correct frame (i.e. during a resize), don't change the size so that
+ // we don't pipeline more resizes than we can handle.
+ gfx::Rect bounds(window_->GetBoundsInScreen());
+ if (resize_lock_.get())
+ return gfx::Rect(bounds.origin(), resize_lock_->expected_size());
+ else
+ return bounds;
+}
+
+void RenderWidgetHostViewAura::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostViewBase::SetBackground(background);
+ host_->SetBackground(background);
+ window_->layer()->SetFillsBoundsOpaquely(background.isOpaque());
+}
+
+#if defined(OS_WIN)
+gfx::NativeViewAccessible
+RenderWidgetHostViewAura::AccessibleObjectFromChildId(long child_id) {
+ BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
+ if (!manager)
+ return NULL;
+
+ return manager->ToBrowserAccessibilityManagerWin()->GetFromUniqueIdWin(
+ child_id);
+}
+#endif // defined(OS_WIN)
+
+void RenderWidgetHostViewAura::UpdateCursor(const WebCursor& cursor) {
+ current_cursor_ = cursor;
+ const gfx::Display display = gfx::Screen::GetScreenFor(window_)->
+ GetDisplayNearestWindow(window_);
+ current_cursor_.SetDisplayInfo(display);
+ UpdateCursorIfOverSelf();
+}
+
+void RenderWidgetHostViewAura::SetIsLoading(bool is_loading) {
+ if (is_loading_ && !is_loading && paint_observer_)
+ paint_observer_->OnPageLoadComplete();
+ is_loading_ = is_loading;
+ UpdateCursorIfOverSelf();
+}
+
+void RenderWidgetHostViewAura::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ if (text_input_type_ != type ||
+ can_compose_inline_ != can_compose_inline) {
+ text_input_type_ = type;
+ can_compose_inline_ = can_compose_inline;
+ if (GetInputMethod())
+ GetInputMethod()->OnTextInputTypeChanged(this);
+ if (touch_editing_client_)
+ touch_editing_client_->OnTextInputTypeChanged(text_input_type_);
+ }
+}
+
+void RenderWidgetHostViewAura::ImeCancelComposition() {
+ if (GetInputMethod())
+ GetInputMethod()->CancelComposition(this);
+ has_composition_text_ = false;
+}
+
+void RenderWidgetHostViewAura::ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+ composition_character_bounds_ = character_bounds;
+}
+
+void RenderWidgetHostViewAura::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ if (accelerated_compositing_state_changed_)
+ UpdateExternalTexture();
+
+ software_latency_info_.MergeWith(latency_info);
+
+ // Use the state of the RenderWidgetHost and not the window as the two may
+ // differ. In particular if the window is hidden but the renderer isn't and we
+ // ignore the update and the window is made visible again the layer isn't
+ // marked as dirty and we show the wrong thing.
+ // We do this after UpdateExternalTexture() so that when we become visible
+ // we're not drawing a stale texture.
+ if (host_->is_hidden())
+ return;
+
+ gfx::Rect clip_rect;
+ if (paint_canvas_) {
+ SkRect sk_clip_rect;
+ if (paint_canvas_->sk_canvas()->getClipBounds(&sk_clip_rect))
+ clip_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect));
+ }
+
+ if (!scroll_rect.IsEmpty())
+ SchedulePaintIfNotInClip(scroll_rect, clip_rect);
+
+#if defined(OS_WIN)
+ aura::RootWindow* root_window = window_->GetRootWindow();
+#endif
+ for (size_t i = 0; i < copy_rects.size(); ++i) {
+ gfx::Rect rect = gfx::SubtractRects(copy_rects[i], scroll_rect);
+ if (rect.IsEmpty())
+ continue;
+
+ SchedulePaintIfNotInClip(rect, clip_rect);
+
+#if defined(OS_WIN)
+ if (root_window) {
+ // Send the invalid rect in screen coordinates.
+ gfx::Rect screen_rect = GetViewBounds();
+ gfx::Rect invalid_screen_rect(rect);
+ invalid_screen_rect.Offset(screen_rect.x(), screen_rect.y());
+ HWND hwnd = root_window->GetAcceleratedWidget();
+ PaintPluginWindowsHelper(hwnd, invalid_screen_rect);
+ }
+#endif // defined(OS_WIN)
+ }
+}
+
+void RenderWidgetHostViewAura::RenderProcessGone(base::TerminationStatus status,
+ int error_code) {
+ UpdateCursorIfOverSelf();
+ Destroy();
+}
+
+void RenderWidgetHostViewAura::Destroy() {
+ // Beware, this function is not called on all destruction paths. It will
+ // implicitly end up calling ~RenderWidgetHostViewAura though, so all
+ // destruction/cleanup code should happen there, not here.
+ in_shutdown_ = true;
+ delete window_;
+}
+
+void RenderWidgetHostViewAura::SetTooltipText(const string16& tooltip_text) {
+ tooltip_ = tooltip_text;
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ aura::client::TooltipClient* tooltip_client =
+ aura::client::GetTooltipClient(root_window);
+ if (tooltip_client) {
+ tooltip_client->UpdateTooltip(window_);
+ // Content tooltips should be visible indefinitely.
+ tooltip_client->SetTooltipShownTimeout(window_, 0);
+ }
+}
+
+void RenderWidgetHostViewAura::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
+
+#if defined(USE_X11) && !defined(OS_CHROMEOS)
+ if (text.empty() || range.is_empty())
+ return;
+
+ // Set the BUFFER_SELECTION to the ui::Clipboard.
+ ui::ScopedClipboardWriter clipboard_writer(
+ ui::Clipboard::GetForCurrentThread(),
+ ui::Clipboard::BUFFER_SELECTION);
+ clipboard_writer.WriteText(text);
+#endif // defined(USE_X11) && !defined(OS_CHROMEOS)
+}
+
+void RenderWidgetHostViewAura::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ if (selection_anchor_rect_ == params.anchor_rect &&
+ selection_focus_rect_ == params.focus_rect)
+ return;
+
+ selection_anchor_rect_ = params.anchor_rect;
+ selection_focus_rect_ = params.focus_rect;
+
+ if (GetInputMethod())
+ GetInputMethod()->OnCaretBoundsChanged(this);
+
+ if (touch_editing_client_) {
+ touch_editing_client_->OnSelectionOrCursorChanged(selection_anchor_rect_,
+ selection_focus_rect_);
+ }
+}
+
+void RenderWidgetHostViewAura::ScrollOffsetChanged() {
+ aura::RootWindow* root = window_->GetRootWindow();
+ if (!root)
+ return;
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root);
+ if (cursor_client && !cursor_client->IsCursorVisible())
+ cursor_client->DisableMouseEvents();
+}
+
+BackingStore* RenderWidgetHostViewAura::AllocBackingStore(
+ const gfx::Size& size) {
+ return new BackingStoreAura(host_, size);
+}
+
+void RenderWidgetHostViewAura::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ if (!CanCopyToBitmap()) {
+ callback.Run(false, SkBitmap());
+ return;
+ }
+
+ const gfx::Size& dst_size_in_pixel = ConvertViewSizeToPixel(this, dst_size);
+ scoped_ptr<cc::CopyOutputRequest> request =
+ cc::CopyOutputRequest::CreateRequest(base::Bind(
+ &RenderWidgetHostViewAura::CopyFromCompositingSurfaceHasResult,
+ dst_size_in_pixel,
+ callback));
+ gfx::Rect src_subrect_in_pixel =
+ ConvertRectToPixel(current_device_scale_factor_, src_subrect);
+ request->set_area(src_subrect_in_pixel);
+ window_->layer()->RequestCopyOfOutput(request.Pass());
+}
+
+void RenderWidgetHostViewAura::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ if (!CanCopyToVideoFrame()) {
+ callback.Run(false);
+ return;
+ }
+
+ scoped_ptr<cc::CopyOutputRequest> request =
+ cc::CopyOutputRequest::CreateRequest(base::Bind(
+ &RenderWidgetHostViewAura::
+ CopyFromCompositingSurfaceHasResultForVideo,
+ AsWeakPtr(), // For caching the ReadbackYUVInterface on this class.
+ target,
+ callback));
+ gfx::Rect src_subrect_in_pixel =
+ ConvertRectToPixel(current_device_scale_factor_, src_subrect);
+ request->set_area(src_subrect_in_pixel);
+ window_->layer()->RequestCopyOfOutput(request.Pass());
+}
+
+bool RenderWidgetHostViewAura::CanCopyToBitmap() const {
+ return GetCompositor() && window_->layer()->has_external_content();
+}
+
+bool RenderWidgetHostViewAura::CanCopyToVideoFrame() const {
+ return GetCompositor() &&
+ window_->layer()->has_external_content() &&
+ host_->is_accelerated_compositing_active();
+}
+
+bool RenderWidgetHostViewAura::CanSubscribeFrame() const {
+ return true;
+}
+
+void RenderWidgetHostViewAura::BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ frame_subscriber_ = subscriber.Pass();
+}
+
+void RenderWidgetHostViewAura::EndFrameSubscription() {
+ frame_subscriber_.reset();
+}
+
+
+void RenderWidgetHostViewAura::OnAcceleratedCompositingStateChange() {
+ // Delay processing the state change until we either get a software frame if
+ // switching to software mode or receive a buffers swapped notification
+ // if switching to accelerated mode.
+ // Sometimes (e.g. on a page load) the renderer will spuriously disable then
+ // re-enable accelerated compositing, causing us to flash.
+ // TODO(piman): factor the enable/disable accelerated compositing message into
+ // the UpdateRect/AcceleratedSurfaceBuffersSwapped messages so that we have
+ // fewer inconsistent temporary states.
+ accelerated_compositing_state_changed_ = true;
+}
+
+bool RenderWidgetHostViewAura::ShouldSkipFrame(gfx::Size size_in_dip) const {
+ if (can_lock_compositor_ == NO_PENDING_RENDERER_FRAME ||
+ can_lock_compositor_ == NO_PENDING_COMMIT ||
+ !resize_lock_.get())
+ return false;
+
+ return size_in_dip != resize_lock_->expected_size();
+}
+
+void RenderWidgetHostViewAura::CheckResizeLock() {
+ if (!resize_lock_ || resize_lock_->expected_size() != current_frame_size_)
+ return;
+
+ // Since we got the size we were looking for, unlock the compositor. But delay
+ // the release of the lock until we've kicked a frame with the new texture, to
+ // avoid resizing the UI before we have a chance to draw a "good" frame.
+ resize_lock_->UnlockCompositor();
+ ui::Compositor* compositor = GetCompositor();
+ if (compositor) {
+ if (!compositor->HasObserver(this))
+ compositor->AddObserver(this);
+ }
+}
+
+void RenderWidgetHostViewAura::UpdateExternalTexture() {
+ // Delay processing accelerated compositing state change till here where we
+ // act upon the state change. (Clear the external texture if switching to
+ // software mode or set the external texture if going to accelerated mode).
+ if (accelerated_compositing_state_changed_)
+ accelerated_compositing_state_changed_ = false;
+
+ bool is_compositing_active = host_->is_accelerated_compositing_active();
+ if (is_compositing_active && current_surface_.get()) {
+ window_->layer()->SetExternalTexture(current_surface_.get());
+ current_frame_size_ = ConvertSizeToDIP(
+ current_surface_->device_scale_factor(), current_surface_->size());
+ CheckResizeLock();
+ } else if (is_compositing_active && framebuffer_holder_) {
+ cc::TextureMailbox mailbox = framebuffer_holder_->GetMailbox();
+ window_->layer()->SetTextureMailbox(mailbox,
+ last_swapped_surface_scale_factor_);
+ current_frame_size_ = ConvertSizeToDIP(last_swapped_surface_scale_factor_,
+ mailbox.shared_memory_size());
+ CheckResizeLock();
+ } else {
+ window_->layer()->SetExternalTexture(NULL);
+ resize_lock_.reset();
+ host_->WasResized();
+ }
+}
+
+bool RenderWidgetHostViewAura::SwapBuffersPrepare(
+ const gfx::Rect& surface_rect,
+ float surface_scale_factor,
+ const gfx::Rect& damage_rect,
+ const std::string& mailbox_name,
+ const BufferPresentedCallback& ack_callback) {
+ if (last_swapped_surface_size_ != surface_rect.size()) {
+ // The surface could have shrunk since we skipped an update, in which
+ // case we can expect a full update.
+ DLOG_IF(ERROR, damage_rect != surface_rect) << "Expected full damage rect";
+ skipped_damage_.setEmpty();
+ last_swapped_surface_size_ = surface_rect.size();
+ last_swapped_surface_scale_factor_ = surface_scale_factor;
+ }
+
+ if (ShouldSkipFrame(ConvertSizeToDIP(surface_scale_factor,
+ surface_rect.size())) ||
+ mailbox_name.empty()) {
+ skipped_damage_.op(RectToSkIRect(damage_rect), SkRegion::kUnion_Op);
+ ack_callback.Run(true, scoped_refptr<ui::Texture>());
+ return false;
+ }
+
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ current_surface_ =
+ factory->CreateTransportClient(surface_scale_factor);
+ if (!current_surface_.get()) {
+ LOG(ERROR) << "Failed to create ImageTransport texture";
+ ack_callback.Run(true, scoped_refptr<ui::Texture>());
+ return false;
+ }
+
+ current_surface_->Consume(mailbox_name, surface_rect.size());
+ released_front_lock_ = NULL;
+ UpdateExternalTexture();
+
+ return true;
+}
+
+void RenderWidgetHostViewAura::SwapBuffersCompleted(
+ const BufferPresentedCallback& ack_callback,
+ const scoped_refptr<ui::Texture>& texture_to_return) {
+ ui::Compositor* compositor = GetCompositor();
+ if (!compositor) {
+ ack_callback.Run(false, texture_to_return);
+ } else {
+ AddOnCommitCallbackAndDisableLocks(
+ base::Bind(ack_callback, false, texture_to_return));
+ }
+
+ DidReceiveFrameFromRenderer();
+}
+
+void RenderWidgetHostViewAura::DidReceiveFrameFromRenderer() {
+ if (frame_subscriber() && CanCopyToVideoFrame()) {
+ const base::Time present_time = base::Time::Now();
+ scoped_refptr<media::VideoFrame> frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
+ if (frame_subscriber()->ShouldCaptureFrame(present_time,
+ &frame, &callback)) {
+ CopyFromCompositingSurfaceToVideoFrame(
+ gfx::Rect(current_frame_size_),
+ frame,
+ base::Bind(callback, present_time));
+ }
+ }
+}
+
+#if defined(OS_WIN)
+void RenderWidgetHostViewAura::UpdateTransientRects(
+ const std::vector<gfx::Rect>& rects) {
+ transient_rects_ = rects;
+ UpdateCutoutRects();
+}
+
+void RenderWidgetHostViewAura::UpdateConstrainedWindowRects(
+ const std::vector<gfx::Rect>& rects) {
+ constrained_rects_ = rects;
+ UpdateCutoutRects();
+}
+
+void RenderWidgetHostViewAura::UpdateCutoutRects() {
+ if (!window_->GetRootWindow())
+ return;
+ HWND parent = window_->GetRootWindow()->GetAcceleratedWidget();
+ CutoutRectsParams params;
+ params.widget = this;
+ params.cutout_rects.assign(transient_rects_.begin(), transient_rects_.end());
+ params.cutout_rects.insert(params.cutout_rects.end(),
+ constrained_rects_.begin(),
+ constrained_rects_.end());
+ params.geometry = &plugin_window_moves_;
+ LPARAM lparam = reinterpret_cast<LPARAM>(&params);
+ EnumChildWindows(parent, SetCutoutRectsCallback, lparam);
+}
+#endif
+
+void RenderWidgetHostViewAura::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params_in_pixel,
+ int gpu_host_id) {
+ BufferPresentedCallback ack_callback = base::Bind(
+ &AcknowledgeBufferForGpu,
+ params_in_pixel.route_id,
+ gpu_host_id,
+ params_in_pixel.mailbox_name);
+ BuffersSwapped(params_in_pixel.size,
+ gfx::Rect(params_in_pixel.size),
+ params_in_pixel.scale_factor,
+ params_in_pixel.mailbox_name,
+ params_in_pixel.latency_info,
+ ack_callback);
+}
+
+void RenderWidgetHostViewAura::SwapDelegatedFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::DelegatedFrameData> frame_data,
+ float frame_device_scale_factor,
+ const ui::LatencyInfo& latency_info) {
+ gfx::Size frame_size_in_dip;
+ if (!frame_data->render_pass_list.empty()) {
+ frame_size_in_dip = gfx::ToFlooredSize(gfx::ScaleSize(
+ frame_data->render_pass_list.back()->output_rect.size(),
+ 1.f/frame_device_scale_factor));
+ }
+ if (ShouldSkipFrame(frame_size_in_dip)) {
+ cc::CompositorFrameAck ack;
+ ack.resources.swap(frame_data->resource_list);
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ host_->GetRoutingID(), output_surface_id,
+ host_->GetProcess()->GetID(), ack);
+ return;
+ }
+ window_->layer()->SetDelegatedFrame(frame_data.Pass(), frame_size_in_dip);
+ released_front_lock_ = NULL;
+ current_frame_size_ = frame_size_in_dip;
+ CheckResizeLock();
+
+ if (paint_observer_)
+ paint_observer_->OnUpdateCompositorContent();
+
+ ui::Compositor* compositor = GetCompositor();
+ if (!compositor) {
+ SendDelegatedFrameAck(output_surface_id);
+ } else {
+ compositor->SetLatencyInfo(latency_info);
+ AddOnCommitCallbackAndDisableLocks(
+ base::Bind(&RenderWidgetHostViewAura::SendDelegatedFrameAck,
+ AsWeakPtr(),
+ output_surface_id));
+ }
+ DidReceiveFrameFromRenderer();
+}
+
+void RenderWidgetHostViewAura::SendDelegatedFrameAck(uint32 output_surface_id) {
+ cc::CompositorFrameAck ack;
+ window_->layer()->TakeUnusedResourcesForChildCompositor(&ack.resources);
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ host_->GetRoutingID(), output_surface_id,
+ host_->GetProcess()->GetID(), ack);
+}
+
+void RenderWidgetHostViewAura::SwapSoftwareFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::SoftwareFrameData> frame_data,
+ float frame_device_scale_factor,
+ const ui::LatencyInfo& latency_info) {
+ const gfx::Size& frame_size = frame_data->size;
+ const gfx::Rect& damage_rect = frame_data->damage_rect;
+ gfx::Size frame_size_in_dip =
+ ConvertSizeToDIP(frame_device_scale_factor, frame_size);
+ if (ShouldSkipFrame(frame_size_in_dip)) {
+ SendSoftwareFrameAck(output_surface_id, frame_data->id);
+ return;
+ }
+
+ const size_t size_in_bytes = 4 * frame_size.GetArea();
+#ifdef OS_WIN
+ scoped_ptr<base::SharedMemory> shared_memory(
+ new base::SharedMemory(frame_data->handle, true,
+ host_->GetProcess()->GetHandle()));
+#else
+ scoped_ptr<base::SharedMemory> shared_memory(
+ new base::SharedMemory(frame_data->handle, true));
+#endif
+
+ if (!shared_memory->Map(size_in_bytes)) {
+ host_->GetProcess()->ReceivedBadMessage();
+ return;
+ }
+
+ if (last_swapped_surface_size_ != frame_size) {
+ DLOG_IF(ERROR, damage_rect != gfx::Rect(frame_size))
+ << "Expected full damage rect";
+ }
+ last_swapped_surface_size_ = frame_size;
+ last_swapped_surface_scale_factor_ = frame_device_scale_factor;
+
+ scoped_refptr<MemoryHolder> holder(new MemoryHolder(
+ shared_memory.Pass(),
+ frame_size,
+ base::Bind(&RenderWidgetHostViewAura::SendSoftwareFrameAck,
+ AsWeakPtr(),
+ output_surface_id,
+ frame_data->id)));
+ framebuffer_holder_.swap(holder);
+ cc::TextureMailbox mailbox = framebuffer_holder_->GetMailbox();
+ DCHECK(mailbox.IsSharedMemory());
+ current_frame_size_ = frame_size_in_dip;
+
+ released_front_lock_ = NULL;
+ CheckResizeLock();
+ window_->layer()->SetTextureMailbox(mailbox, frame_device_scale_factor);
+ window_->SchedulePaintInRect(
+ ConvertRectToDIP(frame_device_scale_factor, damage_rect));
+
+ ui::Compositor* compositor = GetCompositor();
+ if (compositor)
+ compositor->SetLatencyInfo(latency_info);
+ if (paint_observer_)
+ paint_observer_->OnUpdateCompositorContent();
+ DidReceiveFrameFromRenderer();
+}
+
+void RenderWidgetHostViewAura::SendSoftwareFrameAck(
+ uint32 output_surface_id, unsigned software_frame_id) {
+ cc::CompositorFrameAck ack;
+ ack.last_software_frame_id = software_frame_id;
+ RenderWidgetHostImpl::SendSwapCompositorFrameAck(
+ host_->GetRoutingID(), output_surface_id,
+ host_->GetProcess()->GetID(), ack);
+}
+
+void RenderWidgetHostViewAura::OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) {
+ if (frame->delegated_frame_data) {
+ SwapDelegatedFrame(output_surface_id,
+ frame->delegated_frame_data.Pass(),
+ frame->metadata.device_scale_factor,
+ frame->metadata.latency_info);
+ return;
+ }
+
+ if (frame->software_frame_data) {
+ SwapSoftwareFrame(output_surface_id,
+ frame->software_frame_data.Pass(),
+ frame->metadata.device_scale_factor,
+ frame->metadata.latency_info);
+ return;
+ }
+
+ if (!frame->gl_frame_data || frame->gl_frame_data->mailbox.IsZero())
+ return;
+
+ BufferPresentedCallback ack_callback = base::Bind(
+ &SendCompositorFrameAck,
+ host_->GetRoutingID(), output_surface_id, host_->GetProcess()->GetID(),
+ frame->gl_frame_data->mailbox, frame->gl_frame_data->size);
+
+ if (!frame->gl_frame_data->sync_point) {
+ LOG(ERROR) << "CompositorFrame without sync point. Skipping frame...";
+ ack_callback.Run(true, scoped_refptr<ui::Texture>());
+ return;
+ }
+
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ factory->WaitSyncPoint(frame->gl_frame_data->sync_point);
+
+ std::string mailbox_name(
+ reinterpret_cast<const char*>(frame->gl_frame_data->mailbox.name),
+ sizeof(frame->gl_frame_data->mailbox.name));
+ BuffersSwapped(frame->gl_frame_data->size,
+ frame->gl_frame_data->sub_buffer_rect,
+ frame->metadata.device_scale_factor,
+ mailbox_name,
+ frame->metadata.latency_info,
+ ack_callback);
+}
+
+#if defined(OS_WIN)
+void RenderWidgetHostViewAura::SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) {
+ if (GetBrowserAccessibilityManager()) {
+ GetBrowserAccessibilityManager()->ToBrowserAccessibilityManagerWin()
+ ->set_parent_iaccessible(accessible_parent);
+ }
+}
+#endif
+
+void RenderWidgetHostViewAura::BuffersSwapped(
+ const gfx::Size& surface_size,
+ const gfx::Rect& damage_rect,
+ float surface_scale_factor,
+ const std::string& mailbox_name,
+ const ui::LatencyInfo& latency_info,
+ const BufferPresentedCallback& ack_callback) {
+ scoped_refptr<ui::Texture> previous_texture(current_surface_);
+ const gfx::Rect surface_rect = gfx::Rect(surface_size);
+
+ if (!SwapBuffersPrepare(surface_rect,
+ surface_scale_factor,
+ damage_rect,
+ mailbox_name,
+ ack_callback)) {
+ return;
+ }
+
+ SkRegion damage(RectToSkIRect(damage_rect));
+ if (!skipped_damage_.isEmpty()) {
+ damage.op(skipped_damage_, SkRegion::kUnion_Op);
+ skipped_damage_.setEmpty();
+ }
+
+ DCHECK(surface_rect.Contains(SkIRectToRect(damage.getBounds())));
+ ui::Texture* current_texture = current_surface_.get();
+
+ const gfx::Size surface_size_in_pixel = surface_size;
+ DLOG_IF(ERROR, previous_texture.get() &&
+ previous_texture->size() != current_texture->size() &&
+ SkIRectToRect(damage.getBounds()) != surface_rect) <<
+ "Expected full damage rect after size change";
+ if (previous_texture.get() && !previous_damage_.isEmpty() &&
+ previous_texture->size() == current_texture->size()) {
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ GLHelper* gl_helper = factory->GetGLHelper();
+ gl_helper->CopySubBufferDamage(
+ current_texture->PrepareTexture(),
+ previous_texture->PrepareTexture(),
+ damage,
+ previous_damage_);
+ }
+ previous_damage_ = damage;
+
+ ui::Compositor* compositor = GetCompositor();
+ if (compositor) {
+ // Co-ordinates come in OpenGL co-ordinate space.
+ // We need to convert to layer space.
+ gfx::Rect rect_to_paint =
+ ConvertRectToDIP(surface_scale_factor,
+ gfx::Rect(damage_rect.x(),
+ surface_size_in_pixel.height() -
+ damage_rect.y() - damage_rect.height(),
+ damage_rect.width(),
+ damage_rect.height()));
+
+ // Damage may not have been DIP aligned, so inflate damage to compensate
+ // for any round-off error.
+ rect_to_paint.Inset(-1, -1);
+ rect_to_paint.Intersect(window_->bounds());
+
+ if (paint_observer_)
+ paint_observer_->OnUpdateCompositorContent();
+ window_->SchedulePaintInRect(rect_to_paint);
+ compositor->SetLatencyInfo(latency_info);
+ }
+
+ SwapBuffersCompleted(ack_callback, previous_texture);
+}
+
+void RenderWidgetHostViewAura::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params_in_pixel,
+ int gpu_host_id) {
+ gfx::Rect damage_rect(params_in_pixel.x,
+ params_in_pixel.y,
+ params_in_pixel.width,
+ params_in_pixel.height);
+ BufferPresentedCallback ack_callback =
+ base::Bind(&AcknowledgeBufferForGpu,
+ params_in_pixel.route_id,
+ gpu_host_id,
+ params_in_pixel.mailbox_name);
+ BuffersSwapped(params_in_pixel.surface_size,
+ damage_rect,
+ params_in_pixel.surface_scale_factor,
+ params_in_pixel.mailbox_name,
+ params_in_pixel.latency_info,
+ ack_callback);
+}
+
+void RenderWidgetHostViewAura::AcceleratedSurfaceSuspend() {
+}
+
+void RenderWidgetHostViewAura::AcceleratedSurfaceRelease() {
+ // This really tells us to release the frontbuffer.
+ if (current_surface_.get()) {
+ ui::Compositor* compositor = GetCompositor();
+ if (compositor) {
+ // We need to wait for a commit to clear to guarantee that all we
+ // will not issue any more GL referencing the previous surface.
+ AddOnCommitCallbackAndDisableLocks(
+ base::Bind(&RenderWidgetHostViewAura::
+ SetSurfaceNotInUseByCompositor,
+ AsWeakPtr(),
+ current_surface_)); // Hold a ref so the texture will not
+ // get deleted until after commit.
+ }
+ current_surface_ = NULL;
+ UpdateExternalTexture();
+ }
+}
+
+bool RenderWidgetHostViewAura::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ // Aura doesn't use GetBackingStore for accelerated pages, so it doesn't
+ // matter what is returned here as GetBackingStore is the only caller of this
+ // method. TODO(jbates) implement this if other Aura code needs it.
+ return false;
+}
+
+void RenderWidgetHostViewAura::SetSurfaceNotInUseByCompositor(
+ scoped_refptr<ui::Texture>) {
+}
+
+// static
+void RenderWidgetHostViewAura::CopyFromCompositingSurfaceHasResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ if (result->IsEmpty() || result->size().IsEmpty()) {
+ callback.Run(false, SkBitmap());
+ return;
+ }
+
+ if (result->HasTexture()) {
+ PrepareTextureCopyOutputResult(dst_size_in_pixel, callback, result.Pass());
+ return;
+ }
+
+ DCHECK(result->HasBitmap());
+ PrepareBitmapCopyOutputResult(dst_size_in_pixel, callback, result.Pass());
+}
+
+static void CopyFromCompositingSurfaceFinished(
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ const cc::TextureMailbox::ReleaseCallback& release_callback,
+ scoped_ptr<SkBitmap> bitmap,
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock,
+ bool result) {
+ bitmap_pixels_lock.reset();
+ release_callback.Run(0, false);
+ callback.Run(result, *bitmap);
+}
+
+// static
+void RenderWidgetHostViewAura::PrepareTextureCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+
+ DCHECK(result->HasTexture());
+ if (!result->HasTexture())
+ return;
+
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ dst_size_in_pixel.width(), dst_size_in_pixel.height());
+ if (!bitmap->allocPixels())
+ return;
+ bitmap->setIsOpaque(true);
+
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ GLHelper* gl_helper = factory->GetGLHelper();
+ if (!gl_helper)
+ return;
+
+ scoped_ptr<SkAutoLockPixels> bitmap_pixels_lock(
+ new SkAutoLockPixels(*bitmap));
+ uint8* pixels = static_cast<uint8*>(bitmap->getPixels());
+
+ scoped_ptr<cc::TextureMailbox> texture_mailbox = result->TakeTexture();
+ DCHECK(texture_mailbox->IsTexture());
+ if (!texture_mailbox->IsTexture())
+ return;
+
+ scoped_callback_runner.Release();
+
+ gl_helper->CropScaleReadbackAndCleanMailbox(
+ texture_mailbox->name(),
+ texture_mailbox->sync_point(),
+ result->size(),
+ gfx::Rect(result->size()),
+ dst_size_in_pixel,
+ pixels,
+ base::Bind(&CopyFromCompositingSurfaceFinished,
+ callback,
+ texture_mailbox->callback(),
+ base::Passed(&bitmap),
+ base::Passed(&bitmap_pixels_lock)));
+}
+
+// static
+void RenderWidgetHostViewAura::PrepareBitmapCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ DCHECK(result->HasBitmap());
+
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+ if (!result->HasBitmap())
+ return;
+
+ scoped_ptr<SkBitmap> source = result->TakeBitmap();
+ DCHECK(source);
+ if (!source)
+ return;
+
+ scoped_callback_runner.Release();
+
+ SkBitmap bitmap = skia::ImageOperations::Resize(
+ *source,
+ skia::ImageOperations::RESIZE_BEST,
+ dst_size_in_pixel.width(),
+ dst_size_in_pixel.height());
+ callback.Run(true, bitmap);
+}
+
+static void CopyFromCompositingSurfaceFinishedForVideo(
+ const base::Callback<void(bool)>& callback,
+ const cc::TextureMailbox::ReleaseCallback& release_callback,
+ bool result) {
+ release_callback.Run(0, false);
+ callback.Run(result);
+}
+
+// static
+void RenderWidgetHostViewAura::CopyFromCompositingSurfaceHasResultForVideo(
+ base::WeakPtr<RenderWidgetHostViewAura> rwhva,
+ scoped_refptr<media::VideoFrame> video_frame,
+ const base::Callback<void(bool)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result) {
+ base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
+
+ if (!rwhva)
+ return;
+
+ if (result->IsEmpty())
+ return;
+ if (result->size().IsEmpty())
+ return;
+
+ // Compute the dest size we want after the letterboxing resize. Make the
+ // coordinates and sizes even because we letterbox in YUV space
+ // (see CopyRGBToVideoFrame). They need to be even for the UV samples to
+ // line up correctly.
+ // The video frame's coded_size() and the result's size() are both physical
+ // pixels.
+ gfx::Rect region_in_frame =
+ media::ComputeLetterboxRegion(gfx::Rect(video_frame->coded_size()),
+ result->size());
+ region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
+ region_in_frame.y() & ~1,
+ region_in_frame.width() & ~1,
+ region_in_frame.height() & ~1);
+ if (region_in_frame.IsEmpty())
+ return;
+
+ // We only handle texture readbacks for now. If the compositor is in software
+ // mode, we could produce a software-backed VideoFrame here as well.
+ if (!result->HasTexture()) {
+ DCHECK(result->HasBitmap());
+ scoped_ptr<SkBitmap> bitmap = result->TakeBitmap();
+ // Scale the bitmap to the required size, if necessary.
+ SkBitmap scaled_bitmap;
+ if (result->size().width() != region_in_frame.width() ||
+ result->size().height() != region_in_frame.height()) {
+ skia::ImageOperations::ResizeMethod method =
+ skia::ImageOperations::RESIZE_GOOD;
+ scaled_bitmap = skia::ImageOperations::Resize(*bitmap.get(), method,
+ region_in_frame.width(),
+ region_in_frame.height());
+ } else {
+ scaled_bitmap = *bitmap.get();
+ }
+
+ {
+ SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
+
+ media::CopyRGBToVideoFrame(
+ reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
+ scaled_bitmap.rowBytes(),
+ region_in_frame,
+ video_frame.get());
+ }
+ scoped_callback_runner.Release();
+ callback.Run(true);
+ return;
+ }
+
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ GLHelper* gl_helper = factory->GetGLHelper();
+ if (!gl_helper)
+ return;
+
+ scoped_ptr<cc::TextureMailbox> texture_mailbox = result->TakeTexture();
+ DCHECK(texture_mailbox->IsTexture());
+ if (!texture_mailbox->IsTexture())
+ return;
+
+ gfx::Rect result_rect(result->size());
+
+ content::ReadbackYUVInterface* yuv_readback_pipeline =
+ rwhva->yuv_readback_pipeline_.get();
+ if (yuv_readback_pipeline == NULL ||
+ yuv_readback_pipeline->scaler()->SrcSize() != result_rect.size() ||
+ yuv_readback_pipeline->scaler()->SrcSubrect() != result_rect ||
+ yuv_readback_pipeline->scaler()->DstSize() != region_in_frame.size()) {
+ GLHelper::ScalerQuality quality = GLHelper::SCALER_QUALITY_FAST;
+ std::string quality_switch = switches::kTabCaptureDownscaleQuality;
+ // If we're scaling up, we can use the "best" quality.
+ if (result_rect.size().width() < region_in_frame.size().width() &&
+ result_rect.size().height() < region_in_frame.size().height())
+ quality_switch = switches::kTabCaptureUpscaleQuality;
+
+ std::string switch_value =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(quality_switch);
+ if (switch_value == "fast")
+ quality = GLHelper::SCALER_QUALITY_FAST;
+ else if (switch_value == "good")
+ quality = GLHelper::SCALER_QUALITY_GOOD;
+ else if (switch_value == "best")
+ quality = GLHelper::SCALER_QUALITY_BEST;
+
+ rwhva->yuv_readback_pipeline_.reset(
+ gl_helper->CreateReadbackPipelineYUV(quality,
+ result_rect.size(),
+ result_rect,
+ video_frame->coded_size(),
+ region_in_frame,
+ true,
+ false));
+ yuv_readback_pipeline = rwhva->yuv_readback_pipeline_.get();
+ }
+
+ scoped_callback_runner.Release();
+ base::Callback<void(bool result)> finished_callback = base::Bind(
+ &CopyFromCompositingSurfaceFinishedForVideo,
+ callback,
+ texture_mailbox->callback());
+ yuv_readback_pipeline->ReadbackYUV(
+ texture_mailbox->name(),
+ texture_mailbox->sync_point(),
+ video_frame.get(),
+ finished_callback);
+}
+
+void RenderWidgetHostViewAura::GetScreenInfo(WebScreenInfo* results) {
+ GetScreenInfoForWindow(results, window_->GetRootWindow() ? window_ : NULL);
+}
+
+gfx::Rect RenderWidgetHostViewAura::GetBoundsInRootWindow() {
+ return window_->GetToplevelWindow()->GetBoundsInScreen();
+}
+
+void RenderWidgetHostViewAura::GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) {
+ if (touch_editing_client_)
+ touch_editing_client_->GestureEventAck(gesture_event_type);
+}
+
+void RenderWidgetHostViewAura::ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) {
+ ScopedVector<ui::TouchEvent> events;
+ if (!MakeUITouchEventsFromWebTouchEvents(touch, &events,
+ SCREEN_COORDINATES))
+ return;
+
+ aura::RootWindow* root = window_->GetRootWindow();
+ // |root| is NULL during tests.
+ if (!root)
+ return;
+
+ ui::EventResult result = (ack_result ==
+ INPUT_EVENT_ACK_STATE_CONSUMED) ? ui::ER_HANDLED : ui::ER_UNHANDLED;
+ for (ScopedVector<ui::TouchEvent>::iterator iter = events.begin(),
+ end = events.end(); iter != end; ++iter) {
+ root->ProcessedTouchEvent((*iter), window_, result);
+ }
+}
+
+SmoothScrollGesture* RenderWidgetHostViewAura::CreateSmoothScrollGesture(
+ bool scroll_down,
+ int pixels_to_scroll,
+ int mouse_event_x,
+ int mouse_event_y) {
+ return new TouchSmoothScrollGestureAura(scroll_down,
+ pixels_to_scroll,
+ mouse_event_x,
+ mouse_event_y,
+ window_);
+}
+
+void RenderWidgetHostViewAura::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+ // Not needed. Mac-only.
+}
+
+void RenderWidgetHostViewAura::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ // Not needed. Mac-only.
+}
+
+void RenderWidgetHostViewAura::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ BrowserAccessibilityManager* manager =
+ GetOrCreateBrowserAccessibilityManager();
+ if (manager)
+ manager->OnAccessibilityNotifications(params);
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostViewAura::GetCompositingSurface() {
+ if (shared_surface_handle_.is_null()) {
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ shared_surface_handle_ = factory->CreateSharedSurfaceHandle();
+ if (!shared_surface_handle_.is_null())
+ factory->AddObserver(this);
+ }
+ return shared_surface_handle_;
+}
+
+bool RenderWidgetHostViewAura::LockMouse() {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return false;
+
+ if (mouse_locked_)
+ return true;
+
+ mouse_locked_ = true;
+ window_->SetCapture();
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window);
+ if (cursor_client) {
+ cursor_client->HideCursor();
+ cursor_client->LockCursor();
+ }
+
+ if (ShouldMoveToCenter()) {
+ synthetic_move_sent_ = true;
+ window_->MoveCursorTo(gfx::Rect(window_->bounds().size()).CenterPoint());
+ }
+ if (aura::client::GetTooltipClient(root_window))
+ aura::client::GetTooltipClient(root_window)->SetTooltipsEnabled(false);
+ return true;
+}
+
+void RenderWidgetHostViewAura::UnlockMouse() {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!mouse_locked_ || !root_window)
+ return;
+
+ mouse_locked_ = false;
+
+ window_->ReleaseCapture();
+ window_->MoveCursorTo(unlocked_mouse_position_);
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window);
+ if (cursor_client) {
+ cursor_client->UnlockCursor();
+ cursor_client->ShowCursor();
+ }
+
+ if (aura::client::GetTooltipClient(root_window))
+ aura::client::GetTooltipClient(root_window)->SetTooltipsEnabled(true);
+
+ host_->LostMouseLock();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, ui::TextInputClient implementation:
+void RenderWidgetHostViewAura::SetCompositionText(
+ const ui::CompositionText& composition) {
+ if (!host_)
+ return;
+
+ // ui::CompositionUnderline should be identical to
+ // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
+ COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
+ sizeof(WebKit::WebCompositionUnderline),
+ ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
+
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition.underlines);
+
+ // TODO(suzhe): due to a bug of webkit, we can't use selection range with
+ // composition string. See: https://bugs.webkit.org/show_bug.cgi?id=37788
+ host_->ImeSetComposition(composition.text, underlines,
+ composition.selection.end(),
+ composition.selection.end());
+
+ has_composition_text_ = !composition.text.empty();
+}
+
+void RenderWidgetHostViewAura::ConfirmCompositionText() {
+ if (host_ && has_composition_text_)
+ host_->ImeConfirmComposition(string16(), ui::Range::InvalidRange(), false);
+ has_composition_text_ = false;
+}
+
+void RenderWidgetHostViewAura::ClearCompositionText() {
+ if (host_ && has_composition_text_)
+ host_->ImeCancelComposition();
+ has_composition_text_ = false;
+}
+
+void RenderWidgetHostViewAura::InsertText(const string16& text) {
+ DCHECK(text_input_type_ != ui::TEXT_INPUT_TYPE_NONE);
+ if (host_)
+ host_->ImeConfirmComposition(text, ui::Range::InvalidRange(), false);
+ has_composition_text_ = false;
+}
+
+void RenderWidgetHostViewAura::InsertChar(char16 ch, int flags) {
+ if (popup_child_host_view_ && popup_child_host_view_->NeedsInputGrab()) {
+ popup_child_host_view_->InsertChar(ch, flags);
+ return;
+ }
+
+ if (host_) {
+ double now = ui::EventTimeForNow().InSecondsF();
+ // Send a WebKit::WebInputEvent::Char event to |host_|.
+ NativeWebKeyboardEvent webkit_event(ui::ET_KEY_PRESSED,
+ true /* is_char */,
+ ch,
+ flags,
+ now);
+ host_->ForwardKeyboardEvent(webkit_event);
+ }
+}
+
+gfx::NativeWindow RenderWidgetHostViewAura::GetAttachedWindow() const {
+ return window_;
+}
+
+ui::TextInputType RenderWidgetHostViewAura::GetTextInputType() const {
+ return text_input_type_;
+}
+
+ui::TextInputMode RenderWidgetHostViewAura::GetTextInputMode() const {
+ return ui::TEXT_INPUT_MODE_DEFAULT;
+}
+
+bool RenderWidgetHostViewAura::CanComposeInline() const {
+ return can_compose_inline_;
+}
+
+gfx::Rect RenderWidgetHostViewAura::ConvertRectToScreen(const gfx::Rect& rect) {
+ gfx::Point origin = rect.origin();
+ gfx::Point end = gfx::Point(rect.right(), rect.bottom());
+
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (root_window) {
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(root_window);
+ screen_position_client->ConvertPointToScreen(window_, &origin);
+ screen_position_client->ConvertPointToScreen(window_, &end);
+ return gfx::Rect(origin.x(),
+ origin.y(),
+ end.x() - origin.x(),
+ end.y() - origin.y());
+ }
+
+ return rect;
+}
+
+gfx::Rect RenderWidgetHostViewAura::ConvertRectFromScreen(
+ const gfx::Rect& rect) {
+ gfx::Point origin = rect.origin();
+ gfx::Point end = gfx::Point(rect.right(), rect.bottom());
+
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (root_window) {
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(root_window);
+ screen_position_client->ConvertPointFromScreen(window_, &origin);
+ screen_position_client->ConvertPointFromScreen(window_, &end);
+ return gfx::Rect(origin.x(),
+ origin.y(),
+ end.x() - origin.x(),
+ end.y() - origin.y());
+ }
+
+ return rect;
+}
+
+gfx::Rect RenderWidgetHostViewAura::GetCaretBounds() {
+ const gfx::Rect rect =
+ gfx::UnionRects(selection_anchor_rect_, selection_focus_rect_);
+ return ConvertRectToScreen(rect);
+}
+
+bool RenderWidgetHostViewAura::GetCompositionCharacterBounds(uint32 index,
+ gfx::Rect* rect) {
+ DCHECK(rect);
+ if (index >= composition_character_bounds_.size())
+ return false;
+ *rect = ConvertRectToScreen(composition_character_bounds_[index]);
+ return true;
+}
+
+bool RenderWidgetHostViewAura::HasCompositionText() {
+ return has_composition_text_;
+}
+
+bool RenderWidgetHostViewAura::GetTextRange(ui::Range* range) {
+ range->set_start(selection_text_offset_);
+ range->set_end(selection_text_offset_ + selection_text_.length());
+ return true;
+}
+
+bool RenderWidgetHostViewAura::GetCompositionTextRange(ui::Range* range) {
+ // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewAura::GetSelectionRange(ui::Range* range) {
+ range->set_start(selection_range_.start());
+ range->set_end(selection_range_.end());
+ return true;
+}
+
+bool RenderWidgetHostViewAura::SetSelectionRange(const ui::Range& range) {
+ // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewAura::DeleteRange(const ui::Range& range) {
+ // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewAura::GetTextFromRange(
+ const ui::Range& range,
+ string16* text) {
+ ui::Range selection_text_range(selection_text_offset_,
+ selection_text_offset_ + selection_text_.length());
+
+ if (!selection_text_range.Contains(range)) {
+ text->clear();
+ return false;
+ }
+ if (selection_text_range.EqualsIgnoringDirection(range)) {
+ // Avoid calling substr whose performance is low.
+ *text = selection_text_;
+ } else {
+ *text = selection_text_.substr(
+ range.GetMin() - selection_text_offset_,
+ range.length());
+ }
+ return true;
+}
+
+void RenderWidgetHostViewAura::OnInputMethodChanged() {
+ if (!host_)
+ return;
+
+ if (GetInputMethod())
+ host_->SetInputMethodActive(GetInputMethod()->IsActive());
+
+ // TODO(suzhe): implement the newly added “locale” property of HTML DOM
+ // TextEvent.
+}
+
+bool RenderWidgetHostViewAura::ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) {
+ if (!host_)
+ return false;
+ host_->UpdateTextDirection(
+ direction == base::i18n::RIGHT_TO_LEFT ?
+ WebKit::WebTextDirectionRightToLeft :
+ WebKit::WebTextDirectionLeftToRight);
+ host_->NotifyTextDirection();
+ return true;
+}
+
+void RenderWidgetHostViewAura::ExtendSelectionAndDelete(
+ size_t before, size_t after) {
+ if (host_)
+ host_->ExtendSelectionAndDelete(before, after);
+}
+
+void RenderWidgetHostViewAura::EnsureCaretInRect(const gfx::Rect& rect) {
+ gfx::Rect intersected_rect(
+ gfx::IntersectRects(rect, window_->GetBoundsInScreen()));
+
+ if (intersected_rect.IsEmpty())
+ return;
+
+ host_->ScrollFocusedEditableNodeIntoRect(
+ ConvertRectFromScreen(intersected_rect));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, gfx::DisplayObserver implementation:
+
+void RenderWidgetHostViewAura::OnDisplayBoundsChanged(
+ const gfx::Display& display) {
+ gfx::Screen* screen = gfx::Screen::GetScreenFor(window_);
+ if (display.id() == screen->GetDisplayNearestWindow(window_).id()) {
+ UpdateScreenInfo(window_);
+ current_cursor_.SetDisplayInfo(display);
+ UpdateCursorIfOverSelf();
+ }
+}
+
+void RenderWidgetHostViewAura::OnDisplayAdded(
+ const gfx::Display& new_display) {
+}
+
+void RenderWidgetHostViewAura::OnDisplayRemoved(
+ const gfx::Display& old_display) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, aura::WindowDelegate implementation:
+
+gfx::Size RenderWidgetHostViewAura::GetMinimumSize() const {
+ return gfx::Size();
+}
+
+gfx::Size RenderWidgetHostViewAura::GetMaximumSize() const {
+ return gfx::Size();
+}
+
+void RenderWidgetHostViewAura::OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ // We care about this only in fullscreen mode, where there is no
+ // WebContentsViewAura. We are sized via SetSize() or SetBounds() by
+ // WebContentsViewAura in other cases.
+ if (is_fullscreen_)
+ SetSize(new_bounds.size());
+}
+
+gfx::NativeCursor RenderWidgetHostViewAura::GetCursor(const gfx::Point& point) {
+ if (mouse_locked_)
+ return ui::kCursorNone;
+ return current_cursor_.GetNativeCursor();
+}
+
+int RenderWidgetHostViewAura::GetNonClientComponent(
+ const gfx::Point& point) const {
+ return HTCLIENT;
+}
+
+bool RenderWidgetHostViewAura::ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) {
+ return true;
+}
+
+bool RenderWidgetHostViewAura::CanFocus() {
+ return popup_type_ == WebKit::WebPopupTypeNone;
+}
+
+void RenderWidgetHostViewAura::OnCaptureLost() {
+ host_->LostCapture();
+ if (touch_editing_client_)
+ touch_editing_client_->EndTouchEditing();
+}
+
+void RenderWidgetHostViewAura::OnPaint(gfx::Canvas* canvas) {
+ bool has_backing_store = !!host_->GetBackingStore(false);
+ if (has_backing_store) {
+ paint_canvas_ = canvas;
+ BackingStoreAura* backing_store = static_cast<BackingStoreAura*>(
+ host_->GetBackingStore(true));
+ paint_canvas_ = NULL;
+ backing_store->SkiaShowRect(gfx::Point(), canvas);
+
+ if (paint_observer_)
+ paint_observer_->OnPaintComplete();
+ ui::Compositor* compositor = GetCompositor();
+ if (compositor) {
+ compositor->SetLatencyInfo(software_latency_info_);
+ software_latency_info_.Clear();
+ }
+ } else {
+ // For non-opaque windows, we don't draw anything, since we depend on the
+ // canvas coming from the compositor to already be initialized as
+ // transparent.
+ if (window_->layer()->fills_bounds_opaquely())
+ canvas->DrawColor(SK_ColorWHITE);
+ }
+}
+
+void RenderWidgetHostViewAura::OnDeviceScaleFactorChanged(
+ float device_scale_factor) {
+ if (!host_)
+ return;
+
+ BackingStoreAura* backing_store = static_cast<BackingStoreAura*>(
+ host_->GetBackingStore(false));
+ if (backing_store) // NULL in hardware path.
+ backing_store->ScaleFactorChanged(device_scale_factor);
+
+ UpdateScreenInfo(window_);
+
+ const gfx::Display display = gfx::Screen::GetScreenFor(window_)->
+ GetDisplayNearestWindow(window_);
+ DCHECK_EQ(device_scale_factor, display.device_scale_factor());
+ current_cursor_.SetDisplayInfo(display);
+}
+
+void RenderWidgetHostViewAura::OnWindowDestroying() {
+#if defined(OS_WIN)
+ HWND parent = NULL;
+ // If the tab was hidden and it's closed, host_->is_hidden would have been
+ // reset to false in RenderWidgetHostImpl::RendererExited.
+ if (!window_->GetRootWindow() || host_->is_hidden()) {
+ parent = ui::GetHiddenWindow();
+ } else {
+ parent = window_->GetRootWindow()->GetAcceleratedWidget();
+ }
+ LPARAM lparam = reinterpret_cast<LPARAM>(this);
+ EnumChildWindows(parent, WindowDestroyingCallback, lparam);
+#endif
+}
+
+void RenderWidgetHostViewAura::OnWindowDestroyed() {
+ host_->ViewDestroyed();
+ delete this;
+}
+
+void RenderWidgetHostViewAura::OnWindowTargetVisibilityChanged(bool visible) {
+}
+
+bool RenderWidgetHostViewAura::HasHitTestMask() const {
+ return false;
+}
+
+void RenderWidgetHostViewAura::GetHitTestMask(gfx::Path* mask) const {
+}
+
+scoped_refptr<ui::Texture> RenderWidgetHostViewAura::CopyTexture() {
+ if (!host_->is_accelerated_compositing_active())
+ return scoped_refptr<ui::Texture>();
+
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ GLHelper* gl_helper = factory->GetGLHelper();
+ if (!gl_helper)
+ return scoped_refptr<ui::Texture>();
+
+ if (!current_surface_.get())
+ return scoped_refptr<ui::Texture>();
+
+ WebKit::WebGLId texture_id =
+ gl_helper->CopyTexture(current_surface_->PrepareTexture(),
+ current_surface_->size());
+ if (!texture_id)
+ return scoped_refptr<ui::Texture>();
+
+ return scoped_refptr<ui::Texture>(
+ factory->CreateOwnedTexture(
+ current_surface_->size(),
+ current_surface_->device_scale_factor(), texture_id));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, ui::EventHandler implementation:
+
+void RenderWidgetHostViewAura::OnKeyEvent(ui::KeyEvent* event) {
+ TRACE_EVENT0("input", "RenderWidgetHostViewAura::OnKeyEvent");
+ if (touch_editing_client_ && touch_editing_client_->HandleInputEvent(event))
+ return;
+
+ if (popup_child_host_view_ && popup_child_host_view_->NeedsInputGrab()) {
+ popup_child_host_view_->OnKeyEvent(event);
+ if (event->handled())
+ return;
+ }
+
+ // We need to handle the Escape key for Pepper Flash.
+ if (is_fullscreen_ && event->key_code() == ui::VKEY_ESCAPE) {
+ // Focus the window we were created from.
+ if (host_tracker_.get() && !host_tracker_->windows().empty()) {
+ aura::Window* host = *(host_tracker_->windows().begin());
+ aura::client::FocusClient* client = aura::client::GetFocusClient(host);
+ if (client) {
+ // Calling host->Focus() may delete |this|. We create a local observer
+ // for that. In that case we exit without further access to any members.
+ aura::WindowTracker tracker;
+ aura::Window* window = window_;
+ tracker.Add(window);
+ host->Focus();
+ if (!tracker.Contains(window)) {
+ event->SetHandled();
+ return;
+ }
+ }
+ }
+ if (!in_shutdown_) {
+ in_shutdown_ = true;
+ host_->Shutdown();
+ }
+ } else {
+ // Windows does not have a specific key code for AltGr and sends
+ // left-Control and right-Alt when the AltGr key is pressed. Also
+ // Windows translates AltGr modifier to Ctrl+Alt modifier. To be compatible
+ // with this behavior, we re-write keyboard event from AltGr to Alt + Ctrl
+ // key event here.
+ if (event->key_code() == ui::VKEY_ALTGR) {
+ // Synthesize Ctrl & Alt events.
+ NativeWebKeyboardEvent ctrl_webkit_event(
+ event->type(),
+ false,
+ ui::VKEY_CONTROL,
+ event->flags(),
+ ui::EventTimeForNow().InSecondsF());
+ host_->ForwardKeyboardEvent(ctrl_webkit_event);
+
+ NativeWebKeyboardEvent alt_webkit_event(
+ event->type(),
+ false,
+ ui::VKEY_MENU,
+ event->flags(),
+ ui::EventTimeForNow().InSecondsF());
+ host_->ForwardKeyboardEvent(alt_webkit_event);
+ event->SetHandled();
+ return;
+ }
+
+ // We don't have to communicate with an input method here.
+ if (!event->HasNativeEvent()) {
+ NativeWebKeyboardEvent webkit_event(
+ event->type(),
+ event->is_char(),
+ event->is_char() ? event->GetCharacter() : event->key_code(),
+ event->flags(),
+ ui::EventTimeForNow().InSecondsF());
+ host_->ForwardKeyboardEvent(webkit_event);
+ } else {
+ NativeWebKeyboardEvent webkit_event(event);
+ host_->ForwardKeyboardEvent(webkit_event);
+ }
+ }
+ event->SetHandled();
+}
+
+void RenderWidgetHostViewAura::OnMouseEvent(ui::MouseEvent* event) {
+ TRACE_EVENT0("input", "RenderWidgetHostViewAura::OnMouseEvent");
+
+ if (touch_editing_client_ && touch_editing_client_->HandleInputEvent(event))
+ return;
+
+ if (mouse_locked_) {
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(window_->GetRootWindow());
+ DCHECK(!cursor_client || !cursor_client->IsCursorVisible());
+
+ if (event->type() == ui::ET_MOUSEWHEEL) {
+ WebKit::WebMouseWheelEvent mouse_wheel_event =
+ MakeWebMouseWheelEvent(static_cast<ui::MouseWheelEvent*>(event));
+ if (mouse_wheel_event.deltaX != 0 || mouse_wheel_event.deltaY != 0)
+ host_->ForwardWheelEvent(mouse_wheel_event);
+ return;
+ }
+
+ WebKit::WebMouseEvent mouse_event = MakeWebMouseEvent(event);
+ gfx::Point center(gfx::Rect(window_->bounds().size()).CenterPoint());
+
+ bool is_move_to_center_event = (event->type() == ui::ET_MOUSE_MOVED ||
+ event->type() == ui::ET_MOUSE_DRAGGED) &&
+ mouse_event.x == center.x() && mouse_event.y == center.y();
+
+ ModifyEventMovementAndCoords(&mouse_event);
+
+ bool should_not_forward = is_move_to_center_event && synthetic_move_sent_;
+ if (should_not_forward) {
+ synthetic_move_sent_ = false;
+ } else {
+ // Check if the mouse has reached the border and needs to be centered.
+ if (ShouldMoveToCenter()) {
+ synthetic_move_sent_ = true;
+ window_->MoveCursorTo(center);
+ }
+ // Forward event to renderer.
+ if (CanRendererHandleEvent(event) &&
+ !(event->flags() & ui::EF_FROM_TOUCH))
+ host_->ForwardMouseEvent(mouse_event);
+ }
+ return;
+ }
+
+ // As the overscroll is handled during scroll events from the trackpad, the
+ // RWHVA window is transformed by the overscroll controller. This transform
+ // triggers a synthetic mouse-move event to be generated (by the aura
+ // RootWindow). But this event interferes with the overscroll gesture. So,
+ // ignore such synthetic mouse-move events if an overscroll gesture is in
+ // progress.
+ if (host_->overscroll_controller() &&
+ host_->overscroll_controller()->overscroll_mode() != OVERSCROLL_NONE &&
+ event->flags() & ui::EF_IS_SYNTHESIZED &&
+ (event->type() == ui::ET_MOUSE_ENTERED ||
+ event->type() == ui::ET_MOUSE_EXITED ||
+ event->type() == ui::ET_MOUSE_MOVED)) {
+ event->StopPropagation();
+ return;
+ }
+
+ if (event->type() == ui::ET_MOUSEWHEEL) {
+#if defined(OS_WIN)
+ // We get mouse wheel/scroll messages even if we are not in the foreground.
+ // So here we check if we have any owned popup windows in the foreground and
+ // dismiss them.
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (root_window) {
+ HWND parent = root_window->GetAcceleratedWidget();
+ HWND toplevel_hwnd = ::GetAncestor(parent, GA_ROOT);
+ EnumThreadWindows(GetCurrentThreadId(),
+ DismissOwnedPopups,
+ reinterpret_cast<LPARAM>(toplevel_hwnd));
+ }
+#endif
+ WebKit::WebMouseWheelEvent mouse_wheel_event =
+ MakeWebMouseWheelEvent(static_cast<ui::MouseWheelEvent*>(event));
+ if (mouse_wheel_event.deltaX != 0 || mouse_wheel_event.deltaY != 0)
+ host_->ForwardWheelEvent(mouse_wheel_event);
+ } else if (CanRendererHandleEvent(event) &&
+ !(event->flags() & ui::EF_FROM_TOUCH)) {
+ WebKit::WebMouseEvent mouse_event = MakeWebMouseEvent(event);
+ ModifyEventMovementAndCoords(&mouse_event);
+ host_->ForwardMouseEvent(mouse_event);
+ }
+
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ window_->SetCapture();
+ // Confirm existing composition text on mouse click events, to make sure
+ // the input caret won't be moved with an ongoing composition text.
+ FinishImeCompositionSession();
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ window_->ReleaseCapture();
+ break;
+ default:
+ break;
+ }
+
+ // Needed to propagate mouse event to native_tab_contents_view_aura.
+ // TODO(pkotwicz): Find a better way of doing this.
+ // In fullscreen mode which is typically used by flash, don't forward
+ // the mouse events to the parent. The renderer and the plugin process
+ // handle these events.
+ if (!is_fullscreen_ && window_->parent()->delegate() &&
+ !(event->flags() & ui::EF_FROM_TOUCH))
+ window_->parent()->delegate()->OnMouseEvent(event);
+
+ if (!IsXButtonUpEvent(event))
+ event->SetHandled();
+}
+
+void RenderWidgetHostViewAura::OnScrollEvent(ui::ScrollEvent* event) {
+ TRACE_EVENT0("input", "RenderWidgetHostViewAura::OnScrollEvent");
+ if (touch_editing_client_ && touch_editing_client_->HandleInputEvent(event))
+ return;
+
+ if (event->type() == ui::ET_SCROLL) {
+ if (event->finger_count() != 2)
+ return;
+ WebKit::WebGestureEvent gesture_event =
+ MakeWebGestureEventFlingCancel();
+ host_->ForwardGestureEvent(gesture_event);
+ WebKit::WebMouseWheelEvent mouse_wheel_event =
+ MakeWebMouseWheelEvent(event);
+ host_->ForwardWheelEvent(mouse_wheel_event);
+ RecordAction(UserMetricsAction("TrackpadScroll"));
+ } else if (event->type() == ui::ET_SCROLL_FLING_START ||
+ event->type() == ui::ET_SCROLL_FLING_CANCEL) {
+ WebKit::WebGestureEvent gesture_event =
+ MakeWebGestureEvent(event);
+ host_->ForwardGestureEvent(gesture_event);
+ if (event->type() == ui::ET_SCROLL_FLING_START)
+ RecordAction(UserMetricsAction("TrackpadScrollFling"));
+ }
+
+ event->SetHandled();
+}
+
+void RenderWidgetHostViewAura::OnTouchEvent(ui::TouchEvent* event) {
+ TRACE_EVENT0("input", "RenderWidgetHostViewAura::OnTouchEvent");
+ if (touch_editing_client_ && touch_editing_client_->HandleInputEvent(event))
+ return;
+
+ // Update the touch event first.
+ WebKit::WebTouchPoint* point = UpdateWebTouchEventFromUIEvent(*event,
+ &touch_event_);
+
+ // Forward the touch event only if a touch point was updated, and there's a
+ // touch-event handler in the page, and no other touch-event is in the queue.
+ // It is important to always consume the event if there is a touch-event
+ // handler in the page, or some touch-event is already in the queue, even if
+ // no point has been updated, to make sure that this event does not get
+ // processed by the gesture recognizer before the events in the queue.
+ if (host_->ShouldForwardTouchEvent())
+ event->StopPropagation();
+
+ if (point) {
+ if (host_->ShouldForwardTouchEvent())
+ host_->ForwardTouchEventWithLatencyInfo(touch_event_, *event->latency());
+ UpdateWebTouchEventAfterDispatch(&touch_event_, point);
+ }
+}
+
+void RenderWidgetHostViewAura::OnGestureEvent(ui::GestureEvent* event) {
+ TRACE_EVENT0("input", "RenderWidgetHostViewAura::OnGestureEvent");
+ // Pinch gestures are currently disabled by default. See crbug.com/128477.
+ if ((event->type() == ui::ET_GESTURE_PINCH_BEGIN ||
+ event->type() == ui::ET_GESTURE_PINCH_UPDATE ||
+ event->type() == ui::ET_GESTURE_PINCH_END) && !ShouldSendPinchGesture()) {
+ event->SetHandled();
+ return;
+ }
+
+ if (touch_editing_client_ && touch_editing_client_->HandleInputEvent(event))
+ return;
+
+ RenderViewHostDelegate* delegate = NULL;
+ if (popup_type_ == WebKit::WebPopupTypeNone && !is_fullscreen_)
+ delegate = RenderViewHost::From(host_)->GetDelegate();
+
+ if (delegate && event->type() == ui::ET_GESTURE_BEGIN &&
+ event->details().touch_points() == 1) {
+ delegate->HandleGestureBegin();
+ }
+
+ WebKit::WebGestureEvent gesture = MakeWebGestureEvent(event);
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
+ // Webkit does not stop a fling-scroll on tap-down. So explicitly send an
+ // event to stop any in-progress flings.
+ WebKit::WebGestureEvent fling_cancel = gesture;
+ fling_cancel.type = WebKit::WebInputEvent::GestureFlingCancel;
+ fling_cancel.sourceDevice = WebKit::WebGestureEvent::Touchscreen;
+ host_->ForwardGestureEvent(fling_cancel);
+ }
+
+ if (gesture.type != WebKit::WebInputEvent::Undefined) {
+ host_->ForwardGestureEventWithLatencyInfo(gesture, *event->latency());
+
+ if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
+ event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
+ event->type() == ui::ET_GESTURE_SCROLL_END) {
+ RecordAction(UserMetricsAction("TouchscreenScroll"));
+ } else if (event->type() == ui::ET_SCROLL_FLING_START) {
+ RecordAction(UserMetricsAction("TouchscreenScrollFling"));
+ }
+ }
+
+ if (delegate && event->type() == ui::ET_GESTURE_END &&
+ event->details().touch_points() == 1) {
+ delegate->HandleGestureEnd();
+ }
+
+ // If a gesture is not processed by the webpage, then WebKit processes it
+ // (e.g. generates synthetic mouse events).
+ event->SetHandled();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, aura::client::ActivationDelegate implementation:
+
+bool RenderWidgetHostViewAura::ShouldActivate() const {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return true;
+ const ui::Event* event = root_window->current_event();
+ if (!event)
+ return true;
+ return is_fullscreen_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura,
+// aura::client::ActivationChangeObserver implementation:
+
+void RenderWidgetHostViewAura::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ DCHECK(window_ == gained_active || window_ == lost_active);
+ if (window_ == gained_active) {
+ const ui::Event* event = window_->GetRootWindow()->current_event();
+ if (event && PointerEventActivates(*event))
+ host_->OnPointerEventActivate();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, aura::client::CursorClientObserver implementation:
+
+void RenderWidgetHostViewAura::OnCursorVisibilityChanged(bool is_visible) {
+ NotifyRendererOfCursorVisibilityState(is_visible);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, aura::client::FocusChangeObserver implementation:
+
+void RenderWidgetHostViewAura::OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) {
+ DCHECK(window_ == gained_focus || window_ == lost_focus);
+ if (window_ == gained_focus) {
+ // We need to honor input bypass if the associated tab is does not want
+ // input. This gives the current focused window a chance to be the text
+ // input client and handle events.
+ if (host_->ignore_input_events())
+ return;
+
+ host_->GotFocus();
+ host_->SetActive(true);
+
+ ui::InputMethod* input_method = GetInputMethod();
+ if (input_method) {
+ // Ask the system-wide IME to send all TextInputClient messages to |this|
+ // object.
+ input_method->SetFocusedTextInputClient(this);
+ host_->SetInputMethodActive(input_method->IsActive());
+
+ // Often the application can set focus to the view in response to a key
+ // down. However the following char event shouldn't be sent to the web
+ // page.
+ host_->SuppressNextCharEvents();
+ } else {
+ host_->SetInputMethodActive(false);
+ }
+ } else if (window_ == lost_focus) {
+ host_->SetActive(false);
+ host_->Blur();
+
+ DetachFromInputMethod();
+ host_->SetInputMethodActive(false);
+
+ if (touch_editing_client_)
+ touch_editing_client_->EndTouchEditing();
+
+ // If we lose the focus while fullscreen, close the window; Pepper Flash
+ // won't do it for us (unlike NPAPI Flash). However, we do not close the
+ // window if we lose the focus to a window on another display.
+ gfx::Screen* screen = gfx::Screen::GetScreenFor(window_);
+ bool focusing_other_display =
+ gained_focus && screen->GetNumDisplays() > 1 &&
+ (screen->GetDisplayNearestWindow(window_).id() !=
+ screen->GetDisplayNearestWindow(gained_focus).id());
+ if (is_fullscreen_ && !in_shutdown_ && !focusing_other_display) {
+ in_shutdown_ = true;
+ host_->Shutdown();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, aura::RootWindowObserver implementation:
+
+void RenderWidgetHostViewAura::OnRootWindowHostMoved(
+ const aura::RootWindow* root,
+ const gfx::Point& new_origin) {
+ UpdateScreenInfo(window_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, ui::CompositorObserver implementation:
+
+void RenderWidgetHostViewAura::OnCompositingDidCommit(
+ ui::Compositor* compositor) {
+ if (can_lock_compositor_ == NO_PENDING_COMMIT) {
+ can_lock_compositor_ = YES;
+ if (resize_lock_.get() && resize_lock_->GrabDeferredLock())
+ can_lock_compositor_ = YES_DID_LOCK;
+ }
+ RunOnCommitCallbacks();
+ if (resize_lock_ && resize_lock_->expected_size() == current_frame_size_) {
+ resize_lock_.reset();
+ host_->WasResized();
+ // We may have had a resize while we had the lock (e.g. if the lock expired,
+ // or if the UI still gave us some resizes), so make sure we grab a new lock
+ // if necessary.
+ MaybeCreateResizeLock();
+ }
+}
+
+void RenderWidgetHostViewAura::OnCompositingStarted(
+ ui::Compositor* compositor, base::TimeTicks start_time) {
+ last_draw_ended_ = start_time;
+}
+
+void RenderWidgetHostViewAura::OnCompositingEnded(
+ ui::Compositor* compositor) {
+ if (paint_observer_)
+ paint_observer_->OnCompositingComplete();
+}
+
+void RenderWidgetHostViewAura::OnCompositingAborted(
+ ui::Compositor* compositor) {
+}
+
+void RenderWidgetHostViewAura::OnCompositingLockStateChanged(
+ ui::Compositor* compositor) {
+ // A compositor lock that is part of a resize lock timed out. We
+ // should display a renderer frame.
+ if (!compositor->IsLocked() && can_lock_compositor_ == YES_DID_LOCK) {
+ can_lock_compositor_ = NO_PENDING_RENDERER_FRAME;
+ }
+}
+
+void RenderWidgetHostViewAura::OnUpdateVSyncParameters(
+ ui::Compositor* compositor,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ if (IsShowing() && !last_draw_ended_.is_null())
+ host_->UpdateVSyncParameters(last_draw_ended_, interval);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, BrowserAccessibilityDelegate implementation:
+
+void RenderWidgetHostViewAura::SetAccessibilityFocus(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetFocus(acc_obj_id);
+}
+
+void RenderWidgetHostViewAura::AccessibilityDoDefaultAction(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityDoDefaultAction(acc_obj_id);
+}
+
+void RenderWidgetHostViewAura::AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToMakeVisible(acc_obj_id, subfocus);
+}
+
+void RenderWidgetHostViewAura::AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToPoint(acc_obj_id, point);
+}
+
+void RenderWidgetHostViewAura::AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetTextSelection(
+ acc_obj_id, start_offset, end_offset);
+}
+
+gfx::Point RenderWidgetHostViewAura::GetLastTouchEventLocation() const {
+ // Only needed for Win 8 non-aura.
+ return gfx::Point();
+}
+
+void RenderWidgetHostViewAura::FatalAccessibilityTreeError() {
+ host_->FatalAccessibilityTreeError();
+ SetBrowserAccessibilityManager(NULL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, ImageTransportFactoryObserver implementation:
+
+void RenderWidgetHostViewAura::OnLostResources() {
+ current_surface_ = NULL;
+ UpdateExternalTexture();
+
+ // Make sure all ImageTransportClients are deleted now that the context those
+ // are using is becoming invalid. This sends pending ACKs and needs to happen
+ // after calling UpdateExternalTexture() which syncs with the impl thread.
+ RunOnCommitCallbacks();
+
+ DCHECK(!shared_surface_handle_.is_null());
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ factory->DestroySharedSurfaceHandle(shared_surface_handle_);
+ shared_surface_handle_ = factory->CreateSharedSurfaceHandle();
+ host_->CompositingSurfaceUpdated();
+ host_->ScheduleComposite();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewAura, private:
+
+RenderWidgetHostViewAura::~RenderWidgetHostViewAura() {
+ if (paint_observer_)
+ paint_observer_->OnViewDestroyed();
+ if (touch_editing_client_)
+ touch_editing_client_->OnViewDestroyed();
+ if (!shared_surface_handle_.is_null()) {
+ ImageTransportFactory* factory = ImageTransportFactory::GetInstance();
+ factory->DestroySharedSurfaceHandle(shared_surface_handle_);
+ factory->RemoveObserver(this);
+ }
+ window_observer_.reset();
+#if defined(OS_WIN)
+ transient_observer_.reset();
+#endif
+ if (window_->GetRootWindow())
+ window_->GetRootWindow()->RemoveRootWindowObserver(this);
+ UnlockMouse();
+ if (popup_type_ != WebKit::WebPopupTypeNone && popup_parent_host_view_) {
+ DCHECK(popup_parent_host_view_->popup_child_host_view_ == NULL ||
+ popup_parent_host_view_->popup_child_host_view_ == this);
+ popup_parent_host_view_->popup_child_host_view_ = NULL;
+ }
+ if (popup_child_host_view_) {
+ DCHECK(popup_child_host_view_->popup_parent_host_view_ == NULL ||
+ popup_child_host_view_->popup_parent_host_view_ == this);
+ popup_child_host_view_->popup_parent_host_view_ = NULL;
+ }
+ aura::client::SetTooltipText(window_, NULL);
+ gfx::Screen::GetScreenFor(window_)->RemoveObserver(this);
+
+ // This call is usually no-op since |this| object is already removed from the
+ // Aura root window and we don't have a way to get an input method object
+ // associated with the window, but just in case.
+ DetachFromInputMethod();
+}
+
+void RenderWidgetHostViewAura::UpdateCursorIfOverSelf() {
+ const gfx::Point screen_point =
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint();
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return;
+
+ gfx::Rect screen_rect = GetViewBounds();
+ gfx::Point local_point = screen_point;
+ local_point.Offset(-screen_rect.x(), -screen_rect.y());
+
+ if (root_window->GetEventHandlerForPoint(local_point) != window_)
+ return;
+
+ gfx::NativeCursor cursor = current_cursor_.GetNativeCursor();
+ // Do not show loading cursor when the cursor is currently hidden.
+ if (is_loading_ && cursor != ui::kCursorNone)
+ cursor = ui::kCursorPointer;
+
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window);
+ if (cursor_client) {
+ cursor_client->SetCursor(cursor);
+ }
+}
+
+ui::InputMethod* RenderWidgetHostViewAura::GetInputMethod() const {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ if (!root_window)
+ return NULL;
+ return root_window->GetProperty(aura::client::kRootWindowInputMethodKey);
+}
+
+bool RenderWidgetHostViewAura::NeedsInputGrab() {
+ return popup_type_ == WebKit::WebPopupTypeSelect;
+}
+
+void RenderWidgetHostViewAura::FinishImeCompositionSession() {
+ if (!has_composition_text_)
+ return;
+ if (host_)
+ host_->ImeConfirmComposition(string16(), ui::Range::InvalidRange(), false);
+ ImeCancelComposition();
+}
+
+void RenderWidgetHostViewAura::ModifyEventMovementAndCoords(
+ WebKit::WebMouseEvent* event) {
+ // If the mouse has just entered, we must report zero movementX/Y. Hence we
+ // reset any global_mouse_position set previously.
+ if (event->type == WebKit::WebInputEvent::MouseEnter ||
+ event->type == WebKit::WebInputEvent::MouseLeave)
+ global_mouse_position_.SetPoint(event->globalX, event->globalY);
+
+ // Movement is computed by taking the difference of the new cursor position
+ // and the previous. Under mouse lock the cursor will be warped back to the
+ // center so that we are not limited by clipping boundaries.
+ // We do not measure movement as the delta from cursor to center because
+ // we may receive more mouse movement events before our warp has taken
+ // effect.
+ event->movementX = event->globalX - global_mouse_position_.x();
+ event->movementY = event->globalY - global_mouse_position_.y();
+
+ global_mouse_position_.SetPoint(event->globalX, event->globalY);
+
+ // Under mouse lock, coordinates of mouse are locked to what they were when
+ // mouse lock was entered.
+ if (mouse_locked_) {
+ event->x = unlocked_mouse_position_.x();
+ event->y = unlocked_mouse_position_.y();
+ event->windowX = unlocked_mouse_position_.x();
+ event->windowY = unlocked_mouse_position_.y();
+ event->globalX = unlocked_global_mouse_position_.x();
+ event->globalY = unlocked_global_mouse_position_.y();
+ } else {
+ unlocked_mouse_position_.SetPoint(event->windowX, event->windowY);
+ unlocked_global_mouse_position_.SetPoint(event->globalX, event->globalY);
+ }
+}
+
+void RenderWidgetHostViewAura::NotifyRendererOfCursorVisibilityState(
+ bool is_visible) {
+ if (host_->is_hidden() ||
+ (cursor_visibility_state_in_renderer_ == VISIBLE && is_visible) ||
+ (cursor_visibility_state_in_renderer_ == NOT_VISIBLE && !is_visible))
+ return;
+
+ cursor_visibility_state_in_renderer_ = is_visible ? VISIBLE : NOT_VISIBLE;
+ host_->SendCursorVisibilityState(is_visible);
+}
+
+void RenderWidgetHostViewAura::SchedulePaintIfNotInClip(
+ const gfx::Rect& rect,
+ const gfx::Rect& clip) {
+ if (!clip.IsEmpty()) {
+ gfx::Rect to_paint = gfx::SubtractRects(rect, clip);
+ if (!to_paint.IsEmpty())
+ window_->SchedulePaintInRect(to_paint);
+ } else {
+ window_->SchedulePaintInRect(rect);
+ }
+}
+
+bool RenderWidgetHostViewAura::ShouldMoveToCenter() {
+ gfx::Rect rect = window_->bounds();
+ rect = ConvertRectToScreen(rect);
+ int border_x = rect.width() * kMouseLockBorderPercentage / 100;
+ int border_y = rect.height() * kMouseLockBorderPercentage / 100;
+
+ return global_mouse_position_.x() < rect.x() + border_x ||
+ global_mouse_position_.x() > rect.right() - border_x ||
+ global_mouse_position_.y() < rect.y() + border_y ||
+ global_mouse_position_.y() > rect.bottom() - border_y;
+}
+
+void RenderWidgetHostViewAura::RunOnCommitCallbacks() {
+ for (std::vector<base::Closure>::const_iterator
+ it = on_compositing_did_commit_callbacks_.begin();
+ it != on_compositing_did_commit_callbacks_.end(); ++it) {
+ it->Run();
+ }
+ on_compositing_did_commit_callbacks_.clear();
+}
+
+void RenderWidgetHostViewAura::AddOnCommitCallbackAndDisableLocks(
+ const base::Closure& callback) {
+ ui::Compositor* compositor = GetCompositor();
+ DCHECK(compositor);
+
+ if (!compositor->HasObserver(this))
+ compositor->AddObserver(this);
+
+ can_lock_compositor_ = NO_PENDING_COMMIT;
+ on_compositing_did_commit_callbacks_.push_back(callback);
+}
+
+void RenderWidgetHostViewAura::AddedToRootWindow() {
+ window_->GetRootWindow()->AddRootWindowObserver(this);
+ host_->ParentChanged(GetNativeViewId());
+ UpdateScreenInfo(window_);
+ if (popup_type_ != WebKit::WebPopupTypeNone)
+ event_filter_for_popup_exit_.reset(new EventFilterForPopupExit(this));
+
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(window_->GetRootWindow());
+ if (cursor_client) {
+ cursor_client->AddObserver(this);
+ NotifyRendererOfCursorVisibilityState(cursor_client->IsCursorVisible());
+ }
+ UpdateExternalTexture();
+}
+
+void RenderWidgetHostViewAura::RemovingFromRootWindow() {
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(window_->GetRootWindow());
+ if (cursor_client)
+ cursor_client->RemoveObserver(this);
+
+ event_filter_for_popup_exit_.reset();
+ window_->GetRootWindow()->RemoveRootWindowObserver(this);
+ host_->ParentChanged(0);
+ ui::Compositor* compositor = GetCompositor();
+ // We can't get notification for commits after this point, which would
+ // guarantee that the compositor isn't using an old texture any more, so
+ // instead we force the texture to NULL which synchronizes with the compositor
+ // thread, and makes it safe to run the callback.
+ window_->layer()->SetExternalTexture(NULL);
+ RunOnCommitCallbacks();
+ resize_lock_.reset();
+ host_->WasResized();
+ if (compositor && compositor->HasObserver(this))
+ compositor->RemoveObserver(this);
+}
+
+ui::Compositor* RenderWidgetHostViewAura::GetCompositor() const {
+ aura::RootWindow* root_window = window_->GetRootWindow();
+ return root_window ? root_window->compositor() : NULL;
+}
+
+void RenderWidgetHostViewAura::DetachFromInputMethod() {
+ ui::InputMethod* input_method = GetInputMethod();
+ if (input_method && input_method->GetTextInputClient() == this)
+ input_method->SetFocusedTextInputClient(NULL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostView, public:
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewAura(widget);
+}
+
+// static
+void RenderWidgetHostViewPort::GetDefaultScreenInfo(WebScreenInfo* results) {
+ GetScreenInfoForWindow(results, NULL);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_aura.h b/chromium/content/browser/renderer_host/render_widget_host_view_aura.h
new file mode 100644
index 00000000000..c4a16271634
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -0,0 +1,708 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_AURA_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_AURA_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/resources/texture_mailbox.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/aura/image_transport_factory.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/content_export.h"
+#include "content/common/gpu/client/gl_helper.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/client/activation_delegate.h"
+#include "ui/aura/client/cursor_client_observer.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/root_window_observer.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_observer.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/rect.h"
+#include "webkit/common/cursors/webcursor.h"
+
+namespace aura {
+class WindowTracker;
+}
+
+namespace cc {
+class CopyOutputResult;
+class DelegatedFrameData;
+}
+
+namespace gfx {
+class Canvas;
+class Display;
+}
+
+namespace ui {
+class CompositorLock;
+class InputMethod;
+class Texture;
+}
+
+namespace content {
+class MemoryHolder;
+class RenderWidgetHostImpl;
+class RenderWidgetHostView;
+
+// RenderWidgetHostView class hierarchy described in render_widget_host_view.h.
+class RenderWidgetHostViewAura
+ : public RenderWidgetHostViewBase,
+ public ui::CompositorObserver,
+ public ui::TextInputClient,
+ public gfx::DisplayObserver,
+ public aura::RootWindowObserver,
+ public aura::WindowDelegate,
+ public aura::client::ActivationDelegate,
+ public aura::client::ActivationChangeObserver,
+ public aura::client::FocusChangeObserver,
+ public aura::client::CursorClientObserver,
+ public ImageTransportFactoryObserver,
+ public BrowserAccessibilityDelegate,
+ public base::SupportsWeakPtr<RenderWidgetHostViewAura> {
+ public:
+ // Used to notify whenever the paint-content of the view changes.
+ class PaintObserver {
+ public:
+ PaintObserver() {}
+ virtual ~PaintObserver() {}
+
+ // This is called when painting of the page is completed.
+ virtual void OnPaintComplete() = 0;
+
+ // This is called when compositor painting of the page is completed.
+ virtual void OnCompositingComplete() = 0;
+
+ // This is called when the contents for compositor painting changes.
+ virtual void OnUpdateCompositorContent() = 0;
+
+ // This is called loading the page has completed.
+ virtual void OnPageLoadComplete() = 0;
+
+ // This is called when the view is destroyed, so that the observer can
+ // perform any necessary clean-up.
+ virtual void OnViewDestroyed() = 0;
+ };
+
+ // Displays and controls touch editing elements such as selection handles.
+ class TouchEditingClient {
+ public:
+ TouchEditingClient() {}
+
+ // Tells the client to start showing touch editing handles.
+ virtual void StartTouchEditing() = 0;
+
+ // Notifies the client that touch editing is no longer needed.
+ virtual void EndTouchEditing() = 0;
+
+ // Notifies the client that the selection bounds need to be updated.
+ virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor,
+ const gfx::Rect& focus) = 0;
+
+ // Notifies the client that the current text input type as changed.
+ virtual void OnTextInputTypeChanged(ui::TextInputType type) = 0;
+
+ // Notifies the client that an input event is about to be sent to the
+ // renderer. Returns true if the client wants to stop event propagation.
+ virtual bool HandleInputEvent(const ui::Event* event) = 0;
+
+ // Notifies the client that a gesture event ack was received.
+ virtual void GestureEventAck(int gesture_event_type) = 0;
+
+ // This is called when the view is destroyed, so that the client can
+ // perform any necessary clean-up.
+ virtual void OnViewDestroyed() = 0;
+
+ protected:
+ virtual ~TouchEditingClient() {}
+ };
+
+ void set_paint_observer(PaintObserver* observer) {
+ paint_observer_ = observer;
+ }
+
+ void set_touch_editing_client(TouchEditingClient* client) {
+ touch_editing_client_ = client;
+ }
+
+ // RenderWidgetHostView implementation.
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+#if defined(OS_WIN)
+ virtual gfx::NativeViewAccessible AccessibleObjectFromChildId(long child_id)
+ OVERRIDE;
+#endif
+
+ // Overridden from RenderWidgetHostViewPort:
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual bool CanSubscribeFrame() const OVERRIDE;
+ virtual void BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) OVERRIDE;
+ virtual void EndFrameSubscription() OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params_in_pixel,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params_in_pixel,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual void GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual SmoothScrollGesture* CreateSmoothScrollGesture(
+ bool scroll_down,
+ int pixels_to_scroll,
+ int mouse_event_x,
+ int mouse_event_y) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>&
+ params) OVERRIDE;
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) OVERRIDE;
+#if defined(OS_WIN)
+ virtual void SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) OVERRIDE;
+#endif
+
+ // Overridden from ui::TextInputClient:
+ virtual void SetCompositionText(
+ const ui::CompositionText& composition) OVERRIDE;
+ virtual void ConfirmCompositionText() OVERRIDE;
+ virtual void ClearCompositionText() OVERRIDE;
+ virtual void InsertText(const string16& text) OVERRIDE;
+ virtual void InsertChar(char16 ch, int flags) OVERRIDE;
+ virtual gfx::NativeWindow GetAttachedWindow() const OVERRIDE;
+ virtual ui::TextInputType GetTextInputType() const OVERRIDE;
+ virtual ui::TextInputMode GetTextInputMode() const OVERRIDE;
+ virtual bool CanComposeInline() const OVERRIDE;
+ virtual gfx::Rect GetCaretBounds() OVERRIDE;
+ virtual bool GetCompositionCharacterBounds(uint32 index,
+ gfx::Rect* rect) OVERRIDE;
+ virtual bool HasCompositionText() OVERRIDE;
+ virtual bool GetTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetCompositionTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetSelectionRange(ui::Range* range) OVERRIDE;
+ virtual bool SetSelectionRange(const ui::Range& range) OVERRIDE;
+ virtual bool DeleteRange(const ui::Range& range) OVERRIDE;
+ virtual bool GetTextFromRange(const ui::Range& range,
+ string16* text) OVERRIDE;
+ virtual void OnInputMethodChanged() OVERRIDE;
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE;
+ virtual void ExtendSelectionAndDelete(size_t before, size_t after) OVERRIDE;
+ virtual void EnsureCaretInRect(const gfx::Rect& rect) OVERRIDE;
+
+ // Overridden from gfx::DisplayObserver:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
+
+ // Overridden from aura::WindowDelegate:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE;
+ virtual gfx::Size GetMaximumSize() const OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE;
+ virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE;
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE;
+ virtual bool CanFocus() OVERRIDE;
+ virtual void OnCaptureLost() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE;
+ virtual void OnWindowDestroying() OVERRIDE;
+ virtual void OnWindowDestroyed() OVERRIDE;
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE;
+ virtual bool HasHitTestMask() const OVERRIDE;
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden from aura::client::ActivationDelegate:
+ virtual bool ShouldActivate() const OVERRIDE;
+
+ // Overridden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_activation,
+ aura::Window* lost_activation) OVERRIDE;
+
+ // Overridden from aura::client::CursorClientObserver:
+ virtual void OnCursorVisibilityChanged(bool is_visible) OVERRIDE;
+
+ // Overridden from aura::client::FocusChangeObserver:
+ virtual void OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) OVERRIDE;
+
+ // Overridden from aura::RootWindowObserver:
+ virtual void OnRootWindowHostMoved(const aura::RootWindow* root,
+ const gfx::Point& new_origin) OVERRIDE;
+
+ bool CanCopyToBitmap() const;
+
+#if defined(OS_WIN)
+ // Sets the cutout rects from constrained windows. These are rectangles that
+ // windowed NPAPI plugins shouldn't paint in. Overwrites any previous cutout
+ // rects.
+ void UpdateConstrainedWindowRects(const std::vector<gfx::Rect>& rects);
+#endif
+
+ protected:
+ friend class RenderWidgetHostView;
+
+ // Should construct only via RenderWidgetHostView::CreateViewForWidget.
+ explicit RenderWidgetHostViewAura(RenderWidgetHost* host);
+
+ RenderWidgetHostViewFrameSubscriber* frame_subscriber() const {
+ return frame_subscriber_.get();
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest, SetCompositionText);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest, TouchEventState);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest, TouchEventSyncAsync);
+
+ class WindowObserver;
+ friend class WindowObserver;
+#if defined(OS_WIN)
+ class TransientWindowObserver;
+ friend class TransientWindowObserver;
+#endif
+
+ // Overridden from ui::CompositorObserver:
+ virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE;
+ virtual void OnCompositingStarted(ui::Compositor* compositor,
+ base::TimeTicks start_time) OVERRIDE;
+ virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE;
+ virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE;
+ virtual void OnCompositingLockStateChanged(
+ ui::Compositor* compositor) OVERRIDE;
+ virtual void OnUpdateVSyncParameters(ui::Compositor* compositor,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE;
+
+ // Overridden from ImageTransportFactoryObserver:
+ virtual void OnLostResources() OVERRIDE;
+
+ // Overridden from BrowserAccessibilityDelegate:
+ virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) OVERRIDE;
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) OVERRIDE;
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) OVERRIDE;
+ virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE;
+ virtual void FatalAccessibilityTreeError() OVERRIDE;
+
+ virtual ~RenderWidgetHostViewAura();
+
+ void UpdateCursorIfOverSelf();
+ bool ShouldSkipFrame(gfx::Size size_in_dip) const;
+
+ // Lazily grab a resize lock if the aura window size doesn't match the current
+ // frame size, to give time to the renderer.
+ void MaybeCreateResizeLock();
+
+ // Checks if the resize lock can be released because we received an new frame.
+ void CheckResizeLock();
+
+ void UpdateExternalTexture();
+ ui::InputMethod* GetInputMethod() const;
+
+ // Returns whether the widget needs an input grab to work properly.
+ bool NeedsInputGrab();
+
+ // Confirm existing composition text in the webpage and ask the input method
+ // to cancel its ongoing composition session.
+ void FinishImeCompositionSession();
+
+ // This method computes movementX/Y and keeps track of mouse location for
+ // mouse lock on all mouse move events.
+ void ModifyEventMovementAndCoords(WebKit::WebMouseEvent* event);
+
+ // Sends an IPC to the renderer process to communicate whether or not
+ // the mouse cursor is visible anywhere on the screen.
+ void NotifyRendererOfCursorVisibilityState(bool is_visible);
+
+ // If |clip| is non-empty and and doesn't contain |rect| or |clip| is empty
+ // SchedulePaint() is invoked for |rect|.
+ void SchedulePaintIfNotInClip(const gfx::Rect& rect, const gfx::Rect& clip);
+
+ // Helper method to determine if, in mouse locked mode, the cursor should be
+ // moved to center.
+ bool ShouldMoveToCenter();
+
+ // Run all on compositing commit callbacks.
+ void RunOnCommitCallbacks();
+
+ // Add on compositing commit callback.
+ void AddOnCommitCallbackAndDisableLocks(const base::Closure& callback);
+
+ // Called after |window_| is parented to a RootWindow.
+ void AddedToRootWindow();
+
+ // Called prior to removing |window_| from a RootWindow.
+ void RemovingFromRootWindow();
+
+ // Called after commit for the last reference to the texture going away
+ // after it was released as the frontbuffer.
+ void SetSurfaceNotInUseByCompositor(scoped_refptr<ui::Texture>);
+
+ // Called after async thumbnailer task completes. Scales and crops the result
+ // of the copy.
+ static void CopyFromCompositingSurfaceHasResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+ static void PrepareTextureCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+ static void PrepareBitmapCopyOutputResult(
+ const gfx::Size& dst_size_in_pixel,
+ const base::Callback<void(bool, const SkBitmap&)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+ static void CopyFromCompositingSurfaceHasResultForVideo(
+ base::WeakPtr<RenderWidgetHostViewAura> rwhva,
+ scoped_refptr<media::VideoFrame> video_frame,
+ const base::Callback<void(bool)>& callback,
+ scoped_ptr<cc::CopyOutputResult> result);
+
+ ui::Compositor* GetCompositor() const;
+
+ // Detaches |this| from the input method object.
+ void DetachFromInputMethod();
+
+ // Dismisses a Web Popup on mouse press outside the popup and its parent.
+ void ApplyEventFilterForPopupExit(ui::MouseEvent* event);
+
+ // Converts |rect| from window coordinate to screen coordinate.
+ gfx::Rect ConvertRectToScreen(const gfx::Rect& rect);
+
+ // Converts |rect| from screen coordinate to window coordinate.
+ gfx::Rect ConvertRectFromScreen(const gfx::Rect& rect);
+
+ typedef base::Callback<void(bool, const scoped_refptr<ui::Texture>&)>
+ BufferPresentedCallback;
+
+ // The common entry point for buffer updates from renderer
+ // and GPU process.
+ void BuffersSwapped(const gfx::Size& surface_size,
+ const gfx::Rect& damage_rect,
+ float surface_scale_factor,
+ const std::string& mailbox_name,
+ const ui::LatencyInfo& latency_info,
+ const BufferPresentedCallback& ack_callback);
+
+ bool SwapBuffersPrepare(const gfx::Rect& surface_rect,
+ float surface_scale_factor,
+ const gfx::Rect& damage_rect,
+ const std::string& mailbox_name,
+ const BufferPresentedCallback& ack_callback);
+
+ void SwapBuffersCompleted(
+ const BufferPresentedCallback& ack_callback,
+ const scoped_refptr<ui::Texture>& texture_to_return);
+
+ void SwapDelegatedFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::DelegatedFrameData> frame_data,
+ float frame_device_scale_factor,
+ const ui::LatencyInfo& latency_info);
+ void SendDelegatedFrameAck(uint32 output_surface_id);
+
+ void SwapSoftwareFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::SoftwareFrameData> frame_data,
+ float frame_device_scale_factor,
+ const ui::LatencyInfo& latency_info);
+ void SendSoftwareFrameAck(uint32 output_surface_id,
+ unsigned software_frame_id);
+
+ void DidReceiveFrameFromRenderer();
+
+ BrowserAccessibilityManager* GetOrCreateBrowserAccessibilityManager();
+
+#if defined(OS_WIN)
+ // Sets the cutout rects from transient windows. These are rectangles that
+ // windowed NPAPI plugins shouldn't paint in. Overwrites any previous cutout
+ // rects.
+ void UpdateTransientRects(const std::vector<gfx::Rect>& rects);
+
+ // Updates the total list of cutout rects, which is the union of transient
+ // windows and constrained windows.
+ void UpdateCutoutRects();
+#endif
+
+ // The model object.
+ RenderWidgetHostImpl* host_;
+
+ aura::Window* window_;
+
+ scoped_ptr<WindowObserver> window_observer_;
+
+ // Are we in the process of closing? Tracked so fullscreen views can avoid
+ // sending a second shutdown request to the host when they lose the focus
+ // after requesting shutdown for another reason (e.g. Escape key).
+ bool in_shutdown_;
+
+ // Is this a fullscreen view?
+ bool is_fullscreen_;
+
+ // Our parent host view, if this is a popup. NULL otherwise.
+ RenderWidgetHostViewAura* popup_parent_host_view_;
+
+ // Our child popup host. NULL if we do not have a child popup.
+ RenderWidgetHostViewAura* popup_child_host_view_;
+
+ class EventFilterForPopupExit;
+ friend class EventFilterForPopupExit;
+ scoped_ptr<ui::EventHandler> event_filter_for_popup_exit_;
+
+ // True when content is being loaded. Used to show an hourglass cursor.
+ bool is_loading_;
+
+ // The cursor for the page. This is passed up from the renderer.
+ WebCursor current_cursor_;
+
+ // The touch-event. Its touch-points are updated as necessary. A new
+ // touch-point is added from an ET_TOUCH_PRESSED event, and a touch-point is
+ // removed from the list on an ET_TOUCH_RELEASED event.
+ WebKit::WebTouchEvent touch_event_;
+
+ // The current text input type.
+ ui::TextInputType text_input_type_;
+ bool can_compose_inline_;
+
+ // Rectangles for the selection anchor and focus.
+ gfx::Rect selection_anchor_rect_;
+ gfx::Rect selection_focus_rect_;
+
+ // The current composition character bounds.
+ std::vector<gfx::Rect> composition_character_bounds_;
+
+ // Indicates if there is onging composition text.
+ bool has_composition_text_;
+
+ // Current tooltip text.
+ string16 tooltip_;
+
+ std::vector<base::Closure> on_compositing_did_commit_callbacks_;
+
+ // The current frontbuffer texture.
+ scoped_refptr<ui::Texture> current_surface_;
+
+ // This holds the current software framebuffer.
+ scoped_refptr<MemoryHolder> framebuffer_holder_;
+
+ // The damage in the previously presented buffer.
+ SkRegion previous_damage_;
+
+ // Pending damage from previous frames that we skipped.
+ SkRegion skipped_damage_;
+
+ // The size of the last frame that was swapped (even if we skipped it).
+ // Used to determine when the skipped_damage_ needs to be reset due to
+ // size changes between front- and backbuffer.
+ gfx::Size last_swapped_surface_size_;
+ float last_swapped_surface_scale_factor_;
+
+ gfx::GLSurfaceHandle shared_surface_handle_;
+
+ // If non-NULL we're in OnPaint() and this is the supplied canvas.
+ gfx::Canvas* paint_canvas_;
+
+ // Used to record the last position of the mouse.
+ // While the mouse is locked, they store the last known position just as mouse
+ // lock was entered.
+ // Relative to the upper-left corner of the view.
+ gfx::Point unlocked_mouse_position_;
+ // Relative to the upper-left corner of the screen.
+ gfx::Point unlocked_global_mouse_position_;
+ // Last cursor position relative to screen. Used to compute movementX/Y.
+ gfx::Point global_mouse_position_;
+ // In mouse locked mode, we syntheticaly move the mouse cursor to the center
+ // of the window when it reaches the window borders to avoid it going outside.
+ // This flag is used to differentiate between these synthetic mouse move
+ // events vs. normal mouse move events.
+ bool synthetic_move_sent_;
+
+ // Signals that the accelerated compositing has been turned on or off.
+ // This is used to signal to turn off the external texture as soon as the
+ // software backing store is updated.
+ bool accelerated_compositing_state_changed_;
+
+ // Used to prevent further resizes while a resize is pending.
+ class ResizeLock;
+
+ // This lock is the one waiting for a frame of the right size to come back
+ // from the renderer/GPU process. It is set from the moment the aura window
+ // got resized, to the moment we committed the renderer frame of the same
+ // size. It keeps track of the size we expect from the renderer, and locks the
+ // compositor, as well as the UI for a short time to give a chance to the
+ // renderer of producing a frame of the right size.
+ scoped_ptr<ResizeLock> resize_lock_;
+
+ // Keeps track of the current frame size.
+ gfx::Size current_frame_size_;
+
+ // This lock is for waiting for a front surface to become available to draw.
+ scoped_refptr<ui::CompositorLock> released_front_lock_;
+
+ // Used to track the state of the window we're created from. Only used when
+ // created fullscreen.
+ scoped_ptr<aura::WindowTracker> host_tracker_;
+
+ enum CanLockCompositorState {
+ YES,
+ // We locked, so at some point we'll need to kick a frame.
+ YES_DID_LOCK,
+ // No. A lock timed out, we need to kick a new frame before locking again.
+ NO_PENDING_RENDERER_FRAME,
+ // No. We've got a frame, but it hasn't been committed.
+ NO_PENDING_COMMIT,
+ };
+ CanLockCompositorState can_lock_compositor_;
+
+ // Used to track the last cursor visibility update that was sent to the
+ // renderer via NotifyRendererOfCursorVisibilityState().
+ enum CursorVisibilityState {
+ UNKNOWN,
+ VISIBLE,
+ NOT_VISIBLE,
+ };
+ CursorVisibilityState cursor_visibility_state_in_renderer_;
+
+ // An observer to notify that the paint content of the view has changed. The
+ // observer is not owned by the view, and must remove itself as an oberver
+ // when it is being destroyed.
+ PaintObserver* paint_observer_;
+
+#if defined(OS_WIN)
+ scoped_ptr<TransientWindowObserver> transient_observer_;
+
+ // The list of rectangles from transient and constrained windows over this
+ // view. Windowed NPAPI plugins shouldn't draw over them.
+ std::vector<gfx::Rect> transient_rects_;
+ std::vector<gfx::Rect> constrained_rects_;
+
+ typedef std::map<HWND, WebPluginGeometry> PluginWindowMoves;
+ // Contains information about each windowed plugin's clip and cutout rects (
+ // from the renderer). This is needed because when the transient windoiws
+ // over this view changes, we need this information in order to create a new
+ // region for the HWND.
+ PluginWindowMoves plugin_window_moves_;
+#endif
+
+ base::TimeTicks last_draw_ended_;
+
+ // Subscriber that listens to frame presentation events.
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> frame_subscriber_;
+
+ // YUV readback pipeline.
+ scoped_ptr<content::ReadbackYUVInterface>
+ yuv_readback_pipeline_;
+
+ TouchEditingClient* touch_editing_client_;
+
+ ui::LatencyInfo software_latency_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAura);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_AURA_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
new file mode 100644
index 00000000000..03f7d5c591f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -0,0 +1,587 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_aura.h"
+
+#include "base/basictypes.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_helper.h"
+#include "ui/aura/test/test_cursor_client.h"
+#include "ui/aura/test/test_screen.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/ui_base_types.h"
+
+namespace content {
+namespace {
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+// Simple observer that keeps track of changes to a window for tests.
+class TestWindowObserver : public aura::WindowObserver {
+ public:
+ explicit TestWindowObserver(aura::Window* window_to_observe)
+ : window_(window_to_observe) {
+ window_->AddObserver(this);
+ }
+ virtual ~TestWindowObserver() {
+ if (window_)
+ window_->RemoveObserver(this);
+ }
+
+ bool destroyed() const { return destroyed_; }
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {
+ CHECK_EQ(window, window_);
+ destroyed_ = true;
+ window_ = NULL;
+ }
+
+ private:
+ // Window that we're observing, or NULL if it's been destroyed.
+ aura::Window* window_;
+
+ // Was |window_| destroyed?
+ bool destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowObserver);
+};
+
+class RenderWidgetHostViewAuraTest : public testing::Test {
+ public:
+ RenderWidgetHostViewAuraTest() {}
+
+ virtual void SetUp() {
+ aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
+ aura_test_helper_->SetUp();
+
+ browser_context_.reset(new TestBrowserContext);
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(browser_context_.get());
+
+ sink_ = &process_host->sink();
+
+ parent_host_ = new RenderWidgetHostImpl(
+ &delegate_, process_host, MSG_ROUTING_NONE);
+ parent_view_ = static_cast<RenderWidgetHostViewAura*>(
+ RenderWidgetHostView::CreateViewForWidget(parent_host_));
+ parent_view_->InitAsChild(NULL);
+ parent_view_->GetNativeView()->SetDefaultParentByRootWindow(
+ aura_test_helper_->root_window(), gfx::Rect());
+
+ widget_host_ = new RenderWidgetHostImpl(
+ &delegate_, process_host, MSG_ROUTING_NONE);
+ widget_host_->Init();
+ view_ = static_cast<RenderWidgetHostViewAura*>(
+ RenderWidgetHostView::CreateViewForWidget(widget_host_));
+ }
+
+ virtual void TearDown() {
+ sink_ = NULL;
+ if (view_)
+ view_->Destroy();
+ delete widget_host_;
+
+ parent_view_->Destroy();
+ delete parent_host_;
+
+ browser_context_.reset();
+ aura_test_helper_->TearDown();
+
+ message_loop_.DeleteSoon(FROM_HERE, browser_context_.release());
+ message_loop_.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
+ scoped_ptr<BrowserContext> browser_context_;
+ MockRenderWidgetHostDelegate delegate_;
+
+ // Tests should set these to NULL if they've already triggered their
+ // destruction.
+ RenderWidgetHostImpl* parent_host_;
+ RenderWidgetHostViewAura* parent_view_;
+
+ // Tests should set these to NULL if they've already triggered their
+ // destruction.
+ RenderWidgetHostImpl* widget_host_;
+ RenderWidgetHostViewAura* view_;
+
+ IPC::TestSink* sink_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraTest);
+};
+
+// A layout manager that always resizes a child to the root window size.
+class FullscreenLayoutManager : public aura::LayoutManager {
+ public:
+ explicit FullscreenLayoutManager(aura::RootWindow* owner)
+ : owner_(owner) {}
+ virtual ~FullscreenLayoutManager() {}
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE {
+ aura::Window::Windows::const_iterator i;
+ for (i = owner_->children().begin(); i != owner_->children().end(); ++i) {
+ (*i)->SetBounds(gfx::Rect());
+ }
+ }
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
+ child->SetBounds(gfx::Rect());
+ }
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {
+ }
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {
+ }
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE {
+ }
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE {
+ SetChildBoundsDirect(child, gfx::Rect(owner_->bounds().size()));
+ }
+
+ private:
+ aura::RootWindow* owner_;
+ DISALLOW_COPY_AND_ASSIGN(FullscreenLayoutManager);
+};
+
+} // namespace
+
+// Checks that a fullscreen view has the correct show-state and receives the
+// focus.
+TEST_F(RenderWidgetHostViewAuraTest, FocusFullscreen) {
+ view_->InitAsFullscreen(parent_view_);
+ aura::Window* window = view_->GetNativeView();
+ ASSERT_TRUE(window != NULL);
+ EXPECT_EQ(ui::SHOW_STATE_FULLSCREEN,
+ window->GetProperty(aura::client::kShowStateKey));
+
+ // Check that we requested and received the focus.
+ EXPECT_TRUE(window->HasFocus());
+
+ // Check that we'll also say it's okay to activate the window when there's an
+ // ActivationClient defined.
+ EXPECT_TRUE(view_->ShouldActivate());
+}
+
+// Checks that a fullscreen view is destroyed when it loses the focus.
+TEST_F(RenderWidgetHostViewAuraTest, DestroyFullscreenOnBlur) {
+ view_->InitAsFullscreen(parent_view_);
+ aura::Window* window = view_->GetNativeView();
+ ASSERT_TRUE(window != NULL);
+ ASSERT_TRUE(window->HasFocus());
+
+ // After we create and focus another window, the RWHVA's window should be
+ // destroyed.
+ TestWindowObserver observer(window);
+ aura::test::TestWindowDelegate delegate;
+ scoped_ptr<aura::Window> sibling(new aura::Window(&delegate));
+ sibling->Init(ui::LAYER_TEXTURED);
+ sibling->Show();
+ window->parent()->AddChild(sibling.get());
+ sibling->Focus();
+ ASSERT_TRUE(sibling->HasFocus());
+ ASSERT_TRUE(observer.destroyed());
+
+ widget_host_ = NULL;
+ view_ = NULL;
+}
+
+// Checks that IME-composition-event state is maintained correctly.
+TEST_F(RenderWidgetHostViewAuraTest, SetCompositionText) {
+ view_->InitAsChild(NULL);
+ view_->Show();
+
+ ui::CompositionText composition_text;
+ composition_text.text = ASCIIToUTF16("|a|b");
+
+ // Focused segment
+ composition_text.underlines.push_back(
+ ui::CompositionUnderline(0, 3, 0xff000000, true));
+
+ // Non-focused segment
+ composition_text.underlines.push_back(
+ ui::CompositionUnderline(3, 4, 0xff000000, false));
+
+ const ui::CompositionUnderlines& underlines = composition_text.underlines;
+
+ // Caret is at the end. (This emulates Japanese MSIME 2007 and later)
+ composition_text.selection = ui::Range(4);
+
+ sink_->ClearMessages();
+ view_->SetCompositionText(composition_text);
+ EXPECT_TRUE(view_->has_composition_text_);
+ {
+ const IPC::Message* msg =
+ sink_->GetFirstMessageMatching(ViewMsg_ImeSetComposition::ID);
+ ASSERT_TRUE(msg != NULL);
+
+ ViewMsg_ImeSetComposition::Param params;
+ ViewMsg_ImeSetComposition::Read(msg, &params);
+ // composition text
+ EXPECT_EQ(composition_text.text, params.a);
+ // underlines
+ ASSERT_EQ(underlines.size(), params.b.size());
+ for (size_t i = 0; i < underlines.size(); ++i) {
+ EXPECT_EQ(underlines[i].start_offset, params.b[i].startOffset);
+ EXPECT_EQ(underlines[i].end_offset, params.b[i].endOffset);
+ EXPECT_EQ(underlines[i].color, params.b[i].color);
+ EXPECT_EQ(underlines[i].thick, params.b[i].thick);
+ }
+ // highlighted range
+ EXPECT_EQ(4, params.c) << "Should be the same to the caret pos";
+ EXPECT_EQ(4, params.d) << "Should be the same to the caret pos";
+ }
+
+ view_->ImeCancelComposition();
+ EXPECT_FALSE(view_->has_composition_text_);
+}
+
+// Checks that touch-event state is maintained correctly.
+TEST_F(RenderWidgetHostViewAuraTest, TouchEventState) {
+ view_->InitAsChild(NULL);
+ view_->Show();
+
+ // Start with no touch-event handler in the renderer.
+ widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false));
+ EXPECT_FALSE(widget_host_->ShouldForwardTouchEvent());
+
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ gfx::Point(30, 30),
+ 0,
+ ui::EventTimeForNow());
+ ui::TouchEvent move(ui::ET_TOUCH_MOVED,
+ gfx::Point(20, 20),
+ 0,
+ ui::EventTimeForNow());
+ ui::TouchEvent release(ui::ET_TOUCH_RELEASED,
+ gfx::Point(20, 20),
+ 0,
+ ui::EventTimeForNow());
+
+ view_->OnTouchEvent(&press);
+ EXPECT_FALSE(press.handled());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchStart, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StatePressed,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&move);
+ EXPECT_FALSE(move.handled());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchMove, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StateMoved,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&release);
+ EXPECT_FALSE(release.handled());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchEnd, view_->touch_event_.type);
+ EXPECT_EQ(0U, view_->touch_event_.touchesLength);
+
+ // Now install some touch-event handlers and do the same steps. The touch
+ // events should now be consumed. However, the touch-event state should be
+ // updated as before.
+ widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_TRUE(widget_host_->ShouldForwardTouchEvent());
+
+ view_->OnTouchEvent(&press);
+ EXPECT_TRUE(press.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchStart, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StatePressed,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&move);
+ EXPECT_TRUE(move.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchMove, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StateMoved,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&release);
+ EXPECT_TRUE(release.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchEnd, view_->touch_event_.type);
+ EXPECT_EQ(0U, view_->touch_event_.touchesLength);
+
+ // Now start a touch event, and remove the event-handlers before the release.
+ view_->OnTouchEvent(&press);
+ EXPECT_TRUE(press.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchStart, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StatePressed,
+ view_->touch_event_.touches[0].state);
+
+ widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false));
+ EXPECT_FALSE(widget_host_->ShouldForwardTouchEvent());
+
+ ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(20, 20), 0,
+ base::Time::NowFromSystemTime() - base::Time());
+ view_->OnTouchEvent(&move2);
+ EXPECT_FALSE(move2.handled());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchMove, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StateMoved,
+ view_->touch_event_.touches[0].state);
+
+ ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), 0,
+ base::Time::NowFromSystemTime() - base::Time());
+ view_->OnTouchEvent(&release2);
+ EXPECT_FALSE(release2.handled());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchEnd, view_->touch_event_.type);
+ EXPECT_EQ(0U, view_->touch_event_.touchesLength);
+}
+
+// Checks that touch-events are queued properly when there is a touch-event
+// handler on the page.
+TEST_F(RenderWidgetHostViewAuraTest, TouchEventSyncAsync) {
+ view_->InitAsChild(NULL);
+ view_->Show();
+
+ widget_host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
+ EXPECT_TRUE(widget_host_->ShouldForwardTouchEvent());
+
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ gfx::Point(30, 30),
+ 0,
+ ui::EventTimeForNow());
+ ui::TouchEvent move(ui::ET_TOUCH_MOVED,
+ gfx::Point(20, 20),
+ 0,
+ ui::EventTimeForNow());
+ ui::TouchEvent release(ui::ET_TOUCH_RELEASED,
+ gfx::Point(20, 20),
+ 0,
+ ui::EventTimeForNow());
+
+ view_->OnTouchEvent(&press);
+ EXPECT_TRUE(press.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchStart, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StatePressed,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&move);
+ EXPECT_TRUE(move.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchMove, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StateMoved,
+ view_->touch_event_.touches[0].state);
+
+ // Send the same move event. Since the point hasn't moved, it won't affect the
+ // queue. However, the view should consume the event.
+ view_->OnTouchEvent(&move);
+ EXPECT_TRUE(move.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchMove, view_->touch_event_.type);
+ EXPECT_EQ(1U, view_->touch_event_.touchesLength);
+ EXPECT_EQ(WebKit::WebTouchPoint::StateMoved,
+ view_->touch_event_.touches[0].state);
+
+ view_->OnTouchEvent(&release);
+ EXPECT_TRUE(release.stopped_propagation());
+ EXPECT_EQ(WebKit::WebInputEvent::TouchEnd, view_->touch_event_.type);
+ EXPECT_EQ(0U, view_->touch_event_.touchesLength);
+}
+
+TEST_F(RenderWidgetHostViewAuraTest, PhysicalBackingSizeWithScale) {
+ view_->InitAsChild(NULL);
+ view_->GetNativeView()->SetDefaultParentByRootWindow(
+ parent_view_->GetNativeView()->GetRootWindow(), gfx::Rect());
+ sink_->ClearMessages();
+ view_->SetSize(gfx::Size(100, 100));
+ EXPECT_EQ("100x100", view_->GetPhysicalBackingSize().ToString());
+ EXPECT_EQ(1u, sink_->message_count());
+ EXPECT_EQ(ViewMsg_Resize::ID, sink_->GetMessageAt(0)->type());
+ {
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(ViewMsg_Resize::ID, msg->type());
+ ViewMsg_Resize::Param params;
+ ViewMsg_Resize::Read(msg, &params);
+ EXPECT_EQ("100x100", params.a.new_size.ToString()); // dip size
+ EXPECT_EQ("100x100",
+ params.a.physical_backing_size.ToString()); // backing size
+ }
+
+ widget_host_->ResetSizeAndRepaintPendingFlags();
+ sink_->ClearMessages();
+
+ aura_test_helper_->test_screen()->SetDeviceScaleFactor(2.0f);
+ EXPECT_EQ("200x200", view_->GetPhysicalBackingSize().ToString());
+ // Extra ScreenInfoChanged message for |parent_view_|.
+ EXPECT_EQ(1u, sink_->message_count());
+ {
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(ViewMsg_Resize::ID, msg->type());
+ ViewMsg_Resize::Param params;
+ ViewMsg_Resize::Read(msg, &params);
+ EXPECT_EQ(2.0f, params.a.screen_info.deviceScaleFactor);
+ EXPECT_EQ("100x100", params.a.new_size.ToString()); // dip size
+ EXPECT_EQ("200x200",
+ params.a.physical_backing_size.ToString()); // backing size
+ }
+
+ widget_host_->ResetSizeAndRepaintPendingFlags();
+ sink_->ClearMessages();
+
+ aura_test_helper_->test_screen()->SetDeviceScaleFactor(1.0f);
+ // Extra ScreenInfoChanged message for |parent_view_|.
+ EXPECT_EQ(1u, sink_->message_count());
+ EXPECT_EQ("100x100", view_->GetPhysicalBackingSize().ToString());
+ {
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(ViewMsg_Resize::ID, msg->type());
+ ViewMsg_Resize::Param params;
+ ViewMsg_Resize::Read(msg, &params);
+ EXPECT_EQ(1.0f, params.a.screen_info.deviceScaleFactor);
+ EXPECT_EQ("100x100", params.a.new_size.ToString()); // dip size
+ EXPECT_EQ("100x100",
+ params.a.physical_backing_size.ToString()); // backing size
+ }
+}
+
+// Checks that InputMsg_CursorVisibilityChange IPC messages are dispatched
+// to the renderer at the correct times.
+TEST_F(RenderWidgetHostViewAuraTest, CursorVisibilityChange) {
+ view_->InitAsChild(NULL);
+ view_->GetNativeView()->SetDefaultParentByRootWindow(
+ parent_view_->GetNativeView()->GetRootWindow(), gfx::Rect());
+ view_->SetSize(gfx::Size(100, 100));
+
+ aura::test::TestCursorClient cursor_client(
+ parent_view_->GetNativeView()->GetRootWindow());
+
+ cursor_client.AddObserver(view_);
+
+ // Expect a message the first time the cursor is shown.
+ view_->WasShown();
+ sink_->ClearMessages();
+ cursor_client.ShowCursor();
+ EXPECT_EQ(1u, sink_->message_count());
+ EXPECT_TRUE(sink_->GetUniqueMessageMatching(
+ InputMsg_CursorVisibilityChange::ID));
+
+ // No message expected if the renderer already knows the cursor is visible.
+ sink_->ClearMessages();
+ cursor_client.ShowCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+
+ // Hiding the cursor should send a message.
+ sink_->ClearMessages();
+ cursor_client.HideCursor();
+ EXPECT_EQ(1u, sink_->message_count());
+ EXPECT_TRUE(sink_->GetUniqueMessageMatching(
+ InputMsg_CursorVisibilityChange::ID));
+
+ // No message expected if the renderer already knows the cursor is invisible.
+ sink_->ClearMessages();
+ cursor_client.HideCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+
+ // No messages should be sent while the view is invisible.
+ view_->WasHidden();
+ sink_->ClearMessages();
+ cursor_client.ShowCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+ cursor_client.HideCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+
+ // Show the view. Since the cursor was invisible when the view was hidden,
+ // no message should be sent.
+ sink_->ClearMessages();
+ view_->WasShown();
+ EXPECT_FALSE(sink_->GetUniqueMessageMatching(
+ InputMsg_CursorVisibilityChange::ID));
+
+ // No message expected if the renderer already knows the cursor is invisible.
+ sink_->ClearMessages();
+ cursor_client.HideCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+
+ // Showing the cursor should send a message.
+ sink_->ClearMessages();
+ cursor_client.ShowCursor();
+ EXPECT_EQ(1u, sink_->message_count());
+ EXPECT_TRUE(sink_->GetUniqueMessageMatching(
+ InputMsg_CursorVisibilityChange::ID));
+
+ // No messages should be sent while the view is invisible.
+ view_->WasHidden();
+ sink_->ClearMessages();
+ cursor_client.HideCursor();
+ EXPECT_EQ(0u, sink_->message_count());
+
+ // Show the view. Since the cursor was visible when the view was hidden,
+ // a message is expected to be sent.
+ sink_->ClearMessages();
+ view_->WasShown();
+ EXPECT_TRUE(sink_->GetUniqueMessageMatching(
+ InputMsg_CursorVisibilityChange::ID));
+
+ cursor_client.RemoveObserver(view_);
+}
+
+// Resizing in fullscreen mode should send the up-to-date screen info.
+TEST_F(RenderWidgetHostViewAuraTest, FullscreenResize) {
+ aura::RootWindow* root_window = aura_test_helper_->root_window();
+ root_window->SetLayoutManager(new FullscreenLayoutManager(root_window));
+ view_->InitAsFullscreen(parent_view_);
+ view_->WasShown();
+ widget_host_->ResetSizeAndRepaintPendingFlags();
+ sink_->ClearMessages();
+
+ // Call WasResized to flush the old screen info.
+ view_->GetRenderWidgetHost()->WasResized();
+ {
+ // 0 is CreatingNew message.
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(ViewMsg_Resize::ID, msg->type());
+ ViewMsg_Resize::Param params;
+ ViewMsg_Resize::Read(msg, &params);
+ EXPECT_EQ("0,0 800x600",
+ gfx::Rect(params.a.screen_info.availableRect).ToString());
+ EXPECT_EQ("800x600", params.a.new_size.ToString());
+ }
+
+ widget_host_->ResetSizeAndRepaintPendingFlags();
+ sink_->ClearMessages();
+
+ // Make sure the corrent screen size is set along in the resize
+ // request when the screen size has changed.
+ aura_test_helper_->test_screen()->SetUIScale(0.5);
+ EXPECT_EQ(1u, sink_->message_count());
+ {
+ const IPC::Message* msg = sink_->GetMessageAt(0);
+ EXPECT_EQ(ViewMsg_Resize::ID, msg->type());
+ ViewMsg_Resize::Param params;
+ ViewMsg_Resize::Read(msg, &params);
+ EXPECT_EQ("0,0 1600x1200",
+ gfx::Rect(params.a.screen_info.availableRect).ToString());
+ EXPECT_EQ("1600x1200", params.a.new_size.ToString());
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_base.cc b/chromium/content/browser/renderer_host/render_widget_host_view_base.cc
new file mode 100644
index 00000000000..d6b1e789e2a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -0,0 +1,564 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_base.h"
+
+#include "base/logging.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/renderer_host/basic_mouse_wheel_smooth_scroll_gesture.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/size_f.h"
+
+#if defined(OS_WIN)
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/win/wrapped_window_proc.h"
+#include "content/browser/plugin_process_host.h"
+#include "content/browser/plugin_service_impl.h"
+#include "content/common/plugin_constants_win.h"
+#include "content/common/webplugin_geometry.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/win/dpi.h"
+#include "ui/base/win/hwnd_util.h"
+#include "ui/gfx/gdi_util.h"
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "content/browser/renderer_host/gtk_window_utils.h"
+#endif
+
+namespace content {
+
+// static
+RenderWidgetHostViewPort* RenderWidgetHostViewPort::FromRWHV(
+ RenderWidgetHostView* rwhv) {
+ return static_cast<RenderWidgetHostViewPort*>(rwhv);
+}
+
+// static
+RenderWidgetHostViewPort* RenderWidgetHostViewPort::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return FromRWHV(RenderWidgetHostView::CreateViewForWidget(widget));
+}
+
+#if defined(OS_WIN)
+
+namespace {
+
+// |window| is the plugin HWND, created and destroyed in the plugin process.
+// |parent| is the parent HWND, created and destroyed on the browser UI thread.
+void NotifyPluginProcessHostHelper(HWND window, HWND parent, int tries) {
+ // How long to wait between each try.
+ static const int kTryDelayMs = 200;
+
+ DWORD plugin_process_id;
+ bool found_starting_plugin_process = false;
+ GetWindowThreadProcessId(window, &plugin_process_id);
+ for (PluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (!iter.GetData().handle) {
+ found_starting_plugin_process = true;
+ continue;
+ }
+ if (base::GetProcId(iter.GetData().handle) == plugin_process_id) {
+ iter->AddWindow(parent);
+ return;
+ }
+ }
+
+ if (found_starting_plugin_process) {
+ // A plugin process has started but we don't have its handle yet. Since
+ // it's most likely the one for this plugin, try a few more times after a
+ // delay.
+ if (tries > 0) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NotifyPluginProcessHostHelper, window, parent, tries - 1),
+ base::TimeDelta::FromMilliseconds(kTryDelayMs));
+ return;
+ }
+ }
+
+ // The plugin process might have died in the time to execute the task, don't
+ // leak the HWND.
+ PostMessage(parent, WM_CLOSE, 0, 0);
+}
+
+// The plugin wrapper window which lives in the browser process has this proc
+// as its window procedure. We only handle the WM_PARENTNOTIFY message sent by
+// windowed plugins for mouse input. This is forwarded off to the wrappers
+// parent which is typically the RVH window which turns on user gesture.
+LRESULT CALLBACK PluginWrapperWindowProc(HWND window, unsigned int message,
+ WPARAM wparam, LPARAM lparam) {
+ if (message == WM_PARENTNOTIFY) {
+ switch (LOWORD(wparam)) {
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ ::SendMessage(GetParent(window), message, wparam, lparam);
+ return 0;
+ default:
+ break;
+ }
+ }
+ return ::DefWindowProc(window, message, wparam, lparam);
+}
+
+bool IsPluginWrapperWindow(HWND window) {
+ return ui::GetClassNameW(window) ==
+ string16(kWrapperNativeWindowClassName);
+}
+
+// Create an intermediate window between the given HWND and its parent.
+HWND ReparentWindow(HWND window, HWND parent) {
+ static ATOM atom = 0;
+ static HMODULE instance = NULL;
+ if (!atom) {
+ WNDCLASSEX window_class;
+ base::win::InitializeWindowClass(
+ kWrapperNativeWindowClassName,
+ &base::win::WrappedWindowProc<PluginWrapperWindowProc>,
+ CS_DBLCLKS,
+ 0,
+ 0,
+ NULL,
+ // xxx reinterpret_cast<HBRUSH>(COLOR_WINDOW+1),
+ reinterpret_cast<HBRUSH>(COLOR_GRAYTEXT+1),
+ NULL,
+ NULL,
+ NULL,
+ &window_class);
+ instance = window_class.hInstance;
+ atom = RegisterClassEx(&window_class);
+ }
+ DCHECK(atom);
+
+ HWND new_parent = CreateWindowEx(
+ WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR,
+ MAKEINTATOM(atom), 0,
+ WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ 0, 0, 0, 0, parent, 0, instance, 0);
+ ui::CheckWindowCreated(new_parent);
+ ::SetParent(window, new_parent);
+ // How many times we try to find a PluginProcessHost whose process matches
+ // the HWND.
+ static const int kMaxTries = 5;
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&NotifyPluginProcessHostHelper, window, new_parent,
+ kMaxTries));
+ return new_parent;
+}
+
+BOOL CALLBACK PaintEnumChildProc(HWND hwnd, LPARAM lparam) {
+ if (!PluginServiceImpl::GetInstance()->IsPluginWindow(hwnd))
+ return TRUE;
+
+ gfx::Rect* rect = reinterpret_cast<gfx::Rect*>(lparam);
+ gfx::Rect rect_in_pixels = ui::win::DIPToScreenRect(*rect);
+ static UINT msg = RegisterWindowMessage(kPaintMessageName);
+ WPARAM wparam = MAKEWPARAM(rect_in_pixels.x(), rect_in_pixels.y());
+ lparam = MAKELPARAM(rect_in_pixels.width(), rect_in_pixels.height());
+
+ // SendMessage gets the message across much quicker than PostMessage, since it
+ // doesn't get queued. When the plugin thread calls PeekMessage or other
+ // Win32 APIs, sent messages are dispatched automatically.
+ SendNotifyMessage(hwnd, msg, wparam, lparam);
+
+ return TRUE;
+}
+
+// Windows callback for OnDestroy to detach the plugin windows.
+BOOL CALLBACK DetachPluginWindowsCallbackInternal(HWND window, LPARAM param) {
+ RenderWidgetHostViewBase::DetachPluginWindowsCallback(window);
+ return TRUE;
+}
+
+} // namespace
+
+// static
+void RenderWidgetHostViewBase::DetachPluginWindowsCallback(HWND window) {
+ if (PluginServiceImpl::GetInstance()->IsPluginWindow(window) &&
+ !IsHungAppWindow(window)) {
+ ::ShowWindow(window, SW_HIDE);
+ SetParent(window, NULL);
+ }
+}
+
+// static
+void RenderWidgetHostViewBase::MovePluginWindowsHelper(
+ HWND parent,
+ const std::vector<WebPluginGeometry>& moves) {
+ if (moves.empty())
+ return;
+
+ bool oop_plugins =
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessPlugins);
+
+ HDWP defer_window_pos_info =
+ ::BeginDeferWindowPos(static_cast<int>(moves.size()));
+
+ if (!defer_window_pos_info) {
+ NOTREACHED();
+ return;
+ }
+
+#if defined(USE_AURA)
+ std::vector<RECT> invalidate_rects;
+#endif
+
+ for (size_t i = 0; i < moves.size(); ++i) {
+ unsigned long flags = 0;
+ const WebPluginGeometry& move = moves[i];
+ HWND window = move.window;
+
+ // As the plugin parent window which lives on the browser UI thread is
+ // destroyed asynchronously, it is possible that we have a stale window
+ // sent in by the renderer for moving around.
+ // Note: get the parent before checking if the window is valid, to avoid a
+ // race condition where the window is destroyed after the check but before
+ // the GetParent call.
+ HWND cur_parent = ::GetParent(window);
+ if (!::IsWindow(window))
+ continue;
+
+ if (!PluginServiceImpl::GetInstance()->IsPluginWindow(window)) {
+ // The renderer should only be trying to move plugin windows. However,
+ // this may happen as a result of a race condition (i.e. even after the
+ // check right above), so we ignore it.
+ continue;
+ }
+
+ if (oop_plugins) {
+ if (cur_parent == GetDesktopWindow()) {
+ // The plugin window hasn't been parented yet, add an intermediate
+ // window that lives on this thread to speed up scrolling. Note this
+ // only works with out of process plugins since we depend on
+ // PluginProcessHost to destroy the intermediate HWNDs.
+ cur_parent = ReparentWindow(window, parent);
+ ::ShowWindow(window, SW_SHOW); // Window was created hidden.
+ } else if (!IsPluginWrapperWindow(cur_parent)) {
+ continue; // Race if plugin process is shutting down.
+ }
+
+ // We move the intermediate parent window which doesn't result in cross-
+ // process synchronous Windows messages.
+ window = cur_parent;
+ } else {
+ if (cur_parent == GetDesktopWindow())
+ SetParent(window, parent);
+ }
+
+ if (move.visible)
+ flags |= SWP_SHOWWINDOW;
+ else
+ flags |= SWP_HIDEWINDOW;
+
+#if defined(USE_AURA)
+ if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) {
+ // Without this flag, Windows repaints the parent area uncovered by this
+ // move. However when software compositing is used the clipping region is
+ // ignored. Since in Aura the browser chrome could be under the plugin, if
+ // if Windows tries to paint it synchronously inside EndDeferWindowsPos
+ // then it won't have the data and it will flash white. So instead we
+ // manually redraw the plugin.
+ // Why not do this for native Windows? Not sure if there are any
+ // performance issues with this.
+ flags |= SWP_NOREDRAW;
+ }
+#endif
+
+ if (move.rects_valid) {
+ gfx::Rect clip_rect_in_pixel = ui::win::DIPToScreenRect(move.clip_rect);
+ HRGN hrgn = ::CreateRectRgn(clip_rect_in_pixel.x(),
+ clip_rect_in_pixel.y(),
+ clip_rect_in_pixel.right(),
+ clip_rect_in_pixel.bottom());
+ gfx::SubtractRectanglesFromRegion(hrgn, move.cutout_rects);
+
+ // Note: System will own the hrgn after we call SetWindowRgn,
+ // so we don't need to call DeleteObject(hrgn)
+ ::SetWindowRgn(window, hrgn, !move.clip_rect.IsEmpty());
+
+#if defined(USE_AURA)
+ // When using the software compositor, if the clipping rectangle is empty
+ // then DeferWindowPos won't redraw the newly uncovered area under the
+ // plugin.
+ if (clip_rect_in_pixel.IsEmpty() &&
+ !GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) {
+ RECT r;
+ GetClientRect(window, &r);
+ MapWindowPoints(window, parent, reinterpret_cast<POINT*>(&r), 2);
+ invalidate_rects.push_back(r);
+ }
+#endif
+ } else {
+ flags |= SWP_NOMOVE;
+ flags |= SWP_NOSIZE;
+ }
+
+ gfx::Rect window_rect_in_pixel =
+ ui::win::DIPToScreenRect(move.window_rect);
+ defer_window_pos_info = ::DeferWindowPos(defer_window_pos_info,
+ window, NULL,
+ window_rect_in_pixel.x(),
+ window_rect_in_pixel.y(),
+ window_rect_in_pixel.width(),
+ window_rect_in_pixel.height(),
+ flags);
+
+ if (!defer_window_pos_info) {
+ DCHECK(false) << "DeferWindowPos failed, so all plugin moves ignored.";
+ return;
+ }
+ }
+
+ ::EndDeferWindowPos(defer_window_pos_info);
+
+#if defined(USE_AURA)
+ if (GpuDataManagerImpl::GetInstance()->CanUseGpuBrowserCompositor()) {
+ for (size_t i = 0; i < moves.size(); ++i) {
+ const WebPluginGeometry& move = moves[i];
+ RECT r;
+ GetWindowRect(move.window, &r);
+ gfx::Rect gr(r);
+ PaintEnumChildProc(move.window, reinterpret_cast<LPARAM>(&gr));
+ }
+ } else {
+ for (size_t i = 0; i < invalidate_rects.size(); ++i) {
+ ::RedrawWindow(
+ parent, &invalidate_rects[i], NULL,
+ // These flags are from WebPluginDelegateImpl::NativeWndProc.
+ RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME | RDW_UPDATENOW);
+ }
+ }
+#endif
+}
+
+// static
+void RenderWidgetHostViewBase::PaintPluginWindowsHelper(
+ HWND parent, const gfx::Rect& damaged_screen_rect) {
+ LPARAM lparam = reinterpret_cast<LPARAM>(&damaged_screen_rect);
+ EnumChildWindows(parent, PaintEnumChildProc, lparam);
+}
+
+// static
+void RenderWidgetHostViewBase::DetachPluginsHelper(HWND parent) {
+ // When a tab is closed all its child plugin windows are destroyed
+ // automatically. This happens before plugins get any notification that its
+ // instances are tearing down.
+ //
+ // Plugins like Quicktime assume that their windows will remain valid as long
+ // as they have plugin instances active. Quicktime crashes in this case
+ // because its windowing code cleans up an internal data structure that the
+ // handler for NPP_DestroyStream relies on.
+ //
+ // The fix is to detach plugin windows from web contents when it is going
+ // away. This will prevent the plugin windows from getting destroyed
+ // automatically. The detached plugin windows will get cleaned up in proper
+ // sequence as part of the usual cleanup when the plugin instance goes away.
+ EnumChildWindows(parent, DetachPluginWindowsCallbackInternal, NULL);
+}
+
+#endif // OS_WIN
+
+RenderWidgetHostViewBase::RenderWidgetHostViewBase()
+ : popup_type_(WebKit::WebPopupTypeNone),
+ mouse_locked_(false),
+ showing_context_menu_(false),
+ selection_text_offset_(0),
+ selection_range_(ui::Range::InvalidRange()),
+ current_device_scale_factor_(0),
+ renderer_frame_number_(0) {
+}
+
+RenderWidgetHostViewBase::~RenderWidgetHostViewBase() {
+ DCHECK(!mouse_locked_);
+}
+
+bool RenderWidgetHostViewBase::OnMessageReceived(const IPC::Message& msg){
+ return false;
+}
+
+void RenderWidgetHostViewBase::SetBackground(const SkBitmap& background) {
+ background_ = background;
+}
+
+const SkBitmap& RenderWidgetHostViewBase::GetBackground() {
+ return background_;
+}
+
+gfx::Size RenderWidgetHostViewBase::GetPhysicalBackingSize() const {
+ gfx::NativeView view = GetNativeView();
+ gfx::Display display =
+ gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
+ return gfx::ToCeiledSize(gfx::ScaleSize(GetViewBounds().size(),
+ display.device_scale_factor()));
+}
+
+float RenderWidgetHostViewBase::GetOverdrawBottomHeight() const {
+ return 0.f;
+}
+
+void RenderWidgetHostViewBase::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ selection_text_ = text;
+ selection_text_offset_ = offset;
+ selection_range_.set_start(range.start());
+ selection_range_.set_end(range.end());
+}
+
+bool RenderWidgetHostViewBase::IsShowingContextMenu() const {
+ return showing_context_menu_;
+}
+
+void RenderWidgetHostViewBase::SetShowingContextMenu(bool showing) {
+ DCHECK_NE(showing_context_menu_, showing);
+ showing_context_menu_ = showing;
+}
+
+string16 RenderWidgetHostViewBase::GetSelectedText() const {
+ if (!selection_range_.IsValid())
+ return string16();
+ return selection_text_.substr(
+ selection_range_.GetMin() - selection_text_offset_,
+ selection_range_.length());
+}
+
+bool RenderWidgetHostViewBase::IsMouseLocked() {
+ return mouse_locked_;
+}
+
+void RenderWidgetHostViewBase::UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ // Most implementations don't need to do anything here.
+}
+
+InputEventAckState RenderWidgetHostViewBase::FilterInputEvent(
+ const WebKit::WebInputEvent& input_event) {
+ // By default, input events are simply forwarded to the renderer.
+ return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
+}
+
+void RenderWidgetHostViewBase::GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) {}
+
+void RenderWidgetHostViewBase::SetPopupType(WebKit::WebPopupType popup_type) {
+ popup_type_ = popup_type;
+}
+
+WebKit::WebPopupType RenderWidgetHostViewBase::GetPopupType() {
+ return popup_type_;
+}
+
+BrowserAccessibilityManager*
+ RenderWidgetHostViewBase::GetBrowserAccessibilityManager() const {
+ return browser_accessibility_manager_.get();
+}
+
+void RenderWidgetHostViewBase::SetBrowserAccessibilityManager(
+ BrowserAccessibilityManager* manager) {
+ browser_accessibility_manager_.reset(manager);
+}
+
+void RenderWidgetHostViewBase::UpdateScreenInfo(gfx::NativeView view) {
+ RenderWidgetHostImpl* impl = NULL;
+ if (GetRenderWidgetHost())
+ impl = RenderWidgetHostImpl::From(GetRenderWidgetHost());
+
+ if (impl)
+ impl->SendScreenRects();
+
+ if (HasDisplayPropertyChanged(view) && impl)
+ impl->NotifyScreenInfoChanged();
+}
+
+bool RenderWidgetHostViewBase::HasDisplayPropertyChanged(gfx::NativeView view) {
+ gfx::Display display =
+ gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
+ if (current_display_area_ == display.work_area() &&
+ current_device_scale_factor_ == display.device_scale_factor()) {
+ return false;
+ }
+ current_display_area_ = display.work_area();
+ current_device_scale_factor_ = display.device_scale_factor();
+ return true;
+}
+
+SmoothScrollGesture* RenderWidgetHostViewBase::CreateSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll, int mouse_event_x,
+ int mouse_event_y) {
+ return new BasicMouseWheelSmoothScrollGesture(scroll_down, pixels_to_scroll,
+ mouse_event_x, mouse_event_y);
+}
+
+void RenderWidgetHostViewBase::ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) {
+}
+
+// Platform implementation should override this method to allow frame
+// subscription. Frame subscriber is set to RenderProcessHost, which is
+// platform independent. It should be set to the specific presenter on each
+// platform.
+bool RenderWidgetHostViewBase::CanSubscribeFrame() const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+// Base implementation for this method sets the subscriber to RenderProcessHost,
+// which is platform independent. Note: Implementation only support subscribing
+// to accelerated composited frames.
+void RenderWidgetHostViewBase::BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ RenderWidgetHostImpl* impl = NULL;
+ if (GetRenderWidgetHost())
+ impl = RenderWidgetHostImpl::From(GetRenderWidgetHost());
+ if (!impl)
+ return;
+ RenderProcessHostImpl* render_process_host =
+ static_cast<RenderProcessHostImpl*>(impl->GetProcess());
+ render_process_host->BeginFrameSubscription(impl->GetRoutingID(),
+ subscriber.Pass());
+}
+
+void RenderWidgetHostViewBase::EndFrameSubscription() {
+ RenderWidgetHostImpl* impl = NULL;
+ if (GetRenderWidgetHost())
+ impl = RenderWidgetHostImpl::From(GetRenderWidgetHost());
+ if (!impl)
+ return;
+ RenderProcessHostImpl* render_process_host =
+ static_cast<RenderProcessHostImpl*>(impl->GetProcess());
+ render_process_host->EndFrameSubscription(impl->GetRoutingID());
+}
+
+void RenderWidgetHostViewBase::OnOverscrolled(
+ gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity) {
+}
+
+uint32 RenderWidgetHostViewBase::RendererFrameNumber() {
+ return renderer_frame_number_;
+}
+
+void RenderWidgetHostViewBase::DidReceiveRendererFrame() {
+ ++renderer_frame_number_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_base.h b/chromium/content/browser/renderer_host/render_widget_host_view_base.h
new file mode 100644
index 00000000000..68fcaffaea7
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_base.h
@@ -0,0 +1,166 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_BASE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_BASE_H_
+
+#if defined(OS_MACOSX)
+#include <OpenGL/OpenGL.h>
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#endif
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/callback_forward.h"
+#include "content/common/content_export.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+class RenderWidgetHostImpl;
+
+// Basic implementation shared by concrete RenderWidgetHostView
+// subclasses.
+//
+// Note that nothing should use this class, except concrete subclasses
+// that are deriving from it, and code that is written specifically to
+// use one of these concrete subclasses (i.e. platform-specific code).
+//
+// To enable embedders that add ports, everything else in content/
+// should use the RenderWidgetHostViewPort interface.
+//
+// RenderWidgetHostView class hierarchy described in render_widget_host_view.h.
+class CONTENT_EXPORT RenderWidgetHostViewBase
+ : public RenderWidgetHostViewPort {
+ public:
+ virtual ~RenderWidgetHostViewBase();
+
+ // RenderWidgetHostViewPort implementation.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+ virtual const SkBitmap& GetBackground() OVERRIDE;
+ virtual gfx::Size GetPhysicalBackingSize() const OVERRIDE;
+ virtual float GetOverdrawBottomHeight() const OVERRIDE;
+ virtual bool IsShowingContextMenu() const OVERRIDE;
+ virtual void SetShowingContextMenu(bool showing_menu) OVERRIDE;
+ virtual string16 GetSelectedText() const OVERRIDE;
+ virtual bool IsMouseLocked() OVERRIDE;
+ virtual void UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+ virtual InputEventAckState FilterInputEvent(
+ const WebKit::WebInputEvent& input_event) OVERRIDE;
+ virtual void GestureEventAck(int gesture_event_type,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void SetPopupType(WebKit::WebPopupType popup_type) OVERRIDE;
+ virtual WebKit::WebPopupType GetPopupType() OVERRIDE;
+ virtual BrowserAccessibilityManager*
+ GetBrowserAccessibilityManager() const OVERRIDE;
+ virtual void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual SmoothScrollGesture* CreateSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll, int mouse_event_x,
+ int mouse_event_y) OVERRIDE;
+ virtual bool CanSubscribeFrame() const OVERRIDE;
+ virtual void BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) OVERRIDE;
+ virtual void EndFrameSubscription() OVERRIDE;
+ virtual void OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) OVERRIDE {}
+ virtual void OnOverscrolled(gfx::Vector2dF accumulated_overscroll,
+ gfx::Vector2dF current_fling_velocity) OVERRIDE;
+ virtual uint32 RendererFrameNumber() OVERRIDE;
+ virtual void DidReceiveRendererFrame() OVERRIDE;
+
+ void SetBrowserAccessibilityManager(BrowserAccessibilityManager* manager);
+
+ // Notification that a resize or move session ended on the native widget.
+ void UpdateScreenInfo(gfx::NativeView view);
+
+ // Tells if the display property (work area/scale factor) has
+ // changed since the last time.
+ bool HasDisplayPropertyChanged(gfx::NativeView view);
+
+#if defined(OS_WIN)
+ // The callback that DetachPluginsHelper calls for each child window. Call
+ // this directly if you want to do custom filtering on plugin windows first.
+ static void DetachPluginWindowsCallback(HWND window);
+#endif
+
+ protected:
+ // Interface class only, do not construct.
+ RenderWidgetHostViewBase();
+
+#if defined(OS_WIN)
+ // Shared implementation of MovePluginWindows for use by win and aura/wina.
+ static void MovePluginWindowsHelper(
+ HWND parent,
+ const std::vector<WebPluginGeometry>& moves);
+
+ static void PaintPluginWindowsHelper(
+ HWND parent,
+ const gfx::Rect& damaged_screen_rect);
+
+ // Needs to be called before the HWND backing the view goes away to avoid
+ // crashes in Windowed plugins.
+ static void DetachPluginsHelper(HWND parent);
+#endif
+
+ // Whether this view is a popup and what kind of popup it is (select,
+ // autofill...).
+ WebKit::WebPopupType popup_type_;
+
+ // A custom background to paint behind the web content. This will be tiled
+ // horizontally. Can be null, in which case we fall back to painting white.
+ SkBitmap background_;
+
+ // While the mouse is locked, the cursor is hidden from the user. Mouse events
+ // are still generated. However, the position they report is the last known
+ // mouse position just as mouse lock was entered; the movement they report
+ // indicates what the change in position of the mouse would be had it not been
+ // locked.
+ bool mouse_locked_;
+
+ // Whether we are showing a context menu.
+ bool showing_context_menu_;
+
+ // A buffer containing the text inside and around the current selection range.
+ string16 selection_text_;
+
+ // The offset of the text stored in |selection_text_| relative to the start of
+ // the web page.
+ size_t selection_text_offset_;
+
+ // The current selection range relative to the start of the web page.
+ ui::Range selection_range_;
+
+protected:
+ // The scale factor of the display the renderer is currently on.
+ float current_device_scale_factor_;
+
+ private:
+ // Manager of the tree representation of the WebKit render tree.
+ scoped_ptr<BrowserAccessibilityManager> browser_accessibility_manager_;
+
+ gfx::Rect current_display_area_;
+
+ uint32 renderer_frame_number_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewBase);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_BASE_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_browsertest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_browsertest.cc
new file mode 100644
index 00000000000..2b47ff19315
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_browsertest.cc
@@ -0,0 +1,880 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/compositor_util.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "media/base/video_frame.h"
+#include "media/filters/skcanvas_video_renderer.h"
+#include "net/base/net_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gl/gl_switches.h"
+
+#if defined(OS_MACOSX)
+#include "ui/gl/io_surface_support_mac.h"
+#endif
+
+#if defined(OS_WIN)
+#include "ui/base/win/dpi.h"
+#endif
+
+namespace content {
+namespace {
+
+// Convenience macro: Short-circuit a pass for the tests where platform support
+// for forced-compositing mode (or disabled-compositing mode) is lacking.
+#define SET_UP_SURFACE_OR_PASS_TEST(wait_message) \
+ if (!SetUpSourceSurface(wait_message)) { \
+ LOG(WARNING) \
+ << ("Blindly passing this test: This platform does not support " \
+ "forced compositing (or forced-disabled compositing) mode."); \
+ return; \
+ }
+
+// Convenience macro: Short-circuit a pass for platforms where setting up
+// high-DPI fails.
+#define PASS_TEST_IF_SCALE_FACTOR_NOT_SUPPORTED(factor) \
+ if (ui::GetScaleFactorScale( \
+ GetScaleFactorForView(GetRenderWidgetHostViewPort())) != factor) { \
+ LOG(WARNING) << "Blindly passing this test: failed to set up " \
+ "scale factor: " << factor; \
+ return false; \
+ }
+
+// Common base class for browser tests. This is subclassed twice: Once to test
+// the browser in forced-compositing mode, and once to test with compositing
+// mode disabled.
+class RenderWidgetHostViewBrowserTest : public ContentBrowserTest {
+ public:
+ RenderWidgetHostViewBrowserTest()
+ : frame_size_(400, 300),
+ callback_invoke_count_(0),
+ frames_captured_(0) {}
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir_));
+ ContentBrowserTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ // Attempts to set up the source surface. Returns false if unsupported on the
+ // current platform.
+ virtual bool SetUpSourceSurface(const char* wait_message) = 0;
+
+ int callback_invoke_count() const {
+ return callback_invoke_count_;
+ }
+
+ int frames_captured() const {
+ return frames_captured_;
+ }
+
+ const gfx::Size& frame_size() const {
+ return frame_size_;
+ }
+
+ const base::FilePath& test_dir() const {
+ return test_dir_;
+ }
+
+ RenderViewHost* GetRenderViewHost() const {
+ RenderViewHost* const rvh = shell()->web_contents()->GetRenderViewHost();
+ CHECK(rvh);
+ return rvh;
+ }
+
+ RenderWidgetHostImpl* GetRenderWidgetHost() const {
+ RenderWidgetHostImpl* const rwh = RenderWidgetHostImpl::From(
+ shell()->web_contents()->GetRenderWidgetHostView()->
+ GetRenderWidgetHost());
+ CHECK(rwh);
+ return rwh;
+ }
+
+ RenderWidgetHostViewPort* GetRenderWidgetHostViewPort() const {
+ RenderWidgetHostViewPort* const view =
+ RenderWidgetHostViewPort::FromRWHV(GetRenderViewHost()->GetView());
+ CHECK(view);
+ return view;
+ }
+
+ // Callback when using CopyFromBackingStore() API.
+ void FinishCopyFromBackingStore(const base::Closure& quit_closure,
+ bool frame_captured,
+ const SkBitmap& bitmap) {
+ ++callback_invoke_count_;
+ if (frame_captured) {
+ ++frames_captured_;
+ EXPECT_FALSE(bitmap.empty());
+ }
+ if (!quit_closure.is_null())
+ quit_closure.Run();
+ }
+
+ // Callback when using CopyFromCompositingSurfaceToVideoFrame() API.
+ void FinishCopyFromCompositingSurface(const base::Closure& quit_closure,
+ bool frame_captured) {
+ ++callback_invoke_count_;
+ if (frame_captured)
+ ++frames_captured_;
+ if (!quit_closure.is_null())
+ quit_closure.Run();
+ }
+
+ // Callback when using frame subscriber API.
+ void FrameDelivered(const scoped_refptr<base::MessageLoopProxy>& loop,
+ base::Closure quit_closure,
+ base::Time timestamp,
+ bool frame_captured) {
+ ++callback_invoke_count_;
+ if (frame_captured)
+ ++frames_captured_;
+ if (!quit_closure.is_null())
+ loop->PostTask(FROM_HERE, quit_closure);
+ }
+
+ // Copy one frame using the CopyFromBackingStore API.
+ void RunBasicCopyFromBackingStoreTest() {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+
+ // Repeatedly call CopyFromBackingStore() since, on some platforms (e.g.,
+ // Windows), the operation will fail until the first "present" has been
+ // made.
+ int count_attempts = 0;
+ while (true) {
+ ++count_attempts;
+ base::RunLoop run_loop;
+ GetRenderViewHost()->CopyFromBackingStore(
+ gfx::Rect(),
+ frame_size(),
+ base::Bind(
+ &RenderWidgetHostViewBrowserTest::FinishCopyFromBackingStore,
+ base::Unretained(this),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+
+ if (frames_captured())
+ break;
+ else
+ GiveItSomeTime();
+ }
+
+ EXPECT_EQ(count_attempts, callback_invoke_count());
+ EXPECT_EQ(1, frames_captured());
+ }
+
+ protected:
+ // Waits until the source is available for copying.
+ void WaitForCopySourceReady() {
+ while (!GetRenderWidgetHostViewPort()->IsSurfaceAvailableForCopy())
+ GiveItSomeTime();
+ }
+
+ // Run the current message loop for a short time without unwinding the current
+ // call stack.
+ static void GiveItSomeTime() {
+ base::RunLoop run_loop;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ run_loop.QuitClosure(),
+ base::TimeDelta::FromMilliseconds(10));
+ run_loop.Run();
+ }
+
+ private:
+ const gfx::Size frame_size_;
+ base::FilePath test_dir_;
+ int callback_invoke_count_;
+ int frames_captured_;
+};
+
+class CompositingRenderWidgetHostViewBrowserTest
+ : public RenderWidgetHostViewBrowserTest {
+ public:
+ virtual void SetUp() OVERRIDE {
+ // We expect real pixel output for these tests.
+ UseRealGLContexts();
+
+ // On legacy windows, these tests need real GL bindings to pass.
+#if defined(OS_WIN) && !defined(USE_AURA)
+ UseRealGLBindings();
+#endif
+
+ RenderWidgetHostViewBrowserTest::SetUp();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // Note: Not appending kForceCompositingMode switch here, since not all bots
+ // support compositing. Some bots will run with compositing on, and others
+ // won't. Therefore, the call to SetUpSourceSurface() later on will detect
+ // whether compositing mode is actually on or not. If not, the tests will
+ // pass blindly, logging a warning message, since we cannot test what the
+ // platform/implementation does not support.
+ RenderWidgetHostViewBrowserTest::SetUpCommandLine(command_line);
+ }
+
+ virtual GURL TestUrl() {
+ return net::FilePathToFileURL(
+ test_dir().AppendASCII("rwhv_compositing_animation.html"));
+ }
+
+ virtual bool SetUpSourceSurface(const char* wait_message) OVERRIDE {
+ if (!IsForceCompositingModeEnabled())
+ return false; // See comment in SetUpCommandLine().
+#if defined(OS_MACOSX)
+ CHECK(IOSurfaceSupport::Initialize());
+#endif
+
+ content::DOMMessageQueue message_queue;
+ NavigateToURL(shell(), TestUrl());
+ if (wait_message != NULL) {
+ std::string result(wait_message);
+ if (!message_queue.WaitForMessage(&result)) {
+ EXPECT_TRUE(false) << "WaitForMessage " << result << " failed.";
+ return false;
+ }
+ }
+
+#if !defined(USE_AURA)
+ if (!GetRenderWidgetHost()->is_accelerated_compositing_active())
+ return false; // Renderer did not turn on accelerated compositing.
+#endif
+
+ // Using accelerated compositing, but a compositing surface might not be
+ // available yet. So, wait for it.
+ WaitForCopySourceReady();
+ return true;
+ }
+};
+
+class NonCompositingRenderWidgetHostViewBrowserTest
+ : public RenderWidgetHostViewBrowserTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ // Note: Appending the kDisableAcceleratedCompositing switch here, but there
+ // are some builds that only use compositing and will ignore this switch.
+ // Therefore, the call to SetUpSourceSurface() later on will detect whether
+ // compositing mode is actually off. If it's on, the tests will pass
+ // blindly, logging a warning message, since we cannot test what the
+ // platform/implementation does not support.
+ command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
+ RenderWidgetHostViewBrowserTest::SetUpCommandLine(command_line);
+ }
+
+ virtual GURL TestUrl() {
+ return GURL(kAboutBlankURL);
+ }
+
+ virtual bool SetUpSourceSurface(const char* wait_message) OVERRIDE {
+ if (IsForceCompositingModeEnabled())
+ return false; // See comment in SetUpCommandLine().
+
+ content::DOMMessageQueue message_queue;
+ NavigateToURL(shell(), TestUrl());
+ if (wait_message != NULL) {
+ std::string result(wait_message);
+ if (!message_queue.WaitForMessage(&result)) {
+ EXPECT_TRUE(false) << "WaitForMessage " << result << " failed.";
+ return false;
+ }
+ }
+
+ WaitForCopySourceReady();
+ // Return whether the renderer left accelerated compositing turned off.
+ return !GetRenderWidgetHost()->is_accelerated_compositing_active();
+ }
+};
+
+class FakeFrameSubscriber : public RenderWidgetHostViewFrameSubscriber {
+ public:
+ FakeFrameSubscriber(
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback)
+ : callback_(callback) {
+ }
+
+ virtual bool ShouldCaptureFrame(
+ base::Time present_time,
+ scoped_refptr<media::VideoFrame>* storage,
+ DeliverFrameCallback* callback) OVERRIDE {
+ // Only allow one frame capture to be made. Otherwise, the compositor could
+ // start multiple captures, unbounded, and eventually its own limiter logic
+ // will begin invoking |callback| with a |false| result. This flakes out
+ // the unit tests, since they receive a "failed" callback before the later
+ // "success" callbacks.
+ if (callback_.is_null())
+ return false;
+ *storage = media::VideoFrame::CreateBlackFrame(gfx::Size(100, 100));
+ *callback = callback_;
+ callback_.Reset();
+ return true;
+ }
+
+ private:
+ DeliverFrameCallback callback_;
+};
+
+// Disable tests for Android and IOS as these platforms have incomplete
+// implementation.
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+
+// The CopyFromBackingStore() API should work on all platforms when compositing
+// is enabled.
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTest,
+ CopyFromBackingStore) {
+ RunBasicCopyFromBackingStoreTest();
+}
+
+// The CopyFromBackingStore() API should work on all platforms when compositing
+// is disabled.
+IN_PROC_BROWSER_TEST_F(NonCompositingRenderWidgetHostViewBrowserTest,
+ CopyFromBackingStore) {
+ RunBasicCopyFromBackingStoreTest();
+}
+
+// Tests that the callback passed to CopyFromBackingStore is always called,
+// even when the RenderWidgetHost is deleting in the middle of an async copy.
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTest,
+ CopyFromBackingStore_CallbackDespiteDelete) {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+
+ base::RunLoop run_loop;
+ GetRenderViewHost()->CopyFromBackingStore(
+ gfx::Rect(),
+ frame_size(),
+ base::Bind(&RenderWidgetHostViewBrowserTest::FinishCopyFromBackingStore,
+ base::Unretained(this), run_loop.QuitClosure()));
+ // Delete the surface before the callback is run.
+ GetRenderWidgetHostViewPort()->AcceleratedSurfaceRelease();
+ run_loop.Run();
+
+ EXPECT_EQ(1, callback_invoke_count());
+}
+
+// Tests that the callback passed to CopyFromCompositingSurfaceToVideoFrame is
+// always called, even when the RenderWidgetHost is deleting in the middle of
+// an async copy.
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTest,
+ CopyFromCompositingSurface_CallbackDespiteDelete) {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+ RenderWidgetHostViewPort* const view = GetRenderWidgetHostViewPort();
+ if (!view->CanCopyToVideoFrame()) {
+ LOG(WARNING) <<
+ ("Blindly passing this test: CopyFromCompositingSurfaceToVideoFrame() "
+ "not supported on this platform.");
+ return;
+ }
+
+ base::RunLoop run_loop;
+ scoped_refptr<media::VideoFrame> dest =
+ media::VideoFrame::CreateBlackFrame(frame_size());
+ view->CopyFromCompositingSurfaceToVideoFrame(
+ gfx::Rect(view->GetViewBounds().size()), dest, base::Bind(
+ &RenderWidgetHostViewBrowserTest::FinishCopyFromCompositingSurface,
+ base::Unretained(this), run_loop.QuitClosure()));
+ // Delete the surface before the callback is run.
+ view->AcceleratedSurfaceRelease();
+ run_loop.Run();
+
+ EXPECT_EQ(1, callback_invoke_count());
+}
+
+// With compositing turned off, no platforms should support the
+// CopyFromCompositingSurfaceToVideoFrame() API.
+IN_PROC_BROWSER_TEST_F(NonCompositingRenderWidgetHostViewBrowserTest,
+ CopyFromCompositingSurfaceToVideoFrameCallbackTest) {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+ EXPECT_FALSE(GetRenderWidgetHostViewPort()->CanCopyToVideoFrame());
+}
+
+// Test basic frame subscription functionality. We subscribe, and then run
+// until at least one DeliverFrameCallback has been invoked.
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTest,
+ FrameSubscriberTest) {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+ RenderWidgetHostViewPort* const view = GetRenderWidgetHostViewPort();
+ if (!view->CanSubscribeFrame()) {
+ LOG(WARNING) << ("Blindly passing this test: Frame subscription not "
+ "supported on this platform.");
+ return;
+ }
+
+ base::RunLoop run_loop;
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber(
+ new FakeFrameSubscriber(
+ base::Bind(&RenderWidgetHostViewBrowserTest::FrameDelivered,
+ base::Unretained(this),
+ base::MessageLoopProxy::current(),
+ run_loop.QuitClosure())));
+ view->BeginFrameSubscription(subscriber.Pass());
+ run_loop.Run();
+ view->EndFrameSubscription();
+
+ EXPECT_LE(1, callback_invoke_count());
+ EXPECT_LE(1, frames_captured());
+}
+
+// Test that we can copy twice from an accelerated composited page.
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTest, CopyTwice) {
+ SET_UP_SURFACE_OR_PASS_TEST(NULL);
+ RenderWidgetHostViewPort* const view = GetRenderWidgetHostViewPort();
+ if (!view->CanCopyToVideoFrame()) {
+ LOG(WARNING) << ("Blindly passing this test: "
+ "CopyFromCompositingSurfaceToVideoFrame() not supported "
+ "on this platform.");
+ return;
+ }
+
+ base::RunLoop run_loop;
+ scoped_refptr<media::VideoFrame> first_output =
+ media::VideoFrame::CreateBlackFrame(frame_size());
+ ASSERT_TRUE(first_output.get());
+ scoped_refptr<media::VideoFrame> second_output =
+ media::VideoFrame::CreateBlackFrame(frame_size());
+ ASSERT_TRUE(second_output.get());
+ view->CopyFromCompositingSurfaceToVideoFrame(
+ gfx::Rect(view->GetViewBounds().size()),
+ first_output,
+ base::Bind(&RenderWidgetHostViewBrowserTest::FrameDelivered,
+ base::Unretained(this),
+ base::MessageLoopProxy::current(),
+ base::Closure(),
+ base::Time::Now()));
+ view->CopyFromCompositingSurfaceToVideoFrame(
+ gfx::Rect(view->GetViewBounds().size()), second_output,
+ base::Bind(&RenderWidgetHostViewBrowserTest::FrameDelivered,
+ base::Unretained(this),
+ base::MessageLoopProxy::current(),
+ run_loop.QuitClosure(),
+ base::Time::Now()));
+ run_loop.Run();
+
+ EXPECT_EQ(2, callback_invoke_count());
+ EXPECT_EQ(2, frames_captured());
+}
+
+class CompositingRenderWidgetHostViewBrowserTestTabCapture
+ : public CompositingRenderWidgetHostViewBrowserTest {
+ public:
+ CompositingRenderWidgetHostViewBrowserTestTabCapture()
+ : expected_copy_from_compositing_surface_result_(false),
+ allowable_error_(0),
+ test_url_("data:text/html,<!doctype html>") {}
+
+ void CopyFromCompositingSurfaceCallback(base::Closure quit_callback,
+ bool result,
+ const SkBitmap& bitmap) {
+ EXPECT_EQ(expected_copy_from_compositing_surface_result_, result);
+ if (!result) {
+ quit_callback.Run();
+ return;
+ }
+
+ const SkBitmap& expected_bitmap =
+ expected_copy_from_compositing_surface_bitmap_;
+ EXPECT_EQ(expected_bitmap.width(), bitmap.width());
+ EXPECT_EQ(expected_bitmap.height(), bitmap.height());
+ EXPECT_EQ(expected_bitmap.config(), bitmap.config());
+ SkAutoLockPixels expected_bitmap_lock(expected_bitmap);
+ SkAutoLockPixels bitmap_lock(bitmap);
+ int fails = 0;
+ for (int i = 0; i < bitmap.width() && fails < 10; ++i) {
+ for (int j = 0; j < bitmap.height() && fails < 10; ++j) {
+ if (!exclude_rect_.IsEmpty() && exclude_rect_.Contains(i, j))
+ continue;
+
+ SkColor expected_color = expected_bitmap.getColor(i, j);
+ SkColor color = bitmap.getColor(i, j);
+ int expected_alpha = SkColorGetA(expected_color);
+ int alpha = SkColorGetA(color);
+ int expected_red = SkColorGetR(expected_color);
+ int red = SkColorGetR(color);
+ int expected_green = SkColorGetG(expected_color);
+ int green = SkColorGetG(color);
+ int expected_blue = SkColorGetB(expected_color);
+ int blue = SkColorGetB(color);
+ EXPECT_NEAR(expected_alpha, alpha, allowable_error_)
+ << "expected_color: " << std::hex << expected_color
+ << " color: " << color
+ << " Failed at " << std::dec << i << ", " << j
+ << " Failure " << ++fails;
+ EXPECT_NEAR(expected_red, red, allowable_error_)
+ << "expected_color: " << std::hex << expected_color
+ << " color: " << color
+ << " Failed at " << std::dec << i << ", " << j
+ << " Failure " << ++fails;
+ EXPECT_NEAR(expected_green, green, allowable_error_)
+ << "expected_color: " << std::hex << expected_color
+ << " color: " << color
+ << " Failed at " << std::dec << i << ", " << j
+ << " Failure " << ++fails;
+ EXPECT_NEAR(expected_blue, blue, allowable_error_)
+ << "expected_color: " << std::hex << expected_color
+ << " color: " << color
+ << " Failed at " << std::dec << i << ", " << j
+ << " Failure " << ++fails;
+ }
+ }
+ EXPECT_LT(fails, 10);
+
+ quit_callback.Run();
+ }
+
+ void CopyFromCompositingSurfaceCallbackForVideo(
+ scoped_refptr<media::VideoFrame> video_frame,
+ base::Closure quit_callback,
+ bool result) {
+ EXPECT_EQ(expected_copy_from_compositing_surface_result_, result);
+ if (!result) {
+ quit_callback.Run();
+ return;
+ }
+
+ media::SkCanvasVideoRenderer video_renderer;
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ video_frame->visible_rect().width(),
+ video_frame->visible_rect().height());
+ bitmap.allocPixels();
+ bitmap.setIsOpaque(true);
+
+ SkDevice device(bitmap);
+ SkCanvas canvas(&device);
+
+ video_renderer.Paint(video_frame.get(),
+ &canvas,
+ video_frame->visible_rect(),
+ 0xff);
+
+ CopyFromCompositingSurfaceCallback(quit_callback,
+ result,
+ bitmap);
+ }
+
+ void SetExpectedCopyFromCompositingSurfaceResult(bool result,
+ const SkBitmap& bitmap) {
+ expected_copy_from_compositing_surface_result_ = result;
+ expected_copy_from_compositing_surface_bitmap_ = bitmap;
+ }
+
+ void SetAllowableError(int amount) { allowable_error_ = amount; }
+ void SetExcludeRect(gfx::Rect exclude) { exclude_rect_ = exclude; }
+
+ virtual GURL TestUrl() OVERRIDE {
+ return GURL(test_url_);
+ }
+
+ void SetTestUrl(std::string url) { test_url_ = url; }
+
+ // Loads a page two boxes side-by-side, each half the width of
+ // |html_rect_size|, and with different background colors. The test then
+ // copies from |copy_rect| region of the page into a bitmap of size
+ // |output_size|, and compares that with a bitmap of size
+ // |expected_bitmap_size|.
+ // Note that |output_size| may not have the same size as |copy_rect| (e.g.
+ // when the output is scaled). Also note that |expected_bitmap_size| may not
+ // be the same as |output_size| (e.g. when the device scale factor is not 1).
+ void PerformTestWithLeftRightRects(const gfx::Size& html_rect_size,
+ const gfx::Rect& copy_rect,
+ const gfx::Size& output_size,
+ const gfx::Size& expected_bitmap_size,
+ bool video_frame) {
+ const gfx::Size box_size(html_rect_size.width() / 2,
+ html_rect_size.height());
+ SetTestUrl(base::StringPrintf(
+ "data:text/html,<!doctype html>"
+ "<div class='left'>"
+ " <div class='right'></div>"
+ "</div>"
+ "<style>"
+ "body { padding: 0; margin: 0; }"
+ ".left { position: absolute;"
+ " background: #0ff;"
+ " width: %dpx;"
+ " height: %dpx;"
+ "}"
+ ".right { position: absolute;"
+ " left: %dpx;"
+ " background: #ff0;"
+ " width: %dpx;"
+ " height: %dpx;"
+ "}"
+ "</style>"
+ "<script>"
+ " domAutomationController.setAutomationId(0);"
+ " domAutomationController.send(\"DONE\");"
+ "</script>",
+ box_size.width(),
+ box_size.height(),
+ box_size.width(),
+ box_size.width(),
+ box_size.height()));
+
+ SET_UP_SURFACE_OR_PASS_TEST("\"DONE\"");
+ if (!ShouldContinueAfterTestURLLoad())
+ return;
+
+ RenderWidgetHostViewPort* rwhvp = GetRenderWidgetHostViewPort();
+
+ // The page is loaded in the renderer, wait for a new frame to arrive.
+ uint32 frame = rwhvp->RendererFrameNumber();
+ GetRenderWidgetHost()->ScheduleComposite();
+ while (rwhvp->RendererFrameNumber() == frame)
+ GiveItSomeTime();
+
+ SkBitmap expected_bitmap;
+ SetupLeftRightBitmap(expected_bitmap_size, &expected_bitmap);
+ SetExpectedCopyFromCompositingSurfaceResult(true, expected_bitmap);
+
+ base::RunLoop run_loop;
+ if (video_frame) {
+ // Allow pixel differences as long as we have the right idea.
+ SetAllowableError(0x10);
+ // Exclude the middle two columns which are blended between the two sides.
+ SetExcludeRect(
+ gfx::Rect(output_size.width() / 2 - 1, 0, 2, output_size.height()));
+
+ scoped_refptr<media::VideoFrame> video_frame =
+ media::VideoFrame::CreateFrame(media::VideoFrame::YV12,
+ expected_bitmap_size,
+ gfx::Rect(expected_bitmap_size),
+ expected_bitmap_size,
+ base::TimeDelta());
+
+ base::Callback<void(bool success)> callback =
+ base::Bind(&CompositingRenderWidgetHostViewBrowserTestTabCapture::
+ CopyFromCompositingSurfaceCallbackForVideo,
+ base::Unretained(this),
+ video_frame,
+ run_loop.QuitClosure());
+ rwhvp->CopyFromCompositingSurfaceToVideoFrame(copy_rect,
+ video_frame,
+ callback);
+ } else {
+ base::Callback<void(bool, const SkBitmap&)> callback =
+ base::Bind(&CompositingRenderWidgetHostViewBrowserTestTabCapture::
+ CopyFromCompositingSurfaceCallback,
+ base::Unretained(this),
+ run_loop.QuitClosure());
+ rwhvp->CopyFromCompositingSurface(copy_rect, output_size, callback);
+ }
+ run_loop.Run();
+ }
+
+ // Sets up |bitmap| to have size |copy_size|. It floods the left half with
+ // #0ff and the right half with #ff0.
+ void SetupLeftRightBitmap(const gfx::Size& copy_size, SkBitmap* bitmap) {
+ bitmap->setConfig(
+ SkBitmap::kARGB_8888_Config, copy_size.width(), copy_size.height());
+ bitmap->allocPixels();
+ // Left half is #0ff.
+ bitmap->eraseARGB(255, 0, 255, 255);
+ // Right half is #ff0.
+ {
+ SkAutoLockPixels lock(*bitmap);
+ for (int i = 0; i < copy_size.width() / 2; ++i) {
+ for (int j = 0; j < copy_size.height(); ++j) {
+ *(bitmap->getAddr32(copy_size.width() / 2 + i, j)) =
+ SkColorSetARGB(255, 255, 255, 0);
+ }
+ }
+ }
+ }
+
+ protected:
+ virtual bool ShouldContinueAfterTestURLLoad() {
+ return true;
+ }
+
+ private:
+ bool expected_copy_from_compositing_surface_result_;
+ SkBitmap expected_copy_from_compositing_surface_bitmap_;
+ int allowable_error_;
+ gfx::Rect exclude_rect_;
+ std::string test_url_;
+};
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_Origin_Unscaled) {
+ gfx::Rect copy_rect(400, 300);
+ gfx::Size output_size = copy_rect.size();
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = false;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_Origin_Scaled) {
+ gfx::Rect copy_rect(400, 300);
+ gfx::Size output_size(200, 100);
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = false;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_Cropped_Unscaled) {
+ // Grab 60x60 pixels from the center of the tab contents.
+ gfx::Rect copy_rect(400, 300);
+ copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30),
+ gfx::Size(60, 60));
+ gfx::Size output_size = copy_rect.size();
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = false;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_Cropped_Scaled) {
+ // Grab 60x60 pixels from the center of the tab contents.
+ gfx::Rect copy_rect(400, 300);
+ copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30),
+ gfx::Size(60, 60));
+ gfx::Size output_size(20, 10);
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = false;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_ForVideoFrame) {
+ // Grab 90x60 pixels from the center of the tab contents.
+ gfx::Rect copy_rect(400, 300);
+ copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(45, 30),
+ gfx::Size(90, 60));
+ gfx::Size output_size = copy_rect.size();
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = true;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewBrowserTestTabCapture,
+ CopyFromCompositingSurface_ForVideoFrame_Scaled) {
+ // Grab 90x60 pixels from the center of the tab contents.
+ gfx::Rect copy_rect(400, 300);
+ copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(45, 30),
+ gfx::Size(90, 60));
+ // Scale to 30 x 20 (preserve aspect ratio).
+ gfx::Size output_size(30, 20);
+ gfx::Size expected_bitmap_size = output_size;
+ gfx::Size html_rect_size(400, 300);
+ bool video_frame = true;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+class CompositingRenderWidgetHostViewTabCaptureHighDPI
+ : public CompositingRenderWidgetHostViewBrowserTestTabCapture {
+ public:
+ CompositingRenderWidgetHostViewTabCaptureHighDPI()
+ : kScale(2.f) {
+ }
+
+ virtual void SetUpCommandLine(CommandLine* cmd) OVERRIDE {
+ CompositingRenderWidgetHostViewBrowserTestTabCapture::SetUpCommandLine(cmd);
+ cmd->AppendSwitchASCII(switches::kForceDeviceScaleFactor,
+ base::StringPrintf("%f", scale()));
+#if defined(OS_WIN)
+ cmd->AppendSwitchASCII(switches::kHighDPISupport, "1");
+ ui::EnableHighDPISupport();
+#endif
+ }
+
+ float scale() const { return kScale; }
+
+ private:
+ virtual bool ShouldContinueAfterTestURLLoad() OVERRIDE {
+ PASS_TEST_IF_SCALE_FACTOR_NOT_SUPPORTED(scale());
+ return true;
+ }
+
+ const float kScale;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositingRenderWidgetHostViewTabCaptureHighDPI);
+};
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewTabCaptureHighDPI,
+ CopyFromCompositingSurface) {
+ gfx::Rect copy_rect(200, 150);
+ gfx::Size output_size = copy_rect.size();
+ gfx::Size expected_bitmap_size =
+ gfx::ToFlooredSize(gfx::ScaleSize(output_size, scale(), scale()));
+ gfx::Size html_rect_size(200, 150);
+ bool video_frame = false;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+IN_PROC_BROWSER_TEST_F(CompositingRenderWidgetHostViewTabCaptureHighDPI,
+ CopyFromCompositingSurfaceVideoFrame) {
+ gfx::Size html_rect_size(200, 150);
+ // Grab 90x60 pixels from the center of the tab contents.
+ gfx::Rect copy_rect =
+ gfx::Rect(gfx::Rect(html_rect_size).CenterPoint() - gfx::Vector2d(45, 30),
+ gfx::Size(90, 60));
+ gfx::Size output_size = copy_rect.size();
+ gfx::Size expected_bitmap_size =
+ gfx::ToFlooredSize(gfx::ScaleSize(output_size, scale(), scale()));
+ bool video_frame = true;
+ PerformTestWithLeftRightRects(html_rect_size,
+ copy_rect,
+ output_size,
+ expected_bitmap_size,
+ video_frame);
+}
+
+#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_gtk.cc b/chromium/content/browser/renderer_host/render_widget_host_view_gtk.cc
new file mode 100644
index 00000000000..ddaf9528cfd
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -0,0 +1,1584 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_gtk.h"
+
+#include <cairo/cairo.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/accessibility/browser_accessibility_gtk.h"
+#include "content/browser/accessibility/browser_accessibility_manager_gtk.h"
+#include "content/browser/renderer_host/backing_store_gtk.h"
+#include "content/browser/renderer_host/gtk_im_context_wrapper.h"
+#include "content/browser/renderer_host/gtk_key_bindings_handler.h"
+#include "content/browser/renderer_host/gtk_window_utils.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/common/webplugin_geometry.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/common/content_switches.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+#include "third_party/WebKit/public/web/gtk/WebInputEventFactory.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/base/x/active_window_watcher_x.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/gtk_native_view_id_manager.h"
+#include "ui/gfx/gtk_preserve_window.h"
+#include "webkit/common/cursors/webcursor_gtk_data.h"
+
+using WebKit::WebInputEventFactory;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebScreenInfo;
+
+namespace content {
+namespace {
+
+// Paint rects on Linux are bounded by the maximum size of a shared memory
+// region. By default that's 32MB, but many distros increase it significantly
+// (i.e. to 256MB).
+//
+// We fetch the maximum value from /proc/sys/kernel/shmmax at runtime and, if
+// we exceed that, then we limit the height of the paint rect in the renderer.
+//
+// These constants are here to ensure that, in the event that we exceed it, we
+// end up with something a little more square. Previously we had 4000x4000, but
+// people's monitor setups are actually exceeding that these days.
+const int kMaxWindowWidth = 10000;
+const int kMaxWindowHeight = 10000;
+
+// See WebInputEventFactor.cpp for a reason for this being the default
+// scroll size for linux.
+const float kDefaultScrollPixelsPerTick = 160.0f / 3.0f;
+
+const GdkColor kBGColor =
+#if defined(NDEBUG)
+ { 0, 0xff * 257, 0xff * 257, 0xff * 257 };
+#else
+ { 0, 0x00 * 257, 0xff * 257, 0x00 * 257 };
+#endif
+
+// Returns the spinning cursor used for loading state.
+GdkCursor* GetMozSpinningCursor() {
+ static GdkCursor* moz_spinning_cursor = NULL;
+ if (!moz_spinning_cursor) {
+ const GdkColor fg = { 0, 0, 0, 0 };
+ const GdkColor bg = { 65535, 65535, 65535, 65535 };
+ GdkPixmap* source = gdk_bitmap_create_from_data(
+ NULL, reinterpret_cast<const gchar*>(moz_spinning_bits), 32, 32);
+ GdkPixmap* mask = gdk_bitmap_create_from_data(
+ NULL, reinterpret_cast<const gchar*>(moz_spinning_mask_bits), 32, 32);
+ moz_spinning_cursor =
+ gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 2, 2);
+ g_object_unref(source);
+ g_object_unref(mask);
+ }
+ return moz_spinning_cursor;
+}
+
+bool MovedToPoint(const WebKit::WebMouseEvent& mouse_event,
+ const gfx::Point& center) {
+ return mouse_event.globalX == center.x() &&
+ mouse_event.globalY == center.y();
+}
+
+} // namespace
+
+// This class is a simple convenience wrapper for Gtk functions. It has only
+// static methods.
+class RenderWidgetHostViewGtkWidget {
+ public:
+ static AtkObject* GetAccessible(void* userdata) {
+ return (static_cast<RenderWidgetHostViewGtk*>(userdata))->
+ GetAccessible();
+ }
+
+ static GtkWidget* CreateNewWidget(RenderWidgetHostViewGtk* host_view) {
+ GtkWidget* widget = gtk_preserve_window_new();
+ gtk_widget_set_name(widget, "chrome-render-widget-host-view");
+ // We manually double-buffer in Paint() because Paint() may or may not be
+ // called in repsonse to an "expose-event" signal.
+ gtk_widget_set_double_buffered(widget, FALSE);
+ gtk_widget_set_redraw_on_allocate(widget, FALSE);
+ gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &kBGColor);
+ // Allow the browser window to be resized freely.
+ gtk_widget_set_size_request(widget, 0, 0);
+
+ gtk_widget_add_events(widget, GDK_EXPOSURE_MASK |
+ GDK_STRUCTURE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_FOCUS_CHANGE_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK);
+ gtk_widget_set_can_focus(widget, TRUE);
+
+ g_signal_connect(widget, "expose-event",
+ G_CALLBACK(OnExposeEvent), host_view);
+ g_signal_connect(widget, "realize",
+ G_CALLBACK(OnRealize), host_view);
+ g_signal_connect(widget, "configure-event",
+ G_CALLBACK(OnConfigureEvent), host_view);
+ g_signal_connect(widget, "key-press-event",
+ G_CALLBACK(OnKeyPressReleaseEvent), host_view);
+ g_signal_connect(widget, "key-release-event",
+ G_CALLBACK(OnKeyPressReleaseEvent), host_view);
+ g_signal_connect(widget, "focus-in-event",
+ G_CALLBACK(OnFocusIn), host_view);
+ g_signal_connect(widget, "focus-out-event",
+ G_CALLBACK(OnFocusOut), host_view);
+ g_signal_connect(widget, "grab-notify",
+ G_CALLBACK(OnGrabNotify), host_view);
+ g_signal_connect(widget, "button-press-event",
+ G_CALLBACK(OnButtonPressReleaseEvent), host_view);
+ g_signal_connect(widget, "button-release-event",
+ G_CALLBACK(OnButtonPressReleaseEvent), host_view);
+ g_signal_connect(widget, "motion-notify-event",
+ G_CALLBACK(OnMouseMoveEvent), host_view);
+ g_signal_connect(widget, "enter-notify-event",
+ G_CALLBACK(OnCrossingEvent), host_view);
+ g_signal_connect(widget, "leave-notify-event",
+ G_CALLBACK(OnCrossingEvent), host_view);
+ g_signal_connect(widget, "client-event",
+ G_CALLBACK(OnClientEvent), host_view);
+
+
+ // Connect after so that we are called after the handler installed by the
+ // WebContentsView which handles zoom events.
+ g_signal_connect_after(widget, "scroll-event",
+ G_CALLBACK(OnMouseScrollEvent), host_view);
+
+ // Route calls to get_accessible to the view.
+ gtk_preserve_window_set_accessible_factory(
+ GTK_PRESERVE_WINDOW(widget), GetAccessible, host_view);
+
+ return widget;
+ }
+
+ private:
+ static gboolean OnExposeEvent(GtkWidget* widget,
+ GdkEventExpose* expose,
+ RenderWidgetHostViewGtk* host_view) {
+ if (host_view->is_hidden_)
+ return FALSE;
+ const gfx::Rect damage_rect(expose->area);
+ host_view->Paint(damage_rect);
+ return FALSE;
+ }
+
+ static gboolean OnRealize(GtkWidget* widget,
+ RenderWidgetHostViewGtk* host_view) {
+ // Use GtkSignalRegistrar to register events on a widget we don't
+ // control the lifetime of, auto disconnecting at our end of our life.
+ host_view->signals_.Connect(gtk_widget_get_toplevel(widget),
+ "configure-event",
+ G_CALLBACK(OnConfigureEvent), host_view);
+ return FALSE;
+ }
+
+ static gboolean OnConfigureEvent(GtkWidget* widget,
+ GdkEventConfigure* event,
+ RenderWidgetHostViewGtk* host_view) {
+ host_view->MarkCachedWidgetCenterStale();
+ host_view->UpdateScreenInfo(host_view->GetNativeView());
+ return FALSE;
+ }
+
+ static gboolean OnKeyPressReleaseEvent(GtkWidget* widget,
+ GdkEventKey* event,
+ RenderWidgetHostViewGtk* host_view) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewGtkWidget::OnKeyPressReleaseEvent");
+ // Force popups or fullscreen windows to close on Escape so they won't keep
+ // the keyboard grabbed or be stuck onscreen if the renderer is hanging.
+ bool should_close_on_escape =
+ (host_view->IsPopup() && host_view->NeedsInputGrab()) ||
+ host_view->is_fullscreen_;
+ if (should_close_on_escape && GDK_Escape == event->keyval) {
+ host_view->host_->Shutdown();
+ } else {
+ // Send key event to input method.
+ host_view->im_context_->ProcessKeyEvent(event);
+ }
+
+ // We return TRUE because we did handle the event. If it turns out webkit
+ // can't handle the event, we'll deal with it in
+ // RenderView::UnhandledKeyboardEvent().
+ return TRUE;
+ }
+
+ static gboolean OnFocusIn(GtkWidget* widget,
+ GdkEventFocus* focus,
+ RenderWidgetHostViewGtk* host_view) {
+ host_view->ShowCurrentCursor();
+ RenderWidgetHostImpl* host =
+ RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost());
+ host->GotFocus();
+ host->SetActive(true);
+
+ // The only way to enable a GtkIMContext object is to call its focus in
+ // handler.
+ host_view->im_context_->OnFocusIn();
+
+ return TRUE;
+ }
+
+ static gboolean OnFocusOut(GtkWidget* widget,
+ GdkEventFocus* focus,
+ RenderWidgetHostViewGtk* host_view) {
+ // Whenever we lose focus, set the cursor back to that of our parent window,
+ // which should be the default arrow.
+ gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
+ // If we are showing a context menu, maintain the illusion that webkit has
+ // focus.
+ if (!host_view->IsShowingContextMenu()) {
+ RenderWidgetHostImpl* host =
+ RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost());
+ host->SetActive(false);
+ host->Blur();
+ }
+
+ // Prevents us from stealing input context focus in OnGrabNotify() handler.
+ host_view->was_imcontext_focused_before_grab_ = false;
+
+ // Disable the GtkIMContext object.
+ host_view->im_context_->OnFocusOut();
+
+ host_view->set_last_mouse_down(NULL);
+
+ return TRUE;
+ }
+
+ // Called when we are shadowed or unshadowed by a keyboard grab (which will
+ // occur for activatable popups, such as dropdown menus). Popup windows do not
+ // take focus, so we never get a focus out or focus in event when they are
+ // shown, and must rely on this signal instead.
+ static void OnGrabNotify(GtkWidget* widget, gboolean was_grabbed,
+ RenderWidgetHostViewGtk* host_view) {
+ if (was_grabbed) {
+ if (host_view->was_imcontext_focused_before_grab_)
+ host_view->im_context_->OnFocusIn();
+ } else {
+ host_view->was_imcontext_focused_before_grab_ =
+ host_view->im_context_->is_focused();
+ if (host_view->was_imcontext_focused_before_grab_) {
+ gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
+ host_view->im_context_->OnFocusOut();
+ }
+ }
+ }
+
+ static gboolean OnButtonPressReleaseEvent(
+ GtkWidget* widget,
+ GdkEventButton* event,
+ RenderWidgetHostViewGtk* host_view) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewGtkWidget::OnButtonPressReleaseEvent");
+
+ if (event->type != GDK_BUTTON_RELEASE)
+ host_view->set_last_mouse_down(event);
+
+ if (!(event->button == 1 || event->button == 2 || event->button == 3))
+ return FALSE; // We do not forward any other buttons to the renderer.
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ return FALSE;
+
+ // If we don't have focus already, this mouse click will focus us.
+ if (!gtk_widget_is_focus(widget))
+ host_view->host_->OnPointerEventActivate();
+
+ // Confirm existing composition text on mouse click events, to make sure
+ // the input caret won't be moved with an ongoing composition session.
+ if (event->type != GDK_BUTTON_RELEASE)
+ host_view->im_context_->ConfirmComposition();
+
+ // We want to translate the coordinates of events that do not originate
+ // from this widget to be relative to the top left of the widget.
+ GtkWidget* event_widget = gtk_get_event_widget(
+ reinterpret_cast<GdkEvent*>(event));
+ if (event_widget != widget) {
+ int x = 0;
+ int y = 0;
+ gtk_widget_get_pointer(widget, &x, &y);
+ // If the mouse event happens outside our popup, force the popup to
+ // close. We do this so a hung renderer doesn't prevent us from
+ // releasing the x pointer grab.
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ bool click_in_popup = x >= 0 && y >= 0 && x < allocation.width &&
+ y < allocation.height;
+ // Only Shutdown on mouse downs. Mouse ups can occur outside the render
+ // view if the user drags for DnD or while using the scrollbar on a select
+ // dropdown. Don't shutdown if we are not a popup.
+ if (event->type != GDK_BUTTON_RELEASE && host_view->IsPopup() &&
+ !host_view->is_popup_first_mouse_release_ && !click_in_popup) {
+ host_view->host_->Shutdown();
+ return FALSE;
+ }
+ event->x = x;
+ event->y = y;
+ }
+
+ // TODO(evanm): why is this necessary here but not in test shell?
+ // This logic is the same as GtkButton.
+ if (event->type == GDK_BUTTON_PRESS && !gtk_widget_has_focus(widget))
+ gtk_widget_grab_focus(widget);
+
+ host_view->is_popup_first_mouse_release_ = false;
+ RenderWidgetHostImpl* widget_host =
+ RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost());
+ if (widget_host)
+ widget_host->ForwardMouseEvent(WebInputEventFactory::mouseEvent(event));
+
+ // Although we did handle the mouse event, we need to let other handlers
+ // run (in particular the one installed by WebContentsViewGtk).
+ return FALSE;
+ }
+
+ static gboolean OnMouseMoveEvent(GtkWidget* widget,
+ GdkEventMotion* event,
+ RenderWidgetHostViewGtk* host_view) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewGtkWidget::OnMouseMoveEvent");
+ // We want to translate the coordinates of events that do not originate
+ // from this widget to be relative to the top left of the widget.
+ GtkWidget* event_widget = gtk_get_event_widget(
+ reinterpret_cast<GdkEvent*>(event));
+ if (event_widget != widget) {
+ int x = 0;
+ int y = 0;
+ gtk_widget_get_pointer(widget, &x, &y);
+ event->x = x;
+ event->y = y;
+ }
+
+ host_view->ModifyEventForEdgeDragging(widget, event);
+
+ WebKit::WebMouseEvent mouse_event =
+ WebInputEventFactory::mouseEvent(event);
+
+ if (host_view->mouse_locked_) {
+ gfx::Point center = host_view->GetWidgetCenter();
+
+ bool moved_to_center = MovedToPoint(mouse_event, center);
+ if (moved_to_center)
+ host_view->mouse_has_been_warped_to_new_center_ = true;
+
+ host_view->ModifyEventMovementAndCoords(&mouse_event);
+
+ if (!moved_to_center &&
+ (mouse_event.movementX || mouse_event.movementY)) {
+ GdkDisplay* display = gtk_widget_get_display(widget);
+ GdkScreen* screen = gtk_widget_get_screen(widget);
+ gdk_display_warp_pointer(display, screen, center.x(), center.y());
+ if (host_view->mouse_has_been_warped_to_new_center_)
+ RenderWidgetHostImpl::From(
+ host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event);
+ }
+ } else { // Mouse is not locked.
+ host_view->ModifyEventMovementAndCoords(&mouse_event);
+ // Do not send mouse events while the mouse cursor is being warped back
+ // to the unlocked location.
+ if (!host_view->mouse_is_being_warped_to_unlocked_position_) {
+ RenderWidgetHostImpl::From(
+ host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event);
+ }
+ }
+ return FALSE;
+ }
+
+ static gboolean OnCrossingEvent(GtkWidget* widget,
+ GdkEventCrossing* event,
+ RenderWidgetHostViewGtk* host_view) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewGtkWidget::OnCrossingEvent");
+ const int any_button_mask =
+ GDK_BUTTON1_MASK |
+ GDK_BUTTON2_MASK |
+ GDK_BUTTON3_MASK |
+ GDK_BUTTON4_MASK |
+ GDK_BUTTON5_MASK;
+
+ // Only forward crossing events if the mouse button is not down.
+ // (When the mouse button is down, the proper events are already being
+ // sent by ButtonPressReleaseEvent and MouseMoveEvent, above, and if we
+ // additionally send this crossing event with the state indicating the
+ // button is down, it causes problems with drag and drop in WebKit.)
+ if (!(event->state & any_button_mask)) {
+ WebKit::WebMouseEvent mouse_event =
+ WebInputEventFactory::mouseEvent(event);
+ host_view->ModifyEventMovementAndCoords(&mouse_event);
+ // When crossing out and back into a render view the movement values
+ // must represent the instantaneous movement of the mouse, not the jump
+ // from the exit to re-entry point.
+ mouse_event.movementX = 0;
+ mouse_event.movementY = 0;
+ RenderWidgetHostImpl::From(
+ host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event);
+ }
+
+ return FALSE;
+ }
+
+ static gboolean OnClientEvent(GtkWidget* widget,
+ GdkEventClient* event,
+ RenderWidgetHostViewGtk* host_view) {
+ VLOG(1) << "client event type: " << event->message_type
+ << " data_format: " << event->data_format
+ << " data: " << event->data.l;
+ return TRUE;
+ }
+
+ // Return the net up / down (or left / right) distance represented by events
+ // in the events will be removed from the queue. We only look at the top of
+ // queue...any other type of event will cause us not to look farther.
+ // If there is a change to the set of modifier keys or scroll axis
+ // in the events we will stop looking as well.
+ static int GetPendingScrollDelta(bool vert, guint current_event_state) {
+ int num_clicks = 0;
+ GdkEvent* event;
+ bool event_coalesced = true;
+ while ((event = gdk_event_get()) && event_coalesced) {
+ event_coalesced = false;
+ if (event->type == GDK_SCROLL) {
+ GdkEventScroll scroll = event->scroll;
+ if (scroll.state & GDK_SHIFT_MASK) {
+ if (scroll.direction == GDK_SCROLL_UP)
+ scroll.direction = GDK_SCROLL_LEFT;
+ else if (scroll.direction == GDK_SCROLL_DOWN)
+ scroll.direction = GDK_SCROLL_RIGHT;
+ }
+ if (vert) {
+ if (scroll.direction == GDK_SCROLL_UP ||
+ scroll.direction == GDK_SCROLL_DOWN) {
+ if (scroll.state == current_event_state) {
+ num_clicks += (scroll.direction == GDK_SCROLL_UP ? 1 : -1);
+ gdk_event_free(event);
+ event_coalesced = true;
+ }
+ }
+ } else {
+ if (scroll.direction == GDK_SCROLL_LEFT ||
+ scroll.direction == GDK_SCROLL_RIGHT) {
+ if (scroll.state == current_event_state) {
+ num_clicks += (scroll.direction == GDK_SCROLL_LEFT ? 1 : -1);
+ gdk_event_free(event);
+ event_coalesced = true;
+ }
+ }
+ }
+ }
+ }
+ // If we have an event left we put it back on the queue.
+ if (event) {
+ gdk_event_put(event);
+ gdk_event_free(event);
+ }
+ return num_clicks * kDefaultScrollPixelsPerTick;
+ }
+
+ static gboolean OnMouseScrollEvent(GtkWidget* widget,
+ GdkEventScroll* event,
+ RenderWidgetHostViewGtk* host_view) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewGtkWidget::OnMouseScrollEvent");
+ // If the user is holding shift, translate it into a horizontal scroll. We
+ // don't care what other modifiers the user may be holding (zooming is
+ // handled at the WebContentsView level).
+ if (event->state & GDK_SHIFT_MASK) {
+ if (event->direction == GDK_SCROLL_UP)
+ event->direction = GDK_SCROLL_LEFT;
+ else if (event->direction == GDK_SCROLL_DOWN)
+ event->direction = GDK_SCROLL_RIGHT;
+ }
+
+ WebMouseWheelEvent web_event = WebInputEventFactory::mouseWheelEvent(event);
+ // We peek ahead at the top of the queue to look for additional pending
+ // scroll events.
+ if (event->direction == GDK_SCROLL_UP ||
+ event->direction == GDK_SCROLL_DOWN) {
+ if (event->direction == GDK_SCROLL_UP)
+ web_event.deltaY = kDefaultScrollPixelsPerTick;
+ else
+ web_event.deltaY = -kDefaultScrollPixelsPerTick;
+ web_event.deltaY += GetPendingScrollDelta(true, event->state);
+ } else {
+ if (event->direction == GDK_SCROLL_LEFT)
+ web_event.deltaX = kDefaultScrollPixelsPerTick;
+ else
+ web_event.deltaX = -kDefaultScrollPixelsPerTick;
+ web_event.deltaX += GetPendingScrollDelta(false, event->state);
+ }
+ RenderWidgetHostImpl::From(
+ host_view->GetRenderWidgetHost())->ForwardWheelEvent(web_event);
+ return FALSE;
+ }
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget);
+};
+
+RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host)
+ : host_(RenderWidgetHostImpl::From(widget_host)),
+ about_to_validate_and_paint_(false),
+ is_hidden_(false),
+ is_loading_(false),
+ parent_(NULL),
+ is_popup_first_mouse_release_(true),
+ was_imcontext_focused_before_grab_(false),
+ do_x_grab_(false),
+ is_fullscreen_(false),
+ made_active_(false),
+ mouse_is_being_warped_to_unlocked_position_(false),
+ destroy_handler_id_(0),
+ dragged_at_horizontal_edge_(0),
+ dragged_at_vertical_edge_(0),
+ compositing_surface_(gfx::kNullPluginWindow),
+ last_mouse_down_(NULL) {
+ host_->SetView(this);
+}
+
+RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() {
+ UnlockMouse();
+ set_last_mouse_down(NULL);
+ view_.Destroy();
+}
+
+bool RenderWidgetHostViewGtk::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewGtk, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreatePluginContainer,
+ OnCreatePluginContainer)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DestroyPluginContainer,
+ OnDestroyPluginContainer)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void RenderWidgetHostViewGtk::InitAsChild(
+ gfx::NativeView parent_view) {
+ DoSharedInit();
+ gtk_widget_show(view_.get());
+}
+
+void RenderWidgetHostViewGtk::InitAsPopup(
+ RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
+ // If we aren't a popup, then |window| will be leaked.
+ DCHECK(IsPopup());
+
+ DoSharedInit();
+ parent_ = parent_host_view->GetNativeView();
+ GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_container_add(GTK_CONTAINER(window), view_.get());
+ DoPopupOrFullscreenInit(window, pos);
+
+ // Grab all input for the app. If a click lands outside the bounds of the
+ // popup, WebKit will notice and destroy us. The underlying X window needs to
+ // be created and mapped by the above code before we can grab the input
+ // devices.
+ if (NeedsInputGrab()) {
+ // If our parent is in a widget hierarchy that ends with a window, add
+ // ourselves to the same window group to make sure that our GTK grab
+ // covers it.
+ GtkWidget* toplevel = gtk_widget_get_toplevel(parent_);
+ if (toplevel &&
+ GTK_WIDGET_TOPLEVEL(toplevel) &&
+ GTK_IS_WINDOW(toplevel)) {
+ gtk_window_group_add_window(
+ gtk_window_get_group(GTK_WINDOW(toplevel)), window);
+ }
+
+ // Install an application-level GTK grab to make sure that we receive all of
+ // the app's input.
+ gtk_grab_add(view_.get());
+
+ // We need to install an X grab as well. However if the app already has an X
+ // grab (as in the case of extension popup), an app grab will suffice.
+ do_x_grab_ = !gdk_pointer_is_grabbed();
+ if (do_x_grab_) {
+ // Install the grab on behalf our parent window if it and all of its
+ // ancestors are mapped; otherwise, just use ourselves (maybe we're being
+ // shown on behalf of an inactive tab).
+ GdkWindow* grab_window = gtk_widget_get_window(parent_);
+ if (!grab_window || !gdk_window_is_viewable(grab_window))
+ grab_window = gtk_widget_get_window(view_.get());
+
+ gdk_pointer_grab(
+ grab_window,
+ TRUE, // Only events outside of the window are reported with
+ // respect to |parent_->window|.
+ static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK),
+ NULL,
+ NULL,
+ GDK_CURRENT_TIME);
+ // We grab keyboard events too so things like alt+tab are eaten.
+ gdk_keyboard_grab(grab_window, TRUE, GDK_CURRENT_TIME);
+ }
+ }
+}
+
+void RenderWidgetHostViewGtk::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ DCHECK(reference_host_view);
+ DoSharedInit();
+
+ is_fullscreen_ = true;
+ GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ gtk_window_set_decorated(window, FALSE);
+ destroy_handler_id_ = g_signal_connect(GTK_WIDGET(window),
+ "destroy",
+ G_CALLBACK(OnDestroyThunk),
+ this);
+ gtk_container_add(GTK_CONTAINER(window), view_.get());
+
+ // Try to move and resize the window to cover the screen in case the window
+ // manager doesn't support _NET_WM_STATE_FULLSCREEN.
+ GdkScreen* screen = gtk_window_get_screen(window);
+ GdkWindow* ref_gdk_window = gtk_widget_get_window(
+ reference_host_view->GetNativeView());
+
+ gfx::Rect bounds;
+ if (ref_gdk_window) {
+ const int monitor_id = gdk_screen_get_monitor_at_window(screen,
+ ref_gdk_window);
+ GdkRectangle monitor_rect;
+ gdk_screen_get_monitor_geometry(screen, monitor_id, &monitor_rect);
+ bounds = gfx::Rect(monitor_rect);
+ } else {
+ bounds = gfx::Rect(
+ 0, 0, gdk_screen_get_width(screen), gdk_screen_get_height(screen));
+ }
+ gtk_window_move(window, bounds.x(), bounds.y());
+ gtk_window_resize(window, bounds.width(), bounds.height());
+ gtk_window_fullscreen(window);
+ DoPopupOrFullscreenInit(window, bounds);
+}
+
+RenderWidgetHost* RenderWidgetHostViewGtk::GetRenderWidgetHost() const {
+ return host_;
+}
+
+void RenderWidgetHostViewGtk::WasShown() {
+ if (!is_hidden_)
+ return;
+
+ if (web_contents_switch_paint_time_.is_null())
+ web_contents_switch_paint_time_ = base::TimeTicks::Now();
+ is_hidden_ = false;
+ host_->WasShown();
+}
+
+void RenderWidgetHostViewGtk::WasHidden() {
+ if (is_hidden_)
+ return;
+
+ // If we receive any more paint messages while we are hidden, we want to
+ // ignore them so we don't re-allocate the backing store. We will paint
+ // everything again when we become selected again.
+ is_hidden_ = true;
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ host_->WasHidden();
+
+ web_contents_switch_paint_time_ = base::TimeTicks();
+}
+
+void RenderWidgetHostViewGtk::SetSize(const gfx::Size& size) {
+ int width = std::min(size.width(), kMaxWindowWidth);
+ int height = std::min(size.height(), kMaxWindowHeight);
+ if (IsPopup()) {
+ // We're a popup, honor the size request.
+ gtk_widget_set_size_request(view_.get(), width, height);
+ }
+
+ // Update the size of the RWH.
+ if (requested_size_.width() != width ||
+ requested_size_.height() != height) {
+ requested_size_ = gfx::Size(width, height);
+ host_->SendScreenRects();
+ host_->WasResized();
+ }
+}
+
+void RenderWidgetHostViewGtk::SetBounds(const gfx::Rect& rect) {
+ // This is called when webkit has sent us a Move message.
+ if (IsPopup()) {
+ gtk_window_move(GTK_WINDOW(gtk_widget_get_toplevel(view_.get())),
+ rect.x(), rect.y());
+ }
+
+ SetSize(rect.size());
+}
+
+gfx::NativeView RenderWidgetHostViewGtk::GetNativeView() const {
+ return view_.get();
+}
+
+gfx::NativeViewId RenderWidgetHostViewGtk::GetNativeViewId() const {
+ return GtkNativeViewManager::GetInstance()->GetIdForWidget(view_.get());
+}
+
+gfx::NativeViewAccessible RenderWidgetHostViewGtk::GetNativeViewAccessible() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void RenderWidgetHostViewGtk::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) {
+ for (size_t i = 0; i < moves.size(); ++i) {
+ plugin_container_manager_.MovePluginContainer(moves[i]);
+ }
+}
+
+void RenderWidgetHostViewGtk::Focus() {
+ gtk_widget_grab_focus(view_.get());
+}
+
+void RenderWidgetHostViewGtk::Blur() {
+ // TODO(estade): We should be clearing native focus as well, but I know of no
+ // way to do that without focusing another widget.
+ host_->Blur();
+}
+
+bool RenderWidgetHostViewGtk::HasFocus() const {
+ return gtk_widget_has_focus(view_.get());
+}
+
+void RenderWidgetHostViewGtk::ActiveWindowChanged(GdkWindow* window) {
+ GdkWindow* our_window = gtk_widget_get_parent_window(view_.get());
+
+ if (our_window == window)
+ made_active_ = true;
+
+ // If the window was previously active, but isn't active anymore, shut it
+ // down.
+ if (is_fullscreen_ && our_window != window && made_active_)
+ host_->Shutdown();
+}
+
+bool RenderWidgetHostViewGtk::Send(IPC::Message* message) {
+ return host_->Send(message);
+}
+
+bool RenderWidgetHostViewGtk::IsSurfaceAvailableForCopy() const {
+ return true;
+}
+
+void RenderWidgetHostViewGtk::Show() {
+ gtk_widget_show(view_.get());
+}
+
+void RenderWidgetHostViewGtk::Hide() {
+ gtk_widget_hide(view_.get());
+}
+
+bool RenderWidgetHostViewGtk::IsShowing() {
+ return gtk_widget_get_visible(view_.get());
+}
+
+gfx::Rect RenderWidgetHostViewGtk::GetViewBounds() const {
+ GdkWindow* gdk_window = gtk_widget_get_window(view_.get());
+ if (!gdk_window)
+ return gfx::Rect(requested_size_);
+ GdkRectangle window_rect;
+ gdk_window_get_origin(gdk_window, &window_rect.x, &window_rect.y);
+ return gfx::Rect(window_rect.x, window_rect.y,
+ requested_size_.width(), requested_size_.height());
+}
+
+void RenderWidgetHostViewGtk::UpdateCursor(const WebCursor& cursor) {
+ // Optimize the common case, where the cursor hasn't changed.
+ // However, we can switch between different pixmaps, so only on the
+ // non-pixmap branch.
+ if (current_cursor_.GetCursorType() != GDK_CURSOR_IS_PIXMAP &&
+ current_cursor_.GetCursorType() == cursor.GetCursorType()) {
+ return;
+ }
+
+ current_cursor_ = cursor;
+ ShowCurrentCursor();
+}
+
+void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ // Only call ShowCurrentCursor() when it will actually change the cursor.
+ if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR)
+ ShowCurrentCursor();
+}
+
+void RenderWidgetHostViewGtk::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ im_context_->UpdateInputMethodState(type, can_compose_inline);
+}
+
+void RenderWidgetHostViewGtk::ImeCancelComposition() {
+ im_context_->CancelComposition();
+}
+
+void RenderWidgetHostViewGtk::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ TRACE_EVENT0("ui::gtk", "RenderWidgetHostViewGtk::DidUpdateBackingStore");
+ software_latency_info_.MergeWith(latency_info);
+
+ if (is_hidden_)
+ return;
+
+ // TODO(darin): Implement the equivalent of Win32's ScrollWindowEX. Can that
+ // be done using XCopyArea? Perhaps similar to
+ // BackingStore::ScrollBackingStore?
+ if (about_to_validate_and_paint_)
+ invalid_rect_.Union(scroll_rect);
+ else
+ Paint(scroll_rect);
+
+ for (size_t i = 0; i < copy_rects.size(); ++i) {
+ // Avoid double painting. NOTE: This is only relevant given the call to
+ // Paint(scroll_rect) above.
+ gfx::Rect rect = gfx::SubtractRects(copy_rects[i], scroll_rect);
+ if (rect.IsEmpty())
+ continue;
+
+ if (about_to_validate_and_paint_)
+ invalid_rect_.Union(rect);
+ else
+ Paint(rect);
+ }
+}
+
+void RenderWidgetHostViewGtk::RenderProcessGone(base::TerminationStatus status,
+ int error_code) {
+ Destroy();
+ plugin_container_manager_.set_host_widget(NULL);
+}
+
+void RenderWidgetHostViewGtk::Destroy() {
+ if (compositing_surface_ != gfx::kNullPluginWindow) {
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ manager->ReleasePermanentXID(compositing_surface_);
+ }
+
+ if (do_x_grab_) {
+ // Undo the X grab.
+ GdkDisplay* display = gtk_widget_get_display(parent_);
+ gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME);
+ gdk_display_keyboard_ungrab(display, GDK_CURRENT_TIME);
+ }
+
+ if (view_.get()) {
+ // If this is a popup or fullscreen widget, then we need to destroy the
+ // window that we created to hold it.
+ if (IsPopup() || is_fullscreen_) {
+ GtkWidget* window = gtk_widget_get_parent(view_.get());
+
+ ui::ActiveWindowWatcherX::RemoveObserver(this);
+
+ // Disconnect the destroy handler so that we don't try to shutdown twice.
+ if (is_fullscreen_)
+ g_signal_handler_disconnect(window, destroy_handler_id_);
+
+ gtk_widget_destroy(window);
+ }
+
+ // Remove |view_| from all containers now, so nothing else can hold a
+ // reference to |view_|'s widget except possibly a gtk signal handler if
+ // this code is currently executing within the context of a gtk signal
+ // handler. Note that |view_| is still alive after this call. It will be
+ // deallocated in the destructor.
+ // See http://crbug.com/11847 for details.
+ gtk_widget_destroy(view_.get());
+ }
+
+ // The RenderWidgetHost's destruction led here, so don't call it.
+ host_ = NULL;
+
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+void RenderWidgetHostViewGtk::SetTooltipText(const string16& tooltip_text) {
+ // Maximum number of characters we allow in a tooltip.
+ const int kMaxTooltipLength = 8 << 10;
+ // Clamp the tooltip length to kMaxTooltipLength so that we don't
+ // accidentally DOS the user with a mega tooltip (since GTK doesn't do
+ // this itself).
+ // I filed https://bugzilla.gnome.org/show_bug.cgi?id=604641 upstream.
+ const string16 clamped_tooltip =
+ ui::TruncateString(tooltip_text, kMaxTooltipLength);
+
+ if (clamped_tooltip.empty()) {
+ gtk_widget_set_has_tooltip(view_.get(), FALSE);
+ } else {
+ gtk_widget_set_tooltip_text(view_.get(),
+ UTF16ToUTF8(clamped_tooltip).c_str());
+ }
+}
+
+void RenderWidgetHostViewGtk::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
+
+ if (text.empty() || range.is_empty())
+ return;
+ size_t pos = range.GetMin() - offset;
+ size_t n = range.length();
+
+ DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
+ if (pos >= text.length()) {
+ NOTREACHED() << "The text can not cover range.";
+ return;
+ }
+
+ // Set the BUFFER_SELECTION to the ui::Clipboard.
+ ui::ScopedClipboardWriter clipboard_writer(
+ ui::Clipboard::GetForCurrentThread(),
+ ui::Clipboard::BUFFER_SELECTION);
+ clipboard_writer.WriteText(text.substr(pos, n));
+}
+
+void RenderWidgetHostViewGtk::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ im_context_->UpdateCaretBounds(
+ gfx::UnionRects(params.anchor_rect, params.focus_rect));
+}
+
+void RenderWidgetHostViewGtk::ScrollOffsetChanged() {
+}
+
+GdkEventButton* RenderWidgetHostViewGtk::GetLastMouseDown() {
+ return last_mouse_down_;
+}
+
+gfx::NativeView RenderWidgetHostViewGtk::BuildInputMethodsGtkMenu() {
+ return im_context_->BuildInputMethodsGtkMenu();
+}
+
+void RenderWidgetHostViewGtk::OnDestroy(GtkWidget* widget) {
+ DCHECK(is_fullscreen_);
+ host_->Shutdown();
+}
+
+bool RenderWidgetHostViewGtk::NeedsInputGrab() {
+ return popup_type_ == WebKit::WebPopupTypeSelect;
+}
+
+bool RenderWidgetHostViewGtk::IsPopup() const {
+ return popup_type_ != WebKit::WebPopupTypeNone;
+}
+
+void RenderWidgetHostViewGtk::DoSharedInit() {
+ view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
+ im_context_.reset(new GtkIMContextWrapper(this));
+ plugin_container_manager_.set_host_widget(view_.get());
+ key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
+}
+
+void RenderWidgetHostViewGtk::DoPopupOrFullscreenInit(GtkWindow* window,
+ const gfx::Rect& bounds) {
+ requested_size_.SetSize(std::min(bounds.width(), kMaxWindowWidth),
+ std::min(bounds.height(), kMaxWindowHeight));
+ host_->WasResized();
+
+ ui::ActiveWindowWatcherX::AddObserver(this);
+
+ // Don't set the size when we're going fullscreen. This can confuse the
+ // window manager into thinking we're resizing a fullscreen window and
+ // therefore not fullscreen anymore.
+ if (!is_fullscreen_) {
+ gtk_widget_set_size_request(
+ view_.get(), requested_size_.width(), requested_size_.height());
+
+ // Don't allow the window to be resized. This also forces the window to
+ // shrink down to the size of its child contents.
+ gtk_window_set_resizable(window, FALSE);
+ gtk_window_set_default_size(window, -1, -1);
+ gtk_window_move(window, bounds.x(), bounds.y());
+ }
+
+ gtk_widget_show_all(GTK_WIDGET(window));
+}
+
+BackingStore* RenderWidgetHostViewGtk::AllocBackingStore(
+ const gfx::Size& size) {
+ gint depth = gdk_visual_get_depth(gtk_widget_get_visual(view_.get()));
+ return new BackingStoreGtk(host_, size,
+ ui::GetVisualFromGtkWidget(view_.get()),
+ depth);
+}
+
+// NOTE: |output| is initialized with the size of |src_subrect|, and |dst_size|
+// is ignored on GTK.
+void RenderWidgetHostViewGtk::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& /* dst_size */,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ // Grab the snapshot from the renderer as that's the only reliable way to
+ // readback from the GPU for this platform right now.
+ GetRenderWidgetHost()->GetSnapshotFromRenderer(src_subrect, callback);
+}
+
+void RenderWidgetHostViewGtk::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ NOTIMPLEMENTED();
+ callback.Run(false);
+}
+
+bool RenderWidgetHostViewGtk::CanCopyToVideoFrame() const {
+ return false;
+}
+
+void RenderWidgetHostViewGtk::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ params.route_id, gpu_host_id, ack_params);
+ host_->FrameSwapped(params.latency_info);
+}
+
+void RenderWidgetHostViewGtk::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ params.route_id, gpu_host_id, ack_params);
+ host_->FrameSwapped(params.latency_info);
+}
+
+void RenderWidgetHostViewGtk::AcceleratedSurfaceSuspend() {
+}
+
+void RenderWidgetHostViewGtk::AcceleratedSurfaceRelease() {
+}
+
+bool RenderWidgetHostViewGtk::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ // TODO(jbates) Implement this so this view can use GetBackingStore for both
+ // software and GPU frames. Defaulting to false just makes GetBackingStore
+ // only useable for software frames.
+ return false;
+}
+
+void RenderWidgetHostViewGtk::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostViewBase::SetBackground(background);
+ Send(new ViewMsg_SetBackground(host_->GetRoutingID(), background));
+}
+
+void RenderWidgetHostViewGtk::ModifyEventForEdgeDragging(
+ GtkWidget* widget, GdkEventMotion* event) {
+ // If the widget is aligned with an edge of the monitor its on and the user
+ // attempts to drag past that edge we track the number of times it has
+ // occurred, so that we can force the widget to scroll when it otherwise
+ // would be unable to, by modifying the (x,y) position in the drag
+ // event that we forward on to webkit. If we get a move that's no longer a
+ // drag or a drag indicating the user is no longer at that edge we stop
+ // altering the drag events.
+ int new_dragged_at_horizontal_edge = 0;
+ int new_dragged_at_vertical_edge = 0;
+ // Used for checking the edges of the monitor. We cache the values to save
+ // roundtrips to the X server.
+ CR_DEFINE_STATIC_LOCAL(gfx::Size, drag_monitor_size, ());
+ if (event->state & GDK_BUTTON1_MASK) {
+ if (drag_monitor_size.IsEmpty()) {
+ // We can safely cache the monitor size for the duration of a drag.
+ GdkScreen* screen = gtk_widget_get_screen(widget);
+ int monitor =
+ gdk_screen_get_monitor_at_point(screen, event->x_root, event->y_root);
+ GdkRectangle geometry;
+ gdk_screen_get_monitor_geometry(screen, monitor, &geometry);
+ drag_monitor_size.SetSize(geometry.width, geometry.height);
+ }
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ // Check X and Y independently, as the user could be dragging into a corner.
+ if (event->x == 0 && event->x_root == 0) {
+ new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ - 1;
+ } else if (allocation.width - 1 == static_cast<gint>(event->x) &&
+ drag_monitor_size.width() - 1 == static_cast<gint>(event->x_root)) {
+ new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ + 1;
+ }
+
+ if (event->y == 0 && event->y_root == 0) {
+ new_dragged_at_vertical_edge = dragged_at_vertical_edge_ - 1;
+ } else if (allocation.height - 1 == static_cast<gint>(event->y) &&
+ drag_monitor_size.height() - 1 == static_cast<gint>(event->y_root)) {
+ new_dragged_at_vertical_edge = dragged_at_vertical_edge_ + 1;
+ }
+
+ event->x_root += new_dragged_at_horizontal_edge;
+ event->x += new_dragged_at_horizontal_edge;
+ event->y_root += new_dragged_at_vertical_edge;
+ event->y += new_dragged_at_vertical_edge;
+ } else {
+ // Clear whenever we get a non-drag mouse move.
+ drag_monitor_size.SetSize(0, 0);
+ }
+ dragged_at_horizontal_edge_ = new_dragged_at_horizontal_edge;
+ dragged_at_vertical_edge_ = new_dragged_at_vertical_edge;
+}
+
+void RenderWidgetHostViewGtk::Paint(const gfx::Rect& damage_rect) {
+ TRACE_EVENT0("ui::gtk", "RenderWidgetHostViewGtk::Paint");
+
+ // If the GPU process is rendering directly into the View,
+ // call the compositor directly.
+ RenderWidgetHostImpl* render_widget_host =
+ RenderWidgetHostImpl::From(GetRenderWidgetHost());
+ if (render_widget_host->is_accelerated_compositing_active()) {
+ host_->ScheduleComposite();
+ return;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(view_.get());
+ DCHECK(!about_to_validate_and_paint_);
+
+ invalid_rect_ = damage_rect;
+ about_to_validate_and_paint_ = true;
+
+ // If the size of our canvas is (0,0), then we don't want to block here. We
+ // are doing one of our first paints and probably have animations going on.
+ bool force_create = !host_->empty();
+ BackingStoreGtk* backing_store = static_cast<BackingStoreGtk*>(
+ host_->GetBackingStore(force_create));
+ // Calling GetBackingStore maybe have changed |invalid_rect_|...
+ about_to_validate_and_paint_ = false;
+
+ gfx::Rect paint_rect = gfx::Rect(0, 0, kMaxWindowWidth, kMaxWindowHeight);
+ paint_rect.Intersect(invalid_rect_);
+
+ if (backing_store) {
+ // Only render the widget if it is attached to a window; there's a short
+ // period where this object isn't attached to a window but hasn't been
+ // Destroy()ed yet and it receives paint messages...
+ if (window) {
+ backing_store->XShowRect(gfx::Point(0, 0),
+ paint_rect, ui::GetX11WindowFromGtkWidget(view_.get()));
+ }
+ if (!whiteout_start_time_.is_null()) {
+ base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
+ whiteout_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
+
+ // Reset the start time to 0 so that we start recording again the next
+ // time the backing store is NULL...
+ whiteout_start_time_ = base::TimeTicks();
+ }
+ if (!web_contents_switch_paint_time_.is_null()) {
+ base::TimeDelta web_contents_switch_paint_duration =
+ base::TimeTicks::Now() - web_contents_switch_paint_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
+ web_contents_switch_paint_duration);
+ // Reset web_contents_switch_paint_time_ to 0 so future tab selections are
+ // recorded.
+ web_contents_switch_paint_time_ = base::TimeTicks();
+ }
+ software_latency_info_.swap_timestamp = base::TimeTicks::HighResNow();
+ render_widget_host->FrameSwapped(software_latency_info_);
+ software_latency_info_.Clear();
+ } else {
+ if (window)
+ gdk_window_clear(window);
+ if (whiteout_start_time_.is_null())
+ whiteout_start_time_ = base::TimeTicks::Now();
+ }
+}
+
+void RenderWidgetHostViewGtk::ShowCurrentCursor() {
+ // The widget may not have a window. If that's the case, abort mission. This
+ // is the same issue as that explained above in Paint().
+ if (!gtk_widget_get_window(view_.get()))
+ return;
+
+ // TODO(port): WebKit bug https://bugs.webkit.org/show_bug.cgi?id=16388 is
+ // that calling gdk_window_set_cursor repeatedly is expensive. We should
+ // avoid it here where possible.
+ GdkCursor* gdk_cursor;
+ if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) {
+ // Use MOZ_CURSOR_SPINNING if we are showing the default cursor and
+ // the page is loading.
+ gdk_cursor = is_loading_ ? GetMozSpinningCursor() : NULL;
+ } else {
+ gdk_cursor = current_cursor_.GetNativeCursor();
+ }
+ gdk_window_set_cursor(gtk_widget_get_window(view_.get()), gdk_cursor);
+}
+
+void RenderWidgetHostViewGtk::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+}
+
+void RenderWidgetHostViewGtk::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+}
+
+
+void RenderWidgetHostViewGtk::OnAcceleratedCompositingStateChange() {
+ bool activated = host_->is_accelerated_compositing_active();
+ GtkPreserveWindow* widget = reinterpret_cast<GtkPreserveWindow*>(view_.get());
+
+ gtk_preserve_window_delegate_resize(widget, activated);
+}
+
+void RenderWidgetHostViewGtk::GetScreenInfo(WebScreenInfo* results) {
+ GdkWindow* gdk_window = gtk_widget_get_window(view_.get());
+ if (!gdk_window) {
+ GdkDisplay* display = gdk_display_get_default();
+ gdk_window = gdk_display_get_default_group(display);
+ }
+ if (!gdk_window)
+ return;
+ GetScreenInfoFromNativeWindow(gdk_window, results);
+}
+
+gfx::Rect RenderWidgetHostViewGtk::GetBoundsInRootWindow() {
+ GtkWidget* toplevel = gtk_widget_get_toplevel(view_.get());
+ if (!toplevel)
+ return GetViewBounds();
+
+ GdkRectangle frame_extents;
+ GdkWindow* gdk_window = gtk_widget_get_window(toplevel);
+ if (!gdk_window)
+ return GetViewBounds();
+
+ gdk_window_get_frame_extents(gdk_window, &frame_extents);
+ return gfx::Rect(frame_extents.x, frame_extents.y,
+ frame_extents.width, frame_extents.height);
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostViewGtk::GetCompositingSurface() {
+ if (compositing_surface_ == gfx::kNullPluginWindow) {
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ gfx::NativeViewId view_id = GetNativeViewId();
+
+ if (!manager->GetPermanentXIDForId(&compositing_surface_, view_id)) {
+ DLOG(ERROR) << "Can't find XID for view id " << view_id;
+ }
+ }
+ return gfx::GLSurfaceHandle(compositing_surface_, gfx::NATIVE_TRANSPORT);
+}
+
+bool RenderWidgetHostViewGtk::LockMouse() {
+ if (mouse_locked_)
+ return true;
+
+ mouse_locked_ = true;
+
+ // Release any current grab.
+ GtkWidget* current_grab_window = gtk_grab_get_current();
+ if (current_grab_window) {
+ gtk_grab_remove(current_grab_window);
+ LOG(WARNING) << "Locking Mouse with gdk_pointer_grab, "
+ << "but had to steal grab from another window";
+ }
+
+ GtkWidget* widget = view_.get();
+ GdkWindow* window = gtk_widget_get_window(widget);
+ GdkCursor* cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
+
+ GdkGrabStatus grab_status =
+ gdk_pointer_grab(window,
+ FALSE, // owner_events
+ static_cast<GdkEventMask>(
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK),
+ window, // confine_to
+ cursor,
+ GDK_CURRENT_TIME);
+
+ if (grab_status != GDK_GRAB_SUCCESS) {
+ LOG(WARNING) << "Failed to grab pointer for LockMouse. "
+ << "gdk_pointer_grab returned: " << grab_status;
+ mouse_locked_ = false;
+ return false;
+ }
+
+ // Clear the tooltip window.
+ SetTooltipText(string16());
+
+ // Ensure that the widget center location will be relevant for this mouse
+ // lock session. It is updated whenever the window geometry moves
+ // but may be out of date due to switching tabs.
+ MarkCachedWidgetCenterStale();
+
+ return true;
+}
+
+void RenderWidgetHostViewGtk::UnlockMouse() {
+ if (!mouse_locked_)
+ return;
+
+ mouse_locked_ = false;
+
+ GtkWidget* widget = view_.get();
+ GdkDisplay* display = gtk_widget_get_display(widget);
+ GdkScreen* screen = gtk_widget_get_screen(widget);
+ gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME);
+ gdk_display_warp_pointer(display, screen,
+ unlocked_global_mouse_position_.x(),
+ unlocked_global_mouse_position_.y());
+ mouse_is_being_warped_to_unlocked_position_ = true;
+
+ if (host_)
+ host_->LostMouseLock();
+}
+
+void RenderWidgetHostViewGtk::ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (!host_)
+ return;
+
+ EditCommands edit_commands;
+ if (!event.skip_in_browser &&
+ key_bindings_handler_->Match(event, &edit_commands)) {
+ Send(new InputMsg_SetEditCommandsForNextKeyEvent(
+ host_->GetRoutingID(), edit_commands));
+ NativeWebKeyboardEvent copy_event(event);
+ copy_event.match_edit_command = true;
+ host_->ForwardKeyboardEvent(copy_event);
+ return;
+ }
+
+ host_->ForwardKeyboardEvent(event);
+}
+
+bool RenderWidgetHostViewGtk::RetrieveSurrounding(std::string* text,
+ size_t* cursor_index) {
+ if (!selection_range_.IsValid())
+ return false;
+
+ size_t offset = selection_range_.GetMin() - selection_text_offset_;
+ DCHECK(offset <= selection_text_.length());
+
+ if (offset == selection_text_.length()) {
+ *text = UTF16ToUTF8(selection_text_);
+ *cursor_index = text->length();
+ return true;
+ }
+
+ *text = base::UTF16ToUTF8AndAdjustOffset(
+ base::StringPiece16(selection_text_), &offset);
+ if (offset == string16::npos) {
+ NOTREACHED() << "Invalid offset in UTF16 string.";
+ return false;
+ }
+ *cursor_index = offset;
+ return true;
+}
+
+void RenderWidgetHostViewGtk::set_last_mouse_down(GdkEventButton* event) {
+ GdkEventButton* temp = NULL;
+ if (event) {
+ temp = reinterpret_cast<GdkEventButton*>(
+ gdk_event_copy(reinterpret_cast<GdkEvent*>(event)));
+ }
+
+ if (last_mouse_down_)
+ gdk_event_free(reinterpret_cast<GdkEvent*>(last_mouse_down_));
+
+ last_mouse_down_ = temp;
+}
+
+void RenderWidgetHostViewGtk::MarkCachedWidgetCenterStale() {
+ widget_center_valid_ = false;
+ mouse_has_been_warped_to_new_center_ = false;
+}
+
+gfx::Point RenderWidgetHostViewGtk::GetWidgetCenter() {
+ if (widget_center_valid_)
+ return widget_center_;
+
+ GdkWindow* window = gtk_widget_get_window(view_.get());
+ gint window_x = 0;
+ gint window_y = 0;
+ gdk_window_get_origin(window, &window_x, &window_y);
+ gint window_w = gdk_window_get_width(window);
+ gint window_h = gdk_window_get_height(window);
+ widget_center_.SetPoint(window_x + window_w / 2,
+ window_y + window_h / 2);
+ widget_center_valid_ = true;
+ return widget_center_;
+}
+
+void RenderWidgetHostViewGtk::ModifyEventMovementAndCoords(
+ WebKit::WebMouseEvent* event) {
+ // Movement is computed by taking the difference of the new cursor position
+ // and the previous. Under mouse lock the cursor will be warped back to the
+ // center so that we are not limited by clipping boundaries.
+ // We do not measure movement as the delta from cursor to center because
+ // we may receive more mouse movement events before our warp has taken
+ // effect.
+ event->movementX = event->globalX - global_mouse_position_.x();
+ event->movementY = event->globalY - global_mouse_position_.y();
+
+ // While the cursor is being warped back to the unlocked position, suppress
+ // the movement member data.
+ if (mouse_is_being_warped_to_unlocked_position_) {
+ event->movementX = 0;
+ event->movementY = 0;
+ if (MovedToPoint(*event, unlocked_global_mouse_position_))
+ mouse_is_being_warped_to_unlocked_position_ = false;
+ }
+
+ global_mouse_position_.SetPoint(event->globalX, event->globalY);
+
+ // Under mouse lock, coordinates of mouse are locked to what they were when
+ // mouse lock was entered.
+ if (mouse_locked_) {
+ event->x = unlocked_mouse_position_.x();
+ event->y = unlocked_mouse_position_.y();
+ event->windowX = unlocked_mouse_position_.x();
+ event->windowY = unlocked_mouse_position_.y();
+ event->globalX = unlocked_global_mouse_position_.x();
+ event->globalY = unlocked_global_mouse_position_.y();
+ } else {
+ unlocked_mouse_position_.SetPoint(event->windowX, event->windowY);
+ unlocked_global_mouse_position_.SetPoint(event->globalX, event->globalY);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostView, public:
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewGtk(widget);
+}
+
+// static
+void RenderWidgetHostViewPort::GetDefaultScreenInfo(WebScreenInfo* results) {
+ GdkWindow* gdk_window =
+ gdk_display_get_default_group(gdk_display_get_default());
+ GetScreenInfoFromNativeWindow(gdk_window, results);
+}
+
+void RenderWidgetHostViewGtk::SetAccessibilityFocus(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetFocus(acc_obj_id);
+}
+
+void RenderWidgetHostViewGtk::AccessibilityDoDefaultAction(int acc_obj_id) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityDoDefaultAction(acc_obj_id);
+}
+
+void RenderWidgetHostViewGtk::AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToMakeVisible(acc_obj_id, subfocus);
+}
+
+void RenderWidgetHostViewGtk::AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) {
+ if (!host_)
+ return;
+
+ host_->AccessibilityScrollToPoint(acc_obj_id, point);
+}
+
+void RenderWidgetHostViewGtk::AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) {
+ if (!host_)
+ return;
+
+ host_->AccessibilitySetTextSelection(acc_obj_id, start_offset, end_offset);
+}
+
+gfx::Point RenderWidgetHostViewGtk::GetLastTouchEventLocation() const {
+ // Not needed on Linux.
+ return gfx::Point();
+}
+
+void RenderWidgetHostViewGtk::FatalAccessibilityTreeError() {
+ if (host_) {
+ host_->FatalAccessibilityTreeError();
+ SetBrowserAccessibilityManager(NULL);
+ } else {
+ CHECK(FALSE);
+ }
+}
+
+void RenderWidgetHostViewGtk::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ if (!GetBrowserAccessibilityManager()) {
+ GtkWidget* parent = gtk_widget_get_parent(view_.get());
+ SetBrowserAccessibilityManager(
+ new BrowserAccessibilityManagerGtk(
+ parent,
+ BrowserAccessibilityManagerGtk::GetEmptyDocument(),
+ this));
+ }
+ GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params);
+}
+
+AtkObject* RenderWidgetHostViewGtk::GetAccessible() {
+ if (!GetBrowserAccessibilityManager()) {
+ GtkWidget* parent = gtk_widget_get_parent(view_.get());
+ SetBrowserAccessibilityManager(
+ new BrowserAccessibilityManagerGtk(
+ parent,
+ BrowserAccessibilityManagerGtk::GetEmptyDocument(),
+ this));
+ }
+ BrowserAccessibilityGtk* root =
+ GetBrowserAccessibilityManager()->GetRoot()->ToBrowserAccessibilityGtk();
+
+ atk_object_set_role(root->GetAtkObject(), ATK_ROLE_HTML_CONTAINER);
+ return root->GetAtkObject();
+}
+
+void RenderWidgetHostViewGtk::OnCreatePluginContainer(
+ gfx::PluginWindowHandle id) {
+ plugin_container_manager_.CreatePluginContainer(id);
+}
+
+void RenderWidgetHostViewGtk::OnDestroyPluginContainer(
+ gfx::PluginWindowHandle id) {
+ plugin_container_manager_.DestroyPluginContainer(id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_gtk.h b/chromium/content/browser/renderer_host/render_widget_host_view_gtk.h
new file mode 100644
index 00000000000..9ff410a07b1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_gtk.h
@@ -0,0 +1,342 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
+
+#include <gdk/gdk.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/renderer_host/gtk_plugin_container_manager.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/content_export.h"
+#include "ipc/ipc_sender.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/slide_animation.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/gtk/gtk_signal_registrar.h"
+#include "ui/base/gtk/owned_widget_gtk.h"
+#include "ui/base/x/active_window_watcher_x_observer.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "webkit/common/cursors/webcursor.h"
+
+typedef struct _GtkClipboard GtkClipboard;
+typedef struct _GtkSelectionData GtkSelectionData;
+
+namespace content {
+class GtkIMContextWrapper;
+class GtkKeyBindingsHandler;
+class RenderWidgetHost;
+class RenderWidgetHostImpl;
+struct NativeWebKeyboardEvent;
+
+// -----------------------------------------------------------------------------
+// See comments in render_widget_host_view.h about this class and its members.
+// -----------------------------------------------------------------------------
+class CONTENT_EXPORT RenderWidgetHostViewGtk
+ : public RenderWidgetHostViewBase,
+ public BrowserAccessibilityDelegate,
+ public ui::ActiveWindowWatcherXObserver,
+ public IPC::Sender {
+ public:
+ virtual ~RenderWidgetHostViewGtk();
+
+ // RenderWidgetHostView implementation.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual GdkEventButton* GetLastMouseDown() OVERRIDE;
+ virtual gfx::NativeView BuildInputMethodsGtkMenu() OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+
+ // RenderWidgetHostViewPort implementation.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {}
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params)
+ OVERRIDE;
+
+ // ActiveWindowWatcherXObserver implementation.
+ virtual void ActiveWindowChanged(GdkWindow* active_window) OVERRIDE;
+
+ // IPC::Sender implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // If the widget is aligned with an edge of the monitor its on and the user
+ // attempts to drag past that edge we track the number of times it has
+ // occurred, so that we can force the widget to scroll when it otherwise
+ // would be unable to.
+ void ModifyEventForEdgeDragging(GtkWidget* widget, GdkEventMotion* event);
+
+ // Mouse events always provide a movementX/Y which needs to be computed.
+ // Also, mouse lock requires knowledge of last unlocked cursor coordinates.
+ // State is stored on the host view to do this, and the mouse event modified.
+ void ModifyEventMovementAndCoords(WebKit::WebMouseEvent* event);
+
+ void Paint(const gfx::Rect&);
+
+ // Called by GtkIMContextWrapper to forward a keyboard event to renderer.
+ // On Linux (not ChromeOS):
+ // Before calling RenderWidgetHost::ForwardKeyboardEvent(), this method
+ // calls GtkKeyBindingsHandler::Match() against the event and send matched
+ // edit commands to renderer by calling
+ // RenderWidgetHost::ForwardEditCommandsForNextKeyEvent().
+ void ForwardKeyboardEvent(const NativeWebKeyboardEvent& event);
+
+ bool RetrieveSurrounding(std::string* text, size_t* cursor_index);
+
+ // BrowserAccessibilityDelegate implementation.
+ virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) OVERRIDE;
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) OVERRIDE;
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) OVERRIDE;
+ virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE;
+ virtual void FatalAccessibilityTreeError() OVERRIDE;
+
+ // Get the root of the AtkObject* tree for accessibility.
+ AtkObject* GetAccessible();
+
+ protected:
+ friend class RenderWidgetHostView;
+
+ // Should construct only via RenderWidgetHostView::CreateViewForWidget.
+ explicit RenderWidgetHostViewGtk(RenderWidgetHost* widget);
+
+ private:
+ friend class RenderWidgetHostViewGtkWidget;
+
+ CHROMEGTK_CALLBACK_0(RenderWidgetHostViewGtk,
+ void,
+ OnDestroy);
+
+ // Returns whether the widget needs an input grab (GTK+ and X) to work
+ // properly.
+ bool NeedsInputGrab();
+
+ // Returns whether this render view is a popup (<select> dropdown or
+ // autocomplete window).
+ bool IsPopup() const;
+
+ // Do initialization needed by all InitAs*() methods.
+ void DoSharedInit();
+
+ // Do initialization needed just by InitAsPopup() and InitAsFullscreen().
+ // We move and resize |window| to |bounds| and show it and its contents.
+ void DoPopupOrFullscreenInit(GtkWindow* window, const gfx::Rect& bounds);
+
+ // Update the display cursor for the render view.
+ void ShowCurrentCursor();
+
+ void set_last_mouse_down(GdkEventButton* event);
+
+ // Cause the next query for the widget center to recompute the cached value.
+ void MarkCachedWidgetCenterStale();
+
+ void OnCreatePluginContainer(gfx::PluginWindowHandle id);
+ void OnDestroyPluginContainer(gfx::PluginWindowHandle id);
+
+ gfx::Point GetWidgetCenter();
+
+ // The model object.
+ RenderWidgetHostImpl* host_;
+
+ // The native UI widget.
+ ui::OwnedWidgetGtk view_;
+
+ // This is true when we are currently painting and thus should handle extra
+ // paint requests by expanding the invalid rect rather than actually
+ // painting.
+ bool about_to_validate_and_paint_;
+
+ // This is the rectangle which we'll paint.
+ gfx::Rect invalid_rect_;
+
+ // Whether or not this widget is hidden.
+ bool is_hidden_;
+
+ // Whether we are currently loading.
+ bool is_loading_;
+
+ // The cursor for the page. This is passed up from the renderer.
+ WebCursor current_cursor_;
+
+ // The time at which this view started displaying white pixels as a result of
+ // not having anything to paint (empty backing store from renderer). This
+ // value returns true for is_null() if we are not recording whiteout times.
+ base::TimeTicks whiteout_start_time_;
+
+ // The time it took after this view was selected for it to be fully painted.
+ base::TimeTicks web_contents_switch_paint_time_;
+
+ // The native view of our parent widget. Used only for popups.
+ GtkWidget* parent_;
+
+ // We ignore the first mouse release on popups so the popup will remain open.
+ bool is_popup_first_mouse_release_;
+
+ // Whether or not this widget's input context was focused before being
+ // shadowed by another widget. Used in OnGrabNotify() handler to track the
+ // focused state correctly.
+ bool was_imcontext_focused_before_grab_;
+
+ // True if we are responsible for creating an X grab. This will only be used
+ // for <select> dropdowns. It should be true for most such cases, but false
+ // for extension popups.
+ bool do_x_grab_;
+
+ // Is the widget fullscreen?
+ bool is_fullscreen_;
+
+ // Has the window ever been marked active? Only valid for fullscreen or
+ // popup windows.
+ bool made_active_;
+
+ // Used to record the last position of the mouse.
+ // While the mouse is locked, they store the last known position just as mouse
+ // lock was entered.
+ // Relative to the upper-left corner of the view.
+ gfx::Point unlocked_mouse_position_;
+ // Relative to the upper-left corner of the screen.
+ gfx::Point unlocked_global_mouse_position_;
+ // Last hidden cursor position. Relative to screen.
+ gfx::Point global_mouse_position_;
+ // Indicates when mouse motion is valid after the widget has moved.
+ bool mouse_has_been_warped_to_new_center_;
+ // Indicates the cursor has been warped to the unlocked position,
+ // but a move event has not yet been received for it there.
+ bool mouse_is_being_warped_to_unlocked_position_;
+
+ // For full-screen windows we have a OnDestroy handler that we need to remove,
+ // so we keep it ID here.
+ unsigned long destroy_handler_id_;
+
+ // A convenience wrapper object for GtkIMContext;
+ scoped_ptr<GtkIMContextWrapper> im_context_;
+
+ // A convenience object for handling editor key bindings defined in gtk
+ // keyboard theme.
+ scoped_ptr<GtkKeyBindingsHandler> key_bindings_handler_;
+
+ // Helper class that lets us allocate plugin containers and move them.
+ GtkPluginContainerManager plugin_container_manager_;
+
+ // The size that we want the renderer to be. We keep this in a separate
+ // variable because resizing in GTK+ is async.
+ gfx::Size requested_size_;
+
+ // The latest reported center of the widget, use GetWidgetCenter() to access.
+ gfx::Point widget_center_;
+ // If the window moves the widget_center will not be valid until we recompute.
+ bool widget_center_valid_;
+
+ // The number of times the user has dragged against horizontal edge of the
+ // monitor (if the widget is aligned with that edge). Negative values
+ // indicate the left edge, positive the right.
+ int dragged_at_horizontal_edge_;
+
+ // The number of times the user has dragged against vertical edge of the
+ // monitor (if the widget is aligned with that edge). Negative values
+ // indicate the top edge, positive the bottom.
+ int dragged_at_vertical_edge_;
+
+ gfx::PluginWindowHandle compositing_surface_;
+
+ // The event for the last mouse down we handled. We need this for context
+ // menus and drags.
+ GdkEventButton* last_mouse_down_;
+
+ // Instance of accessibility information for the root of the AtkObject
+ // tree representation of the WebKit render tree.
+ scoped_ptr<BrowserAccessibilityManager> browser_accessibility_manager_;
+
+ ui::GtkSignalRegistrar signals_;
+
+ ui::LatencyInfo software_latency_info_;
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_guest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_guest.cc
new file mode 100644
index 00000000000..097468fb848
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_guest.cc
@@ -0,0 +1,577 @@
+// 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 "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_guest.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/view_messages.h"
+#include "content/common/webplugin_geometry.h"
+#include "content/public/common/content_switches.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+
+#if defined(OS_MACOSX)
+#import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
+#endif
+
+#if defined(OS_WIN) || defined(USE_AURA)
+#include "content/browser/renderer_host/ui_events_helper.h"
+#endif
+
+namespace content {
+
+namespace {
+
+bool ShouldSendPinchGesture() {
+ static bool pinch_allowed =
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePinch);
+ return pinch_allowed;
+}
+
+WebKit::WebGestureEvent CreateFlingCancelEvent(double time_stamp) {
+ WebKit::WebGestureEvent gesture_event;
+ gesture_event.timeStampSeconds = time_stamp;
+ gesture_event.type = WebKit::WebGestureEvent::GestureFlingCancel;
+ gesture_event.sourceDevice = WebKit::WebGestureEvent::Touchscreen;
+ return gesture_event;
+}
+
+} // namespace
+
+RenderWidgetHostViewGuest::RenderWidgetHostViewGuest(
+ RenderWidgetHost* widget_host,
+ BrowserPluginGuest* guest,
+ RenderWidgetHostView* platform_view)
+ : host_(RenderWidgetHostImpl::From(widget_host)),
+ guest_(guest),
+ is_hidden_(false),
+ platform_view_(static_cast<RenderWidgetHostViewPort*>(platform_view)) {
+#if defined(OS_WIN) || defined(USE_AURA)
+ gesture_recognizer_.reset(ui::GestureRecognizer::Create(this));
+#endif // defined(OS_WIN) || defined(USE_AURA)
+ host_->SetView(this);
+}
+
+RenderWidgetHostViewGuest::~RenderWidgetHostViewGuest() {
+}
+
+RenderWidgetHost* RenderWidgetHostViewGuest::GetRenderWidgetHost() const {
+ return host_;
+}
+
+void RenderWidgetHostViewGuest::WasShown() {
+ if (!is_hidden_)
+ return;
+ is_hidden_ = false;
+ host_->WasShown();
+}
+
+void RenderWidgetHostViewGuest::WasHidden() {
+ if (is_hidden_)
+ return;
+ is_hidden_ = true;
+ host_->WasHidden();
+}
+
+void RenderWidgetHostViewGuest::SetSize(const gfx::Size& size) {
+ size_ = size;
+ host_->WasResized();
+}
+
+gfx::Rect RenderWidgetHostViewGuest::GetBoundsInRootWindow() {
+ // We do not have any root window specific parts in this view.
+ return GetViewBounds();
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostViewGuest::GetCompositingSurface() {
+ return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::TEXTURE_TRANSPORT);
+}
+
+#if defined(OS_WIN) || defined(USE_AURA)
+void RenderWidgetHostViewGuest::ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) {
+ // TODO(fsamuel): Currently we will only take this codepath if the guest has
+ // requested touch events. A better solution is to always forward touchpresses
+ // to the embedder process to target a BrowserPlugin, and then route all
+ // subsequent touch points of that touchdown to the appropriate guest until
+ // that touch point is released.
+ ScopedVector<ui::TouchEvent> events;
+ if (!MakeUITouchEventsFromWebTouchEvents(touch, &events, LOCAL_COORDINATES))
+ return;
+
+ ui::EventResult result = (ack_result ==
+ INPUT_EVENT_ACK_STATE_CONSUMED) ? ui::ER_HANDLED : ui::ER_UNHANDLED;
+ for (ScopedVector<ui::TouchEvent>::iterator iter = events.begin(),
+ end = events.end(); iter != end; ++iter) {
+ scoped_ptr<ui::GestureRecognizer::Gestures> gestures;
+ gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture(
+ *(*iter), result, this));
+ ProcessGestures(gestures.get());
+ }
+}
+#endif
+
+void RenderWidgetHostViewGuest::Show() {
+ WasShown();
+}
+
+void RenderWidgetHostViewGuest::Hide() {
+ WasHidden();
+}
+
+bool RenderWidgetHostViewGuest::IsShowing() {
+ return !is_hidden_;
+}
+
+gfx::Rect RenderWidgetHostViewGuest::GetViewBounds() const {
+ RenderWidgetHostViewPort* rwhv = static_cast<RenderWidgetHostViewPort*>(
+ guest_->GetEmbedderRenderWidgetHostView());
+ gfx::Rect embedder_bounds;
+ if (rwhv)
+ embedder_bounds = rwhv->GetViewBounds();
+ gfx::Rect shifted_rect = guest_->ToGuestRect(embedder_bounds);
+ shifted_rect.set_width(size_.width());
+ shifted_rect.set_height(size_.height());
+ return shifted_rect;
+}
+
+void RenderWidgetHostViewGuest::RenderProcessGone(
+ base::TerminationStatus status,
+ int error_code) {
+ platform_view_->RenderProcessGone(status, error_code);
+ // Destroy the guest view instance only, so we don't end up calling
+ // platform_view_->Destroy().
+ DestroyGuestView();
+}
+
+void RenderWidgetHostViewGuest::Destroy() {
+ // The RenderWidgetHost's destruction led here, so don't call it.
+ DestroyGuestView();
+
+ platform_view_->Destroy();
+}
+
+void RenderWidgetHostViewGuest::SetTooltipText(const string16& tooltip_text) {
+ platform_view_->SetTooltipText(tooltip_text);
+}
+
+void RenderWidgetHostViewGuest::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+ // If accelerated surface buffers are getting swapped then we're not using
+ // the software path.
+ guest_->clear_damage_buffer();
+ BrowserPluginMsg_BuffersSwapped_Params guest_params;
+ guest_params.size = params.size;
+ guest_params.mailbox_name = params.mailbox_name;
+ guest_params.route_id = params.route_id;
+ guest_params.host_id = gpu_host_id;
+ guest_->SendMessageToEmbedder(
+ new BrowserPluginMsg_BuffersSwapped(guest_->instance_id(), guest_params));
+}
+
+void RenderWidgetHostViewGuest::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewGuest::OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) {
+ if (frame->software_frame_data) {
+ cc::SoftwareFrameData* frame_data = frame->software_frame_data.get();
+#ifdef OS_WIN
+ base::SharedMemory shared_memory(frame_data->handle, true,
+ host_->GetProcess()->GetHandle());
+#else
+ base::SharedMemory shared_memory(frame_data->handle, true);
+#endif
+
+ RenderWidgetHostView* embedder_view =
+ guest_->GetEmbedderRenderWidgetHostView();
+ base::ProcessHandle embedder_pid =
+ embedder_view->GetRenderWidgetHost()->GetProcess()->GetHandle();
+
+ shared_memory.GiveToProcess(embedder_pid, &frame_data->handle);
+ }
+
+ guest_->clear_damage_buffer();
+ guest_->SendMessageToEmbedder(
+ new BrowserPluginMsg_CompositorFrameSwapped(
+ guest_->instance_id(),
+ *frame,
+ host_->GetRoutingID(),
+ output_surface_id,
+ host_->GetProcess()->GetID()));
+}
+
+void RenderWidgetHostViewGuest::SetBounds(const gfx::Rect& rect) {
+ SetSize(rect.size());
+}
+
+bool RenderWidgetHostViewGuest::OnMessageReceived(const IPC::Message& msg) {
+ return platform_view_->OnMessageReceived(msg);
+}
+
+void RenderWidgetHostViewGuest::InitAsChild(
+ gfx::NativeView parent_view) {
+ platform_view_->InitAsChild(parent_view);
+}
+
+void RenderWidgetHostViewGuest::InitAsPopup(
+ RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
+ // This should never get called.
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewGuest::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ // This should never get called.
+ NOTREACHED();
+}
+
+gfx::NativeView RenderWidgetHostViewGuest::GetNativeView() const {
+ return guest_->GetEmbedderRenderWidgetHostView()->GetNativeView();
+}
+
+gfx::NativeViewId RenderWidgetHostViewGuest::GetNativeViewId() const {
+ return guest_->GetEmbedderRenderWidgetHostView()->GetNativeViewId();
+}
+
+gfx::NativeViewAccessible RenderWidgetHostViewGuest::GetNativeViewAccessible() {
+ return guest_->GetEmbedderRenderWidgetHostView()->GetNativeViewAccessible();
+}
+
+void RenderWidgetHostViewGuest::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) {
+ platform_view_->MovePluginWindows(scroll_offset, moves);
+}
+
+void RenderWidgetHostViewGuest::Focus() {
+}
+
+void RenderWidgetHostViewGuest::Blur() {
+}
+
+bool RenderWidgetHostViewGuest::HasFocus() const {
+ return false;
+}
+
+bool RenderWidgetHostViewGuest::IsSurfaceAvailableForCopy() const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void RenderWidgetHostViewGuest::UpdateCursor(const WebCursor& cursor) {
+ platform_view_->UpdateCursor(cursor);
+}
+
+void RenderWidgetHostViewGuest::SetIsLoading(bool is_loading) {
+ platform_view_->SetIsLoading(is_loading);
+}
+
+void RenderWidgetHostViewGuest::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ RenderWidgetHostViewPort::FromRWHV(
+ guest_->GetEmbedderRenderWidgetHostView())->
+ TextInputTypeChanged(type, can_compose_inline, input_mode);
+}
+
+void RenderWidgetHostViewGuest::ImeCancelComposition() {
+ platform_view_->ImeCancelComposition();
+}
+
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+void RenderWidgetHostViewGuest::ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+}
+#endif
+
+void RenderWidgetHostViewGuest::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewGuest::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ platform_view_->SelectionChanged(text, offset, range);
+}
+
+void RenderWidgetHostViewGuest::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ platform_view_->SelectionBoundsChanged(params);
+}
+
+void RenderWidgetHostViewGuest::ScrollOffsetChanged() {
+}
+
+BackingStore* RenderWidgetHostViewGuest::AllocBackingStore(
+ const gfx::Size& size) {
+ NOTREACHED();
+ return NULL;
+}
+
+void RenderWidgetHostViewGuest::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& /* dst_size */,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ callback.Run(false, SkBitmap());
+}
+
+void RenderWidgetHostViewGuest::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ NOTIMPLEMENTED();
+ callback.Run(false);
+}
+
+bool RenderWidgetHostViewGuest::CanCopyToVideoFrame() const {
+ return false;
+}
+
+void RenderWidgetHostViewGuest::AcceleratedSurfaceSuspend() {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewGuest::AcceleratedSurfaceRelease() {
+}
+
+bool RenderWidgetHostViewGuest::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ return false;
+}
+
+void RenderWidgetHostViewGuest::SetBackground(const SkBitmap& background) {
+ platform_view_->SetBackground(background);
+}
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+void RenderWidgetHostViewGuest::SetClickthroughRegion(SkRegion* region) {
+}
+#endif
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible
+RenderWidgetHostViewGuest::AccessibleObjectFromChildId(long child_id) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+#endif
+
+void RenderWidgetHostViewGuest::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+ platform_view_->SetHasHorizontalScrollbar(has_horizontal_scrollbar);
+}
+
+void RenderWidgetHostViewGuest::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ platform_view_->SetScrollOffsetPinning(
+ is_pinned_to_left, is_pinned_to_right);
+}
+
+void RenderWidgetHostViewGuest::OnAcceleratedCompositingStateChange() {
+}
+
+bool RenderWidgetHostViewGuest::LockMouse() {
+ return platform_view_->LockMouse();
+}
+
+void RenderWidgetHostViewGuest::UnlockMouse() {
+ return platform_view_->UnlockMouse();
+}
+
+void RenderWidgetHostViewGuest::GetScreenInfo(WebKit::WebScreenInfo* results) {
+ RenderWidgetHostViewPort* embedder_view =
+ RenderWidgetHostViewPort::FromRWHV(
+ guest_->GetEmbedderRenderWidgetHostView());
+ embedder_view->GetScreenInfo(results);
+}
+
+void RenderWidgetHostViewGuest::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+}
+
+#if defined(OS_MACOSX)
+void RenderWidgetHostViewGuest::SetActive(bool active) {
+ platform_view_->SetActive(active);
+}
+
+void RenderWidgetHostViewGuest::SetTakesFocusOnlyOnMouseDown(bool flag) {
+ platform_view_->SetTakesFocusOnlyOnMouseDown(flag);
+}
+
+void RenderWidgetHostViewGuest::SetWindowVisibility(bool visible) {
+ platform_view_->SetWindowVisibility(visible);
+}
+
+void RenderWidgetHostViewGuest::WindowFrameChanged() {
+ platform_view_->WindowFrameChanged();
+}
+
+void RenderWidgetHostViewGuest::ShowDefinitionForSelection() {
+ gfx::Point origin;
+ gfx::Rect guest_bounds = GetViewBounds();
+ gfx::Rect embedder_bounds =
+ guest_->GetEmbedderRenderWidgetHostView()->GetViewBounds();
+
+ gfx::Vector2d guest_offset = gfx::Vector2d(
+ // Horizontal offset of guest from embedder.
+ guest_bounds.x() - embedder_bounds.x(),
+ // Vertical offset from guest's top to embedder's bottom edge.
+ embedder_bounds.bottom() - guest_bounds.y());
+
+ RenderWidgetHostViewMacDictionaryHelper helper(platform_view_);
+ helper.SetTargetView(guest_->GetEmbedderRenderWidgetHostView());
+ helper.set_offset(guest_offset);
+ helper.ShowDefinitionForSelection();
+}
+
+bool RenderWidgetHostViewGuest::SupportsSpeech() const {
+ return platform_view_->SupportsSpeech();
+}
+
+void RenderWidgetHostViewGuest::SpeakSelection() {
+ platform_view_->SpeakSelection();
+}
+
+bool RenderWidgetHostViewGuest::IsSpeaking() const {
+ return platform_view_->IsSpeaking();
+}
+
+void RenderWidgetHostViewGuest::StopSpeaking() {
+ platform_view_->StopSpeaking();
+}
+
+void RenderWidgetHostViewGuest::AboutToWaitForBackingStoreMsg() {
+ NOTREACHED();
+}
+
+bool RenderWidgetHostViewGuest::PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) {
+ return false;
+}
+
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_ANDROID)
+void RenderWidgetHostViewGuest::ShowDisambiguationPopup(
+ const gfx::Rect& target_rect,
+ const SkBitmap& zoomed_bitmap) {
+}
+
+void RenderWidgetHostViewGuest::HasTouchEventHandlers(bool need_touch_events) {
+}
+#endif // defined(OS_ANDROID)
+
+#if defined(TOOLKIT_GTK)
+GdkEventButton* RenderWidgetHostViewGuest::GetLastMouseDown() {
+ return NULL;
+}
+
+gfx::NativeView RenderWidgetHostViewGuest::BuildInputMethodsGtkMenu() {
+ return platform_view_->BuildInputMethodsGtkMenu();
+}
+#endif // defined(TOOLKIT_GTK)
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+void RenderWidgetHostViewGuest::WillWmDestroy() {
+}
+#endif
+
+#if defined(OS_WIN) && defined(USE_AURA)
+void RenderWidgetHostViewGuest::SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) {
+}
+#endif
+
+void RenderWidgetHostViewGuest::DestroyGuestView() {
+ host_->SetView(NULL);
+ host_ = NULL;
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+bool RenderWidgetHostViewGuest::DispatchLongPressGestureEvent(
+ ui::GestureEvent* event) {
+ return ForwardGestureEventToRenderer(event);
+}
+
+bool RenderWidgetHostViewGuest::DispatchCancelTouchEvent(
+ ui::TouchEvent* event) {
+ if (!host_)
+ return false;
+
+ WebKit::WebTouchEvent cancel_event;
+ cancel_event.type = WebKit::WebInputEvent::TouchCancel;
+ cancel_event.timeStampSeconds = event->time_stamp().InSecondsF();
+ host_->ForwardTouchEventWithLatencyInfo(cancel_event, *event->latency());
+ return true;
+}
+
+bool RenderWidgetHostViewGuest::ForwardGestureEventToRenderer(
+ ui::GestureEvent* gesture) {
+#if defined(OS_WIN) || defined(USE_AURA)
+ if (!host_)
+ return false;
+
+ // Pinch gestures are disabled by default on windows desktop. See
+ // crbug.com/128477 and crbug.com/148816
+ if ((gesture->type() == ui::ET_GESTURE_PINCH_BEGIN ||
+ gesture->type() == ui::ET_GESTURE_PINCH_UPDATE ||
+ gesture->type() == ui::ET_GESTURE_PINCH_END) &&
+ !ShouldSendPinchGesture()) {
+ return true;
+ }
+
+ WebKit::WebGestureEvent web_gesture =
+ MakeWebGestureEventFromUIEvent(*gesture);
+ const gfx::Point& client_point = gesture->location();
+ const gfx::Point& screen_point = gesture->location();
+
+ web_gesture.x = client_point.x();
+ web_gesture.y = client_point.y();
+ web_gesture.globalX = screen_point.x();
+ web_gesture.globalY = screen_point.y();
+
+ if (web_gesture.type == WebKit::WebGestureEvent::Undefined)
+ return false;
+ if (web_gesture.type == WebKit::WebGestureEvent::GestureTapDown) {
+ host_->ForwardGestureEvent(
+ CreateFlingCancelEvent(gesture->time_stamp().InSecondsF()));
+ }
+ host_->ForwardGestureEvent(web_gesture);
+ return true;
+#else
+ return false;
+#endif
+}
+
+void RenderWidgetHostViewGuest::ProcessGestures(
+ ui::GestureRecognizer::Gestures* gestures) {
+ if ((gestures == NULL) || gestures->empty())
+ return;
+ for (ui::GestureRecognizer::Gestures::iterator g_it = gestures->begin();
+ g_it != gestures->end();
+ ++g_it) {
+ ForwardGestureEventToRenderer(*g_it);
+ }
+}
+
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_guest.h b/chromium/content/browser/renderer_host/render_widget_host_view_guest.h
new file mode 100644
index 00000000000..15b0acc1a8b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_guest.h
@@ -0,0 +1,227 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GUEST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GUEST_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/content_export.h"
+#include "ui/base/events/event.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+#include "ui/base/gestures/gesture_types.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d_f.h"
+#include "webkit/common/cursors/webcursor.h"
+
+#if defined(TOOLKIT_GTK)
+#include "content/browser/renderer_host/gtk_plugin_container_manager.h"
+#endif // defined(TOOLKIT_GTK)
+
+namespace content {
+class RenderWidgetHost;
+class RenderWidgetHostImpl;
+class BrowserPluginGuest;
+struct NativeWebKeyboardEvent;
+
+// -----------------------------------------------------------------------------
+// See comments in render_widget_host_view.h about this class and its members.
+// This version is for the webview plugin which handles a lot of the
+// functionality in a diffent place and isn't platform specific.
+//
+// Some elements that are platform specific will be deal with by delegating
+// the relevant calls to the platform view.
+// -----------------------------------------------------------------------------
+class CONTENT_EXPORT RenderWidgetHostViewGuest
+ : public RenderWidgetHostViewBase,
+ public ui::GestureConsumer,
+ public ui::GestureEventHelper {
+ public:
+ RenderWidgetHostViewGuest(RenderWidgetHost* widget,
+ BrowserPluginGuest* guest,
+ RenderWidgetHostView* platform_view);
+ virtual ~RenderWidgetHostViewGuest();
+
+ // RenderWidgetHostView implementation.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+#if defined(OS_WIN) && !defined(USE_AURA)
+ virtual void SetClickthroughRegion(SkRegion* region) OVERRIDE;
+#endif
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual gfx::NativeViewAccessible AccessibleObjectFromChildId(long child_id)
+ OVERRIDE;
+#endif
+
+ // RenderWidgetHostViewPort implementation.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+ virtual void ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) OVERRIDE;
+#endif
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {}
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+#if defined(OS_WIN) || defined(USE_AURA)
+ virtual void ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE;
+#endif // defined(OS_WIN) || defined(USE_AURA)
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>&
+ params) OVERRIDE;
+
+#if defined(OS_MACOSX)
+ // RenderWidgetHostView implementation.
+ virtual void SetActive(bool active) OVERRIDE;
+ virtual void SetTakesFocusOnlyOnMouseDown(bool flag) OVERRIDE;
+ virtual void SetWindowVisibility(bool visible) OVERRIDE;
+ virtual void WindowFrameChanged() OVERRIDE;
+ virtual void ShowDefinitionForSelection() OVERRIDE;
+ virtual bool SupportsSpeech() const OVERRIDE;
+ virtual void SpeakSelection() OVERRIDE;
+ virtual bool IsSpeaking() const OVERRIDE;
+ virtual void StopSpeaking() OVERRIDE;
+
+ // RenderWidgetHostViewPort implementation.
+ virtual void AboutToWaitForBackingStoreMsg() OVERRIDE;
+ virtual bool PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_ANDROID)
+ // RenderWidgetHostViewPort implementation.
+ virtual void ShowDisambiguationPopup(const gfx::Rect& target_rect,
+ const SkBitmap& zoomed_bitmap) OVERRIDE;
+ virtual void HasTouchEventHandlers(bool need_touch_events) OVERRIDE;
+#endif // defined(OS_ANDROID)
+
+#if defined(TOOLKIT_GTK)
+ virtual GdkEventButton* GetLastMouseDown() OVERRIDE;
+ virtual gfx::NativeView BuildInputMethodsGtkMenu() OVERRIDE;
+#endif // defined(TOOLKIT_GTK)
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+ virtual void WillWmDestroy() OVERRIDE;
+#endif // defined(OS_WIN) && !defined(USE_AURA)
+
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual void SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) OVERRIDE;
+#endif
+
+ // Overridden from ui::GestureEventHelper.
+ virtual bool DispatchLongPressGestureEvent(ui::GestureEvent* event) OVERRIDE;
+ virtual bool DispatchCancelTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ protected:
+ friend class RenderWidgetHostView;
+
+ private:
+ // Destroys this view without calling |Destroy| on |platform_view_|.
+ void DestroyGuestView();
+
+ // Builds and forwards a WebKitGestureEvent to the renderer.
+ bool ForwardGestureEventToRenderer(ui::GestureEvent* gesture);
+
+ // Process all of the given gestures (passes them on to renderer)
+ void ProcessGestures(ui::GestureRecognizer::Gestures* gestures);
+
+ // The model object.
+ RenderWidgetHostImpl* host_;
+
+ BrowserPluginGuest *guest_;
+ bool is_hidden_;
+ gfx::Size size_;
+ // The platform view for this RenderWidgetHostView.
+ // RenderWidgetHostViewGuest mostly only cares about stuff related to
+ // compositing, the rest are directly forwared to this |platform_view_|.
+ RenderWidgetHostViewPort* platform_view_;
+#if defined(OS_WIN) || defined(USE_AURA)
+ scoped_ptr<ui::GestureRecognizer> gesture_recognizer_;
+#endif // defined(OS_WIN) || defined(USE_AURA)
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewGuest);
+};
+
+} // namespace content
+
+#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GUEST_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_guest_unittest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_guest_unittest.cc
new file mode 100644
index 00000000000..959f5e404a3
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_guest_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 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 "content/browser/renderer_host/render_widget_host_view_guest.h"
+
+#include "base/basictypes.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+class RenderWidgetHostViewGuestTest : public testing::Test {
+ public:
+ RenderWidgetHostViewGuestTest() {}
+
+ virtual void SetUp() {
+ browser_context_.reset(new TestBrowserContext);
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(browser_context_.get());
+ widget_host_ = new RenderWidgetHostImpl(
+ &delegate_, process_host, MSG_ROUTING_NONE);
+ view_ = new RenderWidgetHostViewGuest(
+ widget_host_, NULL, new TestRenderWidgetHostView(widget_host_));
+ }
+
+ virtual void TearDown() {
+ if (view_)
+ view_->Destroy();
+ delete widget_host_;
+
+ browser_context_.reset();
+
+ message_loop_.DeleteSoon(FROM_HERE, browser_context_.release());
+ message_loop_.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ scoped_ptr<BrowserContext> browser_context_;
+ MockRenderWidgetHostDelegate delegate_;
+
+ // Tests should set these to NULL if they've already triggered their
+ // destruction.
+ RenderWidgetHostImpl* widget_host_;
+ RenderWidgetHostViewGuest* view_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewGuestTest);
+};
+
+} // namespace
+
+TEST_F(RenderWidgetHostViewGuestTest, VisibilityTest) {
+ view_->Show();
+ ASSERT_TRUE(view_->IsShowing());
+
+ view_->Hide();
+ ASSERT_FALSE(view_->IsShowing());
+
+ view_->WasShown();
+ ASSERT_TRUE(view_->IsShowing());
+
+ view_->WasHidden();
+ ASSERT_FALSE(view_->IsShowing());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac.h b/chromium/content/browser/renderer_host/render_widget_host_view_mac.h
new file mode 100644
index 00000000000..6cbe6dc7b69
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -0,0 +1,565 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/accessibility/browser_accessibility_delegate_mac.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/edit_command.h"
+#import "content/public/browser/render_widget_host_view_mac_base.h"
+#include "ipc/ipc_sender.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "ui/base/cocoa/base_view.h"
+#include "webkit/common/cursors/webcursor.h"
+
+namespace content {
+class CompositingIOSurfaceMac;
+class CompositingIOSurfaceContext;
+class RenderWidgetHostViewMac;
+class RenderWidgetHostViewMacEditCommandHelper;
+}
+
+@class CompositingIOSurfaceLayer;
+@class FullscreenWindowManager;
+@protocol RenderWidgetHostViewMacDelegate;
+@class ToolTip;
+
+@protocol RenderWidgetHostViewMacOwner
+- (content::RenderWidgetHostViewMac*)renderWidgetHostViewMac;
+@end
+
+// This is the view that lives in the Cocoa view hierarchy. In Windows-land,
+// RenderWidgetHostViewWin is both the view and the delegate. We split the roles
+// but that means that the view needs to own the delegate and will dispose of it
+// when it's removed from the view system.
+@interface RenderWidgetHostViewCocoa
+ : BaseView <RenderWidgetHostViewMacBase,
+ RenderWidgetHostViewMacOwner,
+ NSTextInputClient,
+ BrowserAccessibilityDelegateCocoa> {
+ @private
+ scoped_ptr<content::RenderWidgetHostViewMac> renderWidgetHostView_;
+ NSObject<RenderWidgetHostViewMacDelegate>* delegate_; // weak
+ BOOL canBeKeyView_;
+ BOOL takesFocusOnlyOnMouseDown_;
+ BOOL closeOnDeactivate_;
+ scoped_ptr<content::RenderWidgetHostViewMacEditCommandHelper>
+ editCommand_helper_;
+
+ // These are part of the magic tooltip code from WebKit's WebHTMLView:
+ id trackingRectOwner_; // (not retained)
+ void* trackingRectUserData_;
+ NSTrackingRectTag lastToolTipTag_;
+ base::scoped_nsobject<NSString> toolTip_;
+
+ // Is YES if there was a mouse-down as yet unbalanced with a mouse-up.
+ BOOL hasOpenMouseDown_;
+
+ NSWindow* lastWindow_; // weak
+
+ // The cursor for the page. This is passed up from the renderer.
+ base::scoped_nsobject<NSCursor> currentCursor_;
+
+ // Variables used by our implementaion of the NSTextInput protocol.
+ // An input method of Mac calls the methods of this protocol not only to
+ // notify an application of its status, but also to retrieve the status of
+ // the application. That is, an application cannot control an input method
+ // directly.
+ // This object keeps the status of a composition of the renderer and returns
+ // it when an input method asks for it.
+ // We need to implement Objective-C methods for the NSTextInput protocol. On
+ // the other hand, we need to implement a C++ method for an IPC-message
+ // handler which receives input-method events from the renderer.
+
+ // Represents the input-method attributes supported by this object.
+ base::scoped_nsobject<NSArray> validAttributesForMarkedText_;
+
+ // Indicates if we are currently handling a key down event.
+ BOOL handlingKeyDown_;
+
+ // Indicates if there is any marked text.
+ BOOL hasMarkedText_;
+
+ // Indicates if unmarkText is called or not when handling a keyboard
+ // event.
+ BOOL unmarkTextCalled_;
+
+ // The range of current marked text inside the whole content of the DOM node
+ // being edited.
+ // TODO(suzhe): This is currently a fake value, as we do not support accessing
+ // the whole content yet.
+ NSRange markedRange_;
+
+ // The selected range, cached from a message sent by the renderer.
+ NSRange selectedRange_;
+
+ // Text to be inserted which was generated by handling a key down event.
+ string16 textToBeInserted_;
+
+ // Marked text which was generated by handling a key down event.
+ string16 markedText_;
+
+ // Underline information of the |markedText_|.
+ std::vector<WebKit::WebCompositionUnderline> underlines_;
+
+ // Indicates if doCommandBySelector method receives any edit command when
+ // handling a key down event.
+ BOOL hasEditCommands_;
+
+ // Contains edit commands received by the -doCommandBySelector: method when
+ // handling a key down event, not including inserting commands, eg. insertTab,
+ // etc.
+ content::EditCommands editCommands_;
+
+ // The plugin that currently has focus (-1 if no plugin has focus).
+ int focusedPluginIdentifier_;
+
+ // Whether or not plugin IME is currently enabled active.
+ BOOL pluginImeActive_;
+
+ // Whether the previous mouse event was ignored due to hitTest check.
+ BOOL mouseEventWasIgnored_;
+
+ // Event monitor for scroll wheel end event.
+ id endWheelMonitor_;
+
+ // OpenGL Support:
+
+ // recursive globalFrameDidChange protection:
+ BOOL handlingGlobalFrameDidChange_;
+
+ // The scale factor of the display this view is in.
+ float deviceScaleFactor_;
+
+ // If true then escape key down events are suppressed until the first escape
+ // key up event. (The up event is suppressed as well). This is used by the
+ // flash fullscreen code to avoid sending a key up event without a matching
+ // key down event.
+ BOOL suppressNextEscapeKeyUp_;
+}
+
+@property(nonatomic, readonly) NSRange selectedRange;
+@property(nonatomic, readonly) BOOL suppressNextEscapeKeyUp;
+
+- (void)setCanBeKeyView:(BOOL)can;
+- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b;
+- (void)setCloseOnDeactivate:(BOOL)b;
+- (void)setToolTipAtMousePoint:(NSString *)string;
+// True for always-on-top special windows (e.g. Balloons and Panels).
+- (BOOL)acceptsMouseEventsWhenInactive;
+// Cancel ongoing composition (abandon the marked text).
+- (void)cancelComposition;
+// Confirm ongoing composition.
+- (void)confirmComposition;
+// Enables or disables plugin IME.
+- (void)setPluginImeActive:(BOOL)active;
+// Updates the current plugin focus state.
+- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId;
+// Evaluates the event in the context of plugin IME, if plugin IME is enabled.
+// Returns YES if the event was handled.
+- (BOOL)postProcessEventForPluginIme:(NSEvent*)event;
+- (void)updateCursor:(NSCursor*)cursor;
+- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
+ actualRange:(NSRangePointer)actualRange;
+@end
+
+namespace content {
+class RenderWidgetHostImpl;
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac
+//
+// An object representing the "View" of a rendered web page. This object is
+// responsible for displaying the content of the web page, and integrating with
+// the Cocoa view system. It is the implementation of the RenderWidgetHostView
+// that the cross-platform RenderWidgetHost object uses
+// to display the data.
+//
+// Comment excerpted from render_widget_host.h:
+//
+// "The lifetime of the RenderWidgetHost* is tied to the render process.
+// If the render process dies, the RenderWidgetHost* goes away and all
+// references to it must become NULL."
+//
+// RenderWidgetHostView class hierarchy described in render_widget_host_view.h.
+class RenderWidgetHostViewMac : public RenderWidgetHostViewBase,
+ public IPC::Sender {
+ public:
+ virtual ~RenderWidgetHostViewMac();
+
+ RenderWidgetHostViewCocoa* cocoa_view() const { return cocoa_view_; }
+
+ CONTENT_EXPORT void SetDelegate(
+ NSObject<RenderWidgetHostViewMacDelegate>* delegate);
+ void SetAllowOverlappingViews(bool overlapping);
+
+ // RenderWidgetHostView implementation.
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void SetShowingContextMenu(bool showing) OVERRIDE;
+ virtual void SetActive(bool active) OVERRIDE;
+ virtual void SetTakesFocusOnlyOnMouseDown(bool flag) OVERRIDE;
+ virtual void SetWindowVisibility(bool visible) OVERRIDE;
+ virtual void WindowFrameChanged() OVERRIDE;
+ virtual void ShowDefinitionForSelection() OVERRIDE;
+ virtual bool SupportsSpeech() const OVERRIDE;
+ virtual void SpeakSelection() OVERRIDE;
+ virtual bool IsSpeaking() const OVERRIDE;
+ virtual void StopSpeaking() OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+
+ // Implementation of RenderWidgetHostViewPort.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual bool CanSubscribeFrame() const OVERRIDE;
+ virtual void BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) OVERRIDE;
+ virtual void EndFrameSubscription() OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params
+ ) OVERRIDE;
+ virtual bool PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void AboutToWaitForBackingStoreMsg() OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+
+ // IPC::Sender implementation.
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // Forwards the mouse event to the renderer.
+ void ForwardMouseEvent(const WebKit::WebMouseEvent& event);
+
+ void KillSelf();
+
+ void SetTextInputActive(bool active);
+
+ // Change this view to use CoreAnimation to draw.
+ void EnableCoreAnimation();
+
+ // Sends completed plugin IME notification and text back to the renderer.
+ void PluginImeCompositionCompleted(const string16& text, int plugin_id);
+
+ const std::string& selected_text() const { return selected_text_; }
+
+ // Update the IOSurface to be drawn and call setNeedsDisplay on
+ // |cocoa_view_|.
+ void CompositorSwapBuffers(uint64 surface_handle,
+ const gfx::Size& size,
+ float scale_factor,
+ const ui::LatencyInfo& latency_info);
+
+ // Draw the IOSurface by making its context current to this view.
+ bool DrawIOSurfaceWithoutCoreAnimation();
+
+ // Called when a GPU error is detected. Deletes all compositing state.
+ void GotAcceleratedCompositingError();
+
+ // Returns true and stores first rectangle for character range if the
+ // requested |range| is already cached, otherwise returns false.
+ // Exposed for testing.
+ CONTENT_EXPORT bool GetCachedFirstRectForCharacterRange(
+ NSRange range, NSRect* rect, NSRange* actual_range);
+
+ // Returns true if there is line break in |range| and stores line breaking
+ // point to |line_breaking_point|. The |line_break_point| is valid only if
+ // this function returns true.
+ bool GetLineBreakIndex(const std::vector<gfx::Rect>& bounds,
+ const ui::Range& range,
+ size_t* line_break_point);
+
+ // Returns composition character boundary rectangle. The |range| is
+ // composition based range. Also stores |actual_range| which is corresponding
+ // to actually used range for returned rectangle.
+ gfx::Rect GetFirstRectForCompositionRange(const ui::Range& range,
+ ui::Range* actual_range);
+
+ // Converts from given whole character range to composition oriented range. If
+ // the conversion failed, return ui::Range::InvalidRange.
+ ui::Range ConvertCharacterRangeToCompositionRange(
+ const ui::Range& request_range);
+
+ // These member variables should be private, but the associated ObjC class
+ // needs access to them and can't be made a friend.
+
+ // The associated Model. Can be NULL if Destroy() is called when
+ // someone (other than superview) has retained |cocoa_view_|.
+ RenderWidgetHostImpl* render_widget_host_;
+
+ // This is true when we are currently painting and thus should handle extra
+ // paint requests by expanding the invalid rect rather than actually painting.
+ bool about_to_validate_and_paint_;
+
+ // This is true when we have already scheduled a call to
+ // |-callSetNeedsDisplayInRect:| but it has not been fulfilled yet. Used to
+ // prevent us from scheduling multiple calls.
+ bool call_set_needs_display_in_rect_pending_;
+
+ // Whether last rendered frame was accelerated.
+ bool last_frame_was_accelerated_;
+
+ // The invalid rect that needs to be painted by callSetNeedsDisplayInRect.
+ // This value is only meaningful when
+ // |call_set_needs_display_in_rect_pending_| is true.
+ NSRect invalid_rect_;
+
+ // The time at which this view started displaying white pixels as a result of
+ // not having anything to paint (empty backing store from renderer). This
+ // value returns true for is_null() if we are not recording whiteout times.
+ base::TimeTicks whiteout_start_time_;
+
+ // The time it took after this view was selected for it to be fully painted.
+ base::TimeTicks web_contents_switch_paint_time_;
+
+ // Current text input type.
+ ui::TextInputType text_input_type_;
+ bool can_compose_inline_;
+
+ base::scoped_nsobject<CALayer> software_layer_;
+
+ // Accelerated compositing structures. These may be dynamically created and
+ // destroyed together in Create/DestroyCompositedIOSurfaceAndLayer.
+ base::scoped_nsobject<CompositingIOSurfaceLayer> compositing_iosurface_layer_;
+ scoped_ptr<CompositingIOSurfaceMac> compositing_iosurface_;
+ scoped_refptr<CompositingIOSurfaceContext> compositing_iosurface_context_;
+
+ // Whether to allow overlapping views.
+ bool allow_overlapping_views_;
+
+ // Whether to use the CoreAnimation path to draw content.
+ bool use_core_animation_;
+
+ ui::LatencyInfo software_latency_info_;
+
+ NSWindow* pepper_fullscreen_window() const {
+ return pepper_fullscreen_window_;
+ }
+
+ CONTENT_EXPORT void release_pepper_fullscreen_window_for_testing();
+
+ RenderWidgetHostViewMac* fullscreen_parent_host_view() const {
+ return fullscreen_parent_host_view_;
+ }
+
+ RenderWidgetHostViewFrameSubscriber* frame_subscriber() const {
+ return frame_subscriber_.get();
+ }
+
+ int window_number() const;
+
+ float scale_factor() const;
+
+ bool is_hidden() const { return is_hidden_; }
+
+ void FrameSwapped();
+
+ private:
+ friend class RenderWidgetHostView;
+ friend class RenderWidgetHostViewMacTest;
+
+ void GetVSyncParameters(
+ base::TimeTicks* timebase, base::TimeDelta* interval);
+
+ // The view will associate itself with the given widget. The native view must
+ // be hooked up immediately to the view hierarchy, or else when it is
+ // deleted it will delete this out from under the caller.
+ explicit RenderWidgetHostViewMac(RenderWidgetHost* widget);
+
+ // Returns whether this render view is a popup (autocomplete window).
+ bool IsPopup() const;
+
+ // Shuts down the render_widget_host_. This is a separate function so we can
+ // invoke it from the message loop.
+ void ShutdownHost();
+
+ bool CreateCompositedIOSurface();
+ bool CreateCompositedIOSurfaceLayer();
+ enum DestroyContextBehavior {
+ kLeaveContextBoundToView,
+ kDestroyContext,
+ };
+ void DestroyCompositedIOSurfaceAndLayer(DestroyContextBehavior
+ destroy_context_behavior);
+
+ // Unbind the GL context (if any) that is bound to |cocoa_view_|.
+ void ClearBoundContextDrawable();
+
+ // Called when a GPU SwapBuffers is received.
+ void GotAcceleratedFrame();
+
+ // Called when a software DIB is received.
+ void GotSoftwareFrame();
+
+ // Ack pending SwapBuffers requests, if any, to unblock the GPU process. Has
+ // no effect if there are no pending requests.
+ void AckPendingSwapBuffers();
+
+ // Ack pending SwapBuffers requests, but no more frequently than the vsync
+ // rate if the renderer is not throttling the swap rate.
+ void ThrottledAckPendingSwapBuffers();
+
+ void OnPluginFocusChanged(bool focused, int plugin_id);
+ void OnStartPluginIme();
+ CONTENT_EXPORT void OnAcceleratedSurfaceSetIOSurface(
+ gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ uint64 mach_port);
+ void OnAcceleratedSurfaceSetTransportDIB(gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ TransportDIB::Handle transport_dib);
+ void OnAcceleratedSurfaceBuffersSwapped(gfx::PluginWindowHandle window,
+ uint64 surface_handle);
+
+ // Convert |rect| from the views coordinate (upper-left origin) into
+ // the OpenGL coordinate (lower-left origin) and scale for HiDPI displays.
+ gfx::Rect GetScaledOpenGLPixelRect(const gfx::Rect& rect);
+
+ // The associated view. This is weak and is inserted into the view hierarchy
+ // to own this RenderWidgetHostViewMac object. Set to nil at the start of the
+ // destructor.
+ RenderWidgetHostViewCocoa* cocoa_view_;
+
+ // Indicates if the page is loading.
+ bool is_loading_;
+
+ // true if the View is not visible.
+ bool is_hidden_;
+
+ // The text to be shown in the tooltip, supplied by the renderer.
+ string16 tooltip_text_;
+
+ // Factory used to safely scope delayed calls to ShutdownHost().
+ base::WeakPtrFactory<RenderWidgetHostViewMac> weak_factory_;
+
+ // selected text on the renderer.
+ std::string selected_text_;
+
+ // The window used for popup widgets.
+ base::scoped_nsobject<NSWindow> popup_window_;
+
+ // The fullscreen window used for pepper flash.
+ base::scoped_nsobject<NSWindow> pepper_fullscreen_window_;
+ base::scoped_nsobject<FullscreenWindowManager> fullscreen_window_manager_;
+ // Our parent host view, if this is fullscreen. NULL otherwise.
+ RenderWidgetHostViewMac* fullscreen_parent_host_view_;
+
+ // List of pending swaps for deferred acking:
+ // pairs of (route_id, gpu_host_id).
+ std::list<std::pair<int32, int32> > pending_swap_buffers_acks_;
+
+ // Factory used to cancel outstanding throttled AckPendingSwapBuffers calls.
+ base::WeakPtrFactory<RenderWidgetHostViewMac>
+ pending_swap_buffers_acks_weak_factory_;
+
+ // The earliest time at which the next swap ack may be sent. Only relevant
+ // when swaps are not being throttled by the renderer (when threaded
+ // compositing is off).
+ base::Time next_swap_ack_time_;
+
+ // The current composition character range and its bounds.
+ ui::Range composition_range_;
+ std::vector<gfx::Rect> composition_bounds_;
+
+ // The current caret bounds.
+ gfx::Rect caret_rect_;
+
+ // Subscriber that listens to frame presentation events.
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> frame_subscriber_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac.mm b/chromium/content/browser/renderer_host/render_widget_host_view_mac.mm
new file mode 100644
index 00000000000..07448884b5d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -0,0 +1,3850 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+#import <objc/runtime.h>
+#include <QuartzCore/QuartzCore.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/mac/sdk_forward_declarations.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#import "content/browser/accessibility/browser_accessibility_cocoa.h"
+#include "content/browser/accessibility/browser_accessibility_manager_mac.h"
+#include "content/browser/renderer_host/backing_store_mac.h"
+#include "content/browser/renderer_host/backing_store_manager.h"
+#include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_mac.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+#import "content/browser/renderer_host/text_input_client_mac.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/edit_command.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/common/webplugin_geometry.h"
+#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#import "content/public/browser/render_widget_host_view_mac_delegate.h"
+#include "content/public/common/content_switches.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "third_party/WebKit/public/web/WebScreenInfo.h"
+#include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
+#import "third_party/mozilla/ComplexTextInputPanel.h"
+#include "ui/base/cocoa/animation_utils.h"
+#import "ui/base/cocoa/fullscreen_window_manager.h"
+#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gl/io_surface_support_mac.h"
+
+using content::BackingStoreMac;
+using content::BrowserAccessibility;
+using content::BrowserAccessibilityManager;
+using content::EditCommand;
+using content::NativeWebKeyboardEvent;
+using content::RenderViewHostImpl;
+using content::RenderWidgetHostImpl;
+using content::RenderWidgetHostViewMac;
+using content::RenderWidgetHostViewMacEditCommandHelper;
+using content::TextInputClientMac;
+using WebKit::WebInputEvent;
+using WebKit::WebInputEventFactory;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+
+enum CoreAnimationStatus {
+ CORE_ANIMATION_DISABLED,
+ CORE_ANIMATION_ENABLED_LAZY,
+ CORE_ANIMATION_ENABLED_ALWAYS,
+};
+
+static CoreAnimationStatus GetCoreAnimationStatus() {
+ // TODO(sail) Remove this.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseCoreAnimation)) {
+ return CORE_ANIMATION_DISABLED;
+ }
+ if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kUseCoreAnimation) == "lazy") {
+ return CORE_ANIMATION_ENABLED_LAZY;
+ }
+ if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kUseCoreAnimation) == "disabled") {
+ return CORE_ANIMATION_DISABLED;
+ }
+ return CORE_ANIMATION_ENABLED_ALWAYS;
+}
+
+// These are not documented, so use only after checking -respondsToSelector:.
+@interface NSApplication (UndocumentedSpeechMethods)
+- (void)speakString:(NSString*)string;
+- (void)stopSpeaking:(id)sender;
+- (BOOL)isSpeaking;
+@end
+
+// Declare things that are part of the 10.7 SDK.
+#if !defined(MAC_OS_X_VERSION_10_7) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
+@interface NSView (NSOpenGLSurfaceResolutionLionAPI)
+- (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
+@end
+
+static NSString* const NSWindowDidChangeBackingPropertiesNotification =
+ @"NSWindowDidChangeBackingPropertiesNotification";
+static NSString* const NSBackingPropertyOldScaleFactorKey =
+ @"NSBackingPropertyOldScaleFactorKey";
+// Note: Apple's example code (linked from the comment above
+// -windowDidChangeBackingProperties:) uses
+// @"NSWindowBackingPropertiesChangeOldBackingScaleFactorKey", but that always
+// returns an old scale of 0. @"NSBackingPropertyOldScaleFactorKey" seems to
+// work in practice, and it's what's used in Apple's WebKit port
+// (WebKit/mac/WebView/WebView.mm).
+
+#endif // 10.7
+
+static inline int ToWebKitModifiers(NSUInteger flags) {
+ int modifiers = 0;
+ if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey;
+ if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey;
+ if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey;
+ if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey;
+ return modifiers;
+}
+
+// This method will return YES for OS X versions 10.7.3 and later, and NO
+// otherwise.
+// Used to prevent a crash when building with the 10.7 SDK and accessing the
+// notification below. See: http://crbug.com/260595.
+static BOOL SupportsBackingPropertiesChangedNotification() {
+ // windowDidChangeBackingProperties: method has been added to the
+ // NSWindowDelegate protocol in 10.7.3, at the same time as the
+ // NSWindowDidChangeBackingPropertiesNotification notification was added.
+ // If the protocol contains this method description, the notification should
+ // be supported as well.
+ Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
+ struct objc_method_description methodDescription =
+ protocol_getMethodDescription(
+ windowDelegateProtocol,
+ @selector(windowDidChangeBackingProperties:),
+ NO,
+ YES);
+
+ // If the protocol does not contain the method, the returned method
+ // description is {NULL, NULL}
+ return methodDescription.name != NULL || methodDescription.types != NULL;
+}
+
+static float ScaleFactor(NSView* view) {
+ return ui::GetScaleFactorScale(ui::GetScaleFactorForNativeView(view));
+}
+
+// Private methods:
+@interface RenderWidgetHostViewCocoa ()
+@property(nonatomic, assign) NSRange selectedRange;
+@property(nonatomic, assign) NSRange markedRange;
+@property(nonatomic, assign)
+ NSObject<RenderWidgetHostViewMacDelegate>* delegate;
+
++ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
+- (void)gotUnhandledWheelEvent;
+- (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
+- (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
+- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
+- (void)windowDidChangeBackingProperties:(NSNotification*)notification;
+- (void)windowChangedGlobalFrame:(NSNotification*)notification;
+- (void)drawBackingStore:(BackingStoreMac*)backingStore
+ dirtyRect:(CGRect)dirtyRect
+ inContext:(CGContextRef)context;
+- (void)updateSoftwareLayerScaleFactor;
+- (void)checkForPluginImeCancellation;
+- (void)updateTabBackingStoreScaleFactor;
+@end
+
+// NSEvent subtype for scroll gestures events.
+static const short kIOHIDEventTypeScroll = 6;
+
+// A window subclass that allows the fullscreen window to become main and gain
+// keyboard focus. This is only used for pepper flash. Normal fullscreen is
+// handled by the browser.
+@interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
+@end
+
+@implementation PepperFlashFullscreenWindow
+
+- (BOOL)canBecomeKeyWindow {
+ return YES;
+}
+
+- (BOOL)canBecomeMainWindow {
+ return YES;
+}
+
+@end
+
+@interface RenderWidgetPopupWindow : NSWindow {
+ // The event tap that allows monitoring of all events, to properly close with
+ // a click outside the bounds of the window.
+ id clickEventTap_;
+}
+@end
+
+@implementation RenderWidgetPopupWindow
+
+- (id)initWithContentRect:(NSRect)contentRect
+ styleMask:(NSUInteger)windowStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)deferCreation {
+ if (self = [super initWithContentRect:contentRect
+ styleMask:windowStyle
+ backing:bufferingType
+ defer:deferCreation]) {
+ CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
+ [self setOpaque:NO];
+ [self setBackgroundColor:[NSColor clearColor]];
+ [self startObservingClicks];
+ }
+ return self;
+}
+
+- (void)close {
+ [self stopObservingClicks];
+ [super close];
+}
+
+// Gets called when the menubar is clicked.
+// Needed because the local event monitor doesn't see the click on the menubar.
+- (void)beganTracking:(NSNotification*)notification {
+ [self close];
+}
+
+// Install the callback.
+- (void)startObservingClicks {
+ clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
+ handler:^NSEvent* (NSEvent* event) {
+ if ([event window] == self)
+ return event;
+ NSEventType eventType = [event type];
+ if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
+ [self close];
+ return event;
+ }];
+
+ NSNotificationCenter* notificationCenter =
+ [NSNotificationCenter defaultCenter];
+ [notificationCenter addObserver:self
+ selector:@selector(beganTracking:)
+ name:NSMenuDidBeginTrackingNotification
+ object:[NSApp mainMenu]];
+}
+
+// Remove the callback.
+- (void)stopObservingClicks {
+ if (!clickEventTap_)
+ return;
+
+ [NSEvent removeMonitor:clickEventTap_];
+ clickEventTap_ = nil;
+
+ NSNotificationCenter* notificationCenter =
+ [NSNotificationCenter defaultCenter];
+ [notificationCenter removeObserver:self
+ name:NSMenuDidBeginTrackingNotification
+ object:[NSApp mainMenu]];
+}
+
+@end
+
+namespace {
+
+// Maximum number of characters we allow in a tooltip.
+const size_t kMaxTooltipLength = 1024;
+
+// TODO(suzhe): Upstream this function.
+WebKit::WebColor WebColorFromNSColor(NSColor *color) {
+ CGFloat r, g, b, a;
+ [color getRed:&r green:&g blue:&b alpha:&a];
+
+ return
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
+}
+
+// Extract underline information from an attributed string. Mostly copied from
+// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
+void ExtractUnderlines(
+ NSAttributedString* string,
+ std::vector<WebKit::WebCompositionUnderline>* underlines) {
+ int length = [[string string] length];
+ int i = 0;
+ while (i < length) {
+ NSRange range;
+ NSDictionary* attrs = [string attributesAtIndex:i
+ longestEffectiveRange:&range
+ inRange:NSMakeRange(i, length - i)];
+ if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
+ WebKit::WebColor color = SK_ColorBLACK;
+ if (NSColor *colorAttr =
+ [attrs objectForKey:NSUnderlineColorAttributeName]) {
+ color = WebColorFromNSColor(
+ [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
+ }
+ underlines->push_back(WebKit::WebCompositionUnderline(
+ range.location, NSMaxRange(range), color, [style intValue] > 1));
+ }
+ i = range.location + range.length;
+ }
+}
+
+// EnablePasswordInput() and DisablePasswordInput() are copied from
+// enableSecureTextInput() and disableSecureTextInput() functions in
+// third_party/WebKit/WebCore/platform/SecureTextInput.cpp
+// But we don't call EnableSecureEventInput() and DisableSecureEventInput()
+// here, because they are already called in webkit and they are system wide
+// functions.
+void EnablePasswordInput() {
+ CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
+ TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
+ sizeof(CFArrayRef), &inputSources);
+ CFRelease(inputSources);
+}
+
+void DisablePasswordInput() {
+ TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
+}
+
+// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
+// left of the primary screen (Carbon coordinates), and stuffs it into a
+// gfx::Rect.
+gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
+ gfx::Rect new_rect(NSRectToCGRect(rect));
+ if ([[NSScreen screens] count] > 0) {
+ new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
+ new_rect.y() - new_rect.height());
+ }
+ return new_rect;
+}
+
+// Returns the window that visually contains the given view. This is different
+// from [view window] in the case of tab dragging, where the view's owning
+// window is a floating panel attached to the actual browser window that the tab
+// is visually part of.
+NSWindow* ApparentWindowForView(NSView* view) {
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = [view window];
+
+ // See if this is a tab drag window. The width check is to distinguish that
+ // case from extension popup windows.
+ NSWindow* ancestor_window = [enclosing_window parentWindow];
+ if (ancestor_window && (NSWidth([enclosing_window frame]) ==
+ NSWidth([ancestor_window frame]))) {
+ enclosing_window = ancestor_window;
+ }
+
+ return enclosing_window;
+}
+
+WebKit::WebScreenInfo GetWebScreenInfo(NSView* view) {
+ gfx::Display display =
+ gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
+
+ NSScreen* screen = [NSScreen deepestScreen];
+
+ WebKit::WebScreenInfo results;
+
+ results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
+ results.depth = NSBitsPerPixelFromDepth([screen depth]);
+ results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
+ results.isMonochrome =
+ [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
+ results.rect = display.bounds();
+ results.availableRect = display.work_area();
+ return results;
+}
+
+} // namespace
+
+namespace content {
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostView, public:
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewMac(widget);
+}
+
+// static
+void RenderWidgetHostViewPort::GetDefaultScreenInfo(
+ WebKit::WebScreenInfo* results) {
+ *results = GetWebScreenInfo(NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac, public:
+
+RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
+ : render_widget_host_(RenderWidgetHostImpl::From(widget)),
+ about_to_validate_and_paint_(false),
+ call_set_needs_display_in_rect_pending_(false),
+ last_frame_was_accelerated_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
+ can_compose_inline_(true),
+ allow_overlapping_views_(false),
+ use_core_animation_(false),
+ is_loading_(false),
+ is_hidden_(false),
+ weak_factory_(this),
+ fullscreen_parent_host_view_(NULL),
+ pending_swap_buffers_acks_weak_factory_(this),
+ next_swap_ack_time_(base::Time::Now()) {
+ // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
+ // goes away. Since we autorelease it, our caller must put
+ // |GetNativeView()| into the view hierarchy right after calling us.
+ cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
+ initWithRenderWidgetHostViewMac:this] autorelease];
+
+ if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_ALWAYS) {
+ EnableCoreAnimation();
+ }
+
+ render_widget_host_->SetView(this);
+}
+
+RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
+ // This is being called from |cocoa_view_|'s destructor, so invalidate the
+ // pointer.
+ cocoa_view_ = nil;
+
+ AckPendingSwapBuffers();
+ UnlockMouse();
+
+ // Make sure that the layer doesn't reach into the now-invalid object.
+ DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
+ software_layer_.reset();
+
+ // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
+ // RenderWidgetHost does we need to tell it not to hold a stale pointer to
+ // us.
+ if (render_widget_host_)
+ render_widget_host_->SetView(NULL);
+}
+
+void RenderWidgetHostViewMac::SetDelegate(
+ NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
+ [cocoa_view_ setDelegate:delegate];
+}
+
+void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
+ if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY) {
+ if (overlapping) {
+ ScopedCAActionDisabler disabler;
+ EnableCoreAnimation();
+ return;
+ }
+ }
+
+ if (allow_overlapping_views_ == overlapping)
+ return;
+ allow_overlapping_views_ = overlapping;
+ [cocoa_view_ setNeedsDisplay:YES];
+ [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac, RenderWidgetHostView implementation:
+
+void RenderWidgetHostViewMac::EnableCoreAnimation() {
+ if (use_core_animation_)
+ return;
+
+ use_core_animation_ = true;
+
+ // Un-bind the GL context from this view because the CoreAnimation path will
+ // not use explicit setView and clearDrawable calls.
+ ClearBoundContextDrawable();
+
+ software_layer_.reset([[CALayer alloc] init]);
+ if (!software_layer_)
+ LOG(ERROR) << "Failed to create CALayer for software rendering";
+ [software_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
+ [software_layer_ setDelegate:cocoa_view_];
+ [software_layer_ setContentsGravity:kCAGravityTopLeft];
+ [software_layer_ setFrame:NSRectToCGRect([cocoa_view_ bounds])];
+ [software_layer_ setNeedsDisplay];
+ [cocoa_view_ updateSoftwareLayerScaleFactor];
+
+ [cocoa_view_ setLayer:software_layer_];
+ [cocoa_view_ setWantsLayer:YES];
+
+ if (compositing_iosurface_) {
+ if (!CreateCompositedIOSurfaceLayer()) {
+ LOG(ERROR) << "Failed to create CALayer for existing IOSurface";
+ GotAcceleratedCompositingError();
+ return;
+ }
+ }
+}
+
+bool RenderWidgetHostViewMac::CreateCompositedIOSurface() {
+ if (compositing_iosurface_context_ && compositing_iosurface_)
+ return true;
+
+ ScopedCAActionDisabler disabler;
+
+ // Create the GL context and shaders.
+ if (!compositing_iosurface_context_) {
+ compositing_iosurface_context_ =
+ CompositingIOSurfaceContext::Get(window_number());
+ if (!compositing_iosurface_context_) {
+ LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
+ return false;
+ }
+ }
+ // Create the IOSurface texture.
+ if (!compositing_iosurface_) {
+ compositing_iosurface_.reset(CompositingIOSurfaceMac::Create(
+ compositing_iosurface_context_));
+ if (!compositing_iosurface_) {
+ LOG(ERROR) << "Failed to create CompositingIOSurface";
+ return false;
+ }
+ }
+ // Make sure that the IOSurface is updated to use the context that is owned
+ // by the view.
+ compositing_iosurface_->SetContext(compositing_iosurface_context_);
+
+ return true;
+}
+
+bool RenderWidgetHostViewMac::CreateCompositedIOSurfaceLayer() {
+ CHECK(compositing_iosurface_context_ && compositing_iosurface_);
+ if (compositing_iosurface_layer_ || !use_core_animation_)
+ return true;
+
+ ScopedCAActionDisabler disabler;
+
+ // Create the GL CoreAnimation layer.
+ if (!compositing_iosurface_layer_) {
+ compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
+ initWithRenderWidgetHostViewMac:this]);
+ if (!compositing_iosurface_layer_) {
+ LOG(ERROR) << "Failed to create CALayer for IOSurface";
+ return false;
+ }
+ [software_layer_ addSublayer:compositing_iosurface_layer_];
+ }
+
+ // Creating the CompositingIOSurfaceLayer may attempt to draw in setLayer,
+ // which, if it fails, will promptly tear down everything that was just
+ // created. If that happened, return failure.
+ return compositing_iosurface_context_ &&
+ compositing_iosurface_ &&
+ (compositing_iosurface_layer_ || !use_core_animation_);
+}
+
+void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
+ DestroyContextBehavior destroy_context_behavior) {
+ ScopedCAActionDisabler disabler;
+
+ compositing_iosurface_.reset();
+ if (compositing_iosurface_layer_) {
+ [software_layer_ setNeedsDisplay];
+ [compositing_iosurface_layer_ removeFromSuperlayer];
+ [compositing_iosurface_layer_ disableCompositing];
+ compositing_iosurface_layer_.reset();
+ }
+ switch (destroy_context_behavior) {
+ case kLeaveContextBoundToView:
+ break;
+ case kDestroyContext:
+ ClearBoundContextDrawable();
+ compositing_iosurface_context_ = NULL;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
+ if (compositing_iosurface_context_ &&
+ cocoa_view_ &&
+ [[compositing_iosurface_context_->nsgl_context() view]
+ isEqual:cocoa_view_]) {
+ [compositing_iosurface_context_->nsgl_context() clearDrawable];
+ }
+}
+
+bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void RenderWidgetHostViewMac::InitAsChild(
+ gfx::NativeView parent_view) {
+}
+
+void RenderWidgetHostViewMac::InitAsPopup(
+ RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) {
+ bool activatable = popup_type_ == WebKit::WebPopupTypeNone;
+ [cocoa_view_ setCloseOnDeactivate:YES];
+ [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
+
+ NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
+ if ([[NSScreen screens] count] > 0) {
+ origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
+ pos.height() - origin_global.y;
+ }
+
+ popup_window_.reset([[RenderWidgetPopupWindow alloc]
+ initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
+ pos.width(), pos.height())
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ [popup_window_ setLevel:NSPopUpMenuWindowLevel];
+ [popup_window_ setReleasedWhenClosed:NO];
+ [popup_window_ makeKeyAndOrderFront:nil];
+ [[popup_window_ contentView] addSubview:cocoa_view_];
+ [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
+ [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:cocoa_view_
+ selector:@selector(popupWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:popup_window_];
+}
+
+// This function creates the fullscreen window and hides the dock and menubar if
+// necessary. Note, this codepath is only used for pepper flash when
+// pp::FlashFullScreen::SetFullscreen() is called. If
+// pp::FullScreen::SetFullscreen() is called then the entire browser window
+// will enter fullscreen instead.
+void RenderWidgetHostViewMac::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ fullscreen_parent_host_view_ =
+ static_cast<RenderWidgetHostViewMac*>(reference_host_view);
+ NSWindow* parent_window = nil;
+ if (reference_host_view)
+ parent_window = [reference_host_view->GetNativeView() window];
+ NSScreen* screen = [parent_window screen];
+ if (!screen)
+ screen = [NSScreen mainScreen];
+
+ pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
+ initWithContentRect:[screen frame]
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
+ [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
+ [cocoa_view_ setCanBeKeyView:YES];
+ [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
+ [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ // If the pepper fullscreen window isn't opaque then there are performance
+ // issues when it's on the discrete GPU and the Chrome window is being drawn
+ // to. http://crbug.com/171911
+ [pepper_fullscreen_window_ setOpaque:YES];
+
+ // Note that this forms a reference cycle between the fullscreen window and
+ // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
+ // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
+ // This cycle is normally broken when -keyEvent: receives an <esc> key, which
+ // explicitly calls Shutdown on the render_widget_host_, which calls
+ // Destroy() on RWHVMac, which drops the reference to
+ // pepper_fullscreen_window_.
+ [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
+
+ // Note that this keeps another reference to pepper_fullscreen_window_.
+ fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
+ initWithWindow:pepper_fullscreen_window_.get()
+ desiredScreen:screen]);
+ [fullscreen_window_manager_ enterFullscreenMode];
+ [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
+}
+
+void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
+ // See comment in InitAsFullscreen(): There is a reference cycle between
+ // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
+ // Tests that test pepper fullscreen mode without sending an <esc> event
+ // need to call this method to break the reference cycle.
+ [fullscreen_window_manager_ exitFullscreenMode];
+ fullscreen_window_manager_.reset();
+ [pepper_fullscreen_window_ close];
+ pepper_fullscreen_window_.reset();
+}
+
+int RenderWidgetHostViewMac::window_number() const {
+ NSWindow* window = [cocoa_view_ window];
+ if (!window)
+ return -1;
+ return [window windowNumber];
+}
+
+float RenderWidgetHostViewMac::scale_factor() const {
+ return ScaleFactor(cocoa_view_);
+}
+
+RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
+ return render_widget_host_;
+}
+
+void RenderWidgetHostViewMac::WasShown() {
+ if (!is_hidden_)
+ return;
+
+ if (web_contents_switch_paint_time_.is_null())
+ web_contents_switch_paint_time_ = base::TimeTicks::Now();
+ is_hidden_ = false;
+ render_widget_host_->WasShown();
+
+ // We're messing with the window, so do this to ensure no flashes.
+ if (!use_core_animation_)
+ [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
+
+ [compositing_iosurface_layer_ setNeedsDisplay];
+}
+
+void RenderWidgetHostViewMac::WasHidden() {
+ if (is_hidden_)
+ return;
+
+ // Send ACKs for any pending SwapBuffers (if any) since we won't be displaying
+ // them and the GPU process is waiting.
+ AckPendingSwapBuffers();
+
+ // If we receive any more paint messages while we are hidden, we want to
+ // ignore them so we don't re-allocate the backing store. We will paint
+ // everything again when we become selected again.
+ is_hidden_ = true;
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ render_widget_host_->WasHidden();
+
+ // There can be a transparent flash as this view is removed and the next is
+ // added, because of OSX windowing races between displaying the contents of
+ // the NSView and its corresponding OpenGL context.
+ // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
+ // screen updates until the next tab draws.
+ if (!use_core_animation_)
+ [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
+
+ web_contents_switch_paint_time_ = base::TimeTicks();
+}
+
+void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
+ gfx::Rect rect = GetViewBounds();
+ rect.set_size(size);
+ SetBounds(rect);
+}
+
+void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
+ // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
+ // TODO(thakis): fix, http://crbug.com/73362
+ if (is_hidden_)
+ return;
+
+ // During the initial creation of the RenderWidgetHostView in
+ // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
+ // an empty size. In the Windows code flow, it is not ignored because
+ // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
+ // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
+ // flags to keep things sized properly. On the other hand, if the size is not
+ // empty then this is a valid request for a pop-up.
+ if (rect.size().IsEmpty())
+ return;
+
+ // Ignore the position of |rect| for non-popup rwhvs. This is because
+ // background tabs do not have a window, but the window is required for the
+ // coordinate conversions. Popups are always for a visible tab.
+ if (IsPopup()) {
+ // The position of |rect| is screen coordinate system and we have to
+ // consider Cocoa coordinate system is upside-down and also multi-screen.
+ NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
+ NSSize size = NSMakeSize(rect.width(), rect.height());
+ size = [cocoa_view_ convertSize:size toView:nil];
+ if ([[NSScreen screens] count] > 0) {
+ NSScreen* screen =
+ static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
+ origin_global.y =
+ NSHeight([screen frame]) - size.height - origin_global.y;
+ }
+ [popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
+ size.width, size.height)
+ display:YES];
+ } else {
+ DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
+ BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
+ gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
+ rect2.set_width(rect.width());
+ rect2.set_height(rect.height());
+ [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
+ }
+}
+
+gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
+ return cocoa_view_;
+}
+
+gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
+ return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
+}
+
+gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
+ NOTIMPLEMENTED();
+ return static_cast<gfx::NativeViewAccessible>(NULL);
+}
+
+void RenderWidgetHostViewMac::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) {
+ // Must be overridden, but unused on this platform. Core Animation
+ // plugins are drawn by the GPU process (through the compositor),
+ // and Core Graphics plugins are drawn by the renderer process.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+void RenderWidgetHostViewMac::Focus() {
+ [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
+}
+
+void RenderWidgetHostViewMac::Blur() {
+ UnlockMouse();
+ [[cocoa_view_ window] makeFirstResponder:nil];
+}
+
+bool RenderWidgetHostViewMac::HasFocus() const {
+ return [[cocoa_view_ window] firstResponder] == cocoa_view_;
+}
+
+bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
+ return !!render_widget_host_->GetBackingStore(false) ||
+ (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
+}
+
+void RenderWidgetHostViewMac::Show() {
+ [cocoa_view_ setHidden:NO];
+
+ WasShown();
+}
+
+void RenderWidgetHostViewMac::Hide() {
+ [cocoa_view_ setHidden:YES];
+
+ WasHidden();
+}
+
+bool RenderWidgetHostViewMac::IsShowing() {
+ return ![cocoa_view_ isHidden];
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
+ NSRect bounds = [cocoa_view_ bounds];
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
+ if (!enclosing_window)
+ return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
+
+ bounds = [cocoa_view_ convertRect:bounds toView:nil];
+ bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
+ return FlipNSRectToRectScreen(bounds);
+}
+
+void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
+ WebCursor web_cursor = cursor;
+ [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
+}
+
+void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ // If we ever decide to show the waiting cursor while the page is loading
+ // like Chrome does on Windows, call |UpdateCursor()| here.
+}
+
+void RenderWidgetHostViewMac::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ if (text_input_type_ != type
+ || can_compose_inline_ != can_compose_inline) {
+ text_input_type_ = type;
+ can_compose_inline_ = can_compose_inline;
+ if (HasFocus()) {
+ SetTextInputActive(true);
+
+ // Let AppKit cache the new input context to make IMEs happy.
+ // See http://crbug.com/73039.
+ [NSApp updateWindows];
+
+#ifndef __LP64__
+ UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
+#endif
+ }
+ }
+}
+
+void RenderWidgetHostViewMac::ImeCancelComposition() {
+ [cocoa_view_ cancelComposition];
+}
+
+void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+ // The RangeChanged message is only sent with valid values. The current
+ // caret position (start == end) will be sent if there is no IME range.
+ [cocoa_view_ setMarkedRange:range.ToNSRange()];
+ composition_range_ = range;
+ composition_bounds_ = character_bounds;
+}
+
+void RenderWidgetHostViewMac::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ GotSoftwareFrame();
+
+ software_latency_info_.MergeWith(latency_info);
+
+ if (!is_hidden_) {
+ std::vector<gfx::Rect> rects(copy_rects);
+
+ // Because the findbar might be open, we cannot use scrollRect:by: here. For
+ // now, simply mark all of scroll rect as dirty.
+ if (!scroll_rect.IsEmpty())
+ rects.push_back(scroll_rect);
+
+ for (size_t i = 0; i < rects.size(); ++i) {
+ NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
+
+ if (about_to_validate_and_paint_) {
+ // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
+ // We're in the middle of executing a -drawRect:, and as soon as it
+ // returns Cocoa will clear its record of what needs display. We
+ // instead use |performSelector:| to call |setNeedsDisplayInRect:|
+ // after returning to the main loop, at which point |drawRect:| is no
+ // longer on the stack.
+ DCHECK([NSThread isMainThread]);
+ if (!call_set_needs_display_in_rect_pending_) {
+ [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
+ withObject:nil
+ afterDelay:0];
+ call_set_needs_display_in_rect_pending_ = true;
+ invalid_rect_ = ns_rect;
+ } else {
+ // The old invalid rect is probably invalid now, since the view has
+ // most likely been resized, but there's no harm in dirtying the
+ // union. In the limit, this becomes equivalent to dirtying the
+ // whole view.
+ invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
+ }
+ } else {
+ [cocoa_view_ setNeedsDisplayInRect:ns_rect];
+ }
+ }
+
+ if (!about_to_validate_and_paint_)
+ [cocoa_view_ displayIfNeeded];
+ }
+}
+
+void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
+ int error_code) {
+ Destroy();
+}
+
+void RenderWidgetHostViewMac::Destroy() {
+ AckPendingSwapBuffers();
+
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:cocoa_view_
+ name:NSWindowWillCloseNotification
+ object:popup_window_];
+
+ // We've been told to destroy.
+ [cocoa_view_ retain];
+ [cocoa_view_ removeFromSuperview];
+ [cocoa_view_ autorelease];
+
+ [popup_window_ close];
+ popup_window_.autorelease();
+
+ [fullscreen_window_manager_ exitFullscreenMode];
+ fullscreen_window_manager_.reset();
+ [pepper_fullscreen_window_ close];
+
+ // This can be called as part of processing the window's responder
+ // chain, for instance |-performKeyEquivalent:|. In that case the
+ // object needs to survive until the stack unwinds.
+ pepper_fullscreen_window_.autorelease();
+
+ // We get this call just before |render_widget_host_| deletes
+ // itself. But we are owned by |cocoa_view_|, which may be retained
+ // by some other code. Examples are WebContentsViewMac's
+ // |latent_focus_view_| and TabWindowController's
+ // |cachedContentView_|.
+ render_widget_host_ = NULL;
+}
+
+// Called from the renderer to tell us what the tooltip text should be. It
+// calls us frequently so we need to cache the value to prevent doing a lot
+// of repeat work.
+void RenderWidgetHostViewMac::SetTooltipText(const string16& tooltip_text) {
+ if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
+ tooltip_text_ = tooltip_text;
+
+ // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
+ // Windows; we're just trying to be polite. Don't persist the trimmed
+ // string, as then the comparison above will always fail and we'll try to
+ // set it again every single time the mouse moves.
+ string16 display_text = tooltip_text_;
+ if (tooltip_text_.length() > kMaxTooltipLength)
+ display_text = tooltip_text_.substr(0, kMaxTooltipLength);
+
+ NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
+ [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
+ }
+}
+
+bool RenderWidgetHostViewMac::SupportsSpeech() const {
+ return [NSApp respondsToSelector:@selector(speakString:)] &&
+ [NSApp respondsToSelector:@selector(stopSpeaking:)];
+}
+
+void RenderWidgetHostViewMac::SpeakSelection() {
+ if ([NSApp respondsToSelector:@selector(speakString:)])
+ [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
+}
+
+bool RenderWidgetHostViewMac::IsSpeaking() const {
+ return [NSApp respondsToSelector:@selector(isSpeaking)] &&
+ [NSApp isSpeaking];
+}
+
+void RenderWidgetHostViewMac::StopSpeaking() {
+ if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
+ [NSApp stopSpeaking:cocoa_view_];
+}
+
+//
+// RenderWidgetHostViewCocoa uses the stored selection text,
+// which implements NSServicesRequests protocol.
+//
+void RenderWidgetHostViewMac::SelectionChanged(const string16& text,
+ size_t offset,
+ const ui::Range& range) {
+ if (range.is_empty() || text.empty()) {
+ selected_text_.clear();
+ } else {
+ size_t pos = range.GetMin() - offset;
+ size_t n = range.length();
+
+ DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
+ if (pos >= text.length()) {
+ DCHECK(false) << "The text can not cover range.";
+ return;
+ }
+ selected_text_ = UTF16ToUTF8(text.substr(pos, n));
+ }
+
+ [cocoa_view_ setSelectedRange:range.ToNSRange()];
+ // Updates markedRange when there is no marked text so that retrieving
+ // markedRange immediately after calling setMarkdText: returns the current
+ // caret position.
+ if (![cocoa_view_ hasMarkedText]) {
+ [cocoa_view_ setMarkedRange:range.ToNSRange()];
+ }
+
+ RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
+}
+
+void RenderWidgetHostViewMac::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ if (params.anchor_rect == params.focus_rect)
+ caret_rect_ = params.anchor_rect;
+}
+
+void RenderWidgetHostViewMac::ScrollOffsetChanged() {
+}
+
+void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
+ RenderWidgetHostViewBase::SetShowingContextMenu(showing);
+
+ // Create a fake mouse event to inform the render widget that the mouse
+ // left or entered.
+ NSWindow* window = [cocoa_view_ window];
+ // TODO(asvitkine): If the location outside of the event stream doesn't
+ // correspond to the current event (due to delayed event processing), then
+ // this may result in a cursor flicker if there are later mouse move events
+ // in the pipeline. Find a way to use the mouse location from the event that
+ // dismissed the context menu.
+ NSPoint location = [window mouseLocationOutsideOfEventStream];
+ NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
+ location:location
+ modifierFlags:0
+ timestamp:0
+ windowNumber:window_number()
+ context:nil
+ eventNumber:0
+ clickCount:0
+ pressure:0];
+ WebMouseEvent web_event =
+ WebInputEventFactory::mouseEvent(event, cocoa_view_);
+ if (showing)
+ web_event.type = WebInputEvent::MouseLeave;
+ ForwardMouseEvent(web_event);
+}
+
+bool RenderWidgetHostViewMac::IsPopup() const {
+ return popup_type_ != WebKit::WebPopupTypeNone;
+}
+
+BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
+ const gfx::Size& size) {
+ float scale = ScaleFactor(cocoa_view_);
+ return new BackingStoreMac(render_widget_host_, size, scale);
+}
+
+void RenderWidgetHostViewMac::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+ if (!compositing_iosurface_ ||
+ !compositing_iosurface_->HasIOSurface())
+ return;
+
+ float scale = ScaleFactor(cocoa_view_);
+ gfx::Size dst_pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(dst_size, scale));
+
+ scoped_callback_runner.Release();
+
+ compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
+ dst_pixel_size,
+ callback);
+}
+
+void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
+ if (!render_widget_host_->is_accelerated_compositing_active() ||
+ !compositing_iosurface_ ||
+ !compositing_iosurface_->HasIOSurface())
+ return;
+
+ if (!target.get()) {
+ NOTREACHED();
+ return;
+ }
+
+ if (target->format() != media::VideoFrame::YV12 &&
+ target->format() != media::VideoFrame::I420) {
+ NOTREACHED();
+ return;
+ }
+
+ if (src_subrect.IsEmpty())
+ return;
+
+ scoped_callback_runner.Release();
+ compositing_iosurface_->CopyToVideoFrame(
+ GetScaledOpenGLPixelRect(src_subrect),
+ target,
+ callback);
+}
+
+bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
+ return (!render_widget_host_->GetBackingStore(false) &&
+ render_widget_host_->is_accelerated_compositing_active() &&
+ compositing_iosurface_ &&
+ compositing_iosurface_->HasIOSurface());
+}
+
+bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
+ return true;
+}
+
+void RenderWidgetHostViewMac::BeginFrameSubscription(
+ scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
+ frame_subscriber_ = subscriber.Pass();
+}
+
+void RenderWidgetHostViewMac::EndFrameSubscription() {
+ frame_subscriber_.reset();
+}
+
+// Sets whether or not to accept first responder status.
+void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
+ [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
+}
+
+void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
+ if (render_widget_host_)
+ render_widget_host_->ForwardMouseEvent(event);
+
+ if (event.type == WebInputEvent::MouseLeave) {
+ [cocoa_view_ setToolTipAtMousePoint:nil];
+ tooltip_text_.clear();
+ }
+}
+
+void RenderWidgetHostViewMac::KillSelf() {
+ if (!weak_factory_.HasWeakPtrs()) {
+ [cocoa_view_ setHidden:YES];
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) {
+ // Check WebInputEvent type since multiple types of events can be sent into
+ // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
+ // necessary to avoid double processing.
+ // Also check the native type, since NSFlagsChanged is considered a key event
+ // for WebKit purposes, but isn't considered a key event by the OS.
+ if (event.type == WebInputEvent::RawKeyDown &&
+ [event.os_event type] == NSKeyDown)
+ return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
+ return false;
+}
+
+void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
+ const string16& text, int plugin_id) {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
+ render_widget_host_->GetRoutingID(), text, plugin_id));
+ }
+}
+
+void RenderWidgetHostViewMac::CompositorSwapBuffers(
+ uint64 surface_handle,
+ const gfx::Size& size,
+ float surface_scale_factor,
+ const ui::LatencyInfo& latency_info) {
+ if (is_hidden_)
+ return;
+
+ NSWindow* window = [cocoa_view_ window];
+ if (window_number() <= 0) {
+ // There is no window to present so capturing during present won't work.
+ // We check if frame subscriber wants this frame and capture manually.
+ if (compositing_iosurface_ && frame_subscriber_) {
+ const base::Time present_time = base::Time::Now();
+ scoped_refptr<media::VideoFrame> frame;
+ RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
+ if (frame_subscriber_->ShouldCaptureFrame(present_time,
+ &frame, &callback)) {
+ compositing_iosurface_->SetIOSurface(
+ surface_handle, size, surface_scale_factor, latency_info);
+ compositing_iosurface_->CopyToVideoFrame(
+ gfx::Rect(size), frame,
+ base::Bind(callback, present_time));
+ return;
+ }
+ }
+
+ // TODO(shess) If the view does not have a window, or the window
+ // does not have backing, the IOSurface will log "invalid drawable"
+ // in -setView:. It is not clear how this code is reached with such
+ // a case, so record some info into breakpad (some subset of
+ // browsers are likely to crash later for unrelated reasons).
+ // http://crbug.com/148882
+ const char* const kCrashKey = "rwhvm_window";
+ if (!window) {
+ base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
+ } else {
+ std::string value =
+ base::StringPrintf("window %s delegate %s controller %s",
+ object_getClassName(window),
+ object_getClassName([window delegate]),
+ object_getClassName([window windowController]));
+ base::debug::SetCrashKeyValue(kCrashKey, value);
+ }
+
+ return;
+ }
+
+ if (!CreateCompositedIOSurface()) {
+ LOG(ERROR) << "Failed to create CompositingIOSurface";
+ GotAcceleratedCompositingError();
+ return;
+ }
+
+ if (!compositing_iosurface_->SetIOSurface(
+ surface_handle, size, surface_scale_factor, latency_info)) {
+ LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
+ GotAcceleratedCompositingError();
+ return;
+ }
+
+ // Create the layer for the composited content only after the IOSurface has
+ // been initialized.
+ if (!CreateCompositedIOSurfaceLayer()) {
+ LOG(ERROR) << "Failed to create CompositingIOSurface layer";
+ GotAcceleratedCompositingError();
+ return;
+ }
+
+ GotAcceleratedFrame();
+
+ gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
+ if (window_size.IsEmpty()) {
+ // setNeedsDisplay will never display and we'll never ack if the window is
+ // empty, so ack now and don't bother calling setNeedsDisplay below.
+ return;
+ }
+
+ // No need to draw the surface if we are inside a drawRect. It will be done
+ // later.
+ if (!about_to_validate_and_paint_) {
+ if (use_core_animation_) {
+ DCHECK(compositing_iosurface_layer_);
+ [compositing_iosurface_layer_ setNeedsDisplay];
+ } else {
+ if (!DrawIOSurfaceWithoutCoreAnimation()) {
+ [cocoa_view_ setNeedsDisplay:YES];
+ GotAcceleratedCompositingError();
+ return;
+ }
+ }
+ }
+}
+
+void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewMac::AckPendingSwapBuffers");
+
+ // Cancel any outstanding delayed calls to this function.
+ pending_swap_buffers_acks_weak_factory_.InvalidateWeakPtrs();
+
+ while (!pending_swap_buffers_acks_.empty()) {
+ if (pending_swap_buffers_acks_.front().first != 0) {
+ AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
+ ack_params.sync_point = 0;
+ if (compositing_iosurface_)
+ ack_params.renderer_id = compositing_iosurface_->GetRendererID();
+ RenderWidgetHostImpl::AcknowledgeBufferPresent(
+ pending_swap_buffers_acks_.front().first,
+ pending_swap_buffers_acks_.front().second,
+ ack_params);
+ if (render_widget_host_) {
+ render_widget_host_->AcknowledgeSwapBuffersToRenderer();
+ }
+ }
+ pending_swap_buffers_acks_.erase(pending_swap_buffers_acks_.begin());
+ }
+}
+
+void RenderWidgetHostViewMac::ThrottledAckPendingSwapBuffers() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Send VSync parameters to the renderer's compositor thread.
+ base::TimeTicks vsync_timebase;
+ base::TimeDelta vsync_interval;
+ GetVSyncParameters(&vsync_timebase, &vsync_interval);
+ if (render_widget_host_ && compositing_iosurface_)
+ render_widget_host_->UpdateVSyncParameters(vsync_timebase, vsync_interval);
+
+ // If the render widget host is responsible for throttling swaps to vsync rate
+ // then don't ack the swapbuffers until a full vsync has passed since the last
+ // ack was sent.
+ bool throttle_swap_ack =
+ render_widget_host_ &&
+ !render_widget_host_->is_threaded_compositing_enabled() &&
+ compositing_iosurface_ &&
+ !compositing_iosurface_->is_vsync_disabled();
+ base::Time now = base::Time::Now();
+ if (throttle_swap_ack && next_swap_ack_time_ > now) {
+ base::TimeDelta next_swap_ack_delay = next_swap_ack_time_ - now;
+ next_swap_ack_time_ += vsync_interval;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RenderWidgetHostViewMac::AckPendingSwapBuffers,
+ pending_swap_buffers_acks_weak_factory_.GetWeakPtr()),
+ next_swap_ack_delay);
+ } else {
+ next_swap_ack_time_ = now + vsync_interval;
+ AckPendingSwapBuffers();
+ }
+}
+
+bool RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
+ CHECK(!use_core_animation_);
+ CHECK(compositing_iosurface_);
+ CHECK(compositing_iosurface_context_ == compositing_iosurface_->context());
+
+ GLint old_gl_surface_order = 0;
+ GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
+ [compositing_iosurface_context_->nsgl_context()
+ getValues:&old_gl_surface_order
+ forParameter:NSOpenGLCPSurfaceOrder];
+ if (old_gl_surface_order != new_gl_surface_order) {
+ [compositing_iosurface_context_->nsgl_context()
+ setValues:&new_gl_surface_order
+ forParameter:NSOpenGLCPSurfaceOrder];
+ }
+
+ CGLError cgl_error = CGLSetCurrentContext(
+ compositing_iosurface_context_->cgl_context());
+ if (cgl_error != kCGLNoError) {
+ LOG(ERROR) << "CGLSetCurrentContext error in DrawIOSurface: " << cgl_error;
+ return false;
+ }
+
+ [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
+ return compositing_iosurface_->DrawIOSurface(
+ gfx::Size(NSSizeToCGSize([cocoa_view_ frame].size)),
+ scale_factor(),
+ frame_subscriber(),
+ false);
+}
+
+void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
+ AckPendingSwapBuffers();
+ DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
+ // The existing GL contexts may be in a bad state, so don't re-use any of the
+ // existing ones anymore, rather, allocate new ones.
+ CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable();
+ // Request that a new frame be generated.
+ if (render_widget_host_)
+ render_widget_host_->ScheduleComposite();
+ // TODO(ccameron): It may be a good idea to request that the renderer recreate
+ // its GL context as well, and fall back to software if this happens
+ // repeatedly.
+}
+
+void RenderWidgetHostViewMac::GetVSyncParameters(
+ base::TimeTicks* timebase, base::TimeDelta* interval) {
+ if (compositing_iosurface_) {
+ uint32 numerator = 0;
+ uint32 denominator = 0;
+ compositing_iosurface_->GetVSyncParameters(
+ timebase, &numerator, &denominator);
+ if (numerator > 0 && denominator > 0) {
+ int64 interval_micros =
+ 1000000 * static_cast<int64>(numerator) / denominator;
+ *interval = base::TimeDelta::FromMicroseconds(interval_micros);
+ return;
+ }
+ }
+
+ // Pass reasonable default values if unable to get the actual ones
+ // (e.g. CVDisplayLink failed to return them because the display is
+ // in sleep mode).
+ static const int64 kOneOverSixtyMicroseconds = 16669;
+ *timebase = base::TimeTicks::Now(),
+ *interval = base::TimeDelta::FromMicroseconds(kOneOverSixtyMicroseconds);
+}
+
+bool RenderWidgetHostViewMac::GetLineBreakIndex(
+ const std::vector<gfx::Rect>& bounds,
+ const ui::Range& range,
+ size_t* line_break_point) {
+ DCHECK(line_break_point);
+ if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
+ return false;
+
+ // We can't check line breaking completely from only rectangle array. Thus we
+ // assume the line breaking as the next character's y offset is larger than
+ // a threshold. Currently the threshold is determined as minimum y offset plus
+ // 75% of maximum height.
+ // TODO(nona): Check the threshold is reliable or not.
+ // TODO(nona): Bidi support.
+ const size_t loop_end_idx = std::min(bounds.size(), range.end());
+ int max_height = 0;
+ int min_y_offset = kint32max;
+ for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
+ max_height = std::max(max_height, bounds[idx].height());
+ min_y_offset = std::min(min_y_offset, bounds[idx].y());
+ }
+ int line_break_threshold = min_y_offset + (max_height * 3 / 4);
+ for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
+ if (bounds[idx].y() > line_break_threshold) {
+ *line_break_point = idx;
+ return true;
+ }
+ }
+ return false;
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
+ const ui::Range& range,
+ ui::Range* actual_range) {
+ DCHECK(actual_range);
+ DCHECK(!composition_bounds_.empty());
+ DCHECK(range.start() <= composition_bounds_.size());
+ DCHECK(range.end() <= composition_bounds_.size());
+
+ if (range.is_empty()) {
+ *actual_range = range;
+ if (range.start() == composition_bounds_.size()) {
+ return gfx::Rect(composition_bounds_[range.start() - 1].right(),
+ composition_bounds_[range.start() - 1].y(),
+ 0,
+ composition_bounds_[range.start() - 1].height());
+ } else {
+ return gfx::Rect(composition_bounds_[range.start()].x(),
+ composition_bounds_[range.start()].y(),
+ 0,
+ composition_bounds_[range.start()].height());
+ }
+ }
+
+ size_t end_idx;
+ if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
+ end_idx = range.end();
+ }
+ *actual_range = ui::Range(range.start(), end_idx);
+ gfx::Rect rect = composition_bounds_[range.start()];
+ for (size_t i = range.start() + 1; i < end_idx; ++i) {
+ rect.Union(composition_bounds_[i]);
+ }
+ return rect;
+}
+
+ui::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
+ const ui::Range& request_range) {
+ if (composition_range_.is_empty())
+ return ui::Range::InvalidRange();
+
+ if (request_range.is_reversed())
+ return ui::Range::InvalidRange();
+
+ if (request_range.start() < composition_range_.start() ||
+ request_range.start() > composition_range_.end() ||
+ request_range.end() > composition_range_.end()) {
+ return ui::Range::InvalidRange();
+ }
+
+ return ui::Range(
+ request_range.start() - composition_range_.start(),
+ request_range.end() - composition_range_.start());
+}
+
+bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
+ NSRange range,
+ NSRect* rect,
+ NSRange* actual_range) {
+ DCHECK(rect);
+ // This exists to make IMEs more responsive, see http://crbug.com/115920
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
+
+ // If requested range is same as caret location, we can just return it.
+ if (selection_range_.is_empty() && ui::Range(range) == selection_range_) {
+ if (actual_range)
+ *actual_range = range;
+ *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
+ return true;
+ }
+
+ const ui::Range request_range_in_composition =
+ ConvertCharacterRangeToCompositionRange(ui::Range(range));
+ if (request_range_in_composition == ui::Range::InvalidRange())
+ return false;
+
+ // If firstRectForCharacterRange in WebFrame is failed in renderer,
+ // ImeCompositionRangeChanged will be sent with empty vector.
+ if (composition_bounds_.empty())
+ return false;
+ DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
+
+ ui::Range ui_actual_range;
+ *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
+ request_range_in_composition,
+ &ui_actual_range).ToCGRect());
+ if (actual_range) {
+ *actual_range = ui::Range(
+ composition_range_.start() + ui_actual_range.start(),
+ composition_range_.start() + ui_actual_range.end()).ToNSRange();
+ }
+ return true;
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
+ gpu_host_id));
+
+ CompositorSwapBuffers(params.surface_handle,
+ params.size,
+ params.scale_factor,
+ params.latency_info);
+
+ ThrottledAckPendingSwapBuffers();
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
+ gpu_host_id));
+
+ CompositorSwapBuffers(params.surface_handle,
+ params.surface_size,
+ params.surface_scale_factor,
+ params.latency_info);
+
+ ThrottledAckPendingSwapBuffers();
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
+ if (compositing_iosurface_)
+ compositing_iosurface_->UnrefIOSurface();
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
+ DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
+}
+
+bool RenderWidgetHostViewMac::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ return last_frame_was_accelerated_ &&
+ compositing_iosurface_ &&
+ compositing_iosurface_->HasIOSurface() &&
+ (desired_size.IsEmpty() ||
+ compositing_iosurface_->dip_io_surface_size() == desired_size);
+}
+
+void RenderWidgetHostViewMac::AboutToWaitForBackingStoreMsg() {
+ AckPendingSwapBuffers();
+}
+
+void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
+}
+
+void RenderWidgetHostViewMac::GetScreenInfo(WebKit::WebScreenInfo* results) {
+ *results = GetWebScreenInfo(GetNativeView());
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
+ if (!enclosing_window)
+ return gfx::Rect();
+
+ NSRect bounds = [enclosing_window frame];
+ return FlipNSRectToRectScreen(bounds);
+}
+
+gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
+ // TODO(kbr): may be able to eliminate PluginWindowHandle argument
+ // completely on Mac OS.
+ return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
+}
+
+void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+ [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
+}
+
+void RenderWidgetHostViewMac::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
+ toRight:is_pinned_to_right];
+}
+
+bool RenderWidgetHostViewMac::LockMouse() {
+ if (mouse_locked_)
+ return true;
+
+ mouse_locked_ = true;
+
+ // Lock position of mouse cursor and hide it.
+ CGAssociateMouseAndMouseCursorPosition(NO);
+ [NSCursor hide];
+
+ // Clear the tooltip window.
+ SetTooltipText(string16());
+
+ return true;
+}
+
+void RenderWidgetHostViewMac::UnlockMouse() {
+ if (!mouse_locked_)
+ return;
+ mouse_locked_ = false;
+
+ // Unlock position of mouse cursor and unhide it.
+ CGAssociateMouseAndMouseCursorPosition(YES);
+ [NSCursor unhide];
+
+ if (render_widget_host_)
+ render_widget_host_->LostMouseLock();
+}
+
+void RenderWidgetHostViewMac::UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ // Only record a wheel event as unhandled if JavaScript handlers got a chance
+ // to see it (no-op wheel events are ignored by the event dispatcher)
+ if (event.deltaX || event.deltaY)
+ [cocoa_view_ gotUnhandledWheelEvent];
+}
+
+bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
+ if (render_widget_host_)
+ return render_widget_host_->Send(message);
+ delete message;
+ return false;
+}
+
+
+void RenderWidgetHostViewMac::ShutdownHost() {
+ weak_factory_.InvalidateWeakPtrs();
+ render_widget_host_->Shutdown();
+ // Do not touch any members at this point, |this| has been deleted.
+}
+
+void RenderWidgetHostViewMac::GotAcceleratedFrame() {
+ // Update the scale factor of the layer to match the scale factor of the
+ // IOSurface.
+ [compositing_iosurface_layer_ updateScaleFactor];
+
+ if (!last_frame_was_accelerated_) {
+ last_frame_was_accelerated_ = true;
+
+ if (!use_core_animation_) {
+ // Need to wipe the software view with transparency to expose the GL
+ // underlay. Invalidate the whole window to do that.
+ [cocoa_view_ setNeedsDisplay:YES];
+ }
+
+ // Delete software backingstore.
+ BackingStoreManager::RemoveBackingStore(render_widget_host_);
+ }
+}
+
+void RenderWidgetHostViewMac::GotSoftwareFrame() {
+ if (last_frame_was_accelerated_) {
+ last_frame_was_accelerated_ = false;
+
+ AckPendingSwapBuffers();
+
+ // If overlapping views are allowed, then don't unbind the context
+ // from the view (that is, don't call clearDrawble -- just delete the
+ // texture and IOSurface). Rather, let it sit behind the software frame
+ // that will be put up in front. This will prevent transparent
+ // flashes.
+ // http://crbug.com/154531
+ // Also note that it is necessary that clearDrawable be called if
+ // overlapping views are not allowed, e.g, for content shell.
+ // http://crbug.com/178408
+ if (allow_overlapping_views_)
+ DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
+ else
+ DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
+ }
+}
+
+void RenderWidgetHostViewMac::SetActive(bool active) {
+ if (render_widget_host_) {
+ render_widget_host_->SetActive(active);
+ if (active) {
+ if (HasFocus())
+ render_widget_host_->Focus();
+ } else {
+ render_widget_host_->Blur();
+ }
+ }
+ if (HasFocus())
+ SetTextInputActive(active);
+ if (!active) {
+ [cocoa_view_ setPluginImeActive:NO];
+ UnlockMouse();
+ }
+}
+
+void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
+ render_widget_host_->GetRoutingID(), visible));
+ }
+}
+
+void RenderWidgetHostViewMac::WindowFrameChanged() {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
+ render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
+ GetViewBounds()));
+ }
+
+ if (compositing_iosurface_ && use_core_animation_) {
+ scoped_refptr<CompositingIOSurfaceContext> new_context =
+ CompositingIOSurfaceContext::Get(window_number());
+ if (new_context) {
+ // Un-bind the GL context from this view before binding the new GL
+ // context. Having two GL contexts bound to a view will result in
+ // crashes and corruption.
+ // http://crbug.com/230883
+ ClearBoundContextDrawable();
+ compositing_iosurface_context_ = new_context;
+ compositing_iosurface_->SetContext(compositing_iosurface_context_);
+ }
+ }
+}
+
+void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
+ RenderWidgetHostViewMacDictionaryHelper helper(this);
+ helper.ShowDefinitionForSelection();
+}
+
+void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostViewBase::SetBackground(background);
+ if (render_widget_host_)
+ render_widget_host_->Send(new ViewMsg_SetBackground(
+ render_widget_host_->GetRoutingID(), background));
+}
+
+void RenderWidgetHostViewMac::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ if (!GetBrowserAccessibilityManager()) {
+ SetBrowserAccessibilityManager(
+ new BrowserAccessibilityManagerMac(
+ cocoa_view_,
+ BrowserAccessibilityManagerMac::GetEmptyDocument(),
+ NULL));
+ }
+ GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params);
+}
+
+void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
+ if (active) {
+ if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
+ EnablePasswordInput();
+ else
+ DisablePasswordInput();
+ } else {
+ if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
+ DisablePasswordInput();
+ }
+}
+
+void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
+ int plugin_id) {
+ [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
+}
+
+void RenderWidgetHostViewMac::OnStartPluginIme() {
+ [cocoa_view_ setPluginImeActive:YES];
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
+ const gfx::Rect& rect) {
+ gfx::Rect src_gl_subrect = rect;
+ src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
+
+ return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
+ scale_factor()));
+}
+
+void RenderWidgetHostViewMac::FrameSwapped() {
+ software_latency_info_.swap_timestamp = base::TimeTicks::HighResNow();
+ render_widget_host_->FrameSwapped(software_latency_info_);
+ software_latency_info_.Clear();
+}
+
+} // namespace content
+
+// RenderWidgetHostViewCocoa ---------------------------------------------------
+
+@implementation RenderWidgetHostViewCocoa
+
+@synthesize selectedRange = selectedRange_;
+@synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
+@synthesize markedRange = markedRange_;
+@synthesize delegate = delegate_;
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
+ self = [super initWithFrame:NSZeroRect];
+ if (self) {
+ editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
+ editCommand_helper_->AddEditingSelectorsToClass([self class]);
+
+ renderWidgetHostView_.reset(r);
+ canBeKeyView_ = YES;
+ focusedPluginIdentifier_ = -1;
+ deviceScaleFactor_ = ScaleFactor(self);
+
+ // OpenGL support:
+ if ([self respondsToSelector:
+ @selector(setWantsBestResolutionOpenGLSurface:)]) {
+ [self setWantsBestResolutionOpenGLSurface:YES];
+ }
+ handlingGlobalFrameDidChange_ = NO;
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(globalFrameDidChange:)
+ name:NSViewGlobalFrameDidChangeNotification
+ object:self];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ // Unbind the GL context from this view. If this is not done before super's
+ // dealloc is called then the GL context will crash when it reaches into
+ // the view in its destructor.
+ // http://crbug.com/255608
+ if (renderWidgetHostView_)
+ renderWidgetHostView_->AcceleratedSurfaceRelease();
+
+ if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
+ [delegate_ viewGone:self];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+- (void)resetCursorRects {
+ if (currentCursor_) {
+ [self addCursorRect:[self visibleRect] cursor:currentCursor_];
+ [currentCursor_ setOnMouseEntered:YES];
+ }
+}
+
+- (void)gotUnhandledWheelEvent {
+ if (delegate_ &&
+ [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
+ [delegate_ gotUnhandledWheelEvent];
+ }
+}
+
+- (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
+ if (delegate_ && [delegate_ respondsToSelector:
+ @selector(scrollOffsetPinnedToLeft:toRight:)]) {
+ [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
+ }
+}
+
+- (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
+ if (delegate_ &&
+ [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
+ [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
+ }
+}
+
+- (BOOL)respondsToSelector:(SEL)selector {
+ // Trickiness: this doesn't mean "does this object's superclass respond to
+ // this selector" but rather "does the -respondsToSelector impl from the
+ // superclass say that this class responds to the selector".
+ if ([super respondsToSelector:selector])
+ return YES;
+
+ if (delegate_)
+ return [delegate_ respondsToSelector:selector];
+
+ return NO;
+}
+
+- (id)forwardingTargetForSelector:(SEL)selector {
+ if ([delegate_ respondsToSelector:selector])
+ return delegate_;
+
+ return [super forwardingTargetForSelector:selector];
+}
+
+- (void)setCanBeKeyView:(BOOL)can {
+ canBeKeyView_ = can;
+}
+
+- (BOOL)acceptsMouseEventsWhenInactive {
+ // Some types of windows (balloons, always-on-top panels) want to accept mouse
+ // clicks w/o the first click being treated as 'activation'. Same applies to
+ // mouse move events.
+ return [[self window] level] > NSNormalWindowLevel;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
+ return [self acceptsMouseEventsWhenInactive];
+}
+
+- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
+ takesFocusOnlyOnMouseDown_ = b;
+}
+
+- (void)setCloseOnDeactivate:(BOOL)b {
+ closeOnDeactivate_ = b;
+}
+
+- (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
+ NSWindow* window = [self window];
+ // If this is a background window, don't handle mouse movement events. This
+ // is the expected behavior on the Mac as evidenced by other applications.
+ if ([theEvent type] == NSMouseMoved &&
+ ![self acceptsMouseEventsWhenInactive] &&
+ ![window isKeyWindow]) {
+ return YES;
+ }
+
+ // Use hitTest to check whether the mouse is over a nonWebContentView - in
+ // which case the mouse event should not be handled by the render host.
+ const SEL nonWebContentViewSelector = @selector(nonWebContentView);
+ NSView* contentView = [window contentView];
+ NSView* view = [contentView hitTest:[theEvent locationInWindow]];
+ // Traverse the superview hierarchy as the hitTest will return the frontmost
+ // view, such as an NSTextView, while nonWebContentView may be specified by
+ // its parent view.
+ while (view) {
+ if ([view respondsToSelector:nonWebContentViewSelector] &&
+ [view performSelector:nonWebContentViewSelector]) {
+ // The cursor is over a nonWebContentView - ignore this mouse event.
+ return YES;
+ }
+ if ([view isKindOfClass:[self class]] && ![view isEqual:self]) {
+ // The cursor is over an overlapping render widget. This check is done by
+ // both views so the one that's returned by -hitTest: will end up
+ // processing the event.
+ return YES;
+ }
+ view = [view superview];
+ }
+ return NO;
+}
+
+- (void)mouseEvent:(NSEvent*)theEvent {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:theEvent];
+ if (handled)
+ return;
+ }
+
+ if ([self shouldIgnoreMouseEvent:theEvent]) {
+ // If this is the first such event, send a mouse exit to the host view.
+ if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
+ WebMouseEvent exitEvent =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+ exitEvent.type = WebInputEvent::MouseLeave;
+ exitEvent.button = WebMouseEvent::ButtonNone;
+ renderWidgetHostView_->ForwardMouseEvent(exitEvent);
+ }
+ mouseEventWasIgnored_ = YES;
+ return;
+ }
+
+ if (mouseEventWasIgnored_) {
+ // If this is the first mouse event after a previous event that was ignored
+ // due to the hitTest, send a mouse enter event to the host view.
+ if (renderWidgetHostView_->render_widget_host_) {
+ WebMouseEvent enterEvent =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+ enterEvent.type = WebInputEvent::MouseMove;
+ enterEvent.button = WebMouseEvent::ButtonNone;
+ renderWidgetHostView_->ForwardMouseEvent(enterEvent);
+ }
+ }
+ mouseEventWasIgnored_ = NO;
+
+ // TODO(rohitrao): Probably need to handle other mouse down events here.
+ if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
+
+ // Manually take focus after the click but before forwarding it to the
+ // renderer.
+ [[self window] makeFirstResponder:self];
+ }
+
+ // Don't cancel child popups; killing them on a mouse click would prevent the
+ // user from positioning the insertion point in the text field spawning the
+ // popup. A click outside the text field would cause the text field to drop
+ // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
+ // the popup anyway, so we're OK.
+
+ NSEventType type = [theEvent type];
+ if (type == NSLeftMouseDown)
+ hasOpenMouseDown_ = YES;
+ else if (type == NSLeftMouseUp)
+ hasOpenMouseDown_ = NO;
+
+ // TODO(suzhe): We should send mouse events to the input method first if it
+ // wants to handle them. But it won't work without implementing method
+ // - (NSUInteger)characterIndexForPoint:.
+ // See: http://code.google.com/p/chromium/issues/detail?id=47141
+ // Instead of sending mouse events to the input method first, we now just
+ // simply confirm all ongoing composition here.
+ if (type == NSLeftMouseDown || type == NSRightMouseDown ||
+ type == NSOtherMouseDown) {
+ [self confirmComposition];
+ }
+
+ const WebMouseEvent event =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+ renderWidgetHostView_->ForwardMouseEvent(event);
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
+ // |performKeyEquivalent:| is sent to all views of a window, not only down the
+ // responder chain (cf. "Handling Key Equivalents" in
+ // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
+ // ). We only want to handle key equivalents if we're first responder.
+ if ([[self window] firstResponder] != self)
+ return NO;
+
+ // If we return |NO| from this function, cocoa will send the key event to
+ // the menu and only if the menu does not process the event to |keyDown:|. We
+ // want to send the event to a renderer _before_ sending it to the menu, so
+ // we need to return |YES| for all events that might be swallowed by the menu.
+ // We do not return |YES| for every keypress because we don't get |keyDown:|
+ // events for keys that we handle this way.
+ NSUInteger modifierFlags = [theEvent modifierFlags];
+ if ((modifierFlags & NSCommandKeyMask) == 0) {
+ // Make sure the menu does not contain key equivalents that don't
+ // contain cmd.
+ DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
+ return NO;
+ }
+
+ // Command key combinations are sent via performKeyEquivalent rather than
+ // keyDown:. We just forward this on and if WebCore doesn't want to handle
+ // it, we let the WebContentsView figure out how to reinject it.
+ [self keyEvent:theEvent wasKeyEquivalent:YES];
+ return YES;
+}
+
+- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
+ // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
+ // returned NO. If this function returns |YES|, Cocoa sends the event to
+ // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
+ // to us instead of doing key view loop control, ctrl-left/right get handled
+ // correctly, etc.
+ // (However, there are still some keys that Cocoa swallows, e.g. the key
+ // equivalent that Cocoa uses for toggling the input language. In this case,
+ // that's actually a good thing, though -- see http://crbug.com/26115 .)
+ return YES;
+}
+
+- (void)keyEvent:(NSEvent*)theEvent {
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:theEvent];
+ if (handled)
+ return;
+ }
+
+ [self keyEvent:theEvent wasKeyEquivalent:NO];
+}
+
+- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
+ DCHECK([theEvent type] != NSKeyDown ||
+ !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
+
+ if ([theEvent type] == NSFlagsChanged) {
+ // Ignore NSFlagsChanged events from the NumLock and Fn keys as
+ // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
+ int keyCode = [theEvent keyCode];
+ if (!keyCode || keyCode == 10 || keyCode == 63)
+ return;
+ }
+
+ // Don't cancel child popups; the key events are probably what's triggering
+ // the popup in the first place.
+
+ RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
+ DCHECK(widgetHost);
+
+ NativeWebKeyboardEvent event(theEvent);
+
+ // Force fullscreen windows to close on Escape so they won't keep the keyboard
+ // grabbed or be stuck onscreen if the renderer is hanging.
+ if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
+ event.windowsKeyCode == ui::VKEY_ESCAPE &&
+ renderWidgetHostView_->pepper_fullscreen_window()) {
+ RenderWidgetHostViewMac* parent =
+ renderWidgetHostView_->fullscreen_parent_host_view();
+ if (parent)
+ parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
+ widgetHost->Shutdown();
+ return;
+ }
+
+ // Suppress the escape key up event if necessary.
+ if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
+ if (event.type == NativeWebKeyboardEvent::KeyUp)
+ suppressNextEscapeKeyUp_ = NO;
+ return;
+ }
+
+ // We only handle key down events and just simply forward other events.
+ if ([theEvent type] != NSKeyDown) {
+ widgetHost->ForwardKeyboardEvent(event);
+
+ // Possibly autohide the cursor.
+ if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
+ [NSCursor setHiddenUntilMouseMoves:YES];
+
+ return;
+ }
+
+ base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
+
+ // Records the current marked text state, so that we can know if the marked
+ // text was deleted or not after handling the key down event.
+ BOOL oldHasMarkedText = hasMarkedText_;
+
+ // This method should not be called recursively.
+ DCHECK(!handlingKeyDown_);
+
+ // Tells insertText: and doCommandBySelector: that we are handling a key
+ // down event.
+ handlingKeyDown_ = YES;
+
+ // These variables might be set when handling the keyboard event.
+ // Clear them here so that we can know whether they have changed afterwards.
+ textToBeInserted_.clear();
+ markedText_.clear();
+ underlines_.clear();
+ unmarkTextCalled_ = NO;
+ hasEditCommands_ = NO;
+ editCommands_.clear();
+
+ // Before doing anything with a key down, check to see if plugin IME has been
+ // cancelled, since the plugin host needs to be informed of that before
+ // receiving the keydown.
+ if ([theEvent type] == NSKeyDown)
+ [self checkForPluginImeCancellation];
+
+ // Sends key down events to input method first, then we can decide what should
+ // be done according to input method's feedback.
+ // If a plugin is active, bypass this step since events are forwarded directly
+ // to the plugin IME.
+ if (focusedPluginIdentifier_ == -1)
+ [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
+
+ handlingKeyDown_ = NO;
+
+ // Indicates if we should send the key event and corresponding editor commands
+ // after processing the input method result.
+ BOOL delayEventUntilAfterImeCompostion = NO;
+
+ // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
+ // while an input method is composing or inserting a text.
+ // Gmail checks this code in its onkeydown handler to stop auto-completing
+ // e-mail addresses while composing a CJK text.
+ // If the text to be inserted has only one character, then we don't need this
+ // trick, because we'll send the text as a key press event instead.
+ if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
+ NativeWebKeyboardEvent fakeEvent = event;
+ fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
+ fakeEvent.setKeyIdentifierFromWindowsKeyCode();
+ fakeEvent.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(fakeEvent);
+ // If this key event was handled by the input method, but
+ // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
+ // enqueued edit commands, then in order to let webkit handle them
+ // correctly, we need to send the real key event and corresponding edit
+ // commands after processing the input method result.
+ // We shouldn't do this if a new marked text was set by the input method,
+ // otherwise the new marked text might be cancelled by webkit.
+ if (hasEditCommands_ && !hasMarkedText_)
+ delayEventUntilAfterImeCompostion = YES;
+ } else {
+ if (!editCommands_.empty()) {
+ widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
+ widgetHost->GetRoutingID(), editCommands_));
+ }
+ widgetHost->ForwardKeyboardEvent(event);
+ }
+
+ // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
+ // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
+ // be set to NULL. So we check it here and return immediately if it's NULL.
+ if (!renderWidgetHostView_->render_widget_host_)
+ return;
+
+ // Then send keypress and/or composition related events.
+ // If there was a marked text or the text to be inserted is longer than 1
+ // character, then we send the text by calling ConfirmComposition().
+ // Otherwise, if the text to be inserted only contains 1 character, then we
+ // can just send a keypress event which is fabricated by changing the type of
+ // the keydown event, so that we can retain all necessary informations, such
+ // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
+ // prevent the browser from handling it again.
+ // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
+ // handle BMP characters here, as we can always insert non-BMP characters as
+ // text.
+ BOOL textInserted = NO;
+ if (textToBeInserted_.length() >
+ ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
+ widgetHost->ImeConfirmComposition(
+ textToBeInserted_, ui::Range::InvalidRange(), false);
+ textInserted = YES;
+ }
+
+ // Updates or cancels the composition. If some text has been inserted, then
+ // we don't need to cancel the composition explicitly.
+ if (hasMarkedText_ && markedText_.length()) {
+ // Sends the updated marked text to the renderer so it can update the
+ // composition node in WebKit.
+ // When marked text is available, |selectedRange_| will be the range being
+ // selected inside the marked text.
+ widgetHost->ImeSetComposition(markedText_, underlines_,
+ selectedRange_.location,
+ NSMaxRange(selectedRange_));
+ } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
+ if (unmarkTextCalled_) {
+ widgetHost->ImeConfirmComposition(
+ string16(), ui::Range::InvalidRange(), false);
+ } else {
+ widgetHost->ImeCancelComposition();
+ }
+ }
+
+ // If the key event was handled by the input method but it also generated some
+ // edit commands, then we need to send the real key event and corresponding
+ // edit commands here. This usually occurs when the input method wants to
+ // finish current composition session but still wants the application to
+ // handle the key event. See http://crbug.com/48161 for reference.
+ if (delayEventUntilAfterImeCompostion) {
+ // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
+ // with windowsKeyCode == 0xE5 has already been sent to webkit.
+ // So before sending the real key down event, we need to send a fake key up
+ // event to balance it.
+ NativeWebKeyboardEvent fakeEvent = event;
+ fakeEvent.type = WebKit::WebInputEvent::KeyUp;
+ fakeEvent.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(fakeEvent);
+ // Not checking |renderWidgetHostView_->render_widget_host_| here because
+ // a key event with |skip_in_browser| == true won't be handled by browser,
+ // thus it won't destroy the widget.
+
+ if (!editCommands_.empty()) {
+ widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
+ widgetHost->GetRoutingID(), editCommands_));
+ }
+ widgetHost->ForwardKeyboardEvent(event);
+
+ // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
+ // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
+ // be set to NULL. So we check it here and return immediately if it's NULL.
+ if (!renderWidgetHostView_->render_widget_host_)
+ return;
+ }
+
+ const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
+ // Only send a corresponding key press event if there is no marked text.
+ if (!hasMarkedText_) {
+ if (!textInserted && textToBeInserted_.length() == 1) {
+ // If a single character was inserted, then we just send it as a keypress
+ // event.
+ event.type = WebKit::WebInputEvent::Char;
+ event.text[0] = textToBeInserted_[0];
+ event.text[1] = 0;
+ event.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(event);
+ } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
+ [[theEvent characters] length] > 0 &&
+ (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
+ (hasEditCommands_ && editCommands_.empty()))) {
+ // We don't get insertText: calls if ctrl or cmd is down, or the key event
+ // generates an insert command. So synthesize a keypress event for these
+ // cases, unless the key event generated any other command.
+ event.type = WebKit::WebInputEvent::Char;
+ event.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(event);
+ }
+ }
+
+ // Possibly autohide the cursor.
+ if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
+ [NSCursor setHiddenUntilMouseMoves:YES];
+}
+
+- (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
+ DCHECK(base::mac::IsOSLionOrLater());
+
+ if ([event phase] != NSEventPhaseEnded &&
+ [event phase] != NSEventPhaseCancelled) {
+ return;
+ }
+
+ if (renderWidgetHostView_->render_widget_host_) {
+ const WebMouseWheelEvent& webEvent =
+ WebInputEventFactory::mouseWheelEvent(event, self);
+ renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
+ }
+
+ if (endWheelMonitor_) {
+ [NSEvent removeMonitor:endWheelMonitor_];
+ endWheelMonitor_ = nil;
+ }
+}
+
+- (void)scrollWheel:(NSEvent*)event {
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:event];
+ if (handled)
+ return;
+ }
+
+ // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
+ // the event is received even when the mouse cursor is no longer over the view
+ // when the scrolling ends (e.g. if the tab was switched). This is necessary
+ // for ending rubber-banding in such cases.
+ if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
+ !endWheelMonitor_) {
+ endWheelMonitor_ =
+ [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
+ handler:^(NSEvent* blockEvent) {
+ [self shortCircuitScrollWheelEvent:blockEvent];
+ return blockEvent;
+ }];
+ }
+
+ if (renderWidgetHostView_->render_widget_host_) {
+ const WebMouseWheelEvent& webEvent =
+ WebInputEventFactory::mouseWheelEvent(event, self);
+ renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
+ }
+}
+
+- (void)viewWillMoveToWindow:(NSWindow*)newWindow {
+ NSWindow* oldWindow = [self window];
+
+ // We're messing with the window, so do this to ensure no flashes. This one
+ // prevents a flash when the current tab is closed.
+ if (!renderWidgetHostView_->use_core_animation_)
+ [oldWindow disableScreenUpdatesUntilFlush];
+
+ // If the new window for this view is using CoreAnimation then enable
+ // CoreAnimation on this view.
+ if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY &&
+ [[newWindow contentView] wantsLayer]) {
+ renderWidgetHostView_->EnableCoreAnimation();
+ }
+
+ NSNotificationCenter* notificationCenter =
+ [NSNotificationCenter defaultCenter];
+
+ // Backing property notifications crash on 10.6 when building with the 10.7
+ // SDK, see http://crbug.com/260595.
+ static BOOL supportsBackingPropertiesNotification =
+ SupportsBackingPropertiesChangedNotification();
+
+ if (oldWindow) {
+ if (supportsBackingPropertiesNotification) {
+ [notificationCenter
+ removeObserver:self
+ name:NSWindowDidChangeBackingPropertiesNotification
+ object:oldWindow];
+ }
+ [notificationCenter
+ removeObserver:self
+ name:NSWindowDidMoveNotification
+ object:oldWindow];
+ [notificationCenter
+ removeObserver:self
+ name:NSWindowDidEndLiveResizeNotification
+ object:oldWindow];
+ }
+ if (newWindow) {
+ if (supportsBackingPropertiesNotification) {
+ [notificationCenter
+ addObserver:self
+ selector:@selector(windowDidChangeBackingProperties:)
+ name:NSWindowDidChangeBackingPropertiesNotification
+ object:newWindow];
+ }
+ [notificationCenter
+ addObserver:self
+ selector:@selector(windowChangedGlobalFrame:)
+ name:NSWindowDidMoveNotification
+ object:newWindow];
+ [notificationCenter
+ addObserver:self
+ selector:@selector(windowChangedGlobalFrame:)
+ name:NSWindowDidEndLiveResizeNotification
+ object:newWindow];
+ }
+}
+
+- (void)updateTabBackingStoreScaleFactor {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return;
+
+ float scaleFactor = ScaleFactor(self);
+ if (scaleFactor == deviceScaleFactor_)
+ return;
+ deviceScaleFactor_ = scaleFactor;
+
+ BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
+ renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
+ if (backingStore) // NULL in hardware path.
+ backingStore->ScaleFactorChanged(scaleFactor);
+
+ [self updateSoftwareLayerScaleFactor];
+ renderWidgetHostView_->render_widget_host_->NotifyScreenInfoChanged();
+}
+
+// http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
+- (void)windowDidChangeBackingProperties:(NSNotification*)notification {
+ NSWindow* window = (NSWindow*)[notification object];
+
+ CGFloat newBackingScaleFactor = [window backingScaleFactor];
+ CGFloat oldBackingScaleFactor = [base::mac::ObjCCast<NSNumber>(
+ [[notification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey])
+ doubleValue];
+ if (newBackingScaleFactor != oldBackingScaleFactor) {
+ // Background tabs check if their scale factor changed when they are added
+ // to a window.
+
+ // Allocating a CGLayerRef with the current scale factor immediately from
+ // this handler doesn't work. Schedule the backing store update on the
+ // next runloop cycle, then things are read for CGLayerRef allocations to
+ // work.
+ [self performSelector:@selector(updateTabBackingStoreScaleFactor)
+ withObject:nil
+ afterDelay:0];
+ }
+}
+
+- (void)globalFrameDidChange:(NSNotification*)notification {
+ if (handlingGlobalFrameDidChange_)
+ return;
+
+ handlingGlobalFrameDidChange_ = YES;
+ if (renderWidgetHostView_->compositing_iosurface_context_) {
+ [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
+ update];
+ }
+ handlingGlobalFrameDidChange_ = NO;
+}
+
+- (void)windowChangedGlobalFrame:(NSNotification*)notification {
+ renderWidgetHostView_->UpdateScreenInfo(
+ renderWidgetHostView_->GetNativeView());
+}
+
+- (void)setFrameSize:(NSSize)newSize {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
+
+ // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
+ // -setFrame: isn't neccessary.
+ [super setFrameSize:newSize];
+ if (renderWidgetHostView_->render_widget_host_) {
+ renderWidgetHostView_->render_widget_host_->SendScreenRects();
+ renderWidgetHostView_->render_widget_host_->WasResized();
+ }
+
+ // This call is necessary to make the window wait for a new frame at the new
+ // size to be available before the resize completes. Calling only
+ // setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay on
+ // this is not sufficient.
+ ScopedCAActionDisabler disabler;
+ CGRect frame = NSRectToCGRect([renderWidgetHostView_->cocoa_view() bounds]);
+ [renderWidgetHostView_->software_layer_ setFrame:frame];
+ [renderWidgetHostView_->software_layer_ setNeedsDisplay];
+ [renderWidgetHostView_->compositing_iosurface_layer_ setFrame:frame];
+ [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
+}
+
+- (void)callSetNeedsDisplayInRect {
+ DCHECK([NSThread isMainThread]);
+ DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
+ [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
+ renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
+ renderWidgetHostView_->invalid_rect_ = NSZeroRect;
+
+ if (renderWidgetHostView_->compositing_iosurface_layer_)
+ [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
+}
+
+// Fills with white the parts of the area to the right and bottom for |rect|
+// that intersect |damagedRect|.
+- (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
+ dirtyRect:(gfx::Rect)damagedRect
+ inContext:(CGContextRef)context {
+ if (damagedRect.right() > rect.right()) {
+ int x = std::max(rect.right(), damagedRect.x());
+ int y = std::min(rect.bottom(), damagedRect.bottom());
+ int width = damagedRect.right() - x;
+ int height = damagedRect.y() - y;
+
+ // Extra fun to get around the fact that gfx::Rects can't have
+ // negative sizes.
+ if (width < 0) {
+ x += width;
+ width = -width;
+ }
+ if (height < 0) {
+ y += height;
+ height = -height;
+ }
+
+ NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
+ CGContextSetFillColorWithColor(context,
+ CGColorGetConstantColor(kCGColorWhite));
+ CGContextFillRect(context, NSRectToCGRect(r));
+ }
+ if (damagedRect.bottom() > rect.bottom()) {
+ int x = damagedRect.x();
+ int y = damagedRect.bottom();
+ int width = damagedRect.right() - x;
+ int height = std::max(rect.bottom(), damagedRect.y()) - y;
+
+ // Extra fun to get around the fact that gfx::Rects can't have
+ // negative sizes.
+ if (width < 0) {
+ x += width;
+ width = -width;
+ }
+ if (height < 0) {
+ y += height;
+ height = -height;
+ }
+
+ NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
+ CGContextSetFillColorWithColor(context,
+ CGColorGetConstantColor(kCGColorWhite));
+ CGContextFillRect(context, NSRectToCGRect(r));
+ }
+}
+
+- (void)drawRect:(NSRect)dirtyRect {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
+ CHECK(!renderWidgetHostView_->use_core_animation_);
+
+ if (!renderWidgetHostView_->render_widget_host_) {
+ // TODO(shess): Consider using something more noticable?
+ [[NSColor whiteColor] set];
+ NSRectFill(dirtyRect);
+ return;
+ }
+
+ DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
+
+ // GetBackingStore works for both software and accelerated frames. If a
+ // SwapBuffers occurs while GetBackingStore is blocking, we will continue to
+ // blit the IOSurface below.
+ renderWidgetHostView_->about_to_validate_and_paint_ = true;
+ BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
+ renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
+ renderWidgetHostView_->about_to_validate_and_paint_ = false;
+
+ const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
+
+ if (renderWidgetHostView_->last_frame_was_accelerated_ &&
+ renderWidgetHostView_->compositing_iosurface_) {
+ if (renderWidgetHostView_->allow_overlapping_views_) {
+ CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
+
+ // If overlapping views need to be allowed, punch a hole in the window
+ // to expose the GL underlay.
+ TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
+ "h", damagedRect.height());
+ // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
+ // this is only done when it's a real invalidation from window damage (not
+ // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
+ // can take many milliseconds sometimes (!) so this is skipped completely
+ // for drawRects that are triggered by BuffersSwapped messages.
+ [[NSColor clearColor] set];
+ NSRectFill(dirtyRect);
+ }
+
+ if (renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation())
+ return;
+
+ // On error, fall back to software and fall through to the non-accelerated
+ // drawing path.
+ renderWidgetHostView_->GotAcceleratedCompositingError();
+ }
+
+ CGContextRef context = static_cast<CGContextRef>(
+ [[NSGraphicsContext currentContext] graphicsPort]);
+ [self drawBackingStore:backingStore
+ dirtyRect:NSRectToCGRect(dirtyRect)
+ inContext:context];
+}
+
+- (void)drawBackingStore:(BackingStoreMac*)backingStore
+ dirtyRect:(CGRect)dirtyRect
+ inContext:(CGContextRef)context {
+ if (backingStore) {
+ // Note: All coordinates are in view units, not pixels.
+ gfx::Rect bitmapRect(0, 0,
+ backingStore->size().width(),
+ backingStore->size().height());
+
+ // Specify the proper y offset to ensure that the view is rooted to the
+ // upper left corner. This can be negative, if the window was resized
+ // smaller and the renderer hasn't yet repainted.
+ int yOffset = NSHeight([self bounds]) - backingStore->size().height();
+
+ NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
+ const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
+
+ gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
+ if (!paintRect.IsEmpty()) {
+ // if we have a CGLayer, draw that into the window
+ if (backingStore->cg_layer()) {
+ // TODO: add clipping to dirtyRect if it improves drawing performance.
+ CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
+ backingStore->cg_layer());
+ } else {
+ // if we haven't created a layer yet, draw the cached bitmap into
+ // the window. The CGLayer will be created the next time the renderer
+ // paints.
+ base::ScopedCFTypeRef<CGImageRef> image(
+ CGBitmapContextCreateImage(backingStore->cg_bitmap()));
+ CGRect imageRect = bitmapRect.ToCGRect();
+ imageRect.origin.y = yOffset;
+ CGContextDrawImage(context, imageRect, image);
+ }
+ }
+
+ renderWidgetHostView_->FrameSwapped();
+
+ // Fill the remaining portion of the damagedRect with white
+ [self fillBottomRightRemainderOfRect:bitmapRect
+ dirtyRect:damagedRect
+ inContext:context];
+
+ if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
+ base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
+ renderWidgetHostView_->whiteout_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
+
+ // Reset the start time to 0 so that we start recording again the next
+ // time the backing store is NULL...
+ renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
+ }
+ if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
+ base::TimeDelta web_contents_switch_paint_duration =
+ base::TimeTicks::Now() -
+ renderWidgetHostView_->web_contents_switch_paint_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
+ web_contents_switch_paint_duration);
+ // Reset contents_switch_paint_time_ to 0 so future tab selections are
+ // recorded.
+ renderWidgetHostView_->web_contents_switch_paint_time_ =
+ base::TimeTicks();
+ }
+ } else {
+ CGContextSetFillColorWithColor(context,
+ CGColorGetConstantColor(kCGColorWhite));
+ CGContextFillRect(context, dirtyRect);
+ if (renderWidgetHostView_->whiteout_start_time_.is_null())
+ renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
+ }
+}
+
+- (BOOL)canBecomeKeyView {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ return canBeKeyView_;
+}
+
+- (BOOL)acceptsFirstResponder {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
+}
+
+- (BOOL)becomeFirstResponder {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ renderWidgetHostView_->render_widget_host_->Focus();
+ renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
+ renderWidgetHostView_->SetTextInputActive(true);
+
+ // Cancel any onging composition text which was left before we lost focus.
+ // TODO(suzhe): We should do it in -resignFirstResponder: method, but
+ // somehow that method won't be called when switching among different tabs.
+ // See http://crbug.com/47209
+ [self cancelComposition];
+
+ NSNumber* direction = [NSNumber numberWithUnsignedInteger:
+ [[self window] keyViewSelectionDirection]];
+ NSDictionary* userInfo =
+ [NSDictionary dictionaryWithObject:direction
+ forKey:kSelectionDirection];
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kViewDidBecomeFirstResponder
+ object:self
+ userInfo:userInfo];
+
+ return YES;
+}
+
+- (BOOL)resignFirstResponder {
+ renderWidgetHostView_->SetTextInputActive(false);
+ if (!renderWidgetHostView_->render_widget_host_)
+ return YES;
+
+ if (closeOnDeactivate_)
+ renderWidgetHostView_->KillSelf();
+
+ renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
+ renderWidgetHostView_->render_widget_host_->Blur();
+
+ // We should cancel any onging composition whenever RWH's Blur() method gets
+ // called, because in this case, webkit will confirm the ongoing composition
+ // internally.
+ [self cancelComposition];
+
+ return YES;
+}
+
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
+ if (delegate_ && [delegate_ respondsToSelector:
+ @selector(validateUserInterfaceItem:isValidItem:)]) {
+ BOOL valid;
+ BOOL known = [delegate_ validateUserInterfaceItem:item
+ isValidItem:&valid];
+ if (known)
+ return valid;
+ }
+
+ SEL action = [item action];
+
+ if (action == @selector(stopSpeaking:)) {
+ return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
+ renderWidgetHostView_->IsSpeaking();
+ }
+ if (action == @selector(startSpeaking:)) {
+ return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
+ renderWidgetHostView_->SupportsSpeech();
+ }
+
+ // For now, these actions are always enabled for render view,
+ // this is sub-optimal.
+ // TODO(suzhe): Plumb the "can*" methods up from WebCore.
+ if (action == @selector(undo:) ||
+ action == @selector(redo:) ||
+ action == @selector(cut:) ||
+ action == @selector(copy:) ||
+ action == @selector(copyToFindPboard:) ||
+ action == @selector(paste:) ||
+ action == @selector(pasteAndMatchStyle:)) {
+ return renderWidgetHostView_->render_widget_host_->IsRenderView();
+ }
+
+ return editCommand_helper_->IsMenuItemEnabled(action, self);
+}
+
+- (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
+ return renderWidgetHostView_.get();
+}
+
+// Determine whether we should autohide the cursor (i.e., hide it until mouse
+// move) for the given event. Customize here to be more selective about which
+// key presses to autohide on.
++ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
+ return ([event type] == NSKeyDown) ? YES : NO;
+}
+
+- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
+ index:(NSUInteger)index
+ maxCount:(NSUInteger)maxCount {
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ NSUInteger totalLength = [fullArray count];
+ if (index >= totalLength)
+ return nil;
+ NSUInteger length = MIN(totalLength - index, maxCount);
+ return [fullArray subarrayWithRange:NSMakeRange(index, length)];
+}
+
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ return [fullArray count];
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->GetBrowserAccessibilityManager();
+
+ // Contents specifies document view of RenderWidgetHostViewCocoa provided by
+ // BrowserAccessibilityManager. Children includes all subviews in addition to
+ // contents. Currently we do not have subviews besides the document view.
+ if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
+ [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
+ manager) {
+ return [NSArray arrayWithObjects:manager->
+ GetRoot()->ToBrowserAccessibilityCocoa(), nil];
+ } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
+ return NSAccessibilityScrollAreaRole;
+ }
+ id ret = [super accessibilityAttributeValue:attribute];
+ return ret;
+}
+
+- (NSArray*)accessibilityAttributeNames {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ [ret addObject:NSAccessibilityContentsAttribute];
+ [ret addObjectsFromArray:[super accessibilityAttributeNames]];
+ return ret;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
+ return self;
+ NSPoint pointInWindow = [[self window] convertScreenToBase:point];
+ NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
+ localPoint.y = NSHeight([self bounds]) - localPoint.y;
+ BrowserAccessibilityCocoa* root = renderWidgetHostView_->
+ GetBrowserAccessibilityManager()->
+ GetRoot()->ToBrowserAccessibilityCocoa();
+ id obj = [root accessibilityHitTest:localPoint];
+ return obj;
+}
+
+- (BOOL)accessibilityIsIgnored {
+ return !renderWidgetHostView_->GetBrowserAccessibilityManager();
+}
+
+- (NSUInteger)accessibilityGetIndexOf:(id)child {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->GetBrowserAccessibilityManager();
+ // Only child is root.
+ if (manager &&
+ manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
+ return 0;
+ } else {
+ return NSNotFound;
+ }
+}
+
+- (id)accessibilityFocusedUIElement {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->GetBrowserAccessibilityManager();
+ if (manager) {
+ BrowserAccessibility* focused_item = manager->GetFocus(NULL);
+ DCHECK(focused_item);
+ if (focused_item) {
+ BrowserAccessibilityCocoa* focused_item_cocoa =
+ focused_item->ToBrowserAccessibilityCocoa();
+ DCHECK(focused_item_cocoa);
+ if (focused_item_cocoa)
+ return focused_item_cocoa;
+ }
+ }
+ return [super accessibilityFocusedUIElement];
+}
+
+- (void)doDefaultAction:(int32)accessibilityObjectId {
+ RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new AccessibilityMsg_DoDefaultAction(
+ rwh->GetRoutingID(), accessibilityObjectId));
+}
+
+// VoiceOver uses this method to move the caret to the beginning of the next
+// word in a text field.
+- (void)accessibilitySetTextSelection:(int32)accId
+ startOffset:(int32)startOffset
+ endOffset:(int32)endOffset {
+ RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
+}
+
+// Convert a web accessibility's location in web coordinates into a cocoa
+// screen coordinate.
+- (NSPoint)accessibilityPointInScreen:
+ (BrowserAccessibilityCocoa*)accessibility {
+ NSPoint origin = [accessibility origin];
+ NSSize size = [[accessibility size] sizeValue];
+ origin.y = NSHeight([self bounds]) - origin.y;
+ NSPoint originInWindow = [self convertPoint:origin toView:nil];
+ NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
+ originInScreen.y = originInScreen.y - size.height;
+ return originInScreen;
+}
+
+- (void)setAccessibilityFocus:(BOOL)focus
+ accessibilityId:(int32)accessibilityObjectId {
+ if (focus) {
+ RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new AccessibilityMsg_SetFocus(
+ rwh->GetRoutingID(), accessibilityObjectId));
+
+ // Immediately set the focused item even though we have not officially set
+ // focus on it as VoiceOver expects to get the focused item after this
+ // method returns.
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->GetBrowserAccessibilityManager();
+ manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
+ }
+}
+
+- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
+ // Performs a right click copying WebKit's
+ // accessibilityPerformShowMenuAction.
+ NSPoint location = [self accessibilityPointInScreen:accessibility];
+ NSSize size = [[accessibility size] sizeValue];
+ location = [[self window] convertScreenToBase:location];
+ location.x += size.width/2;
+ location.y += size.height/2;
+
+ NSEvent* fakeRightClick = [NSEvent
+ mouseEventWithType:NSRightMouseDown
+ location:location
+ modifierFlags:0
+ timestamp:0
+ windowNumber:[[self window] windowNumber]
+ context:[NSGraphicsContext currentContext]
+ eventNumber:0
+ clickCount:1
+ pressure:0];
+
+ [self mouseEvent:fakeRightClick];
+}
+
+// Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
+// with minor modifications for code style and commenting.
+//
+// The 'public' interface is -setToolTipAtMousePoint:. This differs from
+// -setToolTip: in that the updated tooltip takes effect immediately,
+// without the user's having to move the mouse out of and back into the view.
+//
+// Unfortunately, doing this requires sending fake mouseEnter/Exit events to
+// the view, which in turn requires overriding some internal tracking-rect
+// methods (to keep track of its owner & userdata, which need to be filled out
+// in the fake events.) --snej 7/6/09
+
+
+/*
+ * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Any non-zero value will do, but using something recognizable might help us
+// debug some day.
+static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
+
+// Override of a public NSView method, replacing the inherited functionality.
+// See above for rationale.
+- (NSTrackingRectTag)addTrackingRect:(NSRect)rect
+ owner:(id)owner
+ userData:(void *)data
+ assumeInside:(BOOL)assumeInside {
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = data;
+ return kTrackingRectTag;
+}
+
+// Override of (apparently) a private NSView method(!) See above for rationale.
+- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
+ owner:(id)owner
+ userData:(void *)data
+ assumeInside:(BOOL)assumeInside
+ useTrackingNum:(int)tag {
+ DCHECK(tag == 0 || tag == kTrackingRectTag);
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = data;
+ return kTrackingRectTag;
+}
+
+// Override of (apparently) a private NSView method(!) See above for rationale.
+- (void)_addTrackingRects:(NSRect *)rects
+ owner:(id)owner
+ userDataList:(void **)userDataList
+ assumeInsideList:(BOOL *)assumeInsideList
+ trackingNums:(NSTrackingRectTag *)trackingNums
+ count:(int)count {
+ DCHECK(count == 1);
+ DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = userDataList[0];
+ trackingNums[0] = kTrackingRectTag;
+}
+
+// Override of a public NSView method, replacing the inherited functionality.
+// See above for rationale.
+- (void)removeTrackingRect:(NSTrackingRectTag)tag {
+ if (tag == 0)
+ return;
+
+ if (tag == kTrackingRectTag) {
+ trackingRectOwner_ = nil;
+ return;
+ }
+
+ if (tag == lastToolTipTag_) {
+ [super removeTrackingRect:tag];
+ lastToolTipTag_ = 0;
+ return;
+ }
+
+ // If any other tracking rect is being removed, we don't know how it was
+ // created and it's possible there's a leak involved (see Radar 3500217).
+ NOTREACHED();
+}
+
+// Override of (apparently) a private NSView method(!)
+- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
+ for (int i = 0; i < count; ++i) {
+ int tag = tags[i];
+ if (tag == 0)
+ continue;
+ DCHECK(tag == kTrackingRectTag);
+ trackingRectOwner_ = nil;
+ }
+}
+
+// Sends a fake NSMouseExited event to the view for its current tracking rect.
+- (void)_sendToolTipMouseExited {
+ // Nothing matters except window, trackingNumber, and userData.
+ int windowNumber = [[self window] windowNumber];
+ NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:windowNumber
+ context:NULL
+ eventNumber:0
+ trackingNumber:kTrackingRectTag
+ userData:trackingRectUserData_];
+ [trackingRectOwner_ mouseExited:fakeEvent];
+}
+
+// Sends a fake NSMouseEntered event to the view for its current tracking rect.
+- (void)_sendToolTipMouseEntered {
+ // Nothing matters except window, trackingNumber, and userData.
+ int windowNumber = [[self window] windowNumber];
+ NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:windowNumber
+ context:NULL
+ eventNumber:0
+ trackingNumber:kTrackingRectTag
+ userData:trackingRectUserData_];
+ [trackingRectOwner_ mouseEntered:fakeEvent];
+}
+
+// Sets the view's current tooltip, to be displayed at the current mouse
+// location. (This does not make the tooltip appear -- as usual, it only
+// appears after a delay.) Pass null to remove the tooltip.
+- (void)setToolTipAtMousePoint:(NSString *)string {
+ NSString *toolTip = [string length] == 0 ? nil : string;
+ if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
+ (!toolTip && !toolTip_)) {
+ return;
+ }
+
+ if (toolTip_) {
+ [self _sendToolTipMouseExited];
+ }
+
+ toolTip_.reset([toolTip copy]);
+
+ if (toolTip) {
+ // See radar 3500217 for why we remove all tooltips
+ // rather than just the single one we created.
+ [self removeAllToolTips];
+ NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
+ lastToolTipTag_ = [self addToolTipRect:wideOpenRect
+ owner:self
+ userData:NULL];
+ [self _sendToolTipMouseEntered];
+ }
+}
+
+// NSView calls this to get the text when displaying the tooltip.
+- (NSString *)view:(NSView *)view
+ stringForToolTip:(NSToolTipTag)tag
+ point:(NSPoint)point
+ userData:(void *)data {
+ return [[toolTip_ copy] autorelease];
+}
+
+// Below is our NSTextInputClient implementation.
+//
+// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
+// functions to process this event.
+//
+// [WebHTMLView keyDown] ->
+// EventHandler::keyEvent() ->
+// ...
+// [WebEditorClient handleKeyboardEvent] ->
+// [WebHTMLView _interceptEditingKeyEvent] ->
+// [NSResponder interpretKeyEvents] ->
+// [WebHTMLView insertText] ->
+// Editor::insertText()
+//
+// Unfortunately, it is hard for Chromium to use this implementation because
+// it causes key-typing jank.
+// RenderWidgetHostViewMac is running in a browser process. On the other
+// hand, Editor and EventHandler are running in a renderer process.
+// So, if we used this implementation, a NSKeyDown event is dispatched to
+// the following functions of Chromium.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Sync IPC (KeyDown)| (*1) ->
+// EventHandler::keyEvent() (renderer) ->
+// ...
+// EditorClientImpl::handleKeyboardEvent() (renderer) ->
+// |Sync IPC| (*2) ->
+// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC| ->
+// Editor::insertText() (renderer)
+//
+// (*1) we need to wait until this call finishes since WebHTMLView uses the
+// result of EventHandler::keyEvent().
+// (*2) we need to wait until this call finishes since WebEditorClient uses
+// the result of [WebHTMLView _interceptEditingKeyEvent].
+//
+// This needs many sync IPC messages sent between a browser and a renderer for
+// each key event, which would probably result in key-typing jank.
+// To avoid this problem, this implementation processes key events (and input
+// method events) totally in a browser process and sends asynchronous input
+// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
+// renderer process.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Async IPC (RawKeyDown)| ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC (Char)| ->
+// Editor::insertText() (renderer)
+//
+// Since this implementation doesn't have to wait any IPC calls, this doesn't
+// make any key-typing jank. --hbono 7/23/09
+//
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+- (NSArray *)validAttributesForMarkedText {
+ // This code is just copied from WebKit except renaming variables.
+ if (!validAttributesForMarkedText_) {
+ validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
+ NSUnderlineStyleAttributeName,
+ NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName,
+ nil]);
+ }
+ return validAttributesForMarkedText_.get();
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
+ DCHECK([self window]);
+ // |thePoint| is in screen coordinates, but needs to be converted to WebKit
+ // coordinates (upper left origin). Scroll offsets will be taken care of in
+ // the renderer.
+ thePoint = [[self window] convertScreenToBase:thePoint];
+ thePoint = [self convertPoint:thePoint fromView:nil];
+ thePoint.y = NSHeight([self frame]) - thePoint.y;
+
+ NSUInteger index =
+ TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
+ renderWidgetHostView_->render_widget_host_,
+ gfx::Point(thePoint.x, thePoint.y));
+ return index;
+}
+
+- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
+ actualRange:(NSRangePointer)actualRange {
+ NSRect rect;
+ if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
+ theRange,
+ &rect,
+ actualRange)) {
+ rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
+ renderWidgetHostView_->render_widget_host_, theRange);
+
+ // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
+ if (actualRange)
+ *actualRange = theRange;
+ }
+
+ // The returned rectangle is in WebKit coordinates (upper left origin), so
+ // flip the coordinate system.
+ NSRect viewFrame = [self frame];
+ rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
+ return rect;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)theRange
+ actualRange:(NSRangePointer)actualRange {
+ NSRect rect = [self firstViewRectForCharacterRange:theRange
+ actualRange:actualRange];
+
+ // Convert into screen coordinates for return.
+ rect = [self convertRect:rect toView:nil];
+ rect.origin = [[self window] convertBaseToScreen:rect.origin];
+ return rect;
+}
+
+- (NSRange)markedRange {
+ // An input method calls this method to check if an application really has
+ // a text being composed when hasMarkedText call returns true.
+ // Returns the range saved in the setMarkedText method so the input method
+ // calls the setMarkedText method and we can update the composition node
+ // there. (When this method returns an empty range, the input method doesn't
+ // call the setMarkedText method.)
+ return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
+ actualRange:(NSRangePointer)actualRange {
+ // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
+ if (actualRange)
+ *actualRange = range;
+ NSAttributedString* str =
+ TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
+ renderWidgetHostView_->render_widget_host_, range);
+ return str;
+}
+
+- (NSInteger)conversationIdentifier {
+ return reinterpret_cast<NSInteger>(self);
+}
+
+// Each RenderWidgetHostViewCocoa has its own input context, but we return
+// nil when the caret is in non-editable content or password box to avoid
+// making input methods do their work.
+- (NSTextInputContext *)inputContext {
+ if (focusedPluginIdentifier_ != -1)
+ return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
+
+ switch(renderWidgetHostView_->text_input_type_) {
+ case ui::TEXT_INPUT_TYPE_NONE:
+ case ui::TEXT_INPUT_TYPE_PASSWORD:
+ return nil;
+ default:
+ return [super inputContext];
+ }
+}
+
+- (BOOL)hasMarkedText {
+ // An input method calls this function to figure out whether or not an
+ // application is really composing a text. If it is composing, it calls
+ // the markedRange method, and maybe calls the setMarkedText method.
+ // It seems an input method usually calls this function when it is about to
+ // cancel an ongoing composition. If an application has a non-empty marked
+ // range, it calls the setMarkedText method to delete the range.
+ return hasMarkedText_;
+}
+
+- (void)unmarkText {
+ // Delete the composition node of the renderer and finish an ongoing
+ // composition.
+ // It seems an input method calls the setMarkedText method and set an empty
+ // text when it cancels an ongoing composition, i.e. I have never seen an
+ // input method calls this method.
+ hasMarkedText_ = NO;
+ markedText_.clear();
+ underlines_.clear();
+
+ // If we are handling a key down event, then ConfirmComposition() will be
+ // called in keyEvent: method.
+ if (!handlingKeyDown_) {
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
+ string16(), ui::Range::InvalidRange(), false);
+ } else {
+ unmarkTextCalled_ = YES;
+ }
+}
+
+- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
+ replacementRange:(NSRange)replacementRange {
+ // An input method updates the composition string.
+ // We send the given text and range to the renderer so it can update the
+ // composition node of WebKit.
+ // TODO(suzhe): It's hard for us to support replacementRange without accessing
+ // the full web content.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ int length = [im_text length];
+
+ // |markedRange_| will get set on a callback from ImeSetComposition().
+ selectedRange_ = newSelRange;
+ markedText_ = base::SysNSStringToUTF16(im_text);
+ hasMarkedText_ = (length > 0);
+
+ underlines_.clear();
+ if (isAttributedString) {
+ ExtractUnderlines(string, &underlines_);
+ } else {
+ // Use a thin black underline by default.
+ underlines_.push_back(
+ WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
+ }
+
+ // If we are handling a key down event, then SetComposition() will be
+ // called in keyEvent: method.
+ // Input methods of Mac use setMarkedText calls with an empty text to cancel
+ // an ongoing composition. So, we should check whether or not the given text
+ // is empty to update the input method state. (Our input method backend can
+ // automatically cancels an ongoing composition when we send an empty text.
+ // So, it is OK to send an empty text to the renderer.)
+ if (!handlingKeyDown_) {
+ renderWidgetHostView_->render_widget_host_->ImeSetComposition(
+ markedText_, underlines_,
+ newSelRange.location, NSMaxRange(newSelRange));
+ }
+}
+
+- (void)doCommandBySelector:(SEL)selector {
+ // An input method calls this function to dispatch an editing command to be
+ // handled by this view.
+ if (selector == @selector(noop:))
+ return;
+
+ std::string command(
+ [RenderWidgetHostViewMacEditCommandHelper::
+ CommandNameForSelector(selector) UTF8String]);
+
+ // If this method is called when handling a key down event, then we need to
+ // handle the command in the key event handler. Otherwise we can just handle
+ // it here.
+ if (handlingKeyDown_) {
+ hasEditCommands_ = YES;
+ // We ignore commands that insert characters, because this was causing
+ // strange behavior (e.g. tab always inserted a tab rather than moving to
+ // the next field on the page).
+ if (!StartsWithASCII(command, "insert", false))
+ editCommands_.push_back(EditCommand(command, ""));
+ } else {
+ RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
+ command, ""));
+ }
+}
+
+- (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
+ // An input method has characters to be inserted.
+ // Same as Linux, Mac calls this method not only:
+ // * when an input method finishs composing text, but also;
+ // * when we type an ASCII character (without using input methods).
+ // When we aren't using input methods, we should send the given character as
+ // a Char event so it is dispatched to an onkeypress() event handler of
+ // JavaScript.
+ // On the other hand, when we are using input methods, we should send the
+ // given characters as an input method event and prevent the characters from
+ // being dispatched to onkeypress() event handlers.
+ // Text inserting might be initiated by other source instead of keyboard
+ // events, such as the Characters dialog. In this case the text should be
+ // sent as an input method event as well.
+ // TODO(suzhe): It's hard for us to support replacementRange without accessing
+ // the full web content.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ if (handlingKeyDown_) {
+ textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
+ } else {
+ ui::Range replacement_range(replacementRange);
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
+ base::SysNSStringToUTF16(im_text), replacement_range, false);
+ }
+
+ // Inserting text will delete all marked text automatically.
+ hasMarkedText_ = NO;
+}
+
+- (void)insertText:(id)string {
+ [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
+}
+
+- (void)viewDidMoveToWindow {
+ if ([self window])
+ [self updateTabBackingStoreScaleFactor];
+
+ if (canBeKeyView_) {
+ NSWindow* newWindow = [self window];
+ // Pointer comparison only, since we don't know if lastWindow_ is still
+ // valid.
+ if (newWindow) {
+ // If we move into a new window, refresh the frame information. We
+ // don't need to do it if it was the same window as it used to be in,
+ // since that case is covered by WasShown(). We only want to do this for
+ // real browser views, not popups.
+ if (newWindow != lastWindow_) {
+ lastWindow_ = newWindow;
+ renderWidgetHostView_->WindowFrameChanged();
+ }
+ }
+ }
+
+ // If we switch windows (or are removed from the view hierarchy), cancel any
+ // open mouse-downs.
+ if (hasOpenMouseDown_) {
+ WebMouseEvent event;
+ event.type = WebInputEvent::MouseUp;
+ event.button = WebMouseEvent::ButtonLeft;
+ renderWidgetHostView_->ForwardMouseEvent(event);
+
+ hasOpenMouseDown_ = NO;
+ }
+
+ // Resize the view's layers to match the new window size.
+ ScopedCAActionDisabler disabler;
+ [renderWidgetHostView_->software_layer_
+ setFrame:NSRectToCGRect([self bounds])];
+ [renderWidgetHostView_->compositing_iosurface_layer_
+ setFrame:NSRectToCGRect([self bounds])];
+}
+
+- (void)undo:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->Undo();
+ }
+}
+
+- (void)redo:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->Redo();
+ }
+}
+
+- (void)cut:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->Cut();
+ }
+}
+
+- (void)copy:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->Copy();
+ }
+}
+
+- (void)copyToFindPboard:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->CopyToFindPboard();
+ }
+}
+
+- (void)paste:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->Paste();
+ }
+}
+
+- (void)pasteAndMatchStyle:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->PasteAndMatchStyle();
+ }
+}
+
+- (void)selectAll:(id)sender {
+ // editCommand_helper_ adds implementations for most NSResponder methods
+ // dynamically. But the renderer side only sends selection results back to
+ // the browser if they were triggered by a keyboard event or went through
+ // one of the Select methods on RWH. Since selectAll: is called from the
+ // menu handler, neither is true.
+ // Explicitly call SelectAll() here to make sure the renderer returns
+ // selection results.
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHostImpl*>(
+ renderWidgetHostView_->render_widget_host_)->SelectAll();
+ }
+}
+
+- (void)startSpeaking:(id)sender {
+ renderWidgetHostView_->SpeakSelection();
+}
+
+- (void)stopSpeaking:(id)sender {
+ renderWidgetHostView_->StopSpeaking();
+}
+
+- (void)cancelComposition {
+ if (!hasMarkedText_)
+ return;
+
+ // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
+ // doesn't call any NSTextInput functions, such as setMarkedText or
+ // insertText. So, we need to send an IPC message to a renderer so it can
+ // delete the composition node.
+ NSInputManager *currentInputManager = [NSInputManager currentInputManager];
+ [currentInputManager markedTextAbandoned:self];
+
+ hasMarkedText_ = NO;
+ // Should not call [self unmarkText] here, because it'll send unnecessary
+ // cancel composition IPC message to the renderer.
+}
+
+- (void)confirmComposition {
+ if (!hasMarkedText_)
+ return;
+
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
+ string16(), ui::Range::InvalidRange(), false);
+
+ [self cancelComposition];
+}
+
+- (void)setPluginImeActive:(BOOL)active {
+ if (active == pluginImeActive_)
+ return;
+
+ pluginImeActive_ = active;
+ if (!active) {
+ [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ string16(), focusedPluginIdentifier_);
+ }
+}
+
+- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
+ if (focused)
+ focusedPluginIdentifier_ = pluginId;
+ else if (focusedPluginIdentifier_ == pluginId)
+ focusedPluginIdentifier_ = -1;
+
+ // Whenever plugin focus changes, plugin IME resets.
+ [self setPluginImeActive:NO];
+}
+
+- (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
+ if (!pluginImeActive_)
+ return false;
+
+ ComplexTextInputPanel* inputPanel =
+ [ComplexTextInputPanel sharedComplexTextInputPanel];
+ NSString* composited_string = nil;
+ BOOL handled = [inputPanel interpretKeyEvent:event
+ string:&composited_string];
+ if (composited_string) {
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
+ pluginImeActive_ = NO;
+ }
+ return handled;
+}
+
+- (void)checkForPluginImeCancellation {
+ if (pluginImeActive_ &&
+ ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ string16(), focusedPluginIdentifier_);
+ pluginImeActive_ = NO;
+ }
+}
+
+// Overriding a NSResponder method to support application services.
+
+- (id)validRequestorForSendType:(NSString*)sendType
+ returnType:(NSString*)returnType {
+ id requestor = nil;
+ BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
+ BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
+ BOOL hasText = !renderWidgetHostView_->selected_text().empty();
+ BOOL takesText =
+ renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
+
+ if (sendTypeIsString && hasText && !returnType) {
+ requestor = self;
+ } else if (!sendType && returnTypeIsString && takesText) {
+ requestor = self;
+ } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
+ requestor = self;
+ } else {
+ requestor = [super validRequestorForSendType:sendType
+ returnType:returnType];
+ }
+ return requestor;
+}
+
+- (void)viewWillStartLiveResize {
+ [super viewWillStartLiveResize];
+ RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
+ if (widget)
+ widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
+}
+
+- (void)viewDidEndLiveResize {
+ [super viewDidEndLiveResize];
+ RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
+ if (widget)
+ widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
+}
+
+- (void)updateCursor:(NSCursor*)cursor {
+ if (currentCursor_ == cursor)
+ return;
+
+ currentCursor_.reset([cursor retain]);
+ [[self window] invalidateCursorRectsForView:self];
+}
+
+- (void)popupWindowWillClose:(NSNotification *)notification {
+ renderWidgetHostView_->KillSelf();
+}
+
+- (void)updateSoftwareLayerScaleFactor {
+ if (![renderWidgetHostView_->software_layer_
+ respondsToSelector:@selector(setContentsScale:)])
+ return;
+
+ ScopedCAActionDisabler disabler;
+ [renderWidgetHostView_->software_layer_ setContentsScale:deviceScaleFactor_];
+}
+
+// Delegate methods for the software CALayer
+- (void)drawLayer:(CALayer*)layer
+ inContext:(CGContextRef)context {
+ TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawLayer");
+
+ DCHECK(renderWidgetHostView_->use_core_animation_);
+ DCHECK([layer isEqual:renderWidgetHostView_->software_layer_]);
+
+ CGRect clipRect = CGContextGetClipBoundingBox(context);
+
+ if (!renderWidgetHostView_->render_widget_host_ ||
+ renderWidgetHostView_->is_hidden()) {
+ CGContextSetFillColorWithColor(context,
+ CGColorGetConstantColor(kCGColorWhite));
+ CGContextFillRect(context, clipRect);
+ return;
+ }
+
+ renderWidgetHostView_->about_to_validate_and_paint_ = true;
+ BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
+ renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
+ renderWidgetHostView_->about_to_validate_and_paint_ = false;
+
+ [self drawBackingStore:backingStore
+ dirtyRect:clipRect
+ inContext:context];
+}
+
+- (void)setNeedsDisplay:(BOOL)flag {
+ [renderWidgetHostView_->software_layer_ setNeedsDisplay];
+ [super setNeedsDisplay:flag];
+}
+
+- (void)setNeedsDisplayInRect:(NSRect)rect {
+ [renderWidgetHostView_->software_layer_
+ setNeedsDisplayInRect:NSRectToCGRect(rect)];
+ [super setNeedsDisplayInRect:rect];
+}
+
+@end
+
+//
+// Supporting application services
+//
+@implementation RenderWidgetHostViewCocoa(NSServicesRequests)
+
+- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
+ types:(NSArray*)types {
+ const std::string& str = renderWidgetHostView_->selected_text();
+ if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
+
+ base::scoped_nsobject<NSString> text(
+ [[NSString alloc] initWithUTF8String:str.c_str()]);
+ NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
+ [pboard declareTypes:toDeclare owner:nil];
+ return [pboard setString:text forType:NSStringPboardType];
+}
+
+- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
+ NSString *string = [pboard stringForType:NSStringPboardType];
+ if (!string) return NO;
+
+ // If the user is currently using an IME, confirm the IME input,
+ // and then insert the text from the service, the same as TextEdit and Safari.
+ [self confirmComposition];
+ [self insertText:string];
+ return YES;
+}
+
+- (BOOL)isOpaque {
+ if (renderWidgetHostView_->use_core_animation_)
+ return YES;
+ return [super isOpaque];
+}
+
+@end
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h b/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h
new file mode 100644
index 00000000000..79ccc4d2b5e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h
@@ -0,0 +1,52 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_DICTIONARY_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_DICTIONARY_HELPER_H_
+
+#include "base/basictypes.h"
+#include "ui/gfx/vector2d.h"
+
+namespace content {
+
+class RenderWidgetHostView;
+class RenderWidgetHostViewMac;
+class RenderWidgetHostViewPort;
+
+// A helper class to bring up definition of word for a RWHV.
+//
+// This is triggered by "Lookup in Dictionary" context menu item.
+// Either uses Dictionary.app or a light-weight dictionary panel, based on
+// system settings.
+class RenderWidgetHostViewMacDictionaryHelper {
+ public:
+ explicit RenderWidgetHostViewMacDictionaryHelper(
+ RenderWidgetHostViewPort* view);
+
+ // Overrides the view to use to bring up dictionary panel.
+ // This |target_view| can be different from |view_|, |view_| is used to get
+ // the current selection value where |target_view| is used to bring up the
+ // cocoa dictionary panel.
+ void SetTargetView(RenderWidgetHostView* target_view);
+ void set_offset(const gfx::Vector2d& offset) { offset_ = offset; }
+
+ // Brings up either Dictionary.app or a light-weight dictionary panel,
+ // depending on system settings.
+ void ShowDefinitionForSelection();
+
+ private:
+ // This class shows definition for this view.
+ RenderWidgetHostViewMac* view_;
+ // This view is use to bring up the dictionary panel. Generally this is the
+ // same as |view_|. One can override the view to use via SetTargetView().
+ RenderWidgetHostViewMac* target_view_;
+ // The extra offset to use while positioning the dicitonary panel.
+ gfx::Vector2d offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacDictionaryHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_DICTIONARY_HELPER_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.mm b/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.mm
new file mode 100644
index 00000000000..8402a43267c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.mm
@@ -0,0 +1,58 @@
+// 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 "base/strings/sys_string_conversions.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+namespace content {
+
+RenderWidgetHostViewMacDictionaryHelper::
+ RenderWidgetHostViewMacDictionaryHelper(RenderWidgetHostViewPort* view)
+ : view_(static_cast<RenderWidgetHostViewMac*>(view)),
+ target_view_(static_cast<RenderWidgetHostViewMac*>(view)) {
+}
+
+void RenderWidgetHostViewMacDictionaryHelper::SetTargetView(
+ RenderWidgetHostView* target_view) {
+ target_view_ = static_cast<RenderWidgetHostViewMac*>(target_view);
+}
+
+void RenderWidgetHostViewMacDictionaryHelper::ShowDefinitionForSelection() {
+ NSRange selection_range = [view_->cocoa_view() selectedRange];
+ NSAttributedString* attr_string =
+ [view_->cocoa_view() attributedSubstringForProposedRange:selection_range
+ actualRange:nil];
+ if (!attr_string) {
+ if (view_->selected_text().empty())
+ return;
+ // The PDF plugin does not support getting the attributed string. Until it
+ // does, use NSPerformService(), which opens Dictionary.app.
+ // http://crbug.com/152438
+ // TODO(asvitkine): This should be removed after the above support is added.
+ NSString* text = base::SysUTF8ToNSString(view_->selected_text());
+ NSPasteboard* pasteboard = [NSPasteboard pasteboardWithUniqueName];
+ NSArray* types = [NSArray arrayWithObject:NSStringPboardType];
+ [pasteboard declareTypes:types owner:nil];
+ if ([pasteboard setString:text forType:NSStringPboardType])
+ NSPerformService(@"Look Up in Dictionary", pasteboard);
+ return;
+ }
+
+ NSRect rect =
+ [view_->cocoa_view() firstViewRectForCharacterRange:selection_range
+ actualRange:nil];
+
+ NSDictionary* attrs = [attr_string attributesAtIndex:0 effectiveRange:nil];
+ NSFont* font = [attrs objectForKey:NSFontAttributeName];
+ rect.origin.y += NSHeight(rect) - [font ascender];
+
+ rect.origin.x += offset_.x();
+ rect.origin.y += offset_.y();
+
+ [target_view_->cocoa_view() showDefinitionForAttributedString:attr_string
+ atPoint:rect.origin];
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h
new file mode 100644
index 00000000000..9ba16b32162
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h
@@ -0,0 +1,73 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/gtest_prod_util.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+namespace content {
+
+// This class mimics the behavior of WebKit's WebView class in a way that makes
+// sense for Chrome.
+//
+// WebCore has the concept of "core commands", basically named actions such as
+// "Select All" and "Move Cursor Left". The commands are executed using their
+// string value by WebCore.
+//
+// This class is responsible for 2 things:
+// 1. Provide an abstraction to determine the enabled/disabled state of menu
+// items that correspond to edit commands.
+// 2. Hook up a bunch of objc selectors to the RenderWidgetHostViewCocoa object.
+// (note that this is not a misspelling of RenderWidgetHostViewMac, it's in
+// fact a distinct object) When these selectors are called, the relevant
+// edit command is executed in WebCore.
+class CONTENT_EXPORT RenderWidgetHostViewMacEditCommandHelper {
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestAddEditingSelectorsToClass);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestEditingCommandDelivery);
+
+ public:
+ RenderWidgetHostViewMacEditCommandHelper();
+ ~RenderWidgetHostViewMacEditCommandHelper();
+
+ // Adds editing selectors to the objc class using the objc runtime APIs.
+ // Each selector is connected to a single c method which forwards the message
+ // to WebCore's ExecuteEditCommand() function.
+ // This method is idempotent.
+ // The class passed in must conform to the RenderWidgetHostViewMacOwner
+ // protocol.
+ void AddEditingSelectorsToClass(Class klass);
+
+ // Is a given menu item currently enabled?
+ // SEL - the objc selector currently associated with an NSMenuItem.
+ // owner - An object we can retrieve a RenderWidgetHostViewMac from to
+ // determine the command states.
+ bool IsMenuItemEnabled(SEL item_action,
+ id<RenderWidgetHostViewMacOwner> owner);
+
+ // Converts an editing selector into a command name that can be sent to
+ // webkit.
+ static NSString* CommandNameForSelector(SEL selector);
+
+ protected:
+ // Gets a list of all the selectors that AddEditingSelectorsToClass adds to
+ // the aforementioned class.
+ // returns an array of NSStrings WITHOUT the trailing ':'s.
+ NSArray* GetEditSelectorNames();
+
+ private:
+ base::hash_set<std::string> edit_command_set_;
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacEditCommandHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm
new file mode 100644
index 00000000000..14ee158199f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm
@@ -0,0 +1,240 @@
+// 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.
+
+#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+
+#import <objc/runtime.h>
+
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+namespace content {
+namespace {
+
+// The names of all the objc selectors w/o ':'s added to an object by
+// AddEditingSelectorsToClass().
+//
+// This needs to be kept in Sync with WEB_COMMAND list in the WebKit tree at:
+// WebKit/mac/WebView/WebHTMLView.mm .
+const char* kEditCommands[] = {
+ "alignCenter",
+ "alignJustified",
+ "alignLeft",
+ "alignRight",
+ "copy",
+ "cut",
+ "delete",
+ "deleteBackward",
+ "deleteBackwardByDecomposingPreviousCharacter",
+ "deleteForward",
+ "deleteToBeginningOfLine",
+ "deleteToBeginningOfParagraph",
+ "deleteToEndOfLine",
+ "deleteToEndOfParagraph",
+ "deleteToMark",
+ "deleteWordBackward",
+ "deleteWordForward",
+ "ignoreSpelling",
+ "indent",
+ "insertBacktab",
+ "insertLineBreak",
+ "insertNewline",
+ "insertNewlineIgnoringFieldEditor",
+ "insertParagraphSeparator",
+ "insertTab",
+ "insertTabIgnoringFieldEditor",
+ "makeTextWritingDirectionLeftToRight",
+ "makeTextWritingDirectionNatural",
+ "makeTextWritingDirectionRightToLeft",
+ "moveBackward",
+ "moveBackwardAndModifySelection",
+ "moveDown",
+ "moveDownAndModifySelection",
+ "moveForward",
+ "moveForwardAndModifySelection",
+ "moveLeft",
+ "moveLeftAndModifySelection",
+ "moveParagraphBackwardAndModifySelection",
+ "moveParagraphForwardAndModifySelection",
+ "moveRight",
+ "moveRightAndModifySelection",
+ "moveToBeginningOfDocument",
+ "moveToBeginningOfDocumentAndModifySelection",
+ "moveToBeginningOfLine",
+ "moveToBeginningOfLineAndModifySelection",
+ "moveToBeginningOfParagraph",
+ "moveToBeginningOfParagraphAndModifySelection",
+ "moveToBeginningOfSentence",
+ "moveToBeginningOfSentenceAndModifySelection",
+ "moveToEndOfDocument",
+ "moveToEndOfDocumentAndModifySelection",
+ "moveToEndOfLine",
+ "moveToEndOfLineAndModifySelection",
+ "moveToEndOfParagraph",
+ "moveToEndOfParagraphAndModifySelection",
+ "moveToEndOfSentence",
+ "moveToEndOfSentenceAndModifySelection",
+ "moveUp",
+ "moveUpAndModifySelection",
+ "moveWordBackward",
+ "moveWordBackwardAndModifySelection",
+ "moveWordForward",
+ "moveWordForwardAndModifySelection",
+ "moveWordLeft",
+ "moveWordLeftAndModifySelection",
+ "moveWordRight",
+ "moveWordRightAndModifySelection",
+ "outdent",
+ "pageDown",
+ "pageDownAndModifySelection",
+ "pageUp",
+ "pageUpAndModifySelection",
+ "selectAll",
+ "selectLine",
+ "selectParagraph",
+ "selectSentence",
+ "selectToMark",
+ "selectWord",
+ "setMark",
+ "showGuessPanel",
+ "subscript",
+ "superscript",
+ "swapWithMark",
+ "transpose",
+ "underline",
+ "unscript",
+ "yank",
+ "yankAndSelect"};
+
+
+// This function is installed via the objc runtime as the implementation of all
+// the various editing selectors.
+// The objc runtime hookup occurs in
+// RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass().
+//
+// self - the object we're attached to; it must implement the
+// RenderWidgetHostViewMacOwner protocol.
+// _cmd - the selector that fired.
+// sender - the id of the object that sent the message.
+//
+// The selector is translated into an edit comand and then forwarded down the
+// pipeline to WebCore.
+// The route the message takes is:
+// RenderWidgetHostViewMac -> RenderViewHost ->
+// | IPC | ->
+// RenderView -> currently focused WebFrame.
+// The WebFrame is in the Chrome glue layer and forwards the message to WebCore.
+void EditCommandImp(id self, SEL _cmd, id sender) {
+ // Make sure |self| is the right type.
+ DCHECK([self conformsToProtocol:@protocol(RenderWidgetHostViewMacOwner)]);
+
+ // SEL -> command name string.
+ NSString* command_name_ns =
+ RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(_cmd);
+ std::string command([command_name_ns UTF8String]);
+
+ // Forward the edit command string down the pipeline.
+ RenderWidgetHostViewMac* rwhv = [(id<RenderWidgetHostViewMacOwner>)self
+ renderWidgetHostViewMac];
+ DCHECK(rwhv);
+
+ RenderWidgetHostImpl* rwh =
+ RenderWidgetHostImpl::From(rwhv->GetRenderWidgetHost());
+ // The second parameter is the core command value which isn't used here.
+ rwh->ExecuteEditCommand(command, "");
+}
+
+} // namespace
+
+// Maps an objc-selector to a core command name.
+//
+// Returns the core command name (which is the selector name with the trailing
+// ':' stripped in most cases).
+//
+// Adapted from a function by the same name in
+// WebKit/mac/WebView/WebHTMLView.mm .
+// Capitalized names are returned from this function, but that's simply
+// matching WebHTMLView.mm.
+NSString* RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(
+ SEL selector) {
+ if (selector == @selector(insertParagraphSeparator:) ||
+ selector == @selector(insertNewlineIgnoringFieldEditor:))
+ return @"InsertNewline";
+ if (selector == @selector(insertTabIgnoringFieldEditor:))
+ return @"InsertTab";
+ if (selector == @selector(pageDown:))
+ return @"MovePageDown";
+ if (selector == @selector(pageDownAndModifySelection:))
+ return @"MovePageDownAndModifySelection";
+ if (selector == @selector(pageUp:))
+ return @"MovePageUp";
+ if (selector == @selector(pageUpAndModifySelection:))
+ return @"MovePageUpAndModifySelection";
+
+ // Remove the trailing colon.
+ NSString* selector_str = NSStringFromSelector(selector);
+ int selector_len = [selector_str length];
+ return [selector_str substringToIndex:selector_len - 1];
+}
+
+RenderWidgetHostViewMacEditCommandHelper::
+ RenderWidgetHostViewMacEditCommandHelper() {
+ for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
+ edit_command_set_.insert(kEditCommands[i]);
+ }
+}
+
+RenderWidgetHostViewMacEditCommandHelper::
+ ~RenderWidgetHostViewMacEditCommandHelper() {}
+
+// Dynamically adds Selectors to the aformentioned class.
+void RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass(
+ Class klass) {
+ for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
+ // Append trailing ':' to command name to get selector name.
+ NSString* sel_str = [NSString stringWithFormat: @"%s:", kEditCommands[i]];
+
+ SEL edit_selector = NSSelectorFromString(sel_str);
+ // May want to use @encode() for the last parameter to this method.
+ // If class_addMethod fails we assume that all the editing selectors where
+ // added to the class.
+ // If a certain class already implements a method then class_addMethod
+ // returns NO, which we can safely ignore.
+ class_addMethod(klass, edit_selector, (IMP)EditCommandImp, "v@:@");
+ }
+}
+
+bool RenderWidgetHostViewMacEditCommandHelper::IsMenuItemEnabled(
+ SEL item_action,
+ id<RenderWidgetHostViewMacOwner> owner) {
+ const char* selector_name = sel_getName(item_action);
+ // TODO(jeremy): The final form of this function will check state
+ // associated with the Browser.
+
+ // For now just mark all edit commands as enabled.
+ NSString* selector_name_ns = [NSString stringWithUTF8String:selector_name];
+
+ // Remove trailing ':'
+ size_t str_len = [selector_name_ns length];
+ selector_name_ns = [selector_name_ns substringToIndex:str_len - 1];
+ std::string edit_command_name([selector_name_ns UTF8String]);
+
+ // search for presence in set and return.
+ bool ret = edit_command_set_.find(edit_command_name) !=
+ edit_command_set_.end();
+ return ret;
+}
+
+NSArray* RenderWidgetHostViewMacEditCommandHelper::GetEditSelectorNames() {
+ size_t num_edit_commands = arraysize(kEditCommands);
+ NSMutableArray* ret = [NSMutableArray arrayWithCapacity:num_edit_commands];
+
+ for (size_t i = 0; i < num_edit_commands; ++i) {
+ [ret addObject:[NSString stringWithUTF8String:kEditCommands[i]]];
+ }
+
+ return ret;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
new file mode 100644
index 00000000000..0d3ee0a8aa0
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
@@ -0,0 +1,182 @@
+// 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.
+
+#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/input_messages.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+using content::RenderWidgetHostViewMac;
+
+// Bare bones obj-c class for testing purposes.
+@interface RenderWidgetHostViewMacEditCommandHelperTestClass : NSObject
+@end
+
+@implementation RenderWidgetHostViewMacEditCommandHelperTestClass
+@end
+
+// Class that owns a RenderWidgetHostViewMac.
+@interface RenderWidgetHostViewMacOwner :
+ NSObject<RenderWidgetHostViewMacOwner> {
+ RenderWidgetHostViewMac* rwhvm_;
+}
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm;
+@end
+
+@implementation RenderWidgetHostViewMacOwner
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm {
+ if ((self = [super init])) {
+ rwhvm_ = rwhvm;
+ }
+ return self;
+}
+
+- (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
+ return rwhvm_;
+}
+
+@end
+
+namespace content {
+namespace {
+
+// Returns true if all the edit command names in the array are present in
+// test_obj. edit_commands is a list of NSStrings, selector names are formed
+// by appending a trailing ':' to the string.
+bool CheckObjectRespondsToEditCommands(NSArray* edit_commands, id test_obj) {
+ for (NSString* edit_command_name in edit_commands) {
+ NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
+ if (![test_obj respondsToSelector:NSSelectorFromString(sel_str)]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+// Create a RenderWidget for which we can filter messages.
+class RenderWidgetHostEditCommandCounter : public RenderWidgetHostImpl {
+ public:
+ RenderWidgetHostEditCommandCounter(
+ RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id)
+ : RenderWidgetHostImpl(delegate, process, routing_id),
+ edit_command_message_count_(0) {
+ }
+
+ virtual bool Send(IPC::Message* message) OVERRIDE {
+ if (message->type() == InputMsg_ExecuteEditCommand::ID)
+ edit_command_message_count_++;
+ return RenderWidgetHostImpl::Send(message);
+ }
+
+ unsigned int edit_command_message_count_;
+};
+
+class RenderWidgetHostViewMacEditCommandHelperTest : public PlatformTest {
+};
+
+} // namespace
+
+// Tests that editing commands make it through the pipeline all the way to
+// RenderWidgetHost.
+// Disabled, http://crbug.com/93286.
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest,
+ DISABLED_TestEditingCommandDelivery) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ NSArray* edit_command_strings = helper.GetEditSelectorNames();
+
+ // Set up a mock render widget and set expectations.
+ base::MessageLoopForUI message_loop;
+ TestBrowserContext browser_context;
+ MockRenderProcessHost mock_process(&browser_context);
+ MockRenderWidgetHostDelegate delegate;
+ RenderWidgetHostEditCommandCounter render_widget(&delegate, &mock_process, 0);
+
+ // RenderWidgetHostViewMac self destructs (RenderWidgetHostViewMacCocoa
+ // takes ownership) so no need to delete it ourselves.
+ RenderWidgetHostViewMac* rwhvm = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(&render_widget));
+
+ RenderWidgetHostViewMacOwner* rwhwvm_owner =
+ [[[RenderWidgetHostViewMacOwner alloc]
+ initWithRenderWidgetHostViewMac:rwhvm] autorelease];
+
+ helper.AddEditingSelectorsToClass([rwhwvm_owner class]);
+
+ for (NSString* edit_command_name in edit_command_strings) {
+ NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
+ [rwhwvm_owner performSelector:NSSelectorFromString(sel_str) withObject:nil];
+ }
+
+ size_t num_edit_commands = [edit_command_strings count];
+ EXPECT_EQ(render_widget.edit_command_message_count_, num_edit_commands);
+}
+
+// Test RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestAddEditingSelectorsToClass) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ NSArray* edit_command_strings = helper.GetEditSelectorNames();
+ ASSERT_GT([edit_command_strings count], 0U);
+
+ // Create a class instance and add methods to the class.
+ RenderWidgetHostViewMacEditCommandHelperTestClass* test_obj =
+ [[[RenderWidgetHostViewMacEditCommandHelperTestClass alloc] init]
+ autorelease];
+
+ // Check that edit commands aren't already attached to the object.
+ ASSERT_FALSE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+
+ helper.AddEditingSelectorsToClass([test_obj class]);
+
+ // Check that all edit commands where added.
+ ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+
+ // AddEditingSelectorsToClass() should be idempotent.
+ helper.AddEditingSelectorsToClass([test_obj class]);
+
+ // Check that all edit commands are still there.
+ ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+}
+
+// Test RenderWidgetHostViewMacEditCommandHelper::IsMenuItemEnabled.
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest, TestMenuItemEnabling) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ RenderWidgetHostViewMacOwner* rwhvm_owner =
+ [[[RenderWidgetHostViewMacOwner alloc] init] autorelease];
+
+ // The select all menu should always be enabled.
+ SEL select_all = NSSelectorFromString(@"selectAll:");
+ ASSERT_TRUE(helper.IsMenuItemEnabled(select_all, rwhvm_owner));
+
+ // Random selectors should be enabled by the function.
+ SEL garbage_selector = NSSelectorFromString(@"randomGarbageSelector:");
+ ASSERT_FALSE(helper.IsMenuItemEnabled(garbage_selector, rwhvm_owner));
+
+ // TODO(jeremy): Currently IsMenuItemEnabled just returns true for all edit
+ // selectors. Once we go past that we should do more extensive testing here.
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/chromium/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
new file mode 100644
index 00000000000..3c91bc6abdf
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -0,0 +1,785 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/input_messages.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_widget_host_view_mac_delegate.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/test/cocoa_test_event_utils.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+// Declare things that are part of the 10.7 SDK.
+#if !defined(MAC_OS_X_VERSION_10_7) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
+enum {
+ NSEventPhaseNone = 0, // event not associated with a phase.
+ NSEventPhaseBegan = 0x1 << 0,
+ NSEventPhaseStationary = 0x1 << 1,
+ NSEventPhaseChanged = 0x1 << 2,
+ NSEventPhaseEnded = 0x1 << 3,
+ NSEventPhaseCancelled = 0x1 << 4,
+};
+typedef NSUInteger NSEventPhase;
+
+@interface NSEvent (LionAPI)
+- (NSEventPhase)phase;
+@end
+
+#endif // 10.7
+
+// Helper class with methods used to mock -[NSEvent phase], used by
+// |MockScrollWheelEventWithPhase()|.
+@interface MockPhaseMethods : NSObject {
+}
+
+- (NSEventPhase)phaseBegan;
+- (NSEventPhase)phaseChanged;
+- (NSEventPhase)phaseEnded;
+@end
+
+@implementation MockPhaseMethods
+
+- (NSEventPhase)phaseBegan {
+ return NSEventPhaseBegan;
+}
+- (NSEventPhase)phaseChanged {
+ return NSEventPhaseChanged;
+}
+- (NSEventPhase)phaseEnded {
+ return NSEventPhaseEnded;
+}
+
+@end
+
+@interface MockRenderWidgetHostViewMacDelegate
+ : NSObject<RenderWidgetHostViewMacDelegate> {
+ BOOL unhandledWheelEventReceived_;
+}
+
+@property(nonatomic) BOOL unhandledWheelEventReceived;
+
+- (void)gotUnhandledWheelEvent;
+@end
+
+@implementation MockRenderWidgetHostViewMacDelegate
+
+@synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
+
+- (void)gotUnhandledWheelEvent {
+ unhandledWheelEventReceived_ = true;
+}
+
+@end
+
+namespace content {
+
+namespace {
+
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
+ public:
+ MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id)
+ : RenderWidgetHostImpl(delegate, process, routing_id) {
+ }
+
+ MOCK_METHOD0(Focus, void());
+ MOCK_METHOD0(Blur, void());
+};
+
+// Generates the |length| of composition rectangle vector and save them to
+// |output|. It starts from |origin| and each rectangle contains |unit_size|.
+void GenerateCompositionRectArray(const gfx::Point& origin,
+ const gfx::Size& unit_size,
+ size_t length,
+ const std::vector<size_t>& break_points,
+ std::vector<gfx::Rect>* output) {
+ DCHECK(output);
+ output->clear();
+
+ std::queue<int> break_point_queue;
+ for (size_t i = 0; i < break_points.size(); ++i)
+ break_point_queue.push(break_points[i]);
+ break_point_queue.push(length);
+ size_t next_break_point = break_point_queue.front();
+ break_point_queue.pop();
+
+ gfx::Rect current_rect(origin, unit_size);
+ for (size_t i = 0; i < length; ++i) {
+ if (i == next_break_point) {
+ current_rect.set_x(origin.x());
+ current_rect.set_y(current_rect.y() + current_rect.height());
+ next_break_point = break_point_queue.front();
+ break_point_queue.pop();
+ }
+ output->push_back(current_rect);
+ current_rect.set_x(current_rect.right());
+ }
+}
+
+gfx::Rect GetExpectedRect(const gfx::Point& origin,
+ const gfx::Size& size,
+ const ui::Range& range,
+ int line_no) {
+ return gfx::Rect(
+ origin.x() + range.start() * size.width(),
+ origin.y() + line_no * size.height(),
+ range.length() * size.width(),
+ size.height());
+}
+
+// Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
+// correspond to a method in |MockPhaseMethods| that returns the desired phase.
+NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
+ CGEventRef cg_event =
+ CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
+ NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
+ CFRelease(cg_event);
+ method_setImplementation(
+ class_getInstanceMethod([NSEvent class], @selector(phase)),
+ [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
+ return event;
+}
+
+} // namespace
+
+class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
+ public:
+ RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
+
+ virtual void SetUp() {
+ RenderViewHostImplTestHarness::SetUp();
+
+ // TestRenderViewHost's destruction assumes that its view is a
+ // TestRenderWidgetHostView, so store its view and reset it back to the
+ // stored view in |TearDown()|.
+ old_rwhv_ = rvh()->GetView();
+
+ // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
+ rwhv_mac_ = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(rvh()));
+ rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
+ }
+ virtual void TearDown() {
+ // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
+ rwhv_cocoa_.reset();
+ pool_.Recycle();
+ base::MessageLoop::current()->RunUntilIdle();
+ pool_.Recycle();
+
+ // See comment in SetUp().
+ test_rvh()->SetView(old_rwhv_);
+
+ RenderViewHostImplTestHarness::TearDown();
+ }
+ protected:
+ private:
+ // This class isn't derived from PlatformTest.
+ base::mac::ScopedNSAutoreleasePool pool_;
+
+ RenderWidgetHostView* old_rwhv_;
+
+ protected:
+ RenderWidgetHostViewMac* rwhv_mac_;
+ base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
+};
+
+TEST_F(RenderWidgetHostViewMacTest, Basic) {
+}
+
+TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
+ // The RWHVCocoa should normally accept first responder status.
+ EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ // Unless we tell it not to.
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
+ EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ // But we can set things back to the way they were originally.
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(false);
+ EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
+}
+
+TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) {
+ base::scoped_nsobject<CocoaTestHelperWindow> window(
+ [[CocoaTestHelperWindow alloc] init]);
+ [[window contentView] addSubview:rwhv_cocoa_.get()];
+
+ // Even if the RWHVCocoa disallows first responder, clicking on it gives it
+ // focus.
+ [window setPretendIsKeyWindow:YES];
+ [window makeFirstResponder:nil];
+ ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]);
+
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
+ EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ std::pair<NSEvent*, NSEvent*> clicks =
+ cocoa_test_event_utils::MouseClickInView(rwhv_cocoa_.get(), 1);
+ [rwhv_cocoa_.get() mouseDown:clicks.first];
+ EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
+}
+
+TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
+ rwhv_mac_->InitAsFullscreen(NULL);
+ EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
+
+ // Break the reference cycle caused by pepper_fullscreen_window() without
+ // an <esc> event. See comment in
+ // release_pepper_fullscreen_window_for_testing().
+ rwhv_mac_->release_pepper_fullscreen_window_for_testing();
+}
+
+// Verify that escape key down in fullscreen mode suppressed the keyup event on
+// the parent.
+TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
+ // Use our own RWH since we need to destroy it.
+ MockRenderWidgetHostDelegate delegate;
+ TestBrowserContext browser_context;
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(&browser_context);
+ // Owned by its |cocoa_view()|.
+ RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
+ &delegate, process_host, MSG_ROUTING_NONE);
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(rwh));
+
+ view->InitAsFullscreen(rwhv_mac_);
+
+ WindowedNotificationObserver observer(
+ NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(rwh));
+ EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
+
+ // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
+ // the parent.
+ [view->cocoa_view() keyEvent:
+ cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
+ observer.Wait();
+ EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
+
+ // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
+ [rwhv_mac_->cocoa_view() keyEvent:
+ cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
+ EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
+}
+
+// Test that command accelerators which destroy the fullscreen window
+// don't crash when forwarded via the window's responder machinery.
+TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
+ // Use our own RWH since we need to destroy it.
+ MockRenderWidgetHostDelegate delegate;
+ TestBrowserContext browser_context;
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(&browser_context);
+ // Owned by its |cocoa_view()|.
+ RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
+ &delegate, process_host, MSG_ROUTING_NONE);
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(rwh));
+
+ view->InitAsFullscreen(rwhv_mac_);
+
+ WindowedNotificationObserver observer(
+ NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(rwh));
+
+ // Command-ESC will destroy the view, while the window is still in
+ // |-performKeyEquivalent:|. There are other cases where this can
+ // happen, Command-ESC is the easiest to trigger.
+ [[view->cocoa_view() window] performKeyEquivalent:
+ cocoa_test_event_utils::KeyEventWithKeyCode(
+ 53, 27, NSKeyDown, NSCommandKeyMask)];
+ observer.Wait();
+}
+
+TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
+ const string16 kDummyString = UTF8ToUTF16("hogehoge");
+ const size_t kDummyOffset = 0;
+
+ gfx::Rect caret_rect(10, 11, 0, 10);
+ ui::Range caret_range(0, 0);
+ ViewHostMsg_SelectionBounds_Params params;
+
+ NSRect rect;
+ NSRange actual_range;
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ params.anchor_rect = params.focus_rect = caret_rect;
+ params.anchor_dir = params.focus_dir = WebKit::WebTextDirectionLeftToRight;
+ rwhv_mac_->SelectionBoundsChanged(params);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ caret_range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
+ EXPECT_EQ(caret_range, ui::Range(actual_range));
+
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 3).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ // Caret moved.
+ caret_rect = gfx::Rect(20, 11, 0, 10);
+ caret_range = ui::Range(1, 1);
+ params.anchor_rect = params.focus_rect = caret_rect;
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ rwhv_mac_->SelectionBoundsChanged(params);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ caret_range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
+ EXPECT_EQ(caret_range, ui::Range(actual_range));
+
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 3).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ // No caret.
+ caret_range = ui::Range(1, 2);
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ params.anchor_rect = caret_rect;
+ params.focus_rect = gfx::Rect(30, 11, 0, 10);
+ rwhv_mac_->SelectionBoundsChanged(params);
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+}
+
+TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
+ const gfx::Point kOrigin(10, 11);
+ const gfx::Size kBoundsUnit(10, 20);
+
+ NSRect rect;
+ // Make sure not crashing by passing NULL pointer instead of |actual_range|.
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ NULL));
+
+ // If there are no update from renderer, always returned caret position.
+ NSRange actual_range;
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ // If the firstRectForCharacterRange is failed in renderer, empty rect vector
+ // is sent. Make sure this does not crash.
+ rwhv_mac_->ImeCompositionRangeChanged(ui::Range(10, 12),
+ std::vector<gfx::Rect>());
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(10, 11).ToNSRange(),
+ &rect,
+ NULL));
+
+ const int kCompositionLength = 10;
+ std::vector<gfx::Rect> composition_bounds;
+ const int kCompositionStart = 3;
+ const ui::Range kCompositionRange(kCompositionStart,
+ kCompositionStart + kCompositionLength);
+ GenerateCompositionRectArray(kOrigin,
+ kBoundsUnit,
+ kCompositionLength,
+ std::vector<size_t>(),
+ &composition_bounds);
+ rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
+
+ // Out of range requests will return caret position.
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(13, 14).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(14, 15).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ for (int i = 0; i <= kCompositionLength; ++i) {
+ for (int j = 0; j <= kCompositionLength - i; ++j) {
+ const ui::Range range(i, i + j);
+ const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
+ kBoundsUnit,
+ range,
+ 0);
+ const NSRange request_range = ui::Range(
+ kCompositionStart + range.start(),
+ kCompositionStart + range.end()).ToNSRange();
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ request_range,
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(request_range), ui::Range(actual_range));
+ EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
+
+ // Make sure not crashing by passing NULL pointer instead of
+ // |actual_range|.
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ request_range,
+ &rect,
+ NULL));
+ }
+ }
+}
+
+TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
+ const gfx::Point kOrigin(10, 11);
+ const gfx::Size kBoundsUnit(10, 20);
+ NSRect rect;
+
+ const int kCompositionLength = 30;
+ std::vector<gfx::Rect> composition_bounds;
+ const ui::Range kCompositionRange(0, kCompositionLength);
+ // Set breaking point at 10 and 20.
+ std::vector<size_t> break_points;
+ break_points.push_back(10);
+ break_points.push_back(20);
+ GenerateCompositionRectArray(kOrigin,
+ kBoundsUnit,
+ kCompositionLength,
+ break_points,
+ &composition_bounds);
+ rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
+
+ // Range doesn't contain line breaking point.
+ ui::Range range;
+ range = ui::Range(5, 8);
+ NSRange actual_range;
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(15, 18);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 8), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(25, 28);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 8), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Range contains line breaking point.
+ range = ui::Range(8, 12);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(8, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(8, 10), 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(18, 22);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(18, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(8, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start point is line breaking point.
+ range = ui::Range(10, 12);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 12), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 2), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(20, 22);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(20, 22), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 2), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // End point is line breaking point.
+ range = ui::Range(5, 10);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(5, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 10), 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(15, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(15, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start and end point are same line breaking point.
+ range = ui::Range(10, 10);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 0), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(20, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(20, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 0), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start and end point are different line breaking point.
+ range = ui::Range(10, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+}
+
+// Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
+// |RenderWidgetHostImp::Focus()|.
+TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
+ MockRenderWidgetHostDelegate delegate;
+ TestBrowserContext browser_context;
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(&browser_context);
+
+ // Owned by its |cocoa_view()|.
+ MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
+ &delegate, process_host, MSG_ROUTING_NONE);
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(rwh));
+
+ base::scoped_nsobject<CocoaTestHelperWindow> window(
+ [[CocoaTestHelperWindow alloc] init]);
+ [[window contentView] addSubview:view->cocoa_view()];
+
+ EXPECT_CALL(*rwh, Focus());
+ [window makeFirstResponder:view->cocoa_view()];
+ testing::Mock::VerifyAndClearExpectations(rwh);
+
+ EXPECT_CALL(*rwh, Blur());
+ view->SetActive(false);
+ testing::Mock::VerifyAndClearExpectations(rwh);
+
+ EXPECT_CALL(*rwh, Focus());
+ view->SetActive(true);
+ testing::Mock::VerifyAndClearExpectations(rwh);
+
+ // Unsetting first responder should blur.
+ EXPECT_CALL(*rwh, Blur());
+ [window makeFirstResponder:nil];
+ testing::Mock::VerifyAndClearExpectations(rwh);
+
+ // |SetActive()| shoud not focus if view is not first responder.
+ EXPECT_CALL(*rwh, Focus()).Times(0);
+ view->SetActive(true);
+ testing::Mock::VerifyAndClearExpectations(rwh);
+
+ // Clean up.
+ rwh->Shutdown();
+}
+
+TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
+ // This tests Lion+ functionality, so don't run the test pre-Lion.
+ if (!base::mac::IsOSLionOrLater())
+ return;
+
+ // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
+ // the MockRenderProcessHost that is set up by the test harness which mocks
+ // out |OnMessageReceived()|.
+ TestBrowserContext browser_context;
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(&browser_context);
+ MockRenderWidgetHostDelegate delegate;
+ MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
+ &delegate, process_host, MSG_ROUTING_NONE);
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(host));
+
+ // Send an initial wheel event with NSEventPhaseBegan to the view.
+ NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
+ [view->cocoa_view() scrollWheel:event1];
+ ASSERT_EQ(1U, process_host->sink().message_count());
+
+ // Send an ACK for the first wheel event, so that the queue will be flushed.
+ scoped_ptr<IPC::Message> response(new InputHostMsg_HandleInputEvent_ACK(
+ 0, WebKit::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED,
+ ui::LatencyInfo()));
+ host->OnMessageReceived(*response);
+
+ // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
+ // render view receives it.
+ NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
+ [NSApp postEvent:event2 atStart:NO];
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_EQ(2U, process_host->sink().message_count());
+
+ // Clean up.
+ host->Shutdown();
+}
+
+TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
+ // This tests Lion+ functionality, so don't run the test pre-Lion.
+ if (!base::mac::IsOSLionOrLater())
+ return;
+
+ // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
+ // the MockRenderProcessHost that is set up by the test harness which mocks
+ // out |OnMessageReceived()|.
+ TestBrowserContext browser_context;
+ MockRenderProcessHost* process_host =
+ new MockRenderProcessHost(&browser_context);
+ MockRenderWidgetHostDelegate delegate;
+ MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
+ &delegate, process_host, MSG_ROUTING_NONE);
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(host));
+
+ // Add a delegate to the view.
+ base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
+ [[MockRenderWidgetHostViewMacDelegate alloc] init]);
+ view->SetDelegate(view_delegate.get());
+
+ // Send an initial wheel event for scrolling by 3 lines.
+ NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
+ [view->cocoa_view() scrollWheel:event1];
+ ASSERT_EQ(1U, process_host->sink().message_count());
+ process_host->sink().ClearMessages();
+
+ // Indicate that the wheel event was unhandled.
+ scoped_ptr<IPC::Message> response1(new InputHostMsg_HandleInputEvent_ACK(0,
+ WebKit::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
+ ui::LatencyInfo()));
+ host->OnMessageReceived(*response1);
+
+ // Check that the view delegate got an unhandled wheel event.
+ ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
+ view_delegate.get().unhandledWheelEventReceived = NO;
+
+ // Send another wheel event, this time for scrolling by 0 lines (empty event).
+ NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
+ [view->cocoa_view() scrollWheel:event2];
+ ASSERT_EQ(1U, process_host->sink().message_count());
+
+ // Indicate that the wheel event was also unhandled.
+ scoped_ptr<IPC::Message> response2(new InputHostMsg_HandleInputEvent_ACK(0,
+ WebKit::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
+ ui::LatencyInfo()));
+ host->OnMessageReceived(*response2);
+
+ // Check that the view delegate ignored the empty unhandled wheel event.
+ ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
+
+ // Clean up.
+ host->Shutdown();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_win.cc b/chromium/content/browser/renderer_host/render_widget_host_view_win.cc
new file mode 100644
index 00000000000..a6ee28b0189
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_win.cc
@@ -0,0 +1,3201 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_win.h"
+
+#include <InputScope.h>
+#include <wtsapi32.h>
+#pragma comment(lib, "wtsapi32.lib")
+
+#include <algorithm>
+#include <map>
+#include <stack>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/i18n/rtl.h"
+#include "base/metrics/histogram.h"
+#include "base/threading/thread.h"
+#include "base/win/metro.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/scoped_gdi_object.h"
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "base/win/wrapped_window_proc.h"
+#include "content/browser/accessibility/browser_accessibility_manager_win.h"
+#include "content/browser/accessibility/browser_accessibility_state_impl.h"
+#include "content/browser/accessibility/browser_accessibility_win.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/renderer_host/backing_store.h"
+#include "content/browser/renderer_host/backing_store_win.h"
+#include "content/browser/renderer_host/input/web_input_event_builders_win.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/plugin_constants_win.h"
+#include "content/common/view_messages.h"
+#include "content/common/webplugin_geometry.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/page_zoom.h"
+#include "content/public/common/process_type.h"
+#include "skia/ext/skia_utils_win.h"
+#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/win/imm32_manager.h"
+#include "ui/base/ime/win/tsf_input_scope.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/base/touch/touch_device.h"
+#include "ui/base/touch/touch_enabled.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/base/view_prop.h"
+#include "ui/base/win/dpi.h"
+#include "ui/base/win/hwnd_util.h"
+#include "ui/base/win/mouse_wheel_util.h"
+#include "ui/base/win/touch_input.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+#include "webkit/common/cursors/webcursor.h"
+#include "win8/util/win8_util.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::ViewProp;
+using WebKit::WebInputEvent;
+using WebKit::WebMouseEvent;
+using WebKit::WebTextDirection;
+
+namespace content {
+namespace {
+
+// Tooltips will wrap after this width. Yes, wrap. Imagine that!
+const int kTooltipMaxWidthPixels = 300;
+
+// Maximum number of characters we allow in a tooltip.
+const int kMaxTooltipLength = 1024;
+
+// A custom MSAA object id used to determine if a screen reader is actively
+// listening for MSAA events.
+const int kIdCustom = 1;
+
+// The delay before the compositor host window is destroyed. This gives the GPU
+// process a grace period to stop referencing it.
+const int kDestroyCompositorHostWindowDelay = 10000;
+
+// In mouse lock mode, we need to prevent the (invisible) cursor from hitting
+// the border of the view, in order to get valid movement information. However,
+// forcing the cursor back to the center of the view after each mouse move
+// doesn't work well. It reduces the frequency of useful WM_MOUSEMOVE messages
+// significantly. Therefore, we move the cursor to the center of the view only
+// if it approaches the border. |kMouseLockBorderPercentage| specifies the width
+// of the border area, in percentage of the corresponding dimension.
+const int kMouseLockBorderPercentage = 15;
+
+// A callback function for EnumThreadWindows to enumerate and dismiss
+// any owned popup windows.
+BOOL CALLBACK DismissOwnedPopups(HWND window, LPARAM arg) {
+ const HWND toplevel_hwnd = reinterpret_cast<HWND>(arg);
+
+ if (::IsWindowVisible(window)) {
+ const HWND owner = ::GetWindow(window, GW_OWNER);
+ if (toplevel_hwnd == owner) {
+ ::PostMessage(window, WM_CANCELMODE, 0, 0);
+ }
+ }
+
+ return TRUE;
+}
+
+void SendToGpuProcessHost(int gpu_host_id, scoped_ptr<IPC::Message> message) {
+ GpuProcessHost* gpu_process_host = GpuProcessHost::FromID(gpu_host_id);
+ if (!gpu_process_host)
+ return;
+
+ gpu_process_host->Send(message.release());
+}
+
+void PostTaskOnIOThread(const tracked_objects::Location& from_here,
+ base::Closure task) {
+ BrowserThread::PostTask(BrowserThread::IO, from_here, task);
+}
+
+bool DecodeZoomGesture(HWND hwnd, const GESTUREINFO& gi,
+ PageZoom* zoom, POINT* zoom_center) {
+ static long start = 0;
+ static POINT zoom_first;
+
+ if (gi.dwFlags == GF_BEGIN) {
+ start = gi.ullArguments;
+ zoom_first.x = gi.ptsLocation.x;
+ zoom_first.y = gi.ptsLocation.y;
+ ScreenToClient(hwnd, &zoom_first);
+ return false;
+ }
+
+ if (gi.dwFlags == GF_END)
+ return false;
+
+ POINT zoom_second = {0};
+ zoom_second.x = gi.ptsLocation.x;
+ zoom_second.y = gi.ptsLocation.y;
+ ScreenToClient(hwnd, &zoom_second);
+
+ if (zoom_first.x == zoom_second.x && zoom_first.y == zoom_second.y)
+ return false;
+
+ zoom_center->x = (zoom_first.x + zoom_second.x) / 2;
+ zoom_center->y = (zoom_first.y + zoom_second.y) / 2;
+
+ double zoom_factor =
+ static_cast<double>(gi.ullArguments)/static_cast<double>(start);
+
+ *zoom = zoom_factor >= 1 ? PAGE_ZOOM_IN : PAGE_ZOOM_OUT;
+
+ start = gi.ullArguments;
+ zoom_first = zoom_second;
+ return true;
+}
+
+bool DecodeScrollGesture(const GESTUREINFO& gi,
+ POINT* start,
+ POINT* delta){
+ // Windows gestures are streams of messages with begin/end messages that
+ // separate each new gesture. We key off the begin message to reset
+ // the static variables.
+ static POINT last_pt;
+ static POINT start_pt;
+
+ if (gi.dwFlags == GF_BEGIN) {
+ delta->x = 0;
+ delta->y = 0;
+ start_pt.x = gi.ptsLocation.x;
+ start_pt.y = gi.ptsLocation.y;
+ } else {
+ delta->x = gi.ptsLocation.x - last_pt.x;
+ delta->y = gi.ptsLocation.y - last_pt.y;
+ }
+ last_pt.x = gi.ptsLocation.x;
+ last_pt.y = gi.ptsLocation.y;
+ *start = start_pt;
+ return true;
+}
+
+WebKit::WebMouseWheelEvent MakeFakeScrollWheelEvent(HWND hwnd,
+ POINT start,
+ POINT delta) {
+ WebKit::WebMouseWheelEvent result;
+ result.type = WebInputEvent::MouseWheel;
+ result.timeStampSeconds = ::GetMessageTime() / 1000.0;
+ result.button = WebMouseEvent::ButtonNone;
+ result.globalX = start.x;
+ result.globalY = start.y;
+ // Map to window coordinates.
+ POINT client_point = { result.globalX, result.globalY };
+ MapWindowPoints(0, hwnd, &client_point, 1);
+ result.x = client_point.x;
+ result.y = client_point.y;
+ result.windowX = result.x;
+ result.windowY = result.y;
+ // Note that we support diagonal scrolling.
+ result.deltaX = static_cast<float>(delta.x);
+ result.wheelTicksX = WHEEL_DELTA;
+ result.deltaY = static_cast<float>(delta.y);
+ result.wheelTicksY = WHEEL_DELTA;
+ return result;
+}
+
+static const int kTouchMask = 0x7;
+
+inline int GetTouchType(const TOUCHINPUT& point) {
+ return point.dwFlags & kTouchMask;
+}
+
+inline void SetTouchType(TOUCHINPUT* point, int type) {
+ point->dwFlags = (point->dwFlags & kTouchMask) | type;
+}
+
+ui::EventType ConvertToUIEvent(WebKit::WebTouchPoint::State t) {
+ switch (t) {
+ case WebKit::WebTouchPoint::StatePressed:
+ return ui::ET_TOUCH_PRESSED;
+ case WebKit::WebTouchPoint::StateMoved:
+ return ui::ET_TOUCH_MOVED;
+ case WebKit::WebTouchPoint::StateStationary:
+ return ui::ET_TOUCH_STATIONARY;
+ case WebKit::WebTouchPoint::StateReleased:
+ return ui::ET_TOUCH_RELEASED;
+ case WebKit::WebTouchPoint::StateCancelled:
+ return ui::ET_TOUCH_CANCELLED;
+ default:
+ DCHECK(false) << "Unexpected ui type. " << t;
+ return ui::ET_UNKNOWN;
+ }
+}
+
+// Creates a WebGestureEvent corresponding to the given |gesture|
+WebKit::WebGestureEvent CreateWebGestureEvent(HWND hwnd,
+ const ui::GestureEvent& gesture) {
+ WebKit::WebGestureEvent gesture_event =
+ MakeWebGestureEventFromUIEvent(gesture);
+
+ POINT client_point = gesture.location().ToPOINT();
+ POINT screen_point = gesture.location().ToPOINT();
+ MapWindowPoints(hwnd, HWND_DESKTOP, &screen_point, 1);
+
+ gesture_event.x = client_point.x;
+ gesture_event.y = client_point.y;
+ gesture_event.globalX = screen_point.x;
+ gesture_event.globalY = screen_point.y;
+
+ return gesture_event;
+}
+
+WebKit::WebGestureEvent CreateFlingCancelEvent(double time_stamp) {
+ WebKit::WebGestureEvent gesture_event;
+ gesture_event.timeStampSeconds = time_stamp;
+ gesture_event.type = WebKit::WebGestureEvent::GestureFlingCancel;
+ gesture_event.sourceDevice = WebKit::WebGestureEvent::Touchscreen;
+ return gesture_event;
+}
+
+class TouchEventFromWebTouchPoint : public ui::TouchEvent {
+ public:
+ TouchEventFromWebTouchPoint(const WebKit::WebTouchPoint& touch_point,
+ base::TimeDelta& timestamp)
+ : ui::TouchEvent(ConvertToUIEvent(touch_point.state),
+ touch_point.position,
+ touch_point.id,
+ timestamp) {
+ set_radius(touch_point.radiusX, touch_point.radiusY);
+ set_rotation_angle(touch_point.rotationAngle);
+ set_force(touch_point.force);
+ set_flags(ui::GetModifiersFromKeyState());
+ }
+
+ virtual ~TouchEventFromWebTouchPoint() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TouchEventFromWebTouchPoint);
+};
+
+bool ShouldSendPinchGesture() {
+ static bool pinch_allowed =
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePinch);
+ return pinch_allowed;
+}
+
+void GetScreenInfoForWindow(gfx::NativeViewId id,
+ WebKit::WebScreenInfo* results) {
+ HWND window = gfx::NativeViewFromId(id);
+
+ HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
+
+ MONITORINFOEX monitor_info;
+ monitor_info.cbSize = sizeof(MONITORINFOEX);
+ if (!GetMonitorInfo(monitor, &monitor_info))
+ return;
+
+ DEVMODE dev_mode;
+ dev_mode.dmSize = sizeof(dev_mode);
+ dev_mode.dmDriverExtra = 0;
+ EnumDisplaySettings(monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode);
+
+ WebKit::WebScreenInfo screen_info;
+ screen_info.depth = dev_mode.dmBitsPerPel;
+ screen_info.depthPerComponent = dev_mode.dmBitsPerPel / 3; // Assumes RGB
+ screen_info.deviceScaleFactor = ui::win::GetDeviceScaleFactor();
+ screen_info.isMonochrome = dev_mode.dmColor == DMCOLOR_MONOCHROME;
+ screen_info.rect = gfx::Rect(monitor_info.rcMonitor);
+ screen_info.availableRect = gfx::Rect(monitor_info.rcWork);
+
+ *results = screen_info;
+}
+
+} // namespace
+
+const wchar_t kRenderWidgetHostHWNDClass[] = L"Chrome_RenderWidgetHostHWND";
+
+// Wrapper for maintaining touchstate associated with a WebTouchEvent.
+class WebTouchState {
+ public:
+ explicit WebTouchState(const RenderWidgetHostViewWin* window);
+
+ // Updates the current touchpoint state with the supplied touches.
+ // Touches will be consumed only if they are of the same type (e.g. down,
+ // up, move). Returns the number of consumed touches.
+ size_t UpdateTouchPoints(TOUCHINPUT* points, size_t count);
+
+ // Marks all active touchpoints as released.
+ bool ReleaseTouchPoints();
+
+ // The contained WebTouchEvent.
+ const WebKit::WebTouchEvent& touch_event() { return touch_event_; }
+
+ // Returns if any touches are modified in the event.
+ bool is_changed() { return touch_event_.changedTouchesLength != 0; }
+
+ private:
+ typedef std::map<unsigned int, int> MapType;
+
+ // Adds a touch point or returns NULL if there's not enough space.
+ WebKit::WebTouchPoint* AddTouchPoint(TOUCHINPUT* touch_input);
+
+ // Copy details from a TOUCHINPUT to an existing WebTouchPoint, returning
+ // true if the resulting point is a stationary move.
+ bool UpdateTouchPoint(WebKit::WebTouchPoint* touch_point,
+ TOUCHINPUT* touch_input);
+
+ // Find (or create) a mapping for _os_touch_id_.
+ unsigned int GetMappedTouch(unsigned int os_touch_id);
+
+ // Remove any mappings that are no longer in use.
+ void RemoveExpiredMappings();
+
+ WebKit::WebTouchEvent touch_event_;
+ const RenderWidgetHostViewWin* const window_;
+
+ // Maps OS touch Id's into an internal (WebKit-friendly) touch-id.
+ // WebKit expects small consecutive integers, starting at 0.
+ MapType touch_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebTouchState);
+};
+
+typedef void (*MetroSetFrameWindow)(HWND window);
+typedef void (*MetroCloseFrameWindow)(HWND window);
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewWin, public:
+
+RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget)
+ : render_widget_host_(RenderWidgetHostImpl::From(widget)),
+ compositor_host_window_(NULL),
+ hide_compositor_window_at_next_paint_(false),
+ track_mouse_leave_(false),
+ imm32_manager_(new ui::IMM32Manager),
+ ime_notification_(false),
+ capture_enter_key_(false),
+ is_hidden_(false),
+ about_to_validate_and_paint_(false),
+ close_on_deactivate_(false),
+ being_destroyed_(false),
+ tooltip_hwnd_(NULL),
+ tooltip_showing_(false),
+ weak_factory_(this),
+ is_loading_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
+ text_input_mode_(ui::TEXT_INPUT_MODE_DEFAULT),
+ can_compose_inline_(true),
+ is_fullscreen_(false),
+ ignore_mouse_movement_(true),
+ composition_range_(ui::Range::InvalidRange()),
+ touch_state_(new WebTouchState(this)),
+ pointer_down_context_(false),
+ last_touch_location_(-1, -1),
+ touch_events_enabled_(ui::AreTouchEventsEnabled()),
+ gesture_recognizer_(ui::GestureRecognizer::Create(this)) {
+ render_widget_host_->SetView(this);
+ registrar_.Add(this,
+ NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+RenderWidgetHostViewWin::~RenderWidgetHostViewWin() {
+ UnlockMouse();
+ ResetTooltip();
+}
+
+void RenderWidgetHostViewWin::CreateWnd(HWND parent) {
+ // ATL function to create the window.
+ Create(parent);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewWin, RenderWidgetHostView implementation:
+
+void RenderWidgetHostViewWin::InitAsChild(
+ gfx::NativeView parent_view) {
+ CreateWnd(parent_view);
+}
+
+void RenderWidgetHostViewWin::InitAsPopup(
+ RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
+ close_on_deactivate_ = true;
+ DoPopupOrFullscreenInit(parent_host_view->GetNativeView(), pos,
+ WS_EX_TOOLWINDOW);
+}
+
+void RenderWidgetHostViewWin::InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) {
+ gfx::Rect pos = gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(
+ reference_host_view->GetNativeView()).bounds();
+ is_fullscreen_ = true;
+ DoPopupOrFullscreenInit(ui::GetWindowToParentTo(true), pos, 0);
+}
+
+RenderWidgetHost* RenderWidgetHostViewWin::GetRenderWidgetHost() const {
+ return render_widget_host_;
+}
+
+void RenderWidgetHostViewWin::WasShown() {
+ if (!is_hidden_)
+ return;
+
+ if (web_contents_switch_paint_time_.is_null())
+ web_contents_switch_paint_time_ = TimeTicks::Now();
+ is_hidden_ = false;
+
+ // |render_widget_host_| may be NULL if the WebContentsImpl is in the process
+ // of closing.
+ if (render_widget_host_)
+ render_widget_host_->WasShown();
+}
+
+void RenderWidgetHostViewWin::WasHidden() {
+ if (is_hidden_)
+ return;
+
+ // If we receive any more paint messages while we are hidden, we want to
+ // ignore them so we don't re-allocate the backing store. We will paint
+ // everything again when we become selected again.
+ is_hidden_ = true;
+
+ ResetTooltip();
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ if (render_widget_host_)
+ render_widget_host_->WasHidden();
+
+ if (accelerated_surface_)
+ accelerated_surface_->WasHidden();
+
+ if (GetBrowserAccessibilityManager())
+ GetBrowserAccessibilityManager()->WasHidden();
+
+ web_contents_switch_paint_time_ = base::TimeTicks();
+}
+
+void RenderWidgetHostViewWin::SetSize(const gfx::Size& size) {
+ SetBounds(gfx::Rect(GetPixelBounds().origin(), size));
+}
+
+void RenderWidgetHostViewWin::SetBounds(const gfx::Rect& rect) {
+ if (is_hidden_)
+ return;
+
+ // No SWP_NOREDRAW as autofill popups can move and the underneath window
+ // should redraw in that case.
+ UINT swp_flags = SWP_NOSENDCHANGING | SWP_NOOWNERZORDER | SWP_NOCOPYBITS |
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE;
+
+ // If the style is not popup, you have to convert the point to client
+ // coordinate.
+ POINT point = { rect.x(), rect.y() };
+ if (GetStyle() & WS_CHILD)
+ ScreenToClient(&point);
+
+ SetWindowPos(NULL, point.x, point.y, rect.width(), rect.height(), swp_flags);
+ render_widget_host_->WasResized();
+}
+
+gfx::NativeView RenderWidgetHostViewWin::GetNativeView() const {
+ return m_hWnd;
+}
+
+gfx::NativeViewId RenderWidgetHostViewWin::GetNativeViewId() const {
+ return reinterpret_cast<gfx::NativeViewId>(m_hWnd);
+}
+
+gfx::NativeViewAccessible
+RenderWidgetHostViewWin::GetNativeViewAccessible() {
+ if (render_widget_host_ &&
+ !BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) {
+ // Attempt to detect screen readers by sending an event with our custom id.
+ NotifyWinEvent(EVENT_SYSTEM_ALERT, m_hWnd, kIdCustom, CHILDID_SELF);
+ }
+
+ CreateBrowserAccessibilityManagerIfNeeded();
+
+ return GetBrowserAccessibilityManager()->GetRoot()->
+ ToBrowserAccessibilityWin();
+}
+
+void RenderWidgetHostViewWin::CreateBrowserAccessibilityManagerIfNeeded() {
+ if (GetBrowserAccessibilityManager())
+ return;
+
+ HRESULT hr = ::CreateStdAccessibleObject(
+ m_hWnd, OBJID_WINDOW, IID_IAccessible,
+ reinterpret_cast<void **>(&window_iaccessible_));
+ DCHECK(SUCCEEDED(hr));
+
+ SetBrowserAccessibilityManager(
+ new BrowserAccessibilityManagerWin(
+ m_hWnd,
+ window_iaccessible_.get(),
+ BrowserAccessibilityManagerWin::GetEmptyDocument(),
+ this));
+}
+
+void RenderWidgetHostViewWin::MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& plugin_window_moves) {
+ MovePluginWindowsHelper(m_hWnd, plugin_window_moves);
+}
+
+static BOOL CALLBACK AddChildWindowToVector(HWND hwnd, LPARAM lparam) {
+ std::vector<HWND>* vector = reinterpret_cast<std::vector<HWND>*>(lparam);
+ vector->push_back(hwnd);
+ return TRUE;
+}
+
+void RenderWidgetHostViewWin::CleanupCompositorWindow() {
+ if (!compositor_host_window_)
+ return;
+
+ ui::SetWindowUserData(compositor_host_window_, NULL);
+
+ // Hide the compositor and parent it to the desktop rather than destroying
+ // it immediately. The GPU process has a grace period to stop accessing the
+ // window. TODO(apatrick): the GPU process should acknowledge that it has
+ // finished with the window handle and the browser process should destroy it
+ // at that point.
+ ::ShowWindow(compositor_host_window_, SW_HIDE);
+ ::SetParent(compositor_host_window_, NULL);
+
+ BrowserThread::PostDelayedTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&::DestroyWindow),
+ compositor_host_window_),
+ base::TimeDelta::FromMilliseconds(kDestroyCompositorHostWindowDelay));
+
+ compositor_host_window_ = NULL;
+}
+
+bool RenderWidgetHostViewWin::IsActivatable() const {
+ // Popups should not be activated.
+ return popup_type_ == WebKit::WebPopupTypeNone;
+}
+
+void RenderWidgetHostViewWin::Focus() {
+ if (IsWindow())
+ SetFocus();
+}
+
+void RenderWidgetHostViewWin::Blur() {
+ NOTREACHED();
+}
+
+bool RenderWidgetHostViewWin::HasFocus() const {
+ return ::GetFocus() == m_hWnd;
+}
+
+bool RenderWidgetHostViewWin::IsSurfaceAvailableForCopy() const {
+ if (render_widget_host_->is_accelerated_compositing_active())
+ return accelerated_surface_.get() && accelerated_surface_->IsReadyForCopy();
+ else
+ return !!render_widget_host_->GetBackingStore(false);
+}
+
+void RenderWidgetHostViewWin::Show() {
+ ShowWindow(SW_SHOW);
+ WasShown();
+}
+
+void RenderWidgetHostViewWin::Hide() {
+ if (!is_fullscreen_ && GetParent() == ui::GetWindowToParentTo(true)) {
+ LOG(WARNING) << "Hide() called twice in a row: " << this << ":"
+ << GetParent();
+ return;
+ }
+
+ if (::GetFocus() == m_hWnd)
+ ::SetFocus(NULL);
+ ShowWindow(SW_HIDE);
+
+ WasHidden();
+}
+
+bool RenderWidgetHostViewWin::IsShowing() {
+ return !!IsWindowVisible();
+}
+
+gfx::Rect RenderWidgetHostViewWin::GetViewBounds() const {
+ return ui::win::ScreenToDIPRect(GetPixelBounds());
+}
+
+gfx::Rect RenderWidgetHostViewWin::GetPixelBounds() const {
+ CRect window_rect;
+ GetWindowRect(&window_rect);
+ return gfx::Rect(window_rect);
+}
+
+void RenderWidgetHostViewWin::UpdateCursor(const WebCursor& cursor) {
+ current_cursor_ = cursor;
+ UpdateCursorIfOverSelf();
+}
+
+void RenderWidgetHostViewWin::UpdateCursorIfOverSelf() {
+ static HCURSOR kCursorArrow = LoadCursor(NULL, IDC_ARROW);
+ static HCURSOR kCursorAppStarting = LoadCursor(NULL, IDC_APPSTARTING);
+ static HINSTANCE module_handle = GetModuleHandle(
+ GetContentClient()->browser()->GetResourceDllName());
+
+ // If the mouse is over our HWND, then update the cursor state immediately.
+ CPoint pt;
+ GetCursorPos(&pt);
+ if (WindowFromPoint(pt) == m_hWnd) {
+ // We cannot pass in NULL as the module handle as this would only work for
+ // standard win32 cursors. We can also receive cursor types which are
+ // defined as webkit resources. We need to specify the module handle of
+ // chrome.dll while loading these cursors.
+ HCURSOR display_cursor = current_cursor_.GetCursor(module_handle);
+
+ // If a page is in the loading state, we want to show the Arrow+Hourglass
+ // cursor only when the current cursor is the ARROW cursor. In all other
+ // cases we should continue to display the current cursor.
+ if (is_loading_ && display_cursor == kCursorArrow)
+ display_cursor = kCursorAppStarting;
+
+ SetCursor(display_cursor);
+ }
+}
+
+void RenderWidgetHostViewWin::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ UpdateCursorIfOverSelf();
+}
+
+void RenderWidgetHostViewWin::TextInputTypeChanged(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) {
+ if (text_input_type_ != type ||
+ text_input_mode_ != input_mode ||
+ can_compose_inline_ != can_compose_inline) {
+ const bool text_input_type_changed = (text_input_type_ != type) ||
+ (text_input_mode_ != input_mode);
+ text_input_type_ = type;
+ text_input_mode_ = input_mode;
+ can_compose_inline_ = can_compose_inline;
+ UpdateIMEState();
+ if (text_input_type_changed)
+ UpdateInputScopeIfNecessary(text_input_type_);
+ }
+}
+
+void RenderWidgetHostViewWin::SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) {
+ bool is_enabled = (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
+ text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD);
+ // Only update caret position if the input method is enabled.
+ if (is_enabled) {
+ caret_rect_ = gfx::UnionRects(params.anchor_rect, params.focus_rect);
+ imm32_manager_->UpdateCaretRect(m_hWnd, caret_rect_);
+ }
+}
+
+void RenderWidgetHostViewWin::ScrollOffsetChanged() {
+}
+
+void RenderWidgetHostViewWin::ImeCancelComposition() {
+ imm32_manager_->CancelIME(m_hWnd);
+}
+
+void RenderWidgetHostViewWin::ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) {
+ composition_range_ = range;
+ composition_character_bounds_ = character_bounds;
+}
+
+void RenderWidgetHostViewWin::Redraw() {
+ RECT damage_bounds;
+ GetUpdateRect(&damage_bounds, FALSE);
+
+ base::win::ScopedGDIObject<HRGN> damage_region(CreateRectRgn(0, 0, 0, 0));
+ GetUpdateRgn(damage_region, FALSE);
+
+ // Paint the invalid region synchronously. Our caller will not paint again
+ // until we return, so by painting to the screen here, we ensure effective
+ // rate-limiting of backing store updates. This helps a lot on pages that
+ // have animations or fairly expensive layout (e.g., google maps).
+ //
+ // We paint this window synchronously, however child windows (i.e. plugins)
+ // are painted asynchronously. By avoiding synchronous cross-process window
+ // message dispatching we allow scrolling to be smooth, and also avoid the
+ // browser process locking up if the plugin process is hung.
+ //
+ RedrawWindow(NULL, damage_region, RDW_UPDATENOW | RDW_NOCHILDREN);
+
+ // Send the invalid rect in screen coordinates.
+ gfx::Rect invalid_screen_rect(damage_bounds);
+ invalid_screen_rect.Offset(GetPixelBounds().OffsetFromOrigin());
+
+ PaintPluginWindowsHelper(m_hWnd, invalid_screen_rect);
+}
+
+void RenderWidgetHostViewWin::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) {
+ software_latency_info_.MergeWith(latency_info);
+ if (is_hidden_)
+ return;
+
+ // Schedule invalidations first so that the ScrollWindowEx call is closer to
+ // Redraw. That minimizes chances of "flicker" resulting if the screen
+ // refreshes before we have a chance to paint the exposed area. Somewhat
+ // surprisingly, this ordering matters.
+
+ for (size_t i = 0; i < copy_rects.size(); ++i) {
+ gfx::Rect pixel_rect = ui::win::DIPToScreenRect(copy_rects[i]);
+ // Damage might not be DIP aligned.
+ pixel_rect.Inset(-1, -1);
+ RECT bounds = pixel_rect.ToRECT();
+ InvalidateRect(&bounds, false);
+ }
+
+ if (!scroll_rect.IsEmpty()) {
+ gfx::Rect pixel_rect = ui::win::DIPToScreenRect(scroll_rect);
+ // Damage might not be DIP aligned.
+ pixel_rect.Inset(-1, -1);
+ RECT clip_rect = pixel_rect.ToRECT();
+ float scale = ui::win::GetDeviceScaleFactor();
+ int dx = static_cast<int>(scale * scroll_delta.x());
+ int dy = static_cast<int>(scale * scroll_delta.y());
+ ScrollWindowEx(dx, dy, NULL, &clip_rect, NULL, NULL, SW_INVALIDATE);
+ }
+
+ if (!about_to_validate_and_paint_)
+ Redraw();
+}
+
+void RenderWidgetHostViewWin::RenderProcessGone(base::TerminationStatus status,
+ int error_code) {
+ UpdateCursorIfOverSelf();
+ Destroy();
+}
+
+bool RenderWidgetHostViewWin::CanSubscribeFrame() const {
+ return render_widget_host_ != NULL;
+}
+
+void RenderWidgetHostViewWin::WillWmDestroy() {
+ CleanupCompositorWindow();
+ if (base::win::IsTSFAwareRequired() && GetFocus() == m_hWnd)
+ ui::TSFBridge::GetInstance()->RemoveFocusedClient(this);
+}
+
+void RenderWidgetHostViewWin::Destroy() {
+ // We've been told to destroy.
+ // By clearing close_on_deactivate_, we prevent further deactivations
+ // (caused by windows messages resulting from the DestroyWindow) from
+ // triggering further destructions. The deletion of this is handled by
+ // OnFinalMessage();
+ close_on_deactivate_ = false;
+ render_widget_host_ = NULL;
+ being_destroyed_ = true;
+ CleanupCompositorWindow();
+
+ // This releases the resources associated with input scope.
+ UpdateInputScopeIfNecessary(ui::TEXT_INPUT_TYPE_NONE);
+
+ if (is_fullscreen_ && win8::IsSingleWindowMetroMode()) {
+ MetroCloseFrameWindow close_frame_window =
+ reinterpret_cast<MetroCloseFrameWindow>(
+ ::GetProcAddress(base::win::GetMetroModule(), "CloseFrameWindow"));
+ DCHECK(close_frame_window);
+ close_frame_window(m_hWnd);
+ }
+
+ DestroyWindow();
+}
+
+void RenderWidgetHostViewWin::SetTooltipText(const string16& tooltip_text) {
+ if (!is_hidden_)
+ EnsureTooltip();
+
+ // Clamp the tooltip length to kMaxTooltipLength so that we don't
+ // accidentally DOS the user with a mega tooltip (since Windows doesn't seem
+ // to do this itself).
+ const string16 new_tooltip_text =
+ ui::TruncateString(tooltip_text, kMaxTooltipLength);
+
+ if (new_tooltip_text != tooltip_text_) {
+ tooltip_text_ = new_tooltip_text;
+
+ // Need to check if the tooltip is already showing so that we don't
+ // immediately show the tooltip with no delay when we move the mouse from
+ // a region with no tooltip to a region with a tooltip.
+ if (::IsWindow(tooltip_hwnd_) && tooltip_showing_) {
+ ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0);
+ ::SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0);
+ }
+ } else {
+ // Make sure the tooltip gets closed after TTN_POP gets sent. For some
+ // reason this doesn't happen automatically, so moving the mouse around
+ // within the same link/image/etc doesn't cause the tooltip to re-appear.
+ if (!tooltip_showing_) {
+ if (::IsWindow(tooltip_hwnd_))
+ ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0);
+ }
+ }
+}
+
+BackingStore* RenderWidgetHostViewWin::AllocBackingStore(
+ const gfx::Size& size) {
+ return new BackingStoreWin(render_widget_host_, size);
+}
+
+void RenderWidgetHostViewWin::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ base::ScopedClosureRunner scoped_callback_runner(
+ base::Bind(callback, false, SkBitmap()));
+ if (!accelerated_surface_)
+ return;
+
+ if (dst_size.IsEmpty() || src_subrect.IsEmpty())
+ return;
+
+ scoped_callback_runner.Release();
+ accelerated_surface_->AsyncCopyTo(src_subrect, dst_size, callback);
+}
+
+void RenderWidgetHostViewWin::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
+ if (!accelerated_surface_)
+ return;
+
+ if (!target || (target->format() != media::VideoFrame::YV12 &&
+ target->format() != media::VideoFrame::I420))
+ return;
+
+ if (src_subrect.IsEmpty())
+ return;
+
+ scoped_callback_runner.Release();
+ accelerated_surface_->AsyncCopyToVideoFrame(src_subrect, target, callback);
+}
+
+bool RenderWidgetHostViewWin::CanCopyToVideoFrame() const {
+ return accelerated_surface_.get() && render_widget_host_ &&
+ render_widget_host_->is_accelerated_compositing_active();
+}
+
+void RenderWidgetHostViewWin::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostViewBase::SetBackground(background);
+ render_widget_host_->SetBackground(background);
+}
+
+void RenderWidgetHostViewWin::ProcessAckedTouchEvent(
+ const TouchEventWithLatencyInfo& touch, InputEventAckState ack_result) {
+ DCHECK(touch_events_enabled_);
+
+ ScopedVector<ui::TouchEvent> events;
+ if (!MakeUITouchEventsFromWebTouchEvents(touch, &events, LOCAL_COORDINATES))
+ return;
+
+ ui::EventResult result = (ack_result ==
+ INPUT_EVENT_ACK_STATE_CONSUMED) ? ui::ER_HANDLED : ui::ER_UNHANDLED;
+ for (ScopedVector<ui::TouchEvent>::iterator iter = events.begin(),
+ end = events.end(); iter != end; ++iter) {
+ scoped_ptr<ui::GestureRecognizer::Gestures> gestures;
+ gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture(
+ *(*iter), result, this));
+ ProcessGestures(gestures.get());
+ }
+}
+
+void RenderWidgetHostViewWin::UpdateDesiredTouchMode() {
+ // Make sure that touch events even make sense.
+ if (base::win::GetVersion() < base::win::VERSION_WIN7)
+ return;
+ if (touch_events_enabled_) {
+ CHECK(RegisterTouchWindow(m_hWnd, TWF_WANTPALM));
+ }
+}
+
+bool RenderWidgetHostViewWin::DispatchLongPressGestureEvent(
+ ui::GestureEvent* event) {
+ return ForwardGestureEventToRenderer(event);
+}
+
+bool RenderWidgetHostViewWin::DispatchCancelTouchEvent(
+ ui::TouchEvent* event) {
+ if (!render_widget_host_ || !touch_events_enabled_ ||
+ !render_widget_host_->ShouldForwardTouchEvent()) {
+ return false;
+ }
+ DCHECK(event->type() == WebKit::WebInputEvent::TouchCancel);
+ WebKit::WebTouchEvent cancel_event;
+ cancel_event.type = WebKit::WebInputEvent::TouchCancel;
+ cancel_event.timeStampSeconds = event->time_stamp().InSecondsF();
+ render_widget_host_->ForwardTouchEventWithLatencyInfo(
+ cancel_event, *event->latency());
+ return true;
+}
+
+void RenderWidgetHostViewWin::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+}
+
+void RenderWidgetHostViewWin::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+}
+
+void RenderWidgetHostViewWin::SetCompositionText(
+ const ui::CompositionText& composition) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ if (!render_widget_host_)
+ return;
+ // ui::CompositionUnderline should be identical to
+ // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
+ COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
+ sizeof(WebKit::WebCompositionUnderline),
+ ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition.underlines);
+ render_widget_host_->ImeSetComposition(composition.text, underlines,
+ composition.selection.end(),
+ composition.selection.end());
+}
+
+void RenderWidgetHostViewWin::ConfirmCompositionText() {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewWin::ClearCompositionText() {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewWin::InsertText(const string16& text) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ if (render_widget_host_)
+ render_widget_host_->ImeConfirmComposition(text,
+ ui::Range::InvalidRange(),
+ false);
+}
+
+void RenderWidgetHostViewWin::InsertChar(char16 ch, int flags) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+}
+
+gfx::NativeWindow RenderWidgetHostViewWin::GetAttachedWindow() const {
+ return m_hWnd;
+}
+
+ui::TextInputType RenderWidgetHostViewWin::GetTextInputType() const {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return ui::TEXT_INPUT_TYPE_NONE;
+ }
+ return text_input_type_;
+}
+
+ui::TextInputMode RenderWidgetHostViewWin::GetTextInputMode() const {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return ui::TEXT_INPUT_MODE_DEFAULT;
+ }
+ return ui::TEXT_INPUT_MODE_DEFAULT;
+}
+
+bool RenderWidgetHostViewWin::CanComposeInline() const {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+gfx::Rect RenderWidgetHostViewWin::GetCaretBounds() {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return gfx::Rect(0, 0, 0, 0);
+ }
+ RECT tmp_rect = caret_rect_.ToRECT();
+ ClientToScreen(&tmp_rect);
+ return gfx::Rect(tmp_rect);
+}
+
+bool RenderWidgetHostViewWin::GetCompositionCharacterBounds(
+ uint32 index, gfx::Rect* rect) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ DCHECK(rect);
+ if (index >= composition_character_bounds_.size())
+ return false;
+ RECT rec = composition_character_bounds_[index].ToRECT();
+ ClientToScreen(&rec);
+ *rect = gfx::Rect(rec);
+ return true;
+}
+
+bool RenderWidgetHostViewWin::HasCompositionText() {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewWin::GetTextRange(ui::Range* range) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ range->set_start(selection_text_offset_);
+ range->set_end(selection_text_offset_ + selection_text_.length());
+ return false;
+}
+
+bool RenderWidgetHostViewWin::GetCompositionTextRange(ui::Range* range) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewWin::GetSelectionRange(ui::Range* range) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ range->set_start(selection_range_.start());
+ range->set_end(selection_range_.end());
+ return false;
+}
+
+bool RenderWidgetHostViewWin::SetSelectionRange(const ui::Range& range) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewWin::DeleteRange(const ui::Range& range) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RenderWidgetHostViewWin::GetTextFromRange(const ui::Range& range,
+ string16* text) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ ui::Range selection_text_range(selection_text_offset_,
+ selection_text_offset_ + selection_text_.length());
+ if (!selection_text_range.Contains(range)) {
+ text->clear();
+ return false;
+ }
+ if (selection_text_range.EqualsIgnoringDirection(range)) {
+ *text = selection_text_;
+ } else {
+ *text = selection_text_.substr(
+ range.GetMin() - selection_text_offset_,
+ range.length());
+ }
+ return true;
+}
+
+void RenderWidgetHostViewWin::OnInputMethodChanged() {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+}
+
+bool RenderWidgetHostViewWin::ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return false;
+ }
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void RenderWidgetHostViewWin::ExtendSelectionAndDelete(
+ size_t before,
+ size_t after) {
+ if (!base::win::IsTSFAwareRequired()) {
+ NOTREACHED();
+ return;
+ }
+ if (!render_widget_host_)
+ return;
+ render_widget_host_->ExtendSelectionAndDelete(before, after);
+}
+
+void RenderWidgetHostViewWin::EnsureCaretInRect(const gfx::Rect& rect) {
+ // TODO(nona): Implement this function.
+ NOTIMPLEMENTED();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewWin, private:
+
+LRESULT RenderWidgetHostViewWin::OnCreate(CREATESTRUCT* create_struct) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnCreate");
+ // Call the WM_INPUTLANGCHANGE message handler to initialize the input locale
+ // of a browser process.
+ OnInputLangChange(0, 0);
+ // Marks that window as supporting mouse-wheel messages rerouting so it is
+ // scrolled when under the mouse pointer even if inactive.
+ props_.push_back(ui::SetWindowSupportsRerouteMouseWheel(m_hWnd));
+
+ WTSRegisterSessionNotification(m_hWnd, NOTIFY_FOR_THIS_SESSION);
+
+ UpdateDesiredTouchMode();
+ UpdateIMEState();
+
+ return 0;
+}
+
+void RenderWidgetHostViewWin::OnActivate(UINT action, BOOL minimized,
+ HWND window) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnActivate");
+ // If the container is a popup, clicking elsewhere on screen should close the
+ // popup.
+ if (close_on_deactivate_ && action == WA_INACTIVE) {
+ // Send a windows message so that any derived classes
+ // will get a change to override the default handling
+ SendMessage(WM_CANCELMODE);
+ }
+}
+
+void RenderWidgetHostViewWin::OnDestroy() {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnDestroy");
+ DetachPluginsHelper(m_hWnd);
+
+ props_.clear();
+
+ if (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
+ IsTouchWindow(m_hWnd, NULL)) {
+ UnregisterTouchWindow(m_hWnd);
+ }
+
+ CleanupCompositorWindow();
+
+ WTSUnRegisterSessionNotification(m_hWnd);
+
+ ResetTooltip();
+ TrackMouseLeave(false);
+}
+
+void RenderWidgetHostViewWin::OnPaint(HDC unused_dc) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnPaint");
+
+ // Grab the region to paint before creation of paint_dc since it clears the
+ // damage region.
+ base::win::ScopedGDIObject<HRGN> damage_region(CreateRectRgn(0, 0, 0, 0));
+ GetUpdateRgn(damage_region, FALSE);
+
+ CPaintDC paint_dc(m_hWnd);
+
+ if (!render_widget_host_)
+ return;
+
+ DCHECK(render_widget_host_->GetProcess()->HasConnection());
+
+ // If the GPU process is rendering to a child window, compositing is
+ // already triggered by damage to compositor_host_window_, so all we need to
+ // do here is clear borders during resize.
+ if (compositor_host_window_ &&
+ render_widget_host_->is_accelerated_compositing_active()) {
+ RECT host_rect, child_rect;
+ GetClientRect(&host_rect);
+ if (::GetClientRect(compositor_host_window_, &child_rect) &&
+ (child_rect.right < host_rect.right ||
+ child_rect.bottom < host_rect.bottom)) {
+ paint_dc.FillRect(&host_rect,
+ reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
+ }
+ return;
+ }
+
+ if (accelerated_surface_.get() &&
+ render_widget_host_->is_accelerated_compositing_active()) {
+ AcceleratedPaint(paint_dc.m_hDC);
+ return;
+ }
+
+ about_to_validate_and_paint_ = true;
+ BackingStoreWin* backing_store = static_cast<BackingStoreWin*>(
+ render_widget_host_->GetBackingStore(true));
+
+ // We initialize |paint_dc| (and thus call BeginPaint()) after calling
+ // GetBackingStore(), so that if it updates the invalid rect we'll catch the
+ // changes and repaint them.
+ about_to_validate_and_paint_ = false;
+
+ if (compositor_host_window_ && hide_compositor_window_at_next_paint_) {
+ ::ShowWindow(compositor_host_window_, SW_HIDE);
+ hide_compositor_window_at_next_paint_ = false;
+ }
+
+ gfx::Rect damaged_rect(paint_dc.m_ps.rcPaint);
+ if (damaged_rect.IsEmpty())
+ return;
+
+ if (backing_store) {
+ gfx::Rect bitmap_rect(gfx::Point(),
+ ui::win::DIPToScreenSize(backing_store->size()));
+
+ bool manage_colors = BackingStoreWin::ColorManagementEnabled();
+ if (manage_colors)
+ SetICMMode(paint_dc.m_hDC, ICM_ON);
+
+ // Blit only the damaged regions from the backing store.
+ DWORD data_size = GetRegionData(damage_region, 0, NULL);
+ scoped_ptr<char[]> region_data_buf;
+ RGNDATA* region_data = NULL;
+ RECT* region_rects = NULL;
+
+ if (data_size) {
+ region_data_buf.reset(new char[data_size]);
+ region_data = reinterpret_cast<RGNDATA*>(region_data_buf.get());
+ region_rects = reinterpret_cast<RECT*>(region_data->Buffer);
+ data_size = GetRegionData(damage_region, data_size, region_data);
+ }
+
+ if (!data_size) {
+ // Grabbing the damaged regions failed, fake with the whole rect.
+ data_size = sizeof(RGNDATAHEADER) + sizeof(RECT);
+ region_data_buf.reset(new char[data_size]);
+ region_data = reinterpret_cast<RGNDATA*>(region_data_buf.get());
+ region_rects = reinterpret_cast<RECT*>(region_data->Buffer);
+ region_data->rdh.nCount = 1;
+ region_rects[0] = damaged_rect.ToRECT();
+ }
+
+ for (DWORD i = 0; i < region_data->rdh.nCount; ++i) {
+ gfx::Rect paint_rect =
+ gfx::IntersectRects(bitmap_rect, gfx::Rect(region_rects[i]));
+ if (!paint_rect.IsEmpty()) {
+ BitBlt(paint_dc.m_hDC,
+ paint_rect.x(),
+ paint_rect.y(),
+ paint_rect.width(),
+ paint_rect.height(),
+ backing_store->hdc(),
+ paint_rect.x(),
+ paint_rect.y(),
+ SRCCOPY);
+ }
+ }
+
+ if (manage_colors)
+ SetICMMode(paint_dc.m_hDC, ICM_OFF);
+
+ // Fill the remaining portion of the damaged_rect with the background
+ if (damaged_rect.right() > bitmap_rect.right()) {
+ RECT r;
+ r.left = std::max(bitmap_rect.right(), damaged_rect.x());
+ r.right = damaged_rect.right();
+ r.top = damaged_rect.y();
+ r.bottom = std::min(bitmap_rect.bottom(), damaged_rect.bottom());
+ DrawBackground(r, &paint_dc);
+ }
+ if (damaged_rect.bottom() > bitmap_rect.bottom()) {
+ RECT r;
+ r.left = damaged_rect.x();
+ r.right = damaged_rect.right();
+ r.top = std::max(bitmap_rect.bottom(), damaged_rect.y());
+ r.bottom = damaged_rect.bottom();
+ DrawBackground(r, &paint_dc);
+ }
+ if (!whiteout_start_time_.is_null()) {
+ TimeDelta whiteout_duration = TimeTicks::Now() - whiteout_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
+
+ // Reset the start time to 0 so that we start recording again the next
+ // time the backing store is NULL...
+ whiteout_start_time_ = TimeTicks();
+ }
+ if (!web_contents_switch_paint_time_.is_null()) {
+ TimeDelta web_contents_switch_paint_duration = TimeTicks::Now() -
+ web_contents_switch_paint_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
+ web_contents_switch_paint_duration);
+ // Reset contents_switch_paint_time_ to 0 so future tab selections are
+ // recorded.
+ web_contents_switch_paint_time_ = TimeTicks();
+ }
+
+ software_latency_info_.swap_timestamp = TimeTicks::HighResNow();
+ render_widget_host_->FrameSwapped(software_latency_info_);
+ software_latency_info_.Clear();
+ } else {
+ DrawBackground(paint_dc.m_ps.rcPaint, &paint_dc);
+ if (whiteout_start_time_.is_null())
+ whiteout_start_time_ = TimeTicks::Now();
+ }
+}
+
+void RenderWidgetHostViewWin::DrawBackground(const RECT& dirty_rect,
+ CPaintDC* dc) {
+ if (!background_.empty()) {
+ gfx::Rect dirty_area(dirty_rect);
+ gfx::Canvas canvas(dirty_area.size(), ui::SCALE_FACTOR_100P, true);
+ canvas.Translate(-dirty_area.OffsetFromOrigin());
+
+ gfx::Rect dc_rect(dc->m_ps.rcPaint);
+ // TODO(pkotwicz): Fix |background_| such that it is an ImageSkia.
+ canvas.TileImageInt(gfx::ImageSkia::CreateFrom1xBitmap(background_),
+ 0, 0, dc_rect.width(), dc_rect.height());
+
+ skia::DrawToNativeContext(canvas.sk_canvas(), *dc, dirty_area.x(),
+ dirty_area.y(), NULL);
+ } else {
+ HBRUSH white_brush = reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
+ dc->FillRect(&dirty_rect, white_brush);
+ }
+}
+
+void RenderWidgetHostViewWin::OnNCPaint(HRGN update_region) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnNCPaint");
+ // Do nothing. This suppresses the resize corner that Windows would
+ // otherwise draw for us.
+}
+
+void RenderWidgetHostViewWin::SetClickthroughRegion(SkRegion* region) {
+ transparent_region_.reset(region);
+}
+
+LRESULT RenderWidgetHostViewWin::OnNCHitTest(const CPoint& point) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnNCHitTest");
+ RECT rc;
+ GetWindowRect(&rc);
+ if (transparent_region_.get() &&
+ transparent_region_->contains(point.x - rc.left, point.y - rc.top)) {
+ SetMsgHandled(TRUE);
+ return HTTRANSPARENT;
+ }
+ SetMsgHandled(FALSE);
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnEraseBkgnd(HDC dc) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnEraseBkgnd");
+ return 1;
+}
+
+LRESULT RenderWidgetHostViewWin::OnSetCursor(HWND window, UINT hittest_code,
+ UINT mouse_message_id) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnSetCursor");
+ UpdateCursorIfOverSelf();
+ return 0;
+}
+
+void RenderWidgetHostViewWin::OnSetFocus(HWND window) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnSetFocus");
+ if (!render_widget_host_)
+ return;
+
+ if (GetBrowserAccessibilityManager())
+ GetBrowserAccessibilityManager()->GotFocus(pointer_down_context_);
+
+ render_widget_host_->GotFocus();
+ render_widget_host_->SetActive(true);
+
+ if (base::win::IsTSFAwareRequired())
+ ui::TSFBridge::GetInstance()->SetFocusedClient(m_hWnd, this);
+}
+
+void RenderWidgetHostViewWin::OnKillFocus(HWND window) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnKillFocus");
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->SetActive(false);
+ render_widget_host_->Blur();
+
+ last_touch_location_ = gfx::Point(-1, -1);
+
+ if (base::win::IsTSFAwareRequired())
+ ui::TSFBridge::GetInstance()->RemoveFocusedClient(this);
+}
+
+void RenderWidgetHostViewWin::OnCaptureChanged(HWND window) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnCaptureChanged");
+ if (render_widget_host_)
+ render_widget_host_->LostCapture();
+ pointer_down_context_ = false;
+}
+
+void RenderWidgetHostViewWin::OnCancelMode() {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnCancelMode");
+ if (render_widget_host_)
+ render_widget_host_->LostCapture();
+
+ if ((is_fullscreen_ || close_on_deactivate_) &&
+ !weak_factory_.HasWeakPtrs()) {
+ // Dismiss popups and menus. We do this asynchronously to avoid changing
+ // activation within this callstack, which may interfere with another window
+ // being activated. We can synchronously hide the window, but we need to
+ // not change activation while doing so.
+ SetWindowPos(NULL, 0, 0, 0, 0,
+ SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RenderWidgetHostViewWin::ShutdownHost,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void RenderWidgetHostViewWin::OnInputLangChange(DWORD character_set,
+ HKL input_language_id) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnInputLangChange");
+ // Send the given Locale ID to the IMM32Manager object and retrieves whether
+ // or not the current input context has IMEs.
+ // If the current input context has IMEs, a browser process has to send a
+ // request to a renderer process that it needs status messages about
+ // the focused edit control from the renderer process.
+ // On the other hand, if the current input context does not have IMEs, the
+ // browser process also has to send a request to the renderer process that
+ // it does not need the status messages any longer.
+ // To minimize the number of this notification request, we should check if
+ // the browser process is actually retrieving the status messages (this
+ // state is stored in ime_notification_) and send a request only if the
+ // browser process has to update this status, its details are listed below:
+ // * If a browser process is not retrieving the status messages,
+ // (i.e. ime_notification_ == false),
+ // send this request only if the input context does have IMEs,
+ // (i.e. ime_status == true);
+ // When it successfully sends the request, toggle its notification status,
+ // (i.e.ime_notification_ = !ime_notification_ = true).
+ // * If a browser process is retrieving the status messages
+ // (i.e. ime_notification_ == true),
+ // send this request only if the input context does not have IMEs,
+ // (i.e. ime_status == false).
+ // When it successfully sends the request, toggle its notification status,
+ // (i.e.ime_notification_ = !ime_notification_ = false).
+ // To analyze the above actions, we can optimize them into the ones
+ // listed below:
+ // 1 Sending a request only if ime_status_ != ime_notification_, and;
+ // 2 Copying ime_status to ime_notification_ if it sends the request
+ // successfully (because Action 1 shows ime_status = !ime_notification_.)
+ bool ime_status = imm32_manager_->SetInputLanguage();
+ if (ime_status != ime_notification_) {
+ if (render_widget_host_) {
+ render_widget_host_->SetInputMethodActive(ime_status);
+ ime_notification_ = ime_status;
+ }
+ }
+ // Call DefWindowProc() for consistency with other Chrome windows.
+ // TODO(hbono): This is a speculative fix for Bug 36354 and this code may be
+ // reverted if it does not fix it.
+ SetMsgHandled(FALSE);
+}
+
+void RenderWidgetHostViewWin::OnThemeChanged() {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnThemeChanged");
+ if (!render_widget_host_)
+ return;
+ render_widget_host_->Send(new ViewMsg_ThemeChanged(
+ render_widget_host_->GetRoutingID()));
+}
+
+LRESULT RenderWidgetHostViewWin::OnNotify(int w_param, NMHDR* header) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnNotify");
+ if (tooltip_hwnd_ == NULL)
+ return 0;
+
+ switch (header->code) {
+ case TTN_GETDISPINFO: {
+ NMTTDISPINFOW* tooltip_info = reinterpret_cast<NMTTDISPINFOW*>(header);
+ tooltip_info->szText[0] = L'\0';
+ tooltip_info->lpszText = const_cast<WCHAR*>(tooltip_text_.c_str());
+ ::SendMessage(
+ tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, kTooltipMaxWidthPixels);
+ SetMsgHandled(TRUE);
+ break;
+ }
+ case TTN_POP:
+ tooltip_showing_ = false;
+ SetMsgHandled(TRUE);
+ break;
+ case TTN_SHOW:
+ // Tooltip shouldn't be shown when the mouse is locked.
+ DCHECK(!mouse_locked_);
+ tooltip_showing_ = true;
+ SetMsgHandled(TRUE);
+ break;
+ }
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnImeSetContext(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnImeSetContext");
+ if (!render_widget_host_)
+ return 0;
+
+ // We need status messages about the focused input control from a
+ // renderer process when:
+ // * the current input context has IMEs, and;
+ // * an application is activated.
+ // This seems to tell we should also check if the current input context has
+ // IMEs before sending a request, however, this WM_IME_SETCONTEXT is
+ // fortunately sent to an application only while the input context has IMEs.
+ // Therefore, we just start/stop status messages according to the activation
+ // status of this application without checks.
+ bool activated = (wparam == TRUE);
+ if (render_widget_host_) {
+ render_widget_host_->SetInputMethodActive(activated);
+ ime_notification_ = activated;
+ }
+
+ if (ime_notification_)
+ imm32_manager_->CreateImeWindow(m_hWnd);
+
+ imm32_manager_->CleanupComposition(m_hWnd);
+ return imm32_manager_->SetImeWindowStyle(
+ m_hWnd, message, wparam, lparam, &handled);
+}
+
+LRESULT RenderWidgetHostViewWin::OnImeStartComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnImeStartComposition");
+ if (!render_widget_host_)
+ return 0;
+
+ // Reset the composition status and create IME windows.
+ imm32_manager_->CreateImeWindow(m_hWnd);
+ imm32_manager_->ResetComposition(m_hWnd);
+ // When the focus is on an element that does not draw composition by itself
+ // (i.e., PPAPI plugin not handling IME), let IME to draw the text. Otherwise
+ // we have to prevent WTL from calling ::DefWindowProc() because the function
+ // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
+ // over-write the position of IME windows.
+ handled = (can_compose_inline_ ? TRUE : FALSE);
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnImeComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnImeComposition");
+ if (!render_widget_host_)
+ return 0;
+
+ // At first, update the position of the IME window.
+ imm32_manager_->UpdateImeWindow(m_hWnd);
+
+ // ui::CompositionUnderline should be identical to
+ // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
+ COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
+ sizeof(WebKit::WebCompositionUnderline),
+ ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
+
+ // Retrieve the result string and its attributes of the ongoing composition
+ // and send it to a renderer process.
+ ui::CompositionText composition;
+ if (imm32_manager_->GetResult(m_hWnd, lparam, &composition.text)) {
+ render_widget_host_->ImeConfirmComposition(
+ composition.text, ui::Range::InvalidRange(), false);
+ imm32_manager_->ResetComposition(m_hWnd);
+ // Fall though and try reading the composition string.
+ // Japanese IMEs send a message containing both GCS_RESULTSTR and
+ // GCS_COMPSTR, which means an ongoing composition has been finished
+ // by the start of another composition.
+ }
+ // Retrieve the composition string and its attributes of the ongoing
+ // composition and send it to a renderer process.
+ if (imm32_manager_->GetComposition(m_hWnd, lparam, &composition)) {
+ // TODO(suzhe): due to a bug of webkit, we can't use selection range with
+ // composition string. See: https://bugs.webkit.org/show_bug.cgi?id=37788
+ composition.selection = ui::Range(composition.selection.end());
+
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition.underlines);
+ render_widget_host_->ImeSetComposition(
+ composition.text, underlines,
+ composition.selection.start(), composition.selection.end());
+ }
+ // We have to prevent WTL from calling ::DefWindowProc() because we do not
+ // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
+ handled = TRUE;
+ if (!can_compose_inline_) {
+ // When the focus is on an element that does not draw composition by itself
+ // (i.e., PPAPI plugin not handling IME), let IME to draw the text, which
+ // is the default behavior of DefWindowProc. Note, however, even in this
+ // case we don't want GCS_RESULTSTR to be converted to WM_IME_CHAR messages.
+ // Thus we explicitly drop the flag.
+ return ::DefWindowProc(m_hWnd, message, wparam, lparam & ~GCS_RESULTSTR);
+ }
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnImeEndComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnImeEndComposition");
+ if (!render_widget_host_)
+ return 0;
+
+ if (imm32_manager_->is_composing()) {
+ // A composition has been ended while there is an ongoing composition,
+ // i.e. the ongoing composition has been canceled.
+ // We need to reset the composition status both of the IMM32Manager object
+ // and of the renderer process.
+ render_widget_host_->ImeCancelComposition();
+ imm32_manager_->ResetComposition(m_hWnd);
+ }
+ imm32_manager_->DestroyImeWindow(m_hWnd);
+ // Let WTL call ::DefWindowProc() and release its resources.
+ handled = FALSE;
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnImeRequest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnImeRequest");
+ if (!render_widget_host_) {
+ handled = FALSE;
+ return 0;
+ }
+
+ // Should not receive WM_IME_REQUEST message, if IME is disabled.
+ if (text_input_type_ == ui::TEXT_INPUT_TYPE_NONE ||
+ text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) {
+ handled = FALSE;
+ return 0;
+ }
+
+ switch (wparam) {
+ case IMR_RECONVERTSTRING:
+ return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
+ case IMR_DOCUMENTFEED:
+ return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
+ case IMR_QUERYCHARPOSITION:
+ return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam));
+ default:
+ handled = FALSE;
+ return 0;
+ }
+}
+
+LRESULT RenderWidgetHostViewWin::OnMouseEvent(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnMouseEvent");
+ handled = TRUE;
+
+ if (message == WM_MOUSELEAVE)
+ ignore_mouse_movement_ = true;
+
+ if (mouse_locked_) {
+ HandleLockedMouseEvent(message, wparam, lparam);
+ MoveCursorToCenterIfNecessary();
+ return 0;
+ }
+
+ if (::IsWindow(tooltip_hwnd_)) {
+ // Forward mouse events through to the tooltip window
+ MSG msg;
+ msg.hwnd = m_hWnd;
+ msg.message = message;
+ msg.wParam = wparam;
+ msg.lParam = lparam;
+ SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, NULL,
+ reinterpret_cast<LPARAM>(&msg));
+ }
+
+ // Due to a bug in Windows, the simulated mouse events for a touch event
+ // outside our bounds are delivered to us if we were previously focused
+ // causing crbug.com/159982. As a workaround, we check if this event is a
+ // simulated mouse event outside our bounds, and if so, we send it to the
+ // right window.
+ if ((message == WM_LBUTTONDOWN || message == WM_LBUTTONUP) &&
+ ui::IsMouseEventFromTouch(message)) {
+ CPoint cursor_pos(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
+ ClientToScreen(&cursor_pos);
+ if (!GetPixelBounds().Contains(cursor_pos.x, cursor_pos.y)) {
+ HWND window = WindowFromPoint(cursor_pos);
+ if (window) {
+ LRESULT nc_hit_result = SendMessage(window, WM_NCHITTEST, 0,
+ MAKELPARAM(cursor_pos.x, cursor_pos.y));
+ const bool in_client_area = (nc_hit_result == HTCLIENT);
+ int event_type;
+ if (message == WM_LBUTTONDOWN)
+ event_type = in_client_area ? WM_LBUTTONDOWN : WM_NCLBUTTONDOWN;
+ else
+ event_type = in_client_area ? WM_LBUTTONUP : WM_NCLBUTTONUP;
+
+ // Convert the coordinates to the target window.
+ RECT window_bounds;
+ ::GetWindowRect(window, &window_bounds);
+ int window_x = cursor_pos.x - window_bounds.left;
+ int window_y = cursor_pos.y - window_bounds.top;
+ if (in_client_area) {
+ ::PostMessage(window, event_type, wparam,
+ MAKELPARAM(window_x, window_y));
+ } else {
+ ::PostMessage(window, event_type, nc_hit_result,
+ MAKELPARAM(cursor_pos.x, cursor_pos.y));
+ }
+ return 0;
+ }
+ }
+ }
+
+ // TODO(jcampan): I am not sure if we should forward the message to the
+ // WebContentsImpl first in the case of popups. If we do, we would need to
+ // convert the click from the popup window coordinates to the WebContentsImpl'
+ // window coordinates. For now we don't forward the message in that case to
+ // address bug #907474.
+ // Note: GetParent() on popup windows returns the top window and not the
+ // parent the window was created with (the parent and the owner of the popup
+ // is the first non-child view of the view that was specified to the create
+ // call). So the WebContentsImpl's window would have to be specified to the
+ // RenderViewHostHWND as there is no way to retrieve it from the HWND.
+
+ // Don't forward if the container is a popup or fullscreen widget.
+ if (!is_fullscreen_ && !close_on_deactivate_) {
+ switch (message) {
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ // Finish the ongoing composition whenever a mouse click happens.
+ // It matches IE's behavior.
+ if (base::win::IsTSFAwareRequired()) {
+ ui::TSFBridge::GetInstance()->CancelComposition();
+ } else {
+ imm32_manager_->CleanupComposition(m_hWnd);
+ }
+ // Fall through.
+ case WM_MOUSEMOVE:
+ case WM_MOUSELEAVE: {
+ // Give the WebContentsImpl first crack at the message. It may want to
+ // prevent forwarding to the renderer if some higher level browser
+ // functionality is invoked.
+ LPARAM parent_msg_lparam = lparam;
+ if (message != WM_MOUSELEAVE) {
+ // For the messages except WM_MOUSELEAVE, before forwarding them to
+ // parent window, we should adjust cursor position from client
+ // coordinates in current window to client coordinates in its parent
+ // window.
+ CPoint cursor_pos(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
+ ClientToScreen(&cursor_pos);
+ GetParent().ScreenToClient(&cursor_pos);
+ parent_msg_lparam = MAKELPARAM(cursor_pos.x, cursor_pos.y);
+ }
+ if (SendMessage(GetParent(), message, wparam, parent_msg_lparam) != 0) {
+ TRACE_EVENT0("browser", "EarlyOut_SentToParent");
+ return 1;
+ }
+ }
+ }
+ }
+
+ if (message == WM_LBUTTONDOWN && pointer_down_context_ &&
+ GetBrowserAccessibilityManager()) {
+ GetBrowserAccessibilityManager()->GotMouseDown();
+ }
+
+ if (message == WM_LBUTTONUP && ui::IsMouseEventFromTouch(message) &&
+ base::win::IsMetroProcess())
+ pointer_down_context_ = false;
+
+ ForwardMouseEventToRenderer(message, wparam, lparam);
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnKeyEvent(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnKeyEvent");
+ handled = TRUE;
+
+ // When Escape is pressed, force fullscreen windows to close if necessary.
+ if ((message == WM_KEYDOWN || message == WM_KEYUP) && wparam == VK_ESCAPE) {
+ if (is_fullscreen_) {
+ SendMessage(WM_CANCELMODE);
+ return 0;
+ }
+ }
+
+ // If we are a pop-up, forward tab related messages to our parent HWND, so
+ // that we are dismissed appropriately and so that the focus advance in our
+ // parent.
+ // TODO(jcampan): http://b/issue?id=1192881 Could be abstracted in the
+ // FocusManager.
+ if (close_on_deactivate_ &&
+ (((message == WM_KEYDOWN || message == WM_KEYUP) && (wparam == VK_TAB)) ||
+ (message == WM_CHAR && wparam == L'\t'))) {
+ // First close the pop-up.
+ SendMessage(WM_CANCELMODE);
+ // Then move the focus by forwarding the tab key to the parent.
+ return ::SendMessage(GetParent(), message, wparam, lparam);
+ }
+
+ if (!render_widget_host_)
+ return 0;
+
+ // Bug 1845: we need to update the text direction when a user releases
+ // either a right-shift key or a right-control key after pressing both of
+ // them. So, we just update the text direction while a user is pressing the
+ // keys, and we notify the text direction when a user releases either of them.
+ // Bug 9718: http://crbug.com/9718 To investigate IE and notepad, this
+ // shortcut is enabled only on a PC having RTL keyboard layouts installed.
+ // We should emulate them.
+ if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled()) {
+ if (message == WM_KEYDOWN) {
+ if (wparam == VK_SHIFT) {
+ base::i18n::TextDirection dir;
+ if (ui::IMM32Manager::IsCtrlShiftPressed(&dir)) {
+ render_widget_host_->UpdateTextDirection(
+ dir == base::i18n::RIGHT_TO_LEFT ?
+ WebKit::WebTextDirectionRightToLeft :
+ WebKit::WebTextDirectionLeftToRight);
+ }
+ } else if (wparam != VK_CONTROL) {
+ // Bug 9762: http://crbug.com/9762 A user pressed a key except shift
+ // and control keys.
+ // When a user presses a key while he/she holds control and shift keys,
+ // we cancel sending an IPC message in NotifyTextDirection() below and
+ // ignore succeeding UpdateTextDirection() calls while we call
+ // NotifyTextDirection().
+ // To cancel it, this call set a flag that prevents sending an IPC
+ // message in NotifyTextDirection() only if we are going to send it.
+ // It is harmless to call this function if we aren't going to send it.
+ render_widget_host_->CancelUpdateTextDirection();
+ }
+ } else if (message == WM_KEYUP &&
+ (wparam == VK_SHIFT || wparam == VK_CONTROL)) {
+ // We send an IPC message only if we need to update the text direction.
+ render_widget_host_->NotifyTextDirection();
+ }
+ }
+
+ // Special processing for enter key: When user hits enter in omnibox
+ // we change focus to render host after the navigation, so repeat WM_KEYDOWNs
+ // and WM_KEYUP are going to render host, despite being initiated in other
+ // window. This code filters out these messages.
+ bool ignore_keyboard_event = false;
+ if (wparam == VK_RETURN) {
+ if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) {
+ if (KF_REPEAT & HIWORD(lparam)) {
+ // this is a repeated key
+ if (!capture_enter_key_)
+ ignore_keyboard_event = true;
+ } else {
+ capture_enter_key_ = true;
+ }
+ } else if (message == WM_KEYUP || message == WM_SYSKEYUP) {
+ if (!capture_enter_key_)
+ ignore_keyboard_event = true;
+ capture_enter_key_ = false;
+ } else {
+ // Ignore all other keyboard events for the enter key if not captured.
+ if (!capture_enter_key_)
+ ignore_keyboard_event = true;
+ }
+ }
+
+ if (render_widget_host_ && !ignore_keyboard_event) {
+ MSG msg = { m_hWnd, message, wparam, lparam };
+ render_widget_host_->ForwardKeyboardEvent(NativeWebKeyboardEvent(msg));
+ }
+
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnWheelEvent(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnWheelEvent");
+ // Forward the mouse-wheel message to the window under the mouse if it belongs
+ // to us.
+ if (message == WM_MOUSEWHEEL &&
+ ui::RerouteMouseWheel(m_hWnd, wparam, lparam)) {
+ handled = TRUE;
+ return 0;
+ }
+
+ // We get mouse wheel/scroll messages even if we are not in the foreground.
+ // So here we check if we have any owned popup windows in the foreground and
+ // dismiss them.
+ if (m_hWnd != GetForegroundWindow()) {
+ HWND toplevel_hwnd = ::GetAncestor(m_hWnd, GA_ROOT);
+ EnumThreadWindows(
+ GetCurrentThreadId(),
+ DismissOwnedPopups,
+ reinterpret_cast<LPARAM>(toplevel_hwnd));
+ }
+
+ if (render_widget_host_) {
+ WebKit::WebMouseWheelEvent wheel_event =
+ WebMouseWheelEventBuilder::Build(m_hWnd, message, wparam, lparam);
+ float scale = ui::win::GetDeviceScaleFactor();
+ wheel_event.x /= scale;
+ wheel_event.y /= scale;
+ wheel_event.deltaX /= scale;
+ wheel_event.deltaY /= scale;
+
+ render_widget_host_->ForwardWheelEvent(wheel_event);
+ }
+ handled = TRUE;
+ return 0;
+}
+
+WebTouchState::WebTouchState(const RenderWidgetHostViewWin* window)
+ : window_(window) { }
+
+size_t WebTouchState::UpdateTouchPoints(
+ TOUCHINPUT* points, size_t count) {
+ // First we reset all touch event state. This involves removing any released
+ // touchpoints and marking the rest as stationary. After that we go through
+ // and alter/add any touchpoints (from the touch input buffer) that we can
+ // coalesce into a single message. The return value is the number of consumed
+ // input message.
+ WebKit::WebTouchPoint* point = touch_event_.touches;
+ WebKit::WebTouchPoint* end = point + touch_event_.touchesLength;
+ while (point < end) {
+ if (point->state == WebKit::WebTouchPoint::StateReleased) {
+ *point = *(--end);
+ --touch_event_.touchesLength;
+ } else {
+ point->state = WebKit::WebTouchPoint::StateStationary;
+ point++;
+ }
+ }
+ touch_event_.changedTouchesLength = 0;
+ touch_event_.modifiers = content::EventFlagsToWebEventModifiers(
+ ui::GetModifiersFromKeyState());
+
+ // Consume all events of the same type and add them to the changed list.
+ int last_type = 0;
+ for (size_t i = 0; i < count; ++i) {
+ unsigned int mapped_id = GetMappedTouch(points[i].dwID);
+
+ WebKit::WebTouchPoint* point = NULL;
+ for (unsigned j = 0; j < touch_event_.touchesLength; ++j) {
+ if (static_cast<DWORD>(touch_event_.touches[j].id) == mapped_id) {
+ point = &touch_event_.touches[j];
+ break;
+ }
+ }
+
+ // Use a move instead if we see a down on a point we already have.
+ int type = GetTouchType(points[i]);
+ if (point && type == TOUCHEVENTF_DOWN)
+ SetTouchType(&points[i], TOUCHEVENTF_MOVE);
+
+ // Stop processing when the event type changes.
+ if (touch_event_.changedTouchesLength && type != last_type)
+ return i;
+
+ touch_event_.timeStampSeconds = points[i].dwTime / 1000.0;
+
+ last_type = type;
+ switch (type) {
+ case TOUCHEVENTF_DOWN: {
+ if (!(point = AddTouchPoint(&points[i])))
+ continue;
+ touch_event_.type = WebKit::WebInputEvent::TouchStart;
+ break;
+ }
+
+ case TOUCHEVENTF_UP: {
+ if (!point) // Just throw away a stray up.
+ continue;
+ point->state = WebKit::WebTouchPoint::StateReleased;
+ UpdateTouchPoint(point, &points[i]);
+ touch_event_.type = WebKit::WebInputEvent::TouchEnd;
+ break;
+ }
+
+ case TOUCHEVENTF_MOVE: {
+ if (point) {
+ point->state = WebKit::WebTouchPoint::StateMoved;
+ // Don't update the message if the point didn't really move.
+ if (UpdateTouchPoint(point, &points[i]))
+ continue;
+ touch_event_.type = WebKit::WebInputEvent::TouchMove;
+ } else if (touch_event_.changedTouchesLength) {
+ RemoveExpiredMappings();
+ // Can't add a point if we're already handling move events.
+ return i;
+ } else {
+ // Treat a move with no existing point as a down.
+ if (!(point = AddTouchPoint(&points[i])))
+ continue;
+ last_type = TOUCHEVENTF_DOWN;
+ SetTouchType(&points[i], TOUCHEVENTF_DOWN);
+ touch_event_.type = WebKit::WebInputEvent::TouchStart;
+ }
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ continue;
+ }
+ touch_event_.changedTouches[touch_event_.changedTouchesLength++] = *point;
+ }
+
+ RemoveExpiredMappings();
+ return count;
+}
+
+void WebTouchState::RemoveExpiredMappings() {
+ WebTouchState::MapType new_map;
+ for (MapType::iterator it = touch_map_.begin();
+ it != touch_map_.end();
+ ++it) {
+ WebKit::WebTouchPoint* point = touch_event_.touches;
+ WebKit::WebTouchPoint* end = point + touch_event_.touchesLength;
+ while (point < end) {
+ if ((point->id == it->second) &&
+ (point->state != WebKit::WebTouchPoint::StateReleased)) {
+ new_map.insert(*it);
+ break;
+ }
+ point++;
+ }
+ }
+ touch_map_.swap(new_map);
+}
+
+
+bool WebTouchState::ReleaseTouchPoints() {
+ if (touch_event_.touchesLength == 0)
+ return false;
+ // Mark every active touchpoint as released.
+ touch_event_.type = WebKit::WebInputEvent::TouchEnd;
+ touch_event_.changedTouchesLength = touch_event_.touchesLength;
+ for (unsigned int i = 0; i < touch_event_.touchesLength; ++i) {
+ touch_event_.touches[i].state = WebKit::WebTouchPoint::StateReleased;
+ touch_event_.changedTouches[i].state =
+ WebKit::WebTouchPoint::StateReleased;
+ }
+
+ return true;
+}
+
+WebKit::WebTouchPoint* WebTouchState::AddTouchPoint(
+ TOUCHINPUT* touch_input) {
+ DCHECK(touch_event_.touchesLength <
+ WebKit::WebTouchEvent::touchesLengthCap);
+ if (touch_event_.touchesLength >=
+ WebKit::WebTouchEvent::touchesLengthCap)
+ return NULL;
+ WebKit::WebTouchPoint* point =
+ &touch_event_.touches[touch_event_.touchesLength++];
+ point->state = WebKit::WebTouchPoint::StatePressed;
+ point->id = GetMappedTouch(touch_input->dwID);
+ UpdateTouchPoint(point, touch_input);
+ return point;
+}
+
+bool WebTouchState::UpdateTouchPoint(
+ WebKit::WebTouchPoint* touch_point,
+ TOUCHINPUT* touch_input) {
+ CPoint coordinates(
+ TOUCH_COORD_TO_PIXEL(touch_input->x) / ui::win::GetUndocumentedDPIScale(),
+ TOUCH_COORD_TO_PIXEL(touch_input->y) / ui::win::GetUndocumentedDPIScale());
+ int radius_x = 1;
+ int radius_y = 1;
+ if (touch_input->dwMask & TOUCHINPUTMASKF_CONTACTAREA) {
+ // Some touch drivers send a contact area of "-1", yet flag it as valid.
+ radius_x = std::max(1,
+ static_cast<int>(TOUCH_COORD_TO_PIXEL(touch_input->cxContact) /
+ ui::win::GetUndocumentedDPIScale()));
+ radius_y = std::max(1,
+ static_cast<int>(TOUCH_COORD_TO_PIXEL(touch_input->cyContact) /
+ ui::win::GetUndocumentedDPIScale()));
+ }
+
+ // Detect and exclude stationary moves.
+ if (GetTouchType(*touch_input) == TOUCHEVENTF_MOVE &&
+ touch_point->screenPosition.x == coordinates.x &&
+ touch_point->screenPosition.y == coordinates.y &&
+ touch_point->radiusX == radius_x &&
+ touch_point->radiusY == radius_y) {
+ touch_point->state = WebKit::WebTouchPoint::StateStationary;
+ return true;
+ }
+
+ touch_point->screenPosition.x = coordinates.x;
+ touch_point->screenPosition.y = coordinates.y;
+ window_->ScreenToClient(&coordinates);
+ static float scale = ui::win::GetDeviceScaleFactor();
+ touch_point->position.x = coordinates.x / scale;
+ touch_point->position.y = coordinates.y / scale;
+ touch_point->radiusX = radius_x;
+ touch_point->radiusY = radius_y;
+ touch_point->force = 0;
+ touch_point->rotationAngle = 0;
+ return false;
+}
+
+// Find (or create) a mapping for _os_touch_id_.
+unsigned int WebTouchState::GetMappedTouch(unsigned int os_touch_id) {
+ MapType::iterator it = touch_map_.find(os_touch_id);
+ if (it != touch_map_.end())
+ return it->second;
+ int next_value = 0;
+ for (it = touch_map_.begin(); it != touch_map_.end(); ++it)
+ next_value = std::max(next_value, it->second + 1);
+ touch_map_[os_touch_id] = next_value;
+ return next_value;
+}
+
+LRESULT RenderWidgetHostViewWin::OnTouchEvent(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnTouchEvent");
+ // Finish the ongoing composition whenever a touch event happens.
+ // It matches IE's behavior.
+ if (base::win::IsTSFAwareRequired()) {
+ ui::TSFBridge::GetInstance()->CancelComposition();
+ } else {
+ imm32_manager_->CleanupComposition(m_hWnd);
+ }
+
+ // TODO(jschuh): Add support for an arbitrary number of touchpoints.
+ size_t total = std::min(static_cast<int>(LOWORD(wparam)),
+ static_cast<int>(WebKit::WebTouchEvent::touchesLengthCap));
+ TOUCHINPUT points[WebKit::WebTouchEvent::touchesLengthCap];
+
+ if (!total || !ui::GetTouchInputInfoWrapper((HTOUCHINPUT)lparam, total,
+ points, sizeof(TOUCHINPUT))) {
+ TRACE_EVENT0("browser", "EarlyOut_NothingToDo");
+ return 0;
+ }
+
+ if (total == 1 && (points[0].dwFlags & TOUCHEVENTF_DOWN)) {
+ pointer_down_context_ = true;
+ last_touch_location_ = gfx::Point(
+ TOUCH_COORD_TO_PIXEL(points[0].x) / ui::win::GetUndocumentedDPIScale(),
+ TOUCH_COORD_TO_PIXEL(points[0].y) / ui::win::GetUndocumentedDPIScale());
+ }
+
+ bool should_forward = render_widget_host_->ShouldForwardTouchEvent() &&
+ touch_events_enabled_;
+
+ // Send a copy of the touch events on to the gesture recognizer.
+ for (size_t start = 0; start < total;) {
+ start += touch_state_->UpdateTouchPoints(points + start, total - start);
+ if (should_forward) {
+ if (touch_state_->is_changed())
+ render_widget_host_->ForwardTouchEventWithLatencyInfo(
+ touch_state_->touch_event(), ui::LatencyInfo());
+ } else {
+ const WebKit::WebTouchEvent& touch_event = touch_state_->touch_event();
+ base::TimeDelta timestamp = base::TimeDelta::FromMilliseconds(
+ touch_event.timeStampSeconds * 1000);
+ for (size_t i = 0; i < touch_event.touchesLength; ++i) {
+ scoped_ptr<ui::GestureRecognizer::Gestures> gestures;
+ gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture(
+ TouchEventFromWebTouchPoint(touch_event.touches[i], timestamp),
+ ui::ER_UNHANDLED, this));
+ ProcessGestures(gestures.get());
+ }
+ }
+ }
+
+ CloseTouchInputHandle((HTOUCHINPUT)lparam);
+
+ return 0;
+}
+
+void RenderWidgetHostViewWin::ProcessGestures(
+ ui::GestureRecognizer::Gestures* gestures) {
+ if ((gestures == NULL) || gestures->empty())
+ return;
+ for (ui::GestureRecognizer::Gestures::iterator g_it = gestures->begin();
+ g_it != gestures->end();
+ ++g_it) {
+ ForwardGestureEventToRenderer(*g_it);
+ }
+}
+
+LRESULT RenderWidgetHostViewWin::OnMouseActivate(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnMouseActivate");
+ if (!render_widget_host_)
+ return MA_NOACTIVATE;
+
+ if (!IsActivatable())
+ return MA_NOACTIVATE;
+
+ HWND focus_window = GetFocus();
+ if (!::IsWindow(focus_window) || !IsChild(focus_window)) {
+ // We handle WM_MOUSEACTIVATE to set focus to the underlying plugin
+ // child window. This is to ensure that keyboard events are received
+ // by the plugin. The correct way to fix this would be send over
+ // an event to the renderer which would then eventually send over
+ // a setFocus call to the plugin widget. This would ensure that
+ // the renderer (webkit) knows about the plugin widget receiving
+ // focus.
+ // TODO(iyengar) Do the right thing as per the above comment.
+ POINT cursor_pos = {0};
+ ::GetCursorPos(&cursor_pos);
+ ::ScreenToClient(m_hWnd, &cursor_pos);
+ HWND child_window = ::RealChildWindowFromPoint(m_hWnd, cursor_pos);
+ if (::IsWindow(child_window) && child_window != m_hWnd) {
+ if (ui::GetClassName(child_window) == kWrapperNativeWindowClassName)
+ child_window = ::GetWindow(child_window, GW_CHILD);
+
+ ::SetFocus(child_window);
+ return MA_NOACTIVATE;
+ }
+ }
+ handled = FALSE;
+ render_widget_host_->OnPointerEventActivate();
+ return MA_ACTIVATE;
+}
+
+LRESULT RenderWidgetHostViewWin::OnGestureEvent(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnGestureEvent");
+
+ DCHECK(!touch_events_enabled_);
+ handled = FALSE;
+
+ GESTUREINFO gi = {sizeof(GESTUREINFO)};
+ HGESTUREINFO gi_handle = reinterpret_cast<HGESTUREINFO>(lparam);
+ if (!::GetGestureInfo(gi_handle, &gi)) {
+ DWORD error = GetLastError();
+ NOTREACHED() << "Unable to get gesture info. Error : " << error;
+ return 0;
+ }
+
+ if (gi.dwID == GID_ZOOM) {
+ PageZoom zoom = PAGE_ZOOM_RESET;
+ POINT zoom_center = {0};
+ if (DecodeZoomGesture(m_hWnd, gi, &zoom, &zoom_center)) {
+ handled = TRUE;
+ Send(new ViewMsg_ZoomFactor(render_widget_host_->GetRoutingID(),
+ zoom, zoom_center.x, zoom_center.y));
+ }
+ } else if (gi.dwID == GID_PAN) {
+ // Right now we only decode scroll gestures and we forward to the page
+ // as scroll events.
+ POINT start;
+ POINT delta;
+ if (DecodeScrollGesture(gi, &start, &delta)) {
+ handled = TRUE;
+ render_widget_host_->ForwardWheelEvent(
+ MakeFakeScrollWheelEvent(m_hWnd, start, delta));
+ }
+ }
+ ::CloseGestureInfoHandle(gi_handle);
+ return 0;
+}
+
+LRESULT RenderWidgetHostViewWin::OnMoveOrSize(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ // Reset the cliping rectangle if the mouse is locked.
+ if (mouse_locked_) {
+ CRect rect;
+ GetWindowRect(&rect);
+ ::ClipCursor(&rect);
+ }
+ return 0;
+}
+
+void RenderWidgetHostViewWin::OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
+ CreateBrowserAccessibilityManagerIfNeeded();
+ GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params);
+}
+
+bool RenderWidgetHostViewWin::LockMouse() {
+ if (mouse_locked_)
+ return true;
+
+ mouse_locked_ = true;
+
+ // Hide the tooltip window if it is currently visible. When the mouse is
+ // locked, no mouse message is relayed to the tooltip window, so we don't need
+ // to worry that it will reappear.
+ if (::IsWindow(tooltip_hwnd_) && tooltip_showing_) {
+ ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0);
+ // Sending a TTM_POP message doesn't seem to actually hide the tooltip
+ // window, although we will receive a TTN_POP notification. As a result,
+ // ShowWindow() is explicitly called to hide the window.
+ ::ShowWindow(tooltip_hwnd_, SW_HIDE);
+ }
+
+ // TODO(yzshen): ShowCursor(FALSE) causes SetCursorPos() to be ignored on
+ // Remote Desktop.
+ ::ShowCursor(FALSE);
+
+ move_to_center_request_.pending = false;
+ last_mouse_position_.locked_global = last_mouse_position_.unlocked_global;
+
+ // Must set the clip rectangle before MoveCursorToCenterIfNecessary()
+ // so that if the cursor is moved it uses the clip rect set to the window
+ // rect. Otherwise, MoveCursorToCenterIfNecessary() may move the cursor
+ // to the center of the screen, and then we would clip to the window
+ // rect, thus moving the cursor and causing a movement delta.
+ CRect rect;
+ GetWindowRect(&rect);
+ ::ClipCursor(&rect);
+ MoveCursorToCenterIfNecessary();
+
+ return true;
+}
+
+void RenderWidgetHostViewWin::UnlockMouse() {
+ if (!mouse_locked_)
+ return;
+
+ mouse_locked_ = false;
+
+ ::ClipCursor(NULL);
+ ::SetCursorPos(last_mouse_position_.unlocked_global.x(),
+ last_mouse_position_.unlocked_global.y());
+ ::ShowCursor(TRUE);
+
+ if (render_widget_host_)
+ render_widget_host_->LostMouseLock();
+}
+
+void RenderWidgetHostViewWin::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+
+ // Get the RenderProcessHost that posted this notification, and exit
+ // if it's not the one associated with this host view.
+ RenderProcessHost* render_process_host =
+ Source<RenderProcessHost>(source).ptr();
+ DCHECK(render_process_host);
+ if (!render_widget_host_ ||
+ render_process_host != render_widget_host_->GetProcess()) {
+ return;
+ }
+
+ // If it was our RenderProcessHost that posted the notification,
+ // clear the BrowserAccessibilityManager, because the renderer is
+ // dead and any accessibility information we have is now stale.
+ SetBrowserAccessibilityManager(NULL);
+}
+
+static void PaintCompositorHostWindow(HWND hWnd) {
+ PAINTSTRUCT paint;
+ BeginPaint(hWnd, &paint);
+
+ RenderWidgetHostViewWin* win = static_cast<RenderWidgetHostViewWin*>(
+ ui::GetWindowUserData(hWnd));
+ // Trigger composite to rerender window.
+ if (win)
+ win->AcceleratedPaint(paint.hdc);
+
+ EndPaint(hWnd, &paint);
+}
+
+// WndProc for the compositor host window. We use this instead of Default so
+// we can drop WM_PAINT and WM_ERASEBKGD messages on the floor.
+static LRESULT CALLBACK CompositorHostWindowProc(HWND hWnd, UINT message,
+ WPARAM wParam, LPARAM lParam) {
+ switch (message) {
+ case WM_ERASEBKGND:
+ return 0;
+ case WM_PAINT:
+ PaintCompositorHostWindow(hWnd);
+ return 0;
+ default:
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ }
+}
+
+void RenderWidgetHostViewWin::AcceleratedPaint(HDC dc) {
+ if (render_widget_host_)
+ render_widget_host_->ScheduleComposite();
+ if (accelerated_surface_)
+ accelerated_surface_->Present(dc);
+}
+
+void RenderWidgetHostViewWin::GetScreenInfo(WebKit::WebScreenInfo* results) {
+ GetScreenInfoForWindow(GetNativeViewId(), results);
+}
+
+gfx::Rect RenderWidgetHostViewWin::GetBoundsInRootWindow() {
+ RECT window_rect = {0};
+ HWND root_window = GetAncestor(m_hWnd, GA_ROOT);
+ ::GetWindowRect(root_window, &window_rect);
+ gfx::Rect rect(window_rect);
+
+ // Maximized windows are outdented from the work area by the frame thickness
+ // even though this "frame" is not painted. This confuses code (and people)
+ // that think of a maximized window as corresponding exactly to the work area.
+ // Correct for this by subtracting the frame thickness back off.
+ if (::IsZoomed(root_window)) {
+ rect.Inset(GetSystemMetrics(SM_CXSIZEFRAME),
+ GetSystemMetrics(SM_CYSIZEFRAME));
+ }
+
+ return ui::win::ScreenToDIPRect(rect);
+}
+
+// Creates a HWND within the RenderWidgetHostView that will serve as a host
+// for a HWND that the GPU process will create. The host window is used
+// to Z-position the GPU's window relative to other plugin windows.
+gfx::GLSurfaceHandle RenderWidgetHostViewWin::GetCompositingSurface() {
+ // If the window has been created, don't recreate it a second time
+ if (compositor_host_window_)
+ return gfx::GLSurfaceHandle(compositor_host_window_, gfx::NATIVE_TRANSPORT);
+
+ // On Vista and later we present directly to the view window rather than a
+ // child window.
+ if (GpuDataManagerImpl::GetInstance()->IsUsingAcceleratedSurface()) {
+ if (!accelerated_surface_)
+ accelerated_surface_.reset(new AcceleratedSurface(m_hWnd));
+ return gfx::GLSurfaceHandle(m_hWnd, gfx::NATIVE_TRANSPORT);
+ }
+
+ // On XP we need a child window that can be resized independently of the
+ // parent.
+ static ATOM atom = 0;
+ static HMODULE instance = NULL;
+ if (!atom) {
+ WNDCLASSEX window_class;
+ base::win::InitializeWindowClass(
+ L"CompositorHostWindowClass",
+ &base::win::WrappedWindowProc<CompositorHostWindowProc>,
+ 0, 0, 0, NULL, NULL, NULL, NULL, NULL,
+ &window_class);
+ instance = window_class.hInstance;
+ atom = RegisterClassEx(&window_class);
+ DCHECK(atom);
+ }
+
+ RECT currentRect;
+ GetClientRect(&currentRect);
+
+ // Ensure window does not have zero area because D3D cannot create a zero
+ // area swap chain.
+ int width = std::max(1,
+ static_cast<int>(currentRect.right - currentRect.left));
+ int height = std::max(1,
+ static_cast<int>(currentRect.bottom - currentRect.top));
+
+ compositor_host_window_ = CreateWindowEx(
+ WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR,
+ MAKEINTATOM(atom), 0,
+ WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_DISABLED,
+ 0, 0, width, height, m_hWnd, 0, instance, 0);
+ ui::CheckWindowCreated(compositor_host_window_);
+
+ ui::SetWindowUserData(compositor_host_window_, this);
+
+ gfx::GLSurfaceHandle surface_handle(compositor_host_window_,
+ gfx::NATIVE_TRANSPORT);
+ return surface_handle;
+}
+
+void RenderWidgetHostViewWin::OnAcceleratedCompositingStateChange() {
+ bool show = render_widget_host_->is_accelerated_compositing_active();
+ // When we first create the compositor, we will get a show request from
+ // the renderer before we have gotten the create request from the GPU. In this
+ // case, simply ignore the show request.
+ if (compositor_host_window_ == NULL)
+ return;
+
+ if (show) {
+ ::ShowWindow(compositor_host_window_, SW_SHOW);
+
+ // Get all the child windows of this view, including the compositor window.
+ std::vector<HWND> all_child_windows;
+ ::EnumChildWindows(m_hWnd, AddChildWindowToVector,
+ reinterpret_cast<LPARAM>(&all_child_windows));
+
+ // Build a list of just the plugin window handles
+ std::vector<HWND> plugin_windows;
+ bool compositor_host_window_found = false;
+ for (size_t i = 0; i < all_child_windows.size(); ++i) {
+ if (all_child_windows[i] != compositor_host_window_)
+ plugin_windows.push_back(all_child_windows[i]);
+ else
+ compositor_host_window_found = true;
+ }
+ DCHECK(compositor_host_window_found);
+
+ // Set all the plugin windows to be "after" the compositor window.
+ // When the compositor window is created, gets placed above plugins.
+ for (size_t i = 0; i < plugin_windows.size(); ++i) {
+ HWND next;
+ if (i + 1 < plugin_windows.size())
+ next = plugin_windows[i+1];
+ else
+ next = compositor_host_window_;
+ ::SetWindowPos(plugin_windows[i], next, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
+ }
+ } else {
+ // Drop the backing store for the accelerated surface when the accelerated
+ // compositor is disabled. Otherwise, a flash of the last presented frame
+ // could appear when it is next enabled.
+ if (accelerated_surface_)
+ accelerated_surface_->Suspend();
+ hide_compositor_window_at_next_paint_ = true;
+ }
+}
+
+void RenderWidgetHostViewWin::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewWin::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+ NOTREACHED();
+}
+
+void RenderWidgetHostViewWin::AcceleratedSurfaceSuspend() {
+ if (!accelerated_surface_)
+ return;
+
+ accelerated_surface_->Suspend();
+}
+
+void RenderWidgetHostViewWin::AcceleratedSurfaceRelease() {
+}
+
+bool RenderWidgetHostViewWin::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ // TODO(jbates) Implement this so this view can use GetBackingStore for both
+ // software and GPU frames. Defaulting to false just makes GetBackingStore
+ // only useable for software frames.
+ return false;
+}
+
+void RenderWidgetHostViewWin::SetAccessibilityFocus(int acc_obj_id) {
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->AccessibilitySetFocus(acc_obj_id);
+}
+
+void RenderWidgetHostViewWin::AccessibilityDoDefaultAction(int acc_obj_id) {
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->AccessibilityDoDefaultAction(acc_obj_id);
+}
+
+void RenderWidgetHostViewWin::AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) {
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->AccessibilityScrollToMakeVisible(acc_obj_id, subfocus);
+}
+
+void RenderWidgetHostViewWin::AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) {
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->AccessibilityScrollToPoint(acc_obj_id, point);
+}
+
+void RenderWidgetHostViewWin::AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) {
+ if (!render_widget_host_)
+ return;
+
+ render_widget_host_->AccessibilitySetTextSelection(
+ acc_obj_id, start_offset, end_offset);
+}
+
+gfx::Point RenderWidgetHostViewWin::GetLastTouchEventLocation() const {
+ return last_touch_location_;
+}
+
+void RenderWidgetHostViewWin::FatalAccessibilityTreeError() {
+ render_widget_host_->FatalAccessibilityTreeError();
+ SetBrowserAccessibilityManager(NULL);
+}
+
+LRESULT RenderWidgetHostViewWin::OnGetObject(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnGetObject");
+ if (kIdCustom == lparam) {
+ // An MSAA client requestes our custom id. Assume that we have detected an
+ // active windows screen reader.
+ BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected();
+ render_widget_host_->SetAccessibilityMode(
+ BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode());
+
+ // Return with failure.
+ return static_cast<LRESULT>(0L);
+ }
+
+ if (lparam != OBJID_CLIENT) {
+ handled = false;
+ return static_cast<LRESULT>(0L);
+ }
+
+ IAccessible* iaccessible = GetNativeViewAccessible();
+ if (iaccessible)
+ return LresultFromObject(IID_IAccessible, wparam, iaccessible);
+
+ handled = false;
+ return static_cast<LRESULT>(0L);
+}
+
+LRESULT RenderWidgetHostViewWin::OnParentNotify(UINT message, WPARAM wparam,
+ LPARAM lparam, BOOL& handled) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnParentNotify");
+ handled = FALSE;
+
+ if (!render_widget_host_)
+ return 0;
+
+ switch (LOWORD(wparam)) {
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ render_widget_host_->StartUserGesture();
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void RenderWidgetHostViewWin::OnFinalMessage(HWND window) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnFinalMessage");
+ // When the render widget host is being destroyed, it ends up calling
+ // Destroy() which NULLs render_widget_host_.
+ // Note: the following bug http://crbug.com/24248 seems to report that
+ // OnFinalMessage is called with a deleted |render_widget_host_|. It is not
+ // clear how this could happen, hence the NULLing of render_widget_host_
+ // above.
+ if (!render_widget_host_ && !being_destroyed_) {
+ // If you hit this NOTREACHED, please add a comment to report it on
+ // http://crbug.com/24248, including what you did when it happened and if
+ // you can repro.
+ NOTREACHED();
+ }
+ if (render_widget_host_)
+ render_widget_host_->ViewDestroyed();
+ delete this;
+}
+
+LRESULT RenderWidgetHostViewWin::OnSessionChange(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ BOOL& handled) {
+ handled = FALSE;
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnSessionChange");
+
+ if (!accelerated_surface_)
+ return 0;
+
+ switch (wparam) {
+ case WTS_SESSION_LOCK:
+ accelerated_surface_->SetIsSessionLocked(true);
+ break;
+ case WTS_SESSION_UNLOCK:
+ // Force a repaint to update the window contents.
+ if (!is_hidden_)
+ InvalidateRect(NULL, FALSE);
+ accelerated_surface_->SetIsSessionLocked(false);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void RenderWidgetHostViewWin::TrackMouseLeave(bool track) {
+ if (track == track_mouse_leave_)
+ return;
+ track_mouse_leave_ = track;
+
+ DCHECK(m_hWnd);
+
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ tme.dwFlags = TME_LEAVE;
+ if (!track_mouse_leave_)
+ tme.dwFlags |= TME_CANCEL;
+ tme.hwndTrack = m_hWnd;
+
+ TrackMouseEvent(&tme);
+}
+
+bool RenderWidgetHostViewWin::Send(IPC::Message* message) {
+ if (!render_widget_host_)
+ return false;
+ return render_widget_host_->Send(message);
+}
+
+void RenderWidgetHostViewWin::EnsureTooltip() {
+ UINT message = TTM_NEWTOOLRECT;
+
+ TOOLINFO ti = {0};
+ ti.cbSize = sizeof(ti);
+ ti.hwnd = m_hWnd;
+ ti.uId = 0;
+ if (!::IsWindow(tooltip_hwnd_)) {
+ message = TTM_ADDTOOL;
+ tooltip_hwnd_ = CreateWindowEx(
+ WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(),
+ TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, m_hWnd, NULL,
+ NULL, NULL);
+ if (!tooltip_hwnd_) {
+ // Tooltip creation can inexplicably fail. See bug 82913 for details.
+ LOG_GETLASTERROR(WARNING) <<
+ "Tooltip creation failed, tooltips won't work";
+ return;
+ }
+ ti.uFlags = TTF_TRANSPARENT;
+ ti.lpszText = LPSTR_TEXTCALLBACK;
+
+ // Ensure web content tooltips are displayed for at least this amount of
+ // time, to give users a chance to read longer messages.
+ const int kMinimumAutopopDurationMs = 10 * 1000;
+ int autopop_duration_ms =
+ SendMessage(tooltip_hwnd_, TTM_GETDELAYTIME, TTDT_AUTOPOP, NULL);
+ if (autopop_duration_ms < kMinimumAutopopDurationMs) {
+ SendMessage(tooltip_hwnd_, TTM_SETDELAYTIME, TTDT_AUTOPOP,
+ kMinimumAutopopDurationMs);
+ }
+ }
+
+ CRect cr;
+ GetClientRect(&ti.rect);
+ SendMessage(tooltip_hwnd_, message, NULL, reinterpret_cast<LPARAM>(&ti));
+}
+
+void RenderWidgetHostViewWin::ResetTooltip() {
+ if (::IsWindow(tooltip_hwnd_))
+ ::DestroyWindow(tooltip_hwnd_);
+ tooltip_hwnd_ = NULL;
+}
+
+bool RenderWidgetHostViewWin::ForwardGestureEventToRenderer(
+ ui::GestureEvent* gesture) {
+ if (!render_widget_host_)
+ return false;
+
+ // Pinch gestures are disabled by default on windows desktop. See
+ // crbug.com/128477 and crbug.com/148816
+ if ((gesture->type() == ui::ET_GESTURE_PINCH_BEGIN ||
+ gesture->type() == ui::ET_GESTURE_PINCH_UPDATE ||
+ gesture->type() == ui::ET_GESTURE_PINCH_END) &&
+ !ShouldSendPinchGesture()) {
+ return true;
+ }
+
+ WebKit::WebGestureEvent web_gesture = CreateWebGestureEvent(m_hWnd, *gesture);
+ if (web_gesture.type == WebKit::WebGestureEvent::Undefined)
+ return false;
+ if (web_gesture.type == WebKit::WebGestureEvent::GestureTapDown) {
+ render_widget_host_->ForwardGestureEvent(
+ CreateFlingCancelEvent(gesture->time_stamp().InSecondsF()));
+ }
+ render_widget_host_->ForwardGestureEventWithLatencyInfo(web_gesture,
+ *gesture->latency());
+ return true;
+}
+
+void RenderWidgetHostViewWin::ForwardMouseEventToRenderer(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewWin::ForwardMouseEventToRenderer");
+ if (!render_widget_host_) {
+ TRACE_EVENT0("browser", "EarlyOut_NoRWH");
+ return;
+ }
+
+ gfx::Point point = ui::win::ScreenToDIPPoint(
+ gfx::Point(static_cast<short>(LOWORD(lparam)),
+ static_cast<short>(HIWORD(lparam))));
+ lparam = MAKELPARAM(point.x(), point.y());
+
+ WebMouseEvent event(
+ WebMouseEventBuilder::Build(m_hWnd, message, wparam, lparam));
+
+ if (mouse_locked_) {
+ event.movementX = event.globalX - last_mouse_position_.locked_global.x();
+ event.movementY = event.globalY - last_mouse_position_.locked_global.y();
+ last_mouse_position_.locked_global.SetPoint(event.globalX, event.globalY);
+
+ event.x = last_mouse_position_.unlocked.x();
+ event.y = last_mouse_position_.unlocked.y();
+ event.windowX = last_mouse_position_.unlocked.x();
+ event.windowY = last_mouse_position_.unlocked.y();
+ event.globalX = last_mouse_position_.unlocked_global.x();
+ event.globalY = last_mouse_position_.unlocked_global.y();
+ } else {
+ if (ignore_mouse_movement_) {
+ ignore_mouse_movement_ = false;
+ event.movementX = 0;
+ event.movementY = 0;
+ } else {
+ event.movementX =
+ event.globalX - last_mouse_position_.unlocked_global.x();
+ event.movementY =
+ event.globalY - last_mouse_position_.unlocked_global.y();
+ }
+
+ last_mouse_position_.unlocked.SetPoint(event.windowX, event.windowY);
+ last_mouse_position_.unlocked_global.SetPoint(event.globalX, event.globalY);
+ }
+
+ // Windows sends (fake) mouse messages for touch events. Don't send these to
+ // the render widget.
+ if (!touch_events_enabled_ || !ui::IsMouseEventFromTouch(message)) {
+ // Send the event to the renderer before changing mouse capture, so that
+ // the capturelost event arrives after mouseup.
+ render_widget_host_->ForwardMouseEvent(event);
+
+ switch (event.type) {
+ case WebInputEvent::MouseMove:
+ TrackMouseLeave(true);
+ break;
+ case WebInputEvent::MouseLeave:
+ TrackMouseLeave(false);
+ break;
+ case WebInputEvent::MouseDown:
+ SetCapture();
+ break;
+ case WebInputEvent::MouseUp:
+ if (GetCapture() == m_hWnd)
+ ReleaseCapture();
+ break;
+ }
+ }
+
+ if (IsActivatable() && event.type == WebInputEvent::MouseDown) {
+ // This is a temporary workaround for bug 765011 to get focus when the
+ // mouse is clicked. This happens after the mouse down event is sent to
+ // the renderer because normally Windows does a WM_SETFOCUS after
+ // WM_LBUTTONDOWN.
+ SetFocus();
+ }
+}
+
+void RenderWidgetHostViewWin::ShutdownHost() {
+ weak_factory_.InvalidateWeakPtrs();
+ if (render_widget_host_)
+ render_widget_host_->Shutdown();
+ // Do not touch any members at this point, |this| has been deleted.
+}
+
+void RenderWidgetHostViewWin::DoPopupOrFullscreenInit(HWND parent_hwnd,
+ const gfx::Rect& pos,
+ DWORD ex_style) {
+ Create(parent_hwnd, NULL, NULL, WS_POPUP, ex_style);
+ gfx::Rect screen_rect = ui::win::DIPToScreenRect(pos);
+ MoveWindow(screen_rect.x(), screen_rect.y(), screen_rect.width(),
+ screen_rect.height(), TRUE);
+ ShowWindow(IsActivatable() ? SW_SHOW : SW_SHOWNA);
+
+ if (is_fullscreen_ && win8::IsSingleWindowMetroMode()) {
+ MetroSetFrameWindow set_frame_window =
+ reinterpret_cast<MetroSetFrameWindow>(
+ ::GetProcAddress(base::win::GetMetroModule(), "SetFrameWindow"));
+ DCHECK(set_frame_window);
+ set_frame_window(m_hWnd);
+ }
+}
+
+CPoint RenderWidgetHostViewWin::GetClientCenter() const {
+ CRect rect;
+ GetClientRect(&rect);
+ return rect.CenterPoint();
+}
+
+void RenderWidgetHostViewWin::MoveCursorToCenterIfNecessary() {
+ DCHECK(mouse_locked_);
+
+ CRect rect;
+ GetClipCursor(&rect);
+ int border_x = rect.Width() * kMouseLockBorderPercentage / 100;
+ int border_y = rect.Height() * kMouseLockBorderPercentage / 100;
+
+ bool should_move =
+ last_mouse_position_.locked_global.x() < rect.left + border_x ||
+ last_mouse_position_.locked_global.x() > rect.right - border_x ||
+ last_mouse_position_.locked_global.y() < rect.top + border_y ||
+ last_mouse_position_.locked_global.y() > rect.bottom - border_y;
+
+ if (should_move) {
+ move_to_center_request_.pending = true;
+ move_to_center_request_.target = rect.CenterPoint();
+ if (!::SetCursorPos(move_to_center_request_.target.x(),
+ move_to_center_request_.target.y())) {
+ LOG_GETLASTERROR(WARNING) << "Failed to set cursor position.";
+ }
+ }
+}
+
+void RenderWidgetHostViewWin::HandleLockedMouseEvent(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewWin::HandleLockedMouseEvent");
+ DCHECK(mouse_locked_);
+
+ if (message == WM_MOUSEMOVE && move_to_center_request_.pending) {
+ // Ignore WM_MOUSEMOVE messages generated by
+ // MoveCursorToCenterIfNecessary().
+ CPoint current_position(LOWORD(lparam), HIWORD(lparam));
+ ClientToScreen(&current_position);
+ if (move_to_center_request_.target.x() == current_position.x &&
+ move_to_center_request_.target.y() == current_position.y) {
+ move_to_center_request_.pending = false;
+ last_mouse_position_.locked_global = move_to_center_request_.target;
+ return;
+ }
+ }
+
+ ForwardMouseEventToRenderer(message, wparam, lparam);
+}
+
+LRESULT RenderWidgetHostViewWin::OnDocumentFeed(RECONVERTSTRING* reconv) {
+ size_t target_offset;
+ size_t target_length;
+ bool has_composition;
+ if (!composition_range_.is_empty()) {
+ target_offset = composition_range_.GetMin();
+ target_length = composition_range_.length();
+ has_composition = true;
+ } else if (selection_range_.IsValid()) {
+ target_offset = selection_range_.GetMin();
+ target_length = selection_range_.length();
+ has_composition = false;
+ } else {
+ return 0;
+ }
+
+ size_t len = selection_text_.length();
+ size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (target_offset < selection_text_offset_ ||
+ target_offset + target_length > selection_text_offset_ + len) {
+ return 0;
+ }
+
+ if (!reconv)
+ return need_size;
+
+ if (reconv->dwSize < need_size)
+ return 0;
+
+ reconv->dwVersion = 0;
+ reconv->dwStrLen = len;
+ reconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ reconv->dwCompStrLen = has_composition ? target_length: 0;
+ reconv->dwCompStrOffset =
+ (target_offset - selection_text_offset_) * sizeof(WCHAR);
+ reconv->dwTargetStrLen = target_length;
+ reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
+ memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
+ selection_text_.c_str(), len * sizeof(WCHAR));
+
+ // According to Microsft API document, IMR_RECONVERTSTRING and
+ // IMR_DOCUMENTFEED should return reconv, but some applications return
+ // need_size.
+ return reinterpret_cast<LRESULT>(reconv);
+}
+
+LRESULT RenderWidgetHostViewWin::OnReconvertString(RECONVERTSTRING* reconv) {
+ // If there is a composition string already, we don't allow reconversion.
+ if (imm32_manager_->is_composing())
+ return 0;
+
+ if (selection_range_.is_empty())
+ return 0;
+
+ if (selection_text_.empty())
+ return 0;
+
+ if (selection_range_.GetMin() < selection_text_offset_ ||
+ selection_range_.GetMax() >
+ selection_text_offset_ + selection_text_.length()) {
+ return 0;
+ }
+
+ size_t len = selection_range_.length();
+ size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!reconv)
+ return need_size;
+
+ if (reconv->dwSize < need_size)
+ return 0;
+
+ reconv->dwVersion = 0;
+ reconv->dwStrLen = len;
+ reconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ reconv->dwCompStrLen = len;
+ reconv->dwCompStrOffset = 0;
+ reconv->dwTargetStrLen = len;
+ reconv->dwTargetStrOffset = 0;
+
+ size_t offset = selection_range_.GetMin() - selection_text_offset_;
+ memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
+ selection_text_.c_str() + offset, len * sizeof(WCHAR));
+
+ // According to Microsft API document, IMR_RECONVERTSTRING and
+ // IMR_DOCUMENTFEED should return reconv, but some applications return
+ // need_size.
+ return reinterpret_cast<LRESULT>(reconv);
+}
+
+LRESULT RenderWidgetHostViewWin::OnQueryCharPosition(
+ IMECHARPOSITION* position) {
+ DCHECK(position);
+
+ if (position->dwSize < sizeof(IMECHARPOSITION))
+ return 0;
+
+ RECT target_rect = {};
+ if (imm32_manager_->is_composing() && !composition_range_.is_empty() &&
+ position->dwCharPos < composition_character_bounds_.size()) {
+ target_rect =
+ composition_character_bounds_[position->dwCharPos].ToRECT();
+ } else if (position->dwCharPos == 0) {
+ // When there is no on-going composition but |position->dwCharPos| is 0,
+ // use the caret rect. This behavior is the same to RichEdit. In fact,
+ // CUAS (Cicero Unaware Application Support) relies on this behavior to
+ // implement ITfContextView::GetTextExt on top of IMM32-based applications.
+ target_rect = caret_rect_.ToRECT();
+ } else {
+ return 0;
+ }
+ ClientToScreen(&target_rect);
+
+ RECT document_rect = GetPixelBounds().ToRECT();
+ ClientToScreen(&document_rect);
+
+ position->pt.x = target_rect.left;
+ position->pt.y = target_rect.top;
+ position->cLineHeight = target_rect.bottom - target_rect.top;
+ position->rcDocument = document_rect;
+ return 1;
+}
+
+void RenderWidgetHostViewWin::UpdateIMEState() {
+ if (base::win::IsTSFAwareRequired()) {
+ ui::TSFBridge::GetInstance()->OnTextInputTypeChanged(this);
+ return;
+ }
+ if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
+ text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD) {
+ imm32_manager_->EnableIME(m_hWnd);
+ imm32_manager_->SetUseCompositionWindow(!can_compose_inline_);
+ } else {
+ imm32_manager_->DisableIME(m_hWnd);
+ }
+
+ imm32_manager_->SetTextInputMode(m_hWnd, text_input_mode_);
+}
+
+void RenderWidgetHostViewWin::UpdateInputScopeIfNecessary(
+ ui::TextInputType text_input_type) {
+ // The text store is responsible for handling input scope when TSF-aware is
+ // required.
+ if (base::win::IsTSFAwareRequired())
+ return;
+
+ ui::tsf_inputscope::SetInputScopeForTsfUnawareWindow(
+ m_hWnd, text_input_type, ui::TEXT_INPUT_MODE_DEFAULT);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostView, public:
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewWin(widget);
+}
+
+// static
+void RenderWidgetHostViewPort::GetDefaultScreenInfo(
+ WebKit::WebScreenInfo* results) {
+ GetScreenInfoForWindow(0, results);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_win.h b/chromium/content/browser/renderer_host/render_widget_host_view_win.h
new file mode 100644
index 00000000000..52030d78f39
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_win.h
@@ -0,0 +1,614 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_WIN_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_WIN_H_
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlmisc.h>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/win/scoped_comptr.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+#include "ui/base/gestures/gesture_types.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/base/ime/win/tsf_bridge.h"
+#include "ui/base/win/extra_sdk_defines.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/surface/accelerated_surface_win.h"
+#include "webkit/common/cursors/webcursor.h"
+
+class SkRegion;
+
+namespace gfx {
+class Size;
+class Rect;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace ui {
+class IMM32Manager;
+class ViewProp;
+}
+
+namespace WebKit {
+struct WebScreenInfo;
+}
+
+namespace content {
+class BackingStore;
+class RenderWidgetHost;
+class WebTouchState;
+
+typedef CWinTraits<WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0>
+ RenderWidgetHostHWNDTraits;
+
+CONTENT_EXPORT extern const wchar_t kRenderWidgetHostHWNDClass[];
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewWin
+//
+// An object representing the "View" of a rendered web page. This object is
+// responsible for displaying the content of the web page, receiving windows
+// messages, and containing plugins HWNDs. It is the implementation of the
+// RenderWidgetHostView that the cross-platform RenderWidgetHost object uses
+// to display the data.
+//
+// Comment excerpted from render_widget_host.h:
+//
+// "The lifetime of the RenderWidgetHostHWND is tied to the render process.
+// If the render process dies, the RenderWidgetHostHWND goes away and all
+// references to it must become NULL."
+//
+// RenderWidgetHostView class hierarchy described in render_widget_host_view.h.
+class RenderWidgetHostViewWin
+ : public CWindowImpl<RenderWidgetHostViewWin,
+ CWindow,
+ RenderWidgetHostHWNDTraits>,
+ public RenderWidgetHostViewBase,
+ public NotificationObserver,
+ public BrowserAccessibilityDelegate,
+ public ui::GestureConsumer,
+ public ui::GestureEventHelper,
+ public ui::TextInputClient { // for Win8/metro TSF support.
+ public:
+ virtual ~RenderWidgetHostViewWin();
+
+ CONTENT_EXPORT void CreateWnd(HWND parent);
+
+ void AcceleratedPaint(HDC dc);
+
+ DECLARE_WND_CLASS_EX(kRenderWidgetHostHWNDClass, CS_DBLCLKS, 0);
+
+ BEGIN_MSG_MAP(RenderWidgetHostHWND)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_ACTIVATE(OnActivate)
+ MSG_WM_DESTROY(OnDestroy)
+ MSG_WM_PAINT(OnPaint)
+ MSG_WM_NCPAINT(OnNCPaint)
+ MSG_WM_NCHITTEST(OnNCHitTest)
+ MSG_WM_ERASEBKGND(OnEraseBkgnd)
+ MSG_WM_SETCURSOR(OnSetCursor)
+ MSG_WM_SETFOCUS(OnSetFocus)
+ MSG_WM_KILLFOCUS(OnKillFocus)
+ MSG_WM_CAPTURECHANGED(OnCaptureChanged)
+ MSG_WM_CANCELMODE(OnCancelMode)
+ MSG_WM_INPUTLANGCHANGE(OnInputLangChange)
+ MSG_WM_THEMECHANGED(OnThemeChanged)
+ MSG_WM_NOTIFY(OnNotify)
+ MESSAGE_HANDLER(WM_IME_SETCONTEXT, OnImeSetContext)
+ MESSAGE_HANDLER(WM_IME_STARTCOMPOSITION, OnImeStartComposition)
+ MESSAGE_HANDLER(WM_IME_COMPOSITION, OnImeComposition)
+ MESSAGE_HANDLER(WM_IME_ENDCOMPOSITION, OnImeEndComposition)
+ MESSAGE_HANDLER(WM_IME_REQUEST, OnImeRequest)
+ MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseEvent)
+ MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseEvent)
+ MESSAGE_HANDLER(WM_LBUTTONDOWN, OnMouseEvent)
+ MESSAGE_HANDLER(WM_MBUTTONDOWN, OnMouseEvent)
+ MESSAGE_HANDLER(WM_RBUTTONDOWN, OnMouseEvent)
+ MESSAGE_HANDLER(WM_LBUTTONUP, OnMouseEvent)
+ MESSAGE_HANDLER(WM_MBUTTONUP, OnMouseEvent)
+ MESSAGE_HANDLER(WM_RBUTTONUP, OnMouseEvent)
+ MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnMouseEvent)
+ MESSAGE_HANDLER(WM_MBUTTONDBLCLK, OnMouseEvent)
+ MESSAGE_HANDLER(WM_RBUTTONDBLCLK, OnMouseEvent)
+ MESSAGE_HANDLER(WM_SYSKEYDOWN, OnKeyEvent)
+ MESSAGE_HANDLER(WM_SYSKEYUP, OnKeyEvent)
+ MESSAGE_HANDLER(WM_KEYDOWN, OnKeyEvent)
+ MESSAGE_HANDLER(WM_KEYUP, OnKeyEvent)
+ MESSAGE_HANDLER(WM_MOUSEWHEEL, OnWheelEvent)
+ MESSAGE_HANDLER(WM_MOUSEHWHEEL, OnWheelEvent)
+ MESSAGE_HANDLER(WM_HSCROLL, OnWheelEvent)
+ MESSAGE_HANDLER(WM_VSCROLL, OnWheelEvent)
+ MESSAGE_HANDLER(WM_CHAR, OnKeyEvent)
+ MESSAGE_HANDLER(WM_SYSCHAR, OnKeyEvent)
+ MESSAGE_HANDLER(WM_TOUCH, OnTouchEvent)
+ MESSAGE_HANDLER(WM_IME_CHAR, OnKeyEvent)
+ MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
+ MESSAGE_HANDLER(WM_GETOBJECT, OnGetObject)
+ MESSAGE_HANDLER(WM_PARENTNOTIFY, OnParentNotify)
+ MESSAGE_HANDLER(WM_GESTURE, OnGestureEvent)
+ MESSAGE_HANDLER(WM_MOVE, OnMoveOrSize)
+ MESSAGE_HANDLER(WM_SIZE, OnMoveOrSize)
+ MESSAGE_HANDLER(WM_WTSSESSION_CHANGE, OnSessionChange)
+ END_MSG_MAP()
+
+ // RenderWidgetHostView implementation.
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+
+ // Implementation of RenderWidgetHostViewPort.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE;
+ virtual void ScrollOffsetChanged() OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& copy_rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE;
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual bool CanSubscribeFrame() const OVERRIDE;
+
+ // called by WebContentsImpl before DestroyWindow
+ virtual void WillWmDestroy() OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& touch,
+ InputEventAckState ack_result) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE;
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>& params
+ ) OVERRIDE;
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+ virtual void SetClickthroughRegion(SkRegion* region) OVERRIDE;
+
+ // Implementation of NotificationObserver:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Implementation of BrowserAccessibilityDelegate:
+ virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE;
+ virtual void AccessibilityScrollToMakeVisible(
+ int acc_obj_id, gfx::Rect subfocus) OVERRIDE;
+ virtual void AccessibilityScrollToPoint(
+ int acc_obj_id, gfx::Point point) OVERRIDE;
+ virtual void AccessibilitySetTextSelection(
+ int acc_obj_id, int start_offset, int end_offset) OVERRIDE;
+ virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE;
+ virtual void FatalAccessibilityTreeError() OVERRIDE;
+
+ // Overridden from ui::GestureEventHelper.
+ virtual bool DispatchLongPressGestureEvent(ui::GestureEvent* event) OVERRIDE;
+ virtual bool DispatchCancelTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ // Overridden from ui::TextInputClient for Win8/metro TSF support.
+ // Following methods are not used in existing IMM32 related implementation.
+ virtual void SetCompositionText(
+ const ui::CompositionText& composition) OVERRIDE;
+ virtual void ConfirmCompositionText() OVERRIDE;
+ virtual void ClearCompositionText() OVERRIDE;
+ virtual void InsertText(const string16& text) OVERRIDE;
+ virtual void InsertChar(char16 ch, int flags) OVERRIDE;
+ virtual gfx::NativeWindow GetAttachedWindow() const OVERRIDE;
+ virtual ui::TextInputType GetTextInputType() const OVERRIDE;
+ virtual ui::TextInputMode GetTextInputMode() const OVERRIDE;
+ virtual bool CanComposeInline() const OVERRIDE;
+ virtual gfx::Rect GetCaretBounds() OVERRIDE;
+ virtual bool GetCompositionCharacterBounds(uint32 index,
+ gfx::Rect* rect) OVERRIDE;
+ virtual bool HasCompositionText() OVERRIDE;
+ virtual bool GetTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetCompositionTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetSelectionRange(ui::Range* range) OVERRIDE;
+ virtual bool SetSelectionRange(const ui::Range& range) OVERRIDE;
+ virtual bool DeleteRange(const ui::Range& range) OVERRIDE;
+ virtual bool GetTextFromRange(const ui::Range& range,
+ string16* text) OVERRIDE;
+ virtual void OnInputMethodChanged() OVERRIDE;
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE;
+ virtual void ExtendSelectionAndDelete(size_t before, size_t after) OVERRIDE;
+ virtual void EnsureCaretInRect(const gfx::Rect& rect) OVERRIDE;
+
+ protected:
+ friend class RenderWidgetHostView;
+
+ // Should construct only via RenderWidgetHostView::CreateViewForWidget.
+ //
+ // The view will associate itself with the given widget.
+ explicit RenderWidgetHostViewWin(RenderWidgetHost* widget);
+
+ // Windows Message Handlers
+ LRESULT OnCreate(CREATESTRUCT* create_struct);
+ void OnActivate(UINT, BOOL, HWND);
+ void OnDestroy();
+ void OnPaint(HDC unused_dc);
+ void OnNCPaint(HRGN update_region);
+ LRESULT OnNCHitTest(const CPoint& pt);
+ LRESULT OnEraseBkgnd(HDC dc);
+ LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT mouse_message_id);
+ void OnSetFocus(HWND window);
+ void OnKillFocus(HWND window);
+ void OnCaptureChanged(HWND window);
+ void OnCancelMode();
+ void OnInputLangChange(DWORD character_set, HKL input_language_id);
+ void OnThemeChanged();
+ LRESULT OnNotify(int w_param, NMHDR* header);
+ LRESULT OnImeSetContext(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnImeStartComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnImeComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnImeEndComposition(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnImeRequest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnMouseEvent(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnKeyEvent(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnWheelEvent(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnTouchEvent(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnMouseActivate(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ BOOL& handled);
+ // Handle MSAA requests for accessibility information.
+ LRESULT OnGetObject(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled);
+ // Handle vertical scrolling.
+ LRESULT OnVScroll(int code, short position, HWND scrollbar_control);
+ // Handle horizontal scrolling.
+ LRESULT OnHScroll(int code, short position, HWND scrollbar_control);
+
+ LRESULT OnParentNotify(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled);
+
+ // Handle high-level touch events.
+ LRESULT OnGestureEvent(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled);
+ LRESULT OnMoveOrSize(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled);
+
+ // Handle transitioning in and out of screensaver mode.
+ LRESULT OnSessionChange(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ BOOL& handled);
+
+ void OnFinalMessage(HWND window);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewWinBrowserTest,
+ TextInputTypeChanged);
+
+ // Updates the display cursor to the current cursor if the cursor is over this
+ // render view.
+ void UpdateCursorIfOverSelf();
+
+ // Tells Windows that we want to hear about mouse exit messages.
+ void TrackMouseLeave(bool start_tracking);
+
+ // Sends a message to the RenderView in the renderer process.
+ bool Send(IPC::Message* message);
+
+ // Set the tooltip region to the size of the window, creating the tooltip
+ // hwnd if it has not been created yet.
+ void EnsureTooltip();
+
+ // Tooltips become invalid when the root ancestor changes. When the View
+ // becomes hidden, this method is called to reset the tooltip.
+ void ResetTooltip();
+
+ // Builds and forwards a WebKitGestureEvent to the renderer.
+ bool ForwardGestureEventToRenderer(
+ ui::GestureEvent* gesture);
+
+ // Process all of the given gestures (passes them on to renderer)
+ void ProcessGestures(ui::GestureRecognizer::Gestures* gestures);
+
+ // Sends the specified mouse event to the renderer.
+ void ForwardMouseEventToRenderer(UINT message, WPARAM wparam, LPARAM lparam);
+
+ // Synthesize mouse wheel event.
+ LRESULT SynthesizeMouseWheel(bool is_vertical, int scroll_code,
+ short scroll_position);
+
+ // Shuts down the render_widget_host_. This is a separate function so we can
+ // invoke it from the message loop.
+ void ShutdownHost();
+
+ // Redraws the window synchronously, and any child windows (i.e. plugins)
+ // asynchronously.
+ void Redraw();
+
+ // Draw our background over the given HDC in the given |rect|. The background
+ // will be tiled such that it lines up with existing tiles starting from the
+ // origin of |dc|.
+ void DrawBackground(const RECT& rect, CPaintDC* dc);
+
+ // Clean up the compositor window, if needed.
+ void CleanupCompositorWindow();
+
+ // Whether the window should be activated.
+ bool IsActivatable() const;
+
+ // Do initialization needed by both InitAsPopup() and InitAsFullscreen().
+ void DoPopupOrFullscreenInit(HWND parent_hwnd,
+ const gfx::Rect& pos,
+ DWORD ex_style);
+
+ CPoint GetClientCenter() const;
+ // In mouse lock mode, moves the mouse cursor to the center of the view if it
+ // is too close to the border.
+ void MoveCursorToCenterIfNecessary();
+
+ void HandleLockedMouseEvent(UINT message, WPARAM wparam, LPARAM lparam);
+
+ LRESULT OnDocumentFeed(RECONVERTSTRING* reconv);
+ LRESULT OnReconvertString(RECONVERTSTRING* reconv);
+ LRESULT OnQueryCharPosition(IMECHARPOSITION* position);
+
+ // Sets the appropriate mode for raw-touches or gestures. Currently touch mode
+ // will only take effect on Win7+.
+ void UpdateDesiredTouchMode();
+
+ // Configures the enable/disable state of |ime_input_| to match with the
+ // current |text_input_type_|.
+ void UpdateIMEState();
+
+ // Returns bounds of the view in pixels.
+ gfx::Rect GetPixelBounds() const;
+
+ // Sets the appropriate input scope for given |text_input_type| if TSF-aware
+ // is not required. Does nothing if TSF-aware is required (and TSF text store
+ // is responsible for managing input scope). Currently input scope will only
+ // take effect on Vista+.
+ void UpdateInputScopeIfNecessary(ui::TextInputType text_input_type);
+
+ // Create a BrowserAccessibilityManager with an empty document if it
+ // doesn't already exist.
+ void CreateBrowserAccessibilityManagerIfNeeded();
+
+ // The associated Model. While |this| is being Destroyed,
+ // |render_widget_host_| is NULL and the Windows message loop is run one last
+ // time. Message handlers must check for a NULL |render_widget_host_|.
+ RenderWidgetHostImpl* render_widget_host_;
+
+ // When we are doing accelerated compositing
+ HWND compositor_host_window_;
+
+ // Presents a texture received from another process to the compositing
+ // window.
+ scoped_ptr<AcceleratedSurface> accelerated_surface_;
+
+ // true if the compositor host window must be hidden after the
+ // software renderered view is updated.
+ bool hide_compositor_window_at_next_paint_;
+
+ // The cursor for the page. This is passed up from the renderer.
+ WebCursor current_cursor_;
+
+ // Indicates if the page is loading.
+ bool is_loading_;
+
+ // true if we are currently tracking WM_MOUSEEXIT messages.
+ bool track_mouse_leave_;
+
+ // Wrapper class for IMM32 APIs.
+ // (See "ui/base/ime/win/imm32_manager.h" for its details.)
+ scoped_ptr<ui::IMM32Manager> imm32_manager_;
+
+ // Represents whether or not this browser process is receiving status
+ // messages about the focused edit control from a renderer process.
+ bool ime_notification_;
+
+ // true if Enter was hit when render widget host was in focus.
+ bool capture_enter_key_;
+
+ // true if the View is not visible.
+ bool is_hidden_;
+
+ // The touch-state. Its touch-points are updated as necessary. A new
+ // touch-point is added from an TOUCHEVENTF_DOWN message, and a touch-point
+ // is removed from the list on an TOUCHEVENTF_UP message.
+ scoped_ptr<WebTouchState> touch_state_;
+
+ // True if we're in the midst of a paint operation and should respond to
+ // DidPaintRect() notifications by merely invalidating. See comments on
+ // render_widget_host_view.h:DidPaintRect().
+ bool about_to_validate_and_paint_;
+
+ // true if the View should be closed when its HWND is deactivated (used to
+ // support SELECT popups which are closed when they are deactivated).
+ bool close_on_deactivate_;
+
+ // Whether Destroy() has been called. Used to detect a crasher
+ // (http://crbug.com/24248) where render_view_host_ has been deleted when
+ // OnFinalMessage is called.
+ bool being_destroyed_;
+
+ // Tooltips
+ // The text to be shown in the tooltip, supplied by the renderer.
+ string16 tooltip_text_;
+ // The tooltip control hwnd
+ HWND tooltip_hwnd_;
+ // Whether or not a tooltip is currently visible. We use this to track
+ // whether or not we want to force-close the tooltip when we receive mouse
+ // move notifications from the renderer. See comment in OnMsgSetTooltipText.
+ bool tooltip_showing_;
+
+ // Factory used to safely scope delayed calls to ShutdownHost().
+ base::WeakPtrFactory<RenderWidgetHostViewWin> weak_factory_;
+
+ // The time at which this view started displaying white pixels as a result of
+ // not having anything to paint (empty backing store from renderer). This
+ // value returns true for is_null() if we are not recording whiteout times.
+ base::TimeTicks whiteout_start_time_;
+
+ // The time it took after this view was selected for it to be fully painted.
+ base::TimeTicks web_contents_switch_paint_time_;
+
+ // Registrar so we can listen to RENDERER_PROCESS_TERMINATED events.
+ NotificationRegistrar registrar_;
+
+ // Stores the current text input type received by TextInputStateChanged()
+ // method.
+ ui::TextInputType text_input_type_;
+ ui::TextInputMode text_input_mode_;
+ bool can_compose_inline_;
+
+ ScopedVector<ui::ViewProp> props_;
+
+ // Is the widget fullscreen?
+ bool is_fullscreen_;
+
+ // Used to record the last position of the mouse.
+ struct {
+ // While the mouse is locked, |unlocked| and |unlocked_global| store the
+ // last known position just as mouse lock was entered.
+ // Relative to the upper-left corner of the view.
+ gfx::Point unlocked;
+ // Relative to the upper-left corner of the screen.
+ gfx::Point unlocked_global;
+
+ // Only valid while the mouse is locked.
+ gfx::Point locked_global;
+ } last_mouse_position_;
+
+ // When the mouse cursor is moved to the center of the view by
+ // MoveCursorToCenterIfNecessary(), we ignore the resulting WM_MOUSEMOVE
+ // message.
+ struct {
+ bool pending;
+ // Relative to the upper-left corner of the screen.
+ gfx::Point target;
+ } move_to_center_request_;
+
+ // In the case of the mouse being moved away from the view and then moved
+ // back, we regard the mouse movement as (0, 0).
+ bool ignore_mouse_movement_;
+
+ ui::Range composition_range_;
+
+ // The current composition character bounds.
+ std::vector<gfx::Rect> composition_character_bounds_;
+
+ // A cached latest caret rectangle sent from renderer.
+ gfx::Rect caret_rect_;
+
+ // TODO(ananta)
+ // The WM_POINTERDOWN and touch related members should be moved to an
+ // independent class to reduce the clutter. This includes members
+ // pointer_down_context_ and last_touch_location_;
+
+ // Set to true if we are in the context of a WM_POINTERDOWN message
+ bool pointer_down_context_;
+
+ // The global x, y coordinates of the last point a touch event was
+ // received, used to determine if it's okay to open the on-screen
+ // keyboard. Reset when the window loses focus.
+ gfx::Point last_touch_location_;
+
+ // Region in which the view will be transparent to clicks.
+ scoped_ptr<SkRegion> transparent_region_;
+
+ // Are touch events currently enabled?
+ bool touch_events_enabled_;
+
+ scoped_ptr<ui::GestureRecognizer> gesture_recognizer_;
+
+ // The OS-provided default IAccessible instance for our hwnd.
+ base::win::ScopedComPtr<IAccessible> window_iaccessible_;
+
+ ui::LatencyInfo software_latency_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_WIN_H_
diff --git a/chromium/content/browser/renderer_host/render_widget_host_view_win_browsertest.cc b/chromium/content/browser/renderer_host/render_widget_host_view_win_browsertest.cc
new file mode 100644
index 00000000000..76070bc70e5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/render_widget_host_view_win_browsertest.cc
@@ -0,0 +1,226 @@
+// 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 <vector>
+
+#include "base/command_line.h"
+#include "base/win/metro.h"
+#include "content/browser/renderer_host/render_widget_host_view_win.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/content_browser_test.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_type.h"
+#include "ui/base/ime/win/imm32_manager.h"
+#include "ui/base/ime/win/mock_tsf_bridge.h"
+#include "ui/base/ime/win/tsf_bridge.h"
+
+namespace content {
+namespace {
+
+class MockIMM32Manager : public ui::IMM32Manager {
+ public:
+ MockIMM32Manager()
+ : window_handle_(NULL),
+ input_mode_(ui::TEXT_INPUT_MODE_DEFAULT),
+ call_count_(0) {
+ }
+ virtual ~MockIMM32Manager() {}
+
+ virtual void SetTextInputMode(HWND window_handle,
+ ui::TextInputMode input_mode) OVERRIDE {
+ ++call_count_;
+ window_handle_ = window_handle;
+ input_mode_ = input_mode;
+ }
+
+ void Reset() {
+ window_handle_ = NULL;
+ input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
+ call_count_ = 0;
+ }
+
+ HWND window_handle() const { return window_handle_; }
+ ui::TextInputMode input_mode() const { return input_mode_; }
+ size_t call_count() const { return call_count_; }
+
+ private:
+ HWND window_handle_;
+ ui::TextInputMode input_mode_;
+ size_t call_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockIMM32Manager);
+};
+
+// Testing class serving initialized RenderWidgetHostViewWin instance;
+class RenderWidgetHostViewWinBrowserTest : public ContentBrowserTest {
+ public:
+ RenderWidgetHostViewWinBrowserTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ContentBrowserTest::SetUpOnMainThread();
+
+ NavigateToURL(shell(), GURL("about:blank"));
+
+ view_ = static_cast<RenderWidgetHostViewWin*>(
+ RenderWidgetHostViewPort::FromRWHV(
+ shell()->web_contents()->GetRenderViewHost()->GetView()));
+ CHECK(view_);
+ }
+
+ protected:
+ RenderWidgetHostViewWin* view_;
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewWinBrowserTest,
+ TextInputTypeChanged) {
+ ASSERT_TRUE(view_->m_hWnd);
+
+ MockIMM32Manager* mock = new MockIMM32Manager();
+ mock->Reset();
+ view_->imm32_manager_.reset(mock);
+ view_->TextInputTypeChanged(ui::TEXT_INPUT_TYPE_NONE, false,
+ ui::TEXT_INPUT_MODE_EMAIL);
+
+ EXPECT_EQ(1, mock->call_count());
+ EXPECT_EQ(view_->m_hWnd, mock->window_handle());
+ EXPECT_EQ(ui::TEXT_INPUT_MODE_EMAIL, mock->input_mode());
+
+ mock->Reset();
+ view_->TextInputTypeChanged(ui::TEXT_INPUT_TYPE_NONE, false,
+ ui::TEXT_INPUT_MODE_EMAIL);
+ EXPECT_EQ(0, mock->call_count());
+}
+
+class RenderWidgetHostViewWinTSFTest : public ContentBrowserTest {
+ public:
+ RenderWidgetHostViewWinTSFTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) {
+ command_line->AppendSwitch(switches::kEnableTextServicesFramework);
+ }
+};
+
+// crbug.com/151798
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewWinTSFTest,
+ DISABLED_SwichToPasswordField) {
+ ui::MockTSFBridge mock_bridge;
+ ui::TSFBridge* old_bridge = ui::TSFBridge::ReplaceForTesting(&mock_bridge);
+ GURL test_url = GetTestUrl("textinput", "ime_enable_disable_test.html");
+
+ NavigateToURL(shell(), test_url);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, mock_bridge.latest_text_iput_type());
+
+ // Focus to the text field, the IME should be enabled.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(text01_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, mock_bridge.latest_text_iput_type());
+
+ // Focus to the password field, the IME should be disabled.
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(password02_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, mock_bridge.latest_text_iput_type());
+
+ ui::TSFBridge::ReplaceForTesting(old_bridge);
+}
+
+// crbug.com/151798
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewWinTSFTest,
+ DISABLED_SwitchToSameField) {
+ ui::MockTSFBridge mock_bridge;
+ ui::TSFBridge* old_bridge = ui::TSFBridge::ReplaceForTesting(&mock_bridge);
+ GURL test_url = GetTestUrl("textinput", "ime_enable_disable_test.html");
+
+ NavigateToURL(shell(), test_url);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, mock_bridge.latest_text_iput_type());
+
+ // Focus to the text field, the IME should be enabled.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(text01_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, mock_bridge.latest_text_iput_type());
+
+ // Focus to another text field, the IME should be enabled.
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(text02_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, mock_bridge.latest_text_iput_type());
+
+ ui::TSFBridge::ReplaceForTesting(old_bridge);
+}
+
+// crbug.com/151798
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewWinTSFTest,
+ DISABLED_SwitchToSamePasswordField) {
+ ui::MockTSFBridge mock_bridge;
+ ui::TSFBridge* old_bridge = ui::TSFBridge::ReplaceForTesting(&mock_bridge);
+ GURL test_url = GetTestUrl("textinput", "ime_enable_disable_test.html");
+
+ NavigateToURL(shell(), test_url);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, mock_bridge.latest_text_iput_type());
+
+ // Focus to the password field, the IME should be disabled.
+ bool success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(password01_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, mock_bridge.latest_text_iput_type());
+
+ // Focus to the another password field, the IME should be disabled.
+ success = false;
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(
+ shell()->web_contents(),
+ "window.domAutomationController.send(password02_focus());",
+ &success));
+ EXPECT_TRUE(success);
+ WaitForLoadStop(shell()->web_contents());
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, mock_bridge.latest_text_iput_type());
+
+ ui::TSFBridge::ReplaceForTesting(old_bridge);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/smooth_scroll_calculator.cc b/chromium/content/browser/renderer_host/smooth_scroll_calculator.cc
new file mode 100644
index 00000000000..2538a188354
--- /dev/null
+++ b/chromium/content/browser/renderer_host/smooth_scroll_calculator.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 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 "content/browser/renderer_host/smooth_scroll_calculator.h"
+
+namespace content {
+
+SmoothScrollCalculator::SmoothScrollCalculator() {
+}
+
+SmoothScrollCalculator::~SmoothScrollCalculator() {
+}
+
+double SmoothScrollCalculator::GetScrollDelta(
+ base::TimeTicks now, base::TimeDelta desired_interval) {
+ double position_delta = 10;
+ if (!last_tick_time_.is_null()) {
+ double velocity = 10 / desired_interval.InMillisecondsF();
+ double time_delta = (now - last_tick_time_).InMillisecondsF();
+ position_delta = velocity * time_delta;
+ }
+
+ last_tick_time_ = now;
+ return position_delta;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/smooth_scroll_calculator.h b/chromium/content/browser/renderer_host/smooth_scroll_calculator.h
new file mode 100644
index 00000000000..1427416be41
--- /dev/null
+++ b/chromium/content/browser/renderer_host/smooth_scroll_calculator.h
@@ -0,0 +1,29 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_CALCULATOR_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_CALCULATOR_H_
+
+#include "base/time/time.h"
+
+namespace content {
+
+// An utility class to calculate the delta for smooth scroll gesture
+// events.
+class SmoothScrollCalculator {
+ public:
+ SmoothScrollCalculator();
+ ~SmoothScrollCalculator();
+
+ double GetScrollDelta(base::TimeTicks now, base::TimeDelta desired_interval);
+
+ private:
+ base::TimeTicks last_tick_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(SmoothScrollCalculator);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_CALCULATOR_H_
diff --git a/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.cc b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.cc
new file mode 100644
index 00000000000..ca9dfae9b78
--- /dev/null
+++ b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 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 "content/browser/renderer_host/smooth_scroll_gesture_controller.h"
+
+#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+#include "content/public/browser/render_widget_host.h"
+
+namespace content {
+
+namespace {
+
+// How many milliseconds apart synthetic scroll messages should be sent.
+const int kSyntheticScrollMessageIntervalMs = 7;
+
+} // namespace
+
+SmoothScrollGestureController::SmoothScrollGestureController()
+ : rwh_(NULL) {
+}
+
+SmoothScrollGestureController::~SmoothScrollGestureController() {
+}
+
+void SmoothScrollGestureController::BeginSmoothScroll(
+ RenderWidgetHostViewPort* view,
+ const ViewHostMsg_BeginSmoothScroll_Params& params) {
+ if (pending_smooth_scroll_gesture_.get())
+ return;
+
+ rwh_ = view->GetRenderWidgetHost();
+ pending_smooth_scroll_gesture_ = view->CreateSmoothScrollGesture(
+ params.scroll_down,
+ params.pixels_to_scroll,
+ params.mouse_event_x,
+ params.mouse_event_y);
+
+ timer_.Start(FROM_HERE, GetSyntheticScrollMessageInterval(), this,
+ &SmoothScrollGestureController::OnTimer);
+}
+
+base::TimeDelta
+ SmoothScrollGestureController::GetSyntheticScrollMessageInterval() const {
+ return base::TimeDelta::FromMilliseconds(kSyntheticScrollMessageIntervalMs);
+}
+
+void SmoothScrollGestureController::OnTimer() {
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (!pending_smooth_scroll_gesture_->ForwardInputEvents(now, rwh_)) {
+ timer_.Stop();
+ pending_smooth_scroll_gesture_ = NULL;
+ rwh_->Send(new ViewMsg_SmoothScrollCompleted(rwh_->GetRoutingID()));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.h b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.h
new file mode 100644
index 00000000000..1477a171e6a
--- /dev/null
+++ b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller.h
@@ -0,0 +1,52 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_GESTURE_CONTROLLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_GESTURE_CONTROLLER_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/common/content_export.h"
+
+struct ViewHostMsg_BeginSmoothScroll_Params;
+
+namespace content {
+
+class RenderWidgetHost;
+class RenderWidgetHostViewPort;
+class SmoothScrollGesture;
+
+// Controls SmoothScrollGestures, used to inject synthetic events
+// for performance test harness.
+class CONTENT_EXPORT SmoothScrollGestureController {
+ public:
+ SmoothScrollGestureController();
+ ~SmoothScrollGestureController();
+
+ // Initiates a synthetic event stream.
+ void BeginSmoothScroll(RenderWidgetHostViewPort* view,
+ const ViewHostMsg_BeginSmoothScroll_Params& params);
+
+ base::TimeDelta GetSyntheticScrollMessageInterval() const;
+
+ private:
+ // Called periodically to advance the active scroll gesture after being
+ // initiated by OnBeginSmoothScroll.
+ void OnTimer();
+
+ base::RepeatingTimer<SmoothScrollGestureController> timer_;
+
+ RenderWidgetHost* rwh_;
+
+ scoped_refptr<SmoothScrollGesture> pending_smooth_scroll_gesture_;
+
+ DISALLOW_COPY_AND_ASSIGN(SmoothScrollGestureController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_SMOOTH_SCROLL_GESTURE_CONTROLLER_H_
diff --git a/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller_unittest.cc b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller_unittest.cc
new file mode 100644
index 00000000000..95d26473ee1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/smooth_scroll_gesture_controller_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/smooth_scroll_gesture_controller.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/env.h"
+#include "ui/aura/test/test_screen.h"
+#endif
+
+using base::TimeDelta;
+
+namespace content {
+
+namespace {
+
+class MockSmoothScrollGesture : public SmoothScrollGesture {
+ public:
+ MockSmoothScrollGesture() :
+ called_(0) {
+ }
+
+ // SmoothScrollGesture implementation:
+ virtual bool ForwardInputEvents(base::TimeTicks now,
+ RenderWidgetHost* host) OVERRIDE {
+ ++called_;
+ return true;
+ }
+
+ int called_;
+
+ protected:
+ virtual ~MockSmoothScrollGesture() {
+ }
+};
+
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {
+ }
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+class MockRenderWidgetHost : public RenderWidgetHostImpl {
+ public:
+ MockRenderWidgetHost(
+ RenderWidgetHostDelegate* delegate,
+ RenderProcessHost* process,
+ int routing_id)
+ : RenderWidgetHostImpl(delegate, process, routing_id) {
+ }
+ virtual ~MockRenderWidgetHost() {}
+};
+
+class TestView : public TestRenderWidgetHostView {
+ public:
+ explicit TestView(RenderWidgetHostImpl* rwh)
+ : TestRenderWidgetHostView(rwh),
+ mock_gesture_(NULL) {
+ }
+ virtual ~TestView() {}
+
+ // TestRenderWidgetHostView implementation:
+ virtual SmoothScrollGesture* CreateSmoothScrollGesture(
+ bool scroll_down, int pixels_to_scroll, int mouse_event_x,
+ int mouse_event_y) OVERRIDE {
+ mock_gesture_ = new MockSmoothScrollGesture();
+ return mock_gesture_;
+ }
+
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE {
+ return rwh_;
+ }
+
+ MockSmoothScrollGesture* mock_gesture_;
+};
+
+class SmoothScrollGestureControllerTest : public testing::Test {
+ public:
+ SmoothScrollGestureControllerTest() : process_(NULL) {
+ }
+ virtual ~SmoothScrollGestureControllerTest() {}
+
+ protected:
+ // testing::Test implementation:
+ virtual void SetUp() OVERRIDE {
+ browser_context_.reset(new TestBrowserContext());
+ delegate_.reset(new MockRenderWidgetHostDelegate());
+ process_ = new MockRenderProcessHost(browser_context_.get());
+#if defined(USE_AURA)
+ screen_.reset(aura::TestScreen::Create());
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get());
+#endif
+ host_.reset(
+ new MockRenderWidgetHost(delegate_.get(), process_, MSG_ROUTING_NONE));
+ view_.reset(new TestView(host_.get()));
+ host_->SetView(view_.get());
+ host_->Init();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ view_.reset();
+ host_.reset();
+ delegate_.reset();
+ process_ = NULL;
+ browser_context_.reset();
+
+#if defined(USE_AURA)
+ aura::Env::DeleteInstance();
+ screen_.reset();
+#endif
+
+ // Process all pending tasks to avoid leaks.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void PostQuitMessageAndRun() {
+ // Allow the message loop to process pending synthetic scrolls, then quit.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::MessageLoop::QuitClosure(),
+ TimeDelta::FromMilliseconds(
+ controller_.GetSyntheticScrollMessageInterval().InMilliseconds() *
+ 3));
+ base::MessageLoop::current()->Run();
+ }
+
+ base::MessageLoopForUI message_loop_;
+
+ scoped_ptr<TestBrowserContext> browser_context_;
+ MockRenderProcessHost* process_; // Deleted automatically by the widget.
+ scoped_ptr<MockRenderWidgetHostDelegate> delegate_;
+ scoped_ptr<MockRenderWidgetHost> host_;
+ scoped_ptr<TestView> view_;
+#if defined(USE_AURA)
+ scoped_ptr<gfx::Screen> screen_;
+#endif
+
+ SmoothScrollGestureController controller_;
+};
+
+TEST_F(SmoothScrollGestureControllerTest, Tick) {
+ ViewHostMsg_BeginSmoothScroll_Params params;
+ params.scroll_down = true;
+ params.pixels_to_scroll = 10;
+ params.mouse_event_x = 20;
+ params.mouse_event_y = 30;
+
+ // Begin a smooth scroll, |mock_gesture_| won't be |called| until we post
+ // message.
+ controller_.BeginSmoothScroll(view_.get(), params);
+ EXPECT_TRUE(view_->mock_gesture_ != NULL);
+ EXPECT_EQ(0, view_->mock_gesture_->called_);
+
+ PostQuitMessageAndRun();
+ const int current_ticks = view_->mock_gesture_->called_;
+ EXPECT_LT(0, current_ticks);
+
+ // Ensure it won't start another smooth scroll.
+ MockSmoothScrollGesture* original_gesture = view_->mock_gesture_;
+ controller_.BeginSmoothScroll(view_.get(), params);
+ PostQuitMessageAndRun();
+ EXPECT_EQ(original_gesture, view_->mock_gesture_);
+
+ // Ensure the smooth scroll is ticked.
+ PostQuitMessageAndRun();
+ EXPECT_LT(current_ticks, view_->mock_gesture_->called_);
+}
+
+} // namespace
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.cc b/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.cc
new file mode 100644
index 00000000000..7f122df2c73
--- /dev/null
+++ b/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.cc
@@ -0,0 +1,284 @@
+// 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 "content/browser/renderer_host/socket_stream_dispatcher_host.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "content/browser/renderer_host/socket_stream_host.h"
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/common/resource_messages.h"
+#include "content/common/socket_stream.h"
+#include "content/common/socket_stream_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/global_request_id.h"
+#include "net/base/net_errors.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/websockets/websocket_job.h"
+#include "net/websockets/websocket_throttle.h"
+
+namespace content {
+
+namespace {
+
+const size_t kMaxSocketStreamHosts = 16 * 1024;
+
+} // namespace
+
+SocketStreamDispatcherHost::SocketStreamDispatcherHost(
+ int render_process_id,
+ ResourceMessageFilter::URLRequestContextSelector* selector,
+ ResourceContext* resource_context)
+ : render_process_id_(render_process_id),
+ url_request_context_selector_(selector),
+ resource_context_(resource_context),
+ weak_ptr_factory_(this),
+ on_shutdown_(false) {
+ DCHECK(selector);
+ net::WebSocketJob::EnsureInit();
+}
+
+bool SocketStreamDispatcherHost::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ if (on_shutdown_)
+ return false;
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(SocketStreamDispatcherHost, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(SocketStreamHostMsg_Connect, OnConnect)
+ IPC_MESSAGE_HANDLER(SocketStreamHostMsg_SendData, OnSendData)
+ IPC_MESSAGE_HANDLER(SocketStreamHostMsg_Close, OnCloseReq)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+// SocketStream::Delegate methods implementations.
+void SocketStreamDispatcherHost::OnConnected(net::SocketStream* socket,
+ int max_pending_send_allowed) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnConnected socket_id=" << socket_id
+ << " max_pending_send_allowed=" << max_pending_send_allowed;
+ if (socket_id == kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnConnected";
+ return;
+ }
+ if (!Send(new SocketStreamMsg_Connected(
+ socket_id, max_pending_send_allowed))) {
+ DVLOG(1) << "SocketStreamMsg_Connected failed.";
+ DeleteSocketStreamHost(socket_id);
+ }
+}
+
+void SocketStreamDispatcherHost::OnSentData(net::SocketStream* socket,
+ int amount_sent) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnSentData socket_id=" << socket_id
+ << " amount_sent=" << amount_sent;
+ if (socket_id == kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnSentData";
+ return;
+ }
+ if (!Send(new SocketStreamMsg_SentData(socket_id, amount_sent))) {
+ DVLOG(1) << "SocketStreamMsg_SentData failed.";
+ DeleteSocketStreamHost(socket_id);
+ }
+}
+
+void SocketStreamDispatcherHost::OnReceivedData(
+ net::SocketStream* socket, const char* data, int len) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnReceiveData socket_id="
+ << socket_id;
+ if (socket_id == kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnReceivedData";
+ return;
+ }
+ if (!Send(new SocketStreamMsg_ReceivedData(
+ socket_id, std::vector<char>(data, data + len)))) {
+ DVLOG(1) << "SocketStreamMsg_ReceivedData failed.";
+ DeleteSocketStreamHost(socket_id);
+ }
+}
+
+void SocketStreamDispatcherHost::OnClose(net::SocketStream* socket) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnClosed socket_id=" << socket_id;
+ if (socket_id == kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnClose";
+ return;
+ }
+ DeleteSocketStreamHost(socket_id);
+}
+
+void SocketStreamDispatcherHost::OnError(const net::SocketStream* socket,
+ int error) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnError socket_id=" << socket_id;
+ if (socket_id == content::kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnError";
+ return;
+ }
+ // SocketStream::Delegate::OnError() events are handled as WebSocket error
+ // event when user agent was required to fail WebSocket connection or the
+ // WebSocket connection is closed with prejudice.
+ if (!Send(new SocketStreamMsg_Failed(socket_id, error))) {
+ DVLOG(1) << "SocketStreamMsg_Failed failed.";
+ DeleteSocketStreamHost(socket_id);
+ }
+}
+
+void SocketStreamDispatcherHost::OnSSLCertificateError(
+ net::SocketStream* socket, const net::SSLInfo& ssl_info, bool fatal) {
+ int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket);
+ DVLOG(2) << "SocketStreamDispatcherHost::OnSSLCertificateError socket_id="
+ << socket_id;
+ if (socket_id == kNoSocketId) {
+ DVLOG(1) << "NoSocketId in OnSSLCertificateError";
+ return;
+ }
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ DCHECK(socket_stream_host);
+ GlobalRequestID request_id(-1, socket_id);
+ SSLManager::OnSSLCertificateError(
+ weak_ptr_factory_.GetWeakPtr(), request_id, ResourceType::SUB_RESOURCE,
+ socket->url(), render_process_id_, socket_stream_host->render_view_id(),
+ ssl_info, fatal);
+}
+
+bool SocketStreamDispatcherHost::CanGetCookies(net::SocketStream* socket,
+ const GURL& url) {
+ return GetContentClient()->browser()->AllowGetCookie(
+ url, url, net::CookieList(), resource_context_, 0, MSG_ROUTING_NONE);
+}
+
+bool SocketStreamDispatcherHost::CanSetCookie(net::SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ net::CookieOptions* options) {
+ return GetContentClient()->browser()->AllowSetCookie(
+ url, url, cookie_line, resource_context_, 0, MSG_ROUTING_NONE, options);
+}
+
+void SocketStreamDispatcherHost::CancelSSLRequest(
+ const GlobalRequestID& id,
+ int error,
+ const net::SSLInfo* ssl_info) {
+ int socket_id = id.request_id;
+ DVLOG(2) << "SocketStreamDispatcherHost::CancelSSLRequest socket_id="
+ << socket_id;
+ DCHECK_NE(kNoSocketId, socket_id);
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ DCHECK(socket_stream_host);
+ if (ssl_info)
+ socket_stream_host->CancelWithSSLError(*ssl_info);
+ else
+ socket_stream_host->CancelWithError(error);
+}
+
+void SocketStreamDispatcherHost::ContinueSSLRequest(
+ const GlobalRequestID& id) {
+ int socket_id = id.request_id;
+ DVLOG(2) << "SocketStreamDispatcherHost::ContinueSSLRequest socket_id="
+ << socket_id;
+ DCHECK_NE(kNoSocketId, socket_id);
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ DCHECK(socket_stream_host);
+ socket_stream_host->ContinueDespiteError();
+}
+
+SocketStreamDispatcherHost::~SocketStreamDispatcherHost() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ Shutdown();
+}
+
+// Message handlers called by OnMessageReceived.
+void SocketStreamDispatcherHost::OnConnect(int render_view_id,
+ const GURL& url,
+ int socket_id) {
+ DVLOG(2) << "SocketStreamDispatcherHost::OnConnect"
+ << " render_view_id=" << render_view_id
+ << " url=" << url
+ << " socket_id=" << socket_id;
+ DCHECK_NE(kNoSocketId, socket_id);
+
+ if (hosts_.size() >= kMaxSocketStreamHosts) {
+ if (!Send(new SocketStreamMsg_Failed(socket_id,
+ net::ERR_TOO_MANY_SOCKET_STREAMS))) {
+ DVLOG(1) << "SocketStreamMsg_Failed failed.";
+ }
+ if (!Send(new SocketStreamMsg_Closed(socket_id))) {
+ DVLOG(1) << "SocketStreamMsg_Closed failed.";
+ }
+ return;
+ }
+
+ if (hosts_.Lookup(socket_id)) {
+ DVLOG(1) << "socket_id=" << socket_id << " already registered.";
+ return;
+ }
+
+ // Note that the SocketStreamHost is responsible for checking that |url|
+ // is valid.
+ SocketStreamHost* socket_stream_host =
+ new SocketStreamHost(this, render_view_id, socket_id);
+ hosts_.AddWithID(socket_stream_host, socket_id);
+ socket_stream_host->Connect(url, GetURLRequestContext());
+ DVLOG(2) << "SocketStreamDispatcherHost::OnConnect -> " << socket_id;
+}
+
+void SocketStreamDispatcherHost::OnSendData(
+ int socket_id, const std::vector<char>& data) {
+ DVLOG(2) << "SocketStreamDispatcherHost::OnSendData socket_id=" << socket_id;
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ if (!socket_stream_host) {
+ DVLOG(1) << "socket_id=" << socket_id << " already closed.";
+ return;
+ }
+ if (!socket_stream_host->SendData(data)) {
+ // Cannot accept more data to send.
+ socket_stream_host->Close();
+ }
+}
+
+void SocketStreamDispatcherHost::OnCloseReq(int socket_id) {
+ DVLOG(2) << "SocketStreamDispatcherHost::OnCloseReq socket_id=" << socket_id;
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ if (!socket_stream_host)
+ return;
+ socket_stream_host->Close();
+}
+
+void SocketStreamDispatcherHost::DeleteSocketStreamHost(int socket_id) {
+ SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id);
+ DCHECK(socket_stream_host);
+ delete socket_stream_host;
+ hosts_.Remove(socket_id);
+ if (!Send(new SocketStreamMsg_Closed(socket_id))) {
+ DVLOG(1) << "SocketStreamMsg_Closed failed.";
+ }
+}
+
+net::URLRequestContext* SocketStreamDispatcherHost::GetURLRequestContext() {
+ return url_request_context_selector_->GetRequestContext(
+ ResourceType::SUB_RESOURCE);
+}
+
+void SocketStreamDispatcherHost::Shutdown() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // TODO(ukai): Implement IDMap::RemoveAll().
+ for (IDMap<SocketStreamHost>::const_iterator iter(&hosts_);
+ !iter.IsAtEnd();
+ iter.Advance()) {
+ int socket_id = iter.GetCurrentKey();
+ const SocketStreamHost* socket_stream_host = iter.GetCurrentValue();
+ delete socket_stream_host;
+ hosts_.Remove(socket_id);
+ }
+ on_shutdown_ = true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.h b/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.h
new file mode 100644
index 00000000000..732923e690f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/socket_stream_dispatcher_host.h
@@ -0,0 +1,99 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_
+
+#include <vector>
+
+#include "base/id_map.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/ssl/ssl_error_handler.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "net/socket_stream/socket_stream.h"
+
+class GURL;
+
+namespace net {
+class SSLInfo;
+}
+
+namespace content {
+class ResourceContext;
+class SocketStreamHost;
+
+// Dispatches ViewHostMsg_SocketStream_* messages sent from renderer.
+// It also acts as SocketStream::Delegate so that it sends
+// ViewMsg_SocketStream_* messages back to renderer.
+class SocketStreamDispatcherHost
+ : public BrowserMessageFilter,
+ public net::SocketStream::Delegate,
+ public SSLErrorHandler::Delegate {
+ public:
+ SocketStreamDispatcherHost(
+ int render_process_id,
+ ResourceMessageFilter::URLRequestContextSelector* selector,
+ ResourceContext* resource_context);
+
+ // BrowserMessageFilter:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ // Make this object inactive.
+ // Remove all active SocketStreamHost objects.
+ void Shutdown();
+
+ // SocketStream::Delegate:
+ virtual void OnConnected(net::SocketStream* socket,
+ int max_pending_send_allowed) OVERRIDE;
+ virtual void OnSentData(net::SocketStream* socket, int amount_sent) OVERRIDE;
+ virtual void OnReceivedData(net::SocketStream* socket,
+ const char* data, int len) OVERRIDE;
+ virtual void OnClose(net::SocketStream* socket) OVERRIDE;
+ virtual void OnError(const net::SocketStream* socket, int error) OVERRIDE;
+ virtual void OnSSLCertificateError(net::SocketStream* socket,
+ const net::SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+ virtual bool CanGetCookies(net::SocketStream* socket,
+ const GURL& url) OVERRIDE;
+ virtual bool CanSetCookie(net::SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ net::CookieOptions* options) OVERRIDE;
+
+ // SSLErrorHandler::Delegate methods:
+ virtual void CancelSSLRequest(const GlobalRequestID& id,
+ int error,
+ const net::SSLInfo* ssl_info) OVERRIDE;
+ virtual void ContinueSSLRequest(const GlobalRequestID& id) OVERRIDE;
+
+ protected:
+ virtual ~SocketStreamDispatcherHost();
+
+ private:
+ // Message handlers called by OnMessageReceived.
+ void OnConnect(int render_view_id, const GURL& url, int socket_id);
+ void OnSendData(int socket_id, const std::vector<char>& data);
+ void OnCloseReq(int socket_id);
+
+ void DeleteSocketStreamHost(int socket_id);
+
+ net::URLRequestContext* GetURLRequestContext();
+
+ IDMap<SocketStreamHost> hosts_;
+ int render_process_id_;
+ const scoped_ptr<ResourceMessageFilter::URLRequestContextSelector>
+ url_request_context_selector_;
+ ResourceContext* resource_context_;
+
+ base::WeakPtrFactory<SocketStreamDispatcherHost> weak_ptr_factory_;
+ bool on_shutdown_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/renderer_host/socket_stream_host.cc b/chromium/content/browser/renderer_host/socket_stream_host.cc
new file mode 100644
index 00000000000..ecf6ceec80c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/socket_stream_host.cc
@@ -0,0 +1,102 @@
+// 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 "content/browser/renderer_host/socket_stream_host.h"
+
+#include "base/logging.h"
+#include "content/common/socket_stream.h"
+#include "net/socket_stream/socket_stream_job.h"
+#include "net/url_request/url_request_context.h"
+
+namespace content {
+namespace {
+
+const char* kSocketIdKey = "socketId";
+
+class SocketStreamId : public net::SocketStream::UserData {
+ public:
+ explicit SocketStreamId(int socket_id) : socket_id_(socket_id) {}
+ virtual ~SocketStreamId() {}
+ int socket_id() const { return socket_id_; }
+
+ private:
+ int socket_id_;
+};
+
+} // namespace
+
+SocketStreamHost::SocketStreamHost(
+ net::SocketStream::Delegate* delegate,
+ int render_view_id,
+ int socket_id)
+ : delegate_(delegate),
+ render_view_id_(render_view_id),
+ socket_id_(socket_id) {
+ DCHECK_NE(socket_id_, kNoSocketId);
+ VLOG(1) << "SocketStreamHost: render_view_id=" << render_view_id
+ << " socket_id=" << socket_id_;
+}
+
+/* static */
+int SocketStreamHost::SocketIdFromSocketStream(
+ const net::SocketStream* socket) {
+ net::SocketStream::UserData* d = socket->GetUserData(kSocketIdKey);
+ if (d) {
+ SocketStreamId* socket_stream_id = static_cast<SocketStreamId*>(d);
+ return socket_stream_id->socket_id();
+ }
+ return kNoSocketId;
+}
+
+SocketStreamHost::~SocketStreamHost() {
+ VLOG(1) << "SocketStreamHost destructed socket_id=" << socket_id_;
+ job_->set_context(NULL);
+ job_->DetachDelegate();
+}
+
+void SocketStreamHost::Connect(const GURL& url,
+ net::URLRequestContext* request_context) {
+ VLOG(1) << "SocketStreamHost::Connect url=" << url;
+ job_ = net::SocketStreamJob::CreateSocketStreamJob(
+ url, delegate_, request_context->transport_security_state(),
+ request_context->ssl_config_service());
+ job_->set_context(request_context);
+ job_->SetUserData(kSocketIdKey, new SocketStreamId(socket_id_));
+ job_->Connect();
+}
+
+bool SocketStreamHost::SendData(const std::vector<char>& data) {
+ VLOG(1) << "SocketStreamHost::SendData";
+ return job_.get() && job_->SendData(&data[0], data.size());
+}
+
+void SocketStreamHost::Close() {
+ VLOG(1) << "SocketStreamHost::Close";
+ if (!job_.get())
+ return;
+ job_->Close();
+}
+
+void SocketStreamHost::CancelWithError(int error) {
+ VLOG(1) << "SocketStreamHost::CancelWithError: error=" << error;
+ if (!job_.get())
+ return;
+ job_->CancelWithError(error);
+}
+
+void SocketStreamHost::CancelWithSSLError(const net::SSLInfo& ssl_info) {
+ VLOG(1) << "SocketStreamHost::CancelWithSSLError";
+ if (!job_.get())
+ return;
+ job_->CancelWithSSLError(ssl_info);
+}
+
+void SocketStreamHost::ContinueDespiteError() {
+ VLOG(1) << "SocketStreamHost::ContinueDespiteError";
+ if (!job_.get())
+ return;
+ job_->ContinueDespiteError();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/socket_stream_host.h b/chromium/content/browser/renderer_host/socket_stream_host.h
new file mode 100644
index 00000000000..237e915e64c
--- /dev/null
+++ b/chromium/content/browser/renderer_host/socket_stream_host.h
@@ -0,0 +1,79 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/socket_stream/socket_stream.h"
+
+class GURL;
+
+namespace net {
+class SocketStreamJob;
+class URLRequestContext;
+class SSLInfo;
+} // namespace net
+
+namespace content {
+
+// Host of SocketStreamHandle. Each SocketStreamHandle will have an unique
+// socket_id assigned by SocketStreamHost constructor. If socket id is
+// kNoSocketId, there is no SocketStreamHost. Each SocketStreamHost has
+// SocketStream to manage bi-directional communication over socket stream. The
+// lifetime of an instance of this class is completely controlled by the
+// SocketStreamDispatcherHost.
+class SocketStreamHost {
+ public:
+ SocketStreamHost(net::SocketStream::Delegate* delegate,
+ int render_view_id,
+ int socket_id);
+ ~SocketStreamHost();
+
+ // Gets socket_id associated with |socket|.
+ static int SocketIdFromSocketStream(const net::SocketStream* socket);
+
+ int render_view_id() const { return render_view_id_; }
+ int socket_id() const { return socket_id_; }
+
+ // Starts to open connection to |url|.
+ void Connect(const GURL& url, net::URLRequestContext* request_context);
+
+ // Sends |data| over the socket stream.
+ // socket stream must be open to send data.
+ // Returns true if the data is put in transmit buffer in socket stream.
+ // Returns false otherwise (transmit buffer exceeds limit, or socket
+ // stream is closed).
+ bool SendData(const std::vector<char>& data);
+
+ // Closes the socket stream.
+ void Close();
+
+ // Following CancelWithError, CancelWithSSLError, and ContinueDespiteError
+ // will be called by net::SocketStream::Delegate in OnSSLCertificateError.
+ // CancelWithError Cancels the connection because of an error.
+ // |error| is net::Error which represents the error.
+ void CancelWithError(int error);
+
+ // Cancels the connection because of receiving a certificate with an error.
+ void CancelWithSSLError(const net::SSLInfo& ssl_info);
+
+ // Continue to establish the connection in spite of an error.
+ void ContinueDespiteError();
+
+ private:
+ net::SocketStream::Delegate* delegate_;
+ int render_view_id_;
+ int socket_id_;
+
+ scoped_refptr<net::SocketStreamJob> job_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_
diff --git a/chromium/content/browser/renderer_host/surface_texture_transport_client_android.cc b/chromium/content/browser/renderer_host/surface_texture_transport_client_android.cc
new file mode 100644
index 00000000000..b563480efd9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/surface_texture_transport_client_android.cc
@@ -0,0 +1,143 @@
+// 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 "content/browser/renderer_host/surface_texture_transport_client_android.h"
+
+#include <android/native_window_jni.h>
+
+#include "base/bind.h"
+#include "cc/layers/video_layer.h"
+#include "content/browser/gpu/gpu_surface_tracker.h"
+#include "content/browser/renderer_host/compositor_impl_android.h"
+#include "content/browser/renderer_host/image_transport_factory_android.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gl/android/surface_texture_bridge.h"
+
+namespace content {
+
+namespace {
+
+static const uint32 kGLTextureExternalOES = 0x8D65;
+
+class SurfaceRefAndroid : public GpuSurfaceTracker::SurfaceRef {
+ public:
+ SurfaceRefAndroid(
+ const scoped_refptr<gfx::SurfaceTextureBridge>& surface,
+ ANativeWindow* window)
+ : surface_(surface),
+ window_(window) {
+ ANativeWindow_acquire(window_);
+ }
+
+ private:
+ virtual ~SurfaceRefAndroid() {
+ DCHECK(window_);
+ ANativeWindow_release(window_);
+ }
+
+ scoped_refptr<gfx::SurfaceTextureBridge> surface_;
+ ANativeWindow* window_;
+};
+
+} // anonymous namespace
+
+SurfaceTextureTransportClient::SurfaceTextureTransportClient()
+ : window_(NULL),
+ texture_id_(0),
+ texture_mailbox_sync_point_(0),
+ surface_id_(0),
+ weak_factory_(this) {
+}
+
+SurfaceTextureTransportClient::~SurfaceTextureTransportClient() {
+}
+
+scoped_refptr<cc::Layer> SurfaceTextureTransportClient::Initialize() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Use a SurfaceTexture to stream frames to the UI thread.
+ video_layer_ = cc::VideoLayer::Create(this);
+
+ surface_texture_ = new gfx::SurfaceTextureBridge(0);
+ surface_texture_->SetFrameAvailableCallback(
+ base::Bind(
+ &SurfaceTextureTransportClient::OnSurfaceTextureFrameAvailable,
+ weak_factory_.GetWeakPtr()));
+ surface_texture_->DetachFromGLContext();
+ return video_layer_.get();
+}
+
+gfx::GLSurfaceHandle
+SurfaceTextureTransportClient::GetCompositingSurface(int surface_id) {
+ DCHECK(surface_id);
+ surface_id_ = surface_id;
+
+ if (!window_) {
+ window_ = surface_texture_->CreateSurface();
+
+ GpuSurfaceTracker::Get()->SetNativeWidget(
+ surface_id, window_, new SurfaceRefAndroid(surface_texture_, window_));
+ // SurfaceRefAndroid took ownership (and an extra ref to) window_.
+ ANativeWindow_release(window_);
+ }
+
+ return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_DIRECT);
+}
+
+void SurfaceTextureTransportClient::SetSize(const gfx::Size& size) {
+ if (size.width() > 0 && size.height() > 0) {
+ surface_texture_->SetDefaultBufferSize(size.width(), size.height());
+ }
+ video_layer_->SetBounds(size);
+ video_frame_ = NULL;
+}
+
+scoped_refptr<media::VideoFrame> SurfaceTextureTransportClient::
+ GetCurrentFrame() {
+ if (!texture_id_) {
+ WebKit::WebGraphicsContext3D* context =
+ ImageTransportFactoryAndroid::GetInstance()->GetContext3D();
+ context->makeContextCurrent();
+ texture_id_ = context->createTexture();
+ context->bindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_);
+ context->flush();
+ surface_texture_->AttachToGLContext();
+
+ context->genMailboxCHROMIUM(texture_mailbox_.name);
+ context->produceTextureCHROMIUM(kGLTextureExternalOES,
+ texture_mailbox_.name);
+ texture_mailbox_sync_point_ = context->insertSyncPoint();
+ }
+ if (!video_frame_.get()) {
+ const gfx::Size size = video_layer_->bounds();
+ video_frame_ = media::VideoFrame::WrapNativeTexture(
+ new media::VideoFrame::MailboxHolder(
+ texture_mailbox_,
+ texture_mailbox_sync_point_,
+ media::VideoFrame::MailboxHolder::TextureNoLongerNeededCallback()),
+ kGLTextureExternalOES,
+ size,
+ gfx::Rect(gfx::Point(), size),
+ size,
+ base::TimeDelta(),
+ media::VideoFrame::ReadPixelsCB(),
+ base::Closure());
+ }
+ surface_texture_->UpdateTexImage();
+
+ return video_frame_;
+}
+
+void SurfaceTextureTransportClient::PutCurrentFrame(
+ const scoped_refptr<media::VideoFrame>& frame) {
+}
+
+void SurfaceTextureTransportClient::OnSurfaceTextureFrameAvailable() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ video_layer_->SetNeedsDisplay();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/surface_texture_transport_client_android.h b/chromium/content/browser/renderer_host/surface_texture_transport_client_android.h
new file mode 100644
index 00000000000..83d917552dc
--- /dev/null
+++ b/chromium/content/browser/renderer_host/surface_texture_transport_client_android.h
@@ -0,0 +1,62 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_SURFACE_TEXTURE_TRANSPORT_CLIENT_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SURFACE_TEXTURE_TRANSPORT_CLIENT_ANDROID_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/layers/video_frame_provider.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+struct ANativeWindow;
+
+namespace cc {
+class Layer;
+class VideoLayer;
+}
+
+namespace gfx {
+class SurfaceTextureBridge;
+}
+
+namespace content {
+
+class SurfaceTextureTransportClient : public cc::VideoFrameProvider {
+ public:
+ SurfaceTextureTransportClient();
+ virtual ~SurfaceTextureTransportClient();
+
+ scoped_refptr<cc::Layer> Initialize();
+ gfx::GLSurfaceHandle GetCompositingSurface(int surface_id);
+ void SetSize(const gfx::Size& size);
+
+ // cc::VideoFrameProvider implementation.
+ virtual void SetVideoFrameProviderClient(Client*) OVERRIDE {}
+ virtual scoped_refptr<media::VideoFrame> GetCurrentFrame() OVERRIDE;
+ virtual void PutCurrentFrame(const scoped_refptr<media::VideoFrame>& frame)
+ OVERRIDE;
+
+ private:
+ void OnSurfaceTextureFrameAvailable();
+
+ scoped_refptr<cc::VideoLayer> video_layer_;
+ scoped_refptr<gfx::SurfaceTextureBridge> surface_texture_;
+ ANativeWindow* window_;
+ scoped_refptr<media::VideoFrame> video_frame_;
+ uint32 texture_id_;
+ gpu::Mailbox texture_mailbox_;
+ uint32 texture_mailbox_sync_point_;
+ int surface_id_;
+ base::WeakPtrFactory<SurfaceTextureTransportClient> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfaceTextureTransportClient);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_SURFACE_TEXTURE_TRANSPORT_CLIENT_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/test_backing_store.cc b/chromium/content/browser/renderer_host/test_backing_store.cc
new file mode 100644
index 00000000000..74fa44d4872
--- /dev/null
+++ b/chromium/content/browser/renderer_host/test_backing_store.cc
@@ -0,0 +1,38 @@
+// 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 "content/browser/renderer_host/test_backing_store.h"
+
+namespace content {
+
+TestBackingStore::TestBackingStore(RenderWidgetHost* widget,
+ const gfx::Size& size)
+ : BackingStore(widget, size) {
+}
+
+TestBackingStore::~TestBackingStore() {
+}
+
+void TestBackingStore::PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) {
+ *scheduled_completion_callback = false;
+}
+
+bool TestBackingStore::CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) {
+ return false;
+}
+
+void TestBackingStore::ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/test_backing_store.h b/chromium/content/browser/renderer_host/test_backing_store.h
new file mode 100644
index 00000000000..4b76851c742
--- /dev/null
+++ b/chromium/content/browser/renderer_host/test_backing_store.h
@@ -0,0 +1,39 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_TEST_TEST_BACKING_STORE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TEST_TEST_BACKING_STORE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/browser/renderer_host/backing_store.h"
+
+namespace content {
+
+class TestBackingStore : public BackingStore {
+ public:
+ TestBackingStore(RenderWidgetHost* widget, const gfx::Size& size);
+ virtual ~TestBackingStore();
+
+ // BackingStore implementation.
+ virtual void PaintToBackingStore(
+ RenderProcessHost* process,
+ TransportDIB::Id bitmap,
+ const gfx::Rect& bitmap_rect,
+ const std::vector<gfx::Rect>& copy_rects,
+ float scale_factor,
+ const base::Closure& completion_callback,
+ bool* scheduled_completion_callback) OVERRIDE;
+ virtual bool CopyFromBackingStore(const gfx::Rect& rect,
+ skia::PlatformBitmap* output) OVERRIDE;
+ virtual void ScrollBackingStore(const gfx::Vector2d& delta,
+ const gfx::Rect& clip_rect,
+ const gfx::Size& view_size) OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestBackingStore);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TEST_TEST_BACKING_STORE_H_
diff --git a/chromium/content/browser/renderer_host/test_render_view_host.cc b/chromium/content/browser/renderer_host/test_render_view_host.cc
new file mode 100644
index 00000000000..129591cac7b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/test_render_view_host.cc
@@ -0,0 +1,428 @@
+// 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 "content/browser/renderer_host/test_render_view_host.h"
+
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/renderer_host/test_backing_store.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/page_state.h"
+#include "content/public/common/password_form.h"
+#include "content/test/test_web_contents.h"
+#include "media/base/video_frame.h"
+#include "ui/gfx/rect.h"
+#include "webkit/common/webpreferences.h"
+
+namespace content {
+
+namespace {
+
+const int64 kFrameId = 13UL;
+
+} // namespace
+
+
+void InitNavigateParams(ViewHostMsg_FrameNavigate_Params* params,
+ int page_id,
+ const GURL& url,
+ PageTransition transition) {
+ params->page_id = page_id;
+ params->url = url;
+ params->referrer = Referrer();
+ params->transition = transition;
+ params->redirects = std::vector<GURL>();
+ params->should_update_history = false;
+ params->searchable_form_url = GURL();
+ params->searchable_form_encoding = std::string();
+ params->password_form = PasswordForm();
+ params->security_info = std::string();
+ params->gesture = NavigationGestureUser;
+ params->was_within_same_page = false;
+ params->is_post = false;
+ params->page_state = PageState::CreateFromURL(url);
+}
+
+TestRenderWidgetHostView::TestRenderWidgetHostView(RenderWidgetHost* rwh)
+ : rwh_(RenderWidgetHostImpl::From(rwh)),
+ is_showing_(false),
+ did_swap_compositor_frame_(false) {
+ rwh_->SetView(this);
+}
+
+TestRenderWidgetHostView::~TestRenderWidgetHostView() {
+}
+
+RenderWidgetHost* TestRenderWidgetHostView::GetRenderWidgetHost() const {
+ return NULL;
+}
+
+gfx::NativeView TestRenderWidgetHostView::GetNativeView() const {
+ return NULL;
+}
+
+gfx::NativeViewId TestRenderWidgetHostView::GetNativeViewId() const {
+ return 0;
+}
+
+gfx::NativeViewAccessible TestRenderWidgetHostView::GetNativeViewAccessible() {
+ return NULL;
+}
+
+bool TestRenderWidgetHostView::HasFocus() const {
+ return true;
+}
+
+bool TestRenderWidgetHostView::IsSurfaceAvailableForCopy() const {
+ return true;
+}
+
+void TestRenderWidgetHostView::Show() {
+ is_showing_ = true;
+}
+
+void TestRenderWidgetHostView::Hide() {
+ is_showing_ = false;
+}
+
+bool TestRenderWidgetHostView::IsShowing() {
+ return is_showing_;
+}
+
+void TestRenderWidgetHostView::RenderProcessGone(base::TerminationStatus status,
+ int error_code) {
+ delete this;
+}
+
+void TestRenderWidgetHostView::Destroy() { delete this; }
+
+gfx::Rect TestRenderWidgetHostView::GetViewBounds() const {
+ return gfx::Rect();
+}
+
+BackingStore* TestRenderWidgetHostView::AllocBackingStore(
+ const gfx::Size& size) {
+ return new TestBackingStore(rwh_, size);
+}
+
+void TestRenderWidgetHostView::CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) {
+ callback.Run(false, SkBitmap());
+}
+
+void TestRenderWidgetHostView::CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) {
+ callback.Run(false);
+}
+
+bool TestRenderWidgetHostView::CanCopyToVideoFrame() const {
+ return false;
+}
+
+void TestRenderWidgetHostView::OnAcceleratedCompositingStateChange() {
+}
+
+void TestRenderWidgetHostView::AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) {
+}
+
+void TestRenderWidgetHostView::AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) {
+}
+
+void TestRenderWidgetHostView::AcceleratedSurfaceSuspend() {
+}
+
+bool TestRenderWidgetHostView::HasAcceleratedSurface(
+ const gfx::Size& desired_size) {
+ return false;
+}
+
+#if defined(OS_MACOSX)
+
+void TestRenderWidgetHostView::AboutToWaitForBackingStoreMsg() {
+}
+
+void TestRenderWidgetHostView::SetActive(bool active) {
+ // <viettrungluu@gmail.com>: Do I need to do anything here?
+}
+
+bool TestRenderWidgetHostView::SupportsSpeech() const {
+ return false;
+}
+
+void TestRenderWidgetHostView::SpeakSelection() {
+}
+
+bool TestRenderWidgetHostView::IsSpeaking() const {
+ return false;
+}
+
+void TestRenderWidgetHostView::StopSpeaking() {
+}
+
+bool TestRenderWidgetHostView::PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) {
+ return false;
+}
+
+#elif defined(OS_WIN) && !defined(USE_AURA)
+void TestRenderWidgetHostView::WillWmDestroy() {
+}
+#endif
+
+gfx::Rect TestRenderWidgetHostView::GetBoundsInRootWindow() {
+ return gfx::Rect();
+}
+
+#if defined(TOOLKIT_GTK)
+GdkEventButton* TestRenderWidgetHostView::GetLastMouseDown() {
+ return NULL;
+}
+
+gfx::NativeView TestRenderWidgetHostView::BuildInputMethodsGtkMenu() {
+ return NULL;
+}
+#endif // defined(TOOLKIT_GTK)
+
+void TestRenderWidgetHostView::OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) {
+ did_swap_compositor_frame_ = true;
+}
+
+
+gfx::GLSurfaceHandle TestRenderWidgetHostView::GetCompositingSurface() {
+ return gfx::GLSurfaceHandle();
+}
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+void TestRenderWidgetHostView::SetClickthroughRegion(SkRegion* region) {
+}
+#endif
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible
+TestRenderWidgetHostView::AccessibleObjectFromChildId(long child_id) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+#endif
+
+bool TestRenderWidgetHostView::LockMouse() {
+ return false;
+}
+
+void TestRenderWidgetHostView::UnlockMouse() {
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+void TestRenderWidgetHostView::SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) {
+}
+#endif
+
+TestRenderViewHost::TestRenderViewHost(
+ SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out)
+ : RenderViewHostImpl(instance,
+ delegate,
+ widget_delegate,
+ routing_id,
+ main_frame_routing_id,
+ swapped_out),
+ render_view_created_(false),
+ delete_counter_(NULL),
+ simulate_fetch_via_proxy_(false),
+ simulate_history_list_was_cleared_(false),
+ contents_mime_type_("text/html"),
+ opener_route_id_(MSG_ROUTING_NONE) {
+ // TestRenderWidgetHostView installs itself into this->view_ in its
+ // constructor, and deletes itself when TestRenderWidgetHostView::Destroy() is
+ // called.
+ new TestRenderWidgetHostView(this);
+
+ main_frame_id_ = kFrameId;
+}
+
+TestRenderViewHost::~TestRenderViewHost() {
+ if (delete_counter_)
+ ++*delete_counter_;
+}
+
+bool TestRenderViewHost::CreateRenderView(
+ const string16& frame_name,
+ int opener_route_id,
+ int32 max_page_id) {
+ DCHECK(!render_view_created_);
+ render_view_created_ = true;
+ opener_route_id_ = opener_route_id;
+ return true;
+}
+
+bool TestRenderViewHost::IsRenderViewLive() const {
+ return render_view_created_;
+}
+
+void TestRenderViewHost::SendNavigate(int page_id, const GURL& url) {
+ SendNavigateWithTransition(page_id, url, PAGE_TRANSITION_LINK);
+}
+
+void TestRenderViewHost::SendFailedNavigate(int page_id, const GURL& url) {
+ SendNavigateWithTransitionAndResponseCode(
+ page_id, url, PAGE_TRANSITION_LINK, 500);
+}
+
+void TestRenderViewHost::SendNavigateWithTransition(
+ int page_id, const GURL& url, PageTransition transition) {
+ SendNavigateWithTransitionAndResponseCode(page_id, url, transition, 200);
+}
+
+void TestRenderViewHost::SendNavigateWithOriginalRequestURL(
+ int page_id, const GURL& url, const GURL& original_request_url) {
+ OnDidStartProvisionalLoadForFrame(kFrameId, -1, true, url);
+ SendNavigateWithParameters(page_id, url, PAGE_TRANSITION_LINK,
+ original_request_url, 200, 0);
+}
+
+void TestRenderViewHost::SendNavigateWithFile(
+ int page_id, const GURL& url, const base::FilePath& file_path) {
+ SendNavigateWithParameters(page_id, url, PAGE_TRANSITION_LINK,
+ url, 200, &file_path);
+}
+
+void TestRenderViewHost::SendNavigateWithTransitionAndResponseCode(
+ int page_id, const GURL& url, PageTransition transition,
+ int response_code) {
+ // DidStartProvisionalLoad may delete the pending entry that holds |url|,
+ // so we keep a copy of it to use in SendNavigateWithParameters.
+ GURL url_copy(url);
+ OnDidStartProvisionalLoadForFrame(kFrameId, -1, true, url_copy);
+ SendNavigateWithParameters(page_id, url_copy, transition, url_copy,
+ response_code, 0);
+}
+
+void TestRenderViewHost::SendNavigateWithParameters(
+ int page_id, const GURL& url, PageTransition transition,
+ const GURL& original_request_url, int response_code,
+ const base::FilePath* file_path_for_history_item) {
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = page_id;
+ params.frame_id = kFrameId;
+ params.url = url;
+ params.referrer = Referrer();
+ params.transition = transition;
+ params.redirects = std::vector<GURL>();
+ params.should_update_history = true;
+ params.searchable_form_url = GURL();
+ params.searchable_form_encoding = std::string();
+ params.password_form = PasswordForm();
+ params.security_info = std::string();
+ params.gesture = NavigationGestureUser;
+ params.contents_mime_type = contents_mime_type_;
+ params.is_post = false;
+ params.was_within_same_page = false;
+ params.http_status_code = response_code;
+ params.socket_address.set_host("2001:db8::1");
+ params.socket_address.set_port(80);
+ params.was_fetched_via_proxy = simulate_fetch_via_proxy_;
+ params.history_list_was_cleared = simulate_history_list_was_cleared_;
+ params.original_request_url = original_request_url;
+
+ params.page_state = PageState::CreateForTesting(
+ url,
+ false,
+ file_path_for_history_item ? "data" : NULL,
+ file_path_for_history_item);
+
+ ViewHostMsg_FrameNavigate msg(1, params);
+ OnNavigate(msg);
+}
+
+void TestRenderViewHost::SendShouldCloseACK(bool proceed) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ OnShouldCloseACK(proceed, now, now);
+}
+
+void TestRenderViewHost::SetContentsMimeType(const std::string& mime_type) {
+ contents_mime_type_ = mime_type;
+}
+
+void TestRenderViewHost::SimulateSwapOutACK() {
+ OnSwappedOut(false);
+}
+
+void TestRenderViewHost::SimulateWasHidden() {
+ WasHidden();
+}
+
+void TestRenderViewHost::SimulateWasShown() {
+ WasShown();
+}
+
+void TestRenderViewHost::TestOnStartDragging(
+ const DropData& drop_data) {
+ WebKit::WebDragOperationsMask drag_operation = WebKit::WebDragOperationEvery;
+ DragEventSourceInfo event_info;
+ OnStartDragging(drop_data, drag_operation, SkBitmap(), gfx::Vector2d(),
+ event_info);
+}
+
+void TestRenderViewHost::TestOnUpdateStateWithFile(
+ int process_id,
+ const base::FilePath& file_path) {
+ OnUpdateState(process_id,
+ PageState::CreateForTesting(GURL("http://www.google.com"),
+ false,
+ "data",
+ &file_path));
+}
+
+void TestRenderViewHost::set_simulate_fetch_via_proxy(bool proxy) {
+ simulate_fetch_via_proxy_ = proxy;
+}
+
+void TestRenderViewHost::set_simulate_history_list_was_cleared(bool cleared) {
+ simulate_history_list_was_cleared_ = cleared;
+}
+
+RenderViewHostImplTestHarness::RenderViewHostImplTestHarness() {
+}
+
+RenderViewHostImplTestHarness::~RenderViewHostImplTestHarness() {
+}
+
+TestRenderViewHost* RenderViewHostImplTestHarness::test_rvh() {
+ return static_cast<TestRenderViewHost*>(rvh());
+}
+
+TestRenderViewHost* RenderViewHostImplTestHarness::pending_test_rvh() {
+ return static_cast<TestRenderViewHost*>(pending_rvh());
+}
+
+TestRenderViewHost* RenderViewHostImplTestHarness::active_test_rvh() {
+ return static_cast<TestRenderViewHost*>(active_rvh());
+}
+
+TestWebContents* RenderViewHostImplTestHarness::contents() {
+ return static_cast<TestWebContents*>(web_contents());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/test_render_view_host.h b/chromium/content/browser/renderer_host/test_render_view_host.h
new file mode 100644
index 00000000000..37b46e2bef8
--- /dev/null
+++ b/chromium/content/browser/renderer_host/test_render_view_host.h
@@ -0,0 +1,383 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_VIEW_HOST_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_VIEW_HOST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/test/test_renderer_host.h"
+#include "ui/gfx/vector2d_f.h"
+
+// This file provides a testing framework for mocking out the RenderProcessHost
+// layer. It allows you to test RenderViewHost, WebContentsImpl,
+// NavigationController, and other layers above that without running an actual
+// renderer process.
+//
+// To use, derive your test base class from RenderViewHostImplTestHarness.
+
+struct ViewHostMsg_FrameNavigate_Params;
+
+namespace gfx {
+class Rect;
+}
+
+namespace content {
+
+class SiteInstance;
+class TestWebContents;
+
+// Utility function to initialize ViewHostMsg_NavigateParams_Params
+// with given |page_id|, |url| and |transition_type|.
+void InitNavigateParams(ViewHostMsg_FrameNavigate_Params* params,
+ int page_id,
+ const GURL& url,
+ PageTransition transition_type);
+
+// TestRenderViewHostView ------------------------------------------------------
+
+// Subclass the RenderViewHost's view so that we can call Show(), etc.,
+// without having side-effects.
+class TestRenderWidgetHostView : public RenderWidgetHostViewBase {
+ public:
+ explicit TestRenderWidgetHostView(RenderWidgetHost* rwh);
+ virtual ~TestRenderWidgetHostView();
+
+ // RenderWidgetHostView implementation.
+ virtual void InitAsChild(gfx::NativeView parent_view) OVERRIDE {}
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE {}
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE {}
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() OVERRIDE;
+ virtual bool HasFocus() const OVERRIDE;
+ virtual bool IsSurfaceAvailableForCopy() const OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+#if defined(OS_MACOSX)
+ virtual void SetActive(bool active) OVERRIDE;
+ virtual void SetTakesFocusOnlyOnMouseDown(bool flag) OVERRIDE {}
+ virtual void SetWindowVisibility(bool visible) OVERRIDE {}
+ virtual void WindowFrameChanged() OVERRIDE {}
+ virtual void ShowDefinitionForSelection() OVERRIDE {}
+ virtual bool SupportsSpeech() const OVERRIDE;
+ virtual void SpeakSelection() OVERRIDE;
+ virtual bool IsSpeaking() const OVERRIDE;
+ virtual void StopSpeaking() OVERRIDE;
+#endif // defined(OS_MACOSX)
+#if defined(TOOLKIT_GTK)
+ virtual GdkEventButton* GetLastMouseDown() OVERRIDE;
+ virtual gfx::NativeView BuildInputMethodsGtkMenu() OVERRIDE;
+#endif // defined(TOOLKIT_GTK)
+ virtual void OnSwapCompositorFrame(
+ uint32 output_surface_id,
+ scoped_ptr<cc::CompositorFrame> frame) OVERRIDE;
+
+ // RenderWidgetHostViewPort implementation.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE {}
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE {}
+ virtual void WasShown() OVERRIDE {}
+ virtual void WasHidden() OVERRIDE {}
+ virtual void MovePluginWindows(
+ const gfx::Vector2d& scroll_offset,
+ const std::vector<WebPluginGeometry>& moves) OVERRIDE {}
+ virtual void Focus() OVERRIDE {}
+ virtual void Blur() OVERRIDE {}
+ virtual void SetIsLoading(bool is_loading) OVERRIDE {}
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE {}
+ virtual void TextInputTypeChanged(ui::TextInputType type,
+ bool can_compose_inline,
+ ui::TextInputMode input_mode) OVERRIDE {}
+ virtual void ImeCancelComposition() OVERRIDE {}
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_AURA)
+ virtual void ImeCompositionRangeChanged(
+ const ui::Range& range,
+ const std::vector<gfx::Rect>& character_bounds) OVERRIDE {}
+#endif
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect,
+ const gfx::Vector2d& scroll_delta,
+ const std::vector<gfx::Rect>& rects,
+ const ui::LatencyInfo& latency_info) OVERRIDE {}
+ virtual void RenderProcessGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) { }
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE {}
+ virtual void SelectionBoundsChanged(
+ const ViewHostMsg_SelectionBounds_Params& params) OVERRIDE {}
+ virtual void ScrollOffsetChanged() OVERRIDE {}
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void CopyFromCompositingSurface(
+ const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size,
+ const base::Callback<void(bool, const SkBitmap&)>& callback) OVERRIDE;
+ virtual void CopyFromCompositingSurfaceToVideoFrame(
+ const gfx::Rect& src_subrect,
+ const scoped_refptr<media::VideoFrame>& target,
+ const base::Callback<void(bool)>& callback) OVERRIDE;
+ virtual bool CanCopyToVideoFrame() const OVERRIDE;
+ virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfacePostSubBuffer(
+ const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
+ int gpu_host_id) OVERRIDE;
+ virtual void AcceleratedSurfaceSuspend() OVERRIDE;
+ virtual void AcceleratedSurfaceRelease() OVERRIDE {}
+ virtual bool HasAcceleratedSurface(const gfx::Size& desired_size) OVERRIDE;
+#if defined(OS_MACOSX)
+ virtual void AboutToWaitForBackingStoreMsg() OVERRIDE;
+ virtual bool PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+#elif defined(OS_ANDROID)
+ virtual void ShowDisambiguationPopup(
+ const gfx::Rect& target_rect,
+ const SkBitmap& zoomed_bitmap) OVERRIDE {}
+ virtual void HasTouchEventHandlers(bool need_touch_events) OVERRIDE {}
+#elif defined(OS_WIN) && !defined(USE_AURA)
+ virtual void WillWmDestroy() OVERRIDE;
+#endif
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE {}
+ virtual gfx::Rect GetBoundsInRootWindow() OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE { }
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE { }
+ virtual void OnAccessibilityNotifications(
+ const std::vector<AccessibilityHostMsg_NotificationParams>&
+ params) OVERRIDE {}
+ virtual gfx::GLSurfaceHandle GetCompositingSurface() OVERRIDE;
+#if defined(OS_WIN) && !defined(USE_AURA)
+ virtual void SetClickthroughRegion(SkRegion* region) OVERRIDE;
+#endif
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual gfx::NativeViewAccessible AccessibleObjectFromChildId(long child_id)
+ OVERRIDE;
+#endif
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual void SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) OVERRIDE;
+#endif
+
+ bool is_showing() const { return is_showing_; }
+ bool did_swap_compositor_frame() const { return did_swap_compositor_frame_; }
+
+ protected:
+ RenderWidgetHostImpl* rwh_;
+
+ private:
+ bool is_showing_;
+ bool did_swap_compositor_frame_;
+};
+
+#if defined(COMPILER_MSVC)
+// See comment for same warning on RenderViewHostImpl.
+#pragma warning(push)
+#pragma warning(disable: 4250)
+#endif
+
+// TestRenderViewHost ----------------------------------------------------------
+
+// TODO(brettw) this should use a TestWebContents which should be generalized
+// from the WebContentsImpl test. We will probably also need that class' version
+// of CreateRenderViewForRenderManager when more complicated tests start using
+// this.
+//
+// Note that users outside of content must use this class by getting
+// the separate RenderViewHostTester interface via
+// RenderViewHostTester::For(rvh) on the RenderViewHost they want to
+// drive tests on.
+//
+// Users within content may directly static_cast from a
+// RenderViewHost* to a TestRenderViewHost*.
+//
+// The reasons we do it this way rather than extending the parallel
+// inheritance hierarchy we have for RenderWidgetHost/RenderViewHost
+// vs. RenderWidgetHostImpl/RenderViewHostImpl are:
+//
+// a) Extending the parallel class hierarchy further would require
+// more classes to use virtual inheritance. This is a complexity that
+// is better to avoid, especially when it would be introduced in the
+// production code solely to facilitate testing code.
+//
+// b) While users outside of content only need to drive tests on a
+// RenderViewHost, content needs a test version of the full
+// RenderViewHostImpl so that it can test all methods on that concrete
+// class (e.g. overriding a method such as
+// RenderViewHostImpl::CreateRenderView). This would have complicated
+// the dual class hierarchy even further.
+//
+// The reason we do it this way instead of using composition is
+// similar to (b) above, essentially it gets very tricky. By using
+// the split interface we avoid complexity within content and maintain
+// reasonable utility for embedders.
+class TestRenderViewHost
+ : public RenderViewHostImpl,
+ public RenderViewHostTester {
+ public:
+ TestRenderViewHost(SiteInstance* instance,
+ RenderViewHostDelegate* delegate,
+ RenderWidgetHostDelegate* widget_delegate,
+ int routing_id,
+ int main_frame_routing_id,
+ bool swapped_out);
+ virtual ~TestRenderViewHost();
+
+ // RenderViewHostTester implementation. Note that CreateRenderView
+ // is not specified since it is synonymous with the one from
+ // RenderViewHostImpl, see below.
+ virtual void SendNavigate(int page_id, const GURL& url) OVERRIDE;
+ virtual void SendFailedNavigate(int page_id, const GURL& url) OVERRIDE;
+ virtual void SendNavigateWithTransition(int page_id, const GURL& url,
+ PageTransition transition) OVERRIDE;
+ virtual void SendShouldCloseACK(bool proceed) OVERRIDE;
+ virtual void SetContentsMimeType(const std::string& mime_type) OVERRIDE;
+ virtual void SimulateSwapOutACK() OVERRIDE;
+ virtual void SimulateWasHidden() OVERRIDE;
+ virtual void SimulateWasShown() OVERRIDE;
+
+ // Calls OnNavigate on the RenderViewHost with the given information,
+ // including a custom original request URL. Sets the rest of the
+ // parameters in the message to the "typical" values. This is a helper
+ // function for simulating the most common types of loads.
+ void SendNavigateWithOriginalRequestURL(
+ int page_id, const GURL& url, const GURL& original_request_url);
+
+ void SendNavigateWithFile(
+ int page_id, const GURL& url, const base::FilePath& file_path);
+
+ void TestOnUpdateStateWithFile(
+ int process_id, const base::FilePath& file_path);
+
+ void TestOnStartDragging(const DropData& drop_data);
+
+ // If set, *delete_counter is incremented when this object destructs.
+ void set_delete_counter(int* delete_counter) {
+ delete_counter_ = delete_counter;
+ }
+
+ // Sets whether the RenderView currently exists or not. This controls the
+ // return value from IsRenderViewLive, which the rest of the system uses to
+ // check whether the RenderView has crashed or not.
+ void set_render_view_created(bool created) {
+ render_view_created_ = created;
+ }
+
+ // Returns whether the RenderViewHost is currently waiting to hear the result
+ // of a before unload handler from the renderer.
+ bool is_waiting_for_beforeunload_ack() const {
+ return is_waiting_for_beforeunload_ack_;
+ }
+
+ // Returns whether the RenderViewHost is currently waiting to hear the result
+ // of an unload handler from the renderer.
+ bool is_waiting_for_unload_ack() const {
+ return is_waiting_for_unload_ack_;
+ }
+
+ // Sets whether the RenderViewHost is currently swapped out, and thus
+ // filtering messages from the renderer.
+ void set_is_swapped_out(bool is_swapped_out) {
+ is_swapped_out_ = is_swapped_out;
+ }
+
+ // If set, navigations will appear to have loaded through a proxy
+ // (ViewHostMsg_FrameNavigte_Params::was_fetched_via_proxy).
+ // False by default.
+ void set_simulate_fetch_via_proxy(bool proxy);
+
+ // If set, navigations will appear to have cleared the history list in the
+ // RenderView (ViewHostMsg_FrameNavigate_Params::history_list_was_cleared).
+ // False by default.
+ void set_simulate_history_list_was_cleared(bool cleared);
+
+ // The opener route id passed to CreateRenderView().
+ int opener_route_id() const { return opener_route_id_; }
+
+ // RenderViewHost overrides --------------------------------------------------
+
+ virtual bool CreateRenderView(const string16& frame_name,
+ int opener_route_id,
+ int32 max_page_id) OVERRIDE;
+ virtual bool IsRenderViewLive() const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(RenderViewHostTest, FilterNavigate);
+
+ void SendNavigateWithTransitionAndResponseCode(int page_id,
+ const GURL& url,
+ PageTransition transition,
+ int response_code);
+
+ // Calls OnNavigate on the RenderViewHost with the given information.
+ // Sets the rest of the parameters in the message to the "typical" values.
+ // This is a helper function for simulating the most common types of loads.
+ void SendNavigateWithParameters(
+ int page_id,
+ const GURL& url,
+ PageTransition transition,
+ const GURL& original_request_url,
+ int response_code,
+ const base::FilePath* file_path_for_history_item);
+
+ // Tracks if the caller thinks if it created the RenderView. This is so we can
+ // respond to IsRenderViewLive appropriately.
+ bool render_view_created_;
+
+ // See set_delete_counter() above. May be NULL.
+ int* delete_counter_;
+
+ // See set_simulate_fetch_via_proxy() above.
+ bool simulate_fetch_via_proxy_;
+
+ // See set_simulate_history_list_was_cleared() above.
+ bool simulate_history_list_was_cleared_;
+
+ // See SetContentsMimeType() above.
+ std::string contents_mime_type_;
+
+ // See opener_route_id() above.
+ int opener_route_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestRenderViewHost);
+};
+
+#if defined(COMPILER_MSVC)
+#pragma warning(pop)
+#endif
+
+// Adds methods to get straight at the impl classes.
+class RenderViewHostImplTestHarness : public RenderViewHostTestHarness {
+ public:
+ RenderViewHostImplTestHarness();
+ virtual ~RenderViewHostImplTestHarness();
+
+ TestRenderViewHost* test_rvh();
+ TestRenderViewHost* pending_test_rvh();
+ TestRenderViewHost* active_test_rvh();
+ TestWebContents* contents();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostImplTestHarness);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_VIEW_HOST_H_
diff --git a/chromium/content/browser/renderer_host/text_input_client_mac.h b/chromium/content/browser/renderer_host/text_input_client_mac.h
new file mode 100644
index 00000000000..53318f95cb5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/text_input_client_mac.h
@@ -0,0 +1,90 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+#include "ui/gfx/point.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace content {
+class RenderWidgetHost;
+
+// This class helps with the Mac OS X dictionary popup. For the design overview,
+// look at this document:
+// http://dev.chromium.org/developers/design-documents/system-dictionary-pop-up-architecture
+//
+// This service is used to marshall information for these three methods that are
+// implemented in RenderWidgetHostViewMac:
+// -[NSTextInput characterIndexForPoint:]
+// -[NSTextInput attributedSubstringFromRange:]
+// -[NSTextInput firstRectForCharacterRange:]
+//
+// Because these methods are part of a synchronous system API, implementing them
+// requires getting information from the renderer synchronously. Rather than
+// using an actual sync IPC message, a normal async ViewMsg is used with a lock
+// and condition (managed by this service).
+class CONTENT_EXPORT TextInputClientMac {
+ public:
+ // Returns the singleton instance.
+ static TextInputClientMac* GetInstance();
+
+ // Each of the three methods mentioned above has an associated pair of methods
+ // to get data from the renderer. The Get*() methods block the calling thread
+ // (always the UI thread) with a short timeout after the async message has
+ // been sent to the renderer to lookup the information needed to respond to
+ // the system. The Set*AndSignal() methods store the looked up information in
+ // this service and signal the condition to allow the Get*() methods to
+ // unlock and return that stored value.
+ //
+ // Returns NSNotFound if the request times out or is not completed.
+ NSUInteger GetCharacterIndexAtPoint(RenderWidgetHost* rwh, gfx::Point point);
+ // Returns nil if the request times out or is completed.
+ NSAttributedString* GetAttributedSubstringFromRange(
+ RenderWidgetHost* rwh, NSRange range);
+ // Returns NSZeroRect if the request times out or is not completed. The result
+ // is in WebKit coordinates.
+ NSRect GetFirstRectForRange(RenderWidgetHost* rwh, NSRange range);
+
+ // When the renderer sends the ViewHostMsg reply, the RenderMessageFilter will
+ // call the corresponding method on the IO thread to unlock the condition and
+ // allow the Get*() methods to continue/return.
+ void SetCharacterIndexAndSignal(NSUInteger index);
+ void SetFirstRectAndSignal(NSRect first_rect);
+ void SetSubstringAndSignal(NSAttributedString* string);
+
+ private:
+ friend struct DefaultSingletonTraits<TextInputClientMac>;
+ TextInputClientMac();
+ ~TextInputClientMac();
+
+ // The critical sections that the Condition guards are in Get*() methods.
+ // These methods lock the internal condition for use before the asynchronous
+ // message is sent to the renderer to lookup the required information. These
+ // are only used on the UI thread.
+ void BeforeRequest();
+ // Called at the end of a critical section. This will release the lock and
+ // condition.
+ void AfterRequest();
+
+ NSUInteger character_index_;
+ NSRect first_rect_;
+ base::scoped_nsobject<NSAttributedString> substring_;
+
+ base::Lock lock_;
+ base::ConditionVariable condition_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextInputClientMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_
diff --git a/chromium/content/browser/renderer_host/text_input_client_mac.mm b/chromium/content/browser/renderer_host/text_input_client_mac.mm
new file mode 100644
index 00000000000..cdf56a1eb19
--- /dev/null
+++ b/chromium/content/browser/renderer_host/text_input_client_mac.mm
@@ -0,0 +1,140 @@
+// 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.
+
+#import "content/browser/renderer_host/text_input_client_mac.h"
+
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/common/text_input_client_messages.h"
+
+namespace content {
+
+// The amount of time in milliseconds that the browser process will wait for a
+// response from the renderer.
+// TODO(rsesek): Using the histogram data, find the best upper-bound for this
+// value.
+const float kWaitTimeout = 1500;
+
+TextInputClientMac::TextInputClientMac()
+ : character_index_(NSNotFound),
+ lock_(),
+ condition_(&lock_) {
+}
+
+TextInputClientMac::~TextInputClientMac() {
+}
+
+// static
+TextInputClientMac* TextInputClientMac::GetInstance() {
+ return Singleton<TextInputClientMac>::get();
+}
+
+NSUInteger TextInputClientMac::GetCharacterIndexAtPoint(RenderWidgetHost* rwh,
+ gfx::Point point) {
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ BeforeRequest();
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
+ rwhi->Send(new TextInputClientMsg_CharacterIndexForPoint(rwhi->GetRoutingID(),
+ point));
+ // http://crbug.com/121917
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
+ AfterRequest();
+
+ base::TimeDelta delta(base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_LONG_TIMES("TextInputClient.CharacterIndex",
+ delta * base::Time::kMicrosecondsPerMillisecond);
+
+ return character_index_;
+}
+
+NSRect TextInputClientMac::GetFirstRectForRange(RenderWidgetHost* rwh,
+ NSRange range) {
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ BeforeRequest();
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
+ rwhi->Send(
+ new TextInputClientMsg_FirstRectForCharacterRange(rwhi->GetRoutingID(),
+ ui::Range(range)));
+ // http://crbug.com/121917
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
+ AfterRequest();
+
+ base::TimeDelta delta(base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_LONG_TIMES("TextInputClient.FirstRect",
+ delta * base::Time::kMicrosecondsPerMillisecond);
+
+ return first_rect_;
+}
+
+NSAttributedString* TextInputClientMac::GetAttributedSubstringFromRange(
+ RenderWidgetHost* rwh,
+ NSRange range) {
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ BeforeRequest();
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
+ rwhi->Send(new TextInputClientMsg_StringForRange(rwhi->GetRoutingID(),
+ ui::Range(range)));
+ // http://crbug.com/121917
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
+ AfterRequest();
+
+ base::TimeDelta delta(base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_LONG_TIMES("TextInputClient.Substring",
+ delta * base::Time::kMicrosecondsPerMillisecond);
+
+ // Lookup.framework calls this method repeatedly and expects that repeated
+ // calls don't deallocate previous results immediately. Returning an
+ // autoreleased string is better convention anyway.
+ return [[substring_.get() retain] autorelease];
+}
+
+void TextInputClientMac::SetCharacterIndexAndSignal(NSUInteger index) {
+ lock_.Acquire();
+ character_index_ = index;
+ lock_.Release();
+ condition_.Signal();
+}
+
+void TextInputClientMac::SetFirstRectAndSignal(NSRect first_rect) {
+ lock_.Acquire();
+ first_rect_ = first_rect;
+ lock_.Release();
+ condition_.Signal();
+}
+
+void TextInputClientMac::SetSubstringAndSignal(NSAttributedString* string) {
+ lock_.Acquire();
+ substring_.reset([string copy]);
+ lock_.Release();
+ condition_.Signal();
+}
+
+void TextInputClientMac::BeforeRequest() {
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ lock_.Acquire();
+
+ base::TimeDelta delta(base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_LONG_TIMES("TextInputClient.LockWait",
+ delta * base::Time::kMicrosecondsPerMillisecond);
+
+ character_index_ = NSNotFound;
+ first_rect_ = NSZeroRect;
+ substring_.reset();
+}
+
+void TextInputClientMac::AfterRequest() {
+ lock_.Release();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/text_input_client_mac_unittest.mm b/chromium/content/browser/renderer_host/text_input_client_mac_unittest.mm
new file mode 100644
index 00000000000..2a4c6d7f08d
--- /dev/null
+++ b/chromium/content/browser/renderer_host/text_input_client_mac_unittest.mm
@@ -0,0 +1,233 @@
+// 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.
+
+#import "content/browser/renderer_host/text_input_client_mac.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/text_input_client_message_filter.h"
+#include "content/common/text_input_client_messages.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+
+namespace content {
+
+namespace {
+const int64 kTaskDelayMs = 200;
+
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ virtual ~MockRenderWidgetHostDelegate() {}
+};
+
+// This test does not test the WebKit side of the dictionary system (which
+// performs the actual data fetching), but rather this just tests that the
+// service's signaling system works.
+class TextInputClientMacTest : public testing::Test {
+ public:
+ TextInputClientMacTest()
+ : message_loop_(base::MessageLoop::TYPE_UI),
+ browser_context_(),
+ process_factory_(),
+ delegate_(),
+ widget_(&delegate_,
+ process_factory_.CreateRenderProcessHost(
+ &browser_context_, NULL),
+ MSG_ROUTING_NONE),
+ thread_("TextInputClientMacTestThread") {}
+
+ // Accessor for the TextInputClientMac instance.
+ TextInputClientMac* service() {
+ return TextInputClientMac::GetInstance();
+ }
+
+ // Helper method to post a task on the testing thread's MessageLoop after
+ // a short delay.
+ void PostTask(const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs));
+ }
+
+ void PostTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const base::TimeDelta delay) {
+ thread_.message_loop()->PostDelayedTask(from_here, task, delay);
+ }
+
+ RenderWidgetHostImpl* widget() {
+ return &widget_;
+ }
+
+ IPC::TestSink& ipc_sink() {
+ return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink();
+ }
+
+ private:
+ friend class ScopedTestingThread;
+
+ base::MessageLoop message_loop_;
+ TestBrowserContext browser_context_;
+
+ // Gets deleted when the last RWH in the "process" gets destroyed.
+ MockRenderProcessHostFactory process_factory_;
+ MockRenderWidgetHostDelegate delegate_;
+ RenderWidgetHostImpl widget_;
+
+ base::Thread thread_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Helper class that Start()s and Stop()s a thread according to the scope of the
+// object.
+class ScopedTestingThread {
+ public:
+ ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) {
+ thread_.Start();
+ }
+ ~ScopedTestingThread() {
+ thread_.Stop();
+ }
+
+ private:
+ base::Thread& thread_;
+};
+
+// Adapter for OnMessageReceived to ignore return type so it can be posted
+// to a MessageLoop.
+void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter,
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ filter->OnMessageReceived(message, message_was_ok);
+}
+
+} // namespace
+
+// Test Cases //////////////////////////////////////////////////////////////////
+
+TEST_F(TextInputClientMacTest, GetCharacterIndex) {
+ ScopedTestingThread thread(this);
+ const NSUInteger kSuccessValue = 42;
+
+ PostTask(FROM_HERE,
+ base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
+ base::Unretained(service()), kSuccessValue));
+ NSUInteger index = service()->GetCharacterIndexAtPoint(
+ widget(), gfx::Point(2, 2));
+
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_CharacterIndexForPoint::ID));
+ EXPECT_EQ(kSuccessValue, index);
+}
+
+TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) {
+ NSUInteger index = service()->GetCharacterIndexAtPoint(
+ widget(), gfx::Point(2, 2));
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_CharacterIndexForPoint::ID));
+ EXPECT_EQ(NSNotFound, index);
+}
+
+TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) {
+ ScopedTestingThread thread(this);
+ const NSUInteger kPreviousValue = 42;
+ const size_t kNotFoundValue = static_cast<size_t>(-1);
+
+ // Set an arbitrary value to ensure the index is not |NSNotFound|.
+ PostTask(FROM_HERE,
+ base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
+ base::Unretained(service()), kPreviousValue));
+
+ scoped_refptr<TextInputClientMessageFilter> filter(
+ new TextInputClientMessageFilter(widget()->GetProcess()->GetID()));
+ scoped_ptr<IPC::Message> message(
+ new TextInputClientReplyMsg_GotCharacterIndexForPoint(
+ widget()->GetRoutingID(), kNotFoundValue));
+ bool message_ok = true;
+ // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous
+ // setting.
+ PostTask(FROM_HERE,
+ base::Bind(&CallOnMessageReceived, filter, *message, &message_ok),
+ base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2);
+
+ NSUInteger index = service()->GetCharacterIndexAtPoint(
+ widget(), gfx::Point(2, 2));
+ EXPECT_EQ(kPreviousValue, index);
+ index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2));
+ EXPECT_EQ(NSNotFound, index);
+
+ EXPECT_EQ(2U, ipc_sink().message_count());
+ for (size_t i = 0; i < ipc_sink().message_count(); ++i) {
+ const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i);
+ EXPECT_EQ(ipc_message->type(),
+ TextInputClientMsg_CharacterIndexForPoint::ID);
+ }
+}
+
+TEST_F(TextInputClientMacTest, GetRectForRange) {
+ ScopedTestingThread thread(this);
+ const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45);
+
+ PostTask(FROM_HERE,
+ base::Bind(&TextInputClientMac::SetFirstRectAndSignal,
+ base::Unretained(service()), kSuccessValue));
+ NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
+
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_FirstRectForCharacterRange::ID));
+ EXPECT_TRUE(NSEqualRects(kSuccessValue, rect));
+}
+
+TEST_F(TextInputClientMacTest, TimeoutRectForRange) {
+ NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_FirstRectForCharacterRange::ID));
+ EXPECT_TRUE(NSEqualRects(NSZeroRect, rect));
+}
+
+TEST_F(TextInputClientMacTest, GetSubstring) {
+ ScopedTestingThread thread(this);
+ NSDictionary* attributes =
+ [NSDictionary dictionaryWithObject:[NSColor purpleColor]
+ forKey:NSForegroundColorAttributeName];
+ base::scoped_nsobject<NSAttributedString> kSuccessValue(
+ [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur"
+ attributes:attributes]);
+
+ PostTask(FROM_HERE,
+ base::Bind(&TextInputClientMac::SetSubstringAndSignal,
+ base::Unretained(service()),
+ base::Unretained(kSuccessValue.get())));
+ NSAttributedString* string = service()->GetAttributedSubstringFromRange(
+ widget(), NSMakeRange(0, 32));
+
+ EXPECT_NSEQ(kSuccessValue, string);
+ EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy.
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_StringForRange::ID));
+}
+
+TEST_F(TextInputClientMacTest, TimeoutSubstring) {
+ NSAttributedString* string = service()->GetAttributedSubstringFromRange(
+ widget(), NSMakeRange(0, 32));
+ EXPECT_EQ(nil, string);
+ EXPECT_EQ(1U, ipc_sink().message_count());
+ EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
+ TextInputClientMsg_StringForRange::ID));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/text_input_client_message_filter.h b/chromium/content/browser/renderer_host/text_input_client_message_filter.h
new file mode 100644
index 00000000000..2a374df5c5e
--- /dev/null
+++ b/chromium/content/browser/renderer_host/text_input_client_message_filter.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_
+
+#include "content/common/mac/attributed_string_coder.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+class Range;
+}
+
+namespace content {
+
+// This is a browser-side message filter that lives on the IO thread to handle
+// replies to messages sent by the TextInputClientMac. See
+// content/browser/renderer_host/text_input_client_mac.h for more information.
+class CONTENT_EXPORT TextInputClientMessageFilter
+ : public BrowserMessageFilter {
+ public:
+ explicit TextInputClientMessageFilter(int child_id);
+
+ // BrowserMessageFilter override:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ protected:
+ virtual ~TextInputClientMessageFilter();
+
+ private:
+ // IPC Message handlers:
+ void OnGotCharacterIndexForPoint(size_t index);
+ void OnGotFirstRectForRange(const gfx::Rect& rect);
+ void OnGotStringFromRange(
+ const mac::AttributedStringCoder::EncodedString& string);
+
+ // Child process id.
+ int child_process_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextInputClientMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/renderer_host/text_input_client_message_filter.mm b/chromium/content/browser/renderer_host/text_input_client_message_filter.mm
new file mode 100644
index 00000000000..cea0a347d70
--- /dev/null
+++ b/chromium/content/browser/renderer_host/text_input_client_message_filter.mm
@@ -0,0 +1,68 @@
+// 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 "content/browser/renderer_host/text_input_client_message_filter.h"
+
+#include "base/strings/string16.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/text_input_client_mac.h"
+#include "content/common/text_input_client_messages.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "ipc/ipc_message_macros.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+TextInputClientMessageFilter::TextInputClientMessageFilter(int child_id)
+ : BrowserMessageFilter(),
+ child_process_id_(child_id) {
+}
+
+bool TextInputClientMessageFilter::OnMessageReceived(
+ const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(TextInputClientMessageFilter, message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotCharacterIndexForPoint,
+ OnGotCharacterIndexForPoint)
+ IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotFirstRectForRange,
+ OnGotFirstRectForRange)
+ IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotStringForRange,
+ OnGotStringFromRange)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+TextInputClientMessageFilter::~TextInputClientMessageFilter() {}
+
+void TextInputClientMessageFilter::OnGotCharacterIndexForPoint(size_t index) {
+ TextInputClientMac* service = TextInputClientMac::GetInstance();
+ // |index| could be WTF::notFound (-1) and its value is different from
+ // NSNotFound so we need to convert it.
+ if (index == static_cast<size_t>(-1)) {
+ index = NSNotFound;
+ }
+ service->SetCharacterIndexAndSignal(index);
+}
+
+void TextInputClientMessageFilter::OnGotFirstRectForRange(
+ const gfx::Rect& rect) {
+ TextInputClientMac* service = TextInputClientMac::GetInstance();
+ service->SetFirstRectAndSignal(NSRectFromCGRect(rect.ToCGRect()));
+}
+
+void TextInputClientMessageFilter::OnGotStringFromRange(
+ const mac::AttributedStringCoder::EncodedString& encoded_string) {
+ TextInputClientMac* service = TextInputClientMac::GetInstance();
+ NSAttributedString* string =
+ mac::AttributedStringCoder::Decode(&encoded_string);
+ if (![string length])
+ string = nil;
+ service->SetSubstringAndSignal(string);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.cc b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.cc
new file mode 100644
index 00000000000..c504f6c07c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 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 "content/browser/renderer_host/touch_smooth_scroll_gesture_android.h"
+
+#include "base/debug/trace_event.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "jni/SmoothScroller_jni.h"
+
+namespace {
+bool g_jni_initialized = false;
+
+void RegisterNativesIfNeeded(JNIEnv* env) {
+ if (!g_jni_initialized) {
+ content::RegisterNativesImpl(env);
+ g_jni_initialized = true;
+ }
+}
+} // namespace
+
+namespace content {
+
+TouchSmoothScrollGestureAndroid::TouchSmoothScrollGestureAndroid(
+ int pixels_to_scroll,
+ RenderWidgetHost* rwh,
+ base::android::ScopedJavaLocalRef<jobject> java_scroller)
+ : pixels_scrolled_(0),
+ has_started_(false),
+ has_sent_motion_up_(false),
+ pixels_to_scroll_(pixels_to_scroll),
+ rwh_(rwh),
+ java_scroller_(java_scroller) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ RegisterNativesIfNeeded(env);
+}
+
+TouchSmoothScrollGestureAndroid::~TouchSmoothScrollGestureAndroid() {
+}
+
+bool TouchSmoothScrollGestureAndroid::ForwardInputEvents(
+ base::TimeTicks now, RenderWidgetHost* host) {
+ if (!has_started_) {
+ has_started_ = true;
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_SmoothScroller_start(
+ env, java_scroller_.obj(), reinterpret_cast<int>(this));
+ }
+
+ TRACE_COUNTER_ID1(
+ "gpu", "smooth_scroll_by_pixels_scrolled", this, pixels_scrolled_);
+
+ return !has_sent_motion_up_;
+}
+
+double TouchSmoothScrollGestureAndroid::GetScrollDelta(
+ JNIEnv* env, jobject obj, double scale) {
+ double delta = smooth_scroll_calculator_.GetScrollDelta(
+ base::TimeTicks::Now(),
+ RenderWidgetHostImpl::From(rwh_)->GetSyntheticScrollMessageInterval())
+ * scale;
+ pixels_scrolled_ += delta;
+ return delta;
+}
+
+bool TouchSmoothScrollGestureAndroid::HasFinished(JNIEnv* env, jobject obj) {
+ return pixels_scrolled_ >= pixels_to_scroll_;
+}
+
+void TouchSmoothScrollGestureAndroid::SetHasSentMotionUp(
+ JNIEnv* env, jobject obj) {
+ has_sent_motion_up_ = true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.h b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.h
new file mode 100644
index 00000000000..0b892670643
--- /dev/null
+++ b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_android.h
@@ -0,0 +1,52 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_GESTURE_ANDROID_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_GESTURE_ANDROID_H_
+
+#include "base/android/jni_android.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/smooth_scroll_calculator.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+
+namespace content {
+
+class ContentViewCore;
+class RenderWidgetHost;
+
+class TouchSmoothScrollGestureAndroid : public SmoothScrollGesture {
+ public:
+ TouchSmoothScrollGestureAndroid(
+ int pixels_to_scroll,
+ RenderWidgetHost* rwh,
+ base::android::ScopedJavaLocalRef<jobject> java_scroller);
+
+ // Called by the java side once the TimeAnimator ticks.
+ double GetScrollDelta(JNIEnv* env, jobject obj, double scale);
+ bool HasFinished(JNIEnv* env, jobject obj);
+ void SetHasSentMotionUp(JNIEnv* env, jobject obj);
+
+ // SmoothScrollGesture
+ virtual bool ForwardInputEvents(base::TimeTicks now,
+ RenderWidgetHost* host) OVERRIDE;
+
+ private:
+ virtual ~TouchSmoothScrollGestureAndroid();
+
+ SmoothScrollCalculator smooth_scroll_calculator_;
+
+ int pixels_scrolled_;
+ bool has_started_;
+ bool has_sent_motion_up_;
+
+ int pixels_to_scroll_;
+ RenderWidgetHost* rwh_;
+ base::android::ScopedJavaGlobalRef<jobject> java_scroller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchSmoothScrollGestureAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_GESTURE_ANDROID_H_
diff --git a/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.cc b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.cc
new file mode 100644
index 00000000000..ffb1564ded1
--- /dev/null
+++ b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 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 "content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h"
+
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/gfx/transform.h"
+
+namespace {
+
+void InjectTouchEvent(const gfx::Point& location,
+ ui::EventType type,
+ aura::Window* window) {
+ gfx::Point screen_location = location;
+ // First convert the location from Window to RootWindow.
+ aura::RootWindow* root_window = window->GetRootWindow();
+ aura::Window::ConvertPointToTarget(window, root_window, &screen_location);
+ // Then convert the location from RootWindow to screen.
+ root_window->ConvertPointToHost(&screen_location);
+ ui::TouchEvent touch(type, screen_location, 0, 0, ui::EventTimeForNow(),
+ 1.0f, 1.0f, 1.0f, 1.0f);
+ aura::RootWindowHostDelegate* root_window_host_delegate =
+ root_window->AsRootWindowHostDelegate();
+ root_window_host_delegate->OnHostTouchEvent(&touch);
+}
+
+} // namespace
+
+namespace content {
+
+TouchSmoothScrollGestureAura::TouchSmoothScrollGestureAura(bool scroll_down,
+ int pixels_to_scroll,
+ int mouse_event_x,
+ int mouse_event_y,
+ aura::Window* window)
+ : scroll_down_(scroll_down),
+ pixels_to_scroll_(pixels_to_scroll),
+ pixels_scrolled_(0),
+ location_(mouse_event_x, mouse_event_y),
+ window_(window) {
+}
+
+TouchSmoothScrollGestureAura::~TouchSmoothScrollGestureAura() {}
+
+bool TouchSmoothScrollGestureAura::ForwardInputEvents(
+ base::TimeTicks now,
+ RenderWidgetHost* host) {
+ if (pixels_scrolled_ >= pixels_to_scroll_)
+ return false;
+
+ RenderWidgetHostImpl* host_impl = RenderWidgetHostImpl::From(host);
+ double position_delta = smooth_scroll_calculator_.GetScrollDelta(now,
+ host_impl->GetSyntheticScrollMessageInterval());
+
+ if (pixels_scrolled_ == 0) {
+ InjectTouchEvent(location_, ui::ET_TOUCH_PRESSED, window_);
+ }
+
+ location_.Offset(0, scroll_down_ ? -position_delta : position_delta);
+ InjectTouchEvent(location_, ui::ET_TOUCH_MOVED, window_);
+
+ pixels_scrolled_ += abs(position_delta);
+
+ if (pixels_scrolled_ >= pixels_to_scroll_) {
+ InjectTouchEvent(location_, ui::ET_TOUCH_RELEASED, window_);
+ }
+
+ return true;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h
new file mode 100644
index 00000000000..779e02d6dd0
--- /dev/null
+++ b/chromium/content/browser/renderer_host/touch_smooth_scroll_gesture_aura.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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 CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_SCROLL_GESTURE_
+#define CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_SCROLL_GESTURE_
+
+#include "base/time/time.h"
+#include "content/browser/renderer_host/smooth_scroll_calculator.h"
+#include "content/port/browser/smooth_scroll_gesture.h"
+#include "ui/gfx/point.h"
+
+namespace aura {
+class Window;
+}
+
+namespace content {
+
+class TouchSmoothScrollGestureAura : public SmoothScrollGesture {
+ public:
+ TouchSmoothScrollGestureAura(bool scroll_down,
+ int pixels_to_scroll,
+ int mouse_event_x,
+ int mouse_event_y,
+ aura::Window* window);
+ private:
+ virtual ~TouchSmoothScrollGestureAura();
+
+ // Overridden from SmoothScrollGesture.
+ virtual bool ForwardInputEvents(base::TimeTicks now,
+ RenderWidgetHost* host) OVERRIDE;
+
+ bool scroll_down_;
+ int pixels_to_scroll_;
+ int pixels_scrolled_;
+ gfx::Point location_;
+ aura::Window* window_;
+ SmoothScrollCalculator smooth_scroll_calculator_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchSmoothScrollGestureAura);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_TOUCH_SMOOTH_SCROLL_GESTURE_
diff --git a/chromium/content/browser/renderer_host/ui_events_helper.cc b/chromium/content/browser/renderer_host/ui_events_helper.cc
new file mode 100644
index 00000000000..afbbf990176
--- /dev/null
+++ b/chromium/content/browser/renderer_host/ui_events_helper.cc
@@ -0,0 +1,336 @@
+// 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 "content/browser/renderer_host/ui_events_helper.h"
+
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+
+namespace {
+
+int WebModifiersToUIFlags(int modifiers) {
+ int flags = ui::EF_NONE;
+
+ if (modifiers & WebKit::WebInputEvent::ShiftKey)
+ flags |= ui::EF_SHIFT_DOWN;
+ if (modifiers & WebKit::WebInputEvent::ControlKey)
+ flags |= ui::EF_CONTROL_DOWN;
+ if (modifiers & WebKit::WebInputEvent::AltKey)
+ flags |= ui::EF_ALT_DOWN;
+
+ if (modifiers & WebKit::WebInputEvent::LeftButtonDown)
+ flags |= ui::EF_LEFT_MOUSE_BUTTON;
+ if (modifiers & WebKit::WebInputEvent::RightButtonDown)
+ flags |= ui::EF_RIGHT_MOUSE_BUTTON;
+ if (modifiers & WebKit::WebInputEvent::MiddleButtonDown)
+ flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
+
+ if (modifiers & WebKit::WebInputEvent::CapsLockOn)
+ flags |= ui::EF_CAPS_LOCK_DOWN;
+
+ return flags;
+}
+
+ui::EventType WebTouchPointStateToEventType(
+ WebKit::WebTouchPoint::State state) {
+ switch (state) {
+ case WebKit::WebTouchPoint::StateReleased:
+ return ui::ET_TOUCH_RELEASED;
+
+ case WebKit::WebTouchPoint::StatePressed:
+ return ui::ET_TOUCH_PRESSED;
+
+ case WebKit::WebTouchPoint::StateMoved:
+ return ui::ET_TOUCH_MOVED;
+
+ case WebKit::WebTouchPoint::StateCancelled:
+ return ui::ET_TOUCH_CANCELLED;
+
+ default:
+ return ui::ET_UNKNOWN;
+ }
+}
+
+WebKit::WebTouchPoint::State TouchPointStateFromEvent(
+ const ui::TouchEvent& event) {
+ switch (event.type()) {
+ case ui::ET_TOUCH_PRESSED:
+ return WebKit::WebTouchPoint::StatePressed;
+ case ui::ET_TOUCH_RELEASED:
+ return WebKit::WebTouchPoint::StateReleased;
+ case ui::ET_TOUCH_MOVED:
+ return WebKit::WebTouchPoint::StateMoved;
+ case ui::ET_TOUCH_CANCELLED:
+ return WebKit::WebTouchPoint::StateCancelled;
+ default:
+ return WebKit::WebTouchPoint::StateUndefined;
+ }
+}
+
+WebKit::WebInputEvent::Type TouchEventTypeFromEvent(
+ const ui::TouchEvent& event) {
+ switch (event.type()) {
+ case ui::ET_TOUCH_PRESSED:
+ return WebKit::WebInputEvent::TouchStart;
+ case ui::ET_TOUCH_RELEASED:
+ return WebKit::WebInputEvent::TouchEnd;
+ case ui::ET_TOUCH_MOVED:
+ return WebKit::WebInputEvent::TouchMove;
+ case ui::ET_TOUCH_CANCELLED:
+ return WebKit::WebInputEvent::TouchCancel;
+ default:
+ return WebKit::WebInputEvent::Undefined;
+ }
+}
+
+} // namespace
+
+namespace content {
+
+bool MakeUITouchEventsFromWebTouchEvents(
+ const TouchEventWithLatencyInfo& touch_with_latency,
+ ScopedVector<ui::TouchEvent>* list,
+ TouchEventCoordinateSystem coordinate_system) {
+ const WebKit::WebTouchEvent& touch = touch_with_latency.event;
+ ui::EventType type = ui::ET_UNKNOWN;
+ switch (touch.type) {
+ case WebKit::WebInputEvent::TouchStart:
+ type = ui::ET_TOUCH_PRESSED;
+ break;
+ case WebKit::WebInputEvent::TouchEnd:
+ type = ui::ET_TOUCH_RELEASED;
+ break;
+ case WebKit::WebInputEvent::TouchMove:
+ type = ui::ET_TOUCH_MOVED;
+ break;
+ case WebKit::WebInputEvent::TouchCancel:
+ type = ui::ET_TOUCH_CANCELLED;
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ int flags = WebModifiersToUIFlags(touch.modifiers);
+ base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
+ static_cast<int64>(touch.timeStampSeconds * 1000000));
+ for (unsigned i = 0; i < touch.touchesLength; ++i) {
+ const WebKit::WebTouchPoint& point = touch.touches[i];
+ if (WebTouchPointStateToEventType(point.state) != type)
+ continue;
+ // In aura, the touch-event needs to be in the screen coordinate, since the
+ // touch-event is routed to RootWindow first. In Windows, on the other hand,
+ // the touch-event is dispatched directly to the gesture-recognizer, so the
+ // location needs to be in the local coordinate space.
+#if defined(USE_AURA)
+ gfx::Point location;
+ if (coordinate_system == LOCAL_COORDINATES)
+ location = gfx::Point(point.position.x, point.position.y);
+ else
+ location = gfx::Point(point.screenPosition.x, point.screenPosition.y);
+#else
+ gfx::Point location(point.position.x, point.position.y);
+#endif
+ ui::TouchEvent* uievent = new ui::TouchEvent(type,
+ location,
+ flags,
+ point.id,
+ timestamp,
+ point.radiusX,
+ point.radiusY,
+ point.rotationAngle,
+ point.force);
+ uievent->set_latency(touch_with_latency.latency);
+ list->push_back(uievent);
+ }
+ return true;
+}
+
+WebKit::WebGestureEvent MakeWebGestureEventFromUIEvent(
+ const ui::GestureEvent& event) {
+ WebKit::WebGestureEvent gesture_event;
+
+ switch (event.type()) {
+ case ui::ET_GESTURE_TAP:
+ gesture_event.type = WebKit::WebInputEvent::GestureTap;
+ gesture_event.data.tap.tapCount = event.details().tap_count();
+ gesture_event.data.tap.width = event.details().bounding_box().width();
+ gesture_event.data.tap.height = event.details().bounding_box().height();
+ break;
+ case ui::ET_GESTURE_TAP_DOWN:
+ gesture_event.type = WebKit::WebInputEvent::GestureTapDown;
+ gesture_event.data.tapDown.width =
+ event.details().bounding_box().width();
+ gesture_event.data.tapDown.height =
+ event.details().bounding_box().height();
+ break;
+ case ui::ET_GESTURE_TAP_CANCEL:
+ gesture_event.type = WebKit::WebInputEvent::GestureTapCancel;
+ break;
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ gesture_event.type = WebKit::WebInputEvent::GestureScrollBegin;
+ break;
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ gesture_event.type = WebKit::WebInputEvent::GestureScrollUpdate;
+ gesture_event.data.scrollUpdate.deltaX = event.details().scroll_x();
+ gesture_event.data.scrollUpdate.deltaY = event.details().scroll_y();
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ gesture_event.type = WebKit::WebInputEvent::GestureScrollEnd;
+ break;
+ case ui::ET_GESTURE_PINCH_BEGIN:
+ gesture_event.type = WebKit::WebInputEvent::GesturePinchBegin;
+ break;
+ case ui::ET_GESTURE_PINCH_UPDATE:
+ gesture_event.type = WebKit::WebInputEvent::GesturePinchUpdate;
+ gesture_event.data.pinchUpdate.scale = event.details().scale();
+ break;
+ case ui::ET_GESTURE_PINCH_END:
+ gesture_event.type = WebKit::WebInputEvent::GesturePinchEnd;
+ break;
+ case ui::ET_SCROLL_FLING_START:
+ gesture_event.type = WebKit::WebInputEvent::GestureFlingStart;
+ gesture_event.data.flingStart.velocityX = event.details().velocity_x();
+ gesture_event.data.flingStart.velocityY = event.details().velocity_y();
+ break;
+ case ui::ET_SCROLL_FLING_CANCEL:
+ gesture_event.type = WebKit::WebInputEvent::GestureFlingCancel;
+ break;
+ case ui::ET_GESTURE_LONG_PRESS:
+ gesture_event.type = WebKit::WebInputEvent::GestureLongPress;
+ gesture_event.data.longPress.width =
+ event.details().bounding_box().width();
+ gesture_event.data.longPress.height =
+ event.details().bounding_box().height();
+ break;
+ case ui::ET_GESTURE_LONG_TAP:
+ gesture_event.type = WebKit::WebInputEvent::GestureLongTap;
+ gesture_event.data.longPress.width =
+ event.details().bounding_box().width();
+ gesture_event.data.longPress.height =
+ event.details().bounding_box().height();
+ break;
+ case ui::ET_GESTURE_TWO_FINGER_TAP:
+ gesture_event.type = WebKit::WebInputEvent::GestureTwoFingerTap;
+ gesture_event.data.twoFingerTap.firstFingerWidth =
+ event.details().first_finger_width();
+ gesture_event.data.twoFingerTap.firstFingerHeight =
+ event.details().first_finger_height();
+ break;
+ case ui::ET_GESTURE_BEGIN:
+ case ui::ET_GESTURE_END:
+ case ui::ET_GESTURE_MULTIFINGER_SWIPE:
+ gesture_event.type = WebKit::WebInputEvent::Undefined;
+ break;
+ default:
+ NOTREACHED() << "Unknown gesture type: " << event.type();
+ }
+
+ gesture_event.sourceDevice = WebKit::WebGestureEvent::Touchscreen;
+ gesture_event.modifiers = EventFlagsToWebEventModifiers(event.flags());
+ gesture_event.timeStampSeconds = event.time_stamp().InSecondsF();
+
+ return gesture_event;
+}
+
+int EventFlagsToWebEventModifiers(int flags) {
+ int modifiers = 0;
+
+ // Translate AltGr modifier to Ctrl+Alt first.
+ if (flags & ui::EF_ALTGR_DOWN) {
+ modifiers |= WebKit::WebInputEvent::ControlKey |
+ WebKit::WebInputEvent::AltKey;
+ }
+ if (flags & ui::EF_SHIFT_DOWN)
+ modifiers |= WebKit::WebInputEvent::ShiftKey;
+ if (flags & ui::EF_CONTROL_DOWN)
+ modifiers |= WebKit::WebInputEvent::ControlKey;
+ if (flags & ui::EF_ALT_DOWN)
+ modifiers |= WebKit::WebInputEvent::AltKey;
+ // TODO(beng): MetaKey/META_MASK
+ if (flags & ui::EF_LEFT_MOUSE_BUTTON)
+ modifiers |= WebKit::WebInputEvent::LeftButtonDown;
+ if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
+ modifiers |= WebKit::WebInputEvent::MiddleButtonDown;
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
+ modifiers |= WebKit::WebInputEvent::RightButtonDown;
+ if (flags & ui::EF_CAPS_LOCK_DOWN)
+ modifiers |= WebKit::WebInputEvent::CapsLockOn;
+ return modifiers;
+}
+
+WebKit::WebTouchPoint* UpdateWebTouchEventFromUIEvent(
+ const ui::TouchEvent& event,
+ WebKit::WebTouchEvent* web_event) {
+ WebKit::WebTouchPoint* point = NULL;
+ switch (event.type()) {
+ case ui::ET_TOUCH_PRESSED:
+ // Add a new touch point.
+ if (web_event->touchesLength < WebKit::WebTouchEvent::touchesLengthCap) {
+ point = &web_event->touches[web_event->touchesLength++];
+ point->id = event.touch_id();
+ }
+ break;
+ case ui::ET_TOUCH_RELEASED:
+ case ui::ET_TOUCH_CANCELLED:
+ case ui::ET_TOUCH_MOVED: {
+ // The touch point should have been added to the event from an earlier
+ // _PRESSED event. So find that.
+ // At the moment, only a maximum of 4 touch-points are allowed. So a
+ // simple loop should be sufficient.
+ for (unsigned i = 0; i < web_event->touchesLength; ++i) {
+ point = web_event->touches + i;
+ if (point->id == event.touch_id())
+ break;
+ point = NULL;
+ }
+ break;
+ }
+ default:
+ DLOG(WARNING) << "Unknown touch event " << event.type();
+ break;
+ }
+
+ if (!point)
+ return NULL;
+
+ // The spec requires the radii values to be positive (and 1 when unknown).
+ point->radiusX = std::max(1.f, event.radius_x());
+ point->radiusY = std::max(1.f, event.radius_y());
+ point->rotationAngle = event.rotation_angle();
+ point->force = event.force();
+
+ // Update the location and state of the point.
+ point->state = TouchPointStateFromEvent(event);
+ if (point->state == WebKit::WebTouchPoint::StateMoved) {
+ // It is possible for badly written touch drivers to emit Move events even
+ // when the touch location hasn't changed. In such cases, consume the event
+ // and pretend nothing happened.
+ if (point->position.x == event.x() && point->position.y == event.y())
+ return NULL;
+ }
+ point->position.x = event.x();
+ point->position.y = event.y();
+
+ const gfx::Point root_point = event.root_location();
+ point->screenPosition.x = root_point.x();
+ point->screenPosition.y = root_point.y();
+
+ // Mark the rest of the points as stationary.
+ for (unsigned i = 0; i < web_event->touchesLength; ++i) {
+ WebKit::WebTouchPoint* iter = web_event->touches + i;
+ if (iter != point)
+ iter->state = WebKit::WebTouchPoint::StateStationary;
+ }
+
+ // Update the type of the touch event.
+ web_event->type = TouchEventTypeFromEvent(event);
+ web_event->timeStampSeconds = event.time_stamp().InSecondsF();
+ web_event->modifiers = EventFlagsToWebEventModifiers(event.flags());
+
+ return point;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/ui_events_helper.h b/chromium/content/browser/renderer_host/ui_events_helper.h
new file mode 100644
index 00000000000..734a3146ef5
--- /dev/null
+++ b/chromium/content/browser/renderer_host/ui_events_helper.h
@@ -0,0 +1,57 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_UI_EVENTS_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_UI_EVENTS_HELPER_H_
+
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+#include "content/port/browser/event_with_latency_info.h"
+
+namespace WebKit {
+class WebGestureEvent;
+class WebTouchEvent;
+class WebTouchPoint;
+}
+
+namespace ui {
+class GestureEvent;
+class TouchEvent;
+}
+
+namespace content {
+
+enum TouchEventCoordinateSystem {
+ SCREEN_COORDINATES,
+ LOCAL_COORDINATES
+};
+
+// Creates a list of ui::TouchEvents out of a single WebTouchEvent.
+// A WebTouchEvent can contain information about a number of WebTouchPoints,
+// whereas a ui::TouchEvent contains information about a single touch-point. So
+// it is possible to create more than one ui::TouchEvents out of a single
+// WebTouchEvent. All the ui::TouchEvent in the list will carry the same
+// LatencyInfo the WebTouchEvent carries.
+CONTENT_EXPORT bool MakeUITouchEventsFromWebTouchEvents(
+ const TouchEventWithLatencyInfo& touch,
+ ScopedVector<ui::TouchEvent>* list,
+ TouchEventCoordinateSystem coordinate_system);
+
+// Creates a WebGestureEvent from a ui::GestureEvent. Note that it does not
+// populate the event coordinates (i.e. |x|, |y|, |globalX|, and |globalY|). So
+// the caller must populate these fields.
+WebKit::WebGestureEvent MakeWebGestureEventFromUIEvent(
+ const ui::GestureEvent& event);
+
+int EventFlagsToWebEventModifiers(int flags);
+
+// Updates the WebTouchEvent based on the TouchEvent. It returns the updated
+// WebTouchPoint contained in the WebTouchEvent, or NULL if no point was
+// updated.
+WebKit::WebTouchPoint* UpdateWebTouchEventFromUIEvent(
+ const ui::TouchEvent& event,
+ WebKit::WebTouchEvent* web_event);
+}
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_UI_EVENTS_HELPER_H_
diff --git a/chromium/content/browser/renderer_host/web_input_event_aura.cc b/chromium/content/browser/renderer_host/web_input_event_aura.cc
new file mode 100644
index 00000000000..8931f1f5d91
--- /dev/null
+++ b/chromium/content/browser/renderer_host/web_input_event_aura.cc
@@ -0,0 +1,268 @@
+// 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 "content/browser/renderer_host/web_input_event_aura.h"
+
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+
+namespace content {
+
+#if defined(OS_WIN)
+WebKit::WebMouseEvent MakeUntranslatedWebMouseEventFromNativeEvent(
+ base::NativeEvent native_event);
+WebKit::WebMouseWheelEvent MakeUntranslatedWebMouseWheelEventFromNativeEvent(
+ base::NativeEvent native_event);
+WebKit::WebKeyboardEvent MakeWebKeyboardEventFromNativeEvent(
+ base::NativeEvent native_event);
+WebKit::WebGestureEvent MakeWebGestureEventFromNativeEvent(
+ base::NativeEvent native_event);
+#elif defined(USE_X11)
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEventFromAuraEvent(
+ ui::ScrollEvent* event);
+WebKit::WebKeyboardEvent MakeWebKeyboardEventFromAuraEvent(
+ ui::KeyEvent* event);
+WebKit::WebGestureEvent MakeWebGestureEventFromAuraEvent(
+ ui::ScrollEvent* event);
+#else
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEventFromAuraEvent(
+ ui::ScrollEvent* event) {
+ WebKit::WebMouseWheelEvent webkit_event;
+ return webkit_event;
+}
+
+WebKit::WebKeyboardEvent MakeWebKeyboardEventFromAuraEvent(
+ ui::KeyEvent* event) {
+ WebKit::WebKeyboardEvent webkit_event;
+ return webkit_event;
+}
+
+WebKit::WebGestureEvent MakeWebGestureEventFromAuraEvent(
+ ui::ScrollEvent* event) {
+ WebKit::WebGestureEvent webkit_event;
+ return webkit_event;
+}
+
+#endif
+
+WebKit::WebMouseEvent MakeWebMouseEventFromAuraEvent(
+ ui::MouseEvent* event);
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEventFromAuraEvent(
+ ui::MouseWheelEvent* event);
+
+// General approach:
+//
+// ui::Event only carries a subset of possible event data provided to Aura by
+// the host platform. WebKit utilizes a larger subset of that information than
+// Aura itself. WebKit includes some built in cracking functionality that we
+// rely on to obtain this information cleanly and consistently.
+//
+// The only place where an ui::Event's data differs from what the underlying
+// base::NativeEvent would provide is position data, since we would like to
+// provide coordinates relative to the aura::Window that is hosting the
+// renderer, not the top level platform window.
+//
+// The approach is to fully construct a WebKit::WebInputEvent from the
+// ui::Event's base::NativeEvent, and then replace the coordinate fields with
+// the translated values from the ui::Event.
+//
+// The exception is mouse events on linux. The ui::MouseEvent contains enough
+// necessary information to construct a WebMouseEvent. So instead of extracting
+// the information from the XEvent, which can be tricky when supporting both
+// XInput2 and XInput, the WebMouseEvent is constructed from the
+// ui::MouseEvent. This will not be necessary once only XInput2 is supported.
+//
+
+WebKit::WebMouseEvent MakeWebMouseEvent(ui::MouseEvent* event) {
+ // Construct an untranslated event from the platform event data.
+ WebKit::WebMouseEvent webkit_event =
+#if defined(OS_WIN)
+ // On Windows we have WM_ events comming from desktop and pure aura
+ // events comming from metro mode.
+ event->native_event().message ?
+ MakeUntranslatedWebMouseEventFromNativeEvent(event->native_event()) :
+ MakeWebMouseEventFromAuraEvent(event);
+#else
+ MakeWebMouseEventFromAuraEvent(event);
+#endif
+ // Replace the event's coordinate fields with translated position data from
+ // |event|.
+ webkit_event.windowX = webkit_event.x = event->x();
+ webkit_event.windowY = webkit_event.y = event->y();
+
+#if defined(OS_WIN)
+ if (event->native_event().message)
+ return webkit_event;
+#endif
+ const gfx::Point root_point = event->root_location();
+ webkit_event.globalX = root_point.x();
+ webkit_event.globalY = root_point.y();
+
+ return webkit_event;
+}
+
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEvent(ui::MouseWheelEvent* event) {
+#if defined(OS_WIN)
+ // Construct an untranslated event from the platform event data.
+ WebKit::WebMouseWheelEvent webkit_event = event->native_event().message ?
+ MakeUntranslatedWebMouseWheelEventFromNativeEvent(event->native_event()) :
+ MakeWebMouseWheelEventFromAuraEvent(event);
+#else
+ WebKit::WebMouseWheelEvent webkit_event =
+ MakeWebMouseWheelEventFromAuraEvent(event);
+#endif
+
+ // Replace the event's coordinate fields with translated position data from
+ // |event|.
+ webkit_event.windowX = webkit_event.x = event->x();
+ webkit_event.windowY = webkit_event.y = event->y();
+
+ const gfx::Point root_point = event->root_location();
+ webkit_event.globalX = root_point.x();
+ webkit_event.globalY = root_point.y();
+
+ return webkit_event;
+}
+
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEvent(ui::ScrollEvent* event) {
+#if defined(OS_WIN)
+ // Construct an untranslated event from the platform event data.
+ WebKit::WebMouseWheelEvent webkit_event =
+ MakeUntranslatedWebMouseWheelEventFromNativeEvent(event->native_event());
+#else
+ WebKit::WebMouseWheelEvent webkit_event =
+ MakeWebMouseWheelEventFromAuraEvent(event);
+#endif
+
+ // Replace the event's coordinate fields with translated position data from
+ // |event|.
+ webkit_event.windowX = webkit_event.x = event->x();
+ webkit_event.windowY = webkit_event.y = event->y();
+
+ const gfx::Point root_point = event->root_location();
+ webkit_event.globalX = root_point.x();
+ webkit_event.globalY = root_point.y();
+
+ return webkit_event;
+}
+
+WebKit::WebKeyboardEvent MakeWebKeyboardEvent(ui::KeyEvent* event) {
+ // Windows can figure out whether or not to construct a RawKeyDown or a Char
+ // WebInputEvent based on the type of message carried in
+ // event->native_event(). X11 is not so fortunate, there is no separate
+ // translated event type, so DesktopHostLinux sends an extra KeyEvent with
+ // is_char() == true. We need to pass the ui::KeyEvent to the X11 function
+ // to detect this case so the right event type can be constructed.
+#if defined(OS_WIN)
+ // Key events require no translation by the aura system.
+ return MakeWebKeyboardEventFromNativeEvent(event->native_event());
+#else
+ return MakeWebKeyboardEventFromAuraEvent(event);
+#endif
+}
+
+WebKit::WebGestureEvent MakeWebGestureEvent(ui::GestureEvent* event) {
+ WebKit::WebGestureEvent gesture_event;
+#if defined(OS_WIN)
+ if (event->HasNativeEvent())
+ gesture_event = MakeWebGestureEventFromNativeEvent(event->native_event());
+ else
+ gesture_event = MakeWebGestureEventFromUIEvent(*event);
+#else
+ gesture_event = MakeWebGestureEventFromUIEvent(*event);
+#endif
+
+ gesture_event.x = event->x();
+ gesture_event.y = event->y();
+
+ const gfx::Point root_point = event->root_location();
+ gesture_event.globalX = root_point.x();
+ gesture_event.globalY = root_point.y();
+
+ return gesture_event;
+}
+
+WebKit::WebGestureEvent MakeWebGestureEvent(ui::ScrollEvent* event) {
+ WebKit::WebGestureEvent gesture_event;
+
+#if defined(OS_WIN)
+ gesture_event = MakeWebGestureEventFromNativeEvent(event->native_event());
+#else
+ gesture_event = MakeWebGestureEventFromAuraEvent(event);
+#endif
+
+ gesture_event.x = event->x();
+ gesture_event.y = event->y();
+
+ const gfx::Point root_point = event->root_location();
+ gesture_event.globalX = root_point.x();
+ gesture_event.globalY = root_point.y();
+
+ return gesture_event;
+}
+
+WebKit::WebGestureEvent MakeWebGestureEventFlingCancel() {
+ WebKit::WebGestureEvent gesture_event;
+
+ // All other fields are ignored on a GestureFlingCancel event.
+ gesture_event.type = WebKit::WebInputEvent::GestureFlingCancel;
+ gesture_event.sourceDevice = WebKit::WebGestureEvent::Touchpad;
+ return gesture_event;
+}
+
+WebKit::WebMouseEvent MakeWebMouseEventFromAuraEvent(ui::MouseEvent* event) {
+ WebKit::WebMouseEvent webkit_event;
+
+ webkit_event.modifiers = EventFlagsToWebEventModifiers(event->flags());
+ webkit_event.timeStampSeconds = event->time_stamp().InSecondsF();
+
+ webkit_event.button = WebKit::WebMouseEvent::ButtonNone;
+ if (event->flags() & ui::EF_LEFT_MOUSE_BUTTON)
+ webkit_event.button = WebKit::WebMouseEvent::ButtonLeft;
+ if (event->flags() & ui::EF_MIDDLE_MOUSE_BUTTON)
+ webkit_event.button = WebKit::WebMouseEvent::ButtonMiddle;
+ if (event->flags() & ui::EF_RIGHT_MOUSE_BUTTON)
+ webkit_event.button = WebKit::WebMouseEvent::ButtonRight;
+
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ webkit_event.type = WebKit::WebInputEvent::MouseDown;
+ webkit_event.clickCount = event->GetClickCount();
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ webkit_event.type = WebKit::WebInputEvent::MouseUp;
+ break;
+ case ui::ET_MOUSE_ENTERED:
+ case ui::ET_MOUSE_EXITED:
+ case ui::ET_MOUSE_MOVED:
+ case ui::ET_MOUSE_DRAGGED:
+ webkit_event.type = WebKit::WebInputEvent::MouseMove;
+ break;
+ default:
+ NOTIMPLEMENTED() << "Received unexpected event: " << event->type();
+ break;
+ }
+
+ return webkit_event;
+}
+
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEventFromAuraEvent(
+ ui::MouseWheelEvent* event) {
+ WebKit::WebMouseWheelEvent webkit_event;
+
+ webkit_event.type = WebKit::WebInputEvent::MouseWheel;
+ webkit_event.button = WebKit::WebMouseEvent::ButtonNone;
+ webkit_event.modifiers = EventFlagsToWebEventModifiers(event->flags());
+ webkit_event.timeStampSeconds = event->time_stamp().InSecondsF();
+ webkit_event.deltaX = event->x_offset();
+ webkit_event.deltaY = event->y_offset();
+ webkit_event.wheelTicksX = webkit_event.deltaX / kPixelsPerTick;
+ webkit_event.wheelTicksY = webkit_event.deltaY / kPixelsPerTick;
+
+ return webkit_event;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/web_input_event_aura.h b/chromium/content/browser/renderer_host/web_input_event_aura.h
new file mode 100644
index 00000000000..ddc59e0c46f
--- /dev/null
+++ b/chromium/content/browser/renderer_host/web_input_event_aura.h
@@ -0,0 +1,41 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_WEB_INPUT_EVENT_AURA_H_
+#define CONTENT_BROWSER_RENDERER_HOST_WEB_INPUT_EVENT_AURA_H_
+
+#include "content/common/content_export.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace ui {
+class GestureEvent;
+class KeyEvent;
+class MouseEvent;
+class MouseWheelEvent;
+class ScrollEvent;
+class TouchEvent;
+}
+
+namespace content {
+
+// Used for scrolling. This matches Firefox behavior.
+const int kPixelsPerTick = 53;
+
+CONTENT_EXPORT WebKit::WebMouseEvent MakeWebMouseEvent(
+ ui::MouseEvent* event);
+CONTENT_EXPORT WebKit::WebMouseWheelEvent MakeWebMouseWheelEvent(
+ ui::MouseWheelEvent* event);
+CONTENT_EXPORT WebKit::WebMouseWheelEvent MakeWebMouseWheelEvent(
+ ui::ScrollEvent* event);
+CONTENT_EXPORT WebKit::WebKeyboardEvent MakeWebKeyboardEvent(
+ ui::KeyEvent* event);
+CONTENT_EXPORT WebKit::WebGestureEvent MakeWebGestureEvent(
+ ui::GestureEvent* event);
+CONTENT_EXPORT WebKit::WebGestureEvent MakeWebGestureEvent(
+ ui::ScrollEvent* event);
+CONTENT_EXPORT WebKit::WebGestureEvent MakeWebGestureEventFlingCancel();
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_WEB_INPUT_EVENT_AURA_H_
diff --git a/chromium/content/browser/renderer_host/web_input_event_aura_unittest.cc b/chromium/content/browser/renderer_host/web_input_event_aura_unittest.cc
new file mode 100644
index 00000000000..0443c30dad9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/web_input_event_aura_unittest.cc
@@ -0,0 +1,85 @@
+// 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 "content/browser/renderer_host/web_input_event_aura.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/events/event.h"
+
+#if defined(USE_X11)
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include "ui/base/x/x11_util.h"
+#endif
+
+namespace content {
+
+// Checks that MakeWebKeyboardEvent makes a DOM3 spec compliant key event.
+// crbug.com/127142
+TEST(WebInputEventAuraTest, TestMakeWebKeyboardEvent) {
+#if defined(USE_X11)
+ XEvent xev;
+ {
+ // Press Ctrl.
+ ui::InitXKeyEventForTesting(ui::ET_KEY_PRESSED,
+ ui::VKEY_CONTROL,
+ 0, // X does not set ControlMask for KeyPress.
+ &xev);
+ ui::KeyEvent event(&xev, false /* is_char */);
+ WebKit::WebKeyboardEvent webkit_event = MakeWebKeyboardEvent(&event);
+ // However, modifier bit for Control in |webkit_event| should be set.
+ EXPECT_EQ(webkit_event.modifiers, WebKit::WebInputEvent::ControlKey);
+ }
+ {
+ // Release Ctrl.
+ ui::InitXKeyEventForTesting(ui::ET_KEY_RELEASED,
+ ui::VKEY_CONTROL,
+ ControlMask, // X sets the mask for KeyRelease.
+ &xev);
+ ui::KeyEvent event(&xev, false /* is_char */);
+ WebKit::WebKeyboardEvent webkit_event = MakeWebKeyboardEvent(&event);
+ // However, modifier bit for Control in |webkit_event| shouldn't be set.
+ EXPECT_EQ(webkit_event.modifiers, 0);
+ }
+#endif
+}
+
+// Checks that MakeWebKeyboardEvent returns a correct windowsKeyCode.
+TEST(WebInputEventAuraTest, TestMakeWebKeyboardEventWindowsKeyCode) {
+#if defined(USE_X11)
+ XEvent xev;
+ {
+ // Press left Ctrl.
+ ui::InitXKeyEventForTesting(ui::ET_KEY_PRESSED,
+ ui::VKEY_CONTROL,
+ 0, // X does not set ControlMask for KeyPress.
+ &xev);
+ xev.xkey.keycode = XKeysymToKeycode(ui::GetXDisplay(), XK_Control_L);
+ ui::KeyEvent event(&xev, false /* is_char */);
+ WebKit::WebKeyboardEvent webkit_event = MakeWebKeyboardEvent(&event);
+ // ui::VKEY_LCONTROL, instead of ui::VKEY_CONTROL, should be filled.
+ EXPECT_EQ(ui::VKEY_LCONTROL, webkit_event.windowsKeyCode);
+ }
+ {
+ // Press right Ctrl.
+ ui::InitXKeyEventForTesting(ui::ET_KEY_PRESSED,
+ ui::VKEY_CONTROL,
+ 0, // X does not set ControlMask for KeyPress.
+ &xev);
+ xev.xkey.keycode = XKeysymToKeycode(ui::GetXDisplay(), XK_Control_R);
+ ui::KeyEvent event(&xev, false /* is_char */);
+ WebKit::WebKeyboardEvent webkit_event = MakeWebKeyboardEvent(&event);
+ // ui::VKEY_RCONTROL, instead of ui::VKEY_CONTROL, should be filled.
+ EXPECT_EQ(ui::VKEY_RCONTROL, webkit_event.windowsKeyCode);
+ }
+#elif defined(OS_WIN)
+ // TODO(yusukes): Add tests for win_aura once keyboardEvent() in
+ // third_party/WebKit/Source/web/win/WebInputEventFactory.cpp is modified
+ // to return VKEY_[LR]XXX instead of VKEY_XXX.
+ // https://bugs.webkit.org/show_bug.cgi?id=86694
+#endif
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/web_input_event_aurawin.cc b/chromium/content/browser/renderer_host/web_input_event_aurawin.cc
new file mode 100644
index 00000000000..771b79208c9
--- /dev/null
+++ b/chromium/content/browser/renderer_host/web_input_event_aurawin.cc
@@ -0,0 +1,47 @@
+// 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 "content/browser/renderer_host/web_input_event_aura.h"
+
+#include "base/event_types.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/input/web_input_event_builders_win.h"
+
+namespace content {
+
+// On Windows, we can just use the builtin WebKit factory methods to fully
+// construct our pre-translated events.
+
+WebKit::WebMouseEvent MakeUntranslatedWebMouseEventFromNativeEvent(
+ base::NativeEvent native_event) {
+ return WebMouseEventBuilder::Build(native_event.hwnd,
+ native_event.message,
+ native_event.wParam,
+ native_event.lParam);
+}
+
+WebKit::WebMouseWheelEvent MakeUntranslatedWebMouseWheelEventFromNativeEvent(
+ base::NativeEvent native_event) {
+ return WebMouseWheelEventBuilder::Build(native_event.hwnd,
+ native_event.message,
+ native_event.wParam,
+ native_event.lParam);
+}
+
+WebKit::WebKeyboardEvent MakeWebKeyboardEventFromNativeEvent(
+ base::NativeEvent native_event) {
+ return WebKeyboardEventBuilder::Build(native_event.hwnd,
+ native_event.message,
+ native_event.wParam,
+ native_event.lParam);
+}
+
+WebKit::WebGestureEvent MakeWebGestureEventFromNativeEvent(
+ base::NativeEvent native_event) {
+ // TODO: Create gestures from native event.
+ NOTIMPLEMENTED();
+ return WebKit::WebGestureEvent();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/web_input_event_aurax11.cc b/chromium/content/browser/renderer_host/web_input_event_aurax11.cc
new file mode 100644
index 00000000000..fdde17edc7b
--- /dev/null
+++ b/chromium/content/browser/renderer_host/web_input_event_aurax11.cc
@@ -0,0 +1,238 @@
+// 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.
+
+// Portions based heavily on:
+// third_party/WebKit/public/web/gtk/WebInputEventFactory.cpp
+//
+/*
+ * Copyright (C) 2006-2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "content/browser/renderer_host/web_input_event_aura.h"
+
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <cstdlib>
+
+#include "base/event_types.h"
+#include "base/logging.h"
+#include "content/browser/renderer_host/ui_events_helper.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/keycodes/keyboard_code_conversion_x.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace content {
+
+// chromium WebKit does not provide a WebInputEventFactory for X11, so we have
+// to do the work here ourselves.
+
+namespace {
+
+int XKeyEventToWindowsKeyCode(XKeyEvent* event) {
+ int windows_key_code =
+ ui::KeyboardCodeFromXKeyEvent(reinterpret_cast<XEvent*>(event));
+ if (windows_key_code == ui::VKEY_SHIFT ||
+ windows_key_code == ui::VKEY_CONTROL ||
+ windows_key_code == ui::VKEY_MENU) {
+ // To support DOM3 'location' attribute, we need to lookup an X KeySym and
+ // set ui::VKEY_[LR]XXX instead of ui::VKEY_XXX.
+ KeySym keysym = XK_VoidSymbol;
+ XLookupString(event, NULL, 0, &keysym, NULL);
+ switch (keysym) {
+ case XK_Shift_L:
+ return ui::VKEY_LSHIFT;
+ case XK_Shift_R:
+ return ui::VKEY_RSHIFT;
+ case XK_Control_L:
+ return ui::VKEY_LCONTROL;
+ case XK_Control_R:
+ return ui::VKEY_RCONTROL;
+ case XK_Meta_L:
+ case XK_Alt_L:
+ return ui::VKEY_LMENU;
+ case XK_Meta_R:
+ case XK_Alt_R:
+ return ui::VKEY_RMENU;
+ }
+ }
+ return windows_key_code;
+}
+
+// From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp:
+WebKit::WebUChar GetControlCharacter(int windows_key_code, bool shift) {
+ if (windows_key_code >= ui::VKEY_A &&
+ windows_key_code <= ui::VKEY_Z) {
+ // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
+ return windows_key_code - ui::VKEY_A + 1;
+ }
+ if (shift) {
+ // following graphics chars require shift key to input.
+ switch (windows_key_code) {
+ // ctrl-@ maps to \x00 (Null byte)
+ case ui::VKEY_2:
+ return 0;
+ // ctrl-^ maps to \x1E (Record separator, Information separator two)
+ case ui::VKEY_6:
+ return 0x1E;
+ // ctrl-_ maps to \x1F (Unit separator, Information separator one)
+ case ui::VKEY_OEM_MINUS:
+ return 0x1F;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ break;
+ }
+ } else {
+ switch (windows_key_code) {
+ // ctrl-[ maps to \x1B (Escape)
+ case ui::VKEY_OEM_4:
+ return 0x1B;
+ // ctrl-\ maps to \x1C (File separator, Information separator four)
+ case ui::VKEY_OEM_5:
+ return 0x1C;
+ // ctrl-] maps to \x1D (Group separator, Information separator three)
+ case ui::VKEY_OEM_6:
+ return 0x1D;
+ // ctrl-Enter maps to \x0A (Line feed)
+ case ui::VKEY_RETURN:
+ return 0x0A;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+} // namespace
+
+WebKit::WebMouseWheelEvent MakeWebMouseWheelEventFromAuraEvent(
+ ui::ScrollEvent* event) {
+ WebKit::WebMouseWheelEvent webkit_event;
+
+ webkit_event.type = WebKit::WebInputEvent::MouseWheel;
+ webkit_event.button = WebKit::WebMouseEvent::ButtonNone;
+ webkit_event.modifiers = EventFlagsToWebEventModifiers(event->flags());
+ webkit_event.timeStampSeconds = event->time_stamp().InSecondsF();
+ webkit_event.hasPreciseScrollingDeltas = true;
+ webkit_event.deltaX = event->x_offset();
+ if (event->x_offset_ordinal() != 0.f && event->x_offset() != 0.f) {
+ webkit_event.accelerationRatioX =
+ event->x_offset_ordinal() / event->x_offset();
+ }
+ webkit_event.wheelTicksX = webkit_event.deltaX / kPixelsPerTick;
+ webkit_event.deltaY = event->y_offset();
+ webkit_event.wheelTicksY = webkit_event.deltaY / kPixelsPerTick;
+ if (event->y_offset_ordinal() != 0.f && event->y_offset() != 0.f) {
+ webkit_event.accelerationRatioY =
+ event->y_offset_ordinal() / event->y_offset();
+ }
+
+ return webkit_event;
+}
+
+// NOTE: ui::ScrollEvent instances come from the touchpad.
+WebKit::WebGestureEvent MakeWebGestureEventFromAuraEvent(
+ ui::ScrollEvent* event) {
+ WebKit::WebGestureEvent webkit_event;
+
+ switch (event->type()) {
+ case ui::ET_SCROLL_FLING_START:
+ webkit_event.type = WebKit::WebInputEvent::GestureFlingStart;
+ webkit_event.data.flingStart.velocityX = event->x_offset();
+ webkit_event.data.flingStart.velocityY = event->y_offset();
+ break;
+ case ui::ET_SCROLL_FLING_CANCEL:
+ webkit_event.type = WebKit::WebInputEvent::GestureFlingCancel;
+ break;
+ case ui::ET_SCROLL:
+ NOTREACHED() << "Invalid gesture type: " << event->type();
+ break;
+ default:
+ NOTREACHED() << "Unknown gesture type: " << event->type();
+ }
+
+ webkit_event.sourceDevice = WebKit::WebGestureEvent::Touchpad;
+ webkit_event.modifiers = EventFlagsToWebEventModifiers(event->flags());
+ webkit_event.timeStampSeconds = event->time_stamp().InSecondsF();
+
+ return webkit_event;
+}
+
+WebKit::WebKeyboardEvent MakeWebKeyboardEventFromAuraEvent(
+ ui::KeyEvent* event) {
+ base::NativeEvent native_event = event->native_event();
+ WebKit::WebKeyboardEvent webkit_event;
+ XKeyEvent* native_key_event = &native_event->xkey;
+
+ webkit_event.timeStampSeconds = event->time_stamp().InSecondsF();
+ webkit_event.modifiers = EventFlagsToWebEventModifiers(event->flags());
+
+ switch (native_event->type) {
+ case KeyPress:
+ webkit_event.type = event->is_char() ? WebKit::WebInputEvent::Char :
+ WebKit::WebInputEvent::RawKeyDown;
+ break;
+ case KeyRelease:
+ webkit_event.type = WebKit::WebInputEvent::KeyUp;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (webkit_event.modifiers & WebKit::WebInputEvent::AltKey)
+ webkit_event.isSystemKey = true;
+
+ webkit_event.windowsKeyCode = XKeyEventToWindowsKeyCode(native_key_event);
+ webkit_event.nativeKeyCode = native_key_event->keycode;
+
+ if (webkit_event.windowsKeyCode == ui::VKEY_RETURN)
+ webkit_event.unmodifiedText[0] = '\r';
+ else
+ webkit_event.unmodifiedText[0] = ui::GetCharacterFromXEvent(native_event);
+
+ if (webkit_event.modifiers & WebKit::WebInputEvent::ControlKey) {
+ webkit_event.text[0] =
+ GetControlCharacter(
+ webkit_event.windowsKeyCode,
+ webkit_event.modifiers & WebKit::WebInputEvent::ShiftKey);
+ } else {
+ webkit_event.text[0] = webkit_event.unmodifiedText[0];
+ }
+
+ webkit_event.setKeyIdentifierFromWindowsKeyCode();
+
+ // TODO: IsAutoRepeat/IsKeyPad?
+
+ return webkit_event;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/renderer_host/webmenurunner_mac.h b/chromium/content/browser/renderer_host/webmenurunner_mac.h
new file mode 100644
index 00000000000..e1ce91a9e43
--- /dev/null
+++ b/chromium/content/browser/renderer_host/webmenurunner_mac.h
@@ -0,0 +1,61 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_WEBMENURUNNER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_WEBMENURUNNER_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include <vector>
+
+#include "base/mac/scoped_nsobject.h"
+#include "content/public/common/menu_item.h"
+
+
+// WebMenuRunner ---------------------------------------------------------------
+// A class for determining whether an item was selected from an HTML select
+// control, or if the menu was dismissed without making a selection. If a menu
+// item is selected, MenuDelegate is informed and sets a flag which can be
+// queried after the menu has finished running.
+
+@interface WebMenuRunner : NSObject {
+ @private
+ // The native menu control.
+ base::scoped_nsobject<NSMenu> menu_;
+
+ // A flag set to YES if a menu item was chosen, or NO if the menu was
+ // dismissed without selecting an item.
+ BOOL menuItemWasChosen_;
+
+ // The index of the selected menu item.
+ int index_;
+
+ // The font size being used for the menu.
+ CGFloat fontSize_;
+
+ // Whether the menu should be displayed right-aligned.
+ BOOL rightAligned_;
+}
+
+// Initializes the MenuDelegate with a list of items sent from WebKit.
+- (id)initWithItems:(const std::vector<content::MenuItem>&)items
+ fontSize:(CGFloat)fontSize
+ rightAligned:(BOOL)rightAligned;
+
+// Returns YES if an item was selected from the menu, NO if the menu was
+// dismissed.
+- (BOOL)menuItemWasChosen;
+
+// Displays and runs a native popup menu.
+- (void)runMenuInView:(NSView*)view
+ withBounds:(NSRect)bounds
+ initialIndex:(int)index;
+
+// Returns the index of selected menu item, or its initial value (-1) if no item
+// was selected.
+- (int)indexOfSelectedItem;
+
+@end // @interface WebMenuRunner
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_WEBMENURUNNER_MAC_H_
diff --git a/chromium/content/browser/renderer_host/webmenurunner_mac.mm b/chromium/content/browser/renderer_host/webmenurunner_mac.mm
new file mode 100644
index 00000000000..a81d2c9e2f6
--- /dev/null
+++ b/chromium/content/browser/renderer_host/webmenurunner_mac.mm
@@ -0,0 +1,147 @@
+// 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 "content/browser/renderer_host/webmenurunner_mac.h"
+
+#include "base/strings/sys_string_conversions.h"
+
+@interface WebMenuRunner (PrivateAPI)
+
+// Worker function used during initialization.
+- (void)addItem:(const content::MenuItem&)item;
+
+// A callback for the menu controller object to call when an item is selected
+// from the menu. This is not called if the menu is dismissed without a
+// selection.
+- (void)menuItemSelected:(id)sender;
+
+@end // WebMenuRunner (PrivateAPI)
+
+@implementation WebMenuRunner
+
+- (id)initWithItems:(const std::vector<content::MenuItem>&)items
+ fontSize:(CGFloat)fontSize
+ rightAligned:(BOOL)rightAligned {
+ if ((self = [super init])) {
+ menu_.reset([[NSMenu alloc] initWithTitle:@""]);
+ [menu_ setAutoenablesItems:NO];
+ index_ = -1;
+ fontSize_ = fontSize;
+ rightAligned_ = rightAligned;
+ for (size_t i = 0; i < items.size(); ++i)
+ [self addItem:items[i]];
+ }
+ return self;
+}
+
+- (void)addItem:(const content::MenuItem&)item {
+ if (item.type == content::MenuItem::SEPARATOR) {
+ [menu_ addItem:[NSMenuItem separatorItem]];
+ return;
+ }
+
+ NSString* title = base::SysUTF16ToNSString(item.label);
+ NSMenuItem* menuItem = [menu_ addItemWithTitle:title
+ action:@selector(menuItemSelected:)
+ keyEquivalent:@""];
+ if (!item.tool_tip.empty()) {
+ NSString* toolTip = base::SysUTF16ToNSString(item.tool_tip);
+ [menuItem setToolTip:toolTip];
+ }
+ [menuItem setEnabled:(item.enabled && item.type != content::MenuItem::GROUP)];
+ [menuItem setTarget:self];
+
+ // Set various alignment/language attributes. Note that many (if not most) of
+ // these attributes are functional only on 10.6 and above.
+ base::scoped_nsobject<NSMutableDictionary> attrs(
+ [[NSMutableDictionary alloc] initWithCapacity:3]);
+ base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
+ [[NSMutableParagraphStyle alloc] init]);
+ [paragraphStyle setAlignment:rightAligned_ ? NSRightTextAlignment
+ : NSLeftTextAlignment];
+ NSWritingDirection writingDirection =
+ item.rtl ? NSWritingDirectionRightToLeft
+ : NSWritingDirectionLeftToRight;
+ [paragraphStyle setBaseWritingDirection:writingDirection];
+ [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
+
+ if (item.has_directional_override) {
+ base::scoped_nsobject<NSNumber> directionValue(
+ [[NSNumber alloc] initWithInteger:
+ writingDirection + NSTextWritingDirectionOverride]);
+ base::scoped_nsobject<NSArray> directionArray(
+ [[NSArray alloc] initWithObjects:directionValue.get(), nil]);
+ [attrs setObject:directionArray forKey:NSWritingDirectionAttributeName];
+ }
+
+ [attrs setObject:[NSFont menuFontOfSize:fontSize_]
+ forKey:NSFontAttributeName];
+
+ base::scoped_nsobject<NSAttributedString> attrTitle(
+ [[NSAttributedString alloc] initWithString:title attributes:attrs]);
+ [menuItem setAttributedTitle:attrTitle];
+
+ [menuItem setTag:[menu_ numberOfItems] - 1];
+}
+
+// Reflects the result of the user's interaction with the popup menu. If NO, the
+// menu was dismissed without the user choosing an item, which can happen if the
+// user clicked outside the menu region or hit the escape key. If YES, the user
+// selected an item from the menu.
+- (BOOL)menuItemWasChosen {
+ return menuItemWasChosen_;
+}
+
+- (void)menuItemSelected:(id)sender {
+ menuItemWasChosen_ = YES;
+}
+
+- (void)runMenuInView:(NSView*)view
+ withBounds:(NSRect)bounds
+ initialIndex:(int)index {
+ // Set up the button cell, converting to NSView coordinates. The menu is
+ // positioned such that the currently selected menu item appears over the
+ // popup button, which is the expected Mac popup menu behavior.
+ base::scoped_nsobject<NSPopUpButtonCell> cell(
+ [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
+ [cell setMenu:menu_];
+ // We use selectItemWithTag below so if the index is out-of-bounds nothing
+ // bad happens.
+ [cell selectItemWithTag:index];
+
+ if (rightAligned_ &&
+ [cell respondsToSelector:@selector(setUserInterfaceLayoutDirection:)]) {
+ [cell setUserInterfaceLayoutDirection:
+ NSUserInterfaceLayoutDirectionRightToLeft];
+ }
+
+ // When popping up a menu near the Dock, Cocoa restricts the menu
+ // size to not overlap the Dock, with a scroll arrow. Below a
+ // certain point this doesn't work. At that point the menu is
+ // popped up above the element, so that the current item can be
+ // selected without mouse-tracking selecting a different item
+ // immediately.
+ //
+ // Unfortunately, instead of popping up above the passed |bounds|,
+ // it pops up above the bounds of the view passed to inView:. Use a
+ // dummy view to fake this out.
+ base::scoped_nsobject<NSView> dummyView(
+ [[NSView alloc] initWithFrame:bounds]);
+ [view addSubview:dummyView];
+
+ // Display the menu, and set a flag if a menu item was chosen.
+ [cell attachPopUpWithFrame:[dummyView bounds] inView:dummyView];
+ [cell performClickWithFrame:[dummyView bounds] inView:dummyView];
+
+ [dummyView removeFromSuperview];
+
+ if ([self menuItemWasChosen])
+ index_ = [cell indexOfSelectedItem];
+}
+
+- (int)indexOfSelectedItem {
+ return index_;
+}
+
+@end // WebMenuRunner
diff --git a/chromium/content/browser/resolve_proxy_msg_helper.cc b/chromium/content/browser/resolve_proxy_msg_helper.cc
new file mode 100644
index 00000000000..52d0987edb9
--- /dev/null
+++ b/chromium/content/browser/resolve_proxy_msg_helper.cc
@@ -0,0 +1,103 @@
+// 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 "content/browser/resolve_proxy_msg_helper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "content/common/view_messages.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace content {
+
+ResolveProxyMsgHelper::ResolveProxyMsgHelper(
+ net::URLRequestContextGetter* getter)
+ : context_getter_(getter),
+ proxy_service_(NULL) {
+}
+
+ResolveProxyMsgHelper::ResolveProxyMsgHelper(net::ProxyService* proxy_service)
+ : proxy_service_(proxy_service) {
+}
+
+bool ResolveProxyMsgHelper::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(ResolveProxyMsgHelper, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ResolveProxy, OnResolveProxy)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ResolveProxyMsgHelper::OnResolveProxy(const GURL& url,
+ IPC::Message* reply_msg) {
+ // Enqueue the pending request.
+ pending_requests_.push_back(PendingRequest(url, reply_msg));
+
+ // If nothing is in progress, start.
+ if (pending_requests_.size() == 1)
+ StartPendingRequest();
+}
+
+ResolveProxyMsgHelper::~ResolveProxyMsgHelper() {
+ // Clear all pending requests if the ProxyService is still alive (if we have a
+ // default request context or override).
+ if (!pending_requests_.empty()) {
+ PendingRequest req = pending_requests_.front();
+ proxy_service_->CancelPacRequest(req.pac_req);
+ }
+
+ for (PendingRequestList::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();
+ ++it) {
+ delete it->reply_msg;
+ }
+
+ pending_requests_.clear();
+}
+
+void ResolveProxyMsgHelper::OnResolveProxyCompleted(int result) {
+ CHECK(!pending_requests_.empty());
+
+ const PendingRequest& completed_req = pending_requests_.front();
+ ViewHostMsg_ResolveProxy::WriteReplyParams(
+ completed_req.reply_msg, result == net::OK, proxy_info_.ToPacString());
+ Send(completed_req.reply_msg);
+
+ // Clear the current (completed) request.
+ pending_requests_.pop_front();
+
+ // Start the next request.
+ if (!pending_requests_.empty())
+ StartPendingRequest();
+}
+
+void ResolveProxyMsgHelper::StartPendingRequest() {
+ PendingRequest& req = pending_requests_.front();
+
+ // Verify the request wasn't started yet.
+ DCHECK(NULL == req.pac_req);
+
+ if (context_getter_.get()) {
+ proxy_service_ = context_getter_->GetURLRequestContext()->proxy_service();
+ context_getter_ = NULL;
+ }
+
+ // Start the request.
+ int result = proxy_service_->ResolveProxy(
+ req.url, &proxy_info_,
+ base::Bind(&ResolveProxyMsgHelper::OnResolveProxyCompleted,
+ base::Unretained(this)),
+ &req.pac_req, net::BoundNetLog());
+
+ // Completed synchronously.
+ if (result != net::ERR_IO_PENDING)
+ OnResolveProxyCompleted(result);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/resolve_proxy_msg_helper.h b/chromium/content/browser/resolve_proxy_msg_helper.h
new file mode 100644
index 00000000000..a55e6698a3f
--- /dev/null
+++ b/chromium/content/browser/resolve_proxy_msg_helper.h
@@ -0,0 +1,87 @@
+// 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 CONTENT_BROWSER_RESOLVE_PROXY_MSG_HELPER_H_
+#define CONTENT_BROWSER_RESOLVE_PROXY_MSG_HELPER_H_
+
+#include <deque>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_service.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+// Responds to ChildProcessHostMsg_ResolveProxy, kicking off a ProxyResolve
+// request on the IO thread using the specified proxy service. Completion is
+// notified through the delegate. If multiple requests are started at the same
+// time, they will run in FIFO order, with only 1 being outstanding at a time.
+//
+// When an instance of ResolveProxyMsgHelper is destroyed, it cancels any
+// outstanding proxy resolve requests with the proxy service. It also deletes
+// the stored IPC::Message pointers for pending requests.
+//
+// This object is expected to live on the IO thread.
+class CONTENT_EXPORT ResolveProxyMsgHelper : public BrowserMessageFilter {
+ public:
+ explicit ResolveProxyMsgHelper(net::URLRequestContextGetter* getter);
+ // Constructor used by unittests.
+ explicit ResolveProxyMsgHelper(net::ProxyService* proxy_service);
+
+ // BrowserMessageFilter implementation
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ void OnResolveProxy(const GURL& url, IPC::Message* reply_msg);
+
+ protected:
+ // Destruction cancels the current outstanding request, and clears the
+ // pending queue.
+ virtual ~ResolveProxyMsgHelper();
+
+ private:
+ // Callback for the ProxyService (bound to |callback_|).
+ void OnResolveProxyCompleted(int result);
+
+ // Starts the first pending request.
+ void StartPendingRequest();
+
+ // A PendingRequest is a resolve request that is in progress, or queued.
+ struct PendingRequest {
+ public:
+ PendingRequest(const GURL& url, IPC::Message* reply_msg) :
+ url(url), reply_msg(reply_msg), pac_req(NULL) { }
+
+ // The URL of the request.
+ GURL url;
+
+ // Data to pass back to the delegate on completion (we own it until then).
+ IPC::Message* reply_msg;
+
+ // Handle for cancelling the current request if it has started (else NULL).
+ net::ProxyService::PacRequest* pac_req;
+ };
+
+ // Info about the current outstanding proxy request.
+ net::ProxyInfo proxy_info_;
+
+ // FIFO queue of pending requests. The first entry is always the current one.
+ typedef std::deque<PendingRequest> PendingRequestList;
+ PendingRequestList pending_requests_;
+
+ scoped_refptr<net::URLRequestContextGetter> context_getter_;
+ net::ProxyService* proxy_service_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RESOLVE_PROXY_MSG_HELPER_H_
diff --git a/chromium/content/browser/resolve_proxy_msg_helper_unittest.cc b/chromium/content/browser/resolve_proxy_msg_helper_unittest.cc
new file mode 100644
index 00000000000..628df860641
--- /dev/null
+++ b/chromium/content/browser/resolve_proxy_msg_helper_unittest.cc
@@ -0,0 +1,239 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/resolve_proxy_msg_helper.h"
+
+#include "content/browser/browser_thread_impl.h"
+#include "content/common/view_messages.h"
+#include "ipc/ipc_test_sink.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/mock_proxy_resolver.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+// This ProxyConfigService always returns "http://pac" as the PAC url to use.
+class MockProxyConfigService : public net::ProxyConfigService {
+ public:
+ virtual void AddObserver(Observer* observer) OVERRIDE {}
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {}
+ virtual ConfigAvailability GetLatestProxyConfig(
+ net::ProxyConfig* results) OVERRIDE {
+ *results = net::ProxyConfig::CreateFromCustomPacURL(GURL("http://pac"));
+ return CONFIG_VALID;
+ }
+};
+
+class ResolveProxyMsgHelperTest : public testing::Test, public IPC::Listener {
+ public:
+ struct PendingResult {
+ PendingResult(bool result,
+ const std::string& proxy_list)
+ : result(result), proxy_list(proxy_list) {
+ }
+
+ bool result;
+ std::string proxy_list;
+ };
+
+ ResolveProxyMsgHelperTest()
+ : resolver_(new net::MockAsyncProxyResolver),
+ service_(
+ new net::ProxyService(new MockProxyConfigService, resolver_, NULL)),
+ helper_(new ResolveProxyMsgHelper(service_.get())),
+ message_loop_(base::MessageLoop::TYPE_IO),
+ io_thread_(BrowserThread::IO, &message_loop_) {
+ test_sink_.AddFilter(this);
+ helper_->OnFilterAdded(&test_sink_);
+ }
+
+ protected:
+ const PendingResult* pending_result() const { return pending_result_.get(); }
+
+ void clear_pending_result() {
+ pending_result_.reset();
+ }
+
+ IPC::Message* GenerateReply() {
+ bool temp_bool;
+ std::string temp_string;
+ ViewHostMsg_ResolveProxy message(GURL(), &temp_bool, &temp_string);
+ return IPC::SyncMessage::GenerateReply(&message);
+ }
+
+ net::MockAsyncProxyResolver* resolver_;
+ scoped_ptr<net::ProxyService> service_;
+ scoped_refptr<ResolveProxyMsgHelper> helper_;
+ scoped_ptr<PendingResult> pending_result_;
+
+ private:
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE {
+ TupleTypes<ViewHostMsg_ResolveProxy::ReplyParam>::ValueTuple reply_data;
+ EXPECT_TRUE(ViewHostMsg_ResolveProxy::ReadReplyParam(&msg, &reply_data));
+ DCHECK(!pending_result_.get());
+ pending_result_.reset(new PendingResult(reply_data.a, reply_data.b));
+ test_sink_.ClearMessages();
+ return true;
+ }
+
+ base::MessageLoop message_loop_;
+ BrowserThreadImpl io_thread_;
+ IPC::TestSink test_sink_;
+};
+
+// Issue three sequential requests -- each should succeed.
+TEST_F(ResolveProxyMsgHelperTest, Sequential) {
+ GURL url1("http://www.google1.com/");
+ GURL url2("http://www.google2.com/");
+ GURL url3("http://www.google3.com/");
+
+ // Messages are deleted by the sink.
+ IPC::Message* msg1 = GenerateReply();
+ IPC::Message* msg2 = GenerateReply();
+ IPC::Message* msg3 = GenerateReply();
+
+ // Execute each request sequentially (so there are never 2 requests
+ // outstanding at the same time).
+
+ helper_->OnResolveProxy(url1, msg1);
+
+ // Finish ProxyService's initialization.
+ resolver_->pending_set_pac_script_request()->CompleteNow(net::OK);
+
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url1, resolver_->pending_requests()[0]->url());
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result1:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result1:80", pending_result()->proxy_list);
+ clear_pending_result();
+
+ helper_->OnResolveProxy(url2, msg2);
+
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url2, resolver_->pending_requests()[0]->url());
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result2:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result2:80", pending_result()->proxy_list);
+ clear_pending_result();
+
+ helper_->OnResolveProxy(url3, msg3);
+
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url3, resolver_->pending_requests()[0]->url());
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result3:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result3:80", pending_result()->proxy_list);
+ clear_pending_result();
+}
+
+// Issue a request while one is already in progress -- should be queued.
+TEST_F(ResolveProxyMsgHelperTest, QueueRequests) {
+ GURL url1("http://www.google1.com/");
+ GURL url2("http://www.google2.com/");
+ GURL url3("http://www.google3.com/");
+
+ IPC::Message* msg1 = GenerateReply();
+ IPC::Message* msg2 = GenerateReply();
+ IPC::Message* msg3 = GenerateReply();
+
+ // Start three requests. Since the proxy resolver is async, all the
+ // requests will be pending.
+
+ helper_->OnResolveProxy(url1, msg1);
+
+ // Finish ProxyService's initialization.
+ resolver_->pending_set_pac_script_request()->CompleteNow(net::OK);
+
+ helper_->OnResolveProxy(url2, msg2);
+ helper_->OnResolveProxy(url3, msg3);
+
+ // ResolveProxyHelper only keeps 1 request outstanding in ProxyService
+ // at a time.
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url1, resolver_->pending_requests()[0]->url());
+
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result1:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result1:80", pending_result()->proxy_list);
+ clear_pending_result();
+
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url2, resolver_->pending_requests()[0]->url());
+
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result2:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result2:80", pending_result()->proxy_list);
+ clear_pending_result();
+
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url3, resolver_->pending_requests()[0]->url());
+
+ resolver_->pending_requests()[0]->results()->UseNamedProxy("result3:80");
+ resolver_->pending_requests()[0]->CompleteNow(net::OK);
+
+ // Check result.
+ EXPECT_EQ(true, pending_result()->result);
+ EXPECT_EQ("PROXY result3:80", pending_result()->proxy_list);
+ clear_pending_result();
+}
+
+// Delete the helper while a request is in progress, and others are pending.
+TEST_F(ResolveProxyMsgHelperTest, CancelPendingRequests) {
+ GURL url1("http://www.google1.com/");
+ GURL url2("http://www.google2.com/");
+ GURL url3("http://www.google3.com/");
+
+ // They will be deleted by the request's cancellation.
+ IPC::Message* msg1 = GenerateReply();
+ IPC::Message* msg2 = GenerateReply();
+ IPC::Message* msg3 = GenerateReply();
+
+ // Start three requests. Since the proxy resolver is async, all the
+ // requests will be pending.
+
+ helper_->OnResolveProxy(url1, msg1);
+
+ // Finish ProxyService's initialization.
+ resolver_->pending_set_pac_script_request()->CompleteNow(net::OK);
+
+ helper_->OnResolveProxy(url2, msg2);
+ helper_->OnResolveProxy(url3, msg3);
+
+ // ResolveProxyHelper only keeps 1 request outstanding in ProxyService
+ // at a time.
+ ASSERT_EQ(1u, resolver_->pending_requests().size());
+ EXPECT_EQ(url1, resolver_->pending_requests()[0]->url());
+
+ // Delete the underlying ResolveProxyMsgHelper -- this should cancel all
+ // the requests which are outstanding.
+ helper_ = NULL;
+
+ // The pending requests sent to the proxy resolver should have been cancelled.
+
+ EXPECT_EQ(0u, resolver_->pending_requests().size());
+
+ EXPECT_TRUE(pending_result() == NULL);
+
+ // It should also be the case that msg1, msg2, msg3 were deleted by the
+ // cancellation. (Else will show up as a leak in Valgrind).
+}
+
+} // namespace content
diff --git a/chromium/content/browser/resource_context_impl.cc b/chromium/content/browser/resource_context_impl.cc
new file mode 100644
index 00000000000..448015888f0
--- /dev/null
+++ b/chromium/content/browser/resource_context_impl.cc
@@ -0,0 +1,112 @@
+// 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 "content/browser/resource_context_impl.h"
+
+#include "base/logging.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/host_zoom_map_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/browser/webui/url_data_manager_backend.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+
+using base::UserDataAdapter;
+
+namespace content {
+
+namespace {
+
+// Key names on ResourceContext.
+const char kBlobStorageContextKeyName[] = "content_blob_storage_context";
+const char kHostZoomMapKeyName[] = "content_host_zoom_map";
+const char kStreamContextKeyName[] = "content_stream_context";
+const char kURLDataManagerBackendKeyName[] = "url_data_manager_backend";
+
+class NonOwningZoomData : public base::SupportsUserData::Data {
+ public:
+ explicit NonOwningZoomData(HostZoomMap* hzm) : host_zoom_map_(hzm) {}
+ HostZoomMap* host_zoom_map() { return host_zoom_map_; }
+
+ private:
+ HostZoomMap* host_zoom_map_;
+};
+
+} // namespace
+
+
+ResourceContext::ResourceContext() {
+ if (ResourceDispatcherHostImpl::Get())
+ ResourceDispatcherHostImpl::Get()->AddResourceContext(this);
+}
+
+ResourceContext::~ResourceContext() {
+ ResourceDispatcherHostImpl* rdhi = ResourceDispatcherHostImpl::Get();
+ if (rdhi) {
+ rdhi->CancelRequestsForContext(this);
+ rdhi->RemoveResourceContext(this);
+ }
+
+ // In some tests this object is destructed on UI thread.
+ DetachUserDataThread();
+}
+
+ChromeBlobStorageContext* GetChromeBlobStorageContextForResourceContext(
+ ResourceContext* resource_context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return UserDataAdapter<ChromeBlobStorageContext>::Get(
+ resource_context, kBlobStorageContextKeyName);
+}
+
+StreamContext* GetStreamContextForResourceContext(
+ ResourceContext* resource_context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return UserDataAdapter<StreamContext>::Get(
+ resource_context, kStreamContextKeyName);
+}
+
+HostZoomMap* GetHostZoomMapForResourceContext(ResourceContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return static_cast<NonOwningZoomData*>(
+ context->GetUserData(kHostZoomMapKeyName))->host_zoom_map();
+}
+
+URLDataManagerBackend* GetURLDataManagerForResourceContext(
+ ResourceContext* context) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!context->GetUserData(kURLDataManagerBackendKeyName)) {
+ context->SetUserData(kURLDataManagerBackendKeyName,
+ new URLDataManagerBackend());
+ }
+ return static_cast<URLDataManagerBackend*>(
+ context->GetUserData(kURLDataManagerBackendKeyName));
+}
+
+void InitializeResourceContext(BrowserContext* browser_context) {
+ ResourceContext* resource_context = browser_context->GetResourceContext();
+ DCHECK(!resource_context->GetUserData(kHostZoomMapKeyName));
+
+ resource_context->SetUserData(
+ kBlobStorageContextKeyName,
+ new UserDataAdapter<ChromeBlobStorageContext>(
+ ChromeBlobStorageContext::GetFor(browser_context)));
+
+ resource_context->SetUserData(
+ kStreamContextKeyName,
+ new UserDataAdapter<StreamContext>(
+ StreamContext::GetFor(browser_context)));
+
+ // This object is owned by the BrowserContext and not ResourceContext, so
+ // store a non-owning pointer here.
+ resource_context->SetUserData(
+ kHostZoomMapKeyName,
+ new NonOwningZoomData(
+ HostZoomMap::GetForBrowserContext(browser_context)));
+
+ resource_context->DetachUserDataThread();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/resource_context_impl.h b/chromium/content/browser/resource_context_impl.h
new file mode 100644
index 00000000000..ed4de4de4a3
--- /dev/null
+++ b/chromium/content/browser/resource_context_impl.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_BROWSER_RESOURCE_CONTEXT_IMPL_H_
+#define CONTENT_BROWSER_RESOURCE_CONTEXT_IMPL_H_
+
+#include "content/public/browser/resource_context.h"
+
+namespace content {
+
+class ChromeBlobStorageContext;
+class StreamContext;
+class BrowserContext;
+class HostZoomMap;
+class URLDataManagerBackend;
+
+// Getters for objects that are part of BrowserContext which are also used on
+// the IO thread. These are only accessed by content so they're not on the
+// public API.
+
+ChromeBlobStorageContext* GetChromeBlobStorageContextForResourceContext(
+ ResourceContext* resource_context);
+
+StreamContext* GetStreamContextForResourceContext(
+ ResourceContext* resource_context);
+
+HostZoomMap* GetHostZoomMapForResourceContext(ResourceContext* context);
+
+URLDataManagerBackend* GetURLDataManagerForResourceContext(
+ ResourceContext* context);
+
+// Initialize the above data on the ResourceContext from a given BrowserContext.
+void InitializeResourceContext(BrowserContext* browser_context);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RESOURCE_CONTEXT_IMPL_H_
diff --git a/chromium/content/browser/resources/accessibility/accessibility.css b/chromium/content/browser/resources/accessibility/accessibility.css
new file mode 100644
index 00000000000..e00a9223e89
--- /dev/null
+++ b/chromium/content/browser/resources/accessibility/accessibility.css
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 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.
+ */
+
+body {
+ font-family: Arial, sans-serif;
+ font-size: 12px;
+ margin: 10px;
+ min-width: 47em;
+ padding-bottom: 65px;
+}
+
+img {
+ float: left;
+ height: 16px;
+ padding-right: 5px;
+ width: 16px;
+}
+
+.row {
+ border-bottom: 1px solid #A0A0A0;
+ padding: 5px;
+}
+
+.url {
+ color: #A0A0A0;
+}
+
diff --git a/chromium/content/browser/resources/accessibility/accessibility.html b/chromium/content/browser/resources/accessibility/accessibility.html
new file mode 100644
index 00000000000..c1a2d8d0bfc
--- /dev/null
+++ b/chromium/content/browser/resources/accessibility/accessibility.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Copyright (c) 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.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Accessibility</title>
+ <link rel="stylesheet" href="accessibility.css">
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="strings.js"></script>
+ <script src="accessibility.js"></script>
+</head>
+<body>
+ <h1>Accessibility</h1>
+ <div id="global" class="row">Global accessibility mode:
+ <a id="toggle_global" href="#"></a></div>
+ <div id="pages" class="list"></div>
+ <script src="chrome://resources/js/i18n_template2.js"></script>
+</body>
+</html>
diff --git a/chromium/content/browser/resources/accessibility/accessibility.js b/chromium/content/browser/resources/accessibility/accessibility.js
new file mode 100644
index 00000000000..5d98b48c62f
--- /dev/null
+++ b/chromium/content/browser/resources/accessibility/accessibility.js
@@ -0,0 +1,212 @@
+// Copyright (c) 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.
+
+cr.define('accessibility', function() {
+ 'use strict';
+
+ function requestData() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'targets-data.json', false);
+ xhr.send(null);
+ if (xhr.status === 200) {
+ console.log(xhr.responseText);
+ return JSON.parse(xhr.responseText);
+ }
+ return [];
+ }
+
+ // TODO(aboxhall): add a mechanism to request individual and global a11y
+ // mode, xhr them on toggle... or just re-requestData and be smarter about
+ // ID-ing rows?
+
+ function toggleAccessibility(data, element) {
+ chrome.send('toggleAccessibility',
+ [String(data.processId), String(data.routeId)]);
+ var a11y_was_on = (element.textContent.match(/on/) != null);
+ element.textContent = ' accessibility ' + (a11y_was_on ? ' off' : ' on');
+ var row = element.parentElement;
+ if (a11y_was_on) {
+ while (row.lastChild != element)
+ row.removeChild(row.lastChild);
+ } else {
+ row.appendChild(document.createTextNode(' | '));
+ row.appendChild(createShowAccessibilityTreeElement(data, row, false));
+ }
+ }
+
+ function requestAccessibilityTree(data, element) {
+ chrome.send('requestAccessibilityTree',
+ [String(data.processId), String(data.routeId)]);
+ }
+
+ function toggleGlobalAccessibility() {
+ chrome.send('toggleGlobalAccessibility');
+ document.location.reload(); // FIXME see TODO above
+ }
+
+ function initialize() {
+ console.log('initialize');
+ var data = requestData();
+
+ addGlobalAccessibilityModeToggle(data['global_a11y_mode']);
+
+ $('pages').textContent = '';
+
+ var list = data['list'];
+ for (var i = 0; i < list.length; i++) {
+ addToPagesList(list[i]);
+ }
+ }
+
+ function addGlobalAccessibilityModeToggle(global_a11y_mode) {
+ $('toggle_global').textContent = (global_a11y_mode == 0 ? 'off' : 'on');
+ $('toggle_global').addEventListener('click',
+ toggleGlobalAccessibility);
+ }
+
+ function addToPagesList(data) {
+ // TODO: iterate through data and pages rows instead
+ var id = data['processId'] + '.' + data['routeId'];
+ var row = document.createElement('div');
+ row.className = 'row';
+ row.id = id;
+ formatRow(row, data);
+
+ row.processId = data.processId;
+ row.routeId = data.routeId;
+
+ var list = $('pages');
+ list.appendChild(row);
+ }
+
+ function formatRow(row, data) {
+ if (!('url' in data)) {
+ if ('error' in data) {
+ row.appendChild(createErrorMessageElement(data, row));
+ return;
+ }
+ }
+ var properties = ['favicon_url', 'name', 'url'];
+ for (var j = 0; j < properties.length; j++)
+ row.appendChild(formatValue(data, properties[j]));
+
+ row.appendChild(createToggleAccessibilityElement(data));
+ if (data['a11y_mode'] != 0) {
+ row.appendChild(document.createTextNode(' | '));
+ if ('tree' in data) {
+ row.appendChild(createShowAccessibilityTreeElement(data, row, true));
+ row.appendChild(document.createTextNode(' | '));
+ row.appendChild(createHideAccessibilityTreeElement(row.id));
+ row.appendChild(createAccessibilityTreeElement(data));
+ }
+ else {
+ row.appendChild(createShowAccessibilityTreeElement(data, row, false));
+ if ('error' in data)
+ row.appendChild(createErrorMessageElement(data, row));
+ }
+ }
+ }
+
+ function formatValue(data, property) {
+ var value = data[property];
+
+ if (property == 'favicon_url') {
+ var faviconElement = document.createElement('img');
+ if (value)
+ faviconElement.src = value;
+ faviconElement.alt = "";
+ return faviconElement;
+ }
+
+ var text = value ? String(value) : '';
+ if (text.length > 100)
+ text = text.substring(0, 100) + '\u2026'; // ellipsis
+
+ var span = document.createElement('span');
+ span.textContent = ' ' + text + ' ';
+ span.className = property;
+ return span;
+ }
+
+ function createToggleAccessibilityElement(data) {
+ var link = document.createElement('a');
+ link.setAttribute('href', '#');
+ var a11y_mode = data['a11y_mode'];
+ link.textContent = 'accessibility ' + (a11y_mode == 0 ? 'off' : 'on');
+ link.addEventListener('click',
+ toggleAccessibility.bind(this, data, link));
+ return link;
+ }
+
+ function createShowAccessibilityTreeElement(data, row, opt_refresh) {
+ var link = document.createElement('a');
+ link.setAttribute('href', '#');
+ if (opt_refresh)
+ link.textContent = 'refresh accessibility tree';
+ else
+ link.textContent = 'show accessibility tree';
+ link.id = row.id + ':showTree';
+ link.addEventListener('click',
+ requestAccessibilityTree.bind(this, data, link));
+ return link;
+ }
+
+ function createHideAccessibilityTreeElement(id) {
+ var link = document.createElement('a');
+ link.setAttribute('href', '#');
+ link.textContent = 'hide accessibility tree';
+ link.addEventListener('click',
+ function() {
+ $(id + ':showTree').textContent = 'show accessibility tree';
+ var existingTreeElements = $(id).getElementsByTagName('pre');
+ for (var i = 0; i < existingTreeElements.length; i++)
+ $(id).removeChild(existingTreeElements[i]);
+ var row = $(id);
+ while (row.lastChild != $(id + ':showTree'))
+ row.removeChild(row.lastChild);
+ });
+ return link;
+ }
+
+ function createErrorMessageElement(data) {
+ var errorMessageElement = document.createElement('div');
+ var errorMessage = data.error;
+ errorMessageElement.innerHTML = errorMessage + '&nbsp;';
+ var closeLink = document.createElement('a');
+ closeLink.href='#';
+ closeLink.textContent = '[close]';
+ closeLink.addEventListener('click', function() {
+ var parentElement = errorMessageElement.parentElement;
+ parentElement.removeChild(errorMessageElement);
+ if (parentElement.childElementCount == 0)
+ parentElement.parentElement.removeChild(parentElement);
+ });
+ errorMessageElement.appendChild(closeLink);
+ return errorMessageElement;
+ }
+
+ function showTree(data) {
+ var id = data.processId + '.' + data.routeId;
+ var row = $(id);
+ if (!row)
+ return;
+
+ row.textContent = '';
+ formatRow(row, data);
+ }
+
+ function createAccessibilityTreeElement(data) {
+ var treeElement = document.createElement('pre');
+ var tree = data.tree;
+ treeElement.textContent = tree;
+ return treeElement;
+ }
+
+ return {
+ initialize: initialize,
+ showTree: showTree
+ };
+});
+
+document.addEventListener('DOMContentLoaded', accessibility.initialize);
diff --git a/chromium/content/browser/resources/gpu/OWNERS b/chromium/content/browser/resources/gpu/OWNERS
new file mode 100644
index 00000000000..93d1471e1ef
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/OWNERS
@@ -0,0 +1 @@
+nduca@chromium.org
diff --git a/chromium/content/browser/resources/gpu/browser_bridge.js b/chromium/content/browser/resources/gpu/browser_bridge.js
new file mode 100644
index 00000000000..cb4133a3398
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/browser_bridge.js
@@ -0,0 +1,147 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+cr.define('gpu', function() {
+ /**
+ * This class provides a 'bridge' for communicating between javascript and the
+ * browser. When run outside of WebUI, e.g. as a regular webpage, it provides
+ * synthetic data to assist in testing.
+ * @constructor
+ */
+ function BrowserBridge() {
+ // If we are not running inside WebUI, output chrome.send messages
+ // to the console to help with quick-iteration debugging.
+ this.debugMode_ = (chrome.send === undefined && console.log);
+ if (this.debugMode_) {
+ var browserBridgeTests = document.createElement('script');
+ browserBridgeTests.src = './gpu_internals/browser_bridge_tests.js';
+ document.body.appendChild(browserBridgeTests);
+ }
+
+ this.nextRequestId_ = 0;
+ this.pendingCallbacks_ = [];
+ this.logMessages_ = [];
+
+ // Tell c++ code that we are ready to receive GPU Info.
+ if (!this.debugMode_) {
+ chrome.send('browserBridgeInitialized');
+ this.beginRequestClientInfo_();
+ this.beginRequestLogMessages_();
+ }
+ }
+
+ BrowserBridge.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ applySimulatedData_: function applySimulatedData(data) {
+ // set up things according to the simulated data
+ this.gpuInfo_ = data.gpuInfo;
+ this.clientInfo_ = data.clientInfo;
+ this.logMessages_ = data.logMessages;
+ cr.dispatchSimpleEvent(this, 'gpuInfoUpdate');
+ cr.dispatchSimpleEvent(this, 'clientInfoChange');
+ cr.dispatchSimpleEvent(this, 'logMessagesChange');
+ },
+
+ /**
+ * Returns true if the page is hosted inside Chrome WebUI
+ * Helps have behavior conditional to emulate_webui.py
+ */
+ get debugMode() {
+ return this.debugMode_;
+ },
+
+ /**
+ * Sends a message to the browser with specified args. The
+ * browser will reply asynchronously via the provided callback.
+ */
+ callAsync: function(submessage, args, callback) {
+ var requestId = this.nextRequestId_;
+ this.nextRequestId_ += 1;
+ this.pendingCallbacks_[requestId] = callback;
+ if (!args) {
+ chrome.send('callAsync', [requestId.toString(), submessage]);
+ } else {
+ var allArgs = [requestId.toString(), submessage].concat(args);
+ chrome.send('callAsync', allArgs);
+ }
+ },
+
+ /**
+ * Called by gpu c++ code when client info is ready.
+ */
+ onCallAsyncReply: function(requestId, args) {
+ if (this.pendingCallbacks_[requestId] === undefined) {
+ throw new Error('requestId ' + requestId + ' is not pending');
+ }
+ var callback = this.pendingCallbacks_[requestId];
+ callback(args);
+ delete this.pendingCallbacks_[requestId];
+ },
+
+ /**
+ * Get gpuInfo data.
+ */
+ get gpuInfo() {
+ return this.gpuInfo_;
+ },
+
+ /**
+ * Called from gpu c++ code when GPU Info is updated.
+ */
+ onGpuInfoUpdate: function(gpuInfo) {
+ this.gpuInfo_ = gpuInfo;
+ cr.dispatchSimpleEvent(this, 'gpuInfoUpdate');
+ },
+
+ /**
+ * This function begins a request for the ClientInfo. If it comes back
+ * as undefined, then we will issue the request again in 250ms.
+ */
+ beginRequestClientInfo_: function() {
+ this.callAsync('requestClientInfo', undefined, (function(data) {
+ if (data === undefined) { // try again in 250 ms
+ window.setTimeout(this.beginRequestClientInfo_.bind(this), 250);
+ } else {
+ this.clientInfo_ = data;
+ cr.dispatchSimpleEvent(this, 'clientInfoChange');
+ }
+ }).bind(this));
+ },
+
+ /**
+ * Returns information about the currently running Chrome build.
+ */
+ get clientInfo() {
+ return this.clientInfo_;
+ },
+
+ /**
+ * This function checks for new GPU_LOG messages.
+ * If any are found, a refresh is triggered.
+ */
+ beginRequestLogMessages_: function() {
+ this.callAsync('requestLogMessages', undefined,
+ (function(messages) {
+ if (messages.length != this.logMessages_.length) {
+ this.logMessages_ = messages;
+ cr.dispatchSimpleEvent(this, 'logMessagesChange');
+ }
+ // check again in 250 ms
+ window.setTimeout(this.beginRequestLogMessages_.bind(this), 250);
+ }).bind(this));
+ },
+
+ /**
+ * Returns an array of log messages issued by the GPU process, if any.
+ */
+ get logMessages() {
+ return this.logMessages_;
+ },
+
+ };
+
+ return {
+ BrowserBridge: BrowserBridge
+ };
+});
diff --git a/chromium/content/browser/resources/gpu/browser_bridge_tests.js b/chromium/content/browser/resources/gpu/browser_bridge_tests.js
new file mode 100644
index 00000000000..ebff6a775ce
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/browser_bridge_tests.js
@@ -0,0 +1,346 @@
+// 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.
+var commandLineFlags = ['--flag-switches-begin',
+ '--show-composited-layer-borders',
+ '--show-fps-counter',
+ '--flag-switches-end'];
+var commandLineStr = './out/Debug/chrome ' + commandLineFlags.join(' ');
+
+var glValueArray = ['GL_ARB_compatibility',
+ 'GL_ARB_copy_buffer',
+ 'GL_ARB_depth_buffer_float',
+ 'GL_ARB_depth_clamp',
+ 'GL_ARB_depth_texture',
+ 'GL_ARB_draw_buffers',
+ 'GL_ARB_draw_elements_base_vertex',
+ 'GL_ARB_draw_instanced',
+ 'GL_ARB_fragment_coord_conventions',
+ 'GL_ARB_fragment_program',
+ 'GL_ARB_fragment_program_shadow',
+ 'GL_ARB_fragment_shader',
+ 'GL_ARB_framebuffer_object',
+ 'GL_ARB_framebuffer_sRGB',
+ 'GL_ARB_geometry_shader4',
+ 'GL_ARB_half_float_pixel',
+ 'GL_ARB_half_float_vertex',
+ 'GL_ARB_imaging',
+ 'GL_ARB_map_buffer_range',
+ 'GL_ARB_multisample',
+ 'GL_ARB_multitexture',
+ 'GL_ARB_occlusion_query',
+ 'GL_ARB_pixel_buffer_object',
+ 'GL_ARB_point_parameters',
+ 'GL_ARB_point_sprite',
+ 'GL_ARB_provoking_vertex',
+ 'GL_ARB_seamless_cube_map',
+ 'GL_ARB_shader_objects',
+ 'GL_ARB_shading_language_100',
+ 'GL_ARB_shadow',
+ 'GL_ARB_sync',
+ 'GL_ARB_texture_border_clamp',
+ 'GL_ARB_texture_buffer_object',
+ 'GL_ARB_texture_compression',
+ 'GL_ARB_texture_compression_rgtc',
+ 'GL_ARB_texture_cube_map',
+ 'GL_ARB_texture_env_add',
+ 'GL_ARB_texture_env_combine',
+ 'GL_ARB_texture_env_crossbar',
+ 'GL_ARB_texture_env_dot3',
+ 'GL_ARB_texture_float',
+ 'GL_ARB_texture_mirrored_repeat',
+ 'GL_ARB_texture_multisample',
+ 'GL_ARB_texture_non_power_of_two',
+ 'GL_ARB_texture_rectangle',
+ 'GL_ARB_texture_rg',
+ 'GL_ARB_transpose_matrix',
+ 'GL_ARB_uniform_buffer_object',
+ 'GL_ARB_vertex_array_bgra',
+ 'GL_ARB_vertex_array_object',
+ 'GL_ARB_vertex_buffer_object',
+ 'GL_ARB_vertex_program',
+ 'GL_ARB_vertex_shader',
+ 'GL_ARB_window_pos',
+ 'GL_ATI_draw_buffers',
+ 'GL_ATI_texture_float',
+ 'GL_ATI_texture_mirror_once',
+ 'GL_S3_s3tc',
+ 'GL_EXT_texture_env_add',
+ 'GL_EXT_abgr',
+ 'GL_EXT_bgra',
+ 'GL_EXT_bindable_uniform',
+ 'GL_EXT_blend_color',
+ 'GL_EXT_blend_equation_separate',
+ 'GL_EXT_blend_func_separate',
+ 'GL_EXT_blend_minmax',
+ 'GL_EXT_blend_subtract',
+ 'GL_EXT_compiled_vertex_array',
+ 'GL_EXT_Cg_shader',
+ 'GL_EXT_depth_bounds_test',
+ 'GL_EXT_direct_state_access',
+ 'GL_EXT_draw_buffers2',
+ 'GL_EXT_draw_instanced',
+ 'GL_EXT_draw_range_elements',
+ 'GL_EXT_fog_coord',
+ 'GL_EXT_framebuffer_blit',
+ 'GL_EXT_framebuffer_multisample',
+ 'GL_EXTX_framebuffer_mixed_formats',
+ 'GL_EXT_framebuffer_object',
+ 'GL_EXT_framebuffer_sRGB',
+ 'GL_EXT_geometry_shader4',
+ 'GL_EXT_gpu_program_parameters',
+ 'GL_EXT_gpu_shader4',
+ 'GL_EXT_multi_draw_arrays',
+ 'GL_EXT_packed_depth_stencil',
+ 'GL_EXT_packed_float',
+ 'GL_EXT_packed_pixels',
+ 'GL_EXT_pixel_buffer_object',
+ 'GL_EXT_point_parameters',
+ 'GL_EXT_provoking_vertex',
+ 'GL_EXT_rescale_normal',
+ 'GL_EXT_secondary_color',
+ 'GL_EXT_separate_shader_objects',
+ 'GL_EXT_separate_specular_color',
+ 'GL_EXT_shadow_funcs',
+ 'GL_EXT_stencil_two_side',
+ 'GL_EXT_stencil_wrap',
+ 'GL_EXT_texture3D',
+ 'GL_EXT_texture_array',
+ 'GL_EXT_texture_buffer_object',
+ 'GL_EXT_texture_compression_latc',
+ 'GL_EXT_texture_compression_rgtc',
+ 'GL_EXT_texture_compression_s3tc',
+ 'GL_EXT_texture_cube_map',
+ 'GL_EXT_texture_edge_clamp',
+ 'GL_EXT_texture_env_combine',
+ 'GL_EXT_texture_env_dot3',
+ 'GL_EXT_texture_filter_anisotropic',
+ 'GL_EXT_texture_integer',
+ 'GL_EXT_texture_lod',
+ 'GL_EXT_texture_lod_bias',
+ 'GL_EXT_texture_mirror_clamp',
+ 'GL_EXT_texture_object',
+ 'GL_EXT_texture_shared_exponent',
+ 'GL_EXT_texture_sRGB',
+ 'GL_EXT_texture_swizzle',
+ 'GL_EXT_timer_query',
+ 'GL_EXT_vertex_array',
+ 'GL_EXT_vertex_array_bgra',
+ 'GL_IBM_rasterpos_clip',
+ 'GL_IBM_texture_mirrored_repeat',
+ 'GL_KTX_buffer_region',
+ 'GL_NV_blend_square',
+ 'GL_NV_conditional_render',
+ 'GL_NV_copy_depth_to_color',
+ 'GL_NV_copy_image',
+ 'GL_NV_depth_buffer_float',
+ 'GL_NV_depth_clamp',
+ 'GL_NV_explicit_multisample',
+ 'GL_NV_fence',
+ 'GL_NV_float_buffer',
+ 'GL_NV_fog_distance',
+ 'GL_NV_fragment_program',
+ 'GL_NV_fragment_program_option',
+ 'GL_NV_fragment_program2',
+ 'GL_NV_framebuffer_multisample_coverage',
+ 'GL_NV_geometry_shader4',
+ 'GL_NV_gpu_program4',
+ 'GL_NV_half_float',
+ 'GL_NV_light_max_exponent',
+ 'GL_NV_multisample_coverage',
+ 'GL_NV_multisample_filter_hint',
+ 'GL_NV_occlusion_query',
+ 'GL_NV_packed_depth_stencil',
+ 'GL_NV_parameter_buffer_object',
+ 'GL_NV_parameter_buffer_object2',
+ 'GL_NV_pixel_data_range',
+ 'GL_NV_point_sprite',
+ 'GL_NV_primitive_restart',
+ 'GL_NV_register_combiners',
+ 'GL_NV_register_combiners2',
+ 'GL_NV_shader_buffer_load',
+ 'GL_NV_texgen_reflection',
+ 'GL_NV_texture_barrier',
+ 'GL_NV_texture_compression_vtc',
+ 'GL_NV_texture_env_combine4',
+ 'GL_NV_texture_expand_normal',
+ 'GL_NV_texture_rectangle',
+ 'GL_NV_texture_shader',
+ 'GL_NV_texture_shader2',
+ 'GL_NV_texture_shader3',
+ 'GL_NV_transform_feedback',
+ 'GL_NV_vertex_array_range',
+ 'GL_NV_vertex_array_range2',
+ 'GL_NV_vertex_buffer_unified_memory',
+ 'GL_NV_vertex_program',
+ 'GL_NV_vertex_program1_1',
+ 'GL_NV_vertex_program2',
+ 'GL_NV_vertex_program2_option',
+ 'GL_NV_vertex_program3',
+ 'GL_NVX_conditional_render',
+ 'GL_NVX_gpu_memory_info',
+ 'GL_SGIS_generate_mipmap',
+ 'GL_SGIS_texture_lod',
+ 'GL_SGIX_depth_texture',
+ 'GL_SGIX_shadow',
+ 'GL_SUN_slice_accum'];
+(function() {
+ var dataSets = [
+ {
+ name: 'full_data_linux',
+ gpuInfo: {
+ basic_info: [
+ {
+ description: 'Initialization time',
+ value: '111'
+ },
+ {
+ description: 'Vendor Id',
+ value: '0x10de'
+ },
+ {
+ description: 'Device Id',
+ value: '0x0658'
+ },
+ {
+ description: 'Driver vendor',
+ value: 'NVIDIA'
+ },
+ {
+ description: 'Driver version',
+ value: '195.36.24'
+ },
+ {
+ description: 'Driver date',
+ value: ''
+ },
+ {
+ description: 'Pixel shader version',
+ value: '1.50'
+ },
+ {
+ description: 'Vertex shader version',
+ value: '1.50'
+ },
+ {
+ description: 'GL version',
+ value: '3.2'
+ },
+ {
+ description: 'GL_VENDOR',
+ value: 'NVIDIA Corporation'
+ },
+ {
+ description: 'GL_RENDERER',
+ value: 'Quadro FX 380/PCI/SSE2'
+ },
+ {
+ description: 'GL_VERSION',
+ value: '3.2.0 NVIDIA 195.36.24'
+ },
+ {
+ description: 'GL_EXTENSIONS',
+ value: glValueArray.join(' '),
+ }
+ ],
+ featureStatus: {
+ featureStatus:
+ [
+ {'status': 'enabled', name: '2d_canvas'},
+ {'status': 'enabled', name: '3d_css'},
+ {'status': 'enabled', name: 'compositing'},
+ {'status': 'enabled', name: 'webgl'},
+ {'status': 'enabled', name: 'multisampling'}
+ ],
+ problems: []
+ }
+ },
+ clientInfo: {
+ blacklist_version: '1.10',
+ command_line: commandLineStr,
+ version: 'Chrome/12.0.729.0',
+ },
+ logMessages: []
+ },
+ {
+ name: 'no_data',
+ gpuInfo: undefined,
+ clientInfo: undefined,
+ logMessages: undefined
+ },
+ {
+ name: 'logs',
+ gpuInfo: undefined,
+ clientInfo: undefined,
+ logMessages: [
+ {header: 'foo', message: 'Bar'}
+ ]
+ },
+
+ // tests for 'status'
+ {
+ name: 'feature_states',
+ gpuInfo: {
+ basic_info: undefined,
+ featureStatus: {
+ featureStatus: [
+ {'status': 'disabled_off', name: '2d_canvas'},
+ {'status': 'unavailable_software', name: '3d_css'},
+ {'status': 'disabled_software', name: 'compositing'},
+ {'status': 'software', name: 'compositing'},
+ {'status': 'unavailable_off', name: 'webgl'},
+ {'status': 'enabled', name: 'multisampling'}
+ ],
+ problems: [
+ {
+ description: 'Something wrong',
+ crBugs: [],
+ webkitBugs: []
+ },
+ {
+ description: 'SomethingElse',
+ crBugs: [],
+ webkitBugs: []
+ },
+ {
+ description: 'WebKit and Chrome bug',
+ crBugs: [23456],
+ webkitBugs: [789, 2123]
+ }
+ ]
+ }
+ },
+ clientInfo: undefined,
+ logMessages: []
+ }
+
+ ];
+
+ var selectEl = document.createElement('select');
+ for (var i = 0; i < dataSets.length; ++i) {
+ var optionEl = document.createElement('option');
+ optionEl.textContent = dataSets[i].name;
+ optionEl.dataSet = dataSets[i];
+ selectEl.add(optionEl);
+ }
+ selectEl.addEventListener('change', function() {
+ browserBridge.applySimulatedData_(dataSets[selectEl.selectedIndex]);
+ });
+ selectEl.addEventListener('keydown', function() {
+ window.setTimeout(function() {
+ browserBridge.applySimulatedData_(dataSets[selectEl.selectedIndex]);
+ }, 0);
+ });
+
+ var controlEl = document.createElement('div');
+ var textEl = document.createElement('span');
+ textEl.textContent = 'GPU Info:';
+ controlEl.appendChild(textEl);
+ controlEl.appendChild(selectEl);
+
+ document.querySelector('#debug-div').appendChild(controlEl,
+ document.body.firstChild);
+
+ browserBridge.applySimulatedData_(dataSets[0]);
+
+ })();
diff --git a/chromium/content/browser/resources/gpu/gpu_internals.html b/chromium/content/browser/resources/gpu/gpu_internals.html
new file mode 100644
index 00000000000..ad59aa817aa
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/gpu_internals.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+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.
+-->
+<head i18n-values="dir:textdirection;">
+<style>
+* {
+ box-sizing: border-box;
+ -webkit-user-select: none;
+}
+
+body {
+ cursor: default;
+ font-family: sans-serif;
+ padding: 0;
+}
+
+#debug-div {
+ display: -webkit-box;
+ position: fixed;
+ top: 0px;
+ left: 50%;
+ border: 1px solid red;
+}
+
+tabbox tabpanels {
+ padding: 10px;
+}
+
+</style>
+<link rel="stylesheet" href="info_view.css">
+<link rel="stylesheet" href="chrome://resources/css/tabs.css">
+<link rel="stylesheet" href="chrome://resources/css/widgets.css">
+<script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/cr/event_target.js"></script>
+<script src="chrome://resources/js/cr/ui.js"></script>
+<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
+<script src="chrome://resources/js/cr/ui/tabs.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<script src="chrome://gpu/gpu_internals.js"></script>
+<script src="chrome://gpu/strings.js"></script>
+</head>
+<body>
+ <div id="debug-div">
+ </div>
+ <include src="info_view.html">
+ <script src="chrome://resources/js/i18n_template.js"></script>
+ <script src="chrome://resources/js/i18n_process.js"></script>
+ <script src="chrome://resources/js/jstemplate_compiled.js"></script>
+</body>
+</html>
diff --git a/chromium/content/browser/resources/gpu/gpu_internals.js b/chromium/content/browser/resources/gpu/gpu_internals.js
new file mode 100644
index 00000000000..49b43e2553e
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/gpu_internals.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 The Chromium 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 src="browser_bridge.js"/>
+<include src="info_view.js"/>
+
+var browserBridge;
+
+/**
+ * Main entry point. called once the page has loaded.
+ */
+function onLoad() {
+ browserBridge = new gpu.BrowserBridge();
+
+ // Create the views.
+ cr.ui.decorate('#info-view', gpu.InfoView);
+}
+
+document.addEventListener('DOMContentLoaded', onLoad);
diff --git a/chromium/content/browser/resources/gpu/info_view.css b/chromium/content/browser/resources/gpu/info_view.css
new file mode 100644
index 00000000000..867690952cf
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/info_view.css
@@ -0,0 +1,58 @@
+/* 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. */
+
+#info-view {
+ -webkit-box-flex: 1;
+ overflow: auto;
+ padding: 10px;
+}
+
+#info-view * {
+ -webkit-user-select: text;
+}
+
+#info-view[selected] {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+}
+
+#info-view h3,
+#info-view ul {
+ -webkit-margin-after: 0;
+ -webkit-margin-before: 0;
+}
+
+#info-view > div {
+ -webkit-margin-after: 1em;
+}
+
+#info-view .row-title {
+ font-weight: bold;
+}
+
+#info-view table {
+ border-collapse: collapse;
+ cursor: text;
+}
+
+#info-view table,
+#info-view th,
+#info-view td {
+ border: 1px solid #777;
+ padding-left: 4px;
+ padding-right: 4px;
+ text-align: top;
+}
+
+#info-view .feature-green {
+ color: rgb(0, 128, 0);
+}
+
+#info-view .feature-yellow {
+ color: rgb(128, 128, 0);
+}
+
+#info-view .feature-red {
+ color: rgb(255, 0, 0);
+}
diff --git a/chromium/content/browser/resources/gpu/info_view.html b/chromium/content/browser/resources/gpu/info_view.html
new file mode 100644
index 00000000000..5a4d9f4acc9
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/info_view.html
@@ -0,0 +1,73 @@
+<!--
+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.
+-->
+<tabpanel id="info-view">
+ <div>
+ <h3>Graphics Feature Status</h3>
+ <ul class="feature-status-list">
+ </ul>
+ </div>
+ <div class='problems-div'>
+ <h3>Problems Detected</h3>
+ <ul class="problems-list">
+ </ul>
+ </div>
+
+ <div class='workarounds-div'>
+ <h3>Driver Bug Workarounds</h3>
+ <ul class="workarounds-list">
+ </ul>
+ </div>
+
+ <div>
+ <h3>Version Information</h3>
+ <div id="client-info"></div>
+ </div>
+
+ <div class="performance-div">
+ <h3>Performance Information</h3>
+ <div id="performance-info"></div>
+ </div>
+
+ <div>
+ <h3>Driver Information</h3>
+ <div id="basic-info"></div>
+ </div>
+
+ <div class="diagnostics">
+ <h3>Diagnostics</h3>
+ <div class="diagnostics-loading">... loading ...</div>
+ <div id="diagnostics-table">None</div>
+ </div>
+
+ <div id="log-messages" jsdisplay="values.length">
+ <h3>Log Messages</h3>
+ <ul>
+ <li jsselect="values">
+ <span jscontent="header"></span>: <span jscontent="message"></span>
+ </li>
+ </ul>
+ </div>
+
+ <!-- templates -->
+ <div style="display:none">
+ <div id="info-view-table-template">
+ <table id="info-view-table">
+ <tr jsselect="value">
+ <td jsdisplay="!(value instanceof Array)">
+ <span class="row-title" jscontent="description">title</span>
+ </td>
+ <td jsdisplay="!(value instanceof Array)">
+ <span jscontent="value">value</span>
+ </td>
+ <td jsdisplay="value instanceof Array" colspan=2>
+ <span jscontent="description" class="row-title"></span>
+ <div transclude="info-view-table-template"></div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</tabpanel>
diff --git a/chromium/content/browser/resources/gpu/info_view.js b/chromium/content/browser/resources/gpu/info_view.js
new file mode 100644
index 00000000000..d7d21d5a749
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/info_view.js
@@ -0,0 +1,321 @@
+// 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.
+
+
+/**
+ * @fileoverview This view displays information on the current GPU
+ * hardware. Its primary usefulness is to allow users to copy-paste
+ * their data in an easy to read format for bug reports.
+ */
+cr.define('gpu', function() {
+ /**
+ * Provides information on the GPU process and underlying graphics hardware.
+ * @constructor
+ * @extends {cr.ui.TabPanel}
+ */
+ var InfoView = cr.ui.define(cr.ui.TabPanel);
+
+ InfoView.prototype = {
+ __proto__: cr.ui.TabPanel.prototype,
+
+ decorate: function() {
+ cr.ui.TabPanel.prototype.decorate.apply(this);
+
+ browserBridge.addEventListener('gpuInfoUpdate', this.refresh.bind(this));
+ browserBridge.addEventListener('logMessagesChange',
+ this.refresh.bind(this));
+ browserBridge.addEventListener('clientInfoChange',
+ this.refresh.bind(this));
+ this.refresh();
+ },
+
+ /**
+ * Updates the view based on its currently known data
+ */
+ refresh: function(data) {
+ // Client info
+ if (browserBridge.clientInfo) {
+ var clientInfo = browserBridge.clientInfo;
+ this.setTable_('client-info', [
+ {
+ description: 'Data exported',
+ value: (new Date()).toLocaleString()
+ },
+ {
+ description: 'Chrome version',
+ value: clientInfo.version
+ },
+ {
+ description: 'Operating system',
+ value: clientInfo.operating_system
+ },
+ {
+ description: 'Software rendering list version',
+ value: clientInfo.blacklist_version
+ },
+ {
+ description: 'Driver bug list version',
+ value: clientInfo.driver_bug_list_version
+ },
+ {
+ description: 'ANGLE revision',
+ value: clientInfo.angle_revision
+ },
+ {
+ description: '2D graphics backend',
+ value: clientInfo.graphics_backend
+ }]);
+ } else {
+ this.setText_('client-info', '... loading...');
+ }
+
+ // Feature map
+ var featureLabelMap = {
+ '2d_canvas': 'Canvas',
+ '3d_css': '3D CSS',
+ 'css_animation': 'CSS Animation',
+ 'compositing': 'Compositing',
+ 'webgl': 'WebGL',
+ 'multisampling': 'WebGL multisampling',
+ 'flash_3d': 'Flash 3D',
+ 'flash_stage3d': 'Flash Stage3D',
+ 'flash_stage3d_baseline': 'Flash Stage3D Baseline profile',
+ 'texture_sharing': 'Texture Sharing',
+ 'video_decode': 'Video Decode',
+ 'video': 'Video',
+ // GPU Switching
+ 'gpu_switching': 'GPU Switching',
+ 'panel_fitting': 'Panel Fitting',
+ 'force_compositing_mode': 'Force Compositing Mode',
+ 'raster': 'Rasterization',
+ };
+ var statusLabelMap = {
+ 'disabled_software': 'Software only. Hardware acceleration disabled.',
+ 'disabled_software_animated': 'Software animated.',
+ 'disabled_off': 'Unavailable. Hardware acceleration disabled.',
+ 'software': 'Software rendered. Hardware acceleration not enabled.',
+ 'unavailable_off': 'Unavailable. Hardware acceleration unavailable',
+ 'unavailable_software':
+ 'Software only, hardware acceleration unavailable',
+ 'enabled_readback': 'Hardware accelerated, but at reduced performance',
+ 'enabled_force': 'Hardware accelerated on all pages',
+ 'enabled_threaded': 'Hardware accelerated on demand and threaded',
+ 'enabled_force_threaded':
+ 'Hardware accelerated on all pages and threaded',
+ 'enabled': 'Hardware accelerated',
+ 'accelerated': 'Accelerated',
+ 'accelerated_threaded': 'Accelerated and threaded',
+ // GPU Switching
+ 'gpu_switching_automatic': 'Automatic switching',
+ 'gpu_switching_force_discrete': 'Always on discrete GPU',
+ 'gpu_switching_force_integrated': 'Always on integrated GPU',
+ 'disabled_software_multithreaded': 'Software only, multi-threaded',
+ };
+
+ var statusClassMap = {
+ 'disabled_software': 'feature-yellow',
+ 'disabled_software_animated': 'feature-yellow',
+ 'disabled_off': 'feature-red',
+ 'software': 'feature-yellow',
+ 'unavailable_off': 'feature-red',
+ 'unavailable_software': 'feature-yellow',
+ 'enabled_force': 'feature-green',
+ 'enabled_readback': 'feature-yellow',
+ 'enabled_threaded': 'feature-green',
+ 'enabled_force_threaded': 'feature-green',
+ 'enabled': 'feature-green',
+ 'accelerated': 'feature-green',
+ 'accelerated_threaded': 'feature-green',
+ // GPU Switching
+ 'gpu_switching_automatic': 'feature-green',
+ 'gpu_switching_force_discrete': 'feature-red',
+ 'gpu_switching_force_integrated': 'feature-red',
+ 'disabled_software_multithreaded': 'feature-yellow',
+ };
+
+ // GPU info, basic
+ var diagnosticsDiv = this.querySelector('.diagnostics');
+ var diagnosticsLoadingDiv = this.querySelector('.diagnostics-loading');
+ var featureStatusList = this.querySelector('.feature-status-list');
+ var problemsDiv = this.querySelector('.problems-div');
+ var problemsList = this.querySelector('.problems-list');
+ var workaroundsDiv = this.querySelector('.workarounds-div');
+ var workaroundsList = this.querySelector('.workarounds-list');
+ var performanceDiv = this.querySelector('.performance-div');
+ var gpuInfo = browserBridge.gpuInfo;
+ var i;
+ if (gpuInfo) {
+ // Not using jstemplate here for blacklist status because we construct
+ // href from data, which jstemplate can't seem to do.
+ if (gpuInfo.featureStatus) {
+ // feature status list
+ featureStatusList.textContent = '';
+ for (i = 0; i < gpuInfo.featureStatus.featureStatus.length;
+ i++) {
+ var feature = gpuInfo.featureStatus.featureStatus[i];
+ var featureEl = document.createElement('li');
+
+ var nameEl = document.createElement('span');
+ if (!featureLabelMap[feature.name])
+ console.log('Missing featureLabel for', feature.name);
+ nameEl.textContent = featureLabelMap[feature.name] + ': ';
+ featureEl.appendChild(nameEl);
+
+ var statusEl = document.createElement('span');
+ if (!statusLabelMap[feature.status])
+ console.log('Missing statusLabel for', feature.status);
+ if (!statusClassMap[feature.status])
+ console.log('Missing statusClass for', feature.status);
+ statusEl.textContent = statusLabelMap[feature.status];
+ statusEl.className = statusClassMap[feature.status];
+ featureEl.appendChild(statusEl);
+
+ featureStatusList.appendChild(featureEl);
+ }
+
+ // problems list
+ if (gpuInfo.featureStatus.problems.length) {
+ problemsDiv.hidden = false;
+ problemsList.textContent = '';
+ for (i = 0; i < gpuInfo.featureStatus.problems.length; i++) {
+ var problem = gpuInfo.featureStatus.problems[i];
+ var problemEl = this.createProblemEl_(problem);
+ problemsList.appendChild(problemEl);
+ }
+ } else {
+ problemsDiv.hidden = true;
+ }
+
+ // driver bug workarounds list
+ if (gpuInfo.featureStatus.workarounds.length) {
+ workaroundsDiv.hidden = false;
+ workaroundsList.textContent = '';
+ for (i = 0; i < gpuInfo.featureStatus.workarounds.length; i++) {
+ var workaroundEl = document.createElement('li');
+ workaroundEl.textContent = gpuInfo.featureStatus.workarounds[i];
+ workaroundsList.appendChild(workaroundEl);
+ }
+ } else {
+ workaroundsDiv.hidden = true;
+ }
+
+ } else {
+ featureStatusList.textContent = '';
+ problemsList.hidden = true;
+ workaroundsList.hidden = true;
+ }
+ if (gpuInfo.basic_info)
+ this.setTable_('basic-info', gpuInfo.basic_info);
+ else
+ this.setTable_('basic-info', []);
+
+ if (gpuInfo.performance_info) {
+ performanceDiv.hidden = false;
+ this.setTable_('performance-info', gpuInfo.performance_info);
+ } else {
+ performanceDiv.hidden = true;
+ }
+
+ if (gpuInfo.diagnostics) {
+ diagnosticsDiv.hidden = false;
+ diagnosticsLoadingDiv.hidden = true;
+ $('diagnostics-table').hidden = false;
+ this.setTable_('diagnostics-table', gpuInfo.diagnostics);
+ } else if (gpuInfo.diagnostics === null) {
+ // gpu_internals.cc sets diagnostics to null when it is being loaded
+ diagnosticsDiv.hidden = false;
+ diagnosticsLoadingDiv.hidden = false;
+ $('diagnostics-table').hidden = true;
+ } else {
+ diagnosticsDiv.hidden = true;
+ }
+ } else {
+ this.setText_('basic-info', '... loading ...');
+ diagnosticsDiv.hidden = true;
+ featureStatusList.textContent = '';
+ problemsDiv.hidden = true;
+ }
+
+ // Log messages
+ jstProcess(new JsEvalContext({values: browserBridge.logMessages}),
+ $('log-messages'));
+ },
+
+ createProblemEl_: function(problem) {
+ var problemEl;
+ problemEl = document.createElement('li');
+
+ // Description of issue
+ var desc = document.createElement('a');
+ desc.textContent = problem.description;
+ problemEl.appendChild(desc);
+
+ // Spacing ':' element
+ if (problem.crBugs.length + problem.webkitBugs.length > 0) {
+ var tmp = document.createElement('span');
+ tmp.textContent = ': ';
+ problemEl.appendChild(tmp);
+ }
+
+ var nbugs = 0;
+ var j;
+
+ // crBugs
+ for (j = 0; j < problem.crBugs.length; ++j) {
+ if (nbugs > 0) {
+ var tmp = document.createElement('span');
+ tmp.textContent = ', ';
+ problemEl.appendChild(tmp);
+ }
+
+ var link = document.createElement('a');
+ var bugid = parseInt(problem.crBugs[j]);
+ link.textContent = bugid;
+ link.href = 'http://crbug.com/' + bugid;
+ problemEl.appendChild(link);
+ nbugs++;
+ }
+
+ for (j = 0; j < problem.webkitBugs.length; ++j) {
+ if (nbugs > 0) {
+ var tmp = document.createElement('span');
+ tmp.textContent = ', ';
+ problemEl.appendChild(tmp);
+ }
+
+ var link = document.createElement('a');
+ var bugid = parseInt(problem.webkitBugs[j]);
+ link.textContent = bugid;
+
+ link.href = 'https://bugs.webkit.org/show_bug.cgi?id=' + bugid;
+ problemEl.appendChild(link);
+ nbugs++;
+ }
+
+ return problemEl;
+ },
+
+ setText_: function(outputElementId, text) {
+ var peg = document.getElementById(outputElementId);
+ peg.textContent = text;
+ },
+
+ setTable_: function(outputElementId, inputData) {
+ var template = jstGetTemplate('info-view-table-template');
+ jstProcess(new JsEvalContext({value: inputData}),
+ template);
+
+ var peg = document.getElementById(outputElementId);
+ if (!peg)
+ throw new Error('Node ' + outputElementId + ' not found');
+
+ peg.innerHTML = '';
+ peg.appendChild(template);
+ }
+ };
+
+ return {
+ InfoView: InfoView
+ };
+});
diff --git a/chromium/content/browser/resources/gpu/timeline_test.html b/chromium/content/browser/resources/gpu/timeline_test.html
new file mode 100644
index 00000000000..e21f90e705e
--- /dev/null
+++ b/chromium/content/browser/resources/gpu/timeline_test.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<head>
+<title></title>
+<link rel="stylesheet" href="timeline.css">
+<!--<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>-->
+<script src="../../../../ui/webui/resources/js/cr.js"></script>
+<script src="../../../../ui/webui/resources/js/cr/event_target.js"></script>
+<script src="../../../../ui/webui/resources/js/cr/ui.js"></script>
+<script src="fast_rect_renderer.js"></script>
+<script src="sorted_array_utils.js"></script>
+<script src="timeline.js"></script>
+<script src="timeline_track.js"></script>
+<script src="timeline_model.js"></script>
+<!--
+<script>
+
+goog.require('goog.testing.jsunit');
+
+</script>
+-->
+</head>
+<body>
+<div id="sandbox"></div>
+<script>
+
+var sandbox = document.getElementById('sandbox');
+var timeline;
+
+function testTimeline() {
+ model = new gpu.TimelineModel();
+ model.importEvents(tracingControllerDataSets);
+ timeline = new gpu.Timeline();
+ timeline.model = model;
+ sandbox.appendChild(timeline);
+}
+document.addEventListener('DOMContentLoaded', testTimeline);
+</script>
+
+</body>
+</html>
diff --git a/chromium/content/browser/resources/indexed_db/OWNERS b/chromium/content/browser/resources/indexed_db/OWNERS
new file mode 100644
index 00000000000..b106dad853f
--- /dev/null
+++ b/chromium/content/browser/resources/indexed_db/OWNERS
@@ -0,0 +1,4 @@
+dgrogan@chromium.org
+michaeln@chromium.org
+jsbell@chromium.org
+alecflett@chromium.org
diff --git a/chromium/content/browser/resources/indexed_db/indexeddb_internals.css b/chromium/content/browser/resources/indexed_db/indexeddb_internals.css
new file mode 100644
index 00000000000..76653f0921c
--- /dev/null
+++ b/chromium/content/browser/resources/indexed_db/indexeddb_internals.css
@@ -0,0 +1,72 @@
+/* Copyright (c) 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. */
+
+.indexeddb-summary {
+ background-color: rgb(235, 239, 249);
+ border-top: 1px solid rgb(156, 194, 239);
+ margin-bottom: 6px;
+ margin-top: 12px;
+ padding: 3px;
+ font-weight: bold;
+}
+
+.indexeddb-item {
+ margin-bottom: 15px;
+ margin-top: 6px;
+ position: relative;
+}
+
+.indexeddb-url {
+ color: rgb(85, 102, 221);
+ display: inline-block;
+ max-width: 500px;
+ overflow: hidden;
+ padding-bottom: 1px;
+ padding-top: 4px;
+ text-decoration: none;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.indexeddb-database {
+ margin-bottom: 6px;
+ margin-top: 6px;
+ margin-left: 12px;
+
+ position: relative;
+}
+
+.indexeddb-database > div {
+ margin-left: 12px;
+}
+
+.indexeddb-connection-count {
+ margin: 0 8px;
+}
+.indexeddb-connection-count.pending {
+ font-weight: bold;
+}
+
+.indexeddb-transaction {
+ background-color: rgb(235, 239, 249);
+ margin: 4px 0;
+ border-radius: 4px;
+ padding: 2px;
+ position: relative;
+}
+
+.indexeddb-transaction.running {
+ font-weight: bold;
+}
+
+.indexeddb-transaction-mode,
+.indexeddb-transaction-scope,
+.indexeddb-transaction-state {
+ margin: 0 8px;
+}
+
+.controls a {
+ -webkit-margin-end: 16px;
+ color: #777;
+}
diff --git a/chromium/content/browser/resources/indexed_db/indexeddb_internals.html b/chromium/content/browser/resources/indexed_db/indexeddb_internals.html
new file mode 100644
index 00000000000..153aef1aa11
--- /dev/null
+++ b/chromium/content/browser/resources/indexed_db/indexeddb_internals.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html i18n-values="dir:textdirection;">
+<head>
+ <meta charset="utf-8">
+ <title>IndexedDB</title>
+ <link rel="stylesheet" href="chrome://resources/css/tabs.css">
+ <link rel="stylesheet" href="chrome://resources/css/widgets.css">
+ <link rel="stylesheet" href="indexeddb_internals.css">
+</head>
+<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+ <!-- templates -->
+ <div style="display:none">
+ <div id="indexeddb-list-template"
+ jsvalues="$partition_path:$this.partition_path">
+ <div class="indexeddb-summary">
+ <span>Instances in: </span>
+ <span jscontent="$this.partition_path"></span>
+ <span jscontent="'(' + $this.idbs.length + ')'"></span>
+ </div>
+ <div class="indexeddb-item" jsselect="$this.idbs">
+ <a class="indexeddb-url" jscontent="url" jsvalues="href:url"
+ target="_blank"></a>
+ <div class="indexeddb-size">
+ <span>Size:</span>
+ <span jscontent="size"></span>
+ </div>
+ <div class="indexeddb-last-modified">
+ <span>Last modified:</span>
+ <span jscontent="new Date(last_modified)"></span>
+ </div>
+ <div>
+ <span>Open connections:</span>
+ <span class="connection-count"
+ jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path"
+ jscontent="connection_count">
+ </div>
+ <div class="indexeddb-last-modified">
+ <span>Path:</span>
+ <span jscontent="path"></span>
+ </div>
+ <div class="controls">
+ <a href="#" class="force-close"
+ jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Force close</a>
+ <a href="#" class="download"
+ jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Download</a>
+ <span class="download-status" style="display: none">Loading...</span>
+ </div>
+ <div class="indexeddb-database" jsselect="$this.databases">
+
+ <span>Open database:</span>
+ <span jscontent="name"></span>
+
+ <div>
+ <span>Connections:</span>
+
+ <span class="indexeddb-connection-count"
+ jsdisplay="connection_count">
+ <span>open:</span>
+ <span jscontent="connection_count"></span>
+ </span>
+
+ <span class="indexeddb-connection-count pending"
+ jsdisplay="pending_opens">
+ <span>pending opens:</span>
+ <span jscontent="pending_opens"></span>
+ </span>
+
+ <span class="indexeddb-connection-count pending"
+ jsdisplay="pending_upgrades">
+ <span>pending upgrades:</span>
+ <span jscontent="pending_upgrades"></span>
+ </span>
+
+ <span class="indexeddb-connection-count pending"
+ jsdisplay="running_upgrades">
+ <span>running upgrades:</span>
+ <span jscontent="running_upgrades"></span>
+ </span>
+
+ <span class="indexeddb-connection-count pending"
+ jsdisplay="pending_deletes">
+ <span>pending deletes:</span>
+ <span jscontent="pending_deletes"></span>
+ </span>
+
+ </div>
+ <div class="indexeddb-transaction"
+ jsselect="$this.transactions"
+ jseval="this.classList.add($this.running ? 'running' : 'waiting')">
+ <span>Transaction:</span>
+ <span class="indexeddb-transaction-mode" jscontent="mode"></span>
+ <span class="indexeddb-transaction-scope" jsdisplay="scope">
+ <span>scope:</span> <span jscontent="'[ ' + scope.join(', ') + ' ]'"></span>
+ </span>
+ <span class="indexeddb-transaction-state"
+ jscontent="running ? '(running)' : '(blocked)'"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <h1>IndexedDB</h1>
+ <div class="content">
+ <div id="indexeddb-list">
+ </div>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="indexeddb_internals.js"></script>
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://resources/js/jstemplate_compiled.js"></script>
+ <script src="strings.js"></script>
+ <script src="chrome://resources/js/i18n_template2.js"></script>
+</body>
+</html>
+
diff --git a/chromium/content/browser/resources/indexed_db/indexeddb_internals.js b/chromium/content/browser/resources/indexed_db/indexeddb_internals.js
new file mode 100644
index 00000000000..fc80a25d024
--- /dev/null
+++ b/chromium/content/browser/resources/indexed_db/indexeddb_internals.js
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+cr.define('indexeddb', function() {
+ 'use strict';
+
+ function initialize() {
+ chrome.send('getAllOrigins');
+ }
+
+ function progressNodeFor(link) {
+ return link.parentNode.querySelector('.download-status');
+ }
+
+ function downloadOriginData(event) {
+ var link = event.target;
+ progressNodeFor(link).style.display = 'inline';
+ chrome.send('downloadOriginData', [link.idb_partition_path,
+ link.idb_origin_url]);
+ return false;
+ }
+
+ function forceClose(event) {
+ var link = event.target;
+ progressNodeFor(link).style.display = 'inline';
+ chrome.send('forceClose', [link.idb_partition_path,
+ link.idb_origin_url]);
+ return false;
+ }
+
+ function withNode(selector, partition_path, origin_url, callback) {
+ var links = document.querySelectorAll(selector);
+ for (var i = 0; i < links.length; ++i) {
+ var link = links[i];
+ if (partition_path == link.idb_partition_path &&
+ origin_url == link.idb_origin_url) {
+ callback(link);
+ }
+ }
+ }
+ // Fired from the backend after the data has been zipped up, and the
+ // download manager has begun downloading the file.
+ function onOriginDownloadReady(partition_path, origin_url, connection_count) {
+ withNode('a.download', partition_path, origin_url, function(link) {
+ progressNodeFor(link).style.display = 'none';
+ });
+ withNode('.connection-count', partition_path, origin_url, function(span) {
+ span.innerText = connection_count;
+ });
+ }
+
+ function onForcedClose(partition_path, origin_url, connection_count) {
+ withNode('a.force-close', partition_path, origin_url, function(link) {
+ progressNodeFor(link).style.display = 'none';
+ });
+ withNode('.connection-count', partition_path, origin_url, function(span) {
+ span.innerText = connection_count;
+ });
+ }
+
+ // Fired from the backend with a single partition's worth of
+ // IndexedDB metadata.
+ function onOriginsReady(origins, partition_path) {
+ var template = jstGetTemplate('indexeddb-list-template');
+ var container = $('indexeddb-list');
+ container.appendChild(template);
+ jstProcess(new JsEvalContext({ idbs: origins,
+ partition_path: partition_path}), template);
+
+ var downloadLinks = container.querySelectorAll('a.download');
+ for (var i = 0; i < downloadLinks.length; ++i) {
+ downloadLinks[i].addEventListener('click', downloadOriginData, false);
+ }
+ var forceCloseLinks = container.querySelectorAll('a.force-close');
+ for (i = 0; i < forceCloseLinks.length; ++i) {
+ forceCloseLinks[i].addEventListener('click', forceClose, false);
+ }
+ }
+
+ return {
+ initialize: initialize,
+ onForcedClose: onForcedClose,
+ onOriginDownloadReady: onOriginDownloadReady,
+ onOriginsReady: onOriginsReady,
+ };
+});
+
+document.addEventListener('DOMContentLoaded', indexeddb.initialize);
diff --git a/chromium/content/browser/resources/media/OWNERS b/chromium/content/browser/resources/media/OWNERS
new file mode 100644
index 00000000000..d132d0e6061
--- /dev/null
+++ b/chromium/content/browser/resources/media/OWNERS
@@ -0,0 +1,11 @@
+acolwell@chromium.org
+dalecurtis@chromium.org
+ddorwin@chromium.org
+fischman@chromium.org
+scherkus@chromium.org
+shadi@chromium.org
+tommi@chromium.org
+vrk@chromium.org
+wjia@chromium.org
+xhwang@chromium.org
+xians@chromium.org
diff --git a/chromium/content/browser/resources/media/cache_entry.js b/chromium/content/browser/resources/media/cache_entry.js
new file mode 100644
index 00000000000..275a8c74a50
--- /dev/null
+++ b/chromium/content/browser/resources/media/cache_entry.js
@@ -0,0 +1,237 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+ 'use strict';
+
+ /**
+ * This class represents a file cached by net.
+ */
+ function CacheEntry() {
+ this.read_ = new media.DisjointRangeSet;
+ this.written_ = new media.DisjointRangeSet;
+ this.available_ = new media.DisjointRangeSet;
+
+ // Set to true when we know the entry is sparse.
+ this.sparse = false;
+ this.key = null;
+ this.size = null;
+
+ // The <details> element representing this CacheEntry.
+ this.details_ = document.createElement('details');
+ this.details_.className = 'cache-entry';
+ this.details_.open = false;
+
+ // The <details> summary line. It contains a chart of requested file ranges
+ // and the url if we know it.
+ var summary = document.createElement('summary');
+
+ this.summaryText_ = document.createTextNode('');
+ summary.appendChild(this.summaryText_);
+
+ summary.appendChild(document.createTextNode(' '));
+
+ // Controls to modify this CacheEntry.
+ var controls = document.createElement('span');
+ controls.className = 'cache-entry-controls';
+ summary.appendChild(controls);
+ summary.appendChild(document.createElement('br'));
+
+ // A link to clear recorded data from this CacheEntry.
+ var clearControl = document.createElement('a');
+ clearControl.href = 'javascript:void(0)';
+ clearControl.onclick = this.clear.bind(this);
+ clearControl.textContent = '(clear entry)';
+ controls.appendChild(clearControl);
+
+ this.details_.appendChild(summary);
+
+ // The canvas for drawing cache writes.
+ this.writeCanvas = document.createElement('canvas');
+ this.writeCanvas.width = media.BAR_WIDTH;
+ this.writeCanvas.height = media.BAR_HEIGHT;
+ this.details_.appendChild(this.writeCanvas);
+
+ // The canvas for drawing cache reads.
+ this.readCanvas = document.createElement('canvas');
+ this.readCanvas.width = media.BAR_WIDTH;
+ this.readCanvas.height = media.BAR_HEIGHT;
+ this.details_.appendChild(this.readCanvas);
+
+ // A tabular representation of the data in the above canvas.
+ this.detailTable_ = document.createElement('table');
+ this.detailTable_.className = 'cache-table';
+ this.details_.appendChild(this.detailTable_);
+ }
+
+ CacheEntry.prototype = {
+ /**
+ * Mark a range of bytes as read from the cache.
+ * @param {int} start The first byte read.
+ * @param {int} length The number of bytes read.
+ */
+ readBytes: function(start, length) {
+ start = parseInt(start);
+ length = parseInt(length);
+ this.read_.add(start, start + length);
+ this.available_.add(start, start + length);
+ this.sparse = true;
+ },
+
+ /**
+ * Mark a range of bytes as written to the cache.
+ * @param {int} start The first byte written.
+ * @param {int} length The number of bytes written.
+ */
+ writeBytes: function(start, length) {
+ start = parseInt(start);
+ length = parseInt(length);
+ this.written_.add(start, start + length);
+ this.available_.add(start, start + length);
+ this.sparse = true;
+ },
+
+ /**
+ * Merge this CacheEntry with another, merging recorded ranges and flags.
+ * @param {CacheEntry} other The CacheEntry to merge into this one.
+ */
+ merge: function(other) {
+ this.read_.merge(other.read_);
+ this.written_.merge(other.written_);
+ this.available_.merge(other.available_);
+ this.sparse = this.sparse || other.sparse;
+ this.key = this.key || other.key;
+ this.size = this.size || other.size;
+ },
+
+ /**
+ * Clear all recorded ranges from this CacheEntry and redraw this.details_.
+ */
+ clear: function() {
+ this.read_ = new media.DisjointRangeSet;
+ this.written_ = new media.DisjointRangeSet;
+ this.available_ = new media.DisjointRangeSet;
+ this.generateDetails();
+ },
+
+ /**
+ * Helper for drawCacheReadsToCanvas() and drawCacheWritesToCanvas().
+ *
+ * Accepts the entries to draw, a canvas fill style, and the canvas to
+ * draw on.
+ */
+ drawCacheEntriesToCanvas: function(entries, fillStyle, canvas) {
+ // Don't bother drawing anything if we don't know the total size.
+ if (!this.size) {
+ return;
+ }
+
+ var width = canvas.width;
+ var height = canvas.height;
+ var context = canvas.getContext('2d');
+ var fileSize = this.size;
+
+ context.fillStyle = '#aaa';
+ context.fillRect(0, 0, width, height);
+
+ function drawRange(start, end) {
+ var left = start / fileSize * width;
+ var right = end / fileSize * width;
+ context.fillRect(left, 0, right - left, height);
+ }
+
+ context.fillStyle = fillStyle;
+ entries.map(function(start, end) {
+ drawRange(start, end);
+ });
+ },
+
+ /**
+ * Draw cache writes to the given canvas.
+ *
+ * It should consist of a horizontal bar with highlighted sections to
+ * represent which parts of a file have been written to the cache.
+ *
+ * e.g. |xxxxxx----------x|
+ */
+ drawCacheWritesToCanvas: function(canvas) {
+ this.drawCacheEntriesToCanvas(this.written_, '#00a', canvas);
+ },
+
+ /**
+ * Draw cache reads to the given canvas.
+ *
+ * It should consist of a horizontal bar with highlighted sections to
+ * represent which parts of a file have been read from the cache.
+ *
+ * e.g. |xxxxxx----------x|
+ */
+ drawCacheReadsToCanvas: function(canvas) {
+ this.drawCacheEntriesToCanvas(this.read_, '#0a0', canvas);
+ },
+
+ /**
+ * Update this.details_ to contain everything we currently know about
+ * this file.
+ */
+ generateDetails: function() {
+ this.details_.id = this.key;
+ this.summaryText_.textContent = this.key || 'Unknown File';
+
+ this.detailTable_.textContent = '';
+ var header = document.createElement('thead');
+ var footer = document.createElement('tfoot');
+ var body = document.createElement('tbody');
+ this.detailTable_.appendChild(header);
+ this.detailTable_.appendChild(footer);
+ this.detailTable_.appendChild(body);
+
+ var headerRow = document.createElement('tr');
+ headerRow.appendChild(media.makeElement('th', 'Read From Cache'));
+ headerRow.appendChild(media.makeElement('th', 'Written To Cache'));
+ header.appendChild(headerRow);
+
+ var footerRow = document.createElement('tr');
+ var footerCell = document.createElement('td');
+ footerCell.textContent = 'Out of ' + (this.size || 'unkown size');
+ footerCell.setAttribute('colspan', 2);
+ footerRow.appendChild(footerCell);
+ footer.appendChild(footerRow);
+
+ var read = this.read_.map(function(start, end) {
+ return start + ' - ' + end;
+ });
+ var written = this.written_.map(function(start, end) {
+ return start + ' - ' + end;
+ });
+
+ var length = Math.max(read.length, written.length);
+ for (var i = 0; i < length; i++) {
+ var row = document.createElement('tr');
+ row.appendChild(media.makeElement('td', read[i] || ''));
+ row.appendChild(media.makeElement('td', written[i] || ''));
+ body.appendChild(row);
+ }
+
+ this.drawCacheWritesToCanvas(this.writeCanvas);
+ this.drawCacheReadsToCanvas(this.readCanvas);
+ },
+
+ /**
+ * Render this CacheEntry as a <li>.
+ * @return {HTMLElement} A <li> representing this CacheEntry.
+ */
+ toListItem: function() {
+ this.generateDetails();
+
+ var result = document.createElement('li');
+ result.appendChild(this.details_);
+ return result;
+ }
+ };
+
+ return {
+ CacheEntry: CacheEntry
+ };
+});
diff --git a/chromium/content/browser/resources/media/data_series.js b/chromium/content/browser/resources/media/data_series.js
new file mode 100644
index 00000000000..8947492dbf4
--- /dev/null
+++ b/chromium/content/browser/resources/media/data_series.js
@@ -0,0 +1,132 @@
+// Copyright (c) 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.
+
+/**
+ * A TimelineDataSeries collects an ordered series of (time, value) pairs,
+ * and converts them to graph points. It also keeps track of its color and
+ * current visibility state.
+ * It keeps MAX_STATS_DATA_POINT_BUFFER_SIZE data points at most. Old data
+ * points will be dropped when it reaches this size.
+ */
+var TimelineDataSeries = (function() {
+ 'use strict';
+
+ /**
+ * @constructor
+ */
+ function TimelineDataSeries() {
+ // List of DataPoints in chronological order.
+ this.dataPoints_ = [];
+
+ // Default color. Should always be overridden prior to display.
+ this.color_ = 'red';
+ // Whether or not the data series should be drawn.
+ this.isVisible_ = true;
+
+ this.cacheStartTime_ = null;
+ this.cacheStepSize_ = 0;
+ this.cacheValues_ = [];
+ }
+
+ TimelineDataSeries.prototype = {
+ /**
+ * @override
+ */
+ toJSON: function() {
+ if (this.dataPoints_.length < 1)
+ return {};
+
+ var values = [];
+ for (var i = 0; i < this.dataPoints_.length; ++i) {
+ values.push(this.dataPoints_[i].value);
+ }
+ return {
+ startTime: this.dataPoints_[0].time,
+ endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
+ values: JSON.stringify(values),
+ };
+ },
+
+ /**
+ * Adds a DataPoint to |this| with the specified time and value.
+ * DataPoints are assumed to be received in chronological order.
+ */
+ addPoint: function(timeTicks, value) {
+ var time = new Date(timeTicks);
+ this.dataPoints_.push(new DataPoint(time, value));
+
+ if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE)
+ this.dataPoints_.shift();
+ },
+
+ isVisible: function() {
+ return this.isVisible_;
+ },
+
+ show: function(isVisible) {
+ this.isVisible_ = isVisible;
+ },
+
+ getColor: function() {
+ return this.color_;
+ },
+
+ setColor: function(color) {
+ this.color_ = color;
+ },
+
+ /**
+ * Returns a list containing the values of the data series at |count|
+ * points, starting at |startTime|, and |stepSize| milliseconds apart.
+ * Caches values, so showing/hiding individual data series is fast.
+ */
+ getValues: function(startTime, stepSize, count) {
+ // Use cached values, if we can.
+ if (this.cacheStartTime_ == startTime &&
+ this.cacheStepSize_ == stepSize &&
+ this.cacheValues_.length == count) {
+ return this.cacheValues_;
+ }
+
+ // Do all the work.
+ this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
+ this.cacheStartTime_ = startTime;
+ this.cacheStepSize_ = stepSize;
+
+ return this.cacheValues_;
+ },
+
+ /**
+ * Returns the cached |values| in the specified time period.
+ */
+ getValuesInternal_: function(startTime, stepSize, count) {
+ var values = [];
+ var nextPoint = 0;
+ var currentValue = 0;
+ var time = startTime;
+ for (var i = 0; i < count; ++i) {
+ while (nextPoint < this.dataPoints_.length &&
+ this.dataPoints_[nextPoint].time < time) {
+ currentValue = this.dataPoints_[nextPoint].value;
+ ++nextPoint;
+ }
+ values[i] = currentValue;
+ time += stepSize;
+ }
+ return values;
+ }
+ };
+
+ /**
+ * A single point in a data series. Each point has a time, in the form of
+ * milliseconds since the Unix epoch, and a numeric value.
+ * @constructor
+ */
+ function DataPoint(time, value) {
+ this.time = time;
+ this.value = value;
+ }
+
+ return TimelineDataSeries;
+})();
diff --git a/chromium/content/browser/resources/media/disjoint_range_set.js b/chromium/content/browser/resources/media/disjoint_range_set.js
new file mode 100644
index 00000000000..bd504bb9a37
--- /dev/null
+++ b/chromium/content/browser/resources/media/disjoint_range_set.js
@@ -0,0 +1,145 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+
+ /**
+ * This class represents a collection of non-intersecting ranges. Ranges
+ * specified by (start, end) can be added and removed at will. It is used to
+ * record which sections of a media file have been cached, e.g. the first and
+ * last few kB plus several MB in the middle.
+ *
+ * Example usage:
+ * someRange.add(0, 100); // Contains 0-100.
+ * someRange.add(150, 200); // Contains 0-100, 150-200.
+ * someRange.remove(25, 75); // Contains 0-24, 76-100, 150-200.
+ * someRange.add(25, 149); // Contains 0-200.
+ */
+ function DisjointRangeSet() {
+ this.ranges_ = {};
+ }
+
+ DisjointRangeSet.prototype = {
+ /**
+ * Deletes all ranges intersecting with (start ... end) and returns the
+ * extents of the cleared area.
+ * @param {int} start The start of the range to remove.
+ * @param {int} end The end of the range to remove.
+ * @param {int} sloppiness 0 removes only strictly overlapping ranges, and
+ * 1 removes adjacent ones.
+ * @return {Object} The start and end of the newly cleared range.
+ */
+ clearRange: function(start, end, sloppiness) {
+ var ranges = this.ranges_;
+ var result = {start: start, end: end};
+
+ for (var rangeStart in this.ranges_) {
+ rangeEnd = this.ranges_[rangeStart];
+ // A range intersects another if its start lies within the other range
+ // or vice versa.
+ if ((rangeStart >= start && rangeStart <= (end + sloppiness)) ||
+ (start >= rangeStart && start <= (rangeEnd + sloppiness))) {
+ delete ranges[rangeStart];
+ result.start = Math.min(result.start, rangeStart);
+ result.end = Math.max(result.end, rangeEnd);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Adds a range to this DisjointRangeSet.
+ * Joins adjacent and overlapping ranges together.
+ * @param {int} start The beginning of the range to add, inclusive.
+ * @param {int} end The end of the range to add, inclusive.
+ */
+ add: function(start, end) {
+ if (end < start)
+ return;
+
+ // Remove all touching ranges.
+ result = this.clearRange(start, end, 1);
+ // Add back a single contiguous range.
+ this.ranges_[Math.min(start, result.start)] = Math.max(end, result.end);
+ },
+
+ /**
+ * Combines a DisjointRangeSet with this one.
+ * @param {DisjointRangeSet} ranges A DisjointRangeSet to be squished into
+ * this one.
+ */
+ merge: function(other) {
+ var ranges = this;
+ other.forEach(function(start, end) { ranges.add(start, end); });
+ },
+
+ /**
+ * Removes a range from this DisjointRangeSet.
+ * Will split existing ranges if necessary.
+ * @param {int} start The beginning of the range to remove, inclusive.
+ * @param {int} end The end of the range to remove, inclusive.
+ */
+ remove: function(start, end) {
+ if (end < start)
+ return;
+
+ // Remove instersecting ranges.
+ result = this.clearRange(start, end, 0);
+
+ // Add back non-overlapping ranges.
+ if (result.start < start)
+ this.ranges_[result.start] = start - 1;
+ if (result.end > end)
+ this.ranges_[end + 1] = result.end;
+ },
+
+ /**
+ * Iterates over every contiguous range in this DisjointRangeSet, calling a
+ * function for each (start, end).
+ * @param {function(int, int)} iterator The function to call on each range.
+ */
+ forEach: function(iterator) {
+ for (var start in this.ranges_)
+ iterator(start, this.ranges_[start]);
+ },
+
+ /**
+ * Maps this DisjointRangeSet to an array by calling a given function on the
+ * start and end of each contiguous range, sorted by start.
+ * @param {function(int, int)} mapper Maps a range to an array element.
+ * @return {Array} An array of each mapper(range).
+ */
+ map: function(mapper) {
+ var starts = [];
+ for (var start in this.ranges_)
+ starts.push(parseInt(start));
+ starts.sort(function(a, b) {
+ return a - b;
+ });
+
+ var ranges = this.ranges_;
+ var results = starts.map(function(s) {
+ return mapper(s, ranges[s]);
+ });
+
+ return results;
+ },
+
+ /**
+ * Finds the maximum value present in any of the contained ranges.
+ * @return {int} The maximum value contained by this DisjointRangeSet.
+ */
+ max: function() {
+ var max = -Infinity;
+ for (var start in this.ranges_)
+ max = Math.max(max, this.ranges_[start]);
+ return max;
+ },
+ };
+
+ return {
+ DisjointRangeSet: DisjointRangeSet
+ };
+});
diff --git a/chromium/content/browser/resources/media/disjoint_range_set_test.html b/chromium/content/browser/resources/media/disjoint_range_set_test.html
new file mode 100644
index 00000000000..39db9b34b45
--- /dev/null
+++ b/chromium/content/browser/resources/media/disjoint_range_set_test.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<!--
+Copyright (c) 2011 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+ <head>
+ <title></title>
+ <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+ <script src="../../../../ui/webui/resources/js/cr.js"></script>
+ <script src="disjoint_range_set.js"></script>
+ <script>
+ goog.require('goog.testing.jsunit');
+ </script>
+ </head>
+ <body>
+ <script>
+
+ var range;
+
+ function assertRangeEquals(ranges) {
+ assertArrayEquals(
+ ranges, range.map(function(start, end) { return [start, end]; }));
+ };
+
+ function setUp() {
+ range = new media.DisjointRangeSet;
+ };
+
+ function testAdd() {
+ range.add(1, 6);
+ assertRangeEquals([[1, 6]]);
+ range.add(-5, -3);
+ assertRangeEquals([[-5, -3], [1, 6]]);
+ };
+
+ function testAddAdjacent() {
+ range.add(3, 6);
+ assertRangeEquals([[3, 6]]);
+ range.add(1, 2);
+ assertRangeEquals([[1, 6]]);
+ range.add(7, 9);
+ assertRangeEquals([[1, 9]]);
+ };
+
+ function testAddNotQuiteAdjacent() {
+ range.add(3, 6);
+ assertRangeEquals([[3, 6]]);
+ range.add(0, 1);
+ assertRangeEquals([[0, 1], [3, 6]]);
+ range.add(8, 9);
+ assertRangeEquals([[0, 1], [3, 6], [8, 9]]);
+ };
+
+ function testAddOverlapping() {
+ range.add(1, 6);
+ assertRangeEquals([[1, 6]]);
+ range.add(5, 8);
+ assertRangeEquals([[1, 8]]);
+ range.add(0, 1);
+ assertRangeEquals([[0, 8]]);
+ };
+
+ function testMax() {
+ assertNull(range.max());
+ range.add(1, 6);
+ assertEquals(range.max(), 6);
+ range.add(3, 8);
+ assertEquals(range.max(), 8);
+ range.remove(2, 3);
+ assertEquals(range.max(), 8);
+ range.remove(4, 10);
+ assertEquals(range.max(), 1);
+ range.remove(1, 1);
+ assertNull(range.max());
+ };
+
+ function testRemove() {
+ range.add(1, 20);
+ assertRangeEquals([[1, 20]]);
+ range.remove(0, 3);
+ assertRangeEquals([[4, 20]]);
+ range.remove(18, 20);
+ assertRangeEquals([[4, 17]]);
+ range.remove(5, 16);
+ assertRangeEquals([[4, 4], [17, 17]]);
+ };
+
+ function testStartsEmpty() {
+ assertRangeEquals([]);
+ };
+
+ </script>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/dump_creator.js b/chromium/content/browser/resources/media/dump_creator.js
new file mode 100644
index 00000000000..5daddabc61c
--- /dev/null
+++ b/chromium/content/browser/resources/media/dump_creator.js
@@ -0,0 +1,131 @@
+// Copyright (c) 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.
+
+
+/**
+ * Provides the UI to start and stop RTP recording, forwards the start/stop
+ * commands to Chrome, and updates the UI based on dump updates. Also provides
+ * creating a file containing all PeerConnection updates and stats.
+ */
+var DumpCreator = (function() {
+ /**
+ * @param {Element} containerElement The parent element of the dump creation
+ * UI.
+ * @constructor
+ */
+ function DumpCreator(containerElement) {
+ /**
+ * True if the RTP packets are being recorded.
+ * @type {bool}
+ * @private
+ */
+ this.recording_ = false;
+
+ /**
+ * @type {!Object.<string>}
+ * @private
+ * @const
+ */
+ this.StatusStrings_ = {
+ NOT_STARTED: 'not started.',
+ RECORDING: 'recording...',
+ },
+
+ /**
+ * The status of dump creation.
+ * @type {string}
+ * @private
+ */
+ this.status_ = this.StatusStrings_.NOT_STARTED;
+
+ /**
+ * The root element of the dump creation UI.
+ * @type {Element}
+ * @private
+ */
+ this.root_ = document.createElement('details');
+
+ this.root_.className = 'peer-connection-dump-root';
+ containerElement.appendChild(this.root_);
+ var summary = document.createElement('summary');
+ this.root_.appendChild(summary);
+ summary.textContent = 'Create Dump';
+ var content = document.createElement('pre');
+ this.root_.appendChild(content);
+
+ content.innerHTML = '<button disabled></button> Status: <span></span>' +
+ '<div><form><button>' +
+ 'Download the PeerConnection updates and stats data' +
+ '</button></form></div>';
+ content.getElementsByTagName('button')[0].addEventListener(
+ 'click', this.onRtpToggled_.bind(this));
+ content.getElementsByTagName('button')[1].addEventListener(
+ 'click', this.onDownloadData_.bind(this));
+
+ this.updateDisplay_();
+ }
+
+ DumpCreator.prototype = {
+ /**
+ * Downloads the PeerConnection updates and stats data as a file.
+ *
+ * @private
+ */
+ onDownloadData_: function() {
+ var textBlob =
+ new Blob([JSON.stringify(peerConnectionDataStore, null, ' ')],
+ {type: 'octet/stream'});
+ var URL = window.webkitURL.createObjectURL(textBlob);
+ this.root_.getElementsByTagName('form')[0].action = URL;
+ // The default action of the button will submit the form.
+ },
+
+ /**
+ * Handles the event of toggling the rtp recording state.
+ *
+ * @private
+ */
+ onRtpToggled_: function() {
+ if (this.recording_) {
+ this.recording_ = false;
+ this.status_ = this.StatusStrings_.NOT_STARTED;
+ chrome.send('stopRtpRecording');
+ } else {
+ this.recording_ = true;
+ this.status_ = this.StatusStrings_.RECORDING;
+ chrome.send('startRtpRecording');
+ }
+ this.updateDisplay_();
+ },
+
+ /**
+ * Updates the UI based on the recording status.
+ *
+ * @private
+ */
+ updateDisplay_: function() {
+ if (this.recording_) {
+ this.root_.getElementsByTagName('button')[0].textContent =
+ 'Stop Recording RTP Packets';
+ } else {
+ this.root_.getElementsByTagName('button')[0].textContent =
+ 'Start Recording RTP Packets';
+ }
+
+ this.root_.getElementsByTagName('span')[0].textContent = this.status_;
+ },
+
+ /**
+ * Set the status to the content of the update.
+ * @param {!Object} update
+ */
+ onUpdate: function(update) {
+ if (this.recording_) {
+ this.status_ = JSON.stringify(update);
+ this.updateDisplay_();
+ }
+ },
+ };
+ return DumpCreator;
+})();
diff --git a/chromium/content/browser/resources/media/event_list.js b/chromium/content/browser/resources/media/event_list.js
new file mode 100644
index 00000000000..df4d4273063
--- /dev/null
+++ b/chromium/content/browser/resources/media/event_list.js
@@ -0,0 +1,64 @@
+// 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.
+
+cr.define('media', function() {
+ 'use strict';
+
+ /**
+ * This class holds a list of MediaLogEvents.
+ * It inherits from <li> and contains a tabular list of said events,
+ * the time at which they occurred, and their parameters.
+ */
+ var EventList = cr.ui.define('li');
+
+ EventList.prototype = {
+ __proto__: HTMLLIElement.prototype,
+ startTime_: null,
+
+ /**
+ * Decorate this list item as an EventList.
+ */
+ decorate: function() {
+ this.table_ = document.createElement('table');
+ var details = document.createElement('details');
+ var summary = media.makeElement('summary', 'Log:');
+ details.appendChild(summary);
+ details.appendChild(this.table_);
+ this.appendChild(details);
+
+ var hRow = document.createElement('tr');
+ hRow.appendChild(media.makeElement('th', 'Time:'));
+ hRow.appendChild(media.makeElement('th', 'Event:'));
+ hRow.appendChild(media.makeElement('th', 'Parameters:'));
+ var header = document.createElement('thead');
+ header.appendChild(hRow);
+ this.table_.appendChild(header);
+ },
+
+ /**
+ * Add an event to the list. It is stored as a new row in this.table_.
+ * @param {Object} event The MediaLogEvent that has occurred.
+ */
+ addEvent: function(event) {
+ this.startTime_ = this.startTime_ || event.ticksMillis;
+ var normalizedTicksMillis = event.ticksMillis - this.startTime_;
+
+ var row = document.createElement('tr');
+ row.appendChild(media.makeElement(
+ 'td', normalizedTicksMillis.toFixed(1)));
+ row.appendChild(media.makeElement('td', event.type));
+ var params = [];
+ for (var key in event.params) {
+ params.push(key + ': ' + event.params[key]);
+ }
+
+ row.appendChild(media.makeElement('td', params.join(', ')));
+ this.table_.appendChild(row);
+ }
+ };
+
+ return {
+ EventList: EventList
+ };
+});
diff --git a/chromium/content/browser/resources/media/item_store.js b/chromium/content/browser/resources/media/item_store.js
new file mode 100644
index 00000000000..a6e3a6c4577
--- /dev/null
+++ b/chromium/content/browser/resources/media/item_store.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+
+ /**
+ * This class stores hashes by their id field and provides basic methods for
+ * iterating over the collection.
+ * @constructor
+ */
+ function ItemStore() {
+ this.items_ = {};
+ }
+
+ ItemStore.prototype = {
+ /**
+ * Get a sorted list of item ids.
+ * @return {Array} A sorted array of ids.
+ */
+ ids: function() {
+ var ids = [];
+ for (var i in this.items_)
+ ids.push(i);
+ return ids.sort();
+ },
+
+ /**
+ * Add an item to the store.
+ * @param {Object} item The item to be added.
+ * @param {string} item.id The id of the item.
+ */
+ addItem: function(item) {
+ this.items_[item.id] = item;
+ },
+
+ /**
+ * Add a dictionary of items to the store.
+ * @param {Object} items A dictionary of individual items. The keys are
+ * irrelevant but each must have an id field.
+ */
+ addItems: function(items) {
+ for (id in items)
+ this.addItem(items[id]);
+ },
+
+ /**
+ * Remove an item from the store.
+ * @param {string} id The id of the item to be removed.
+ */
+ removeItem: function(id) {
+ delete this.items_[id];
+ },
+
+ /**
+ * Map this itemStore to an Array. Items are sorted by id.
+ * @param {function(*)} mapper The mapping function applied to each item.
+ * @return {Array} An array of mapped items.
+ */
+ map: function(mapper) {
+ var items = this.items_;
+ var ids = this.ids();
+ return ids.map(function(id) { return mapper(items[id]); });
+ }
+ };
+
+ return {
+ ItemStore: ItemStore
+ };
+});
diff --git a/chromium/content/browser/resources/media/media_internals.css b/chromium/content/browser/resources/media/media_internals.css
new file mode 100644
index 00000000000..d83b6b71eeb
--- /dev/null
+++ b/chromium/content/browser/resources/media/media_internals.css
@@ -0,0 +1,83 @@
+/* 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. */
+
+body {
+ font-family: sans-serif;
+}
+
+h2 {
+ margin: 15px 0 5px 0;
+}
+
+ul,
+p,
+canvas {
+ margin: 0;
+}
+
+[hidden] {
+ display: none !important;
+}
+
+#media-players td,
+#media-players th {
+ padding: 0 10px;
+}
+
+.audio-stream[status='created'] {
+ color: blue;
+}
+
+.audio-stream[status='closed'] {
+ text-decoration: line-through;
+}
+
+.audio-stream[status='error'] {
+ color: red;
+}
+
+#cache-entries ul,
+#media-players ul,
+#media-players {
+ list-style-type: none;
+}
+
+.cache-entry {
+ margin: 0 0 5px 0;
+}
+
+.cache-entry-controls {
+ font-size: smaller;
+}
+
+.cache-table {
+ table-layout: fixed;
+ width: 500px;
+}
+
+thead {
+ text-align: left;
+}
+
+tfoot {
+ text-align: right;
+}
+
+.buffered {
+ display: table;
+}
+
+.buffered > div {
+ display: table-row;
+}
+
+.buffered > div > div {
+ display: table-cell;
+ vertical-align: bottom;
+}
+
+.buffered > div > div:first-child {
+ font-weight: bold;
+ padding-right: 2px;
+}
diff --git a/chromium/content/browser/resources/media/media_internals.html b/chromium/content/browser/resources/media/media_internals.html
new file mode 100644
index 00000000000..05d321f0ac7
--- /dev/null
+++ b/chromium/content/browser/resources/media/media_internals.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html i18n-values="dir:textdirection;">
+<!--
+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.
+-->
+ <head>
+ <link rel="stylesheet" href="media_internals.css">
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/cr/ui.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://media-internals/media_internals.js"></script>
+ <script src="chrome://media-internals/strings.js"></script>
+ <title>Media Internals</title>
+ </head>
+ <body>
+ <h2>Active media players:</h2>
+ <ul id="media-players"></ul>
+ <h2>Active audio streams:</h2>
+ <div id="audio-streams"></div>
+ <h2>Cached resources:</h2>
+ <div id="cache-entries"></div>
+ <script src="chrome://resources/js/i18n_template.js"></script>
+ <script src="chrome://resources/js/i18n_process.js"></script>
+ <script src="chrome://resources/js/jstemplate_compiled.js"></script>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/media_internals.js b/chromium/content/browser/resources/media/media_internals.js
new file mode 100644
index 00000000000..c3e3a1ed1d1
--- /dev/null
+++ b/chromium/content/browser/resources/media/media_internals.js
@@ -0,0 +1,281 @@
+// 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 src="cache_entry.js"/>
+<include src="disjoint_range_set.js"/>
+<include src="event_list.js"/>
+<include src="item_store.js"/>
+<include src="media_player.js"/>
+<include src="metrics.js"/>
+<include src="util.js"/>
+
+cr.define('media', function() {
+ 'use strict';
+
+ // Stores information on open audio streams, referenced by id.
+ var audioStreams = new media.ItemStore;
+
+ // Active media players, indexed by 'render_id:player_id'.
+ var mediaPlayers = {};
+
+ // Cached files indexed by key and source id.
+ var cacheEntriesByKey = {};
+ var cacheEntries = {};
+
+ // Map of event source -> url.
+ var requestURLs = {};
+
+ // Constants passed to us from Chrome.
+ var eventTypes = {};
+ var eventPhases = {};
+
+ // The <div>s on the page in which to display information.
+ var audioStreamDiv;
+ var cacheDiv;
+
+ // A timer used to limit the rate of redrawing the Media Players section.
+ var redrawTimer = null;
+
+ /**
+ * Initialize variables and ask MediaInternals for all its data.
+ */
+ function initialize() {
+ audioStreamDiv = $('audio-streams');
+ cacheDiv = $('cache-entries');
+
+ // Get information about all currently active media.
+ chrome.send('getEverything');
+ }
+
+ /**
+ * Write the set of audio streams to the DOM.
+ */
+ function printAudioStreams() {
+
+ /**
+ * Render a single stream as a <li>.
+ * @param {Object} stream The stream to render.
+ * @return {HTMLElement} A <li> containing the stream information.
+ */
+ function printStream(stream) {
+ var out = document.createElement('li');
+ out.id = stream.id;
+ out.className = 'audio-stream';
+ out.setAttribute('status', stream.status);
+
+ out.textContent += 'Audio stream ' + stream.id.split('.')[1];
+ out.textContent += ' is ' + (stream.playing ? 'playing' : 'paused');
+ if (typeof stream.volume != 'undefined') {
+ out.textContent += ' at ' + (stream.volume * 100).toFixed(0);
+ out.textContent += '% volume.';
+ }
+ return out;
+ }
+
+ var out = document.createElement('ul');
+ audioStreams.map(printStream).forEach(function(s) {
+ out.appendChild(s);
+ });
+
+ audioStreamDiv.textContent = '';
+ audioStreamDiv.appendChild(out);
+ }
+
+ /**
+ * Redraw each MediaPlayer.
+ */
+ function printMediaPlayers() {
+ for (var key in mediaPlayers) {
+ mediaPlayers[key].redraw();
+ }
+ redrawTimer = null;
+ }
+
+ /**
+ * Write the set of sparse CacheEntries to the DOM.
+ */
+ function printSparseCacheEntries() {
+ var out = document.createElement('ul');
+ for (var key in cacheEntriesByKey) {
+ if (cacheEntriesByKey[key].sparse)
+ out.appendChild(cacheEntriesByKey[key].toListItem());
+ }
+
+ cacheDiv.textContent = '';
+ cacheDiv.appendChild(out);
+ }
+
+ /**
+ * Receiving data for an audio stream.
+ * Add it to audioStreams and update the page.
+ * @param {Object} stream JSON representation of an audio stream.
+ */
+ function addAudioStream(stream) {
+ audioStreams.addItem(stream);
+ printAudioStreams();
+ }
+
+ /**
+ * Receiving all data.
+ * Add it all to the appropriate stores and update the page.
+ * @param {Object} stuff JSON containing lists of data.
+ * @param {Object} stuff.audio_streams A dictionary of audio streams.
+ */
+ function onReceiveEverything(stuff) {
+ audioStreams.addItems(stuff.audio_streams);
+ printAudioStreams();
+ }
+
+ /**
+ * Removing an item from the appropriate store.
+ * @param {string} id The id of the item to be removed, in the format
+ * "item_type.identifying_info".
+ */
+ function onItemDeleted(id) {
+ var type = id.split('.')[0];
+ switch (type) {
+ case 'audio_streams':
+ audioStreams.removeItem(id);
+ printAudioStreams();
+ break;
+ }
+ }
+
+ /**
+ * A render process has ended, delete any media players associated with it.
+ * @param {number} renderer The id of the render process.
+ */
+ function onRendererTerminated(renderer) {
+ for (var key in mediaPlayers) {
+ if (mediaPlayers[key].renderer == renderer) {
+ $('media-players').removeChild(mediaPlayers[key]);
+ delete mediaPlayers[key];
+ break;
+ }
+ }
+ printMediaPlayers();
+ }
+
+ /**
+ * Receiving net events.
+ * Update cache information and update that section of the page.
+ * @param {Array} updates A list of net events that have occurred.
+ */
+ function onNetUpdate(updates) {
+ updates.forEach(function(update) {
+ var id = update.source.id;
+ if (!cacheEntries[id])
+ cacheEntries[id] = new media.CacheEntry;
+
+ switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
+ case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
+ var key = update.params.key;
+
+ // Merge this source with anything we already know about this key.
+ if (cacheEntriesByKey[key]) {
+ cacheEntriesByKey[key].merge(cacheEntries[id]);
+ cacheEntries[id] = cacheEntriesByKey[key];
+ } else {
+ cacheEntriesByKey[key] = cacheEntries[id];
+ }
+ cacheEntriesByKey[key].key = key;
+ break;
+
+ case 'PHASE_BEGIN.SPARSE_READ':
+ cacheEntries[id].readBytes(update.params.offset,
+ update.params.buff_len);
+ cacheEntries[id].sparse = true;
+ break;
+
+ case 'PHASE_BEGIN.SPARSE_WRITE':
+ cacheEntries[id].writeBytes(update.params.offset,
+ update.params.buff_len);
+ cacheEntries[id].sparse = true;
+ break;
+
+ case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
+ requestURLs[update.source.id] = update.params.url;
+ break;
+
+ case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
+ // Record the total size of the file if this was a range request.
+ var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
+ update.params.headers);
+ var key = requestURLs[update.source.id];
+ delete requestURLs[update.source.id];
+ if (range && key) {
+ if (!cacheEntriesByKey[key]) {
+ cacheEntriesByKey[key] = new media.CacheEntry;
+ cacheEntriesByKey[key].key = key;
+ }
+ cacheEntriesByKey[key].size = range[1];
+ }
+ break;
+ }
+ });
+
+ printSparseCacheEntries();
+ }
+
+ /**
+ * Receiving values for constants. Store them for later use.
+ * @param {Object} constants A dictionary of constants.
+ * @param {Object} constants.eventTypes A dictionary of event name -> int.
+ * @param {Object} constants.eventPhases A dictionary of event phase -> int.
+ */
+ function onReceiveConstants(constants) {
+ var events = constants.eventTypes;
+ for (var e in events) {
+ eventTypes[events[e]] = e;
+ }
+
+ var phases = constants.eventPhases;
+ for (var p in phases) {
+ eventPhases[phases[p]] = p;
+ }
+ }
+
+ /**
+ * Receiving notification of a media event.
+ * @param {Object} event The json representation of a MediaLogEvent.
+ */
+ function onMediaEvent(event) {
+ var source = event.renderer + ':' + event.player;
+ var item = mediaPlayers[source] ||
+ new media.MediaPlayer({id: source, renderer: event.renderer});
+ mediaPlayers[source] = item;
+ item.addEvent(event);
+
+ // Both media and net events could provide the size of the file.
+ // Media takes priority, but keep the size in both places synchronized.
+ if (cacheEntriesByKey[item.properties.url]) {
+ item.properties.total_bytes = item.properties.total_bytes ||
+ cacheEntriesByKey[item.properties.url].size;
+ cacheEntriesByKey[item.properties.url].size = item.properties.total_bytes;
+ }
+
+ // Events tend to arrive in groups; don't redraw the page too often.
+ if (!redrawTimer)
+ redrawTimer = setTimeout(printMediaPlayers, 50);
+ }
+
+ return {
+ initialize: initialize,
+ addAudioStream: addAudioStream,
+ cacheEntriesByKey: cacheEntriesByKey,
+ onReceiveEverything: onReceiveEverything,
+ onItemDeleted: onItemDeleted,
+ onRendererTerminated: onRendererTerminated,
+ onNetUpdate: onNetUpdate,
+ onReceiveConstants: onReceiveConstants,
+ onMediaEvent: onMediaEvent
+ };
+});
+
+/**
+ * Initialize everything once we have access to the DOM.
+ */
+document.addEventListener('DOMContentLoaded', function() {
+ media.initialize();
+});
diff --git a/chromium/content/browser/resources/media/media_player.js b/chromium/content/browser/resources/media/media_player.js
new file mode 100644
index 00000000000..a9d5d6b3333
--- /dev/null
+++ b/chromium/content/browser/resources/media/media_player.js
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+ 'use strict';
+
+ /**
+ * This class inherits from <li> and is designed to store and display
+ * information about an open media player.
+ */
+ var MediaPlayer = cr.ui.define('li');
+
+ MediaPlayer.prototype = {
+ __proto__: HTMLLIElement.prototype,
+ renderer: null,
+ id: null,
+
+ /**
+ * Decorate this <li> as a MediaPlayer.
+ */
+ decorate: function() {
+ this.properties = {};
+
+ this.url_ = document.createElement('span');
+ this.url_.textContent = 'URL Unknown';
+
+ this.summary_ = document.createElement('summary');
+ this.summary_.appendChild(this.url_);
+
+ var bufferedDiv = document.createElement('div');
+ bufferedDiv.className = 'buffered';
+ this.summary_.appendChild(bufferedDiv);
+
+ // Create our canvii.
+ function createCanvas(label) {
+ var canvas = document.createElement('canvas');
+ canvas.width = media.BAR_WIDTH;
+ canvas.height = media.BAR_HEIGHT;
+ return canvas;
+ }
+ this.bufferedCanvas_ = createCanvas();
+ this.cacheReadsCanvas_ = createCanvas();
+ this.cacheWritesCanvas_ = createCanvas();
+
+ // Create our per-canvas entry divs that are initially hidden.
+ function addEntry(label, canvas) {
+ var labelDiv = document.createElement('div');
+ labelDiv.textContent = label;
+ var canvasDiv = document.createElement('div');
+ canvasDiv.appendChild(canvas);
+ var entryDiv = document.createElement('div');
+ entryDiv.appendChild(labelDiv);
+ entryDiv.appendChild(canvasDiv);
+ entryDiv.hidden = true;
+ bufferedDiv.appendChild(entryDiv);
+ return entryDiv;
+ }
+ this.bufferedEntry_ = addEntry('Buffered', this.bufferedCanvas_);
+ this.cacheReadsEntry_ = addEntry('Cache Reads', this.cacheReadsCanvas_);
+ this.cacheWritesEntry_ = addEntry(
+ 'Cache Writes', this.cacheWritesCanvas_);
+
+ this.details_ = document.createElement('details');
+ this.details_.appendChild(this.summary_);
+
+ this.propertyTable_ = document.createElement('table');
+ this.events_ = new media.EventList;
+ this.metrics_ = new media.Metrics;
+
+ var properties = media.createDetailsLi();
+ properties.summary.textContent = 'Properties:';
+ properties.details.appendChild(this.propertyTable_);
+
+ var ul = document.createElement('ul');
+ ul.appendChild(properties);
+ ul.appendChild(this.metrics_);
+ ul.appendChild(this.events_);
+ this.details_.appendChild(ul);
+
+ this.appendChild(this.details_);
+ $('media-players').appendChild(this);
+ },
+
+ /**
+ * Record an event and update statistics etc.
+ * @param {Object} event The event that occurred.
+ */
+ addEvent: function(event) {
+ for (var key in event.params) {
+ this.properties[key] = event.params[key];
+ }
+
+ if (event.type == 'LOAD' && event.params['url']) {
+ this.url_.textContent = event.params['url'];
+ }
+
+ if (event.type == 'BUFFERED_EXTENTS_CHANGED') {
+ return;
+ }
+ this.events_.addEvent(event);
+ this.metrics_.addEvent(event);
+ },
+
+ /**
+ * Update the summary line and properties table and redraw the canvas.
+ * @return {HTMLElement} A <li> representing this MediaPlayer.
+ */
+ redraw: function() {
+ media.appendDictionaryToTable(this.properties, this.propertyTable_);
+
+ this.setAttribute('status', this.properties.state);
+
+ // Don't bother drawing anything if we don't know the total size.
+ var size = this.properties.total_bytes;
+ if (!size) {
+ return;
+ }
+
+ // Draw the state of BufferedResourceLoader.
+ this.bufferedEntry_.hidden = false;
+ var canvas = this.bufferedCanvas_;
+ var context = canvas.getContext('2d');
+ context.fillStyle = '#aaa';
+ context.fillRect(0, 0, canvas.width, canvas.height);
+
+ var left = this.properties.buffer_start / size * canvas.width;
+ var middle = this.properties.buffer_current / size * canvas.width;
+ var right = this.properties.buffer_end / size * canvas.width;
+ context.fillStyle = '#a0a';
+ context.fillRect(left, 0, middle - left, canvas.height);
+ context.fillStyle = '#aa0';
+ context.fillRect(middle, 0, right - middle, canvas.height);
+
+ // Only show cached file information if we have something.
+ var cacheEntry = media.cacheEntriesByKey[this.properties.url];
+ if (!cacheEntry) {
+ return;
+ }
+
+ // Draw cache reads.
+ this.cacheReadsEntry_.hidden = false;
+ cacheEntry.drawCacheReadsToCanvas(this.cacheReadsCanvas_);
+
+ // Draw cache writes.
+ this.cacheWritesEntry_.hidden = false;
+ cacheEntry.drawCacheWritesToCanvas(this.cacheWritesCanvas_);
+ },
+ };
+
+ return {
+ MediaPlayer: MediaPlayer
+ };
+});
diff --git a/chromium/content/browser/resources/media/metrics.js b/chromium/content/browser/resources/media/metrics.js
new file mode 100644
index 00000000000..c812d44f56c
--- /dev/null
+++ b/chromium/content/browser/resources/media/metrics.js
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+ 'use strict';
+
+ // A set of parameter names. An entry of 'abc' allows metrics to specify
+ // events with specific values of 'abc'.
+ var metricProperties = {
+ 'pipeline_state': true,
+ };
+
+ // A set of metrics to measure. The user will see the most recent and average
+ // measurement of the time between each metric's start and end events.
+ var metrics = {
+ 'seek': {
+ 'start': 'SEEK',
+ 'end': 'pipeline_state=started'
+ },
+ 'first frame': {
+ 'start': 'WEBMEDIAPLAYER_CREATED',
+ 'end': 'pipeline_state=started'
+ },
+ };
+
+ /**
+ * This class measures times between the events specified above. It inherits
+ * <li> and contains a table that displays the measurements.
+ */
+ var Metrics = cr.ui.define('li');
+
+ Metrics.prototype = {
+ __proto__: HTMLLIElement.prototype,
+
+ /**
+ * Decorate this <li> as a Metrics.
+ */
+ decorate: function() {
+ this.table_ = document.createElement('table');
+ var details = document.createElement('details');
+ var summary = media.makeElement('summary', 'Metrics:');
+ details.appendChild(summary);
+ details.appendChild(this.table_);
+ this.appendChild(details);
+
+ var hRow = document.createElement('tr');
+ hRow.appendChild(media.makeElement('th', 'Metric:'));
+ hRow.appendChild(media.makeElement('th', 'Last Measure:'));
+ hRow.appendChild(media.makeElement('th', 'Average:'));
+ var header = document.createElement('thead');
+ header.appendChild(hRow);
+ this.table_.appendChild(header);
+
+ for (var metric in metrics) {
+ var last = document.createElement('td');
+ var avg = document.createElement('td');
+ this[metric] = {
+ count: 0,
+ total: 0,
+ start: null,
+ last: last,
+ avg: avg
+ };
+ var row = document.createElement('tr');
+ row.appendChild(media.makeElement('td', metric + ':'));
+ row.appendChild(last);
+ row.appendChild(avg);
+ this.table_.appendChild(row);
+ }
+ },
+
+ /**
+ * An event has occurred. Update any metrics that refer to this type
+ * of event. Can be called multiple times by addEvent below if the metrics
+ * refer to specific parameters.
+ * @param {Object} event The MediaLogEvent that has occurred.
+ * @param {string} type The type of event.
+ */
+ addEventInternal: function(event, type) {
+ for (var metric in metrics) {
+ var m = this[metric];
+ if (type == metrics[metric].start && !m.start) {
+ m.start = event.ticksMillis;
+ } else if (type == metrics[metric].end && m.start != null) {
+ var last = event.ticksMillis - m.start;
+ m.last.textContent = last.toFixed(1);
+ m.total += last;
+ m.count++;
+ if (m.count > 1)
+ m.avg.textContent = (m.total / m.count).toFixed(1);
+ m.start = null;
+ }
+ }
+ },
+
+ /**
+ * An event has occurred. Update any metrics that refer to events of this
+ * type or with this event's parameters.
+ * @param {Object} event The MediaLogEvent that has occurred.
+ */
+ addEvent: function(event) {
+ this.addEventInternal(event, event.type);
+ for (var p in event.params) {
+ if (p in metricProperties) {
+ var type = p + '=' + event.params[p];
+ this.addEventInternal(event, type);
+ }
+ }
+ },
+ };
+
+ return {
+ Metrics: Metrics,
+ };
+});
diff --git a/chromium/content/browser/resources/media/new/integration_test.html b/chromium/content/browser/resources/media/new/integration_test.html
new file mode 100644
index 00000000000..3a5225cb647
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/integration_test.html
@@ -0,0 +1,86 @@
+<!--
+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.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="webui_resource_test.js"></script>
+ <script src="util.js"></script>
+ <script src="player_manager.js"></script>
+ <script src="player_info.js"></script>
+ <script src="main.js"></script>
+ </head>
+ <body>
+ <script>
+ window.setUp = function() {
+ var doNothing = function() {};
+ var mockRenderer = {
+ redrawList: doNothing,
+ update: doNothing,
+ select: doNothing
+ };
+
+ var manager = new PlayerManager(mockRenderer);
+ media.initialize(manager);
+
+ window.playerManager = manager;
+ };
+
+ // The renderer and player ids are completely arbitrarily.
+ var TEST_RENDERER = 12;
+ var TEST_PLAYER = 4;
+ var TEST_NAME = TEST_RENDERER + ':' + TEST_PLAYER;
+
+ // Correctly use the information from a media event.
+ window.testOnMediaEvent = function() {
+ var event = {
+ ticksMillis: 132,
+ renderer: TEST_RENDERER,
+ player: TEST_PLAYER,
+ params: {
+ fps: 60,
+ other: 'hi'
+ }
+ };
+
+ window.media.onMediaEvent(event);
+ var info = window.playerManager.players_[TEST_NAME];
+
+ assertEquals(event.ticksMillis, info.firstTimestamp_);
+ assertEquals(TEST_NAME, info.id);
+ assertEquals(event.params.fps, info.properties.fps);
+ };
+
+ // Remove a player.
+ window.testOnRenderTerminated = function() {
+ window.testOnMediaEvent();
+
+ window.playerManager.shouldRemovePlayer_ = function() {
+ return true;
+ };
+
+ window.media.onRendererTerminated(TEST_RENDERER);
+ assertEquals(undefined, window.playerManager.players_[TEST_NAME]);
+ };
+
+ // Audio Streams are weird, they are handled separately
+ window.testAddAudioStream = function() {
+ var event = {
+ id: 'ID',
+ status: 'created',
+ playing: true
+ };
+
+ window.media.addAudioStream(event);
+
+ var player = window.playerManager.players_[event.id];
+ assertTrue(undefined !== player);
+ assertEquals(event.playing, player.properties['playing']);
+ };
+
+ runTests();
+ </script>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/new/main.js b/chromium/content/browser/resources/media/new/main.js
new file mode 100644
index 00000000000..61f6407bcf5
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/main.js
@@ -0,0 +1,134 @@
+// 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.
+
+/**
+ * A global object that gets used by the C++ interface.
+ */
+var media = (function() {
+ 'use strict';
+
+ var manager = null;
+
+ /**
+ * Users of |media| must call initialize prior to calling other methods.
+ */
+ function initialize(playerManager) {
+ manager = playerManager;
+ }
+
+ /**
+ * Call to modify or add a system property.
+ */
+ function onSystemProperty(timestamp, key, value) {
+ console.log('System properties not yet implemented');
+ }
+
+ /**
+ * Call to modify or add a property on a player.
+ */
+ function onPlayerProperty(id, timestamp, key, value) {
+ manager.updatePlayerInfo(id, timestamp, key, value);
+ }
+
+ function onPlayerPropertyNoRecord(id, timestamp, key, value) {
+ manager.updatePlayerInfoNoRecord(id, timestamp, key, value);
+ }
+
+ /**
+ * Call to add a player.
+ */
+ function onPlayerOpen(id, timestamp) {
+ manager.addPlayer(id, timestamp);
+ }
+
+ /**
+ * Call to remove a player.
+ */
+ function onPlayerClose(id) {
+ manager.removePlayer(id);
+ }
+
+ var media = {
+ onSystemProperty: onSystemProperty,
+ onPlayerProperty: onPlayerProperty,
+ onPlayerPropertyNoRecord: onPlayerPropertyNoRecord,
+ onPlayerOpen: onPlayerOpen,
+ onPlayerClose: onPlayerClose,
+
+ initialize: initialize
+ };
+
+ // Everything beyond this point is for backwards compatibility reasons.
+ // It will go away when the backend is updated.
+
+ media.onNetUpdate = function(update) {
+ // TODO(tyoverby): Implement
+ };
+
+ media.onRendererTerminated = function(renderId) {
+ util.object.forEach(manager.players_, function(playerInfo, id) {
+ if (playerInfo.properties['render_id'] == renderId) {
+ media.onPlayerClose(id);
+ }
+ });
+ };
+
+ // For whatever reason, addAudioStream is also called on
+ // the removal of audio streams.
+ media.addAudioStream = function(event) {
+ switch (event.status) {
+ case 'created':
+ media.onPlayerOpen(event.id);
+ // We have to simulate the timestamp since it isn't provided to us.
+ media.onPlayerProperty(
+ event.id, (new Date()).getTime(), 'playing', event.playing);
+ break;
+ case 'closed':
+ media.onPlayerClose(event.id);
+ break;
+ }
+ };
+ media.onItemDeleted = function() {
+ // This only gets called when an audio stream is removed, which
+ // for whatever reason is also handled by addAudioStream...
+ // Because it is already handled, we can safely ignore it.
+ };
+
+ media.onMediaEvent = function(event) {
+ var source = event.renderer + ':' + event.player;
+
+ // Although this gets called on every event, there is nothing we can do
+ // about this because there is no onOpen event.
+ media.onPlayerOpen(source);
+ media.onPlayerPropertyNoRecord(
+ source, event.ticksMillis, 'render_id', event.renderer);
+ media.onPlayerPropertyNoRecord(
+ source, event.ticksMillis, 'player_id', event.player);
+
+ var propertyCount = 0;
+ util.object.forEach(event.params, function(value, key) {
+ key = key.trim();
+
+ // These keys get spammed *a lot*, so put them on the display
+ // but don't log list.
+ if (key === 'buffer_start' ||
+ key === 'buffer_end' ||
+ key === 'buffer_current' ||
+ key === 'is_downloading_data') {
+ media.onPlayerPropertyNoRecord(
+ source, event.ticksMillis, key, value);
+ } else {
+ media.onPlayerProperty(source, event.ticksMillis, key, value);
+ }
+ propertyCount += 1;
+ });
+
+ if (propertyCount === 0) {
+ media.onPlayerProperty(
+ source, event.ticksMillis, 'EVENT', event.type);
+ }
+ };
+
+ return media;
+}());
diff --git a/chromium/content/browser/resources/media/new/media_internals.html b/chromium/content/browser/resources/media/new/media_internals.html
new file mode 100644
index 00000000000..0e95353aeef
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/media_internals.html
@@ -0,0 +1,18 @@
+<!--
+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.
+-->
+<!DOCTYPE html>
+<html i18n-values="dir:textdirection">
+<head>
+ <meta charset="utf-8">
+ <title i18n-content="Media Internals"></title>
+
+ <script src="chrome://media-internals/media_internals.js"></script>
+</head>
+
+<body>
+ Hello World
+</body>
+</html>
diff --git a/chromium/content/browser/resources/media/new/media_internals.js b/chromium/content/browser/resources/media/new/media_internals.js
new file mode 100644
index 00000000000..103ef74fbc0
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/media_internals.js
@@ -0,0 +1,18 @@
+// 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.
+
+var media = {};
+
+var doNothing = function() {};
+
+// Silence the backend calls.
+media.initialize = doNothing;
+media.addAudioStream = doNothing;
+media.cacheEntriesByKey = doNothing;
+media.onReceiveEverything = doNothing;
+media.onItemDeleted = doNothing;
+media.onRendererTerminated = doNothing;
+media.onNetUpdate = doNothing;
+media.onReceiveConstants = doNothing;
+media.onMediaEvent = doNothing;
diff --git a/chromium/content/browser/resources/media/new/player_info.js b/chromium/content/browser/resources/media/new/player_info.js
new file mode 100644
index 00000000000..af1f1944518
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/player_info.js
@@ -0,0 +1,80 @@
+// 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.
+
+/**
+ * @fileoverview A class for keeping track of the details of a player.
+ */
+
+var PlayerInfo = (function() {
+ 'use strict';
+
+ /**
+ * A class that keeps track of properties on a media player.
+ * @param id A unique id that can be used to identify this player.
+ */
+ function PlayerInfo(id) {
+ this.id = id;
+ // The current value of the properties for this player.
+ this.properties = {};
+ // All of the past (and present) values of the properties.
+ this.pastValues = {};
+
+ // Every single event in the order in which they were received.
+ this.allEvents = [];
+ this.lastRendered = 0;
+
+ this.firstTimestamp_ = -1;
+ }
+
+ PlayerInfo.prototype = {
+ /**
+ * Adds or set a property on this player.
+ * This is the default logging method as it keeps track of old values.
+ * @param timestamp The time in milliseconds since the Epoch.
+ * @param key A String key that describes the property.
+ * @param value The value of the property.
+ */
+ addProperty: function(timestamp, key, value) {
+ // The first timestamp that we get will be recorded.
+ // Then, all future timestamps are deltas of that.
+ if (this.firstTimestamp_ === -1) {
+ this.firstTimestamp_ = timestamp;
+ }
+
+ if (typeof key !== 'string') {
+ throw new Error(typeof key + ' is not a valid key type');
+ }
+
+ this.properties[key] = value;
+
+ if (!this.pastValues[key]) {
+ this.pastValues[key] = [];
+ }
+
+ var recordValue = {
+ time: timestamp - this.firstTimestamp_,
+ key: key,
+ value: value
+ };
+
+ this.pastValues[key].push(recordValue);
+ this.allEvents.push(recordValue);
+ },
+
+ /**
+ * Adds or set a property on this player.
+ * Does not keep track of old values. This is better for
+ * values that get spammed repeatedly.
+ * @param timestamp The time in milliseconds since the Epoch.
+ * @param key A String key that describes the property.
+ * @param value The value of the property.
+ */
+ addPropertyNoRecord: function(timestamp, key, value) {
+ this.addProperty(timestamp, key, value);
+ this.allEvents.pop();
+ }
+ };
+
+ return PlayerInfo;
+}());
diff --git a/chromium/content/browser/resources/media/new/player_info_test.html b/chromium/content/browser/resources/media/new/player_info_test.html
new file mode 100644
index 00000000000..46cc05ee3d9
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/player_info_test.html
@@ -0,0 +1,146 @@
+<!--
+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.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="webui_resource_test.js"></script>
+ <script src="player_manager.js"></script>
+ <script src="player_info.js"></script>
+ </head>
+ <body>
+ <script>
+ window.setUp = function() {
+ window.pi = new PlayerInfo('example_id');
+ };
+
+ window.tearDown = function() {
+ window.pi = null;
+ };
+
+ // Test that an ID is set correctly.
+ window.testConstructorStringID = function() {
+ assertEquals('example_id', window.pi.id);
+ };
+
+ // Test that numerical IDs are valid.
+ window.testConstructorNumberId = function() {
+ var pi = new PlayerInfo(5);
+ assertEquals(5, pi.id);
+ };
+
+ // Make sure that a new PlayerInfo has no events.
+ window.testEmptyEvents = function() {
+ assertEquals(0, window.pi.allEvents.length);
+ };
+
+ // Check that the most recent property gets updated.
+ window.testAddProperty = function() {
+ var key = 'key',
+ value = 'value',
+ value2 = 'value2';
+
+ window.pi.addProperty(0, key, value);
+ assertEquals(value, window.pi.properties[key]);
+
+ window.pi.addProperty(0, key, value2);
+ assertEquals(value2, window.pi.properties[key]);
+
+ };
+
+ // Make sure that the first timestamp that gets sent
+ // is recorded as the base timestamp.
+ window.testFirstTimestamp = function() {
+ var pi = new PlayerInfo('example_ID');
+ var timestamp = 5000;
+ pi.addProperty(timestamp, 'key', 'value');
+
+ assertEquals(timestamp, pi.firstTimestamp_);
+ };
+
+ // Adding a property with a non-string key should
+ // throw an exception.
+ window.testWrongKeyType = function() {
+ var pi = new PlayerInfo('example_ID');
+ assertThrows(function() {
+ pi.addProperty(0, 5, 'some value');
+ });
+ };
+
+ // Subsequent events should have their log offset based
+ // on the first timestamp added.
+ window.testAddPropertyTimestampOffset = function() {
+ var firstTimestamp = 500,
+ secondTimestamp = 550,
+ deltaT = secondTimestamp - firstTimestamp,
+ key = 'key',
+ value = 'value';
+
+ var pi = new PlayerInfo('example_ID');
+ pi.addProperty(firstTimestamp, key, value);
+ pi.addProperty(secondTimestamp, key, value);
+
+ assertEquals(firstTimestamp, pi.firstTimestamp_);
+ assertEquals(0, pi.allEvents[0].time);
+ assertEquals(deltaT, pi.allEvents[1].time);
+
+ assertTrue(undefined !== pi.pastValues[key]);
+
+ console.log(pi.pastValues);
+
+ assertEquals(0, pi.pastValues[key][0].time);
+ assertEquals(deltaT, pi.pastValues[key][1].time);
+ };
+
+ // Check to make sure that properties are correctly
+ // added to the relevant pastValues array.
+ window.testAddPropertyPastValues = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value';
+
+ pi.addProperty(timestamp, key, value);
+
+ assertEquals(value, pi.pastValues[key][0].value);
+ assertEquals(key, pi.pastValues[key][0].key);
+ assertEquals(0, pi.pastValues[key][0].time);
+ };
+
+ // The list of all events should be recorded in correctly.
+ window.testAllEvents = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value',
+ key2 = 'key2',
+ value2 = 'value2';
+
+ pi.addProperty(timestamp, key, value);
+ assertEquals(value, pi.allEvents[0].value);
+ assertEquals(key, pi.allEvents[0].key);
+
+ pi.addProperty(timestamp, key2, value2);
+ assertEquals(value2, pi.allEvents[1].value);
+ assertEquals(key2, pi.allEvents[1].key);
+ };
+
+ // Using noRecord should make it not show up in allEvents,
+ // but it should still show up in pastValues[key].
+ window.testNoRecord = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value';
+ pi.addPropertyNoRecord(timestamp, key, value);
+
+ assertEquals(value, pi.properties[key]);
+ assertEquals(0, pi.allEvents.length);
+ assertEquals(1, pi.pastValues[key].length);
+ };
+ runTests();
+ </script>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/new/player_manager.js b/chromium/content/browser/resources/media/new/player_manager.js
new file mode 100644
index 00000000000..3de93357f98
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/player_manager.js
@@ -0,0 +1,111 @@
+// 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.
+
+/**
+ * @fileoverview Keeps track of all the existing
+ * PlayerProperty objects and is the entry-point for messages from the backend.
+ */
+var PlayerManager = (function() {
+ 'use strict';
+
+ function PlayerManager(renderManager) {
+ this.players_ = {};
+ this.renderman_ = renderManager;
+ renderManager.playerManager = this;
+
+ this.shouldRemovePlayer_ = function() {
+ // This is only temporary until we get the UI hooked up.
+ return true;
+ };
+ }
+
+ PlayerManager.prototype = {
+
+ /**
+ * Adds a player to the list of players to manage.
+ */
+ addPlayer: function(id) {
+ if (this.players_[id]) {
+ return;
+ }
+ // Make the PlayerProperty and add it to the mapping
+ this.players_[id] = new PlayerInfo(id);
+
+ this.renderman_.redrawList();
+ },
+
+ /**
+ * Attempts to remove a player from the UI.
+ * @param id The ID of the player to remove.
+ */
+ removePlayer: function(id) {
+ // Look at the check box to see if we should actually
+ // remove it from the UI
+ if (this.shouldRemovePlayer_()) {
+ delete this.players_[id];
+ this.renderman_.redrawList();
+ } else if (this.players_[id]) {
+ // Set a property on it to be removed at a later time
+ this.players_[id].toRemove = true;
+ }
+ },
+
+ /**
+ * Selects a player and displays it on the UI.
+ * This method is called from the UI.
+ * @param id The ID of the player to display.
+ */
+ selectPlayer: function(id) {
+ if (!this.players_[id]) {
+ throw new Error('[selectPlayer] Id ' + id + ' does not exist.');
+ }
+
+ this.renderman_.select(id);
+ },
+
+ updatePlayerInfoNoRecord: function(id, timestamp, key, value) {
+ if (!this.players_[id]) {
+ console.error('[updatePlayerInfo] Id ' + id +
+ ' does not exist');
+ return;
+ }
+
+ this.players_[id].addPropertyNoRecord(timestamp, key, value);
+
+ // If we can potentially rename the player, do so.
+ if (key === 'name' || key === 'url') {
+ this.renderman_.redrawList();
+ }
+
+ this.renderman_.update();
+ },
+
+ /**
+ *
+ * @param id The unique ID that identifies the player to be updated.
+ * @param timestamp The timestamp of when the change occured. This
+ * timestamp is *not* normalized.
+ * @param key The name of the property to be added/changed.
+ * @param value The value of the property.
+ */
+ updatePlayerInfo: function(id, timestamp, key, value) {
+ if (!this.players_[id]) {
+ console.error('[updatePlayerInfo] Id ' + id +
+ ' does not exist');
+ return;
+ }
+
+ this.players_[id].addProperty(timestamp, key, value);
+
+ // If we can potentially rename the player, do so.
+ if (key === 'name' || key === 'url') {
+ this.renderman_.redrawList();
+ }
+
+ this.renderman_.update();
+ }
+ };
+
+ return PlayerManager;
+}());
diff --git a/chromium/content/browser/resources/media/new/player_manager_test.html b/chromium/content/browser/resources/media/new/player_manager_test.html
new file mode 100644
index 00000000000..eff78b53c59
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/player_manager_test.html
@@ -0,0 +1,155 @@
+<!--
+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.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="webui_resource_test.js"></script>
+ <script src="player_manager.js"></script>
+ <script src="player_info.js"></script>
+ </head>
+ <body>
+ <script>
+ var doNothing = function() {
+ };
+
+ var emptyRenderMan = {
+ redrawList: doNothing,
+ update: doNothing,
+ select: doNothing
+ };
+
+ window.setUp = function() {
+ window.pm = new PlayerManager(emptyRenderMan);
+ };
+
+ window.tearDown = function() {
+ window.pm = null;
+ };
+
+ // Test a normal case of .addPlayer
+ window.testAddPlayer = function() {
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+ // Make sure that adding a player forces a redraw
+ // on the renderManager.
+ window.testAddPlayerForceRedraw = function() {
+ var redrew = false;
+ var mockRenderManager = {
+ redrawList: function() {
+ redrew = true;
+ }
+ };
+ var pm = new PlayerManager(mockRenderManager);
+
+ pm.addPlayer('someid');
+ assertTrue(redrew);
+ };
+
+ // On occasion, the backend will add an existing ID multiple times.
+ // make sure this doesn't break anything.
+ window.testAddPlayerAlreadyExisting = function() {
+ window.pm.addPlayer('someid');
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+ // If the removal is set, make sure that a player
+ // gets removed from the PlayerManager.
+ window.testRemovePlayerShouldRemove = function() {
+ // Because we don't have the checkbox.
+ window.pm.shouldRemovePlayer_ = function() {
+ return true;
+ };
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ window.pm.removePlayer('someid');
+ assertTrue(undefined === window.pm.players_['someid']);
+ };
+
+ // On the removal of a player, the renderer should be forced
+ // to redraw the list.
+ window.testRemovePlayerRedraw = function() {
+ var redrew = false;
+
+ var fakeObj = {
+ redrawList: function() {
+ redrew = true;
+ }
+ };
+
+ var pm = new PlayerManager(fakeObj);
+ // Because we don't have the checkbox;
+ pm.shouldRemovePlayer_ = function() {
+ return true;
+ };
+
+
+ pm.addPlayer('someid');
+ assertTrue(undefined !== pm.players_['someid']);
+ pm.removePlayer('someid');
+ assertTrue(undefined === pm.players_['someid']);
+
+ assertTrue(redrew);
+ };
+
+ // If you shouldn't remove the player, the player shouldn't be
+ // removed.
+ window.testRemovePlayerNoRemove = function() {
+ window.pm = new PlayerManager(emptyRenderMan);
+ // Because we don't have the checkbox;
+ window.pm.shouldRemovePlayer_ = function() {
+ return false;
+ };
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ window.pm.removePlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+
+ // Removing a nonexistant player shouldn't break anything
+ // The backend also occasionally does this.
+ window.testRemovePlayerNonExistant = function() {
+ // Because we don't have the checkbox;
+ window.pm.shouldRemovePlayer_ = function() {
+ return false;
+ };
+ window.pm.removePlayer('someid');
+ assertTrue(undefined === window.pm.players_['someid']);
+ };
+
+ // Trying to select a non-existant player should throw
+ // an exception
+ window.testSelectNonExistant = function() {
+ assertThrows(function() {
+ window.pm.selectPlayer('someId');
+ });
+ };
+
+ // Selecting an existing player should trigger a redraw
+ window.testSelectExistingPlayer = function() {
+ var selected = false;
+ var redrew = false;
+ var pm = new PlayerManager({
+ select: function() {
+ selected = true;
+ },
+ redrawList: function() {
+ redrew = true;
+ }
+ });
+ pm.addPlayer('someId');
+ pm.selectPlayer('someId');
+
+ assertTrue(selected);
+ assertTrue(redrew);
+ };
+ runTests();
+ </script>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/new/util.js b/chromium/content/browser/resources/media/new/util.js
new file mode 100644
index 00000000000..5909e9ee1eb
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/util.js
@@ -0,0 +1,34 @@
+// 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.
+
+/**
+ * @fileoverview Some utility functions that don't belong anywhere else in the
+ * code.
+ */
+
+var util = (function() {
+ var util = {};
+ util.object = {};
+ /**
+ * Calls a function for each element in an object/map/hash.
+ *
+ * @param obj The object to iterate over.
+ * @param f The function to call on every value in the object. F should have
+ * the following arguments: f(value, key, object) where value is the value
+ * of the property, key is the corresponding key, and obj is the object that
+ * was passed in originally.
+ * @param optObj The object use as 'this' within f.
+ */
+ util.object.forEach = function(obj, f, optObj) {
+ 'use strict';
+ var key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ f.call(optObj, obj[key], key, obj);
+ }
+ }
+ };
+
+ return util;
+}());
diff --git a/chromium/content/browser/resources/media/new/webui_resource_test.js b/chromium/content/browser/resources/media/new/webui_resource_test.js
new file mode 100644
index 00000000000..6b05a305a70
--- /dev/null
+++ b/chromium/content/browser/resources/media/new/webui_resource_test.js
@@ -0,0 +1,210 @@
+// 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.
+
+/**
+ * Tests that an observation matches the expected value.
+ * @param {Object} expected The expected value.
+ * @param {Object} observed The actual value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertEquals(expected, observed, opt_message) {
+ if (observed !== expected) {
+ var message = 'Assertion Failed\n Observed: ' + observed +
+ '\n Expected: ' + expected;
+ if (opt_message)
+ message = message + '\n ' + opt_message;
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that a test result is true.
+ * @param {boolean} observed The observed value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertTrue(observed, opt_message) {
+ assertEquals(true, observed, opt_message);
+}
+
+/**
+ * Verifies that a test result is false.
+ * @param {boolean} observed The observed value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertFalse(observed, opt_message) {
+ assertEquals(false, observed, opt_message);
+}
+
+/**
+ * Verifies that the observed and reference values differ.
+ * @param {Object} reference The target value for comparison.
+ * @param {Object} observed The test result.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertNotEqual(reference, observed, opt_message) {
+ if (observed === reference) {
+ var message = 'Assertion Failed\n Observed: ' + observed +
+ '\n Reference: ' + reference;
+ if (opt_message)
+ message = message + '\n ' + opt_message;
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that a test evaluation results in an exception.
+ * @param {!Function} f The test function.
+ */
+function assertThrows(f) {
+ var triggeredError = false;
+ try {
+ f();
+ } catch (err) {
+ triggeredError = true;
+ }
+ if (!triggeredError)
+ throw new Error('Assertion Failed: throw expected.');
+}
+
+/**
+ * Verifies that the contents of the expected and observed arrays match.
+ * @param {!Array} expected The expected result.
+ * @param {!Array} observed The actual result.
+ */
+function assertArrayEquals(expected, observed) {
+ var v1 = Array.prototype.slice.call(expected);
+ var v2 = Array.prototype.slice.call(observed);
+ var equal = v1.length == v2.length;
+ if (equal) {
+ for (var i = 0; i < v1.length; i++) {
+ if (v1[i] !== v2[i]) {
+ equal = false;
+ break;
+ }
+ }
+ }
+ if (!equal) {
+ var message =
+ ['Assertion Failed', 'Observed: ' + v2, 'Expected: ' + v1].join('\n ');
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that the expected and observed result have the same content.
+ * @param {*} expected The expected result.
+ * @param {*} observed The actual result.
+ */
+function assertDeepEquals(expected, observed, opt_message) {
+ if (typeof expected == 'object' && expected != null) {
+ assertNotEqual(null, observed);
+ for (var key in expected) {
+ assertTrue(key in observed, opt_message);
+ assertDeepEquals(expected[key], observed[key], opt_message);
+ }
+ for (var key in observed) {
+ assertTrue(key in expected, opt_message);
+ }
+ } else {
+ assertEquals(expected, observed, opt_message);
+ }
+}
+
+/**
+ * Defines runTests.
+ */
+(function(exports) {
+ /**
+ * List of test cases.
+ * @type {Array.<string>} List of function names for tests to run.
+ */
+ var testCases = [];
+
+ /**
+ * Indicates if all tests have run successfully.
+ * @type {boolean}
+ */
+ var cleanTestRun = true;
+
+ /**
+ * Armed during setup of a test to call the matching tear down code.
+ * @type {Function}
+ */
+ var pendingTearDown = null;
+
+ /**
+ * Runs all functions starting with test and reports success or
+ * failure of the test suite.
+ */
+ function runTests() {
+ for (var name in window) {
+ if (typeof window[name] == 'function' && /^test/.test(name))
+ testCases.push(name);
+ }
+ if (!testCases.length) {
+ console.error('Failed to find test cases.');
+ cleanTestRun = false;
+ }
+ continueTesting();
+ }
+
+ function reportPass(name) {
+ 'use strict';
+ var text = document.createTextNode(name + ': PASSED');
+ var span = document.createElement('span');
+ span.appendChild(text);
+ document.body.appendChild(span);
+ document.body.appendChild(document.createElement('br'));
+ }
+
+ function reportFail(name) {
+ 'use strict';
+ var text = document.createTextNode(name + ': =========FAILED=======');
+ var span = document.createElement('span');
+ span.appendChild(text);
+ document.body.appendChild(span);
+ document.body.appendChild(document.createElement('br'));
+ }
+
+ /**
+ * Runs the next test in the queue. Reports the test results if the queue is
+ * empty.
+ */
+ function continueTesting() {
+ if (pendingTearDown) {
+ pendingTearDown();
+ pendingTearDown = null;
+ }
+ if (testCases.length > 0) {
+ var fn = testCases.pop();
+ var isAsyncTest = window[fn].length;
+ try {
+ if (window.setUp)
+ window.setUp();
+ pendingTearDown = window.tearDown;
+ window[fn](continueTesting);
+ reportPass(fn);
+ } catch (err) {
+ reportFail(fn);
+ console.error('Failure in test ' + fn + '\n' + err);
+ console.log(err.stack);
+ cleanTestRun = false;
+ }
+ // Asynchronous tests must manually call continueTesting when complete.
+ if (!isAsyncTest)
+ continueTesting();
+ }
+ if (testCases.length) {
+ domAutomationController.setAutomationId(1);
+ domAutomationController.send('PENDING');
+ }
+ };
+
+ exports.runTests = runTests;
+})(this);
+
diff --git a/chromium/content/browser/resources/media/peer_connection_update_table.js b/chromium/content/browser/resources/media/peer_connection_update_table.js
new file mode 100644
index 00000000000..0f4cc0cde90
--- /dev/null
+++ b/chromium/content/browser/resources/media/peer_connection_update_table.js
@@ -0,0 +1,128 @@
+// Copyright (c) 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.
+
+
+/**
+ * The data of a peer connection update.
+ * @param {number} pid The id of the renderer.
+ * @param {number} lid The id of the peer conneciton inside a renderer.
+ * @param {string} type The type of the update.
+ * @param {string} value The details of the update.
+ * @constructor
+ */
+var PeerConnectionUpdateEntry = function(pid, lid, type, value) {
+ /**
+ * @type {number}
+ */
+ this.pid = pid;
+
+ /**
+ * @type {number}
+ */
+ this.lid = lid;
+
+ /**
+ * @type {string}
+ */
+ this.type = type;
+
+ /**
+ * @type {string}
+ */
+ this.value = value;
+};
+
+
+/**
+ * Maintains the peer connection update log table.
+ */
+var PeerConnectionUpdateTable = (function() {
+ 'use strict';
+
+ /**
+ * @constructor
+ */
+ function PeerConnectionUpdateTable() {
+ /**
+ * @type {string}
+ * @const
+ * @private
+ */
+ this.UPDATE_LOG_ID_SUFFIX_ = '-update-log';
+
+ /**
+ * @type {string}
+ * @const
+ * @private
+ */
+ this.UPDATE_LOG_CONTAINER_CLASS_ = 'update-log-container';
+
+ /**
+ * @type {string}
+ * @const
+ * @private
+ */
+ this.UPDATE_LOG_TABLE_CLASS = 'update-log-table';
+ }
+
+ PeerConnectionUpdateTable.prototype = {
+ /**
+ * Adds the update to the update table as a new row. The type of the update
+ * is set to the summary of the cell; clicking the cell will reveal or hide
+ * the details as the content of a TextArea element.
+ *
+ * @param {!Element} peerConnectionElement The root element.
+ * @param {!PeerConnectionUpdateEntry} update The update to add.
+ */
+ addPeerConnectionUpdate: function(peerConnectionElement, update) {
+ var tableElement = this.ensureUpdateContainer_(peerConnectionElement);
+
+ var row = document.createElement('tr');
+ tableElement.firstChild.appendChild(row);
+
+ row.innerHTML = '<td>' + (new Date()).toLocaleString() + '</td>';
+
+ if (update.value.length == 0) {
+ row.innerHTML += '<td>' + update.type + '</td>';
+ return;
+ }
+
+ row.innerHTML += '<td><details><summary>' + update.type +
+ '</summary></details></td>';
+
+ var valueContainer = document.createElement('pre');
+ var details = row.cells[1].childNodes[0];
+ details.appendChild(valueContainer);
+ valueContainer.textContent = update.value;
+ },
+
+ /**
+ * Makes sure the update log table of the peer connection is created.
+ *
+ * @param {!Element} peerConnectionElement The root element.
+ * @return {!Element} The log table element.
+ * @private
+ */
+ ensureUpdateContainer_: function(peerConnectionElement) {
+ var tableId = peerConnectionElement.id + this.UPDATE_LOG_ID_SUFFIX_;
+ var tableElement = $(tableId);
+ if (!tableElement) {
+ var tableContainer = document.createElement('div');
+ tableContainer.className = this.UPDATE_LOG_CONTAINER_CLASS_;
+ peerConnectionElement.appendChild(tableContainer);
+
+ tableElement = document.createElement('table');
+ tableElement.className = this.UPDATE_LOG_TABLE_CLASS;
+ tableElement.id = tableId;
+ tableElement.border = 1;
+ tableContainer.appendChild(tableElement);
+ tableElement.innerHTML = '<tr><th>Time</th>' +
+ '<th class="update-log-header-event">Event</th></tr>';
+ }
+ return tableElement;
+ }
+ };
+
+ return PeerConnectionUpdateTable;
+})();
diff --git a/chromium/content/browser/resources/media/ssrc_info_manager.js b/chromium/content/browser/resources/media/ssrc_info_manager.js
new file mode 100644
index 00000000000..bb99f81d7e9
--- /dev/null
+++ b/chromium/content/browser/resources/media/ssrc_info_manager.js
@@ -0,0 +1,166 @@
+// Copyright (c) 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.
+
+
+
+/**
+ * Get the ssrc if |report| is an ssrc report.
+ *
+ * @param {!Object} report The object contains id, type, and stats, where stats
+ * is the object containing timestamp and values, which is an array of
+ * strings, whose even index entry is the name of the stat, and the odd
+ * index entry is the value.
+ * @return {?string} The ssrc.
+ */
+function GetSsrcFromReport(report) {
+ if (report.type != 'ssrc') {
+ console.warn("Trying to get ssrc from non-ssrc report.");
+ return null;
+ }
+
+ // If the 'ssrc' name-value pair exists, return the value; otherwise, return
+ // the report id.
+ // The 'ssrc' name-value pair only exists in an upcoming Libjingle change. Old
+ // versions use id to refer to the ssrc.
+ //
+ // TODO(jiayl): remove the fallback to id once the Libjingle change is rolled
+ // to Chrome.
+ if (report.stats && report.stats.values) {
+ for (var i = 0; i < report.stats.values.length - 1; i += 2) {
+ if (report.stats.values[i] == 'ssrc') {
+ return report.stats.values[i + 1];
+ }
+ }
+ }
+ return report.id;
+};
+
+/**
+ * SsrcInfoManager stores the ssrc stream info extracted from SDP.
+ */
+var SsrcInfoManager = (function() {
+ 'use strict';
+
+ /**
+ * @constructor
+ */
+ function SsrcInfoManager() {
+ /**
+ * Map from ssrc id to an object containing all the stream properties.
+ * @type {!Object.<string, !Object.<string>>}
+ * @private
+ */
+ this.streamInfoContainer_ = {};
+
+ /**
+ * The string separating attibutes in an SDP.
+ * @type {string}
+ * @const
+ * @private
+ */
+ this.ATTRIBUTE_SEPARATOR_ = /[\r,\n]/;
+
+ /**
+ * The regex separating fields within an ssrc description.
+ * @type {RegExp}
+ * @const
+ * @private
+ */
+ this.FIELD_SEPARATOR_REGEX_ = / .*:/;
+
+ /**
+ * The prefix string of an ssrc description.
+ * @type {string}
+ * @const
+ * @private
+ */
+ this.SSRC_ATTRIBUTE_PREFIX_ = 'a=ssrc:';
+
+ /**
+ * The className of the ssrc info parent element.
+ * @type {string}
+ * @const
+ */
+ this.SSRC_INFO_BLOCK_CLASS = 'ssrc-info-block';
+ }
+
+ SsrcInfoManager.prototype = {
+ /**
+ * Extracts the stream information from |sdp| and saves it.
+ * For example:
+ * a=ssrc:1234 msid:abcd
+ * a=ssrc:1234 label:hello
+ *
+ * @param {string} sdp The SDP string.
+ */
+ addSsrcStreamInfo: function(sdp) {
+ var attributes = sdp.split(this.ATTRIBUTE_SEPARATOR_);
+ for (var i = 0; i < attributes.length; ++i) {
+ // Check if this is a ssrc attribute.
+ if (attributes[i].indexOf(this.SSRC_ATTRIBUTE_PREFIX_) != 0)
+ continue;
+
+ var nextFieldIndex = attributes[i].search(this.FIELD_SEPARATOR_REGEX_);
+
+ if (nextFieldIndex == -1)
+ continue;
+
+ var ssrc = attributes[i].substring(this.SSRC_ATTRIBUTE_PREFIX_.length,
+ nextFieldIndex);
+ if (!this.streamInfoContainer_[ssrc])
+ this.streamInfoContainer_[ssrc] = {};
+
+ // Make |rest| starting at the next field.
+ var rest = attributes[i].substring(nextFieldIndex + 1);
+ var name, value;
+ while (rest.length > 0) {
+ nextFieldIndex = rest.search(this.FIELD_SEPARATOR_REGEX_);
+ if (nextFieldIndex == -1)
+ nextFieldIndex = rest.length;
+
+ // The field name is the string before the colon.
+ name = rest.substring(0, rest.indexOf(':'));
+ // The field value is from after the colon to the next field.
+ value = rest.substring(rest.indexOf(':') + 1, nextFieldIndex);
+ this.streamInfoContainer_[ssrc][name] = value;
+
+ // Move |rest| to the start of the next field.
+ rest = rest.substring(nextFieldIndex + 1);
+ }
+ }
+ },
+
+ /**
+ * @param {string} sdp The ssrc id.
+ * @return {!Object.<string>} The object containing the ssrc infomation.
+ */
+ getStreamInfo: function(ssrc) {
+ return this.streamInfoContainer_[ssrc];
+ },
+
+ /**
+ * Populate the ssrc information into |parentElement|, each field as a
+ * DIV element.
+ *
+ * @param {!Element} parentElement The parent element for the ssrc info.
+ * @param {string} ssrc The ssrc id.
+ */
+ populateSsrcInfo: function(parentElement, ssrc) {
+ if (!this.streamInfoContainer_[ssrc])
+ return;
+
+ parentElement.className = this.SSRC_INFO_BLOCK_CLASS;
+
+ var fieldElement;
+ for (var property in this.streamInfoContainer_[ssrc]) {
+ fieldElement = document.createElement('div');
+ parentElement.appendChild(fieldElement);
+ fieldElement.textContent =
+ property + ':' + this.streamInfoContainer_[ssrc][property];
+ }
+ }
+ };
+
+ return SsrcInfoManager;
+})();
diff --git a/chromium/content/browser/resources/media/stats_graph_helper.js b/chromium/content/browser/resources/media/stats_graph_helper.js
new file mode 100644
index 00000000000..ce9e7185776
--- /dev/null
+++ b/chromium/content/browser/resources/media/stats_graph_helper.js
@@ -0,0 +1,265 @@
+// Copyright (c) 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.
+
+//
+// This file contains helper methods to draw the stats timeline graphs.
+// Each graph represents a series of stats report for a PeerConnection,
+// e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent
+// for ssrc-abcd123 of PeerConnection 0 in process 1234.
+// The graphs are drawn as CANVAS, grouped per report type per PeerConnection.
+// Each group has an expand/collapse button and is collapsed initially.
+//
+
+<include src="timeline_graph_view.js"/>
+
+var STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading';
+
+// Specifies which stats should be drawn on the 'bweCompound' graph and how.
+var bweCompoundGraphConfig = {
+ googAvailableSendBandwidth: {color: 'red'},
+ googTargetEncBitrateCorrected: {color: 'purple'},
+ googActualEncBitrate: {color: 'orange'},
+ googRetransmitBitrate: {color: 'blue'},
+ googTransmitBitrate: {color: 'green'},
+};
+
+// Converts the last entry of |srcDataSeries| from the total amount to the
+// amount per second.
+var totalToPerSecond = function(srcDataSeries) {
+ var length = srcDataSeries.dataPoints_.length;
+ if (length >= 2) {
+ var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
+ var secondLastDataPoint = srcDataSeries.dataPoints_[length - 2];
+ return (lastDataPoint.value - secondLastDataPoint.value) * 1000 /
+ (lastDataPoint.time - secondLastDataPoint.time);
+ }
+
+ return 0;
+};
+
+// Converts the value of total bytes to bits per second.
+var totalBytesToBitsPerSecond = function(srcDataSeries) {
+ return totalToPerSecond(srcDataSeries) * 8;
+};
+
+// Specifies which stats should be converted before drawn and how.
+// |convertedName| is the name of the converted value, |convertFunction|
+// is the function used to calculate the new converted value based on the
+// original dataSeries.
+var dataConversionConfig = {
+ packetsSent: {
+ convertedName: 'packetsSentPerSecond',
+ convertFunction: totalToPerSecond,
+ },
+ bytesSent: {
+ convertedName: 'bitsSentPerSecond',
+ convertFunction: totalBytesToBitsPerSecond,
+ },
+ packetsReceived: {
+ convertedName: 'packetsReceivedPerSecond',
+ convertFunction: totalToPerSecond,
+ },
+ bytesReceived: {
+ convertedName: 'bitsReceivedPerSecond',
+ convertFunction: totalBytesToBitsPerSecond,
+ },
+ // This is due to a bug of wrong units reported for googTargetEncBitrate.
+ // TODO (jiayl): remove this when the unit bug is fixed.
+ googTargetEncBitrate: {
+ convertedName: 'googTargetEncBitrateCorrected',
+ convertFunction: function (srcDataSeries) {
+ var length = srcDataSeries.dataPoints_.length;
+ var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
+ if (lastDataPoint.value < 5000)
+ return lastDataPoint.value * 1000;
+ return lastDataPoint.value;
+ }
+ }
+};
+
+
+// The object contains the stats names that should not be added to the graph,
+// even if they are numbers.
+var statsNameBlackList = {
+ 'ssrc': true,
+ 'googTrackId': true,
+ 'googComponent': true,
+ 'googLocalAddress': true,
+ 'googRemoteAddress': true,
+};
+
+var graphViews = {};
+
+// Returns number parsed from |value|, or NaN if the stats name is black-listed.
+function getNumberFromValue(name, value) {
+ if (statsNameBlackList[name])
+ return NaN;
+ return parseFloat(value);
+}
+
+// Adds the stats report |report| to the timeline graph for the given
+// |peerConnectionElement|.
+function drawSingleReport(peerConnectionElement, report) {
+ var reportType = report.type;
+ var reportId = report.id;
+ var stats = report.stats;
+ if (!stats || !stats.values)
+ return;
+
+ for (var i = 0; i < stats.values.length - 1; i = i + 2) {
+ var rawLabel = stats.values[i];
+ var rawDataSeriesId = reportId + '-' + rawLabel;
+ var rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]);
+ if (isNaN(rawValue)) {
+ // We do not draw non-numerical values, but still want to record it in the
+ // data series.
+ addDataSeriesPoint(peerConnectionElement,
+ rawDataSeriesId, stats.timestamp,
+ rawLabel, stats.values[i + 1]);
+ continue;
+ }
+
+ var finalDataSeriesId = rawDataSeriesId;
+ var finalLabel = rawLabel;
+ var finalValue = rawValue;
+ // We need to convert the value if dataConversionConfig[rawLabel] exists.
+ if (dataConversionConfig[rawLabel]) {
+ // Updates the original dataSeries before the conversion.
+ addDataSeriesPoint(peerConnectionElement,
+ rawDataSeriesId, stats.timestamp,
+ rawLabel, rawValue);
+
+ // Convert to another value to draw on graph, using the original
+ // dataSeries as input.
+ finalValue = dataConversionConfig[rawLabel].convertFunction(
+ peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
+ rawDataSeriesId));
+ finalLabel = dataConversionConfig[rawLabel].convertedName;
+ finalDataSeriesId = reportId + '-' + finalLabel;
+ }
+
+ // Updates the final dataSeries to draw.
+ addDataSeriesPoint(peerConnectionElement,
+ finalDataSeriesId,
+ stats.timestamp,
+ finalLabel,
+ finalValue);
+
+ // Updates the graph.
+ var graphType = bweCompoundGraphConfig[finalLabel] ?
+ 'bweCompound' : finalLabel;
+ var graphViewId =
+ peerConnectionElement.id + '-' + reportId + '-' + graphType;
+
+ if (!graphViews[graphViewId]) {
+ graphViews[graphViewId] = createStatsGraphView(peerConnectionElement,
+ report,
+ graphType);
+ var date = new Date(stats.timestamp);
+ graphViews[graphViewId].setDateRange(date, date);
+ }
+ // Adds the new dataSeries to the graphView. We have to do it here to cover
+ // both the simple and compound graph cases.
+ var dataSeries =
+ peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
+ finalDataSeriesId);
+ if (!graphViews[graphViewId].hasDataSeries(dataSeries))
+ graphViews[graphViewId].addDataSeries(dataSeries);
+ graphViews[graphViewId].updateEndDate();
+ }
+}
+
+// Makes sure the TimelineDataSeries with id |dataSeriesId| is created,
+// and adds the new data point to it.
+function addDataSeriesPoint(
+ peerConnectionElement, dataSeriesId, time, label, value) {
+ var dataSeries =
+ peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
+ dataSeriesId);
+ if (!dataSeries) {
+ dataSeries = new TimelineDataSeries();
+ peerConnectionDataStore[peerConnectionElement.id].setDataSeries(
+ dataSeriesId, dataSeries);
+ if (bweCompoundGraphConfig[label]) {
+ dataSeries.setColor(bweCompoundGraphConfig[label].color);
+ }
+ }
+ dataSeries.addPoint(time, value);
+}
+
+// Ensures a div container to hold all stats graphs for one track is created as
+// a child of |peerConnectionElement|.
+function ensureStatsGraphTopContainer(peerConnectionElement, report) {
+ var containerId = peerConnectionElement.id + '-' +
+ report.type + '-' + report.id + '-graph-container';
+ var container = $(containerId);
+ if (!container) {
+ container = document.createElement('details');
+ container.id = containerId;
+ container.className = 'stats-graph-container';
+
+ peerConnectionElement.appendChild(container);
+ container.innerHTML ='<summary><span></span></summary>';
+ container.firstChild.firstChild.className =
+ STATS_GRAPH_CONTAINER_HEADING_CLASS;
+ container.firstChild.firstChild.textContent =
+ 'Stats graphs for ' + report.id;
+
+ if (report.type == 'ssrc') {
+ var ssrcInfoElement = document.createElement('div');
+ container.firstChild.appendChild(ssrcInfoElement);
+ ssrcInfoManager.populateSsrcInfo(ssrcInfoElement,
+ GetSsrcFromReport(report));
+ }
+ }
+ return container;
+}
+
+// Creates the container elements holding a timeline graph
+// and the TimelineGraphView object.
+function createStatsGraphView(
+ peerConnectionElement, report, statsName) {
+ var topContainer = ensureStatsGraphTopContainer(peerConnectionElement,
+ report);
+
+ var graphViewId =
+ peerConnectionElement.id + '-' + report.id + '-' + statsName;
+ var divId = graphViewId + '-div';
+ var canvasId = graphViewId + '-canvas';
+ var container = document.createElement("div");
+ container.className = 'stats-graph-sub-container';
+
+ topContainer.appendChild(container);
+ container.innerHTML = '<div>' + statsName + '</div>' +
+ '<div id=' + divId + '><canvas id=' + canvasId + '></canvas></div>';
+ if (statsName == 'bweCompound') {
+ container.insertBefore(
+ createBweCompoundLegend(peerConnectionElement, report.id),
+ $(divId));
+ }
+ return new TimelineGraphView(divId, canvasId);
+}
+
+// Creates the legend section for the bweCompound graph.
+// Returns the legend element.
+function createBweCompoundLegend(peerConnectionElement, reportId) {
+ var legend = document.createElement('div');
+ for (var prop in bweCompoundGraphConfig) {
+ var div = document.createElement('div');
+ legend.appendChild(div);
+ div.innerHTML = '<input type=checkbox checked></input>' + prop;
+ div.style.color = bweCompoundGraphConfig[prop].color;
+ div.dataSeriesId = reportId + '-' + prop;
+ div.graphViewId =
+ peerConnectionElement.id + '-' + reportId + '-bweCompound';
+ div.firstChild.addEventListener('click', function(event) {
+ var target =
+ peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
+ event.target.parentNode.dataSeriesId);
+ target.show(event.target.checked);
+ graphViews[event.target.parentNode.graphViewId].repaint();
+ });
+ }
+ return legend;
+}
diff --git a/chromium/content/browser/resources/media/stats_table.js b/chromium/content/browser/resources/media/stats_table.js
new file mode 100644
index 00000000000..6b3ae5230de
--- /dev/null
+++ b/chromium/content/browser/resources/media/stats_table.js
@@ -0,0 +1,137 @@
+// Copyright (c) 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.
+
+
+/**
+ * Maintains the stats table.
+ * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
+ */
+var StatsTable = (function(ssrcInfoManager) {
+ 'use strict';
+
+ /**
+ * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
+ * @constructor
+ */
+ function StatsTable(ssrcInfoManager) {
+ /**
+ * @type {SsrcInfoManager}
+ * @private
+ */
+ this.ssrcInfoManager_ = ssrcInfoManager;
+ }
+
+ StatsTable.prototype = {
+ /**
+ * Adds |report| to the stats table of |peerConnectionElement|.
+ *
+ * @param {!Element} peerConnectionElement The root element.
+ * @param {!Object} report The object containing stats, which is the object
+ * containing timestamp and values, which is an array of strings, whose
+ * even index entry is the name of the stat, and the odd index entry is
+ * the value.
+ */
+ addStatsReport: function(peerConnectionElement, report) {
+ var statsTable = this.ensureStatsTable_(peerConnectionElement, report);
+
+ if (report.stats) {
+ this.addStatsToTable_(statsTable,
+ report.stats.timestamp, report.stats.values);
+ }
+ },
+
+ /**
+ * Ensure the DIV container for the stats tables is created as a child of
+ * |peerConnectionElement|.
+ *
+ * @param {!Element} peerConnectionElement The root element.
+ * @return {!Element} The stats table container.
+ * @private
+ */
+ ensureStatsTableContainer_: function(peerConnectionElement) {
+ var containerId = peerConnectionElement.id + '-table-container';
+ var container = $(containerId);
+ if (!container) {
+ container = document.createElement('div');
+ container.id = containerId;
+ container.className = 'stats-table-container';
+ peerConnectionElement.appendChild(container);
+ }
+ return container;
+ },
+
+ /**
+ * Ensure the stats table for track specified by |report| of PeerConnection
+ * |peerConnectionElement| is created.
+ *
+ * @param {!Element} peerConnectionElement The root element.
+ * @param {!Object} report The object containing stats, which is the object
+ * containing timestamp and values, which is an array of strings, whose
+ * even index entry is the name of the stat, and the odd index entry is
+ * the value.
+ * @return {!Element} The stats table element.
+ * @private
+ */
+ ensureStatsTable_: function(peerConnectionElement, report) {
+ var tableId = peerConnectionElement.id + '-table-' + report.id;
+ var table = $(tableId);
+ if (!table) {
+ var container = this.ensureStatsTableContainer_(peerConnectionElement);
+ table = document.createElement('table');
+ container.appendChild(table);
+ table.id = tableId;
+ table.border = 1;
+
+ table.innerHTML = '<tr><th colspan=2></th></tr>';
+ table.rows[0].cells[0].textContent = 'Statistics ' + report.id;
+ if (report.type == 'ssrc') {
+ table.insertRow(1);
+ table.rows[1].innerHTML = '<td colspan=2></td>';
+ this.ssrcInfoManager_.populateSsrcInfo(
+ table.rows[1].cells[0], GetSsrcFromReport(report));
+ }
+ }
+ return table;
+ },
+
+ /**
+ * Update |statsTable| with |time| and |statsData|.
+ *
+ * @param {!Element} statsTable Which table to update.
+ * @param {number} time The number of miliseconds since epoch.
+ * @param {Array.<string>} statsData An array of stats name and value pairs.
+ * @private
+ */
+ addStatsToTable_: function(statsTable, time, statsData) {
+ var date = Date(time);
+ this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString());
+ for (var i = 0; i < statsData.length - 1; i = i + 2) {
+ this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]);
+ }
+ },
+
+ /**
+ * Update the value column of the stats row of |rowName| to |value|.
+ * A new row is created is this is the first report of this stats.
+ *
+ * @param {!Element} statsTable Which table to update.
+ * @param {string} rowName The name of the row to update.
+ * @param {string} value The new value to set.
+ * @private
+ */
+ updateStatsTableRow_: function(statsTable, rowName, value) {
+ var trId = statsTable.id + '-' + rowName;
+ var trElement = $(trId);
+ if (!trElement) {
+ trElement = document.createElement('tr');
+ trElement.id = trId;
+ statsTable.firstChild.appendChild(trElement);
+ trElement.innerHTML = '<td>' + rowName + '</td><td></td>';
+ }
+ trElement.cells[1].textContent = value;
+ }
+ };
+
+ return StatsTable;
+})();
diff --git a/chromium/content/browser/resources/media/timeline_graph_view.js b/chromium/content/browser/resources/media/timeline_graph_view.js
new file mode 100644
index 00000000000..89b557e1710
--- /dev/null
+++ b/chromium/content/browser/resources/media/timeline_graph_view.js
@@ -0,0 +1,523 @@
+// Copyright (c) 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.
+
+/**
+ * A TimelineGraphView displays a timeline graph on a canvas element.
+ */
+var TimelineGraphView = (function() {
+ 'use strict';
+
+ // Default starting scale factor, in terms of milliseconds per pixel.
+ var DEFAULT_SCALE = 1000;
+
+ // Maximum number of labels placed vertically along the sides of the graph.
+ var MAX_VERTICAL_LABELS = 6;
+
+ // Vertical spacing between labels and between the graph and labels.
+ var LABEL_VERTICAL_SPACING = 4;
+ // Horizontal spacing between vertically placed labels and the edges of the
+ // graph.
+ var LABEL_HORIZONTAL_SPACING = 3;
+ // Horizintal spacing between two horitonally placed labels along the bottom
+ // of the graph.
+ var LABEL_LABEL_HORIZONTAL_SPACING = 25;
+
+ // Length of ticks, in pixels, next to y-axis labels. The x-axis only has
+ // one set of labels, so it can use lines instead.
+ var Y_AXIS_TICK_LENGTH = 10;
+
+ var GRID_COLOR = '#CCC';
+ var TEXT_COLOR = '#000';
+ var BACKGROUND_COLOR = '#FFF';
+
+ /**
+ * @constructor
+ */
+ function TimelineGraphView(divId, canvasId) {
+ this.scrollbar_ = {position_: 0, range_: 0};
+
+ this.graphDiv_ = $(divId);
+ this.canvas_ = $(canvasId);
+
+ // Set the range and scale of the graph. Times are in milliseconds since
+ // the Unix epoch.
+
+ // All measurements we have must be after this time.
+ this.startTime_ = 0;
+ // The current rightmost position of the graph is always at most this.
+ this.endTime_ = 1;
+
+ this.graph_ = null;
+
+ // Initialize the scrollbar.
+ this.updateScrollbarRange_(true);
+ }
+
+ TimelineGraphView.prototype = {
+ // Returns the total length of the graph, in pixels.
+ getLength_: function() {
+ var timeRange = this.endTime_ - this.startTime_;
+ // Math.floor is used to ignore the last partial area, of length less
+ // than DEFAULT_SCALE.
+ return Math.floor(timeRange / DEFAULT_SCALE);
+ },
+
+ /**
+ * Returns true if the graph is scrolled all the way to the right.
+ */
+ graphScrolledToRightEdge_: function() {
+ return this.scrollbar_.position_ == this.scrollbar_.range_;
+ },
+
+ /**
+ * Update the range of the scrollbar. If |resetPosition| is true, also
+ * sets the slider to point at the rightmost position and triggers a
+ * repaint.
+ */
+ updateScrollbarRange_: function(resetPosition) {
+ var scrollbarRange = this.getLength_() - this.canvas_.width;
+ if (scrollbarRange < 0)
+ scrollbarRange = 0;
+
+ // If we've decreased the range to less than the current scroll position,
+ // we need to move the scroll position.
+ if (this.scrollbar_.position_ > scrollbarRange)
+ resetPosition = true;
+
+ this.scrollbar_.range_ = scrollbarRange;
+ if (resetPosition) {
+ this.scrollbar_.position_ = scrollbarRange;
+ this.repaint();
+ }
+ },
+
+ /**
+ * Sets the date range displayed on the graph, switches to the default
+ * scale factor, and moves the scrollbar all the way to the right.
+ */
+ setDateRange: function(startDate, endDate) {
+ this.startTime_ = startDate.getTime();
+ this.endTime_ = endDate.getTime();
+
+ // Safety check.
+ if (this.endTime_ <= this.startTime_)
+ this.startTime_ = this.endTime_ - 1;
+
+ this.updateScrollbarRange_(true);
+ },
+
+ /**
+ * Updates the end time at the right of the graph to be the current time.
+ * Specifically, updates the scrollbar's range, and if the scrollbar is
+ * all the way to the right, keeps it all the way to the right. Otherwise,
+ * leaves the view as-is and doesn't redraw anything.
+ */
+ updateEndDate: function() {
+ this.endTime_ = (new Date()).getTime();
+ this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
+ },
+
+ getStartDate: function() {
+ return new Date(this.startTime_);
+ },
+
+ /**
+ * Replaces the current TimelineDataSeries with |dataSeries|.
+ */
+ setDataSeries: function(dataSeries) {
+ // Simply recreates the Graph.
+ this.graph_ = new Graph();
+ for (var i = 0; i < dataSeries.length; ++i)
+ this.graph_.addDataSeries(dataSeries[i]);
+ this.repaint();
+ },
+
+ /**
+ * Adds |dataSeries| to the current graph.
+ */
+ addDataSeries: function(dataSeries) {
+ if (!this.graph_)
+ this.graph_ = new Graph();
+ this.graph_.addDataSeries(dataSeries);
+ this.repaint();
+ },
+
+ /**
+ * Draws the graph on |canvas_|.
+ */
+ repaint: function() {
+ this.repaintTimerRunning_ = false;
+
+ var width = this.canvas_.width;
+ var height = this.canvas_.height;
+ var context = this.canvas_.getContext('2d');
+
+ // Clear the canvas.
+ context.fillStyle = BACKGROUND_COLOR;
+ context.fillRect(0, 0, width, height);
+
+ // Try to get font height in pixels. Needed for layout.
+ var fontHeightString = context.font.match(/([0-9]+)px/)[1];
+ var fontHeight = parseInt(fontHeightString);
+
+ // Safety check, to avoid drawing anything too ugly.
+ if (fontHeightString.length == 0 || fontHeight <= 0 ||
+ fontHeight * 4 > height || width < 50) {
+ return;
+ }
+
+ // Save current transformation matrix so we can restore it later.
+ context.save();
+
+ // The center of an HTML canvas pixel is technically at (0.5, 0.5). This
+ // makes near straight lines look bad, due to anti-aliasing. This
+ // translation reduces the problem a little.
+ context.translate(0.5, 0.5);
+
+ // Figure out what time values to display.
+ var position = this.scrollbar_.position_;
+ // If the entire time range is being displayed, align the right edge of
+ // the graph to the end of the time range.
+ if (this.scrollbar_.range_ == 0)
+ position = this.getLength_() - this.canvas_.width;
+ var visibleStartTime = this.startTime_ + position * DEFAULT_SCALE;
+
+ // Make space at the bottom of the graph for the time labels, and then
+ // draw the labels.
+ var textHeight = height;
+ height -= fontHeight + LABEL_VERTICAL_SPACING;
+ this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
+
+ // Draw outline of the main graph area.
+ context.strokeStyle = GRID_COLOR;
+ context.strokeRect(0, 0, width - 1, height - 1);
+
+ if (this.graph_) {
+ // Layout graph and have them draw their tick marks.
+ this.graph_.layout(
+ width, height, fontHeight, visibleStartTime, DEFAULT_SCALE);
+ this.graph_.drawTicks(context);
+
+ // Draw the lines of all graphs, and then draw their labels.
+ this.graph_.drawLines(context);
+ this.graph_.drawLabels(context);
+ }
+
+ // Restore original transformation matrix.
+ context.restore();
+ },
+
+ /**
+ * Draw time labels below the graph. Takes in start time as an argument
+ * since it may not be |startTime_|, when we're displaying the entire
+ * time range.
+ */
+ drawTimeLabels: function(context, width, height, textHeight, startTime) {
+ // Draw the labels 1 minute apart.
+ var timeStep = 1000 * 60;
+
+ // Find the time for the first label. This time is a perfect multiple of
+ // timeStep because of how UTC times work.
+ var time = Math.ceil(startTime / timeStep) * timeStep;
+
+ context.textBaseline = 'bottom';
+ context.textAlign = 'center';
+ context.fillStyle = TEXT_COLOR;
+ context.strokeStyle = GRID_COLOR;
+
+ // Draw labels and vertical grid lines.
+ while (true) {
+ var x = Math.round((time - startTime) / DEFAULT_SCALE);
+ if (x >= width)
+ break;
+ var text = (new Date(time)).toLocaleTimeString();
+ context.fillText(text, x, textHeight);
+ context.beginPath();
+ context.lineTo(x, 0);
+ context.lineTo(x, height);
+ context.stroke();
+ time += timeStep;
+ }
+ },
+
+ getDataSeriesCount: function() {
+ if (this.graph_)
+ return this.graph_.dataSeries_.length;
+ return 0;
+ },
+
+ hasDataSeries: function(dataSeries) {
+ if (this.graph_)
+ return this.graph_.hasDataSeries(dataSeries);
+ return false;
+ },
+
+ };
+
+ /**
+ * A Graph is responsible for drawing all the TimelineDataSeries that have
+ * the same data type. Graphs are responsible for scaling the values, laying
+ * out labels, and drawing both labels and lines for its data series.
+ */
+ var Graph = (function() {
+ /**
+ * @constructor
+ */
+ function Graph() {
+ this.dataSeries_ = [];
+
+ // Cached properties of the graph, set in layout.
+ this.width_ = 0;
+ this.height_ = 0;
+ this.fontHeight_ = 0;
+ this.startTime_ = 0;
+ this.scale_ = 0;
+
+ // At least the highest value in the displayed range of the graph.
+ // Used for scaling and setting labels. Set in layoutLabels.
+ this.max_ = 0;
+
+ // Cached text of equally spaced labels. Set in layoutLabels.
+ this.labels_ = [];
+ }
+
+ /**
+ * A Label is the label at a particular position along the y-axis.
+ * @constructor
+ */
+ function Label(height, text) {
+ this.height = height;
+ this.text = text;
+ }
+
+ Graph.prototype = {
+ addDataSeries: function(dataSeries) {
+ this.dataSeries_.push(dataSeries);
+ },
+
+ hasDataSeries: function(dataSeries) {
+ for (var i = 0; i < this.dataSeries_.length; ++i) {
+ if (this.dataSeries_[i] == dataSeries)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Returns a list of all the values that should be displayed for a given
+ * data series, using the current graph layout.
+ */
+ getValues: function(dataSeries) {
+ if (!dataSeries.isVisible())
+ return null;
+ return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
+ },
+
+ /**
+ * Updates the graph's layout. In particular, both the max value and
+ * label positions are updated. Must be called before calling any of the
+ * drawing functions.
+ */
+ layout: function(width, height, fontHeight, startTime, scale) {
+ this.width_ = width;
+ this.height_ = height;
+ this.fontHeight_ = fontHeight;
+ this.startTime_ = startTime;
+ this.scale_ = scale;
+
+ // Find largest value.
+ var max = 0;
+ for (var i = 0; i < this.dataSeries_.length; ++i) {
+ var values = this.getValues(this.dataSeries_[i]);
+ if (!values)
+ continue;
+ for (var j = 0; j < values.length; ++j) {
+ if (values[j] > max)
+ max = values[j];
+ }
+ }
+
+ this.layoutLabels_(max);
+ },
+
+ /**
+ * Lays out labels and sets |max_|, taking the time units into
+ * consideration. |maxValue| is the actual maximum value, and
+ * |max_| will be set to the value of the largest label, which
+ * will be at least |maxValue|.
+ */
+ layoutLabels_: function(maxValue) {
+ if (maxValue < 1024) {
+ this.layoutLabelsBasic_(maxValue, 0);
+ return;
+ }
+
+ // Find appropriate units to use.
+ var units = ['', 'k', 'M', 'G', 'T', 'P'];
+ // Units to use for labels. 0 is '1', 1 is K, etc.
+ // We start with 1, and work our way up.
+ var unit = 1;
+ maxValue /= 1024;
+ while (units[unit + 1] && maxValue >= 1024) {
+ maxValue /= 1024;
+ ++unit;
+ }
+
+ // Calculate labels.
+ this.layoutLabelsBasic_(maxValue, 1);
+
+ // Append units to labels.
+ for (var i = 0; i < this.labels_.length; ++i)
+ this.labels_[i] += ' ' + units[unit];
+
+ // Convert |max_| back to unit '1'.
+ this.max_ *= Math.pow(1024, unit);
+ },
+
+ /**
+ * Same as layoutLabels_, but ignores units. |maxDecimalDigits| is the
+ * maximum number of decimal digits allowed. The minimum allowed
+ * difference between two adjacent labels is 10^-|maxDecimalDigits|.
+ */
+ layoutLabelsBasic_: function(maxValue, maxDecimalDigits) {
+ this.labels_ = [];
+ // No labels if |maxValue| is 0.
+ if (maxValue == 0) {
+ this.max_ = maxValue;
+ return;
+ }
+
+ // The maximum number of equally spaced labels allowed. |fontHeight_|
+ // is doubled because the top two labels are both drawn in the same
+ // gap.
+ var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
+
+ // The + 1 is for the top label.
+ var maxLabels = 1 + this.height_ / minLabelSpacing;
+ if (maxLabels < 2) {
+ maxLabels = 2;
+ } else if (maxLabels > MAX_VERTICAL_LABELS) {
+ maxLabels = MAX_VERTICAL_LABELS;
+ }
+
+ // Initial try for step size between conecutive labels.
+ var stepSize = Math.pow(10, -maxDecimalDigits);
+ // Number of digits to the right of the decimal of |stepSize|.
+ // Used for formating label strings.
+ var stepSizeDecimalDigits = maxDecimalDigits;
+
+ // Pick a reasonable step size.
+ while (true) {
+ // If we use a step size of |stepSize| between labels, we'll need:
+ //
+ // Math.ceil(maxValue / stepSize) + 1
+ //
+ // labels. The + 1 is because we need labels at both at 0 and at
+ // the top of the graph.
+
+ // Check if we can use steps of size |stepSize|.
+ if (Math.ceil(maxValue / stepSize) + 1 <= maxLabels)
+ break;
+ // Check |stepSize| * 2.
+ if (Math.ceil(maxValue / (stepSize * 2)) + 1 <= maxLabels) {
+ stepSize *= 2;
+ break;
+ }
+ // Check |stepSize| * 5.
+ if (Math.ceil(maxValue / (stepSize * 5)) + 1 <= maxLabels) {
+ stepSize *= 5;
+ break;
+ }
+ stepSize *= 10;
+ if (stepSizeDecimalDigits > 0)
+ --stepSizeDecimalDigits;
+ }
+
+ // Set the max so it's an exact multiple of the chosen step size.
+ this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
+
+ // Create labels.
+ for (var label = this.max_; label >= 0; label -= stepSize)
+ this.labels_.push(label.toFixed(stepSizeDecimalDigits));
+ },
+
+ /**
+ * Draws tick marks for each of the labels in |labels_|.
+ */
+ drawTicks: function(context) {
+ var x1;
+ var x2;
+ x1 = this.width_ - 1;
+ x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
+
+ context.fillStyle = GRID_COLOR;
+ context.beginPath();
+ for (var i = 1; i < this.labels_.length - 1; ++i) {
+ // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
+ // lines.
+ var y = Math.round(this.height_ * i / (this.labels_.length - 1));
+ context.moveTo(x1, y);
+ context.lineTo(x2, y);
+ }
+ context.stroke();
+ },
+
+ /**
+ * Draws a graph line for each of the data series.
+ */
+ drawLines: function(context) {
+ // Factor by which to scale all values to convert them to a number from
+ // 0 to height - 1.
+ var scale = 0;
+ var bottom = this.height_ - 1;
+ if (this.max_)
+ scale = bottom / this.max_;
+
+ // Draw in reverse order, so earlier data series are drawn on top of
+ // subsequent ones.
+ for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
+ var values = this.getValues(this.dataSeries_[i]);
+ if (!values)
+ continue;
+ context.strokeStyle = this.dataSeries_[i].getColor();
+ context.beginPath();
+ for (var x = 0; x < values.length; ++x) {
+ // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
+ // horizontal lines.
+ context.lineTo(x, bottom - Math.round(values[x] * scale));
+ }
+ context.stroke();
+ }
+ },
+
+ /**
+ * Draw labels in |labels_|.
+ */
+ drawLabels: function(context) {
+ if (this.labels_.length == 0)
+ return;
+ var x = this.width_ - LABEL_HORIZONTAL_SPACING;
+
+ // Set up the context.
+ context.fillStyle = TEXT_COLOR;
+ context.textAlign = 'right';
+
+ // Draw top label, which is the only one that appears below its tick
+ // mark.
+ context.textBaseline = 'top';
+ context.fillText(this.labels_[0], x, 0);
+
+ // Draw all the other labels.
+ context.textBaseline = 'bottom';
+ var step = (this.height_ - 1) / (this.labels_.length - 1);
+ for (var i = 1; i < this.labels_.length; ++i)
+ context.fillText(this.labels_[i], x, step * i);
+ }
+ };
+
+ return Graph;
+ })();
+
+ return TimelineGraphView;
+})();
diff --git a/chromium/content/browser/resources/media/util.js b/chromium/content/browser/resources/media/util.js
new file mode 100644
index 00000000000..c61ae0e9793
--- /dev/null
+++ b/chromium/content/browser/resources/media/util.js
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('media', function() {
+ 'use strict';
+
+ /**
+ * The width and height of a bar drawn on a file canvas in pixels.
+ */
+ var BAR_WIDTH = 500;
+ var BAR_HEIGHT = 16;
+
+ /**
+ * Draws a 1px white horizontal line across |context|.
+ */
+ function drawLine(context, top) {
+ context.moveTo(0, top);
+ context.lineTo(BAR_WIDTH, top);
+ context.strokeStyle = '#fff';
+ context.stroke();
+ }
+
+ /**
+ * Creates an HTMLElement of type |type| with textContent |content|.
+ * @param {string} type The type of element to create.
+ * @param {string} content The content to place in the element.
+ * @return {HTMLElement} A newly initialized element.
+ */
+ function makeElement(type, content) {
+ var element = document.createElement(type);
+ element.textContent = content;
+ return element;
+ }
+
+ /**
+ * Creates a new <li> containing a <details> with a <summary> and sets
+ * properties to reference them.
+ * @return {Object} The new <li>.
+ */
+ function createDetailsLi() {
+ var li = document.createElement('li');
+ li.details = document.createElement('details');
+ li.summary = document.createElement('summary');
+ li.appendChild(li.details);
+ li.details.appendChild(li.summary);
+ return li
+ }
+
+ /**
+ * Appends each key-value pair in a dictionary to a row in a table.
+ * @param {Object} dict The dictionary to append.
+ * @param {HTMLElement} table The <table> element to append to.
+ */
+ function appendDictionaryToTable(dict, table) {
+ table.textContent = '';
+ for (var key in dict) {
+ var tr = document.createElement('tr');
+ tr.appendChild(makeElement('td', key + ':'));
+ tr.appendChild(makeElement('td', dict[key]));
+ table.appendChild(tr);
+ }
+ return table;
+ }
+
+ return {
+ BAR_WIDTH: BAR_WIDTH,
+ BAR_HEIGHT: BAR_HEIGHT,
+ drawLine: drawLine,
+ makeElement: makeElement,
+ createDetailsLi: createDetailsLi,
+ appendDictionaryToTable: appendDictionaryToTable
+ };
+});
diff --git a/chromium/content/browser/resources/media/webrtc_internals.css b/chromium/content/browser/resources/media/webrtc_internals.css
new file mode 100644
index 00000000000..c0ac402728f
--- /dev/null
+++ b/chromium/content/browser/resources/media/webrtc_internals.css
@@ -0,0 +1,118 @@
+/* Copyright (c) 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. */
+
+
+.peer-connection-dump-root {
+ font-size: 0.8em;
+ padding-bottom: 3px;
+}
+
+.peer-connection-hidden > *:not(h3) {
+ display:none;
+}
+
+.update-log-container {
+ float: left;
+}
+
+.ssrc-info-block {
+ color: #999;
+ font-size: 0.8em;
+}
+
+.stats-graph-container {
+ clear: both;
+ margin: 0.5em 0 0.5em 0;
+}
+
+.stats-graph-container-heading {
+ font-size: 0.8em;
+ font-weight: bold;
+}
+
+.stats-graph-sub-container {
+ float: left;
+ margin: 0.5em;
+}
+
+.stats-graph-sub-container > div {
+ float: left;
+}
+
+.stats-graph-sub-container > div:first-child {
+ float: none;
+}
+
+.stats-table-container {
+ float: left;
+ padding: 0 0 0 0;
+}
+
+body {
+ font-family: 'Lucida Grande', sans-serif;
+ padding: 20px;
+}
+
+details {
+ min-width: 30em;
+}
+
+h3 + div {
+ font-size: 0.8em;
+ margin: 0 0 1.2em 0;
+}
+
+h2 {
+ border-bottom: 1px solid #eee;
+ color: #666;
+ font-size: 1.1em;
+ margin: 0 0 1.6em 0;
+ padding: 0 0 0.7em 0;
+}
+
+h3 {
+ color: #555;
+ cursor: pointer;
+ font-size: 0.9em;
+ margin: 2em 0 0.5em 0;
+ text-decoration: underline;
+}
+
+li {
+ clear: both;
+ list-style-type: none;
+ margin: none;
+ padding: none;
+}
+
+table {
+ border: none;
+ margin: 0 1em 1em 0;
+}
+
+td {
+ border: none;
+ font-size: 0.8em;
+ padding: 0 1em 0.5em 0;
+}
+
+td:first-child {
+ padding-top: 0.3em;
+}
+
+table > tr {
+ vertical-align: top;
+}
+
+th {
+ border: none;
+ font-size: 0.8em;
+ padding: 0 0 0.5em 0;
+}
+
+ul {
+ margin: none;
+ padding: none;
+ -webkit-padding-start: 0px;
+}
diff --git a/chromium/content/browser/resources/media/webrtc_internals.html b/chromium/content/browser/resources/media/webrtc_internals.html
new file mode 100644
index 00000000000..72576207f88
--- /dev/null
+++ b/chromium/content/browser/resources/media/webrtc_internals.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html i18n-values="dir:textdirection;">
+ <head>
+ <meta charset="utf-8">
+ <title>WebRTC Internals</title>
+ <link rel="stylesheet" href="webrtc_internals.css">
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://webrtc-internals/webrtc_internals.js"></script>
+ </head>
+ <body>
+ <h2>WebRTC Internals</h2>
+ <p>
+ <ul id="peer-connections-list">
+ </ul>
+ </p>
+ </body>
+</html>
diff --git a/chromium/content/browser/resources/media/webrtc_internals.js b/chromium/content/browser/resources/media/webrtc_internals.js
new file mode 100644
index 00000000000..fd2d34a3faf
--- /dev/null
+++ b/chromium/content/browser/resources/media/webrtc_internals.js
@@ -0,0 +1,264 @@
+// Copyright (c) 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.
+
+
+var peerConnectionsListElem = null;
+var ssrcInfoManager = null;
+var peerConnectionUpdateTable = null;
+var statsTable = null;
+var dumpCreator = null;
+/** A map from peer connection id to the PeerConnectionRecord. */
+var peerConnectionDataStore = {};
+
+/** A simple class to store the updates and stats data for a peer connection. */
+var PeerConnectionRecord = (function() {
+ /** @constructor */
+ function PeerConnectionRecord() {
+ /** @private */
+ this.record_ = {
+ constraints: {},
+ servers: [],
+ stats: {},
+ updateLog: [],
+ url: '',
+ };
+ };
+
+ PeerConnectionRecord.prototype = {
+ /** @override */
+ toJSON: function() {
+ return this.record_;
+ },
+
+ /**
+ * Adds the initilization info of the peer connection.
+ * @param {string} url The URL of the web page owning the peer connection.
+ * @param {Array} servers STUN servers used by the peer connection.
+ * @param {!Object} constraints Media constraints.
+ */
+ initialize: function(url, servers, constraints) {
+ this.record_.url = url;
+ this.record_.servers = servers;
+ this.record_.constraints = constraints;
+ },
+
+ /**
+ * @param {string} dataSeriesId The TimelineDataSeries identifier.
+ * @return {!TimelineDataSeries}
+ */
+ getDataSeries: function(dataSeriesId) {
+ return this.record_.stats[dataSeriesId];
+ },
+
+ /**
+ * @param {string} dataSeriesId The TimelineDataSeries identifier.
+ * @param {!TimelineDataSeries} dataSeries The TimelineDataSeries to set to.
+ */
+ setDataSeries: function(dataSeriesId, dataSeries) {
+ this.record_.stats[dataSeriesId] = dataSeries;
+ },
+
+ /**
+ * @param {string} type The type of the update.
+ * @param {string} value The value of the update.
+ */
+ addUpdate: function(type, value) {
+ this.record_.updateLog.push({
+ time: (new Date()).toLocaleString(),
+ type: type,
+ value: value,
+ });
+ },
+ };
+
+ return PeerConnectionRecord;
+})();
+
+// The maximum number of data points bufferred for each stats. Old data points
+// will be shifted out when the buffer is full.
+var MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
+
+<include src="data_series.js"/>
+<include src="ssrc_info_manager.js"/>
+<include src="stats_graph_helper.js"/>
+<include src="stats_table.js"/>
+<include src="peer_connection_update_table.js"/>
+<include src="dump_creator.js"/>
+
+
+function initialize() {
+ peerConnectionsListElem = $('peer-connections-list');
+ dumpCreator = new DumpCreator(peerConnectionsListElem);
+ ssrcInfoManager = new SsrcInfoManager();
+ peerConnectionUpdateTable = new PeerConnectionUpdateTable();
+ statsTable = new StatsTable(ssrcInfoManager);
+
+ chrome.send('getAllUpdates');
+
+ // Requests stats from all peer connections every second.
+ window.setInterval(function() {
+ if (peerConnectionsListElem.getElementsByTagName('li').length > 0)
+ chrome.send('getAllStats');
+ }, 1000);
+}
+document.addEventListener('DOMContentLoaded', initialize);
+
+
+/**
+ * A helper function for getting a peer connection element id.
+ *
+ * @param {!Object.<string, number>} data The object containing the pid and lid
+ * of the peer connection.
+ * @return {string} The peer connection element id.
+ */
+function getPeerConnectionId(data) {
+ return data.pid + '-' + data.lid;
+}
+
+
+/**
+ * Extracts ssrc info from a setLocal/setRemoteDescription update.
+ *
+ * @param {!PeerConnectionUpdateEntry} data The peer connection update data.
+ */
+function extractSsrcInfo(data) {
+ if (data.type == 'setLocalDescription' ||
+ data.type == 'setRemoteDescription') {
+ ssrcInfoManager.addSsrcStreamInfo(data.value);
+ }
+}
+
+
+/**
+ * Helper for adding a peer connection update.
+ *
+ * @param {Element} peerConnectionElement
+ * @param {!PeerConnectionUpdateEntry} update The peer connection update data.
+ */
+function addPeerConnectionUpdate(peerConnectionElement, update) {
+ peerConnectionUpdateTable.addPeerConnectionUpdate(peerConnectionElement,
+ update);
+ extractSsrcInfo(update);
+ peerConnectionDataStore[peerConnectionElement.id].addUpdate(
+ update.type, update.value);
+}
+
+
+/** Browser message handlers. */
+
+
+/**
+ * Removes all information about a peer connection.
+ *
+ * @param {!Object.<string, number>} data The object containing the pid and lid
+ * of a peer connection.
+ */
+function removePeerConnection(data) {
+ var element = $(getPeerConnectionId(data));
+ if (element) {
+ delete peerConnectionDataStore[element.id];
+ peerConnectionsListElem.removeChild(element);
+ }
+}
+
+
+/**
+ * Adds a peer connection.
+ *
+ * @param {!Object} data The object containing the pid, lid, url, servers, and
+ * constraints of a peer connection.
+ */
+function addPeerConnection(data) {
+ var id = getPeerConnectionId(data);
+
+ if (!peerConnectionDataStore[id]) {
+ peerConnectionDataStore[id] = new PeerConnectionRecord();
+ }
+ peerConnectionDataStore[id].initialize(
+ data.url, data.servers, data.constraints);
+
+ var peerConnectionElement = $(id);
+ if (!peerConnectionElement) {
+ peerConnectionElement = document.createElement('li');
+ peerConnectionsListElem.appendChild(peerConnectionElement);
+ peerConnectionElement.id = id;
+ }
+ peerConnectionElement.innerHTML =
+ '<h3>PeerConnection ' + peerConnectionElement.id + '</h3>' +
+ '<div>' + data.url + ' ' + data.servers + ' ' + data.constraints +
+ '</div>';
+
+ // Clicking the heading can expand or collapse the peer connection item.
+ peerConnectionElement.firstChild.title = 'Click to collapse or expand';
+ peerConnectionElement.firstChild.addEventListener('click', function(e) {
+ if (e.target.parentElement.className == '')
+ e.target.parentElement.className = 'peer-connection-hidden';
+ else
+ e.target.parentElement.className = '';
+ });
+
+ return peerConnectionElement;
+}
+
+
+/**
+ * Adds a peer connection update.
+ *
+ * @param {!PeerConnectionUpdateEntry} data The peer connection update data.
+ */
+function updatePeerConnection(data) {
+ var peerConnectionElement = $(getPeerConnectionId(data));
+ addPeerConnectionUpdate(peerConnectionElement, data);
+}
+
+
+/**
+ * Adds the information of all peer connections created so far.
+ *
+ * @param {Array.<!Object>} data An array of the information of all peer
+ * connections. Each array item contains pid, lid, url, servers,
+ * constraints, and an array of updates as the log.
+ */
+function updateAllPeerConnections(data) {
+ for (var i = 0; i < data.length; ++i) {
+ var peerConnection = addPeerConnection(data[i]);
+
+ var log = data[i].log;
+ for (var j = 0; j < log.length; ++j) {
+ addPeerConnectionUpdate(peerConnection, log[j]);
+ }
+ }
+}
+
+
+/**
+ * Handles the report of stats.
+ *
+ * @param {!Object} data The object containing pid, lid, and reports, where
+ * reports is an array of stats reports. Each report contains id, type,
+ * and stats, where stats is the object containing timestamp and values,
+ * which is an array of strings, whose even index entry is the name of the
+ * stat, and the odd index entry is the value.
+ */
+function addStats(data) {
+ var peerConnectionElement = $(getPeerConnectionId(data));
+ if (!peerConnectionElement)
+ return;
+
+ for (var i = 0; i < data.reports.length; ++i) {
+ var report = data.reports[i];
+ statsTable.addStatsReport(peerConnectionElement, report);
+ drawSingleReport(peerConnectionElement, report);
+ }
+}
+
+
+/**
+ * Delegates to dumpCreator to update the recording status.
+ * @param {!Object.<string>} update Key-value pairs describing the status of the
+ * RTP recording.
+ */
+function updateDumpStatus(update) {
+ dumpCreator.onUpdate(update);
+}
diff --git a/chromium/content/browser/safe_util_win.cc b/chromium/content/browser/safe_util_win.cc
new file mode 100644
index 00000000000..ac077f15350
--- /dev/null
+++ b/chromium/content/browser/safe_util_win.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Chromium 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 <shlobj.h>
+#include <shobjidl.h>
+
+#include "content/browser/safe_util_win.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_comptr.h"
+#include "ui/base/win/shell.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+// Sets the Zone Identifier on the file to "Internet" (3). Returns true if the
+// function succeeds, false otherwise. A failure is expected on system where
+// the Zone Identifier is not supported, like a machine with a FAT32 filesystem.
+// This function does not invoke Windows Attachment Execution Services.
+//
+// |full_path| is the path to the downloaded file.
+bool SetInternetZoneIdentifierDirectly(const base::FilePath& full_path) {
+ const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ std::wstring path = full_path.value() + L":Zone.Identifier";
+ HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, NULL,
+ OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == file)
+ return false;
+
+ static const char kIdentifier[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
+ // Don't include trailing null in data written.
+ static const DWORD kIdentifierSize = arraysize(kIdentifier) - 1;
+ DWORD written = 0;
+ BOOL result = WriteFile(file, kIdentifier, kIdentifierSize, &written, NULL);
+ BOOL flush_result = FlushFileBuffers(file);
+ CloseHandle(file);
+
+ if (!result || !flush_result || written != kIdentifierSize) {
+ NOTREACHED();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+HRESULT AVScanFile(const base::FilePath& full_path,
+ const std::string& source_url,
+ const GUID& client_guid) {
+ base::win::ScopedComPtr<IAttachmentExecute> attachment_services;
+ HRESULT hr = attachment_services.CreateInstance(CLSID_AttachmentServices);
+
+ if (FAILED(hr)) {
+ // The thread must have COM initialized.
+ DCHECK_NE(CO_E_NOTINITIALIZED, hr);
+
+ // We don't have Attachment Execution Services, it must be a pre-XP.SP2
+ // Windows installation, or the thread does not have COM initialized. Try to
+ // set the zone information directly. Failure is not considered an error.
+ SetInternetZoneIdentifierDirectly(full_path);
+ return hr;
+ }
+
+ if (!IsEqualGUID(client_guid, GUID_NULL)) {
+ hr = attachment_services->SetClientGuid(client_guid);
+ if (FAILED(hr))
+ return hr;
+ }
+
+ hr = attachment_services->SetLocalPath(full_path.value().c_str());
+ if (FAILED(hr))
+ return hr;
+
+ // Note: SetSource looks like it needs to be called, even if empty.
+ // Docs say it is optional, but it appears not calling it at all sets
+ // a zone that is too restrictive.
+ hr = attachment_services->SetSource(UTF8ToWide(source_url).c_str());
+ if (FAILED(hr))
+ return hr;
+
+ // A failure in the Save() call below could result in the downloaded file
+ // being deleted.
+ return attachment_services->Save();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/safe_util_win.h b/chromium/content/browser/safe_util_win.h
new file mode 100644
index 00000000000..38604298d49
--- /dev/null
+++ b/chromium/content/browser/safe_util_win.h
@@ -0,0 +1,53 @@
+// 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 CONTENT_COMMON_SAFE_UTIL_WIN_H_
+#define CONTENT_COMMON_SAFE_UTIL_WIN_H_
+
+#include <string>
+#include <windows.h>
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+// Invokes IAttachmentExecute::Save to validate the downloaded file. The call
+// may scan the file for viruses and if necessary, annotate it with evidence. As
+// a result of the validation, the file may be deleted. See:
+// http://msdn.microsoft.com/en-us/bb776299
+//
+// If Attachment Execution Services is unavailable, then this function will
+// attempt to manually annotate the file with security zone information. A
+// failure code will be returned in this case even if the file is sucessfully
+// annotated.
+//
+// IAE::Save() will delete the file if it was found to be blocked by local
+// security policy or if it was found to be infected. The call may also delete
+// the file due to other failures (http://crbug.com/153212). A failure code will
+// be returned in these cases.
+//
+// Typical return values:
+// S_OK : The file was okay. If any viruses were found, they were cleaned.
+// E_FAIL : Virus infected.
+// INET_E_SECURITY_PROBLEM : The file was blocked due to security policy.
+//
+// Any other return value indicates an unexpected error during the scan.
+//
+// |full_path| : is the path to the downloaded file. This should be the final
+// path of the download. Must be present.
+// |source_url|: the source URL for the download. If empty, the source will
+// not be set.
+// |client_guid|: the GUID to be set in the IAttachmentExecute client slot.
+// Used to identify the app to the system AV function.
+// If GUID_NULL is passed, no client GUID is set.
+HRESULT AVScanFile(const base::FilePath& full_path,
+ const std::string& source_url,
+ const GUID& client_guid);
+} // namespace content
+
+#endif // CONTENT_COMMON_SAFE_UTIL_WIN_H_
diff --git a/chromium/content/browser/security_exploit_browsertest.cc b/chromium/content/browser/security_exploit_browsertest.cc
new file mode 100644
index 00000000000..bc554c2bb82
--- /dev/null
+++ b/chromium/content/browser/security_exploit_browsertest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 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 "base/command_line.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+// The goal of these tests will be to "simulate" exploited renderer processes,
+// which can send arbitrary IPC messages and confuse browser process internal
+// state, leading to security bugs. We are trying to verify that the browser
+// doesn't perform any dangerous operations in such cases.
+class SecurityExploitBrowserTest : public ContentBrowserTest {
+ public:
+ SecurityExploitBrowserTest() {}
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ ASSERT_TRUE(test_server()->Start());
+
+ // Add a host resolver rule to map all outgoing requests to the test server.
+ // This allows us to use "real" hostnames in URLs, which we can use to
+ // create arbitrary SiteInstances.
+ command_line->AppendSwitchASCII(
+ switches::kHostResolverRules,
+ "MAP * " + test_server()->host_port_pair().ToString() +
+ ",EXCLUDE localhost");
+ }
+};
+
+// Ensure that we kill the renderer process if we try to give it WebUI
+// properties and it doesn't have enabled WebUI bindings.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) {
+ GURL foo("http://foo.com/files/simple_page.html");
+
+ NavigateToURL(shell(), foo);
+ EXPECT_EQ(0,
+ shell()->web_contents()->GetRenderViewHost()->GetEnabledBindings());
+
+ content::WindowedNotificationObserver terminated(
+ content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ content::NotificationService::AllSources());
+ shell()->web_contents()->GetRenderViewHost()->SetWebUIProperty(
+ "toolkit", "views");
+ terminated.Wait();
+}
+
+}
diff --git a/chromium/content/browser/session_history_browsertest.cc b/chromium/content/browser/session_history_browsertest.cc
new file mode 100644
index 00000000000..966ccc7d941
--- /dev/null
+++ b/chromium/content/browser/session_history_browsertest.cc
@@ -0,0 +1,499 @@
+// 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 "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// Handles |request| by serving a response with title set to request contents.
+scoped_ptr<net::test_server::HttpResponse> HandleEchoTitleRequest(
+ const std::string& echotitle_path,
+ const net::test_server::HttpRequest& request) {
+ if (!StartsWithASCII(request.relative_url, echotitle_path, true))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(
+ base::StringPrintf(
+ "<html><head><title>%s</title></head></html>",
+ request.content.c_str()));
+ return http_response.PassAs<net::test_server::HttpResponse>();
+}
+
+} // namespace
+
+class SessionHistoryTest : public ContentBrowserTest {
+ protected:
+ SessionHistoryTest() {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&HandleEchoTitleRequest, "/echotitle"));
+
+ NavigateToURL(shell(), GURL(kAboutBlankURL));
+ }
+
+ // Simulate clicking a link. Only works on the frames.html testserver page.
+ void ClickLink(std::string node_id) {
+ GURL url("javascript:clickLink('" + node_id + "')");
+ NavigateToURL(shell(), url);
+ }
+
+ // Simulate filling in form data. Only works on the frames.html page with
+ // subframe = form.html, and on form.html itself.
+ void FillForm(std::string node_id, std::string value) {
+ GURL url("javascript:fillForm('" + node_id + "', '" + value + "')");
+ // This will return immediately, but since the JS executes synchronously
+ // on the renderer, it will complete before the next navigate message is
+ // processed.
+ NavigateToURL(shell(), url);
+ }
+
+ // Simulate submitting a form. Only works on the frames.html page with
+ // subframe = form.html, and on form.html itself.
+ void SubmitForm(std::string node_id) {
+ GURL url("javascript:submitForm('" + node_id + "')");
+ NavigateToURL(shell(), url);
+ }
+
+ // Navigate session history using history.go(distance).
+ void JavascriptGo(std::string distance) {
+ GURL url("javascript:history.go('" + distance + "')");
+ NavigateToURL(shell(), url);
+ }
+
+ std::string GetTabTitle() {
+ return UTF16ToASCII(shell()->web_contents()->GetTitle());
+ }
+
+ GURL GetTabURL() {
+ return shell()->web_contents()->GetLastCommittedURL();
+ }
+
+ GURL GetURL(const std::string file) {
+ return embedded_test_server()->GetURL(
+ std::string("/session_history/") + file);
+ }
+
+ void NavigateAndCheckTitle(const char* filename,
+ const std::string& expected_title) {
+ string16 expected_title16(ASCIIToUTF16(expected_title));
+ TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
+ NavigateToURL(shell(), GetURL(filename));
+ ASSERT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
+ }
+
+ bool CanGoBack() {
+ return shell()->web_contents()->GetController().CanGoBack();
+ }
+
+ bool CanGoForward() {
+ return shell()->web_contents()->GetController().CanGoForward();
+ }
+
+ void GoBack() {
+ WindowedNotificationObserver load_stop_observer(
+ NOTIFICATION_LOAD_STOP,
+ NotificationService::AllSources());
+ shell()->web_contents()->GetController().GoBack();
+ load_stop_observer.Wait();
+ }
+
+ void GoForward() {
+ WindowedNotificationObserver load_stop_observer(
+ NOTIFICATION_LOAD_STOP,
+ NotificationService::AllSources());
+ shell()->web_contents()->GetController().GoForward();
+ load_stop_observer.Wait();
+ }
+};
+
+// If this flakes, use http://crbug.com/61619 on windows and
+// http://crbug.com/102094 on mac.
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, BasicBackForward) {
+ ASSERT_FALSE(CanGoBack());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot1.html", "bot1"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3"));
+
+ // history is [blank, bot1, bot2, *bot3]
+
+ GoBack();
+ EXPECT_EQ("bot2", GetTabTitle());
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ GoForward();
+ EXPECT_EQ("bot2", GetTabTitle());
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3"));
+
+ // history is [blank, bot1, *bot3]
+
+ ASSERT_FALSE(CanGoForward());
+ EXPECT_EQ("bot3", GetTabTitle());
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ GoBack();
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ ASSERT_FALSE(CanGoBack());
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ GoForward();
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ GoForward();
+ EXPECT_EQ("bot3", GetTabTitle());
+}
+
+// Test that back/forward works when navigating in subframes.
+// If this flakes, use http://crbug.com/48833
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FrameBackForward) {
+ ASSERT_FALSE(CanGoBack());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1"));
+
+ ClickLink("abot2");
+ EXPECT_EQ("bot2", GetTabTitle());
+ GURL frames(GetURL("frames.html"));
+ EXPECT_EQ(frames, GetTabURL());
+
+ ClickLink("abot3");
+ EXPECT_EQ("bot3", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, bot2, *bot3]
+
+ GoBack();
+ EXPECT_EQ("bot2", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+ EXPECT_EQ(GURL(kAboutBlankURL), GetTabURL());
+
+ GoForward();
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoForward();
+ EXPECT_EQ("bot2", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ ClickLink("abot1");
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, bot2, *bot1]
+
+ ASSERT_FALSE(CanGoForward());
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("bot2", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+}
+
+// Test that back/forward preserves POST data and document state in subframes.
+// If this flakes use http://crbug.com/61619
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FrameFormBackForward) {
+ ASSERT_FALSE(CanGoBack());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1"));
+
+ ClickLink("aform");
+ EXPECT_EQ("form", GetTabTitle());
+ GURL frames(GetURL("frames.html"));
+ EXPECT_EQ(frames, GetTabURL());
+
+ SubmitForm("isubmit");
+ EXPECT_EQ("text=&select=a", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("form", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, *form, post]
+
+ ClickLink("abot2");
+ EXPECT_EQ("bot2", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, form, *bot2]
+
+ GoBack();
+ EXPECT_EQ("form", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ SubmitForm("isubmit");
+ EXPECT_EQ("text=&select=a", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, form, *post]
+
+ // TODO(mpcomplete): reenable this when WebKit bug 10199 is fixed:
+ // "returning to a POST result within a frame does a GET instead of a POST"
+ ClickLink("abot2");
+ EXPECT_EQ("bot2", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("text=&select=a", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+}
+
+// TODO(mpcomplete): enable this when Bug 734372 is fixed:
+// "Doing a session history navigation does not restore newly-created subframe
+// document state"
+// Test that back/forward preserves POST data and document state when navigating
+// across frames (ie, from frame -> nonframe).
+// Hangs, see http://crbug.com/45058.
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, CrossFrameFormBackForward) {
+ ASSERT_FALSE(CanGoBack());
+
+ GURL frames(GetURL("frames.html"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("frames.html", "bot1"));
+
+ ClickLink("aform");
+ EXPECT_EQ("form", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ SubmitForm("isubmit");
+ EXPECT_EQ("text=&select=a", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ GoBack();
+ EXPECT_EQ("form", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ // history is [blank, bot1, *form, post]
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2"));
+
+ // history is [blank, bot1, form, *bot2]
+
+ GoBack();
+ EXPECT_EQ("bot1", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+
+ SubmitForm("isubmit");
+ EXPECT_EQ("text=&select=a", GetTabTitle());
+ EXPECT_EQ(frames, GetTabURL());
+}
+
+// Test that back/forward entries are created for reference fragment
+// navigations. Bug 730379.
+// If this flakes use http://crbug.com/61619.
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, FragmentBackForward) {
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&HandleEchoTitleRequest, "/echotitle"));
+
+ ASSERT_FALSE(CanGoBack());
+
+ GURL fragment(GetURL("fragment.html"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html", "fragment"));
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#a", "fragment"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#b", "fragment"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("fragment.html#c", "fragment"));
+
+ // history is [blank, fragment, fragment#a, fragment#b, *fragment#c]
+
+ GoBack();
+ EXPECT_EQ(GetURL("fragment.html#b"), GetTabURL());
+
+ GoBack();
+ EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL());
+
+ GoBack();
+ EXPECT_EQ(GetURL("fragment.html"), GetTabURL());
+
+ GoForward();
+ EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3"));
+
+ // history is [blank, fragment, fragment#a, bot3]
+
+ ASSERT_FALSE(CanGoForward());
+ EXPECT_EQ(GetURL("bot3.html"), GetTabURL());
+
+ GoBack();
+ EXPECT_EQ(GetURL("fragment.html#a"), GetTabURL());
+
+ GoBack();
+ EXPECT_EQ(GetURL("fragment.html"), GetTabURL());
+}
+
+// Test that the javascript window.history object works.
+// NOTE: history.go(N) does not do anything if N is outside the bounds of the
+// back/forward list (such as trigger our start/stop loading events). This
+// means the test will hang if it attempts to navigate too far forward or back,
+// since we'll be waiting forever for a load stop event.
+//
+// TODO(brettw) bug 50648: fix flakyness. This test seems like it was failing
+// about 1/4 of the time on Vista by failing to execute JavascriptGo (see bug).
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, JavascriptHistory) {
+ ASSERT_FALSE(CanGoBack());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot1.html", "bot1"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot2.html", "bot2"));
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3"));
+
+ // history is [blank, bot1, bot2, *bot3]
+
+ JavascriptGo("-1");
+ EXPECT_EQ("bot2", GetTabTitle());
+
+ JavascriptGo("-1");
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ JavascriptGo("1");
+ EXPECT_EQ("bot2", GetTabTitle());
+
+ JavascriptGo("-1");
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ JavascriptGo("2");
+ EXPECT_EQ("bot3", GetTabTitle());
+
+ // history is [blank, bot1, bot2, *bot3]
+
+ JavascriptGo("-3");
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ ASSERT_FALSE(CanGoBack());
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ JavascriptGo("1");
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle("bot3.html", "bot3"));
+
+ // history is [blank, bot1, *bot3]
+
+ ASSERT_FALSE(CanGoForward());
+ EXPECT_EQ("bot3", GetTabTitle());
+
+ JavascriptGo("-1");
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ JavascriptGo("-1");
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ ASSERT_FALSE(CanGoBack());
+ EXPECT_EQ(std::string(kAboutBlankURL), GetTabTitle());
+
+ JavascriptGo("1");
+ EXPECT_EQ("bot1", GetTabTitle());
+
+ JavascriptGo("1");
+ EXPECT_EQ("bot3", GetTabTitle());
+
+ // TODO(creis): Test that JavaScript history navigations work across tab
+ // types. For example, load about:network in a tab, then a real page, then
+ // try to go back and forward with JavaScript. Bug 1136715.
+ // (Hard to test right now, because pages like about:network cause the
+ // TabProxy to hang. This is because they do not appear to use the
+ // NotificationService.)
+}
+
+// This test is failing consistently. See http://crbug.com/22560
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, LocationReplace) {
+ // Test that using location.replace doesn't leave the title of the old page
+ // visible.
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle(
+ "replace.html?bot1.html", "bot1"));
+}
+
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, LocationChangeInSubframe) {
+ ASSERT_NO_FATAL_FAILURE(NavigateAndCheckTitle(
+ "location_redirect.html", "Default Title"));
+
+ NavigateToURL(shell(), GURL("javascript:void(frames[0].navigate())"));
+ EXPECT_EQ("foo", GetTabTitle());
+
+ GoBack();
+ EXPECT_EQ("Default Title", GetTabTitle());
+}
+
+// http://code.google.com/p/chromium/issues/detail?id=56267
+IN_PROC_BROWSER_TEST_F(SessionHistoryTest, HistoryLength) {
+ int length;
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "domAutomationController.send(history.length)",
+ &length));
+ EXPECT_EQ(1, length);
+
+ NavigateToURL(shell(), GetURL("title1.html"));
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "domAutomationController.send(history.length)",
+ &length));
+ EXPECT_EQ(2, length);
+
+ // Now test that history.length is updated when the navigation is committed.
+ NavigateToURL(shell(), GetURL("record_length.html"));
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "domAutomationController.send(history.length)",
+ &length));
+ EXPECT_EQ(3, length);
+
+ GoBack();
+ GoBack();
+
+ // Ensure history.length is properly truncated.
+ NavigateToURL(shell(), GetURL("title2.html"));
+
+ ASSERT_TRUE(ExecuteScriptAndExtractInt(
+ shell()->web_contents(),
+ "domAutomationController.send(history.length)",
+ &length));
+ EXPECT_EQ(2, length);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/site_instance_impl.cc b/chromium/content/browser/site_instance_impl.cc
new file mode 100644
index 00000000000..42680674d76
--- /dev/null
+++ b/chromium/content/browser/site_instance_impl.cc
@@ -0,0 +1,350 @@
+// 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 "content/browser/site_instance_impl.h"
+
+#include "base/command_line.h"
+#include "content/browser/browsing_instance.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host_factory.h"
+#include "content/public/browser/web_ui_controller_factory.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+
+namespace content {
+
+static bool IsURLSameAsAnySiteInstance(const GURL& url) {
+ if (!url.is_valid())
+ return false;
+
+ // We treat javascript: as the same site as any URL since it is actually
+ // a modifier on existing pages.
+ if (url.SchemeIs(chrome::kJavaScriptScheme))
+ return true;
+
+ return url == GURL(kChromeUICrashURL) ||
+ url == GURL(kChromeUIKillURL) ||
+ url == GURL(kChromeUIHangURL) ||
+ url == GURL(kChromeUIShorthangURL);
+}
+
+const RenderProcessHostFactory*
+ SiteInstanceImpl::g_render_process_host_factory_ = NULL;
+int32 SiteInstanceImpl::next_site_instance_id_ = 1;
+
+SiteInstanceImpl::SiteInstanceImpl(BrowsingInstance* browsing_instance)
+ : id_(next_site_instance_id_++),
+ active_view_count_(0),
+ browsing_instance_(browsing_instance),
+ process_(NULL),
+ has_site_(false) {
+ DCHECK(browsing_instance);
+
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+SiteInstanceImpl::~SiteInstanceImpl() {
+ GetContentClient()->browser()->SiteInstanceDeleting(this);
+
+ // Now that no one is referencing us, we can safely remove ourselves from
+ // the BrowsingInstance. Any future visits to a page from this site
+ // (within the same BrowsingInstance) can safely create a new SiteInstance.
+ if (has_site_)
+ browsing_instance_->UnregisterSiteInstance(
+ static_cast<SiteInstance*>(this));
+}
+
+int32 SiteInstanceImpl::GetId() {
+ return id_;
+}
+
+bool SiteInstanceImpl::HasProcess() const {
+ if (process_ != NULL)
+ return true;
+
+ // If we would use process-per-site for this site, also check if there is an
+ // existing process that we would use if GetProcess() were called.
+ BrowserContext* browser_context =
+ browsing_instance_->browser_context();
+ if (has_site_ &&
+ RenderProcessHost::ShouldUseProcessPerSite(browser_context, site_) &&
+ RenderProcessHostImpl::GetProcessHostForSite(browser_context, site_)) {
+ return true;
+ }
+
+ return false;
+}
+
+RenderProcessHost* SiteInstanceImpl::GetProcess() {
+ // TODO(erikkay) It would be nice to ensure that the renderer type had been
+ // properly set before we get here. The default tab creation case winds up
+ // with no site set at this point, so it will default to TYPE_NORMAL. This
+ // may not be correct, so we'll wind up potentially creating a process that
+ // we then throw away, or worse sharing a process with the wrong process type.
+ // See crbug.com/43448.
+
+ // Create a new process if ours went away or was reused.
+ if (!process_) {
+ BrowserContext* browser_context = browsing_instance_->browser_context();
+
+ // If we should use process-per-site mode (either in general or for the
+ // given site), then look for an existing RenderProcessHost for the site.
+ bool use_process_per_site = has_site_ &&
+ RenderProcessHost::ShouldUseProcessPerSite(browser_context, site_);
+ if (use_process_per_site) {
+ process_ = RenderProcessHostImpl::GetProcessHostForSite(browser_context,
+ site_);
+ }
+
+ // If not (or if none found), see if we should reuse an existing process.
+ if (!process_ && RenderProcessHostImpl::ShouldTryToUseExistingProcessHost(
+ browser_context, site_)) {
+ process_ = RenderProcessHostImpl::GetExistingProcessHost(browser_context,
+ site_);
+ }
+
+ // Otherwise (or if that fails), create a new one.
+ if (!process_) {
+ if (g_render_process_host_factory_) {
+ process_ = g_render_process_host_factory_->CreateRenderProcessHost(
+ browser_context, this);
+ } else {
+ StoragePartitionImpl* partition =
+ static_cast<StoragePartitionImpl*>(
+ BrowserContext::GetStoragePartition(browser_context, this));
+ bool supports_browser_plugin = GetContentClient()->browser()->
+ SupportsBrowserPlugin(browser_context, site_);
+ process_ =
+ new RenderProcessHostImpl(browser_context, partition,
+ supports_browser_plugin,
+ site_.SchemeIs(chrome::kGuestScheme));
+ }
+ }
+ CHECK(process_);
+
+ // If we are using process-per-site, we need to register this process
+ // for the current site so that we can find it again. (If no site is set
+ // at this time, we will register it in SetSite().)
+ if (use_process_per_site) {
+ RenderProcessHostImpl::RegisterProcessHostForSite(browser_context,
+ process_, site_);
+ }
+
+ GetContentClient()->browser()->SiteInstanceGotProcess(this);
+
+ if (has_site_)
+ LockToOrigin();
+ }
+ DCHECK(process_);
+
+ return process_;
+}
+
+void SiteInstanceImpl::SetSite(const GURL& url) {
+ // A SiteInstance's site should not change.
+ // TODO(creis): When following links or script navigations, we can currently
+ // render pages from other sites in this SiteInstance. This will eventually
+ // be fixed, but until then, we should still not set the site of a
+ // SiteInstance more than once.
+ DCHECK(!has_site_);
+
+ // Remember that this SiteInstance has been used to load a URL, even if the
+ // URL is invalid.
+ has_site_ = true;
+ BrowserContext* browser_context = browsing_instance_->browser_context();
+ site_ = GetSiteForURL(browser_context, url);
+
+ // Now that we have a site, register it with the BrowsingInstance. This
+ // ensures that we won't create another SiteInstance for this site within
+ // the same BrowsingInstance, because all same-site pages within a
+ // BrowsingInstance can script each other.
+ browsing_instance_->RegisterSiteInstance(this);
+
+ if (process_) {
+ LockToOrigin();
+
+ // Ensure the process is registered for this site if necessary.
+ if (RenderProcessHost::ShouldUseProcessPerSite(browser_context, site_)) {
+ RenderProcessHostImpl::RegisterProcessHostForSite(
+ browser_context, process_, site_);
+ }
+ }
+}
+
+const GURL& SiteInstanceImpl::GetSiteURL() const {
+ return site_;
+}
+
+bool SiteInstanceImpl::HasSite() const {
+ return has_site_;
+}
+
+bool SiteInstanceImpl::HasRelatedSiteInstance(const GURL& url) {
+ return browsing_instance_->HasSiteInstance(url);
+}
+
+SiteInstance* SiteInstanceImpl::GetRelatedSiteInstance(const GURL& url) {
+ return browsing_instance_->GetSiteInstanceForURL(url);
+}
+
+bool SiteInstanceImpl::IsRelatedSiteInstance(const SiteInstance* instance) {
+ return browsing_instance_.get() == static_cast<const SiteInstanceImpl*>(
+ instance)->browsing_instance_.get();
+}
+
+bool SiteInstanceImpl::HasWrongProcessForURL(const GURL& url) {
+ // Having no process isn't a problem, since we'll assign it correctly.
+ // Note that HasProcess() may return true if process_ is null, in
+ // process-per-site cases where there's an existing process available.
+ // We want to use such a process in the IsSuitableHost check, so we
+ // may end up assigning process_ in the GetProcess() call below.
+ if (!HasProcess())
+ return false;
+
+ // If the URL to navigate to can be associated with any site instance,
+ // we want to keep it in the same process.
+ if (IsURLSameAsAnySiteInstance(url))
+ return false;
+
+ // If the site URL is an extension (e.g., for hosted apps or WebUI) but the
+ // process is not (or vice versa), make sure we notice and fix it.
+ GURL site_url = GetSiteForURL(browsing_instance_->browser_context(), url);
+ return !RenderProcessHostImpl::IsSuitableHost(
+ GetProcess(), browsing_instance_->browser_context(), site_url);
+}
+
+void SiteInstanceImpl::set_render_process_host_factory(
+ const RenderProcessHostFactory* rph_factory) {
+ g_render_process_host_factory_ = rph_factory;
+}
+
+BrowserContext* SiteInstanceImpl::GetBrowserContext() const {
+ return browsing_instance_->browser_context();
+}
+
+/*static*/
+SiteInstance* SiteInstance::Create(BrowserContext* browser_context) {
+ return new SiteInstanceImpl(new BrowsingInstance(browser_context));
+}
+
+/*static*/
+SiteInstance* SiteInstance::CreateForURL(BrowserContext* browser_context,
+ const GURL& url) {
+ // This BrowsingInstance may be deleted if it returns an existing
+ // SiteInstance.
+ scoped_refptr<BrowsingInstance> instance(
+ new BrowsingInstance(browser_context));
+ return instance->GetSiteInstanceForURL(url);
+}
+
+/*static*/
+bool SiteInstance::IsSameWebSite(BrowserContext* browser_context,
+ const GURL& real_url1,
+ const GURL& real_url2) {
+ GURL url1 = SiteInstanceImpl::GetEffectiveURL(browser_context, real_url1);
+ GURL url2 = SiteInstanceImpl::GetEffectiveURL(browser_context, real_url2);
+
+ // We infer web site boundaries based on the registered domain name of the
+ // top-level page and the scheme. We do not pay attention to the port if
+ // one is present, because pages served from different ports can still
+ // access each other if they change their document.domain variable.
+
+ // Some special URLs will match the site instance of any other URL. This is
+ // done before checking both of them for validity, since we want these URLs
+ // to have the same site instance as even an invalid one.
+ if (IsURLSameAsAnySiteInstance(url1) || IsURLSameAsAnySiteInstance(url2))
+ return true;
+
+ // If either URL is invalid, they aren't part of the same site.
+ if (!url1.is_valid() || !url2.is_valid())
+ return false;
+
+ // If the schemes differ, they aren't part of the same site.
+ if (url1.scheme() != url2.scheme())
+ return false;
+
+ return net::registry_controlled_domains::SameDomainOrHost(
+ url1,
+ url2,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+/*static*/
+GURL SiteInstance::GetSiteForURL(BrowserContext* browser_context,
+ const GURL& real_url) {
+ // TODO(fsamuel, creis): For some reason appID is not recognized as a host.
+ if (real_url.SchemeIs(chrome::kGuestScheme))
+ return real_url;
+
+ GURL url = SiteInstanceImpl::GetEffectiveURL(browser_context, real_url);
+
+ // URLs with no host should have an empty site.
+ GURL site;
+
+ // TODO(creis): For many protocols, we should just treat the scheme as the
+ // site, since there is no host. e.g., file:, about:, chrome:
+
+ // If the url has a host, then determine the site.
+ if (url.has_host()) {
+ // Only keep the scheme and registered domain as given by GetOrigin. This
+ // may also include a port, which we need to drop.
+ site = url.GetOrigin();
+
+ // Remove port, if any.
+ if (site.has_port()) {
+ GURL::Replacements rep;
+ rep.ClearPort();
+ site = site.ReplaceComponents(rep);
+ }
+
+ // If this URL has a registered domain, we only want to remember that part.
+ std::string domain =
+ net::registry_controlled_domains::GetDomainAndRegistry(
+ url,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if (!domain.empty()) {
+ GURL::Replacements rep;
+ rep.SetHostStr(domain);
+ site = site.ReplaceComponents(rep);
+ }
+ }
+ return site;
+}
+
+/*static*/
+GURL SiteInstanceImpl::GetEffectiveURL(BrowserContext* browser_context,
+ const GURL& url) {
+ return GetContentClient()->browser()->
+ GetEffectiveURL(browser_context, url);
+}
+
+void SiteInstanceImpl::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFICATION_RENDERER_PROCESS_TERMINATED);
+ RenderProcessHost* rph = Source<RenderProcessHost>(source).ptr();
+ if (rph == process_)
+ process_ = NULL;
+}
+
+void SiteInstanceImpl::LockToOrigin() {
+ // We currently only restrict this process to a particular site if the
+ // --enable-strict-site-isolation or --site-per-process flags are present.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kEnableStrictSiteIsolation) ||
+ command_line.HasSwitch(switches::kSitePerProcess)) {
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ policy->LockToOrigin(process_->GetID(), site_);
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/site_instance_impl.h b/chromium/content/browser/site_instance_impl.h
new file mode 100644
index 00000000000..714fee0c23b
--- /dev/null
+++ b/chromium/content/browser/site_instance_impl.h
@@ -0,0 +1,133 @@
+// 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 CONTENT_BROWSER_SITE_INSTANCE_IMPL_H_
+#define CONTENT_BROWSER_SITE_INSTANCE_IMPL_H_
+
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/site_instance.h"
+#include "url/gurl.h"
+
+namespace content {
+class RenderProcessHostFactory;
+
+class CONTENT_EXPORT SiteInstanceImpl : public SiteInstance,
+ public NotificationObserver {
+ public:
+ // SiteInstance interface overrides.
+ virtual int32 GetId() OVERRIDE;
+ virtual bool HasProcess() const OVERRIDE;
+ virtual RenderProcessHost* GetProcess() OVERRIDE;
+ virtual const GURL& GetSiteURL() const OVERRIDE;
+ virtual SiteInstance* GetRelatedSiteInstance(const GURL& url) OVERRIDE;
+ virtual bool IsRelatedSiteInstance(const SiteInstance* instance) OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+
+ // Set the web site that this SiteInstance is rendering pages for.
+ // This includes the scheme and registered domain, but not the port. If the
+ // URL does not have a valid registered domain, then the full hostname is
+ // stored.
+ void SetSite(const GURL& url);
+ bool HasSite() const;
+
+ // Returns whether there is currently a related SiteInstance (registered with
+ // BrowsingInstance) for the site of the given url. If so, we should try to
+ // avoid dedicating an unused SiteInstance to it (e.g., in a new tab).
+ bool HasRelatedSiteInstance(const GURL& url);
+
+ // Returns whether this SiteInstance has a process that is the wrong type for
+ // the given URL. If so, the browser should force a process swap when
+ // navigating to the URL.
+ bool HasWrongProcessForURL(const GURL& url);
+
+ // Increase the number of active views in this SiteInstance. This is
+ // increased when a view is created, or a currently swapped out view
+ // is swapped in.
+ void increment_active_view_count() { active_view_count_++; }
+
+ // Decrease the number of active views in this SiteInstance. This is
+ // decreased when a view is destroyed, or a currently active view is
+ // swapped out.
+ void decrement_active_view_count() { active_view_count_--; }
+
+ // Get the number of active views which belong to this
+ // SiteInstance. If there is no active view left in this
+ // SiteInstance, all view in this SiteInstance can be safely
+ // discarded to save memory.
+ size_t active_view_count() { return active_view_count_; }
+
+ // Sets the global factory used to create new RenderProcessHosts. It may be
+ // NULL, in which case the default BrowserRenderProcessHost will be created
+ // (this is the behavior if you don't call this function). The factory must
+ // be set back to NULL before it's destroyed; ownership is not transferred.
+ static void set_render_process_host_factory(
+ const RenderProcessHostFactory* rph_factory);
+
+ protected:
+ friend class BrowsingInstance;
+ friend class SiteInstance;
+
+ // Virtual to allow tests to extend it.
+ virtual ~SiteInstanceImpl();
+
+ // Create a new SiteInstance. Protected to give access to BrowsingInstance
+ // and tests; most callers should use Create or GetRelatedSiteInstance
+ // instead.
+ explicit SiteInstanceImpl(BrowsingInstance* browsing_instance);
+
+ private:
+ // Get the effective URL for the given actual URL.
+ static GURL GetEffectiveURL(BrowserContext* browser_context,
+ const GURL& url);
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Used to restrict a process' origin access rights.
+ void LockToOrigin();
+
+ // An object used to construct RenderProcessHosts.
+ static const RenderProcessHostFactory* g_render_process_host_factory_;
+
+ // The next available SiteInstance ID.
+ static int32 next_site_instance_id_;
+
+ // A unique ID for this SiteInstance.
+ int32 id_;
+
+ // The number of active views under this SiteInstance.
+ size_t active_view_count_;
+
+ NotificationRegistrar registrar_;
+
+ // BrowsingInstance to which this SiteInstance belongs.
+ scoped_refptr<BrowsingInstance> browsing_instance_;
+
+ // Factory for new RenderProcessHosts, not owned by this class. NULL indiactes
+ // that the default BrowserRenderProcessHost should be created.
+ const RenderProcessHostFactory* render_process_host_factory_;
+
+ // Current RenderProcessHost that is rendering pages for this SiteInstance.
+ // This pointer will only change once the RenderProcessHost is destructed. It
+ // will still remain the same even if the process crashes, since in that
+ // scenario the RenderProcessHost remains the same.
+ RenderProcessHost* process_;
+
+ // The web site that this SiteInstance is rendering pages for.
+ GURL site_;
+
+ // Whether SetSite has been called.
+ bool has_site_;
+
+ DISALLOW_COPY_AND_ASSIGN(SiteInstanceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SITE_INSTANCE_IMPL_H_
diff --git a/chromium/content/browser/site_instance_impl_unittest.cc b/chromium/content/browser/site_instance_impl_unittest.cc
new file mode 100644
index 00000000000..d61c70b353d
--- /dev/null
+++ b/chromium/content/browser/site_instance_impl_unittest.cc
@@ -0,0 +1,760 @@
+// 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 "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/browsing_instance.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/common/url_utils.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_util.h"
+
+namespace content {
+namespace {
+
+const char kSameAsAnyInstanceURL[] = "about:internets";
+
+const char kPrivilegedScheme[] = "privileged";
+
+class SiteInstanceTestWebUIControllerFactory : public WebUIControllerFactory {
+ public:
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const OVERRIDE {
+ return NULL;
+ }
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return WebUI::kNoWebUI;
+ }
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+};
+
+class SiteInstanceTestBrowserClient : public TestContentBrowserClient {
+ public:
+ SiteInstanceTestBrowserClient()
+ : privileged_process_id_(-1) {
+ WebUIControllerFactory::RegisterFactory(&factory_);
+ }
+
+ virtual ~SiteInstanceTestBrowserClient() {
+ WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
+ }
+
+ virtual bool IsSuitableHost(RenderProcessHost* process_host,
+ const GURL& site_url) OVERRIDE {
+ return (privileged_process_id_ == process_host->GetID()) ==
+ site_url.SchemeIs(kPrivilegedScheme);
+ }
+
+ void set_privileged_process_id(int process_id) {
+ privileged_process_id_ = process_id;
+ }
+
+ private:
+ SiteInstanceTestWebUIControllerFactory factory_;
+ int privileged_process_id_;
+};
+
+class SiteInstanceTest : public testing::Test {
+ public:
+ SiteInstanceTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ file_user_blocking_thread_(BrowserThread::FILE_USER_BLOCKING,
+ &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_),
+ old_browser_client_(NULL) {
+ }
+
+ virtual void SetUp() {
+ old_browser_client_ = SetBrowserClientForTesting(&browser_client_);
+ url_util::AddStandardScheme(kPrivilegedScheme);
+ url_util::AddStandardScheme(chrome::kChromeUIScheme);
+ }
+
+ virtual void TearDown() {
+ // Ensure that no RenderProcessHosts are left over after the tests.
+ EXPECT_TRUE(RenderProcessHost::AllHostsIterator().IsAtEnd());
+
+ SetBrowserClientForTesting(old_browser_client_);
+ SiteInstanceImpl::set_render_process_host_factory(NULL);
+
+ // http://crbug.com/143565 found SiteInstanceTest leaking an
+ // AppCacheDatabase. This happens because some part of the test indirectly
+ // calls StoragePartitionImplMap::PostCreateInitialization(), which posts
+ // a task to the IO thread to create the AppCacheDatabase. Since the
+ // message loop is not running, the AppCacheDatabase ends up getting
+ // created when DrainMessageLoops() gets called at the end of a test case.
+ // Immediately after, the test case ends and the AppCacheDatabase gets
+ // scheduled for deletion. Here, call DrainMessageLoops() again so the
+ // AppCacheDatabase actually gets deleted.
+ DrainMessageLoops();
+ }
+
+ void set_privileged_process_id(int process_id) {
+ browser_client_.set_privileged_process_id(process_id);
+ }
+
+ void DrainMessageLoops() {
+ // We don't just do this in TearDown() because we create TestBrowserContext
+ // objects in each test, which will be destructed before
+ // TearDown() is called.
+ base::MessageLoop::current()->RunUntilIdle();
+ message_loop_.RunUntilIdle();
+ }
+
+ private:
+ base::MessageLoopForUI message_loop_;
+ TestBrowserThread ui_thread_;
+ TestBrowserThread file_user_blocking_thread_;
+ TestBrowserThread io_thread_;
+
+ SiteInstanceTestBrowserClient browser_client_;
+ ContentBrowserClient* old_browser_client_;
+};
+
+// Subclass of BrowsingInstance that updates a counter when deleted and
+// returns TestSiteInstances from GetSiteInstanceForURL.
+class TestBrowsingInstance : public BrowsingInstance {
+ public:
+ TestBrowsingInstance(BrowserContext* browser_context, int* delete_counter)
+ : BrowsingInstance(browser_context),
+ delete_counter_(delete_counter) {
+ }
+
+ // Make a few methods public for tests.
+ using BrowsingInstance::browser_context;
+ using BrowsingInstance::HasSiteInstance;
+ using BrowsingInstance::GetSiteInstanceForURL;
+ using BrowsingInstance::RegisterSiteInstance;
+ using BrowsingInstance::UnregisterSiteInstance;
+
+ private:
+ virtual ~TestBrowsingInstance() {
+ (*delete_counter_)++;
+ }
+
+ int* delete_counter_;
+};
+
+// Subclass of SiteInstanceImpl that updates a counter when deleted.
+class TestSiteInstance : public SiteInstanceImpl {
+ public:
+ static TestSiteInstance* CreateTestSiteInstance(
+ BrowserContext* browser_context,
+ int* site_delete_counter,
+ int* browsing_delete_counter) {
+ TestBrowsingInstance* browsing_instance =
+ new TestBrowsingInstance(browser_context, browsing_delete_counter);
+ return new TestSiteInstance(browsing_instance, site_delete_counter);
+ }
+
+ private:
+ TestSiteInstance(BrowsingInstance* browsing_instance, int* delete_counter)
+ : SiteInstanceImpl(browsing_instance), delete_counter_(delete_counter) {}
+ virtual ~TestSiteInstance() {
+ (*delete_counter_)++;
+ }
+
+ int* delete_counter_;
+};
+
+} // namespace
+
+// Test to ensure no memory leaks for SiteInstance objects.
+TEST_F(SiteInstanceTest, SiteInstanceDestructor) {
+ // The existence of this object will cause WebContentsImpl to create our
+ // test one instead of the real one.
+ RenderViewHostTestEnabler rvh_test_enabler;
+ int site_delete_counter = 0;
+ int browsing_delete_counter = 0;
+ const GURL url("test:foo");
+
+ // Ensure that instances are deleted when their NavigationEntries are gone.
+ TestSiteInstance* instance =
+ TestSiteInstance::CreateTestSiteInstance(NULL, &site_delete_counter,
+ &browsing_delete_counter);
+ EXPECT_EQ(0, site_delete_counter);
+
+ NavigationEntryImpl* e1 = new NavigationEntryImpl(
+ instance, 0, url, Referrer(), string16(), PAGE_TRANSITION_LINK, false);
+
+ // Redundantly setting e1's SiteInstance shouldn't affect the ref count.
+ e1->set_site_instance(instance);
+ EXPECT_EQ(0, site_delete_counter);
+
+ // Add a second reference
+ NavigationEntryImpl* e2 = new NavigationEntryImpl(
+ instance, 0, url, Referrer(), string16(), PAGE_TRANSITION_LINK, false);
+
+ // Now delete both entries and be sure the SiteInstance goes away.
+ delete e1;
+ EXPECT_EQ(0, site_delete_counter);
+ EXPECT_EQ(0, browsing_delete_counter);
+ delete e2;
+ EXPECT_EQ(1, site_delete_counter);
+ // instance is now deleted
+ EXPECT_EQ(1, browsing_delete_counter);
+ // browsing_instance is now deleted
+
+ // Ensure that instances are deleted when their RenderViewHosts are gone.
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ instance =
+ TestSiteInstance::CreateTestSiteInstance(browser_context.get(),
+ &site_delete_counter,
+ &browsing_delete_counter);
+ {
+ scoped_ptr<WebContentsImpl> web_contents(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(
+ browser_context.get(), instance))));
+ EXPECT_EQ(1, site_delete_counter);
+ EXPECT_EQ(1, browsing_delete_counter);
+ }
+
+ // Make sure that we flush any messages related to the above WebContentsImpl
+ // destruction.
+ DrainMessageLoops();
+
+ EXPECT_EQ(2, site_delete_counter);
+ EXPECT_EQ(2, browsing_delete_counter);
+ // contents is now deleted, along with instance and browsing_instance
+}
+
+// Test that NavigationEntries with SiteInstances can be cloned, but that their
+// SiteInstances can be changed afterwards. Also tests that the ref counts are
+// updated properly after the change.
+TEST_F(SiteInstanceTest, CloneNavigationEntry) {
+ int site_delete_counter1 = 0;
+ int site_delete_counter2 = 0;
+ int browsing_delete_counter = 0;
+ const GURL url("test:foo");
+
+ SiteInstanceImpl* instance1 =
+ TestSiteInstance::CreateTestSiteInstance(NULL, &site_delete_counter1,
+ &browsing_delete_counter);
+ SiteInstanceImpl* instance2 =
+ TestSiteInstance::CreateTestSiteInstance(NULL, &site_delete_counter2,
+ &browsing_delete_counter);
+
+ NavigationEntryImpl* e1 = new NavigationEntryImpl(
+ instance1, 0, url, Referrer(), string16(), PAGE_TRANSITION_LINK, false);
+ // Clone the entry
+ NavigationEntryImpl* e2 = new NavigationEntryImpl(*e1);
+
+ // Should be able to change the SiteInstance of the cloned entry.
+ e2->set_site_instance(instance2);
+
+ // The first SiteInstance should go away after deleting e1, since e2 should
+ // no longer be referencing it.
+ delete e1;
+ EXPECT_EQ(1, site_delete_counter1);
+ EXPECT_EQ(0, site_delete_counter2);
+
+ // The second SiteInstance should go away after deleting e2.
+ delete e2;
+ EXPECT_EQ(1, site_delete_counter1);
+ EXPECT_EQ(1, site_delete_counter2);
+
+ // Both BrowsingInstances are also now deleted
+ EXPECT_EQ(2, browsing_delete_counter);
+
+ DrainMessageLoops();
+}
+
+// Test to ensure GetProcess returns and creates processes correctly.
+TEST_F(SiteInstanceTest, GetProcess) {
+ // Ensure that GetProcess returns a process.
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ scoped_ptr<RenderProcessHost> host1;
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+ host1.reset(instance->GetProcess());
+ EXPECT_TRUE(host1.get() != NULL);
+
+ // Ensure that GetProcess creates a new process.
+ scoped_refptr<SiteInstanceImpl> instance2(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+ scoped_ptr<RenderProcessHost> host2(instance2->GetProcess());
+ EXPECT_TRUE(host2.get() != NULL);
+ EXPECT_NE(host1.get(), host2.get());
+
+ DrainMessageLoops();
+}
+
+// Test to ensure SetSite and site() work properly.
+TEST_F(SiteInstanceTest, SetSite) {
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(NULL)));
+ EXPECT_FALSE(instance->HasSite());
+ EXPECT_TRUE(instance->GetSiteURL().is_empty());
+
+ instance->SetSite(GURL("http://www.google.com/index.html"));
+ EXPECT_EQ(GURL("http://google.com"), instance->GetSiteURL());
+
+ EXPECT_TRUE(instance->HasSite());
+
+ DrainMessageLoops();
+}
+
+// Test to ensure GetSiteForURL properly returns sites for URLs.
+TEST_F(SiteInstanceTest, GetSiteForURL) {
+ // Pages are irrelevant.
+ GURL test_url = GURL("http://www.google.com/index.html");
+ EXPECT_EQ(GURL("http://google.com"),
+ SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ // Ports are irrlevant.
+ test_url = GURL("https://www.google.com:8080");
+ EXPECT_EQ(GURL("https://google.com"),
+ SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ // Javascript URLs have no site.
+ test_url = GURL("javascript:foo();");
+ EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ test_url = GURL("http://foo/a.html");
+ EXPECT_EQ(GURL("http://foo"), SiteInstanceImpl::GetSiteForURL(
+ NULL, test_url));
+
+ test_url = GURL("file:///C:/Downloads/");
+ EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ std::string guest_url(chrome::kGuestScheme);
+ guest_url.append("://abc123");
+ test_url = GURL(guest_url);
+ EXPECT_EQ(test_url, SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ // TODO(creis): Do we want to special case file URLs to ensure they have
+ // either no site or a special "file://" site? We currently return
+ // "file://home/" as the site, which seems broken.
+ // test_url = GURL("file://home/");
+ // EXPECT_EQ(GURL(), SiteInstanceImpl::GetSiteForURL(NULL, test_url));
+
+ DrainMessageLoops();
+}
+
+// Test of distinguishing URLs from different sites. Most of this logic is
+// tested in RegistryControlledDomainTest. This test focuses on URLs with
+// different schemes or ports.
+TEST_F(SiteInstanceTest, IsSameWebSite) {
+ GURL url_foo = GURL("http://foo/a.html");
+ GURL url_foo2 = GURL("http://foo/b.html");
+ GURL url_foo_https = GURL("https://foo/a.html");
+ GURL url_foo_port = GURL("http://foo:8080/a.html");
+ GURL url_javascript = GURL("javascript:alert(1);");
+
+ // Same scheme and port -> same site.
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo2));
+
+ // Different scheme -> different site.
+ EXPECT_FALSE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_https));
+
+ // Different port -> same site.
+ // (Changes to document.domain make renderer ignore the port.)
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_foo, url_foo_port));
+
+ // JavaScript links should be considered same site for anything.
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_https));
+ EXPECT_TRUE(SiteInstance::IsSameWebSite(NULL, url_javascript, url_foo_port));
+
+ DrainMessageLoops();
+}
+
+// Test to ensure that there is only one SiteInstance per site in a given
+// BrowsingInstance, when process-per-site is not in use.
+TEST_F(SiteInstanceTest, OneSiteInstancePerSite) {
+ ASSERT_FALSE(CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kProcessPerSite));
+ int delete_counter = 0;
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ TestBrowsingInstance* browsing_instance =
+ new TestBrowsingInstance(browser_context.get(), &delete_counter);
+
+ const GURL url_a1("http://www.google.com/1.html");
+ scoped_refptr<SiteInstanceImpl> site_instance_a1(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance->GetSiteInstanceForURL(url_a1)));
+ EXPECT_TRUE(site_instance_a1.get() != NULL);
+
+ // A separate site should create a separate SiteInstance.
+ const GURL url_b1("http://www.yahoo.com/");
+ scoped_refptr<SiteInstanceImpl> site_instance_b1(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance->GetSiteInstanceForURL(url_b1)));
+ EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
+ EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
+
+ // Getting the new SiteInstance from the BrowsingInstance and from another
+ // SiteInstance in the BrowsingInstance should give the same result.
+ EXPECT_EQ(site_instance_b1.get(),
+ site_instance_a1->GetRelatedSiteInstance(url_b1));
+
+ // A second visit to the original site should return the same SiteInstance.
+ const GURL url_a2("http://www.google.com/2.html");
+ EXPECT_EQ(site_instance_a1.get(),
+ browsing_instance->GetSiteInstanceForURL(url_a2));
+ EXPECT_EQ(site_instance_a1.get(),
+ site_instance_a1->GetRelatedSiteInstance(url_a2));
+
+ // A visit to the original site in a new BrowsingInstance (same or different
+ // browser context) should return a different SiteInstance.
+ TestBrowsingInstance* browsing_instance2 =
+ new TestBrowsingInstance(browser_context.get(), &delete_counter);
+ // Ensure the new SiteInstance is ref counted so that it gets deleted.
+ scoped_refptr<SiteInstanceImpl> site_instance_a2_2(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance2->GetSiteInstanceForURL(url_a2)));
+ EXPECT_NE(site_instance_a1.get(), site_instance_a2_2.get());
+ EXPECT_FALSE(
+ site_instance_a1->IsRelatedSiteInstance(site_instance_a2_2.get()));
+
+ // The two SiteInstances for http://google.com should not use the same process
+ // if process-per-site is not enabled.
+ scoped_ptr<RenderProcessHost> process_a1(site_instance_a1->GetProcess());
+ scoped_ptr<RenderProcessHost> process_a2_2(site_instance_a2_2->GetProcess());
+ EXPECT_NE(process_a1.get(), process_a2_2.get());
+
+ // Should be able to see that we do have SiteInstances.
+ EXPECT_TRUE(browsing_instance->HasSiteInstance(
+ GURL("http://mail.google.com")));
+ EXPECT_TRUE(browsing_instance2->HasSiteInstance(
+ GURL("http://mail.google.com")));
+ EXPECT_TRUE(browsing_instance->HasSiteInstance(
+ GURL("http://mail.yahoo.com")));
+
+ // Should be able to see that we don't have SiteInstances.
+ EXPECT_FALSE(browsing_instance->HasSiteInstance(
+ GURL("https://www.google.com")));
+ EXPECT_FALSE(browsing_instance2->HasSiteInstance(
+ GURL("http://www.yahoo.com")));
+
+ // browsing_instances will be deleted when their SiteInstances are deleted.
+ // The processes will be unregistered when the RPH scoped_ptrs go away.
+
+ DrainMessageLoops();
+}
+
+// Test to ensure that there is only one RenderProcessHost per site for an
+// entire BrowserContext, if process-per-site is in use.
+TEST_F(SiteInstanceTest, OneSiteInstancePerSiteInBrowserContext) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kProcessPerSite);
+ int delete_counter = 0;
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ TestBrowsingInstance* browsing_instance =
+ new TestBrowsingInstance(browser_context.get(), &delete_counter);
+
+ const GURL url_a1("http://www.google.com/1.html");
+ scoped_refptr<SiteInstanceImpl> site_instance_a1(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance->GetSiteInstanceForURL(url_a1)));
+ EXPECT_TRUE(site_instance_a1.get() != NULL);
+ scoped_ptr<RenderProcessHost> process_a1(site_instance_a1->GetProcess());
+
+ // A separate site should create a separate SiteInstance.
+ const GURL url_b1("http://www.yahoo.com/");
+ scoped_refptr<SiteInstanceImpl> site_instance_b1(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance->GetSiteInstanceForURL(url_b1)));
+ EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
+ EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
+
+ // Getting the new SiteInstance from the BrowsingInstance and from another
+ // SiteInstance in the BrowsingInstance should give the same result.
+ EXPECT_EQ(site_instance_b1.get(),
+ site_instance_a1->GetRelatedSiteInstance(url_b1));
+
+ // A second visit to the original site should return the same SiteInstance.
+ const GURL url_a2("http://www.google.com/2.html");
+ EXPECT_EQ(site_instance_a1.get(),
+ browsing_instance->GetSiteInstanceForURL(url_a2));
+ EXPECT_EQ(site_instance_a1.get(),
+ site_instance_a1->GetRelatedSiteInstance(url_a2));
+
+ // A visit to the original site in a new BrowsingInstance (same browser
+ // context) should return a different SiteInstance with the same process.
+ TestBrowsingInstance* browsing_instance2 =
+ new TestBrowsingInstance(browser_context.get(), &delete_counter);
+ scoped_refptr<SiteInstanceImpl> site_instance_a1_2(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance2->GetSiteInstanceForURL(url_a1)));
+ EXPECT_TRUE(site_instance_a1.get() != NULL);
+ EXPECT_NE(site_instance_a1.get(), site_instance_a1_2.get());
+ EXPECT_EQ(process_a1.get(), site_instance_a1_2->GetProcess());
+
+ // A visit to the original site in a new BrowsingInstance (different browser
+ // context) should return a different SiteInstance with a different process.
+ scoped_ptr<TestBrowserContext> browser_context2(new TestBrowserContext());
+ TestBrowsingInstance* browsing_instance3 =
+ new TestBrowsingInstance(browser_context2.get(), &delete_counter);
+ scoped_refptr<SiteInstanceImpl> site_instance_a2_3(
+ static_cast<SiteInstanceImpl*>(
+ browsing_instance3->GetSiteInstanceForURL(url_a2)));
+ EXPECT_TRUE(site_instance_a2_3.get() != NULL);
+ scoped_ptr<RenderProcessHost> process_a2_3(site_instance_a2_3->GetProcess());
+ EXPECT_NE(site_instance_a1.get(), site_instance_a2_3.get());
+ EXPECT_NE(process_a1.get(), process_a2_3.get());
+
+ // Should be able to see that we do have SiteInstances.
+ EXPECT_TRUE(browsing_instance->HasSiteInstance(
+ GURL("http://mail.google.com"))); // visited before
+ EXPECT_TRUE(browsing_instance2->HasSiteInstance(
+ GURL("http://mail.google.com"))); // visited before
+ EXPECT_TRUE(browsing_instance->HasSiteInstance(
+ GURL("http://mail.yahoo.com"))); // visited before
+
+ // Should be able to see that we don't have SiteInstances.
+ EXPECT_FALSE(browsing_instance2->HasSiteInstance(
+ GURL("http://www.yahoo.com"))); // different BI, same browser context
+ EXPECT_FALSE(browsing_instance->HasSiteInstance(
+ GURL("https://www.google.com"))); // not visited before
+ EXPECT_FALSE(browsing_instance3->HasSiteInstance(
+ GURL("http://www.yahoo.com"))); // different BI, different context
+
+ // browsing_instances will be deleted when their SiteInstances are deleted.
+ // The processes will be unregistered when the RPH scoped_ptrs go away.
+
+ DrainMessageLoops();
+}
+
+static SiteInstanceImpl* CreateSiteInstance(BrowserContext* browser_context,
+ const GURL& url) {
+ return static_cast<SiteInstanceImpl*>(
+ SiteInstance::CreateForURL(browser_context, url));
+}
+
+// Test to ensure that pages that require certain privileges are grouped
+// in processes with similar pages.
+TEST_F(SiteInstanceTest, ProcessSharingByType) {
+ MockRenderProcessHostFactory rph_factory;
+ SiteInstanceImpl::set_render_process_host_factory(&rph_factory);
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+
+ // Make a bunch of mock renderers so that we hit the limit.
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ ScopedVector<MockRenderProcessHost> hosts;
+ for (size_t i = 0; i < kMaxRendererProcessCount; ++i)
+ hosts.push_back(new MockRenderProcessHost(browser_context.get()));
+
+ // Create some extension instances and make sure they share a process.
+ scoped_refptr<SiteInstanceImpl> extension1_instance(
+ CreateSiteInstance(browser_context.get(),
+ GURL(kPrivilegedScheme + std::string("://foo/bar"))));
+ set_privileged_process_id(extension1_instance->GetProcess()->GetID());
+
+ scoped_refptr<SiteInstanceImpl> extension2_instance(
+ CreateSiteInstance(browser_context.get(),
+ GURL(kPrivilegedScheme + std::string("://baz/bar"))));
+
+ scoped_ptr<RenderProcessHost> extension_host(
+ extension1_instance->GetProcess());
+ EXPECT_EQ(extension1_instance->GetProcess(),
+ extension2_instance->GetProcess());
+
+ // Create some WebUI instances and make sure they share a process.
+ scoped_refptr<SiteInstanceImpl> webui1_instance(CreateSiteInstance(
+ browser_context.get(),
+ GURL(chrome::kChromeUIScheme + std::string("://newtab"))));
+ policy->GrantWebUIBindings(webui1_instance->GetProcess()->GetID());
+
+ scoped_refptr<SiteInstanceImpl> webui2_instance(CreateSiteInstance(
+ browser_context.get(),
+ GURL(chrome::kChromeUIScheme + std::string("://history"))));
+
+ scoped_ptr<RenderProcessHost> dom_host(webui1_instance->GetProcess());
+ EXPECT_EQ(webui1_instance->GetProcess(), webui2_instance->GetProcess());
+
+ // Make sure none of differing privilege processes are mixed.
+ EXPECT_NE(extension1_instance->GetProcess(), webui1_instance->GetProcess());
+
+ for (size_t i = 0; i < kMaxRendererProcessCount; ++i) {
+ EXPECT_NE(extension1_instance->GetProcess(), hosts[i]);
+ EXPECT_NE(webui1_instance->GetProcess(), hosts[i]);
+ }
+
+ DrainMessageLoops();
+}
+
+// Test to ensure that HasWrongProcessForURL behaves properly for different
+// types of URLs.
+TEST_F(SiteInstanceTest, HasWrongProcessForURL) {
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ scoped_ptr<RenderProcessHost> host;
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+
+ EXPECT_FALSE(instance->HasSite());
+ EXPECT_TRUE(instance->GetSiteURL().is_empty());
+
+ instance->SetSite(GURL("http://evernote.com/"));
+ EXPECT_TRUE(instance->HasSite());
+
+ // Check prior to "assigning" a process to the instance, which is expected
+ // to return false due to not being attached to any process yet.
+ EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://google.com")));
+
+ // The call to GetProcess actually creates a new real process, which works
+ // fine, but might be a cause for problems in different contexts.
+ host.reset(instance->GetProcess());
+ EXPECT_TRUE(host.get() != NULL);
+ EXPECT_TRUE(instance->HasProcess());
+
+ EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://evernote.com")));
+ EXPECT_FALSE(instance->HasWrongProcessForURL(
+ GURL("javascript:alert(document.location.href);")));
+
+ EXPECT_TRUE(instance->HasWrongProcessForURL(GURL("chrome://settings")));
+
+ // Test that WebUI SiteInstances reject normal web URLs.
+ const GURL webui_url("chrome://settings");
+ scoped_refptr<SiteInstanceImpl> webui_instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+ webui_instance->SetSite(webui_url);
+ scoped_ptr<RenderProcessHost> webui_host(webui_instance->GetProcess());
+
+ // Simulate granting WebUI bindings for the process.
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
+ webui_host->GetID());
+
+ EXPECT_TRUE(webui_instance->HasProcess());
+ EXPECT_FALSE(webui_instance->HasWrongProcessForURL(webui_url));
+ EXPECT_TRUE(webui_instance->HasWrongProcessForURL(GURL("http://google.com")));
+
+ // WebUI uses process-per-site, so another instance will use the same process
+ // even if we haven't called GetProcess yet. Make sure HasWrongProcessForURL
+ // doesn't crash (http://crbug.com/137070).
+ scoped_refptr<SiteInstanceImpl> webui_instance2(
+ static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+ webui_instance2->SetSite(webui_url);
+ EXPECT_FALSE(webui_instance2->HasWrongProcessForURL(webui_url));
+ EXPECT_TRUE(
+ webui_instance2->HasWrongProcessForURL(GURL("http://google.com")));
+
+ DrainMessageLoops();
+}
+
+// Test to ensure that HasWrongProcessForURL behaves properly even when
+// --site-per-process is used (http://crbug.com/160671).
+TEST_F(SiteInstanceTest, HasWrongProcessForURLInSitePerProcess) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kSitePerProcess);
+
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ scoped_ptr<RenderProcessHost> host;
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+
+ instance->SetSite(GURL("http://evernote.com/"));
+ EXPECT_TRUE(instance->HasSite());
+
+ // Check prior to "assigning" a process to the instance, which is expected
+ // to return false due to not being attached to any process yet.
+ EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://google.com")));
+
+ // The call to GetProcess actually creates a new real process, which works
+ // fine, but might be a cause for problems in different contexts.
+ host.reset(instance->GetProcess());
+ EXPECT_TRUE(host.get() != NULL);
+ EXPECT_TRUE(instance->HasProcess());
+
+ EXPECT_FALSE(instance->HasWrongProcessForURL(GURL("http://evernote.com")));
+ EXPECT_FALSE(instance->HasWrongProcessForURL(
+ GURL("javascript:alert(document.location.href);")));
+
+ EXPECT_TRUE(instance->HasWrongProcessForURL(GURL("chrome://settings")));
+
+ DrainMessageLoops();
+}
+
+// Test that we do not reuse a process in process-per-site mode if it has the
+// wrong bindings for its URL. http://crbug.com/174059.
+TEST_F(SiteInstanceTest, ProcessPerSiteWithWrongBindings) {
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ scoped_ptr<RenderProcessHost> host;
+ scoped_ptr<RenderProcessHost> host2;
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+
+ EXPECT_FALSE(instance->HasSite());
+ EXPECT_TRUE(instance->GetSiteURL().is_empty());
+
+ // Simulate navigating to a WebUI URL in a process that does not have WebUI
+ // bindings. This already requires bypassing security checks.
+ const GURL webui_url("chrome://settings");
+ instance->SetSite(webui_url);
+ EXPECT_TRUE(instance->HasSite());
+
+ // The call to GetProcess actually creates a new real process.
+ host.reset(instance->GetProcess());
+ EXPECT_TRUE(host.get() != NULL);
+ EXPECT_TRUE(instance->HasProcess());
+
+ // Without bindings, this should look like the wrong process.
+ EXPECT_TRUE(instance->HasWrongProcessForURL(webui_url));
+
+ // WebUI uses process-per-site, so another instance would normally use the
+ // same process. Make sure it doesn't use the same process if the bindings
+ // are missing.
+ scoped_refptr<SiteInstanceImpl> instance2(
+ static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+ instance2->SetSite(webui_url);
+ host2.reset(instance2->GetProcess());
+ EXPECT_TRUE(host2.get() != NULL);
+ EXPECT_TRUE(instance2->HasProcess());
+ EXPECT_NE(host.get(), host2.get());
+
+ DrainMessageLoops();
+}
+
+// Test that we do not register processes with empty sites for process-per-site
+// mode.
+TEST_F(SiteInstanceTest, NoProcessPerSiteForEmptySite) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kProcessPerSite);
+ scoped_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
+ scoped_ptr<RenderProcessHost> host;
+ scoped_refptr<SiteInstanceImpl> instance(static_cast<SiteInstanceImpl*>(
+ SiteInstance::Create(browser_context.get())));
+
+ instance->SetSite(GURL());
+ EXPECT_TRUE(instance->HasSite());
+ EXPECT_TRUE(instance->GetSiteURL().is_empty());
+ host.reset(instance->GetProcess());
+
+ EXPECT_FALSE(RenderProcessHostImpl::GetProcessHostForSite(
+ browser_context.get(), GURL()));
+
+ DrainMessageLoops();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/site_per_process_browsertest.cc b/chromium/content/browser/site_per_process_browsertest.cc
new file mode 100644
index 00000000000..3485fc51fed
--- /dev/null
+++ b/chromium/content/browser/site_per_process_browsertest.cc
@@ -0,0 +1,402 @@
+// 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 "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+class SitePerProcessWebContentsObserver: public WebContentsObserver {
+ public:
+ explicit SitePerProcessWebContentsObserver(WebContents* web_contents)
+ : WebContentsObserver(web_contents),
+ navigation_succeeded_(true) {}
+ virtual ~SitePerProcessWebContentsObserver() {}
+
+ virtual void DidFailProvisionalLoad(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) OVERRIDE {
+ navigation_url_ = validated_url;
+ navigation_succeeded_ = false;
+ }
+
+ virtual void DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) OVERRIDE{
+ navigation_url_ = url;
+ navigation_succeeded_ = true;
+ }
+
+ const GURL& navigation_url() const {
+ return navigation_url_;
+ }
+
+ int navigation_succeeded() const { return navigation_succeeded_; }
+
+ private:
+ GURL navigation_url_;
+ bool navigation_succeeded_;
+
+ DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver);
+};
+
+class RedirectNotificationObserver : public NotificationObserver {
+ public:
+ // Register to listen for notifications of the given type from either a
+ // specific source, or from all sources if |source| is
+ // NotificationService::AllSources().
+ RedirectNotificationObserver(int notification_type,
+ const NotificationSource& source);
+ virtual ~RedirectNotificationObserver();
+
+ // Wait until the specified notification occurs. If the notification was
+ // emitted between the construction of this object and this call then it
+ // returns immediately.
+ void Wait();
+
+ // Returns NotificationService::AllSources() if we haven't observed a
+ // notification yet.
+ const NotificationSource& source() const {
+ return source_;
+ }
+
+ const NotificationDetails& details() const {
+ return details_;
+ }
+
+ // NotificationObserver:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ private:
+ bool seen_;
+ bool seen_twice_;
+ bool running_;
+ NotificationRegistrar registrar_;
+
+ NotificationSource source_;
+ NotificationDetails details_;
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver);
+};
+
+RedirectNotificationObserver::RedirectNotificationObserver(
+ int notification_type,
+ const NotificationSource& source)
+ : seen_(false),
+ running_(false),
+ source_(NotificationService::AllSources()) {
+ registrar_.Add(this, notification_type, source);
+}
+
+RedirectNotificationObserver::~RedirectNotificationObserver() {}
+
+void RedirectNotificationObserver::Wait() {
+ if (seen_ && seen_twice_)
+ return;
+
+ running_ = true;
+ message_loop_runner_ = new MessageLoopRunner;
+ message_loop_runner_->Run();
+ EXPECT_TRUE(seen_);
+}
+
+void RedirectNotificationObserver::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ source_ = source;
+ details_ = details;
+ seen_twice_ = seen_;
+ seen_ = true;
+ if (!running_)
+ return;
+
+ message_loop_runner_->Quit();
+ running_ = false;
+}
+
+class SitePerProcessBrowserTest : public ContentBrowserTest {
+ public:
+ SitePerProcessBrowserTest() {}
+
+ bool NavigateIframeToURL(Shell* window,
+ const GURL& url,
+ std::string iframe_id) {
+ std::string script = base::StringPrintf(
+ "var iframes = document.getElementById('%s');iframes.src='%s';",
+ iframe_id.c_str(), url.spec().c_str());
+ WindowedNotificationObserver load_observer(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+ bool result = ExecuteScript(window->web_contents(), script);
+ load_observer.Wait();
+ return result;
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(switches::kSitePerProcess);
+ }
+};
+
+// TODO(nasko): Disable this test until out-of-process iframes is ready and the
+// security checks are back in place.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframe) {
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load same-site page into Iframe.
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site page into Iframe.
+ GURL https_url(https_server.GetURL("files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), https_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+}
+
+// TODO(nasko): Disable this test until out-of-process iframes is ready and the
+// security checks are back in place.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+ DISABLED_CrossSiteIframeRedirectOnce) {
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ GURL https_url(https_server.GetURL("files/title1.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load cross-site client-redirect page into Iframe.
+ // Should be blocked.
+ GURL client_redirect_https_url(https_server.GetURL(
+ "client-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_https_url, "test"));
+ // DidFailProvisionalLoad when navigating to client_redirect_https_url.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site server-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL server_redirect_http_url(https_server.GetURL(
+ "server-redirect?" + http_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site server-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL server_redirect_http_url(https_server.GetURL(
+ "server-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ // DidFailProvisionalLoad when navigating to https_url.
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site server-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site client-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + https_url.spec()));
+
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_http_url, "test"));
+
+ // Same-site Client-Redirect Page should be loaded successfully.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+
+ // Redirecting to Cross-site Page should be blocked.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site server-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site client-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + http_url.spec()));
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_http_url, "test"));
+
+ // Same-site Client-Redirect Page should be loaded successfully.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+
+ // Redirecting to Same-site Page should be loaded successfully.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+}
+
+// TODO(nasko): Disable this test until out-of-process iframes is ready and the
+// security checks are back in place.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+ DISABLED_CrossSiteIframeRedirectTwice) {
+ ASSERT_TRUE(test_server()->Start());
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ GURL https_url(https_server.GetURL("files/title1.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load client-redirect page pointing to a cross-site client-redirect page,
+ // which eventually redirects back to same-site page.
+ GURL client_redirect_https_url(https_server.GetURL(
+ "client-redirect?" + http_url.spec()));
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + client_redirect_https_url.spec()));
+
+ // We should wait until second client redirect get cancelled.
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to client_redirect_https_url.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site server-redirect page,
+ // which eventually redirect back to same-site page.
+ GURL server_redirect_https_url(https_server.GetURL(
+ "server-redirect?" + http_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + server_redirect_https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site server-redirect page,
+ // which eventually redirects back to cross-site page.
+ GURL server_redirect_https_url(https_server.GetURL(
+ "server-redirect?" + https_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + server_redirect_https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to https_url.
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site client-redirect page,
+ // which eventually redirects back to same-site page.
+ GURL client_redirect_http_url(https_server.GetURL(
+ "client-redirect?" + http_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + client_redirect_http_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to client_redirect_http_url.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+}
+
+}
diff --git a/chromium/content/browser/speech/DEPS b/chromium/content/browser/speech/DEPS
new file mode 100644
index 00000000000..b0e678d15b5
--- /dev/null
+++ b/chromium/content/browser/speech/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+google_apis", # Exception to general rule, see content/DEPS for details.
+ "+media/audio", # For audio input.
+]
diff --git a/chromium/content/browser/speech/OWNERS b/chromium/content/browser/speech/OWNERS
new file mode 100644
index 00000000000..b38caa6f1ff
--- /dev/null
+++ b/chromium/content/browser/speech/OWNERS
@@ -0,0 +1,3 @@
+hans@chromium.org
+tommi@chromium.org
+xians@chromium.org
diff --git a/chromium/content/browser/speech/audio_buffer.cc b/chromium/content/browser/speech/audio_buffer.cc
new file mode 100644
index 00000000000..3e7d2a4a68e
--- /dev/null
+++ b/chromium/content/browser/speech/audio_buffer.cc
@@ -0,0 +1,91 @@
+// 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 "content/browser/speech/audio_buffer.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace content {
+
+AudioChunk::AudioChunk(int bytes_per_sample)
+ : bytes_per_sample_(bytes_per_sample) {
+}
+
+AudioChunk::AudioChunk(const uint8* data, size_t length, int bytes_per_sample)
+ : data_string_(reinterpret_cast<const char*>(data), length),
+ bytes_per_sample_(bytes_per_sample) {
+ DCHECK_EQ(length % bytes_per_sample, 0U);
+}
+
+bool AudioChunk::IsEmpty() const {
+ return data_string_.empty();
+}
+
+size_t AudioChunk::NumSamples() const {
+ return data_string_.size() / bytes_per_sample_;
+}
+
+const std::string& AudioChunk::AsString() const {
+ return data_string_;
+}
+
+int16 AudioChunk::GetSample16(size_t index) const {
+ DCHECK(index < (data_string_.size() / sizeof(int16)));
+ return SamplesData16()[index];
+}
+
+const int16* AudioChunk::SamplesData16() const {
+ return reinterpret_cast<const int16*>(data_string_.data());
+}
+
+
+AudioBuffer::AudioBuffer(int bytes_per_sample)
+ : bytes_per_sample_(bytes_per_sample) {
+ DCHECK(bytes_per_sample == 1 ||
+ bytes_per_sample == 2 ||
+ bytes_per_sample == 4);
+}
+
+AudioBuffer::~AudioBuffer() {
+ Clear();
+}
+
+void AudioBuffer::Enqueue(const uint8* data, size_t length) {
+ chunks_.push_back(new AudioChunk(data, length, bytes_per_sample_));
+}
+
+scoped_refptr<AudioChunk> AudioBuffer::DequeueSingleChunk() {
+ DCHECK(!chunks_.empty());
+ scoped_refptr<AudioChunk> chunk(chunks_.front());
+ chunks_.pop_front();
+ return chunk;
+}
+
+scoped_refptr<AudioChunk> AudioBuffer::DequeueAll() {
+ scoped_refptr<AudioChunk> chunk(new AudioChunk(bytes_per_sample_));
+ size_t resulting_length = 0;
+ ChunksContainer::const_iterator it;
+ // In order to improve performance, calulate in advance the total length
+ // and then copy the chunks.
+ for (it = chunks_.begin(); it != chunks_.end(); ++it) {
+ resulting_length += (*it)->data_string_.length();
+ }
+ chunk->data_string_.reserve(resulting_length);
+ for (it = chunks_.begin(); it != chunks_.end(); ++it) {
+ chunk->data_string_.append((*it)->data_string_);
+ }
+ Clear();
+ return chunk;
+}
+
+void AudioBuffer::Clear() {
+ chunks_.clear();
+}
+
+bool AudioBuffer::IsEmpty() const {
+ return chunks_.empty();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/audio_buffer.h b/chromium/content/browser/speech/audio_buffer.h
new file mode 100644
index 00000000000..783ae6679f3
--- /dev/null
+++ b/chromium/content/browser/speech/audio_buffer.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_SPEECH_AUDIO_BUFFER_H_
+#define CONTENT_BROWSER_SPEECH_AUDIO_BUFFER_H_
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Models a chunk derived from an AudioBuffer.
+class CONTENT_EXPORT AudioChunk :
+ public base::RefCountedThreadSafe<AudioChunk> {
+ public:
+ explicit AudioChunk(int bytes_per_sample);
+ AudioChunk(const uint8* data, size_t length, int bytes_per_sample);
+
+ bool IsEmpty() const;
+ int bytes_per_sample() const { return bytes_per_sample_; }
+ size_t NumSamples() const;
+ const std::string& AsString() const;
+ int16 GetSample16(size_t index) const;
+ const int16* SamplesData16() const;
+ friend class AudioBuffer;
+
+ private:
+ ~AudioChunk() {}
+ friend class base::RefCountedThreadSafe<AudioChunk>;
+
+ std::string data_string_;
+ int bytes_per_sample_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioChunk);
+};
+
+// Models an audio buffer. The current implementation relies on on-demand
+// allocations of AudioChunk(s) (which uses a string as storage).
+class AudioBuffer {
+ public:
+ explicit AudioBuffer(int bytes_per_sample);
+ ~AudioBuffer();
+
+ // Enqueues a copy of |length| bytes of |data| buffer.
+ void Enqueue(const uint8* data, size_t length);
+
+ // Dequeues, in FIFO order, a single chunk respecting the length of the
+ // corresponding Enqueue call (in a nutshell: multiple Enqueue calls followed
+ // by Dequeue calls will return the individual chunks without merging them).
+ scoped_refptr<AudioChunk> DequeueSingleChunk();
+
+ // Dequeues all previously enqueued chunks, merging them in a single chunk.
+ scoped_refptr<AudioChunk> DequeueAll();
+
+ // Removes and frees all the enqueued chunks.
+ void Clear();
+
+ // Checks whether the buffer is empty.
+ bool IsEmpty() const;
+
+ private:
+ typedef std::deque<scoped_refptr<AudioChunk> > ChunksContainer;
+ ChunksContainer chunks_;
+ int bytes_per_sample_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioBuffer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_AUDIO_BUFFER_H_
diff --git a/chromium/content/browser/speech/audio_encoder.cc b/chromium/content/browser/speech/audio_encoder.cc
new file mode 100644
index 00000000000..db8e2015c1d
--- /dev/null
+++ b/chromium/content/browser/speech/audio_encoder.cc
@@ -0,0 +1,194 @@
+// 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 "content/browser/speech/audio_encoder.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "third_party/flac/include/FLAC/stream_encoder.h"
+#include "third_party/speex/include/speex/speex.h"
+
+namespace content {
+namespace {
+
+//-------------------------------- FLACEncoder ---------------------------------
+
+const char* const kContentTypeFLAC = "audio/x-flac; rate=";
+const int kFLACCompressionLevel = 0; // 0 for speed
+
+class FLACEncoder : public AudioEncoder {
+ public:
+ FLACEncoder(int sampling_rate, int bits_per_sample);
+ virtual ~FLACEncoder();
+ virtual void Encode(const AudioChunk& raw_audio) OVERRIDE;
+ virtual void Flush() OVERRIDE;
+
+ private:
+ static FLAC__StreamEncoderWriteStatus WriteCallback(
+ const FLAC__StreamEncoder* encoder,
+ const FLAC__byte buffer[],
+ size_t bytes,
+ unsigned samples,
+ unsigned current_frame,
+ void* client_data);
+
+ FLAC__StreamEncoder* encoder_;
+ bool is_encoder_initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(FLACEncoder);
+};
+
+FLAC__StreamEncoderWriteStatus FLACEncoder::WriteCallback(
+ const FLAC__StreamEncoder* encoder,
+ const FLAC__byte buffer[],
+ size_t bytes,
+ unsigned samples,
+ unsigned current_frame,
+ void* client_data) {
+ FLACEncoder* me = static_cast<FLACEncoder*>(client_data);
+ DCHECK(me->encoder_ == encoder);
+ me->encoded_audio_buffer_.Enqueue(buffer, bytes);
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+FLACEncoder::FLACEncoder(int sampling_rate, int bits_per_sample)
+ : AudioEncoder(std::string(kContentTypeFLAC) +
+ base::IntToString(sampling_rate),
+ bits_per_sample),
+ encoder_(FLAC__stream_encoder_new()),
+ is_encoder_initialized_(false) {
+ FLAC__stream_encoder_set_channels(encoder_, 1);
+ FLAC__stream_encoder_set_bits_per_sample(encoder_, bits_per_sample);
+ FLAC__stream_encoder_set_sample_rate(encoder_, sampling_rate);
+ FLAC__stream_encoder_set_compression_level(encoder_, kFLACCompressionLevel);
+
+ // Initializing the encoder will cause sync bytes to be written to
+ // its output stream, so we wait until the first call to this method
+ // before doing so.
+}
+
+FLACEncoder::~FLACEncoder() {
+ FLAC__stream_encoder_delete(encoder_);
+}
+
+void FLACEncoder::Encode(const AudioChunk& raw_audio) {
+ DCHECK_EQ(raw_audio.bytes_per_sample(), 2);
+ if (!is_encoder_initialized_) {
+ const FLAC__StreamEncoderInitStatus encoder_status =
+ FLAC__stream_encoder_init_stream(encoder_, WriteCallback, NULL, NULL,
+ NULL, this);
+ DCHECK_EQ(encoder_status, FLAC__STREAM_ENCODER_INIT_STATUS_OK);
+ is_encoder_initialized_ = true;
+ }
+
+ // FLAC encoder wants samples as int32s.
+ const int num_samples = raw_audio.NumSamples();
+ scoped_ptr<FLAC__int32[]> flac_samples(new FLAC__int32[num_samples]);
+ FLAC__int32* flac_samples_ptr = flac_samples.get();
+ for (int i = 0; i < num_samples; ++i)
+ flac_samples_ptr[i] = static_cast<FLAC__int32>(raw_audio.GetSample16(i));
+
+ FLAC__stream_encoder_process(encoder_, &flac_samples_ptr, num_samples);
+}
+
+void FLACEncoder::Flush() {
+ FLAC__stream_encoder_finish(encoder_);
+}
+
+//-------------------------------- SpeexEncoder --------------------------------
+
+const char* const kContentTypeSpeex = "audio/x-speex-with-header-byte; rate=";
+const int kSpeexEncodingQuality = 8;
+const int kMaxSpeexFrameLength = 110; // (44kbps rate sampled at 32kHz).
+
+// Since the frame length gets written out as a byte in the encoded packet,
+// make sure it is within the byte range.
+COMPILE_ASSERT(kMaxSpeexFrameLength <= 0xFF, invalidLength);
+
+class SpeexEncoder : public AudioEncoder {
+ public:
+ explicit SpeexEncoder(int sampling_rate, int bits_per_sample);
+ virtual ~SpeexEncoder();
+ virtual void Encode(const AudioChunk& raw_audio) OVERRIDE;
+ virtual void Flush() OVERRIDE {}
+
+ private:
+ void* encoder_state_;
+ SpeexBits bits_;
+ int samples_per_frame_;
+ char encoded_frame_data_[kMaxSpeexFrameLength + 1]; // +1 for the frame size.
+ DISALLOW_COPY_AND_ASSIGN(SpeexEncoder);
+};
+
+SpeexEncoder::SpeexEncoder(int sampling_rate, int bits_per_sample)
+ : AudioEncoder(std::string(kContentTypeSpeex) +
+ base::IntToString(sampling_rate),
+ bits_per_sample) {
+ // speex_bits_init() does not initialize all of the |bits_| struct.
+ memset(&bits_, 0, sizeof(bits_));
+ speex_bits_init(&bits_);
+ encoder_state_ = speex_encoder_init(&speex_wb_mode);
+ DCHECK(encoder_state_);
+ speex_encoder_ctl(encoder_state_, SPEEX_GET_FRAME_SIZE, &samples_per_frame_);
+ DCHECK(samples_per_frame_ > 0);
+ int quality = kSpeexEncodingQuality;
+ speex_encoder_ctl(encoder_state_, SPEEX_SET_QUALITY, &quality);
+ int vbr = 1;
+ speex_encoder_ctl(encoder_state_, SPEEX_SET_VBR, &vbr);
+ memset(encoded_frame_data_, 0, sizeof(encoded_frame_data_));
+}
+
+SpeexEncoder::~SpeexEncoder() {
+ speex_bits_destroy(&bits_);
+ speex_encoder_destroy(encoder_state_);
+}
+
+void SpeexEncoder::Encode(const AudioChunk& raw_audio) {
+ spx_int16_t* src_buffer =
+ const_cast<spx_int16_t*>(raw_audio.SamplesData16());
+ int num_samples = raw_audio.NumSamples();
+ // Drop incomplete frames, typically those which come in when recording stops.
+ num_samples -= (num_samples % samples_per_frame_);
+ for (int i = 0; i < num_samples; i += samples_per_frame_) {
+ speex_bits_reset(&bits_);
+ speex_encode_int(encoder_state_, src_buffer + i, &bits_);
+
+ // Encode the frame and place the size of the frame as the first byte. This
+ // is the packet format for MIME type x-speex-with-header-byte.
+ int frame_length = speex_bits_write(&bits_, encoded_frame_data_ + 1,
+ kMaxSpeexFrameLength);
+ encoded_frame_data_[0] = static_cast<char>(frame_length);
+ encoded_audio_buffer_.Enqueue(
+ reinterpret_cast<uint8*>(&encoded_frame_data_[0]), frame_length + 1);
+ }
+}
+
+} // namespace
+
+AudioEncoder* AudioEncoder::Create(Codec codec,
+ int sampling_rate,
+ int bits_per_sample) {
+ if (codec == CODEC_FLAC)
+ return new FLACEncoder(sampling_rate, bits_per_sample);
+ return new SpeexEncoder(sampling_rate, bits_per_sample);
+}
+
+AudioEncoder::AudioEncoder(const std::string& mime_type, int bits_per_sample)
+ : encoded_audio_buffer_(1), /* Byte granularity of encoded samples. */
+ mime_type_(mime_type),
+ bits_per_sample_(bits_per_sample) {
+}
+
+AudioEncoder::~AudioEncoder() {
+}
+
+scoped_refptr<AudioChunk> AudioEncoder::GetEncodedDataAndClear() {
+ return encoded_audio_buffer_.DequeueAll();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/audio_encoder.h b/chromium/content/browser/speech/audio_encoder.h
new file mode 100644
index 00000000000..581eafd2af5
--- /dev/null
+++ b/chromium/content/browser/speech/audio_encoder.h
@@ -0,0 +1,60 @@
+// 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 CONTENT_BROWSER_SPEECH_AUDIO_ENCODER_H_
+#define CONTENT_BROWSER_SPEECH_AUDIO_ENCODER_H_
+
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/speech/audio_buffer.h"
+
+namespace content{
+class AudioChunk;
+
+// Provides a simple interface to encode raw audio using the various speech
+// codecs.
+class AudioEncoder {
+ public:
+ enum Codec {
+ CODEC_FLAC,
+ CODEC_SPEEX,
+ };
+
+ static AudioEncoder* Create(Codec codec,
+ int sampling_rate,
+ int bits_per_sample);
+
+ virtual ~AudioEncoder();
+
+ // Encodes |raw audio| to the internal buffer. Use
+ // |GetEncodedDataAndClear| to read the result after this call or when
+ // audio capture completes.
+ virtual void Encode(const AudioChunk& raw_audio) = 0;
+
+ // Finish encoding and flush any pending encoded bits out.
+ virtual void Flush() = 0;
+
+ // Merges, retrieves and clears all the accumulated encoded audio chunks.
+ scoped_refptr<AudioChunk> GetEncodedDataAndClear();
+
+ const std::string& mime_type() { return mime_type_; }
+ int bits_per_sample() { return bits_per_sample_; }
+
+ protected:
+ AudioEncoder(const std::string& mime_type, int bits_per_sample);
+ AudioBuffer encoded_audio_buffer_;
+
+ private:
+ std::string mime_type_;
+ int bits_per_sample_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_AUDIO_ENCODER_H_
diff --git a/chromium/content/browser/speech/chunked_byte_buffer.cc b/chromium/content/browser/speech/chunked_byte_buffer.cc
new file mode 100644
index 00000000000..ae8a6ce8245
--- /dev/null
+++ b/chromium/content/browser/speech/chunked_byte_buffer.cc
@@ -0,0 +1,136 @@
+// 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 "content/browser/speech/chunked_byte_buffer.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+namespace {
+
+static const size_t kHeaderLength = sizeof(uint32);
+
+COMPILE_ASSERT(sizeof(size_t) >= kHeaderLength,
+ ChunkedByteBufferNotSupportedOnThisArchitecture);
+
+uint32 ReadBigEndian32(const uint8* buffer) {
+ return (static_cast<uint32>(buffer[3])) |
+ (static_cast<uint32>(buffer[2]) << 8) |
+ (static_cast<uint32>(buffer[1]) << 16) |
+ (static_cast<uint32>(buffer[0]) << 24);
+}
+
+} // namespace
+
+namespace content {
+
+ChunkedByteBuffer::ChunkedByteBuffer()
+ : partial_chunk_(new Chunk()),
+ total_bytes_stored_(0) {
+}
+
+ChunkedByteBuffer::~ChunkedByteBuffer() {
+ Clear();
+}
+
+void ChunkedByteBuffer::Append(const uint8* start, size_t length) {
+ size_t remaining_bytes = length;
+ const uint8* next_data = start;
+
+ while (remaining_bytes > 0) {
+ DCHECK(partial_chunk_ != NULL);
+ size_t insert_length = 0;
+ bool header_completed = false;
+ bool content_completed = false;
+ std::vector<uint8>* insert_target;
+
+ if (partial_chunk_->header.size() < kHeaderLength) {
+ const size_t bytes_to_complete_header =
+ kHeaderLength - partial_chunk_->header.size();
+ insert_length = std::min(bytes_to_complete_header, remaining_bytes);
+ insert_target = &partial_chunk_->header;
+ header_completed = (remaining_bytes >= bytes_to_complete_header);
+ } else {
+ DCHECK_LT(partial_chunk_->content->size(),
+ partial_chunk_->ExpectedContentLength());
+ const size_t bytes_to_complete_chunk =
+ partial_chunk_->ExpectedContentLength() -
+ partial_chunk_->content->size();
+ insert_length = std::min(bytes_to_complete_chunk, remaining_bytes);
+ insert_target = partial_chunk_->content.get();
+ content_completed = (remaining_bytes >= bytes_to_complete_chunk);
+ }
+
+ DCHECK_GT(insert_length, 0U);
+ DCHECK_LE(insert_length, remaining_bytes);
+ DCHECK_LE(next_data + insert_length, start + length);
+ insert_target->insert(insert_target->end(),
+ next_data,
+ next_data + insert_length);
+ next_data += insert_length;
+ remaining_bytes -= insert_length;
+
+ if (header_completed) {
+ DCHECK_EQ(partial_chunk_->header.size(), kHeaderLength);
+ if (partial_chunk_->ExpectedContentLength() == 0) {
+ // Handle zero-byte chunks.
+ chunks_.push_back(partial_chunk_.release());
+ partial_chunk_.reset(new Chunk());
+ } else {
+ partial_chunk_->content->reserve(
+ partial_chunk_->ExpectedContentLength());
+ }
+ } else if (content_completed) {
+ DCHECK_EQ(partial_chunk_->content->size(),
+ partial_chunk_->ExpectedContentLength());
+ chunks_.push_back(partial_chunk_.release());
+ partial_chunk_.reset(new Chunk());
+ }
+ }
+ DCHECK_EQ(next_data, start + length);
+ total_bytes_stored_ += length;
+}
+
+void ChunkedByteBuffer::Append(const std::string& string) {
+ Append(reinterpret_cast<const uint8*>(string.data()), string.size());
+}
+
+bool ChunkedByteBuffer::HasChunks() const {
+ return !chunks_.empty();
+}
+
+scoped_ptr< std::vector<uint8> > ChunkedByteBuffer::PopChunk() {
+ if (chunks_.empty())
+ return scoped_ptr< std::vector<uint8> >();
+ scoped_ptr<Chunk> chunk(*chunks_.begin());
+ chunks_.weak_erase(chunks_.begin());
+ DCHECK_EQ(chunk->header.size(), kHeaderLength);
+ DCHECK_EQ(chunk->content->size(), chunk->ExpectedContentLength());
+ total_bytes_stored_ -= chunk->content->size();
+ total_bytes_stored_ -= kHeaderLength;
+ return chunk->content.Pass();
+}
+
+void ChunkedByteBuffer::Clear() {
+ chunks_.clear();
+ partial_chunk_.reset(new Chunk());
+ total_bytes_stored_ = 0;
+}
+
+ChunkedByteBuffer::Chunk::Chunk()
+ : content(new std::vector<uint8>()) {
+}
+
+ChunkedByteBuffer::Chunk::~Chunk() {
+}
+
+size_t ChunkedByteBuffer::Chunk::ExpectedContentLength() const {
+ DCHECK_EQ(header.size(), kHeaderLength);
+ return static_cast<size_t>(ReadBigEndian32(&header[0]));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/chunked_byte_buffer.h b/chromium/content/browser/speech/chunked_byte_buffer.h
new file mode 100644
index 00000000000..8b9d2378111
--- /dev/null
+++ b/chromium/content/browser/speech/chunked_byte_buffer.h
@@ -0,0 +1,75 @@
+// 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 CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
+#define CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Models a chunk-oriented byte buffer. The term chunk is herein defined as an
+// arbitrary sequence of bytes that is preceeded by N header bytes, indicating
+// its size. Data may be appended to the buffer with no particular respect of
+// chunks boundaries. However, chunks can be extracted (FIFO) only when their
+// content (according to their header) is fully available in the buffer.
+// The current implementation support only 4 byte Big Endian headers.
+// Empty chunks (i.e. the sequence 00 00 00 00) are NOT allowed.
+//
+// E.g. 00 00 00 04 xx xx xx xx 00 00 00 02 yy yy 00 00 00 04 zz zz zz zz
+// [----- CHUNK 1 -------] [--- CHUNK 2 ---] [------ CHUNK 3 ------]
+class CONTENT_EXPORT ChunkedByteBuffer {
+ public:
+ ChunkedByteBuffer();
+ ~ChunkedByteBuffer();
+
+ // Appends |length| bytes starting from |start| to the buffer.
+ void Append(const uint8* start, size_t length);
+
+ // Appends bytes contained in the |string| to the buffer.
+ void Append(const std::string& string);
+
+ // Checks whether one or more complete chunks are available in the buffer.
+ bool HasChunks() const;
+
+ // If enough data is available, reads and removes the first complete chunk
+ // from the buffer. Returns a NULL pointer if no complete chunk is available.
+ scoped_ptr< std::vector<uint8> > PopChunk();
+
+ // Clears all the content of the buffer.
+ void Clear();
+
+ // Returns the number of raw bytes (including headers) present.
+ size_t GetTotalLength() const { return total_bytes_stored_; }
+
+ private:
+ struct Chunk {
+ Chunk();
+ ~Chunk();
+
+ std::vector<uint8> header;
+ scoped_ptr< std::vector<uint8> > content;
+ size_t ExpectedContentLength() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Chunk);
+ };
+
+ ScopedVector<Chunk> chunks_;
+ scoped_ptr<Chunk> partial_chunk_;
+ size_t total_bytes_stored_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChunkedByteBuffer);
+};
+
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
diff --git a/chromium/content/browser/speech/chunked_byte_buffer_unittest.cc b/chromium/content/browser/speech/chunked_byte_buffer_unittest.cc
new file mode 100644
index 00000000000..57fdf4ca1bf
--- /dev/null
+++ b/chromium/content/browser/speech/chunked_byte_buffer_unittest.cc
@@ -0,0 +1,76 @@
+// 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 <string>
+#include <vector>
+
+#include "content/browser/speech/chunked_byte_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+typedef std::vector<uint8> ByteVector;
+
+TEST(ChunkedByteBufferTest, BasicTest) {
+ ChunkedByteBuffer buffer;
+
+ const uint8 kChunks[] = {
+ 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, // Chunk 1: 4 bytes
+ 0x00, 0x00, 0x00, 0x02, 0x05, 0x06, // Chunk 2: 2 bytes
+ 0x00, 0x00, 0x00, 0x01, 0x07 // Chunk 3: 1 bytes
+ };
+
+ EXPECT_EQ(0U, buffer.GetTotalLength());
+ EXPECT_FALSE(buffer.HasChunks());
+
+ // Append partially chunk 1.
+ buffer.Append(kChunks, 2);
+ EXPECT_EQ(2U, buffer.GetTotalLength());
+ EXPECT_FALSE(buffer.HasChunks());
+
+ // Complete chunk 1.
+ buffer.Append(kChunks + 2, 6);
+ EXPECT_EQ(8U, buffer.GetTotalLength());
+ EXPECT_TRUE(buffer.HasChunks());
+
+ // Append fully chunk 2.
+ buffer.Append(kChunks + 8, 6);
+ EXPECT_EQ(14U, buffer.GetTotalLength());
+ EXPECT_TRUE(buffer.HasChunks());
+
+ // Remove and check chunk 1.
+ scoped_ptr<ByteVector> chunk;
+ chunk = buffer.PopChunk();
+ EXPECT_TRUE(chunk != NULL);
+ EXPECT_EQ(4U, chunk->size());
+ EXPECT_EQ(0, std::char_traits<uint8>::compare(kChunks + 4,
+ &(*chunk)[0],
+ chunk->size()));
+ EXPECT_EQ(6U, buffer.GetTotalLength());
+ EXPECT_TRUE(buffer.HasChunks());
+
+ // Read and check chunk 2.
+ chunk = buffer.PopChunk();
+ EXPECT_TRUE(chunk != NULL);
+ EXPECT_EQ(2U, chunk->size());
+ EXPECT_EQ(0, std::char_traits<uint8>::compare(kChunks + 12,
+ &(*chunk)[0],
+ chunk->size()));
+ EXPECT_EQ(0U, buffer.GetTotalLength());
+ EXPECT_FALSE(buffer.HasChunks());
+
+ // Append fully chunk 3.
+ buffer.Append(kChunks + 14, 5);
+ EXPECT_EQ(5U, buffer.GetTotalLength());
+
+ // Remove and check chunk 3.
+ chunk = buffer.PopChunk();
+ EXPECT_TRUE(chunk != NULL);
+ EXPECT_EQ(1U, chunk->size());
+ EXPECT_EQ((*chunk)[0], kChunks[18]);
+ EXPECT_EQ(0U, buffer.GetTotalLength());
+ EXPECT_FALSE(buffer.HasChunks());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/endpointer/endpointer.cc b/chromium/content/browser/speech/endpointer/endpointer.cc
new file mode 100644
index 00000000000..5ed48ca323f
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/endpointer.cc
@@ -0,0 +1,169 @@
+// 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 "content/browser/speech/endpointer/endpointer.h"
+
+#include "base/time/time.h"
+#include "content/browser/speech/audio_buffer.h"
+
+using base::Time;
+
+namespace {
+const int kFrameRate = 50; // 1 frame = 20ms of audio.
+}
+
+namespace content {
+
+Endpointer::Endpointer(int sample_rate)
+ : speech_input_possibly_complete_silence_length_us_(-1),
+ speech_input_complete_silence_length_us_(-1),
+ audio_frame_time_us_(0),
+ sample_rate_(sample_rate),
+ frame_size_(0) {
+ Reset();
+
+ frame_size_ = static_cast<int>(sample_rate / static_cast<float>(kFrameRate));
+
+ speech_input_minimum_length_us_ =
+ static_cast<int64>(1.7 * Time::kMicrosecondsPerSecond);
+ speech_input_complete_silence_length_us_ =
+ static_cast<int64>(0.5 * Time::kMicrosecondsPerSecond);
+ long_speech_input_complete_silence_length_us_ = -1;
+ long_speech_length_us_ = -1;
+ speech_input_possibly_complete_silence_length_us_ =
+ 1 * Time::kMicrosecondsPerSecond;
+
+ // Set the default configuration for Push To Talk mode.
+ EnergyEndpointerParams ep_config;
+ ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate));
+ ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate));
+ ep_config.set_endpoint_margin(0.2f);
+ ep_config.set_onset_window(0.15f);
+ ep_config.set_speech_on_window(0.4f);
+ ep_config.set_offset_window(0.15f);
+ ep_config.set_onset_detect_dur(0.09f);
+ ep_config.set_onset_confirm_dur(0.075f);
+ ep_config.set_on_maintain_dur(0.10f);
+ ep_config.set_offset_confirm_dur(0.12f);
+ ep_config.set_decision_threshold(1000.0f);
+ ep_config.set_min_decision_threshold(50.0f);
+ ep_config.set_fast_update_dur(0.2f);
+ ep_config.set_sample_rate(static_cast<float>(sample_rate));
+ ep_config.set_min_fundamental_frequency(57.143f);
+ ep_config.set_max_fundamental_frequency(400.0f);
+ ep_config.set_contamination_rejection_period(0.25f);
+ energy_endpointer_.Init(ep_config);
+}
+
+void Endpointer::Reset() {
+ old_ep_status_ = EP_PRE_SPEECH;
+ waiting_for_speech_possibly_complete_timeout_ = false;
+ waiting_for_speech_complete_timeout_ = false;
+ speech_previously_detected_ = false;
+ speech_input_complete_ = false;
+ audio_frame_time_us_ = 0; // Reset time for packets sent to endpointer.
+ speech_end_time_us_ = -1;
+ speech_start_time_us_ = -1;
+}
+
+void Endpointer::StartSession() {
+ Reset();
+ energy_endpointer_.StartSession();
+}
+
+void Endpointer::EndSession() {
+ energy_endpointer_.EndSession();
+}
+
+void Endpointer::SetEnvironmentEstimationMode() {
+ Reset();
+ energy_endpointer_.SetEnvironmentEstimationMode();
+}
+
+void Endpointer::SetUserInputMode() {
+ energy_endpointer_.SetUserInputMode();
+}
+
+EpStatus Endpointer::Status(int64 *time) {
+ return energy_endpointer_.Status(time);
+}
+
+EpStatus Endpointer::ProcessAudio(const AudioChunk& raw_audio, float* rms_out) {
+ const int16* audio_data = raw_audio.SamplesData16();
+ const int num_samples = raw_audio.NumSamples();
+ EpStatus ep_status = EP_PRE_SPEECH;
+
+ // Process the input data in blocks of frame_size_, dropping any incomplete
+ // frames at the end (which is ok since typically the caller will be recording
+ // audio in multiples of our frame size).
+ int sample_index = 0;
+ while (sample_index + frame_size_ <= num_samples) {
+ // Have the endpointer process the frame.
+ energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_,
+ audio_data + sample_index,
+ frame_size_,
+ rms_out);
+ sample_index += frame_size_;
+ audio_frame_time_us_ += (frame_size_ * Time::kMicrosecondsPerSecond) /
+ sample_rate_;
+
+ // Get the status of the endpointer.
+ int64 ep_time;
+ ep_status = energy_endpointer_.Status(&ep_time);
+
+ // Handle state changes.
+ if ((EP_SPEECH_PRESENT == ep_status) &&
+ (EP_POSSIBLE_ONSET == old_ep_status_)) {
+ speech_end_time_us_ = -1;
+ waiting_for_speech_possibly_complete_timeout_ = false;
+ waiting_for_speech_complete_timeout_ = false;
+ // Trigger SpeechInputDidStart event on first detection.
+ if (false == speech_previously_detected_) {
+ speech_previously_detected_ = true;
+ speech_start_time_us_ = ep_time;
+ }
+ }
+ if ((EP_PRE_SPEECH == ep_status) &&
+ (EP_POSSIBLE_OFFSET == old_ep_status_)) {
+ speech_end_time_us_ = ep_time;
+ waiting_for_speech_possibly_complete_timeout_ = true;
+ waiting_for_speech_complete_timeout_ = true;
+ }
+ if (ep_time > speech_input_minimum_length_us_) {
+ // Speech possibly complete timeout.
+ if ((waiting_for_speech_possibly_complete_timeout_) &&
+ (ep_time - speech_end_time_us_ >
+ speech_input_possibly_complete_silence_length_us_)) {
+ waiting_for_speech_possibly_complete_timeout_ = false;
+ }
+ if (waiting_for_speech_complete_timeout_) {
+ // The length of the silence timeout period can be held constant, or it
+ // can be changed after a fixed amount of time from the beginning of
+ // speech.
+ bool has_stepped_silence =
+ (long_speech_length_us_ > 0) &&
+ (long_speech_input_complete_silence_length_us_ > 0);
+ int64 requested_silence_length;
+ if (has_stepped_silence &&
+ (ep_time - speech_start_time_us_) > long_speech_length_us_) {
+ requested_silence_length =
+ long_speech_input_complete_silence_length_us_;
+ } else {
+ requested_silence_length =
+ speech_input_complete_silence_length_us_;
+ }
+
+ // Speech complete timeout.
+ if ((ep_time - speech_end_time_us_) > requested_silence_length) {
+ waiting_for_speech_complete_timeout_ = false;
+ speech_input_complete_ = true;
+ }
+ }
+ }
+ old_ep_status_ = ep_status;
+ }
+ return ep_status;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/endpointer/endpointer.h b/chromium/content/browser/speech/endpointer/endpointer.h
new file mode 100644
index 00000000000..6688ee63dc9
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/endpointer.h
@@ -0,0 +1,153 @@
+// 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 CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+
+#include "base/basictypes.h"
+#include "content/browser/speech/endpointer/energy_endpointer.h"
+#include "content/common/content_export.h"
+
+class EpStatus;
+
+namespace content {
+
+class AudioChunk;
+
+// A simple interface to the underlying energy-endpointer implementation, this
+// class lets callers provide audio as being recorded and let them poll to find
+// when the user has stopped speaking.
+//
+// There are two events that may trigger the end of speech:
+//
+// speechInputPossiblyComplete event:
+//
+// Signals that silence/noise has been detected for a *short* amount of
+// time after some speech has been detected. It can be used for low latency
+// UI feedback. To disable it, set it to a large amount.
+//
+// speechInputComplete event:
+//
+// This event is intended to signal end of input and to stop recording.
+// The amount of time to wait after speech is set by
+// speech_input_complete_silence_length_ and optionally two other
+// parameters (see below).
+// This time can be held constant, or can change as more speech is detected.
+// In the latter case, the time changes after a set amount of time from the
+// *beginning* of speech. This is motivated by the expectation that there
+// will be two distinct types of inputs: short search queries and longer
+// dictation style input.
+//
+// Three parameters are used to define the piecewise constant timeout function.
+// The timeout length is speech_input_complete_silence_length until
+// long_speech_length, when it changes to
+// long_speech_input_complete_silence_length.
+class CONTENT_EXPORT Endpointer {
+ public:
+ explicit Endpointer(int sample_rate);
+
+ // Start the endpointer. This should be called at the beginning of a session.
+ void StartSession();
+
+ // Stop the endpointer.
+ void EndSession();
+
+ // Start environment estimation. Audio will be used for environment estimation
+ // i.e. noise level estimation.
+ void SetEnvironmentEstimationMode();
+
+ // Start user input. This should be called when the user indicates start of
+ // input, e.g. by pressing a button.
+ void SetUserInputMode();
+
+ // Process a segment of audio, which may be more than one frame.
+ // The status of the last frame will be returned.
+ EpStatus ProcessAudio(const AudioChunk& raw_audio, float* rms_out);
+
+ // Get the status of the endpointer.
+ EpStatus Status(int64 *time_us);
+
+ // Returns true if the endpointer detected reasonable audio levels above
+ // background noise which could be user speech, false if not.
+ bool DidStartReceivingSpeech() const {
+ return speech_previously_detected_;
+ }
+
+ bool IsEstimatingEnvironment() const {
+ return energy_endpointer_.estimating_environment();
+ }
+
+ void set_speech_input_complete_silence_length(int64 time_us) {
+ speech_input_complete_silence_length_us_ = time_us;
+ }
+
+ void set_long_speech_input_complete_silence_length(int64 time_us) {
+ long_speech_input_complete_silence_length_us_ = time_us;
+ }
+
+ void set_speech_input_possibly_complete_silence_length(int64 time_us) {
+ speech_input_possibly_complete_silence_length_us_ = time_us;
+ }
+
+ void set_long_speech_length(int64 time_us) {
+ long_speech_length_us_ = time_us;
+ }
+
+ bool speech_input_complete() const {
+ return speech_input_complete_;
+ }
+
+ // RMS background noise level in dB.
+ float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); }
+
+ private:
+ // Reset internal states. Helper method common to initial input utterance
+ // and following input utternaces.
+ void Reset();
+
+ // Minimum allowable length of speech input.
+ int64 speech_input_minimum_length_us_;
+
+ // The speechInputPossiblyComplete event signals that silence/noise has been
+ // detected for a *short* amount of time after some speech has been detected.
+ // This proporty specifies the time period.
+ int64 speech_input_possibly_complete_silence_length_us_;
+
+ // The speechInputComplete event signals that silence/noise has been
+ // detected for a *long* amount of time after some speech has been detected.
+ // This property specifies the time period.
+ int64 speech_input_complete_silence_length_us_;
+
+ // Same as above, this specifies the required silence period after speech
+ // detection. This period is used instead of
+ // speech_input_complete_silence_length_ when the utterance is longer than
+ // long_speech_length_. This parameter is optional.
+ int64 long_speech_input_complete_silence_length_us_;
+
+ // The period of time after which the endpointer should consider
+ // long_speech_input_complete_silence_length_ as a valid silence period
+ // instead of speech_input_complete_silence_length_. This parameter is
+ // optional.
+ int64 long_speech_length_us_;
+
+ // First speech onset time, used in determination of speech complete timeout.
+ int64 speech_start_time_us_;
+
+ // Most recent end time, used in determination of speech complete timeout.
+ int64 speech_end_time_us_;
+
+ int64 audio_frame_time_us_;
+ EpStatus old_ep_status_;
+ bool waiting_for_speech_possibly_complete_timeout_;
+ bool waiting_for_speech_complete_timeout_;
+ bool speech_previously_detected_;
+ bool speech_input_complete_;
+ EnergyEndpointer energy_endpointer_;
+ int sample_rate_;
+ int32 frame_size_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
diff --git a/chromium/content/browser/speech/endpointer/endpointer_unittest.cc b/chromium/content/browser/speech/endpointer/endpointer_unittest.cc
new file mode 100644
index 00000000000..306a5eeca28
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -0,0 +1,152 @@
+// 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 "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/endpointer/endpointer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const int kFrameRate = 50; // 20 ms long frames for AMR encoding.
+const int kSampleRate = 8000; // 8 k samples per second for AMR encoding.
+
+// At 8 sample per second a 20 ms frame is 160 samples, which corrsponds
+// to the AMR codec.
+const int kFrameSize = kSampleRate / kFrameRate; // 160 samples.
+COMPILE_ASSERT(kFrameSize == 160, invalid_frame_size);
+}
+
+namespace content {
+
+class FrameProcessor {
+ public:
+ // Process a single frame of test audio samples.
+ virtual EpStatus ProcessFrame(int64 time, int16* samples, int frame_size) = 0;
+};
+
+void RunEndpointerEventsTest(FrameProcessor* processor) {
+ int16 samples[kFrameSize];
+
+ // We will create a white noise signal of 150 frames. The frames from 50 to
+ // 100 will have more power, and the endpointer should fire on those frames.
+ const int kNumFrames = 150;
+
+ // Create a random sequence of samples.
+ srand(1);
+ float gain = 0.0;
+ int64 time = 0;
+ for (int frame_count = 0; frame_count < kNumFrames; ++frame_count) {
+ // The frames from 50 to 100 will have more power, and the endpointer
+ // should detect those frames as speech.
+ if ((frame_count >= 50) && (frame_count < 100)) {
+ gain = 2000.0;
+ } else {
+ gain = 1.0;
+ }
+ // Create random samples.
+ for (int i = 0; i < kFrameSize; ++i) {
+ float randNum = static_cast<float>(rand() - (RAND_MAX / 2)) /
+ static_cast<float>(RAND_MAX);
+ samples[i] = static_cast<int16>(gain * randNum);
+ }
+
+ EpStatus ep_status = processor->ProcessFrame(time, samples, kFrameSize);
+ time += static_cast<int64>(kFrameSize * (1e6 / kSampleRate));
+
+ // Log the status.
+ if (20 == frame_count)
+ EXPECT_EQ(EP_PRE_SPEECH, ep_status);
+ if (70 == frame_count)
+ EXPECT_EQ(EP_SPEECH_PRESENT, ep_status);
+ if (120 == frame_count)
+ EXPECT_EQ(EP_PRE_SPEECH, ep_status);
+ }
+}
+
+// This test instantiates and initializes a stand alone endpointer module.
+// The test creates FrameData objects with random noise and send them
+// to the endointer module. The energy of the first 50 frames is low,
+// followed by 500 high energy frames, and another 50 low energy frames.
+// We test that the correct start and end frames were detected.
+class EnergyEndpointerFrameProcessor : public FrameProcessor {
+ public:
+ explicit EnergyEndpointerFrameProcessor(EnergyEndpointer* endpointer)
+ : endpointer_(endpointer) {}
+
+ virtual EpStatus ProcessFrame(int64 time,
+ int16* samples,
+ int frame_size) OVERRIDE {
+ endpointer_->ProcessAudioFrame(time, samples, kFrameSize, NULL);
+ int64 ep_time;
+ return endpointer_->Status(&ep_time);
+ }
+
+ private:
+ EnergyEndpointer* endpointer_;
+};
+
+TEST(EndpointerTest, TestEnergyEndpointerEvents) {
+ // Initialize endpointer and configure it. We specify the parameters
+ // here for a 20ms window, and a 20ms step size, which corrsponds to
+ // the narrow band AMR codec.
+ EnergyEndpointerParams ep_config;
+ ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate));
+ ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate));
+ ep_config.set_endpoint_margin(0.2f);
+ ep_config.set_onset_window(0.15f);
+ ep_config.set_speech_on_window(0.4f);
+ ep_config.set_offset_window(0.15f);
+ ep_config.set_onset_detect_dur(0.09f);
+ ep_config.set_onset_confirm_dur(0.075f);
+ ep_config.set_on_maintain_dur(0.10f);
+ ep_config.set_offset_confirm_dur(0.12f);
+ ep_config.set_decision_threshold(100.0f);
+ EnergyEndpointer endpointer;
+ endpointer.Init(ep_config);
+
+ endpointer.StartSession();
+
+ EnergyEndpointerFrameProcessor frame_processor(&endpointer);
+ RunEndpointerEventsTest(&frame_processor);
+
+ endpointer.EndSession();
+};
+
+// Test endpointer wrapper class.
+class EndpointerFrameProcessor : public FrameProcessor {
+ public:
+ explicit EndpointerFrameProcessor(Endpointer* endpointer)
+ : endpointer_(endpointer) {}
+
+ virtual EpStatus ProcessFrame(int64 time,
+ int16* samples,
+ int frame_size) OVERRIDE {
+ scoped_refptr<AudioChunk> frame(
+ new AudioChunk(reinterpret_cast<uint8*>(samples), kFrameSize * 2, 2));
+ endpointer_->ProcessAudio(*frame.get(), NULL);
+ int64 ep_time;
+ return endpointer_->Status(&ep_time);
+ }
+
+ private:
+ Endpointer* endpointer_;
+};
+
+TEST(EndpointerTest, TestEmbeddedEndpointerEvents) {
+ const int kSampleRate = 8000; // 8 k samples per second for AMR encoding.
+
+ Endpointer endpointer(kSampleRate);
+ const int64 kMillisecondsPerMicrosecond = 1000;
+ const int64 short_timeout = 300 * kMillisecondsPerMicrosecond;
+ endpointer.set_speech_input_possibly_complete_silence_length(short_timeout);
+ const int64 long_timeout = 500 * kMillisecondsPerMicrosecond;
+ endpointer.set_speech_input_complete_silence_length(long_timeout);
+ endpointer.StartSession();
+
+ EndpointerFrameProcessor frame_processor(&endpointer);
+ RunEndpointerEventsTest(&frame_processor);
+
+ endpointer.EndSession();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/endpointer/energy_endpointer.cc b/chromium/content/browser/speech/endpointer/energy_endpointer.cc
new file mode 100644
index 00000000000..d8d1274994d
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/energy_endpointer.cc
@@ -0,0 +1,376 @@
+// 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.
+//
+// To know more about the algorithm used and the original code which this is
+// based of, see
+// https://wiki.corp.google.com/twiki/bin/view/Main/ChromeGoogleCodeXRef
+
+#include "content/browser/speech/endpointer/energy_endpointer.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+
+namespace {
+
+// Returns the RMS (quadratic mean) of the input signal.
+float RMS(const int16* samples, int num_samples) {
+ int64 ssq_int64 = 0;
+ int64 sum_int64 = 0;
+ for (int i = 0; i < num_samples; ++i) {
+ sum_int64 += samples[i];
+ ssq_int64 += samples[i] * samples[i];
+ }
+ // now convert to floats.
+ double sum = static_cast<double>(sum_int64);
+ sum /= num_samples;
+ double ssq = static_cast<double>(ssq_int64);
+ return static_cast<float>(sqrt((ssq / num_samples) - (sum * sum)));
+}
+
+int64 Secs2Usecs(float seconds) {
+ return static_cast<int64>(0.5 + (1.0e6 * seconds));
+}
+
+float GetDecibel(float value) {
+ if (value > 1.0e-100)
+ return 20 * log10(value);
+ return -2000.0;
+}
+
+} // namespace
+
+namespace content {
+
+// Stores threshold-crossing histories for making decisions about the speech
+// state.
+class EnergyEndpointer::HistoryRing {
+ public:
+ HistoryRing() : insertion_index_(0) {}
+
+ // Resets the ring to |size| elements each with state |initial_state|
+ void SetRing(int size, bool initial_state);
+
+ // Inserts a new entry into the ring and drops the oldest entry.
+ void Insert(int64 time_us, bool decision);
+
+ // Returns the time in microseconds of the most recently added entry.
+ int64 EndTime() const;
+
+ // Returns the sum of all intervals during which 'decision' is true within
+ // the time in seconds specified by 'duration'. The returned interval is
+ // in seconds.
+ float RingSum(float duration_sec);
+
+ private:
+ struct DecisionPoint {
+ int64 time_us;
+ bool decision;
+ };
+
+ std::vector<DecisionPoint> decision_points_;
+ int insertion_index_; // Index at which the next item gets added/inserted.
+
+ DISALLOW_COPY_AND_ASSIGN(HistoryRing);
+};
+
+void EnergyEndpointer::HistoryRing::SetRing(int size, bool initial_state) {
+ insertion_index_ = 0;
+ decision_points_.clear();
+ DecisionPoint init = { -1, initial_state };
+ decision_points_.resize(size, init);
+}
+
+void EnergyEndpointer::HistoryRing::Insert(int64 time_us, bool decision) {
+ decision_points_[insertion_index_].time_us = time_us;
+ decision_points_[insertion_index_].decision = decision;
+ insertion_index_ = (insertion_index_ + 1) % decision_points_.size();
+}
+
+int64 EnergyEndpointer::HistoryRing::EndTime() const {
+ int ind = insertion_index_ - 1;
+ if (ind < 0)
+ ind = decision_points_.size() - 1;
+ return decision_points_[ind].time_us;
+}
+
+float EnergyEndpointer::HistoryRing::RingSum(float duration_sec) {
+ if (!decision_points_.size())
+ return 0.0;
+
+ int64 sum_us = 0;
+ int ind = insertion_index_ - 1;
+ if (ind < 0)
+ ind = decision_points_.size() - 1;
+ int64 end_us = decision_points_[ind].time_us;
+ bool is_on = decision_points_[ind].decision;
+ int64 start_us = end_us - static_cast<int64>(0.5 + (1.0e6 * duration_sec));
+ if (start_us < 0)
+ start_us = 0;
+ size_t n_summed = 1; // n points ==> (n-1) intervals
+ while ((decision_points_[ind].time_us > start_us) &&
+ (n_summed < decision_points_.size())) {
+ --ind;
+ if (ind < 0)
+ ind = decision_points_.size() - 1;
+ if (is_on)
+ sum_us += end_us - decision_points_[ind].time_us;
+ is_on = decision_points_[ind].decision;
+ end_us = decision_points_[ind].time_us;
+ n_summed++;
+ }
+
+ return 1.0e-6f * sum_us; // Returns total time that was super threshold.
+}
+
+EnergyEndpointer::EnergyEndpointer()
+ : status_(EP_PRE_SPEECH),
+ offset_confirm_dur_sec_(0),
+ endpointer_time_us_(0),
+ fast_update_frames_(0),
+ frame_counter_(0),
+ max_window_dur_(4.0),
+ sample_rate_(0),
+ history_(new HistoryRing()),
+ decision_threshold_(0),
+ estimating_environment_(false),
+ noise_level_(0),
+ rms_adapt_(0),
+ start_lag_(0),
+ end_lag_(0),
+ user_input_start_time_us_(0) {
+}
+
+EnergyEndpointer::~EnergyEndpointer() {
+}
+
+int EnergyEndpointer::TimeToFrame(float time) const {
+ return static_cast<int32>(0.5 + (time / params_.frame_period()));
+}
+
+void EnergyEndpointer::Restart(bool reset_threshold) {
+ status_ = EP_PRE_SPEECH;
+ user_input_start_time_us_ = 0;
+
+ if (reset_threshold) {
+ decision_threshold_ = params_.decision_threshold();
+ rms_adapt_ = decision_threshold_;
+ noise_level_ = params_.decision_threshold() / 2.0f;
+ frame_counter_ = 0; // Used for rapid initial update of levels.
+ }
+
+ // Set up the memories to hold the history windows.
+ history_->SetRing(TimeToFrame(max_window_dur_), false);
+
+ // Flag that indicates that current input should be used for
+ // estimating the environment. The user has not yet started input
+ // by e.g. pressed the push-to-talk button. By default, this is
+ // false for backward compatibility.
+ estimating_environment_ = false;
+}
+
+void EnergyEndpointer::Init(const EnergyEndpointerParams& params) {
+ params_ = params;
+
+ // Find the longest history interval to be used, and make the ring
+ // large enough to accommodate that number of frames. NOTE: This
+ // depends upon ep_frame_period being set correctly in the factory
+ // that did this instantiation.
+ max_window_dur_ = params_.onset_window();
+ if (params_.speech_on_window() > max_window_dur_)
+ max_window_dur_ = params_.speech_on_window();
+ if (params_.offset_window() > max_window_dur_)
+ max_window_dur_ = params_.offset_window();
+ Restart(true);
+
+ offset_confirm_dur_sec_ = params_.offset_window() -
+ params_.offset_confirm_dur();
+ if (offset_confirm_dur_sec_ < 0.0)
+ offset_confirm_dur_sec_ = 0.0;
+
+ user_input_start_time_us_ = 0;
+
+ // Flag that indicates that current input should be used for
+ // estimating the environment. The user has not yet started input
+ // by e.g. pressed the push-to-talk button. By default, this is
+ // false for backward compatibility.
+ estimating_environment_ = false;
+ // The initial value of the noise and speech levels is inconsequential.
+ // The level of the first frame will overwrite these values.
+ noise_level_ = params_.decision_threshold() / 2.0f;
+ fast_update_frames_ =
+ static_cast<int64>(params_.fast_update_dur() / params_.frame_period());
+
+ frame_counter_ = 0; // Used for rapid initial update of levels.
+
+ sample_rate_ = params_.sample_rate();
+ start_lag_ = static_cast<int>(sample_rate_ /
+ params_.max_fundamental_frequency());
+ end_lag_ = static_cast<int>(sample_rate_ /
+ params_.min_fundamental_frequency());
+}
+
+void EnergyEndpointer::StartSession() {
+ Restart(true);
+}
+
+void EnergyEndpointer::EndSession() {
+ status_ = EP_POST_SPEECH;
+}
+
+void EnergyEndpointer::SetEnvironmentEstimationMode() {
+ Restart(true);
+ estimating_environment_ = true;
+}
+
+void EnergyEndpointer::SetUserInputMode() {
+ estimating_environment_ = false;
+ user_input_start_time_us_ = endpointer_time_us_;
+}
+
+void EnergyEndpointer::ProcessAudioFrame(int64 time_us,
+ const int16* samples,
+ int num_samples,
+ float* rms_out) {
+ endpointer_time_us_ = time_us;
+ float rms = RMS(samples, num_samples);
+
+ // Check that this is user input audio vs. pre-input adaptation audio.
+ // Input audio starts when the user indicates start of input, by e.g.
+ // pressing push-to-talk. Audio recieved prior to that is used to update
+ // noise and speech level estimates.
+ if (!estimating_environment_) {
+ bool decision = false;
+ if ((endpointer_time_us_ - user_input_start_time_us_) <
+ Secs2Usecs(params_.contamination_rejection_period())) {
+ decision = false;
+ DVLOG(1) << "decision: forced to false, time: " << endpointer_time_us_;
+ } else {
+ decision = (rms > decision_threshold_);
+ }
+
+ history_->Insert(endpointer_time_us_, decision);
+
+ switch (status_) {
+ case EP_PRE_SPEECH:
+ if (history_->RingSum(params_.onset_window()) >
+ params_.onset_detect_dur()) {
+ status_ = EP_POSSIBLE_ONSET;
+ }
+ break;
+
+ case EP_POSSIBLE_ONSET: {
+ float tsum = history_->RingSum(params_.onset_window());
+ if (tsum > params_.onset_confirm_dur()) {
+ status_ = EP_SPEECH_PRESENT;
+ } else { // If signal is not maintained, drop back to pre-speech.
+ if (tsum <= params_.onset_detect_dur())
+ status_ = EP_PRE_SPEECH;
+ }
+ break;
+ }
+
+ case EP_SPEECH_PRESENT: {
+ // To induce hysteresis in the state residency, we allow a
+ // smaller residency time in the on_ring, than was required to
+ // enter the SPEECH_PERSENT state.
+ float on_time = history_->RingSum(params_.speech_on_window());
+ if (on_time < params_.on_maintain_dur())
+ status_ = EP_POSSIBLE_OFFSET;
+ break;
+ }
+
+ case EP_POSSIBLE_OFFSET:
+ if (history_->RingSum(params_.offset_window()) <=
+ offset_confirm_dur_sec_) {
+ // Note that this offset time may be beyond the end
+ // of the input buffer in a real-time system. It will be up
+ // to the RecognizerSession to decide what to do.
+ status_ = EP_PRE_SPEECH; // Automatically reset for next utterance.
+ } else { // If speech picks up again we allow return to SPEECH_PRESENT.
+ if (history_->RingSum(params_.speech_on_window()) >=
+ params_.on_maintain_dur())
+ status_ = EP_SPEECH_PRESENT;
+ }
+ break;
+
+ default:
+ LOG(WARNING) << "Invalid case in switch: " << status_;
+ break;
+ }
+
+ // If this is a quiet, non-speech region, slowly adapt the detection
+ // threshold to be about 6dB above the average RMS.
+ if ((!decision) && (status_ == EP_PRE_SPEECH)) {
+ decision_threshold_ = (0.98f * decision_threshold_) + (0.02f * 2 * rms);
+ rms_adapt_ = decision_threshold_;
+ } else {
+ // If this is in a speech region, adapt the decision threshold to
+ // be about 10dB below the average RMS. If the noise level is high,
+ // the threshold is pushed up.
+ // Adaptation up to a higher level is 5 times faster than decay to
+ // a lower level.
+ if ((status_ == EP_SPEECH_PRESENT) && decision) {
+ if (rms_adapt_ > rms) {
+ rms_adapt_ = (0.99f * rms_adapt_) + (0.01f * rms);
+ } else {
+ rms_adapt_ = (0.95f * rms_adapt_) + (0.05f * rms);
+ }
+ float target_threshold = 0.3f * rms_adapt_ + noise_level_;
+ decision_threshold_ = (.90f * decision_threshold_) +
+ (0.10f * target_threshold);
+ }
+ }
+
+ // Set a floor
+ if (decision_threshold_ < params_.min_decision_threshold())
+ decision_threshold_ = params_.min_decision_threshold();
+ }
+
+ // Update speech and noise levels.
+ UpdateLevels(rms);
+ ++frame_counter_;
+
+ if (rms_out)
+ *rms_out = GetDecibel(rms);
+}
+
+float EnergyEndpointer::GetNoiseLevelDb() const {
+ return GetDecibel(noise_level_);
+}
+
+void EnergyEndpointer::UpdateLevels(float rms) {
+ // Update quickly initially. We assume this is noise and that
+ // speech is 6dB above the noise.
+ if (frame_counter_ < fast_update_frames_) {
+ // Alpha increases from 0 to (k-1)/k where k is the number of time
+ // steps in the initial adaptation period.
+ float alpha = static_cast<float>(frame_counter_) /
+ static_cast<float>(fast_update_frames_);
+ noise_level_ = (alpha * noise_level_) + ((1 - alpha) * rms);
+ DVLOG(1) << "FAST UPDATE, frame_counter_ " << frame_counter_
+ << ", fast_update_frames_ " << fast_update_frames_;
+ } else {
+ // Update Noise level. The noise level adapts quickly downward, but
+ // slowly upward. The noise_level_ parameter is not currently used
+ // for threshold adaptation. It is used for UI feedback.
+ if (noise_level_ < rms)
+ noise_level_ = (0.999f * noise_level_) + (0.001f * rms);
+ else
+ noise_level_ = (0.95f * noise_level_) + (0.05f * rms);
+ }
+ if (estimating_environment_ || (frame_counter_ < fast_update_frames_)) {
+ decision_threshold_ = noise_level_ * 2; // 6dB above noise level.
+ // Set a floor
+ if (decision_threshold_ < params_.min_decision_threshold())
+ decision_threshold_ = params_.min_decision_threshold();
+ }
+}
+
+EpStatus EnergyEndpointer::Status(int64* status_time) const {
+ *status_time = history_->EndTime();
+ return status_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/endpointer/energy_endpointer.h b/chromium/content/browser/speech/endpointer/energy_endpointer.h
new file mode 100644
index 00000000000..0aa421fb363
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/energy_endpointer.h
@@ -0,0 +1,155 @@
+// 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.
+
+// The EnergyEndpointer class finds likely speech onset and offset points.
+//
+// The implementation described here is about the simplest possible.
+// It is based on timings of threshold crossings for overall signal
+// RMS. It is suitable for light weight applications.
+//
+// As written, the basic idea is that one specifies intervals that
+// must be occupied by super- and sub-threshold energy levels, and
+// defers decisions re onset and offset times until these
+// specifications have been met. Three basic intervals are tested: an
+// onset window, a speech-on window, and an offset window. We require
+// super-threshold to exceed some mimimum total durations in the onset
+// and speech-on windows before declaring the speech onset time, and
+// we specify a required sub-threshold residency in the offset window
+// before declaring speech offset. As the various residency requirements are
+// met, the EnergyEndpointer instance assumes various states, and can return the
+// ID of these states to the client (see EpStatus below).
+//
+// The levels of the speech and background noise are continuously updated. It is
+// important that the background noise level be estimated initially for
+// robustness in noisy conditions. The first frames are assumed to be background
+// noise and a fast update rate is used for the noise level. The duration for
+// fast update is controlled by the fast_update_dur_ paramter.
+//
+// If used in noisy conditions, the endpointer should be started and run in the
+// EnvironmentEstimation mode, for at least 200ms, before switching to
+// UserInputMode.
+// Audio feedback contamination can appear in the input audio, if not cut
+// out or handled by echo cancellation. Audio feedback can trigger a false
+// accept. The false accepts can be ignored by setting
+// ep_contamination_rejection_period.
+
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/speech/endpointer/energy_endpointer_params.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Endpointer status codes
+enum EpStatus {
+ EP_PRE_SPEECH = 10,
+ EP_POSSIBLE_ONSET,
+ EP_SPEECH_PRESENT,
+ EP_POSSIBLE_OFFSET,
+ EP_POST_SPEECH,
+};
+
+class CONTENT_EXPORT EnergyEndpointer {
+ public:
+ // The default construction MUST be followed by Init(), before any
+ // other use can be made of the instance.
+ EnergyEndpointer();
+ virtual ~EnergyEndpointer();
+
+ void Init(const EnergyEndpointerParams& params);
+
+ // Start the endpointer. This should be called at the beginning of a session.
+ void StartSession();
+
+ // Stop the endpointer.
+ void EndSession();
+
+ // Start environment estimation. Audio will be used for environment estimation
+ // i.e. noise level estimation.
+ void SetEnvironmentEstimationMode();
+
+ // Start user input. This should be called when the user indicates start of
+ // input, e.g. by pressing a button.
+ void SetUserInputMode();
+
+ // Computes the next input frame and modifies EnergyEndpointer status as
+ // appropriate based on the computation.
+ void ProcessAudioFrame(int64 time_us,
+ const int16* samples, int num_samples,
+ float* rms_out);
+
+ // Returns the current state of the EnergyEndpointer and the time
+ // corresponding to the most recently computed frame.
+ EpStatus Status(int64* status_time_us) const;
+
+ bool estimating_environment() const {
+ return estimating_environment_;
+ }
+
+ // Returns estimated noise level in dB.
+ float GetNoiseLevelDb() const;
+
+ private:
+ class HistoryRing;
+
+ // Resets the endpointer internal state. If reset_threshold is true, the
+ // state will be reset completely, including adaptive thresholds and the
+ // removal of all history information.
+ void Restart(bool reset_threshold);
+
+ // Update internal speech and noise levels.
+ void UpdateLevels(float rms);
+
+ // Returns the number of frames (or frame number) corresponding to
+ // the 'time' (in seconds).
+ int TimeToFrame(float time) const;
+
+ EpStatus status_; // The current state of this instance.
+ float offset_confirm_dur_sec_; // max on time allowed to confirm POST_SPEECH
+ int64 endpointer_time_us_; // Time of the most recently received audio frame.
+ int64 fast_update_frames_; // Number of frames for initial level adaptation.
+ int64 frame_counter_; // Number of frames seen. Used for initial adaptation.
+ float max_window_dur_; // Largest search window size (seconds)
+ float sample_rate_; // Sampling rate.
+
+ // Ring buffers to hold the speech activity history.
+ scoped_ptr<HistoryRing> history_;
+
+ // Configuration parameters.
+ EnergyEndpointerParams params_;
+
+ // RMS which must be exceeded to conclude frame is speech.
+ float decision_threshold_;
+
+ // Flag to indicate that audio should be used to estimate environment, prior
+ // to receiving user input.
+ bool estimating_environment_;
+
+ // Estimate of the background noise level. Used externally for UI feedback.
+ float noise_level_;
+
+ // An adaptive threshold used to update decision_threshold_ when appropriate.
+ float rms_adapt_;
+
+ // Start lag corresponds to the highest fundamental frequency.
+ int start_lag_;
+
+ // End lag corresponds to the lowest fundamental frequency.
+ int end_lag_;
+
+ // Time when mode switched from environment estimation to user input. This
+ // is used to time forced rejection of audio feedback contamination.
+ int64 user_input_start_time_us_;
+
+ DISALLOW_COPY_AND_ASSIGN(EnergyEndpointer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
diff --git a/chromium/content/browser/speech/endpointer/energy_endpointer_params.cc b/chromium/content/browser/speech/endpointer/energy_endpointer_params.cc
new file mode 100644
index 00000000000..9cdf024bd0c
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/energy_endpointer_params.cc
@@ -0,0 +1,53 @@
+// 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 "content/browser/speech/endpointer/energy_endpointer_params.h"
+
+namespace content {
+
+EnergyEndpointerParams::EnergyEndpointerParams() {
+ SetDefaults();
+}
+
+void EnergyEndpointerParams::SetDefaults() {
+ frame_period_ = 0.01f;
+ frame_duration_ = 0.01f;
+ endpoint_margin_ = 0.2f;
+ onset_window_ = 0.15f;
+ speech_on_window_ = 0.4f;
+ offset_window_ = 0.15f;
+ onset_detect_dur_ = 0.09f;
+ onset_confirm_dur_ = 0.075f;
+ on_maintain_dur_ = 0.10f;
+ offset_confirm_dur_ = 0.12f;
+ decision_threshold_ = 150.0f;
+ min_decision_threshold_ = 50.0f;
+ fast_update_dur_ = 0.2f;
+ sample_rate_ = 8000.0f;
+ min_fundamental_frequency_ = 57.143f;
+ max_fundamental_frequency_ = 400.0f;
+ contamination_rejection_period_ = 0.25f;
+}
+
+void EnergyEndpointerParams::operator=(const EnergyEndpointerParams& source) {
+ frame_period_ = source.frame_period();
+ frame_duration_ = source.frame_duration();
+ endpoint_margin_ = source.endpoint_margin();
+ onset_window_ = source.onset_window();
+ speech_on_window_ = source.speech_on_window();
+ offset_window_ = source.offset_window();
+ onset_detect_dur_ = source.onset_detect_dur();
+ onset_confirm_dur_ = source.onset_confirm_dur();
+ on_maintain_dur_ = source.on_maintain_dur();
+ offset_confirm_dur_ = source.offset_confirm_dur();
+ decision_threshold_ = source.decision_threshold();
+ min_decision_threshold_ = source.min_decision_threshold();
+ fast_update_dur_ = source.fast_update_dur();
+ sample_rate_ = source.sample_rate();
+ min_fundamental_frequency_ = source.min_fundamental_frequency();
+ max_fundamental_frequency_ = source.max_fundamental_frequency();
+ contamination_rejection_period_ = source.contamination_rejection_period();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/endpointer/energy_endpointer_params.h b/chromium/content/browser/speech/endpointer/energy_endpointer_params.h
new file mode 100644
index 00000000000..436adce8dc3
--- /dev/null
+++ b/chromium/content/browser/speech/endpointer/energy_endpointer_params.h
@@ -0,0 +1,138 @@
+// 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 CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Input parameters for the EnergyEndpointer class.
+class CONTENT_EXPORT EnergyEndpointerParams {
+ public:
+ EnergyEndpointerParams();
+
+ void SetDefaults();
+
+ void operator=(const EnergyEndpointerParams& source);
+
+ // Accessors and mutators
+ float frame_period() const { return frame_period_; }
+ void set_frame_period(float frame_period) {
+ frame_period_ = frame_period;
+ }
+
+ float frame_duration() const { return frame_duration_; }
+ void set_frame_duration(float frame_duration) {
+ frame_duration_ = frame_duration;
+ }
+
+ float endpoint_margin() const { return endpoint_margin_; }
+ void set_endpoint_margin(float endpoint_margin) {
+ endpoint_margin_ = endpoint_margin;
+ }
+
+ float onset_window() const { return onset_window_; }
+ void set_onset_window(float onset_window) { onset_window_ = onset_window; }
+
+ float speech_on_window() const { return speech_on_window_; }
+ void set_speech_on_window(float speech_on_window) {
+ speech_on_window_ = speech_on_window;
+ }
+
+ float offset_window() const { return offset_window_; }
+ void set_offset_window(float offset_window) {
+ offset_window_ = offset_window;
+ }
+
+ float onset_detect_dur() const { return onset_detect_dur_; }
+ void set_onset_detect_dur(float onset_detect_dur) {
+ onset_detect_dur_ = onset_detect_dur;
+ }
+
+ float onset_confirm_dur() const { return onset_confirm_dur_; }
+ void set_onset_confirm_dur(float onset_confirm_dur) {
+ onset_confirm_dur_ = onset_confirm_dur;
+ }
+
+ float on_maintain_dur() const { return on_maintain_dur_; }
+ void set_on_maintain_dur(float on_maintain_dur) {
+ on_maintain_dur_ = on_maintain_dur;
+ }
+
+ float offset_confirm_dur() const { return offset_confirm_dur_; }
+ void set_offset_confirm_dur(float offset_confirm_dur) {
+ offset_confirm_dur_ = offset_confirm_dur;
+ }
+
+ float decision_threshold() const { return decision_threshold_; }
+ void set_decision_threshold(float decision_threshold) {
+ decision_threshold_ = decision_threshold;
+ }
+
+ float min_decision_threshold() const { return min_decision_threshold_; }
+ void set_min_decision_threshold(float min_decision_threshold) {
+ min_decision_threshold_ = min_decision_threshold;
+ }
+
+ float fast_update_dur() const { return fast_update_dur_; }
+ void set_fast_update_dur(float fast_update_dur) {
+ fast_update_dur_ = fast_update_dur;
+ }
+
+ float sample_rate() const { return sample_rate_; }
+ void set_sample_rate(float sample_rate) { sample_rate_ = sample_rate; }
+
+ float min_fundamental_frequency() const { return min_fundamental_frequency_; }
+ void set_min_fundamental_frequency(float min_fundamental_frequency) {
+ min_fundamental_frequency_ = min_fundamental_frequency;
+ }
+
+ float max_fundamental_frequency() const { return max_fundamental_frequency_; }
+ void set_max_fundamental_frequency(float max_fundamental_frequency) {
+ max_fundamental_frequency_ = max_fundamental_frequency;
+ }
+
+ float contamination_rejection_period() const {
+ return contamination_rejection_period_;
+ }
+ void set_contamination_rejection_period(
+ float contamination_rejection_period) {
+ contamination_rejection_period_ = contamination_rejection_period;
+ }
+
+ private:
+ float frame_period_; // Frame period
+ float frame_duration_; // Window size
+ float onset_window_; // Interval scanned for onset activity
+ float speech_on_window_; // Inverval scanned for ongoing speech
+ float offset_window_; // Interval scanned for offset evidence
+ float offset_confirm_dur_; // Silence duration required to confirm offset
+ float decision_threshold_; // Initial rms detection threshold
+ float min_decision_threshold_; // Minimum rms detection threshold
+ float fast_update_dur_; // Period for initial estimation of levels.
+ float sample_rate_; // Expected sample rate.
+
+ // Time to add on either side of endpoint threshold crossings
+ float endpoint_margin_;
+ // Total dur within onset_window required to enter ONSET state
+ float onset_detect_dur_;
+ // Total on time within onset_window required to enter SPEECH_ON state
+ float onset_confirm_dur_;
+ // Minimum dur in SPEECH_ON state required to maintain ON state
+ float on_maintain_dur_;
+ // Minimum fundamental frequency for autocorrelation.
+ float min_fundamental_frequency_;
+ // Maximum fundamental frequency for autocorrelation.
+ float max_fundamental_frequency_;
+ // Period after start of user input that above threshold values are ignored.
+ // This is to reject audio feedback contamination.
+ float contamination_rejection_period_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
diff --git a/chromium/content/browser/speech/google_one_shot_remote_engine.cc b/chromium/content/browser/speech/google_one_shot_remote_engine.cc
new file mode 100644
index 00000000000..a421e79c228
--- /dev/null
+++ b/chromium/content/browser/speech/google_one_shot_remote_engine.cc
@@ -0,0 +1,295 @@
+// 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 "content/browser/speech/google_one_shot_remote_engine.h"
+
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+namespace content {
+namespace {
+
+const char* const kDefaultSpeechRecognitionUrl =
+ "https://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&";
+const char* const kStatusString = "status";
+const char* const kHypothesesString = "hypotheses";
+const char* const kUtteranceString = "utterance";
+const char* const kConfidenceString = "confidence";
+const int kWebServiceStatusNoError = 0;
+const int kWebServiceStatusNoSpeech = 4;
+const int kWebServiceStatusNoMatch = 5;
+const AudioEncoder::Codec kDefaultAudioCodec = AudioEncoder::CODEC_FLAC;
+
+bool ParseServerResponse(const std::string& response_body,
+ SpeechRecognitionResult* result,
+ SpeechRecognitionError* error) {
+ if (response_body.empty()) {
+ LOG(WARNING) << "ParseServerResponse: Response was empty.";
+ return false;
+ }
+ DVLOG(1) << "ParseServerResponse: Parsing response " << response_body;
+
+ // Parse the response, ignoring comments.
+ std::string error_msg;
+ scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
+ response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
+ if (response_value == NULL) {
+ LOG(WARNING) << "ParseServerResponse: JSONReader failed : " << error_msg;
+ return false;
+ }
+
+ if (!response_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ VLOG(1) << "ParseServerResponse: Unexpected response type "
+ << response_value->GetType();
+ return false;
+ }
+ const base::DictionaryValue* response_object =
+ static_cast<const base::DictionaryValue*>(response_value.get());
+
+ // Get the status.
+ int status;
+ if (!response_object->GetInteger(kStatusString, &status)) {
+ VLOG(1) << "ParseServerResponse: " << kStatusString
+ << " is not a valid integer value.";
+ return false;
+ }
+
+ // Process the status.
+ switch (status) {
+ case kWebServiceStatusNoError:
+ break;
+ case kWebServiceStatusNoSpeech:
+ error->code = SPEECH_RECOGNITION_ERROR_NO_SPEECH;
+ return false;
+ case kWebServiceStatusNoMatch:
+ error->code = SPEECH_RECOGNITION_ERROR_NO_MATCH;
+ return false;
+ default:
+ error->code = SPEECH_RECOGNITION_ERROR_NETWORK;
+ // Other status codes should not be returned by the server.
+ VLOG(1) << "ParseServerResponse: unexpected status code " << status;
+ return false;
+ }
+
+ // Get the hypotheses.
+ const base::Value* hypotheses_value = NULL;
+ if (!response_object->Get(kHypothesesString, &hypotheses_value)) {
+ VLOG(1) << "ParseServerResponse: Missing hypotheses attribute.";
+ return false;
+ }
+
+ DCHECK(hypotheses_value);
+ if (!hypotheses_value->IsType(base::Value::TYPE_LIST)) {
+ VLOG(1) << "ParseServerResponse: Unexpected hypotheses type "
+ << hypotheses_value->GetType();
+ return false;
+ }
+
+ const base::ListValue* hypotheses_list =
+ static_cast<const base::ListValue*>(hypotheses_value);
+
+ // For now we support only single shot recognition, so we are giving only a
+ // final result, consisting of one fragment (with one or more hypotheses).
+ size_t index = 0;
+ for (; index < hypotheses_list->GetSize(); ++index) {
+ const base::Value* hypothesis = NULL;
+ if (!hypotheses_list->Get(index, &hypothesis)) {
+ LOG(WARNING) << "ParseServerResponse: Unable to read hypothesis value.";
+ break;
+ }
+ DCHECK(hypothesis);
+ if (!hypothesis->IsType(base::Value::TYPE_DICTIONARY)) {
+ LOG(WARNING) << "ParseServerResponse: Unexpected value type "
+ << hypothesis->GetType();
+ break;
+ }
+
+ const base::DictionaryValue* hypothesis_value =
+ static_cast<const base::DictionaryValue*>(hypothesis);
+ string16 utterance;
+
+ if (!hypothesis_value->GetString(kUtteranceString, &utterance)) {
+ LOG(WARNING) << "ParseServerResponse: Missing utterance value.";
+ break;
+ }
+
+ // It is not an error if the 'confidence' field is missing.
+ double confidence = 0.0;
+ hypothesis_value->GetDouble(kConfidenceString, &confidence);
+ result->hypotheses.push_back(SpeechRecognitionHypothesis(utterance,
+ confidence));
+ }
+
+ if (index < hypotheses_list->GetSize()) {
+ result->hypotheses.clear();
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+const int GoogleOneShotRemoteEngine::kAudioPacketIntervalMs = 100;
+int GoogleOneShotRemoteEngine::url_fetcher_id_for_tests = 0;
+
+GoogleOneShotRemoteEngine::GoogleOneShotRemoteEngine(
+ net::URLRequestContextGetter* context)
+ : url_context_(context) {
+}
+
+GoogleOneShotRemoteEngine::~GoogleOneShotRemoteEngine() {}
+
+void GoogleOneShotRemoteEngine::SetConfig(
+ const SpeechRecognitionEngineConfig& config) {
+ config_ = config;
+}
+
+void GoogleOneShotRemoteEngine::StartRecognition() {
+ DCHECK(delegate());
+ DCHECK(!url_fetcher_.get());
+ std::string lang_param = config_.language;
+
+ if (lang_param.empty() && url_context_.get()) {
+ // If no language is provided then we use the first from the accepted
+ // language list. If this list is empty then it defaults to "en-US".
+ // Example of the contents of this list: "es,en-GB;q=0.8", ""
+ net::URLRequestContext* request_context =
+ url_context_->GetURLRequestContext();
+ DCHECK(request_context);
+ // TODO(pauljensen): GoogleOneShotRemoteEngine should be constructed with
+ // a reference to the HttpUserAgentSettings rather than accessing the
+ // accept language through the URLRequestContext.
+ std::string accepted_language_list = request_context->GetAcceptLanguage();
+ size_t separator = accepted_language_list.find_first_of(",;");
+ lang_param = accepted_language_list.substr(0, separator);
+ }
+
+ if (lang_param.empty())
+ lang_param = "en-US";
+
+ std::vector<std::string> parts;
+ parts.push_back("lang=" + net::EscapeQueryParamValue(lang_param, true));
+
+ if (!config_.grammars.empty()) {
+ DCHECK_EQ(config_.grammars.size(), 1U);
+ parts.push_back("lm=" + net::EscapeQueryParamValue(config_.grammars[0].url,
+ true));
+ }
+
+ if (!config_.hardware_info.empty())
+ parts.push_back("xhw=" + net::EscapeQueryParamValue(config_.hardware_info,
+ true));
+ parts.push_back("maxresults=" + base::UintToString(config_.max_hypotheses));
+ parts.push_back(config_.filter_profanities ? "pfilter=2" : "pfilter=0");
+
+ std::string api_key = google_apis::GetAPIKey();
+ parts.push_back("key=" + net::EscapeQueryParamValue(api_key, true));
+
+ GURL url(std::string(kDefaultSpeechRecognitionUrl) + JoinString(parts, '&'));
+
+ encoder_.reset(AudioEncoder::Create(kDefaultAudioCodec,
+ config_.audio_sample_rate,
+ config_.audio_num_bits_per_sample));
+ DCHECK(encoder_.get());
+ url_fetcher_.reset(net::URLFetcher::Create(url_fetcher_id_for_tests,
+ url,
+ net::URLFetcher::POST,
+ this));
+ url_fetcher_->SetChunkedUpload(encoder_->mime_type());
+ url_fetcher_->SetRequestContext(url_context_.get());
+ url_fetcher_->SetReferrer(config_.origin_url);
+
+ // The speech recognition API does not require user identification as part
+ // of requests, so we don't send cookies or auth data for these requests to
+ // prevent any accidental connection between users who are logged into the
+ // domain for other services (e.g. bookmark sync) with the speech requests.
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ url_fetcher_->Start();
+}
+
+void GoogleOneShotRemoteEngine::EndRecognition() {
+ url_fetcher_.reset();
+}
+
+void GoogleOneShotRemoteEngine::TakeAudioChunk(const AudioChunk& data) {
+ DCHECK(url_fetcher_.get());
+ DCHECK(encoder_.get());
+ DCHECK_EQ(data.bytes_per_sample(), config_.audio_num_bits_per_sample / 8);
+ encoder_->Encode(data);
+ scoped_refptr<AudioChunk> encoded_data(encoder_->GetEncodedDataAndClear());
+ url_fetcher_->AppendChunkToUpload(encoded_data->AsString(), false);
+}
+
+void GoogleOneShotRemoteEngine::AudioChunksEnded() {
+ DCHECK(url_fetcher_.get());
+ DCHECK(encoder_.get());
+
+ // UploadAudioChunk requires a non-empty final buffer. So we encode a packet
+ // of silence in case encoder had no data already.
+ std::vector<int16> samples(
+ config_.audio_sample_rate * kAudioPacketIntervalMs / 1000);
+ scoped_refptr<AudioChunk> dummy_chunk(
+ new AudioChunk(reinterpret_cast<uint8*>(&samples[0]),
+ samples.size() * sizeof(int16),
+ encoder_->bits_per_sample() / 8));
+ encoder_->Encode(*dummy_chunk.get());
+ encoder_->Flush();
+ scoped_refptr<AudioChunk> encoded_dummy_data(
+ encoder_->GetEncodedDataAndClear());
+ DCHECK(!encoded_dummy_data->IsEmpty());
+ encoder_.reset();
+
+ url_fetcher_->AppendChunkToUpload(encoded_dummy_data->AsString(), true);
+}
+
+void GoogleOneShotRemoteEngine::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ DCHECK_EQ(url_fetcher_.get(), source);
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ SpeechRecognitionError error(SPEECH_RECOGNITION_ERROR_NETWORK);
+ std::string data;
+
+ // The default error code in case of parse errors is NETWORK_FAILURE, however
+ // ParseServerResponse can change the error to a more appropriate one.
+ bool error_occurred = (!source->GetStatus().is_success() ||
+ source->GetResponseCode() != 200 ||
+ !source->GetResponseAsString(&data) ||
+ !ParseServerResponse(data, &result, &error));
+ url_fetcher_.reset();
+ if (error_occurred) {
+ DVLOG(1) << "GoogleOneShotRemoteEngine: Network Error " << error.code;
+ delegate()->OnSpeechRecognitionEngineError(error);
+ } else {
+ DVLOG(1) << "GoogleOneShotRemoteEngine: Invoking delegate with result.";
+ delegate()->OnSpeechRecognitionEngineResults(results);
+ }
+}
+
+bool GoogleOneShotRemoteEngine::IsRecognitionPending() const {
+ return url_fetcher_ != NULL;
+}
+
+int GoogleOneShotRemoteEngine::GetDesiredAudioChunkDurationMs() const {
+ return kAudioPacketIntervalMs;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/google_one_shot_remote_engine.h b/chromium/content/browser/speech/google_one_shot_remote_engine.h
new file mode 100644
index 00000000000..b02766597b7
--- /dev/null
+++ b/chromium/content/browser/speech/google_one_shot_remote_engine.h
@@ -0,0 +1,61 @@
+// 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 CONTENT_BROWSER_SPEECH_GOOGLE_ONE_SHOT_REMOTE_ENGINE_H_
+#define CONTENT_BROWSER_SPEECH_GOOGLE_ONE_SHOT_REMOTE_ENGINE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/speech/audio_encoder.h"
+#include "content/browser/speech/speech_recognition_engine.h"
+#include "content/common/content_export.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+// Implements a SpeechRecognitionEngine by means of remote interaction with
+// Google speech recognition webservice.
+class CONTENT_EXPORT GoogleOneShotRemoteEngine
+ : public NON_EXPORTED_BASE(SpeechRecognitionEngine),
+ public net::URLFetcherDelegate {
+ public:
+ // Duration of each audio packet.
+ static const int kAudioPacketIntervalMs;
+ // ID passed to URLFetcher::Create(). Used for testing.
+ static int url_fetcher_id_for_tests;
+
+ explicit GoogleOneShotRemoteEngine(net::URLRequestContextGetter* context);
+ virtual ~GoogleOneShotRemoteEngine();
+
+ // SpeechRecognitionEngine methods.
+ virtual void SetConfig(const SpeechRecognitionEngineConfig& config) OVERRIDE;
+ virtual void StartRecognition() OVERRIDE;
+ virtual void EndRecognition() OVERRIDE;
+ virtual void TakeAudioChunk(const AudioChunk& data) OVERRIDE;
+ virtual void AudioChunksEnded() OVERRIDE;
+ virtual bool IsRecognitionPending() const OVERRIDE;
+ virtual int GetDesiredAudioChunkDurationMs() const OVERRIDE;
+
+ // net::URLFetcherDelegate methods.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ SpeechRecognitionEngineConfig config_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ scoped_refptr<net::URLRequestContextGetter> url_context_;
+ scoped_ptr<AudioEncoder> encoder_;
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleOneShotRemoteEngine);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_GOOGLE_ONE_SHOT_REMOTE_ENGINE_H_
diff --git a/chromium/content/browser/speech/google_one_shot_remote_engine_unittest.cc b/chromium/content/browser/speech/google_one_shot_remote_engine_unittest.cc
new file mode 100644
index 00000000000..f7c65615e4e
--- /dev/null
+++ b/chromium/content/browser/speech/google_one_shot_remote_engine_unittest.cc
@@ -0,0 +1,128 @@
+// 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 "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/google_one_shot_remote_engine.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class GoogleOneShotRemoteEngineTest : public SpeechRecognitionEngineDelegate,
+ public testing::Test {
+ public:
+ GoogleOneShotRemoteEngineTest()
+ : error_(SPEECH_RECOGNITION_ERROR_NONE) {}
+
+ // Creates a speech recognition request and invokes its URL fetcher delegate
+ // with the given test data.
+ void CreateAndTestRequest(bool success, const std::string& http_response);
+
+ // SpeechRecognitionRequestDelegate methods.
+ virtual void OnSpeechRecognitionEngineResults(
+ const SpeechRecognitionResults& results) OVERRIDE {
+ results_ = results;
+ }
+
+ virtual void OnSpeechRecognitionEngineError(
+ const SpeechRecognitionError& error) OVERRIDE {
+ error_ = error.code;
+ }
+
+ // Accessor for the only result item.
+ const SpeechRecognitionResult& result() const {
+ DCHECK_EQ(results_.size(), 1U);
+ return results_[0];
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ SpeechRecognitionErrorCode error_;
+ SpeechRecognitionResults results_;
+};
+
+void GoogleOneShotRemoteEngineTest::CreateAndTestRequest(
+ bool success, const std::string& http_response) {
+ GoogleOneShotRemoteEngine client(NULL);
+ unsigned char dummy_audio_buffer_data[2] = {'\0', '\0'};
+ scoped_refptr<AudioChunk> dummy_audio_chunk(
+ new AudioChunk(&dummy_audio_buffer_data[0],
+ sizeof(dummy_audio_buffer_data),
+ 2 /* bytes per sample */));
+ client.set_delegate(this);
+ client.StartRecognition();
+ client.TakeAudioChunk(*dummy_audio_chunk.get());
+ client.AudioChunksEnded();
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ fetcher->set_url(fetcher->GetOriginalURL());
+ net::URLRequestStatus status;
+ status.set_status(success ? net::URLRequestStatus::SUCCESS :
+ net::URLRequestStatus::FAILED);
+ fetcher->set_status(status);
+ fetcher->set_response_code(success ? 200 : 500);
+ fetcher->SetResponseString(http_response);
+
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+ // Parsed response will be available in result().
+}
+
+TEST_F(GoogleOneShotRemoteEngineTest, BasicTest) {
+ // Normal success case with one result.
+ CreateAndTestRequest(true,
+ "{\"status\":0,\"hypotheses\":"
+ "[{\"utterance\":\"123456\",\"confidence\":0.9}]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NONE);
+ EXPECT_EQ(1U, result().hypotheses.size());
+ EXPECT_EQ(ASCIIToUTF16("123456"), result().hypotheses[0].utterance);
+ EXPECT_EQ(0.9, result().hypotheses[0].confidence);
+
+ // Normal success case with multiple results.
+ CreateAndTestRequest(true,
+ "{\"status\":0,\"hypotheses\":["
+ "{\"utterance\":\"hello\",\"confidence\":0.9},"
+ "{\"utterance\":\"123456\",\"confidence\":0.5}]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NONE);
+ EXPECT_EQ(2u, result().hypotheses.size());
+ EXPECT_EQ(ASCIIToUTF16("hello"), result().hypotheses[0].utterance);
+ EXPECT_EQ(0.9, result().hypotheses[0].confidence);
+ EXPECT_EQ(ASCIIToUTF16("123456"), result().hypotheses[1].utterance);
+ EXPECT_EQ(0.5, result().hypotheses[1].confidence);
+
+ // Zero results.
+ CreateAndTestRequest(true, "{\"status\":0,\"hypotheses\":[]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NONE);
+ EXPECT_EQ(0U, result().hypotheses.size());
+
+ // Http failure case.
+ CreateAndTestRequest(false, std::string());
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NETWORK);
+ EXPECT_EQ(0U, result().hypotheses.size());
+
+ // Invalid status case.
+ CreateAndTestRequest(true, "{\"status\":\"invalid\",\"hypotheses\":[]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NETWORK);
+ EXPECT_EQ(0U, result().hypotheses.size());
+
+ // Server-side error case.
+ CreateAndTestRequest(true, "{\"status\":1,\"hypotheses\":[]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NETWORK);
+ EXPECT_EQ(0U, result().hypotheses.size());
+
+ // Malformed JSON case.
+ CreateAndTestRequest(true, "{\"status\":0,\"hypotheses\":"
+ "[{\"unknownkey\":\"hello\"}]}");
+ EXPECT_EQ(error_, SPEECH_RECOGNITION_ERROR_NETWORK);
+ EXPECT_EQ(0U, result().hypotheses.size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/google_streaming_remote_engine.cc b/chromium/content/browser/speech/google_streaming_remote_engine.cc
new file mode 100644
index 00000000000..beacb22be94
--- /dev/null
+++ b/chromium/content/browser/speech/google_streaming_remote_engine.cc
@@ -0,0 +1,594 @@
+// 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 "content/browser/speech/google_streaming_remote_engine.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/proto/google_streaming_api.pb.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using net::URLFetcher;
+
+namespace content {
+namespace {
+
+const char kWebServiceBaseUrl[] =
+ "https://www.google.com/speech-api/full-duplex/v1";
+const char kDownstreamUrl[] = "/down?";
+const char kUpstreamUrl[] = "/up?";
+const int kAudioPacketIntervalMs = 100;
+const AudioEncoder::Codec kDefaultAudioCodec = AudioEncoder::CODEC_FLAC;
+
+// This mathces the maximum maxAlternatives value supported by the server.
+const uint32 kMaxMaxAlternatives = 30;
+
+// TODO(hans): Remove this and other logging when we don't need it anymore.
+void DumpResponse(const std::string& response) {
+ DVLOG(1) << "------------";
+ proto::SpeechRecognitionEvent event;
+ if (!event.ParseFromString(response)) {
+ DVLOG(1) << "Parse failed!";
+ return;
+ }
+ if (event.has_status())
+ DVLOG(1) << "STATUS\t" << event.status();
+ for (int i = 0; i < event.result_size(); ++i) {
+ DVLOG(1) << "RESULT #" << i << ":";
+ const proto::SpeechRecognitionResult& res = event.result(i);
+ if (res.has_final())
+ DVLOG(1) << " FINAL:\t" << res.final();
+ if (res.has_stability())
+ DVLOG(1) << " STABILITY:\t" << res.stability();
+ for (int j = 0; j < res.alternative_size(); ++j) {
+ const proto::SpeechRecognitionAlternative& alt =
+ res.alternative(j);
+ if (alt.has_confidence())
+ DVLOG(1) << " CONFIDENCE:\t" << alt.confidence();
+ if (alt.has_transcript())
+ DVLOG(1) << " TRANSCRIPT:\t" << alt.transcript();
+ }
+ }
+}
+
+std::string GetAPIKey() {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kSpeechRecognitionWebserviceKey)) {
+ DVLOG(1) << "GetAPIKey() used key from command-line.";
+ return command_line.GetSwitchValueASCII(
+ switches::kSpeechRecognitionWebserviceKey);
+ }
+
+ std::string api_key = google_apis::GetAPIKey();
+ if (api_key.empty())
+ DVLOG(1) << "GetAPIKey() returned empty string!";
+
+ return api_key;
+}
+
+} // namespace
+
+const int GoogleStreamingRemoteEngine::kUpstreamUrlFetcherIdForTests = 0;
+const int GoogleStreamingRemoteEngine::kDownstreamUrlFetcherIdForTests = 1;
+const int GoogleStreamingRemoteEngine::kWebserviceStatusNoError = 0;
+const int GoogleStreamingRemoteEngine::kWebserviceStatusErrorNoMatch = 5;
+
+GoogleStreamingRemoteEngine::GoogleStreamingRemoteEngine(
+ net::URLRequestContextGetter* context)
+ : url_context_(context),
+ previous_response_length_(0),
+ got_last_definitive_result_(false),
+ is_dispatching_event_(false),
+ state_(STATE_IDLE) {}
+
+GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {}
+
+void GoogleStreamingRemoteEngine::SetConfig(
+ const SpeechRecognitionEngineConfig& config) {
+ config_ = config;
+}
+
+void GoogleStreamingRemoteEngine::StartRecognition() {
+ FSMEventArgs event_args(EVENT_START_RECOGNITION);
+ DispatchEvent(event_args);
+}
+
+void GoogleStreamingRemoteEngine::EndRecognition() {
+ FSMEventArgs event_args(EVENT_END_RECOGNITION);
+ DispatchEvent(event_args);
+}
+
+void GoogleStreamingRemoteEngine::TakeAudioChunk(const AudioChunk& data) {
+ FSMEventArgs event_args(EVENT_AUDIO_CHUNK);
+ event_args.audio_data = &data;
+ DispatchEvent(event_args);
+}
+
+void GoogleStreamingRemoteEngine::AudioChunksEnded() {
+ FSMEventArgs event_args(EVENT_AUDIO_CHUNKS_ENDED);
+ DispatchEvent(event_args);
+}
+
+void GoogleStreamingRemoteEngine::OnURLFetchComplete(const URLFetcher* source) {
+ const bool kResponseComplete = true;
+ DispatchHTTPResponse(source, kResponseComplete);
+}
+
+void GoogleStreamingRemoteEngine::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 current, int64 total) {
+ const bool kPartialResponse = false;
+ DispatchHTTPResponse(source, kPartialResponse);
+}
+
+void GoogleStreamingRemoteEngine::DispatchHTTPResponse(const URLFetcher* source,
+ bool end_of_response) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(source);
+ const bool response_is_good = source->GetStatus().is_success() &&
+ source->GetResponseCode() == 200;
+ std::string response;
+ if (response_is_good)
+ source->GetResponseAsString(&response);
+ const size_t current_response_length = response.size();
+
+ DVLOG(1) << (source == downstream_fetcher_.get() ? "Downstream" : "Upstream")
+ << "HTTP, code: " << source->GetResponseCode()
+ << " length: " << current_response_length
+ << " eor: " << end_of_response;
+
+ // URLFetcher provides always the entire response buffer, but we are only
+ // interested in the fresh data introduced by the last chunk. Therefore, we
+ // drop the previous content we have already processed.
+ if (current_response_length != 0) {
+ DCHECK_GE(current_response_length, previous_response_length_);
+ response.erase(0, previous_response_length_);
+ previous_response_length_ = current_response_length;
+ }
+
+ if (!response_is_good && source == downstream_fetcher_.get()) {
+ DVLOG(1) << "Downstream error " << source->GetResponseCode();
+ FSMEventArgs event_args(EVENT_DOWNSTREAM_ERROR);
+ DispatchEvent(event_args);
+ return;
+ }
+ if (!response_is_good && source == upstream_fetcher_.get()) {
+ DVLOG(1) << "Upstream error " << source->GetResponseCode()
+ << " EOR " << end_of_response;
+ FSMEventArgs event_args(EVENT_UPSTREAM_ERROR);
+ DispatchEvent(event_args);
+ return;
+ }
+
+ // Ignore incoming data on the upstream connection.
+ if (source == upstream_fetcher_.get())
+ return;
+
+ DCHECK(response_is_good && source == downstream_fetcher_.get());
+
+ // The downstream response is organized in chunks, whose size is determined
+ // by a 4 bytes prefix, transparently handled by the ChunkedByteBuffer class.
+ // Such chunks are sent by the speech recognition webservice over the HTTP
+ // downstream channel using HTTP chunked transfer (unrelated to our chunks).
+ // This function is called every time an HTTP chunk is received by the
+ // url fetcher. However there isn't any particular matching beween our
+ // protocol chunks and HTTP chunks, in the sense that a single HTTP chunk can
+ // contain a portion of one chunk or even more chunks together.
+ chunked_byte_buffer_.Append(response);
+
+ // A single HTTP chunk can contain more than one data chunk, thus the while.
+ while (chunked_byte_buffer_.HasChunks()) {
+ FSMEventArgs event_args(EVENT_DOWNSTREAM_RESPONSE);
+ event_args.response = chunked_byte_buffer_.PopChunk();
+ DCHECK(event_args.response.get());
+ DumpResponse(std::string(event_args.response->begin(),
+ event_args.response->end()));
+ DispatchEvent(event_args);
+ }
+ if (end_of_response) {
+ FSMEventArgs event_args(EVENT_DOWNSTREAM_CLOSED);
+ DispatchEvent(event_args);
+ }
+}
+
+bool GoogleStreamingRemoteEngine::IsRecognitionPending() const {
+ DCHECK(CalledOnValidThread());
+ return state_ != STATE_IDLE;
+}
+
+int GoogleStreamingRemoteEngine::GetDesiredAudioChunkDurationMs() const {
+ return kAudioPacketIntervalMs;
+}
+
+// ----------------------- Core FSM implementation ---------------------------
+
+void GoogleStreamingRemoteEngine::DispatchEvent(
+ const FSMEventArgs& event_args) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LE(event_args.event, EVENT_MAX_VALUE);
+ DCHECK_LE(state_, STATE_MAX_VALUE);
+
+ // Event dispatching must be sequential, otherwise it will break all the rules
+ // and the assumptions of the finite state automata model.
+ DCHECK(!is_dispatching_event_);
+ is_dispatching_event_ = true;
+
+ state_ = ExecuteTransitionAndGetNextState(event_args);
+
+ is_dispatching_event_ = false;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::ExecuteTransitionAndGetNextState(
+ const FSMEventArgs& event_args) {
+ const FSMEvent event = event_args.event;
+ switch (state_) {
+ case STATE_IDLE:
+ switch (event) {
+ case EVENT_START_RECOGNITION:
+ return ConnectBothStreams(event_args);
+ case EVENT_END_RECOGNITION:
+ // Note AUDIO_CHUNK and AUDIO_END events can remain enqueued in case of
+ // abort, so we just silently drop them here.
+ case EVENT_AUDIO_CHUNK:
+ case EVENT_AUDIO_CHUNKS_ENDED:
+ // DOWNSTREAM_CLOSED can be received if we end up here due to an error.
+ case EVENT_DOWNSTREAM_CLOSED:
+ return DoNothing(event_args);
+ case EVENT_UPSTREAM_ERROR:
+ case EVENT_DOWNSTREAM_ERROR:
+ case EVENT_DOWNSTREAM_RESPONSE:
+ return NotFeasible(event_args);
+ }
+ break;
+ case STATE_BOTH_STREAMS_CONNECTED:
+ switch (event) {
+ case EVENT_AUDIO_CHUNK:
+ return TransmitAudioUpstream(event_args);
+ case EVENT_DOWNSTREAM_RESPONSE:
+ return ProcessDownstreamResponse(event_args);
+ case EVENT_AUDIO_CHUNKS_ENDED:
+ return CloseUpstreamAndWaitForResults(event_args);
+ case EVENT_END_RECOGNITION:
+ return AbortSilently(event_args);
+ case EVENT_UPSTREAM_ERROR:
+ case EVENT_DOWNSTREAM_ERROR:
+ case EVENT_DOWNSTREAM_CLOSED:
+ return AbortWithError(event_args);
+ case EVENT_START_RECOGNITION:
+ return NotFeasible(event_args);
+ }
+ break;
+ case STATE_WAITING_DOWNSTREAM_RESULTS:
+ switch (event) {
+ case EVENT_DOWNSTREAM_RESPONSE:
+ return ProcessDownstreamResponse(event_args);
+ case EVENT_DOWNSTREAM_CLOSED:
+ return RaiseNoMatchErrorIfGotNoResults(event_args);
+ case EVENT_END_RECOGNITION:
+ return AbortSilently(event_args);
+ case EVENT_UPSTREAM_ERROR:
+ case EVENT_DOWNSTREAM_ERROR:
+ return AbortWithError(event_args);
+ case EVENT_START_RECOGNITION:
+ case EVENT_AUDIO_CHUNK:
+ case EVENT_AUDIO_CHUNKS_ENDED:
+ return NotFeasible(event_args);
+ }
+ break;
+ }
+ return NotFeasible(event_args);
+}
+
+// ----------- Contract for all the FSM evolution functions below -------------
+// - Are guaranteed to be executed in the same thread (IO, except for tests);
+// - Are guaranteed to be not reentrant (themselves and each other);
+// - event_args members are guaranteed to be stable during the call;
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::ConnectBothStreams(const FSMEventArgs&) {
+ DCHECK(!upstream_fetcher_.get());
+ DCHECK(!downstream_fetcher_.get());
+
+ encoder_.reset(AudioEncoder::Create(kDefaultAudioCodec,
+ config_.audio_sample_rate,
+ config_.audio_num_bits_per_sample));
+ DCHECK(encoder_.get());
+ const std::string request_key = GenerateRequestKey();
+
+ // Setup downstream fetcher.
+ std::vector<std::string> downstream_args;
+ downstream_args.push_back(
+ "key=" + net::EscapeQueryParamValue(GetAPIKey(), true));
+ downstream_args.push_back("pair=" + request_key);
+ downstream_args.push_back("output=pb");
+ GURL downstream_url(std::string(kWebServiceBaseUrl) +
+ std::string(kDownstreamUrl) +
+ JoinString(downstream_args, '&'));
+
+ downstream_fetcher_.reset(URLFetcher::Create(
+ kDownstreamUrlFetcherIdForTests, downstream_url, URLFetcher::GET, this));
+ downstream_fetcher_->SetRequestContext(url_context_.get());
+ downstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ downstream_fetcher_->Start();
+
+ // Setup upstream fetcher.
+ // TODO(hans): Support for user-selected grammars.
+ std::vector<std::string> upstream_args;
+ upstream_args.push_back("key=" +
+ net::EscapeQueryParamValue(GetAPIKey(), true));
+ upstream_args.push_back("pair=" + request_key);
+ upstream_args.push_back("output=pb");
+ upstream_args.push_back(
+ "lang=" + net::EscapeQueryParamValue(GetAcceptedLanguages(), true));
+ upstream_args.push_back(
+ config_.filter_profanities ? "pFilter=2" : "pFilter=0");
+ if (config_.max_hypotheses > 0U) {
+ int max_alternatives = std::min(kMaxMaxAlternatives,
+ config_.max_hypotheses);
+ upstream_args.push_back("maxAlternatives=" +
+ base::UintToString(max_alternatives));
+ }
+ upstream_args.push_back("client=chromium");
+ if (!config_.hardware_info.empty()) {
+ upstream_args.push_back(
+ "xhw=" + net::EscapeQueryParamValue(config_.hardware_info, true));
+ }
+ if (config_.continuous)
+ upstream_args.push_back("continuous");
+ if (config_.interim_results)
+ upstream_args.push_back("interim");
+
+ GURL upstream_url(std::string(kWebServiceBaseUrl) +
+ std::string(kUpstreamUrl) +
+ JoinString(upstream_args, '&'));
+
+ upstream_fetcher_.reset(URLFetcher::Create(
+ kUpstreamUrlFetcherIdForTests, upstream_url, URLFetcher::POST, this));
+ upstream_fetcher_->SetChunkedUpload(encoder_->mime_type());
+ upstream_fetcher_->SetRequestContext(url_context_.get());
+ upstream_fetcher_->SetReferrer(config_.origin_url);
+ upstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ upstream_fetcher_->Start();
+ previous_response_length_ = 0;
+ return STATE_BOTH_STREAMS_CONNECTED;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::TransmitAudioUpstream(
+ const FSMEventArgs& event_args) {
+ DCHECK(upstream_fetcher_.get());
+ DCHECK(event_args.audio_data.get());
+ const AudioChunk& audio = *(event_args.audio_data.get());
+
+ DCHECK_EQ(audio.bytes_per_sample(), config_.audio_num_bits_per_sample / 8);
+ encoder_->Encode(audio);
+ scoped_refptr<AudioChunk> encoded_data(encoder_->GetEncodedDataAndClear());
+ upstream_fetcher_->AppendChunkToUpload(encoded_data->AsString(), false);
+ return state_;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::ProcessDownstreamResponse(
+ const FSMEventArgs& event_args) {
+ DCHECK(event_args.response.get());
+
+ proto::SpeechRecognitionEvent ws_event;
+ if (!ws_event.ParseFromString(std::string(event_args.response->begin(),
+ event_args.response->end())))
+ return AbortWithError(event_args);
+
+ // An empty (default) event is used to notify us that the upstream has
+ // been connected. Ignore.
+ if (!ws_event.result_size() && (!ws_event.has_status() ||
+ ws_event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS)) {
+ DVLOG(1) << "Received empty response";
+ return state_;
+ }
+
+ if (ws_event.has_status()) {
+ switch (ws_event.status()) {
+ case proto::SpeechRecognitionEvent::STATUS_SUCCESS:
+ break;
+ case proto::SpeechRecognitionEvent::STATUS_NO_SPEECH:
+ return Abort(SPEECH_RECOGNITION_ERROR_NO_SPEECH);
+ case proto::SpeechRecognitionEvent::STATUS_ABORTED:
+ return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
+ case proto::SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE:
+ return Abort(SPEECH_RECOGNITION_ERROR_AUDIO);
+ case proto::SpeechRecognitionEvent::STATUS_NETWORK:
+ return Abort(SPEECH_RECOGNITION_ERROR_NETWORK);
+ case proto::SpeechRecognitionEvent::STATUS_NOT_ALLOWED:
+ // TODO(hans): We need a better error code for this.
+ return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
+ case proto::SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED:
+ // TODO(hans): We need a better error code for this.
+ return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
+ case proto::SpeechRecognitionEvent::STATUS_BAD_GRAMMAR:
+ return Abort(SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR);
+ case proto::SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED:
+ // TODO(hans): We need a better error code for this.
+ return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
+ }
+ }
+
+ SpeechRecognitionResults results;
+ for (int i = 0; i < ws_event.result_size(); ++i) {
+ const proto::SpeechRecognitionResult& ws_result = ws_event.result(i);
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = !(ws_result.has_final() && ws_result.final());
+
+ if (!result.is_provisional)
+ got_last_definitive_result_ = true;
+
+ for (int j = 0; j < ws_result.alternative_size(); ++j) {
+ const proto::SpeechRecognitionAlternative& ws_alternative =
+ ws_result.alternative(j);
+ SpeechRecognitionHypothesis hypothesis;
+ if (ws_alternative.has_confidence())
+ hypothesis.confidence = ws_alternative.confidence();
+ else if (ws_result.has_stability())
+ hypothesis.confidence = ws_result.stability();
+ DCHECK(ws_alternative.has_transcript());
+ // TODO(hans): Perhaps the transcript should be required in the proto?
+ if (ws_alternative.has_transcript())
+ hypothesis.utterance = UTF8ToUTF16(ws_alternative.transcript());
+
+ result.hypotheses.push_back(hypothesis);
+ }
+ }
+
+ delegate()->OnSpeechRecognitionEngineResults(results);
+
+ return state_;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::RaiseNoMatchErrorIfGotNoResults(
+ const FSMEventArgs& event_args) {
+ if (!got_last_definitive_result_) {
+ // Provide an empty result to notify that recognition is ended with no
+ // errors, yet neither any further results.
+ delegate()->OnSpeechRecognitionEngineResults(SpeechRecognitionResults());
+ }
+ return AbortSilently(event_args);
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::CloseUpstreamAndWaitForResults(
+ const FSMEventArgs&) {
+ DCHECK(upstream_fetcher_.get());
+ DCHECK(encoder_.get());
+
+ DVLOG(1) << "Closing upstream.";
+
+ // The encoder requires a non-empty final buffer. So we encode a packet
+ // of silence in case encoder had no data already.
+ std::vector<short> samples(
+ config_.audio_sample_rate * kAudioPacketIntervalMs / 1000);
+ scoped_refptr<AudioChunk> dummy_chunk =
+ new AudioChunk(reinterpret_cast<uint8*>(&samples[0]),
+ samples.size() * sizeof(short),
+ encoder_->bits_per_sample() / 8);
+ encoder_->Encode(*dummy_chunk.get());
+ encoder_->Flush();
+ scoped_refptr<AudioChunk> encoded_dummy_data =
+ encoder_->GetEncodedDataAndClear();
+ DCHECK(!encoded_dummy_data->IsEmpty());
+ encoder_.reset();
+
+ upstream_fetcher_->AppendChunkToUpload(encoded_dummy_data->AsString(), true);
+ got_last_definitive_result_ = false;
+ return STATE_WAITING_DOWNSTREAM_RESULTS;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::CloseDownstream(const FSMEventArgs&) {
+ DCHECK(!upstream_fetcher_.get());
+ DCHECK(downstream_fetcher_.get());
+
+ DVLOG(1) << "Closing downstream.";
+ downstream_fetcher_.reset();
+ return STATE_IDLE;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::AbortSilently(const FSMEventArgs&) {
+ return Abort(SPEECH_RECOGNITION_ERROR_NONE);
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::AbortWithError(const FSMEventArgs&) {
+ return Abort(SPEECH_RECOGNITION_ERROR_NETWORK);
+}
+
+GoogleStreamingRemoteEngine::FSMState GoogleStreamingRemoteEngine::Abort(
+ SpeechRecognitionErrorCode error_code) {
+ DVLOG(1) << "Aborting with error " << error_code;
+
+ if (error_code != SPEECH_RECOGNITION_ERROR_NONE) {
+ delegate()->OnSpeechRecognitionEngineError(
+ SpeechRecognitionError(error_code));
+ }
+ downstream_fetcher_.reset();
+ upstream_fetcher_.reset();
+ encoder_.reset();
+ return STATE_IDLE;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs&) {
+ return state_;
+}
+
+GoogleStreamingRemoteEngine::FSMState
+GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs& event_args) {
+ NOTREACHED() << "Unfeasible event " << event_args.event
+ << " in state " << state_;
+ return state_;
+}
+
+std::string GoogleStreamingRemoteEngine::GetAcceptedLanguages() const {
+ std::string langs = config_.language;
+ if (langs.empty() && url_context_.get()) {
+ // If no language is provided then we use the first from the accepted
+ // language list. If this list is empty then it defaults to "en-US".
+ // Example of the contents of this list: "es,en-GB;q=0.8", ""
+ net::URLRequestContext* request_context =
+ url_context_->GetURLRequestContext();
+ DCHECK(request_context);
+ // TODO(pauljensen): GoogleStreamingRemoteEngine should be constructed with
+ // a reference to the HttpUserAgentSettings rather than accessing the
+ // accept language through the URLRequestContext.
+ std::string accepted_language_list = request_context->GetAcceptLanguage();
+ size_t separator = accepted_language_list.find_first_of(",;");
+ if (separator != std::string::npos)
+ langs = accepted_language_list.substr(0, separator);
+ }
+ if (langs.empty())
+ langs = "en-US";
+ return langs;
+}
+
+// TODO(primiano): Is there any utility in the codebase that already does this?
+std::string GoogleStreamingRemoteEngine::GenerateRequestKey() const {
+ const int64 kKeepLowBytes = GG_LONGLONG(0x00000000FFFFFFFF);
+ const int64 kKeepHighBytes = GG_LONGLONG(0xFFFFFFFF00000000);
+
+ // Just keep the least significant bits of timestamp, in order to reduce
+ // probability of collisions.
+ int64 key = (base::Time::Now().ToInternalValue() & kKeepLowBytes) |
+ (base::RandUint64() & kKeepHighBytes);
+ return base::HexEncode(reinterpret_cast<void*>(&key), sizeof(key));
+}
+
+GoogleStreamingRemoteEngine::FSMEventArgs::FSMEventArgs(FSMEvent event_value)
+ : event(event_value) {
+}
+
+GoogleStreamingRemoteEngine::FSMEventArgs::~FSMEventArgs() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/google_streaming_remote_engine.h b/chromium/content/browser/speech/google_streaming_remote_engine.h
new file mode 100644
index 00000000000..488971ba1a6
--- /dev/null
+++ b/chromium/content/browser/speech/google_streaming_remote_engine.h
@@ -0,0 +1,160 @@
+// 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 CONTENT_BROWSER_SPEECH_GOOGLE_STREAMING_REMOTE_ENGINE_H_
+#define CONTENT_BROWSER_SPEECH_GOOGLE_STREAMING_REMOTE_ENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/browser/speech/audio_encoder.h"
+#include "content/browser/speech/chunked_byte_buffer.h"
+#include "content/browser/speech/speech_recognition_engine.h"
+#include "content/common/content_export.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace content {
+
+class AudioChunk;
+struct SpeechRecognitionError;
+struct SpeechRecognitionResult;
+
+// Implements a SpeechRecognitionEngine supporting continuous recognition by
+// means of interaction with Google streaming speech recognition webservice.
+// More in details, this class establishes two HTTP(S) connections with the
+// webservice, for each session, herein called "upstream" and "downstream".
+// Audio chunks are sent on the upstream by means of a chunked HTTP POST upload.
+// Recognition results are retrieved in a full-duplex fashion (i.e. while
+// pushing audio on the upstream) on the downstream by means of a chunked
+// HTTP GET request. Pairing between the two stream is handled through a
+// randomly generated key, unique for each request, which is passed in the
+// &pair= arg to both stream request URLs.
+// In the case of a regular session, the upstream is closed when the audio
+// capture ends (notified through a |AudioChunksEnded| call) and the downstream
+// waits for a corresponding server closure (eventually some late results can
+// come after closing the upstream).
+// Both stream are guaranteed to be closed when |EndRecognition| call is issued.
+class CONTENT_EXPORT GoogleStreamingRemoteEngine
+ : public NON_EXPORTED_BASE(SpeechRecognitionEngine),
+ public net::URLFetcherDelegate,
+ public NON_EXPORTED_BASE(base::NonThreadSafe) {
+ public:
+ explicit GoogleStreamingRemoteEngine(net::URLRequestContextGetter* context);
+ virtual ~GoogleStreamingRemoteEngine();
+
+ // SpeechRecognitionEngine methods.
+ virtual void SetConfig(const SpeechRecognitionEngineConfig& config) OVERRIDE;
+ virtual void StartRecognition() OVERRIDE;
+ virtual void EndRecognition() OVERRIDE;
+ virtual void TakeAudioChunk(const AudioChunk& data) OVERRIDE;
+ virtual void AudioChunksEnded() OVERRIDE;
+ virtual bool IsRecognitionPending() const OVERRIDE;
+ virtual int GetDesiredAudioChunkDurationMs() const OVERRIDE;
+
+ // net::URLFetcherDelegate methods.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+ virtual void OnURLFetchDownloadProgress(const net::URLFetcher* source,
+ int64 current, int64 total) OVERRIDE;
+
+ private:
+ friend class GoogleStreamingRemoteEngineTest;
+
+ // IDs passed to URLFetcher::Create(). Used for testing.
+ static const int kUpstreamUrlFetcherIdForTests;
+ static const int kDownstreamUrlFetcherIdForTests;
+
+ // Response status codes from the speech recognition webservice.
+ static const int kWebserviceStatusNoError;
+ static const int kWebserviceStatusErrorNoMatch;
+
+ // Data types for the internal Finite State Machine (FSM).
+ enum FSMState {
+ STATE_IDLE = 0,
+ STATE_BOTH_STREAMS_CONNECTED,
+ STATE_WAITING_DOWNSTREAM_RESULTS,
+ STATE_MAX_VALUE = STATE_WAITING_DOWNSTREAM_RESULTS
+ };
+
+ enum FSMEvent {
+ EVENT_END_RECOGNITION = 0,
+ EVENT_START_RECOGNITION,
+ EVENT_AUDIO_CHUNK,
+ EVENT_AUDIO_CHUNKS_ENDED,
+ EVENT_UPSTREAM_ERROR,
+ EVENT_DOWNSTREAM_ERROR,
+ EVENT_DOWNSTREAM_RESPONSE,
+ EVENT_DOWNSTREAM_CLOSED,
+ EVENT_MAX_VALUE = EVENT_DOWNSTREAM_CLOSED
+ };
+
+ struct FSMEventArgs {
+ explicit FSMEventArgs(FSMEvent event_value);
+ ~FSMEventArgs();
+
+ FSMEvent event;
+
+ // In case of EVENT_AUDIO_CHUNK, holds the chunk pushed by |TakeAudioChunk|.
+ scoped_refptr<const AudioChunk> audio_data;
+
+ // In case of EVENT_DOWNSTREAM_RESPONSE, hold the current chunk bytes.
+ scoped_ptr<std::vector<uint8> > response;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FSMEventArgs);
+ };
+
+ // Invoked by both upstream and downstream URLFetcher callbacks to handle
+ // new chunk data, connection closed or errors notifications.
+ void DispatchHTTPResponse(const net::URLFetcher* source,
+ bool end_of_response);
+
+ // Entry point for pushing any new external event into the recognizer FSM.
+ void DispatchEvent(const FSMEventArgs& event_args);
+
+ // Defines the behavior of the recognizer FSM, selecting the appropriate
+ // transition according to the current state and event.
+ FSMState ExecuteTransitionAndGetNextState(const FSMEventArgs& event_args);
+
+ // The methods below handle transitions of the recognizer FSM.
+ FSMState ConnectBothStreams(const FSMEventArgs& event_args);
+ FSMState TransmitAudioUpstream(const FSMEventArgs& event_args);
+ FSMState ProcessDownstreamResponse(const FSMEventArgs& event_args);
+ FSMState RaiseNoMatchErrorIfGotNoResults(const FSMEventArgs& event_args);
+ FSMState CloseUpstreamAndWaitForResults(const FSMEventArgs& event_args);
+ FSMState CloseDownstream(const FSMEventArgs& event_args);
+ FSMState AbortSilently(const FSMEventArgs& event_args);
+ FSMState AbortWithError(const FSMEventArgs& event_args);
+ FSMState Abort(SpeechRecognitionErrorCode error);
+ FSMState DoNothing(const FSMEventArgs& event_args);
+ FSMState NotFeasible(const FSMEventArgs& event_args);
+
+ std::string GetAcceptedLanguages() const;
+ std::string GenerateRequestKey() const;
+
+ SpeechRecognitionEngineConfig config_;
+ scoped_ptr<net::URLFetcher> upstream_fetcher_;
+ scoped_ptr<net::URLFetcher> downstream_fetcher_;
+ scoped_refptr<net::URLRequestContextGetter> url_context_;
+ scoped_ptr<AudioEncoder> encoder_;
+ ChunkedByteBuffer chunked_byte_buffer_;
+ size_t previous_response_length_;
+ bool got_last_definitive_result_;
+ bool is_dispatching_event_;
+ FSMState state_;
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleStreamingRemoteEngine);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_GOOGLE_STREAMING_REMOTE_ENGINE_H_
diff --git a/chromium/content/browser/speech/google_streaming_remote_engine_unittest.cc b/chromium/content/browser/speech/google_streaming_remote_engine_unittest.cc
new file mode 100644
index 00000000000..08a3df7cdb7
--- /dev/null
+++ b/chromium/content/browser/speech/google_streaming_remote_engine_unittest.cc
@@ -0,0 +1,503 @@
+// 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 <queue>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/google_streaming_remote_engine.h"
+#include "content/browser/speech/proto/google_streaming_api.pb.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::URLRequestStatus;
+using net::TestURLFetcher;
+using net::TestURLFetcherFactory;
+
+namespace content {
+
+// Note: the terms upstream and downstream are from the point-of-view of the
+// client (engine_under_test_).
+
+class GoogleStreamingRemoteEngineTest : public SpeechRecognitionEngineDelegate,
+ public testing::Test {
+ public:
+ GoogleStreamingRemoteEngineTest()
+ : last_number_of_upstream_chunks_seen_(0U),
+ error_(SPEECH_RECOGNITION_ERROR_NONE) { }
+
+ // Creates a speech recognition request and invokes its URL fetcher delegate
+ // with the given test data.
+ void CreateAndTestRequest(bool success, const std::string& http_response);
+
+ // SpeechRecognitionRequestDelegate methods.
+ virtual void OnSpeechRecognitionEngineResults(
+ const SpeechRecognitionResults& results) OVERRIDE {
+ results_.push(results);
+ }
+ virtual void OnSpeechRecognitionEngineError(
+ const SpeechRecognitionError& error) OVERRIDE {
+ error_ = error.code;
+ }
+
+ // testing::Test methods.
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ protected:
+ enum DownstreamError {
+ DOWNSTREAM_ERROR_NONE,
+ DOWNSTREAM_ERROR_HTTP500,
+ DOWNSTREAM_ERROR_NETWORK,
+ DOWNSTREAM_ERROR_WEBSERVICE_NO_MATCH
+ };
+ static bool ResultsAreEqual(const SpeechRecognitionResults& a,
+ const SpeechRecognitionResults& b);
+ static std::string SerializeProtobufResponse(
+ const proto::SpeechRecognitionEvent& msg);
+ static std::string ToBigEndian32(uint32 value);
+
+ TestURLFetcher* GetUpstreamFetcher();
+ TestURLFetcher* GetDownstreamFetcher();
+ void StartMockRecognition();
+ void EndMockRecognition();
+ void InjectDummyAudioChunk();
+ size_t UpstreamChunksUploadedFromLastCall();
+ void ProvideMockProtoResultDownstream(
+ const proto::SpeechRecognitionEvent& result);
+ void ProvideMockResultDownstream(const SpeechRecognitionResult& result);
+ void ExpectResultsReceived(const SpeechRecognitionResults& result);
+ void CloseMockDownstream(DownstreamError error);
+
+ scoped_ptr<GoogleStreamingRemoteEngine> engine_under_test_;
+ TestURLFetcherFactory url_fetcher_factory_;
+ size_t last_number_of_upstream_chunks_seen_;
+ base::MessageLoop message_loop_;
+ std::string response_buffer_;
+ SpeechRecognitionErrorCode error_;
+ std::queue<SpeechRecognitionResults> results_;
+};
+
+TEST_F(GoogleStreamingRemoteEngineTest, SingleDefinitiveResult) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ // Inject some dummy audio chunks and check a corresponding chunked upload
+ // is performed every time on the server.
+ for (int i = 0; i < 3; ++i) {
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+ }
+
+ // Ensure that a final (empty) audio chunk is uploaded on chunks end.
+ engine_under_test_->AudioChunksEnded();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Simulate a protobuf message streamed from the server containing a single
+ // result with two hypotheses.
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = false;
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("hypothesis 1"), 0.1F));
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("hypothesis 2"), 0.2F));
+
+ ProvideMockResultDownstream(result);
+ ExpectResultsReceived(results);
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Ensure everything is closed cleanly after the downstream is closed.
+ CloseMockDownstream(DOWNSTREAM_ERROR_NONE);
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, SeveralStreamingResults) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ for (int i = 0; i < 4; ++i) {
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = (i % 2 == 0); // Alternate result types.
+ float confidence = result.is_provisional ? 0.0F : (i * 0.1F);
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("hypothesis"), confidence));
+
+ ProvideMockResultDownstream(result);
+ ExpectResultsReceived(results);
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+ }
+
+ // Ensure that a final (empty) audio chunk is uploaded on chunks end.
+ engine_under_test_->AudioChunksEnded();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Simulate a final definitive result.
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = false;
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("The final result"), 1.0F));
+ ProvideMockResultDownstream(result);
+ ExpectResultsReceived(results);
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Ensure everything is closed cleanly after the downstream is closed.
+ CloseMockDownstream(DOWNSTREAM_ERROR_NONE);
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, NoFinalResultAfterAudioChunksEnded) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ // Simulate one pushed audio chunk.
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+
+ // Simulate the corresponding definitive result.
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("hypothesis"), 1.0F));
+ ProvideMockResultDownstream(result);
+ ExpectResultsReceived(results);
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Simulate a silent downstream closure after |AudioChunksEnded|.
+ engine_under_test_->AudioChunksEnded();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+ CloseMockDownstream(DOWNSTREAM_ERROR_NONE);
+
+ // Expect an empty result, aimed at notifying recognition ended with no
+ // actual results nor errors.
+ SpeechRecognitionResults empty_results;
+ ExpectResultsReceived(empty_results);
+
+ // Ensure everything is closed cleanly after the downstream is closed.
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, NoMatchError) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ for (int i = 0; i < 3; ++i)
+ InjectDummyAudioChunk();
+ engine_under_test_->AudioChunksEnded();
+ ASSERT_EQ(4U, UpstreamChunksUploadedFromLastCall());
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Simulate only a provisional result.
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = true;
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("The final result"), 0.0F));
+ ProvideMockResultDownstream(result);
+ ExpectResultsReceived(results);
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ CloseMockDownstream(DOWNSTREAM_ERROR_WEBSERVICE_NO_MATCH);
+
+ // Expect an empty result.
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ SpeechRecognitionResults empty_result;
+ ExpectResultsReceived(empty_result);
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, HTTPError) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+
+ // Close the downstream with a HTTP 500 error.
+ CloseMockDownstream(DOWNSTREAM_ERROR_HTTP500);
+
+ // Expect a SPEECH_RECOGNITION_ERROR_NETWORK error to be raised.
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NETWORK, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, NetworkError) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+
+ // Close the downstream fetcher simulating a network failure.
+ CloseMockDownstream(DOWNSTREAM_ERROR_NETWORK);
+
+ // Expect a SPEECH_RECOGNITION_ERROR_NETWORK error to be raised.
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NETWORK, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+TEST_F(GoogleStreamingRemoteEngineTest, Stability) {
+ StartMockRecognition();
+ ASSERT_TRUE(GetUpstreamFetcher());
+ ASSERT_EQ(0U, UpstreamChunksUploadedFromLastCall());
+
+ // Upload a dummy audio chunk.
+ InjectDummyAudioChunk();
+ ASSERT_EQ(1U, UpstreamChunksUploadedFromLastCall());
+ engine_under_test_->AudioChunksEnded();
+
+ // Simulate a protobuf message with an intermediate result without confidence,
+ // but with stability.
+ proto::SpeechRecognitionEvent proto_event;
+ proto_event.set_status(proto::SpeechRecognitionEvent::STATUS_SUCCESS);
+ proto::SpeechRecognitionResult* proto_result = proto_event.add_result();
+ proto_result->set_stability(0.5);
+ proto::SpeechRecognitionAlternative *proto_alternative =
+ proto_result->add_alternative();
+ proto_alternative->set_transcript("foo");
+ ProvideMockProtoResultDownstream(proto_event);
+
+ // Set up expectations.
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ result.is_provisional = true;
+ result.hypotheses.push_back(
+ SpeechRecognitionHypothesis(UTF8ToUTF16("foo"), 0.5));
+
+ // Check that the protobuf generated the expected result.
+ ExpectResultsReceived(results);
+
+ // Since it was a provisional result, recognition is still pending.
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ // Shut down.
+ CloseMockDownstream(DOWNSTREAM_ERROR_NONE);
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+ EndMockRecognition();
+
+ // Since there was no final result, we get an empty "no match" result.
+ SpeechRecognitionResults empty_result;
+ ExpectResultsReceived(empty_result);
+ ASSERT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ ASSERT_EQ(0U, results_.size());
+}
+
+void GoogleStreamingRemoteEngineTest::SetUp() {
+ engine_under_test_.reset(
+ new GoogleStreamingRemoteEngine(NULL /*URLRequestContextGetter*/));
+ engine_under_test_->set_delegate(this);
+}
+
+void GoogleStreamingRemoteEngineTest::TearDown() {
+ engine_under_test_.reset();
+}
+
+TestURLFetcher* GoogleStreamingRemoteEngineTest::GetUpstreamFetcher() {
+ return url_fetcher_factory_.GetFetcherByID(
+ GoogleStreamingRemoteEngine::kUpstreamUrlFetcherIdForTests);
+}
+
+TestURLFetcher* GoogleStreamingRemoteEngineTest::GetDownstreamFetcher() {
+ return url_fetcher_factory_.GetFetcherByID(
+ GoogleStreamingRemoteEngine::kDownstreamUrlFetcherIdForTests);
+}
+
+// Starts recognition on the engine, ensuring that both stream fetchers are
+// created.
+void GoogleStreamingRemoteEngineTest::StartMockRecognition() {
+ DCHECK(engine_under_test_.get());
+
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+
+ engine_under_test_->StartRecognition();
+ ASSERT_TRUE(engine_under_test_->IsRecognitionPending());
+
+ TestURLFetcher* upstream_fetcher = GetUpstreamFetcher();
+ ASSERT_TRUE(upstream_fetcher);
+ upstream_fetcher->set_url(upstream_fetcher->GetOriginalURL());
+
+ TestURLFetcher* downstream_fetcher = GetDownstreamFetcher();
+ ASSERT_TRUE(downstream_fetcher);
+ downstream_fetcher->set_url(downstream_fetcher->GetOriginalURL());
+}
+
+void GoogleStreamingRemoteEngineTest::EndMockRecognition() {
+ DCHECK(engine_under_test_.get());
+ engine_under_test_->EndRecognition();
+ ASSERT_FALSE(engine_under_test_->IsRecognitionPending());
+
+ // TODO(primiano): In order to be very pedantic we should check that both the
+ // upstream and downstream URL fetchers have been disposed at this time.
+ // Unfortunately it seems that there is no direct way to detect (in tests)
+ // if a url_fetcher has been freed or not, since they are not automatically
+ // de-registered from the TestURLFetcherFactory on destruction.
+}
+
+void GoogleStreamingRemoteEngineTest::InjectDummyAudioChunk() {
+ unsigned char dummy_audio_buffer_data[2] = {'\0', '\0'};
+ scoped_refptr<AudioChunk> dummy_audio_chunk(
+ new AudioChunk(&dummy_audio_buffer_data[0],
+ sizeof(dummy_audio_buffer_data),
+ 2 /* bytes per sample */));
+ DCHECK(engine_under_test_.get());
+ engine_under_test_->TakeAudioChunk(*dummy_audio_chunk.get());
+}
+
+size_t GoogleStreamingRemoteEngineTest::UpstreamChunksUploadedFromLastCall() {
+ TestURLFetcher* upstream_fetcher = GetUpstreamFetcher();
+ DCHECK(upstream_fetcher);
+ const size_t number_of_chunks = upstream_fetcher->upload_chunks().size();
+ DCHECK_GE(number_of_chunks, last_number_of_upstream_chunks_seen_);
+ const size_t new_chunks = number_of_chunks -
+ last_number_of_upstream_chunks_seen_;
+ last_number_of_upstream_chunks_seen_ = number_of_chunks;
+ return new_chunks;
+}
+
+void GoogleStreamingRemoteEngineTest::ProvideMockProtoResultDownstream(
+ const proto::SpeechRecognitionEvent& result) {
+ TestURLFetcher* downstream_fetcher = GetDownstreamFetcher();
+
+ ASSERT_TRUE(downstream_fetcher);
+ downstream_fetcher->set_status(URLRequestStatus(/* default=SUCCESS */));
+ downstream_fetcher->set_response_code(200);
+
+ std::string response_string = SerializeProtobufResponse(result);
+ response_buffer_.append(response_string);
+ downstream_fetcher->SetResponseString(response_buffer_);
+ downstream_fetcher->delegate()->OnURLFetchDownloadProgress(
+ downstream_fetcher,
+ response_buffer_.size(),
+ -1 /* total response length not used */);
+}
+
+void GoogleStreamingRemoteEngineTest::ProvideMockResultDownstream(
+ const SpeechRecognitionResult& result) {
+ proto::SpeechRecognitionEvent proto_event;
+ proto_event.set_status(proto::SpeechRecognitionEvent::STATUS_SUCCESS);
+ proto::SpeechRecognitionResult* proto_result = proto_event.add_result();
+ proto_result->set_final(!result.is_provisional);
+ for (size_t i = 0; i < result.hypotheses.size(); ++i) {
+ proto::SpeechRecognitionAlternative* proto_alternative =
+ proto_result->add_alternative();
+ const SpeechRecognitionHypothesis& hypothesis = result.hypotheses[i];
+ proto_alternative->set_confidence(hypothesis.confidence);
+ proto_alternative->set_transcript(UTF16ToUTF8(hypothesis.utterance));
+ }
+ ProvideMockProtoResultDownstream(proto_event);
+}
+
+void GoogleStreamingRemoteEngineTest::CloseMockDownstream(
+ DownstreamError error) {
+ TestURLFetcher* downstream_fetcher = GetDownstreamFetcher();
+ ASSERT_TRUE(downstream_fetcher);
+
+ const URLRequestStatus::Status fetcher_status =
+ (error == DOWNSTREAM_ERROR_NETWORK) ? URLRequestStatus::FAILED :
+ URLRequestStatus::SUCCESS;
+ downstream_fetcher->set_status(URLRequestStatus(fetcher_status, 0));
+ downstream_fetcher->set_response_code(
+ (error == DOWNSTREAM_ERROR_HTTP500) ? 500 : 200);
+
+ if (error == DOWNSTREAM_ERROR_WEBSERVICE_NO_MATCH) {
+ // Send empty response.
+ proto::SpeechRecognitionEvent response;
+ response_buffer_.append(SerializeProtobufResponse(response));
+ }
+ downstream_fetcher->SetResponseString(response_buffer_);
+ downstream_fetcher->delegate()->OnURLFetchComplete(downstream_fetcher);
+}
+
+void GoogleStreamingRemoteEngineTest::ExpectResultsReceived(
+ const SpeechRecognitionResults& results) {
+ ASSERT_GE(1U, results_.size());
+ ASSERT_TRUE(ResultsAreEqual(results, results_.front()));
+ results_.pop();
+}
+
+bool GoogleStreamingRemoteEngineTest::ResultsAreEqual(
+ const SpeechRecognitionResults& a, const SpeechRecognitionResults& b) {
+ if (a.size() != b.size())
+ return false;
+
+ SpeechRecognitionResults::const_iterator it_a = a.begin();
+ SpeechRecognitionResults::const_iterator it_b = b.begin();
+ for (; it_a != a.end() && it_b != b.end(); ++it_a, ++it_b) {
+ if (it_a->is_provisional != it_b->is_provisional ||
+ it_a->hypotheses.size() != it_b->hypotheses.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < it_a->hypotheses.size(); ++i) {
+ const SpeechRecognitionHypothesis& hyp_a = it_a->hypotheses[i];
+ const SpeechRecognitionHypothesis& hyp_b = it_b->hypotheses[i];
+ if (hyp_a.utterance != hyp_b.utterance ||
+ hyp_a.confidence != hyp_b.confidence) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+std::string GoogleStreamingRemoteEngineTest::SerializeProtobufResponse(
+ const proto::SpeechRecognitionEvent& msg) {
+ std::string msg_string;
+ msg.SerializeToString(&msg_string);
+
+ // Prepend 4 byte prefix length indication to the protobuf message as
+ // envisaged by the google streaming recognition webservice protocol.
+ msg_string.insert(0, ToBigEndian32(msg_string.size()));
+ return msg_string;
+}
+
+std::string GoogleStreamingRemoteEngineTest::ToBigEndian32(uint32 value) {
+ char raw_data[4];
+ raw_data[0] = static_cast<uint8>((value >> 24) & 0xFF);
+ raw_data[1] = static_cast<uint8>((value >> 16) & 0xFF);
+ raw_data[2] = static_cast<uint8>((value >> 8) & 0xFF);
+ raw_data[3] = static_cast<uint8>(value & 0xFF);
+ return std::string(raw_data, sizeof(raw_data));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/input_tag_speech_dispatcher_host.cc b/chromium/content/browser/speech/input_tag_speech_dispatcher_host.cc
new file mode 100644
index 00000000000..4a3af54ef37
--- /dev/null
+++ b/chromium/content/browser/speech/input_tag_speech_dispatcher_host.cc
@@ -0,0 +1,210 @@
+// 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 "content/browser/speech/input_tag_speech_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/speech/speech_recognition_manager_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/speech_recognition_messages.h"
+#include "content/public/browser/speech_recognition_manager_delegate.h"
+#include "content/public/browser/speech_recognition_session_config.h"
+#include "content/public/browser/speech_recognition_session_context.h"
+
+namespace {
+const uint32 kMaxHypothesesForSpeechInputTag = 6;
+}
+
+namespace content {
+
+InputTagSpeechDispatcherHost::InputTagSpeechDispatcherHost(
+ bool guest,
+ int render_process_id,
+ net::URLRequestContextGetter* url_request_context_getter)
+ : guest_(guest),
+ render_process_id_(render_process_id),
+ url_request_context_getter_(url_request_context_getter) {
+ // Do not add any non-trivial initialization here, instead do it lazily when
+ // required (e.g. see the method |SpeechRecognitionManager::GetInstance()|) or
+ // add an Init() method.
+}
+
+InputTagSpeechDispatcherHost::~InputTagSpeechDispatcherHost() {
+ SpeechRecognitionManager::GetInstance()->AbortAllSessionsForListener(this);
+}
+
+bool InputTagSpeechDispatcherHost::OnMessageReceived(
+ const IPC::Message& message, bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(InputTagSpeechDispatcherHost, message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_StartRecognition,
+ OnStartRecognition)
+ IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_CancelRecognition,
+ OnCancelRecognition)
+ IPC_MESSAGE_HANDLER(InputTagSpeechHostMsg_StopRecording,
+ OnStopRecording)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void InputTagSpeechDispatcherHost::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ if (message.type() == InputTagSpeechHostMsg_StartRecognition::ID)
+ *thread = BrowserThread::UI;
+}
+
+void InputTagSpeechDispatcherHost::OnStartRecognition(
+ const InputTagSpeechHostMsg_StartRecognition_Params& params) {
+ InputTagSpeechHostMsg_StartRecognition_Params input_params(params);
+ int render_process_id = render_process_id_;
+ // The chrome layer is mostly oblivious to BrowserPlugin guests and so it
+ // cannot correctly place the speech bubble relative to a guest. Thus, we
+ // set up the speech recognition context relative to the embedder.
+ int guest_render_view_id = 0;
+ if (guest_) {
+ RenderViewHostImpl* render_view_host =
+ RenderViewHostImpl::FromID(render_process_id_, params.render_view_id);
+ WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
+ WebContents::FromRenderViewHost(render_view_host));
+ BrowserPluginGuest* guest = web_contents->GetBrowserPluginGuest();
+ input_params.element_rect.set_origin(
+ guest->GetScreenCoordinates(input_params.element_rect.origin()));
+ guest_render_view_id = params.render_view_id;
+ render_process_id =
+ guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
+ input_params.render_view_id =
+ guest->embedder_web_contents()->GetRoutingID();
+ }
+ bool filter_profanities =
+ SpeechRecognitionManagerImpl::GetInstance() &&
+ SpeechRecognitionManagerImpl::GetInstance()->delegate() &&
+ SpeechRecognitionManagerImpl::GetInstance()->delegate()->
+ FilterProfanities(render_process_id_);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &InputTagSpeechDispatcherHost::StartRecognitionOnIO,
+ this,
+ render_process_id,
+ guest_render_view_id,
+ input_params,
+ filter_profanities));
+}
+
+void InputTagSpeechDispatcherHost::StartRecognitionOnIO(
+ int render_process_id,
+ int guest_render_view_id,
+ const InputTagSpeechHostMsg_StartRecognition_Params& params,
+ bool filter_profanities) {
+ SpeechRecognitionSessionContext context;
+ context.render_process_id = render_process_id;
+ context.render_view_id = params.render_view_id;
+ context.guest_render_view_id = guest_render_view_id;
+ context.request_id = params.request_id;
+ context.element_rect = params.element_rect;
+
+ SpeechRecognitionSessionConfig config;
+ config.language = params.language;
+ if (!params.grammar.empty()) {
+ config.grammars.push_back(SpeechRecognitionGrammar(params.grammar));
+ }
+ config.max_hypotheses = kMaxHypothesesForSpeechInputTag;
+ config.origin_url = params.origin_url;
+ config.initial_context = context;
+ config.url_request_context_getter = url_request_context_getter_.get();
+ config.filter_profanities = filter_profanities;
+ config.event_listener = this;
+
+ int session_id = SpeechRecognitionManager::GetInstance()->CreateSession(
+ config);
+ DCHECK_NE(session_id, SpeechRecognitionManager::kSessionIDInvalid);
+ SpeechRecognitionManager::GetInstance()->StartSession(session_id);
+}
+
+void InputTagSpeechDispatcherHost::OnCancelRecognition(int render_view_id,
+ int request_id) {
+ int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
+ render_process_id_, render_view_id, request_id);
+
+ // The renderer might provide an invalid |request_id| if the session was not
+ // started as expected, e.g., due to unsatisfied security requirements.
+ if (session_id != SpeechRecognitionManager::kSessionIDInvalid)
+ SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
+}
+
+void InputTagSpeechDispatcherHost::OnStopRecording(int render_view_id,
+ int request_id) {
+ int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
+ render_process_id_, render_view_id, request_id);
+
+ // The renderer might provide an invalid |request_id| if the session was not
+ // started as expected, e.g., due to unsatisfied security requirements.
+ if (session_id != SpeechRecognitionManager::kSessionIDInvalid) {
+ SpeechRecognitionManager::GetInstance()->StopAudioCaptureForSession(
+ session_id);
+ }
+}
+
+// -------- SpeechRecognitionEventListener interface implementation -----------
+void InputTagSpeechDispatcherHost::OnRecognitionResults(
+ int session_id,
+ const SpeechRecognitionResults& results) {
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionResults enter";
+
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+
+ int render_view_id = context.guest_render_view_id ?
+ context.guest_render_view_id : context.render_view_id;
+ Send(new InputTagSpeechMsg_SetRecognitionResults(
+ render_view_id,
+ context.request_id,
+ results));
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionResults exit";
+}
+
+void InputTagSpeechDispatcherHost::OnAudioEnd(int session_id) {
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnAudioEnd enter";
+
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ int render_view_id = context.guest_render_view_id ?
+ context.guest_render_view_id : context.render_view_id;
+ Send(new InputTagSpeechMsg_RecordingComplete(render_view_id,
+ context.request_id));
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnAudioEnd exit";
+}
+
+void InputTagSpeechDispatcherHost::OnRecognitionEnd(int session_id) {
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionEnd enter";
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ int render_view_id = context.guest_render_view_id ?
+ context.guest_render_view_id : context.render_view_id;
+ Send(new InputTagSpeechMsg_RecognitionComplete(render_view_id,
+ context.request_id));
+ DVLOG(1) << "InputTagSpeechDispatcherHost::OnRecognitionEnd exit";
+}
+
+// The events below are currently not used by x-webkit-speech implementation.
+void InputTagSpeechDispatcherHost::OnRecognitionStart(int session_id) {}
+void InputTagSpeechDispatcherHost::OnAudioStart(int session_id) {}
+void InputTagSpeechDispatcherHost::OnSoundStart(int session_id) {}
+void InputTagSpeechDispatcherHost::OnSoundEnd(int session_id) {}
+void InputTagSpeechDispatcherHost::OnRecognitionError(
+ int session_id,
+ const SpeechRecognitionError& error) {}
+void InputTagSpeechDispatcherHost::OnAudioLevelsChange(
+ int session_id, float volume, float noise_volume) {}
+void InputTagSpeechDispatcherHost::OnEnvironmentEstimationComplete(
+ int session_id) {}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/input_tag_speech_dispatcher_host.h b/chromium/content/browser/speech/input_tag_speech_dispatcher_host.h
new file mode 100644
index 00000000000..cb0bf3ba4f0
--- /dev/null
+++ b/chromium/content/browser/speech/input_tag_speech_dispatcher_host.h
@@ -0,0 +1,83 @@
+// 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 CONTENT_BROWSER_SPEECH_INPUT_TAG_SPEECH_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_SPEECH_INPUT_TAG_SPEECH_DISPATCHER_HOST_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "net/url_request/url_request_context_getter.h"
+
+struct InputTagSpeechHostMsg_StartRecognition_Params;
+
+namespace content {
+
+class SpeechRecognitionManager;
+
+// InputTagSpeechDispatcherHost is a delegate for Speech API messages used by
+// RenderMessageFilter. Basically it acts as a proxy, relaying the events coming
+// from the SpeechRecognitionManager to IPC messages (and vice versa).
+// It's the complement of SpeechRecognitionDispatcher (owned by RenderView).
+class CONTENT_EXPORT InputTagSpeechDispatcherHost
+ : public BrowserMessageFilter,
+ public SpeechRecognitionEventListener {
+ public:
+ InputTagSpeechDispatcherHost(
+ bool guest,
+ int render_process_id,
+ net::URLRequestContextGetter* url_request_context_getter);
+
+ // SpeechRecognitionEventListener methods.
+ virtual void OnRecognitionStart(int session_id) OVERRIDE;
+ virtual void OnAudioStart(int session_id) OVERRIDE;
+ virtual void OnEnvironmentEstimationComplete(int session_id) OVERRIDE;
+ virtual void OnSoundStart(int session_id) OVERRIDE;
+ virtual void OnSoundEnd(int session_id) OVERRIDE;
+ virtual void OnAudioEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionResults(
+ int session_id,
+ const SpeechRecognitionResults& results) OVERRIDE;
+ virtual void OnRecognitionError(
+ int session_id,
+ const SpeechRecognitionError& error) OVERRIDE;
+ virtual void OnAudioLevelsChange(int session_id,
+ float volume,
+ float noise_volume) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+
+ private:
+ virtual ~InputTagSpeechDispatcherHost();
+
+ void OnStartRecognition(
+ const InputTagSpeechHostMsg_StartRecognition_Params& params);
+ void OnCancelRecognition(int render_view_id, int request_id);
+ void OnStopRecording(int render_view_id, int request_id);
+
+ void StartRecognitionOnIO(
+ int render_process_id,
+ int guest_render_view_id,
+ const InputTagSpeechHostMsg_StartRecognition_Params& params,
+ bool filter_profanities);
+
+ bool guest_;
+ int render_process_id_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputTagSpeechDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_INPUT_TAG_SPEECH_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/speech/proto/google_streaming_api.proto b/chromium/content/browser/speech/proto/google_streaming_api.proto
new file mode 100644
index 00000000000..314051cd663
--- /dev/null
+++ b/chromium/content/browser/speech/proto/google_streaming_api.proto
@@ -0,0 +1,66 @@
+// 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.
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+// TODO(hans): Commented out due to compilation errors.
+// option cc_api_version = 2;
+
+package content.proto;
+
+// SpeechRecognitionEvent is the only message type sent to client.
+//
+// The first SpeechRecognitionEvent is an empty (default) message to indicate
+// as early as possible that the stream connection has been established.
+message SpeechRecognitionEvent {
+ enum StatusCode {
+ // Note: in JavaScript API SpeechRecognitionError 0 is "OTHER" error.
+ STATUS_SUCCESS = 0;
+ STATUS_NO_SPEECH = 1;
+ STATUS_ABORTED = 2;
+ STATUS_AUDIO_CAPTURE = 3;
+ STATUS_NETWORK = 4;
+ STATUS_NOT_ALLOWED = 5;
+ STATUS_SERVICE_NOT_ALLOWED = 6;
+ STATUS_BAD_GRAMMAR = 7;
+ STATUS_LANGUAGE_NOT_SUPPORTED = 8;
+ }
+ optional StatusCode status = 1 [default = STATUS_SUCCESS];
+
+ // May contain zero or one final=true result (the newly settled portion).
+ // May also contain zero or more final=false results.
+ // (Note that this differs from JavaScript API resultHistory in that no more
+ // than one final=true result is returned, so client must accumulate
+ // resultHistory by concatenating the final=true results.)
+ repeated SpeechRecognitionResult result = 2;
+};
+
+message SpeechRecognitionResult {
+ repeated SpeechRecognitionAlternative alternative = 1;
+
+ // True if this is the final time the speech service will return this
+ // particular SpeechRecognitionResult. If false, then this represents an
+ // interim result that could still be changed.
+ optional bool final = 2 [default = false];
+
+ // An estimate of the probability that the recognizer will not change its
+ // guess about this interim result. Values range from 0.0 (completely
+ // unstable) to 1.0 (completely stable). Note that this is not the same as
+ // "confidence", which estimate the probability that a recognition result
+ // is correct. This field is only provided for interim (final=false) results.
+ optional float stability = 3;
+};
+
+// Item in N-best list.
+message SpeechRecognitionAlternative {
+ // Spoken text.
+ optional string transcript = 1;
+
+ // The confidence estimate between 0.0 and 1.0. A higher number means the
+ // system is more confident that the recognition is correct.
+ // This field is typically provided only for the top hypothesis and only for
+ // final results.
+ optional float confidence = 2;
+}
diff --git a/chromium/content/browser/speech/proto/speech_proto.gyp b/chromium/content/browser/speech/proto/speech_proto.gyp
new file mode 100644
index 00000000000..f26021bfe23
--- /dev/null
+++ b/chromium/content/browser/speech/proto/speech_proto.gyp
@@ -0,0 +1,19 @@
+# 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.
+
+{
+ 'targets': [
+ {'target_name': 'speech_proto',
+ 'type': 'static_library',
+ 'sources': [
+ 'google_streaming_api.proto',
+ ],
+ 'variables': {
+ 'proto_in_dir': '.',
+ 'proto_out_dir': 'content/browser/speech/proto',
+ },
+ 'includes': [ '../../../../build/protoc.gypi' ],
+ },
+ ],
+}
diff --git a/chromium/content/browser/speech/speech_recognition_browsertest.cc b/chromium/content/browser/speech/speech_recognition_browsertest.cc
new file mode 100644
index 00000000000..e532bc7a35b
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_browsertest.cc
@@ -0,0 +1,136 @@
+// 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 "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/fake_speech_recognition_manager.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+class SpeechRecognitionBrowserTest : public ContentBrowserTest {
+ public:
+ // ContentBrowserTest methods
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ EXPECT_TRUE(!command_line->HasSwitch(switches::kDisableSpeechInput));
+ }
+
+ protected:
+ void LoadAndStartSpeechRecognitionTest(const char* filename) {
+ // The test page calculates the speech button's coordinate in the page on
+ // load & sets that coordinate in the URL fragment. We send mouse down & up
+ // events at that coordinate to trigger speech recognition.
+ GURL test_url = GetTestUrl("speech", filename);
+ NavigateToURL(shell(), test_url);
+
+ WebKit::WebMouseEvent mouse_event;
+ mouse_event.type = WebKit::WebInputEvent::MouseDown;
+ mouse_event.button = WebKit::WebMouseEvent::ButtonLeft;
+ mouse_event.x = 0;
+ mouse_event.y = 0;
+ mouse_event.clickCount = 1;
+ WebContents* web_contents = shell()->web_contents();
+
+ WindowedNotificationObserver observer(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(&web_contents->GetController()));
+ web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
+ mouse_event.type = WebKit::WebInputEvent::MouseUp;
+ web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
+ fake_speech_recognition_manager_.WaitForRecognitionStarted();
+
+ // We should wait for a navigation event, raised by the test page JS code
+ // upon the onwebkitspeechchange event, in all cases except when the
+ // speech response is inhibited.
+ if (fake_speech_recognition_manager_.should_send_fake_response())
+ observer.Wait();
+ }
+
+ void RunSpeechRecognitionTest(const char* filename) {
+ // The fake speech input manager would receive the speech input
+ // request and return the test string as recognition result. The test page
+ // then sets the URL fragment as 'pass' if it received the expected string.
+ LoadAndStartSpeechRecognitionTest(filename);
+
+ EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
+ }
+
+ // ContentBrowserTest methods.
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ fake_speech_recognition_manager_.set_should_send_fake_response(true);
+ speech_recognition_manager_ = &fake_speech_recognition_manager_;
+
+ // Inject the fake manager factory so that the test result is returned to
+ // the web page.
+ SpeechRecognitionManager::SetManagerForTests(speech_recognition_manager_);
+ }
+
+ virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
+ speech_recognition_manager_ = NULL;
+ }
+
+ FakeSpeechRecognitionManager fake_speech_recognition_manager_;
+
+ // This is used by the static |fakeManager|, and it is a pointer rather than a
+ // direct instance per the style guide.
+ static SpeechRecognitionManager* speech_recognition_manager_;
+};
+
+SpeechRecognitionManager*
+ SpeechRecognitionBrowserTest::speech_recognition_manager_ = NULL;
+
+// TODO(satish): Once this flakiness has been fixed, add a second test here to
+// check for sending many clicks in succession to the speech button and verify
+// that it doesn't cause any crash but works as expected. This should act as the
+// test for http://crbug.com/59173
+//
+// TODO(satish): Similar to above, once this flakiness has been fixed add
+// another test here to check that when speech recognition is in progress and
+// a renderer crashes, we get a call to
+// SpeechRecognitionManager::CancelAllRequestsWithDelegate.
+IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TestBasicRecognition) {
+ RunSpeechRecognitionTest("basic_recognition.html");
+ EXPECT_TRUE(fake_speech_recognition_manager_.grammar().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, GrammarAttribute) {
+ RunSpeechRecognitionTest("grammar_attribute.html");
+ EXPECT_EQ("http://example.com/grammar.xml",
+ fake_speech_recognition_manager_.grammar());
+}
+
+// Flaky on Linux, Windows and Mac http://crbug.com/140765.
+IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, DISABLED_TestCancelAll) {
+ // The test checks that the cancel-all callback gets issued when a session
+ // is pending, so don't send a fake response.
+ // We are not expecting a navigation event being raised from the JS of the
+ // test page JavaScript in this case.
+ fake_speech_recognition_manager_.set_should_send_fake_response(false);
+
+ LoadAndStartSpeechRecognitionTest("basic_recognition.html");
+
+ // Make the renderer crash. This should trigger
+ // InputTagSpeechDispatcherHost to cancel all pending sessions.
+ NavigateToURL(shell(), GURL(kChromeUICrashURL));
+
+ EXPECT_TRUE(fake_speech_recognition_manager_.did_cancel_all());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognition_dispatcher_host.cc b/chromium/content/browser/speech/speech_recognition_dispatcher_host.cc
new file mode 100644
index 00000000000..e506675c816
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_dispatcher_host.cc
@@ -0,0 +1,198 @@
+// 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 "content/browser/speech/speech_recognition_dispatcher_host.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "content/browser/speech/speech_recognition_manager_impl.h"
+#include "content/common/speech_recognition_messages.h"
+#include "content/public/browser/speech_recognition_manager_delegate.h"
+#include "content/public/browser/speech_recognition_session_config.h"
+#include "content/public/browser/speech_recognition_session_context.h"
+#include "content/public/common/content_switches.h"
+
+namespace content {
+
+SpeechRecognitionDispatcherHost::SpeechRecognitionDispatcherHost(
+ int render_process_id,
+ net::URLRequestContextGetter* context_getter)
+ : render_process_id_(render_process_id),
+ context_getter_(context_getter) {
+ // Do not add any non-trivial initialization here, instead do it lazily when
+ // required (e.g. see the method |SpeechRecognitionManager::GetInstance()|) or
+ // add an Init() method.
+}
+
+SpeechRecognitionDispatcherHost::~SpeechRecognitionDispatcherHost() {
+ SpeechRecognitionManager::GetInstance()->AbortAllSessionsForListener(this);
+}
+
+bool SpeechRecognitionDispatcherHost::OnMessageReceived(
+ const IPC::Message& message, bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(SpeechRecognitionDispatcherHost, message,
+ *message_was_ok)
+ IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_StartRequest,
+ OnStartRequest)
+ IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_AbortRequest,
+ OnAbortRequest)
+ IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_StopCaptureRequest,
+ OnStopCaptureRequest)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void SpeechRecognitionDispatcherHost::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ if (message.type() == SpeechRecognitionHostMsg_StartRequest::ID)
+ *thread = BrowserThread::UI;
+}
+
+void SpeechRecognitionDispatcherHost::OnStartRequest(
+ const SpeechRecognitionHostMsg_StartRequest_Params& params) {
+ bool filter_profanities =
+ SpeechRecognitionManagerImpl::GetInstance() &&
+ SpeechRecognitionManagerImpl::GetInstance()->delegate() &&
+ SpeechRecognitionManagerImpl::GetInstance()->delegate()->
+ FilterProfanities(render_process_id_);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionDispatcherHost::OnStartRequestOnIO,
+ this, params, filter_profanities));
+}
+
+void SpeechRecognitionDispatcherHost::OnStartRequestOnIO(
+ const SpeechRecognitionHostMsg_StartRequest_Params& params,
+ bool filter_profanities) {
+ SpeechRecognitionSessionContext context;
+ context.context_name = params.origin_url;
+ context.render_process_id = render_process_id_;
+ context.render_view_id = params.render_view_id;
+ context.request_id = params.request_id;
+ context.requested_by_page_element = false;
+
+ SpeechRecognitionSessionConfig config;
+ config.is_legacy_api = false;
+ config.language = params.language;
+ config.grammars = params.grammars;
+ config.max_hypotheses = params.max_hypotheses;
+ config.origin_url = params.origin_url;
+ config.initial_context = context;
+ config.url_request_context_getter = context_getter_.get();
+ config.filter_profanities = filter_profanities;
+ config.continuous = params.continuous;
+ config.interim_results = params.interim_results;
+ config.event_listener = this;
+
+ int session_id = SpeechRecognitionManager::GetInstance()->CreateSession(
+ config);
+ DCHECK_NE(session_id, SpeechRecognitionManager::kSessionIDInvalid);
+ SpeechRecognitionManager::GetInstance()->StartSession(session_id);
+}
+
+void SpeechRecognitionDispatcherHost::OnAbortRequest(int render_view_id,
+ int request_id) {
+ int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
+ render_process_id_, render_view_id, request_id);
+
+ // The renderer might provide an invalid |request_id| if the session was not
+ // started as expected, e.g., due to unsatisfied security requirements.
+ if (session_id != SpeechRecognitionManager::kSessionIDInvalid)
+ SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
+}
+
+void SpeechRecognitionDispatcherHost::OnStopCaptureRequest(
+ int render_view_id, int request_id) {
+ int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
+ render_process_id_, render_view_id, request_id);
+
+ // The renderer might provide an invalid |request_id| if the session was not
+ // started as expected, e.g., due to unsatisfied security requirements.
+ if (session_id != SpeechRecognitionManager::kSessionIDInvalid) {
+ SpeechRecognitionManager::GetInstance()->StopAudioCaptureForSession(
+ session_id);
+ }
+}
+
+// -------- SpeechRecognitionEventListener interface implementation -----------
+
+void SpeechRecognitionDispatcherHost::OnRecognitionStart(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_Started(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnAudioStart(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_AudioStarted(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnSoundStart(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_SoundStarted(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnSoundEnd(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_SoundEnded(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnAudioEnd(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_AudioEnded(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnRecognitionEnd(int session_id) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_Ended(context.render_view_id,
+ context.request_id));
+}
+
+void SpeechRecognitionDispatcherHost::OnRecognitionResults(
+ int session_id,
+ const SpeechRecognitionResults& results) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_ResultRetrieved(context.render_view_id,
+ context.request_id,
+ results));
+}
+
+void SpeechRecognitionDispatcherHost::OnRecognitionError(
+ int session_id,
+ const SpeechRecognitionError& error) {
+ const SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+ Send(new SpeechRecognitionMsg_ErrorOccurred(context.render_view_id,
+ context.request_id,
+ error));
+}
+
+// The events below are currently not used by speech JS APIs implementation.
+void SpeechRecognitionDispatcherHost::OnAudioLevelsChange(int session_id,
+ float volume,
+ float noise_volume) {
+}
+
+void SpeechRecognitionDispatcherHost::OnEnvironmentEstimationComplete(
+ int session_id) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognition_dispatcher_host.h b/chromium/content/browser/speech/speech_recognition_dispatcher_host.h
new file mode 100644
index 00000000000..1bc12a10580
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_dispatcher_host.h
@@ -0,0 +1,77 @@
+// 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_DISPATCHER_HOST_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_DISPATCHER_HOST_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "net/url_request/url_request_context_getter.h"
+
+struct SpeechRecognitionHostMsg_StartRequest_Params;
+
+namespace content {
+
+class SpeechRecognitionManager;
+struct SpeechRecognitionResult;
+
+// SpeechRecognitionDispatcherHost is a delegate for Speech API messages used by
+// RenderMessageFilter. Basically it acts as a proxy, relaying the events coming
+// from the SpeechRecognitionManager to IPC messages (and vice versa).
+// It's the complement of SpeechRecognitionDispatcher (owned by RenderView).
+class CONTENT_EXPORT SpeechRecognitionDispatcherHost
+ : public BrowserMessageFilter,
+ public SpeechRecognitionEventListener {
+ public:
+ SpeechRecognitionDispatcherHost(
+ int render_process_id,
+ net::URLRequestContextGetter* context_getter);
+
+ // SpeechRecognitionEventListener methods.
+ virtual void OnRecognitionStart(int session_id) OVERRIDE;
+ virtual void OnAudioStart(int session_id) OVERRIDE;
+ virtual void OnEnvironmentEstimationComplete(int session_id) OVERRIDE;
+ virtual void OnSoundStart(int session_id) OVERRIDE;
+ virtual void OnSoundEnd(int session_id) OVERRIDE;
+ virtual void OnAudioEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionResults(
+ int session_id,
+ const SpeechRecognitionResults& results) OVERRIDE;
+ virtual void OnRecognitionError(
+ int session_id,
+ const SpeechRecognitionError& error) OVERRIDE;
+ virtual void OnAudioLevelsChange(int session_id,
+ float volume,
+ float noise_volume) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) OVERRIDE;
+
+ private:
+ virtual ~SpeechRecognitionDispatcherHost();
+
+ void OnStartRequest(
+ const SpeechRecognitionHostMsg_StartRequest_Params& params);
+ void OnStartRequestOnIO(
+ const SpeechRecognitionHostMsg_StartRequest_Params& params,
+ bool filter_profanities);
+ void OnAbortRequest(int render_view_id, int request_id);
+ void OnStopCaptureRequest(int render_view_id, int request_id);
+
+ int render_process_id_;
+ scoped_refptr<net::URLRequestContextGetter> context_getter_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpeechRecognitionDispatcherHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_DISPATCHER_HOST_H_
diff --git a/chromium/content/browser/speech/speech_recognition_engine.cc b/chromium/content/browser/speech/speech_recognition_engine.cc
new file mode 100644
index 00000000000..53f0e91ce56
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_engine.cc
@@ -0,0 +1,27 @@
+// 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 "content/browser/speech/speech_recognition_engine.h"
+
+namespace {
+const int kDefaultConfigSampleRate = 8000;
+const int kDefaultConfigBitsPerSample = 16;
+const uint32 kDefaultMaxHypotheses = 1;
+} // namespace
+
+namespace content {
+
+SpeechRecognitionEngine::Config::Config()
+ : filter_profanities(false),
+ continuous(true),
+ interim_results(true),
+ max_hypotheses(kDefaultMaxHypotheses),
+ audio_sample_rate(kDefaultConfigSampleRate),
+ audio_num_bits_per_sample(kDefaultConfigBitsPerSample) {
+}
+
+SpeechRecognitionEngine::Config::~Config() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognition_engine.h b/chromium/content/browser/speech/speech_recognition_engine.h
new file mode 100644
index 00000000000..73ba26ec7e5
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_engine.h
@@ -0,0 +1,112 @@
+// 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_ENGINE_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_ENGINE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "content/common/content_export.h"
+#include "content/public/common/speech_recognition_grammar.h"
+#include "content/public/common/speech_recognition_result.h"
+
+namespace content {
+
+class AudioChunk;
+struct SpeechRecognitionError;
+
+// This interface models the basic contract that a speech recognition engine,
+// either working locally or relying on a remote web-service, must obey.
+// The expected call sequence for exported methods is:
+// StartRecognition Mandatory at beginning of SR.
+// TakeAudioChunk For every audio chunk pushed.
+// AudioChunksEnded Finalize the audio stream (omitted in case of errors).
+// EndRecognition Mandatory at end of SR (even on errors).
+// No delegate callbacks are allowed before StartRecognition or after
+// EndRecognition. If a recognition was started, the caller can free the
+// SpeechRecognitionEngine only after calling EndRecognition.
+class SpeechRecognitionEngine {
+ public:
+ // Interface for receiving callbacks from this object.
+ class Delegate {
+ public:
+ // Called whenever a result is retrieved. It might be issued several times,
+ // (e.g., in the case of continuous speech recognition engine
+ // implementations).
+ virtual void OnSpeechRecognitionEngineResults(
+ const SpeechRecognitionResults& results) = 0;
+ virtual void OnSpeechRecognitionEngineError(
+ const SpeechRecognitionError& error) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // Remote engine configuration.
+ struct CONTENT_EXPORT Config {
+ Config();
+ ~Config();
+
+ std::string language;
+ SpeechRecognitionGrammarArray grammars;
+ bool filter_profanities;
+ bool continuous;
+ bool interim_results;
+ uint32 max_hypotheses;
+ std::string hardware_info;
+ std::string origin_url;
+ int audio_sample_rate;
+ int audio_num_bits_per_sample;
+ };
+
+ virtual ~SpeechRecognitionEngine() {}
+
+ // Set/change the recognition engine configuration. It is not allowed to call
+ // this function while a recognition is ongoing.
+ virtual void SetConfig(const Config& config) = 0;
+
+ // Called when the speech recognition begins, before any TakeAudioChunk call.
+ virtual void StartRecognition() = 0;
+
+ // End any recognition activity and don't make any further callback.
+ // Must be always called to close the corresponding StartRecognition call,
+ // even in case of errors.
+ // No further TakeAudioChunk/AudioChunksEnded calls are allowed after this.
+ virtual void EndRecognition() = 0;
+
+ // Push a chunk of uncompressed audio data, where the chunk length agrees with
+ // GetDesiredAudioChunkDurationMs().
+ virtual void TakeAudioChunk(const AudioChunk& data) = 0;
+
+ // Notifies the engine that audio capture has completed and no more chunks
+ // will be pushed. The engine, however, can still provide further results
+ // using the audio chunks collected so far.
+ virtual void AudioChunksEnded() = 0;
+
+ // Checks wheter recognition of pushed audio data is pending.
+ virtual bool IsRecognitionPending() const = 0;
+
+ // Retrieves the desired duration, in milliseconds, of pushed AudioChunk(s).
+ virtual int GetDesiredAudioChunkDurationMs() const = 0;
+
+ // set_delegate detached from constructor for lazy dependency injection.
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ protected:
+ Delegate* delegate() const { return delegate_; }
+
+ private:
+ Delegate* delegate_;
+};
+
+// These typedefs are to workaround the issue with certain versions of
+// Visual Studio where it gets confused between multiple Delegate
+// classes and gives a C2500 error.
+typedef SpeechRecognitionEngine::Delegate SpeechRecognitionEngineDelegate;
+typedef SpeechRecognitionEngine::Config SpeechRecognitionEngineConfig;
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_ENGINE_H_
diff --git a/chromium/content/browser/speech/speech_recognition_manager_impl.cc b/chromium/content/browser/speech/speech_recognition_manager_impl.cc
new file mode 100644
index 00000000000..2c1b31735a9
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_manager_impl.cc
@@ -0,0 +1,683 @@
+// Copyright (c) 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 "content/browser/speech/speech_recognition_manager_impl.h"
+
+#include "base/bind.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
+#include "content/browser/speech/google_one_shot_remote_engine.h"
+#include "content/browser/speech/google_streaming_remote_engine.h"
+#include "content/browser/speech/speech_recognition_engine.h"
+#include "content/browser/speech/speech_recognizer_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "content/public/browser/speech_recognition_manager_delegate.h"
+#include "content/public/browser/speech_recognition_session_config.h"
+#include "content/public/browser/speech_recognition_session_context.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_manager_base.h"
+
+#if defined(OS_ANDROID)
+#include "content/browser/speech/speech_recognizer_impl_android.h"
+#endif
+
+using base::Callback;
+
+namespace content {
+
+SpeechRecognitionManager* SpeechRecognitionManager::manager_for_tests_;
+
+namespace {
+
+SpeechRecognitionManagerImpl* g_speech_recognition_manager_impl;
+
+void ShowAudioInputSettingsOnFileThread(media::AudioManager* audio_manager) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ audio_manager->ShowAudioInputSettings();
+}
+
+} // namespace
+
+SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
+ if (manager_for_tests_)
+ return manager_for_tests_;
+ return SpeechRecognitionManagerImpl::GetInstance();
+}
+
+void SpeechRecognitionManager::SetManagerForTests(
+ SpeechRecognitionManager* manager) {
+ manager_for_tests_ = manager;
+}
+
+SpeechRecognitionManagerImpl* SpeechRecognitionManagerImpl::GetInstance() {
+ return g_speech_recognition_manager_impl;
+}
+
+SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl(
+ media::AudioManager* audio_manager,
+ MediaStreamManager* media_stream_manager)
+ : audio_manager_(audio_manager),
+ media_stream_manager_(media_stream_manager),
+ primary_session_id_(kSessionIDInvalid),
+ last_session_id_(kSessionIDInvalid),
+ is_dispatching_event_(false),
+ delegate_(GetContentClient()->browser()->
+ GetSpeechRecognitionManagerDelegate()),
+ weak_factory_(this) {
+ DCHECK(!g_speech_recognition_manager_impl);
+ g_speech_recognition_manager_impl = this;
+}
+
+SpeechRecognitionManagerImpl::~SpeechRecognitionManagerImpl() {
+ DCHECK(g_speech_recognition_manager_impl);
+ g_speech_recognition_manager_impl = NULL;
+
+ for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
+ ++it) {
+ // MediaStreamUIProxy must be deleted on the IO thread.
+ BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
+ it->second->ui.release());
+ delete it->second;
+ }
+ sessions_.clear();
+}
+
+int SpeechRecognitionManagerImpl::CreateSession(
+ const SpeechRecognitionSessionConfig& config) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ const int session_id = GetNextSessionID();
+ DCHECK(!SessionExists(session_id));
+ // Set-up the new session.
+ Session* session = new Session();
+ sessions_[session_id] = session;
+ session->id = session_id;
+ session->config = config;
+ session->context = config.initial_context;
+
+ std::string hardware_info;
+ bool can_report_metrics = false;
+ if (delegate_)
+ delegate_->GetDiagnosticInformation(&can_report_metrics, &hardware_info);
+
+ // The legacy api cannot use continuous mode.
+ DCHECK(!config.is_legacy_api || !config.continuous);
+
+#if !defined(OS_ANDROID)
+ // A SpeechRecognitionEngine (and corresponding Config) is required only
+ // when using SpeechRecognizerImpl, which performs the audio capture and
+ // endpointing in the browser. This is not the case of Android where, not
+ // only the speech recognition, but also the audio capture and endpointing
+ // activities performed outside of the browser (delegated via JNI to the
+ // Android API implementation).
+
+ SpeechRecognitionEngineConfig remote_engine_config;
+ remote_engine_config.language = config.language;
+ remote_engine_config.grammars = config.grammars;
+ remote_engine_config.audio_sample_rate =
+ SpeechRecognizerImpl::kAudioSampleRate;
+ remote_engine_config.audio_num_bits_per_sample =
+ SpeechRecognizerImpl::kNumBitsPerAudioSample;
+ remote_engine_config.filter_profanities = config.filter_profanities;
+ remote_engine_config.continuous = config.continuous;
+ remote_engine_config.interim_results = config.interim_results;
+ remote_engine_config.max_hypotheses = config.max_hypotheses;
+ remote_engine_config.hardware_info = hardware_info;
+ remote_engine_config.origin_url =
+ can_report_metrics ? config.origin_url : std::string();
+
+ SpeechRecognitionEngine* google_remote_engine;
+ if (config.is_legacy_api) {
+ google_remote_engine =
+ new GoogleOneShotRemoteEngine(config.url_request_context_getter.get());
+ } else {
+ google_remote_engine = new GoogleStreamingRemoteEngine(
+ config.url_request_context_getter.get());
+ }
+
+ google_remote_engine->SetConfig(remote_engine_config);
+
+ session->recognizer = new SpeechRecognizerImpl(
+ this,
+ session_id,
+ !config.continuous,
+ google_remote_engine);
+#else
+ session->recognizer = new SpeechRecognizerImplAndroid(this, session_id);
+#endif
+ return session_id;
+}
+
+void SpeechRecognitionManagerImpl::StartSession(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ // If there is another active session, abort that.
+ if (primary_session_id_ != kSessionIDInvalid &&
+ primary_session_id_ != session_id) {
+ AbortSession(primary_session_id_);
+ }
+
+ primary_session_id_ = session_id;
+
+ if (delegate_) {
+ delegate_->CheckRecognitionIsAllowed(
+ session_id,
+ base::Bind(&SpeechRecognitionManagerImpl::RecognitionAllowedCallback,
+ weak_factory_.GetWeakPtr(),
+ session_id));
+ }
+}
+
+void SpeechRecognitionManagerImpl::RecognitionAllowedCallback(int session_id,
+ bool ask_user,
+ bool is_allowed) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (ask_user) {
+ SessionsTable::iterator iter = sessions_.find(session_id);
+ DCHECK(iter != sessions_.end());
+ SpeechRecognitionSessionContext& context = iter->second->context;
+ context.label = media_stream_manager_->MakeMediaAccessRequest(
+ context.render_process_id,
+ context.render_view_id,
+ context.request_id,
+ StreamOptions(MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_NO_SERVICE),
+ GURL(context.context_name),
+ base::Bind(
+ &SpeechRecognitionManagerImpl::MediaRequestPermissionCallback,
+ weak_factory_.GetWeakPtr(), session_id));
+ return;
+ }
+
+ if (is_allowed) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_START));
+ } else {
+ OnRecognitionError(session_id, SpeechRecognitionError(
+ SPEECH_RECOGNITION_ERROR_NOT_ALLOWED));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_ABORT));
+ }
+}
+
+void SpeechRecognitionManagerImpl::MediaRequestPermissionCallback(
+ int session_id,
+ const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUIProxy> stream_ui) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ SessionsTable::iterator iter = sessions_.find(session_id);
+ if (iter == sessions_.end())
+ return;
+
+ bool is_allowed = !devices.empty();
+ if (is_allowed) {
+ // Copy the approved devices array to the context for UI indication.
+ iter->second->context.devices = devices;
+
+ // Save the UI object.
+ iter->second->ui = stream_ui.Pass();
+ }
+
+ // Clear the label to indicate the request has been done.
+ iter->second->context.label.clear();
+
+ // Notify the recognition about the request result.
+ RecognitionAllowedCallback(iter->first, false, is_allowed);
+}
+
+void SpeechRecognitionManagerImpl::AbortSession(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ SessionsTable::iterator iter = sessions_.find(session_id);
+ iter->second->ui.reset();
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_ABORT));
+}
+
+void SpeechRecognitionManagerImpl::StopAudioCaptureForSession(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ SessionsTable::iterator iter = sessions_.find(session_id);
+ iter->second->ui.reset();
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_STOP_CAPTURE));
+}
+
+// Here begins the SpeechRecognitionEventListener interface implementation,
+// which will simply relay the events to the proper listener registered for the
+// particular session (most likely InputTagSpeechDispatcherHost) and to the
+// catch-all listener provided by the delegate (if any).
+
+void SpeechRecognitionManagerImpl::OnRecognitionStart(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ SessionsTable::iterator iter = sessions_.find(session_id);
+ if (iter->second->ui) {
+ // Notify the UI that the devices are being used.
+ iter->second->ui->OnStarted(base::Closure());
+ }
+
+ DCHECK_EQ(primary_session_id_, session_id);
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnRecognitionStart(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnRecognitionStart(session_id);
+}
+
+void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ DCHECK_EQ(primary_session_id_, session_id);
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnAudioStart(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnAudioStart(session_id);
+}
+
+void SpeechRecognitionManagerImpl::OnEnvironmentEstimationComplete(
+ int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ DCHECK_EQ(primary_session_id_, session_id);
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnEnvironmentEstimationComplete(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnEnvironmentEstimationComplete(session_id);
+}
+
+void SpeechRecognitionManagerImpl::OnSoundStart(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ DCHECK_EQ(primary_session_id_, session_id);
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnSoundStart(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnSoundStart(session_id);
+}
+
+void SpeechRecognitionManagerImpl::OnSoundEnd(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnSoundEnd(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnSoundEnd(session_id);
+}
+
+void SpeechRecognitionManagerImpl::OnAudioEnd(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnAudioEnd(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnAudioEnd(session_id);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_AUDIO_ENDED));
+}
+
+void SpeechRecognitionManagerImpl::OnRecognitionResults(
+ int session_id, const SpeechRecognitionResults& results) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnRecognitionResults(session_id, results);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnRecognitionResults(session_id, results);
+}
+
+void SpeechRecognitionManagerImpl::OnRecognitionError(
+ int session_id, const SpeechRecognitionError& error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnRecognitionError(session_id, error);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnRecognitionError(session_id, error);
+}
+
+void SpeechRecognitionManagerImpl::OnAudioLevelsChange(
+ int session_id, float volume, float noise_volume) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnAudioLevelsChange(session_id, volume, noise_volume);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnAudioLevelsChange(session_id, volume, noise_volume);
+}
+
+void SpeechRecognitionManagerImpl::OnRecognitionEnd(int session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!SessionExists(session_id))
+ return;
+
+ if (SpeechRecognitionEventListener* delegate_listener = GetDelegateListener())
+ delegate_listener->OnRecognitionEnd(session_id);
+ if (SpeechRecognitionEventListener* listener = GetListener(session_id))
+ listener->OnRecognitionEnd(session_id);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpeechRecognitionManagerImpl::DispatchEvent,
+ weak_factory_.GetWeakPtr(),
+ session_id,
+ EVENT_RECOGNITION_ENDED));
+}
+
+int SpeechRecognitionManagerImpl::GetSession(
+ int render_process_id, int render_view_id, int request_id) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ SessionsTable::const_iterator iter;
+ for(iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
+ const int session_id = iter->first;
+ const SpeechRecognitionSessionContext& context = iter->second->context;
+ if (context.render_process_id == render_process_id &&
+ context.render_view_id == render_view_id &&
+ context.request_id == request_id) {
+ return session_id;
+ }
+ }
+ return kSessionIDInvalid;
+}
+
+SpeechRecognitionSessionContext
+SpeechRecognitionManagerImpl::GetSessionContext(int session_id) const {
+ return GetSession(session_id)->context;
+}
+
+void SpeechRecognitionManagerImpl::AbortAllSessionsForListener(
+ SpeechRecognitionEventListener* listener) {
+ // This method gracefully destroys sessions for the listener. However, since
+ // the listener itself is likely to be destroyed after this call, we avoid
+ // dispatching further events to it, marking the |listener_is_active| flag.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
+ ++it) {
+ Session* session = it->second;
+ if (session->config.event_listener == listener) {
+ AbortSession(session->id);
+ session->listener_is_active = false;
+ }
+ }
+}
+
+void SpeechRecognitionManagerImpl::AbortAllSessionsForRenderView(
+ int render_process_id,
+ int render_view_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ for (SessionsTable::iterator it = sessions_.begin(); it != sessions_.end();
+ ++it) {
+ Session* session = it->second;
+ if (session->context.render_process_id == render_process_id &&
+ session->context.render_view_id == render_view_id) {
+ AbortSession(session->id);
+ }
+ }
+}
+
+// ----------------------- Core FSM implementation ---------------------------
+void SpeechRecognitionManagerImpl::DispatchEvent(int session_id,
+ FSMEvent event) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // There are some corner cases in which the session might be deleted (due to
+ // an EndRecognition event) between a request (e.g. Abort) and its dispatch.
+ if (!SessionExists(session_id))
+ return;
+
+ Session* session = GetSession(session_id);
+ FSMState session_state = GetSessionState(session_id);
+ DCHECK_LE(session_state, SESSION_STATE_MAX_VALUE);
+ DCHECK_LE(event, EVENT_MAX_VALUE);
+
+ // Event dispatching must be sequential, otherwise it will break all the rules
+ // and the assumptions of the finite state automata model.
+ DCHECK(!is_dispatching_event_);
+ is_dispatching_event_ = true;
+ ExecuteTransitionAndGetNextState(session, session_state, event);
+ is_dispatching_event_ = false;
+}
+
+// This FSM handles the evolution of each session, from the viewpoint of the
+// interaction with the user (that may be either the browser end-user which
+// interacts with UI bubbles, or JS developer intracting with JS methods).
+// All the events received by the SpeechRecognizer instances (one for each
+// session) are always routed to the SpeechRecognitionEventListener(s)
+// regardless the choices taken in this FSM.
+void SpeechRecognitionManagerImpl::ExecuteTransitionAndGetNextState(
+ Session* session, FSMState session_state, FSMEvent event) {
+ // Note: since we're not tracking the state of the recognizer object, rather
+ // we're directly retrieving it (through GetSessionState), we see its events
+ // (that are AUDIO_ENDED and RECOGNITION_ENDED) after its state evolution
+ // (e.g., when we receive the AUDIO_ENDED event, the recognizer has just
+ // completed the transition from CAPTURING_AUDIO to WAITING_FOR_RESULT, thus
+ // we perceive the AUDIO_ENDED event in WAITING_FOR_RESULT).
+ // This makes the code below a bit tricky but avoids a lot of code for
+ // tracking and reconstructing asynchronously the state of the recognizer.
+ switch (session_state) {
+ case SESSION_STATE_IDLE:
+ switch (event) {
+ case EVENT_START:
+ return SessionStart(*session);
+ case EVENT_ABORT:
+ return SessionAbort(*session);
+ case EVENT_RECOGNITION_ENDED:
+ return SessionDelete(session);
+ case EVENT_STOP_CAPTURE:
+ return SessionStopAudioCapture(*session);
+ case EVENT_AUDIO_ENDED:
+ return;
+ }
+ break;
+ case SESSION_STATE_CAPTURING_AUDIO:
+ switch (event) {
+ case EVENT_STOP_CAPTURE:
+ return SessionStopAudioCapture(*session);
+ case EVENT_ABORT:
+ return SessionAbort(*session);
+ case EVENT_START:
+ return;
+ case EVENT_AUDIO_ENDED:
+ case EVENT_RECOGNITION_ENDED:
+ return NotFeasible(*session, event);
+ }
+ break;
+ case SESSION_STATE_WAITING_FOR_RESULT:
+ switch (event) {
+ case EVENT_ABORT:
+ return SessionAbort(*session);
+ case EVENT_AUDIO_ENDED:
+ return ResetCapturingSessionId(*session);
+ case EVENT_START:
+ case EVENT_STOP_CAPTURE:
+ return;
+ case EVENT_RECOGNITION_ENDED:
+ return NotFeasible(*session, event);
+ }
+ break;
+ }
+ return NotFeasible(*session, event);
+}
+
+SpeechRecognitionManagerImpl::FSMState
+SpeechRecognitionManagerImpl::GetSessionState(int session_id) const {
+ Session* session = GetSession(session_id);
+ if (!session->recognizer.get() || !session->recognizer->IsActive())
+ return SESSION_STATE_IDLE;
+ if (session->recognizer->IsCapturingAudio())
+ return SESSION_STATE_CAPTURING_AUDIO;
+ return SESSION_STATE_WAITING_FOR_RESULT;
+}
+
+// ----------- Contract for all the FSM evolution functions below -------------
+// - Are guaranteed to be executed in the IO thread;
+// - Are guaranteed to be not reentrant (themselves and each other);
+
+void SpeechRecognitionManagerImpl::SessionStart(const Session& session) {
+ DCHECK_EQ(primary_session_id_, session.id);
+ const MediaStreamDevices& devices = session.context.devices;
+ std::string device_id;
+ if (devices.empty()) {
+ // From the ask_user=false path, use the default device.
+ // TODO(xians): Abort the session after we do not need to support this path
+ // anymore.
+ device_id = media::AudioManagerBase::kDefaultDeviceId;
+ } else {
+ // From the ask_user=true path, use the selected device.
+ DCHECK_EQ(1u, devices.size());
+ DCHECK_EQ(MEDIA_DEVICE_AUDIO_CAPTURE, devices.front().type);
+ device_id = devices.front().id;
+ }
+
+ session.recognizer->StartRecognition(device_id);
+}
+
+void SpeechRecognitionManagerImpl::SessionAbort(const Session& session) {
+ if (primary_session_id_ == session.id)
+ primary_session_id_ = kSessionIDInvalid;
+ DCHECK(session.recognizer.get());
+ session.recognizer->AbortRecognition();
+}
+
+void SpeechRecognitionManagerImpl::SessionStopAudioCapture(
+ const Session& session) {
+ DCHECK(session.recognizer.get());
+ session.recognizer->StopAudioCapture();
+}
+
+void SpeechRecognitionManagerImpl::ResetCapturingSessionId(
+ const Session& session) {
+ DCHECK_EQ(primary_session_id_, session.id);
+ primary_session_id_ = kSessionIDInvalid;
+}
+
+void SpeechRecognitionManagerImpl::SessionDelete(Session* session) {
+ DCHECK(session->recognizer.get() == NULL || !session->recognizer->IsActive());
+ if (primary_session_id_ == session->id)
+ primary_session_id_ = kSessionIDInvalid;
+ sessions_.erase(session->id);
+ delete session;
+}
+
+void SpeechRecognitionManagerImpl::NotFeasible(const Session& session,
+ FSMEvent event) {
+ NOTREACHED() << "Unfeasible event " << event
+ << " in state " << GetSessionState(session.id)
+ << " for session " << session.id;
+}
+
+int SpeechRecognitionManagerImpl::GetNextSessionID() {
+ ++last_session_id_;
+ // Deal with wrapping of last_session_id_. (How civilized).
+ if (last_session_id_ <= 0)
+ last_session_id_ = 1;
+ return last_session_id_;
+}
+
+bool SpeechRecognitionManagerImpl::SessionExists(int session_id) const {
+ return sessions_.find(session_id) != sessions_.end();
+}
+
+SpeechRecognitionManagerImpl::Session*
+SpeechRecognitionManagerImpl::GetSession(int session_id) const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ SessionsTable::const_iterator iter = sessions_.find(session_id);
+ DCHECK(iter != sessions_.end());
+ return iter->second;
+}
+
+SpeechRecognitionEventListener* SpeechRecognitionManagerImpl::GetListener(
+ int session_id) const {
+ Session* session = GetSession(session_id);
+ return session->listener_is_active ? session->config.event_listener : NULL;
+}
+
+SpeechRecognitionEventListener*
+SpeechRecognitionManagerImpl::GetDelegateListener() const {
+ return delegate_.get() ? delegate_->GetEventListener() : NULL;
+}
+
+const SpeechRecognitionSessionConfig&
+SpeechRecognitionManagerImpl::GetSessionConfig(int session_id) const {
+ return GetSession(session_id)->config;
+}
+
+bool SpeechRecognitionManagerImpl::HasAudioInputDevices() {
+ return audio_manager_->HasAudioInputDevices();
+}
+
+string16 SpeechRecognitionManagerImpl::GetAudioInputDeviceModel() {
+ return audio_manager_->GetAudioInputDeviceModel();
+}
+
+void SpeechRecognitionManagerImpl::ShowAudioInputSettings() {
+ // Since AudioManager::ShowAudioInputSettings can potentially launch external
+ // processes, do that in the FILE thread to not block the calling threads.
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ShowAudioInputSettingsOnFileThread,
+ audio_manager_));
+}
+
+SpeechRecognitionManagerImpl::Session::Session()
+ : id(kSessionIDInvalid),
+ listener_is_active(true) {
+}
+
+SpeechRecognitionManagerImpl::Session::~Session() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognition_manager_impl.h b/chromium/content/browser/speech/speech_recognition_manager_impl.h
new file mode 100644
index 00000000000..043257024cb
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognition_manager_impl.h
@@ -0,0 +1,193 @@
+// 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_MANAGER_IMPL_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_MANAGER_IMPL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/media/media_stream_requester.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "content/public/browser/speech_recognition_manager.h"
+#include "content/public/browser/speech_recognition_session_config.h"
+#include "content/public/browser/speech_recognition_session_context.h"
+#include "content/public/common/speech_recognition_error.h"
+
+namespace media {
+class AudioManager;
+}
+
+namespace content {
+class BrowserMainLoop;
+class MediaStreamManager;
+class MediaStreamUIProxy;
+class SpeechRecognitionManagerDelegate;
+class SpeechRecognizer;
+
+// This is the manager for speech recognition. It is a single instance in
+// the browser process and can serve several requests. Each recognition request
+// corresponds to a session, initiated via |CreateSession|.
+//
+// In any moment, the manager has a single session known as the primary session,
+// |primary_session_id_|.
+// This is the session that is capturing audio, waiting for user permission,
+// etc. There may also be other, non-primary, sessions living in parallel that
+// are waiting for results but not recording audio.
+//
+// The SpeechRecognitionManager has the following responsibilities:
+// - Handles requests received from various render views and makes sure only
+// one of them accesses the audio device at any given time.
+// - Handles the instantiation of SpeechRecognitionEngine objects when
+// requested by SpeechRecognitionSessions.
+// - Relays recognition results/status/error events of each session to the
+// corresponding listener (demuxing on the base of their session_id).
+// - Relays also recognition results/status/error events of every session to
+// the catch-all snoop listener (optionally) provided by the delegate.
+class CONTENT_EXPORT SpeechRecognitionManagerImpl :
+ public NON_EXPORTED_BASE(SpeechRecognitionManager),
+ public SpeechRecognitionEventListener {
+ public:
+ // Returns the current SpeechRecognitionManagerImpl or NULL if the call is
+ // issued when it is not created yet or destroyed (by BrowserMainLoop).
+ static SpeechRecognitionManagerImpl* GetInstance();
+
+ // SpeechRecognitionManager implementation.
+ virtual int CreateSession(
+ const SpeechRecognitionSessionConfig& config) OVERRIDE;
+ virtual void StartSession(int session_id) OVERRIDE;
+ virtual void AbortSession(int session_id) OVERRIDE;
+ virtual void AbortAllSessionsForListener(
+ SpeechRecognitionEventListener* listener) OVERRIDE;
+ virtual void AbortAllSessionsForRenderView(int render_process_id,
+ int render_view_id) OVERRIDE;
+ virtual void StopAudioCaptureForSession(int session_id) OVERRIDE;
+ virtual const SpeechRecognitionSessionConfig& GetSessionConfig(
+ int session_id) const OVERRIDE;
+ virtual SpeechRecognitionSessionContext GetSessionContext(
+ int session_id) const OVERRIDE;
+ virtual int GetSession(int render_process_id,
+ int render_view_id,
+ int request_id) const OVERRIDE;
+ virtual bool HasAudioInputDevices() OVERRIDE;
+ virtual string16 GetAudioInputDeviceModel() OVERRIDE;
+ virtual void ShowAudioInputSettings() OVERRIDE;
+
+ // SpeechRecognitionEventListener methods.
+ virtual void OnRecognitionStart(int session_id) OVERRIDE;
+ virtual void OnAudioStart(int session_id) OVERRIDE;
+ virtual void OnEnvironmentEstimationComplete(int session_id) OVERRIDE;
+ virtual void OnSoundStart(int session_id) OVERRIDE;
+ virtual void OnSoundEnd(int session_id) OVERRIDE;
+ virtual void OnAudioEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionEnd(int session_id) OVERRIDE;
+ virtual void OnRecognitionResults(
+ int session_id, const SpeechRecognitionResults& result) OVERRIDE;
+ virtual void OnRecognitionError(
+ int session_id, const SpeechRecognitionError& error) OVERRIDE;
+ virtual void OnAudioLevelsChange(int session_id, float volume,
+ float noise_volume) OVERRIDE;
+
+ SpeechRecognitionManagerDelegate* delegate() const { return delegate_.get(); }
+
+ protected:
+ // BrowserMainLoop is the only one allowed to istantiate and free us.
+ friend class BrowserMainLoop;
+ // Needed for dtor.
+ friend struct base::DefaultDeleter<SpeechRecognitionManagerImpl>;
+ SpeechRecognitionManagerImpl(media::AudioManager* audio_manager,
+ MediaStreamManager* media_stream_manager);
+ virtual ~SpeechRecognitionManagerImpl();
+
+ private:
+ // Data types for the internal Finite State Machine (FSM).
+ enum FSMState {
+ SESSION_STATE_IDLE = 0,
+ SESSION_STATE_CAPTURING_AUDIO,
+ SESSION_STATE_WAITING_FOR_RESULT,
+ SESSION_STATE_MAX_VALUE = SESSION_STATE_WAITING_FOR_RESULT
+ };
+
+ enum FSMEvent {
+ EVENT_ABORT = 0,
+ EVENT_START,
+ EVENT_STOP_CAPTURE,
+ EVENT_AUDIO_ENDED,
+ EVENT_RECOGNITION_ENDED,
+ EVENT_MAX_VALUE = EVENT_RECOGNITION_ENDED
+ };
+
+ struct Session {
+ Session();
+ ~Session();
+
+ int id;
+ bool listener_is_active;
+ SpeechRecognitionSessionConfig config;
+ SpeechRecognitionSessionContext context;
+ scoped_refptr<SpeechRecognizer> recognizer;
+ scoped_ptr<MediaStreamUIProxy> ui;
+ };
+
+ // Callback issued by the SpeechRecognitionManagerDelegate for reporting
+ // asynchronously the result of the CheckRecognitionIsAllowed call.
+ void RecognitionAllowedCallback(int session_id,
+ bool ask_user,
+ bool is_allowed);
+
+ // Callback to get back the result of a media request. |devices| is an array
+ // of devices approved to be used for the request, |devices| is empty if the
+ // users deny the request.
+ void MediaRequestPermissionCallback(int session_id,
+ const MediaStreamDevices& devices,
+ scoped_ptr<MediaStreamUIProxy> stream_ui);
+
+ // Entry point for pushing any external event into the session handling FSM.
+ void DispatchEvent(int session_id, FSMEvent event);
+
+ // Defines the behavior of the session handling FSM, selecting the appropriate
+ // transition according to the session, its current state and the event.
+ void ExecuteTransitionAndGetNextState(Session* session,
+ FSMState session_state,
+ FSMEvent event);
+
+ // Retrieves the state of the session, enquiring directly the recognizer.
+ FSMState GetSessionState(int session_id) const;
+
+ // The methods below handle transitions of the session handling FSM.
+ void SessionStart(const Session& session);
+ void SessionAbort(const Session& session);
+ void SessionStopAudioCapture(const Session& session);
+ void ResetCapturingSessionId(const Session& session);
+ void SessionDelete(Session* session);
+ void NotFeasible(const Session& session, FSMEvent event);
+
+ bool SessionExists(int session_id) const;
+ Session* GetSession(int session_id) const;
+ SpeechRecognitionEventListener* GetListener(int session_id) const;
+ SpeechRecognitionEventListener* GetDelegateListener() const;
+ int GetNextSessionID();
+
+ media::AudioManager* audio_manager_;
+ MediaStreamManager* media_stream_manager_;
+ typedef std::map<int, Session*> SessionsTable;
+ SessionsTable sessions_;
+ int primary_session_id_;
+ int last_session_id_;
+ bool is_dispatching_event_;
+ scoped_ptr<SpeechRecognitionManagerDelegate> delegate_;
+
+ // Used for posting asynchronous tasks (on the IO thread) without worrying
+ // about this class being destroyed in the meanwhile (due to browser shutdown)
+ // since tasks pending on a destroyed WeakPtr are automatically discarded.
+ base::WeakPtrFactory<SpeechRecognitionManagerImpl> weak_factory_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNITION_MANAGER_IMPL_H_
diff --git a/chromium/content/browser/speech/speech_recognizer.h b/chromium/content/browser/speech/speech_recognizer.h
new file mode 100644
index 00000000000..92feebb3755
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer.h
@@ -0,0 +1,48 @@
+// Copyright (c) 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_H_
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class SpeechRecognitionEventListener;
+
+// Handles speech recognition for a session (identified by |session_id|).
+class CONTENT_EXPORT SpeechRecognizer
+ : public base::RefCountedThreadSafe<SpeechRecognizer> {
+ public:
+
+ SpeechRecognizer(SpeechRecognitionEventListener* listener, int session_id)
+ : listener_(listener), session_id_(session_id) {
+ DCHECK(listener_);
+ }
+
+ virtual void StartRecognition(const std::string& device_id) = 0;
+ virtual void AbortRecognition() = 0;
+ virtual void StopAudioCapture() = 0;
+ virtual bool IsActive() const = 0;
+ virtual bool IsCapturingAudio() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<SpeechRecognizer>;
+
+ virtual ~SpeechRecognizer() {}
+ SpeechRecognitionEventListener* listener() const { return listener_; }
+ int session_id() const { return session_id_; }
+
+ private:
+ SpeechRecognitionEventListener* listener_;
+ int session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpeechRecognizer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_H_
diff --git a/chromium/content/browser/speech/speech_recognizer_impl.cc b/chromium/content/browser/speech/speech_recognizer_impl.cc
new file mode 100644
index 00000000000..2081b2f982d
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer_impl.cc
@@ -0,0 +1,819 @@
+// Copyright (c) 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 "content/browser/speech/speech_recognizer_impl.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/google_one_shot_remote_engine.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "media/base/audio_converter.h"
+#include "net/url_request/url_request_context_getter.h"
+
+#if defined(OS_WIN)
+#include "media/audio/win/core_audio_util_win.h"
+#endif
+
+using media::AudioBus;
+using media::AudioConverter;
+using media::AudioInputController;
+using media::AudioManager;
+using media::AudioParameters;
+using media::ChannelLayout;
+
+namespace content {
+
+// Private class which encapsulates the audio converter and the
+// AudioConverter::InputCallback. It handles resampling, buffering and
+// channel mixing between input and output parameters.
+class SpeechRecognizerImpl::OnDataConverter
+ : public media::AudioConverter::InputCallback {
+ public:
+ OnDataConverter(const AudioParameters& input_params,
+ const AudioParameters& output_params);
+ virtual ~OnDataConverter();
+
+ // Converts input |data| buffer into an AudioChunk where the input format
+ // is given by |input_parameters_| and the output format by
+ // |output_parameters_|.
+ scoped_refptr<AudioChunk> Convert(const uint8* data, size_t size);
+
+ private:
+ // media::AudioConverter::InputCallback implementation.
+ virtual double ProvideInput(AudioBus* dest,
+ base::TimeDelta buffer_delay) OVERRIDE;
+
+ // Handles resampling, buffering, and channel mixing between input and output
+ // parameters.
+ AudioConverter audio_converter_;
+
+ scoped_ptr<AudioBus> input_bus_;
+ scoped_ptr<AudioBus> output_bus_;
+ const AudioParameters input_parameters_;
+ const AudioParameters output_parameters_;
+ bool waiting_for_input_;
+ scoped_ptr<uint8[]> converted_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(OnDataConverter);
+};
+
+namespace {
+
+// The following constants are related to the volume level indicator shown in
+// the UI for recorded audio.
+// Multiplier used when new volume is greater than previous level.
+const float kUpSmoothingFactor = 1.0f;
+// Multiplier used when new volume is lesser than previous level.
+const float kDownSmoothingFactor = 0.7f;
+// RMS dB value of a maximum (unclipped) sine wave for int16 samples.
+const float kAudioMeterMaxDb = 90.31f;
+// This value corresponds to RMS dB for int16 with 6 most-significant-bits = 0.
+// Values lower than this will display as empty level-meter.
+const float kAudioMeterMinDb = 30.0f;
+const float kAudioMeterDbRange = kAudioMeterMaxDb - kAudioMeterMinDb;
+
+// Maximum level to draw to display unclipped meter. (1.0f displays clipping.)
+const float kAudioMeterRangeMaxUnclipped = 47.0f / 48.0f;
+
+// Returns true if more than 5% of the samples are at min or max value.
+bool DetectClipping(const AudioChunk& chunk) {
+ const int num_samples = chunk.NumSamples();
+ const int16* samples = chunk.SamplesData16();
+ const int kThreshold = num_samples / 20;
+ int clipping_samples = 0;
+
+ for (int i = 0; i < num_samples; ++i) {
+ if (samples[i] <= -32767 || samples[i] >= 32767) {
+ if (++clipping_samples > kThreshold)
+ return true;
+ }
+ }
+ return false;
+}
+
+void KeepAudioControllerRefcountedForDtor(scoped_refptr<AudioInputController>) {
+}
+
+} // namespace
+
+const int SpeechRecognizerImpl::kAudioSampleRate = 16000;
+const ChannelLayout SpeechRecognizerImpl::kChannelLayout =
+ media::CHANNEL_LAYOUT_MONO;
+const int SpeechRecognizerImpl::kNumBitsPerAudioSample = 16;
+const int SpeechRecognizerImpl::kNoSpeechTimeoutMs = 8000;
+const int SpeechRecognizerImpl::kEndpointerEstimationTimeMs = 300;
+media::AudioManager* SpeechRecognizerImpl::audio_manager_for_tests_ = NULL;
+
+COMPILE_ASSERT(SpeechRecognizerImpl::kNumBitsPerAudioSample % 8 == 0,
+ kNumBitsPerAudioSample_must_be_a_multiple_of_8);
+
+// SpeechRecognizerImpl::OnDataConverter implementation
+
+SpeechRecognizerImpl::OnDataConverter::OnDataConverter(
+ const AudioParameters& input_params, const AudioParameters& output_params)
+ : audio_converter_(input_params, output_params, false),
+ input_bus_(AudioBus::Create(input_params)),
+ output_bus_(AudioBus::Create(output_params)),
+ input_parameters_(input_params),
+ output_parameters_(output_params),
+ waiting_for_input_(false),
+ converted_data_(new uint8[output_parameters_.GetBytesPerBuffer()]) {
+ audio_converter_.AddInput(this);
+}
+
+SpeechRecognizerImpl::OnDataConverter::~OnDataConverter() {
+ // It should now be safe to unregister the converter since no more OnData()
+ // callbacks are outstanding at this point.
+ audio_converter_.RemoveInput(this);
+}
+
+scoped_refptr<AudioChunk> SpeechRecognizerImpl::OnDataConverter::Convert(
+ const uint8* data, size_t size) {
+ CHECK_EQ(size, static_cast<size_t>(input_parameters_.GetBytesPerBuffer()));
+
+ input_bus_->FromInterleaved(
+ data, input_bus_->frames(), input_parameters_.bits_per_sample() / 8);
+
+ waiting_for_input_ = true;
+ audio_converter_.Convert(output_bus_.get());
+
+ output_bus_->ToInterleaved(
+ output_bus_->frames(), output_parameters_.bits_per_sample() / 8,
+ converted_data_.get());
+
+ // TODO(primiano): Refactor AudioChunk to avoid the extra-copy here
+ // (see http://crbug.com/249316 for details).
+ return scoped_refptr<AudioChunk>(new AudioChunk(
+ converted_data_.get(),
+ output_parameters_.GetBytesPerBuffer(),
+ output_parameters_.bits_per_sample() / 8));
+}
+
+double SpeechRecognizerImpl::OnDataConverter::ProvideInput(
+ AudioBus* dest, base::TimeDelta buffer_delay) {
+ // The audio converted should never ask for more than one bus in each call
+ // to Convert(). If so, we have a serious issue in our design since we might
+ // miss recorded chunks of 100 ms audio data.
+ CHECK(waiting_for_input_);
+
+ // Read from the input bus to feed the converter.
+ input_bus_->CopyTo(dest);
+
+ // |input_bus_| should only be provide once.
+ waiting_for_input_ = false;
+ return 1;
+}
+
+// SpeechRecognizerImpl implementation
+
+SpeechRecognizerImpl::SpeechRecognizerImpl(
+ SpeechRecognitionEventListener* listener,
+ int session_id,
+ bool is_single_shot,
+ SpeechRecognitionEngine* engine)
+ : SpeechRecognizer(listener, session_id),
+ recognition_engine_(engine),
+ endpointer_(kAudioSampleRate),
+ is_dispatching_event_(false),
+ is_single_shot_(is_single_shot),
+ state_(STATE_IDLE) {
+ DCHECK(recognition_engine_ != NULL);
+ if (is_single_shot) {
+ // In single shot recognition, the session is automatically ended after:
+ // - 0.5 seconds of silence if time < 3 seconds
+ // - 1 seconds of silence if time >= 3 seconds
+ endpointer_.set_speech_input_complete_silence_length(
+ base::Time::kMicrosecondsPerSecond / 2);
+ endpointer_.set_long_speech_input_complete_silence_length(
+ base::Time::kMicrosecondsPerSecond);
+ endpointer_.set_long_speech_length(3 * base::Time::kMicrosecondsPerSecond);
+ } else {
+ // In continuous recognition, the session is automatically ended after 15
+ // seconds of silence.
+ const int64 cont_timeout_us = base::Time::kMicrosecondsPerSecond * 15;
+ endpointer_.set_speech_input_complete_silence_length(cont_timeout_us);
+ endpointer_.set_long_speech_length(0); // Use only a single timeout.
+ }
+ endpointer_.StartSession();
+ recognition_engine_->set_delegate(this);
+}
+
+// ------- Methods that trigger Finite State Machine (FSM) events ------------
+
+// NOTE:all the external events and requests should be enqueued (PostTask), even
+// if they come from the same (IO) thread, in order to preserve the relationship
+// of causality between events and avoid interleaved event processing due to
+// synchronous callbacks.
+
+void SpeechRecognizerImpl::StartRecognition(const std::string& device_id) {
+ DCHECK(!device_id.empty());
+ device_id_ = device_id;
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, FSMEventArgs(EVENT_START)));
+}
+
+void SpeechRecognizerImpl::AbortRecognition() {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, FSMEventArgs(EVENT_ABORT)));
+}
+
+void SpeechRecognizerImpl::StopAudioCapture() {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, FSMEventArgs(EVENT_STOP_CAPTURE)));
+}
+
+bool SpeechRecognizerImpl::IsActive() const {
+ // Checking the FSM state from another thread (thus, while the FSM is
+ // potentially concurrently evolving) is meaningless.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return state_ != STATE_IDLE && state_ != STATE_ENDED;
+}
+
+bool SpeechRecognizerImpl::IsCapturingAudio() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // See IsActive().
+ const bool is_capturing_audio = state_ >= STATE_STARTING &&
+ state_ <= STATE_RECOGNIZING;
+ DCHECK((is_capturing_audio && (audio_controller_.get() != NULL)) ||
+ (!is_capturing_audio && audio_controller_.get() == NULL));
+ return is_capturing_audio;
+}
+
+const SpeechRecognitionEngine&
+SpeechRecognizerImpl::recognition_engine() const {
+ return *(recognition_engine_.get());
+}
+
+SpeechRecognizerImpl::~SpeechRecognizerImpl() {
+ endpointer_.EndSession();
+ if (audio_controller_.get()) {
+ audio_controller_->Close(
+ base::Bind(&KeepAudioControllerRefcountedForDtor, audio_controller_));
+ }
+}
+
+// Invoked in the audio thread.
+void SpeechRecognizerImpl::OnError(AudioInputController* controller) {
+ FSMEventArgs event_args(EVENT_AUDIO_ERROR);
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, event_args));
+}
+
+void SpeechRecognizerImpl::OnData(AudioInputController* controller,
+ const uint8* data, uint32 size) {
+ if (size == 0) // This could happen when audio capture stops and is normal.
+ return;
+
+ // Convert audio from native format to fixed format used by WebSpeech.
+ FSMEventArgs event_args(EVENT_AUDIO_DATA);
+ event_args.audio_data = audio_converter_->Convert(data, size);
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, event_args));
+}
+
+void SpeechRecognizerImpl::OnAudioClosed(AudioInputController*) {}
+
+void SpeechRecognizerImpl::OnSpeechRecognitionEngineResults(
+ const SpeechRecognitionResults& results) {
+ FSMEventArgs event_args(EVENT_ENGINE_RESULT);
+ event_args.engine_results = results;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, event_args));
+}
+
+void SpeechRecognizerImpl::OnSpeechRecognitionEngineError(
+ const SpeechRecognitionError& error) {
+ FSMEventArgs event_args(EVENT_ENGINE_ERROR);
+ event_args.engine_error = error;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SpeechRecognizerImpl::DispatchEvent,
+ this, event_args));
+}
+
+// ----------------------- Core FSM implementation ---------------------------
+// TODO(primiano): After the changes in the media package (r129173), this class
+// slightly violates the SpeechRecognitionEventListener interface contract. In
+// particular, it is not true anymore that this class can be freed after the
+// OnRecognitionEnd event, since the audio_controller_.Close() asynchronous
+// call can be still in progress after the end event. Currently, it does not
+// represent a problem for the browser itself, since refcounting protects us
+// against such race conditions. However, we should fix this in the next CLs.
+// For instance, tests are currently working just because the
+// TestAudioInputController is not closing asynchronously as the real controller
+// does, but they will become flaky if TestAudioInputController will be fixed.
+
+void SpeechRecognizerImpl::DispatchEvent(const FSMEventArgs& event_args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_LE(event_args.event, EVENT_MAX_VALUE);
+ DCHECK_LE(state_, STATE_MAX_VALUE);
+
+ // Event dispatching must be sequential, otherwise it will break all the rules
+ // and the assumptions of the finite state automata model.
+ DCHECK(!is_dispatching_event_);
+ is_dispatching_event_ = true;
+
+ // Guard against the delegate freeing us until we finish processing the event.
+ scoped_refptr<SpeechRecognizerImpl> me(this);
+
+ if (event_args.event == EVENT_AUDIO_DATA) {
+ DCHECK(event_args.audio_data.get() != NULL);
+ ProcessAudioPipeline(*event_args.audio_data.get());
+ }
+
+ // The audio pipeline must be processed before the event dispatch, otherwise
+ // it would take actions according to the future state instead of the current.
+ state_ = ExecuteTransitionAndGetNextState(event_args);
+ is_dispatching_event_ = false;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::ExecuteTransitionAndGetNextState(
+ const FSMEventArgs& event_args) {
+ const FSMEvent event = event_args.event;
+ switch (state_) {
+ case STATE_IDLE:
+ switch (event) {
+ // TODO(primiano): restore UNREACHABLE_CONDITION on EVENT_ABORT and
+ // EVENT_STOP_CAPTURE below once speech input extensions are fixed.
+ case EVENT_ABORT:
+ return AbortSilently(event_args);
+ case EVENT_START:
+ return StartRecording(event_args);
+ case EVENT_STOP_CAPTURE:
+ return AbortSilently(event_args);
+ case EVENT_AUDIO_DATA: // Corner cases related to queued messages
+ case EVENT_ENGINE_RESULT: // being lately dispatched.
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return DoNothing(event_args);
+ }
+ break;
+ case STATE_STARTING:
+ switch (event) {
+ case EVENT_ABORT:
+ return AbortWithError(event_args);
+ case EVENT_START:
+ return NotFeasible(event_args);
+ case EVENT_STOP_CAPTURE:
+ return AbortSilently(event_args);
+ case EVENT_AUDIO_DATA:
+ return StartRecognitionEngine(event_args);
+ case EVENT_ENGINE_RESULT:
+ return NotFeasible(event_args);
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return AbortWithError(event_args);
+ }
+ break;
+ case STATE_ESTIMATING_ENVIRONMENT:
+ switch (event) {
+ case EVENT_ABORT:
+ return AbortWithError(event_args);
+ case EVENT_START:
+ return NotFeasible(event_args);
+ case EVENT_STOP_CAPTURE:
+ return StopCaptureAndWaitForResult(event_args);
+ case EVENT_AUDIO_DATA:
+ return WaitEnvironmentEstimationCompletion(event_args);
+ case EVENT_ENGINE_RESULT:
+ return ProcessIntermediateResult(event_args);
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return AbortWithError(event_args);
+ }
+ break;
+ case STATE_WAITING_FOR_SPEECH:
+ switch (event) {
+ case EVENT_ABORT:
+ return AbortWithError(event_args);
+ case EVENT_START:
+ return NotFeasible(event_args);
+ case EVENT_STOP_CAPTURE:
+ return StopCaptureAndWaitForResult(event_args);
+ case EVENT_AUDIO_DATA:
+ return DetectUserSpeechOrTimeout(event_args);
+ case EVENT_ENGINE_RESULT:
+ return ProcessIntermediateResult(event_args);
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return AbortWithError(event_args);
+ }
+ break;
+ case STATE_RECOGNIZING:
+ switch (event) {
+ case EVENT_ABORT:
+ return AbortWithError(event_args);
+ case EVENT_START:
+ return NotFeasible(event_args);
+ case EVENT_STOP_CAPTURE:
+ return StopCaptureAndWaitForResult(event_args);
+ case EVENT_AUDIO_DATA:
+ return DetectEndOfSpeech(event_args);
+ case EVENT_ENGINE_RESULT:
+ return ProcessIntermediateResult(event_args);
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return AbortWithError(event_args);
+ }
+ break;
+ case STATE_WAITING_FINAL_RESULT:
+ switch (event) {
+ case EVENT_ABORT:
+ return AbortWithError(event_args);
+ case EVENT_START:
+ return NotFeasible(event_args);
+ case EVENT_STOP_CAPTURE:
+ case EVENT_AUDIO_DATA:
+ return DoNothing(event_args);
+ case EVENT_ENGINE_RESULT:
+ return ProcessFinalResult(event_args);
+ case EVENT_ENGINE_ERROR:
+ case EVENT_AUDIO_ERROR:
+ return AbortWithError(event_args);
+ }
+ break;
+
+ // TODO(primiano): remove this state when speech input extensions support
+ // will be removed and STATE_IDLE.EVENT_ABORT,EVENT_STOP_CAPTURE will be
+ // reset to NotFeasible (see TODO above).
+ case STATE_ENDED:
+ return DoNothing(event_args);
+ }
+ return NotFeasible(event_args);
+}
+
+// ----------- Contract for all the FSM evolution functions below -------------
+// - Are guaranteed to be executed in the IO thread;
+// - Are guaranteed to be not reentrant (themselves and each other);
+// - event_args members are guaranteed to be stable during the call;
+// - The class won't be freed in the meanwhile due to callbacks;
+// - IsCapturingAudio() returns true if and only if audio_controller_ != NULL.
+
+// TODO(primiano): the audio pipeline is currently serial. However, the
+// clipper->endpointer->vumeter chain and the sr_engine could be parallelized.
+// We should profile the execution to see if it would be worth or not.
+void SpeechRecognizerImpl::ProcessAudioPipeline(const AudioChunk& raw_audio) {
+ const bool route_to_endpointer = state_ >= STATE_ESTIMATING_ENVIRONMENT &&
+ state_ <= STATE_RECOGNIZING;
+ const bool route_to_sr_engine = route_to_endpointer;
+ const bool route_to_vumeter = state_ >= STATE_WAITING_FOR_SPEECH &&
+ state_ <= STATE_RECOGNIZING;
+ const bool clip_detected = DetectClipping(raw_audio);
+ float rms = 0.0f;
+
+ num_samples_recorded_ += raw_audio.NumSamples();
+
+ if (route_to_endpointer)
+ endpointer_.ProcessAudio(raw_audio, &rms);
+
+ if (route_to_vumeter) {
+ DCHECK(route_to_endpointer); // Depends on endpointer due to |rms|.
+ UpdateSignalAndNoiseLevels(rms, clip_detected);
+ }
+ if (route_to_sr_engine) {
+ DCHECK(recognition_engine_.get() != NULL);
+ recognition_engine_->TakeAudioChunk(raw_audio);
+ }
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::StartRecording(const FSMEventArgs&) {
+ DCHECK(recognition_engine_.get() != NULL);
+ DCHECK(!IsCapturingAudio());
+ const bool unit_test_is_active = (audio_manager_for_tests_ != NULL);
+ AudioManager* audio_manager = unit_test_is_active ?
+ audio_manager_for_tests_ :
+ AudioManager::Get();
+ DCHECK(audio_manager != NULL);
+
+ DVLOG(1) << "SpeechRecognizerImpl starting audio capture.";
+ num_samples_recorded_ = 0;
+ audio_level_ = 0;
+ listener()->OnRecognitionStart(session_id());
+
+ // TODO(xians): Check if the OS has the device with |device_id_|, return
+ // |SPEECH_AUDIO_ERROR_DETAILS_NO_MIC| if the target device does not exist.
+ if (!audio_manager->HasAudioInputDevices()) {
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO,
+ SPEECH_AUDIO_ERROR_DETAILS_NO_MIC));
+ }
+
+ int chunk_duration_ms = recognition_engine_->GetDesiredAudioChunkDurationMs();
+
+ AudioParameters in_params = audio_manager->GetInputStreamParameters(
+ device_id_);
+ if (!in_params.IsValid() && !unit_test_is_active) {
+ DLOG(ERROR) << "Invalid native audio input parameters";
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO));
+ }
+
+ // Audio converter shall provide audio based on these parameters as output.
+ // Hard coded, WebSpeech specific parameters are utilized here.
+ int frames_per_buffer = (kAudioSampleRate * chunk_duration_ms) / 1000;
+ AudioParameters output_parameters = AudioParameters(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout, kAudioSampleRate,
+ kNumBitsPerAudioSample, frames_per_buffer);
+
+ // Audio converter will receive audio based on these parameters as input.
+ // On Windows we start by verifying that Core Audio is supported. If not,
+ // the WaveIn API is used and we might as well avoid all audio conversations
+ // since WaveIn does the conversion for us.
+ // TODO(henrika): this code should be moved to platform dependent audio
+ // managers.
+ bool use_native_audio_params = true;
+#if defined(OS_WIN)
+ use_native_audio_params = media::CoreAudioUtil::IsSupported();
+ DVLOG_IF(1, !use_native_audio_params) << "Reverting to WaveIn for WebSpeech";
+#endif
+
+ AudioParameters input_parameters = output_parameters;
+ if (use_native_audio_params && !unit_test_is_active) {
+ // Use native audio parameters but avoid opening up at the native buffer
+ // size. Instead use same frame size (in milliseconds) as WebSpeech uses.
+ // We rely on internal buffers in the audio back-end to fulfill this request
+ // and the idea is to simplify the audio conversion since each Convert()
+ // call will then render exactly one ProvideInput() call.
+ // Due to implementation details in the audio converter, 2 milliseconds
+ // are added to the default frame size (100 ms) to ensure there is enough
+ // data to generate 100 ms of output when resampling.
+ frames_per_buffer =
+ ((in_params.sample_rate() * (chunk_duration_ms + 2)) / 1000.0) + 0.5;
+ input_parameters.Reset(in_params.format(),
+ in_params.channel_layout(),
+ in_params.channels(),
+ in_params.input_channels(),
+ in_params.sample_rate(),
+ in_params.bits_per_sample(),
+ frames_per_buffer);
+ }
+
+ // Create an audio converter which converts data between native input format
+ // and WebSpeech specific output format.
+ audio_converter_.reset(
+ new OnDataConverter(input_parameters, output_parameters));
+
+ audio_controller_ = AudioInputController::Create(
+ audio_manager, this, input_parameters, device_id_);
+
+ if (!audio_controller_.get()) {
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO));
+ }
+
+ // The endpointer needs to estimate the environment/background noise before
+ // starting to treat the audio as user input. We wait in the state
+ // ESTIMATING_ENVIRONMENT until such interval has elapsed before switching
+ // to user input mode.
+ endpointer_.SetEnvironmentEstimationMode();
+ audio_controller_->Record();
+ return STATE_STARTING;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::StartRecognitionEngine(const FSMEventArgs& event_args) {
+ // This is the first audio packet captured, so the recognition engine is
+ // started and the delegate notified about the event.
+ DCHECK(recognition_engine_.get() != NULL);
+ recognition_engine_->StartRecognition();
+ listener()->OnAudioStart(session_id());
+
+ // This is a little hack, since TakeAudioChunk() is already called by
+ // ProcessAudioPipeline(). It is the best tradeoff, unless we allow dropping
+ // the first audio chunk captured after opening the audio device.
+ recognition_engine_->TakeAudioChunk(*(event_args.audio_data.get()));
+ return STATE_ESTIMATING_ENVIRONMENT;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::WaitEnvironmentEstimationCompletion(const FSMEventArgs&) {
+ DCHECK(endpointer_.IsEstimatingEnvironment());
+ if (GetElapsedTimeMs() >= kEndpointerEstimationTimeMs) {
+ endpointer_.SetUserInputMode();
+ listener()->OnEnvironmentEstimationComplete(session_id());
+ return STATE_WAITING_FOR_SPEECH;
+ } else {
+ return STATE_ESTIMATING_ENVIRONMENT;
+ }
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::DetectUserSpeechOrTimeout(const FSMEventArgs&) {
+ if (endpointer_.DidStartReceivingSpeech()) {
+ listener()->OnSoundStart(session_id());
+ return STATE_RECOGNIZING;
+ } else if (GetElapsedTimeMs() >= kNoSpeechTimeoutMs) {
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_NO_SPEECH));
+ }
+ return STATE_WAITING_FOR_SPEECH;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::DetectEndOfSpeech(const FSMEventArgs& event_args) {
+ if (endpointer_.speech_input_complete())
+ return StopCaptureAndWaitForResult(event_args);
+ return STATE_RECOGNIZING;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::StopCaptureAndWaitForResult(const FSMEventArgs&) {
+ DCHECK(state_ >= STATE_ESTIMATING_ENVIRONMENT && state_ <= STATE_RECOGNIZING);
+
+ DVLOG(1) << "Concluding recognition";
+ CloseAudioControllerAsynchronously();
+ recognition_engine_->AudioChunksEnded();
+
+ if (state_ > STATE_WAITING_FOR_SPEECH)
+ listener()->OnSoundEnd(session_id());
+
+ listener()->OnAudioEnd(session_id());
+ return STATE_WAITING_FINAL_RESULT;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::AbortSilently(const FSMEventArgs& event_args) {
+ DCHECK_NE(event_args.event, EVENT_AUDIO_ERROR);
+ DCHECK_NE(event_args.event, EVENT_ENGINE_ERROR);
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_NONE));
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::AbortWithError(const FSMEventArgs& event_args) {
+ if (event_args.event == EVENT_AUDIO_ERROR) {
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO));
+ } else if (event_args.event == EVENT_ENGINE_ERROR) {
+ return Abort(event_args.engine_error);
+ }
+ return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_ABORTED));
+}
+
+SpeechRecognizerImpl::FSMState SpeechRecognizerImpl::Abort(
+ const SpeechRecognitionError& error) {
+ if (IsCapturingAudio())
+ CloseAudioControllerAsynchronously();
+
+ DVLOG(1) << "SpeechRecognizerImpl canceling recognition. ";
+
+ // The recognition engine is initialized only after STATE_STARTING.
+ if (state_ > STATE_STARTING) {
+ DCHECK(recognition_engine_.get() != NULL);
+ recognition_engine_->EndRecognition();
+ }
+
+ if (state_ > STATE_WAITING_FOR_SPEECH && state_ < STATE_WAITING_FINAL_RESULT)
+ listener()->OnSoundEnd(session_id());
+
+ if (state_ > STATE_STARTING && state_ < STATE_WAITING_FINAL_RESULT)
+ listener()->OnAudioEnd(session_id());
+
+ if (error.code != SPEECH_RECOGNITION_ERROR_NONE)
+ listener()->OnRecognitionError(session_id(), error);
+
+ listener()->OnRecognitionEnd(session_id());
+
+ return STATE_ENDED;
+}
+
+SpeechRecognizerImpl::FSMState SpeechRecognizerImpl::ProcessIntermediateResult(
+ const FSMEventArgs& event_args) {
+ // Provisional results can occur only during continuous (non one-shot) mode.
+ // If this check is reached it means that a continuous speech recognition
+ // engine is being used for a one shot recognition.
+ DCHECK_EQ(false, is_single_shot_);
+
+ // In continuous recognition, intermediate results can occur even when we are
+ // in the ESTIMATING_ENVIRONMENT or WAITING_FOR_SPEECH states (if the
+ // recognition engine is "faster" than our endpointer). In these cases we
+ // skip the endpointer and fast-forward to the RECOGNIZING state, with respect
+ // of the events triggering order.
+ if (state_ == STATE_ESTIMATING_ENVIRONMENT) {
+ DCHECK(endpointer_.IsEstimatingEnvironment());
+ endpointer_.SetUserInputMode();
+ listener()->OnEnvironmentEstimationComplete(session_id());
+ } else if (state_ == STATE_WAITING_FOR_SPEECH) {
+ listener()->OnSoundStart(session_id());
+ } else {
+ DCHECK_EQ(STATE_RECOGNIZING, state_);
+ }
+
+ listener()->OnRecognitionResults(session_id(), event_args.engine_results);
+ return STATE_RECOGNIZING;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::ProcessFinalResult(const FSMEventArgs& event_args) {
+ const SpeechRecognitionResults& results = event_args.engine_results;
+ SpeechRecognitionResults::const_iterator i = results.begin();
+ bool provisional_results_pending = false;
+ bool results_are_empty = true;
+ for (; i != results.end(); ++i) {
+ const SpeechRecognitionResult& result = *i;
+ if (result.is_provisional) {
+ provisional_results_pending = true;
+ DCHECK(!is_single_shot_);
+ } else if (results_are_empty) {
+ results_are_empty = result.hypotheses.empty();
+ }
+ }
+
+ if (provisional_results_pending) {
+ listener()->OnRecognitionResults(session_id(), results);
+ // We don't end the recognition if a provisional result is received in
+ // STATE_WAITING_FINAL_RESULT. A definitive result will come next and will
+ // end the recognition.
+ return state_;
+ }
+
+ recognition_engine_->EndRecognition();
+
+ if (!results_are_empty) {
+ // We could receive an empty result (which we won't propagate further)
+ // in the following (continuous) scenario:
+ // 1. The caller start pushing audio and receives some results;
+ // 2. A |StopAudioCapture| is issued later;
+ // 3. The final audio frames captured in the interval ]1,2] do not lead to
+ // any result (nor any error);
+ // 4. The speech recognition engine, therefore, emits an empty result to
+ // notify that the recognition is ended with no error, yet neither any
+ // further result.
+ listener()->OnRecognitionResults(session_id(), results);
+ }
+
+ listener()->OnRecognitionEnd(session_id());
+ return STATE_ENDED;
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::DoNothing(const FSMEventArgs&) const {
+ return state_; // Just keep the current state.
+}
+
+SpeechRecognizerImpl::FSMState
+SpeechRecognizerImpl::NotFeasible(const FSMEventArgs& event_args) {
+ NOTREACHED() << "Unfeasible event " << event_args.event
+ << " in state " << state_;
+ return state_;
+}
+
+void SpeechRecognizerImpl::CloseAudioControllerAsynchronously() {
+ DCHECK(IsCapturingAudio());
+ DVLOG(1) << "SpeechRecognizerImpl closing audio controller.";
+ // Issues a Close on the audio controller, passing an empty callback. The only
+ // purpose of such callback is to keep the audio controller refcounted until
+ // Close has completed (in the audio thread) and automatically destroy it
+ // afterwards (upon return from OnAudioClosed).
+ audio_controller_->Close(base::Bind(&SpeechRecognizerImpl::OnAudioClosed,
+ this, audio_controller_));
+ audio_controller_ = NULL; // The controller is still refcounted by Bind.
+}
+
+int SpeechRecognizerImpl::GetElapsedTimeMs() const {
+ return (num_samples_recorded_ * 1000) / kAudioSampleRate;
+}
+
+void SpeechRecognizerImpl::UpdateSignalAndNoiseLevels(const float& rms,
+ bool clip_detected) {
+ // Calculate the input volume to display in the UI, smoothing towards the
+ // new level.
+ // TODO(primiano): Do we really need all this floating point arith here?
+ // Perhaps it might be quite expensive on mobile.
+ float level = (rms - kAudioMeterMinDb) /
+ (kAudioMeterDbRange / kAudioMeterRangeMaxUnclipped);
+ level = std::min(std::max(0.0f, level), kAudioMeterRangeMaxUnclipped);
+ const float smoothing_factor = (level > audio_level_) ? kUpSmoothingFactor :
+ kDownSmoothingFactor;
+ audio_level_ += (level - audio_level_) * smoothing_factor;
+
+ float noise_level = (endpointer_.NoiseLevelDb() - kAudioMeterMinDb) /
+ (kAudioMeterDbRange / kAudioMeterRangeMaxUnclipped);
+ noise_level = std::min(std::max(0.0f, noise_level),
+ kAudioMeterRangeMaxUnclipped);
+
+ listener()->OnAudioLevelsChange(
+ session_id(), clip_detected ? 1.0f : audio_level_, noise_level);
+}
+
+void SpeechRecognizerImpl::SetAudioManagerForTests(
+ AudioManager* audio_manager) {
+ audio_manager_for_tests_ = audio_manager;
+}
+
+SpeechRecognizerImpl::FSMEventArgs::FSMEventArgs(FSMEvent event_value)
+ : event(event_value),
+ audio_data(NULL),
+ engine_error(SPEECH_RECOGNITION_ERROR_NONE) {
+}
+
+SpeechRecognizerImpl::FSMEventArgs::~FSMEventArgs() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognizer_impl.h b/chromium/content/browser/speech/speech_recognizer_impl.h
new file mode 100644
index 00000000000..4945132deb7
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer_impl.h
@@ -0,0 +1,164 @@
+// Copyright (c) 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/speech/endpointer/endpointer.h"
+#include "content/browser/speech/speech_recognition_engine.h"
+#include "content/browser/speech/speech_recognizer.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "media/audio/audio_input_controller.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace media {
+class AudioManager;
+}
+
+namespace content {
+
+class SpeechRecognitionEventListener;
+
+// Handles speech recognition for a session (identified by |session_id|), taking
+// care of audio capture, silence detection/endpointer and interaction with the
+// SpeechRecognitionEngine.
+class CONTENT_EXPORT SpeechRecognizerImpl
+ : public SpeechRecognizer,
+ public media::AudioInputController::EventHandler,
+ public NON_EXPORTED_BASE(SpeechRecognitionEngineDelegate) {
+ public:
+ static const int kAudioSampleRate;
+ static const media::ChannelLayout kChannelLayout;
+ static const int kNumBitsPerAudioSample;
+ static const int kNoSpeechTimeoutMs;
+ static const int kEndpointerEstimationTimeMs;
+
+ static void SetAudioManagerForTests(media::AudioManager* audio_manager);
+
+ SpeechRecognizerImpl(SpeechRecognitionEventListener* listener,
+ int session_id,
+ bool is_single_shot,
+ SpeechRecognitionEngine* engine);
+
+ virtual void StartRecognition(const std::string& device_id) OVERRIDE;
+ virtual void AbortRecognition() OVERRIDE;
+ virtual void StopAudioCapture() OVERRIDE;
+ virtual bool IsActive() const OVERRIDE;
+ virtual bool IsCapturingAudio() const OVERRIDE;
+ const SpeechRecognitionEngine& recognition_engine() const;
+
+ private:
+ friend class SpeechRecognizerTest;
+
+ enum FSMState {
+ STATE_IDLE = 0,
+ STATE_STARTING,
+ STATE_ESTIMATING_ENVIRONMENT,
+ STATE_WAITING_FOR_SPEECH,
+ STATE_RECOGNIZING,
+ STATE_WAITING_FINAL_RESULT,
+ STATE_ENDED,
+ STATE_MAX_VALUE = STATE_ENDED
+ };
+
+ enum FSMEvent {
+ EVENT_ABORT = 0,
+ EVENT_START,
+ EVENT_STOP_CAPTURE,
+ EVENT_AUDIO_DATA,
+ EVENT_ENGINE_RESULT,
+ EVENT_ENGINE_ERROR,
+ EVENT_AUDIO_ERROR,
+ EVENT_MAX_VALUE = EVENT_AUDIO_ERROR
+ };
+
+ struct FSMEventArgs {
+ explicit FSMEventArgs(FSMEvent event_value);
+ ~FSMEventArgs();
+
+ FSMEvent event;
+ scoped_refptr<AudioChunk> audio_data;
+ SpeechRecognitionResults engine_results;
+ SpeechRecognitionError engine_error;
+ };
+
+ virtual ~SpeechRecognizerImpl();
+
+ // Entry point for pushing any new external event into the recognizer FSM.
+ void DispatchEvent(const FSMEventArgs& event_args);
+
+ // Defines the behavior of the recognizer FSM, selecting the appropriate
+ // transition according to the current state and event.
+ FSMState ExecuteTransitionAndGetNextState(const FSMEventArgs& args);
+
+ // Process a new audio chunk in the audio pipeline (endpointer, vumeter, etc).
+ void ProcessAudioPipeline(const AudioChunk& raw_audio);
+
+ // The methods below handle transitions of the recognizer FSM.
+ FSMState StartRecording(const FSMEventArgs& event_args);
+ FSMState StartRecognitionEngine(const FSMEventArgs& event_args);
+ FSMState WaitEnvironmentEstimationCompletion(const FSMEventArgs& event_args);
+ FSMState DetectUserSpeechOrTimeout(const FSMEventArgs& event_args);
+ FSMState StopCaptureAndWaitForResult(const FSMEventArgs& event_args);
+ FSMState ProcessIntermediateResult(const FSMEventArgs& event_args);
+ FSMState ProcessFinalResult(const FSMEventArgs& event_args);
+ FSMState AbortSilently(const FSMEventArgs& event_args);
+ FSMState AbortWithError(const FSMEventArgs& event_args);
+ FSMState Abort(const SpeechRecognitionError& error);
+ FSMState DetectEndOfSpeech(const FSMEventArgs& event_args);
+ FSMState DoNothing(const FSMEventArgs& event_args) const;
+ FSMState NotFeasible(const FSMEventArgs& event_args);
+
+ // Returns the time span of captured audio samples since the start of capture.
+ int GetElapsedTimeMs() const;
+
+ // Calculates the input volume to be displayed in the UI, triggering the
+ // OnAudioLevelsChange event accordingly.
+ void UpdateSignalAndNoiseLevels(const float& rms, bool clip_detected);
+
+ void CloseAudioControllerAsynchronously();
+
+ // Callback called on IO thread by audio_controller->Close().
+ void OnAudioClosed(media::AudioInputController*);
+
+ // AudioInputController::EventHandler methods.
+ virtual void OnCreated(media::AudioInputController* controller) OVERRIDE {}
+ virtual void OnRecording(media::AudioInputController* controller) OVERRIDE {}
+ virtual void OnError(media::AudioInputController* controller) OVERRIDE;
+ virtual void OnData(media::AudioInputController* controller,
+ const uint8* data, uint32 size) OVERRIDE;
+
+ // SpeechRecognitionEngineDelegate methods.
+ virtual void OnSpeechRecognitionEngineResults(
+ const SpeechRecognitionResults& results) OVERRIDE;
+ virtual void OnSpeechRecognitionEngineError(
+ const SpeechRecognitionError& error) OVERRIDE;
+
+ static media::AudioManager* audio_manager_for_tests_;
+
+ scoped_ptr<SpeechRecognitionEngine> recognition_engine_;
+ Endpointer endpointer_;
+ scoped_refptr<media::AudioInputController> audio_controller_;
+ int num_samples_recorded_;
+ float audio_level_;
+ bool is_dispatching_event_;
+ bool is_single_shot_;
+ FSMState state_;
+ std::string device_id_;
+
+ class OnDataConverter;
+
+ // Converts data between native input format and a WebSpeech specific
+ // output format.
+ scoped_ptr<SpeechRecognizerImpl::OnDataConverter> audio_converter_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpeechRecognizerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_H_
diff --git a/chromium/content/browser/speech/speech_recognizer_impl_android.cc b/chromium/content/browser/speech/speech_recognizer_impl_android.cc
new file mode 100644
index 00000000000..eb3f7934b7e
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer_impl_android.cc
@@ -0,0 +1,206 @@
+// 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 "content/browser/speech/speech_recognizer_impl_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "content/public/browser/speech_recognition_manager.h"
+#include "content/public/browser/speech_recognition_session_config.h"
+#include "content/public/common/speech_recognition_grammar.h"
+#include "content/public/common/speech_recognition_result.h"
+#include "jni/SpeechRecognition_jni.h"
+
+using base::android::AppendJavaStringArrayToStringVector;
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+using base::android::JavaFloatArrayToFloatVector;
+
+namespace content {
+
+SpeechRecognizerImplAndroid::SpeechRecognizerImplAndroid(
+ SpeechRecognitionEventListener* listener,
+ int session_id)
+ : SpeechRecognizer(listener, session_id),
+ state_(STATE_IDLE) {
+}
+
+SpeechRecognizerImplAndroid::~SpeechRecognizerImplAndroid() { }
+
+void SpeechRecognizerImplAndroid::StartRecognition(
+ const std::string& device_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // TODO(xians): Open the correct device for speech on Android.
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognitionEventListener::OnRecognitionStart,
+ base::Unretained(listener()),
+ session_id()));
+ SpeechRecognitionSessionConfig config =
+ SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id());
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &content::SpeechRecognizerImplAndroid::StartRecognitionOnUIThread, this,
+ config.language, config.continuous, config.interim_results));
+}
+
+void SpeechRecognizerImplAndroid::StartRecognitionOnUIThread(
+ std::string language, bool continuous, bool interim_results) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ j_recognition_.Reset(Java_SpeechRecognition_createSpeechRecognition(env,
+ GetApplicationContext(), reinterpret_cast<jint>(this)));
+ Java_SpeechRecognition_startRecognition(env, j_recognition_.obj(),
+ ConvertUTF8ToJavaString(env, language).obj(), continuous,
+ interim_results);
+}
+
+void SpeechRecognizerImplAndroid::AbortRecognition() {
+ if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ state_ = STATE_IDLE;
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &content::SpeechRecognizerImplAndroid::AbortRecognition, this));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ if (!j_recognition_.is_null())
+ Java_SpeechRecognition_abortRecognition(env, j_recognition_.obj());
+}
+
+void SpeechRecognizerImplAndroid::StopAudioCapture() {
+ if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &content::SpeechRecognizerImplAndroid::StopAudioCapture, this));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ JNIEnv* env = AttachCurrentThread();
+ if (!j_recognition_.is_null())
+ Java_SpeechRecognition_stopRecognition(env, j_recognition_.obj());
+}
+
+bool SpeechRecognizerImplAndroid::IsActive() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return state_ != STATE_IDLE;
+}
+
+bool SpeechRecognizerImplAndroid::IsCapturingAudio() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return state_ == STATE_CAPTURING_AUDIO;
+}
+
+void SpeechRecognizerImplAndroid::OnAudioStart(JNIEnv* env, jobject obj) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnAudioStart, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL)));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state_ = STATE_CAPTURING_AUDIO;
+ listener()->OnAudioStart(session_id());
+}
+
+void SpeechRecognizerImplAndroid::OnSoundStart(JNIEnv* env, jobject obj) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnSoundStart, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL)));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ listener()->OnSoundStart(session_id());
+}
+
+void SpeechRecognizerImplAndroid::OnSoundEnd(JNIEnv* env, jobject obj) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnSoundEnd, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL)));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ listener()->OnSoundEnd(session_id());
+}
+
+void SpeechRecognizerImplAndroid::OnAudioEnd(JNIEnv* env, jobject obj) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnAudioEnd, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL)));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (state_ == STATE_CAPTURING_AUDIO)
+ state_ = STATE_AWAITING_FINAL_RESULT;
+ listener()->OnAudioEnd(session_id());
+}
+
+void SpeechRecognizerImplAndroid::OnRecognitionResults(JNIEnv* env, jobject obj,
+ jobjectArray strings, jfloatArray floats, jboolean provisional) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::vector<string16> options;
+ AppendJavaStringArrayToStringVector(env, strings, &options);
+ std::vector<float> scores(options.size(), 0.0);
+ if (floats != NULL)
+ JavaFloatArrayToFloatVector(env, floats, &scores);
+ SpeechRecognitionResults results;
+ results.push_back(SpeechRecognitionResult());
+ SpeechRecognitionResult& result = results.back();
+ CHECK_EQ(options.size(), scores.size());
+ for (size_t i = 0; i < options.size(); ++i) {
+ result.hypotheses.push_back(SpeechRecognitionHypothesis(
+ options[i], static_cast<double>(scores[i])));
+ }
+ result.is_provisional = provisional;
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread,
+ this, results));
+}
+
+void SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread(
+ SpeechRecognitionResults const &results) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ listener()->OnRecognitionResults(session_id(), results);
+}
+
+void SpeechRecognizerImplAndroid::OnRecognitionError(JNIEnv* env,
+ jobject obj, jint error) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnRecognitionError, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL), error));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ SpeechRecognitionErrorCode code =
+ static_cast<SpeechRecognitionErrorCode>(error);
+ listener()->OnRecognitionError(session_id(), SpeechRecognitionError(code));
+}
+
+void SpeechRecognizerImplAndroid::OnRecognitionEnd(JNIEnv* env,
+ jobject obj) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
+ &SpeechRecognizerImplAndroid::OnRecognitionEnd, this,
+ static_cast<JNIEnv*>(NULL), static_cast<jobject>(NULL)));
+ return;
+ }
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state_ = STATE_IDLE;
+ listener()->OnRecognitionEnd(session_id());
+}
+
+// static
+bool SpeechRecognizerImplAndroid::RegisterSpeechRecognizer(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/speech/speech_recognizer_impl_android.h b/chromium/content/browser/speech/speech_recognizer_impl_android.h
new file mode 100644
index 00000000000..e6e7f09ec69
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer_impl_android.h
@@ -0,0 +1,66 @@
+// 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 CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_ANDROID_H_
+#define CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/speech/speech_recognizer.h"
+#include "content/public/common/speech_recognition_error.h"
+#include "content/public/common/speech_recognition_result.h"
+
+namespace content {
+
+class SpeechRecognitionEventListener;
+
+class CONTENT_EXPORT SpeechRecognizerImplAndroid : public SpeechRecognizer {
+ public:
+ SpeechRecognizerImplAndroid(SpeechRecognitionEventListener* listener,
+ int session_id);
+
+ // SpeechRecognizer methods.
+ virtual void StartRecognition(const std::string& device_id) OVERRIDE;
+ virtual void AbortRecognition() OVERRIDE;
+ virtual void StopAudioCapture() OVERRIDE;
+ virtual bool IsActive() const OVERRIDE;
+ virtual bool IsCapturingAudio() const OVERRIDE;
+
+ // Called from Java methods via JNI.
+ void OnAudioStart(JNIEnv* env, jobject obj);
+ void OnSoundStart(JNIEnv* env, jobject obj);
+ void OnSoundEnd(JNIEnv* env, jobject obj);
+ void OnAudioEnd(JNIEnv* env, jobject obj);
+ void OnRecognitionResults(JNIEnv* env, jobject obj, jobjectArray strings,
+ jfloatArray floats, jboolean interim);
+ void OnRecognitionError(JNIEnv* env, jobject obj, jint error);
+ void OnRecognitionEnd(JNIEnv* env, jobject obj);
+
+ static bool RegisterSpeechRecognizer(JNIEnv* env);
+
+ private:
+ enum State {
+ STATE_IDLE = 0,
+ STATE_CAPTURING_AUDIO,
+ STATE_AWAITING_FINAL_RESULT
+ };
+
+ void StartRecognitionOnUIThread(
+ std::string language, bool continuous, bool interim_results);
+ void OnRecognitionResultsOnIOThread(SpeechRecognitionResults const &results);
+
+ virtual ~SpeechRecognizerImplAndroid();
+
+ base::android::ScopedJavaGlobalRef<jobject> j_recognition_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpeechRecognizerImplAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_SPEECH_RECOGNIZER_IMPL_ANDROID_H_
diff --git a/chromium/content/browser/speech/speech_recognizer_impl_unittest.cc b/chromium/content/browser/speech/speech_recognizer_impl_unittest.cc
new file mode 100644
index 00000000000..dcd4d5be538
--- /dev/null
+++ b/chromium/content/browser/speech/speech_recognizer_impl_unittest.cc
@@ -0,0 +1,499 @@
+// Copyright (c) 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 <vector>
+
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/speech/google_one_shot_remote_engine.h"
+#include "content/browser/speech/speech_recognizer_impl.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/fake_audio_input_stream.h"
+#include "media/audio/fake_audio_output_stream.h"
+#include "media/audio/mock_audio_manager.h"
+#include "media/audio/test_audio_input_controller_factory.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::MessageLoopProxy;
+using media::AudioInputController;
+using media::AudioInputStream;
+using media::AudioManager;
+using media::AudioOutputStream;
+using media::AudioParameters;
+using media::TestAudioInputController;
+using media::TestAudioInputControllerFactory;
+
+namespace content {
+
+class SpeechRecognizerImplTest : public SpeechRecognitionEventListener,
+ public testing::Test {
+ public:
+ SpeechRecognizerImplTest()
+ : io_thread_(BrowserThread::IO, &message_loop_),
+ recognition_started_(false),
+ recognition_ended_(false),
+ result_received_(false),
+ audio_started_(false),
+ audio_ended_(false),
+ sound_started_(false),
+ sound_ended_(false),
+ error_(SPEECH_RECOGNITION_ERROR_NONE),
+ volume_(-1.0f) {
+ // SpeechRecognizer takes ownership of sr_engine.
+ SpeechRecognitionEngine* sr_engine =
+ new GoogleOneShotRemoteEngine(NULL /* URLRequestContextGetter */);
+ SpeechRecognitionEngineConfig config;
+ config.audio_num_bits_per_sample =
+ SpeechRecognizerImpl::kNumBitsPerAudioSample;
+ config.audio_sample_rate = SpeechRecognizerImpl::kAudioSampleRate;
+ config.filter_profanities = false;
+ sr_engine->SetConfig(config);
+
+ const int kTestingSessionId = 1;
+ const bool kOneShotMode = true;
+ recognizer_ = new SpeechRecognizerImpl(
+ this, kTestingSessionId, kOneShotMode, sr_engine);
+ audio_manager_.reset(new media::MockAudioManager(
+ base::MessageLoop::current()->message_loop_proxy().get()));
+ recognizer_->SetAudioManagerForTests(audio_manager_.get());
+
+ int audio_packet_length_bytes =
+ (SpeechRecognizerImpl::kAudioSampleRate *
+ GoogleOneShotRemoteEngine::kAudioPacketIntervalMs *
+ ChannelLayoutToChannelCount(SpeechRecognizerImpl::kChannelLayout) *
+ SpeechRecognizerImpl::kNumBitsPerAudioSample) / (8 * 1000);
+ audio_packet_.resize(audio_packet_length_bytes);
+ }
+
+ void CheckEventsConsistency() {
+ // Note: "!x || y" == "x implies y".
+ EXPECT_TRUE(!recognition_ended_ || recognition_started_);
+ EXPECT_TRUE(!audio_ended_ || audio_started_);
+ EXPECT_TRUE(!sound_ended_ || sound_started_);
+ EXPECT_TRUE(!audio_started_ || recognition_started_);
+ EXPECT_TRUE(!sound_started_ || audio_started_);
+ EXPECT_TRUE(!audio_ended_ || (sound_ended_ || !sound_started_));
+ EXPECT_TRUE(!recognition_ended_ || (audio_ended_ || !audio_started_));
+ }
+
+ void CheckFinalEventsConsistency() {
+ // Note: "!(x ^ y)" == "(x && y) || (!x && !x)".
+ EXPECT_FALSE(recognition_started_ ^ recognition_ended_);
+ EXPECT_FALSE(audio_started_ ^ audio_ended_);
+ EXPECT_FALSE(sound_started_ ^ sound_ended_);
+ }
+
+ // Overridden from SpeechRecognitionEventListener:
+ virtual void OnAudioStart(int session_id) OVERRIDE {
+ audio_started_ = true;
+ CheckEventsConsistency();
+ }
+
+ virtual void OnAudioEnd(int session_id) OVERRIDE {
+ audio_ended_ = true;
+ CheckEventsConsistency();
+ }
+
+ virtual void OnRecognitionResults(
+ int session_id, const SpeechRecognitionResults& results) OVERRIDE {
+ result_received_ = true;
+ }
+
+ virtual void OnRecognitionError(
+ int session_id, const SpeechRecognitionError& error) OVERRIDE {
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_FALSE(recognition_ended_);
+ error_ = error.code;
+ }
+
+ virtual void OnAudioLevelsChange(int session_id, float volume,
+ float noise_volume) OVERRIDE {
+ volume_ = volume;
+ noise_volume_ = noise_volume;
+ }
+
+ virtual void OnRecognitionEnd(int session_id) OVERRIDE {
+ recognition_ended_ = true;
+ CheckEventsConsistency();
+ }
+
+ virtual void OnRecognitionStart(int session_id) OVERRIDE {
+ recognition_started_ = true;
+ CheckEventsConsistency();
+ }
+
+ virtual void OnEnvironmentEstimationComplete(int session_id) OVERRIDE {}
+
+ virtual void OnSoundStart(int session_id) OVERRIDE {
+ sound_started_ = true;
+ CheckEventsConsistency();
+ }
+
+ virtual void OnSoundEnd(int session_id) OVERRIDE {
+ sound_ended_ = true;
+ CheckEventsConsistency();
+ }
+
+ // testing::Test methods.
+ virtual void SetUp() OVERRIDE {
+ AudioInputController::set_factory_for_testing(
+ &audio_input_controller_factory_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ AudioInputController::set_factory_for_testing(NULL);
+ }
+
+ void FillPacketWithTestWaveform() {
+ // Fill the input with a simple pattern, a 125Hz sawtooth waveform.
+ for (size_t i = 0; i < audio_packet_.size(); ++i)
+ audio_packet_[i] = static_cast<uint8>(i);
+ }
+
+ void FillPacketWithNoise() {
+ int value = 0;
+ int factor = 175;
+ for (size_t i = 0; i < audio_packet_.size(); ++i) {
+ value += factor;
+ audio_packet_[i] = value % 100;
+ }
+ }
+
+ protected:
+ base::MessageLoopForIO message_loop_;
+ BrowserThreadImpl io_thread_;
+ scoped_refptr<SpeechRecognizerImpl> recognizer_;
+ scoped_ptr<AudioManager> audio_manager_;
+ bool recognition_started_;
+ bool recognition_ended_;
+ bool result_received_;
+ bool audio_started_;
+ bool audio_ended_;
+ bool sound_started_;
+ bool sound_ended_;
+ SpeechRecognitionErrorCode error_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ TestAudioInputControllerFactory audio_input_controller_factory_;
+ std::vector<uint8> audio_packet_;
+ float volume_;
+ float noise_volume_;
+};
+
+TEST_F(SpeechRecognizerImplTest, StopNoData) {
+ // Check for callbacks when stopping record before any audio gets recorded.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ recognizer_->StopAudioCapture();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_FALSE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, CancelNoData) {
+ // Check for callbacks when canceling recognition before any audio gets
+ // recorded.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ recognizer_->AbortRecognition();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_FALSE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_ABORTED, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, StopWithData) {
+ // Start recording, give some data and then stop. This should wait for the
+ // network callback to arrive before completion.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+
+ // Try sending 5 chunks of mock audio data and verify that each of them
+ // resulted immediately in a packet sent out via the network. This verifies
+ // that we are streaming out encoded data as chunks without waiting for the
+ // full recording to complete.
+ const size_t kNumChunks = 5;
+ for (size_t i = 0; i < kNumChunks; ++i) {
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ EXPECT_EQ(i + 1, fetcher->upload_chunks().size());
+ }
+
+ recognizer_->StopAudioCapture();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(audio_started_);
+ EXPECT_TRUE(audio_ended_);
+ EXPECT_FALSE(recognition_ended_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+
+ // Issue the network callback to complete the process.
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ fetcher->set_url(fetcher->GetOriginalURL());
+ net::URLRequestStatus status;
+ status.set_status(net::URLRequestStatus::SUCCESS);
+ fetcher->set_status(status);
+ fetcher->set_response_code(200);
+ fetcher->SetResponseString(
+ "{\"status\":0,\"hypotheses\":[{\"utterance\":\"123\"}]}");
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_ended_);
+ EXPECT_TRUE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, CancelWithData) {
+ // Start recording, give some data and then cancel.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ recognizer_->AbortRecognition();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(url_fetcher_factory_.GetFetcherByID(0));
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_TRUE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_ABORTED, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, ConnectionError) {
+ // Start recording, give some data and then stop. Issue the network callback
+ // with a connection error and verify that the recognizer bubbles the error up
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ recognizer_->StopAudioCapture();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(audio_started_);
+ EXPECT_TRUE(audio_ended_);
+ EXPECT_FALSE(recognition_ended_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+
+ // Issue the network callback to complete the process.
+ fetcher->set_url(fetcher->GetOriginalURL());
+ net::URLRequestStatus status;
+ status.set_status(net::URLRequestStatus::FAILED);
+ status.set_error(net::ERR_CONNECTION_REFUSED);
+ fetcher->set_status(status);
+ fetcher->set_response_code(0);
+ fetcher->SetResponseString(std::string());
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_ended_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NETWORK, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, ServerError) {
+ // Start recording, give some data and then stop. Issue the network callback
+ // with a 500 error and verify that the recognizer bubbles the error up
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ recognizer_->StopAudioCapture();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(audio_started_);
+ EXPECT_TRUE(audio_ended_);
+ EXPECT_FALSE(recognition_ended_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+
+ // Issue the network callback to complete the process.
+ fetcher->set_url(fetcher->GetOriginalURL());
+ net::URLRequestStatus status;
+ status.set_status(net::URLRequestStatus::SUCCESS);
+ fetcher->set_status(status);
+ fetcher->set_response_code(500);
+ fetcher->SetResponseString("Internal Server Error");
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_ended_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NETWORK, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, AudioControllerErrorNoData) {
+ // Check if things tear down properly if AudioInputController threw an error.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller->event_handler()->OnError(controller);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_FALSE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_AUDIO, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, AudioControllerErrorWithData) {
+ // Check if things tear down properly if AudioInputController threw an error
+ // after giving some audio data.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ controller->event_handler()->OnError(controller);
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(url_fetcher_factory_.GetFetcherByID(0));
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_TRUE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_AUDIO, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, NoSpeechCallbackIssued) {
+ // Start recording and give a lot of packets with audio samples set to zero.
+ // This should trigger the no-speech detector and issue a callback.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+
+ int num_packets = (SpeechRecognizerImpl::kNoSpeechTimeoutMs) /
+ GoogleOneShotRemoteEngine::kAudioPacketIntervalMs + 1;
+ // The vector is already filled with zero value samples on create.
+ for (int i = 0; i < num_packets; ++i) {
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ }
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(recognition_started_);
+ EXPECT_TRUE(audio_started_);
+ EXPECT_FALSE(result_received_);
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NO_SPEECH, error_);
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, NoSpeechCallbackNotIssued) {
+ // Start recording and give a lot of packets with audio samples set to zero
+ // and then some more with reasonably loud audio samples. This should be
+ // treated as normal speech input and the no-speech detector should not get
+ // triggered.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller = audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+
+ int num_packets = (SpeechRecognizerImpl::kNoSpeechTimeoutMs) /
+ GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
+
+ // The vector is already filled with zero value samples on create.
+ for (int i = 0; i < num_packets / 2; ++i) {
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ }
+
+ FillPacketWithTestWaveform();
+ for (int i = 0; i < num_packets / 2; ++i) {
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ EXPECT_TRUE(audio_started_);
+ EXPECT_FALSE(audio_ended_);
+ EXPECT_FALSE(recognition_ended_);
+ recognizer_->AbortRecognition();
+ base::MessageLoop::current()->RunUntilIdle();
+ CheckFinalEventsConsistency();
+}
+
+TEST_F(SpeechRecognizerImplTest, SetInputVolumeCallback) {
+ // Start recording and give a lot of packets with audio samples set to zero
+ // and then some more with reasonably loud audio samples. Check that we don't
+ // get the callback during estimation phase, then get zero for the silence
+ // samples and proper volume for the loud audio.
+ recognizer_->StartRecognition(media::AudioManagerBase::kDefaultDeviceId);
+ base::MessageLoop::current()->RunUntilIdle();
+ TestAudioInputController* controller =
+ audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+ controller = audio_input_controller_factory_.controller();
+ ASSERT_TRUE(controller);
+
+ // Feed some samples to begin with for the endpointer to do noise estimation.
+ int num_packets = SpeechRecognizerImpl::kEndpointerEstimationTimeMs /
+ GoogleOneShotRemoteEngine::kAudioPacketIntervalMs;
+ FillPacketWithNoise();
+ for (int i = 0; i < num_packets; ++i) {
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ }
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(-1.0f, volume_); // No audio volume set yet.
+
+ // The vector is already filled with zero value samples on create.
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FLOAT_EQ(0.74939233f, volume_);
+
+ FillPacketWithTestWaveform();
+ controller->event_handler()->OnData(controller, &audio_packet_[0],
+ audio_packet_.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_NEAR(0.89926866f, volume_, 0.00001f);
+ EXPECT_FLOAT_EQ(0.75071919f, noise_volume_);
+
+ EXPECT_EQ(SPEECH_RECOGNITION_ERROR_NONE, error_);
+ EXPECT_FALSE(audio_ended_);
+ EXPECT_FALSE(recognition_ended_);
+ recognizer_->AbortRecognition();
+ base::MessageLoop::current()->RunUntilIdle();
+ CheckFinalEventsConsistency();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/OWNERS b/chromium/content/browser/ssl/OWNERS
new file mode 100644
index 00000000000..cf00f7132d6
--- /dev/null
+++ b/chromium/content/browser/ssl/OWNERS
@@ -0,0 +1 @@
+abarth@chromium.org
diff --git a/chromium/content/browser/ssl/ssl_cert_error_handler.cc b/chromium/content/browser/ssl/ssl_cert_error_handler.cc
new file mode 100644
index 00000000000..b3bdb4100e9
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_cert_error_handler.cc
@@ -0,0 +1,51 @@
+// 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 "content/browser/ssl/ssl_cert_error_handler.h"
+
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/browser/ssl/ssl_policy.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_certificate.h"
+
+namespace content {
+
+SSLCertErrorHandler::SSLCertErrorHandler(
+ const base::WeakPtr<Delegate>& delegate,
+ const GlobalRequestID& id,
+ ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ const net::SSLInfo& ssl_info,
+ bool fatal)
+ : SSLErrorHandler(delegate, id, resource_type, url, render_process_id,
+ render_view_id),
+ ssl_info_(ssl_info),
+ cert_error_(net::MapCertStatusToNetError(ssl_info.cert_status)),
+ fatal_(fatal) {
+}
+
+SSLCertErrorHandler* SSLCertErrorHandler::AsSSLCertErrorHandler() {
+ return this;
+}
+
+void SSLCertErrorHandler::OnDispatchFailed() {
+ // Requests can fail to dispatch because they don't have a WebContents. See
+ // <http://crbug.com/86537>. In this case we have to make a decision in this
+ // function, so we ignore revocation check failures.
+ if (net::IsCertStatusMinorError(ssl_info().cert_status)) {
+ ContinueRequest();
+ } else {
+ CancelRequest();
+ }
+}
+
+void SSLCertErrorHandler::OnDispatched() {
+ manager_->policy()->OnCertError(this);
+}
+
+SSLCertErrorHandler::~SSLCertErrorHandler() {}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_cert_error_handler.h b/chromium/content/browser/ssl/ssl_cert_error_handler.h
new file mode 100644
index 00000000000..696fb817c8d
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_cert_error_handler.h
@@ -0,0 +1,57 @@
+// 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 CONTENT_BROWSER_SSL_SSL_CERT_ERROR_HANDLER_H_
+#define CONTENT_BROWSER_SSL_SSL_CERT_ERROR_HANDLER_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "content/browser/ssl/ssl_error_handler.h"
+#include "net/ssl/ssl_info.h"
+
+namespace content {
+
+// A CertError represents an error that occurred with the certificate in an
+// SSL session. A CertError object exists both on the IO thread and on the UI
+// thread and allows us to cancel/continue a request it is associated with.
+class SSLCertErrorHandler : public SSLErrorHandler {
+ public:
+ // Construct on the IO thread.
+ SSLCertErrorHandler(const base::WeakPtr<Delegate>& delegate,
+ const GlobalRequestID& id,
+ ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ const net::SSLInfo& ssl_info,
+ bool fatal);
+
+ virtual SSLCertErrorHandler* AsSSLCertErrorHandler() OVERRIDE;
+
+ // These accessors are available on either thread
+ const net::SSLInfo& ssl_info() const { return ssl_info_; }
+ int cert_error() const { return cert_error_; }
+ bool fatal() const { return fatal_; }
+
+ protected:
+ // SSLErrorHandler methods
+ virtual void OnDispatchFailed() OVERRIDE;
+ virtual void OnDispatched() OVERRIDE;
+
+ private:
+ virtual ~SSLCertErrorHandler();
+
+ // These read-only members may be accessed on any thread.
+ const net::SSLInfo ssl_info_;
+ const int cert_error_; // The error we represent.
+ const bool fatal_; // True if the error is from a host requiring
+ // certificate errors to be fatal.
+
+ DISALLOW_COPY_AND_ASSIGN(SSLCertErrorHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_CERT_ERROR_HANDLER_H_
diff --git a/chromium/content/browser/ssl/ssl_client_auth_handler.cc b/chromium/content/browser/ssl/ssl_client_auth_handler.cc
new file mode 100644
index 00000000000..3340334f183
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_client_auth_handler.cc
@@ -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.
+
+#include "content/browser/ssl/ssl_client_auth_handler.h"
+
+#include "base/bind.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace content {
+
+SSLClientAuthHandler::SSLClientAuthHandler(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info)
+ : request_(request),
+ http_network_session_(
+ request_->context()->http_transaction_factory()->GetSession()),
+ cert_request_info_(cert_request_info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+SSLClientAuthHandler::~SSLClientAuthHandler() {
+ // If we were simply dropped, then act as if we selected no certificate.
+ DoCertificateSelected(NULL);
+}
+
+void SSLClientAuthHandler::OnRequestCancelled() {
+ request_ = NULL;
+}
+
+void SSLClientAuthHandler::SelectCertificate() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(request_);
+
+ int render_process_host_id;
+ int render_view_host_id;
+ if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderView(
+ &render_process_host_id,
+ &render_view_host_id))
+ NOTREACHED();
+
+ // If the RVH does not exist by the time this task gets run, then the task
+ // will be dropped and the scoped_refptr to SSLClientAuthHandler will go
+ // away, so we do not leak anything. The destructor takes care of ensuring
+ // the net::URLRequest always gets a response.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &SSLClientAuthHandler::DoSelectCertificate, this,
+ render_process_host_id, render_view_host_id));
+}
+
+void SSLClientAuthHandler::CertificateSelected(net::X509Certificate* cert) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ VLOG(1) << this << " CertificateSelected " << cert;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &SSLClientAuthHandler::DoCertificateSelected, this,
+ make_scoped_refptr(cert)));
+}
+
+void SSLClientAuthHandler::DoCertificateSelected(net::X509Certificate* cert) {
+ VLOG(1) << this << " DoCertificateSelected " << cert;
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // request_ could have been NULLed if the request was cancelled while the
+ // user was choosing a cert, or because we have already responded to the
+ // certificate.
+ if (request_) {
+ request_->ContinueWithCertificate(cert);
+
+ ResourceDispatcherHostImpl::Get()->
+ ClearSSLClientAuthHandlerForRequest(request_);
+ request_ = NULL;
+ }
+}
+
+void SSLClientAuthHandler::DoSelectCertificate(
+ int render_process_host_id, int render_view_host_id) {
+ GetContentClient()->browser()->SelectClientCertificate(
+ render_process_host_id,
+ render_view_host_id,
+ http_network_session_,
+ cert_request_info_.get(),
+ base::Bind(&SSLClientAuthHandler::CertificateSelected, this));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_client_auth_handler.h b/chromium/content/browser/ssl/ssl_client_auth_handler.h
new file mode 100644
index 00000000000..9bd82756525
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_client_auth_handler.h
@@ -0,0 +1,78 @@
+// 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 CONTENT_BROWSER_SSL_SSL_CLIENT_AUTH_HANDLER_H_
+#define CONTENT_BROWSER_SSL_SSL_CLIENT_AUTH_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/ssl/ssl_cert_request_info.h"
+
+namespace net {
+class HttpNetworkSession;
+class URLRequest;
+class X509Certificate;
+} // namespace net
+
+namespace content {
+
+// This class handles the approval and selection of a certificate for SSL client
+// authentication by the user.
+// It is self-owned and deletes itself when the UI reports the user selection or
+// when the net::URLRequest is cancelled.
+class CONTENT_EXPORT SSLClientAuthHandler
+ : public base::RefCountedThreadSafe<
+ SSLClientAuthHandler, BrowserThread::DeleteOnIOThread> {
+ public:
+ SSLClientAuthHandler(net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info);
+
+ // Selects a certificate and resumes the URL request with that certificate.
+ // Should only be called on the IO thread.
+ void SelectCertificate();
+
+ // Invoked when the request associated with this handler is cancelled.
+ // Should only be called on the IO thread.
+ void OnRequestCancelled();
+
+ // Calls DoCertificateSelected on the I/O thread.
+ // Called on the UI thread after the user has made a selection (which may
+ // be long after DoSelectCertificate returns, if the UI is modeless/async.)
+ void CertificateSelected(net::X509Certificate* cert);
+
+ protected:
+ virtual ~SSLClientAuthHandler();
+
+ private:
+ friend class base::RefCountedThreadSafe<
+ SSLClientAuthHandler, BrowserThread::DeleteOnIOThread>;
+ friend class BrowserThread;
+ friend class base::DeleteHelper<SSLClientAuthHandler>;
+
+ // Notifies that the user has selected a cert.
+ // Called on the IO thread.
+ void DoCertificateSelected(net::X509Certificate* cert);
+
+ // Selects a client certificate on the UI thread.
+ void DoSelectCertificate(int render_process_host_id,
+ int render_view_host_id);
+
+ // The net::URLRequest that triggered this client auth.
+ net::URLRequest* request_;
+
+ // The HttpNetworkSession |request_| is associated with.
+ const net::HttpNetworkSession* http_network_session_;
+
+ // The certs to choose from.
+ scoped_refptr<net::SSLCertRequestInfo> cert_request_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLClientAuthHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_CLIENT_AUTH_HANDLER_H_
diff --git a/chromium/content/browser/ssl/ssl_error_handler.cc b/chromium/content/browser/ssl/ssl_error_handler.cc
new file mode 100644
index 00000000000..85cb3a4beda
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_error_handler.cc
@@ -0,0 +1,178 @@
+// 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 "content/browser/ssl/ssl_error_handler.h"
+
+#include "base/bind.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/ssl/ssl_cert_error_handler.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_request_info.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+
+using net::SSLInfo;
+
+namespace content {
+
+SSLErrorHandler::SSLErrorHandler(const base::WeakPtr<Delegate>& delegate,
+ const GlobalRequestID& id,
+ ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id)
+ : manager_(NULL),
+ request_id_(id),
+ delegate_(delegate),
+ render_process_id_(render_process_id),
+ render_view_id_(render_view_id),
+ request_url_(url),
+ resource_type_(resource_type),
+ request_has_been_notified_(false) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(delegate.get());
+
+ // This makes sure we don't disappear on the IO thread until we've given an
+ // answer to the net::URLRequest.
+ //
+ // Release in CompleteCancelRequest, CompleteContinueRequest, or
+ // CompleteTakeNoAction.
+ AddRef();
+}
+
+SSLErrorHandler::~SSLErrorHandler() {}
+
+void SSLErrorHandler::OnDispatchFailed() {
+ TakeNoAction();
+}
+
+void SSLErrorHandler::OnDispatched() {
+ TakeNoAction();
+}
+
+SSLCertErrorHandler* SSLErrorHandler::AsSSLCertErrorHandler() {
+ return NULL;
+}
+
+void SSLErrorHandler::Dispatch() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ WebContents* web_contents = NULL;
+ RenderViewHostImpl* render_view_host =
+ RenderViewHostImpl::FromID(render_process_id_, render_view_id_);
+ if (render_view_host)
+ web_contents = render_view_host->GetDelegate()->GetAsWebContents();
+
+ if (!web_contents) {
+ // We arrived on the UI thread, but the tab we're looking for is no longer
+ // here.
+ OnDispatchFailed();
+ return;
+ }
+
+ // Hand ourselves off to the SSLManager.
+ manager_ =
+ static_cast<NavigationControllerImpl*>(&web_contents->GetController())->
+ ssl_manager();
+ OnDispatched();
+}
+
+void SSLErrorHandler::CancelRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // We need to complete this task on the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &SSLErrorHandler::CompleteCancelRequest, this, net::ERR_ABORTED));
+}
+
+void SSLErrorHandler::DenyRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // We need to complete this task on the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &SSLErrorHandler::CompleteCancelRequest, this,
+ net::ERR_INSECURE_RESPONSE));
+}
+
+void SSLErrorHandler::ContinueRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // We need to complete this task on the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SSLErrorHandler::CompleteContinueRequest, this));
+}
+
+void SSLErrorHandler::TakeNoAction() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // We need to complete this task on the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SSLErrorHandler::CompleteTakeNoAction, this));
+}
+
+void SSLErrorHandler::CompleteCancelRequest(int error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // It is important that we notify the net::URLRequest only once. If we try
+ // to notify the request twice, it may no longer exist and |this| might have
+ // already have been deleted.
+ DCHECK(!request_has_been_notified_);
+ if (request_has_been_notified_)
+ return;
+
+ SSLCertErrorHandler* cert_error = AsSSLCertErrorHandler();
+ const SSLInfo* ssl_info = NULL;
+ if (cert_error)
+ ssl_info = &cert_error->ssl_info();
+ if (delegate_.get())
+ delegate_->CancelSSLRequest(request_id_, error, ssl_info);
+ request_has_been_notified_ = true;
+
+ // We're done with this object on the IO thread.
+ Release();
+}
+
+void SSLErrorHandler::CompleteContinueRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // It is important that we notify the net::URLRequest only once. If we try to
+ // notify the request twice, it may no longer exist and |this| might have
+ // already have been deleted.
+ DCHECK(!request_has_been_notified_);
+ if (request_has_been_notified_)
+ return;
+
+ if (delegate_.get())
+ delegate_->ContinueSSLRequest(request_id_);
+ request_has_been_notified_ = true;
+
+ // We're done with this object on the IO thread.
+ Release();
+}
+
+void SSLErrorHandler::CompleteTakeNoAction() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ // It is important that we notify the net::URLRequest only once. If we try to
+ // notify the request twice, it may no longer exist and |this| might have
+ // already have been deleted.
+ DCHECK(!request_has_been_notified_);
+ if (request_has_been_notified_)
+ return;
+
+ request_has_been_notified_ = true;
+
+ // We're done with this object on the IO thread.
+ Release();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_error_handler.h b/chromium/content/browser/ssl/ssl_error_handler.h
new file mode 100644
index 00000000000..5f9e9e59f5a
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_error_handler.h
@@ -0,0 +1,172 @@
+// 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 CONTENT_BROWSER_SSL_SSL_ERROR_HANDLER_H_
+#define CONTENT_BROWSER_SSL_SSL_ERROR_HANDLER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/global_request_id.h"
+#include "url/gurl.h"
+#include "webkit/common/resource_type.h"
+
+namespace net {
+class SSLInfo;
+class URLRequest;
+} // namespace net
+
+namespace content {
+
+class ResourceDispatcherHostImpl;
+class SSLCertErrorHandler;
+class SSLManager;
+
+// An SSLErrorHandler carries information from the IO thread to the UI thread
+// and is dispatched to the appropriate SSLManager when it arrives on the
+// UI thread. Subclasses should override the OnDispatched/OnDispatchFailed
+// methods to implement the actions that should be taken on the UI thread.
+// These methods can call the different convenience methods ContinueRequest/
+// CancelRequest to perform any required action on the net::URLRequest the
+// ErrorHandler was created with.
+//
+// IMPORTANT NOTE:
+//
+// If you are not doing anything in OnDispatched/OnDispatchFailed, make sure
+// you call TakeNoAction(). This is necessary for ensuring the instance is
+// not leaked.
+//
+class SSLErrorHandler : public base::RefCountedThreadSafe<SSLErrorHandler> {
+ public:
+ // Delegate functions must be called from IO thread. All functions accept
+ // |id| as the first argument. |id| is a copy of the second argument of
+ // SSLManager::OnSSLCertificateError() and represents the request.
+ // Finally, CancelSSLRequest() or ContinueSSLRequest() will be called after
+ // SSLErrorHandler makes a decision on the SSL error.
+ class CONTENT_EXPORT Delegate {
+ public:
+ // Called when SSLErrorHandler decides to cancel the request because of
+ // the SSL error.
+ virtual void CancelSSLRequest(const GlobalRequestID& id,
+ int error,
+ const net::SSLInfo* ssl_info) = 0;
+
+ // Called when SSLErrorHandler decides to continue the request despite the
+ // SSL error.
+ virtual void ContinueSSLRequest(const GlobalRequestID& id) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ virtual SSLCertErrorHandler* AsSSLCertErrorHandler();
+
+ // Find the appropriate SSLManager for the net::URLRequest and begin handling
+ // this error.
+ //
+ // Call on UI thread.
+ void Dispatch();
+
+ // Available on either thread.
+ const GURL& request_url() const { return request_url_; }
+
+ // Available on either thread.
+ ResourceType::Type resource_type() const { return resource_type_; }
+
+ // Cancels the associated net::URLRequest.
+ // This method can be called from OnDispatchFailed and OnDispatched.
+ CONTENT_EXPORT void CancelRequest();
+
+ // Continue the net::URLRequest ignoring any previous errors. Note that some
+ // errors cannot be ignored, in which case this will result in the request
+ // being canceled.
+ // This method can be called from OnDispatchFailed and OnDispatched.
+ void ContinueRequest();
+
+ // Cancels the associated net::URLRequest and mark it as denied. The renderer
+ // processes such request in a special manner, optionally replacing them
+ // with alternate content (typically frames content is replaced with a
+ // warning message).
+ // This method can be called from OnDispatchFailed and OnDispatched.
+ void DenyRequest();
+
+ // Does nothing on the net::URLRequest but ensures the current instance ref
+ // count is decremented appropriately. Subclasses that do not want to
+ // take any specific actions in their OnDispatched/OnDispatchFailed should
+ // call this.
+ void TakeNoAction();
+
+ int render_process_id() const { return render_process_id_; }
+ int render_view_id() const { return render_view_id_; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<SSLErrorHandler>;
+
+ // Construct on the IO thread.
+ SSLErrorHandler(const base::WeakPtr<Delegate>& delegate,
+ const GlobalRequestID& id,
+ ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id);
+
+ virtual ~SSLErrorHandler();
+
+ // The following 2 methods are the methods subclasses should implement.
+ virtual void OnDispatchFailed();
+
+ // Can use the manager_ member.
+ virtual void OnDispatched();
+
+ // Should only be accessed on the UI thread.
+ SSLManager* manager_; // Our manager.
+
+ // The id of the request associated with this object.
+ // Should only be accessed from the IO thread.
+ GlobalRequestID request_id_;
+
+ // The delegate we are associated with.
+ base::WeakPtr<Delegate> delegate_;
+
+ private:
+ // Completes the CancelRequest operation on the IO thread.
+ // Call on the IO thread.
+ void CompleteCancelRequest(int error);
+
+ // Completes the ContinueRequest operation on the IO thread.
+ //
+ // Call on the IO thread.
+ void CompleteContinueRequest();
+
+ // Derefs this instance.
+ // Call on the IO thread.
+ void CompleteTakeNoAction();
+
+ // We use these members to find the correct SSLManager when we arrive on
+ // the UI thread.
+ int render_process_id_;
+ int render_view_id_;
+
+ // The URL that we requested.
+ // This read-only member can be accessed on any thread.
+ const GURL request_url_;
+
+ // What kind of resource is associated with the requested that generated
+ // that error.
+ // This read-only member can be accessed on any thread.
+ const ResourceType::Type resource_type_;
+
+ // A flag to make sure we notify the net::URLRequest exactly once.
+ // Should only be accessed on the IO thread
+ bool request_has_been_notified_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLErrorHandler);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_ERROR_HANDLER_H_
diff --git a/chromium/content/browser/ssl/ssl_host_state.cc b/chromium/content/browser/ssl/ssl_host_state.cc
new file mode 100644
index 00000000000..06c600205fa
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_host_state.cc
@@ -0,0 +1,71 @@
+// 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 "content/browser/ssl/ssl_host_state.h"
+
+#include "base/logging.h"
+#include "base/lazy_instance.h"
+#include "content/public/browser/browser_context.h"
+
+const char kKeyName[] = "content_ssl_host_state";
+
+namespace content {
+
+SSLHostState* SSLHostState::GetFor(BrowserContext* context) {
+ SSLHostState* rv = static_cast<SSLHostState*>(context->GetUserData(kKeyName));
+ if (!rv) {
+ rv = new SSLHostState();
+ context->SetUserData(kKeyName, rv);
+ }
+ return rv;
+}
+
+SSLHostState::SSLHostState() {
+}
+
+SSLHostState::~SSLHostState() {
+}
+
+void SSLHostState::HostRanInsecureContent(const std::string& host, int pid) {
+ DCHECK(CalledOnValidThread());
+ ran_insecure_content_hosts_.insert(BrokenHostEntry(host, pid));
+}
+
+bool SSLHostState::DidHostRunInsecureContent(const std::string& host,
+ int pid) const {
+ DCHECK(CalledOnValidThread());
+ return !!ran_insecure_content_hosts_.count(BrokenHostEntry(host, pid));
+}
+
+void SSLHostState::DenyCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ DCHECK(CalledOnValidThread());
+
+ cert_policy_for_host_[host].Deny(cert, error);
+}
+
+void SSLHostState::AllowCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ DCHECK(CalledOnValidThread());
+
+ cert_policy_for_host_[host].Allow(cert, error);
+}
+
+void SSLHostState::Clear() {
+ DCHECK(CalledOnValidThread());
+
+ cert_policy_for_host_.clear();
+}
+
+net::CertPolicy::Judgment SSLHostState::QueryPolicy(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ DCHECK(CalledOnValidThread());
+
+ return cert_policy_for_host_[host].Check(cert, error);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_host_state.h b/chromium/content/browser/ssl/ssl_host_state.h
new file mode 100644
index 00000000000..820821786d3
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_host_state.h
@@ -0,0 +1,84 @@
+// 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 CONTENT_BROWSER_SSL_SSL_HOST_STATE_H_
+#define CONTENT_BROWSER_SSL_SSL_HOST_STATE_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/supports_user_data.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_certificate.h"
+
+namespace content {
+class BrowserContext;
+
+// SSLHostState
+//
+// The SSLHostState encapulates the host-specific state for SSL errors. For
+// example, SSLHostState remembers whether the user has whitelisted a
+// particular broken cert for use with particular host. We separate this state
+// from the SSLManager because this state is shared across many navigation
+// controllers.
+
+class CONTENT_EXPORT SSLHostState
+ : NON_EXPORTED_BASE(base::SupportsUserData::Data),
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ static SSLHostState* GetFor(BrowserContext* browser_context);
+
+ SSLHostState();
+ virtual ~SSLHostState();
+
+ // Records that a host has run insecure content.
+ void HostRanInsecureContent(const std::string& host, int pid);
+
+ // Returns whether the specified host ran insecure content.
+ bool DidHostRunInsecureContent(const std::string& host, int pid) const;
+
+ // Records that |cert| is not permitted to be used for |host| in the future,
+ // for a specified |error| type..
+ void DenyCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ // Records that |cert| is permitted to be used for |host| in the future, for
+ // a specified |error| type.
+ void AllowCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ // Clear all allow/deny preferences.
+ void Clear();
+
+ // Queries whether |cert| is allowed or denied for |host| and |error|.
+ net::CertPolicy::Judgment QueryPolicy(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ private:
+ // A BrokenHostEntry is a pair of (host, process_id) that indicates the host
+ // contains insecure content in that renderer process.
+ typedef std::pair<std::string, int> BrokenHostEntry;
+
+ // Hosts which have been contaminated with insecure content in the
+ // specified process. Note that insecure content can travel between
+ // same-origin frames in one processs but cannot jump between processes.
+ std::set<BrokenHostEntry> ran_insecure_content_hosts_;
+
+ // Certificate policies for each host.
+ std::map<std::string, net::CertPolicy> cert_policy_for_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLHostState);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_HOST_STATE_H_
diff --git a/chromium/content/browser/ssl/ssl_host_state_unittest.cc b/chromium/content/browser/ssl/ssl_host_state_unittest.cc
new file mode 100644
index 00000000000..5e4366d375d
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_host_state_unittest.cc
@@ -0,0 +1,203 @@
+// 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 "content/browser/ssl/ssl_host_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Certificates for test data. They're obtained with:
+//
+// $ openssl s_client -connect [host]:443 -showcerts
+// $ openssl x509 -inform PEM -outform DER > /tmp/host.der
+// $ xxd -i /tmp/host.der
+
+// Google's cert.
+
+unsigned char google_der[] = {
+ 0x30, 0x82, 0x03, 0x21, 0x30, 0x82, 0x02, 0x8a, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x3c, 0x8d, 0x3a, 0x64, 0xee, 0x18, 0xdd, 0x1b, 0x73,
+ 0x0b, 0xa1, 0x92, 0xee, 0xf8, 0x98, 0x1b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x5a,
+ 0x41, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c,
+ 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75,
+ 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x28, 0x50, 0x74, 0x79, 0x29, 0x20,
+ 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x0d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x47,
+ 0x43, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x35,
+ 0x30, 0x32, 0x31, 0x37, 0x30, 0x32, 0x35, 0x35, 0x5a, 0x17, 0x0d, 0x30,
+ 0x39, 0x30, 0x35, 0x30, 0x32, 0x31, 0x37, 0x30, 0x32, 0x35, 0x35, 0x5a,
+ 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x4d,
+ 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x17, 0x30,
+ 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x77, 0x77, 0x77, 0x2e,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x81,
+ 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02,
+ 0x81, 0x81, 0x00, 0x9b, 0x19, 0xed, 0x5d, 0xa5, 0x56, 0xaf, 0x49, 0x66,
+ 0xdb, 0x79, 0xfd, 0xc2, 0x1c, 0x78, 0x4e, 0x4f, 0x11, 0xa5, 0x8a, 0xac,
+ 0xe2, 0x94, 0xee, 0xe3, 0xe2, 0x4b, 0xc0, 0x03, 0x25, 0xa7, 0x99, 0xcc,
+ 0x65, 0xe1, 0xec, 0x94, 0xae, 0xae, 0xf0, 0xa7, 0x99, 0xbc, 0x10, 0xd7,
+ 0xed, 0x87, 0x30, 0x47, 0xcd, 0x50, 0xf9, 0xaf, 0xd3, 0xd3, 0xf4, 0x0b,
+ 0x8d, 0x47, 0x8a, 0x2e, 0xe2, 0xce, 0x53, 0x9b, 0x91, 0x99, 0x7f, 0x1e,
+ 0x5c, 0xf9, 0x1b, 0xd6, 0xe9, 0x93, 0x67, 0xe3, 0x4a, 0xf8, 0xcf, 0xc4,
+ 0x8c, 0x0c, 0x68, 0xd1, 0x97, 0x54, 0x47, 0x0e, 0x0a, 0x24, 0x30, 0xa7,
+ 0x82, 0x94, 0xae, 0xde, 0xae, 0x3f, 0xbf, 0xba, 0x14, 0xc6, 0xf8, 0xb2,
+ 0x90, 0x8e, 0x36, 0xad, 0xe1, 0xd0, 0xbe, 0x16, 0x9a, 0xb3, 0x5e, 0x72,
+ 0x38, 0x49, 0xda, 0x74, 0xa1, 0x3f, 0xff, 0xd2, 0x87, 0x81, 0xed, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xe7, 0x30, 0x81, 0xe4, 0x30, 0x28,
+ 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x42, 0x04, 0x01, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68,
+ 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x72, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x66, 0x30, 0x64, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x5f, 0x53, 0x47,
+ 0x43, 0x5f, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0c, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x81, 0x81, 0x00, 0x31, 0x0a, 0x6c, 0xa2, 0x9e, 0xe9, 0x54,
+ 0x19, 0x16, 0x68, 0x99, 0x91, 0xd6, 0x43, 0xcb, 0x6b, 0xb4, 0xcc, 0x6c,
+ 0xcc, 0xb0, 0xfb, 0xf1, 0xee, 0x81, 0xbf, 0x00, 0x2b, 0x6f, 0x50, 0x12,
+ 0xc6, 0xaf, 0x02, 0x2a, 0x36, 0xc1, 0x28, 0xde, 0xc5, 0x4c, 0x56, 0x20,
+ 0x6d, 0xf5, 0x3d, 0x42, 0xb9, 0x18, 0x81, 0x20, 0xb2, 0xdd, 0x57, 0x5d,
+ 0xeb, 0xbe, 0x32, 0x84, 0x50, 0x45, 0x51, 0x6e, 0xcd, 0xe4, 0x2e, 0x2a,
+ 0x38, 0x88, 0x9f, 0x52, 0xed, 0x28, 0xff, 0xfc, 0x8d, 0x57, 0xb5, 0xad,
+ 0x64, 0xae, 0x4d, 0x0e, 0x0e, 0xd9, 0x3d, 0xac, 0xb8, 0xfe, 0x66, 0x4c,
+ 0x15, 0x8f, 0x44, 0x52, 0xfa, 0x7c, 0x3c, 0x04, 0xed, 0x7f, 0x37, 0x61,
+ 0x04, 0xfe, 0xd5, 0xe9, 0xb9, 0xb0, 0x9e, 0xfe, 0xa5, 0x11, 0x69, 0xc9,
+ 0x63, 0xd6, 0x46, 0x81, 0x6f, 0x00, 0xd8, 0x72, 0x2f, 0x82, 0x37, 0x44,
+ 0xc1
+};
+
+} // namespace
+
+namespace content {
+
+class SSLHostStateTest : public testing::Test {
+};
+
+TEST_F(SSLHostStateTest, DidHostRunInsecureContent) {
+ SSLHostState state;
+
+ EXPECT_FALSE(state.DidHostRunInsecureContent("www.google.com", 42));
+ EXPECT_FALSE(state.DidHostRunInsecureContent("www.google.com", 191));
+ EXPECT_FALSE(state.DidHostRunInsecureContent("example.com", 42));
+
+ state.HostRanInsecureContent("www.google.com", 42);
+
+ EXPECT_TRUE(state.DidHostRunInsecureContent("www.google.com", 42));
+ EXPECT_FALSE(state.DidHostRunInsecureContent("www.google.com", 191));
+ EXPECT_FALSE(state.DidHostRunInsecureContent("example.com", 42));
+
+ state.HostRanInsecureContent("example.com", 42);
+
+ EXPECT_TRUE(state.DidHostRunInsecureContent("www.google.com", 42));
+ EXPECT_FALSE(state.DidHostRunInsecureContent("www.google.com", 191));
+ EXPECT_TRUE(state.DidHostRunInsecureContent("example.com", 42));
+}
+
+TEST_F(SSLHostStateTest, QueryPolicy) {
+ scoped_refptr<net::X509Certificate> google_cert(
+ net::X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der)));
+
+ SSLHostState state;
+
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID));
+
+ state.AllowCertForHost(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID);
+
+ EXPECT_EQ(net::CertPolicy::ALLOWED,
+ state.QueryPolicy(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID));
+
+ state.AllowCertForHost(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID);
+
+ EXPECT_EQ(net::CertPolicy::ALLOWED,
+ state.QueryPolicy(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::ALLOWED,
+ state.QueryPolicy(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID));
+
+ state.DenyCertForHost(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID);
+
+ EXPECT_EQ(net::CertPolicy::ALLOWED,
+ state.QueryPolicy(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::DENIED,
+ state.QueryPolicy(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID));
+
+ state.Clear();
+
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "www.google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "google.com",
+ net::CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(net::CertPolicy::UNKNOWN,
+ state.QueryPolicy(google_cert.get(),
+ "example.com",
+ net::CERT_STATUS_DATE_INVALID));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_manager.cc b/chromium/content/browser/ssl/ssl_manager.cc
new file mode 100644
index 00000000000..c86dccdcda6
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_manager.cc
@@ -0,0 +1,249 @@
+// 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 "content/browser/ssl/ssl_manager.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/supports_user_data.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/ssl/ssl_cert_error_handler.h"
+#include "content/browser/ssl/ssl_policy.h"
+#include "content/browser/ssl/ssl_request_info.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/ssl_status_serialization.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/load_from_memory_cache_details.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/resource_request_details.h"
+#include "content/public/common/ssl_status.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+namespace {
+
+const char kSSLManagerKeyName[] = "content_ssl_manager";
+
+class SSLManagerSet : public base::SupportsUserData::Data {
+ public:
+ SSLManagerSet() {
+ }
+
+ std::set<SSLManager*>& get() { return set_; }
+
+ private:
+ std::set<SSLManager*> set_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLManagerSet);
+};
+
+} // namespace
+
+// static
+void SSLManager::OnSSLCertificateError(
+ const base::WeakPtr<SSLErrorHandler::Delegate>& delegate,
+ const GlobalRequestID& id,
+ const ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ DCHECK(delegate.get());
+ DVLOG(1) << "OnSSLCertificateError() cert_error: "
+ << net::MapCertStatusToNetError(ssl_info.cert_status) << " id: "
+ << id.child_id << "," << id.request_id << " resource_type: "
+ << resource_type << " url: " << url.spec() << " render_process_id: "
+ << render_process_id << " render_view_id: " << render_view_id
+ << " cert_status: " << std::hex << ssl_info.cert_status;
+
+ // A certificate error occurred. Construct a SSLCertErrorHandler object and
+ // hand it over to the UI thread for processing.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SSLCertErrorHandler::Dispatch,
+ new SSLCertErrorHandler(delegate,
+ id,
+ resource_type,
+ url,
+ render_process_id,
+ render_view_id,
+ ssl_info,
+ fatal)));
+}
+
+// static
+void SSLManager::NotifySSLInternalStateChanged(BrowserContext* context) {
+ SSLManagerSet* managers = static_cast<SSLManagerSet*>(
+ context->GetUserData(kSSLManagerKeyName));
+
+ for (std::set<SSLManager*>::iterator i = managers->get().begin();
+ i != managers->get().end(); ++i) {
+ (*i)->UpdateEntry(NavigationEntryImpl::FromNavigationEntry(
+ (*i)->controller()->GetActiveEntry()));
+ }
+}
+
+SSLManager::SSLManager(NavigationControllerImpl* controller)
+ : backend_(controller),
+ policy_(new SSLPolicy(&backend_)),
+ controller_(controller) {
+ DCHECK(controller_);
+
+ // Subscribe to various notifications.
+ registrar_.Add(
+ this, NOTIFICATION_RESOURCE_RESPONSE_STARTED,
+ Source<WebContents>(controller_->web_contents()));
+ registrar_.Add(
+ this, NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
+ Source<WebContents>(controller_->web_contents()));
+ registrar_.Add(
+ this, NOTIFICATION_LOAD_FROM_MEMORY_CACHE,
+ Source<NavigationController>(controller_));
+
+ SSLManagerSet* managers = static_cast<SSLManagerSet*>(
+ controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName));
+ if (!managers) {
+ managers = new SSLManagerSet;
+ controller_->GetBrowserContext()->SetUserData(kSSLManagerKeyName, managers);
+ }
+ managers->get().insert(this);
+}
+
+SSLManager::~SSLManager() {
+ SSLManagerSet* managers = static_cast<SSLManagerSet*>(
+ controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName));
+ managers->get().erase(this);
+}
+
+void SSLManager::DidCommitProvisionalLoad(
+ const NotificationDetails& in_details) {
+ LoadCommittedDetails* details =
+ Details<LoadCommittedDetails>(in_details).ptr();
+
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(controller_->GetActiveEntry());
+
+ if (details->is_main_frame) {
+ if (entry) {
+ // Decode the security details.
+ int ssl_cert_id;
+ net::CertStatus ssl_cert_status;
+ int ssl_security_bits;
+ int ssl_connection_status;
+ DeserializeSecurityInfo(details->serialized_security_info,
+ &ssl_cert_id,
+ &ssl_cert_status,
+ &ssl_security_bits,
+ &ssl_connection_status);
+
+ // We may not have an entry if this is a navigation to an initial blank
+ // page. Reset the SSL information and add the new data we have.
+ entry->GetSSL() = SSLStatus();
+ entry->GetSSL().cert_id = ssl_cert_id;
+ entry->GetSSL().cert_status = ssl_cert_status;
+ entry->GetSSL().security_bits = ssl_security_bits;
+ entry->GetSSL().connection_status = ssl_connection_status;
+ }
+ }
+
+ UpdateEntry(entry);
+}
+
+void SSLManager::DidDisplayInsecureContent() {
+ UpdateEntry(
+ NavigationEntryImpl::FromNavigationEntry(controller_->GetActiveEntry()));
+}
+
+void SSLManager::DidRunInsecureContent(const std::string& security_origin) {
+ NavigationEntryImpl* navigation_entry =
+ NavigationEntryImpl::FromNavigationEntry(controller_->GetActiveEntry());
+ policy()->DidRunInsecureContent(navigation_entry, security_origin);
+ UpdateEntry(navigation_entry);
+}
+
+void SSLManager::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ // Dispatch by type.
+ switch (type) {
+ case NOTIFICATION_RESOURCE_RESPONSE_STARTED:
+ DidStartResourceResponse(
+ Details<ResourceRequestDetails>(details).ptr());
+ break;
+ case NOTIFICATION_RESOURCE_RECEIVED_REDIRECT:
+ DidReceiveResourceRedirect(
+ Details<ResourceRedirectDetails>(details).ptr());
+ break;
+ case NOTIFICATION_LOAD_FROM_MEMORY_CACHE:
+ DidLoadFromMemoryCache(
+ Details<LoadFromMemoryCacheDetails>(details).ptr());
+ break;
+ default:
+ NOTREACHED() << "The SSLManager received an unexpected notification.";
+ }
+}
+
+void SSLManager::DidLoadFromMemoryCache(LoadFromMemoryCacheDetails* details) {
+ // Simulate loading this resource through the usual path.
+ // Note that we specify SUB_RESOURCE as the resource type as WebCore only
+ // caches sub-resources.
+ // This resource must have been loaded with no filtering because filtered
+ // resouces aren't cachable.
+ scoped_refptr<SSLRequestInfo> info(new SSLRequestInfo(
+ details->url,
+ ResourceType::SUB_RESOURCE,
+ details->pid,
+ details->cert_id,
+ details->cert_status));
+
+ // Simulate loading this resource through the usual path.
+ policy()->OnRequestStarted(info.get());
+}
+
+void SSLManager::DidStartResourceResponse(ResourceRequestDetails* details) {
+ scoped_refptr<SSLRequestInfo> info(new SSLRequestInfo(
+ details->url,
+ details->resource_type,
+ details->origin_child_id,
+ details->ssl_cert_id,
+ details->ssl_cert_status));
+
+ // Notify our policy that we started a resource request. Ideally, the
+ // policy should have the ability to cancel the request, but we can't do
+ // that yet.
+ policy()->OnRequestStarted(info.get());
+}
+
+void SSLManager::DidReceiveResourceRedirect(ResourceRedirectDetails* details) {
+ // TODO(abarth): Make sure our redirect behavior is correct. If we ever see a
+ // non-HTTPS resource in the redirect chain, we want to trigger
+ // insecure content, even if the redirect chain goes back to
+ // HTTPS. This is because the network attacker can redirect the
+ // HTTP request to https://attacker.com/payload.js.
+}
+
+void SSLManager::UpdateEntry(NavigationEntryImpl* entry) {
+ // We don't always have a navigation entry to update, for example in the
+ // case of the Web Inspector.
+ if (!entry)
+ return;
+
+ SSLStatus original_ssl_status = entry->GetSSL(); // Copy!
+
+ policy()->UpdateEntry(entry, controller_->web_contents());
+
+ if (!entry->GetSSL().Equals(original_ssl_status))
+ controller_->web_contents()->DidChangeVisibleSSLState();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_manager.h b/chromium/content/browser/ssl/ssl_manager.h
new file mode 100644
index 00000000000..6c80edd8c04
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_manager.h
@@ -0,0 +1,124 @@
+// 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 CONTENT_BROWSER_SSL_SSL_MANAGER_H_
+#define CONTENT_BROWSER_SSL_SSL_MANAGER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/ssl/ssl_error_handler.h"
+#include "content/browser/ssl/ssl_policy_backend.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_status_flags.h"
+#include "url/gurl.h"
+
+namespace net {
+class SSLInfo;
+}
+
+namespace content {
+class BrowserContext;
+class NavigationEntryImpl;
+class NavigationControllerImpl;
+class SSLPolicy;
+struct LoadFromMemoryCacheDetails;
+struct ResourceRedirectDetails;
+struct ResourceRequestDetails;
+
+// The SSLManager SSLManager controls the SSL UI elements in a WebContents. It
+// listens for various events that influence when these elements should or
+// should not be displayed and adjusts them accordingly.
+//
+// There is one SSLManager per tab.
+// The security state (secure/insecure) is stored in the navigation entry.
+// Along with it are stored any SSL error code and the associated cert.
+
+class SSLManager : public NotificationObserver {
+ public:
+ // Entry point for SSLCertificateErrors. This function begins the process
+ // of resolving a certificate error during an SSL connection. SSLManager
+ // will adjust the security UI and either call |CancelSSLRequest| or
+ // |ContinueSSLRequest| of |delegate| with |id| as the first argument.
+ //
+ // Called on the IO thread.
+ static void OnSSLCertificateError(
+ const base::WeakPtr<SSLErrorHandler::Delegate>& delegate,
+ const GlobalRequestID& id,
+ ResourceType::Type resource_type,
+ const GURL& url,
+ int render_process_id,
+ int render_view_id,
+ const net::SSLInfo& ssl_info,
+ bool fatal);
+
+ // Called when SSL state for a host or tab changes.
+ static void NotifySSLInternalStateChanged(BrowserContext* context);
+
+ // Construct an SSLManager for the specified tab.
+ // If |delegate| is NULL, SSLPolicy::GetDefaultPolicy() is used.
+ explicit SSLManager(NavigationControllerImpl* controller);
+ virtual ~SSLManager();
+
+ SSLPolicy* policy() { return policy_.get(); }
+ SSLPolicyBackend* backend() { return &backend_; }
+
+ // The navigation controller associated with this SSLManager. The
+ // NavigationController is guaranteed to outlive the SSLManager.
+ NavigationControllerImpl* controller() { return controller_; }
+
+ // This entry point is called directly (instead of via the notification
+ // service) because we need more precise control of the order in which folks
+ // are notified of this event.
+ void DidCommitProvisionalLoad(const NotificationDetails& details);
+
+ // Insecure content entry point.
+ void DidDisplayInsecureContent();
+ void DidRunInsecureContent(const std::string& security_origin);
+
+ // Entry point for navigation. This function begins the process of updating
+ // the security UI when the main frame navigates to a new URL.
+ //
+ // Called on the UI thread.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Entry points for notifications to which we subscribe. Note that
+ // DidCommitProvisionalLoad uses the abstract NotificationDetails type since
+ // the type we need is in NavigationController which would create a circular
+ // header file dependency.
+ void DidLoadFromMemoryCache(LoadFromMemoryCacheDetails* details);
+ void DidStartResourceResponse(ResourceRequestDetails* details);
+ void DidReceiveResourceRedirect(ResourceRedirectDetails* details);
+
+ // Update the NavigationEntry with our current state.
+ void UpdateEntry(NavigationEntryImpl* entry);
+
+ // The backend for the SSLPolicy to actuate its decisions.
+ SSLPolicyBackend backend_;
+
+ // The SSLPolicy instance for this manager.
+ scoped_ptr<SSLPolicy> policy_;
+
+ // The NavigationController that owns this SSLManager. We are responsible
+ // for the security UI of this tab.
+ NavigationControllerImpl* controller_;
+
+ // Handles registering notifications with the NotificationService.
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_MANAGER_H_
diff --git a/chromium/content/browser/ssl/ssl_policy.cc b/chromium/content/browser/ssl/ssl_policy.cc
new file mode 100644
index 00000000000..02af3983bf8
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_policy.cc
@@ -0,0 +1,225 @@
+// 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 "content/browser/ssl/ssl_policy.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/ssl/ssl_cert_error_handler.h"
+#include "content/browser/ssl/ssl_request_info.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/ssl_status.h"
+#include "content/public/common/url_constants.h"
+#include "net/ssl/ssl_info.h"
+#include "webkit/common/resource_type.h"
+
+
+namespace content {
+
+SSLPolicy::SSLPolicy(SSLPolicyBackend* backend)
+ : backend_(backend) {
+ DCHECK(backend_);
+}
+
+void SSLPolicy::OnCertError(SSLCertErrorHandler* handler) {
+ // First we check if we know the policy for this error.
+ net::CertPolicy::Judgment judgment = backend_->QueryPolicy(
+ handler->ssl_info().cert.get(),
+ handler->request_url().host(),
+ handler->cert_error());
+
+ if (judgment == net::CertPolicy::ALLOWED) {
+ handler->ContinueRequest();
+ return;
+ }
+
+ // The judgment is either DENIED or UNKNOWN.
+ // For now we handle the DENIED as the UNKNOWN, which means a blocking
+ // page is shown to the user every time he comes back to the page.
+
+ switch (handler->cert_error()) {
+ case net::ERR_CERT_COMMON_NAME_INVALID:
+ case net::ERR_CERT_DATE_INVALID:
+ case net::ERR_CERT_AUTHORITY_INVALID:
+ case net::ERR_CERT_WEAK_SIGNATURE_ALGORITHM:
+ case net::ERR_CERT_WEAK_KEY:
+ OnCertErrorInternal(handler, !handler->fatal(), handler->fatal());
+ break;
+ case net::ERR_CERT_NO_REVOCATION_MECHANISM:
+ // Ignore this error.
+ handler->ContinueRequest();
+ break;
+ case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
+ // We ignore this error but will show a warning status in the location
+ // bar.
+ handler->ContinueRequest();
+ break;
+ case net::ERR_CERT_CONTAINS_ERRORS:
+ case net::ERR_CERT_REVOKED:
+ case net::ERR_CERT_INVALID:
+ OnCertErrorInternal(handler, false, handler->fatal());
+ break;
+ default:
+ NOTREACHED();
+ handler->CancelRequest();
+ break;
+ }
+}
+
+void SSLPolicy::DidRunInsecureContent(NavigationEntryImpl* entry,
+ const std::string& security_origin) {
+ if (!entry)
+ return;
+
+ SiteInstance* site_instance = entry->site_instance();
+ if (!site_instance)
+ return;
+
+ backend_->HostRanInsecureContent(GURL(security_origin).host(),
+ site_instance->GetProcess()->GetID());
+}
+
+void SSLPolicy::OnRequestStarted(SSLRequestInfo* info) {
+ // TODO(abarth): This mechanism is wrong. What we should be doing is sending
+ // this information back through WebKit and out some FrameLoaderClient
+ // methods.
+
+ if (net::IsCertStatusError(info->ssl_cert_status()))
+ backend_->HostRanInsecureContent(info->url().host(), info->child_id());
+}
+
+void SSLPolicy::UpdateEntry(NavigationEntryImpl* entry,
+ WebContentsImpl* web_contents) {
+ DCHECK(entry);
+
+ InitializeEntryIfNeeded(entry);
+
+ if (!entry->GetURL().SchemeIsSecure())
+ return;
+
+ // An HTTPS response may not have a certificate for some reason. When that
+ // happens, use the unauthenticated (HTTP) rather than the authentication
+ // broken security style so that we can detect this error condition.
+ if (!entry->GetSSL().cert_id) {
+ entry->GetSSL().security_style = SECURITY_STYLE_UNAUTHENTICATED;
+ return;
+ }
+
+ if (net::IsCertStatusError(entry->GetSSL().cert_status)) {
+ // Minor errors don't lower the security style to
+ // SECURITY_STYLE_AUTHENTICATION_BROKEN.
+ if (!net::IsCertStatusMinorError(entry->GetSSL().cert_status)) {
+ entry->GetSSL().security_style =
+ SECURITY_STYLE_AUTHENTICATION_BROKEN;
+ }
+ return;
+ }
+
+ SiteInstance* site_instance = entry->site_instance();
+ // Note that |site_instance| can be NULL here because NavigationEntries don't
+ // necessarily have site instances. Without a process, the entry can't
+ // possibly have insecure content. See bug http://crbug.com/12423.
+ if (site_instance &&
+ backend_->DidHostRunInsecureContent(
+ entry->GetURL().host(), site_instance->GetProcess()->GetID())) {
+ entry->GetSSL().security_style =
+ SECURITY_STYLE_AUTHENTICATION_BROKEN;
+ entry->GetSSL().content_status |= SSLStatus::RAN_INSECURE_CONTENT;
+ return;
+ }
+
+ if (web_contents->DisplayedInsecureContent())
+ entry->GetSSL().content_status |= SSLStatus::DISPLAYED_INSECURE_CONTENT;
+ else
+ entry->GetSSL().content_status &= ~SSLStatus::DISPLAYED_INSECURE_CONTENT;
+}
+
+void SSLPolicy::OnAllowCertificate(scoped_refptr<SSLCertErrorHandler> handler,
+ bool allow) {
+ if (allow) {
+ // Default behavior for accepting a certificate.
+ // Note that we should not call SetMaxSecurityStyle here, because the active
+ // NavigationEntry has just been deleted (in HideInterstitialPage) and the
+ // new NavigationEntry will not be set until DidNavigate. This is ok,
+ // because the new NavigationEntry will have its max security style set
+ // within DidNavigate.
+ //
+ // While AllowCertForHost() executes synchronously on this thread,
+ // ContinueRequest() gets posted to a different thread. Calling
+ // AllowCertForHost() first ensures deterministic ordering.
+ backend_->AllowCertForHost(handler->ssl_info().cert.get(),
+ handler->request_url().host(),
+ handler->cert_error());
+ handler->ContinueRequest();
+ } else {
+ // Default behavior for rejecting a certificate.
+ //
+ // While DenyCertForHost() executes synchronously on this thread,
+ // CancelRequest() gets posted to a different thread. Calling
+ // DenyCertForHost() first ensures deterministic ordering.
+ backend_->DenyCertForHost(handler->ssl_info().cert.get(),
+ handler->request_url().host(),
+ handler->cert_error());
+ handler->CancelRequest();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Certificate Error Routines
+
+void SSLPolicy::OnCertErrorInternal(SSLCertErrorHandler* handler,
+ bool overridable,
+ bool strict_enforcement) {
+ CertificateRequestResultType result =
+ CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE;
+ GetContentClient()->browser()->AllowCertificateError(
+ handler->render_process_id(),
+ handler->render_view_id(),
+ handler->cert_error(),
+ handler->ssl_info(),
+ handler->request_url(),
+ handler->resource_type(),
+ overridable,
+ strict_enforcement,
+ base::Bind(&SSLPolicy::OnAllowCertificate, base::Unretained(this),
+ make_scoped_refptr(handler)),
+ &result);
+ switch (result) {
+ case CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE:
+ break;
+ case CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL:
+ handler->CancelRequest();
+ break;
+ case CERTIFICATE_REQUEST_RESULT_TYPE_DENY:
+ handler->DenyRequest();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void SSLPolicy::InitializeEntryIfNeeded(NavigationEntryImpl* entry) {
+ if (entry->GetSSL().security_style != SECURITY_STYLE_UNKNOWN)
+ return;
+
+ entry->GetSSL().security_style = entry->GetURL().SchemeIsSecure() ?
+ SECURITY_STYLE_AUTHENTICATED : SECURITY_STYLE_UNAUTHENTICATED;
+}
+
+void SSLPolicy::OriginRanInsecureContent(const std::string& origin, int pid) {
+ GURL parsed_origin(origin);
+ if (parsed_origin.SchemeIsSecure())
+ backend_->HostRanInsecureContent(parsed_origin.host(), pid);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_policy.h b/chromium/content/browser/ssl/ssl_policy.h
new file mode 100644
index 00000000000..c88e10c8440
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_policy.h
@@ -0,0 +1,77 @@
+// 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 CONTENT_BROWSER_SSL_SSL_POLICY_H_
+#define CONTENT_BROWSER_SSL_SSL_POLICY_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "webkit/common/resource_type.h"
+
+namespace content {
+class NavigationEntryImpl;
+class SSLCertErrorHandler;
+class SSLPolicyBackend;
+class SSLRequestInfo;
+class WebContentsImpl;
+
+// SSLPolicy
+//
+// This class is responsible for making the security decisions that concern the
+// SSL trust indicators. It relies on the SSLPolicyBackend to actually enact
+// the decisions it reaches.
+//
+class SSLPolicy {
+ public:
+ explicit SSLPolicy(SSLPolicyBackend* backend);
+
+ // An error occurred with the certificate in an SSL connection.
+ void OnCertError(SSLCertErrorHandler* handler);
+
+ void DidRunInsecureContent(NavigationEntryImpl* entry,
+ const std::string& security_origin);
+
+ // We have started a resource request with the given info.
+ void OnRequestStarted(SSLRequestInfo* info);
+
+ // Update the SSL information in |entry| to match the current state.
+ // |web_contents| is the WebContentsImpl associated with this entry.
+ void UpdateEntry(NavigationEntryImpl* entry,
+ WebContentsImpl* web_contents);
+
+ SSLPolicyBackend* backend() const { return backend_; }
+
+ private:
+ // Callback that the user chose to accept or deny the certificate.
+ void OnAllowCertificate(scoped_refptr<SSLCertErrorHandler> handler,
+ bool allow);
+
+ // Helper method for derived classes handling certificate errors.
+ //
+ // |overridable| indicates whether or not the user could (assuming perfect
+ // knowledge) successfully override the error and still get the security
+ // guarantees of TLS. |strict_enforcement| indicates whether or not the
+ // site the user is trying to connect to has requested strict enforcement
+ // of certificate validation (e.g. with HTTP Strict-Transport-Security).
+ void OnCertErrorInternal(SSLCertErrorHandler* handler,
+ bool overridable,
+ bool strict_enforcement);
+
+ // If the security style of |entry| has not been initialized, then initialize
+ // it with the default style for its URL.
+ void InitializeEntryIfNeeded(NavigationEntryImpl* entry);
+
+ // Mark |origin| as having run insecure content in the process with ID |pid|.
+ void OriginRanInsecureContent(const std::string& origin, int pid);
+
+ // The backend we use to enact our decisions.
+ SSLPolicyBackend* backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLPolicy);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_POLICY_H_
diff --git a/chromium/content/browser/ssl/ssl_policy_backend.cc b/chromium/content/browser/ssl/ssl_policy_backend.cc
new file mode 100644
index 00000000000..57e05702c88
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_policy_backend.cc
@@ -0,0 +1,48 @@
+// 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 "content/browser/ssl/ssl_policy_backend.h"
+
+#include "content/browser/ssl/ssl_host_state.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/public/browser/browser_context.h"
+
+namespace content {
+
+SSLPolicyBackend::SSLPolicyBackend(NavigationControllerImpl* controller)
+ : ssl_host_state_(SSLHostState::GetFor(controller->GetBrowserContext())),
+ controller_(controller) {
+ DCHECK(controller_);
+}
+
+void SSLPolicyBackend::HostRanInsecureContent(const std::string& host, int id) {
+ ssl_host_state_->HostRanInsecureContent(host, id);
+ SSLManager::NotifySSLInternalStateChanged(controller_->GetBrowserContext());
+}
+
+bool SSLPolicyBackend::DidHostRunInsecureContent(const std::string& host,
+ int pid) const {
+ return ssl_host_state_->DidHostRunInsecureContent(host, pid);
+}
+
+void SSLPolicyBackend::DenyCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ ssl_host_state_->DenyCertForHost(cert, host, error);
+}
+
+void SSLPolicyBackend::AllowCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ ssl_host_state_->AllowCertForHost(cert, host, error);
+}
+
+net::CertPolicy::Judgment SSLPolicyBackend::QueryPolicy(
+ net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error) {
+ return ssl_host_state_->QueryPolicy(cert, host, error);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_policy_backend.h b/chromium/content/browser/ssl/ssl_policy_backend.h
new file mode 100644
index 00000000000..06ea23eccca
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_policy_backend.h
@@ -0,0 +1,58 @@
+// 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 CONTENT_BROWSER_SSL_SSL_POLICY_BACKEND_H_
+#define CONTENT_BROWSER_SSL_SSL_POLICY_BACKEND_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_certificate.h"
+
+namespace content {
+class NavigationControllerImpl;
+class SSLHostState;
+
+class SSLPolicyBackend {
+ public:
+ explicit SSLPolicyBackend(NavigationControllerImpl* controller);
+
+ // Records that a host has run insecure content.
+ void HostRanInsecureContent(const std::string& host, int pid);
+
+ // Returns whether the specified host ran insecure content.
+ bool DidHostRunInsecureContent(const std::string& host, int pid) const;
+
+ // Records that |cert| is not permitted to be used for |host| in the future,
+ // for a specific error type.
+ void DenyCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ // Records that |cert| is permitted to be used for |host| in the future, for
+ // a specific error type.
+ void AllowCertForHost(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ // Queries whether |cert| is allowed or denied for |host|.
+ net::CertPolicy::Judgment QueryPolicy(net::X509Certificate* cert,
+ const std::string& host,
+ net::CertStatus error);
+
+ private:
+ // SSL state specific for each host.
+ SSLHostState* ssl_host_state_;
+
+ NavigationControllerImpl* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLPolicyBackend);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_POLICY_BACKEND_H_
diff --git a/chromium/content/browser/ssl/ssl_request_info.cc b/chromium/content/browser/ssl/ssl_request_info.cc
new file mode 100644
index 00000000000..0c23f83a3ca
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_request_info.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 The Chromium 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 "content/browser/ssl/ssl_request_info.h"
+
+namespace content {
+
+SSLRequestInfo::SSLRequestInfo(const GURL& url,
+ ResourceType::Type resource_type,
+ int child_id,
+ int ssl_cert_id,
+ net::CertStatus ssl_cert_status)
+ : url_(url),
+ resource_type_(resource_type),
+ child_id_(child_id),
+ ssl_cert_id_(ssl_cert_id),
+ ssl_cert_status_(ssl_cert_status) {
+}
+
+SSLRequestInfo::~SSLRequestInfo() {}
+
+} // namespace content
diff --git a/chromium/content/browser/ssl/ssl_request_info.h b/chromium/content/browser/ssl/ssl_request_info.h
new file mode 100644
index 00000000000..cf745fe7cfe
--- /dev/null
+++ b/chromium/content/browser/ssl/ssl_request_info.h
@@ -0,0 +1,50 @@
+// 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 CONTENT_BROWSER_SSL_SSL_REQUEST_INFO_H_
+#define CONTENT_BROWSER_SSL_SSL_REQUEST_INFO_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "net/cert/cert_status_flags.h"
+#include "url/gurl.h"
+#include "webkit/common/resource_type.h"
+
+namespace content {
+
+// SSLRequestInfo wraps up the information SSLPolicy needs about a request in
+// order to update our security IU. SSLRequestInfo is RefCounted in case we
+// need to deal with the request asynchronously.
+class SSLRequestInfo : public base::RefCounted<SSLRequestInfo> {
+ public:
+ SSLRequestInfo(const GURL& url,
+ ResourceType::Type resource_type,
+ int child_id,
+ int ssl_cert_id,
+ net::CertStatus ssl_cert_status);
+
+ const GURL& url() const { return url_; }
+ ResourceType::Type resource_type() const { return resource_type_; }
+ int child_id() const { return child_id_; }
+ int ssl_cert_id() const { return ssl_cert_id_; }
+ net::CertStatus ssl_cert_status() const { return ssl_cert_status_; }
+
+ private:
+ friend class base::RefCounted<SSLRequestInfo>;
+
+ virtual ~SSLRequestInfo();
+
+ GURL url_;
+ ResourceType::Type resource_type_;
+ int child_id_;
+ int ssl_cert_id_;
+ net::CertStatus ssl_cert_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLRequestInfo);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SSL_SSL_REQUEST_INFO_H_
diff --git a/chromium/content/browser/startup_task_runner.cc b/chromium/content/browser/startup_task_runner.cc
new file mode 100644
index 00000000000..a7e730e5d87
--- /dev/null
+++ b/chromium/content/browser/startup_task_runner.cc
@@ -0,0 +1,63 @@
+// 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 "content/browser/startup_task_runner.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+
+namespace content {
+
+StartupTaskRunner::StartupTaskRunner(
+ bool browser_may_start_asynchronously,
+ base::Callback<void(int)> const startup_complete_callback,
+ scoped_refptr<base::SingleThreadTaskRunner> proxy)
+ : asynchronous_startup_(browser_may_start_asynchronously),
+ startup_complete_callback_(startup_complete_callback),
+ proxy_(proxy) {}
+
+void StartupTaskRunner::AddTask(StartupTask& callback) {
+ task_list_.push_back(callback);
+}
+
+void StartupTaskRunner::StartRunningTasks() {
+ DCHECK(proxy_);
+ int result = 0;
+ if (asynchronous_startup_ && !task_list_.empty()) {
+ const base::Closure next_task =
+ base::Bind(&StartupTaskRunner::WrappedTask, this);
+ proxy_->PostNonNestableTask(FROM_HERE, next_task);
+ } else {
+ for (std::list<StartupTask>::iterator it = task_list_.begin();
+ it != task_list_.end();
+ it++) {
+ result = it->Run();
+ if (result > 0) {
+ break;
+ }
+ }
+ if (!startup_complete_callback_.is_null()) {
+ startup_complete_callback_.Run(result);
+ }
+ }
+}
+
+void StartupTaskRunner::WrappedTask() {
+ int result = task_list_.front().Run();
+ task_list_.pop_front();
+ if (result > 0 || task_list_.empty()) {
+ if (!startup_complete_callback_.is_null()) {
+ startup_complete_callback_.Run(result);
+ }
+ } else {
+ const base::Closure next_task =
+ base::Bind(&StartupTaskRunner::WrappedTask, this);
+ proxy_->PostNonNestableTask(FROM_HERE, next_task);
+ }
+}
+
+StartupTaskRunner::~StartupTaskRunner() {}
+
+} // namespace content
diff --git a/chromium/content/browser/startup_task_runner.h b/chromium/content/browser/startup_task_runner.h
new file mode 100644
index 00000000000..5f954edc887
--- /dev/null
+++ b/chromium/content/browser/startup_task_runner.h
@@ -0,0 +1,66 @@
+// 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 CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_
+#define CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_
+
+#include <list>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+
+#include "build/build_config.h"
+
+#include "content/public/browser/browser_main_runner.h"
+
+namespace content {
+
+// A startup task is a void function returning the status on completion.
+// a status of > 0 indicates a failure, and that no further startup tasks should
+// be run.
+typedef base::Callback<int(void)> StartupTask;
+
+// This class runs startup tasks. The tasks are either run immediately inline,
+// or are queued one at a time on the UI thread's message loop. If the events
+// are queued, UI events that are received during startup will be acted upon
+// between startup tasks. The motivation for this is that, on targets where the
+// UI is already started, it allows us to keep the UI responsive during startup.
+//
+// Note that this differs from a SingleThreadedTaskRunner in that there may be
+// no opportunity to handle UI events between the tasks of a
+// SingleThreadedTaskRunner.
+
+class CONTENT_EXPORT StartupTaskRunner
+ : public base::RefCounted<StartupTaskRunner> {
+
+ public:
+ // Constructor: Note that |startup_complete_callback| is optional. If it is
+ // not null it will be called once all the startup tasks have run.
+ StartupTaskRunner(bool browser_may_start_asynchronously,
+ base::Callback<void(int)> startup_complete_callback,
+ scoped_refptr<base::SingleThreadTaskRunner> proxy);
+
+ // Add a task to the queue of startup tasks to be run.
+ virtual void AddTask(StartupTask& callback);
+
+ // Start running the tasks.
+ virtual void StartRunningTasks();
+
+ private:
+ friend class base::RefCounted<StartupTaskRunner>;
+ virtual ~StartupTaskRunner();
+
+ std::list<StartupTask> task_list_;
+ void WrappedTask();
+
+ const bool asynchronous_startup_;
+ base::Callback<void(int)> startup_complete_callback_;
+ scoped_refptr<base::SingleThreadTaskRunner> proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(StartupTaskRunner);
+};
+
+} // namespace content
+#endif // CONTENT_BROWSER_STARTUP_TASK_RUNNER_H_
diff --git a/chromium/content/browser/startup_task_runner_unittest.cc b/chromium/content/browser/startup_task_runner_unittest.cc
new file mode 100644
index 00000000000..2efa79f7ac6
--- /dev/null
+++ b/chromium/content/browser/startup_task_runner_unittest.cc
@@ -0,0 +1,281 @@
+// 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 "content/browser/startup_task_runner.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/task_runner.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+using base::Closure;
+using testing::_;
+using testing::Assign;
+using testing::Invoke;
+using testing::WithArg;
+
+bool observer_called = false;
+int observer_result;
+base::Closure task;
+
+// I couldn't get gMock's SaveArg to compile, hence had to save the argument
+// this way
+bool SaveTaskArg(const Closure& arg) {
+ task = arg;
+ return true;
+}
+
+void Observer(int result) {
+ observer_called = true;
+ observer_result = result;
+}
+
+class StartupTaskRunnerTest : public testing::Test {
+ public:
+
+ virtual void SetUp() {
+ last_task_ = 0;
+ observer_called = false;
+ }
+
+ int Task1() {
+ last_task_ = 1;
+ return 0;
+ }
+
+ int Task2() {
+ last_task_ = 2;
+ return 0;
+ }
+
+ int FailingTask() {
+ // Task returning failure
+ last_task_ = 3;
+ return 1;
+ }
+
+ int GetLastTask() { return last_task_; }
+
+ private:
+
+ int last_task_;
+};
+
+// We can't use the real message loop, even if we want to, since doing so on
+// Android requires a complex Java infrastructure. The test would have to built
+// as a content_shell test; but content_shell startup invokes the class we are
+// trying to test.
+//
+// The mocks are not directly in TaskRunnerProxy because reference counted
+// objects seem to confuse the mocking framework
+
+class MockTaskRunner {
+ public:
+ MOCK_METHOD3(
+ PostDelayedTask,
+ bool(const tracked_objects::Location&, const Closure&, base::TimeDelta));
+ MOCK_METHOD3(
+ PostNonNestableDelayedTask,
+ bool(const tracked_objects::Location&, const Closure&, base::TimeDelta));
+};
+
+class TaskRunnerProxy : public base::SingleThreadTaskRunner {
+ public:
+ TaskRunnerProxy(MockTaskRunner* mock) : mock_(mock) {}
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE { return true; }
+ virtual bool PostDelayedTask(const tracked_objects::Location& location,
+ const Closure& closure,
+ base::TimeDelta delta) OVERRIDE {
+ return mock_->PostDelayedTask(location, closure, delta);
+ }
+ virtual bool PostNonNestableDelayedTask(
+ const tracked_objects::Location& location,
+ const Closure& closure,
+ base::TimeDelta delta) OVERRIDE {
+ return mock_->PostNonNestableDelayedTask(location, closure, delta);
+ }
+
+ private:
+ MockTaskRunner* mock_;
+ virtual ~TaskRunnerProxy() {}
+};
+
+TEST_F(StartupTaskRunnerTest, SynchronousExecution) {
+ MockTaskRunner mock_runner;
+ scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);
+
+ EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
+ EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);
+
+ scoped_refptr<StartupTaskRunner> runner =
+ new StartupTaskRunner(false, base::Bind(&Observer), proxy);
+
+ StartupTask task1 =
+ base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
+ runner->AddTask(task1);
+ EXPECT_EQ(GetLastTask(), 0);
+ StartupTask task2 =
+ base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
+ runner->AddTask(task2);
+
+ // Nothing should run until we tell them to.
+ EXPECT_EQ(GetLastTask(), 0);
+ runner->StartRunningTasks();
+
+ // On an immediate StartupTaskRunner the tasks should now all have run.
+ EXPECT_EQ(GetLastTask(), 2);
+
+ EXPECT_TRUE(observer_called);
+ EXPECT_EQ(observer_result, 0);
+}
+
+TEST_F(StartupTaskRunnerTest, NullObserver) {
+ MockTaskRunner mock_runner;
+ scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);
+
+ EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
+ EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);
+
+ scoped_refptr<StartupTaskRunner> runner =
+ new StartupTaskRunner(false, base::Callback<void(int)>(), proxy);
+
+ StartupTask task1 =
+ base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
+ runner->AddTask(task1);
+ EXPECT_EQ(GetLastTask(), 0);
+ StartupTask task2 =
+ base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
+ runner->AddTask(task2);
+
+ // Nothing should run until we tell them to.
+ EXPECT_EQ(GetLastTask(), 0);
+ runner->StartRunningTasks();
+
+ // On an immediate StartupTaskRunner the tasks should now all have run.
+ EXPECT_EQ(GetLastTask(), 2);
+
+ EXPECT_FALSE(observer_called);
+}
+
+TEST_F(StartupTaskRunnerTest, SynchronousExecutionFailedTask) {
+ MockTaskRunner mock_runner;
+ scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);
+
+ EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
+ EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);
+
+ scoped_refptr<StartupTaskRunner> runner =
+ new StartupTaskRunner(false, base::Bind(&Observer), proxy);
+
+ StartupTask task3 =
+ base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this));
+ runner->AddTask(task3);
+ EXPECT_EQ(GetLastTask(), 0);
+ StartupTask task2 =
+ base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
+ runner->AddTask(task2);
+
+ // Nothing should run until we tell them to.
+ EXPECT_EQ(GetLastTask(), 0);
+ runner->StartRunningTasks();
+
+ // Only the first task should have run, since it failed
+ EXPECT_EQ(GetLastTask(), 3);
+
+ EXPECT_TRUE(observer_called);
+ EXPECT_EQ(observer_result, 1);
+}
+
+TEST_F(StartupTaskRunnerTest, AsynchronousExecution) {
+
+ MockTaskRunner mock_runner;
+ scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);
+
+ EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
+ EXPECT_CALL(
+ mock_runner,
+ PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0)))
+ .Times(testing::Between(2, 3))
+ .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg)));
+
+ scoped_refptr<StartupTaskRunner> runner =
+ new StartupTaskRunner(true, base::Bind(&Observer), proxy);
+
+ StartupTask task1 =
+ base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
+ runner->AddTask(task1);
+ StartupTask task2 =
+ base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
+ runner->AddTask(task2);
+
+ // Nothing should run until we tell them to.
+ EXPECT_EQ(GetLastTask(), 0);
+ runner->StartRunningTasks();
+
+ // No tasks should have run yet, since we the message loop hasn't run.
+ EXPECT_EQ(GetLastTask(), 0);
+
+ // Fake the actual message loop. Each time a task is run a new task should
+ // be added to the queue, hence updating "task". The loop should actually run
+ // at most 3 times (once for each task plus possibly once for the observer),
+ // the "4" is a backstop.
+ for (int i = 0; i < 4 && !observer_called; i++) {
+ task.Run();
+ EXPECT_EQ(i + 1, GetLastTask());
+ }
+ EXPECT_TRUE(observer_called);
+ EXPECT_EQ(observer_result, 0);
+}
+
+TEST_F(StartupTaskRunnerTest, AsynchronousExecutionFailedTask) {
+
+ MockTaskRunner mock_runner;
+ scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);
+
+ EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
+ EXPECT_CALL(
+ mock_runner,
+ PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0)))
+ .Times(testing::Between(1, 2))
+ .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg)));
+
+ scoped_refptr<StartupTaskRunner> runner =
+ new StartupTaskRunner(true, base::Bind(&Observer), proxy);
+
+ StartupTask task3 =
+ base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this));
+ runner->AddTask(task3);
+ StartupTask task2 =
+ base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
+ runner->AddTask(task2);
+
+ // Nothing should run until we tell them to.
+ EXPECT_EQ(GetLastTask(), 0);
+ runner->StartRunningTasks();
+
+ // No tasks should have run yet, since we the message loop hasn't run.
+ EXPECT_EQ(GetLastTask(), 0);
+
+ // Fake the actual message loop. Each time a task is run a new task should
+ // be added to the queue, hence updating "task". The loop should actually run
+ // at most twice (once for the failed task plus possibly once for the
+ // observer), the "4" is a backstop.
+ for (int i = 0; i < 4 && !observer_called; i++) {
+ task.Run();
+ }
+ EXPECT_EQ(GetLastTask(), 3);
+
+ EXPECT_TRUE(observer_called);
+ EXPECT_EQ(observer_result, 1);
+}
+} // namespace
+} // namespace content
diff --git a/chromium/content/browser/storage_partition_impl.cc b/chromium/content/browser/storage_partition_impl.cc
new file mode 100644
index 00000000000..c5471869150
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl.cc
@@ -0,0 +1,669 @@
+// 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 "content/browser/storage_partition_impl.h"
+
+#include "base/sequenced_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/browser/gpu/shader_disk_cache.h"
+#include "content/common/dom_storage/dom_storage_types.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/dom_storage_context.h"
+#include "content/public/browser/indexed_db_context.h"
+#include "content/public/browser/local_storage_usage_info.h"
+#include "content/public/browser/session_storage_usage_info.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "webkit/browser/database/database_tracker.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+namespace content {
+
+namespace {
+
+int GenerateQuotaClientMask(uint32 remove_mask) {
+ int quota_client_mask = 0;
+
+ if (remove_mask & StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS)
+ quota_client_mask |= quota::QuotaClient::kFileSystem;
+ if (remove_mask & StoragePartition::REMOVE_DATA_MASK_WEBSQL)
+ quota_client_mask |= quota::QuotaClient::kDatabase;
+ if (remove_mask & StoragePartition::REMOVE_DATA_MASK_APPCACHE)
+ quota_client_mask |= quota::QuotaClient::kAppcache;
+ if (remove_mask & StoragePartition::REMOVE_DATA_MASK_INDEXEDDB)
+ quota_client_mask |= quota::QuotaClient::kIndexedDatabase;
+
+ return quota_client_mask;
+}
+
+void OnClearedCookies(const base::Closure& callback, int num_deleted) {
+ // The final callback needs to happen from UI thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&OnClearedCookies, callback, num_deleted));
+ return;
+ }
+
+ callback.Run();
+}
+
+void ClearCookiesOnIOThread(
+ const scoped_refptr<net::URLRequestContextGetter>& rq_context,
+ const base::Time begin,
+ const base::Time end,
+ const GURL& remove_origin,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ net::CookieStore* cookie_store = rq_context->
+ GetURLRequestContext()->cookie_store();
+ if (remove_origin.is_empty()) {
+ cookie_store->GetCookieMonster()->DeleteAllCreatedBetweenAsync(
+ begin,
+ end,
+ base::Bind(&OnClearedCookies, callback));
+ } else {
+ cookie_store->GetCookieMonster()->DeleteAllCreatedBetweenForHostAsync(
+ begin,
+ end,
+ remove_origin, base::Bind(&OnClearedCookies, callback));
+ }
+}
+
+void OnQuotaManagedOriginDeleted(const GURL& origin,
+ quota::StorageType type,
+ size_t* origins_to_delete_count,
+ const base::Closure& callback,
+ quota::QuotaStatusCode status) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_GT(*origins_to_delete_count, 0u);
+ if (status != quota::kQuotaStatusOk) {
+ DLOG(ERROR) << "Couldn't remove data of type " << type << " for origin "
+ << origin << ". Status: " << status;
+ }
+
+ (*origins_to_delete_count)--;
+ if (*origins_to_delete_count == 0) {
+ delete origins_to_delete_count;
+ callback.Run();
+ }
+}
+
+void ClearQuotaManagedOriginsOnIOThread(quota::QuotaManager* quota_manager,
+ uint32 remove_mask,
+ const base::Closure& callback,
+ const std::set<GURL>& origins,
+ quota::StorageType quota_storage_type) {
+ // The QuotaManager manages all storage other than cookies, LocalStorage,
+ // and SessionStorage. This loop wipes out most HTML5 storage for the given
+ // origins.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!origins.size()) {
+ // No origins to clear.
+ callback.Run();
+ return;
+ }
+
+ std::set<GURL>::const_iterator origin;
+ size_t* origins_to_delete_count = new size_t(origins.size());
+ for (std::set<GURL>::const_iterator origin = origins.begin();
+ origin != origins.end(); ++origin) {
+ quota_manager->DeleteOriginData(
+ *origin, quota_storage_type,
+ GenerateQuotaClientMask(remove_mask),
+ base::Bind(&OnQuotaManagedOriginDeleted,
+ origin->GetOrigin(), quota_storage_type,
+ origins_to_delete_count, callback));
+ }
+}
+
+void ClearedShaderCache(const base::Closure& callback) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&ClearedShaderCache, callback));
+ return;
+ }
+ callback.Run();
+}
+
+void ClearShaderCacheOnIOThread(const base::FilePath& path,
+ const base::Time begin,
+ const base::Time end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ShaderCacheFactory::GetInstance()->ClearByPath(
+ path, begin, end, base::Bind(&ClearedShaderCache, callback));
+}
+
+void OnLocalStorageUsageInfo(
+ const scoped_refptr<DOMStorageContextWrapper>& dom_storage_context,
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const base::Closure& callback,
+ const std::vector<LocalStorageUsageInfo>& infos) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ for (size_t i = 0; i < infos.size(); ++i) {
+ if (infos[i].last_modified >= delete_begin &&
+ infos[i].last_modified <= delete_end) {
+ dom_storage_context->DeleteLocalStorage(infos[i].origin);
+ }
+ }
+ callback.Run();
+}
+
+void OnSessionStorageUsageInfo(
+ const scoped_refptr<DOMStorageContextWrapper>& dom_storage_context,
+ const base::Closure& callback,
+ const std::vector<SessionStorageUsageInfo>& infos) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ for (size_t i = 0; i < infos.size(); ++i)
+ dom_storage_context->DeleteSessionStorage(infos[i]);
+
+ callback.Run();
+}
+
+void ClearLocalStorageOnUIThread(
+ const scoped_refptr<DOMStorageContextWrapper>& dom_storage_context,
+ const GURL& remove_origin,
+ const base::Time begin,
+ const base::Time end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!remove_origin.is_empty()) {
+ dom_storage_context->DeleteLocalStorage(remove_origin);
+ callback.Run();
+ return;
+ }
+
+ dom_storage_context->GetLocalStorageUsage(
+ base::Bind(&OnLocalStorageUsageInfo,
+ dom_storage_context, begin, end, callback));
+}
+
+void ClearSessionStorageOnUIThread(
+ const scoped_refptr<DOMStorageContextWrapper>& dom_storage_context,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ dom_storage_context->GetSessionStorageUsage(
+ base::Bind(&OnSessionStorageUsageInfo, dom_storage_context, callback));
+}
+
+} // namespace
+
+// Helper for deleting quota managed data from a partition.
+//
+// Most of the operations in this class are done on IO thread.
+struct StoragePartitionImpl::QuotaManagedDataDeletionHelper {
+ QuotaManagedDataDeletionHelper(const base::Closure& callback)
+ : callback(callback), task_count(0) {
+ }
+
+ void IncrementTaskCountOnIO();
+ void DecrementTaskCountOnIO();
+
+ void ClearDataOnIOThread(
+ const scoped_refptr<quota::QuotaManager>& quota_manager,
+ const base::Time begin,
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin);
+
+ // Accessed on IO thread.
+ const base::Closure callback;
+ // Accessed on IO thread.
+ int task_count;
+};
+
+// Helper for deleting all sorts of data from a partition, keeps track of
+// deletion status.
+//
+// StoragePartitionImpl creates an instance of this class to keep track of
+// data deletion progress. Deletion requires deleting multiple bits of data
+// (e.g. cookies, local storage, session storage etc.) and hopping between UI
+// and IO thread. An instance of this class is created in the beginning of
+// deletion process (StoragePartitionImpl::ClearDataImpl) and the instance is
+// forwarded and updated on each (sub) deletion's callback. The instance is
+// finally destroyed when deletion completes (and |callback| is invoked).
+struct StoragePartitionImpl::DataDeletionHelper {
+ DataDeletionHelper(const base::Closure& callback)
+ : callback(callback), task_count(0) {
+ }
+
+ void IncrementTaskCountOnUI();
+ void DecrementTaskCountOnUI();
+
+ void ClearDataOnUIThread(uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin,
+ const base::FilePath& path,
+ net::URLRequestContextGetter* rq_context,
+ DOMStorageContextWrapper* dom_storage_context,
+ quota::QuotaManager* quota_manager,
+ WebRTCIdentityStore* webrtc_identity_store,
+ const base::Time begin,
+ const base::Time end);
+
+ // Accessed on UI thread.
+ const base::Closure callback;
+ // Accessed on UI thread.
+ int task_count;
+};
+
+void ClearQuotaManagedDataOnIOThread(
+ const scoped_refptr<quota::QuotaManager>& quota_manager,
+ const base::Time begin,
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ StoragePartitionImpl::QuotaManagedDataDeletionHelper* helper =
+ new StoragePartitionImpl::QuotaManagedDataDeletionHelper(callback);
+ helper->ClearDataOnIOThread(quota_manager, begin,
+ remove_mask, quota_storage_remove_mask, remove_origin);
+}
+
+StoragePartitionImpl::StoragePartitionImpl(
+ const base::FilePath& partition_path,
+ quota::QuotaManager* quota_manager,
+ ChromeAppCacheService* appcache_service,
+ fileapi::FileSystemContext* filesystem_context,
+ webkit_database::DatabaseTracker* database_tracker,
+ DOMStorageContextWrapper* dom_storage_context,
+ IndexedDBContextImpl* indexed_db_context,
+ WebRTCIdentityStore* webrtc_identity_store)
+ : partition_path_(partition_path),
+ quota_manager_(quota_manager),
+ appcache_service_(appcache_service),
+ filesystem_context_(filesystem_context),
+ database_tracker_(database_tracker),
+ dom_storage_context_(dom_storage_context),
+ indexed_db_context_(indexed_db_context),
+ webrtc_identity_store_(webrtc_identity_store) {}
+
+StoragePartitionImpl::~StoragePartitionImpl() {
+ // These message loop checks are just to avoid leaks in unittests.
+ if (GetDatabaseTracker() &&
+ BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&webkit_database::DatabaseTracker::Shutdown,
+ GetDatabaseTracker()));
+ }
+
+ if (GetFileSystemContext())
+ GetFileSystemContext()->Shutdown();
+
+ if (GetDOMStorageContext())
+ GetDOMStorageContext()->Shutdown();
+}
+
+// TODO(ajwong): Break the direct dependency on |context|. We only
+// need 3 pieces of info from it.
+StoragePartitionImpl* StoragePartitionImpl::Create(
+ BrowserContext* context,
+ bool in_memory,
+ const base::FilePath& partition_path) {
+ // Ensure that these methods are called on the UI thread, except for
+ // unittests where a UI thread might not have been created.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
+ !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
+
+ // All of the clients have to be created and registered with the
+ // QuotaManager prior to the QuotaManger being used. We do them
+ // all together here prior to handing out a reference to anything
+ // that utilizes the QuotaManager.
+ scoped_refptr<quota::QuotaManager> quota_manager = new quota::QuotaManager(
+ in_memory,
+ partition_path,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB).get(),
+ context->GetSpecialStoragePolicy());
+
+ // Each consumer is responsible for registering its QuotaClient during
+ // its construction.
+ scoped_refptr<fileapi::FileSystemContext> filesystem_context =
+ CreateFileSystemContext(context,
+ partition_path, in_memory,
+ quota_manager->proxy());
+
+ scoped_refptr<webkit_database::DatabaseTracker> database_tracker =
+ new webkit_database::DatabaseTracker(
+ partition_path,
+ in_memory,
+ context->GetSpecialStoragePolicy(),
+ quota_manager->proxy(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
+ .get());
+
+ base::FilePath path = in_memory ? base::FilePath() : partition_path;
+ scoped_refptr<DOMStorageContextWrapper> dom_storage_context =
+ new DOMStorageContextWrapper(path, context->GetSpecialStoragePolicy());
+
+ // BrowserMainLoop may not be initialized in unit tests. Tests will
+ // need to inject their own task runner into the IndexedDBContext.
+ base::SequencedTaskRunner* idb_task_runner =
+ BrowserThread::CurrentlyOn(BrowserThread::UI) &&
+ BrowserMainLoop::GetInstance()
+ ? BrowserMainLoop::GetInstance()->indexed_db_thread()
+ ->message_loop_proxy().get()
+ : NULL;
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context =
+ new IndexedDBContextImpl(path,
+ context->GetSpecialStoragePolicy(),
+ quota_manager->proxy(),
+ idb_task_runner);
+
+ scoped_refptr<ChromeAppCacheService> appcache_service =
+ new ChromeAppCacheService(quota_manager->proxy());
+
+ scoped_refptr<WebRTCIdentityStore> webrtc_identity_store(
+ new WebRTCIdentityStore(path, context->GetSpecialStoragePolicy()));
+
+ return new StoragePartitionImpl(partition_path,
+ quota_manager.get(),
+ appcache_service.get(),
+ filesystem_context.get(),
+ database_tracker.get(),
+ dom_storage_context.get(),
+ indexed_db_context.get(),
+ webrtc_identity_store.get());
+}
+
+base::FilePath StoragePartitionImpl::GetPath() {
+ return partition_path_;
+}
+
+net::URLRequestContextGetter* StoragePartitionImpl::GetURLRequestContext() {
+ return url_request_context_.get();
+}
+
+net::URLRequestContextGetter*
+StoragePartitionImpl::GetMediaURLRequestContext() {
+ return media_url_request_context_.get();
+}
+
+quota::QuotaManager* StoragePartitionImpl::GetQuotaManager() {
+ return quota_manager_.get();
+}
+
+ChromeAppCacheService* StoragePartitionImpl::GetAppCacheService() {
+ return appcache_service_.get();
+}
+
+fileapi::FileSystemContext* StoragePartitionImpl::GetFileSystemContext() {
+ return filesystem_context_.get();
+}
+
+webkit_database::DatabaseTracker* StoragePartitionImpl::GetDatabaseTracker() {
+ return database_tracker_.get();
+}
+
+DOMStorageContextWrapper* StoragePartitionImpl::GetDOMStorageContext() {
+ return dom_storage_context_.get();
+}
+
+IndexedDBContextImpl* StoragePartitionImpl::GetIndexedDBContext() {
+ return indexed_db_context_.get();
+}
+
+void StoragePartitionImpl::ClearDataImpl(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin,
+ net::URLRequestContextGetter* rq_context,
+ const base::Time begin,
+ const base::Time end,
+ const base::Closure& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DataDeletionHelper* helper = new DataDeletionHelper(callback);
+ // |helper| deletes itself when done in
+ // DataDeletionHelper::DecrementTaskCountOnUI().
+ helper->ClearDataOnUIThread(
+ remove_mask, quota_storage_remove_mask, remove_origin,
+ GetPath(), rq_context, dom_storage_context_, quota_manager_,
+ webrtc_identity_store_, begin, end);
+}
+
+void StoragePartitionImpl::
+ QuotaManagedDataDeletionHelper::IncrementTaskCountOnIO() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ++task_count;
+}
+
+void StoragePartitionImpl::
+ QuotaManagedDataDeletionHelper::DecrementTaskCountOnIO() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_GT(task_count, 0);
+ --task_count;
+ if (task_count)
+ return;
+
+ callback.Run();
+ delete this;
+}
+
+void StoragePartitionImpl::QuotaManagedDataDeletionHelper::ClearDataOnIOThread(
+ const scoped_refptr<quota::QuotaManager>& quota_manager,
+ const base::Time begin,
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin) {
+ std::set<GURL> origins;
+ if (!remove_origin.is_empty())
+ origins.insert(remove_origin);
+
+ IncrementTaskCountOnIO();
+ base::Closure decrement_callback = base::Bind(
+ &QuotaManagedDataDeletionHelper::DecrementTaskCountOnIO,
+ base::Unretained(this));
+
+ if (quota_storage_remove_mask & QUOTA_MANAGED_STORAGE_MASK_PERSISTENT) {
+ IncrementTaskCountOnIO();
+ if (origins.empty()) { // Remove for all origins.
+ // Ask the QuotaManager for all origins with temporary quota modified
+ // within the user-specified timeframe, and deal with the resulting set in
+ // ClearQuotaManagedOriginsOnIOThread().
+ quota_manager->GetOriginsModifiedSince(
+ quota::kStorageTypePersistent, begin,
+ base::Bind(&ClearQuotaManagedOriginsOnIOThread,
+ quota_manager, remove_mask, decrement_callback));
+ } else {
+ ClearQuotaManagedOriginsOnIOThread(
+ quota_manager, remove_mask, decrement_callback,
+ origins, quota::kStorageTypePersistent);
+ }
+ }
+
+ // Do the same for temporary quota.
+ if (quota_storage_remove_mask & QUOTA_MANAGED_STORAGE_MASK_TEMPORARY) {
+ IncrementTaskCountOnIO();
+ if (origins.empty()) { // Remove for all origins.
+ quota_manager->GetOriginsModifiedSince(
+ quota::kStorageTypeTemporary, begin,
+ base::Bind(&ClearQuotaManagedOriginsOnIOThread,
+ quota_manager, remove_mask, decrement_callback));
+ } else {
+ ClearQuotaManagedOriginsOnIOThread(
+ quota_manager, remove_mask, decrement_callback,
+ origins, quota::kStorageTypeTemporary);
+ }
+ }
+
+ // Do the same for syncable quota.
+ if (quota_storage_remove_mask & QUOTA_MANAGED_STORAGE_MASK_SYNCABLE) {
+ IncrementTaskCountOnIO();
+ if (origins.empty()) { // Remove for all origins.
+ quota_manager->GetOriginsModifiedSince(
+ quota::kStorageTypeSyncable, begin,
+ base::Bind(&ClearQuotaManagedOriginsOnIOThread,
+ quota_manager, remove_mask, decrement_callback));
+ } else {
+ ClearQuotaManagedOriginsOnIOThread(
+ quota_manager, remove_mask, decrement_callback,
+ origins, quota::kStorageTypeSyncable);
+ }
+ }
+
+ DecrementTaskCountOnIO();
+}
+
+void StoragePartitionImpl::DataDeletionHelper::IncrementTaskCountOnUI() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ ++task_count;
+}
+
+void StoragePartitionImpl::DataDeletionHelper::DecrementTaskCountOnUI() {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DataDeletionHelper::DecrementTaskCountOnUI,
+ base::Unretained(this)));
+ return;
+ }
+ DCHECK_GT(task_count, 0);
+ --task_count;
+ if (!task_count) {
+ callback.Run();
+ delete this;
+ }
+}
+
+void StoragePartitionImpl::DataDeletionHelper::ClearDataOnUIThread(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin,
+ const base::FilePath& path,
+ net::URLRequestContextGetter* rq_context,
+ DOMStorageContextWrapper* dom_storage_context,
+ quota::QuotaManager* quota_manager,
+ WebRTCIdentityStore* webrtc_identity_store,
+ const base::Time begin,
+ const base::Time end) {
+ DCHECK_NE(remove_mask, 0u);
+ DCHECK(!callback.is_null());
+
+ IncrementTaskCountOnUI();
+ base::Closure decrement_callback = base::Bind(
+ &DataDeletionHelper::DecrementTaskCountOnUI, base::Unretained(this));
+
+ if (remove_mask & REMOVE_DATA_MASK_COOKIES) {
+ // Handle the cookies.
+ IncrementTaskCountOnUI();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ClearCookiesOnIOThread,
+ make_scoped_refptr(rq_context), begin, end, remove_origin,
+ decrement_callback));
+ }
+
+ if (remove_mask & REMOVE_DATA_MASK_INDEXEDDB ||
+ remove_mask & REMOVE_DATA_MASK_WEBSQL ||
+ remove_mask & REMOVE_DATA_MASK_APPCACHE ||
+ remove_mask & REMOVE_DATA_MASK_FILE_SYSTEMS) {
+ IncrementTaskCountOnUI();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ClearQuotaManagedDataOnIOThread,
+ make_scoped_refptr(quota_manager), begin,
+ remove_mask, quota_storage_remove_mask, remove_origin,
+ decrement_callback));
+ }
+
+ if (remove_mask & REMOVE_DATA_MASK_LOCAL_STORAGE) {
+ IncrementTaskCountOnUI();
+ ClearLocalStorageOnUIThread(
+ make_scoped_refptr(dom_storage_context),
+ remove_origin, begin, end, decrement_callback);
+
+ // ClearDataImpl cannot clear session storage data when a particular origin
+ // is specified. Therefore we ignore clearing session storage in this case.
+ // TODO(lazyboy): Fix.
+ if (remove_origin.is_empty()) {
+ IncrementTaskCountOnUI();
+ ClearSessionStorageOnUIThread(
+ make_scoped_refptr(dom_storage_context), decrement_callback);
+ }
+ }
+
+ if (remove_mask & REMOVE_DATA_MASK_SHADER_CACHE) {
+ IncrementTaskCountOnUI();
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ClearShaderCacheOnIOThread,
+ path, begin, end, decrement_callback));
+ }
+
+ if (remove_mask & REMOVE_DATA_MASK_WEBRTC_IDENTITY) {
+ IncrementTaskCountOnUI();
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&WebRTCIdentityStore::DeleteBetween,
+ webrtc_identity_store,
+ begin,
+ end,
+ decrement_callback));
+ }
+
+ DecrementTaskCountOnUI();
+}
+
+
+void StoragePartitionImpl::ClearDataForOrigin(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& storage_origin,
+ net::URLRequestContextGetter* request_context_getter) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ ClearDataImpl(remove_mask, quota_storage_remove_mask, storage_origin,
+ request_context_getter, base::Time(), base::Time::Max(),
+ base::Bind(&base::DoNothing));
+}
+
+void StoragePartitionImpl::ClearDataForUnboundedRange(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask) {
+ ClearDataImpl(remove_mask, quota_storage_remove_mask, GURL(),
+ GetURLRequestContext(), base::Time(), base::Time::Max(),
+ base::Bind(&base::DoNothing));
+}
+
+void StoragePartitionImpl::ClearDataForRange(uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const base::Time& begin,
+ const base::Time& end,
+ const base::Closure& callback) {
+ ClearDataImpl(remove_mask, quota_storage_remove_mask, GURL(),
+ GetURLRequestContext(), begin, end, callback);
+}
+
+WebRTCIdentityStore* StoragePartitionImpl::GetWebRTCIdentityStore() {
+ return webrtc_identity_store_.get();
+}
+
+void StoragePartitionImpl::SetURLRequestContext(
+ net::URLRequestContextGetter* url_request_context) {
+ url_request_context_ = url_request_context;
+}
+
+void StoragePartitionImpl::SetMediaURLRequestContext(
+ net::URLRequestContextGetter* media_url_request_context) {
+ media_url_request_context_ = media_url_request_context;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/storage_partition_impl.h b/chromium/content/browser/storage_partition_impl.h
new file mode 100644
index 00000000000..e59347cb569
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl.h
@@ -0,0 +1,122 @@
+// 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 CONTENT_BROWSER_STORAGE_PARTITION_IMPL_H_
+#define CONTENT_BROWSER_STORAGE_PARTITION_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "content/browser/media/webrtc_identity_store.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace content {
+
+class StoragePartitionImpl : public StoragePartition {
+ public:
+ CONTENT_EXPORT virtual ~StoragePartitionImpl();
+
+ // StoragePartition interface.
+ virtual base::FilePath GetPath() OVERRIDE;
+ virtual net::URLRequestContextGetter* GetURLRequestContext() OVERRIDE;
+ virtual net::URLRequestContextGetter* GetMediaURLRequestContext() OVERRIDE;
+ virtual quota::QuotaManager* GetQuotaManager() OVERRIDE;
+ virtual ChromeAppCacheService* GetAppCacheService() OVERRIDE;
+ virtual fileapi::FileSystemContext* GetFileSystemContext() OVERRIDE;
+ virtual webkit_database::DatabaseTracker* GetDatabaseTracker() OVERRIDE;
+ virtual DOMStorageContextWrapper* GetDOMStorageContext() OVERRIDE;
+ virtual IndexedDBContextImpl* GetIndexedDBContext() OVERRIDE;
+
+ virtual void ClearDataForOrigin(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& storage_origin,
+ net::URLRequestContextGetter* request_context_getter) OVERRIDE;
+ virtual void ClearDataForUnboundedRange(
+ uint32 remove_mask,
+ uint32 quota_storage_remove_mask) OVERRIDE;
+ virtual void ClearDataForRange(uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const base::Time& begin,
+ const base::Time& end,
+ const base::Closure& callback) OVERRIDE;
+
+ WebRTCIdentityStore* GetWebRTCIdentityStore();
+
+ struct DataDeletionHelper;
+ struct QuotaManagedDataDeletionHelper;
+
+ private:
+ friend class StoragePartitionImplMap;
+ FRIEND_TEST_ALL_PREFIXES(StoragePartitionShaderClearTest, ClearShaderCache);
+
+ // The |partition_path| is the absolute path to the root of this
+ // StoragePartition's on-disk storage.
+ //
+ // If |in_memory| is true, the |partition_path| is (ab)used as a way of
+ // distinguishing different in-memory partitions, but nothing is persisted
+ // on to disk.
+ static StoragePartitionImpl* Create(BrowserContext* context,
+ bool in_memory,
+ const base::FilePath& profile_path);
+
+ // Quota managed data uses a different bitmask for types than
+ // StoragePartition uses. This method generates that mask.
+ static int GenerateQuotaClientMask(uint32 remove_mask);
+
+ CONTENT_EXPORT StoragePartitionImpl(
+ const base::FilePath& partition_path,
+ quota::QuotaManager* quota_manager,
+ ChromeAppCacheService* appcache_service,
+ fileapi::FileSystemContext* filesystem_context,
+ webkit_database::DatabaseTracker* database_tracker,
+ DOMStorageContextWrapper* dom_storage_context,
+ IndexedDBContextImpl* indexed_db_context,
+ WebRTCIdentityStore* webrtc_identity_store);
+
+ void ClearDataImpl(uint32 remove_mask,
+ uint32 quota_storage_remove_mask,
+ const GURL& remove_origin,
+ net::URLRequestContextGetter* rq_context,
+ const base::Time begin,
+ const base::Time end,
+ const base::Closure& callback);
+
+ // Used by StoragePartitionImplMap.
+ //
+ // TODO(ajwong): These should be taken in the constructor and in Create() but
+ // because the URLRequestContextGetter still lives in Profile with a tangled
+ // initialization, if we try to retrieve the URLRequestContextGetter()
+ // before the default StoragePartition is created, we end up reentering the
+ // construction and double-initializing. For now, we retain the legacy
+ // behavior while allowing StoragePartitionImpl to expose these accessors by
+ // letting StoragePartitionImplMap call these two private settings at the
+ // appropriate time. These should move back into the constructor once
+ // URLRequestContextGetter's lifetime is sorted out. We should also move the
+ // PostCreateInitialization() out of StoragePartitionImplMap.
+ void SetURLRequestContext(net::URLRequestContextGetter* url_request_context);
+ void SetMediaURLRequestContext(
+ net::URLRequestContextGetter* media_url_request_context);
+
+ base::FilePath partition_path_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_;
+ scoped_refptr<net::URLRequestContextGetter> media_url_request_context_;
+ scoped_refptr<quota::QuotaManager> quota_manager_;
+ scoped_refptr<ChromeAppCacheService> appcache_service_;
+ scoped_refptr<fileapi::FileSystemContext> filesystem_context_;
+ scoped_refptr<webkit_database::DatabaseTracker> database_tracker_;
+ scoped_refptr<DOMStorageContextWrapper> dom_storage_context_;
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context_;
+ scoped_refptr<WebRTCIdentityStore> webrtc_identity_store_;
+
+ DISALLOW_COPY_AND_ASSIGN(StoragePartitionImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STORAGE_PARTITION_IMPL_H_
diff --git a/chromium/content/browser/storage_partition_impl_map.cc b/chromium/content/browser/storage_partition_impl_map.cc
new file mode 100644
index 00000000000..c5721aee667
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl_map.cc
@@ -0,0 +1,592 @@
+// 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 "content/browser/storage_partition_impl_map.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/browser/streams/stream_url_request_job.h"
+#include "content/browser/webui/url_data_manager_backend.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "crypto/sha2.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "webkit/browser/blob/blob_url_request_job_factory.h"
+#include "webkit/browser/fileapi/file_system_url_request_job_factory.h"
+#include "webkit/common/blob/blob_data.h"
+
+using appcache::AppCacheService;
+using fileapi::FileSystemContext;
+using webkit_blob::BlobStorageController;
+
+namespace content {
+
+namespace {
+
+class BlobProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ BlobProtocolHandler(ChromeBlobStorageContext* blob_storage_context,
+ StreamContext* stream_context,
+ fileapi::FileSystemContext* file_system_context)
+ : blob_storage_context_(blob_storage_context),
+ stream_context_(stream_context),
+ file_system_context_(file_system_context) {}
+
+ virtual ~BlobProtocolHandler() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!webkit_blob_protocol_handler_impl_) {
+ webkit_blob_protocol_handler_impl_.reset(
+ new WebKitBlobProtocolHandlerImpl(blob_storage_context_->controller(),
+ stream_context_.get(),
+ file_system_context_.get()));
+ }
+ return webkit_blob_protocol_handler_impl_->MaybeCreateJob(request,
+ network_delegate);
+ }
+
+ private:
+ // An implementation of webkit_blob::BlobProtocolHandler that gets
+ // the BlobData from ResourceRequestInfoImpl.
+ class WebKitBlobProtocolHandlerImpl
+ : public webkit_blob::BlobProtocolHandler {
+ public:
+ WebKitBlobProtocolHandlerImpl(
+ webkit_blob::BlobStorageController* blob_storage_controller,
+ StreamContext* stream_context,
+ fileapi::FileSystemContext* file_system_context)
+ : webkit_blob::BlobProtocolHandler(
+ blob_storage_controller,
+ file_system_context,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
+ .get()),
+ stream_context_(stream_context) {}
+
+ virtual ~WebKitBlobProtocolHandlerImpl() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ scoped_refptr<Stream> stream =
+ stream_context_->registry()->GetStream(request->url());
+ if (stream.get())
+ return new StreamURLRequestJob(request, network_delegate, stream);
+
+ return webkit_blob::BlobProtocolHandler::MaybeCreateJob(
+ request, network_delegate);
+ }
+
+ private:
+ // webkit_blob::BlobProtocolHandler implementation.
+ virtual scoped_refptr<webkit_blob::BlobData>
+ LookupBlobData(net::URLRequest* request) const OVERRIDE {
+ const ResourceRequestInfoImpl* info =
+ ResourceRequestInfoImpl::ForRequest(request);
+ if (!info)
+ return NULL;
+ return info->requested_blob_data();
+ }
+
+ const scoped_refptr<StreamContext> stream_context_;
+ DISALLOW_COPY_AND_ASSIGN(WebKitBlobProtocolHandlerImpl);
+ };
+
+ const scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
+ const scoped_refptr<StreamContext> stream_context_;
+ const scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+
+ mutable scoped_ptr<WebKitBlobProtocolHandlerImpl>
+ webkit_blob_protocol_handler_impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobProtocolHandler);
+};
+
+// These constants are used to create the directory structure under the profile
+// where renderers with a non-default storage partition keep their persistent
+// state. This will contain a set of directories that partially mirror the
+// directory structure of BrowserContext::GetPath().
+//
+// The kStoragePartitionDirname contains an extensions directory which is
+// further partitioned by extension id, followed by another level of directories
+// for the "default" extension storage partition and one directory for each
+// persistent partition used by a webview tag. Example:
+//
+// Storage/ext/ABCDEF/def
+// Storage/ext/ABCDEF/hash(partition name)
+//
+// The code in GetStoragePartitionPath() constructs these path names.
+//
+// TODO(nasko): Move extension related path code out of content.
+const base::FilePath::CharType kStoragePartitionDirname[] =
+ FILE_PATH_LITERAL("Storage");
+const base::FilePath::CharType kExtensionsDirname[] =
+ FILE_PATH_LITERAL("ext");
+const base::FilePath::CharType kDefaultPartitionDirname[] =
+ FILE_PATH_LITERAL("def");
+const base::FilePath::CharType kTrashDirname[] =
+ FILE_PATH_LITERAL("trash");
+
+// Because partition names are user specified, they can be arbitrarily long
+// which makes them unsuitable for paths names. We use a truncation of a
+// SHA256 hash to perform a deterministic shortening of the string. The
+// kPartitionNameHashBytes constant controls the length of the truncation.
+// We use 6 bytes, which gives us 99.999% reliability against collisions over
+// 1 million partition domains.
+//
+// Analysis:
+// We assume that all partition names within one partition domain are
+// controlled by the the same entity. Thus there is no chance for adverserial
+// attack and all we care about is accidental collision. To get 5 9s over
+// 1 million domains, we need the probability of a collision in any one domain
+// to be
+//
+// p < nroot(1000000, .99999) ~= 10^-11
+//
+// We use the following birthday attack approximation to calculate the max
+// number of unique names for this probability:
+//
+// n(p,H) = sqrt(2*H * ln(1/(1-p)))
+//
+// For a 6-byte hash, H = 2^(6*8). n(10^-11, H) ~= 75
+//
+// An average partition domain is likely to have less than 10 unique
+// partition names which is far lower than 75.
+//
+// Note, that for 4 9s of reliability, the limit is 237 partition names per
+// partition domain.
+const int kPartitionNameHashBytes = 6;
+
+// Needed for selecting all files in ObliterateOneDirectory() below.
+#if defined(OS_POSIX)
+const int kAllFileTypes = base::FileEnumerator::FILES |
+ base::FileEnumerator::DIRECTORIES |
+ base::FileEnumerator::SHOW_SYM_LINKS;
+#else
+const int kAllFileTypes = base::FileEnumerator::FILES |
+ base::FileEnumerator::DIRECTORIES;
+#endif
+
+base::FilePath GetStoragePartitionDomainPath(
+ const std::string& partition_domain) {
+ CHECK(IsStringUTF8(partition_domain));
+
+ return base::FilePath(kStoragePartitionDirname).Append(kExtensionsDirname)
+ .Append(base::FilePath::FromUTF8Unsafe(partition_domain));
+}
+
+// Helper function for doing a depth-first deletion of the data on disk.
+// Examines paths directly in |current_dir| (no recursion) and tries to
+// delete from disk anything that is in, or isn't a parent of something in
+// |paths_to_keep|. Paths that need further expansion are added to
+// |paths_to_consider|.
+void ObliterateOneDirectory(const base::FilePath& current_dir,
+ const std::vector<base::FilePath>& paths_to_keep,
+ std::vector<base::FilePath>* paths_to_consider) {
+ CHECK(current_dir.IsAbsolute());
+
+ base::FileEnumerator enumerator(current_dir, false, kAllFileTypes);
+ for (base::FilePath to_delete = enumerator.Next(); !to_delete.empty();
+ to_delete = enumerator.Next()) {
+ // Enum tracking which of the 3 possible actions to take for |to_delete|.
+ enum { kSkip, kEnqueue, kDelete } action = kDelete;
+
+ for (std::vector<base::FilePath>::const_iterator to_keep =
+ paths_to_keep.begin();
+ to_keep != paths_to_keep.end();
+ ++to_keep) {
+ if (to_delete == *to_keep) {
+ action = kSkip;
+ break;
+ } else if (to_delete.IsParent(*to_keep)) {
+ // |to_delete| contains a path to keep. Add to stack for further
+ // processing.
+ action = kEnqueue;
+ break;
+ }
+ }
+
+ switch (action) {
+ case kDelete:
+ base::DeleteFile(to_delete, true);
+ break;
+
+ case kEnqueue:
+ paths_to_consider->push_back(to_delete);
+ break;
+
+ case kSkip:
+ break;
+ }
+ }
+}
+
+// Synchronously attempts to delete |unnormalized_root|, preserving only
+// entries in |paths_to_keep|. If there are no entries in |paths_to_keep| on
+// disk, then it completely removes |unnormalized_root|. All paths must be
+// absolute paths.
+void BlockingObliteratePath(
+ const base::FilePath& unnormalized_browser_context_root,
+ const base::FilePath& unnormalized_root,
+ const std::vector<base::FilePath>& paths_to_keep,
+ const scoped_refptr<base::TaskRunner>& closure_runner,
+ const base::Closure& on_gc_required) {
+ // Early exit required because MakeAbsoluteFilePath() will fail on POSIX
+ // if |unnormalized_root| does not exist. This is safe because there is
+ // nothing to do in this situation anwyays.
+ if (!base::PathExists(unnormalized_root)) {
+ return;
+ }
+
+ // Never try to obliterate things outside of the browser context root or the
+ // browser context root itself. Die hard.
+ base::FilePath root = base::MakeAbsoluteFilePath(unnormalized_root);
+ base::FilePath browser_context_root =
+ base::MakeAbsoluteFilePath(unnormalized_browser_context_root);
+ CHECK(!root.empty());
+ CHECK(!browser_context_root.empty());
+ CHECK(browser_context_root.IsParent(root) && browser_context_root != root);
+
+ // Reduce |paths_to_keep| set to those under the root and actually on disk.
+ std::vector<base::FilePath> valid_paths_to_keep;
+ for (std::vector<base::FilePath>::const_iterator it = paths_to_keep.begin();
+ it != paths_to_keep.end();
+ ++it) {
+ if (root.IsParent(*it) && base::PathExists(*it))
+ valid_paths_to_keep.push_back(*it);
+ }
+
+ // If none of the |paths_to_keep| are valid anymore then we just whack the
+ // root and be done with it. Otherwise, signal garbage collection and do
+ // a best-effort delete of the on-disk structures.
+ if (valid_paths_to_keep.empty()) {
+ base::DeleteFile(root, true);
+ return;
+ }
+ closure_runner->PostTask(FROM_HERE, on_gc_required);
+
+ // Otherwise, start at the root and delete everything that is not in
+ // |valid_paths_to_keep|.
+ std::vector<base::FilePath> paths_to_consider;
+ paths_to_consider.push_back(root);
+ while(!paths_to_consider.empty()) {
+ base::FilePath path = paths_to_consider.back();
+ paths_to_consider.pop_back();
+ ObliterateOneDirectory(path, valid_paths_to_keep, &paths_to_consider);
+ }
+}
+
+// Deletes all entries inside the |storage_root| that are not in the
+// |active_paths|. Deletion is done in 2 steps:
+//
+// (1) Moving all garbage collected paths into a trash directory.
+// (2) Asynchronously deleting the trash directory.
+//
+// The deletion is asynchronous because after (1) completes, calling code can
+// safely continue to use the paths that had just been garbage collected
+// without fear of race conditions.
+//
+// This code also ignores failed moves rather than attempting a smarter retry.
+// Moves shouldn't fail here unless there is some out-of-band error (eg.,
+// FS corruption). Retry logic is dangerous in the general case because
+// there is not necessarily a guaranteed case where the logic may succeed.
+//
+// This function is still named BlockingGarbageCollect() because it does
+// execute a few filesystem operations synchronously.
+void BlockingGarbageCollect(
+ const base::FilePath& storage_root,
+ const scoped_refptr<base::TaskRunner>& file_access_runner,
+ scoped_ptr<base::hash_set<base::FilePath> > active_paths) {
+ CHECK(storage_root.IsAbsolute());
+
+ base::FileEnumerator enumerator(storage_root, false, kAllFileTypes);
+ base::FilePath trash_directory;
+ if (!file_util::CreateTemporaryDirInDir(storage_root, kTrashDirname,
+ &trash_directory)) {
+ // Unable to continue without creating the trash directory so give up.
+ return;
+ }
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ if (active_paths->find(path) == active_paths->end() &&
+ path != trash_directory) {
+ // Since |trash_directory| is unique for each run of this function there
+ // can be no colllisions on the move.
+ base::Move(path, trash_directory.Append(path.BaseName()));
+ }
+ }
+
+ file_access_runner->PostTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&base::DeleteFile), trash_directory, true));
+}
+
+} // namespace
+
+// static
+base::FilePath StoragePartitionImplMap::GetStoragePartitionPath(
+ const std::string& partition_domain,
+ const std::string& partition_name) {
+ if (partition_domain.empty())
+ return base::FilePath();
+
+ base::FilePath path = GetStoragePartitionDomainPath(partition_domain);
+
+ // TODO(ajwong): Mangle in-memory into this somehow, either by putting
+ // it into the partition_name, or by manually adding another path component
+ // here. Otherwise, it's possible to have an in-memory StoragePartition and
+ // a persistent one that return the same FilePath for GetPath().
+ if (!partition_name.empty()) {
+ // For analysis of why we can ignore collisions, see the comment above
+ // kPartitionNameHashBytes.
+ char buffer[kPartitionNameHashBytes];
+ crypto::SHA256HashString(partition_name, &buffer[0],
+ sizeof(buffer));
+ return path.AppendASCII(base::HexEncode(buffer, sizeof(buffer)));
+ }
+
+ return path.Append(kDefaultPartitionDirname);
+}
+
+StoragePartitionImplMap::StoragePartitionImplMap(
+ BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ resource_context_initialized_(false) {
+ // Doing here instead of initializer list cause it's just too ugly to read.
+ base::SequencedWorkerPool* blocking_pool = BrowserThread::GetBlockingPool();
+ file_access_runner_ =
+ blocking_pool->GetSequencedTaskRunner(blocking_pool->GetSequenceToken());
+}
+
+StoragePartitionImplMap::~StoragePartitionImplMap() {
+ STLDeleteContainerPairSecondPointers(partitions_.begin(),
+ partitions_.end());
+}
+
+StoragePartitionImpl* StoragePartitionImplMap::Get(
+ const std::string& partition_domain,
+ const std::string& partition_name,
+ bool in_memory) {
+ // Find the previously created partition if it's available.
+ StoragePartitionConfig partition_config(
+ partition_domain, partition_name, in_memory);
+
+ PartitionMap::const_iterator it = partitions_.find(partition_config);
+ if (it != partitions_.end())
+ return it->second;
+
+ base::FilePath partition_path =
+ browser_context_->GetPath().Append(
+ GetStoragePartitionPath(partition_domain, partition_name));
+ StoragePartitionImpl* partition =
+ StoragePartitionImpl::Create(browser_context_, in_memory,
+ partition_path);
+ partitions_[partition_config] = partition;
+
+ ChromeBlobStorageContext* blob_storage_context =
+ ChromeBlobStorageContext::GetFor(browser_context_);
+ StreamContext* stream_context = StreamContext::GetFor(browser_context_);
+ ProtocolHandlerMap protocol_handlers;
+ protocol_handlers[chrome::kBlobScheme] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ new BlobProtocolHandler(blob_storage_context,
+ stream_context,
+ partition->GetFileSystemContext()));
+ protocol_handlers[chrome::kFileSystemScheme] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ CreateFileSystemProtocolHandler(partition->GetFileSystemContext()));
+ protocol_handlers[chrome::kChromeUIScheme] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ URLDataManagerBackend::CreateProtocolHandler(
+ browser_context_->GetResourceContext(),
+ browser_context_->IsOffTheRecord(),
+ partition->GetAppCacheService(),
+ blob_storage_context));
+ std::vector<std::string> additional_webui_schemes;
+ GetContentClient()->browser()->GetAdditionalWebUISchemes(
+ &additional_webui_schemes);
+ for (std::vector<std::string>::const_iterator it =
+ additional_webui_schemes.begin();
+ it != additional_webui_schemes.end();
+ ++it) {
+ protocol_handlers[*it] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ URLDataManagerBackend::CreateProtocolHandler(
+ browser_context_->GetResourceContext(),
+ browser_context_->IsOffTheRecord(),
+ partition->GetAppCacheService(),
+ blob_storage_context));
+ }
+ protocol_handlers[chrome::kChromeDevToolsScheme] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ CreateDevToolsProtocolHandler(browser_context_->GetResourceContext(),
+ browser_context_->IsOffTheRecord()));
+
+ // These calls must happen after StoragePartitionImpl::Create().
+ if (partition_domain.empty()) {
+ partition->SetURLRequestContext(
+ GetContentClient()->browser()->CreateRequestContext(
+ browser_context_,
+ &protocol_handlers));
+ } else {
+ partition->SetURLRequestContext(
+ GetContentClient()->browser()->CreateRequestContextForStoragePartition(
+ browser_context_, partition->GetPath(), in_memory,
+ &protocol_handlers));
+ }
+ partition->SetMediaURLRequestContext(
+ partition_domain.empty() ?
+ browser_context_->GetMediaRequestContext() :
+ browser_context_->GetMediaRequestContextForStoragePartition(
+ partition->GetPath(), in_memory));
+
+ PostCreateInitialization(partition, in_memory);
+
+ return partition;
+}
+
+void StoragePartitionImplMap::AsyncObliterate(
+ const GURL& site,
+ const base::Closure& on_gc_required) {
+ // This method should avoid creating any StoragePartition (which would
+ // create more open file handles) so that it can delete as much of the
+ // data off disk as possible.
+ std::string partition_domain;
+ std::string partition_name;
+ bool in_memory = false;
+ GetContentClient()->browser()->GetStoragePartitionConfigForSite(
+ browser_context_, site, false, &partition_domain,
+ &partition_name, &in_memory);
+
+ // Find the active partitions for the domain. Because these partitions are
+ // active, it is not possible to just delete the directories that contain
+ // the backing data structures without causing the browser to crash. Instead,
+ // of deleteing the directory, we tell each storage context later to
+ // remove any data they have saved. This will leave the directory structure
+ // intact but it will only contain empty databases.
+ std::vector<StoragePartitionImpl*> active_partitions;
+ std::vector<base::FilePath> paths_to_keep;
+ for (PartitionMap::const_iterator it = partitions_.begin();
+ it != partitions_.end();
+ ++it) {
+ const StoragePartitionConfig& config = it->first;
+ if (config.partition_domain == partition_domain) {
+ it->second->ClearDataForUnboundedRange(
+ // All except shader cache.
+ StoragePartition::REMOVE_DATA_MASK_ALL &
+ (~StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE),
+ StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL);
+ if (!config.in_memory) {
+ paths_to_keep.push_back(it->second->GetPath());
+ }
+ }
+ }
+
+ // Start a best-effort delete of the on-disk storage excluding paths that are
+ // known to still be in use. This is to delete any previously created
+ // StoragePartition state that just happens to not have been used during this
+ // run of the browser.
+ base::FilePath domain_root = browser_context_->GetPath().Append(
+ GetStoragePartitionDomainPath(partition_domain));
+
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(&BlockingObliteratePath, browser_context_->GetPath(),
+ domain_root, paths_to_keep,
+ base::MessageLoopProxy::current(), on_gc_required));
+}
+
+void StoragePartitionImplMap::GarbageCollect(
+ scoped_ptr<base::hash_set<base::FilePath> > active_paths,
+ const base::Closure& done) {
+ // Include all paths for current StoragePartitions in the active_paths since
+ // they cannot be deleted safely.
+ for (PartitionMap::const_iterator it = partitions_.begin();
+ it != partitions_.end();
+ ++it) {
+ const StoragePartitionConfig& config = it->first;
+ if (!config.in_memory)
+ active_paths->insert(it->second->GetPath());
+ }
+
+ // Find the directory holding the StoragePartitions and delete everything in
+ // there that isn't considered active.
+ base::FilePath storage_root = browser_context_->GetPath().Append(
+ GetStoragePartitionDomainPath(std::string()));
+ file_access_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&BlockingGarbageCollect, storage_root,
+ file_access_runner_,
+ base::Passed(&active_paths)),
+ done);
+}
+
+void StoragePartitionImplMap::ForEach(
+ const BrowserContext::StoragePartitionCallback& callback) {
+ for (PartitionMap::const_iterator it = partitions_.begin();
+ it != partitions_.end();
+ ++it) {
+ callback.Run(it->second);
+ }
+}
+
+void StoragePartitionImplMap::PostCreateInitialization(
+ StoragePartitionImpl* partition,
+ bool in_memory) {
+ // TODO(ajwong): ResourceContexts no longer have any storage related state.
+ // We should move this into a place where it is called once per
+ // BrowserContext creation rather than piggybacking off the default context
+ // creation.
+ // Note: moving this into Get() before partitions_[] is set causes reentrency.
+ if (!resource_context_initialized_) {
+ resource_context_initialized_ = true;
+ InitializeResourceContext(browser_context_);
+ }
+
+ // Check first to avoid memory leak in unittests.
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ChromeAppCacheService::InitializeOnIOThread,
+ partition->GetAppCacheService(),
+ in_memory ? base::FilePath() :
+ partition->GetPath().Append(kAppCacheDirname),
+ browser_context_->GetResourceContext(),
+ make_scoped_refptr(partition->GetURLRequestContext()),
+ make_scoped_refptr(
+ browser_context_->GetSpecialStoragePolicy())));
+
+ // We do not call InitializeURLRequestContext() for media contexts because,
+ // other than the HTTP cache, the media contexts share the same backing
+ // objects as their associated "normal" request context. Thus, the previous
+ // call serves to initialize the media request context for this storage
+ // partition as well.
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/storage_partition_impl_map.h b/chromium/content/browser/storage_partition_impl_map.h
new file mode 100644
index 00000000000..972335a33aa
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl_map.h
@@ -0,0 +1,132 @@
+// 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 CONTENT_BROWSER_STORAGE_PARTITION_MAP_H_
+#define CONTENT_BROWSER_STORAGE_PARTITION_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/containers/hash_tables.h"
+#include "base/gtest_prod_util.h"
+#include "base/supports_user_data.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace content {
+
+class BrowserContext;
+
+// A std::string to StoragePartition map for use with SupportsUserData APIs.
+class StoragePartitionImplMap : public base::SupportsUserData::Data {
+ public:
+ explicit StoragePartitionImplMap(BrowserContext* browser_context);
+
+ virtual ~StoragePartitionImplMap();
+
+ // This map retains ownership of the returned StoragePartition objects.
+ StoragePartitionImpl* Get(const std::string& partition_domain,
+ const std::string& partition_name,
+ bool in_memory);
+
+ // Starts an asynchronous best-effort attempt to delete all on-disk storage
+ // related to |site|, avoiding any directories that are known to be in use.
+ //
+ // |on_gc_required| is called if the AsyncObliterate() call was unable to
+ // fully clean the on-disk storage requiring a call to GarbageCollect() on
+ // the next browser start.
+ void AsyncObliterate(const GURL& site, const base::Closure& on_gc_required);
+
+ // Examines the on-disk storage and removes any entires that are not listed
+ // in the |active_paths|, or in use by current entries in the storage
+ // partition.
+ //
+ // The |done| closure is executed on the calling thread when garbage
+ // collection is complete.
+ void GarbageCollect(scoped_ptr<base::hash_set<base::FilePath> > active_paths,
+ const base::Closure& done);
+
+ void ForEach(const BrowserContext::StoragePartitionCallback& callback);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(StoragePartitionConfigTest, OperatorLess);
+
+ // Each StoragePartition is uniquely identified by which partition domain
+ // it belongs to (such as an app or the browser itself), the user supplied
+ // partition name and the bit indicating whether it should be persisted on
+ // disk or not. This structure contains those elements and is used as
+ // uniqueness key to lookup StoragePartition objects in the global map.
+ //
+ // TODO(nasko): It is equivalent, though not identical to the same structure
+ // that lives in chrome profiles. The difference is that this one has
+ // partition_domain and partition_name separate, while the latter one has
+ // the path produced by combining the two pieces together.
+ // The fix for http://crbug.com/159193 will remove the chrome version.
+ struct StoragePartitionConfig {
+ const std::string partition_domain;
+ const std::string partition_name;
+ const bool in_memory;
+
+ StoragePartitionConfig(const std::string& domain,
+ const std::string& partition,
+ const bool& in_memory_only)
+ : partition_domain(domain),
+ partition_name(partition),
+ in_memory(in_memory_only) {}
+ };
+
+ // Functor for operator <.
+ struct StoragePartitionConfigLess {
+ bool operator()(const StoragePartitionConfig& lhs,
+ const StoragePartitionConfig& rhs) const {
+ if (lhs.partition_domain != rhs.partition_domain)
+ return lhs.partition_domain < rhs.partition_domain;
+ else if (lhs.partition_name != rhs.partition_name)
+ return lhs.partition_name < rhs.partition_name;
+ else if (lhs.in_memory != rhs.in_memory)
+ return lhs.in_memory < rhs.in_memory;
+ else
+ return false;
+ }
+ };
+
+ typedef std::map<StoragePartitionConfig,
+ StoragePartitionImpl*,
+ StoragePartitionConfigLess>
+ PartitionMap;
+
+ // Returns the relative path from the profile's base directory, to the
+ // directory that holds all the state for storage contexts in the given
+ // |partition_domain| and |partition_name|.
+ static base::FilePath GetStoragePartitionPath(
+ const std::string& partition_domain,
+ const std::string& partition_name);
+
+ // This must always be called *after* |partition| has been added to the
+ // partitions_.
+ //
+ // TODO(ajwong): Is there a way to make it so that Get()'s implementation
+ // doesn't need to be aware of this ordering? Revisit when refactoring
+ // ResourceContext and AppCache to respect storage partitions.
+ void PostCreateInitialization(StoragePartitionImpl* partition,
+ bool in_memory);
+
+ BrowserContext* browser_context_; // Not Owned.
+ scoped_refptr<base::SequencedTaskRunner> file_access_runner_;
+ PartitionMap partitions_;
+
+ // Set to true when the ResourceContext for the associated |browser_context_|
+ // is initialized. Can never return to false.
+ bool resource_context_initialized_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STORAGE_PARTITION_MAP_H_
diff --git a/chromium/content/browser/storage_partition_impl_map_unittest.cc b/chromium/content/browser/storage_partition_impl_map_unittest.cc
new file mode 100644
index 00000000000..04127cce7cb
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl_map_unittest.cc
@@ -0,0 +1,63 @@
+// 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 "content/browser/storage_partition_impl_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class StoragePartitionConfigTest : public testing::Test {
+};
+
+// Test that the Less comparison function is implemented properly to uniquely
+// identify storage partitions used as keys in a std::map.
+TEST_F(StoragePartitionConfigTest, OperatorLess) {
+ StoragePartitionImplMap::StoragePartitionConfig c1(
+ std::string(), std::string(), false);
+ StoragePartitionImplMap::StoragePartitionConfig c2(
+ std::string(), std::string(), false);
+ StoragePartitionImplMap::StoragePartitionConfig c3(
+ std::string(), std::string(), true);
+ StoragePartitionImplMap::StoragePartitionConfig c4("a", std::string(), true);
+ StoragePartitionImplMap::StoragePartitionConfig c5("b", std::string(), true);
+ StoragePartitionImplMap::StoragePartitionConfig c6(
+ std::string(), "abc", false);
+ StoragePartitionImplMap::StoragePartitionConfig c7(
+ std::string(), "abc", true);
+ StoragePartitionImplMap::StoragePartitionConfig c8("a", "abc", false);
+ StoragePartitionImplMap::StoragePartitionConfig c9("a", "abc", true);
+
+ StoragePartitionImplMap::StoragePartitionConfigLess less;
+
+ // Let's ensure basic comparison works.
+ EXPECT_TRUE(less(c1, c3));
+ EXPECT_TRUE(less(c1, c4));
+ EXPECT_TRUE(less(c3, c4));
+ EXPECT_TRUE(less(c4, c5));
+ EXPECT_TRUE(less(c4, c8));
+ EXPECT_TRUE(less(c6, c4));
+ EXPECT_TRUE(less(c6, c7));
+ EXPECT_TRUE(less(c8, c9));
+
+ // Now, ensure antisymmetry for each pair we've tested.
+ EXPECT_FALSE(less(c3, c1));
+ EXPECT_FALSE(less(c4, c1));
+ EXPECT_FALSE(less(c4, c3));
+ EXPECT_FALSE(less(c5, c4));
+ EXPECT_FALSE(less(c8, c4));
+ EXPECT_FALSE(less(c4, c6));
+ EXPECT_FALSE(less(c7, c6));
+ EXPECT_FALSE(less(c9, c8));
+
+ // Check for irreflexivity.
+ EXPECT_FALSE(less(c1, c1));
+
+ // Check for transitivity.
+ EXPECT_TRUE(less(c1, c4));
+
+ // Let's enforce that two identical elements obey strict weak ordering.
+ EXPECT_TRUE(!less(c1, c2) && !less(c2, c1));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/storage_partition_impl_unittest.cc b/chromium/content/browser/storage_partition_impl_unittest.cc
new file mode 100644
index 00000000000..a8b47bc74ca
--- /dev/null
+++ b/chromium/content/browser/storage_partition_impl_unittest.cc
@@ -0,0 +1,120 @@
+// 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 "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/gpu/shader_disk_cache.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class TestClosureCallback {
+ public:
+ TestClosureCallback()
+ : callback_(base::Bind(
+ &TestClosureCallback::StopWaiting, base::Unretained(this))) {
+ }
+
+ void WaitForResult() {
+ wait_run_loop_.reset(new base::RunLoop());
+ wait_run_loop_->Run();
+ }
+
+ const base::Closure& callback() { return callback_; }
+
+ private:
+ void StopWaiting() {
+ wait_run_loop_->Quit();
+ }
+
+ base::Closure callback_;
+ scoped_ptr<base::RunLoop> wait_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestClosureCallback);
+};
+
+const int kDefaultClientId = 42;
+const char kCacheKey[] = "key";
+const char kCacheValue[] = "cached value";
+
+} // namespace
+
+class StoragePartitionShaderClearTest : public testing::Test {
+ public:
+ StoragePartitionShaderClearTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+ }
+
+ virtual ~StoragePartitionShaderClearTest() {}
+
+ const base::FilePath& cache_path() { return temp_dir_.path(); }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ShaderCacheFactory::GetInstance()->SetCacheInfo(kDefaultClientId,
+ cache_path());
+
+ cache_ = ShaderCacheFactory::GetInstance()->Get(kDefaultClientId);
+ ASSERT_TRUE(cache_.get() != NULL);
+ }
+
+ void InitCache() {
+ net::TestCompletionCallback available_cb;
+ int rv = cache_->SetAvailableCallback(available_cb.callback());
+ ASSERT_EQ(net::OK, available_cb.GetResult(rv));
+ EXPECT_EQ(0, cache_->Size());
+
+ cache_->Cache(kCacheKey, kCacheValue);
+
+ net::TestCompletionCallback complete_cb;
+
+ rv = cache_->SetCacheCompleteCallback(complete_cb.callback());
+ ASSERT_EQ(net::OK, complete_cb.GetResult(rv));
+ }
+
+ size_t Size() { return cache_->Size(); }
+
+ private:
+ virtual void TearDown() OVERRIDE {
+ cache_ = NULL;
+ ShaderCacheFactory::GetInstance()->RemoveCacheInfo(kDefaultClientId);
+ }
+
+ base::ScopedTempDir temp_dir_;
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ scoped_refptr<ShaderDiskCache> cache_;
+};
+
+void ClearData(content::StoragePartitionImpl* sp,
+ const base::Closure& cb) {
+ base::Time time;
+ sp->ClearDataForRange(
+ StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
+ StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
+ time, time, cb);
+}
+
+TEST_F(StoragePartitionShaderClearTest, ClearShaderCache) {
+ InitCache();
+ EXPECT_EQ(1u, Size());
+
+ TestClosureCallback clear_cb;
+ StoragePartitionImpl sp(
+ cache_path(), NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ClearData, &sp, clear_cb.callback()));
+ clear_cb.WaitForResult();
+ EXPECT_EQ(0u, Size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/OWNERS b/chromium/content/browser/streams/OWNERS
new file mode 100644
index 00000000000..8192dfb0eff
--- /dev/null
+++ b/chromium/content/browser/streams/OWNERS
@@ -0,0 +1 @@
+zork@chromium.org
diff --git a/chromium/content/browser/streams/stream.cc b/chromium/content/browser/streams/stream.cc
new file mode 100644
index 00000000000..6026df9e7c7
--- /dev/null
+++ b/chromium/content/browser/streams/stream.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 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 "content/browser/streams/stream.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "content/browser/streams/stream_handle_impl.h"
+#include "content/browser/streams/stream_read_observer.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/browser/streams/stream_write_observer.h"
+#include "net/base/io_buffer.h"
+
+namespace {
+// Start throttling the connection at about 1MB.
+const size_t kDeferSizeThreshold = 40 * 32768;
+}
+
+namespace content {
+
+Stream::Stream(StreamRegistry* registry,
+ StreamWriteObserver* write_observer,
+ const GURL& url)
+ : data_bytes_read_(0),
+ can_add_data_(true),
+ url_(url),
+ data_length_(0),
+ registry_(registry),
+ read_observer_(NULL),
+ write_observer_(write_observer),
+ stream_handle_(NULL),
+ weak_ptr_factory_(this) {
+ CreateByteStream(base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current(),
+ kDeferSizeThreshold,
+ &writer_,
+ &reader_);
+
+ // Setup callback for writing.
+ writer_->RegisterCallback(base::Bind(&Stream::OnSpaceAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
+ reader_->RegisterCallback(base::Bind(&Stream::OnDataAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ registry_->RegisterStream(this);
+}
+
+Stream::~Stream() {
+}
+
+bool Stream::SetReadObserver(StreamReadObserver* observer) {
+ if (read_observer_)
+ return false;
+ read_observer_ = observer;
+ return true;
+}
+
+void Stream::RemoveReadObserver(StreamReadObserver* observer) {
+ DCHECK(observer == read_observer_);
+ read_observer_ = NULL;
+}
+
+void Stream::RemoveWriteObserver(StreamWriteObserver* observer) {
+ DCHECK(observer == write_observer_);
+ write_observer_ = NULL;
+}
+
+void Stream::AddData(scoped_refptr<net::IOBuffer> buffer, size_t size) {
+ can_add_data_ = writer_->Write(buffer, size);
+}
+
+void Stream::AddData(const char* data, size_t size) {
+ scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(size));
+ memcpy(io_buffer->data(), data, size);
+ can_add_data_ = writer_->Write(io_buffer, size);
+}
+
+void Stream::Finalize() {
+ writer_->Close(0);
+ writer_.reset(NULL);
+
+ // Continue asynchronously.
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Stream::OnDataAvailable, weak_ptr_factory_.GetWeakPtr()));
+}
+
+Stream::StreamState Stream::ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) {
+ DCHECK(buf);
+ DCHECK(bytes_read);
+
+ *bytes_read = 0;
+ if (!data_.get()) {
+ data_length_ = 0;
+ data_bytes_read_ = 0;
+ ByteStreamReader::StreamState state = reader_->Read(&data_, &data_length_);
+ switch (state) {
+ case ByteStreamReader::STREAM_HAS_DATA:
+ break;
+ case ByteStreamReader::STREAM_COMPLETE:
+ registry_->UnregisterStream(url());
+ return STREAM_COMPLETE;
+ case ByteStreamReader::STREAM_EMPTY:
+ return STREAM_EMPTY;
+ }
+ }
+
+ const size_t remaining_bytes = data_length_ - data_bytes_read_;
+ size_t to_read =
+ static_cast<size_t>(buf_size) < remaining_bytes ?
+ buf_size : remaining_bytes;
+ memcpy(buf->data(), data_->data() + data_bytes_read_, to_read);
+ data_bytes_read_ += to_read;
+ if (data_bytes_read_ >= data_length_)
+ data_ = NULL;
+
+ *bytes_read = to_read;
+ return STREAM_HAS_DATA;
+}
+
+scoped_ptr<StreamHandle> Stream::CreateHandle(const GURL& original_url,
+ const std::string& mime_type) {
+ CHECK(!stream_handle_);
+ stream_handle_ = new StreamHandleImpl(weak_ptr_factory_.GetWeakPtr(),
+ original_url,
+ mime_type);
+ return scoped_ptr<StreamHandle>(stream_handle_).Pass();
+}
+
+void Stream::CloseHandle() {
+ // Prevent deletion until this function ends.
+ scoped_refptr<Stream> ref(this);
+
+ CHECK(stream_handle_);
+ stream_handle_ = NULL;
+ registry_->UnregisterStream(url());
+ if (write_observer_)
+ write_observer_->OnClose(this);
+}
+
+void Stream::OnSpaceAvailable() {
+ can_add_data_ = true;
+ if (write_observer_)
+ write_observer_->OnSpaceAvailable(this);
+}
+
+void Stream::OnDataAvailable() {
+ if (read_observer_)
+ read_observer_->OnDataAvailable(this);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream.h b/chromium/content/browser/streams/stream.h
new file mode 100644
index 00000000000..85edc884081
--- /dev/null
+++ b/chromium/content/browser/streams/stream.h
@@ -0,0 +1,115 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/byte_stream.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace content {
+
+class StreamHandle;
+class StreamHandleImpl;
+class StreamReadObserver;
+class StreamRegistry;
+class StreamWriteObserver;
+
+// A stream that sends data from an arbitrary source to an internal URL
+// that can be read by an internal consumer. It will continue to pull from the
+// original URL as long as there is data available. It can be read from
+// multiple clients, but only one can be reading at a time. This allows a
+// reader to consume part of the stream, then pass it along to another client
+// to continue processing the stream.
+class CONTENT_EXPORT Stream : public base::RefCountedThreadSafe<Stream> {
+ public:
+ enum StreamState {
+ STREAM_HAS_DATA,
+ STREAM_COMPLETE,
+ STREAM_EMPTY,
+ };
+
+ // Creates a stream.
+ //
+ // Security origin of Streams is checked in Blink (See BlobRegistry,
+ // BlobURL and SecurityOrigin to understand how it works). There's no security
+ // origin check in Chromium side for now.
+ Stream(StreamRegistry* registry,
+ StreamWriteObserver* write_observer,
+ const GURL& url);
+
+ // Sets the reader of this stream. Returns true on success, or false if there
+ // is already a reader.
+ bool SetReadObserver(StreamReadObserver* observer);
+
+ // Removes the read observer. |observer| must be the current observer.
+ void RemoveReadObserver(StreamReadObserver* observer);
+
+ // Removes the write observer. |observer| must be the current observer.
+ void RemoveWriteObserver(StreamWriteObserver* observer);
+
+ // Adds the data in |buffer| to the stream. Takes ownership of |buffer|.
+ void AddData(scoped_refptr<net::IOBuffer> buffer, size_t size);
+ // Adds data of |size| at |data| to the stream. This method creates a copy
+ // of the data, and then passes it to |writer_|.
+ void AddData(const char* data, size_t size);
+
+ // Notifies this stream that it will not be receiving any more data.
+ void Finalize();
+
+ // Reads a maximum of |buf_size| from the stream into |buf|. Sets
+ // |*bytes_read| to the number of bytes actually read.
+ // Returns STREAM_HAS_DATA if data was read, STREAM_EMPTY if no data was read,
+ // and STREAM_COMPLETE if the stream is finalized and all data has been read.
+ StreamState ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read);
+
+ scoped_ptr<StreamHandle> CreateHandle(const GURL& original_url,
+ const std::string& mime_type);
+ void CloseHandle();
+
+ // Indicates whether there is space in the buffer to add more data.
+ bool can_add_data() const { return can_add_data_; }
+
+ const GURL& url() const { return url_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Stream>;
+
+ virtual ~Stream();
+
+ void OnSpaceAvailable();
+ void OnDataAvailable();
+
+ size_t data_bytes_read_;
+ bool can_add_data_;
+
+ GURL url_;
+
+ scoped_refptr<net::IOBuffer> data_;
+ size_t data_length_;
+
+ scoped_ptr<ByteStreamWriter> writer_;
+ scoped_ptr<ByteStreamReader> reader_;
+
+ StreamRegistry* registry_;
+ StreamReadObserver* read_observer_;
+ StreamWriteObserver* write_observer_;
+
+ StreamHandleImpl* stream_handle_;
+
+ base::WeakPtrFactory<Stream> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(Stream);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_H_
diff --git a/chromium/content/browser/streams/stream_context.cc b/chromium/content/browser/streams/stream_context.cc
new file mode 100644
index 00000000000..44ca0f4a1cd
--- /dev/null
+++ b/chromium/content/browser/streams/stream_context.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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 "content/browser/streams/stream_context.h"
+
+#include "base/bind.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/public/browser/browser_context.h"
+
+using base::UserDataAdapter;
+
+namespace {
+const char* kStreamContextKeyName = "content_stream_context";
+}
+
+namespace content {
+
+StreamContext::StreamContext() {}
+
+StreamContext* StreamContext::GetFor(BrowserContext* context) {
+ if (!context->GetUserData(kStreamContextKeyName)) {
+ scoped_refptr<StreamContext> stream = new StreamContext();
+ context->SetUserData(kStreamContextKeyName,
+ new UserDataAdapter<StreamContext>(stream.get()));
+ // Check first to avoid memory leak in unittests.
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&StreamContext::InitializeOnIOThread, stream));
+ }
+ }
+
+ return UserDataAdapter<StreamContext>::Get(context, kStreamContextKeyName);
+}
+
+void StreamContext::InitializeOnIOThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ registry_.reset(new StreamRegistry());
+}
+
+StreamContext::~StreamContext() {}
+
+void StreamContext::DeleteOnCorrectThread() const {
+ // In many tests, there isn't a valid IO thread. In that case, just delete on
+ // the current thread.
+ // TODO(zork): Remove this custom deleter, and fix the leaks in all the
+ // tests.
+ if (BrowserThread::IsMessageLoopValid(BrowserThread::IO) &&
+ !BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+ BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, this);
+ return;
+ }
+ delete this;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_context.h b/chromium/content/browser/streams/stream_context.h
new file mode 100644
index 00000000000..fb6cdc720ff
--- /dev/null
+++ b/chromium/content/browser/streams/stream_context.h
@@ -0,0 +1,60 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_CONTEXT_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_CONTEXT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+class BrowserContext;
+class StreamRegistry;
+struct StreamContextDeleter;
+
+// A context class that keeps track of StreamRegistry used by the chrome.
+// There is an instance associated with each BrowserContext. There could be
+// multiple URLRequestContexts in the same browser context that refers to the
+// same instance.
+//
+// All methods, except the ctor, are expected to be called on
+// the IO thread (unless specifically called out in doc comments).
+class StreamContext
+ : public base::RefCountedThreadSafe<StreamContext,
+ StreamContextDeleter> {
+ public:
+ StreamContext();
+
+ CONTENT_EXPORT static StreamContext* GetFor(BrowserContext* browser_context);
+
+ void InitializeOnIOThread();
+
+ StreamRegistry* registry() const { return registry_.get(); }
+
+ protected:
+ virtual ~StreamContext();
+
+ private:
+ friend class base::DeleteHelper<StreamContext>;
+ friend class base::RefCountedThreadSafe<StreamContext,
+ StreamContextDeleter>;
+ friend struct StreamContextDeleter;
+
+ void DeleteOnCorrectThread() const;
+
+ scoped_ptr<StreamRegistry> registry_;
+};
+
+struct StreamContextDeleter {
+ static void Destruct(const StreamContext* context) {
+ context->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_CONTEXT_H_
diff --git a/chromium/content/browser/streams/stream_handle_impl.cc b/chromium/content/browser/streams/stream_handle_impl.cc
new file mode 100644
index 00000000000..d9f877cbc76
--- /dev/null
+++ b/chromium/content/browser/streams/stream_handle_impl.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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 "content/browser/streams/stream_handle_impl.h"
+
+#include "content/browser/streams/stream.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+StreamHandleImpl::StreamHandleImpl(const base::WeakPtr<Stream>& stream,
+ const GURL& original_url,
+ const std::string& mime_type)
+ : stream_(stream),
+ url_(stream->url()),
+ original_url_(original_url),
+ mime_type_(mime_type),
+ stream_message_loop_(base::MessageLoopProxy::current().get()) {}
+
+StreamHandleImpl::~StreamHandleImpl() {
+ stream_message_loop_->PostTask(FROM_HERE,
+ base::Bind(&Stream::CloseHandle, stream_));
+}
+
+const GURL& StreamHandleImpl::GetURL() {
+ return url_;
+}
+
+const GURL& StreamHandleImpl::GetOriginalURL() {
+ return original_url_;
+}
+
+const std::string& StreamHandleImpl::GetMimeType() {
+ return mime_type_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_handle_impl.h b/chromium/content/browser/streams/stream_handle_impl.h
new file mode 100644
index 00000000000..aabd0cb7dfe
--- /dev/null
+++ b/chromium/content/browser/streams/stream_handle_impl.h
@@ -0,0 +1,40 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/stream_handle.h"
+
+namespace content {
+
+class Stream;
+
+class StreamHandleImpl : public StreamHandle {
+ public:
+ StreamHandleImpl(const base::WeakPtr<Stream>& stream,
+ const GURL& original_url,
+ const std::string& mime_type);
+ virtual ~StreamHandleImpl();
+
+ private:
+ // StreamHandle overrides
+ virtual const GURL& GetURL() OVERRIDE;
+ virtual const GURL& GetOriginalURL() OVERRIDE;
+ virtual const std::string& GetMimeType() OVERRIDE;
+
+ base::WeakPtr<Stream> stream_;
+ GURL url_;
+ GURL original_url_;
+ std::string mime_type_;
+ base::MessageLoopProxy* stream_message_loop_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_
+
diff --git a/chromium/content/browser/streams/stream_read_observer.h b/chromium/content/browser/streams/stream_read_observer.h
new file mode 100644
index 00000000000..08fd657ac01
--- /dev/null
+++ b/chromium/content/browser/streams/stream_read_observer.h
@@ -0,0 +1,26 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_READ_OBSERVER_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_READ_OBSERVER_H_
+
+#include "content/common/content_export.h"
+
+namespace content {
+
+class Stream;
+
+class CONTENT_EXPORT StreamReadObserver {
+ public:
+ // Sent when there is data available to be read from the stream.
+ virtual void OnDataAvailable(Stream* stream) = 0;
+
+ protected:
+ virtual ~StreamReadObserver() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_READ_OBSERVER_H_
+
diff --git a/chromium/content/browser/streams/stream_registry.cc b/chromium/content/browser/streams/stream_registry.cc
new file mode 100644
index 00000000000..39d24b39f5f
--- /dev/null
+++ b/chromium/content/browser/streams/stream_registry.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 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 "content/browser/streams/stream_registry.h"
+
+#include "content/browser/streams/stream.h"
+
+namespace content {
+
+StreamRegistry::StreamRegistry() {
+}
+
+StreamRegistry::~StreamRegistry() {
+}
+
+void StreamRegistry::RegisterStream(scoped_refptr<Stream> stream) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(stream.get());
+ DCHECK(!stream->url().is_empty());
+ streams_[stream->url()] = stream;
+}
+
+scoped_refptr<Stream> StreamRegistry::GetStream(const GURL& url) {
+ DCHECK(CalledOnValidThread());
+ StreamMap::const_iterator stream = streams_.find(url);
+ if (stream != streams_.end())
+ return stream->second;
+
+ return NULL;
+}
+
+bool StreamRegistry::CloneStream(const GURL& url, const GURL& src_url) {
+ DCHECK(CalledOnValidThread());
+ scoped_refptr<Stream> stream(GetStream(src_url));
+ if (stream.get()) {
+ streams_[url] = stream;
+ return true;
+ }
+ return false;
+}
+
+void StreamRegistry::UnregisterStream(const GURL& url) {
+ DCHECK(CalledOnValidThread());
+ streams_.erase(url);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_registry.h b/chromium/content/browser/streams/stream_registry.h
new file mode 100644
index 00000000000..e75c97c1699
--- /dev/null
+++ b/chromium/content/browser/streams/stream_registry.h
@@ -0,0 +1,51 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_REGISTRY_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_REGISTRY_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class Stream;
+
+// Maintains a mapping of blob: URLs to active streams.
+class CONTENT_EXPORT StreamRegistry : public base::NonThreadSafe {
+ public:
+ StreamRegistry();
+ virtual ~StreamRegistry();
+
+ // Registers a stream, and sets its URL.
+ void RegisterStream(scoped_refptr<Stream> stream);
+
+ // Clones a stream. Returns true on success, or false if |src_url| doesn't
+ // exist.
+ bool CloneStream(const GURL& url, const GURL& src_url);
+
+ void UnregisterStream(const GURL& url);
+
+ // Gets the stream associated with |url|. Returns NULL if there is no such
+ // stream.
+ scoped_refptr<Stream> GetStream(const GURL& url);
+
+ private:
+ typedef std::map<GURL, scoped_refptr<Stream> > StreamMap;
+
+ StreamMap streams_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamRegistry);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_REGISTRY_H_
+
+
diff --git a/chromium/content/browser/streams/stream_unittest.cc b/chromium/content/browser/streams/stream_unittest.cc
new file mode 100644
index 00000000000..c0077b7375b
--- /dev/null
+++ b/chromium/content/browser/streams/stream_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 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 "base/message_loop/message_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_read_observer.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/browser/streams/stream_write_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class StreamTest : public testing::Test {
+ public:
+ StreamTest() : producing_seed_key_(0) {}
+
+ virtual void SetUp() OVERRIDE {
+ registry_.reset(new StreamRegistry());
+ }
+
+ // Create a new IO buffer of the given |buffer_size| and fill it with random
+ // data.
+ scoped_refptr<net::IOBuffer> NewIOBuffer(size_t buffer_size) {
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(buffer_size));
+ char *bufferp = buffer->data();
+ for (size_t i = 0; i < buffer_size; i++)
+ bufferp[i] = (i + producing_seed_key_) % (1 << sizeof(char));
+ ++producing_seed_key_;
+ return buffer;
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ scoped_ptr<StreamRegistry> registry_;
+
+ private:
+ int producing_seed_key_;
+};
+
+class TestStreamReader : public StreamReadObserver {
+ public:
+ TestStreamReader() : buffer_(new net::GrowableIOBuffer()), completed_(false) {
+ }
+ virtual ~TestStreamReader() {}
+
+ void Read(Stream* stream) {
+ const size_t kBufferSize = 32768;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+
+ int bytes_read = 0;
+ while (true) {
+ Stream::StreamState state =
+ stream->ReadRawData(buffer.get(), kBufferSize, &bytes_read);
+ switch (state) {
+ case Stream::STREAM_HAS_DATA:
+ // TODO(tyoshino): Move these expectations to the beginning of Read()
+ // method once Stream::Finalize() is fixed.
+ EXPECT_FALSE(completed_);
+ break;
+ case Stream::STREAM_COMPLETE:
+ completed_ = true;
+ return;
+ case Stream::STREAM_EMPTY:
+ EXPECT_FALSE(completed_);
+ return;
+ }
+ size_t old_capacity = buffer_->capacity();
+ buffer_->SetCapacity(old_capacity + bytes_read);
+ memcpy(buffer_->StartOfBuffer() + old_capacity,
+ buffer->data(), bytes_read);
+ }
+ }
+
+ virtual void OnDataAvailable(Stream* stream) OVERRIDE {
+ Read(stream);
+ }
+
+ scoped_refptr<net::GrowableIOBuffer> buffer() { return buffer_; }
+
+ bool completed() const {
+ return completed_;
+ }
+
+ private:
+ scoped_refptr<net::GrowableIOBuffer> buffer_;
+ bool completed_;
+};
+
+class TestStreamWriter : public StreamWriteObserver {
+ public:
+ TestStreamWriter() {}
+ virtual ~TestStreamWriter() {}
+
+ void Write(Stream* stream,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t buffer_size) {
+ stream->AddData(buffer, buffer_size);
+ }
+
+ virtual void OnSpaceAvailable(Stream* stream) OVERRIDE {
+ }
+
+ virtual void OnClose(Stream* stream) OVERRIDE {
+ }
+};
+
+TEST_F(StreamTest, SetReadObserver) {
+ TestStreamReader reader;
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), &writer, url));
+ EXPECT_TRUE(stream->SetReadObserver(&reader));
+}
+
+TEST_F(StreamTest, SetReadObserver_SecondFails) {
+ TestStreamReader reader1;
+ TestStreamReader reader2;
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), &writer, url));
+ EXPECT_TRUE(stream->SetReadObserver(&reader1));
+ EXPECT_FALSE(stream->SetReadObserver(&reader2));
+}
+
+TEST_F(StreamTest, SetReadObserver_TwoReaders) {
+ TestStreamReader reader1;
+ TestStreamReader reader2;
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), &writer, url));
+ EXPECT_TRUE(stream->SetReadObserver(&reader1));
+
+ // Once the first read observer is removed, a new one can be added.
+ stream->RemoveReadObserver(&reader1);
+ EXPECT_TRUE(stream->SetReadObserver(&reader2));
+}
+
+TEST_F(StreamTest, Stream) {
+ TestStreamReader reader;
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), &writer, url));
+ EXPECT_TRUE(stream->SetReadObserver(&reader));
+
+ const int kBufferSize = 1000000;
+ scoped_refptr<net::IOBuffer> buffer(NewIOBuffer(kBufferSize));
+ writer.Write(stream.get(), buffer, kBufferSize);
+ stream->Finalize();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(reader.completed());
+
+ ASSERT_EQ(reader.buffer()->capacity(), kBufferSize);
+ for (int i = 0; i < kBufferSize; i++)
+ EXPECT_EQ(buffer->data()[i], reader.buffer()->data()[i]);
+}
+
+// Test that even if a reader receives an empty buffer, once TransferData()
+// method is called on it with |source_complete| = true, following Read() calls
+// on it never returns STREAM_EMPTY. Together with StreamTest.Stream above, this
+// guarantees that Reader::Read() call returns only STREAM_HAS_DATA
+// or STREAM_COMPLETE in |data_available_callback_| call corresponding to
+// Writer::Close().
+TEST_F(StreamTest, ClosedReaderDoesNotReturnStreamEmpty) {
+ TestStreamReader reader;
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), &writer, url));
+ EXPECT_TRUE(stream->SetReadObserver(&reader));
+
+ const int kBufferSize = 0;
+ scoped_refptr<net::IOBuffer> buffer(NewIOBuffer(kBufferSize));
+ stream->AddData(buffer, kBufferSize);
+ stream->Finalize();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(reader.completed());
+ EXPECT_EQ(0, reader.buffer()->capacity());
+}
+
+TEST_F(StreamTest, GetStream) {
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream1(
+ new Stream(registry_.get(), &writer, url));
+
+ scoped_refptr<Stream> stream2 = registry_->GetStream(url);
+ ASSERT_EQ(stream1, stream2);
+}
+
+TEST_F(StreamTest, GetStream_Missing) {
+ TestStreamWriter writer;
+
+ GURL url1("blob://stream");
+ scoped_refptr<Stream> stream1(
+ new Stream(registry_.get(), &writer, url1));
+
+ GURL url2("blob://stream2");
+ scoped_refptr<Stream> stream2 = registry_->GetStream(url2);
+ ASSERT_FALSE(stream2.get());
+}
+
+TEST_F(StreamTest, CloneStream) {
+ TestStreamWriter writer;
+
+ GURL url1("blob://stream");
+ scoped_refptr<Stream> stream1(
+ new Stream(registry_.get(), &writer, url1));
+
+ GURL url2("blob://stream2");
+ ASSERT_TRUE(registry_->CloneStream(url2, url1));
+ scoped_refptr<Stream> stream2 = registry_->GetStream(url2);
+ ASSERT_EQ(stream1, stream2);
+}
+
+TEST_F(StreamTest, CloneStream_Missing) {
+ TestStreamWriter writer;
+
+ GURL url1("blob://stream");
+ scoped_refptr<Stream> stream1(
+ new Stream(registry_.get(), &writer, url1));
+
+ GURL url2("blob://stream2");
+ GURL url3("blob://stream3");
+ ASSERT_FALSE(registry_->CloneStream(url2, url3));
+ scoped_refptr<Stream> stream2 = registry_->GetStream(url2);
+ ASSERT_FALSE(stream2.get());
+}
+
+TEST_F(StreamTest, UnregisterStream) {
+ TestStreamWriter writer;
+
+ GURL url("blob://stream");
+ scoped_refptr<Stream> stream1(
+ new Stream(registry_.get(), &writer, url));
+
+ registry_->UnregisterStream(url);
+ scoped_refptr<Stream> stream2 = registry_->GetStream(url);
+ ASSERT_FALSE(stream2.get());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_url_request_job.cc b/chromium/content/browser/streams/stream_url_request_job.cc
new file mode 100644
index 00000000000..0965178de2c
--- /dev/null
+++ b/chromium/content/browser/streams/stream_url_request_job.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 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 "content/browser/streams/stream_url_request_job.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/streams/stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+StreamURLRequestJob::StreamURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ scoped_refptr<Stream> stream)
+ : net::URLRequestJob(request, network_delegate),
+ weak_factory_(this),
+ stream_(stream),
+ headers_set_(false),
+ pending_buffer_size_(0),
+ total_bytes_read_(0),
+ max_range_(0),
+ request_failed_(false) {
+ DCHECK(stream_.get());
+ stream_->SetReadObserver(this);
+}
+
+StreamURLRequestJob::~StreamURLRequestJob() {
+ ClearStream();
+}
+
+void StreamURLRequestJob::OnDataAvailable(Stream* stream) {
+ // Clear the IO_PENDING status.
+ SetStatus(net::URLRequestStatus());
+ // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData()
+ // operation waiting for IO completion.
+ if (!pending_buffer_.get())
+ return;
+
+ // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData()
+ // by URLRequestJob.
+
+ int bytes_read;
+ switch (stream_->ReadRawData(
+ pending_buffer_.get(), pending_buffer_size_, &bytes_read)) {
+ case Stream::STREAM_HAS_DATA:
+ DCHECK_GT(bytes_read, 0);
+ break;
+ case Stream::STREAM_COMPLETE:
+ // Ensure this. Calling NotifyReadComplete call with 0 signals
+ // completion.
+ bytes_read = 0;
+ break;
+ case Stream::STREAM_EMPTY:
+ NOTREACHED();
+ break;
+ }
+
+ // Clear the buffers before notifying the read is complete, so that it is
+ // safe for the observer to read.
+ pending_buffer_ = NULL;
+ pending_buffer_size_ = 0;
+
+ total_bytes_read_ += bytes_read;
+ NotifyReadComplete(bytes_read);
+}
+
+// net::URLRequestJob methods.
+void StreamURLRequestJob::Start() {
+ // Continue asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&StreamURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
+}
+
+void StreamURLRequestJob::Kill() {
+ net::URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+ ClearStream();
+}
+
+bool StreamURLRequestJob::ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) {
+ if (request_failed_)
+ return true;
+
+ DCHECK(buf);
+ DCHECK(bytes_read);
+ int to_read = buf_size;
+ if (max_range_ && to_read) {
+ if (to_read + total_bytes_read_ > max_range_)
+ to_read = max_range_ - total_bytes_read_;
+
+ if (to_read <= 0) {
+ *bytes_read = 0;
+ return true;
+ }
+ }
+
+ switch (stream_->ReadRawData(buf, to_read, bytes_read)) {
+ case Stream::STREAM_HAS_DATA:
+ case Stream::STREAM_COMPLETE:
+ total_bytes_read_ += *bytes_read;
+ return true;
+ case Stream::STREAM_EMPTY:
+ pending_buffer_ = buf;
+ pending_buffer_size_ = to_read;
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool StreamURLRequestJob::GetMimeType(std::string* mime_type) const {
+ if (!response_info_)
+ return false;
+
+ // TODO(zork): Support registered MIME types if needed.
+ return response_info_->headers->GetMimeType(mime_type);
+}
+
+void StreamURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ if (response_info_)
+ *info = *response_info_;
+}
+
+int StreamURLRequestJob::GetResponseCode() const {
+ if (!response_info_)
+ return -1;
+
+ return response_info_->headers->response_code();
+}
+
+void StreamURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ // Streams don't support seeking, so a non-zero starting position
+ // doesn't make sense.
+ if (ranges[0].first_byte_position() == 0) {
+ max_range_ = ranges[0].last_byte_position() + 1;
+ } else {
+ NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
+ return;
+ }
+ } else {
+ NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
+ return;
+ }
+ }
+ }
+}
+
+void StreamURLRequestJob::DidStart() {
+ // We only support GET request.
+ if (request()->method() != "GET") {
+ NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
+ return;
+ }
+
+ HeadersCompleted(net::HTTP_OK);
+}
+
+void StreamURLRequestJob::NotifyFailure(int error_code) {
+ request_failed_ = true;
+
+ // If we already return the headers on success, we can't change the headers
+ // now. Instead, we just error out.
+ if (headers_set_) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ error_code));
+ return;
+ }
+
+ // TODO(zork): Share these with BlobURLRequestJob.
+ net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
+ switch (error_code) {
+ case net::ERR_ACCESS_DENIED:
+ status_code = net::HTTP_FORBIDDEN;
+ break;
+ case net::ERR_FILE_NOT_FOUND:
+ status_code = net::HTTP_NOT_FOUND;
+ break;
+ case net::ERR_METHOD_NOT_SUPPORTED:
+ status_code = net::HTTP_METHOD_NOT_ALLOWED;
+ break;
+ case net::ERR_FAILED:
+ break;
+ default:
+ DCHECK(false);
+ break;
+ }
+ HeadersCompleted(status_code);
+}
+
+void StreamURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
+ std::string status("HTTP/1.1 ");
+ status.append(base::IntToString(status_code));
+ status.append(" ");
+ status.append(net::GetHttpReasonPhrase(status_code));
+ status.append("\0\0", 2);
+ net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
+
+ if (status_code == net::HTTP_OK) {
+ std::string content_type_header(net::HttpRequestHeaders::kContentType);
+ content_type_header.append(": ");
+ content_type_header.append("plain/text");
+ headers->AddHeader(content_type_header);
+ }
+
+ response_info_.reset(new net::HttpResponseInfo());
+ response_info_->headers = headers;
+
+ headers_set_ = true;
+
+ NotifyHeadersComplete();
+}
+
+void StreamURLRequestJob::ClearStream() {
+ if (stream_.get()) {
+ stream_->RemoveReadObserver(this);
+ stream_ = NULL;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_url_request_job.h b/chromium/content/browser/streams/stream_url_request_job.h
new file mode 100644
index 00000000000..03187bf2399
--- /dev/null
+++ b/chromium/content/browser/streams/stream_url_request_job.h
@@ -0,0 +1,66 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_URL_REQUEST_JOB_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_URL_REQUEST_JOB_H_
+
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request_job.h"
+#include "content/browser/streams/stream_read_observer.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class Stream;
+
+// A request job that handles reading stream URLs.
+class CONTENT_EXPORT StreamURLRequestJob
+ : public net::URLRequestJob,
+ public StreamReadObserver {
+ public:
+ StreamURLRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ scoped_refptr<Stream> stream);
+
+ // StreamObserver methods.
+ virtual void OnDataAvailable(Stream* stream) OVERRIDE;
+
+ // net::URLRequestJob methods.
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) OVERRIDE;
+
+ protected:
+ virtual ~StreamURLRequestJob();
+
+ private:
+ void DidStart();
+ void NotifyFailure(int);
+ void HeadersCompleted(net::HttpStatusCode status_code);
+ void ClearStream();
+
+ base::WeakPtrFactory<StreamURLRequestJob> weak_factory_;
+ scoped_refptr<content::Stream> stream_;
+ bool headers_set_;
+ scoped_refptr<net::IOBuffer> pending_buffer_;
+ int pending_buffer_size_;
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+
+ int total_bytes_read_;
+ int max_range_;
+ bool request_failed_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamURLRequestJob);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_URL_REQUEST_JOB_H_
diff --git a/chromium/content/browser/streams/stream_url_request_job_unittest.cc b/chromium/content/browser/streams/stream_url_request_job_unittest.cc
new file mode 100644
index 00000000000..4bb1798b514
--- /dev/null
+++ b/chromium/content/browser/streams/stream_url_request_job_unittest.cc
@@ -0,0 +1,173 @@
+// 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 "base/message_loop/message_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_registry.h"
+#include "content/browser/streams/stream_url_request_job.h"
+#include "content/browser/streams/stream_write_observer.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+const int kBufferSize = 1024;
+const char kTestData1[] = "Hello";
+const char kTestData2[] = "Here it is data.";
+
+const GURL kStreamURL("blob://stream");
+
+} // namespace
+
+class StreamURLRequestJobTest : public testing::Test {
+ public:
+ // A simple ProtocolHandler implementation to create StreamURLRequestJob.
+ class MockProtocolHandler :
+ public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ MockProtocolHandler(StreamRegistry* registry) : registry_(registry) {}
+
+ // net::URLRequestJobFactory::ProtocolHandler override.
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ scoped_refptr<Stream> stream = registry_->GetStream(request->url());
+ if (stream.get())
+ return new StreamURLRequestJob(request, network_delegate, stream);
+ return NULL;
+ }
+
+ private:
+ StreamRegistry* registry_;
+ };
+
+ StreamURLRequestJobTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
+
+ virtual void SetUp() {
+ registry_.reset(new StreamRegistry());
+
+ url_request_job_factory_.SetProtocolHandler(
+ "blob", new MockProtocolHandler(registry_.get()));
+ url_request_context_.set_job_factory(&url_request_job_factory_);
+ }
+
+ virtual void TearDown() {
+ }
+
+ void TestSuccessRequest(const GURL& url,
+ const std::string& expected_response) {
+ TestRequest("GET", url, net::HttpRequestHeaders(), 200, expected_response);
+ }
+
+ void TestRequest(const std::string& method,
+ const GURL& url,
+ const net::HttpRequestHeaders& extra_headers,
+ int expected_status_code,
+ const std::string& expected_response) {
+ net::TestDelegate delegate;
+ request_.reset(url_request_context_.CreateRequest(url, &delegate));
+ request_->set_method(method);
+ if (!extra_headers.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_headers);
+ request_->Start();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify response.
+ EXPECT_TRUE(request_->status().is_success());
+ EXPECT_EQ(expected_status_code,
+ request_->response_headers()->response_code());
+ EXPECT_EQ(expected_response, delegate.data_received());
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ scoped_ptr<StreamRegistry> registry_;
+
+ net::URLRequestContext url_request_context_;
+ net::URLRequestJobFactoryImpl url_request_job_factory_;
+ scoped_ptr<net::URLRequest> request_;
+};
+
+TEST_F(StreamURLRequestJobTest, TestGetSimpleDataRequest) {
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), NULL, kStreamURL));
+
+ scoped_refptr<net::StringIOBuffer> buffer(
+ new net::StringIOBuffer(kTestData1));
+
+ stream->AddData(buffer, buffer->size());
+ stream->Finalize();
+
+ TestSuccessRequest(kStreamURL, kTestData1);
+}
+
+TEST_F(StreamURLRequestJobTest, TestGetLargeStreamRequest) {
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), NULL, kStreamURL));
+
+ std::string large_data;
+ large_data.reserve(kBufferSize * 5);
+ for (int i = 0; i < kBufferSize * 5; ++i)
+ large_data.append(1, static_cast<char>(i % 256));
+
+ scoped_refptr<net::StringIOBuffer> buffer(
+ new net::StringIOBuffer(large_data));
+
+ stream->AddData(buffer, buffer->size());
+ stream->Finalize();
+ TestSuccessRequest(kStreamURL, large_data);
+}
+
+TEST_F(StreamURLRequestJobTest, TestGetNonExistentStreamRequest) {
+ net::TestDelegate delegate;
+ request_.reset(url_request_context_.CreateRequest(kStreamURL, &delegate));
+ request_->set_method("GET");
+ request_->Start();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify response.
+ EXPECT_FALSE(request_->status().is_success());
+}
+
+TEST_F(StreamURLRequestJobTest, TestRangeDataRequest) {
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), NULL, kStreamURL));
+
+ scoped_refptr<net::StringIOBuffer> buffer(
+ new net::StringIOBuffer(kTestData2));
+
+ stream->AddData(buffer, buffer->size());
+ stream->Finalize();
+
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=0-3");
+ TestRequest("GET", kStreamURL, extra_headers,
+ 200, std::string(kTestData2, 4));
+}
+
+TEST_F(StreamURLRequestJobTest, TestInvalidRangeDataRequest) {
+ scoped_refptr<Stream> stream(
+ new Stream(registry_.get(), NULL, kStreamURL));
+
+ scoped_refptr<net::StringIOBuffer> buffer(
+ new net::StringIOBuffer(kTestData2));
+
+ stream->AddData(buffer, buffer->size());
+ stream->Finalize();
+
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=1-3");
+ TestRequest("GET", kStreamURL, extra_headers, 405, std::string());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/streams/stream_write_observer.h b/chromium/content/browser/streams/stream_write_observer.h
new file mode 100644
index 00000000000..deab7ad009e
--- /dev/null
+++ b/chromium/content/browser/streams/stream_write_observer.h
@@ -0,0 +1,27 @@
+// Copyright (c) 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 CONTENT_BROWSER_STREAMS_STREAM_WRITE_OBSERVER_H_
+#define CONTENT_BROWSER_STREAMS_STREAM_WRITE_OBSERVER_H_
+
+namespace content {
+
+class Stream;
+
+class StreamWriteObserver {
+ public:
+ // Sent when space becomes available in the stream, and the source should
+ // resume writing.
+ virtual void OnSpaceAvailable(Stream* stream) = 0;
+
+ // Sent when the stream is closed, and the writer should stop sending data.
+ virtual void OnClose(Stream* stream) = 0;
+
+ protected:
+ virtual ~StreamWriteObserver() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_STREAMS_STREAM_WRITE_OBSERVER_H_
diff --git a/chromium/content/browser/system_message_window_win.cc b/chromium/content/browser/system_message_window_win.cc
new file mode 100644
index 00000000000..08f44cb6a6d
--- /dev/null
+++ b/chromium/content/browser/system_message_window_win.cc
@@ -0,0 +1,162 @@
+// 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 "content/browser/system_message_window_win.h"
+
+#include <dbt.h>
+
+#include "base/logging.h"
+#include "base/system_monitor/system_monitor.h"
+#include "base/win/wrapped_window_proc.h"
+#include "media/audio/win/core_audio_util_win.h"
+
+namespace content {
+
+namespace {
+const wchar_t kWindowClassName[] = L"Chrome_SystemMessageWindow";
+
+// A static map from a device category guid to base::SystemMonitor::DeviceType.
+struct {
+ const GUID device_category;
+ const base::SystemMonitor::DeviceType device_type;
+} const kDeviceCategoryMap[] = {
+ { KSCATEGORY_AUDIO, base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE },
+ { KSCATEGORY_VIDEO, base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE },
+};
+} // namespace
+
+// Manages the device notification handles for SystemMessageWindowWin.
+class SystemMessageWindowWin::DeviceNotifications {
+ public:
+ explicit DeviceNotifications(HWND hwnd) : notifications_() {
+ Register(hwnd);
+ }
+
+ ~DeviceNotifications() {
+ Unregister();
+ }
+
+ void Register(HWND hwnd) {
+ // Request to receive device notifications. All applications receive basic
+ // notifications via WM_DEVICECHANGE but in order to receive detailed device
+ // arrival and removal messages, we need to register.
+ DEV_BROADCAST_DEVICEINTERFACE filter = {0};
+ filter.dbcc_size = sizeof(filter);
+ filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ bool core_audio_support = media::CoreAudioUtil::IsSupported();
+ for (int i = 0; i < arraysize(kDeviceCategoryMap); ++i) {
+ // If CoreAudio is supported, AudioDeviceListenerWin will
+ // take care of monitoring audio devices.
+ if (core_audio_support &&
+ KSCATEGORY_AUDIO == kDeviceCategoryMap[i].device_category) {
+ continue;
+ }
+
+ filter.dbcc_classguid = kDeviceCategoryMap[i].device_category;
+ DCHECK_EQ(notifications_[i], static_cast<HDEVNOTIFY>(NULL));
+ notifications_[i] = RegisterDeviceNotification(
+ hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE);
+ DPLOG_IF(ERROR, !notifications_[i])
+ << "RegisterDeviceNotification failed";
+ }
+ }
+
+ void Unregister() {
+ for (int i = 0; i < arraysize(notifications_); ++i) {
+ if (notifications_[i]) {
+ UnregisterDeviceNotification(notifications_[i]);
+ notifications_[i] = NULL;
+ }
+ }
+ }
+
+ private:
+ HDEVNOTIFY notifications_[arraysize(kDeviceCategoryMap)];
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceNotifications);
+};
+
+
+SystemMessageWindowWin::SystemMessageWindowWin() {
+ WNDCLASSEX window_class;
+ base::win::InitializeWindowClass(
+ kWindowClassName,
+ &base::win::WrappedWindowProc<SystemMessageWindowWin::WndProcThunk>,
+ 0, 0, 0, NULL, NULL, NULL, NULL, NULL,
+ &window_class);
+ instance_ = window_class.hInstance;
+ ATOM clazz = RegisterClassEx(&window_class);
+ DCHECK(clazz);
+
+ window_ = CreateWindow(kWindowClassName,
+ 0, 0, 0, 0, 0, 0, 0, 0, instance_, 0);
+ SetWindowLongPtr(window_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
+ device_notifications_.reset(new DeviceNotifications(window_));
+}
+
+SystemMessageWindowWin::~SystemMessageWindowWin() {
+ if (window_) {
+ DestroyWindow(window_);
+ UnregisterClass(kWindowClassName, instance_);
+ }
+}
+
+LRESULT SystemMessageWindowWin::OnDeviceChange(UINT event_type, LPARAM data) {
+ base::SystemMonitor* monitor = base::SystemMonitor::Get();
+ base::SystemMonitor::DeviceType device_type =
+ base::SystemMonitor::DEVTYPE_UNKNOWN;
+ switch (event_type) {
+ case DBT_DEVNODES_CHANGED:
+ // For this notification, we're happy with the default DEVTYPE_UNKNOWN.
+ break;
+
+ case DBT_DEVICEREMOVECOMPLETE:
+ case DBT_DEVICEARRIVAL: {
+ // This notification has more details about the specific device that
+ // was added or removed. See if this is a category we're interested
+ // in monitoring and if so report the specific device type. If we don't
+ // find the category in our map, ignore the notification and do not
+ // notify the system monitor.
+ DEV_BROADCAST_DEVICEINTERFACE* device_interface =
+ reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data);
+ if (device_interface->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
+ return TRUE;
+ for (int i = 0; i < arraysize(kDeviceCategoryMap); ++i) {
+ if (kDeviceCategoryMap[i].device_category ==
+ device_interface->dbcc_classguid) {
+ device_type = kDeviceCategoryMap[i].device_type;
+ break;
+ }
+ }
+
+ // Devices that we do not have a DEVTYPE_ for, get detected via
+ // DBT_DEVNODES_CHANGED, so we avoid sending additional notifications
+ // for those here.
+ if (device_type == base::SystemMonitor::DEVTYPE_UNKNOWN)
+ return TRUE;
+ break;
+ }
+
+ default:
+ return TRUE;
+ }
+
+ monitor->ProcessDevicesChanged(device_type);
+
+ return TRUE;
+}
+
+LRESULT CALLBACK SystemMessageWindowWin::WndProc(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ switch (message) {
+ case WM_DEVICECHANGE:
+ return OnDeviceChange(static_cast<UINT>(wparam), lparam);
+ default:
+ break;
+ }
+
+ return ::DefWindowProc(hwnd, message, wparam, lparam);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/system_message_window_win.h b/chromium/content/browser/system_message_window_win.h
new file mode 100644
index 00000000000..b29bad57c89
--- /dev/null
+++ b/chromium/content/browser/system_message_window_win.h
@@ -0,0 +1,51 @@
+// 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 CONTENT_BROWSER_SYSTEM_MESSAGE_WINDOW_WIN_H_
+#define CONTENT_BROWSER_SYSTEM_MESSAGE_WINDOW_WIN_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class CONTENT_EXPORT SystemMessageWindowWin {
+ public:
+ SystemMessageWindowWin();
+
+ virtual ~SystemMessageWindowWin();
+
+ virtual LRESULT OnDeviceChange(UINT event_type, LPARAM data);
+
+ private:
+ void Init();
+
+ LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam);
+
+ static LRESULT CALLBACK WndProcThunk(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ SystemMessageWindowWin* msg_wnd = reinterpret_cast<SystemMessageWindowWin*>(
+ GetWindowLongPtr(hwnd, GWLP_USERDATA));
+ if (msg_wnd)
+ return msg_wnd->WndProc(hwnd, message, wparam, lparam);
+ return ::DefWindowProc(hwnd, message, wparam, lparam);
+ }
+
+ HMODULE instance_;
+ HWND window_;
+ class DeviceNotifications;
+ scoped_ptr<DeviceNotifications> device_notifications_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemMessageWindowWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SYSTEM_MESSAGE_WINDOW_WIN_H_
diff --git a/chromium/content/browser/system_message_window_win_unittest.cc b/chromium/content/browser/system_message_window_win_unittest.cc
new file mode 100644
index 00000000000..25c77c9d141
--- /dev/null
+++ b/chromium/content/browser/system_message_window_win_unittest.cc
@@ -0,0 +1,45 @@
+// 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 "content/browser/system_message_window_win.h"
+
+#include <dbt.h>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/system_monitor/system_monitor.h"
+#include "base/test/mock_devices_changed_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class SystemMessageWindowWinTest : public testing::Test {
+ public:
+ virtual ~SystemMessageWindowWinTest() { }
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ system_monitor_.AddDevicesChangedObserver(&observer_);
+ }
+
+ base::MessageLoop message_loop_;
+ base::SystemMonitor system_monitor_;
+ base::MockDevicesChangedObserver observer_;
+ SystemMessageWindowWin window_;
+};
+
+TEST_F(SystemMessageWindowWinTest, DevicesChanged) {
+ EXPECT_CALL(observer_, OnDevicesChanged(testing::_)).Times(1);
+ window_.OnDeviceChange(DBT_DEVNODES_CHANGED, NULL);
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(SystemMessageWindowWinTest, RandomMessage) {
+ window_.OnDeviceChange(DBT_DEVICEQUERYREMOVE, NULL);
+ message_loop_.RunUntilIdle();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tcmalloc_internals_request_job.cc b/chromium/content/browser/tcmalloc_internals_request_job.cc
new file mode 100644
index 00000000000..58e78afeea2
--- /dev/null
+++ b/chromium/content/browser/tcmalloc_internals_request_job.cc
@@ -0,0 +1,121 @@
+// 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 "content/browser/tcmalloc_internals_request_job.h"
+
+#include "base/allocator/allocator_extension.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/process_type.h"
+#include "net/base/net_errors.h"
+
+namespace content {
+
+// static
+AboutTcmallocOutputs* AboutTcmallocOutputs::GetInstance() {
+ return Singleton<AboutTcmallocOutputs>::get();
+}
+
+AboutTcmallocOutputs::AboutTcmallocOutputs() {}
+
+AboutTcmallocOutputs::~AboutTcmallocOutputs() {}
+
+void AboutTcmallocOutputs::OnStatsForChildProcess(
+ base::ProcessId pid, int process_type,
+ const std::string& output) {
+ std::string header = GetProcessTypeNameInEnglish(process_type);
+ base::StringAppendF(&header, " PID %d", static_cast<int>(pid));
+ SetOutput(header, output);
+}
+
+void AboutTcmallocOutputs::SetOutput(const std::string& header,
+ const std::string& output) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ outputs_[header] = output;
+}
+
+void AboutTcmallocOutputs::DumpToHTMLTable(std::string* data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ data->append("<table width=\"100%\">\n");
+ for (AboutTcmallocOutputsType::const_iterator oit = outputs_.begin();
+ oit != outputs_.end();
+ oit++) {
+ data->append("<tr><td bgcolor=\"yellow\">");
+ data->append(oit->first);
+ data->append("</td></tr>\n");
+ data->append("<tr><td><pre>\n");
+ data->append(oit->second);
+ data->append("</pre></td></tr>\n");
+ }
+ data->append("</table>\n");
+ outputs_.clear();
+}
+
+TcmallocInternalsRequestJob::TcmallocInternalsRequestJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate)
+ : net::URLRequestSimpleJob(request, network_delegate) {
+}
+
+#if defined(USE_TCMALLOC)
+void RequestTcmallocStatsFromChildRenderProcesses() {
+ RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
+ while (!it.IsAtEnd()) {
+ it.GetCurrentValue()->Send(new ChildProcessMsg_GetTcmallocStats);
+ it.Advance();
+ }
+}
+
+void AboutTcmalloc(std::string* data) {
+ data->append("<!DOCTYPE html>\n<html>\n<head>\n");
+ data->append(
+ "<meta http-equiv=\"Content-Security-Policy\" "
+ "content=\"object-src 'none'; script-src 'none'\">");
+ data->append("<title>tcmalloc stats</title>");
+ data->append("</head><body>");
+
+ // Display any stats for which we sent off requests the last time.
+ data->append("<p>Stats as of last page load;");
+ data->append("reload to get stats as of this page load.</p>\n");
+ data->append("<table width=\"100%\">\n");
+
+ AboutTcmallocOutputs::GetInstance()->DumpToHTMLTable(data);
+
+ data->append("</body></html>\n");
+
+ // Populate the collector with stats from the local browser process
+ // and send off requests to all the renderer processes.
+ char buffer[1024 * 32];
+ base::allocator::GetStats(buffer, sizeof(buffer));
+ std::string browser("Browser");
+ AboutTcmallocOutputs::GetInstance()->SetOutput(browser, buffer);
+
+ for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
+ iter.Send(new ChildProcessMsg_GetTcmallocStats);
+ }
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
+ &RequestTcmallocStatsFromChildRenderProcesses));
+}
+#endif
+
+int TcmallocInternalsRequestJob::GetData(
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const {
+ mime_type->assign("text/html");
+ charset->assign("UTF8");
+
+ data->clear();
+#if defined(USE_TCMALLOC)
+ AboutTcmalloc(data);
+#endif
+ return net::OK;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tcmalloc_internals_request_job.h b/chromium/content/browser/tcmalloc_internals_request_job.h
new file mode 100644
index 00000000000..20b2d4fc5f7
--- /dev/null
+++ b/chromium/content/browser/tcmalloc_internals_request_job.h
@@ -0,0 +1,67 @@
+// 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 CONTENT_BROWSER_TCMALLOC_INTERNALS_REQUEST_JOB_H_
+#define CONTENT_BROWSER_TCMALLOC_INTERNALS_REQUEST_JOB_H_
+
+#include <map>
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/process/process.h"
+#include "build/build_config.h" // USE_TCMALLOC
+#include "net/url_request/url_request_simple_job.h"
+
+namespace content {
+
+class AboutTcmallocOutputs {
+ public:
+ // Returns the singleton instance.
+ static AboutTcmallocOutputs* GetInstance();
+
+ // Records the output for a specified header string.
+ void SetOutput(const std::string& header, const std::string& output);
+
+ void DumpToHTMLTable(std::string* data);
+
+ // Callback for output returned from a child process. Adds
+ // the output for a canonical process-specific header string that
+ // incorporates the pid.
+ void OnStatsForChildProcess(base::ProcessId pid,
+ int process_type,
+ const std::string& output);
+
+ private:
+ AboutTcmallocOutputs();
+ ~AboutTcmallocOutputs();
+
+ // A map of header strings (e.g. "Browser", "Renderer PID 123")
+ // to the tcmalloc output collected for each process.
+ typedef std::map<std::string, std::string> AboutTcmallocOutputsType;
+ AboutTcmallocOutputsType outputs_;
+
+ friend struct DefaultSingletonTraits<AboutTcmallocOutputs>;
+
+ DISALLOW_COPY_AND_ASSIGN(AboutTcmallocOutputs);
+};
+
+class TcmallocInternalsRequestJob : public net::URLRequestSimpleJob {
+ public:
+ TcmallocInternalsRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate);
+
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE;
+
+ protected:
+ virtual ~TcmallocInternalsRequestJob() {}
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(TcmallocInternalsRequestJob);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_TCMALLOC_INTERNALS_REQUEST_JOB_H_
diff --git a/chromium/content/browser/tracing/DEPS b/chromium/content/browser/tracing/DEPS
new file mode 100644
index 00000000000..b4d8715021b
--- /dev/null
+++ b/chromium/content/browser/tracing/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ # Generated by the local tracing_resources.gyp:tracing_resources
+ "+grit/tracing_resources.h",
+]
diff --git a/chromium/content/browser/tracing/OWNERS b/chromium/content/browser/tracing/OWNERS
new file mode 100644
index 00000000000..93d1471e1ef
--- /dev/null
+++ b/chromium/content/browser/tracing/OWNERS
@@ -0,0 +1 @@
+nduca@chromium.org
diff --git a/chromium/content/browser/tracing/generate_trace_viewer_grd.py b/chromium/content/browser/tracing/generate_trace_viewer_grd.py
new file mode 100755
index 00000000000..767f5c59746
--- /dev/null
+++ b/chromium/content/browser/tracing/generate_trace_viewer_grd.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# 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.
+
+"""Creates a grd file for packaging the trace-viewer files.
+
+ This file is modified from the devtools generate_devtools_grd.py file.
+"""
+
+import errno
+import os
+import shutil
+import sys
+from xml.dom import minidom
+
+kTracingResourcePrefix = 'IDR_TRACING_'
+kGrdTemplate = '''<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/tracing_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="tracing_resources.pak" type="data_package" />
+ <output filename="tracing_resources.rc" type="rc_all" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <if expr="not is_android"></if>
+ </includes>
+ </release>
+</grit>
+
+'''
+
+
+class ParsedArgs:
+ def __init__(self, source_files, output_filename):
+ self.source_files = source_files
+ self.output_filename = output_filename
+
+
+def parse_args(argv):
+ output_position = argv.index('--output')
+ source_files = argv[:output_position]
+ return ParsedArgs(source_files, argv[output_position + 1])
+
+
+def make_name_from_filename(filename):
+ return kTracingResourcePrefix + (os.path.splitext(filename)[1][1:]).upper()
+
+
+def add_file_to_grd(grd_doc, filename):
+ includes_node = grd_doc.getElementsByTagName('if')[0]
+ includes_node.appendChild(grd_doc.createTextNode('\n '))
+
+ new_include_node = grd_doc.createElement('include')
+ new_include_node.setAttribute('name', make_name_from_filename(filename))
+ new_include_node.setAttribute('file', filename)
+ new_include_node.setAttribute('type', 'BINDATA')
+ new_include_node.setAttribute('flattenhtml', 'true')
+ if filename.endswith('.html'):
+ new_include_node.setAttribute('allowexternalscript', 'true')
+ includes_node.appendChild(new_include_node)
+
+
+def main(argv):
+ parsed_args = parse_args(argv[1:])
+
+ output_directory = os.path.dirname(parsed_args.output_filename)
+
+ doc = minidom.parseString(kGrdTemplate)
+ for filename in parsed_args.source_files:
+ add_file_to_grd(doc, os.path.basename(filename))
+
+ with open(parsed_args.output_filename, 'w') as output_file:
+ output_file.write(doc.toxml(encoding='UTF-8'))
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/chromium/content/browser/tracing/trace_controller_impl.cc b/chromium/content/browser/tracing/trace_controller_impl.cc
new file mode 100644
index 00000000000..54e0c86cb42
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_controller_impl.cc
@@ -0,0 +1,407 @@
+// 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 "content/browser/tracing/trace_controller_impl.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/tracing/trace_message_filter.h"
+#include "content/browser/tracing/trace_subscriber_stdio.h"
+#include "content/common/child_process_messages.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/common/content_switches.h"
+
+using base::debug::TraceLog;
+
+namespace content {
+
+namespace {
+
+base::LazyInstance<TraceControllerImpl>::Leaky g_controller =
+ LAZY_INSTANCE_INITIALIZER;
+
+class AutoStopTraceSubscriberStdio : public TraceSubscriberStdio {
+ public:
+ AutoStopTraceSubscriberStdio(const base::FilePath& file_path)
+ : TraceSubscriberStdio(file_path) {}
+
+ static void EndStartupTrace(TraceSubscriberStdio* subscriber) {
+ if (!TraceControllerImpl::GetInstance()->EndTracingAsync(subscriber))
+ delete subscriber;
+ // else, the tracing will end asynchronously in OnEndTracingComplete().
+ }
+
+ virtual void OnEndTracingComplete() OVERRIDE {
+ TraceSubscriberStdio::OnEndTracingComplete();
+ delete this;
+ // TODO(joth): this would be the time to automatically open up
+ // chrome://tracing/ and load up the trace data collected.
+ }
+};
+
+} // namespace
+
+TraceController* TraceController::GetInstance() {
+ return TraceControllerImpl::GetInstance();
+}
+
+TraceControllerImpl::TraceControllerImpl() :
+ subscriber_(NULL),
+ pending_end_ack_count_(0),
+ pending_bpf_ack_count_(0),
+ maximum_bpf_(0.0f),
+ is_tracing_(false),
+ is_get_category_groups_(false),
+ category_filter_(
+ base::debug::CategoryFilter::kDefaultCategoryFilterString) {
+ TraceLog::GetInstance()->SetNotificationCallback(
+ base::Bind(&TraceControllerImpl::OnTraceNotification,
+ base::Unretained(this)));
+}
+
+TraceControllerImpl::~TraceControllerImpl() {
+ // No need to SetNotificationCallback(nil) on the TraceLog since this is a
+ // Leaky instance.
+ NOTREACHED();
+}
+
+TraceControllerImpl* TraceControllerImpl::GetInstance() {
+ return g_controller.Pointer();
+}
+
+void TraceControllerImpl::InitStartupTracing(const CommandLine& command_line) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ base::FilePath trace_file = command_line.GetSwitchValuePath(
+ switches::kTraceStartupFile);
+ // trace_file = "none" means that startup events will show up for the next
+ // begin/end tracing (via about:tracing or AutomationProxy::BeginTracing/
+ // EndTracing, for example).
+ if (trace_file == base::FilePath().AppendASCII("none"))
+ return;
+
+ if (trace_file.empty()) {
+ // Default to saving the startup trace into the current dir.
+ trace_file = base::FilePath().AppendASCII("chrometrace.log");
+ }
+ scoped_ptr<AutoStopTraceSubscriberStdio> subscriber(
+ new AutoStopTraceSubscriberStdio(trace_file));
+ DCHECK(can_begin_tracing(subscriber.get()));
+
+ std::string delay_str = command_line.GetSwitchValueASCII(
+ switches::kTraceStartupDuration);
+ int delay_secs = 5;
+ if (!delay_str.empty() && !base::StringToInt(delay_str, &delay_secs)) {
+ DLOG(WARNING) << "Could not parse --" << switches::kTraceStartupDuration
+ << "=" << delay_str << " defaulting to 5 (secs)";
+ delay_secs = 5;
+ }
+
+ OnTracingBegan(subscriber.get());
+ BrowserThread::PostDelayedTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&AutoStopTraceSubscriberStdio::EndStartupTrace,
+ base::Unretained(subscriber.release())),
+ base::TimeDelta::FromSeconds(delay_secs));
+}
+
+bool TraceControllerImpl::GetKnownCategoryGroupsAsync(
+ TraceSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Known categories come back from child processes with the EndTracingAck
+ // message. So to get known categories, just begin and end tracing immediately
+ // afterwards. This will ping all the child processes for categories.
+ is_get_category_groups_ = true;
+ bool success = BeginTracing(subscriber, "*",
+ TraceLog::GetInstance()->trace_options()) &&
+ EndTracingAsync(subscriber);
+ is_get_category_groups_ = success;
+ return success;
+}
+
+bool TraceControllerImpl::BeginTracing(TraceSubscriber* subscriber,
+ const std::string& category_patterns,
+ base::debug::TraceLog::Options options) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!can_begin_tracing(subscriber))
+ return false;
+
+ // Enable tracing
+ TraceLog::GetInstance()->SetEnabled(
+ base::debug::CategoryFilter(category_patterns), options);
+
+ OnTracingBegan(subscriber);
+
+ return true;
+}
+
+bool TraceControllerImpl::EndTracingAsync(TraceSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!can_end_tracing() || subscriber != subscriber_)
+ return false;
+
+ // There could be a case where there are no child processes and filters_
+ // is empty. In that case we can immediately tell the subscriber that tracing
+ // has ended. To avoid recursive calls back to the subscriber, we will just
+ // use the existing asynchronous OnEndTracingAck code.
+ // Count myself (local trace) in pending_end_ack_count_, acked below.
+ pending_end_ack_count_ = filters_.size() + 1;
+
+ // Handle special case of zero child processes.
+ if (pending_end_ack_count_ == 1) {
+ // Ack asynchronously now, because we don't have any children to wait for.
+ std::vector<std::string> category_groups;
+ TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups);
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnEndTracingAck,
+ base::Unretained(this), category_groups));
+ }
+
+ // Notify all child processes.
+ for (FilterMap::iterator it = filters_.begin(); it != filters_.end(); ++it) {
+ it->get()->SendEndTracing();
+ }
+
+ return true;
+}
+
+bool TraceControllerImpl::GetTraceBufferPercentFullAsync(
+ TraceSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!can_get_buffer_percent_full() || subscriber != subscriber_)
+ return false;
+
+ maximum_bpf_ = 0.0f;
+ pending_bpf_ack_count_ = filters_.size() + 1;
+
+ // Handle special case of zero child processes.
+ if (pending_bpf_ack_count_ == 1) {
+ // Ack asynchronously now, because we don't have any children to wait for.
+ float bpf = TraceLog::GetInstance()->GetBufferPercentFull();
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnTraceBufferPercentFullReply,
+ base::Unretained(this), bpf));
+ }
+
+ // Message all child processes.
+ for (FilterMap::iterator it = filters_.begin(); it != filters_.end(); ++it) {
+ it->get()->SendGetTraceBufferPercentFull();
+ }
+
+ return true;
+}
+
+bool TraceControllerImpl::SetWatchEvent(TraceSubscriber* subscriber,
+ const std::string& category_name,
+ const std::string& event_name) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber != subscriber_)
+ return false;
+
+ watch_category_ = category_name;
+ watch_name_ = event_name;
+
+ TraceLog::GetInstance()->SetWatchEvent(category_name, event_name);
+ for (FilterMap::iterator it = filters_.begin(); it != filters_.end(); ++it)
+ it->get()->SendSetWatchEvent(category_name, event_name);
+
+ return true;
+}
+
+bool TraceControllerImpl::CancelWatchEvent(TraceSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (subscriber != subscriber_)
+ return false;
+
+ watch_category_.clear();
+ watch_name_.clear();
+
+ TraceLog::GetInstance()->CancelWatchEvent();
+ for (FilterMap::iterator it = filters_.begin(); it != filters_.end(); ++it)
+ it->get()->SendCancelWatchEvent();
+
+ return true;
+}
+
+void TraceControllerImpl::CancelSubscriber(TraceSubscriber* subscriber) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (subscriber == subscriber_) {
+ subscriber_ = NULL;
+ // End tracing if necessary.
+ if (is_tracing_ && pending_end_ack_count_ == 0)
+ EndTracingAsync(NULL);
+ }
+}
+
+void TraceControllerImpl::AddFilter(TraceMessageFilter* filter) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::AddFilter, base::Unretained(this),
+ make_scoped_refptr(filter)));
+ return;
+ }
+
+ filters_.insert(filter);
+ if (is_tracing_enabled()) {
+ std::string cf_str = category_filter_.ToString();
+ filter->SendBeginTracing(cf_str, trace_options_);
+ if (!watch_category_.empty())
+ filter->SendSetWatchEvent(watch_category_, watch_name_);
+ }
+}
+
+void TraceControllerImpl::RemoveFilter(TraceMessageFilter* filter) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::RemoveFilter, base::Unretained(this),
+ make_scoped_refptr(filter)));
+ return;
+ }
+
+ filters_.erase(filter);
+}
+
+void TraceControllerImpl::OnTracingBegan(TraceSubscriber* subscriber) {
+ is_tracing_ = true;
+
+ subscriber_ = subscriber;
+
+ category_filter_ = TraceLog::GetInstance()->GetCurrentCategoryFilter();
+ trace_options_ = TraceLog::GetInstance()->trace_options();
+
+ // Notify all child processes.
+ for (FilterMap::iterator it = filters_.begin(); it != filters_.end(); ++it) {
+ it->get()->SendBeginTracing(category_filter_.ToString(), trace_options_);
+ }
+}
+
+void TraceControllerImpl::OnEndTracingAck(
+ const std::vector<std::string>& known_category_groups) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnEndTracingAck,
+ base::Unretained(this), known_category_groups));
+ return;
+ }
+
+ // Merge known_category_groups with known_category_groups_
+ known_category_groups_.insert(known_category_groups.begin(),
+ known_category_groups.end());
+
+ if (pending_end_ack_count_ == 0)
+ return;
+
+ if (--pending_end_ack_count_ == 0) {
+ // All acks have been received.
+ is_tracing_ = false;
+
+ // Disable local trace.
+ TraceLog::GetInstance()->SetDisabled();
+
+ // During this call, our OnTraceDataCollected will be
+ // called with the last of the local trace data. Since we are on the UI
+ // thread, the call to OnTraceDataCollected will be synchronous, so we can
+ // immediately call OnEndTracingComplete below.
+ TraceLog::GetInstance()->Flush(
+ base::Bind(&TraceControllerImpl::OnTraceDataCollected,
+ base::Unretained(this)));
+
+ // Trigger callback if one is set.
+ if (subscriber_) {
+ if (is_get_category_groups_)
+ subscriber_->OnKnownCategoriesCollected(known_category_groups_);
+ else
+ subscriber_->OnEndTracingComplete();
+ // Clear subscriber so that others can use TraceController.
+ subscriber_ = NULL;
+ }
+
+ is_get_category_groups_ = false;
+ }
+
+ if (pending_end_ack_count_ == 1) {
+ // The last ack represents local trace, so we need to ack it now. Note that
+ // this code only executes if there were child processes.
+ std::vector<std::string> category_groups;
+ TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups);
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnEndTracingAck,
+ base::Unretained(this), category_groups));
+ }
+}
+
+void TraceControllerImpl::OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& events_str_ptr) {
+ // OnTraceDataCollected may be called from any browser thread, either by the
+ // local event trace system or from child processes via TraceMessageFilter.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnTraceDataCollected,
+ base::Unretained(this), events_str_ptr));
+ return;
+ }
+
+ // Drop trace events if we are just getting categories.
+ if (subscriber_ && !is_get_category_groups_)
+ subscriber_->OnTraceDataCollected(events_str_ptr);
+}
+
+void TraceControllerImpl::OnTraceNotification(int notification) {
+ // OnTraceNotification may be called from any browser thread, either by the
+ // local event trace system or from child processes via TraceMessageFilter.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnTraceNotification,
+ base::Unretained(this), notification));
+ return;
+ }
+
+ if (notification & base::debug::TraceLog::TRACE_BUFFER_FULL) {
+ // EndTracingAsync may return false if tracing is already in the process
+ // of being ended. That is ok.
+ EndTracingAsync(subscriber_);
+ }
+ if (notification & base::debug::TraceLog::EVENT_WATCH_NOTIFICATION) {
+ if (subscriber_)
+ subscriber_->OnEventWatchNotification();
+ }
+}
+
+void TraceControllerImpl::OnTraceBufferPercentFullReply(float percent_full) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnTraceBufferPercentFullReply,
+ base::Unretained(this), percent_full));
+ return;
+ }
+
+ if (pending_bpf_ack_count_ == 0)
+ return;
+
+ maximum_bpf_ = (maximum_bpf_ > percent_full)? maximum_bpf_ : percent_full;
+
+ if (--pending_bpf_ack_count_ == 0) {
+ // Trigger callback if one is set.
+ if (subscriber_)
+ subscriber_->OnTraceBufferPercentFullReply(maximum_bpf_);
+ }
+
+ if (pending_bpf_ack_count_ == 1) {
+ // The last ack represents local trace, so we need to ack it now. Note that
+ // this code only executes if there were child processes.
+ float bpf = TraceLog::GetInstance()->GetBufferPercentFull();
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&TraceControllerImpl::OnTraceBufferPercentFullReply,
+ base::Unretained(this), bpf));
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tracing/trace_controller_impl.h b/chromium/content/browser/tracing/trace_controller_impl.h
new file mode 100644
index 00000000000..f24d283a125
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_controller_impl.h
@@ -0,0 +1,104 @@
+// 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 CONTENT_BROWSER_TRACING_TRACE_CONTROLLER_IMPL_H_
+#define CONTENT_BROWSER_TRACING_TRACE_CONTROLLER_IMPL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "content/public/browser/trace_controller.h"
+
+class CommandLine;
+
+namespace content {
+class TraceMessageFilter;
+
+class TraceControllerImpl : public TraceController {
+ public:
+ static TraceControllerImpl* GetInstance();
+
+ // Called on the main thread of the browser process to initialize
+ // startup tracing.
+ void InitStartupTracing(const CommandLine& command_line);
+
+ // TraceController implementation:
+ virtual bool BeginTracing(TraceSubscriber* subscriber,
+ const std::string& category_patterns,
+ base::debug::TraceLog::Options options) OVERRIDE;
+ virtual bool EndTracingAsync(TraceSubscriber* subscriber) OVERRIDE;
+ virtual bool GetTraceBufferPercentFullAsync(
+ TraceSubscriber* subscriber) OVERRIDE;
+ virtual bool SetWatchEvent(TraceSubscriber* subscriber,
+ const std::string& category_name,
+ const std::string& event_name) OVERRIDE;
+ virtual bool CancelWatchEvent(TraceSubscriber* subscriber) OVERRIDE;
+ virtual void CancelSubscriber(TraceSubscriber* subscriber) OVERRIDE;
+ virtual bool GetKnownCategoryGroupsAsync(TraceSubscriber* subscriber)
+ OVERRIDE;
+
+ private:
+ typedef std::set<scoped_refptr<TraceMessageFilter> > FilterMap;
+
+ friend struct base::DefaultLazyInstanceTraits<TraceControllerImpl>;
+ friend class TraceMessageFilter;
+
+ TraceControllerImpl();
+ virtual ~TraceControllerImpl();
+
+ bool is_tracing_enabled() const {
+ return can_end_tracing();
+ }
+
+ bool can_end_tracing() const {
+ return is_tracing_ && pending_end_ack_count_ == 0;
+ }
+
+ // Can get Buffer Percent Full
+ bool can_get_buffer_percent_full() const {
+ return is_tracing_ &&
+ pending_end_ack_count_ == 0 &&
+ pending_bpf_ack_count_ == 0;
+ }
+
+ bool can_begin_tracing(TraceSubscriber* subscriber) const {
+ return !is_tracing_ &&
+ (subscriber_ == NULL || subscriber == subscriber_);
+ }
+
+ // Methods for use by TraceMessageFilter.
+
+ void AddFilter(TraceMessageFilter* filter);
+ void RemoveFilter(TraceMessageFilter* filter);
+ void OnTracingBegan(TraceSubscriber* subscriber);
+ void OnEndTracingAck(const std::vector<std::string>& known_category_groups);
+ void OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& events_str_ptr);
+ void OnTraceNotification(int notification);
+ void OnTraceBufferPercentFullReply(float percent_full);
+
+ FilterMap filters_;
+ TraceSubscriber* subscriber_;
+ // Pending acks for EndTracingAsync:
+ int pending_end_ack_count_;
+ // Pending acks for GetTraceBufferPercentFullAsync:
+ int pending_bpf_ack_count_;
+ float maximum_bpf_;
+ bool is_tracing_;
+ bool is_get_category_groups_;
+ std::set<std::string> known_category_groups_;
+ std::string watch_category_;
+ std::string watch_name_;
+ base::debug::TraceLog::Options trace_options_;
+ base::debug::CategoryFilter category_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceControllerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_TRACING_TRACE_CONTROLLER_IMPL_H_
diff --git a/chromium/content/browser/tracing/trace_message_filter.cc b/chromium/content/browser/tracing/trace_message_filter.cc
new file mode 100644
index 00000000000..e75d786142d
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_message_filter.cc
@@ -0,0 +1,128 @@
+// 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 "content/browser/tracing/trace_message_filter.h"
+
+#include "components/tracing/tracing_messages.h"
+#include "content/browser/tracing/trace_controller_impl.h"
+
+namespace content {
+
+TraceMessageFilter::TraceMessageFilter() :
+ has_child_(false),
+ is_awaiting_end_ack_(false),
+ is_awaiting_buffer_percent_full_ack_(false) {
+}
+
+void TraceMessageFilter::OnFilterAdded(IPC::Channel* channel) {
+ // Always on IO thread (BrowserMessageFilter guarantee).
+ BrowserMessageFilter::OnFilterAdded(channel);
+}
+
+void TraceMessageFilter::OnChannelClosing() {
+ // Always on IO thread (BrowserMessageFilter guarantee).
+ BrowserMessageFilter::OnChannelClosing();
+
+ if (has_child_) {
+ if (is_awaiting_end_ack_)
+ OnEndTracingAck(std::vector<std::string>());
+
+ if (is_awaiting_buffer_percent_full_ack_)
+ OnTraceBufferPercentFullReply(0.0f);
+
+ TraceControllerImpl::GetInstance()->RemoveFilter(this);
+ }
+}
+
+bool TraceMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ // Always on IO thread (BrowserMessageFilter guarantee).
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(TraceMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(TracingHostMsg_ChildSupportsTracing,
+ OnChildSupportsTracing)
+ IPC_MESSAGE_HANDLER(TracingHostMsg_EndTracingAck, OnEndTracingAck)
+ IPC_MESSAGE_HANDLER(TracingHostMsg_TraceDataCollected,
+ OnTraceDataCollected)
+ IPC_MESSAGE_HANDLER(TracingHostMsg_TraceNotification,
+ OnTraceNotification)
+ IPC_MESSAGE_HANDLER(TracingHostMsg_TraceBufferPercentFullReply,
+ OnTraceBufferPercentFullReply)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ return handled;
+}
+
+void TraceMessageFilter::SendBeginTracing(
+ const std::string& category_filter_str,
+ base::debug::TraceLog::Options options) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ Send(new TracingMsg_BeginTracing(category_filter_str,
+ base::TimeTicks::NowFromSystemTraceTime(),
+ options));
+}
+
+void TraceMessageFilter::SendEndTracing() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!is_awaiting_end_ack_);
+ is_awaiting_end_ack_ = true;
+ Send(new TracingMsg_EndTracing);
+}
+
+void TraceMessageFilter::SendGetTraceBufferPercentFull() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!is_awaiting_buffer_percent_full_ack_);
+ is_awaiting_buffer_percent_full_ack_ = true;
+ Send(new TracingMsg_GetTraceBufferPercentFull);
+}
+
+void TraceMessageFilter::SendSetWatchEvent(const std::string& category_name,
+ const std::string& event_name) {
+ Send(new TracingMsg_SetWatchEvent(category_name, event_name));
+}
+
+void TraceMessageFilter::SendCancelWatchEvent() {
+ Send(new TracingMsg_CancelWatchEvent);
+}
+
+TraceMessageFilter::~TraceMessageFilter() {}
+
+void TraceMessageFilter::OnChildSupportsTracing() {
+ has_child_ = true;
+ TraceControllerImpl::GetInstance()->AddFilter(this);
+}
+
+void TraceMessageFilter::OnEndTracingAck(
+ const std::vector<std::string>& known_categories) {
+ // is_awaiting_end_ack_ should always be true here, but check in case the
+ // child process is compromised.
+ if (is_awaiting_end_ack_) {
+ is_awaiting_end_ack_ = false;
+ TraceControllerImpl::GetInstance()->OnEndTracingAck(known_categories);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void TraceMessageFilter::OnTraceDataCollected(const std::string& data) {
+ scoped_refptr<base::RefCountedString> data_ptr(new base::RefCountedString());
+ data_ptr->data() = data;
+ TraceControllerImpl::GetInstance()->OnTraceDataCollected(data_ptr);
+}
+
+void TraceMessageFilter::OnTraceNotification(int notification) {
+ TraceControllerImpl::GetInstance()->OnTraceNotification(notification);
+}
+
+void TraceMessageFilter::OnTraceBufferPercentFullReply(float percent_full) {
+ if (is_awaiting_buffer_percent_full_ack_) {
+ is_awaiting_buffer_percent_full_ack_ = false;
+ TraceControllerImpl::GetInstance()->OnTraceBufferPercentFullReply(
+ percent_full);
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tracing/trace_message_filter.h b/chromium/content/browser/tracing/trace_message_filter.h
new file mode 100644
index 00000000000..8da34534604
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_message_filter.h
@@ -0,0 +1,63 @@
+// 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 CONTENT_BROWSER_TRACING_TRACE_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_TRACING_TRACE_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/debug/trace_event.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+
+// This class sends and receives trace messages on the browser process.
+// See also: trace_controller.h
+// See also: child_trace_message_filter.h
+class TraceMessageFilter : public BrowserMessageFilter {
+ public:
+ TraceMessageFilter();
+
+ // BrowserMessageFilter override.
+ virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE;
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ void SendBeginTracing(const std::string& category_filter_str,
+ base::debug::TraceLog::Options options);
+ void SendEndTracing();
+ void SendGetTraceBufferPercentFull();
+ void SendSetWatchEvent(const std::string& category_name,
+ const std::string& event_name);
+ void SendCancelWatchEvent();
+
+ protected:
+ virtual ~TraceMessageFilter();
+
+ private:
+ // Message handlers.
+ void OnChildSupportsTracing();
+ void OnEndTracingAck(const std::vector<std::string>& known_categories);
+ void OnTraceNotification(int notification);
+ void OnTraceBufferPercentFullReply(float percent_full);
+ void OnTraceDataCollected(const std::string& data);
+
+ // ChildTraceMessageFilter exists:
+ bool has_child_;
+
+ // Awaiting ack for previously sent SendEndTracing
+ bool is_awaiting_end_ack_;
+ // Awaiting ack for previously sent SendGetTraceBufferPercentFull
+ bool is_awaiting_buffer_percent_full_ack_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_TRACING_TRACE_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/tracing/trace_subscriber_stdio.cc b/chromium/content/browser/tracing/trace_subscriber_stdio.cc
new file mode 100644
index 00000000000..b5f6e828c60
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_subscriber_stdio.cc
@@ -0,0 +1,105 @@
+// 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 "content/browser/tracing/trace_subscriber_stdio.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+// All method calls on this class are done on a SequencedWorkerPool thread.
+class TraceSubscriberStdioImpl
+ : public base::RefCountedThreadSafe<TraceSubscriberStdioImpl> {
+ public:
+ explicit TraceSubscriberStdioImpl(const base::FilePath& path)
+ : path_(path),
+ file_(0) {}
+
+ void OnStart() {
+ DCHECK(!file_);
+ trace_buffer_.SetOutputCallback(
+ base::Bind(&TraceSubscriberStdioImpl::Write, this));
+ file_ = file_util::OpenFile(path_, "w+");
+ if (IsValid()) {
+ LOG(INFO) << "Logging performance trace to file: " << path_.value();
+ trace_buffer_.Start();
+ } else {
+ LOG(ERROR) << "Failed to open performance trace file: " << path_.value();
+ }
+ }
+
+ void OnData(const scoped_refptr<base::RefCountedString>& data_ptr) {
+ trace_buffer_.AddFragment(data_ptr->data());
+ }
+
+ void OnEnd() {
+ trace_buffer_.Finish();
+ CloseFile();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<TraceSubscriberStdioImpl>;
+
+ ~TraceSubscriberStdioImpl() {
+ CloseFile();
+ }
+
+ bool IsValid() const {
+ return file_ && (0 == ferror(file_));
+ }
+
+ void CloseFile() {
+ if (file_) {
+ fclose(file_);
+ file_ = 0;
+ }
+ // This is important, as it breaks a reference cycle.
+ trace_buffer_.SetOutputCallback(
+ base::debug::TraceResultBuffer::OutputCallback());
+ }
+
+ void Write(const std::string& output_str) {
+ if (IsValid()) {
+ size_t written = fwrite(output_str.data(), 1, output_str.size(), file_);
+ if (written != output_str.size()) {
+ LOG(ERROR) << "Error " << ferror(file_) << " in fwrite() to trace file";
+ CloseFile();
+ }
+ }
+ }
+
+ base::FilePath path_;
+ FILE* file_;
+ base::debug::TraceResultBuffer trace_buffer_;
+};
+
+TraceSubscriberStdio::TraceSubscriberStdio(const base::FilePath& path)
+ : impl_(new TraceSubscriberStdioImpl(path)) {
+ BrowserThread::PostBlockingPoolSequencedTask(
+ __FILE__, FROM_HERE,
+ base::Bind(&TraceSubscriberStdioImpl::OnStart, impl_));
+}
+
+TraceSubscriberStdio::~TraceSubscriberStdio() {
+}
+
+void TraceSubscriberStdio::OnEndTracingComplete() {
+ BrowserThread::PostBlockingPoolSequencedTask(
+ __FILE__, FROM_HERE,
+ base::Bind(&TraceSubscriberStdioImpl::OnEnd, impl_));
+}
+
+void TraceSubscriberStdio::OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& data_ptr) {
+ BrowserThread::PostBlockingPoolSequencedTask(
+ __FILE__, FROM_HERE,
+ base::Bind(&TraceSubscriberStdioImpl::OnData, impl_, data_ptr));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tracing/trace_subscriber_stdio.h b/chromium/content/browser/tracing/trace_subscriber_stdio.h
new file mode 100644
index 00000000000..06a70db8c88
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_subscriber_stdio.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_BROWSER_TRACING_TRACE_SUBSCRIBER_STDIO_H_
+#define CONTENT_BROWSER_TRACING_TRACE_SUBSCRIBER_STDIO_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/trace_subscriber.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+
+class TraceSubscriberStdioImpl;
+
+// Stdio implementation of TraceSubscriber. Use this to write traces to a file.
+class CONTENT_EXPORT TraceSubscriberStdio
+ : NON_EXPORTED_BASE(public TraceSubscriber) {
+ public:
+ // Creates or overwrites the specified file. Check IsValid() for success.
+ explicit TraceSubscriberStdio(const base::FilePath& path);
+ virtual ~TraceSubscriberStdio();
+
+ // Implementation of TraceSubscriber
+ virtual void OnEndTracingComplete() OVERRIDE;
+ virtual void OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& data_ptr) OVERRIDE;
+
+ private:
+ scoped_refptr<TraceSubscriberStdioImpl> impl_;
+ DISALLOW_COPY_AND_ASSIGN(TraceSubscriberStdio);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_TRACING_TRACE_SUBSCRIBER_STDIO_H_
diff --git a/chromium/content/browser/tracing/trace_subscriber_stdio_unittest.cc b/chromium/content/browser/tracing/trace_subscriber_stdio_unittest.cc
new file mode 100644
index 00000000000..0b0e7c2ee80
--- /dev/null
+++ b/chromium/content/browser/tracing/trace_subscriber_stdio_unittest.cc
@@ -0,0 +1,40 @@
+// 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 "content/browser/tracing/trace_subscriber_stdio.h"
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class TraceSubscriberStdioTest : public ::testing::Test {};
+
+TEST_F(TraceSubscriberStdioTest, CanWriteDataToFile) {
+ base::ScopedTempDir trace_dir;
+ ASSERT_TRUE(trace_dir.CreateUniqueTempDir());
+ base::FilePath trace_file(trace_dir.path().AppendASCII("trace.txt"));
+ {
+ TraceSubscriberStdio subscriber(trace_file);
+
+ std::string foo("foo");
+ subscriber.OnTraceDataCollected(
+ make_scoped_refptr(base::RefCountedString::TakeString(&foo)));
+
+ std::string bar("bar");
+ subscriber.OnTraceDataCollected(
+ make_scoped_refptr(base::RefCountedString::TakeString(&bar)));
+
+ subscriber.OnEndTracingComplete();
+ }
+ BrowserThread::GetBlockingPool()->FlushForTesting();
+ std::string result;
+ EXPECT_TRUE(file_util::ReadFileToString(trace_file, &result));
+ EXPECT_EQ("[foo,bar]", result);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tracing/tracing_resources.gyp b/chromium/content/browser/tracing/tracing_resources.gyp
new file mode 100644
index 00000000000..f6854b00d35
--- /dev/null
+++ b/chromium/content/browser/tracing/tracing_resources.gyp
@@ -0,0 +1,81 @@
+# 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.
+
+{
+ 'variables': {
+ 'trace_viewer_src_dir': '../../../third_party/trace-viewer',
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing',
+ },
+ 'targets': [
+ {
+ 'target_name': 'generate_tracing_grd',
+ 'type': 'none',
+ 'dependencies': [
+ '<(trace_viewer_src_dir)/trace_viewer.gyp:generate_about_tracing',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'generate_tracing_grd',
+ 'script_name': 'generate_trace_viewer_grd.py',
+ 'input_pages': [
+ '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing/about_tracing.html',
+ '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing/about_tracing.js'
+ ],
+ 'inputs': [
+ '<@(_script_name)',
+ '<@(_input_pages)'
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing/tracing_resources.grd'
+ ],
+ 'action': [
+ 'python', '<@(_script_name)', '<@(_input_pages)', '--output', '<@(_outputs)'
+ ]
+ }
+ ]
+ },
+
+ {
+ 'target_name': 'tracing_resources',
+ 'type': 'none',
+ 'dependencies': [
+ '<(trace_viewer_src_dir)/trace_viewer.gyp:generate_about_tracing',
+ 'generate_tracing_grd',
+ ],
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing',
+ },
+ 'actions': [
+ {
+ 'action_name': 'tracing_resources',
+ # This can't use build/grit_action.gypi because the grd file
+ # is generated at build time, so the trick of using grit_info to get
+ # the real inputs/outputs at GYP time isn't possible.
+ 'variables': {
+ 'grit_cmd': ['python', '../../../tools/grit/grit.py'],
+ 'grit_grd_file': '<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing/tracing_resources.grd',
+ },
+ 'inputs': [
+ '<(grit_grd_file)',
+ '<!@pymod_do_main(grit_info --inputs)',
+ ],
+ 'outputs': [
+ '<(grit_out_dir)/grit/tracing_resources.h',
+ '<(grit_out_dir)/tracing_resources.pak',
+ '<(grit_out_dir)/tracing_resources.rc',
+ ],
+ 'action': ['<@(grit_cmd)',
+ '-i', '<(grit_grd_file)', 'build',
+ '-f', 'GRIT_DIR/../gritsettings/resource_ids',
+ '-o', '<(grit_out_dir)',
+ '-D', 'SHARED_INTERMEDIATE_DIR=<(SHARED_INTERMEDIATE_DIR)',
+ '<@(grit_defines)' ],
+ 'message': 'Generating resources from <(grit_grd_file)',
+ 'msvs_cygwin_shell': 1,
+ }
+ ],
+ 'includes': [ '../../../build/grit_target.gypi' ]
+ }
+ ]
+}
diff --git a/chromium/content/browser/tracing/tracing_ui.cc b/chromium/content/browser/tracing/tracing_ui.cc
new file mode 100644
index 00000000000..aab652d5bf1
--- /dev/null
+++ b/chromium/content/browser/tracing/tracing_ui.cc
@@ -0,0 +1,561 @@
+// 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 "content/browser/tracing/tracing_ui.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/file_util.h"
+#include "base/json/string_escape.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/trace_controller.h"
+#include "content/public/browser/trace_subscriber.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/browser/web_ui_message_handler.h"
+#include "content/public/common/url_constants.h"
+#include "grit/tracing_resources.h"
+#include "ipc/ipc_channel.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/debug_daemon_client.h"
+#endif
+
+namespace content {
+namespace {
+
+WebUIDataSource* CreateTracingHTMLSource() {
+ WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost);
+
+ source->SetJsonPath("strings.js");
+ source->SetDefaultResource(IDR_TRACING_HTML);
+ source->AddResourcePath("tracing.js", IDR_TRACING_JS);
+ return source;
+}
+
+// This class receives javascript messages from the renderer.
+// Note that the WebUI infrastructure runs on the UI thread, therefore all of
+// this class's methods are expected to run on the UI thread.
+class TracingMessageHandler
+ : public WebUIMessageHandler,
+ public ui::SelectFileDialog::Listener,
+ public base::SupportsWeakPtr<TracingMessageHandler>,
+ public TraceSubscriber {
+ public:
+ TracingMessageHandler();
+ virtual ~TracingMessageHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ // SelectFileDialog::Listener implementation
+ virtual void FileSelected(const base::FilePath& path,
+ int index,
+ void* params) OVERRIDE;
+ virtual void FileSelectionCanceled(void* params) OVERRIDE;
+
+ // TraceSubscriber implementation.
+ virtual void OnEndTracingComplete() OVERRIDE;
+ virtual void OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& trace_fragment) OVERRIDE;
+ virtual void OnTraceBufferPercentFullReply(float percent_full) OVERRIDE;
+ virtual void OnKnownCategoriesCollected(
+ const std::set<std::string>& known_categories) OVERRIDE;
+
+ // Messages.
+ void OnTracingControllerInitialized(const base::ListValue* list);
+ void OnBeginTracing(const base::ListValue* list);
+ void OnEndTracingAsync(const base::ListValue* list);
+ void OnBeginRequestBufferPercentFull(const base::ListValue* list);
+ void OnLoadTraceFile(const base::ListValue* list);
+ void OnSaveTraceFile(const base::ListValue* list);
+ void OnGetKnownCategories(const base::ListValue* list);
+
+ // Callbacks.
+ void LoadTraceFileComplete(string16* file_contents,
+ const base::FilePath &path);
+ void SaveTraceFileComplete();
+
+ private:
+ // The file dialog to select a file for loading or saving traces.
+ scoped_refptr<ui::SelectFileDialog> select_trace_file_dialog_;
+
+ // The type of the file dialog as the same one is used for loading or saving
+ // traces.
+ ui::SelectFileDialog::Type select_trace_file_dialog_type_;
+
+ // The trace data that is to be written to the file on saving.
+ scoped_ptr<std::string> trace_data_to_save_;
+
+ // True while tracing is active.
+ bool trace_enabled_;
+
+ // True while system tracing is active.
+ bool system_trace_in_progress_;
+
+ void OnEndSystemTracingAck(
+ const scoped_refptr<base::RefCountedString>& events_str_ptr);
+
+ DISALLOW_COPY_AND_ASSIGN(TracingMessageHandler);
+};
+
+// A proxy passed to the Read and Write tasks used when loading or saving trace
+// data.
+class TaskProxy : public base::RefCountedThreadSafe<TaskProxy> {
+ public:
+ explicit TaskProxy(const base::WeakPtr<TracingMessageHandler>& handler)
+ : handler_(handler) {}
+ void LoadTraceFileCompleteProxy(string16* file_contents,
+ const base::FilePath& path) {
+ if (handler_.get())
+ handler_->LoadTraceFileComplete(file_contents, path);
+ delete file_contents;
+ }
+
+ void SaveTraceFileCompleteProxy() {
+ if (handler_.get())
+ handler_->SaveTraceFileComplete();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<TaskProxy>;
+ ~TaskProxy() {}
+
+ // The message handler to call callbacks on.
+ base::WeakPtr<TracingMessageHandler> handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskProxy);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// TracingMessageHandler
+//
+////////////////////////////////////////////////////////////////////////////////
+
+TracingMessageHandler::TracingMessageHandler()
+ : select_trace_file_dialog_type_(ui::SelectFileDialog::SELECT_NONE),
+ trace_enabled_(false),
+ system_trace_in_progress_(false) {
+}
+
+TracingMessageHandler::~TracingMessageHandler() {
+ if (select_trace_file_dialog_.get())
+ select_trace_file_dialog_->ListenerDestroyed();
+
+ // If we are the current subscriber, this will result in ending tracing.
+ TraceController::GetInstance()->CancelSubscriber(this);
+
+ // Shutdown any system tracing too.
+ if (system_trace_in_progress_) {
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
+ RequestStopSystemTracing(
+ chromeos::DebugDaemonClient::EmptyStopSystemTracingCallback());
+#endif
+ }
+}
+
+void TracingMessageHandler::RegisterMessages() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ web_ui()->RegisterMessageCallback("tracingControllerInitialized",
+ base::Bind(&TracingMessageHandler::OnTracingControllerInitialized,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("beginTracing",
+ base::Bind(&TracingMessageHandler::OnBeginTracing,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("endTracingAsync",
+ base::Bind(&TracingMessageHandler::OnEndTracingAsync,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("beginRequestBufferPercentFull",
+ base::Bind(&TracingMessageHandler::OnBeginRequestBufferPercentFull,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("loadTraceFile",
+ base::Bind(&TracingMessageHandler::OnLoadTraceFile,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("saveTraceFile",
+ base::Bind(&TracingMessageHandler::OnSaveTraceFile,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("getKnownCategories",
+ base::Bind(&TracingMessageHandler::OnGetKnownCategories,
+ base::Unretained(this)));
+}
+
+void TracingMessageHandler::OnTracingControllerInitialized(
+ const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Send the client info to the tracingController
+ {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetString("version", GetContentClient()->GetProduct());
+
+ dict->SetString("command_line",
+ CommandLine::ForCurrentProcess()->GetCommandLineString());
+
+ web_ui()->CallJavascriptFunction("tracingController.onClientInfoUpdate",
+ *dict);
+ }
+}
+
+void TracingMessageHandler::OnBeginRequestBufferPercentFull(
+ const base::ListValue* list) {
+ TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this);
+}
+
+// A callback used for asynchronously reading a file to a string. Calls the
+// TaskProxy callback when reading is complete.
+void ReadTraceFileCallback(TaskProxy* proxy, const base::FilePath& path) {
+ std::string file_contents;
+ if (!file_util::ReadFileToString(path, &file_contents))
+ return;
+
+ // We need to escape the file contents, because it will go into a javascript
+ // quoted string in TracingMessageHandler::LoadTraceFileComplete. We need to
+ // escape control characters (to have well-formed javascript statements), as
+ // well as \ and ' (the only special characters in a ''-quoted string).
+ // Do the escaping on this thread, it may take a little while for big files
+ // and we don't want to block the UI during that time. Also do the UTF-16
+ // conversion here.
+ // Note: we're using UTF-16 because we'll need to cut the string into slices
+ // to give to Javascript, and it's easier to cut than UTF-8 (since JS strings
+ // are arrays of 16-bit values, UCS-2 really, whereas we can't cut inside of a
+ // multibyte UTF-8 codepoint).
+ size_t size = file_contents.size();
+ std::string escaped_contents;
+ escaped_contents.reserve(size);
+ for (size_t i = 0; i < size; ++i) {
+ char c = file_contents[i];
+ if (c < ' ') {
+ escaped_contents += base::StringPrintf("\\u%04x", c);
+ continue;
+ }
+ if (c == '\\' || c == '\'')
+ escaped_contents.push_back('\\');
+ escaped_contents.push_back(c);
+ }
+ file_contents.clear();
+
+ scoped_ptr<string16> contents16(new string16);
+ UTF8ToUTF16(escaped_contents).swap(*contents16);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&TaskProxy::LoadTraceFileCompleteProxy, proxy,
+ contents16.release(),
+ path));
+}
+
+// A callback used for asynchronously writing a file from a string. Calls the
+// TaskProxy callback when writing is complete.
+void WriteTraceFileCallback(TaskProxy* proxy,
+ const base::FilePath& path,
+ std::string* contents) {
+ if (!file_util::WriteFile(path, contents->c_str(), contents->size()))
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&TaskProxy::SaveTraceFileCompleteProxy, proxy));
+}
+
+void TracingMessageHandler::FileSelected(
+ const base::FilePath& path, int index, void* params) {
+ if (select_trace_file_dialog_type_ ==
+ ui::SelectFileDialog::SELECT_OPEN_FILE) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ReadTraceFileCallback,
+ make_scoped_refptr(new TaskProxy(AsWeakPtr())), path));
+ } else {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&WriteTraceFileCallback,
+ make_scoped_refptr(new TaskProxy(AsWeakPtr())), path,
+ trace_data_to_save_.release()));
+ }
+
+ select_trace_file_dialog_ = NULL;
+}
+
+void TracingMessageHandler::FileSelectionCanceled(void* params) {
+ select_trace_file_dialog_ = NULL;
+ if (select_trace_file_dialog_type_ ==
+ ui::SelectFileDialog::SELECT_OPEN_FILE) {
+ web_ui()->CallJavascriptFunction(
+ "tracingController.onLoadTraceFileCanceled");
+ } else {
+ web_ui()->CallJavascriptFunction(
+ "tracingController.onSaveTraceFileCanceled");
+ }
+}
+
+void TracingMessageHandler::OnLoadTraceFile(const base::ListValue* list) {
+ // Only allow a single dialog at a time.
+ if (select_trace_file_dialog_.get())
+ return;
+ select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
+ select_trace_file_dialog_ = ui::SelectFileDialog::Create(
+ this,
+ GetContentClient()->browser()->CreateSelectFilePolicy(
+ web_ui()->GetWebContents()));
+ select_trace_file_dialog_->SelectFile(
+ ui::SelectFileDialog::SELECT_OPEN_FILE,
+ string16(),
+ base::FilePath(),
+ NULL,
+ 0,
+ base::FilePath::StringType(),
+ web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
+ NULL);
+}
+
+void TracingMessageHandler::LoadTraceFileComplete(string16* contents,
+ const base::FilePath& path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // We need to pass contents to tracingController.onLoadTraceFileComplete, but
+ // that may be arbitrarily big, and IPCs messages are limited in size. So we
+ // need to cut it into pieces and rebuild the string in Javascript.
+ // |contents| has already been escaped in ReadTraceFileCallback.
+ // IPC::Channel::kMaximumMessageSize is in bytes, and we need to account for
+ // overhead.
+ const size_t kMaxSize = IPC::Channel::kMaximumMessageSize / 2 - 128;
+ string16 first_prefix = UTF8ToUTF16("window.traceData = '");
+ string16 prefix = UTF8ToUTF16("window.traceData += '");
+ string16 suffix = UTF8ToUTF16("';");
+
+ RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost();
+ for (size_t i = 0; i < contents->size(); i += kMaxSize) {
+ string16 javascript = i == 0 ? first_prefix : prefix;
+ javascript += contents->substr(i, kMaxSize) + suffix;
+ rvh->ExecuteJavascriptInWebFrame(string16(), javascript);
+ }
+
+ // The CallJavascriptFunction is not used because we need to pass
+ // the first param |window.traceData| through as an un-quoted string.
+ rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
+ "tracingController.onLoadTraceFileComplete(window.traceData," +
+ base::GetDoubleQuotedJson(path.value()) + ");" +
+ "delete window.traceData;"));
+}
+
+void TracingMessageHandler::OnSaveTraceFile(const base::ListValue* list) {
+ // Only allow a single dialog at a time.
+ if (select_trace_file_dialog_.get())
+ return;
+
+ DCHECK(list->GetSize() == 1);
+
+ std::string* trace_data = new std::string();
+ bool ok = list->GetString(0, trace_data);
+ DCHECK(ok);
+ trace_data_to_save_.reset(trace_data);
+
+ select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
+ select_trace_file_dialog_ = ui::SelectFileDialog::Create(
+ this,
+ GetContentClient()->browser()->CreateSelectFilePolicy(
+ web_ui()->GetWebContents()));
+ select_trace_file_dialog_->SelectFile(
+ ui::SelectFileDialog::SELECT_SAVEAS_FILE,
+ string16(),
+ base::FilePath(),
+ NULL,
+ 0,
+ base::FilePath::StringType(),
+ web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
+ NULL);
+}
+
+void TracingMessageHandler::SaveTraceFileComplete() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ web_ui()->CallJavascriptFunction("tracingController.onSaveTraceFileComplete");
+}
+
+void TracingMessageHandler::OnBeginTracing(const base::ListValue* args) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_GE(args->GetSize(), (size_t) 2);
+ DCHECK_LE(args->GetSize(), (size_t) 3);
+
+ bool system_tracing_requested = false;
+ bool ok = args->GetBoolean(0, &system_tracing_requested);
+ DCHECK(ok);
+
+ std::string chrome_categories;
+ ok = args->GetString(1, &chrome_categories);
+ DCHECK(ok);
+
+ base::debug::TraceLog::Options options =
+ base::debug::TraceLog::RECORD_UNTIL_FULL;
+ if (args->GetSize() >= 3) {
+ std::string options_;
+ ok = args->GetString(2, &options_);
+ DCHECK(ok);
+ options = base::debug::TraceLog::TraceOptionsFromString(options_);
+ }
+
+ trace_enabled_ = true;
+ // TODO(jbates) This may fail, but that's OK for current use cases.
+ // Ex: Multiple about:gpu traces can not trace simultaneously.
+ // TODO(nduca) send feedback to javascript about whether or not BeginTracing
+ // was successful.
+ TraceController::GetInstance()->BeginTracing(this, chrome_categories,
+ options);
+
+ if (system_tracing_requested) {
+#if defined(OS_CHROMEOS)
+ DCHECK(!system_trace_in_progress_);
+ chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
+ StartSystemTracing();
+ // TODO(sleffler) async, could wait for completion
+ system_trace_in_progress_ = true;
+#endif
+ }
+}
+
+void TracingMessageHandler::OnEndTracingAsync(const base::ListValue* list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // This is really us beginning to end tracing, rather than tracing being truly
+ // over. When this function yields, we expect to get some number of
+ // OnTraceDataCollected callbacks, which will append data to window.traceData.
+ // To set up for this, set window.traceData to the empty string.
+ web_ui()->GetWebContents()->GetRenderViewHost()->
+ ExecuteJavascriptInWebFrame(string16(),
+ UTF8ToUTF16("window.traceData = '';"));
+
+ // TODO(nduca): fix javascript code to make sure trace_enabled_ is always true
+ // here. triggered a false condition by just clicking stop
+ // trace a few times when it was going slow, and maybe switching
+ // between tabs.
+ if (trace_enabled_ &&
+ !TraceController::GetInstance()->EndTracingAsync(this)) {
+ // Set to false now, since it turns out we never were the trace subscriber.
+ OnEndTracingComplete();
+ }
+}
+
+void TracingMessageHandler::OnEndTracingComplete() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ trace_enabled_ = false;
+ if (system_trace_in_progress_) {
+ // Disable system tracing now that the local trace has shutdown.
+ // This must be done last because we potentially need to push event
+ // records into the system event log for synchronizing system event
+ // timestamps with chrome event timestamps--and since the system event
+ // log is a ring-buffer (on linux) adding them at the end is the only
+ // way we're confident we'll have them in the final result.
+ system_trace_in_progress_ = false;
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
+ RequestStopSystemTracing(
+ base::Bind(&TracingMessageHandler::OnEndSystemTracingAck,
+ base::Unretained(this)));
+ return;
+#endif
+ }
+
+ RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost();
+ rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
+ "tracingController.onEndTracingComplete(window.traceData);"
+ "delete window.traceData;"));
+}
+
+void TracingMessageHandler::OnEndSystemTracingAck(
+ const scoped_refptr<base::RefCountedString>& events_str_ptr) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ web_ui()->CallJavascriptFunction(
+ "tracingController.onSystemTraceDataCollected",
+ *scoped_ptr<base::Value>(new base::StringValue(events_str_ptr->data())));
+ DCHECK(!system_trace_in_progress_);
+
+ OnEndTracingComplete();
+}
+
+void TracingMessageHandler::OnTraceDataCollected(
+ const scoped_refptr<base::RefCountedString>& trace_fragment) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ std::string javascript;
+ javascript.reserve(trace_fragment->size() * 2);
+ javascript.append("window.traceData += \"");
+ base::JsonDoubleQuote(trace_fragment->data(), false, &javascript);
+
+ // Intentionally append a , to the traceData. This technically causes all
+ // traceData that we pass back to JS to end with a comma, but that is actually
+ // something the JS side strips away anyway
+ javascript.append(",\";");
+
+ web_ui()->GetWebContents()->GetRenderViewHost()->
+ ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(javascript));
+}
+
+void TracingMessageHandler::OnTraceBufferPercentFullReply(float percent_full) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ web_ui()->CallJavascriptFunction(
+ "tracingController.onRequestBufferPercentFullComplete",
+ *scoped_ptr<base::Value>(new base::FundamentalValue(percent_full)));
+}
+
+void TracingMessageHandler::OnGetKnownCategories(const base::ListValue* list) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!TraceController::GetInstance()->GetKnownCategoryGroupsAsync(this)) {
+ std::set<std::string> ret;
+ OnKnownCategoriesCollected(ret);
+ }
+}
+
+void TracingMessageHandler::OnKnownCategoriesCollected(
+ const std::set<std::string>& known_categories) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ scoped_ptr<base::ListValue> categories(new base::ListValue());
+ for (std::set<std::string>::const_iterator iter = known_categories.begin();
+ iter != known_categories.end();
+ ++iter) {
+ categories->AppendString(*iter);
+ }
+
+ web_ui()->CallJavascriptFunction(
+ "tracingController.onKnownCategoriesCollected", *categories);
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// TracingUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) {
+ web_ui->AddMessageHandler(new TracingMessageHandler());
+
+ // Set up the chrome://tracing/ source.
+ BrowserContext* browser_context =
+ web_ui->GetWebContents()->GetBrowserContext();
+ WebUIDataSource::Add(browser_context, CreateTracingHTMLSource());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/tracing/tracing_ui.h b/chromium/content/browser/tracing/tracing_ui.h
new file mode 100644
index 00000000000..85bbce15c2c
--- /dev/null
+++ b/chromium/content/browser/tracing/tracing_ui.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_TRACING_UI_H_
+#define CONTENT_BROWSER_TRACING_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace content {
+
+// The C++ back-end for the chrome://tracing webui page.
+class TracingUI : public WebUIController {
+ public:
+ explicit TracingUI(WebUI* web_ui);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TracingUI);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_TRACING_UI_H_
diff --git a/chromium/content/browser/udev_linux.cc b/chromium/content/browser/udev_linux.cc
new file mode 100644
index 00000000000..f6f9443ab3f
--- /dev/null
+++ b/chromium/content/browser/udev_linux.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/udev_linux.h"
+
+#include <libudev.h>
+
+#include "base/message_loop/message_loop.h"
+
+namespace content {
+
+UdevLinux::UdevLinux(const std::vector<UdevMonitorFilter>& filters,
+ const UdevNotificationCallback& callback)
+ : udev_(udev_new()),
+ monitor_(NULL),
+ monitor_fd_(-1),
+ callback_(callback) {
+ CHECK(udev_);
+
+ monitor_ = udev_monitor_new_from_netlink(udev_, "udev");
+ CHECK(monitor_);
+
+ for (size_t i = 0; i < filters.size(); ++i) {
+ int ret = udev_monitor_filter_add_match_subsystem_devtype(
+ monitor_, filters[i].subsystem, filters[i].devtype);
+ CHECK_EQ(0, ret);
+ }
+
+ int ret = udev_monitor_enable_receiving(monitor_);
+ CHECK_EQ(0, ret);
+ monitor_fd_ = udev_monitor_get_fd(monitor_);
+ CHECK_GE(monitor_fd_, 0);
+
+ bool success = base::MessageLoopForIO::current()->WatchFileDescriptor(
+ monitor_fd_,
+ true,
+ base::MessageLoopForIO::WATCH_READ,
+ &monitor_watcher_,
+ this);
+ CHECK(success);
+}
+
+UdevLinux::~UdevLinux() {
+ monitor_watcher_.StopWatchingFileDescriptor();
+ udev_monitor_unref(monitor_);
+ udev_unref(udev_);
+}
+
+udev* UdevLinux::udev_handle() {
+ return udev_;
+}
+
+void UdevLinux::OnFileCanReadWithoutBlocking(int fd) {
+ // Events occur when devices attached to the system are added, removed, or
+ // change state. udev_monitor_receive_device() will return a device object
+ // representing the device which changed and what type of change occured.
+ DCHECK_EQ(monitor_fd_, fd);
+ udev_device* dev = udev_monitor_receive_device(monitor_);
+ if (!dev)
+ return;
+
+ callback_.Run(dev);
+ udev_device_unref(dev);
+}
+
+void UdevLinux::OnFileCanWriteWithoutBlocking(int fd) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/udev_linux.h b/chromium/content/browser/udev_linux.h
new file mode 100644
index 00000000000..2b89094c8b6
--- /dev/null
+++ b/chromium/content/browser/udev_linux.h
@@ -0,0 +1,97 @@
+// 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.
+
+// UdevLinux listens for device change notifications from udev and runs
+// callbacks when notifications occur.
+//
+// UdevLinux must be created on a MessageLoop of TYPE_IO.
+// UdevLinux is not thread-safe.
+//
+// Example usage:
+//
+// class UdevLinux;
+//
+// class Foo {
+// public:
+// Foo() {
+// std::vector<UdevLinux::UdevMonitorFilter> filters;
+// filters.push_back(content::UdevLinux::UdevMonitorFilter("block", NULL));
+// udev_.reset(new UdevLinux(filters,
+// base::Bind(&Foo::Notify, this)));
+// }
+//
+// // Called when a "block" device attaches/detaches.
+// // To hold on to |device|, call udev_device_ref(device).
+// void Notify(udev_device* device) {
+// // Do something with |device|.
+// }
+//
+// private:
+// scoped_ptr<UdevLinux> udev_;
+//
+// DISALLOW_COPY_AND_ASSIGN(Foo);
+// };
+
+#ifndef CONTENT_BROWSER_UDEV_LINUX_H_
+#define CONTENT_BROWSER_UDEV_LINUX_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_pump_libevent.h"
+
+extern "C" {
+struct udev;
+struct udev_device;
+struct udev_monitor;
+}
+
+namespace content {
+
+class UdevLinux : public base::MessagePumpLibevent::Watcher {
+ public:
+ typedef base::Callback<void(udev_device*)> UdevNotificationCallback;
+
+ // subsystem and devtype parameter for
+ // udev_monitor_filter_add_match_subsystem_devtype().
+ struct UdevMonitorFilter {
+ UdevMonitorFilter(const char* subsystem_in, const char* devtype_in)
+ : subsystem(subsystem_in),
+ devtype(devtype_in) {
+ }
+ const char* subsystem;
+ const char* devtype;
+ };
+
+ // Filter incoming devices based on |filters|.
+ // Calls |callback| upon device change events.
+ UdevLinux(const std::vector<UdevMonitorFilter>& filters,
+ const UdevNotificationCallback& callback);
+ virtual ~UdevLinux();
+
+
+ // Returns the udev handle to be passed into other udev_*() functions.
+ udev* udev_handle();
+
+ private:
+ // base::MessagePump:Libevent::Watcher implementation.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ // libudev-related items, the main context, and the monitoring context to be
+ // notified about changes to device states.
+ udev* udev_;
+ udev_monitor* monitor_;
+ int monitor_fd_;
+ base::MessagePumpLibevent::FileDescriptorWatcher monitor_watcher_;
+ UdevNotificationCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UdevLinux);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_UDEV_LINUX_H_
diff --git a/chromium/content/browser/user_metrics.cc b/chromium/content/browser/user_metrics.cc
new file mode 100644
index 00000000000..a1c7819a544
--- /dev/null
+++ b/chromium/content/browser/user_metrics.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 The Chromium 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 "content/public/browser/user_metrics.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+namespace {
+
+base::LazyInstance<std::vector<ActionCallback> > g_action_callbacks =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Forward declare because of circular dependency.
+void CallRecordOnUI(const std::string& action);
+
+void Record(const char *action) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&CallRecordOnUI, action));
+ return;
+ }
+
+ for (size_t i = 0; i < g_action_callbacks.Get().size(); i++)
+ g_action_callbacks.Get()[i].Run(action);
+}
+
+void CallRecordOnUI(const std::string& action) {
+ Record(action.c_str());
+}
+
+} // namespace
+
+void RecordAction(const UserMetricsAction& action) {
+ Record(action.str_);
+}
+
+void RecordComputedAction(const std::string& action) {
+ Record(action.c_str());
+}
+
+void AddActionCallback(const ActionCallback& callback) {
+ g_action_callbacks.Get().push_back(callback);
+}
+
+void RemoveActionCallback(const ActionCallback& callback) {
+ for (size_t i = 0; i < g_action_callbacks.Get().size(); i++) {
+ if (g_action_callbacks.Get()[i].Equals(callback)) {
+ g_action_callbacks.Get().erase(g_action_callbacks.Get().begin() + i);
+ return;
+ }
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/utility_process_host_impl.cc b/chromium/content/browser/utility_process_host_impl.cc
new file mode 100644
index 00000000000..2e562188d24
--- /dev/null
+++ b/chromium/content/browser/utility_process_host_impl.cc
@@ -0,0 +1,297 @@
+// 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 "content/browser/utility_process_host_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/child/child_process.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/utility_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/utility_process_host_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "content/utility/utility_thread_impl.h"
+#include "ipc/ipc_switches.h"
+#include "ui/base/ui_base_switches.h"
+
+#if defined(OS_WIN)
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#endif
+
+namespace content {
+
+#if defined(OS_WIN)
+// NOTE: changes to this class need to be reviewed by the security team.
+class UtilitySandboxedProcessLauncherDelegate
+ : public SandboxedProcessLauncherDelegate {
+ public:
+ explicit UtilitySandboxedProcessLauncherDelegate(
+ const base::FilePath& exposed_dir) : exposed_dir_(exposed_dir) {}
+ virtual ~UtilitySandboxedProcessLauncherDelegate() {}
+
+ virtual void PreSandbox(bool* disable_default_policy,
+ base::FilePath* exposed_dir) OVERRIDE {
+ *exposed_dir = exposed_dir_;
+ }
+
+private:
+ base::FilePath exposed_dir_;
+};
+#endif
+
+// We want to ensure there's only one utility thread running at a time, as there
+// are many globals used in the utility process.
+static base::LazyInstance<base::Lock> g_one_utility_thread_lock;
+
+// Single process not supported in multiple dll mode currently.
+#if !defined(CHROME_MULTIPLE_DLL)
+class UtilityMainThread : public base::Thread {
+ public:
+ UtilityMainThread(const std::string& channel_id)
+ : Thread("Chrome_InProcUtilityThread"),
+ channel_id_(channel_id) {
+ }
+
+ virtual ~UtilityMainThread() {
+ Stop();
+ }
+
+ private:
+ // base::Thread implementation:
+ virtual void Init() OVERRIDE {
+ // We need to return right away or else the main thread that started us will
+ // hang.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&UtilityMainThread::InitInternal, base::Unretained(this)));
+ }
+
+ virtual void CleanUp() OVERRIDE {
+ child_process_.reset();
+
+ // See comment in RendererMainThread.
+ SetThreadWasQuitProperly(true);
+ g_one_utility_thread_lock.Get().Release();
+ }
+
+ void InitInternal() {
+ g_one_utility_thread_lock.Get().Acquire();
+ child_process_.reset(new ChildProcess());
+ child_process_->set_main_thread(new UtilityThreadImpl(channel_id_));
+ }
+
+ std::string channel_id_;
+ scoped_ptr<ChildProcess> child_process_;
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityMainThread);
+};
+#endif // !CHROME_MULTIPLE_DLL
+
+UtilityProcessHost* UtilityProcessHost::Create(
+ UtilityProcessHostClient* client,
+ base::SequencedTaskRunner* client_task_runner) {
+ return new UtilityProcessHostImpl(client, client_task_runner);
+}
+
+UtilityProcessHostImpl::UtilityProcessHostImpl(
+ UtilityProcessHostClient* client,
+ base::SequencedTaskRunner* client_task_runner)
+ : client_(client),
+ client_task_runner_(client_task_runner),
+ is_batch_mode_(false),
+ is_mdns_enabled_(false),
+ no_sandbox_(false),
+#if defined(OS_LINUX)
+ child_flags_(ChildProcessHost::CHILD_ALLOW_SELF),
+#else
+ child_flags_(ChildProcessHost::CHILD_NORMAL),
+#endif
+ use_linux_zygote_(false),
+ started_(false) {
+}
+
+UtilityProcessHostImpl::~UtilityProcessHostImpl() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (is_batch_mode_)
+ EndBatchMode();
+}
+
+bool UtilityProcessHostImpl::Send(IPC::Message* message) {
+ if (!StartProcess())
+ return false;
+
+ return process_->Send(message);
+}
+
+bool UtilityProcessHostImpl::StartBatchMode() {
+ CHECK(!is_batch_mode_);
+ is_batch_mode_ = StartProcess();
+ Send(new UtilityMsg_BatchMode_Started());
+ return is_batch_mode_;
+}
+
+void UtilityProcessHostImpl::EndBatchMode() {
+ CHECK(is_batch_mode_);
+ is_batch_mode_ = false;
+ Send(new UtilityMsg_BatchMode_Finished());
+}
+
+void UtilityProcessHostImpl::SetExposedDir(const base::FilePath& dir) {
+ exposed_dir_ = dir;
+}
+
+void UtilityProcessHostImpl::EnableMDns() {
+ is_mdns_enabled_ = true;
+}
+
+void UtilityProcessHostImpl::DisableSandbox() {
+ no_sandbox_ = true;
+}
+
+void UtilityProcessHostImpl::EnableZygote() {
+ use_linux_zygote_ = true;
+}
+
+const ChildProcessData& UtilityProcessHostImpl::GetData() {
+ return process_->GetData();
+}
+
+#if defined(OS_POSIX)
+
+void UtilityProcessHostImpl::SetEnv(const base::EnvironmentVector& env) {
+ env_ = env;
+}
+
+#endif // OS_POSIX
+
+bool UtilityProcessHostImpl::StartProcess() {
+ if (started_)
+ return true;
+ started_ = true;
+
+ if (is_batch_mode_)
+ return true;
+
+ // Name must be set or metrics_service will crash in any test which
+ // launches a UtilityProcessHost.
+ process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_UTILITY, this));
+ process_->SetName(ASCIIToUTF16("utility process"));
+
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+ // Single process not supported in multiple dll mode currently.
+#if !defined(CHROME_MULTIPLE_DLL)
+ if (RenderProcessHost::run_renderer_in_process()) {
+ // See comment in RenderProcessHostImpl::Init() for the background on why we
+ // support single process mode this way.
+ in_process_thread_.reset(new UtilityMainThread(channel_id));
+ in_process_thread_->Start();
+ } else
+#endif // !CHROME_MULTIPLE_DLL
+ {
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ int child_flags = child_flags_;
+
+#if defined(OS_POSIX)
+ bool has_cmd_prefix = browser_command_line.HasSwitch(
+ switches::kUtilityCmdPrefix);
+
+ // When running under gdb, forking /proc/self/exe ends up forking the gdb
+ // executable instead of Chromium. It is almost safe to assume that no
+ // updates will happen while a developer is running with
+ // |switches::kUtilityCmdPrefix|. See ChildProcessHost::GetChildPath() for
+ // a similar case with Valgrind.
+ if (has_cmd_prefix)
+ child_flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags);
+ if (exe_path.empty()) {
+ NOTREACHED() << "Unable to get utility process binary name.";
+ return false;
+ }
+
+ CommandLine* cmd_line = new CommandLine(exe_path);
+ cmd_line->AppendSwitchASCII(switches::kProcessType,
+ switches::kUtilityProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+ std::string locale = GetContentClient()->browser()->GetApplicationLocale();
+ cmd_line->AppendSwitchASCII(switches::kLang, locale);
+
+ if (no_sandbox_ || browser_command_line.HasSwitch(switches::kNoSandbox))
+ cmd_line->AppendSwitch(switches::kNoSandbox);
+#if defined(OS_MACOSX)
+ if (browser_command_line.HasSwitch(switches::kEnableSandboxLogging))
+ cmd_line->AppendSwitch(switches::kEnableSandboxLogging);
+#endif
+ if (browser_command_line.HasSwitch(switches::kDebugPluginLoading))
+ cmd_line->AppendSwitch(switches::kDebugPluginLoading);
+
+#if defined(OS_POSIX)
+ // TODO(port): Sandbox this on Linux. Also, zygote this to work with
+ // Linux updating.
+ if (has_cmd_prefix) {
+ // launch the utility child process with some prefix (usually "xterm -e gdb
+ // --args").
+ cmd_line->PrependWrapper(browser_command_line.GetSwitchValueNative(
+ switches::kUtilityCmdPrefix));
+ }
+
+ cmd_line->AppendSwitchPath(switches::kUtilityProcessAllowedDir, exposed_dir_);
+#endif
+
+ if (is_mdns_enabled_)
+ cmd_line->AppendSwitch(switches::kUtilityProcessEnableMDns);
+
+ bool use_zygote = false;
+
+#if defined(OS_LINUX)
+ use_zygote = !no_sandbox_ && use_linux_zygote_;
+#endif
+
+ process_->Launch(
+#if defined(OS_WIN)
+ new UtilitySandboxedProcessLauncherDelegate(exposed_dir_),
+#elif defined(OS_POSIX)
+ use_zygote,
+ env_,
+#endif
+ cmd_line);
+ }
+
+ return true;
+}
+
+bool UtilityProcessHostImpl::OnMessageReceived(const IPC::Message& message) {
+ client_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(
+ &UtilityProcessHostClient::OnMessageReceived), client_.get(),
+ message));
+ return true;
+}
+
+void UtilityProcessHostImpl::OnProcessCrashed(int exit_code) {
+ client_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&UtilityProcessHostClient::OnProcessCrashed, client_.get(),
+ exit_code));
+}
+
+} // namespace content
diff --git a/chromium/content/browser/utility_process_host_impl.h b/chromium/content/browser/utility_process_host_impl.h
new file mode 100644
index 00000000000..6eecd3d8985
--- /dev/null
+++ b/chromium/content/browser/utility_process_host_impl.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_UTILITY_PROCESS_HOST_IMPL_H_
+#define CONTENT_BROWSER_UTILITY_PROCESS_HOST_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/utility_process_host.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+class BrowserChildProcessHostImpl;
+class UtilityMainThread;
+
+class CONTENT_EXPORT UtilityProcessHostImpl
+ : public NON_EXPORTED_BASE(UtilityProcessHost),
+ public BrowserChildProcessHostDelegate {
+ public:
+ UtilityProcessHostImpl(UtilityProcessHostClient* client,
+ base::SequencedTaskRunner* client_task_runner);
+ virtual ~UtilityProcessHostImpl();
+
+ // UtilityProcessHost implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+ virtual bool StartBatchMode() OVERRIDE;
+ virtual void EndBatchMode() OVERRIDE;
+ virtual void SetExposedDir(const base::FilePath& dir) OVERRIDE;
+ virtual void EnableMDns() OVERRIDE;
+ virtual void DisableSandbox() OVERRIDE;
+ virtual void EnableZygote() OVERRIDE;
+ virtual const ChildProcessData& GetData() OVERRIDE;
+#if defined(OS_POSIX)
+ virtual void SetEnv(const base::EnvironmentVector& env) OVERRIDE;
+#endif
+
+ void set_child_flags(int flags) { child_flags_ = flags; }
+
+ private:
+ // Starts a process if necessary. Returns true if it succeeded or a process
+ // has already been started via StartBatchMode().
+ bool StartProcess();
+
+ // BrowserChildProcessHost:
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+
+ // A pointer to our client interface, who will be informed of progress.
+ scoped_refptr<UtilityProcessHostClient> client_;
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner_;
+ // True when running in batch mode, i.e., StartBatchMode() has been called
+ // and the utility process will run until EndBatchMode().
+ bool is_batch_mode_;
+
+ base::FilePath exposed_dir_;
+
+ // Whether utility process needs perform presandbox initialization for MDns.
+ bool is_mdns_enabled_;
+
+ // Whether to pass switches::kNoSandbox to the child.
+ bool no_sandbox_;
+
+ // Flags defined in ChildProcessHost with which to start the process.
+ int child_flags_;
+
+ // Launch the utility process from the zygote. Defaults to false.
+ bool use_linux_zygote_;
+
+ base::EnvironmentVector env_;
+
+ bool started_;
+
+ scoped_ptr<BrowserChildProcessHostImpl> process_;
+
+#if !defined(CHROME_MULTIPLE_DLL)
+ // Used in single-process mode instead of process_.
+ scoped_ptr<UtilityMainThread> in_process_thread_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityProcessHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_UTILITY_PROCESS_HOST_IMPL_H_
diff --git a/chromium/content/browser/web_contents/OWNERS b/chromium/content/browser/web_contents/OWNERS
new file mode 100644
index 00000000000..58cb48d8766
--- /dev/null
+++ b/chromium/content/browser/web_contents/OWNERS
@@ -0,0 +1,4 @@
+jochen@chromium.org
+
+# for *aura*
+per-file *aura*=ben@chromium.org
diff --git a/chromium/content/browser/web_contents/aura/image_window_delegate.cc b/chromium/content/browser/web_contents/aura/image_window_delegate.cc
new file mode 100644
index 00000000000..1829f46ba44
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/image_window_delegate.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 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 "content/browser/web_contents/aura/image_window_delegate.h"
+
+#include "ui/base/hit_test.h"
+#include "ui/compositor/compositor.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+ImageWindowDelegate::ImageWindowDelegate()
+ : size_mismatch_(false) {
+}
+
+ImageWindowDelegate::~ImageWindowDelegate() {
+}
+
+void ImageWindowDelegate::SetImage(const gfx::Image& image) {
+ image_ = image;
+ if (!window_size_.IsEmpty() && !image_.IsEmpty())
+ size_mismatch_ = window_size_ != image_.AsImageSkia().size();
+}
+
+gfx::Size ImageWindowDelegate::GetMinimumSize() const {
+ return gfx::Size();
+}
+
+gfx::Size ImageWindowDelegate::GetMaximumSize() const {
+ return gfx::Size();
+}
+
+void ImageWindowDelegate::OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ window_size_ = new_bounds.size();
+ if (!image_.IsEmpty())
+ size_mismatch_ = window_size_ != image_.AsImageSkia().size();
+}
+
+gfx::NativeCursor ImageWindowDelegate::GetCursor(const gfx::Point& point) {
+ return gfx::kNullCursor;
+}
+
+int ImageWindowDelegate::GetNonClientComponent(const gfx::Point& point) const {
+ return HTNOWHERE;
+}
+
+bool ImageWindowDelegate::ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) {
+ return false;
+}
+
+bool ImageWindowDelegate::CanFocus() {
+ return false;
+}
+
+void ImageWindowDelegate::OnCaptureLost() {
+}
+
+void ImageWindowDelegate::OnPaint(gfx::Canvas* canvas) {
+ if (image_.IsEmpty()) {
+ canvas->DrawColor(SK_ColorGRAY);
+ } else {
+ if (size_mismatch_)
+ canvas->DrawColor(SK_ColorWHITE);
+ canvas->DrawImageInt(image_.AsImageSkia(), 0, 0);
+ }
+}
+
+void ImageWindowDelegate::OnDeviceScaleFactorChanged(float scale_factor) {
+}
+
+void ImageWindowDelegate::OnWindowDestroying() {
+}
+
+void ImageWindowDelegate::OnWindowDestroyed() {
+ delete this;
+}
+
+void ImageWindowDelegate::OnWindowTargetVisibilityChanged(bool visible) {
+}
+
+bool ImageWindowDelegate::HasHitTestMask() const {
+ return false;
+}
+
+void ImageWindowDelegate::GetHitTestMask(gfx::Path* mask) const {
+}
+
+scoped_refptr<ui::Texture> ImageWindowDelegate::CopyTexture() {
+ return scoped_refptr<ui::Texture>();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/aura/image_window_delegate.h b/chromium/content/browser/web_contents/aura/image_window_delegate.h
new file mode 100644
index 00000000000..ad92ce28f91
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/image_window_delegate.h
@@ -0,0 +1,61 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEB_CONTENTS_AURA_IMAGE_WINDOW_DELEGATE_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_AURA_IMAGE_WINDOW_DELEGATE_H_
+
+#include "ui/aura/window_delegate.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/size.h"
+
+namespace content {
+
+// An ImageWindowDelegate paints an image for a Window. The delegate destroys
+// itself when the Window is destroyed. The delegate does not consume any event.
+class ImageWindowDelegate : public aura::WindowDelegate {
+ public:
+ ImageWindowDelegate();
+
+ void SetImage(const gfx::Image& image);
+ bool has_image() const { return !image_.IsEmpty(); }
+
+ protected:
+ virtual ~ImageWindowDelegate();
+
+ // Overridden from aura::WindowDelegate:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE;
+ virtual gfx::Size GetMaximumSize() const OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE;
+ virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE;
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE;
+ virtual bool CanFocus() OVERRIDE;
+ virtual void OnCaptureLost() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE;
+ virtual void OnWindowDestroying() OVERRIDE;
+ virtual void OnWindowDestroyed() OVERRIDE;
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE;
+ virtual bool HasHitTestMask() const OVERRIDE;
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE;
+
+ protected:
+ gfx::Image image_;
+ gfx::Size window_size_;
+
+ // Keeps track of whether the window size matches the image size or not. If
+ // the image size is smaller than the window size, then the delegate paints a
+ // white background for the missing regions.
+ bool size_mismatch_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageWindowDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_AURA_IMAGE_WINDOW_DELEGATE_H_
diff --git a/chromium/content/browser/web_contents/aura/shadow_layer_delegate.cc b/chromium/content/browser/web_contents/aura/shadow_layer_delegate.cc
new file mode 100644
index 00000000000..c8819072b6a
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/shadow_layer_delegate.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 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 "content/browser/web_contents/aura/shadow_layer_delegate.h"
+
+#include "base/bind.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/skia_util.h"
+
+namespace {
+
+const SkColor kShadowLightColor = SkColorSetARGB(0x0, 0, 0, 0);
+const SkColor kShadowDarkColor = SkColorSetARGB(0x70, 0, 0, 0);
+const int kShadowThick = 7;
+
+} // namespace
+
+namespace content {
+
+ShadowLayerDelegate::ShadowLayerDelegate(ui::Layer* shadow_for)
+ : layer_(new ui::Layer(ui::LAYER_TEXTURED)) {
+ layer_->set_delegate(this);
+ layer_->SetBounds(gfx::Rect(-kShadowThick, 0, kShadowThick,
+ shadow_for->bounds().height()));
+ layer_->SetFillsBoundsOpaquely(false);
+ shadow_for->Add(layer_.get());
+}
+
+ShadowLayerDelegate::~ShadowLayerDelegate() {
+}
+
+void ShadowLayerDelegate::OnPaintLayer(gfx::Canvas* canvas) {
+ SkPoint points[2];
+ const SkColor kShadowColors[2] = { kShadowLightColor, kShadowDarkColor };
+
+ points[0].iset(0, 0);
+ points[1].iset(kShadowThick, 0);
+
+ skia::RefPtr<SkShader> shader = skia::AdoptRef(
+ SkGradientShader::CreateLinear(points, kShadowColors, NULL,
+ arraysize(points), SkShader::kRepeat_TileMode, NULL));
+
+ gfx::Rect paint_rect = gfx::Rect(0, 0, kShadowThick,
+ layer_->bounds().height());
+ SkPaint paint;
+ paint.setShader(shader.get());
+ canvas->sk_canvas()->drawRect(gfx::RectToSkRect(paint_rect), paint);
+}
+
+void ShadowLayerDelegate::OnDeviceScaleFactorChanged(float scale_factor) {
+}
+
+base::Closure ShadowLayerDelegate::PrepareForLayerBoundsChange() {
+ return base::Bind(&base::DoNothing);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/aura/shadow_layer_delegate.h b/chromium/content/browser/web_contents/aura/shadow_layer_delegate.h
new file mode 100644
index 00000000000..22659d91f11
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/shadow_layer_delegate.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEB_CONTENTS_AURA_SHADOW_LAYER_DELEGATE_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_AURA_SHADOW_LAYER_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/compositor/layer_delegate.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace content {
+
+// ShadowLayerDelegate takes care of drawing a shadow on the left edge of
+// another layer.
+class ShadowLayerDelegate : public ui::LayerDelegate {
+ public:
+ explicit ShadowLayerDelegate(ui::Layer* shadow_for);
+ virtual ~ShadowLayerDelegate();
+
+ // Returns the layer for the shadow. Note that the ShadowLayerDelegate owns
+ // the layer, and the layer is destroyed when the delegate is destroyed.
+ ui::Layer* layer() { return layer_.get(); }
+
+ private:
+ // Overridden from ui::LayerDelegate:
+ virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE;
+ virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE;
+
+ scoped_ptr<ui::Layer> layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShadowLayerDelegate);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_AURA_SHADOW_LAYER_DELEGATE_H_
diff --git a/chromium/content/browser/web_contents/aura/window_slider.cc b/chromium/content/browser/web_contents/aura/window_slider.cc
new file mode 100644
index 00000000000..7afd32dd3d7
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/window_slider.cc
@@ -0,0 +1,303 @@
+// Copyright (c) 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 "content/browser/web_contents/aura/window_slider.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "content/browser/web_contents/aura/shadow_layer_delegate.h"
+#include "content/public/browser/overscroll_configuration.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace content {
+
+namespace {
+
+void DeleteLayerAndShadow(ui::Layer* layer,
+ ShadowLayerDelegate* shadow) {
+ delete shadow;
+ delete layer;
+}
+
+// An animation observer that runs a callback at the end of the animation, and
+// destroys itself.
+class CallbackAnimationObserver : public ui::ImplicitAnimationObserver {
+ public:
+ CallbackAnimationObserver(const base::Closure& closure)
+ : closure_(closure) {
+ }
+
+ virtual ~CallbackAnimationObserver() {}
+
+ private:
+ // Overridden from ui::ImplicitAnimationObserver:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ if (!closure_.is_null())
+ closure_.Run();
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+ }
+
+ const base::Closure closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
+};
+
+} // namespace
+
+WindowSlider::WindowSlider(Delegate* delegate,
+ aura::Window* event_window,
+ aura::Window* owner)
+ : delegate_(delegate),
+ event_window_(event_window),
+ owner_(owner),
+ delta_x_(0.f),
+ weak_factory_(this),
+ horiz_start_threshold_(content::GetOverscrollConfig(
+ content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START)),
+ complete_threshold_(content::GetOverscrollConfig(
+ content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE)) {
+ event_window_->AddPreTargetHandler(this);
+
+ event_window_->AddObserver(this);
+ owner_->AddObserver(this);
+}
+
+WindowSlider::~WindowSlider() {
+ if (event_window_) {
+ event_window_->RemovePreTargetHandler(this);
+ event_window_->RemoveObserver(this);
+ }
+ if (owner_)
+ owner_->RemoveObserver(this);
+ delegate_->OnWindowSliderDestroyed();
+}
+
+void WindowSlider::ChangeOwner(aura::Window* new_owner) {
+ if (owner_)
+ owner_->RemoveObserver(this);
+ owner_ = new_owner;
+ if (owner_) {
+ owner_->AddObserver(this);
+ UpdateForScroll(0.f, 0.f);
+ }
+}
+
+bool WindowSlider::IsSlideInProgress() const {
+ return fabs(delta_x_) >= horiz_start_threshold_ || slider_.get() ||
+ weak_factory_.HasWeakPtrs();
+}
+
+void WindowSlider::SetupSliderLayer() {
+ ui::Layer* parent = owner_->layer()->parent();
+ parent->Add(slider_.get());
+ if (delta_x_ < 0)
+ parent->StackAbove(slider_.get(), owner_->layer());
+ else
+ parent->StackBelow(slider_.get(), owner_->layer());
+ slider_->SetBounds(owner_->layer()->bounds());
+ slider_->SetVisible(true);
+}
+
+void WindowSlider::UpdateForScroll(float x_offset, float y_offset) {
+ float old_delta = delta_x_;
+ delta_x_ += x_offset;
+ if (fabs(delta_x_) < horiz_start_threshold_ && !slider_.get())
+ return;
+
+ if ((old_delta < 0 && delta_x_ > 0) ||
+ (old_delta > 0 && delta_x_ < 0)) {
+ slider_.reset();
+ shadow_.reset();
+ }
+
+ float translate = 0.f;
+ ui::Layer* translate_layer = NULL;
+
+ if (!slider_.get()) {
+ slider_.reset(delta_x_ < 0 ? delegate_->CreateFrontLayer() :
+ delegate_->CreateBackLayer());
+ if (!slider_.get())
+ return;
+ SetupSliderLayer();
+ }
+
+ if (delta_x_ <= -horiz_start_threshold_) {
+ translate = owner_->bounds().width() +
+ std::max(delta_x_ + horiz_start_threshold_,
+ static_cast<float>(-owner_->bounds().width()));
+ translate_layer = slider_.get();
+ } else if (delta_x_ >= horiz_start_threshold_) {
+ translate = std::min(delta_x_ - horiz_start_threshold_,
+ static_cast<float>(owner_->bounds().width()));
+ translate_layer = owner_->layer();
+ } else {
+ return;
+ }
+
+ if (!shadow_.get())
+ shadow_.reset(new ShadowLayerDelegate(translate_layer));
+
+ gfx::Transform transform;
+ transform.Translate(translate, 0);
+ translate_layer->SetTransform(transform);
+}
+
+void WindowSlider::UpdateForFling(float x_velocity, float y_velocity) {
+ if (!slider_.get())
+ return;
+
+ int width = owner_->bounds().width();
+ float ratio = (fabs(delta_x_) - horiz_start_threshold_) / width;
+ if (ratio < complete_threshold_) {
+ ResetScroll();
+ return;
+ }
+
+ ui::Layer* sliding = delta_x_ < 0 ? slider_.get() : owner_->layer();
+ ui::ScopedLayerAnimationSettings settings(sliding->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.AddObserver(new CallbackAnimationObserver(
+ base::Bind(&WindowSlider::CompleteWindowSlideAfterAnimation,
+ weak_factory_.GetWeakPtr())));
+
+ gfx::Transform transform;
+ transform.Translate(delta_x_ < 0 ? 0 : width, 0);
+ sliding->SetTransform(transform);
+}
+
+void WindowSlider::ResetScroll() {
+ if (!slider_.get())
+ return;
+
+ // Do not trigger any callbacks if this animation replaces any in-progress
+ // animation.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Reset the state of the sliding layer.
+ if (slider_.get()) {
+ ui::Layer* layer = slider_.release();
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+
+ // Delete the layer and the shadow at the end of the animation.
+ settings.AddObserver(new CallbackAnimationObserver(
+ base::Bind(&DeleteLayerAndShadow,
+ base::Unretained(layer),
+ base::Unretained(shadow_.release()))));
+
+ gfx::Transform transform;
+ transform.Translate(delta_x_ < 0 ? layer->bounds().width() : 0, 0);
+ layer->SetTransform(transform);
+ }
+
+ // Reset the state of the main layer.
+ {
+ ui::ScopedLayerAnimationSettings settings(owner_->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.AddObserver(new CallbackAnimationObserver(
+ base::Bind(&WindowSlider::AbortWindowSlideAfterAnimation,
+ weak_factory_.GetWeakPtr())));
+ owner_->layer()->SetTransform(gfx::Transform());
+ owner_->layer()->SetLayerBrightness(0.f);
+ }
+
+ delta_x_ = 0.f;
+}
+
+void WindowSlider::CancelScroll() {
+ ResetScroll();
+}
+
+void WindowSlider::CompleteWindowSlideAfterAnimation() {
+ weak_factory_.InvalidateWeakPtrs();
+ shadow_.reset();
+ slider_.reset();
+ delta_x_ = 0.f;
+
+ delegate_->OnWindowSlideComplete();
+}
+
+void WindowSlider::AbortWindowSlideAfterAnimation() {
+ weak_factory_.InvalidateWeakPtrs();
+
+ delegate_->OnWindowSlideAborted();
+}
+
+void WindowSlider::OnKeyEvent(ui::KeyEvent* event) {
+ CancelScroll();
+}
+
+void WindowSlider::OnMouseEvent(ui::MouseEvent* event) {
+ if (!(event->flags() & ui::EF_IS_SYNTHESIZED))
+ CancelScroll();
+}
+
+void WindowSlider::OnScrollEvent(ui::ScrollEvent* event) {
+ if (event->type() == ui::ET_SCROLL)
+ UpdateForScroll(event->x_offset_ordinal(), event->y_offset_ordinal());
+ else if (event->type() == ui::ET_SCROLL_FLING_START)
+ UpdateForFling(event->x_offset_ordinal(), event->y_offset_ordinal());
+ else
+ CancelScroll();
+ event->SetHandled();
+}
+
+void WindowSlider::OnGestureEvent(ui::GestureEvent* event) {
+ const ui::GestureEventDetails& details = event->details();
+ switch (event->type()) {
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ ResetScroll();
+ break;
+
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ UpdateForScroll(details.scroll_x(), details.scroll_y());
+ break;
+
+ case ui::ET_GESTURE_SCROLL_END:
+ UpdateForFling(0.f, 0.f);
+ break;
+
+ case ui::ET_SCROLL_FLING_START:
+ UpdateForFling(details.velocity_x(), details.velocity_y());
+ break;
+
+ case ui::ET_GESTURE_PINCH_BEGIN:
+ case ui::ET_GESTURE_PINCH_UPDATE:
+ case ui::ET_GESTURE_PINCH_END:
+ CancelScroll();
+ break;
+
+ default:
+ break;
+ }
+
+ event->SetHandled();
+}
+
+void WindowSlider::OnWindowRemovingFromRootWindow(aura::Window* window) {
+ if (window == event_window_) {
+ window->RemoveObserver(this);
+ window->RemovePreTargetHandler(this);
+ event_window_ = NULL;
+ } else if (window == owner_) {
+ window->RemoveObserver(this);
+ owner_ = NULL;
+ delete this;
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/aura/window_slider.h b/chromium/content/browser/web_contents/aura/window_slider.h
new file mode 100644
index 00000000000..227ad1ba82c
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/window_slider.h
@@ -0,0 +1,131 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEB_CONTENTS_AURA_WINDOW_SLIDER_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_AURA_WINDOW_SLIDER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ui {
+class Layer;
+}
+
+namespace content {
+
+class ShadowLayerDelegate;
+
+// A class for sliding the layer in a Window on top of other layers.
+class CONTENT_EXPORT WindowSlider : public ui::EventHandler,
+ public aura::WindowObserver {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Creates a layer to show in the background, as the window-layer slides
+ // with the scroll gesture.
+ // The WindowSlider takes ownership of the created layer.
+ virtual ui::Layer* CreateBackLayer() = 0;
+
+ // Creates a layer to slide on top of the window-layer with the scroll
+ // gesture.
+ // The WindowSlider takes ownership of the created layer.
+ virtual ui::Layer* CreateFrontLayer() = 0;
+
+ // Called when the slide is complete. Note that at the end of a completed
+ // slide, the window-layer may have been transformed. The callback here
+ // should reset the transform if necessary.
+ virtual void OnWindowSlideComplete() = 0;
+
+ // Called when the slide is aborted. Note that when the slide is aborted,
+ // the WindowSlider resets any transform it applied on the window-layer.
+ virtual void OnWindowSlideAborted() = 0;
+
+ // Called when the slider is destroyed.
+ virtual void OnWindowSliderDestroyed() = 0;
+ };
+
+ // The WindowSlider slides the layers in the |owner| window. It starts
+ // intercepting scroll events on |event_window|, and uses those events to
+ // control the layer-slide. The lifetime of the slider is managed by the
+ // lifetime of |owner|, i.e. if |owner| is destroyed, then the slider also
+ // destroys itself.
+ WindowSlider(Delegate* delegate,
+ aura::Window* event_window,
+ aura::Window* owner);
+
+ virtual ~WindowSlider();
+
+ // Changes the owner of the slider.
+ void ChangeOwner(aura::Window* new_owner);
+
+ bool IsSlideInProgress() const;
+
+ private:
+ // Sets up the slider layer correctly (sets the correct bounds of the layer,
+ // parents it to the right layer, and sets up the correct stacking order).
+ void SetupSliderLayer();
+
+ void UpdateForScroll(float x_offset, float y_offset);
+
+ void UpdateForFling(float x_velocity, float y_velocity);
+
+ // Resets any in-progress slide.
+ void ResetScroll();
+
+ // Cancels any scroll/animation in progress.
+ void CancelScroll();
+
+ // The following callbacks are triggered after an animation.
+ void CompleteWindowSlideAfterAnimation();
+
+ void AbortWindowSlideAfterAnimation();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE;
+
+ Delegate* delegate_;
+
+ // The slider intercepts scroll events from this window. The slider does not
+ // own |event_window_|. If |event_window_| is destroyed, then the slider stops
+ // listening for events, but it doesn't destroy itself.
+ aura::Window* event_window_;
+
+ // The window the slider operates on. The lifetime of the slider is bound to
+ // this window (i.e. if |owner_| does, the slider destroys itself). The slider
+ // can also delete itself when a slide gesture is completed. This does not
+ // destroy |owner_|.
+ aura::Window* owner_;
+
+ // The accumulated amount of horizontal scroll.
+ float delta_x_;
+
+ // This keeps track of the layer created by the delegate.
+ scoped_ptr<ui::Layer> slider_;
+
+ // This manages the shadow for the layers.
+ scoped_ptr<ShadowLayerDelegate> shadow_;
+
+ base::WeakPtrFactory<WindowSlider> weak_factory_;
+
+ const float horiz_start_threshold_;
+ const float complete_threshold_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSlider);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_AURA_WINDOW_SLIDER_H_
diff --git a/chromium/content/browser/web_contents/aura/window_slider_unittest.cc b/chromium/content/browser/web_contents/aura/window_slider_unittest.cc
new file mode 100644
index 00000000000..c1ffe983275
--- /dev/null
+++ b/chromium/content/browser/web_contents/aura/window_slider_unittest.cc
@@ -0,0 +1,413 @@
+// Copyright (c) 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 "content/browser/web_contents/aura/window_slider.h"
+
+#include "base/bind.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+
+namespace content {
+
+void DispatchEventDuringScrollCallback(aura::RootWindow* root_window,
+ ui::Event* event,
+ ui::EventType type,
+ const gfx::Vector2dF& delta) {
+ if (type != ui::ET_GESTURE_SCROLL_UPDATE)
+ return;
+ aura::RootWindowHostDelegate* delegate =
+ root_window->AsRootWindowHostDelegate();
+ if (event->IsMouseEvent())
+ delegate->OnHostMouseEvent(static_cast<ui::MouseEvent*>(event));
+ else if (event->IsKeyEvent())
+ delegate->OnHostKeyEvent(static_cast<ui::KeyEvent*>(event));
+}
+
+void ChangeSliderOwnerDuringScrollCallback(scoped_ptr<aura::Window>* window,
+ WindowSlider* slider,
+ ui::EventType type,
+ const gfx::Vector2dF& delta) {
+ if (type != ui::ET_GESTURE_SCROLL_UPDATE)
+ return;
+ aura::Window* new_window = new aura::Window(NULL);
+ new_window->Init(ui::LAYER_TEXTURED);
+ new_window->Show();
+ slider->ChangeOwner(new_window);
+ (*window)->parent()->AddChild(new_window);
+ window->reset(new_window);
+}
+
+// The window delegate does not receive any events.
+class NoEventWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+ NoEventWindowDelegate() {
+ }
+ virtual ~NoEventWindowDelegate() {}
+
+ private:
+ // Overridden from aura::WindowDelegate:
+ virtual bool HasHitTestMask() const OVERRIDE { return true; }
+
+ DISALLOW_COPY_AND_ASSIGN(NoEventWindowDelegate);
+};
+
+class WindowSliderDelegateTest : public WindowSlider::Delegate {
+ public:
+ WindowSliderDelegateTest()
+ : can_create_layer_(true),
+ created_back_layer_(false),
+ created_front_layer_(false),
+ slide_completed_(false),
+ slide_aborted_(false),
+ slider_destroyed_(false) {
+ }
+ virtual ~WindowSliderDelegateTest() {}
+
+ void Reset() {
+ can_create_layer_ = true;
+ created_back_layer_ = false;
+ created_front_layer_ = false;
+ slide_completed_ = false;
+ slide_aborted_ = false;
+ slider_destroyed_ = false;
+ }
+
+ void SetCanCreateLayer(bool can_create_layer) {
+ can_create_layer_ = can_create_layer;
+ }
+
+ bool created_back_layer() const { return created_back_layer_; }
+ bool created_front_layer() const { return created_front_layer_; }
+ bool slide_completed() const { return slide_completed_; }
+ bool slide_aborted() const { return slide_aborted_; }
+ bool slider_destroyed() const { return slider_destroyed_; }
+
+ protected:
+ ui::Layer* CreateLayerForTest() {
+ CHECK(can_create_layer_);
+ ui::Layer* layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
+ layer->SetColor(SK_ColorRED);
+ return layer;
+ }
+
+ // Overridden from WindowSlider::Delegate:
+ virtual ui::Layer* CreateBackLayer() OVERRIDE {
+ if (!can_create_layer_)
+ return NULL;
+ created_back_layer_ = true;
+ return CreateLayerForTest();
+ }
+
+ virtual ui::Layer* CreateFrontLayer() OVERRIDE {
+ if (!can_create_layer_)
+ return NULL;
+ created_front_layer_ = true;
+ return CreateLayerForTest();
+ }
+
+ virtual void OnWindowSlideComplete() OVERRIDE {
+ slide_completed_ = true;
+ }
+
+ virtual void OnWindowSlideAborted() OVERRIDE {
+ slide_aborted_ = true;
+ }
+
+ virtual void OnWindowSliderDestroyed() OVERRIDE {
+ slider_destroyed_ = true;
+ }
+
+ private:
+ bool can_create_layer_;
+ bool created_back_layer_;
+ bool created_front_layer_;
+ bool slide_completed_;
+ bool slide_aborted_;
+ bool slider_destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSliderDelegateTest);
+};
+
+// This delegate destroys the owner window when the slider is destroyed.
+class WindowSliderDeleteOwnerOnDestroy : public WindowSliderDelegateTest {
+ public:
+ explicit WindowSliderDeleteOwnerOnDestroy(aura::Window* owner)
+ : owner_(owner) {
+ }
+ virtual ~WindowSliderDeleteOwnerOnDestroy() {}
+
+ private:
+ // Overridden from WindowSlider::Delegate:
+ virtual void OnWindowSliderDestroyed() OVERRIDE {
+ WindowSliderDelegateTest::OnWindowSliderDestroyed();
+ delete owner_;
+ }
+
+ aura::Window* owner_;
+ DISALLOW_COPY_AND_ASSIGN(WindowSliderDeleteOwnerOnDestroy);
+};
+
+// This delegate destroyes the owner window when a slide is completed.
+class WindowSliderDeleteOwnerOnComplete : public WindowSliderDelegateTest {
+ public:
+ explicit WindowSliderDeleteOwnerOnComplete(aura::Window* owner)
+ : owner_(owner) {
+ }
+ virtual ~WindowSliderDeleteOwnerOnComplete() {}
+
+ private:
+ // Overridden from WindowSlider::Delegate:
+ virtual void OnWindowSlideComplete() OVERRIDE {
+ WindowSliderDelegateTest::OnWindowSlideComplete();
+ delete owner_;
+ }
+
+ aura::Window* owner_;
+ DISALLOW_COPY_AND_ASSIGN(WindowSliderDeleteOwnerOnComplete);
+};
+
+typedef aura::test::AuraTestBase WindowSliderTest;
+
+TEST_F(WindowSliderTest, WindowSlideUsingGesture) {
+ scoped_ptr<aura::Window> window(CreateNormalWindow(0, root_window(), NULL));
+ window->SetBounds(gfx::Rect(0, 0, 400, 400));
+ WindowSliderDelegateTest slider_delegate;
+
+ aura::test::EventGenerator generator(root_window());
+
+ // Generate a horizontal overscroll.
+ WindowSlider* slider =
+ new WindowSlider(&slider_delegate, root_window(), window.get());
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(160, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+ EXPECT_FALSE(slider->IsSlideInProgress());
+ slider_delegate.Reset();
+ window->SetTransform(gfx::Transform());
+
+ // Generat a horizontal overscroll in the reverse direction.
+ generator.GestureScrollSequence(gfx::Point(160, 10),
+ gfx::Point(10, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_front_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_back_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+ EXPECT_FALSE(slider->IsSlideInProgress());
+ slider_delegate.Reset();
+
+ // Generate a vertical overscroll.
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(10, 80),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_FALSE(slider_delegate.created_back_layer());
+ EXPECT_FALSE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider->IsSlideInProgress());
+ slider_delegate.Reset();
+
+ // Generate a horizontal scroll that starts overscroll, but doesn't scroll
+ // enough to complete it.
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(60, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+ EXPECT_FALSE(slider->IsSlideInProgress());
+ slider_delegate.Reset();
+
+ // Destroy the window. This should destroy the slider.
+ window.reset();
+ EXPECT_TRUE(slider_delegate.slider_destroyed());
+}
+
+// Tests that the window slide is cancelled when a different type of event
+// happens.
+TEST_F(WindowSliderTest, WindowSlideIsCancelledOnEvent) {
+ scoped_ptr<aura::Window> window(CreateNormalWindow(0, root_window(), NULL));
+ WindowSliderDelegateTest slider_delegate;
+
+ ui::Event* events[] = {
+ new ui::MouseEvent(ui::ET_MOUSE_MOVED,
+ gfx::Point(55, 10),
+ gfx::Point(55, 10),
+ 0),
+ new ui::KeyEvent(ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0,
+ true),
+ NULL
+ };
+
+ new WindowSlider(&slider_delegate, root_window(), window.get());
+ for (int i = 0; events[i]; ++i) {
+ // Generate a horizontal overscroll.
+ aura::test::EventGenerator generator(root_window());
+ generator.GestureScrollSequenceWithCallback(
+ gfx::Point(10, 10),
+ gfx::Point(80, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 1,
+ base::Bind(&DispatchEventDuringScrollCallback,
+ root_window(),
+ base::Owned(events[i])));
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+ slider_delegate.Reset();
+ }
+ window.reset();
+ EXPECT_TRUE(slider_delegate.slider_destroyed());
+}
+
+// Tests that the slide works correctly when the owner of the window changes
+// during the duration of the slide.
+TEST_F(WindowSliderTest, OwnerWindowChangesDuringWindowSlide) {
+ scoped_ptr<aura::Window> parent(CreateNormalWindow(0, root_window(), NULL));
+
+ NoEventWindowDelegate window_delegate;
+ window_delegate.set_window_component(HTNOWHERE);
+ scoped_ptr<aura::Window> window(CreateNormalWindow(1, parent.get(),
+ &window_delegate));
+
+ WindowSliderDelegateTest slider_delegate;
+ scoped_ptr<WindowSlider> slider(
+ new WindowSlider(&slider_delegate, parent.get(), window.get()));
+
+ // Generate a horizontal scroll, and change the owner in the middle of the
+ // scroll.
+ aura::test::EventGenerator generator(root_window());
+ aura::Window* old_window = window.get();
+ generator.GestureScrollSequenceWithCallback(
+ gfx::Point(10, 10),
+ gfx::Point(80, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 1,
+ base::Bind(&ChangeSliderOwnerDuringScrollCallback,
+ base::Unretained(&window),
+ slider.get()));
+ aura::Window* new_window = window.get();
+ EXPECT_NE(old_window, new_window);
+
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+}
+
+TEST_F(WindowSliderTest, NoSlideWhenLayerCantBeCreated) {
+ scoped_ptr<aura::Window> window(CreateNormalWindow(0, root_window(), NULL));
+ window->SetBounds(gfx::Rect(0, 0, 400, 400));
+ WindowSliderDelegateTest slider_delegate;
+ slider_delegate.SetCanCreateLayer(false);
+
+ aura::test::EventGenerator generator(root_window());
+
+ // Generate a horizontal overscroll.
+ scoped_ptr<WindowSlider> slider(
+ new WindowSlider(&slider_delegate, root_window(), window.get()));
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(160, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_FALSE(slider_delegate.created_back_layer());
+ EXPECT_FALSE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+ window->SetTransform(gfx::Transform());
+
+ slider_delegate.SetCanCreateLayer(true);
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(160, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+}
+
+// Tests that the owner window can be destroyed from |OnWindowSliderDestroyed()|
+// delegate callback without causing a crash.
+TEST_F(WindowSliderTest, OwnerIsDestroyedOnSliderDestroy) {
+ size_t child_windows = root_window()->children().size();
+ aura::Window* window = CreateNormalWindow(0, root_window(), NULL);
+ window->SetBounds(gfx::Rect(0, 0, 400, 400));
+ EXPECT_EQ(child_windows + 1, root_window()->children().size());
+
+ WindowSliderDeleteOwnerOnDestroy slider_delegate(window);
+ aura::test::EventGenerator generator(root_window());
+
+ // Generate a horizontal overscroll.
+ scoped_ptr<WindowSlider> slider(
+ new WindowSlider(&slider_delegate, root_window(), window));
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(160, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_FALSE(slider_delegate.slider_destroyed());
+
+ slider.reset();
+ // Destroying the slider would have destroyed |window| too. So |window| should
+ // not need to be destroyed here.
+ EXPECT_EQ(child_windows, root_window()->children().size());
+}
+
+// Tests that the owner window can be destroyed from |OnWindowSlideComplete()|
+// delegate callback without causing a crash.
+TEST_F(WindowSliderTest, OwnerIsDestroyedOnSlideComplete) {
+ size_t child_windows = root_window()->children().size();
+ aura::Window* window = CreateNormalWindow(0, root_window(), NULL);
+ window->SetBounds(gfx::Rect(0, 0, 400, 400));
+ EXPECT_EQ(child_windows + 1, root_window()->children().size());
+
+ WindowSliderDeleteOwnerOnComplete slider_delegate(window);
+ aura::test::EventGenerator generator(root_window());
+
+ // Generate a horizontal overscroll.
+ new WindowSlider(&slider_delegate, root_window(), window);
+ generator.GestureScrollSequence(gfx::Point(10, 10),
+ gfx::Point(160, 10),
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_TRUE(slider_delegate.created_back_layer());
+ EXPECT_TRUE(slider_delegate.slide_completed());
+ EXPECT_FALSE(slider_delegate.created_front_layer());
+ EXPECT_FALSE(slider_delegate.slide_aborted());
+ EXPECT_TRUE(slider_delegate.slider_destroyed());
+
+ // Destroying the slider would have destroyed |window| too. So |window| should
+ // not need to be destroyed here.
+ EXPECT_EQ(child_windows, root_window()->children().size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/debug_urls.cc b/chromium/content/browser/web_contents/debug_urls.cc
new file mode 100644
index 00000000000..1f1222ccfe8
--- /dev/null
+++ b/chromium/content/browser/web_contents/debug_urls.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 "content/browser/web_contents/debug_urls.h"
+
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+void HandlePpapiFlashDebugURL(const GURL& url) {
+#if defined(ENABLE_PLUGINS)
+ bool crash = url == GURL(kChromeUIPpapiFlashCrashURL);
+
+ std::vector<PpapiPluginProcessHost*> hosts;
+ PpapiPluginProcessHost::FindByName(UTF8ToUTF16(kFlashPluginName), &hosts);
+ for (std::vector<PpapiPluginProcessHost*>::iterator iter = hosts.begin();
+ iter != hosts.end(); ++iter) {
+ if (crash)
+ (*iter)->Send(new PpapiMsg_Crash());
+ else
+ (*iter)->Send(new PpapiMsg_Hang());
+ }
+#endif
+}
+
+} // namespace
+
+bool HandleDebugURL(const GURL& url, PageTransition transition) {
+ // Ensure that the user explicitly navigated to this URL.
+ if (!(transition & PAGE_TRANSITION_FROM_ADDRESS_BAR))
+ return false;
+
+ if (url.host() == kChromeUIBrowserCrashHost) {
+ // Induce an intentional crash in the browser process.
+ CHECK(false);
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuCleanURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateRemoveAllContext();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuCrashURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateCrash();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuHangURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateHang();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIPpapiFlashCrashURL) ||
+ url == GURL(kChromeUIPpapiFlashHangURL)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&HandlePpapiFlashDebugURL, url));
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/debug_urls.h b/chromium/content/browser/web_contents/debug_urls.h
new file mode 100644
index 00000000000..79da209c53a
--- /dev/null
+++ b/chromium/content/browser/web_contents/debug_urls.h
@@ -0,0 +1,20 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
+
+#include "content/public/common/page_transition_types.h"
+
+class GURL;
+
+namespace content {
+
+// Checks if the given url is a url used for debugging purposes, and if so
+// handles it and returns true.
+bool HandleDebugURL(const GURL& url, PageTransition transition);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_DEBUG_URLS_H_
diff --git a/chromium/content/browser/web_contents/drag_utils_gtk.cc b/chromium/content/browser/web_contents/drag_utils_gtk.cc
new file mode 100644
index 00000000000..8164c5392e6
--- /dev/null
+++ b/chromium/content/browser/web_contents/drag_utils_gtk.cc
@@ -0,0 +1,38 @@
+// 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 "content/browser/web_contents/drag_utils_gtk.h"
+
+using WebKit::WebDragOperationsMask;
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationNone;
+using WebKit::WebDragOperationCopy;
+using WebKit::WebDragOperationLink;
+using WebKit::WebDragOperationMove;
+
+namespace content {
+
+GdkDragAction WebDragOpToGdkDragAction(WebDragOperationsMask op) {
+ GdkDragAction action = static_cast<GdkDragAction>(0);
+ if (op & WebDragOperationCopy)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_COPY);
+ if (op & WebDragOperationLink)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_LINK);
+ if (op & WebDragOperationMove)
+ action = static_cast<GdkDragAction>(action | GDK_ACTION_MOVE);
+ return action;
+}
+
+WebDragOperationsMask GdkDragActionToWebDragOp(GdkDragAction action) {
+ WebDragOperationsMask op = WebDragOperationNone;
+ if (action & GDK_ACTION_COPY)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationCopy);
+ if (action & GDK_ACTION_LINK)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationLink);
+ if (action & GDK_ACTION_MOVE)
+ op = static_cast<WebDragOperationsMask>(op | WebDragOperationMove);
+ return op;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/drag_utils_gtk.h b/chromium/content/browser/web_contents/drag_utils_gtk.h
new file mode 100644
index 00000000000..7232df136ca
--- /dev/null
+++ b/chromium/content/browser/web_contents/drag_utils_gtk.h
@@ -0,0 +1,24 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include "content/common/content_export.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+
+namespace content {
+
+// Convenience methods for converting between web drag operations and the GDK
+// equivalent.
+CONTENT_EXPORT GdkDragAction WebDragOpToGdkDragAction(
+ WebKit::WebDragOperationsMask op);
+CONTENT_EXPORT WebKit::WebDragOperationsMask GdkDragActionToWebDragOp(
+ GdkDragAction action);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_DRAG_UTILS_GTK_H_
diff --git a/chromium/content/browser/web_contents/frame_tree_node.cc b/chromium/content/browser/web_contents/frame_tree_node.cc
new file mode 100644
index 00000000000..dba0977df25
--- /dev/null
+++ b/chromium/content/browser/web_contents/frame_tree_node.cc
@@ -0,0 +1,37 @@
+// 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 "content/browser/web_contents/frame_tree_node.h"
+
+#include <queue>
+
+#include "base/stl_util.h"
+
+namespace content {
+
+FrameTreeNode::FrameTreeNode(int64 frame_id, const std::string& name)
+ : frame_id_(frame_id),
+ frame_name_(name) {
+}
+
+FrameTreeNode::~FrameTreeNode() {
+}
+
+void FrameTreeNode::AddChild(FrameTreeNode* child) {
+ children_.push_back(child);
+}
+
+void FrameTreeNode::RemoveChild(int64 child_id) {
+ std::vector<FrameTreeNode*>::iterator iter;
+
+ for (iter = children_.begin(); iter != children_.end(); ++iter) {
+ if ((*iter)->frame_id() == child_id)
+ break;
+ }
+
+ if (iter != children_.end())
+ children_.erase(iter);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/frame_tree_node.h b/chromium/content/browser/web_contents/frame_tree_node.h
new file mode 100644
index 00000000000..76d2194d5c4
--- /dev/null
+++ b/chromium/content/browser/web_contents/frame_tree_node.h
@@ -0,0 +1,76 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_FRAME_TREE_NODE_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_FRAME_TREE_NODE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Any page that contains iframes has a tree structure of the frames in the
+// renderer process. We are mirroring this tree in the browser process. This
+// class represents a node in this tree and is a wrapper for all objects that
+// are frame-specific (as opposed to page-specific).
+class CONTENT_EXPORT FrameTreeNode {
+ public:
+ FrameTreeNode(int64 frame_id, const std::string& name);
+ ~FrameTreeNode();
+
+ // This method takes ownership of the child pointer.
+ void AddChild(FrameTreeNode* child);
+ void RemoveChild(int64 child_id);
+
+ int64 frame_id() const {
+ return frame_id_;
+ }
+
+ const std::string& frame_name() const {
+ return frame_name_;
+ }
+
+ size_t child_count() const {
+ return children_.size();
+ }
+
+ FrameTreeNode* child_at(size_t index) const {
+ return children_[index];
+ }
+
+ const GURL& current_url() const {
+ return current_url_;
+ }
+
+ void set_current_url(const GURL& url) {
+ current_url_ = url;
+ }
+
+ private:
+ // The unique identifier for the frame in the page.
+ int64 frame_id_;
+
+ // The assigned name of the frame. This name can be empty, unlike the unique
+ // name generated internally in the DOM tree.
+ std::string frame_name_;
+
+ // The immediate children of this specific frame.
+ ScopedVector<FrameTreeNode> children_;
+
+ // Track the current frame's last committed URL, so we can estimate the
+ // process impact of out-of-process iframes.
+ // TODO(creis): Remove this when we can store subframe URLs in the
+ // NavigationController.
+ GURL current_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameTreeNode);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_FRAME_TREE_NODE_H_
diff --git a/chromium/content/browser/web_contents/interstitial_page_impl.cc b/chromium/content/browser/web_contents/interstitial_page_impl.cc
new file mode 100644
index 00000000000..232217d368f
--- /dev/null
+++ b/chromium/content/browser/web_contents/interstitial_page_impl.cc
@@ -0,0 +1,820 @@
+// 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 "content/browser/web_contents/interstitial_page_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/dom_operation_notification_details.h"
+#include "content/public/browser/interstitial_page_delegate.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/page_transition_types.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace content {
+namespace {
+
+void ResourceRequestHelper(ResourceDispatcherHostImpl* rdh,
+ int process_id,
+ int render_view_host_id,
+ ResourceRequestAction action) {
+ switch (action) {
+ case BLOCK:
+ rdh->BlockRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case RESUME:
+ rdh->ResumeBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case CANCEL:
+ rdh->CancelBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+class InterstitialPageImpl::InterstitialPageRVHDelegateView
+ : public RenderViewHostDelegateView {
+ public:
+ explicit InterstitialPageRVHDelegateView(InterstitialPageImpl* page);
+
+ // RenderViewHostDelegateView implementation:
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebDragOperationsMask operations_allowed,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+ virtual void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ private:
+ InterstitialPageImpl* interstitial_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageRVHDelegateView);
+};
+
+
+// We keep a map of the various blocking pages shown as the UI tests need to
+// be able to retrieve them.
+typedef std::map<WebContents*, InterstitialPageImpl*> InterstitialPageMap;
+static InterstitialPageMap* g_web_contents_to_interstitial_page;
+
+// Initializes g_web_contents_to_interstitial_page in a thread-safe manner.
+// Should be called before accessing g_web_contents_to_interstitial_page.
+static void InitInterstitialPageMap() {
+ if (!g_web_contents_to_interstitial_page)
+ g_web_contents_to_interstitial_page = new InterstitialPageMap;
+}
+
+InterstitialPage* InterstitialPage::Create(WebContents* web_contents,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate) {
+ return new InterstitialPageImpl(web_contents, new_navigation, url, delegate);
+}
+
+InterstitialPage* InterstitialPage::GetInterstitialPage(
+ WebContents* web_contents) {
+ InitInterstitialPageMap();
+ InterstitialPageMap::const_iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents);
+ if (iter == g_web_contents_to_interstitial_page->end())
+ return NULL;
+
+ return iter->second;
+}
+
+InterstitialPageImpl::InterstitialPageImpl(WebContents* web_contents,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate)
+ : WebContentsObserver(web_contents),
+ web_contents_(static_cast<WebContentsImpl*>(web_contents)),
+ url_(url),
+ new_navigation_(new_navigation),
+ should_discard_pending_nav_entry_(new_navigation),
+ reload_on_dont_proceed_(false),
+ enabled_(true),
+ action_taken_(NO_ACTION),
+ render_view_host_(NULL),
+ original_child_id_(web_contents->GetRenderProcessHost()->GetID()),
+ original_rvh_id_(web_contents->GetRenderViewHost()->GetRoutingID()),
+ should_revert_web_contents_title_(false),
+ web_contents_was_loading_(false),
+ resource_dispatcher_host_notified_(false),
+ rvh_delegate_view_(new InterstitialPageRVHDelegateView(this)),
+ create_view_(true),
+ delegate_(delegate),
+ weak_ptr_factory_(this) {
+ InitInterstitialPageMap();
+ // It would be inconsistent to create an interstitial with no new navigation
+ // (which is the case when the interstitial was triggered by a sub-resource on
+ // a page) when we have a pending entry (in the process of loading a new top
+ // frame).
+ DCHECK(new_navigation || !web_contents->GetController().GetPendingEntry());
+}
+
+InterstitialPageImpl::~InterstitialPageImpl() {
+}
+
+void InterstitialPageImpl::Show() {
+ if (!enabled())
+ return;
+
+ // If an interstitial is already showing or about to be shown, close it before
+ // showing the new one.
+ // Be careful not to take an action on the old interstitial more than once.
+ InterstitialPageMap::const_iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents_);
+ if (iter != g_web_contents_to_interstitial_page->end()) {
+ InterstitialPageImpl* interstitial = iter->second;
+ if (interstitial->action_taken_ != NO_ACTION) {
+ interstitial->Hide();
+ } else {
+ // If we are currently showing an interstitial page for which we created
+ // a transient entry and a new interstitial is shown as the result of a
+ // new browser initiated navigation, then that transient entry has already
+ // been discarded and a new pending navigation entry created.
+ // So we should not discard that new pending navigation entry.
+ // See http://crbug.com/9791
+ if (new_navigation_ && interstitial->new_navigation_)
+ interstitial->should_discard_pending_nav_entry_= false;
+ interstitial->DontProceed();
+ }
+ }
+
+ // Block the resource requests for the render view host while it is hidden.
+ TakeActionOnResourceDispatcher(BLOCK);
+ // We need to be notified when the RenderViewHost is destroyed so we can
+ // cancel the blocked requests. We cannot do that on
+ // NOTIFY_WEB_CONTENTS_DESTROYED as at that point the RenderViewHost has
+ // already been destroyed.
+ notification_registrar_.Add(
+ this, NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()));
+
+ // Update the g_web_contents_to_interstitial_page map.
+ iter = g_web_contents_to_interstitial_page->find(web_contents_);
+ DCHECK(iter == g_web_contents_to_interstitial_page->end());
+ (*g_web_contents_to_interstitial_page)[web_contents_] = this;
+
+ if (new_navigation_) {
+ NavigationEntryImpl* entry = new NavigationEntryImpl;
+ entry->SetURL(url_);
+ entry->SetVirtualURL(url_);
+ entry->set_page_type(PAGE_TYPE_INTERSTITIAL);
+
+ // Give delegates a chance to set some states on the navigation entry.
+ delegate_->OverrideEntry(entry);
+
+ web_contents_->GetController().SetTransientEntry(entry);
+ }
+
+ DCHECK(!render_view_host_);
+ render_view_host_ = static_cast<RenderViewHostImpl*>(CreateRenderViewHost());
+ CreateWebContentsView();
+
+ std::string data_url = "data:text/html;charset=utf-8," +
+ net::EscapePath(delegate_->GetHTMLContents());
+ render_view_host_->NavigateToURL(GURL(data_url));
+
+ notification_registrar_.Add(this, NOTIFICATION_NAV_ENTRY_PENDING,
+ Source<NavigationController>(&web_contents_->GetController()));
+ notification_registrar_.Add(
+ this, NOTIFICATION_DOM_OPERATION_RESPONSE,
+ Source<RenderViewHost>(render_view_host_));
+}
+
+void InterstitialPageImpl::Hide() {
+ // We may have already been hidden, and are just waiting to be deleted.
+ // We can't check for enabled() here, because some callers have already
+ // called Disable.
+ if (!render_view_host_)
+ return;
+
+ Disable();
+
+ RenderWidgetHostView* old_view =
+ web_contents_->GetRenderViewHost()->GetView();
+ if (web_contents_->GetInterstitialPage() == this &&
+ old_view && !old_view->IsShowing()) {
+ // Show the original RVH since we're going away. Note it might not exist if
+ // the renderer crashed while the interstitial was showing.
+ // Note that it is important that we don't call Show() if the view is
+ // already showing. That would result in bad things (unparented HWND on
+ // Windows for example) happening.
+ old_view->Show();
+ }
+
+ // If the focus was on the interstitial, let's keep it to the page.
+ // (Note that in unit-tests the RVH may not have a view).
+ if (render_view_host_->GetView() &&
+ render_view_host_->GetView()->HasFocus() &&
+ web_contents_->GetRenderViewHost()->GetView()) {
+ RenderWidgetHostViewPort::FromRWHV(
+ web_contents_->GetRenderViewHost()->GetView())->Focus();
+ }
+
+ // Shutdown the RVH asynchronously, as we may have been called from a RVH
+ // delegate method, and we can't delete the RVH out from under itself.
+ base::MessageLoop::current()->PostNonNestableTask(
+ FROM_HERE,
+ base::Bind(&InterstitialPageImpl::Shutdown,
+ weak_ptr_factory_.GetWeakPtr(),
+ render_view_host_));
+ render_view_host_ = NULL;
+ web_contents_->DetachInterstitialPage();
+ // Let's revert to the original title if necessary.
+ NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
+ if (!new_navigation_ && should_revert_web_contents_title_) {
+ entry->SetTitle(original_web_contents_title_);
+ web_contents_->NotifyNavigationStateChanged(INVALIDATE_TYPE_TITLE);
+ }
+
+ InterstitialPageMap::iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents_);
+ DCHECK(iter != g_web_contents_to_interstitial_page->end());
+ if (iter != g_web_contents_to_interstitial_page->end())
+ g_web_contents_to_interstitial_page->erase(iter);
+
+ // Clear the WebContents pointer, because it may now be deleted.
+ // This signifies that we are in the process of shutting down.
+ web_contents_ = NULL;
+}
+
+void InterstitialPageImpl::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_NAV_ENTRY_PENDING:
+ // We are navigating away from the interstitial (the user has typed a URL
+ // in the location bar or clicked a bookmark). Make sure clicking on the
+ // interstitial will have no effect. Also cancel any blocked requests
+ // on the ResourceDispatcherHost. Note that when we get this notification
+ // the RenderViewHost has not yet navigated so we'll unblock the
+ // RenderViewHost before the resource request for the new page we are
+ // navigating arrives in the ResourceDispatcherHost. This ensures that
+ // request won't be blocked if the same RenderViewHost was used for the
+ // new navigation.
+ Disable();
+ TakeActionOnResourceDispatcher(CANCEL);
+ break;
+ case NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED:
+ if (action_taken_ == NO_ACTION) {
+ // The RenderViewHost is being destroyed (as part of the tab being
+ // closed); make sure we clear the blocked requests.
+ RenderViewHost* rvh = static_cast<RenderViewHost*>(
+ static_cast<RenderViewHostImpl*>(
+ RenderWidgetHostImpl::From(
+ Source<RenderWidgetHost>(source).ptr())));
+ DCHECK(rvh->GetProcess()->GetID() == original_child_id_ &&
+ rvh->GetRoutingID() == original_rvh_id_);
+ TakeActionOnResourceDispatcher(CANCEL);
+ }
+ break;
+ case NOTIFICATION_DOM_OPERATION_RESPONSE:
+ if (enabled()) {
+ Details<DomOperationNotificationDetails> dom_op_details(
+ details);
+ delegate_->CommandReceived(dom_op_details->json);
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void InterstitialPageImpl::NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) {
+ OnNavigatingAwayOrTabClosing();
+}
+
+void InterstitialPageImpl::WebContentsDestroyed(WebContents* web_contents) {
+ OnNavigatingAwayOrTabClosing();
+}
+
+RenderViewHostDelegateView* InterstitialPageImpl::GetDelegateView() {
+ return rvh_delegate_view_.get();
+}
+
+const GURL& InterstitialPageImpl::GetURL() const {
+ return url_;
+}
+
+void InterstitialPageImpl::RenderViewTerminated(
+ RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) {
+ // Our renderer died. This should not happen in normal cases.
+ // If we haven't already started shutdown, just dismiss the interstitial.
+ // We cannot check for enabled() here, because we may have called Disable
+ // without calling Hide.
+ if (render_view_host_)
+ DontProceed();
+}
+
+void InterstitialPageImpl::DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // A fast user could have navigated away from the page that triggered the
+ // interstitial while the interstitial was loading, that would have disabled
+ // us. In that case we can dismiss ourselves.
+ if (!enabled()) {
+ DontProceed();
+ return;
+ }
+ if (PageTransitionCoreTypeIs(params.transition,
+ PAGE_TRANSITION_AUTO_SUBFRAME)) {
+ // No need to handle navigate message from iframe in the interstitial page.
+ return;
+ }
+
+ // The RenderViewHost has loaded its contents, we can show it now.
+ render_view_host_->GetView()->Show();
+ web_contents_->AttachInterstitialPage(this);
+
+ RenderWidgetHostView* rwh_view =
+ web_contents_->GetRenderViewHost()->GetView();
+
+ // The RenderViewHost may already have crashed before we even get here.
+ if (rwh_view) {
+ // If the page has focus, focus the interstitial.
+ if (rwh_view->HasFocus())
+ Focus();
+
+ // Hide the original RVH since we're showing the interstitial instead.
+ rwh_view->Hide();
+ }
+
+ // Notify the tab we are not loading so the throbber is stopped. It also
+ // causes a NOTIFY_LOAD_STOP notification, that the AutomationProvider (used
+ // by the UI tests) expects to consider a navigation as complete. Without
+ // this, navigating in a UI test to a URL that triggers an interstitial would
+ // hang.
+ web_contents_was_loading_ = web_contents_->IsLoading();
+ web_contents_->SetIsLoading(false, NULL);
+}
+
+void InterstitialPageImpl::UpdateTitle(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) {
+ if (!enabled())
+ return;
+
+ DCHECK(render_view_host == render_view_host_);
+ NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
+ if (!entry) {
+ // Crash reports from the field indicate this can be NULL.
+ // This is unexpected as InterstitialPages constructed with the
+ // new_navigation flag set to true create a transient navigation entry
+ // (that is returned as the active entry). And the only case so far of
+ // interstitial created with that flag set to false is with the
+ // SafeBrowsingBlockingPage, when the resource triggering the interstitial
+ // is a sub-resource, meaning the main page has already been loaded and a
+ // navigation entry should have been created.
+ NOTREACHED();
+ return;
+ }
+
+ // If this interstitial is shown on an existing navigation entry, we'll need
+ // to remember its title so we can revert to it when hidden.
+ if (!new_navigation_ && !should_revert_web_contents_title_) {
+ original_web_contents_title_ = entry->GetTitle();
+ should_revert_web_contents_title_ = true;
+ }
+ // TODO(evan): make use of title_direction.
+ // http://code.google.com/p/chromium/issues/detail?id=27094
+ entry->SetTitle(title);
+ web_contents_->NotifyNavigationStateChanged(INVALIDATE_TYPE_TITLE);
+}
+
+RendererPreferences InterstitialPageImpl::GetRendererPrefs(
+ BrowserContext* browser_context) const {
+ delegate_->OverrideRendererPrefs(&renderer_preferences_);
+ return renderer_preferences_;
+}
+
+WebPreferences InterstitialPageImpl::GetWebkitPrefs() {
+ if (!enabled())
+ return WebPreferences();
+
+ return WebContentsImpl::GetWebkitPrefs(render_view_host_, url_);
+}
+
+void InterstitialPageImpl::RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) {
+ delete this;
+}
+
+bool InterstitialPageImpl::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ if (!enabled())
+ return false;
+ return web_contents_->PreHandleKeyboardEvent(event, is_keyboard_shortcut);
+}
+
+void InterstitialPageImpl::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (enabled())
+ web_contents_->HandleKeyboardEvent(event);
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible
+InterstitialPageImpl::GetParentNativeViewAccessible() {
+ return web_contents_->GetParentNativeViewAccessible();
+}
+#endif
+
+WebContents* InterstitialPageImpl::web_contents() const {
+ return web_contents_;
+}
+
+RenderViewHost* InterstitialPageImpl::CreateRenderViewHost() {
+ if (!enabled())
+ return NULL;
+
+ // Interstitial pages don't want to share the session storage so we mint a
+ // new one.
+ BrowserContext* browser_context = web_contents()->GetBrowserContext();
+ scoped_refptr<SiteInstance> site_instance =
+ SiteInstance::Create(browser_context);
+ DOMStorageContextWrapper* dom_storage_context =
+ static_cast<DOMStorageContextWrapper*>(
+ BrowserContext::GetStoragePartition(
+ browser_context, site_instance.get())->GetDOMStorageContext());
+ session_storage_namespace_ =
+ new SessionStorageNamespaceImpl(dom_storage_context);
+
+ RenderViewHostImpl* render_view_host =
+ new RenderViewHostImpl(site_instance.get(),
+ this,
+ this,
+ MSG_ROUTING_NONE,
+ MSG_ROUTING_NONE,
+ false);
+ web_contents_->RenderViewForInterstitialPageCreated(render_view_host);
+ return render_view_host;
+}
+
+WebContentsView* InterstitialPageImpl::CreateWebContentsView() {
+ if (!enabled() || !create_view_)
+ return NULL;
+ WebContentsView* web_contents_view = web_contents()->GetView();
+ WebContentsViewPort* web_contents_view_port =
+ static_cast<WebContentsViewPort*>(web_contents_view);
+ RenderWidgetHostView* view =
+ web_contents_view_port->CreateViewForWidget(render_view_host_);
+ render_view_host_->SetView(view);
+ render_view_host_->AllowBindings(BINDINGS_POLICY_DOM_AUTOMATION);
+
+ int32 max_page_id = web_contents()->
+ GetMaxPageIDForSiteInstance(render_view_host_->GetSiteInstance());
+ render_view_host_->CreateRenderView(string16(),
+ MSG_ROUTING_NONE,
+ max_page_id);
+ view->SetSize(web_contents_view->GetContainerSize());
+ // Don't show the interstitial until we have navigated to it.
+ view->Hide();
+ return web_contents_view;
+}
+
+void InterstitialPageImpl::Proceed() {
+ // Don't repeat this if we are already shutting down. We cannot check for
+ // enabled() here, because we may have called Disable without calling Hide.
+ if (!render_view_host_)
+ return;
+
+ if (action_taken_ != NO_ACTION) {
+ NOTREACHED();
+ return;
+ }
+ Disable();
+ action_taken_ = PROCEED_ACTION;
+
+ // Resumes the throbber, if applicable.
+ if (web_contents_was_loading_)
+ web_contents_->SetIsLoading(true, NULL);
+
+ // If this is a new navigation, the old page is going away, so we cancel any
+ // blocked requests for it. If it is not a new navigation, then it means the
+ // interstitial was shown as a result of a resource loading in the page.
+ // Since the user wants to proceed, we'll let any blocked request go through.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(CANCEL);
+ else
+ TakeActionOnResourceDispatcher(RESUME);
+
+ // No need to hide if we are a new navigation, we'll get hidden when the
+ // navigation is committed.
+ if (!new_navigation_) {
+ Hide();
+ delegate_->OnProceed();
+ return;
+ }
+
+ delegate_->OnProceed();
+}
+
+void InterstitialPageImpl::DontProceed() {
+ // Don't repeat this if we are already shutting down. We cannot check for
+ // enabled() here, because we may have called Disable without calling Hide.
+ if (!render_view_host_)
+ return;
+ DCHECK(action_taken_ != DONT_PROCEED_ACTION);
+
+ Disable();
+ action_taken_ = DONT_PROCEED_ACTION;
+
+ // If this is a new navigation, we are returning to the original page, so we
+ // resume blocked requests for it. If it is not a new navigation, then it
+ // means the interstitial was shown as a result of a resource loading in the
+ // page and we won't return to the original page, so we cancel blocked
+ // requests in that case.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+
+ if (should_discard_pending_nav_entry_) {
+ // Since no navigation happens we have to discard the transient entry
+ // explicitely. Note that by calling DiscardNonCommittedEntries() we also
+ // discard the pending entry, which is what we want, since the navigation is
+ // cancelled.
+ web_contents_->GetController().DiscardNonCommittedEntries();
+ }
+
+ if (reload_on_dont_proceed_)
+ web_contents_->GetController().Reload(true);
+
+ Hide();
+ delegate_->OnDontProceed();
+}
+
+void InterstitialPageImpl::CancelForNavigation() {
+ // The user is trying to navigate away. We should unblock the renderer and
+ // disable the interstitial, but keep it visible until the navigation
+ // completes.
+ Disable();
+ // If this interstitial was shown for a new navigation, allow any navigations
+ // on the original page to resume (e.g., subresource requests, XHRs, etc).
+ // Otherwise, cancel the pending, possibly dangerous navigations.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+}
+
+void InterstitialPageImpl::SetSize(const gfx::Size& size) {
+ if (!enabled())
+ return;
+#if !defined(OS_MACOSX)
+ // When a tab is closed, we might be resized after our view was NULLed
+ // (typically if there was an info-bar).
+ if (render_view_host_->GetView())
+ render_view_host_->GetView()->SetSize(size);
+#else
+ // TODO(port): Does Mac need to SetSize?
+ NOTIMPLEMENTED();
+#endif
+}
+
+void InterstitialPageImpl::Focus() {
+ // Focus the native window.
+ if (!enabled())
+ return;
+ RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus();
+}
+
+void InterstitialPageImpl::FocusThroughTabTraversal(bool reverse) {
+ if (!enabled())
+ return;
+ render_view_host_->SetInitialFocus(reverse);
+}
+
+RenderWidgetHostView* InterstitialPageImpl::GetView() {
+ return render_view_host_->GetView();
+}
+
+RenderViewHost* InterstitialPageImpl::GetRenderViewHostForTesting() const {
+ return render_view_host_;
+}
+
+#if defined(OS_ANDROID)
+RenderViewHost* InterstitialPageImpl::GetRenderViewHost() const {
+ return render_view_host_;
+}
+#endif
+
+InterstitialPageDelegate* InterstitialPageImpl::GetDelegateForTesting() {
+ return delegate_.get();
+}
+
+void InterstitialPageImpl::DontCreateViewForTesting() {
+ create_view_ = false;
+}
+
+gfx::Rect InterstitialPageImpl::GetRootWindowResizerRect() const {
+ return gfx::Rect();
+}
+
+void InterstitialPageImpl::CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::CreateNewFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPageImpl::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::ShowCreatedFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+SessionStorageNamespace* InterstitialPageImpl::GetSessionStorageNamespace(
+ SiteInstance* instance) {
+ return session_storage_namespace_.get();
+}
+
+void InterstitialPageImpl::Disable() {
+ enabled_ = false;
+}
+
+void InterstitialPageImpl::Shutdown(RenderViewHostImpl* render_view_host) {
+ render_view_host->Shutdown();
+ // We are deleted now.
+}
+
+void InterstitialPageImpl::OnNavigatingAwayOrTabClosing() {
+ if (action_taken_ == NO_ACTION) {
+ // We are navigating away from the interstitial or closing a tab with an
+ // interstitial. Default to DontProceed(). We don't just call Hide as
+ // subclasses will almost certainly override DontProceed to do some work
+ // (ex: close pending connections).
+ DontProceed();
+ } else {
+ // User decided to proceed and either the navigation was committed or
+ // the tab was closed before that.
+ Hide();
+ }
+}
+
+void InterstitialPageImpl::TakeActionOnResourceDispatcher(
+ ResourceRequestAction action) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)) <<
+ "TakeActionOnResourceDispatcher should be called on the main thread.";
+
+ if (action == CANCEL || action == RESUME) {
+ if (resource_dispatcher_host_notified_)
+ return;
+ resource_dispatcher_host_notified_ = true;
+ }
+
+ // The tab might not have a render_view_host if it was closed (in which case,
+ // we have taken care of the blocked requests when processing
+ // NOTIFY_RENDER_WIDGET_HOST_DESTROYED.
+ // Also we need to test there is a ResourceDispatcherHostImpl, as when unit-
+ // tests we don't have one.
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(original_child_id_,
+ original_rvh_id_);
+ if (!rvh || !ResourceDispatcherHostImpl::Get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ResourceRequestHelper,
+ ResourceDispatcherHostImpl::Get(),
+ original_child_id_,
+ original_rvh_id_,
+ action));
+}
+
+InterstitialPageImpl::InterstitialPageRVHDelegateView::
+ InterstitialPageRVHDelegateView(InterstitialPageImpl* page)
+ : interstitial_page_(page) {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ NOTREACHED() << "InterstitialPage does not support showing popup menus.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::StartDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask allowed_operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::UpdateDragCursor(
+ WebDragOperation) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::GotFocus() {
+ WebContents* web_contents = interstitial_page_->web_contents();
+ if (web_contents && web_contents->GetDelegate())
+ web_contents->GetDelegate()->WebContentsFocused(web_contents);
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::TakeFocus(
+ bool reverse) {
+ if (!interstitial_page_->web_contents())
+ return;
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(interstitial_page_->web_contents());
+ if (!web_contents->GetDelegateView())
+ return;
+
+ web_contents->GetDelegateView()->TakeFocus(reverse);
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::OnFindReply(
+ int request_id, int number_of_matches, const gfx::Rect& selection_rect,
+ int active_match_ordinal, bool final_update) {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/interstitial_page_impl.h b/chromium/content/browser/web_contents/interstitial_page_impl.h
new file mode 100644
index 00000000000..1f4d94fcc8e
--- /dev/null
+++ b/chromium/content/browser/web_contents/interstitial_page_impl.h
@@ -0,0 +1,253 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/public/browser/interstitial_page.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/renderer_preferences.h"
+#include "url/gurl.h"
+
+namespace content {
+class NavigationEntry;
+class RenderViewHostImpl;
+class RenderWidgetHostView;
+class WebContentsView;
+class WebContentsImpl;
+
+enum ResourceRequestAction {
+ BLOCK,
+ RESUME,
+ CANCEL
+};
+
+class CONTENT_EXPORT InterstitialPageImpl
+ : public NON_EXPORTED_BASE(InterstitialPage),
+ public NotificationObserver,
+ public WebContentsObserver,
+ public RenderViewHostDelegate,
+ public RenderWidgetHostDelegate {
+ public:
+ // The different state of actions the user can take in an interstitial.
+ enum ActionState {
+ NO_ACTION, // No action has been taken yet.
+ PROCEED_ACTION, // "Proceed" was selected.
+ DONT_PROCEED_ACTION // "Don't proceed" was selected.
+ };
+
+ InterstitialPageImpl(WebContents* web_contents,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate);
+ virtual ~InterstitialPageImpl();
+
+ // InterstitialPage implementation:
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual void DontProceed() OVERRIDE;
+ virtual void Proceed() OVERRIDE;
+ virtual RenderViewHost* GetRenderViewHostForTesting() const OVERRIDE;
+ virtual InterstitialPageDelegate* GetDelegateForTesting() OVERRIDE;
+ virtual void DontCreateViewForTesting() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+
+ // Allows the user to navigate away by disabling the interstitial, canceling
+ // the pending request, and unblocking the hidden renderer. The interstitial
+ // will stay visible until the navigation completes.
+ void CancelForNavigation();
+
+ // Focus the first (last if reverse is true) element in the interstitial page.
+ // Called when tab traversing.
+ void FocusThroughTabTraversal(bool reverse);
+
+ RenderWidgetHostView* GetView();
+
+ // See description above field.
+ void set_reload_on_dont_proceed(bool value) {
+ reload_on_dont_proceed_ = value;
+ }
+ bool reload_on_dont_proceed() const { return reload_on_dont_proceed_; }
+
+#if defined(OS_ANDROID)
+ // Android shares a single platform window for all tabs, so we need to expose
+ // the RenderViewHost to properly route gestures to the interstitial.
+ RenderViewHost* GetRenderViewHost() const;
+#endif
+
+ protected:
+ // NotificationObserver method:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // WebContentsObserver implementation:
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+ virtual void NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) OVERRIDE;
+
+ // RenderViewHostDelegate implementation:
+ virtual RenderViewHostDelegateView* GetDelegateView() OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void RenderViewTerminated(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE;
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) OVERRIDE;
+ virtual RendererPreferences GetRendererPrefs(
+ BrowserContext* browser_context) const OVERRIDE;
+ virtual WebPreferences GetWebkitPrefs() OVERRIDE;
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE;
+ virtual void CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) OVERRIDE;
+ virtual void CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) OVERRIDE;
+ virtual void CreateNewFullscreenWidget(int route_id) OVERRIDE;
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) OVERRIDE;
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) OVERRIDE;
+ virtual void ShowCreatedFullscreenWidget(int route_id) OVERRIDE;
+
+ virtual SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance) OVERRIDE;
+
+ // RenderWidgetHostDelegate implementation:
+ virtual void RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) OVERRIDE;
+ virtual bool PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) OVERRIDE;
+ virtual void HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual gfx::NativeViewAccessible GetParentNativeViewAccessible() OVERRIDE;
+#endif
+
+ bool enabled() const { return enabled_; }
+ WebContents* web_contents() const;
+ const GURL& url() const { return url_; }
+
+ // Creates the RenderViewHost containing the interstitial content.
+ // Overriden in unit tests.
+ virtual RenderViewHost* CreateRenderViewHost();
+
+ // Creates the WebContentsView that shows the interstitial RVH.
+ // Overriden in unit tests.
+ virtual WebContentsView* CreateWebContentsView();
+
+ // Notification magic.
+ NotificationRegistrar notification_registrar_;
+
+ private:
+ class InterstitialPageRVHDelegateView;
+
+ // Disable the interstitial:
+ // - if it is not yet showing, then it won't be shown.
+ // - any command sent by the RenderViewHost will be ignored.
+ void Disable();
+
+ // Shutdown the RVH. We will be deleted by the time this method returns.
+ void Shutdown(RenderViewHostImpl* render_view_host);
+
+ void OnNavigatingAwayOrTabClosing();
+
+ // Executes the passed action on the ResourceDispatcher (on the IO thread).
+ // Used to block/resume/cancel requests for the RenderViewHost hidden by this
+ // interstitial.
+ void TakeActionOnResourceDispatcher(ResourceRequestAction action);
+
+ // The contents in which we are displayed. This is valid until Hide is
+ // called, at which point it will be set to NULL because the WebContents
+ // itself may be deleted.
+ WebContentsImpl* web_contents_;
+
+ // The URL that is shown when the interstitial is showing.
+ GURL url_;
+
+ // Whether this interstitial is shown as a result of a new navigation (in
+ // which case a transient navigation entry is created).
+ bool new_navigation_;
+
+ // Whether we should discard the pending navigation entry when not proceeding.
+ // This is to deal with cases where |new_navigation_| is true but a new
+ // pending entry was created since this interstitial was shown and we should
+ // not discard it.
+ bool should_discard_pending_nav_entry_;
+
+ // If true and the user chooses not to proceed the target NavigationController
+ // is reloaded. This is used when two NavigationControllers are merged
+ // (CopyStateFromAndPrune).
+ // The default is false.
+ bool reload_on_dont_proceed_;
+
+ // Whether this interstitial is enabled. See Disable() for more info.
+ bool enabled_;
+
+ // Whether the Proceed or DontProceed methods have been called yet.
+ ActionState action_taken_;
+
+ // The RenderViewHost displaying the interstitial contents. This is valid
+ // until Hide is called, at which point it will be set to NULL, signifying
+ // that shutdown has started.
+ RenderViewHostImpl* render_view_host_;
+
+ // The IDs for the Render[View|Process]Host hidden by this interstitial.
+ int original_child_id_;
+ int original_rvh_id_;
+
+ // Whether or not we should change the title of the contents when hidden (to
+ // revert it to its original value).
+ bool should_revert_web_contents_title_;
+
+ // Whether or not the contents was loading resources when the interstitial was
+ // shown. We restore this state if the user proceeds from the interstitial.
+ bool web_contents_was_loading_;
+
+ // Whether the ResourceDispatcherHost has been notified to cancel/resume the
+ // resource requests blocked for the RenderViewHost.
+ bool resource_dispatcher_host_notified_;
+
+ // The original title of the contents that should be reverted to when the
+ // interstitial is hidden.
+ string16 original_web_contents_title_;
+
+ // Our RenderViewHostViewDelegate, necessary for accelerators to work.
+ scoped_ptr<InterstitialPageRVHDelegateView> rvh_delegate_view_;
+
+ // Settings passed to the renderer.
+ mutable RendererPreferences renderer_preferences_;
+
+ bool create_view_;
+
+ scoped_ptr<InterstitialPageDelegate> delegate_;
+
+ base::WeakPtrFactory<InterstitialPageImpl> weak_ptr_factory_;
+
+ scoped_refptr<SessionStorageNamespace> session_storage_namespace_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_INTERSTITIAL_PAGE_IMPL_H_
diff --git a/chromium/content/browser/web_contents/navigation_controller_impl.cc b/chromium/content/browser/web_contents/navigation_controller_impl.cc
new file mode 100644
index 00000000000..c3e9140b48f
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_controller_impl.cc
@@ -0,0 +1,1693 @@
+// 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 "content/browser/web_contents/navigation_controller_impl.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h" // Temporary
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/browser_url_handler_impl.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h" // Temporary
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/debug_urls.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_contents_screenshot_manager.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/escape.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_util.h"
+#include "skia/ext/platform_canvas.h"
+
+namespace content {
+namespace {
+
+const int kInvalidateAll = 0xFFFFFFFF;
+
+// Invoked when entries have been pruned, or removed. For example, if the
+// current entries are [google, digg, yahoo], with the current entry google,
+// and the user types in cnet, then digg and yahoo are pruned.
+void NotifyPrunedEntries(NavigationControllerImpl* nav_controller,
+ bool from_front,
+ int count) {
+ PrunedDetails details;
+ details.from_front = from_front;
+ details.count = count;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(nav_controller),
+ Details<PrunedDetails>(&details));
+}
+
+// Ensure the given NavigationEntry has a valid state, so that WebKit does not
+// get confused if we navigate back to it.
+//
+// An empty state is treated as a new navigation by WebKit, which would mean
+// losing the navigation entries and generating a new navigation entry after
+// this one. We don't want that. To avoid this we create a valid state which
+// WebKit will not treat as a new navigation.
+void SetPageStateIfEmpty(NavigationEntryImpl* entry) {
+ if (!entry->GetPageState().IsValid())
+ entry->SetPageState(PageState::CreateFromURL(entry->GetURL()));
+}
+
+NavigationEntryImpl::RestoreType ControllerRestoreTypeToEntryType(
+ NavigationController::RestoreType type) {
+ switch (type) {
+ case NavigationController::RESTORE_CURRENT_SESSION:
+ return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
+ case NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY:
+ return NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY;
+ case NavigationController::RESTORE_LAST_SESSION_CRASHED:
+ return NavigationEntryImpl::RESTORE_LAST_SESSION_CRASHED;
+ }
+ NOTREACHED();
+ return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
+}
+
+// Configure all the NavigationEntries in entries for restore. This resets
+// the transition type to reload and makes sure the content state isn't empty.
+void ConfigureEntriesForRestore(
+ std::vector<linked_ptr<NavigationEntryImpl> >* entries,
+ NavigationController::RestoreType type) {
+ for (size_t i = 0; i < entries->size(); ++i) {
+ // Use a transition type of reload so that we don't incorrectly increase
+ // the typed count.
+ (*entries)[i]->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ (*entries)[i]->set_restore_type(ControllerRestoreTypeToEntryType(type));
+ // NOTE(darin): This code is only needed for backwards compat.
+ SetPageStateIfEmpty((*entries)[i].get());
+ }
+}
+
+// See NavigationController::IsURLInPageNavigation for how this works and why.
+bool AreURLsInPageNavigation(const GURL& existing_url,
+ const GURL& new_url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) {
+ if (existing_url == new_url)
+ return renderer_says_in_page;
+
+ if (!new_url.has_ref()) {
+ // When going back from the ref URL to the non ref one the navigation type
+ // is IN_PAGE.
+ return navigation_type == NAVIGATION_TYPE_IN_PAGE;
+ }
+
+ url_canon::Replacements<char> replacements;
+ replacements.ClearRef();
+ return existing_url.ReplaceComponents(replacements) ==
+ new_url.ReplaceComponents(replacements);
+}
+
+// Determines whether or not we should be carrying over a user agent override
+// between two NavigationEntries.
+bool ShouldKeepOverride(const NavigationEntry* last_entry) {
+ return last_entry && last_entry->GetIsOverridingUserAgent();
+}
+
+} // namespace
+
+// NavigationControllerImpl ----------------------------------------------------
+
+const size_t kMaxEntryCountForTestingNotSet = -1;
+
+// static
+size_t NavigationControllerImpl::max_entry_count_for_testing_ =
+ kMaxEntryCountForTestingNotSet;
+
+// Should Reload check for post data? The default is true, but is set to false
+// when testing.
+static bool g_check_for_repost = true;
+
+// static
+NavigationEntry* NavigationController::CreateNavigationEntry(
+ const GURL& url,
+ const Referrer& referrer,
+ PageTransition transition,
+ bool is_renderer_initiated,
+ const std::string& extra_headers,
+ BrowserContext* browser_context) {
+ // Allow the browser URL handler to rewrite the URL. This will, for example,
+ // remove "view-source:" from the beginning of the URL to get the URL that
+ // will actually be loaded. This real URL won't be shown to the user, just
+ // used internally.
+ GURL loaded_url(url);
+ bool reverse_on_redirect = false;
+ BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
+ &loaded_url, browser_context, &reverse_on_redirect);
+
+ NavigationEntryImpl* entry = new NavigationEntryImpl(
+ NULL, // The site instance for tabs is sent on navigation
+ // (WebContents::GetSiteInstance).
+ -1,
+ loaded_url,
+ referrer,
+ string16(),
+ transition,
+ is_renderer_initiated);
+ entry->SetVirtualURL(url);
+ entry->set_user_typed_url(url);
+ entry->set_update_virtual_url_with_url(reverse_on_redirect);
+ entry->set_extra_headers(extra_headers);
+ return entry;
+}
+
+// static
+void NavigationController::DisablePromptOnRepost() {
+ g_check_for_repost = false;
+}
+
+base::Time NavigationControllerImpl::TimeSmoother::GetSmoothedTime(
+ base::Time t) {
+ // If |t| is between the water marks, we're in a run of duplicates
+ // or just getting out of it, so increase the high-water mark to get
+ // a time that probably hasn't been used before and return it.
+ if (low_water_mark_ <= t && t <= high_water_mark_) {
+ high_water_mark_ += base::TimeDelta::FromMicroseconds(1);
+ return high_water_mark_;
+ }
+
+ // Otherwise, we're clear of the last duplicate run, so reset the
+ // water marks.
+ low_water_mark_ = high_water_mark_ = t;
+ return t;
+}
+
+NavigationControllerImpl::NavigationControllerImpl(
+ WebContentsImpl* web_contents,
+ BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ pending_entry_(NULL),
+ last_committed_entry_index_(-1),
+ pending_entry_index_(-1),
+ transient_entry_index_(-1),
+ web_contents_(web_contents),
+ max_restored_page_id_(-1),
+ ssl_manager_(this),
+ needs_reload_(false),
+ is_initial_navigation_(true),
+ pending_reload_(NO_RELOAD),
+ get_timestamp_callback_(base::Bind(&base::Time::Now)),
+ screenshot_manager_(new WebContentsScreenshotManager(this)) {
+ DCHECK(browser_context_);
+}
+
+NavigationControllerImpl::~NavigationControllerImpl() {
+ DiscardNonCommittedEntriesInternal();
+}
+
+WebContents* NavigationControllerImpl::GetWebContents() const {
+ return web_contents_;
+}
+
+BrowserContext* NavigationControllerImpl::GetBrowserContext() const {
+ return browser_context_;
+}
+
+void NavigationControllerImpl::SetBrowserContext(
+ BrowserContext* browser_context) {
+ browser_context_ = browser_context;
+}
+
+void NavigationControllerImpl::Restore(
+ int selected_navigation,
+ RestoreType type,
+ std::vector<NavigationEntry*>* entries) {
+ // Verify that this controller is unused and that the input is valid.
+ DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
+ DCHECK(selected_navigation >= 0 &&
+ selected_navigation < static_cast<int>(entries->size()));
+
+ needs_reload_ = true;
+ for (size_t i = 0; i < entries->size(); ++i) {
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry((*entries)[i]);
+ entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
+ }
+ entries->clear();
+
+ // And finish the restore.
+ FinishRestore(selected_navigation, type);
+}
+
+void NavigationControllerImpl::Reload(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD);
+}
+void NavigationControllerImpl::ReloadIgnoringCache(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE);
+}
+void NavigationControllerImpl::ReloadOriginalRequestURL(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD_ORIGINAL_REQUEST_URL);
+}
+
+void NavigationControllerImpl::ReloadInternal(bool check_for_repost,
+ ReloadType reload_type) {
+ if (transient_entry_index_ != -1) {
+ // If an interstitial is showing, treat a reload as a navigation to the
+ // transient entry's URL.
+ NavigationEntryImpl* active_entry =
+ NavigationEntryImpl::FromNavigationEntry(GetActiveEntry());
+ if (!active_entry)
+ return;
+ LoadURL(active_entry->GetURL(),
+ Referrer(),
+ PAGE_TRANSITION_RELOAD,
+ active_entry->extra_headers());
+ return;
+ }
+
+ NavigationEntryImpl* entry = NULL;
+ int current_index = -1;
+
+ // If we are reloading the initial navigation, just use the current
+ // pending entry. Otherwise look up the current entry.
+ if (IsInitialNavigation() && pending_entry_) {
+ entry = pending_entry_;
+ // The pending entry might be in entries_ (e.g., after a Clone), so we
+ // should also update the current_index.
+ current_index = pending_entry_index_;
+ } else {
+ DiscardNonCommittedEntriesInternal();
+ current_index = GetCurrentEntryIndex();
+ if (current_index != -1) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ GetEntryAtIndex(current_index));
+ }
+ }
+
+ // If we are no where, then we can't reload. TODO(darin): We should add a
+ // CanReload method.
+ if (!entry)
+ return;
+
+ if (g_check_for_repost && check_for_repost &&
+ entry->GetHasPostData()) {
+ // The user is asking to reload a page with POST data. Prompt to make sure
+ // they really want to do this. If they do, the dialog will call us back
+ // with check_for_repost = false.
+ web_contents_->NotifyBeforeFormRepostWarningShow();
+
+ pending_reload_ = reload_type;
+ web_contents_->Activate();
+ web_contents_->GetDelegate()->ShowRepostFormWarningDialog(web_contents_);
+ } else {
+ if (!IsInitialNavigation())
+ DiscardNonCommittedEntriesInternal();
+
+ // If we are reloading an entry that no longer belongs to the current
+ // site instance (for example, refreshing a page for just installed app),
+ // the reload must happen in a new process.
+ // The new entry must have a new page_id and site instance, so it behaves
+ // as new navigation (which happens to clear forward history).
+ // Tabs that are discarded due to low memory conditions may not have a site
+ // instance, and should not be treated as a cross-site reload.
+ SiteInstanceImpl* site_instance = entry->site_instance();
+ if (site_instance &&
+ site_instance->HasWrongProcessForURL(entry->GetURL())) {
+ // Create a navigation entry that resembles the current one, but do not
+ // copy page id, site instance, content state, or timestamp.
+ NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(),
+ false, entry->extra_headers(), browser_context_));
+
+ // Mark the reload type as NO_RELOAD, so navigation will not be considered
+ // a reload in the renderer.
+ reload_type = NavigationController::NO_RELOAD;
+
+ nav_entry->set_should_replace_entry(true);
+ pending_entry_ = nav_entry;
+ } else {
+ pending_entry_ = entry;
+ pending_entry_index_ = current_index;
+
+ // The title of the page being reloaded might have been removed in the
+ // meanwhile, so we need to revert to the default title upon reload and
+ // invalidate the previously cached title (SetTitle will do both).
+ // See Chromium issue 96041.
+ pending_entry_->SetTitle(string16());
+
+ pending_entry_->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ }
+
+ NavigateToPendingEntry(reload_type);
+ }
+}
+
+void NavigationControllerImpl::CancelPendingReload() {
+ DCHECK(pending_reload_ != NO_RELOAD);
+ pending_reload_ = NO_RELOAD;
+}
+
+void NavigationControllerImpl::ContinuePendingReload() {
+ if (pending_reload_ == NO_RELOAD) {
+ NOTREACHED();
+ } else {
+ ReloadInternal(false, pending_reload_);
+ pending_reload_ = NO_RELOAD;
+ }
+}
+
+bool NavigationControllerImpl::IsInitialNavigation() const {
+ return is_initial_navigation_;
+}
+
+NavigationEntryImpl* NavigationControllerImpl::GetEntryWithPageID(
+ SiteInstance* instance, int32 page_id) const {
+ int index = GetEntryIndexWithPageID(instance, page_id);
+ return (index != -1) ? entries_[index].get() : NULL;
+}
+
+void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) {
+ // When navigating to a new page, we don't know for sure if we will actually
+ // end up leaving the current page. The new page load could for example
+ // result in a download or a 'no content' response (e.g., a mailto: URL).
+ SetPendingEntry(entry);
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::SetPendingEntry(NavigationEntryImpl* entry) {
+ DiscardNonCommittedEntriesInternal();
+ pending_entry_ = entry;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_PENDING,
+ Source<NavigationController>(this),
+ Details<NavigationEntry>(entry));
+}
+
+NavigationEntry* NavigationControllerImpl::GetActiveEntry() const {
+ if (transient_entry_index_ != -1)
+ return entries_[transient_entry_index_].get();
+ if (pending_entry_)
+ return pending_entry_;
+ return GetLastCommittedEntry();
+}
+
+NavigationEntry* NavigationControllerImpl::GetVisibleEntry() const {
+ if (transient_entry_index_ != -1)
+ return entries_[transient_entry_index_].get();
+ // The pending entry is safe to return for new (non-history), browser-
+ // initiated navigations. Most renderer-initiated navigations should not
+ // show the pending entry, to prevent URL spoof attacks.
+ //
+ // We make an exception for renderer-initiated navigations in new tabs, as
+ // long as no other page has tried to access the initial empty document in
+ // the new tab. If another page modifies this blank page, a URL spoof is
+ // possible, so we must stop showing the pending entry.
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ bool safe_to_show_pending =
+ pending_entry_ &&
+ // Require a new navigation.
+ pending_entry_->GetPageID() == -1 &&
+ // Require either browser-initiated or an unmodified new tab.
+ (!pending_entry_->is_renderer_initiated() ||
+ (IsInitialNavigation() &&
+ !GetLastCommittedEntry() &&
+ !rvh->has_accessed_initial_document()));
+
+ // Also allow showing the pending entry for history navigations in a new tab,
+ // such as Ctrl+Back. In this case, no existing page is visible and no one
+ // can script the new tab before it commits.
+ if (!safe_to_show_pending &&
+ pending_entry_ &&
+ pending_entry_->GetPageID() != -1 &&
+ IsInitialNavigation() &&
+ !pending_entry_->is_renderer_initiated())
+ safe_to_show_pending = true;
+
+ if (safe_to_show_pending)
+ return pending_entry_;
+ return GetLastCommittedEntry();
+}
+
+int NavigationControllerImpl::GetCurrentEntryIndex() const {
+ if (transient_entry_index_ != -1)
+ return transient_entry_index_;
+ if (pending_entry_index_ != -1)
+ return pending_entry_index_;
+ return last_committed_entry_index_;
+}
+
+NavigationEntry* NavigationControllerImpl::GetLastCommittedEntry() const {
+ if (last_committed_entry_index_ == -1)
+ return NULL;
+ return entries_[last_committed_entry_index_].get();
+}
+
+bool NavigationControllerImpl::CanViewSource() const {
+ const std::string& mime_type = web_contents_->GetContentsMimeType();
+ bool is_viewable_mime_type = net::IsSupportedNonImageMimeType(mime_type) &&
+ !net::IsSupportedMediaMimeType(mime_type);
+ NavigationEntry* active_entry = GetActiveEntry();
+ return active_entry && !active_entry->IsViewSourceMode() &&
+ is_viewable_mime_type && !web_contents_->GetInterstitialPage();
+}
+
+int NavigationControllerImpl::GetLastCommittedEntryIndex() const {
+ return last_committed_entry_index_;
+}
+
+int NavigationControllerImpl::GetEntryCount() const {
+ DCHECK(entries_.size() <= max_entry_count());
+ return static_cast<int>(entries_.size());
+}
+
+NavigationEntry* NavigationControllerImpl::GetEntryAtIndex(
+ int index) const {
+ return entries_.at(index).get();
+}
+
+NavigationEntry* NavigationControllerImpl::GetEntryAtOffset(
+ int offset) const {
+ int index = GetIndexForOffset(offset);
+ if (index < 0 || index >= GetEntryCount())
+ return NULL;
+
+ return entries_[index].get();
+}
+
+int NavigationControllerImpl::GetIndexForOffset(int offset) const {
+ return GetCurrentEntryIndex() + offset;
+}
+
+void NavigationControllerImpl::TakeScreenshot() {
+ screenshot_manager_->TakeScreenshot();
+}
+
+void NavigationControllerImpl::SetScreenshotManager(
+ WebContentsScreenshotManager* manager) {
+ screenshot_manager_.reset(manager ? manager :
+ new WebContentsScreenshotManager(this));
+}
+
+bool NavigationControllerImpl::CanGoBack() const {
+ return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
+}
+
+bool NavigationControllerImpl::CanGoForward() const {
+ int index = GetCurrentEntryIndex();
+ return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
+}
+
+bool NavigationControllerImpl::CanGoToOffset(int offset) const {
+ int index = GetIndexForOffset(offset);
+ return index >= 0 && index < GetEntryCount();
+}
+
+void NavigationControllerImpl::GoBack() {
+ if (!CanGoBack()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index - 1;
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoForward() {
+ if (!CanGoForward()) {
+ NOTREACHED();
+ return;
+ }
+
+ bool transient = (transient_entry_index_ != -1);
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index;
+ // If there was a transient entry, we removed it making the current index
+ // the next page.
+ if (!transient)
+ pending_entry_index_++;
+
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoToIndex(int index) {
+ if (index < 0 || index >= static_cast<int>(entries_.size())) {
+ NOTREACHED();
+ return;
+ }
+
+ if (transient_entry_index_ != -1) {
+ if (index == transient_entry_index_) {
+ // Nothing to do when navigating to the transient.
+ return;
+ }
+ if (index > transient_entry_index_) {
+ // Removing the transient is goint to shift all entries by 1.
+ index--;
+ }
+ }
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = index;
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoToOffset(int offset) {
+ if (!CanGoToOffset(offset))
+ return;
+
+ GoToIndex(GetIndexForOffset(offset));
+}
+
+bool NavigationControllerImpl::RemoveEntryAtIndex(int index) {
+ if (index == last_committed_entry_index_ ||
+ index == pending_entry_index_)
+ return false;
+
+ RemoveEntryAtIndexInternal(index);
+ return true;
+}
+
+void NavigationControllerImpl::UpdateVirtualURLToURL(
+ NavigationEntryImpl* entry, const GURL& new_url) {
+ GURL new_virtual_url(new_url);
+ if (BrowserURLHandlerImpl::GetInstance()->ReverseURLRewrite(
+ &new_virtual_url, entry->GetVirtualURL(), browser_context_)) {
+ entry->SetVirtualURL(new_virtual_url);
+ }
+}
+
+void NavigationControllerImpl::LoadURL(
+ const GURL& url,
+ const Referrer& referrer,
+ PageTransition transition,
+ const std::string& extra_headers) {
+ LoadURLParams params(url);
+ params.referrer = referrer;
+ params.transition_type = transition;
+ params.extra_headers = extra_headers;
+ LoadURLWithParams(params);
+}
+
+void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
+ TRACE_EVENT0("browser", "NavigationControllerImpl::LoadURLWithParams");
+ if (HandleDebugURL(params.url, params.transition_type))
+ return;
+
+ // Checks based on params.load_type.
+ switch (params.load_type) {
+ case LOAD_TYPE_DEFAULT:
+ break;
+ case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
+ if (!params.url.SchemeIs(chrome::kHttpScheme) &&
+ !params.url.SchemeIs(chrome::kHttpsScheme)) {
+ NOTREACHED() << "Http post load must use http(s) scheme.";
+ return;
+ }
+ break;
+ case LOAD_TYPE_DATA:
+ if (!params.url.SchemeIs(chrome::kDataScheme)) {
+ NOTREACHED() << "Data load must use data scheme.";
+ return;
+ }
+ break;
+ default:
+ NOTREACHED();
+ break;
+ };
+
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ bool override = false;
+ switch (params.override_user_agent) {
+ case UA_OVERRIDE_INHERIT:
+ override = ShouldKeepOverride(GetLastCommittedEntry());
+ break;
+ case UA_OVERRIDE_TRUE:
+ override = true;
+ break;
+ case UA_OVERRIDE_FALSE:
+ override = false;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ params.url,
+ params.referrer,
+ params.transition_type,
+ params.is_renderer_initiated,
+ params.extra_headers,
+ browser_context_));
+ if (params.should_replace_current_entry)
+ entry->set_should_replace_entry(true);
+ entry->set_should_clear_history_list(params.should_clear_history_list);
+ entry->SetIsOverridingUserAgent(override);
+ entry->set_transferred_global_request_id(
+ params.transferred_global_request_id);
+ entry->SetFrameToNavigate(params.frame_name);
+
+ switch (params.load_type) {
+ case LOAD_TYPE_DEFAULT:
+ break;
+ case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
+ entry->SetHasPostData(true);
+ entry->SetBrowserInitiatedPostData(
+ params.browser_initiated_post_data.get());
+ break;
+ case LOAD_TYPE_DATA:
+ entry->SetBaseURLForDataURL(params.base_url_for_data_url);
+ entry->SetVirtualURL(params.virtual_url_for_data_url);
+ entry->SetCanLoadLocalResources(params.can_load_local_resources);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ };
+
+ LoadEntry(entry);
+}
+
+bool NavigationControllerImpl::RendererDidNavigate(
+ const ViewHostMsg_FrameNavigate_Params& params,
+ LoadCommittedDetails* details) {
+ is_initial_navigation_ = false;
+
+ // Save the previous state before we clobber it.
+ if (GetLastCommittedEntry()) {
+ details->previous_url = GetLastCommittedEntry()->GetURL();
+ details->previous_entry_index = GetLastCommittedEntryIndex();
+ } else {
+ details->previous_url = GURL();
+ details->previous_entry_index = -1;
+ }
+
+ // If we have a pending entry at this point, it should have a SiteInstance.
+ // Restored entries start out with a null SiteInstance, but we should have
+ // assigned one in NavigateToPendingEntry.
+ DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance());
+
+ // If we are doing a cross-site reload, we need to replace the existing
+ // navigation entry, not add another entry to the history. This has the side
+ // effect of removing forward browsing history, if such existed.
+ // Or if we are doing a cross-site redirect navigation,
+ // we will do a similar thing.
+ details->did_replace_entry =
+ pending_entry_ && pending_entry_->should_replace_entry();
+
+ // Do navigation-type specific actions. These will make and commit an entry.
+ details->type = ClassifyNavigation(params);
+
+ // is_in_page must be computed before the entry gets committed.
+ details->is_in_page = IsURLInPageNavigation(
+ params.url, params.was_within_same_page, details->type);
+
+ switch (details->type) {
+ case NAVIGATION_TYPE_NEW_PAGE:
+ RendererDidNavigateToNewPage(params, details->did_replace_entry);
+ break;
+ case NAVIGATION_TYPE_EXISTING_PAGE:
+ RendererDidNavigateToExistingPage(params);
+ break;
+ case NAVIGATION_TYPE_SAME_PAGE:
+ RendererDidNavigateToSamePage(params);
+ break;
+ case NAVIGATION_TYPE_IN_PAGE:
+ RendererDidNavigateInPage(params, &details->did_replace_entry);
+ break;
+ case NAVIGATION_TYPE_NEW_SUBFRAME:
+ RendererDidNavigateNewSubframe(params);
+ break;
+ case NAVIGATION_TYPE_AUTO_SUBFRAME:
+ if (!RendererDidNavigateAutoSubframe(params))
+ return false;
+ break;
+ case NAVIGATION_TYPE_NAV_IGNORE:
+ // If a pending navigation was in progress, this canceled it. We should
+ // discard it and make sure it is removed from the URL bar. After that,
+ // there is nothing we can do with this navigation, so we just return to
+ // the caller that nothing has happened.
+ if (pending_entry_) {
+ DiscardNonCommittedEntries();
+ web_contents_->NotifyNavigationStateChanged(INVALIDATE_TYPE_URL);
+ }
+ return false;
+ default:
+ NOTREACHED();
+ }
+
+ // At this point, we know that the navigation has just completed, so
+ // record the time.
+ //
+ // TODO(akalin): Use "sane time" as described in
+ // http://www.chromium.org/developers/design-documents/sane-time .
+ base::Time timestamp =
+ time_smoother_.GetSmoothedTime(get_timestamp_callback_.Run());
+ DVLOG(1) << "Navigation finished at (smoothed) timestamp "
+ << timestamp.ToInternalValue();
+
+ // We should not have a pending entry anymore. Clear it again in case any
+ // error cases above forgot to do so.
+ DiscardNonCommittedEntriesInternal();
+
+ // All committed entries should have nonempty content state so WebKit doesn't
+ // get confused when we go back to them (see the function for details).
+ DCHECK(params.page_state.IsValid());
+ NavigationEntryImpl* active_entry =
+ NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
+ active_entry->SetTimestamp(timestamp);
+ active_entry->SetPageState(params.page_state);
+ // No longer needed since content state will hold the post data if any.
+ active_entry->SetBrowserInitiatedPostData(NULL);
+
+ // Once committed, we do not need to track if the entry was initiated by
+ // the renderer.
+ active_entry->set_is_renderer_initiated(false);
+
+ // Once committed, we no longer need to track whether the session history was
+ // cleared. Navigating to this entry again shouldn't clear it again.
+ active_entry->set_should_clear_history_list(false);
+
+ // The active entry's SiteInstance should match our SiteInstance.
+ CHECK(active_entry->site_instance() == web_contents_->GetSiteInstance());
+
+ // Remember the bindings the renderer process has at this point, so that
+ // we do not grant this entry additional bindings if we come back to it.
+ active_entry->SetBindings(
+ web_contents_->GetRenderViewHost()->GetEnabledBindings());
+
+ // Now prep the rest of the details for the notification and broadcast.
+ details->entry = active_entry;
+ details->is_main_frame =
+ PageTransitionIsMainFrame(params.transition);
+ details->serialized_security_info = params.security_info;
+ details->http_status_code = params.http_status_code;
+ NotifyNavigationEntryCommitted(details);
+
+ return true;
+}
+
+NavigationType NavigationControllerImpl::ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const {
+ if (params.page_id == -1) {
+ // The renderer generates the page IDs, and so if it gives us the invalid
+ // page ID (-1) we know it didn't actually navigate. This happens in a few
+ // cases:
+ //
+ // - If a page makes a popup navigated to about blank, and then writes
+ // stuff like a subframe navigated to a real page. We'll get the commit
+ // for the subframe, but there won't be any commit for the outer page.
+ //
+ // - We were also getting these for failed loads (for example, bug 21849).
+ // The guess is that we get a "load commit" for the alternate error page,
+ // but that doesn't affect the page ID, so we get the "old" one, which
+ // could be invalid. This can also happen for a cross-site transition
+ // that causes us to swap processes. Then the error page load will be in
+ // a new process with no page IDs ever assigned (and hence a -1 value),
+ // yet the navigation controller still might have previous pages in its
+ // list.
+ //
+ // In these cases, there's nothing we can do with them, so ignore.
+ return NAVIGATION_TYPE_NAV_IGNORE;
+ }
+
+ if (params.page_id > web_contents_->GetMaxPageID()) {
+ // Greater page IDs than we've ever seen before are new pages. We may or may
+ // not have a pending entry for the page, and this may or may not be the
+ // main frame.
+ if (PageTransitionIsMainFrame(params.transition))
+ return NAVIGATION_TYPE_NEW_PAGE;
+
+ // When this is a new subframe navigation, we should have a committed page
+ // for which it's a suframe in. This may not be the case when an iframe is
+ // navigated on a popup navigated to about:blank (the iframe would be
+ // written into the popup by script on the main page). For these cases,
+ // there isn't any navigation stuff we can do, so just ignore it.
+ if (!GetLastCommittedEntry())
+ return NAVIGATION_TYPE_NAV_IGNORE;
+
+ // Valid subframe navigation.
+ return NAVIGATION_TYPE_NEW_SUBFRAME;
+ }
+
+ // We only clear the session history when navigating to a new page.
+ DCHECK(!params.history_list_was_cleared);
+
+ // Now we know that the notification is for an existing page. Find that entry.
+ int existing_entry_index = GetEntryIndexWithPageID(
+ web_contents_->GetSiteInstance(),
+ params.page_id);
+ if (existing_entry_index == -1) {
+ // The page was not found. It could have been pruned because of the limit on
+ // back/forward entries (not likely since we'll usually tell it to navigate
+ // to such entries). It could also mean that the renderer is smoking crack.
+ NOTREACHED();
+
+ // Because the unknown entry has committed, we risk showing the wrong URL in
+ // release builds. Instead, we'll kill the renderer process to be safe.
+ LOG(ERROR) << "terminating renderer for bad navigation: " << params.url;
+ RecordAction(UserMetricsAction("BadMessageTerminate_NC"));
+
+ // Temporary code so we can get more information. Format:
+ // http://url/foo.html#page1#max3#frame1#ids:2_Nx,1_1x,3_2
+ std::string temp = params.url.spec();
+ temp.append("#page");
+ temp.append(base::IntToString(params.page_id));
+ temp.append("#max");
+ temp.append(base::IntToString(web_contents_->GetMaxPageID()));
+ temp.append("#frame");
+ temp.append(base::IntToString(params.frame_id));
+ temp.append("#ids");
+ for (int i = 0; i < static_cast<int>(entries_.size()); ++i) {
+ // Append entry metadata (e.g., 3_7x):
+ // 3: page_id
+ // 7: SiteInstance ID, or N for null
+ // x: appended if not from the current SiteInstance
+ temp.append(base::IntToString(entries_[i]->GetPageID()));
+ temp.append("_");
+ if (entries_[i]->site_instance())
+ temp.append(base::IntToString(entries_[i]->site_instance()->GetId()));
+ else
+ temp.append("N");
+ if (entries_[i]->site_instance() != web_contents_->GetSiteInstance())
+ temp.append("x");
+ temp.append(",");
+ }
+ GURL url(temp);
+ static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost())->Send(
+ new ViewMsg_TempCrashWithData(url));
+ return NAVIGATION_TYPE_NAV_IGNORE;
+ }
+ NavigationEntryImpl* existing_entry = entries_[existing_entry_index].get();
+
+ if (!PageTransitionIsMainFrame(params.transition)) {
+ // All manual subframes would get new IDs and were handled above, so we
+ // know this is auto. Since the current page was found in the navigation
+ // entry list, we're guaranteed to have a last committed entry.
+ DCHECK(GetLastCommittedEntry());
+ return NAVIGATION_TYPE_AUTO_SUBFRAME;
+ }
+
+ // Anything below here we know is a main frame navigation.
+ if (pending_entry_ &&
+ !pending_entry_->is_renderer_initiated() &&
+ existing_entry != pending_entry_ &&
+ pending_entry_->GetPageID() == -1 &&
+ existing_entry == GetLastCommittedEntry()) {
+ // In this case, we have a pending entry for a URL but WebCore didn't do a
+ // new navigation. This happens when you press enter in the URL bar to
+ // reload. We will create a pending entry, but WebKit will convert it to
+ // a reload since it's the same page and not create a new entry for it
+ // (the user doesn't want to have a new back/forward entry when they do
+ // this). If this matches the last committed entry, we want to just ignore
+ // the pending entry and go back to where we were (the "existing entry").
+ return NAVIGATION_TYPE_SAME_PAGE;
+ }
+
+ // Any toplevel navigations with the same base (minus the reference fragment)
+ // are in-page navigations. We weeded out subframe navigations above. Most of
+ // the time this doesn't matter since WebKit doesn't tell us about subframe
+ // navigations that don't actually navigate, but it can happen when there is
+ // an encoding override (it always sends a navigation request).
+ if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url,
+ params.was_within_same_page,
+ NAVIGATION_TYPE_UNKNOWN)) {
+ return NAVIGATION_TYPE_IN_PAGE;
+ }
+
+ // Since we weeded out "new" navigations above, we know this is an existing
+ // (back/forward) navigation.
+ return NAVIGATION_TYPE_EXISTING_PAGE;
+}
+
+bool NavigationControllerImpl::IsRedirect(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // For main frame transition, we judge by params.transition.
+ // Otherwise, by params.redirects.
+ if (PageTransitionIsMainFrame(params.transition)) {
+ return PageTransitionIsRedirect(params.transition);
+ }
+ return params.redirects.size() > 1;
+}
+
+void NavigationControllerImpl::RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool replace_entry) {
+ NavigationEntryImpl* new_entry;
+ bool update_virtual_url;
+ // Only make a copy of the pending entry if it is appropriate for the new page
+ // that was just loaded. We verify this at a coarse grain by checking that
+ // the SiteInstance hasn't been assigned to something else.
+ if (pending_entry_ &&
+ (!pending_entry_->site_instance() ||
+ pending_entry_->site_instance() == web_contents_->GetSiteInstance())) {
+ new_entry = new NavigationEntryImpl(*pending_entry_);
+
+ // Don't use the page type from the pending entry. Some interstitial page
+ // may have set the type to interstitial. Once we commit, however, the page
+ // type must always be normal.
+ new_entry->set_page_type(PAGE_TYPE_NORMAL);
+ update_virtual_url = new_entry->update_virtual_url_with_url();
+ } else {
+ new_entry = new NavigationEntryImpl;
+
+ // Find out whether the new entry needs to update its virtual URL on URL
+ // change and set up the entry accordingly. This is needed to correctly
+ // update the virtual URL when replaceState is called after a pushState.
+ GURL url = params.url;
+ bool needs_update = false;
+ BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
+ &url, browser_context_, &needs_update);
+ new_entry->set_update_virtual_url_with_url(needs_update);
+
+ // When navigating to a new page, give the browser URL handler a chance to
+ // update the virtual URL based on the new URL. For example, this is needed
+ // to show chrome://bookmarks/#1 when the bookmarks webui extension changes
+ // the URL.
+ update_virtual_url = needs_update;
+ }
+
+ new_entry->SetURL(params.url);
+ if (update_virtual_url)
+ UpdateVirtualURLToURL(new_entry, params.url);
+ new_entry->SetReferrer(params.referrer);
+ new_entry->SetPageID(params.page_id);
+ new_entry->SetTransitionType(params.transition);
+ new_entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(web_contents_->GetSiteInstance()));
+ new_entry->SetHasPostData(params.is_post);
+ new_entry->SetPostID(params.post_id);
+ new_entry->SetOriginalRequestURL(params.original_request_url);
+ new_entry->SetIsOverridingUserAgent(params.is_overriding_user_agent);
+
+ DCHECK(!params.history_list_was_cleared || !replace_entry);
+ // The browser requested to clear the session history when it initiated the
+ // navigation. Now we know that the renderer has updated its state accordingly
+ // and it is safe to also clear the browser side history.
+ if (params.history_list_was_cleared) {
+ DiscardNonCommittedEntriesInternal();
+ entries_.clear();
+ last_committed_entry_index_ = -1;
+ }
+
+ InsertOrReplaceEntry(new_entry, replace_entry);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We should only get here for main frame navigations.
+ DCHECK(PageTransitionIsMainFrame(params.transition));
+
+ // This is a back/forward navigation. The existing page for the ID is
+ // guaranteed to exist by ClassifyNavigation, and we just need to update it
+ // with new information from the renderer.
+ int entry_index = GetEntryIndexWithPageID(web_contents_->GetSiteInstance(),
+ params.page_id);
+ DCHECK(entry_index >= 0 &&
+ entry_index < static_cast<int>(entries_.size()));
+ NavigationEntryImpl* entry = entries_[entry_index].get();
+
+ // The URL may have changed due to redirects.
+ entry->SetURL(params.url);
+ if (entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(entry, params.url);
+
+ // The redirected to page should not inherit the favicon from the previous
+ // page.
+ if (PageTransitionIsRedirect(params.transition))
+ entry->GetFavicon() = FaviconStatus();
+
+ // The site instance will normally be the same except during session restore,
+ // when no site instance will be assigned.
+ DCHECK(entry->site_instance() == NULL ||
+ entry->site_instance() == web_contents_->GetSiteInstance());
+ entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(web_contents_->GetSiteInstance()));
+
+ entry->SetHasPostData(params.is_post);
+ entry->SetPostID(params.post_id);
+
+ // The entry we found in the list might be pending if the user hit
+ // back/forward/reload. This load should commit it (since it's already in the
+ // list, we can just discard the pending pointer). We should also discard the
+ // pending entry if it corresponds to a different navigation, since that one
+ // is now likely canceled. If it is not canceled, we will treat it as a new
+ // navigation when it arrives, which is also ok.
+ //
+ // Note that we need to use the "internal" version since we don't want to
+ // actually change any other state, just kill the pointer.
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(web_contents_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // This mode implies we have a pending entry that's the same as an existing
+ // entry for this page ID. This entry is guaranteed to exist by
+ // ClassifyNavigation. All we need to do is update the existing entry.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ web_contents_->GetSiteInstance(), params.page_id);
+
+ // We assign the entry's unique ID to be that of the new one. Since this is
+ // always the result of a user action, we want to dismiss infobars, etc. like
+ // a regular user-initiated navigation.
+ existing_entry->set_unique_id(pending_entry_->GetUniqueID());
+
+ // The URL may have changed due to redirects.
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+ existing_entry->SetURL(params.url);
+
+ DiscardNonCommittedEntries();
+}
+
+void NavigationControllerImpl::RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
+ DCHECK(PageTransitionIsMainFrame(params.transition)) <<
+ "WebKit should only tell us about in-page navs for the main frame.";
+ // We're guaranteed to have an entry for this one.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ web_contents_->GetSiteInstance(), params.page_id);
+
+ // Reference fragment navigation. We're guaranteed to have the last_committed
+ // entry and it will be the same page as the new navigation (minus the
+ // reference fragments, of course). We'll update the URL of the existing
+ // entry without pruning the forward history.
+ existing_entry->SetURL(params.url);
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+
+ // This replaces the existing entry since the page ID didn't change.
+ *did_replace_entry = true;
+
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(web_contents_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (PageTransitionCoreTypeIs(params.transition,
+ PAGE_TRANSITION_AUTO_SUBFRAME)) {
+ // This is not user-initiated. Ignore.
+ DiscardNonCommittedEntriesInternal();
+ return;
+ }
+
+ // Manual subframe navigations just get the current entry cloned so the user
+ // can go back or forward to it. The actual subframe information will be
+ // stored in the page state for each of those entries. This happens out of
+ // band with the actual navigations.
+ DCHECK(GetLastCommittedEntry()) << "ClassifyNavigation should guarantee "
+ << "that a last committed entry exists.";
+ NavigationEntryImpl* new_entry = new NavigationEntryImpl(
+ *NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry()));
+ new_entry->SetPageID(params.page_id);
+ InsertOrReplaceEntry(new_entry, false);
+}
+
+bool NavigationControllerImpl::RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We're guaranteed to have a previously committed entry, and we now need to
+ // handle navigation inside of a subframe in it without creating a new entry.
+ DCHECK(GetLastCommittedEntry());
+
+ // Handle the case where we're navigating back/forward to a previous subframe
+ // navigation entry. This is case "2." in NAV_AUTO_SUBFRAME comment in the
+ // header file. In case "1." this will be a NOP.
+ int entry_index = GetEntryIndexWithPageID(
+ web_contents_->GetSiteInstance(),
+ params.page_id);
+ if (entry_index < 0 ||
+ entry_index >= static_cast<int>(entries_.size())) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Update the current navigation entry in case we're going back/forward.
+ if (entry_index != last_committed_entry_index_) {
+ last_committed_entry_index_ = entry_index;
+ DiscardNonCommittedEntriesInternal();
+ return true;
+ }
+
+ // We do not need to discard the pending entry in this case, since we will
+ // not generate commit notifications for this auto-subframe navigation.
+ return false;
+}
+
+int NavigationControllerImpl::GetIndexOfEntry(
+ const NavigationEntryImpl* entry) const {
+ const NavigationEntries::const_iterator i(std::find(
+ entries_.begin(),
+ entries_.end(),
+ entry));
+ return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
+}
+
+bool NavigationControllerImpl::IsURLInPageNavigation(
+ const GURL& url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) const {
+ NavigationEntry* last_committed = GetLastCommittedEntry();
+ return last_committed && AreURLsInPageNavigation(
+ last_committed->GetURL(), url, renderer_says_in_page, navigation_type);
+}
+
+void NavigationControllerImpl::CopyStateFrom(
+ const NavigationController& temp) {
+ const NavigationControllerImpl& source =
+ static_cast<const NavigationControllerImpl&>(temp);
+ // Verify that we look new.
+ DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
+
+ if (source.GetEntryCount() == 0)
+ return; // Nothing new to do.
+
+ needs_reload_ = true;
+ InsertEntriesFrom(source, source.GetEntryCount());
+
+ for (SessionStorageNamespaceMap::const_iterator it =
+ source.session_storage_namespace_map_.begin();
+ it != source.session_storage_namespace_map_.end();
+ ++it) {
+ SessionStorageNamespaceImpl* source_namespace =
+ static_cast<SessionStorageNamespaceImpl*>(it->second.get());
+ session_storage_namespace_map_[it->first] = source_namespace->Clone();
+ }
+
+ FinishRestore(source.last_committed_entry_index_, RESTORE_CURRENT_SESSION);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ web_contents_->CopyMaxPageIDsFrom(source.web_contents());
+}
+
+void NavigationControllerImpl::CopyStateFromAndPrune(
+ NavigationController* temp) {
+ // It is up to callers to check the invariants before calling this.
+ CHECK(CanPruneAllButVisible());
+
+ NavigationControllerImpl* source =
+ static_cast<NavigationControllerImpl*>(temp);
+ // The SiteInstance and page_id of the last committed entry needs to be
+ // remembered at this point, in case there is only one committed entry
+ // and it is pruned. We use a scoped_refptr to ensure the SiteInstance
+ // can't be freed during this time period.
+ NavigationEntryImpl* last_committed =
+ NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
+ scoped_refptr<SiteInstance> site_instance(
+ last_committed->site_instance());
+ int32 minimum_page_id = last_committed->GetPageID();
+ int32 max_page_id =
+ web_contents_->GetMaxPageIDForSiteInstance(site_instance.get());
+
+ // Remove all the entries leaving the active entry.
+ PruneAllButVisibleInternal();
+
+ // We now have one entry, possibly with a new pending entry. Ensure that
+ // adding the entries from source won't put us over the limit.
+ DCHECK_EQ(1, GetEntryCount());
+ source->PruneOldestEntryIfFull();
+
+ // Insert the entries from source. Don't use source->GetCurrentEntryIndex as
+ // we don't want to copy over the transient entry. Ignore any pending entry,
+ // since it has not committed in source.
+ int max_source_index = source->last_committed_entry_index_;
+ if (max_source_index == -1)
+ max_source_index = source->GetEntryCount();
+ else
+ max_source_index++;
+ InsertEntriesFrom(*source, max_source_index);
+
+ // Adjust indices such that the last entry and pending are at the end now.
+ last_committed_entry_index_ = GetEntryCount() - 1;
+
+ web_contents_->SetHistoryLengthAndPrune(site_instance.get(),
+ max_source_index,
+ minimum_page_id);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ web_contents_->CopyMaxPageIDsFrom(source->web_contents());
+
+ // If there is a last committed entry, be sure to include it in the new
+ // max page ID map.
+ if (max_page_id > -1) {
+ web_contents_->UpdateMaxPageIDForSiteInstance(site_instance.get(),
+ max_page_id);
+ }
+}
+
+bool NavigationControllerImpl::CanPruneAllButVisible() {
+ // If there is no last committed entry, we cannot prune. Even if there is a
+ // pending entry, it may not commit, leaving this WebContents blank, despite
+ // possibly giving it new entries via CopyStateFromAndPrune.
+ if (last_committed_entry_index_ == -1)
+ return false;
+
+ // We cannot prune if there is a pending entry at an existing entry index.
+ // It may not commit, so we have to keep the last committed entry, and thus
+ // there is no sensible place to keep the pending entry. It is ok to have
+ // a new pending entry, which can optionally commit as a new navigation.
+ if (pending_entry_index_ != -1)
+ return false;
+
+ // We should not prune if we are currently showing a transient entry.
+ if (transient_entry_index_ != -1)
+ return false;
+
+ return true;
+}
+
+void NavigationControllerImpl::PruneAllButVisible() {
+ PruneAllButVisibleInternal();
+
+ // We should still have a last committed entry.
+ DCHECK_NE(-1, last_committed_entry_index_);
+
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(GetActiveEntry());
+ // We pass 0 instead of GetEntryCount() for the history_length parameter of
+ // SetHistoryLengthAndPrune, because it will create history_length additional
+ // history entries.
+ // TODO(jochen): This API is confusing and we should clean it up.
+ // http://crbug.com/178491
+ web_contents_->SetHistoryLengthAndPrune(
+ entry->site_instance(), 0, entry->GetPageID());
+}
+
+void NavigationControllerImpl::PruneAllButVisibleInternal() {
+ // It is up to callers to check the invariants before calling this.
+ CHECK(CanPruneAllButVisible());
+
+ // Erase all entries but the last committed entry. There may still be a
+ // new pending entry after this.
+ entries_.erase(entries_.begin(),
+ entries_.begin() + last_committed_entry_index_);
+ entries_.erase(entries_.begin() + 1, entries_.end());
+ last_committed_entry_index_ = 0;
+}
+
+void NavigationControllerImpl::ClearAllScreenshots() {
+ screenshot_manager_->ClearAllScreenshots();
+}
+
+void NavigationControllerImpl::SetSessionStorageNamespace(
+ const std::string& partition_id,
+ SessionStorageNamespace* session_storage_namespace) {
+ if (!session_storage_namespace)
+ return;
+
+ // We can't overwrite an existing SessionStorage without violating spec.
+ // Attempts to do so may give a tab access to another tab's session storage
+ // so die hard on an error.
+ bool successful_insert = session_storage_namespace_map_.insert(
+ make_pair(partition_id,
+ static_cast<SessionStorageNamespaceImpl*>(
+ session_storage_namespace)))
+ .second;
+ CHECK(successful_insert) << "Cannot replace existing SessionStorageNamespace";
+}
+
+void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) {
+ max_restored_page_id_ = max_id;
+}
+
+int32 NavigationControllerImpl::GetMaxRestoredPageID() const {
+ return max_restored_page_id_;
+}
+
+SessionStorageNamespace*
+NavigationControllerImpl::GetSessionStorageNamespace(SiteInstance* instance) {
+ std::string partition_id;
+ if (instance) {
+ // TODO(ajwong): When GetDefaultSessionStorageNamespace() goes away, remove
+ // this if statement so |instance| must not be NULL.
+ partition_id =
+ GetContentClient()->browser()->GetStoragePartitionIdForSite(
+ browser_context_, instance->GetSiteURL());
+ }
+
+ SessionStorageNamespaceMap::const_iterator it =
+ session_storage_namespace_map_.find(partition_id);
+ if (it != session_storage_namespace_map_.end())
+ return it->second.get();
+
+ // Create one if no one has accessed session storage for this partition yet.
+ //
+ // TODO(ajwong): Should this use the |partition_id| directly rather than
+ // re-lookup via |instance|? http://crbug.com/142685
+ StoragePartition* partition =
+ BrowserContext::GetStoragePartition(browser_context_, instance);
+ SessionStorageNamespaceImpl* session_storage_namespace =
+ new SessionStorageNamespaceImpl(
+ static_cast<DOMStorageContextWrapper*>(
+ partition->GetDOMStorageContext()));
+ session_storage_namespace_map_[partition_id] = session_storage_namespace;
+
+ return session_storage_namespace;
+}
+
+SessionStorageNamespace*
+NavigationControllerImpl::GetDefaultSessionStorageNamespace() {
+ // TODO(ajwong): Remove if statement in GetSessionStorageNamespace().
+ return GetSessionStorageNamespace(NULL);
+}
+
+const SessionStorageNamespaceMap&
+NavigationControllerImpl::GetSessionStorageNamespaceMap() const {
+ return session_storage_namespace_map_;
+}
+
+bool NavigationControllerImpl::NeedsReload() const {
+ return needs_reload_;
+}
+
+void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) {
+ DCHECK(index < GetEntryCount());
+ DCHECK(index != last_committed_entry_index_);
+
+ DiscardNonCommittedEntries();
+
+ entries_.erase(entries_.begin() + index);
+ if (last_committed_entry_index_ > index)
+ last_committed_entry_index_--;
+}
+
+void NavigationControllerImpl::DiscardNonCommittedEntries() {
+ bool transient = transient_entry_index_ != -1;
+ DiscardNonCommittedEntriesInternal();
+
+ // If there was a transient entry, invalidate everything so the new active
+ // entry state is shown.
+ if (transient) {
+ web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
+ }
+}
+
+NavigationEntry* NavigationControllerImpl::GetPendingEntry() const {
+ return pending_entry_;
+}
+
+int NavigationControllerImpl::GetPendingEntryIndex() const {
+ return pending_entry_index_;
+}
+
+void NavigationControllerImpl::InsertOrReplaceEntry(NavigationEntryImpl* entry,
+ bool replace) {
+ DCHECK(entry->GetTransitionType() != PAGE_TRANSITION_AUTO_SUBFRAME);
+
+ // Copy the pending entry's unique ID to the committed entry.
+ // I don't know if pending_entry_index_ can be other than -1 here.
+ const NavigationEntryImpl* const pending_entry =
+ (pending_entry_index_ == -1) ?
+ pending_entry_ : entries_[pending_entry_index_].get();
+ if (pending_entry)
+ entry->set_unique_id(pending_entry->GetUniqueID());
+
+ DiscardNonCommittedEntriesInternal();
+
+ int current_size = static_cast<int>(entries_.size());
+
+ if (current_size > 0) {
+ // Prune any entries which are in front of the current entry.
+ // Also prune the current entry if we are to replace the current entry.
+ // last_committed_entry_index_ must be updated here since calls to
+ // NotifyPrunedEntries() below may re-enter and we must make sure
+ // last_committed_entry_index_ is not left in an invalid state.
+ if (replace)
+ --last_committed_entry_index_;
+
+ int num_pruned = 0;
+ while (last_committed_entry_index_ < (current_size - 1)) {
+ num_pruned++;
+ entries_.pop_back();
+ current_size--;
+ }
+ if (num_pruned > 0) // Only notify if we did prune something.
+ NotifyPrunedEntries(this, false, num_pruned);
+ }
+
+ PruneOldestEntryIfFull();
+
+ entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
+ last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
+
+ // This is a new page ID, so we need everybody to know about it.
+ web_contents_->UpdateMaxPageID(entry->GetPageID());
+}
+
+void NavigationControllerImpl::PruneOldestEntryIfFull() {
+ if (entries_.size() >= max_entry_count()) {
+ DCHECK_EQ(max_entry_count(), entries_.size());
+ DCHECK_GT(last_committed_entry_index_, 0);
+ RemoveEntryAtIndex(0);
+ NotifyPrunedEntries(this, true, 1);
+ }
+}
+
+void NavigationControllerImpl::NavigateToPendingEntry(ReloadType reload_type) {
+ needs_reload_ = false;
+
+ // If we were navigating to a slow-to-commit page, and the user performs
+ // a session history navigation to the last committed page, RenderViewHost
+ // will force the throbber to start, but WebKit will essentially ignore the
+ // navigation, and won't send a message to stop the throbber. To prevent this
+ // from happening, we drop the navigation here and stop the slow-to-commit
+ // page from loading (which would normally happen during the navigation).
+ if (pending_entry_index_ != -1 &&
+ pending_entry_index_ == last_committed_entry_index_ &&
+ (entries_[pending_entry_index_]->restore_type() ==
+ NavigationEntryImpl::RESTORE_NONE) &&
+ (entries_[pending_entry_index_]->GetTransitionType() &
+ PAGE_TRANSITION_FORWARD_BACK)) {
+ web_contents_->Stop();
+
+ // If an interstitial page is showing, we want to close it to get back
+ // to what was showing before.
+ if (web_contents_->GetInterstitialPage())
+ web_contents_->GetInterstitialPage()->DontProceed();
+
+ DiscardNonCommittedEntries();
+ return;
+ }
+
+ // If an interstitial page is showing, the previous renderer is blocked and
+ // cannot make new requests. Unblock (and disable) it to allow this
+ // navigation to succeed. The interstitial will stay visible until the
+ // resulting DidNavigate.
+ if (web_contents_->GetInterstitialPage()) {
+ static_cast<InterstitialPageImpl*>(web_contents_->GetInterstitialPage())->
+ CancelForNavigation();
+ }
+
+ // For session history navigations only the pending_entry_index_ is set.
+ if (!pending_entry_) {
+ DCHECK_NE(pending_entry_index_, -1);
+ pending_entry_ = entries_[pending_entry_index_].get();
+ }
+
+ if (!web_contents_->NavigateToPendingEntry(reload_type))
+ DiscardNonCommittedEntries();
+
+ // If the entry is being restored and doesn't have a SiteInstance yet, fill
+ // it in now that we know. This allows us to find the entry when it commits.
+ // This works for browser-initiated navigations. We handle renderer-initiated
+ // navigations to restored entries in WebContentsImpl::OnGoToEntryAtOffset.
+ if (pending_entry_ && !pending_entry_->site_instance() &&
+ pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
+ pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>(
+ web_contents_->GetPendingSiteInstance()));
+ pending_entry_->set_restore_type(NavigationEntryImpl::RESTORE_NONE);
+ }
+}
+
+void NavigationControllerImpl::NotifyNavigationEntryCommitted(
+ LoadCommittedDetails* details) {
+ details->entry = GetActiveEntry();
+ NotificationDetails notification_details =
+ Details<LoadCommittedDetails>(details);
+
+ // We need to notify the ssl_manager_ before the web_contents_ so the
+ // location bar will have up-to-date information about the security style
+ // when it wants to draw. See http://crbug.com/11157
+ ssl_manager_.DidCommitProvisionalLoad(notification_details);
+
+ // TODO(pkasting): http://b/1113079 Probably these explicit notification paths
+ // should be removed, and interested parties should just listen for the
+ // notification below instead.
+ web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
+
+ web_contents_->NotifyNavigationEntryCommitted(*details);
+
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(this),
+ notification_details);
+}
+
+// static
+size_t NavigationControllerImpl::max_entry_count() {
+ if (max_entry_count_for_testing_ != kMaxEntryCountForTestingNotSet)
+ return max_entry_count_for_testing_;
+ return kMaxSessionHistoryEntries;
+}
+
+void NavigationControllerImpl::SetActive(bool is_active) {
+ if (is_active && needs_reload_)
+ LoadIfNecessary();
+}
+
+void NavigationControllerImpl::LoadIfNecessary() {
+ if (!needs_reload_)
+ return;
+
+ // Calling Reload() results in ignoring state, and not loading.
+ // Explicitly use NavigateToPendingEntry so that the renderer uses the
+ // cached state.
+ pending_entry_index_ = last_committed_entry_index_;
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::NotifyEntryChanged(const NavigationEntry* entry,
+ int index) {
+ EntryChangedDetails det;
+ det.changed_entry = entry;
+ det.index = index;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_CHANGED,
+ Source<NavigationController>(this),
+ Details<EntryChangedDetails>(&det));
+}
+
+void NavigationControllerImpl::FinishRestore(int selected_index,
+ RestoreType type) {
+ DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
+ ConfigureEntriesForRestore(&entries_, type);
+
+ SetMaxRestoredPageID(static_cast<int32>(GetEntryCount()));
+
+ last_committed_entry_index_ = selected_index;
+}
+
+void NavigationControllerImpl::DiscardNonCommittedEntriesInternal() {
+ if (pending_entry_index_ == -1)
+ delete pending_entry_;
+ pending_entry_ = NULL;
+ pending_entry_index_ = -1;
+
+ DiscardTransientEntry();
+}
+
+void NavigationControllerImpl::DiscardTransientEntry() {
+ if (transient_entry_index_ == -1)
+ return;
+ entries_.erase(entries_.begin() + transient_entry_index_);
+ if (last_committed_entry_index_ > transient_entry_index_)
+ last_committed_entry_index_--;
+ transient_entry_index_ = -1;
+}
+
+int NavigationControllerImpl::GetEntryIndexWithPageID(
+ SiteInstance* instance, int32 page_id) const {
+ for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
+ if ((entries_[i]->site_instance() == instance) &&
+ (entries_[i]->GetPageID() == page_id))
+ return i;
+ }
+ return -1;
+}
+
+NavigationEntry* NavigationControllerImpl::GetTransientEntry() const {
+ if (transient_entry_index_ == -1)
+ return NULL;
+ return entries_[transient_entry_index_].get();
+}
+
+void NavigationControllerImpl::SetTransientEntry(NavigationEntry* entry) {
+ // Discard any current transient entry, we can only have one at a time.
+ int index = 0;
+ if (last_committed_entry_index_ != -1)
+ index = last_committed_entry_index_ + 1;
+ DiscardTransientEntry();
+ entries_.insert(
+ entries_.begin() + index, linked_ptr<NavigationEntryImpl>(
+ NavigationEntryImpl::FromNavigationEntry(entry)));
+ transient_entry_index_ = index;
+ web_contents_->NotifyNavigationStateChanged(kInvalidateAll);
+}
+
+void NavigationControllerImpl::InsertEntriesFrom(
+ const NavigationControllerImpl& source,
+ int max_index) {
+ DCHECK_LE(max_index, source.GetEntryCount());
+ size_t insert_index = 0;
+ for (int i = 0; i < max_index; i++) {
+ // When cloning a tab, copy all entries except interstitial pages
+ if (source.entries_[i].get()->GetPageType() !=
+ PAGE_TYPE_INTERSTITIAL) {
+ entries_.insert(entries_.begin() + insert_index++,
+ linked_ptr<NavigationEntryImpl>(
+ new NavigationEntryImpl(*source.entries_[i])));
+ }
+ }
+}
+
+void NavigationControllerImpl::SetGetTimestampCallbackForTest(
+ const base::Callback<base::Time()>& get_timestamp_callback) {
+ get_timestamp_callback_ = get_timestamp_callback;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/navigation_controller_impl.h b/chromium/content/browser/web_contents/navigation_controller_impl.h
new file mode 100644
index 00000000000..bef3be2202f
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_controller_impl.h
@@ -0,0 +1,409 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_type.h"
+
+struct ViewHostMsg_FrameNavigate_Params;
+
+namespace content {
+class NavigationEntryImpl;
+class RenderViewHost;
+class WebContentsImpl;
+class WebContentsScreenshotManager;
+class SiteInstance;
+struct LoadCommittedDetails;
+
+class CONTENT_EXPORT NavigationControllerImpl
+ : public NON_EXPORTED_BASE(NavigationController) {
+ public:
+ NavigationControllerImpl(
+ WebContentsImpl* web_contents,
+ BrowserContext* browser_context);
+ virtual ~NavigationControllerImpl();
+
+ // NavigationController implementation:
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual void SetBrowserContext(
+ BrowserContext* browser_context) OVERRIDE;
+ virtual void Restore(
+ int selected_navigation,
+ RestoreType type,
+ std::vector<NavigationEntry*>* entries) OVERRIDE;
+ virtual NavigationEntry* GetActiveEntry() const OVERRIDE;
+ virtual NavigationEntry* GetVisibleEntry() const OVERRIDE;
+ virtual int GetCurrentEntryIndex() const OVERRIDE;
+ virtual NavigationEntry* GetLastCommittedEntry() const OVERRIDE;
+ virtual int GetLastCommittedEntryIndex() const OVERRIDE;
+ virtual bool CanViewSource() const OVERRIDE;
+ virtual int GetEntryCount() const OVERRIDE;
+ virtual NavigationEntry* GetEntryAtIndex(int index) const OVERRIDE;
+ virtual NavigationEntry* GetEntryAtOffset(int offset) const OVERRIDE;
+ virtual void DiscardNonCommittedEntries() OVERRIDE;
+ virtual NavigationEntry* GetPendingEntry() const OVERRIDE;
+ virtual int GetPendingEntryIndex() const OVERRIDE;
+ virtual NavigationEntry* GetTransientEntry() const OVERRIDE;
+ virtual void SetTransientEntry(NavigationEntry* entry) OVERRIDE;
+ virtual void LoadURL(const GURL& url,
+ const Referrer& referrer,
+ PageTransition type,
+ const std::string& extra_headers) OVERRIDE;
+ virtual void LoadURLWithParams(const LoadURLParams& params) OVERRIDE;
+ virtual void LoadIfNecessary() OVERRIDE;
+ virtual bool CanGoBack() const OVERRIDE;
+ virtual bool CanGoForward() const OVERRIDE;
+ virtual bool CanGoToOffset(int offset) const OVERRIDE;
+ virtual void GoBack() OVERRIDE;
+ virtual void GoForward() OVERRIDE;
+ virtual void GoToIndex(int index) OVERRIDE;
+ virtual void GoToOffset(int offset) OVERRIDE;
+ virtual bool RemoveEntryAtIndex(int index) OVERRIDE;
+ virtual const SessionStorageNamespaceMap&
+ GetSessionStorageNamespaceMap() const OVERRIDE;
+ virtual SessionStorageNamespace*
+ GetDefaultSessionStorageNamespace() OVERRIDE;
+ virtual void SetMaxRestoredPageID(int32 max_id) OVERRIDE;
+ virtual int32 GetMaxRestoredPageID() const OVERRIDE;
+ virtual bool NeedsReload() const OVERRIDE;
+ virtual void CancelPendingReload() OVERRIDE;
+ virtual void ContinuePendingReload() OVERRIDE;
+ virtual bool IsInitialNavigation() const OVERRIDE;
+ virtual void Reload(bool check_for_repost) OVERRIDE;
+ virtual void ReloadIgnoringCache(bool check_for_repost) OVERRIDE;
+ virtual void ReloadOriginalRequestURL(bool check_for_repost) OVERRIDE;
+ virtual void NotifyEntryChanged(const NavigationEntry* entry,
+ int index) OVERRIDE;
+ virtual void CopyStateFrom(
+ const NavigationController& source) OVERRIDE;
+ virtual void CopyStateFromAndPrune(
+ NavigationController* source) OVERRIDE;
+ virtual bool CanPruneAllButVisible() OVERRIDE;
+ virtual void PruneAllButVisible() OVERRIDE;
+ virtual void ClearAllScreenshots() OVERRIDE;
+
+ // The session storage namespace that all child RenderViews belonging to
+ // |instance| should use.
+ SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance);
+
+ // Returns the index of the specified entry, or -1 if entry is not contained
+ // in this NavigationController.
+ int GetIndexOfEntry(const NavigationEntryImpl* entry) const;
+
+ // Return the index of the entry with the corresponding instance and page_id,
+ // or -1 if not found.
+ int GetEntryIndexWithPageID(SiteInstance* instance,
+ int32 page_id) const;
+
+ // Return the entry with the corresponding instance and page_id, or NULL if
+ // not found.
+ NavigationEntryImpl* GetEntryWithPageID(
+ SiteInstance* instance,
+ int32 page_id) const;
+
+ // WebContentsImpl -----------------------------------------------------------
+
+ WebContentsImpl* web_contents() const {
+ return web_contents_;
+ }
+
+ // For use by WebContentsImpl ------------------------------------------------
+
+ // Allow renderer-initiated navigations to create a pending entry when the
+ // provisional load starts.
+ void SetPendingEntry(content::NavigationEntryImpl* entry);
+
+ // Handles updating the navigation state after the renderer has navigated.
+ // This is used by the WebContentsImpl.
+ //
+ // If a new entry is created, it will return true and will have filled the
+ // given details structure and broadcast the NOTIFY_NAV_ENTRY_COMMITTED
+ // notification. The caller can then use the details without worrying about
+ // listening for the notification.
+ //
+ // In the case that nothing has changed, the details structure is undefined
+ // and it will return false.
+ bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params,
+ LoadCommittedDetails* details);
+
+ // Notifies us that we just became active. This is used by the WebContentsImpl
+ // so that we know to load URLs that were pending as "lazy" loads.
+ void SetActive(bool is_active);
+
+ // Returns true if the given URL would be an in-page navigation (i.e. only
+ // the reference fragment is different) from the "last committed entry". We do
+ // not compare it against the "active entry" since the active entry can be
+ // pending and in page navigations only happen on committed pages. If there
+ // is no last committed entry, then nothing will be in-page.
+ //
+ // Special note: if the URLs are the same, it does NOT automatically count as
+ // an in-page navigation. Neither does an input URL that has no ref, even if
+ // the rest is the same. This may seem weird, but when we're considering
+ // whether a navigation happened without loading anything, the same URL could
+ // be a reload, while only a different ref would be in-page (pages can't clear
+ // refs without reload, only change to "#" which we don't count as empty).
+ bool IsURLInPageNavigation(const GURL& url) const {
+ return IsURLInPageNavigation(url, false, NAVIGATION_TYPE_UNKNOWN);
+ }
+
+ // The situation is made murkier by history.replaceState(), which could
+ // provide the same URL as part of an in-page navigation, not a reload. So
+ // we need this form which lets the (untrustworthy) renderer resolve the
+ // ambiguity, but only when the URLs are equal. This should be safe since the
+ // origin isn't changing.
+ bool IsURLInPageNavigation(
+ const GURL& url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) const;
+
+ // Sets the SessionStorageNamespace for the given |partition_id|. This is
+ // used during initialization of a new NavigationController to allow
+ // pre-population of the SessionStorageNamespace objects. Session restore,
+ // prerendering, and the implementaion of window.open() are the primary users
+ // of this API.
+ //
+ // Calling this function when a SessionStorageNamespace has already been
+ // associated with a |partition_id| will CHECK() fail.
+ void SetSessionStorageNamespace(
+ const std::string& partition_id,
+ SessionStorageNamespace* session_storage_namespace);
+
+ // Random data ---------------------------------------------------------------
+
+ SSLManager* ssl_manager() { return &ssl_manager_; }
+
+ // Maximum number of entries before we start removing entries from the front.
+ static void set_max_entry_count_for_testing(size_t max_entry_count) {
+ max_entry_count_for_testing_ = max_entry_count;
+ }
+ static size_t max_entry_count();
+
+ void SetGetTimestampCallbackForTest(
+ const base::Callback<base::Time()>& get_timestamp_callback);
+
+ // Takes a screenshot of the page at the current state.
+ void TakeScreenshot();
+
+ // Sets the screenshot manager for this NavigationControllerImpl. The
+ // controller takes ownership of the screenshot manager and destroys it when
+ // a new screenshot-manager is set, or when the controller is destroyed.
+ // Setting a NULL manager recreates the default screenshot manager and uses
+ // that.
+ void SetScreenshotManager(WebContentsScreenshotManager* manager);
+
+ private:
+ friend class RestoreHelper;
+ friend class WebContentsImpl; // For invoking OnReservedPageIDRange.
+
+ FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest,
+ PurgeScreenshot);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, Basic);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, SingleDuplicate);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ManyDuplicates);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ClockBackwardsJump);
+
+ // Helper class to smooth out runs of duplicate timestamps while still
+ // allowing time to jump backwards.
+ class CONTENT_EXPORT TimeSmoother {
+ public:
+ // Returns |t| with possibly some time added on.
+ base::Time GetSmoothedTime(base::Time t);
+
+ private:
+ // |low_water_mark_| is the first time in a sequence of adjusted
+ // times and |high_water_mark_| is the last.
+ base::Time low_water_mark_;
+ base::Time high_water_mark_;
+ };
+
+ // Classifies the given renderer navigation (see the NavigationType enum).
+ NavigationType ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const;
+
+ // Causes the controller to load the specified entry. The function assumes
+ // ownership of the pointer since it is put in the navigation list.
+ // NOTE: Do not pass an entry that the controller already owns!
+ void LoadEntry(NavigationEntryImpl* entry);
+
+ // Handlers for the different types of navigation types. They will actually
+ // handle the navigations corresponding to the different NavClasses above.
+ // They will NOT broadcast the commit notification, that should be handled by
+ // the caller.
+ //
+ // RendererDidNavigateAutoSubframe is special, it may not actually change
+ // anything if some random subframe is loaded. It will return true if anything
+ // changed, or false if not.
+ //
+ // The functions taking |did_replace_entry| will fill into the given variable
+ // whether the last entry has been replaced or not.
+ // See LoadCommittedDetails.did_replace_entry.
+ void RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool replace_entry);
+ void RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry);
+ void RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ bool RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Helper function for code shared between Reload() and ReloadIgnoringCache().
+ void ReloadInternal(bool check_for_repost, ReloadType reload_type);
+
+ // Actually issues the navigation held in pending_entry.
+ void NavigateToPendingEntry(ReloadType reload_type);
+
+ // Allows the derived class to issue notifications that a load has been
+ // committed. This will fill in the active entry to the details structure.
+ void NotifyNavigationEntryCommitted(LoadCommittedDetails* details);
+
+ // Updates the virtual URL of an entry to match a new URL, for cases where
+ // the real renderer URL is derived from the virtual URL, like view-source:
+ void UpdateVirtualURLToURL(NavigationEntryImpl* entry,
+ const GURL& new_url);
+
+ // Invoked after session/tab restore or cloning a tab. Resets the transition
+ // type of the entries, updates the max page id and creates the active
+ // contents.
+ void FinishRestore(int selected_index, RestoreType type);
+
+ // Inserts a new entry or replaces the current entry with a new one, removing
+ // all entries after it. The new entry will become the active one.
+ void InsertOrReplaceEntry(NavigationEntryImpl* entry, bool replace);
+
+ // Removes the entry at |index|, as long as it is not the current entry.
+ void RemoveEntryAtIndexInternal(int index);
+
+ // Discards the pending and transient entries.
+ void DiscardNonCommittedEntriesInternal();
+
+ // Discards the transient entry.
+ void DiscardTransientEntry();
+
+ // If we have the maximum number of entries, remove the oldest one in
+ // preparation to add another.
+ void PruneOldestEntryIfFull();
+
+ // Removes all entries except the last committed entry. If there is a new
+ // pending navigation it is preserved. In contrast to PruneAllButVisible()
+ // this does not update the session history of the RenderView. Callers
+ // must ensure that |CanPruneAllButVisible| returns true before calling this.
+ void PruneAllButVisibleInternal();
+
+ // Returns true if the navigation is redirect.
+ bool IsRedirect(const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Returns true if the navigation is likley to be automatic rather than
+ // user-initiated.
+ bool IsLikelyAutoNavigation(base::TimeTicks now);
+
+ // Inserts up to |max_index| entries from |source| into this. This does NOT
+ // adjust any of the members that reference entries_
+ // (last_committed_entry_index_, pending_entry_index_ or
+ // transient_entry_index_).
+ void InsertEntriesFrom(const NavigationControllerImpl& source, int max_index);
+
+ // Returns the navigation index that differs from the current entry by the
+ // specified |offset|. The index returned is not guaranteed to be valid.
+ int GetIndexForOffset(int offset) const;
+
+ // ---------------------------------------------------------------------------
+
+ // The user browser context associated with this controller.
+ BrowserContext* browser_context_;
+
+ // List of NavigationEntry for this tab
+ typedef std::vector<linked_ptr<NavigationEntryImpl> > NavigationEntries;
+ NavigationEntries entries_;
+
+ // An entry we haven't gotten a response for yet. This will be discarded
+ // when we navigate again. It's used only so we know what the currently
+ // displayed tab is.
+ //
+ // This may refer to an item in the entries_ list if the pending_entry_index_
+ // == -1, or it may be its own entry that should be deleted. Be careful with
+ // the memory management.
+ NavigationEntryImpl* pending_entry_;
+
+ // currently visible entry
+ int last_committed_entry_index_;
+
+ // index of pending entry if it is in entries_, or -1 if pending_entry_ is a
+ // new entry (created by LoadURL).
+ int pending_entry_index_;
+
+ // The index for the entry that is shown until a navigation occurs. This is
+ // used for interstitial pages. -1 if there are no such entry.
+ // Note that this entry really appears in the list of entries, but only
+ // temporarily (until the next navigation). Any index pointing to an entry
+ // after the transient entry will become invalid if you navigate forward.
+ int transient_entry_index_;
+
+ // The WebContents associated with the controller. Possibly NULL during
+ // setup.
+ WebContentsImpl* web_contents_;
+
+ // The max restored page ID in this controller, if it was restored. We must
+ // store this so that WebContentsImpl can tell any renderer in charge of one
+ // of the restored entries to update its max page ID.
+ int32 max_restored_page_id_;
+
+ // Manages the SSL security UI
+ SSLManager ssl_manager_;
+
+ // Whether we need to be reloaded when made active.
+ bool needs_reload_;
+
+ // Whether this is the initial navigation.
+ // Becomes false when initial navigation commits.
+ bool is_initial_navigation_;
+
+ // Used to find the appropriate SessionStorageNamespace for the storage
+ // partition of a NavigationEntry.
+ //
+ // A NavigationController may contain NavigationEntries that correspond to
+ // different StoragePartitions. Even though they are part of the same
+ // NavigationController, only entries in the same StoragePartition may
+ // share session storage state with one another.
+ SessionStorageNamespaceMap session_storage_namespace_map_;
+
+ // The maximum number of entries that a navigation controller can store.
+ static size_t max_entry_count_for_testing_;
+
+ // If a repost is pending, its type (RELOAD or RELOAD_IGNORING_CACHE),
+ // NO_RELOAD otherwise.
+ ReloadType pending_reload_;
+
+ // Used to get timestamps for newly-created navigation entries.
+ base::Callback<base::Time()> get_timestamp_callback_;
+
+ // Used to smooth out timestamps from |get_timestamp_callback_|.
+ // Without this, whenever there is a run of redirects or
+ // code-generated navigations, those navigations may occur within
+ // the timer resolution, leading to things sometimes showing up in
+ // the wrong order in the history view.
+ TimeSmoother time_smoother_;
+
+ scoped_ptr<WebContentsScreenshotManager> screenshot_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_CONTROLLER_IMPL_H_
diff --git a/chromium/content/browser/web_contents/navigation_controller_impl_unittest.cc b/chromium/content/browser/web_contents/navigation_controller_impl_unittest.cc
new file mode 100644
index 00000000000..7a0b03b2bf0
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_controller_impl_unittest.cc
@@ -0,0 +1,3960 @@
+// 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 "base/basictypes.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+// These are only used for commented out tests. If someone wants to enable
+// them, they should be moved to chrome first.
+// #include "chrome/browser/history/history_service.h"
+// #include "chrome/browser/profiles/profile_manager.h"
+// #include "chrome/browser/sessions/session_service.h"
+// #include "chrome/browser/sessions/session_service_factory.h"
+// #include "chrome/browser/sessions/session_service_test_helper.h"
+// #include "chrome/browser/sessions/session_types.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_contents_screenshot_manager.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/page_state.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_notification_tracker.h"
+#include "content/public/test/test_utils.h"
+#include "content/test/test_web_contents.h"
+#include "net/base/net_util.h"
+#include "skia/ext/platform_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+
+namespace {
+
+// Creates an image with a 1x1 SkBitmap of the specified |color|.
+gfx::Image CreateImage(SkColor color) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ bitmap.allocPixels();
+ bitmap.eraseColor(color);
+ return gfx::Image::CreateFrom1xBitmap(bitmap);
+}
+
+// Returns true if images |a| and |b| have the same pixel data.
+bool DoImagesMatch(const gfx::Image& a, const gfx::Image& b) {
+ // Assume that if the 1x bitmaps match, the images match.
+ SkBitmap a_bitmap = a.AsBitmap();
+ SkBitmap b_bitmap = b.AsBitmap();
+
+ if (a_bitmap.width() != b_bitmap.width() ||
+ a_bitmap.height() != b_bitmap.height()) {
+ return false;
+ }
+ SkAutoLockPixels a_bitmap_lock(a_bitmap);
+ SkAutoLockPixels b_bitmap_lock(b_bitmap);
+ return memcmp(a_bitmap.getPixels(),
+ b_bitmap.getPixels(),
+ a_bitmap.getSize()) == 0;
+}
+
+class MockScreenshotManager : public content::WebContentsScreenshotManager {
+ public:
+ explicit MockScreenshotManager(content::NavigationControllerImpl* owner)
+ : content::WebContentsScreenshotManager(owner),
+ encoding_screenshot_in_progress_(false) {
+ }
+
+ virtual ~MockScreenshotManager() {
+ }
+
+ void TakeScreenshotFor(content::NavigationEntryImpl* entry) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ bitmap.allocPixels();
+ bitmap.eraseRGB(0, 0, 0);
+ encoding_screenshot_in_progress_ = true;
+ OnScreenshotTaken(entry->GetUniqueID(), true, bitmap);
+ WaitUntilScreenshotIsReady();
+ }
+
+ int GetScreenshotCount() {
+ return content::WebContentsScreenshotManager::GetScreenshotCount();
+ }
+
+ void WaitUntilScreenshotIsReady() {
+ if (!encoding_screenshot_in_progress_)
+ return;
+ message_loop_runner_ = new content::MessageLoopRunner;
+ message_loop_runner_->Run();
+ }
+
+ private:
+ // Overridden from content::WebContentsScreenshotManager:
+ virtual void TakeScreenshotImpl(
+ content::RenderViewHost* host,
+ content::NavigationEntryImpl* entry) OVERRIDE {
+ }
+
+ virtual void OnScreenshotSet(content::NavigationEntryImpl* entry) OVERRIDE {
+ encoding_screenshot_in_progress_ = false;
+ WebContentsScreenshotManager::OnScreenshotSet(entry);
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+ }
+
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+ bool encoding_screenshot_in_progress_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockScreenshotManager);
+};
+
+} // namespace
+
+namespace content {
+
+// TimeSmoother tests ----------------------------------------------------------
+
+// With no duplicates, GetSmoothedTime should be the identity
+// function.
+TEST(TimeSmoother, Basic) {
+ NavigationControllerImpl::TimeSmoother smoother;
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// With a single duplicate and timestamps thereafter increasing by one
+// microsecond, the smoothed time should always be one behind.
+TEST(TimeSmoother, SingleDuplicate) {
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1);
+ t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// With k duplicates and timestamps thereafter increasing by one
+// microsecond, the smoothed time should always be k behind.
+TEST(TimeSmoother, ManyDuplicates) {
+ const int64 kNumDuplicates = 100;
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1);
+ for (int64 i = 0; i < kNumDuplicates; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time expected_t =
+ base::Time::FromInternalValue(i + kNumDuplicates);
+ t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// If the clock jumps far back enough after a run of duplicates, it
+// should immediately jump to that value.
+TEST(TimeSmoother, ClockBackwardsJump) {
+ const int64 kNumDuplicates = 100;
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1000);
+ for (int64 i = 0; i < kNumDuplicates; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1000);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+ t = base::Time::FromInternalValue(500);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+}
+
+// NavigationControllerTest ----------------------------------------------------
+
+class NavigationControllerTest
+ : public RenderViewHostImplTestHarness,
+ public WebContentsObserver {
+ public:
+ NavigationControllerTest() : navigation_entry_committed_counter_(0) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ RenderViewHostImplTestHarness::SetUp();
+ WebContents* web_contents = RenderViewHostImplTestHarness::web_contents();
+ ASSERT_TRUE(web_contents); // The WebContents should be created by now.
+ WebContentsObserver::Observe(web_contents);
+ }
+
+ // WebContentsObserver:
+ virtual void NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) OVERRIDE {
+ navigation_entry_committed_counter_++;
+ }
+
+ NavigationControllerImpl& controller_impl() {
+ return static_cast<NavigationControllerImpl&>(controller());
+ }
+
+ protected:
+ size_t navigation_entry_committed_counter_;
+};
+
+void RegisterForAllNavNotifications(TestNotificationTracker* tracker,
+ NavigationController* controller) {
+ tracker->ListenFor(NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ tracker->ListenFor(NOTIFICATION_NAV_ENTRY_CHANGED,
+ Source<NavigationController>(controller));
+}
+
+SiteInstance* GetSiteInstanceFromEntry(NavigationEntry* entry) {
+ return NavigationEntryImpl::FromNavigationEntry(entry)->site_instance();
+}
+
+class TestWebContentsDelegate : public WebContentsDelegate {
+ public:
+ explicit TestWebContentsDelegate() :
+ navigation_state_change_count_(0) {}
+
+ int navigation_state_change_count() {
+ return navigation_state_change_count_;
+ }
+
+ // Keep track of whether the tab has notified us of a navigation state change.
+ virtual void NavigationStateChanged(const WebContents* source,
+ unsigned changed_flags) OVERRIDE {
+ navigation_state_change_count_++;
+ }
+
+ private:
+ // The number of times NavigationStateChanged has been called.
+ int navigation_state_change_count_;
+};
+
+// -----------------------------------------------------------------------------
+
+TEST_F(NavigationControllerTest, Defaults) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.GetActiveEntry());
+ EXPECT_FALSE(controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, GoToOffset) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const int kNumUrls = 5;
+ std::vector<GURL> urls(kNumUrls);
+ for (int i = 0; i < kNumUrls; ++i) {
+ urls[i] = GURL(base::StringPrintf("http://www.a.com/%d", i));
+ }
+
+ test_rvh()->SendNavigate(0, urls[0]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(urls[0], controller.GetActiveEntry()->GetVirtualURL());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ for (int i = 1; i <= 4; ++i) {
+ test_rvh()->SendNavigate(i, urls[i]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(urls[i], controller.GetActiveEntry()->GetVirtualURL());
+ EXPECT_TRUE(controller.CanGoToOffset(-i));
+ EXPECT_FALSE(controller.CanGoToOffset(-(i + 1)));
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+ }
+
+ // We have loaded 5 pages, and are currently at the last-loaded page.
+ int url_index = 4;
+
+ enum Tests {
+ GO_TO_MIDDLE_PAGE = -2,
+ GO_FORWARDS = 1,
+ GO_BACKWARDS = -1,
+ GO_TO_BEGINNING = -2,
+ GO_TO_END = 4,
+ NUM_TESTS = 5,
+ };
+
+ const int test_offsets[NUM_TESTS] = {
+ GO_TO_MIDDLE_PAGE,
+ GO_FORWARDS,
+ GO_BACKWARDS,
+ GO_TO_BEGINNING,
+ GO_TO_END
+ };
+
+ for (int test = 0; test < NUM_TESTS; ++test) {
+ int offset = test_offsets[test];
+ controller.GoToOffset(offset);
+ url_index += offset;
+ // Check that the GoToOffset will land on the expected page.
+ EXPECT_EQ(urls[url_index], controller.GetPendingEntry()->GetVirtualURL());
+ test_rvh()->SendNavigate(url_index, urls[url_index]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ // Check that we can go to any valid offset into the history.
+ for (size_t j = 0; j < urls.size(); ++j)
+ EXPECT_TRUE(controller.CanGoToOffset(j - url_index));
+ // Check that we can't go beyond the beginning or end of the history.
+ EXPECT_FALSE(controller.CanGoToOffset(-(url_index + 1)));
+ EXPECT_FALSE(controller.CanGoToOffset(urls.size() - url_index));
+ }
+}
+
+TEST_F(NavigationControllerTest, LoadURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Creating a pending notification should not have issued any of the
+ // notifications we're listening for.
+ EXPECT_EQ(0U, notifications.size());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ ASSERT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetActiveEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), -1);
+
+ // The timestamp should not have been set yet.
+ EXPECT_TRUE(controller.GetPendingEntry()->GetTimestamp().is_null());
+
+ // We should have gotten no notifications from the preceeding checks.
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetActiveEntry());
+ EXPECT_EQ(controller.GetActiveEntry(), controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // The timestamp should have been set.
+ EXPECT_FALSE(controller.GetActiveEntry()->GetTimestamp().is_null());
+
+ // Load another...
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ ASSERT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetActiveEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetVisibleEntry());
+ // TODO(darin): maybe this should really be true?
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ EXPECT_TRUE(controller.GetPendingEntry()->GetTimestamp().is_null());
+
+ // Simulate the beforeunload ack for the cross-site transition, and then the
+ // commit.
+ test_rvh()->SendShouldCloseACK(true);
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetActiveEntry());
+ EXPECT_EQ(controller.GetActiveEntry(), controller.GetVisibleEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ EXPECT_FALSE(controller.GetActiveEntry()->GetTimestamp().is_null());
+}
+
+namespace {
+
+base::Time GetFixedTime(base::Time time) {
+ return time;
+}
+
+} // namespace
+
+TEST_F(NavigationControllerTest, LoadURLSameTime) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set the clock to always return a timestamp of 1.
+ controller.SetGetTimestampCallbackForTest(
+ base::Bind(&GetFixedTime, base::Time::FromInternalValue(1)));
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Load another...
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Simulate the beforeunload ack for the cross-site transition, and then the
+ // commit.
+ test_rvh()->SendShouldCloseACK(true);
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The two loads should now be committed.
+ ASSERT_EQ(controller.GetEntryCount(), 2);
+
+ // Timestamps should be distinct despite the clock returning the
+ // same value.
+ EXPECT_EQ(1u,
+ controller.GetEntryAtIndex(0)->GetTimestamp().ToInternalValue());
+ EXPECT_EQ(2u,
+ controller.GetEntryAtIndex(1)->GetTimestamp().ToInternalValue());
+}
+
+void CheckNavigationEntryMatchLoadParams(
+ NavigationController::LoadURLParams& load_params,
+ NavigationEntryImpl* entry) {
+ EXPECT_EQ(load_params.url, entry->GetURL());
+ EXPECT_EQ(load_params.referrer.url, entry->GetReferrer().url);
+ EXPECT_EQ(load_params.referrer.policy, entry->GetReferrer().policy);
+ EXPECT_EQ(load_params.transition_type, entry->GetTransitionType());
+ EXPECT_EQ(load_params.extra_headers, entry->extra_headers());
+
+ EXPECT_EQ(load_params.is_renderer_initiated, entry->is_renderer_initiated());
+ EXPECT_EQ(load_params.base_url_for_data_url, entry->GetBaseURLForDataURL());
+ if (!load_params.virtual_url_for_data_url.is_empty()) {
+ EXPECT_EQ(load_params.virtual_url_for_data_url, entry->GetVirtualURL());
+ }
+ if (NavigationController::UA_OVERRIDE_INHERIT !=
+ load_params.override_user_agent) {
+ bool should_override = (NavigationController::UA_OVERRIDE_TRUE ==
+ load_params.override_user_agent);
+ EXPECT_EQ(should_override, entry->GetIsOverridingUserAgent());
+ }
+ EXPECT_EQ(load_params.browser_initiated_post_data,
+ entry->GetBrowserInitiatedPostData());
+ EXPECT_EQ(load_params.transferred_global_request_id,
+ entry->transferred_global_request_id());
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithParams) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(GURL("http://foo"));
+ load_params.referrer =
+ Referrer(GURL("http://referrer"), WebKit::WebReferrerPolicyDefault);
+ load_params.transition_type = PAGE_TRANSITION_GENERATED;
+ load_params.extra_headers = "content-type: text/plain";
+ load_params.load_type = NavigationController::LOAD_TYPE_DEFAULT;
+ load_params.is_renderer_initiated = true;
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE;
+ load_params.transferred_global_request_id = GlobalRequestID(2, 3);
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ // The timestamp should not have been set yet.
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->GetTimestamp().is_null());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithExtraParams_Data) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(
+ GURL("data:text/html,dataurl"));
+ load_params.load_type = NavigationController::LOAD_TYPE_DATA;
+ load_params.base_url_for_data_url = GURL("http://foo");
+ load_params.virtual_url_for_data_url = GURL(kAboutBlankURL);
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_FALSE;
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithExtraParams_HttpPost) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(GURL("https://posturl"));
+ load_params.transition_type = PAGE_TRANSITION_TYPED;
+ load_params.load_type =
+ NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST;
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE;
+
+
+ const unsigned char* raw_data =
+ reinterpret_cast<const unsigned char*>("d\n\0a2");
+ const int length = 5;
+ std::vector<unsigned char> post_data_vector(raw_data, raw_data+length);
+ scoped_refptr<base::RefCountedBytes> data =
+ base::RefCountedBytes::TakeVector(&post_data_vector);
+ load_params.browser_initiated_post_data = data.get();
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+// Tests what happens when the same page is loaded again. Should not create a
+// new session history entry. This is what happens when you press enter in the
+// URL bar to reload: a pending entry is created and then it is discarded when
+// the load commits (because WebCore didn't actually make a new entry).
+TEST_F(NavigationControllerTest, LoadURL_SamePage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ ASSERT_TRUE(controller.GetActiveEntry());
+ const base::Time timestamp = controller.GetActiveEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // We should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetActiveEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // The timestamp should have been updated.
+ //
+ // TODO(akalin): Change this EXPECT_GE (and other similar ones) to
+ // EXPECT_GT once we guarantee that timestamps are unique.
+ EXPECT_GE(controller.GetActiveEntry()->GetTimestamp(), timestamp);
+}
+
+// Tests loading a URL but discarding it before the load commits.
+TEST_F(NavigationControllerTest, LoadURL_Discarded) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ ASSERT_TRUE(controller.GetActiveEntry());
+ const base::Time timestamp = controller.GetActiveEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ controller.DiscardNonCommittedEntries();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetActiveEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // Timestamp should not have changed.
+ EXPECT_EQ(timestamp, controller.GetActiveEntry()->GetTimestamp());
+}
+
+// Tests navigations that come in unrequested. This happens when the user
+// navigates from the web page, and here we test that there is no pending entry.
+TEST_F(NavigationControllerTest, LoadURL_NoPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Do a new navigation without making a pending one.
+ const GURL kNewURL("http://see");
+ test_rvh()->SendNavigate(99, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a new pending navigation that is
+// not the one that just loaded. This will happen if the user types in a URL to
+// somewhere slow, and then navigates the current page before the typed URL
+// commits.
+TEST_F(NavigationControllerTest, LoadURL_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Make a pending entry to somewhere new.
+ const GURL kExistingURL2("http://bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+
+ // After the beforeunload but before it commits, do a new navigation.
+ test_rvh()->SendShouldCloseACK(true);
+ const GURL kNewURL("http://see");
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a pending back/forward
+// navigation. This will happen if the user hits back, but before that commits,
+// they navigate somewhere new.
+TEST_F(NavigationControllerTest, LoadURL_ExistingPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending back/forward navigation. The zeroth entry should be
+ // pending.
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://foo/see");
+ LoadCommittedDetails details;
+ test_rvh()->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a pending back/forward
+// navigation to a cross-process, privileged URL. This will happen if the user
+// hits back, but before that commits, they navigate somewhere new.
+TEST_F(NavigationControllerTest, LoadURL_PrivilegedPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history, starting with a privileged URL.
+ const GURL kExistingURL1("http://privileged");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Pretend it has bindings so we can tell if we incorrectly copy it.
+ test_rvh()->AllowBindings(2);
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Navigate cross-process to a second URL.
+ const GURL kExistingURL2("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendShouldCloseACK(true);
+ TestRenderViewHost* foo_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost());
+ foo_rvh->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending back/forward navigation to a privileged entry.
+ // The zeroth entry should be pending.
+ controller.GoBack();
+ foo_rvh->SendShouldCloseACK(true);
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry())->bindings());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://foo/bee");
+ LoadCommittedDetails details;
+ foo_rvh->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+}
+
+// Tests navigating to an existing URL when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current page fires history.back().
+TEST_F(NavigationControllerTest, LoadURL_BackPreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://foo/see");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, a back navigation from the renderer commits.
+ test_rvh()->SendNavigate(0, kExistingURL1);
+
+ // There should no longer be any pending entry, and the back navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kExistingURL1, controller.GetActiveEntry()->GetURL());
+}
+
+// Tests an ignored navigation when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current blank page reloads. See http://crbug.com/77507.
+TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Without any navigations, the renderer starts at about:blank.
+ const GURL kExistingURL(kAboutBlankURL);
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // Before that commits, a document.write and location.reload can cause the
+ // renderer to send a FrameNavigate with page_id -1.
+ test_rvh()->SendNavigate(-1, kExistingURL);
+
+ // This should clear the pending entry and notify of a navigation state
+ // change, so that we do not keep displaying kNewURL.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, delegate->navigation_state_change_count());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending entry state is correct after an abort.
+// We do not want to clear the pending entry, so that the user doesn't
+// lose a typed URL. (See http://crbug.com/9682.)
+TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Start with a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kNewURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // This should not clear the pending entry or notify of a navigation state
+ // change, so that we keep displaying kNewURL (until the user clears it).
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+ NavigationEntry* pending_entry = controller.GetPendingEntry();
+
+ // Ensure that a reload keeps the same pending entry.
+ controller.Reload(true);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(pending_entry, controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending URL is not visible during a renderer-initiated
+// redirect and abort. See http://crbug.com/83031.
+TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL("http://foo/eh");
+ controller.LoadURL(kExistingURL, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Now make a pending new navigation, initiated by the renderer.
+ const GURL kNewURL("http://foo/bee");
+ NavigationController::LoadURLParams load_url_params(kNewURL);
+ load_url_params.transition_type = PAGE_TRANSITION_TYPED;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, delegate->navigation_state_change_count());
+
+ // The visible entry should be the last committed URL, not the pending one.
+ EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL());
+
+ // Now the navigation redirects.
+ const GURL kRedirectURL("http://foo/see");
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidRedirectProvisionalLoad(0, // routing_id
+ -1, // pending page_id
+ kNewURL, // old url
+ kRedirectURL)); // new url
+
+ // We don't want to change the NavigationEntry's url, in case it cancels.
+ // Prevents regression of http://crbug.com/77786.
+ EXPECT_EQ(kNewURL, controller.GetPendingEntry()->GetURL());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kRedirectURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // This should not clear the pending entry or notify of a navigation state
+ // change.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, delegate->navigation_state_change_count());
+
+ // The visible entry should be the last committed URL, not the pending one,
+ // so that no spoof is possible.
+ EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Ensure that NavigationEntries track which bindings their RenderViewHost had
+// at the time they committed. http://crbug.com/173672.
+TEST_F(NavigationControllerTest, LoadURL_WithBindings) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ // Navigate to a first, unprivileged URL.
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(NavigationEntryImpl::kInvalidBindings,
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry())->bindings());
+
+ // Commit.
+ TestRenderViewHost* orig_rvh = static_cast<TestRenderViewHost*>(test_rvh());
+ orig_rvh->SendNavigate(0, url1);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // Manually increase the number of active views in the SiteInstance
+ // that orig_rvh belongs to, to prevent it from being destroyed when
+ // it gets swapped out, so that we can reuse orig_rvh when the
+ // controller goes back.
+ static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ // Navigate to a second URL, simulate the beforeunload ack for the cross-site
+ // transition, and set bindings on the pending RenderViewHost to simulate a
+ // privileged url.
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ orig_rvh->SendShouldCloseACK(true);
+ contents()->GetPendingRenderViewHost()->AllowBindings(1);
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(1, url2);
+
+ // The second load should be committed, and bindings should be remembered.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_EQ(1, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // Going back, the first entry should still appear unprivileged.
+ controller.GoBack();
+ orig_rvh->SendNavigate(0, url1);
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+}
+
+TEST_F(NavigationControllerTest, Reload) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ ASSERT_TRUE(controller.GetActiveEntry());
+ controller.GetActiveEntry()->SetTitle(ASCIIToUTF16("Title"));
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ const base::Time timestamp = controller.GetActiveEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ // The reload is pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ // Make sure the title has been cleared (will be redrawn just after reload).
+ // Avoids a stale cached title when the new page being reloaded has no title.
+ // See http://crbug.com/96041.
+ EXPECT_TRUE(controller.GetActiveEntry()->GetTitle().empty());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // The timestamp should have been updated.
+ ASSERT_TRUE(controller.GetActiveEntry());
+ EXPECT_GE(controller.GetActiveEntry()->GetTimestamp(), timestamp);
+}
+
+// Tests what happens when a reload navigation produces a new page.
+TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+class TestNavigationObserver : public RenderViewHostObserver {
+ public:
+ explicit TestNavigationObserver(RenderViewHost* render_view_host)
+ : RenderViewHostObserver(render_view_host) {
+ }
+
+ const GURL& navigated_url() const {
+ return navigated_url_;
+ }
+
+ protected:
+ virtual void Navigate(const GURL& url) OVERRIDE {
+ navigated_url_ = url;
+ }
+
+ private:
+ GURL navigated_url_;
+};
+
+#if !defined(OS_ANDROID) // http://crbug.com/157428
+TEST_F(NavigationControllerTest, ReloadOriginalRequestURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+ TestNavigationObserver observer(test_rvh());
+
+ const GURL original_url("http://foo1");
+ const GURL final_url("http://foo2");
+
+ // Load up the original URL, but get redirected.
+ controller.LoadURL(
+ original_url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigateWithOriginalRequestURL(0, final_url, original_url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The NavigationEntry should save both the original URL and the final
+ // redirected URL.
+ EXPECT_EQ(original_url, controller.GetActiveEntry()->GetOriginalRequestURL());
+ EXPECT_EQ(final_url, controller.GetActiveEntry()->GetURL());
+
+ // Reload using the original URL.
+ controller.GetActiveEntry()->SetTitle(ASCIIToUTF16("Title"));
+ controller.ReloadOriginalRequestURL(false);
+ EXPECT_EQ(0U, notifications.size());
+
+ // The reload is pending. The request should point to the original URL.
+ EXPECT_EQ(original_url, observer.navigated_url());
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // Make sure the title has been cleared (will be redrawn just after reload).
+ // Avoids a stale cached title when the new page being reloaded has no title.
+ // See http://crbug.com/96041.
+ EXPECT_TRUE(controller.GetActiveEntry()->GetTitle().empty());
+
+ // Send that the navigation has proceeded; say it got redirected again.
+ test_rvh()->SendNavigate(0, final_url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+#endif // !defined(OS_ANDROID)
+
+// Tests what happens when we navigate back successfully
+TEST_F(NavigationControllerTest, Back) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoToOffset(-1));
+ EXPECT_TRUE(controller.CanGoForward());
+ EXPECT_TRUE(controller.CanGoToOffset(1));
+ EXPECT_FALSE(controller.CanGoToOffset(2)); // Cannot go foward 2 steps.
+
+ // Timestamp for entry 1 should be on or after that of entry 0.
+ EXPECT_FALSE(controller.GetEntryAtIndex(0)->GetTimestamp().is_null());
+ EXPECT_GE(controller.GetEntryAtIndex(1)->GetTimestamp(),
+ controller.GetEntryAtIndex(0)->GetTimestamp());
+
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The back navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoToOffset(-1));
+ EXPECT_TRUE(controller.CanGoForward());
+ EXPECT_TRUE(controller.CanGoToOffset(1));
+ EXPECT_FALSE(controller.CanGoToOffset(2)); // Cannot go foward 2 steps.
+
+ // Timestamp for entry 0 should be on or after that of entry 1
+ // (since we went back to it).
+ EXPECT_GE(controller.GetEntryAtIndex(0)->GetTimestamp(),
+ controller.GetEntryAtIndex(1)->GetTimestamp());
+}
+
+// Tests what happens when a back navigation produces a new page.
+TEST_F(NavigationControllerTest, Back_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The back navigation resulted in a completely new navigation.
+ // TODO(darin): perhaps this behavior will be confusing to users?
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 2);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Receives a back message when there is a new pending navigation entry.
+TEST_F(NavigationControllerTest, Back_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL kUrl1("http://foo1");
+ const GURL kUrl2("http://foo2");
+ const GURL kUrl3("http://foo3");
+
+ // First navigate two places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // controller.LoadURL(kUrl2, PAGE_TRANSITION_TYPED);
+ test_rvh()->SendNavigate(1, kUrl2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now start a new pending navigation and go back before it commits.
+ controller.LoadURL(kUrl3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl3, controller.GetPendingEntry()->GetURL());
+ controller.GoBack();
+
+ // The pending navigation should now be the "back" item and the new one
+ // should be gone.
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetPendingEntry()->GetURL());
+}
+
+// Receives a back message when there is a different renavigation already
+// pending.
+TEST_F(NavigationControllerTest, Back_OtherBackPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL kUrl1("http://foo/1");
+ const GURL kUrl2("http://foo/2");
+ const GURL kUrl3("http://foo/3");
+
+ // First navigate three places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ test_rvh()->SendNavigate(1, kUrl2);
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // With nothing pending, say we get a navigation to the second entry.
+ test_rvh()->SendNavigate(1, kUrl2);
+
+ // We know all the entries have the same site instance, so we can just grab
+ // a random one for looking up other entries.
+ SiteInstance* site_instance =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->site_instance();
+
+ // That second URL should be the last committed and it should have gotten the
+ // new title.
+ EXPECT_EQ(kUrl2, controller.GetEntryWithPageID(site_instance, 1)->GetURL());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // Now go forward to the last item again and say it was committed.
+ controller.GoForward();
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // Now start going back one to the second page. It will be pending.
+ controller.GoBack();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+
+ // Not synthesize a totally new back event to the first page. This will not
+ // match the pending one.
+ test_rvh()->SendNavigate(0, kUrl1);
+
+ // The committed navigation should clear the pending entry.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // But the navigated entry should be the last committed.
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetLastCommittedEntry()->GetURL());
+}
+
+// Tests what happens when we navigate forward successfully.
+TEST_F(NavigationControllerTest, Forward) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoForward();
+
+ // We should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoToOffset(-1));
+ EXPECT_FALSE(controller.CanGoToOffset(-2)); // Cannot go back 2 steps.
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ // Timestamp for entry 0 should be on or after that of entry 1
+ // (since we went back to it).
+ EXPECT_FALSE(controller.GetEntryAtIndex(0)->GetTimestamp().is_null());
+ EXPECT_GE(controller.GetEntryAtIndex(0)->GetTimestamp(),
+ controller.GetEntryAtIndex(1)->GetTimestamp());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The forward navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoToOffset(-1));
+ EXPECT_FALSE(controller.CanGoToOffset(-2)); // Cannot go back 2 steps.
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ // Timestamp for entry 1 should be on or after that of entry 0
+ // (since we went forward to it).
+ EXPECT_GE(controller.GetEntryAtIndex(1)->GetTimestamp(),
+ controller.GetEntryAtIndex(0)->GetTimestamp());
+}
+
+// Tests what happens when a forward navigation produces a new page.
+TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoForward();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(notifications.Check1AndReset(NOTIFICATION_NAV_LIST_PRUNED));
+
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Two consequent navigation for the same URL entered in should be considered
+// as SAME_PAGE navigation even when we are redirected to some other page.
+TEST_F(NavigationControllerTest, Redirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Second request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Similar to Redirect above, but the first URL is requested by POST,
+// the second URL is requested by GET. NavigationEntry::has_post_data_
+// must be cleared. http://crbug.com/21245
+TEST_F(NavigationControllerTest, PostThenRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request as POST
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ controller.GetActiveEntry()->SetHasPostData(true);
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Second request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_FALSE(controller.GetActiveEntry()->GetHasPostData());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// A redirect right off the bat should be a NEW_PAGE.
+TEST_F(NavigationControllerTest, ImmediateRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_NEW_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests navigation via link click within a subframe. A new navigation entry
+// should be created.
+TEST_F(NavigationControllerTest, NewSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(url1, details.previous_url);
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_FALSE(details.is_main_frame);
+
+ // The new entry should be appended.
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // New entry should refer to the new page, but the old URL (entries only
+ // reflect the toplevel URL).
+ EXPECT_EQ(url1, details.entry->GetURL());
+ EXPECT_EQ(params.page_id, details.entry->GetPageID());
+}
+
+// Some pages create a popup, then write an iframe into it. This causes a
+// subframe navigation without having any committed entry. Such navigations
+// just get thrown on the ground, but we shouldn't crash.
+TEST_F(NavigationControllerTest, SubframeOnEmptyPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Navigation controller currently has no entries.
+ const GURL url("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+}
+
+// Auto subframes are ones the page loads automatically like ads. They should
+// not create new navigation entries.
+TEST_F(NavigationControllerTest, AutoSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // Navigating should do nothing.
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+
+ // There should still be only one entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests navigation and then going back to a subframe navigation.
+TEST_F(NavigationControllerTest, BackSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // First manual subframe navigation.
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // This should generate a new entry.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Second manual subframe navigation should also make a new entry.
+ const GURL url3("http://foo3");
+ params.page_id = 2;
+ params.url = url3;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetCurrentEntryIndex());
+
+ // Go back one.
+ controller.GoBack();
+ params.url = url2;
+ params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Go back one more.
+ controller.GoBack();
+ params.url = url1;
+ params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+}
+
+TEST_F(NavigationControllerTest, LinkClick) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, InPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Ensure main page navigation to same url respects the was_within_same_page
+ // hint provided in the params.
+ ViewHostMsg_FrameNavigate_Params self_params;
+ self_params.page_id = 0;
+ self_params.url = url1;
+ self_params.transition = PAGE_TRANSITION_LINK;
+ self_params.should_update_history = false;
+ self_params.gesture = NavigationGestureUser;
+ self_params.is_post = false;
+ self_params.page_state = PageState::CreateFromURL(url1);
+ self_params.was_within_same_page = true;
+
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(self_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller.GetEntryCount());
+
+ // Fragment navigation to a new page_id.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+ params.was_within_same_page = true;
+
+ // This should generate a new entry.
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_FALSE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Go back one.
+ ViewHostMsg_FrameNavigate_Params back_params(params);
+ controller.GoBack();
+ back_params.url = url1;
+ back_params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(back_params.url, controller.GetActiveEntry()->GetURL());
+
+ // Go forward
+ ViewHostMsg_FrameNavigate_Params forward_params(params);
+ controller.GoForward();
+ forward_params.url = url2;
+ forward_params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(forward_params.url,
+ controller.GetActiveEntry()->GetURL());
+
+ // Now go back and forward again. This is to work around a bug where we would
+ // compare the incoming URL with the last committed entry rather than the
+ // one identified by an existing page ID. This would result in the second URL
+ // losing the reference fragment when you navigate away from it and then back.
+ controller.GoBack();
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ controller.GoForward();
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_EQ(forward_params.url,
+ controller.GetActiveEntry()->GetURL());
+
+ // Finally, navigate to an unrelated URL to make sure in_page is not sticky.
+ const GURL url3("http://bar");
+ params.page_id = 2;
+ params.url = url3;
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetCurrentEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, InPage_Replace) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // First navigation.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0; // Same page_id
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // This should NOT generate a new entry, nor prune the list.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests for http://crbug.com/40395
+// Simulates this:
+// <script>
+// window.location.replace("#a");
+// window.location='http://foo3/';
+// </script>
+TEST_F(NavigationControllerTest, ClientRedirectAfterInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Load an initial page.
+ {
+ const GURL url("http://foo/");
+ test_rvh()->SendNavigate(0, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ }
+
+ // Navigate to a new page.
+ {
+ const GURL url("http://foo2/");
+ test_rvh()->SendNavigate(1, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ }
+
+ // Navigate within the page.
+ {
+ const GURL url("http://foo2/#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1; // Same page_id
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ // This should NOT generate a new entry, nor prune the list.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ }
+
+ // Perform a client redirect to a new page.
+ {
+ const GURL url("http://foo3/");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 2; // New page_id
+ params.url = url;
+ params.transition = PAGE_TRANSITION_CLIENT_REDIRECT;
+ params.redirects.push_back(GURL("http://foo2/#a"));
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ // This SHOULD generate a new entry.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ }
+
+ // Verify that BACK brings us back to http://foo2/.
+ {
+ const GURL url("http://foo2/");
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(url, controller.GetActiveEntry()->GetURL());
+ }
+}
+
+// NotificationObserver implementation used in verifying we've received the
+// NOTIFICATION_NAV_LIST_PRUNED method.
+class PrunedListener : public NotificationObserver {
+ public:
+ explicit PrunedListener(NavigationControllerImpl* controller)
+ : notification_count_(0) {
+ registrar_.Add(this, NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ if (type == NOTIFICATION_NAV_LIST_PRUNED) {
+ notification_count_++;
+ details_ = *(Details<PrunedDetails>(details).ptr());
+ }
+ }
+
+ // Number of times NAV_LIST_PRUNED has been observed.
+ int notification_count_;
+
+ // Details from the last NAV_LIST_PRUNED.
+ PrunedDetails details_;
+
+ private:
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrunedListener);
+};
+
+// Tests that we limit the number of navigation entries created correctly.
+TEST_F(NavigationControllerTest, EnforceMaxNavigationCount) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 5;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ int url_index;
+ // Load up to the max count, all entries should be there.
+ for (url_index = 0; url_index < kMaxEntryCount; url_index++) {
+ GURL url(base::StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ }
+
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+
+ // Created a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ // Navigate some more.
+ GURL url(base::StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+
+ // We should have got a pruned navigation.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // We expect http://www.a.com/0 to be gone.
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/1"));
+
+ // More navigations.
+ for (int i = 0; i < 3; i++) {
+ url = GURL(base::StringPrintf("http:////www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+ }
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/4"));
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that we can do a restore and navigate to the restored entries and
+// everything is updated properly. This can be tricky since there is no
+// SiteInstance for the entries created initially.
+TEST_F(NavigationControllerTest, RestoreNavigate) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, Referrer(), PAGE_TRANSITION_RELOAD, false, std::string(),
+ browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetPageState(PageState::CreateFromEncodedData("state"));
+ const base::Time timestamp = base::Time::Now();
+ entry->SetTimestamp(timestamp);
+ entries.push_back(entry);
+ scoped_ptr<WebContentsImpl> our_contents(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ NavigationControllerImpl& our_controller = our_contents->GetController();
+ our_controller.Restore(
+ 0,
+ NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ ASSERT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry
+ (our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // Timestamp should remain the same before the navigation finishes.
+ EXPECT_EQ(timestamp, our_controller.GetEntryAtIndex(0)->GetTimestamp());
+
+ // Say we navigated to that entry.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+ LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no longer any pending entry and one committed one. This
+ // means that we were able to locate the entry, assign its site instance, and
+ // commit it properly.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSiteURL());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+
+ // Timestamp should have been updated.
+ EXPECT_GE(our_controller.GetEntryAtIndex(0)->GetTimestamp(), timestamp);
+}
+
+// Tests that we can still navigate to a restored entry after a different
+// navigation fails and clears the pending entry. http://crbug.com/90085
+TEST_F(NavigationControllerTest, RestoreNavigateAfterFailure) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, Referrer(), PAGE_TRANSITION_RELOAD, false, std::string(),
+ browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetPageState(PageState::CreateFromEncodedData("state"));
+ entries.push_back(entry);
+ scoped_ptr<WebContentsImpl> our_contents(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ NavigationControllerImpl& our_controller = our_contents->GetController();
+ our_controller.Restore(
+ 0, NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // This pending navigation may have caused a different navigation to fail,
+ // which causes the pending entry to be cleared.
+ TestRenderViewHost* rvh =
+ static_cast<TestRenderViewHost*>(our_contents->GetRenderViewHost());
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params fail_load_params;
+ fail_load_params.frame_id = 1;
+ fail_load_params.is_main_frame = true;
+ fail_load_params.error_code = net::ERR_ABORTED;
+ fail_load_params.error_description = string16();
+ fail_load_params.url = url;
+ fail_load_params.showing_repost_interstitial = false;
+ rvh->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ fail_load_params));
+
+ // Now the pending restored entry commits.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+ LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no pending entry and one committed one.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSiteURL());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+}
+
+// Make sure that the page type and stuff is correct after an interstitial.
+TEST_F(NavigationControllerTest, Interstitial) {
+ NavigationControllerImpl& controller = controller_impl();
+ // First navigate somewhere normal.
+ const GURL url1("http://foo");
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+
+ // Now navigate somewhere with an interstitial.
+ const GURL url2("http://bar");
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ set_page_type(PAGE_TYPE_INTERSTITIAL);
+
+ // At this point the interstitial will be displayed and the load will still
+ // be pending. If the user continues, the load will commit.
+ test_rvh()->SendNavigate(1, url2);
+
+ // The page should be a normal page again.
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+ EXPECT_EQ(PAGE_TYPE_NORMAL,
+ controller.GetLastCommittedEntry()->GetPageType());
+}
+
+TEST_F(NavigationControllerTest, RemoveEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+ const GURL url5("http://foo/5");
+ const GURL pending_url("http://foo/pending");
+ const GURL default_url("http://foo/default");
+
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ controller.LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url3);
+ controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(3, url4);
+ controller.LoadURL(
+ url5, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(4, url5);
+
+ // Try to remove the last entry. Will fail because it is the current entry.
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_EQ(5, controller.GetEntryCount());
+ EXPECT_EQ(4, controller.GetLastCommittedEntryIndex());
+
+ // Go back, but don't commit yet. Check that we can't delete the current
+ // and pending entries.
+ controller.GoBack();
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 2));
+
+ // Now commit and delete the last entry.
+ test_rvh()->SendNavigate(3, url4);
+ EXPECT_TRUE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_EQ(4, controller.GetEntryCount());
+ EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove an entry which is not the last committed one.
+ EXPECT_TRUE(controller.RemoveEntryAtIndex(0));
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove the 2 remaining entries.
+ controller.RemoveEntryAtIndex(1);
+ controller.RemoveEntryAtIndex(0);
+
+ // This should leave us with only the last committed entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+}
+
+// Tests the transient entry, making sure it goes away with all navigations.
+TEST_F(NavigationControllerTest, TransientEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url3_ref("http://foo/3#bar");
+ const GURL url4("http://foo/4");
+ const GURL transient_url("http://foo/transient");
+
+ controller.LoadURL(
+ url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url1);
+
+ notifications.Reset();
+
+ // Adding a transient with no pending entry.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+
+ // We should not have received any notifications.
+ EXPECT_EQ(0U, notifications.size());
+
+ // Check our state.
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ // Navigate.
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url2);
+
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+
+ // Add a transient again, then navigate with no pending entry this time.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ // Transient entry should be gone.
+ EXPECT_EQ(url3, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 4);
+
+ // Initiate a navigation, add a transient then commit navigation.
+ controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ test_rvh()->SendNavigate(4, url4);
+ EXPECT_EQ(url4, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+
+ // Add a transient and go back. This should simply remove the transient.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ controller.GoBack();
+ // Transient entry should be gone.
+ EXPECT_EQ(url4, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ test_rvh()->SendNavigate(3, url3);
+
+ // Add a transient and go to an entry before the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ controller.GoToIndex(1);
+ // The navigation should have been initiated, transient entry should be gone.
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ // Visible entry does not update for history navigations until commit.
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go to an entry after the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ controller.GoToIndex(3);
+ // The navigation should have been initiated, transient entry should be gone.
+ // Because of the transient entry that is removed, going to index 3 makes us
+ // land on url2 (which is visible after the commit).
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(2, url2);
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go forward.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoForward());
+ controller.GoForward();
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url3, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and do an in-page navigation, replacing the current entry.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3_ref);
+ // Transient entry should be gone.
+ EXPECT_EQ(url3_ref, controller.GetActiveEntry()->GetURL());
+
+ // Ensure the URLs are correct.
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+ EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), url1);
+ EXPECT_EQ(controller.GetEntryAtIndex(2)->GetURL(), url2);
+ EXPECT_EQ(controller.GetEntryAtIndex(3)->GetURL(), url3_ref);
+ EXPECT_EQ(controller.GetEntryAtIndex(4)->GetURL(), url4);
+}
+
+// Test that Reload initiates a new navigation to a transient entry's URL.
+TEST_F(NavigationControllerTest, ReloadTransient) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL transient_url("http://foo/transient");
+
+ // Load |url0|, and start a pending navigation to |url1|.
+ controller.LoadURL(
+ url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // A transient entry is added, interrupting the navigation.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_TRUE(controller.GetTransientEntry());
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+
+ // The page is reloaded, which should remove the pending entry for |url1| and
+ // the transient entry for |transient_url|, and start a navigation to
+ // |transient_url|.
+ controller.Reload(true);
+ EXPECT_FALSE(controller.GetTransientEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(transient_url, controller.GetActiveEntry()->GetURL());
+ ASSERT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+
+ // Load of |transient_url| completes.
+ test_rvh()->SendNavigate(1, transient_url);
+ ASSERT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+ EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), transient_url);
+}
+
+// Ensure that renderer initiated pending entries get replaced, so that we
+// don't show a stale virtual URL when a navigation commits.
+// See http://crbug.com/266922.
+TEST_F(NavigationControllerTest, RendererInitiatedPendingEntries) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ const GURL url1("nonexistent:12121");
+ const GURL url1_fixed("http://nonexistent:12121/");
+ const GURL url2("http://foo");
+
+ // We create pending entries for renderer-initiated navigations so that we
+ // can show them in new tabs when it is safe.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url1);
+
+ // Simulate what happens if a BrowserURLHandler rewrites the URL, causing
+ // the virtual URL to differ from the URL.
+ controller.GetPendingEntry()->SetURL(url1_fixed);
+ controller.GetPendingEntry()->SetVirtualURL(url1);
+
+ EXPECT_EQ(url1_fixed, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetVirtualURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+
+ // If the user clicks another link, we should replace the pending entry.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url2);
+ EXPECT_EQ(url2, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetPendingEntry()->GetVirtualURL());
+
+ // Once it commits, the URL and virtual URL should reflect the actual page.
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetVirtualURL());
+
+ // We should not replace the pending entry for an error URL.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url1);
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, GURL(kUnreachableWebDataURL));
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+}
+
+// Tests that the URLs for renderer-initiated navigations are not displayed to
+// the user until the navigation commits, to prevent URL spoof attacks.
+// See http://crbug.com/99016.
+TEST_F(NavigationControllerTest, DontShowRendererURLUntilCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ // For typed navigations (browser-initiated), both active and visible entries
+ // should update before commit.
+ controller.LoadURL(url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(url0, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(0, url0);
+
+ // For link clicks (renderer-initiated navigations), the active entry should
+ // update before commit but the visible should not.
+ NavigationController::LoadURLParams load_url_params(url1);
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+
+ // After commit, both should be updated, and we should no longer treat the
+ // entry as renderer-initiated.
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ EXPECT_FALSE(
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->is_renderer_initiated());
+
+ notifications.Reset();
+}
+
+// Tests that the URLs for renderer-initiated navigations in new tabs are
+// displayed to the user before commit, as long as the initial about:blank
+// page has not been modified. If so, we must revert to showing about:blank.
+// See http://crbug.com/9682.
+TEST_F(NavigationControllerTest, ShowRendererURLInNewTabUntilModified) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url("http://foo");
+
+ // For renderer-initiated navigations in new tabs (with no committed entries),
+ // we show the pending entry's URL as long as the about:blank page is not
+ // modified.
+ NavigationController::LoadURLParams load_url_params(url);
+ load_url_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+ EXPECT_TRUE(controller.IsInitialNavigation());
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+
+ // There should be no title yet.
+ EXPECT_TRUE(contents()->GetTitle().empty());
+
+ // If something else modifies the contents of the about:blank page, then
+ // we must revert to showing about:blank to avoid a URL spoof.
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidAccessInitialDocument(0));
+ EXPECT_TRUE(test_rvh()->has_accessed_initial_document());
+ EXPECT_FALSE(controller.GetVisibleEntry());
+ EXPECT_EQ(url, controller.GetActiveEntry()->GetURL());
+
+ notifications.Reset();
+}
+
+TEST_F(NavigationControllerTest, DontShowRendererURLInNewTabAfterCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo/eh");
+ const GURL url2("http://foo/bee");
+
+ // For renderer-initiated navigations in new tabs (with no committed entries),
+ // we show the pending entry's URL as long as the about:blank page is not
+ // modified.
+ NavigationController::LoadURLParams load_url_params(url1);
+ load_url_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url1, controller.GetActiveEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+ EXPECT_TRUE(controller.IsInitialNavigation());
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+
+ // Simulate a commit and then starting a new pending navigation.
+ test_rvh()->SendNavigate(0, url1);
+ NavigationController::LoadURLParams load_url2_params(url2);
+ load_url2_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url2_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url2_params);
+
+ // We should not consider this an initial navigation, and thus should
+ // not show the pending URL.
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+ EXPECT_FALSE(controller.IsInitialNavigation());
+ EXPECT_TRUE(controller.GetVisibleEntry());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ notifications.Reset();
+}
+
+// Tests that IsInPageNavigation returns appropriate results. Prevents
+// regression for bug 1126349.
+TEST_F(NavigationControllerTest, IsInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate to URL with no refs.
+ const GURL url("http://www.google.com/home.html");
+ test_rvh()->SendNavigate(0, url);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ const GURL other_url("http://www.google.com/add.html");
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL url_with_ref("http://www.google.com/home.html#my_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref));
+
+ // Navigate to URL with refs.
+ test_rvh()->SendNavigate(1, url_with_ref);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url_with_ref));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL other_url_with_ref("http://www.google.com/home.html#my_other_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(other_url_with_ref));
+
+ // Going to the same url again will be considered in-page
+ // if the renderer says it is even if the navigation type isn't IN_PAGE.
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref, true,
+ NAVIGATION_TYPE_UNKNOWN));
+
+ // Going back to the non ref url will be considered in-page if the navigation
+ // type is IN_PAGE.
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url, true,
+ NAVIGATION_TYPE_IN_PAGE));
+}
+
+// Some pages can have subframes with the same base URL (minus the reference) as
+// the main page. Even though this is hard, it can happen, and we don't want
+// these subframe navigations to affect the toplevel document. They should
+// instead be ignored. http://crbug.com/5585
+TEST_F(NavigationControllerTest, SameSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate the main frame.
+ const GURL url("http://www.google.com/");
+ test_rvh()->SendNavigate(0, url);
+
+ // We should be at the first navigation entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+
+ // Navigate a subframe that would normally count as in-page.
+ const GURL subframe("http://www.google.com/#");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = subframe;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(subframe);
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // Nothing should have changed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+}
+
+// Make sure that on cloning a WebContentsImpl and going back needs_reload is
+// false.
+TEST_F(NavigationControllerTest, CloneAndGoBack) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const string16 title(ASCIIToUTF16("Title"));
+
+ NavigateAndCommit(url1);
+ controller.GetActiveEntry()->SetTitle(title);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+ EXPECT_TRUE(clone->GetController().NeedsReload());
+ clone->GetController().GoBack();
+ // Navigating back should have triggered needs_reload_ to go false.
+ EXPECT_FALSE(clone->GetController().NeedsReload());
+
+ // Ensure that the pending URL and its title are visible.
+ EXPECT_EQ(url1, clone->GetController().GetVisibleEntry()->GetURL());
+ EXPECT_EQ(title, clone->GetTitle());
+}
+
+// Make sure that reloading a cloned tab doesn't change its pending entry index.
+// See http://crbug.com/234491.
+TEST_F(NavigationControllerTest, CloneAndReload) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const string16 title(ASCIIToUTF16("Title"));
+
+ NavigateAndCommit(url1);
+ controller.GetActiveEntry()->SetTitle(title);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+ clone->GetController().LoadIfNecessary();
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+ EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex());
+
+ clone->GetController().Reload(true);
+ EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex());
+}
+
+// Make sure that cloning a WebContentsImpl doesn't copy interstitials.
+TEST_F(NavigationControllerTest, CloneOmitsInterstitials) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Add an interstitial entry. Should be deleted with controller.
+ NavigationEntryImpl* interstitial_entry = new NavigationEntryImpl();
+ interstitial_entry->set_page_type(PAGE_TYPE_INTERSTITIAL);
+ controller.SetTransientEntry(interstitial_entry);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+}
+
+// Tests a subframe navigation while a toplevel navigation is pending.
+// http://crbug.com/43967
+TEST_F(NavigationControllerTest, SubframeWhilePending) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Load the first page.
+ const GURL url1("http://foo/");
+ NavigateAndCommit(url1);
+
+ // Now start a pending load to a totally different page, but don't commit it.
+ const GURL url2("http://bar/");
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Send a subframe update from the first page, as if one had just
+ // automatically loaded. Auto subframes don't increment the page ID.
+ const GURL url1_sub("http://foo/subframe");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = controller.GetLastCommittedEntry()->GetPageID();
+ params.url = url1_sub;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url1_sub);
+ LoadCommittedDetails details;
+
+ // This should return false meaning that nothing was actually updated.
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // The notification should have updated the last committed one, and not
+ // the pending load.
+ EXPECT_EQ(url1, controller.GetLastCommittedEntry()->GetURL());
+
+ // The active entry should be unchanged by the subframe load.
+ EXPECT_EQ(url2, controller.GetActiveEntry()->GetURL());
+}
+
+// Test CopyStateFrom with 2 urls, the first selected and nothing in the target.
+TEST_F(NavigationControllerTest, CopyStateFrom) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_controller.CopyStateFrom(controller);
+
+ // other_controller should now contain 2 urls.
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ // We should be looking at the first one.
+ ASSERT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ // This is a different site than url1, so the IDs start again at 0.
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(1)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+
+ // Ensure the SessionStorageNamespaceMaps are the same size and have
+ // the same partitons loaded.
+ //
+ // TODO(ajwong): We should load a url from a different partition earlier
+ // to make sure this map has more than one entry.
+ const SessionStorageNamespaceMap& session_storage_namespace_map =
+ controller.GetSessionStorageNamespaceMap();
+ const SessionStorageNamespaceMap& other_session_storage_namespace_map =
+ other_controller.GetSessionStorageNamespaceMap();
+ EXPECT_EQ(session_storage_namespace_map.size(),
+ other_session_storage_namespace_map.size());
+ for (SessionStorageNamespaceMap::const_iterator it =
+ session_storage_namespace_map.begin();
+ it != session_storage_namespace_map.end();
+ ++it) {
+ SessionStorageNamespaceMap::const_iterator other =
+ other_session_storage_namespace_map.find(it->first);
+ EXPECT_TRUE(other != other_session_storage_namespace_map.end());
+ }
+}
+
+// Tests CopyStateFromAndPrune with 2 urls in source, 1 in dest.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // First two entries should have the same SiteInstance.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0));
+ SiteInstance* instance2 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(1));
+ EXPECT_EQ(instance1, instance2);
+ EXPECT_EQ(0, controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(1, contents()->GetMaxPageIDForSiteInstance(instance1));
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain the 3 urls: url1, url2 and url3.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ // A new SiteInstance should be used for the new tab.
+ SiteInstance* instance3 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_NE(instance3, instance1);
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ EXPECT_EQ(1, other_contents->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance3));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and 1 entry in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune2) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url3
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ ASSERT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(1)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the last selected and 2 entries in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune3) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->NavigateAndCommit(url4);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url4
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url4, other_controller.GetEntryAtIndex(2)->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, 2 entries in the target, with
+// not the last entry selected in the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneNotLast) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->NavigateAndCommit(url4);
+ other_controller.GoBack();
+ other_contents->CommitPendingNavigation();
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url3
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and 1 entry plus
+// a pending entry in the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneTargetPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain url1, url3, and a pending entry
+ // for url4.
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+
+ // And there should be a pending entry for url4.
+ ASSERT_TRUE(other_controller.GetPendingEntry());
+ EXPECT_EQ(url4, other_controller.GetPendingEntry()->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 1 url in the source, 1 entry and a pending
+// client redirect entry (with the same page ID) in the target. This used to
+// crash because the last committed entry would be pruned but max_page_id
+// remembered the page ID (http://crbug.com/234809).
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneTargetPending2) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2a("http://foo2/a");
+ const GURL url2b("http://foo2/b");
+
+ NavigateAndCommit(url1);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url2a);
+ // Simulate a client redirect, which has the same page ID as entry 2a.
+ other_controller.LoadURL(
+ url2b, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ other_controller.GetPendingEntry()->SetPageID(
+ other_controller.GetLastCommittedEntry()->GetPageID());
+
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain url1, url2a, and a pending entry
+ // for url2b.
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2a, other_controller.GetEntryAtIndex(1)->GetURL());
+
+ // And there should be a pending entry for url4.
+ ASSERT_TRUE(other_controller.GetPendingEntry());
+ EXPECT_EQ(url2b, other_controller.GetPendingEntry()->GetURL());
+
+ // Let the pending entry commit.
+ other_contents->CommitPendingNavigation();
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, a back navigation pending in the
+// source, and 1 entry in the target. The back pending entry should be ignored.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneSourcePending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url3
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Tests CopyStateFromAndPrune with 3 urls in source, 1 in dest,
+// when the max entry count is 3. We should prune one entry.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneMaxEntries) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 3;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+
+ // Create a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url4);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // We should have received a pruned notification.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // other_controller should now contain only 3 urls: url2, url3 and url4.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url4, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(2, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that navigations initiated from the page (with the history object)
+// work as expected without navigation entries.
+TEST_F(NavigationControllerTest, HistoryNavigate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ // Simulate the page calling history.back(), it should not create a pending
+ // entry.
+ contents()->OnGoToEntryAtOffset(-1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ // Also make sure we told the page to navigate.
+ const IPC::Message* message =
+ process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ Tuple1<ViewMsg_Navigate_Params> nav_params;
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url1, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Now test history.forward()
+ contents()->OnGoToEntryAtOffset(1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url3, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Make sure an extravagant history.go() doesn't break.
+ contents()->OnGoToEntryAtOffset(120); // Out of bounds.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ EXPECT_TRUE(message == NULL);
+}
+
+// Test call to PruneAllButVisible for the only entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForSingle) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ NavigateAndCommit(url1);
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0)), 0,
+ controller.GetEntryAtIndex(0)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButVisible for first entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForFirst) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0)), 0,
+ controller.GetEntryAtIndex(0)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButVisible for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForIntermediate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(1)), 0,
+ controller.GetEntryAtIndex(1)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url2);
+}
+
+// Test call to PruneAllButVisible for a pending entry that is not yet in the
+// list of entries.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForPendingNotInList) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Create a pending entry that is not in the entry list.
+ controller.LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ NULL, 0, controller.GetPendingEntry()->GetPageID());
+ controller.PruneAllButVisible();
+
+ // We should only have the last committed and pending entries at this point,
+ // and the pending entry should still not be in the entry list.
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(1, controller.GetEntryCount());
+
+ // Try to commit the pending entry.
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
+}
+
+// Test to ensure that when we do a history navigation back to the current
+// committed page (e.g., going forward to a slow-loading page, then pressing
+// the back button), we just stop the navigation to prevent the throbber from
+// running continuously. Otherwise, the RenderViewHost forces the throbber to
+// start, but WebKit essentially ignores the navigation and never sends a
+// message to stop the throbber.
+TEST_F(NavigationControllerTest, StopOnHistoryNavigationToCurrentPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ NavigateAndCommit(url0);
+ NavigateAndCommit(url1);
+
+ // Go back to the original page, then forward to the slow page, then back
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller.GoForward();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+
+ controller.GoBack();
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, IsInitialNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Initial state.
+ EXPECT_TRUE(controller.IsInitialNavigation());
+
+ // After commit, it stays false.
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(controller.IsInitialNavigation());
+
+ // After starting a new navigation, it stays false.
+ const GURL url2("http://foo2");
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+}
+
+// Check that the favicon is not reused across a client redirect.
+// (crbug.com/28515)
+TEST_F(NavigationControllerTest, ClearFaviconOnRedirect) {
+ const GURL kPageWithFavicon("http://withfavicon.html");
+ const GURL kPageWithoutFavicon("http://withoutfavicon.html");
+ const GURL kIconURL("http://withfavicon.ico");
+ const gfx::Image kDefaultFavicon = FaviconStatus().image;
+
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ test_rvh()->SendNavigate(0, kPageWithFavicon);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ NavigationEntry* entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kPageWithFavicon, entry->GetURL());
+
+ // Simulate Chromium having set the favicon for |kPageWithFavicon|.
+ content::FaviconStatus& favicon_status = entry->GetFavicon();
+ favicon_status.image = CreateImage(SK_ColorWHITE);
+ favicon_status.url = kIconURL;
+ favicon_status.valid = true;
+ EXPECT_FALSE(DoImagesMatch(kDefaultFavicon, entry->GetFavicon().image));
+
+ test_rvh()->SendNavigateWithTransition(
+ 0, // same page ID.
+ kPageWithoutFavicon,
+ PAGE_TRANSITION_CLIENT_REDIRECT);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kPageWithoutFavicon, entry->GetURL());
+
+ EXPECT_TRUE(DoImagesMatch(kDefaultFavicon, entry->GetFavicon().image));
+}
+
+// Check that the favicon is not cleared for NavigationEntries which were
+// previously navigated to.
+TEST_F(NavigationControllerTest, BackNavigationDoesNotClearFavicon) {
+ const GURL kUrl1("http://www.a.com/1");
+ const GURL kUrl2("http://www.a.com/2");
+ const GURL kIconURL("http://www.a.com/1/favicon.ico");
+
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ test_rvh()->SendNavigate(0, kUrl1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Simulate Chromium having set the favicon for |kUrl1|.
+ gfx::Image favicon_image = CreateImage(SK_ColorWHITE);
+ content::NavigationEntry* entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ content::FaviconStatus& favicon_status = entry->GetFavicon();
+ favicon_status.image = favicon_image;
+ favicon_status.url = kIconURL;
+ favicon_status.valid = true;
+
+ // Navigate to another page and go back to the original page.
+ test_rvh()->SendNavigate(1, kUrl2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ test_rvh()->SendNavigateWithTransition(
+ 0,
+ kUrl1,
+ PAGE_TRANSITION_FORWARD_BACK);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Verify that the favicon for the page at |kUrl1| was not cleared.
+ entry = controller.GetEntryAtIndex(0);
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kUrl1, entry->GetURL());
+ EXPECT_TRUE(DoImagesMatch(favicon_image, entry->GetFavicon().image));
+}
+
+// The test crashes on android: http://crbug.com/170449
+#if defined(OS_ANDROID)
+#define MAYBE_PurgeScreenshot DISABLED_PurgeScreenshot
+#else
+#define MAYBE_PurgeScreenshot PurgeScreenshot
+#endif
+// Tests that screenshot are purged correctly.
+TEST_F(NavigationControllerTest, MAYBE_PurgeScreenshot) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationEntryImpl* entry;
+
+ // Navigate enough times to make sure that some screenshots are purged.
+ for (int i = 0; i < 12; ++i) {
+ const GURL url(base::StringPrintf("http://foo%d/", i));
+ NavigateAndCommit(url);
+ EXPECT_EQ(i, controller.GetCurrentEntryIndex());
+ }
+
+ MockScreenshotManager* screenshot_manager =
+ new MockScreenshotManager(&controller);
+ controller.SetScreenshotManager(screenshot_manager);
+ for (int i = 0; i < controller.GetEntryCount(); ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ EXPECT_TRUE(entry->screenshot().get());
+ }
+
+ NavigateAndCommit(GURL("https://foo/"));
+ EXPECT_EQ(13, controller.GetEntryCount());
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(11));
+ screenshot_manager->TakeScreenshotFor(entry);
+
+ for (int i = 0; i < 2; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ }
+
+ for (int i = 2; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_TRUE(entry->screenshot().get()) << "Screenshot not found for " << i;
+ }
+
+ // Navigate to index 5 and then try to assign screenshot to all entries.
+ controller.GoToIndex(5);
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(5, controller.GetCurrentEntryIndex());
+ for (int i = 0; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ for (int i = 10; i <= 12; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ // Navigate to index 7 and assign screenshot to all entries.
+ controller.GoToIndex(7);
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(7, controller.GetCurrentEntryIndex());
+ for (int i = 0; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ }
+
+ // Clear all screenshots.
+ EXPECT_EQ(13, controller.GetEntryCount());
+ EXPECT_EQ(10, screenshot_manager->GetScreenshotCount());
+ controller.ClearAllScreenshots();
+ EXPECT_EQ(0, screenshot_manager->GetScreenshotCount());
+ for (int i = 0; i < controller.GetEntryCount(); ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not cleared";
+ }
+}
+
+// Test that the navigation controller clears its session history when a
+// navigation commits with the clear history list flag set.
+TEST_F(NavigationControllerTest, ClearHistoryList) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigationControllerImpl& controller = controller_impl();
+
+ // Create a session history with three entries, second entry is active.
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+
+ // Create a new pending navigation, and indicate that the session history
+ // should be cleared.
+ NavigationController::LoadURLParams params(url4);
+ params.should_clear_history_list = true;
+ controller.LoadURLWithParams(params);
+
+ // Verify that the pending entry correctly indicates that the session history
+ // should be cleared.
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->should_clear_history_list());
+
+ // Assume that the RV correctly cleared its history and commit the navigation.
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost())->
+ set_simulate_history_list_was_cleared(true);
+ contents()->CommitPendingNavigation();
+
+ // Verify that the NavigationController's session history was correctly
+ // cleared.
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(url4, controller.GetActiveEntry()->GetURL());
+}
+
+/* TODO(brettw) These test pass on my local machine but fail on the XP buildbot
+ (but not Vista) cleaning up the directory after they run.
+ This should be fixed.
+
+// NavigationControllerHistoryTest ---------------------------------------------
+
+class NavigationControllerHistoryTest : public NavigationControllerTest {
+ public:
+ NavigationControllerHistoryTest()
+ : url0("http://foo1"),
+ url1("http://foo1"),
+ url2("http://foo1"),
+ profile_manager_(NULL) {
+ }
+
+ virtual ~NavigationControllerHistoryTest() {
+ // Prevent our base class from deleting the profile since profile's
+ // lifetime is managed by profile_manager_.
+ STLDeleteElements(&windows_);
+ }
+
+ // testing::Test overrides.
+ virtual void SetUp() {
+ NavigationControllerTest::SetUp();
+
+ // Force the session service to be created.
+ SessionService* service = new SessionService(profile());
+ SessionServiceFactory::SetForTestProfile(profile(), service);
+ service->SetWindowType(window_id, Browser::TYPE_TABBED);
+ service->SetWindowBounds(window_id, gfx::Rect(0, 1, 2, 3), false);
+ service->SetTabIndexInWindow(window_id,
+ controller.session_id(), 0);
+ controller.SetWindowID(window_id);
+
+ session_helper_.SetService(service);
+ }
+
+ virtual void TearDown() {
+ // Release profile's reference to the session service. Otherwise the file
+ // will still be open and we won't be able to delete the directory below.
+ session_helper_.ReleaseService(); // profile owns this
+ SessionServiceFactory::SetForTestProfile(profile(), NULL);
+
+ // Make sure we wait for history to shut down before continuing. The task
+ // we add will cause our message loop to quit once it is destroyed.
+ HistoryService* history = HistoryServiceFactory::GetForProfiles(
+ profile(), Profile::IMPLICIT_ACCESS);
+ if (history) {
+ history->SetOnBackendDestroyTask(base::MessageLoop::QuitClosure());
+ base::MessageLoop::current()->Run();
+ }
+
+ // Do normal cleanup before deleting the profile directory below.
+ NavigationControllerTest::TearDown();
+
+ ASSERT_TRUE(base::DeleteFile(test_dir_, true));
+ ASSERT_FALSE(base::PathExists(test_dir_));
+ }
+
+ // Deletes the current profile manager and creates a new one. Indirectly this
+ // shuts down the history database and reopens it.
+ void ReopenDatabase() {
+ session_helper_.SetService(NULL);
+ SessionServiceFactory::SetForTestProfile(profile(), NULL);
+
+ SessionService* service = new SessionService(profile());
+ SessionServiceFactory::SetForTestProfile(profile(), service);
+ session_helper_.SetService(service);
+ }
+
+ void GetLastSession() {
+ SessionServiceFactory::GetForProfile(profile())->TabClosed(
+ controller.window_id(), controller.session_id(), false);
+
+ ReopenDatabase();
+ Time close_time;
+
+ session_helper_.ReadWindows(&windows_);
+ }
+
+ CancelableRequestConsumer consumer;
+
+ // URLs for testing.
+ const GURL url0;
+ const GURL url1;
+ const GURL url2;
+
+ std::vector<SessionWindow*> windows_;
+
+ SessionID window_id;
+
+ SessionServiceTestHelper session_helper_;
+
+ private:
+ ProfileManager* profile_manager_;
+ base::FilePath test_dir_;
+};
+
+// A basic test case. Navigates to a single url, and make sure the history
+// db matches.
+TEST_F(NavigationControllerHistoryTest, Basic) {
+ NavigationControllerImpl& controller = controller_impl();
+ controller.LoadURL(url0, GURL(), PAGE_TRANSITION_LINK);
+ test_rvh()->SendNavigate(0, url0);
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 1);
+ session_helper_.AssertTabEquals(0, 0, 1, *(windows_[0]->tabs[0]));
+ TabNavigation nav1(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav1,
+ windows_[0]->tabs[0]->navigations[0]);
+}
+
+// Navigates to three urls, then goes back and make sure the history database
+// is in sync.
+TEST_F(NavigationControllerHistoryTest, NavigationThenBack) {
+ NavigationControllerImpl& controller = controller_impl();
+ test_rvh()->SendNavigate(0, url0);
+ test_rvh()->SendNavigate(1, url1);
+ test_rvh()->SendNavigate(2, url2);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 3);
+ session_helper_.AssertTabEquals(0, 1, 3, *(windows_[0]->tabs[0]));
+
+ TabNavigation nav(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[0]);
+ nav.set_url(url1);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[1]);
+ nav.set_url(url2);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[2]);
+}
+
+// Navigates to three urls, then goes back twice, then loads a new url.
+TEST_F(NavigationControllerHistoryTest, NavigationPruning) {
+ NavigationControllerImpl& controller = controller_impl();
+ test_rvh()->SendNavigate(0, url0);
+ test_rvh()->SendNavigate(1, url1);
+ test_rvh()->SendNavigate(2, url2);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url0);
+
+ test_rvh()->SendNavigate(3, url2);
+
+ // Now have url0, and url2.
+
+ GetLastSession();
+
+ session_helper_.AssertSingleWindowWithSingleTab(windows_, 2);
+ session_helper_.AssertTabEquals(0, 1, 2, *(windows_[0]->tabs[0]));
+
+ TabNavigation nav(0, url0, GURL(), string16(),
+ webkit_glue::CreateHistoryStateForURL(url0),
+ PAGE_TRANSITION_LINK);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[0]);
+ nav.set_url(url2);
+ session_helper_.AssertNavigationEquals(nav,
+ windows_[0]->tabs[0]->navigations[1]);
+}
+*/
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/navigation_entry_impl.cc b/chromium/content/browser/web_contents/navigation_entry_impl.cc
new file mode 100644
index 00000000000..a23b7b6623a
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_entry_impl.cc
@@ -0,0 +1,321 @@
+// 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 "content/browser/web_contents/navigation_entry_impl.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/net_util.h"
+#include "ui/base/text/text_elider.h"
+
+// Use this to get a new unique ID for a NavigationEntry during construction.
+// The returned ID is guaranteed to be nonzero (which is the "no ID" indicator).
+static int GetUniqueIDInConstructor() {
+ static int unique_id_counter = 0;
+ return ++unique_id_counter;
+}
+
+namespace content {
+
+int NavigationEntryImpl::kInvalidBindings = -1;
+
+NavigationEntry* NavigationEntry::Create() {
+ return new NavigationEntryImpl();
+}
+
+NavigationEntry* NavigationEntry::Create(const NavigationEntry& copy) {
+ return new NavigationEntryImpl(static_cast<const NavigationEntryImpl&>(copy));
+}
+
+NavigationEntryImpl* NavigationEntryImpl::FromNavigationEntry(
+ NavigationEntry* entry) {
+ return static_cast<NavigationEntryImpl*>(entry);
+}
+
+NavigationEntryImpl::NavigationEntryImpl()
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(NULL),
+ bindings_(kInvalidBindings),
+ page_type_(PAGE_TYPE_NORMAL),
+ update_virtual_url_with_url_(false),
+ page_id_(-1),
+ transition_type_(PAGE_TRANSITION_LINK),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_overriding_user_agent_(false),
+ is_renderer_initiated_(false),
+ should_replace_entry_(false),
+ should_clear_history_list_(false),
+ can_load_local_resources_(false) {
+}
+
+NavigationEntryImpl::NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated)
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(instance),
+ bindings_(kInvalidBindings),
+ page_type_(PAGE_TYPE_NORMAL),
+ url_(url),
+ referrer_(referrer),
+ update_virtual_url_with_url_(false),
+ title_(title),
+ page_id_(page_id),
+ transition_type_(transition_type),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_overriding_user_agent_(false),
+ is_renderer_initiated_(is_renderer_initiated),
+ should_replace_entry_(false),
+ should_clear_history_list_(false),
+ can_load_local_resources_(false) {
+}
+
+NavigationEntryImpl::~NavigationEntryImpl() {
+}
+
+int NavigationEntryImpl::GetUniqueID() const {
+ return unique_id_;
+}
+
+PageType NavigationEntryImpl::GetPageType() const {
+ return page_type_;
+}
+
+void NavigationEntryImpl::SetURL(const GURL& url) {
+ url_ = url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetURL() const {
+ return url_;
+}
+
+void NavigationEntryImpl::SetBaseURLForDataURL(const GURL& url) {
+ base_url_for_data_url_ = url;
+}
+
+const GURL& NavigationEntryImpl::GetBaseURLForDataURL() const {
+ return base_url_for_data_url_;
+}
+
+void NavigationEntryImpl::SetReferrer(const Referrer& referrer) {
+ referrer_ = referrer;
+}
+
+const Referrer& NavigationEntryImpl::GetReferrer() const {
+ return referrer_;
+}
+
+void NavigationEntryImpl::SetVirtualURL(const GURL& url) {
+ virtual_url_ = (url == url_) ? GURL() : url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetVirtualURL() const {
+ return virtual_url_.is_empty() ? url_ : virtual_url_;
+}
+
+void NavigationEntryImpl::SetTitle(const string16& title) {
+ title_ = title;
+ cached_display_title_.clear();
+}
+
+const string16& NavigationEntryImpl::GetTitle() const {
+ return title_;
+}
+
+void NavigationEntryImpl::SetPageState(const PageState& state) {
+ page_state_ = state;
+}
+
+const PageState& NavigationEntryImpl::GetPageState() const {
+ return page_state_;
+}
+
+void NavigationEntryImpl::SetPageID(int page_id) {
+ page_id_ = page_id;
+}
+
+int32 NavigationEntryImpl::GetPageID() const {
+ return page_id_;
+}
+
+void NavigationEntryImpl::set_site_instance(SiteInstanceImpl* site_instance) {
+ site_instance_ = site_instance;
+}
+
+void NavigationEntryImpl::SetBindings(int bindings) {
+ // Ensure this is set to a valid value, and that it stays the same once set.
+ CHECK_NE(bindings, kInvalidBindings);
+ CHECK(bindings_ == kInvalidBindings || bindings_ == bindings);
+ bindings_ = bindings;
+}
+
+const string16& NavigationEntryImpl::GetTitleForDisplay(
+ const std::string& languages) const {
+ // Most pages have real titles. Don't even bother caching anything if this is
+ // the case.
+ if (!title_.empty())
+ return title_;
+
+ // More complicated cases will use the URLs as the title. This result we will
+ // cache since it's more complicated to compute.
+ if (!cached_display_title_.empty())
+ return cached_display_title_;
+
+ // Use the virtual URL first if any, and fall back on using the real URL.
+ string16 title;
+ if (!virtual_url_.is_empty()) {
+ title = net::FormatUrl(virtual_url_, languages);
+ } else if (!url_.is_empty()) {
+ title = net::FormatUrl(url_, languages);
+ }
+
+ // For file:// URLs use the filename as the title, not the full path.
+ if (url_.SchemeIsFile()) {
+ string16::size_type slashpos = title.rfind('/');
+ if (slashpos != string16::npos)
+ title = title.substr(slashpos + 1);
+ }
+
+ ui::ElideString(title, kMaxTitleChars, &cached_display_title_);
+ return cached_display_title_;
+}
+
+bool NavigationEntryImpl::IsViewSourceMode() const {
+ return virtual_url_.SchemeIs(kViewSourceScheme);
+}
+
+void NavigationEntryImpl::SetTransitionType(
+ PageTransition transition_type) {
+ transition_type_ = transition_type;
+}
+
+PageTransition NavigationEntryImpl::GetTransitionType() const {
+ return transition_type_;
+}
+
+const GURL& NavigationEntryImpl::GetUserTypedURL() const {
+ return user_typed_url_;
+}
+
+void NavigationEntryImpl::SetHasPostData(bool has_post_data) {
+ has_post_data_ = has_post_data;
+}
+
+bool NavigationEntryImpl::GetHasPostData() const {
+ return has_post_data_;
+}
+
+void NavigationEntryImpl::SetPostID(int64 post_id) {
+ post_id_ = post_id;
+}
+
+int64 NavigationEntryImpl::GetPostID() const {
+ return post_id_;
+}
+
+void NavigationEntryImpl::SetBrowserInitiatedPostData(
+ const base::RefCountedMemory* data) {
+ browser_initiated_post_data_ = data;
+}
+
+const base::RefCountedMemory*
+NavigationEntryImpl::GetBrowserInitiatedPostData() const {
+ return browser_initiated_post_data_.get();
+}
+
+
+const FaviconStatus& NavigationEntryImpl::GetFavicon() const {
+ return favicon_;
+}
+
+FaviconStatus& NavigationEntryImpl::GetFavicon() {
+ return favicon_;
+}
+
+const SSLStatus& NavigationEntryImpl::GetSSL() const {
+ return ssl_;
+}
+
+SSLStatus& NavigationEntryImpl::GetSSL() {
+ return ssl_;
+}
+
+void NavigationEntryImpl::SetOriginalRequestURL(const GURL& original_url) {
+ original_request_url_ = original_url;
+}
+
+const GURL& NavigationEntryImpl::GetOriginalRequestURL() const {
+ return original_request_url_;
+}
+
+void NavigationEntryImpl::SetIsOverridingUserAgent(bool override) {
+ is_overriding_user_agent_ = override;
+}
+
+bool NavigationEntryImpl::GetIsOverridingUserAgent() const {
+ return is_overriding_user_agent_;
+}
+
+void NavigationEntryImpl::SetTimestamp(base::Time timestamp) {
+ timestamp_ = timestamp;
+}
+
+base::Time NavigationEntryImpl::GetTimestamp() const {
+ return timestamp_;
+}
+
+void NavigationEntryImpl::SetCanLoadLocalResources(bool allow) {
+ can_load_local_resources_ = allow;
+}
+
+bool NavigationEntryImpl::GetCanLoadLocalResources() const {
+ return can_load_local_resources_;
+}
+
+void NavigationEntryImpl::SetFrameToNavigate(const std::string& frame_name) {
+ frame_to_navigate_ = frame_name;
+}
+
+const std::string& NavigationEntryImpl::GetFrameToNavigate() const {
+ return frame_to_navigate_;
+}
+
+void NavigationEntryImpl::SetExtraData(const std::string& key,
+ const string16& data) {
+ extra_data_[key] = data;
+}
+
+bool NavigationEntryImpl::GetExtraData(const std::string& key,
+ string16* data) const {
+ std::map<std::string, string16>::const_iterator iter = extra_data_.find(key);
+ if (iter == extra_data_.end())
+ return false;
+ *data = iter->second;
+ return true;
+}
+
+void NavigationEntryImpl::ClearExtraData(const std::string& key) {
+ extra_data_.erase(key);
+}
+
+void NavigationEntryImpl::SetScreenshotPNGData(
+ scoped_refptr<base::RefCountedBytes> png_data) {
+ screenshot_ = png_data;
+ if (screenshot_.get())
+ UMA_HISTOGRAM_MEMORY_KB("Overscroll.ScreenshotSize", screenshot_->size());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/navigation_entry_impl.h b/chromium/content/browser/web_contents/navigation_entry_impl.h
new file mode 100644
index 00000000000..96d7ac9e2ba
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_entry_impl.h
@@ -0,0 +1,313 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/page_state.h"
+#include "content/public/common/ssl_status.h"
+
+namespace content {
+
+class CONTENT_EXPORT NavigationEntryImpl
+ : public NON_EXPORTED_BASE(NavigationEntry) {
+ public:
+ static NavigationEntryImpl* FromNavigationEntry(NavigationEntry* entry);
+
+ // The value of bindings() before it is set during commit.
+ static int kInvalidBindings;
+
+ NavigationEntryImpl();
+ NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated);
+ virtual ~NavigationEntryImpl();
+
+ // NavigationEntry implementation:
+ virtual int GetUniqueID() const OVERRIDE;
+ virtual PageType GetPageType() const OVERRIDE;
+ virtual void SetURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void SetBaseURLForDataURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetBaseURLForDataURL() const OVERRIDE;
+ virtual void SetReferrer(const Referrer& referrer) OVERRIDE;
+ virtual const Referrer& GetReferrer() const OVERRIDE;
+ virtual void SetVirtualURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetVirtualURL() const OVERRIDE;
+ virtual void SetTitle(const string16& title) OVERRIDE;
+ virtual const string16& GetTitle() const OVERRIDE;
+ virtual void SetPageState(const PageState& state) OVERRIDE;
+ virtual const PageState& GetPageState() const OVERRIDE;
+ virtual void SetPageID(int page_id) OVERRIDE;
+ virtual int32 GetPageID() const OVERRIDE;
+ virtual const string16& GetTitleForDisplay(
+ const std::string& languages) const OVERRIDE;
+ virtual bool IsViewSourceMode() const OVERRIDE;
+ virtual void SetTransitionType(PageTransition transition_type) OVERRIDE;
+ virtual PageTransition GetTransitionType() const OVERRIDE;
+ virtual const GURL& GetUserTypedURL() const OVERRIDE;
+ virtual void SetHasPostData(bool has_post_data) OVERRIDE;
+ virtual bool GetHasPostData() const OVERRIDE;
+ virtual void SetPostID(int64 post_id) OVERRIDE;
+ virtual int64 GetPostID() const OVERRIDE;
+ virtual void SetBrowserInitiatedPostData(
+ const base::RefCountedMemory* data) OVERRIDE;
+ virtual const base::RefCountedMemory*
+ GetBrowserInitiatedPostData() const OVERRIDE;
+ virtual const FaviconStatus& GetFavicon() const OVERRIDE;
+ virtual FaviconStatus& GetFavicon() OVERRIDE;
+ virtual const SSLStatus& GetSSL() const OVERRIDE;
+ virtual SSLStatus& GetSSL() OVERRIDE;
+ virtual void SetOriginalRequestURL(const GURL& original_url) OVERRIDE;
+ virtual const GURL& GetOriginalRequestURL() const OVERRIDE;
+ virtual void SetIsOverridingUserAgent(bool override) OVERRIDE;
+ virtual bool GetIsOverridingUserAgent() const OVERRIDE;
+ virtual void SetTimestamp(base::Time timestamp) OVERRIDE;
+ virtual base::Time GetTimestamp() const OVERRIDE;
+ virtual void SetCanLoadLocalResources(bool allow) OVERRIDE;
+ virtual bool GetCanLoadLocalResources() const OVERRIDE;
+ virtual void SetFrameToNavigate(const std::string& frame_name) OVERRIDE;
+ virtual const std::string& GetFrameToNavigate() const OVERRIDE;
+ virtual void SetExtraData(const std::string& key,
+ const string16& data) OVERRIDE;
+ virtual bool GetExtraData(const std::string& key,
+ string16* data) const OVERRIDE;
+ virtual void ClearExtraData(const std::string& key) OVERRIDE;
+
+ void set_unique_id(int unique_id) {
+ unique_id_ = unique_id;
+ }
+
+ // The SiteInstance tells us how to share sub-processes. This is a reference
+ // counted pointer to a shared site instance.
+ //
+ // Note that the SiteInstance should usually not be changed after it is set,
+ // but this may happen if the NavigationEntry was cloned and needs to use a
+ // different SiteInstance.
+ void set_site_instance(SiteInstanceImpl* site_instance);
+ SiteInstanceImpl* site_instance() const {
+ return site_instance_.get();
+ }
+
+ // Remember the set of bindings granted to this NavigationEntry at the time
+ // of commit, to ensure that we do not grant it additional bindings if we
+ // navigate back to it in the future. This can only be changed once.
+ void SetBindings(int bindings);
+ int bindings() const {
+ return bindings_;
+ }
+
+ void set_page_type(PageType page_type) {
+ page_type_ = page_type;
+ }
+
+ bool has_virtual_url() const {
+ return !virtual_url_.is_empty();
+ }
+
+ bool update_virtual_url_with_url() const {
+ return update_virtual_url_with_url_;
+ }
+ void set_update_virtual_url_with_url(bool update) {
+ update_virtual_url_with_url_ = update;
+ }
+
+ // Extra headers (separated by \n) to send during the request.
+ void set_extra_headers(const std::string& extra_headers) {
+ extra_headers_ = extra_headers;
+ }
+ const std::string& extra_headers() const {
+ return extra_headers_;
+ }
+
+ // Whether this (pending) navigation is renderer-initiated. Resets to false
+ // for all types of navigations after commit.
+ void set_is_renderer_initiated(bool is_renderer_initiated) {
+ is_renderer_initiated_ = is_renderer_initiated;
+ }
+ bool is_renderer_initiated() const {
+ return is_renderer_initiated_;
+ }
+
+ void set_user_typed_url(const GURL& user_typed_url) {
+ user_typed_url_ = user_typed_url;
+ }
+
+ // Enumerations of the possible restore types.
+ enum RestoreType {
+ // Restore from the previous session.
+ RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ RESTORE_LAST_SESSION_CRASHED,
+
+ // The entry has been restored from the current session. This is used when
+ // the user issues 'reopen closed tab'.
+ RESTORE_CURRENT_SESSION,
+
+ // The entry was not restored.
+ RESTORE_NONE
+ };
+
+ // The RestoreType for this entry. This is set if the entry was retored. This
+ // is set to RESTORE_NONE once the entry is loaded.
+ void set_restore_type(RestoreType type) {
+ restore_type_ = type;
+ }
+ RestoreType restore_type() const {
+ return restore_type_;
+ }
+
+ void set_transferred_global_request_id(
+ const GlobalRequestID& transferred_global_request_id) {
+ transferred_global_request_id_ = transferred_global_request_id;
+ }
+
+ GlobalRequestID transferred_global_request_id() const {
+ return transferred_global_request_id_;
+ }
+
+ // Whether this (pending) navigation needs to replace current entry.
+ // Resets to false after commit.
+ bool should_replace_entry() const {
+ return should_replace_entry_;
+ }
+
+ void set_should_replace_entry(bool should_replace_entry) {
+ should_replace_entry_ = should_replace_entry;
+ }
+
+ void SetScreenshotPNGData(scoped_refptr<base::RefCountedBytes> png_data);
+ const scoped_refptr<base::RefCountedBytes> screenshot() const {
+ return screenshot_;
+ }
+
+ // Whether this (pending) navigation should clear the session history. Resets
+ // to false after commit.
+ bool should_clear_history_list() const {
+ return should_clear_history_list_;
+ }
+ void set_should_clear_history_list(bool should_clear_history_list) {
+ should_clear_history_list_ = should_clear_history_list;
+ }
+
+ private:
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ // Session/Tab restore save portions of this class so that it can be recreated
+ // later. If you add a new field that needs to be persisted you'll have to
+ // update SessionService/TabRestoreService and Android WebView
+ // state_serializer.cc appropriately.
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
+ // See the accessors above for descriptions.
+ int unique_id_;
+ scoped_refptr<SiteInstanceImpl> site_instance_;
+ // TODO(creis): Persist bindings_. http://crbug.com/173672.
+ int bindings_;
+ PageType page_type_;
+ GURL url_;
+ Referrer referrer_;
+ GURL virtual_url_;
+ bool update_virtual_url_with_url_;
+ string16 title_;
+ FaviconStatus favicon_;
+ PageState page_state_;
+ int32 page_id_;
+ SSLStatus ssl_;
+ PageTransition transition_type_;
+ GURL user_typed_url_;
+ bool has_post_data_;
+ int64 post_id_;
+ RestoreType restore_type_;
+ GURL original_request_url_;
+ bool is_overriding_user_agent_;
+ base::Time timestamp_;
+
+ // This member is not persisted with session restore because it is transient.
+ // If the post request succeeds, this field is cleared since the same
+ // information is stored in |content_state_| above. It is also only shallow
+ // copied with compiler provided copy constructor.
+ scoped_refptr<const base::RefCountedMemory> browser_initiated_post_data_;
+
+ // This is also a transient member (i.e. is not persisted with session
+ // restore). The screenshot of a page is taken when navigating away from the
+ // page. This screenshot is displayed during an overscroll-navigation
+ // gesture. |screenshot_| will be NULL when the screenshot is not available
+ // (e.g. after a session restore, or if taking the screenshot of a page
+ // failed). The UI is responsible for dealing with missing screenshots
+ // appropriately (e.g. display a placeholder image instead).
+ scoped_refptr<base::RefCountedBytes> screenshot_;
+
+ // This member is not persisted with session restore.
+ std::string extra_headers_;
+
+ // Used for specifying base URL for pages loaded via data URLs. Only used and
+ // persisted by Android WebView.
+ GURL base_url_for_data_url_;
+
+ // Whether the entry, while loading, was created for a renderer-initiated
+ // navigation. This dictates whether the URL should be displayed before the
+ // navigation commits. It is cleared on commit and not persisted.
+ bool is_renderer_initiated_;
+
+ // This is a cached version of the result of GetTitleForDisplay. It prevents
+ // us from having to do URL formatting on the URL every time the title is
+ // displayed. When the URL, virtual URL, or title is set, this should be
+ // cleared to force a refresh.
+ mutable string16 cached_display_title_;
+
+ // In case a navigation is transferred to a new RVH but the request has
+ // been generated in the renderer already, this identifies the old request so
+ // that it can be resumed. The old request is stored until the
+ // ResourceDispatcher receives the navigation from the renderer which
+ // carries this |transferred_global_request_id_| annotation. Once the request
+ // is transferred to the new process, this is cleared and the request
+ // continues as normal.
+ GlobalRequestID transferred_global_request_id_;
+
+ // This is set to true when this entry is being reloaded and due to changes in
+ // the state of the URL, it has to be reloaded in a different site instance.
+ // In such case, we must treat it as an existing navigation in the new site
+ // instance, instead of a new navigation. This value should not be persisted
+ // and is not needed after the entry commits.
+ //
+ // We also use this flag for cross-process redirect navigations, so that the
+ // browser will replace the current navigation entry (which is the page
+ // doing the redirect).
+ bool should_replace_entry_;
+
+ // This is set to true when this entry's navigation should clear the session
+ // history both on the renderer and browser side. The browser side history
+ // won't be cleared until the renderer has committed this navigation. This
+ // entry is not persisted by the session restore system, as it is always
+ // reset to false after commit.
+ bool should_clear_history_list_;
+
+ // Set when this entry should be able to access local file:// resources. This
+ // value is not needed after the entry commits and is not persisted.
+ bool can_load_local_resources_;
+
+ // If not empty, the name of the frame to navigate. This field is not
+ // persisted, because it is currently only used in tests.
+ std::string frame_to_navigate_;
+
+ // Used to store extra data to support browser features. This member is not
+ // persisted, unless specific data is taken out/put back in at save/restore
+ // time (see TabNavigation for an example of this).
+ std::map<std::string, string16> extra_data_;
+
+ // Copy and assignment is explicitly allowed for this class.
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_NAVIGATION_ENTRY_IMPL_H_
diff --git a/chromium/content/browser/web_contents/navigation_entry_impl_unittest.cc b/chromium/content/browser/web_contents/navigation_entry_impl_unittest.cc
new file mode 100644
index 00000000000..138ed74cfd5
--- /dev/null
+++ b/chromium/content/browser/web_contents/navigation_entry_impl_unittest.cc
@@ -0,0 +1,241 @@
+// 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 "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/public/common/ssl_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class NavigationEntryTest : public testing::Test {
+ public:
+ NavigationEntryTest() : instance_(NULL) {
+ }
+
+ virtual void SetUp() {
+ entry1_.reset(new NavigationEntryImpl);
+
+#if !defined(OS_IOS)
+ instance_ = static_cast<SiteInstanceImpl*>(SiteInstance::Create(NULL));
+#endif
+ entry2_.reset(new NavigationEntryImpl(
+ instance_, 3,
+ GURL("test:url"),
+ Referrer(GURL("from"), WebKit::WebReferrerPolicyDefault),
+ ASCIIToUTF16("title"),
+ PAGE_TRANSITION_TYPED,
+ false));
+ }
+
+ virtual void TearDown() {
+ }
+
+ protected:
+ scoped_ptr<NavigationEntryImpl> entry1_;
+ scoped_ptr<NavigationEntryImpl> entry2_;
+ // SiteInstances are deleted when their NavigationEntries are gone.
+ SiteInstanceImpl* instance_;
+};
+
+// Test unique ID accessors
+TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs) {
+ // Two entries should have different IDs by default
+ EXPECT_NE(entry1_->GetUniqueID(), entry2_->GetUniqueID());
+
+ // Can set an entry to have the same ID as another
+ entry2_->set_unique_id(entry1_->GetUniqueID());
+ EXPECT_EQ(entry1_->GetUniqueID(), entry2_->GetUniqueID());
+}
+
+// Test URL accessors
+TEST_F(NavigationEntryTest, NavigationEntryURLs) {
+ // Start with no virtual_url (even if a url is set)
+ EXPECT_FALSE(entry1_->has_virtual_url());
+ EXPECT_FALSE(entry2_->has_virtual_url());
+
+ EXPECT_EQ(GURL(), entry1_->GetURL());
+ EXPECT_EQ(GURL(), entry1_->GetVirtualURL());
+ EXPECT_TRUE(entry1_->GetTitleForDisplay(std::string()).empty());
+
+ // Setting URL affects virtual_url and GetTitleForDisplay
+ entry1_->SetURL(GURL("http://www.google.com"));
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetURL());
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetVirtualURL());
+ EXPECT_EQ(ASCIIToUTF16("www.google.com"),
+ entry1_->GetTitleForDisplay(std::string()));
+
+ // file:/// URLs should only show the filename.
+ entry1_->SetURL(GURL("file:///foo/bar baz.txt"));
+ EXPECT_EQ(ASCIIToUTF16("bar baz.txt"),
+ entry1_->GetTitleForDisplay(std::string()));
+
+ // Title affects GetTitleForDisplay
+ entry1_->SetTitle(ASCIIToUTF16("Google"));
+ EXPECT_EQ(ASCIIToUTF16("Google"), entry1_->GetTitleForDisplay(std::string()));
+
+ // Setting virtual_url doesn't affect URL
+ entry2_->SetVirtualURL(GURL("display:url"));
+ EXPECT_TRUE(entry2_->has_virtual_url());
+ EXPECT_EQ(GURL("test:url"), entry2_->GetURL());
+ EXPECT_EQ(GURL("display:url"), entry2_->GetVirtualURL());
+
+ // Having a title set in constructor overrides virtual URL
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitleForDisplay(std::string()));
+
+ // User typed URL is independent of the others
+ EXPECT_EQ(GURL(), entry1_->GetUserTypedURL());
+ EXPECT_EQ(GURL(), entry2_->GetUserTypedURL());
+ entry2_->set_user_typed_url(GURL("typedurl"));
+ EXPECT_EQ(GURL("typedurl"), entry2_->GetUserTypedURL());
+}
+
+// Test Favicon inner class construction.
+TEST_F(NavigationEntryTest, NavigationEntryFavicons) {
+ EXPECT_EQ(GURL(), entry1_->GetFavicon().url);
+ EXPECT_FALSE(entry1_->GetFavicon().valid);
+}
+
+// Test SSLStatus inner class
+TEST_F(NavigationEntryTest, NavigationEntrySSLStatus) {
+ // Default (unknown)
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry1_->GetSSL().security_style);
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry2_->GetSSL().security_style);
+ EXPECT_EQ(0, entry1_->GetSSL().cert_id);
+ EXPECT_EQ(0U, entry1_->GetSSL().cert_status);
+ EXPECT_EQ(-1, entry1_->GetSSL().security_bits);
+ int content_status = entry1_->GetSSL().content_status;
+ EXPECT_FALSE(!!(content_status & SSLStatus::DISPLAYED_INSECURE_CONTENT));
+ EXPECT_FALSE(!!(content_status & SSLStatus::RAN_INSECURE_CONTENT));
+}
+
+// Test other basic accessors
+TEST_F(NavigationEntryTest, NavigationEntryAccessors) {
+ // SiteInstance
+ EXPECT_TRUE(entry1_->site_instance() == NULL);
+ EXPECT_EQ(instance_, entry2_->site_instance());
+ entry1_->set_site_instance(instance_);
+ EXPECT_EQ(instance_, entry1_->site_instance());
+
+ // Page type
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry1_->GetPageType());
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry2_->GetPageType());
+ entry2_->set_page_type(PAGE_TYPE_INTERSTITIAL);
+ EXPECT_EQ(PAGE_TYPE_INTERSTITIAL, entry2_->GetPageType());
+
+ // Referrer
+ EXPECT_EQ(GURL(), entry1_->GetReferrer().url);
+ EXPECT_EQ(GURL("from"), entry2_->GetReferrer().url);
+ entry2_->SetReferrer(
+ Referrer(GURL("from2"), WebKit::WebReferrerPolicyDefault));
+ EXPECT_EQ(GURL("from2"), entry2_->GetReferrer().url);
+
+ // Title
+ EXPECT_EQ(string16(), entry1_->GetTitle());
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitle());
+ entry2_->SetTitle(ASCIIToUTF16("title2"));
+ EXPECT_EQ(ASCIIToUTF16("title2"), entry2_->GetTitle());
+
+ // State
+ EXPECT_FALSE(entry1_->GetPageState().IsValid());
+ EXPECT_FALSE(entry2_->GetPageState().IsValid());
+ entry2_->SetPageState(PageState::CreateFromEncodedData("state"));
+ EXPECT_EQ("state", entry2_->GetPageState().ToEncodedData());
+
+ // Page ID
+ EXPECT_EQ(-1, entry1_->GetPageID());
+ EXPECT_EQ(3, entry2_->GetPageID());
+ entry2_->SetPageID(2);
+ EXPECT_EQ(2, entry2_->GetPageID());
+
+ // Transition type
+ EXPECT_EQ(PAGE_TRANSITION_LINK, entry1_->GetTransitionType());
+ EXPECT_EQ(PAGE_TRANSITION_TYPED, entry2_->GetTransitionType());
+ entry2_->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ EXPECT_EQ(PAGE_TRANSITION_RELOAD, entry2_->GetTransitionType());
+
+ // Is renderer initiated
+ EXPECT_FALSE(entry1_->is_renderer_initiated());
+ EXPECT_FALSE(entry2_->is_renderer_initiated());
+ entry2_->set_is_renderer_initiated(true);
+ EXPECT_TRUE(entry2_->is_renderer_initiated());
+
+ // Post Data
+ EXPECT_FALSE(entry1_->GetHasPostData());
+ EXPECT_FALSE(entry2_->GetHasPostData());
+ entry2_->SetHasPostData(true);
+ EXPECT_TRUE(entry2_->GetHasPostData());
+
+ // Restored
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry1_->restore_type());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry2_->restore_type());
+ entry2_->set_restore_type(
+ NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY);
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ entry2_->restore_type());
+
+ // Original URL
+ EXPECT_EQ(GURL(), entry1_->GetOriginalRequestURL());
+ EXPECT_EQ(GURL(), entry2_->GetOriginalRequestURL());
+ entry2_->SetOriginalRequestURL(GURL("original_url"));
+ EXPECT_EQ(GURL("original_url"), entry2_->GetOriginalRequestURL());
+
+ // User agent override
+ EXPECT_FALSE(entry1_->GetIsOverridingUserAgent());
+ EXPECT_FALSE(entry2_->GetIsOverridingUserAgent());
+ entry2_->SetIsOverridingUserAgent(true);
+ EXPECT_TRUE(entry2_->GetIsOverridingUserAgent());
+
+ // Browser initiated post data
+ EXPECT_EQ(NULL, entry1_->GetBrowserInitiatedPostData());
+ EXPECT_EQ(NULL, entry2_->GetBrowserInitiatedPostData());
+ const int length = 11;
+ const unsigned char* raw_data =
+ reinterpret_cast<const unsigned char*>("post\n\n\0data");
+ std::vector<unsigned char> post_data_vector(raw_data, raw_data+length);
+ scoped_refptr<base::RefCountedBytes> post_data =
+ base::RefCountedBytes::TakeVector(&post_data_vector);
+ entry2_->SetBrowserInitiatedPostData(post_data.get());
+ EXPECT_EQ(post_data->front(),
+ entry2_->GetBrowserInitiatedPostData()->front());
+
+ // Frame to navigate.
+ EXPECT_TRUE(entry1_->GetFrameToNavigate().empty());
+ EXPECT_TRUE(entry2_->GetFrameToNavigate().empty());
+}
+
+// Test timestamps.
+TEST_F(NavigationEntryTest, NavigationEntryTimestamps) {
+ EXPECT_EQ(base::Time(), entry1_->GetTimestamp());
+ const base::Time now = base::Time::Now();
+ entry1_->SetTimestamp(now);
+ EXPECT_EQ(now, entry1_->GetTimestamp());
+}
+
+// Test extra data stored in the navigation entry.
+TEST_F(NavigationEntryTest, NavigationEntryExtraData) {
+ string16 test_data = ASCIIToUTF16("my search terms");
+ string16 output;
+ entry1_->SetExtraData("search_terms", test_data);
+
+ EXPECT_FALSE(entry1_->GetExtraData("non_existent_key", &output));
+ EXPECT_EQ(ASCIIToUTF16(""), output);
+ EXPECT_TRUE(entry1_->GetExtraData("search_terms", &output));
+ EXPECT_EQ(test_data, output);
+ // Data is cleared.
+ entry1_->ClearExtraData("search_terms");
+ // Content in |output| is not modified if data is not present at the key.
+ EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output));
+ EXPECT_EQ(test_data, output);
+ // Using an empty string shows that the data is not present in the map.
+ string16 output2;
+ EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output2));
+ EXPECT_EQ(ASCIIToUTF16(""), output2);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/render_view_host_manager.cc b/chromium/content/browser/web_contents/render_view_host_manager.cc
new file mode 100644
index 00000000000..9d29a09c6a3
--- /dev/null
+++ b/chromium/content/browser/web_contents/render_view_host_manager.cc
@@ -0,0 +1,1062 @@
+// 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 "content/browser/web_contents/render_view_host_manager.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "content/browser/devtools/render_view_devtools_agent_host.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/browser/webui/web_ui_impl.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+
+namespace content {
+
+RenderViewHostManager::PendingNavigationParams::PendingNavigationParams() {
+}
+
+RenderViewHostManager::PendingNavigationParams::PendingNavigationParams(
+ const GlobalRequestID& global_request_id)
+ : global_request_id(global_request_id) {
+}
+
+RenderViewHostManager::RenderViewHostManager(
+ RenderViewHostDelegate* render_view_delegate,
+ RenderWidgetHostDelegate* render_widget_delegate,
+ Delegate* delegate)
+ : delegate_(delegate),
+ cross_navigation_pending_(false),
+ render_view_delegate_(render_view_delegate),
+ render_widget_delegate_(render_widget_delegate),
+ render_view_host_(NULL),
+ pending_render_view_host_(NULL),
+ interstitial_page_(NULL) {
+}
+
+RenderViewHostManager::~RenderViewHostManager() {
+ if (pending_render_view_host_)
+ CancelPending();
+
+ // We should always have a main RenderViewHost except in some tests.
+ RenderViewHostImpl* render_view_host = render_view_host_;
+ render_view_host_ = NULL;
+ if (render_view_host)
+ render_view_host->Shutdown();
+
+ // Shut down any swapped out RenderViewHosts.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ iter->second->Shutdown();
+ }
+}
+
+void RenderViewHostManager::Init(BrowserContext* browser_context,
+ SiteInstance* site_instance,
+ int routing_id,
+ int main_frame_routing_id) {
+ // Create a RenderViewHost, once we have an instance. It is important to
+ // immediately give this SiteInstance to a RenderViewHost so that it is
+ // ref counted.
+ if (!site_instance)
+ site_instance = SiteInstance::Create(browser_context);
+ render_view_host_ = static_cast<RenderViewHostImpl*>(
+ RenderViewHostFactory::Create(
+ site_instance, render_view_delegate_, render_widget_delegate_,
+ routing_id, main_frame_routing_id, false));
+
+ // Keep track of renderer processes as they start to shut down or are
+ // crashed/killed.
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSING,
+ NotificationService::AllSources());
+}
+
+RenderViewHostImpl* RenderViewHostManager::current_host() const {
+ return render_view_host_;
+}
+
+RenderViewHostImpl* RenderViewHostManager::pending_render_view_host() const {
+ return pending_render_view_host_;
+}
+
+RenderWidgetHostView* RenderViewHostManager::GetRenderWidgetHostView() const {
+ if (interstitial_page_)
+ return interstitial_page_->GetView();
+ if (!render_view_host_)
+ return NULL;
+ return render_view_host_->GetView();
+}
+
+void RenderViewHostManager::SetPendingWebUI(const NavigationEntryImpl& entry) {
+ pending_web_ui_.reset(
+ delegate_->CreateWebUIForRenderManager(entry.GetURL()));
+ pending_and_current_web_ui_.reset();
+
+ // If we have assigned (zero or more) bindings to this NavigationEntry in the
+ // past, make sure we're not granting it different bindings than it had
+ // before. If so, note it and don't give it any bindings, to avoid a
+ // potential privilege escalation.
+ if (pending_web_ui_.get() &&
+ entry.bindings() != NavigationEntryImpl::kInvalidBindings &&
+ pending_web_ui_->GetBindings() != entry.bindings()) {
+ RecordAction(UserMetricsAction("ProcessSwapBindingsMismatch_RVHM"));
+ pending_web_ui_.reset();
+ }
+}
+
+RenderViewHostImpl* RenderViewHostManager::Navigate(
+ const NavigationEntryImpl& entry) {
+ TRACE_EVENT0("browser", "RenderViewHostManager:Navigate");
+ // Create a pending RenderViewHost. It will give us the one we should use
+ RenderViewHostImpl* dest_render_view_host =
+ static_cast<RenderViewHostImpl*>(UpdateRendererStateForNavigate(entry));
+ if (!dest_render_view_host)
+ return NULL; // We weren't able to create a pending render view host.
+
+ // If the current render_view_host_ isn't live, we should create it so
+ // that we don't show a sad tab while the dest_render_view_host fetches
+ // its first page. (Bug 1145340)
+ if (dest_render_view_host != render_view_host_ &&
+ !render_view_host_->IsRenderViewLive()) {
+ // Note: we don't call InitRenderView here because we are navigating away
+ // soon anyway, and we don't have the NavigationEntry for this host.
+ delegate_->CreateRenderViewForRenderManager(render_view_host_,
+ MSG_ROUTING_NONE);
+ }
+
+ // If the renderer crashed, then try to create a new one to satisfy this
+ // navigation request.
+ if (!dest_render_view_host->IsRenderViewLive()) {
+ // Recreate the opener chain.
+ int opener_route_id = delegate_->CreateOpenerRenderViewsForRenderManager(
+ dest_render_view_host->GetSiteInstance());
+ if (!InitRenderView(dest_render_view_host, opener_route_id))
+ return NULL;
+
+ // Now that we've created a new renderer, be sure to hide it if it isn't
+ // our primary one. Otherwise, we might crash if we try to call Show()
+ // on it later.
+ if (dest_render_view_host != render_view_host_ &&
+ dest_render_view_host->GetView()) {
+ dest_render_view_host->GetView()->Hide();
+ } else {
+ // This is our primary renderer, notify here as we won't be calling
+ // CommitPending (which does the notify).
+ RenderViewHost* null_rvh = NULL;
+ std::pair<RenderViewHost*, RenderViewHost*> details =
+ std::make_pair(null_rvh, render_view_host_);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(
+ &delegate_->GetControllerForRenderManager()),
+ Details<std::pair<RenderViewHost*, RenderViewHost*> >(
+ &details));
+ }
+ }
+
+ return dest_render_view_host;
+}
+
+void RenderViewHostManager::Stop() {
+ render_view_host_->Stop();
+
+ // If we are cross-navigating, we should stop the pending renderers. This
+ // will lead to a DidFailProvisionalLoad, which will properly destroy them.
+ if (cross_navigation_pending_) {
+ pending_render_view_host_->Send(
+ new ViewMsg_Stop(pending_render_view_host_->GetRoutingID()));
+ }
+}
+
+void RenderViewHostManager::SetIsLoading(bool is_loading) {
+ render_view_host_->SetIsLoading(is_loading);
+ if (pending_render_view_host_)
+ pending_render_view_host_->SetIsLoading(is_loading);
+}
+
+bool RenderViewHostManager::ShouldCloseTabOnUnresponsiveRenderer() {
+ if (!cross_navigation_pending_)
+ return true;
+
+ // If the tab becomes unresponsive during {before}unload while doing a
+ // cross-site navigation, proceed with the navigation. (This assumes that
+ // the pending RenderViewHost is still responsive.)
+ if (render_view_host_->is_waiting_for_unload_ack()) {
+ // The request has been started and paused while we're waiting for the
+ // unload handler to finish. We'll pretend that it did. The pending
+ // renderer will then be swapped in as part of the usual DidNavigate logic.
+ // (If the unload handler later finishes, this call will be ignored because
+ // the pending_nav_params_ state will already be cleaned up.)
+ current_host()->OnSwappedOut(true);
+ } else if (render_view_host_->is_waiting_for_beforeunload_ack()) {
+ // Haven't gotten around to starting the request, because we're still
+ // waiting for the beforeunload handler to finish. We'll pretend that it
+ // did finish, to let the navigation proceed. Note that there's a danger
+ // that the beforeunload handler will later finish and possibly return
+ // false (meaning the navigation should not proceed), but we'll ignore it
+ // in this case because it took too long.
+ if (pending_render_view_host_->are_navigations_suspended())
+ pending_render_view_host_->SetNavigationsSuspended(
+ false, base::TimeTicks::Now());
+ }
+ return false;
+}
+
+void RenderViewHostManager::SwappedOut(RenderViewHost* render_view_host) {
+ // Make sure this is from our current RVH, and that we have a pending
+ // navigation from OnCrossSiteResponse. (There may be no pending navigation
+ // for data URLs that don't make network requests, for example.) If not,
+ // just return early and ignore.
+ if (render_view_host != render_view_host_ || !pending_nav_params_.get()) {
+ pending_nav_params_.reset();
+ return;
+ }
+
+ // Now that the unload handler has run, we need to resume the paused response.
+ if (pending_render_view_host_) {
+ RenderProcessHostImpl* pending_process =
+ static_cast<RenderProcessHostImpl*>(
+ pending_render_view_host_->GetProcess());
+ pending_process->ResumeDeferredNavigation(
+ pending_nav_params_->global_request_id);
+ }
+ pending_nav_params_.reset();
+}
+
+void RenderViewHostManager::DidNavigateMainFrame(
+ RenderViewHost* render_view_host) {
+ if (!cross_navigation_pending_) {
+ DCHECK(!pending_render_view_host_);
+
+ // We should only hear this from our current renderer.
+ DCHECK(render_view_host == render_view_host_);
+
+ // Even when there is no pending RVH, there may be a pending Web UI.
+ if (pending_web_ui())
+ CommitPending();
+ return;
+ }
+
+ if (render_view_host == pending_render_view_host_) {
+ // The pending cross-site navigation completed, so show the renderer.
+ // If it committed without sending network requests (e.g., data URLs),
+ // then we still need to swap out the old RVH first and run its unload
+ // handler. OK for that to happen in the background.
+ if (pending_render_view_host_->HasPendingCrossSiteRequest())
+ SwapOutOldPage();
+
+ CommitPending();
+ cross_navigation_pending_ = false;
+ } else if (render_view_host == render_view_host_) {
+ // A navigation in the original page has taken place. Cancel the pending
+ // one.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ } else {
+ // No one else should be sending us DidNavigate in this state.
+ DCHECK(false);
+ }
+}
+
+void RenderViewHostManager::DidDisownOpener(RenderViewHost* render_view_host) {
+ // Notify all swapped out hosts, including the pending RVH.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ DCHECK_NE(iter->second->GetSiteInstance(),
+ current_host()->GetSiteInstance());
+ iter->second->DisownOpener();
+ }
+}
+
+void RenderViewHostManager::RendererAbortedProvisionalLoad(
+ RenderViewHost* render_view_host) {
+ // We used to cancel the pending renderer here for cross-site downloads.
+ // However, it's not safe to do that because the download logic repeatedly
+ // looks for this WebContents based on a render view ID. Instead, we just
+ // leave the pending renderer around until the next navigation event
+ // (Navigate, DidNavigate, etc), which will clean it up properly.
+ // TODO(creis): All of this will go away when we move the cross-site logic
+ // to ResourceDispatcherHost, so that we intercept responses rather than
+ // navigation events. (That's necessary to support onunload anyway.) Once
+ // we've made that change, we won't create a pending renderer until we know
+ // the response is not a download.
+}
+
+void RenderViewHostManager::RendererProcessClosing(
+ RenderProcessHost* render_process_host) {
+ // Remove any swapped out RVHs from this process, so that we don't try to
+ // swap them back in while the process is exiting. Start by finding them,
+ // since there could be more than one.
+ std::list<int> ids_to_remove;
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ if (iter->second->GetProcess() == render_process_host)
+ ids_to_remove.push_back(iter->first);
+ }
+
+ // Now delete them.
+ while (!ids_to_remove.empty()) {
+ swapped_out_hosts_[ids_to_remove.back()]->Shutdown();
+ swapped_out_hosts_.erase(ids_to_remove.back());
+ ids_to_remove.pop_back();
+ }
+}
+
+void RenderViewHostManager::ShouldClosePage(
+ bool for_cross_site_transition,
+ bool proceed,
+ const base::TimeTicks& proceed_time) {
+ if (for_cross_site_transition) {
+ // Ignore if we're not in a cross-site navigation.
+ if (!cross_navigation_pending_)
+ return;
+
+ if (proceed) {
+ // Ok to unload the current page, so proceed with the cross-site
+ // navigation. Note that if navigations are not currently suspended, it
+ // might be because the renderer was deemed unresponsive and this call was
+ // already made by ShouldCloseTabOnUnresponsiveRenderer. In that case, it
+ // is ok to do nothing here.
+ if (pending_render_view_host_ &&
+ pending_render_view_host_->are_navigations_suspended()) {
+ pending_render_view_host_->SetNavigationsSuspended(false, proceed_time);
+ }
+ } else {
+ // Current page says to cancel.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+ } else {
+ // Non-cross site transition means closing the entire tab.
+ bool proceed_to_fire_unload;
+ delegate_->BeforeUnloadFiredFromRenderManager(proceed, proceed_time,
+ &proceed_to_fire_unload);
+
+ if (proceed_to_fire_unload) {
+ // This is not a cross-site navigation, the tab is being closed.
+ render_view_host_->ClosePage();
+ }
+ }
+}
+
+void RenderViewHostManager::OnCrossSiteResponse(
+ RenderViewHost* pending_render_view_host,
+ const GlobalRequestID& global_request_id) {
+ // This should be called when the pending RVH is ready to commit.
+ DCHECK_EQ(pending_render_view_host_, pending_render_view_host);
+
+ // Remember the request ID until the unload handler has run.
+ pending_nav_params_.reset(new PendingNavigationParams(global_request_id));
+
+ // Run the unload handler of the current page.
+ SwapOutOldPage();
+}
+
+void RenderViewHostManager::SwapOutOldPage() {
+ // Should only see this while we have a pending renderer.
+ if (!cross_navigation_pending_)
+ return;
+ DCHECK(pending_render_view_host_);
+
+ // Tell the old renderer it is being swapped out. This will fire the unload
+ // handler (without firing the beforeunload handler a second time). When the
+ // unload handler finishes and the navigation completes, we will send a
+ // message to the ResourceDispatcherHost, allowing the pending RVH's response
+ // to resume.
+ render_view_host_->SwapOut();
+
+ // ResourceDispatcherHost has told us to run the onunload handler, which
+ // means it is not a download or unsafe page, and we are going to perform the
+ // navigation. Thus, we no longer need to remember that the RenderViewHost
+ // is part of a pending cross-site request.
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(false);
+}
+
+void RenderViewHostManager::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_RENDERER_PROCESS_CLOSED:
+ case NOTIFICATION_RENDERER_PROCESS_CLOSING:
+ RendererProcessClosing(
+ Source<RenderProcessHost>(source).ptr());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+}
+
+bool RenderViewHostManager::ShouldTransitionCrossSite() {
+ // False in the single-process mode, as it makes RVHs to accumulate
+ // in swapped_out_hosts_.
+ // True if we are using process-per-site-instance (default) or
+ // process-per-site (kProcessPerSite).
+ return
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab);
+}
+
+bool RenderViewHostManager::ShouldSwapProcessesForNavigation(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const {
+ DCHECK(new_entry);
+
+ // Check for reasons to swap processes even if we are in a process model that
+ // doesn't usually swap (e.g., process-per-tab).
+
+ // For security, we should transition between processes when one is a Web UI
+ // page and one isn't. If there's no curr_entry, check the current RVH's
+ // site, which might already be committed to a Web UI URL (such as the NTP).
+ const GURL& current_url = (curr_entry) ? curr_entry->GetURL() :
+ render_view_host_->GetSiteInstance()->GetSiteURL();
+ BrowserContext* browser_context =
+ delegate_->GetControllerForRenderManager().GetBrowserContext();
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, current_url)) {
+ // Force swap if it's not an acceptable URL for Web UI.
+ // Here, data URLs are never allowed.
+ if (!WebUIControllerFactoryRegistry::GetInstance()->IsURLAcceptableForWebUI(
+ browser_context, new_entry->GetURL(), false)) {
+ return true;
+ }
+ } else {
+ // Force swap if it's a Web UI URL.
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, new_entry->GetURL())) {
+ return true;
+ }
+ }
+
+ if (GetContentClient()->browser()->ShouldSwapProcessesForNavigation(
+ render_view_host_->GetSiteInstance(),
+ curr_entry ? curr_entry->GetURL() : GURL(),
+ new_entry->GetURL())) {
+ return true;
+ }
+
+ if (!curr_entry)
+ return false;
+
+ // We can't switch a RenderView between view source and non-view source mode
+ // without screwing up the session history sometimes (when navigating between
+ // "view-source:http://foo.com/" and "http://foo.com/", WebKit doesn't treat
+ // it as a new navigation). So require a view switch.
+ if (curr_entry->IsViewSourceMode() != new_entry->IsViewSourceMode())
+ return true;
+
+ return false;
+}
+
+bool RenderViewHostManager::ShouldReuseWebUI(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const {
+ NavigationControllerImpl& controller =
+ delegate_->GetControllerForRenderManager();
+ return curr_entry && web_ui_.get() &&
+ (WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
+ controller.GetBrowserContext(), curr_entry->GetURL()) ==
+ WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
+ controller.GetBrowserContext(), new_entry->GetURL()));
+}
+
+SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
+ const NavigationEntryImpl& entry,
+ SiteInstance* curr_instance) {
+ // NOTE: This is only called when ShouldTransitionCrossSite is true.
+
+ const GURL& dest_url = entry.GetURL();
+ NavigationControllerImpl& controller =
+ delegate_->GetControllerForRenderManager();
+ BrowserContext* browser_context = controller.GetBrowserContext();
+
+ // If the entry has an instance already we should use it.
+ if (entry.site_instance())
+ return entry.site_instance();
+
+ // (UGLY) HEURISTIC, process-per-site only:
+ //
+ // If this navigation is generated, then it probably corresponds to a search
+ // query. Given that search results typically lead to users navigating to
+ // other sites, we don't really want to use the search engine hostname to
+ // determine the site instance for this navigation.
+ //
+ // NOTE: This can be removed once we have a way to transition between
+ // RenderViews in response to a link click.
+ //
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerSite) &&
+ PageTransitionCoreTypeIs(entry.GetTransitionType(),
+ PAGE_TRANSITION_GENERATED)) {
+ return curr_instance;
+ }
+
+ SiteInstanceImpl* curr_site_instance =
+ static_cast<SiteInstanceImpl*>(curr_instance);
+
+ // If we haven't used our SiteInstance (and thus RVH) yet, then we can use it
+ // for this entry. We won't commit the SiteInstance to this site until the
+ // navigation commits (in DidNavigate), unless the navigation entry was
+ // restored or it's a Web UI as described below.
+ if (!curr_site_instance->HasSite()) {
+ // If we've already created a SiteInstance for our destination, we don't
+ // want to use this unused SiteInstance; use the existing one. (We don't
+ // do this check if the curr_instance has a site, because for now, we want
+ // to compare against the current URL and not the SiteInstance's site. In
+ // this case, there is no current URL, so comparing against the site is ok.
+ // See additional comments below.)
+ //
+ // Also, if the URL should use process-per-site mode and there is an
+ // existing process for the site, we should use it. We can call
+ // GetRelatedSiteInstance() for this, which will eagerly set the site and
+ // thus use the correct process.
+ bool use_process_per_site =
+ RenderProcessHost::ShouldUseProcessPerSite(browser_context, dest_url) &&
+ RenderProcessHostImpl::GetProcessHostForSite(browser_context, dest_url);
+ if (curr_site_instance->HasRelatedSiteInstance(dest_url) ||
+ use_process_per_site) {
+ return curr_site_instance->GetRelatedSiteInstance(dest_url);
+ }
+
+ // For extensions, Web UI URLs (such as the new tab page), and apps we do
+ // not want to use the curr_instance if it has no site, since it will have a
+ // RenderProcessHost of PRIV_NORMAL. Create a new SiteInstance for this
+ // URL instead (with the correct process type).
+ if (curr_site_instance->HasWrongProcessForURL(dest_url))
+ return curr_site_instance->GetRelatedSiteInstance(dest_url);
+
+ // View-source URLs must use a new SiteInstance and BrowsingInstance.
+ // TODO(nasko): This is the same condition as later in the function. This
+ // should be taken into account when refactoring this method as part of
+ // http://crbug.com/123007.
+ if (entry.IsViewSourceMode())
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+
+ // If we are navigating from a blank SiteInstance to a WebUI, make sure we
+ // create a new SiteInstance.
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, dest_url)) {
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ }
+
+ // Normally the "site" on the SiteInstance is set lazily when the load
+ // actually commits. This is to support better process sharing in case
+ // the site redirects to some other site: we want to use the destination
+ // site in the site instance.
+ //
+ // In the case of session restore, as it loads all the pages immediately
+ // we need to set the site first, otherwise after a restore none of the
+ // pages would share renderers in process-per-site.
+ if (entry.restore_type() != NavigationEntryImpl::RESTORE_NONE)
+ curr_site_instance->SetSite(dest_url);
+
+ return curr_site_instance;
+ }
+
+ // Otherwise, only create a new SiteInstance for cross-site navigation.
+
+ // TODO(creis): Once we intercept links and script-based navigations, we
+ // will be able to enforce that all entries in a SiteInstance actually have
+ // the same site, and it will be safe to compare the URL against the
+ // SiteInstance's site, as follows:
+ // const GURL& current_url = curr_instance->site();
+ // For now, though, we're in a hybrid model where you only switch
+ // SiteInstances if you type in a cross-site URL. This means we have to
+ // compare the entry's URL to the last committed entry's URL.
+ NavigationEntry* curr_entry = controller.GetLastCommittedEntry();
+ if (interstitial_page_) {
+ // The interstitial is currently the last committed entry, but we want to
+ // compare against the last non-interstitial entry.
+ curr_entry = controller.GetEntryAtOffset(-1);
+ }
+ // If there is no last non-interstitial entry (and curr_instance already
+ // has a site), then we must have been opened from another tab. We want
+ // to compare against the URL of the page that opened us, but we can't
+ // get to it directly. The best we can do is check against the site of
+ // the SiteInstance. This will be correct when we intercept links and
+ // script-based navigations, but for now, it could place some pages in a
+ // new process unnecessarily. We should only hit this case if a page tries
+ // to open a new tab to an interstitial-inducing URL, and then navigates
+ // the page to a different same-site URL. (This seems very unlikely in
+ // practice.)
+ const GURL& current_url = (curr_entry) ? curr_entry->GetURL() :
+ curr_instance->GetSiteURL();
+
+ // View-source URLs must use a new SiteInstance and BrowsingInstance.
+ // TODO(creis): Refactor this method so this duplicated code isn't needed.
+ // See http://crbug.com/123007.
+ if (curr_entry &&
+ curr_entry->IsViewSourceMode() != entry.IsViewSourceMode()) {
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ }
+
+ // Use the current SiteInstance for same site navigations, as long as the
+ // process type is correct. (The URL may have been installed as an app since
+ // the last time we visited it.)
+ if (SiteInstance::IsSameWebSite(browser_context, current_url, dest_url) &&
+ !static_cast<SiteInstanceImpl*>(curr_instance)->HasWrongProcessForURL(
+ dest_url)) {
+ return curr_instance;
+ } else if (ShouldSwapProcessesForNavigation(curr_entry, &entry)) {
+ // When we're swapping, we need to force the site instance AND browsing
+ // instance to be different ones. This addresses special cases where we use
+ // a single BrowsingInstance for all pages of a certain type (e.g., New Tab
+ // Pages), keeping them in the same process. When you navigate away from
+ // that page, we want to explicity ignore that BrowsingInstance and group
+ // this page into the appropriate SiteInstance for its URL.
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ } else {
+ // Start the new renderer in a new SiteInstance, but in the current
+ // BrowsingInstance. It is important to immediately give this new
+ // SiteInstance to a RenderViewHost (if it is different than our current
+ // SiteInstance), so that it is ref counted. This will happen in
+ // CreateRenderView.
+ return curr_instance->GetRelatedSiteInstance(dest_url);
+ }
+}
+
+int RenderViewHostManager::CreateRenderView(
+ SiteInstance* instance,
+ int opener_route_id,
+ bool swapped_out) {
+ CHECK(instance);
+
+ // Check if we've already created an RVH for this SiteInstance. If so, try
+ // to re-use the existing one, which has already been initialized. We'll
+ // remove it from the list of swapped out hosts if it commits.
+ RenderViewHostImpl* new_render_view_host = static_cast<RenderViewHostImpl*>(
+ GetSwappedOutRenderViewHost(instance));
+ if (new_render_view_host) {
+ // Prevent the process from exiting while we're trying to use it.
+ if (!swapped_out)
+ new_render_view_host->GetProcess()->AddPendingView();
+ } else {
+ // Create a new RenderViewHost if we don't find an existing one.
+ new_render_view_host = static_cast<RenderViewHostImpl*>(
+ RenderViewHostFactory::Create(instance,
+ render_view_delegate_,
+ render_widget_delegate_,
+ MSG_ROUTING_NONE,
+ MSG_ROUTING_NONE,
+ swapped_out));
+
+ // If the new RVH is swapped out already, store it. Otherwise prevent the
+ // process from exiting while we're trying to navigate in it.
+ if (swapped_out) {
+ swapped_out_hosts_[instance->GetId()] = new_render_view_host;
+ } else {
+ new_render_view_host->GetProcess()->AddPendingView();
+ }
+
+ bool success = InitRenderView(new_render_view_host, opener_route_id);
+ if (success) {
+ // Don't show the view until we get a DidNavigate from it.
+ new_render_view_host->GetView()->Hide();
+ } else if (!swapped_out) {
+ CancelPending();
+ }
+ }
+
+ // Use this as our new pending RVH if it isn't swapped out.
+ if (!swapped_out)
+ pending_render_view_host_ = new_render_view_host;
+
+ return new_render_view_host->GetRoutingID();
+}
+
+bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host,
+ int opener_route_id) {
+ // If the pending navigation is to a WebUI, tell the RenderView about any
+ // bindings it will need enabled.
+ if (pending_web_ui())
+ render_view_host->AllowBindings(pending_web_ui()->GetBindings());
+
+ return delegate_->CreateRenderViewForRenderManager(render_view_host,
+ opener_route_id);
+}
+
+void RenderViewHostManager::CommitPending() {
+ // First check whether we're going to want to focus the location bar after
+ // this commit. We do this now because the navigation hasn't formally
+ // committed yet, so if we've already cleared |pending_web_ui_| the call chain
+ // this triggers won't be able to figure out what's going on.
+ bool will_focus_location_bar = delegate_->FocusLocationBarByDefault();
+
+ // Next commit the Web UI, if any. Either replace |web_ui_| with
+ // |pending_web_ui_|, or clear |web_ui_| if there is no pending WebUI, or
+ // leave |web_ui_| as is if reusing it.
+ DCHECK(!(pending_web_ui_.get() && pending_and_current_web_ui_.get()));
+ if (pending_web_ui_)
+ web_ui_.reset(pending_web_ui_.release());
+ else if (!pending_and_current_web_ui_.get())
+ web_ui_.reset();
+
+ // It's possible for the pending_render_view_host_ to be NULL when we aren't
+ // crossing process boundaries. If so, we just needed to handle the Web UI
+ // committing above and we're done.
+ if (!pending_render_view_host_) {
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ return;
+ }
+
+ // Remember if the page was focused so we can focus the new renderer in
+ // that case.
+ bool focus_render_view = !will_focus_location_bar &&
+ render_view_host_->GetView() && render_view_host_->GetView()->HasFocus();
+
+ // Swap in the pending view and make it active.
+ RenderViewHostImpl* old_render_view_host = render_view_host_;
+ render_view_host_ = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+
+ // The process will no longer try to exit, so we can decrement the count.
+ render_view_host_->GetProcess()->RemovePendingView();
+
+ // If the view is gone, then this RenderViewHost died while it was hidden.
+ // We ignored the RenderProcessGone call at the time, so we should send it now
+ // to make sure the sad tab shows up, etc.
+ if (render_view_host_->GetView())
+ render_view_host_->GetView()->Show();
+ else
+ delegate_->RenderProcessGoneFromRenderManager(render_view_host_);
+
+ // Hide the old view now that the new one is visible.
+ if (old_render_view_host->GetView()) {
+ old_render_view_host->GetView()->Hide();
+ old_render_view_host->WasSwappedOut();
+ }
+
+ // Make sure the size is up to date. (Fix for bug 1079768.)
+ delegate_->UpdateRenderViewSizeForRenderManager();
+
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ else if (focus_render_view && render_view_host_->GetView())
+ RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus();
+
+ std::pair<RenderViewHost*, RenderViewHost*> details =
+ std::make_pair(old_render_view_host, render_view_host_);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(
+ &delegate_->GetControllerForRenderManager()),
+ Details<std::pair<RenderViewHost*, RenderViewHost*> >(&details));
+
+ // If the pending view was on the swapped out list, we can remove it.
+ swapped_out_hosts_.erase(render_view_host_->GetSiteInstance()->GetId());
+
+ // Let the task manager know that we've swapped RenderViewHosts,
+ // since it might need to update its process groupings. We do this
+ // before shutting down the RVH so that we can clean up
+ // RendererResources related to the RVH first.
+ delegate_->NotifySwappedFromRenderManager(old_render_view_host);
+
+ // If there are no active RVHs in this SiteInstance, it means that
+ // this RVH was the last active one in the SiteInstance. Now that we
+ // know that all RVHs are swapped out, we can delete all the RVHs in
+ // this SiteInstance.
+ if (!static_cast<SiteInstanceImpl*>(old_render_view_host->GetSiteInstance())->
+ active_view_count()) {
+ ShutdownRenderViewHostsInSiteInstance(
+ old_render_view_host->GetSiteInstance()->GetId());
+ // This is deleted while cleaning up the SitaInstance's views.
+ old_render_view_host = NULL;
+ } else if (old_render_view_host->IsRenderViewLive()) {
+ // If the old RVH is live, we are swapping it out and should keep track of
+ // it in case we navigate back to it.
+ DCHECK(old_render_view_host->is_swapped_out());
+ // Temp fix for http://crbug.com/90867 until we do a better cleanup to make
+ // sure we don't get different rvh instances for the same site instance
+ // in the same rvhmgr.
+ // TODO(creis): Clean this up.
+ int32 old_site_instance_id =
+ old_render_view_host->GetSiteInstance()->GetId();
+ RenderViewHostMap::iterator iter =
+ swapped_out_hosts_.find(old_site_instance_id);
+ if (iter != swapped_out_hosts_.end() &&
+ iter->second != old_render_view_host) {
+ // Shutdown the RVH that will be replaced in the map to avoid a leak.
+ iter->second->Shutdown();
+ }
+ swapped_out_hosts_[old_site_instance_id] = old_render_view_host;
+ } else {
+ old_render_view_host->Shutdown();
+ old_render_view_host = NULL; // Shutdown() deletes it.
+ }
+}
+
+void RenderViewHostManager::ShutdownRenderViewHostsInSiteInstance(
+ int32 site_instance_id) {
+ // First remove any swapped out RVH for this SiteInstance from our
+ // list.
+ swapped_out_hosts_.erase(site_instance_id);
+
+ RenderWidgetHost::List widgets =
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts();
+
+ // Here deleting a RWH in widgets can possibly cause another RWH in
+ // the list to be deleted. This can result in leaving a dangling
+ // pointer in the widgets list. Our assumption is that a widget
+ // deleted as that sort of side-effect should not be directly
+ // deleted here. Therefore, we first gather only widgets directly to
+ // be deleted so that we don't hit any future dangling pointers in
+ // widgets.
+ std::vector<RenderViewHostImpl*> rvhs_to_be_deleted;
+
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ if (!widgets[i]->IsRenderView())
+ continue;
+ RenderViewHostImpl* rvh =
+ static_cast<RenderViewHostImpl*>(RenderViewHost::From(widgets[i]));
+ if (site_instance_id == rvh->GetSiteInstance()->GetId()) {
+ DCHECK(rvh->is_swapped_out());
+ rvhs_to_be_deleted.push_back(rvh);
+ }
+ }
+
+ // Finally we delete the gathered RVHs, which should not indirectly
+ // delete each other.
+ for (size_t i = 0; i < rvhs_to_be_deleted.size(); ++i)
+ rvhs_to_be_deleted[i]->Shutdown();
+}
+
+RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate(
+ const NavigationEntryImpl& entry) {
+ // If we are cross-navigating, then we want to get back to normal and navigate
+ // as usual.
+ if (cross_navigation_pending_) {
+ if (pending_render_view_host_)
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+
+ // render_view_host_ will not be deleted before the end of this method, so we
+ // don't have to worry about this SiteInstance's ref count dropping to zero.
+ SiteInstance* curr_instance = render_view_host_->GetSiteInstance();
+
+ // Determine if we need a new SiteInstance for this entry.
+ // Again, new_instance won't be deleted before the end of this method, so it
+ // is safe to use a normal pointer here.
+ SiteInstance* new_instance = curr_instance;
+ const NavigationEntry* curr_entry =
+ delegate_->GetLastCommittedNavigationEntryForRenderManager();
+ bool is_guest_scheme = curr_instance->GetSiteURL().SchemeIs(
+ chrome::kGuestScheme);
+ bool force_swap = ShouldSwapProcessesForNavigation(curr_entry, &entry);
+ if (!is_guest_scheme && (ShouldTransitionCrossSite() || force_swap))
+ new_instance = GetSiteInstanceForEntry(entry, curr_instance);
+
+ if (!is_guest_scheme && (new_instance != curr_instance || force_swap)) {
+ // New SiteInstance.
+ DCHECK(!cross_navigation_pending_);
+
+ // This will possibly create (set to NULL) a Web UI object for the pending
+ // page. We'll use this later to give the page special access. This must
+ // happen before the new renderer is created below so it will get bindings.
+ // It must also happen after the above conditional call to CancelPending(),
+ // otherwise CancelPending may clear the pending_web_ui_ and the page will
+ // not have its bindings set appropriately.
+ SetPendingWebUI(entry);
+
+ // Ensure that we have created RVHs for the new RVH's opener chain if
+ // we are staying in the same BrowsingInstance. This allows the pending RVH
+ // to send cross-process script calls to its opener(s).
+ int opener_route_id = MSG_ROUTING_NONE;
+ if (new_instance->IsRelatedSiteInstance(curr_instance)) {
+ opener_route_id =
+ delegate_->CreateOpenerRenderViewsForRenderManager(new_instance);
+ }
+
+ // Create a non-swapped-out pending RVH with the given opener and navigate
+ // it.
+ int route_id = CreateRenderView(new_instance, opener_route_id, false);
+ if (route_id == MSG_ROUTING_NONE)
+ return NULL;
+
+ // Check if our current RVH is live before we set up a transition.
+ if (!render_view_host_->IsRenderViewLive()) {
+ if (!cross_navigation_pending_) {
+ // The current RVH is not live. There's no reason to sit around with a
+ // sad tab or a newly created RVH while we wait for the pending RVH to
+ // navigate. Just switch to the pending RVH now and go back to non
+ // cross-navigating (Note that we don't care about on{before}unload
+ // handlers if the current RVH isn't live.)
+ CommitPending();
+ return render_view_host_;
+ } else {
+ NOTREACHED();
+ return render_view_host_;
+ }
+ }
+ // Otherwise, it's safe to treat this as a pending cross-site transition.
+
+ // We need to wait until the beforeunload handler has run, unless we are
+ // transferring an existing request (in which case it has already run).
+ // Suspend the new render view (i.e., don't let it send the cross-site
+ // Navigate message) until we hear back from the old renderer's
+ // beforeunload handler. If the handler returns false, we'll have to
+ // cancel the request.
+ DCHECK(!pending_render_view_host_->are_navigations_suspended());
+ bool is_transfer =
+ entry.transferred_global_request_id() != GlobalRequestID();
+ if (!is_transfer) {
+ // Also make sure the old render view stops, in case a load is in
+ // progress. (We don't want to do this for transfers, since it will
+ // interrupt the transfer with an unexpected DidStopLoading.)
+ render_view_host_->Send(
+ new ViewMsg_Stop(render_view_host_->GetRoutingID()));
+
+ pending_render_view_host_->SetNavigationsSuspended(true,
+ base::TimeTicks());
+ }
+
+ // Tell the CrossSiteRequestManager that this RVH has a pending cross-site
+ // request, so that ResourceDispatcherHost will know to tell us to run the
+ // old page's unload handler before it sends the response.
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(true);
+
+ // We now have a pending RVH.
+ DCHECK(!cross_navigation_pending_);
+ cross_navigation_pending_ = true;
+
+ // Unless we are transferring an existing request, we should now
+ // tell the old render view to run its beforeunload handler, since it
+ // doesn't otherwise know that the cross-site request is happening. This
+ // will trigger a call to ShouldClosePage with the reply.
+ if (!is_transfer)
+ render_view_host_->FirePageBeforeUnload(true);
+
+ return pending_render_view_host_;
+ } else {
+ if (ShouldReuseWebUI(curr_entry, &entry)) {
+ pending_web_ui_.reset();
+ pending_and_current_web_ui_ = web_ui_->AsWeakPtr();
+ } else {
+ SetPendingWebUI(entry);
+
+ // Make sure the new RenderViewHost has the right bindings.
+ if (pending_web_ui())
+ render_view_host_->AllowBindings(pending_web_ui()->GetBindings());
+ }
+
+ if (pending_web_ui() && render_view_host_->IsRenderViewLive())
+ pending_web_ui()->GetController()->RenderViewReused(render_view_host_);
+
+ // The renderer can exit view source mode when any error or cancellation
+ // happen. We must overwrite to recover the mode.
+ if (entry.IsViewSourceMode()) {
+ render_view_host_->Send(
+ new ViewMsg_EnableViewSourceMode(render_view_host_->GetRoutingID()));
+ }
+ }
+
+ // Same SiteInstance can be used. Navigate render_view_host_ if we are not
+ // cross navigating.
+ DCHECK(!cross_navigation_pending_);
+ return render_view_host_;
+}
+
+void RenderViewHostManager::CancelPending() {
+ RenderViewHostImpl* pending_render_view_host = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+
+ RenderViewDevToolsAgentHost::OnCancelPendingNavigation(
+ pending_render_view_host,
+ render_view_host_);
+
+ // We no longer need to prevent the process from exiting.
+ pending_render_view_host->GetProcess()->RemovePendingView();
+
+ // The pending RVH may already be on the swapped out list if we started to
+ // swap it back in and then canceled. If so, make sure it gets swapped out
+ // again. If it's not on the swapped out list (e.g., aborting a pending
+ // load), then it's safe to shut down.
+ if (IsOnSwappedOutList(pending_render_view_host)) {
+ // Any currently suspended navigations are no longer needed.
+ pending_render_view_host->CancelSuspendedNavigations();
+
+ pending_render_view_host->SwapOut();
+ } else {
+ // We won't be coming back, so shut this one down.
+ pending_render_view_host->Shutdown();
+ }
+
+ pending_web_ui_.reset();
+ pending_and_current_web_ui_.reset();
+}
+
+void RenderViewHostManager::RenderViewDeleted(RenderViewHost* rvh) {
+ // We are doing this in order to work around and to track a crasher
+ // (http://crbug.com/23411) where it seems that pending_render_view_host_ is
+ // deleted (not sure from where) but not NULLed.
+ if (rvh == pending_render_view_host_) {
+ // If you hit this NOTREACHED, please report it in the following bug
+ // http://crbug.com/23411 Make sure to include what you were doing when it
+ // happened (navigating to a new page, closing a tab...) and if you can
+ // reproduce.
+ NOTREACHED();
+ pending_render_view_host_ = NULL;
+ }
+
+ // Make sure deleted RVHs are not kept in the swapped out list while we are
+ // still alive. (If render_view_host_ is null, we're already being deleted.)
+ if (!render_view_host_)
+ return;
+ // We can't look it up by SiteInstance ID, which may no longer be valid.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ if (iter->second == rvh) {
+ swapped_out_hosts_.erase(iter);
+ break;
+ }
+ }
+}
+
+bool RenderViewHostManager::IsOnSwappedOutList(RenderViewHost* rvh) const {
+ if (!rvh->GetSiteInstance())
+ return false;
+
+ RenderViewHostMap::const_iterator iter = swapped_out_hosts_.find(
+ rvh->GetSiteInstance()->GetId());
+ if (iter == swapped_out_hosts_.end())
+ return false;
+
+ return iter->second == rvh;
+}
+
+RenderViewHostImpl* RenderViewHostManager::GetSwappedOutRenderViewHost(
+ SiteInstance* instance) {
+ RenderViewHostMap::iterator iter = swapped_out_hosts_.find(instance->GetId());
+ if (iter != swapped_out_hosts_.end())
+ return iter->second;
+
+ return NULL;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/render_view_host_manager.h b/chromium/content/browser/web_contents/render_view_host_manager.h
new file mode 100644
index 00000000000..8c5071d9d65
--- /dev/null
+++ b/chromium/content/browser/web_contents/render_view_host_manager.h
@@ -0,0 +1,356 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+
+namespace content {
+class BrowserContext;
+class InterstitialPageImpl;
+class NavigationControllerImpl;
+class NavigationEntry;
+class NavigationEntryImpl;
+class RenderViewHost;
+class RenderViewHostImpl;
+class RenderViewHostManagerTest;
+class RenderWidgetHostDelegate;
+class RenderWidgetHostView;
+class TestWebContents;
+class WebUIImpl;
+
+// Manages RenderViewHosts for a WebContentsImpl. Normally there is only one and
+// it is easy to do. But we can also have transitions of processes (and hence
+// RenderViewHosts) that can get complex.
+class CONTENT_EXPORT RenderViewHostManager
+ : public RenderViewHostDelegate::RendererManagement,
+ public NotificationObserver {
+ public:
+ // Functions implemented by our owner that we need.
+ //
+ // TODO(brettw) Clean this up! These are all the functions in WebContentsImpl
+ // that are required to run this class. The design should probably be better
+ // such that these are more clear.
+ //
+ // There is additional complexity that some of the functions we need in
+ // WebContentsImpl are inherited and non-virtual. These are named with
+ // "RenderManager" so that the duplicate implementation of them will be clear.
+ class CONTENT_EXPORT Delegate {
+ public:
+ // Initializes the given renderer if necessary and creates the view ID
+ // corresponding to this view host. If this method is not called and the
+ // process is not shared, then the WebContentsImpl will act as though the
+ // renderer is not running (i.e., it will render "sad tab"). This method is
+ // automatically called from LoadURL.
+ //
+ // If you are attaching to an already-existing RenderView, you should call
+ // InitWithExistingID.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host, int opener_route_id) = 0;
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed, const base::TimeTicks& proceed_time,
+ bool* proceed_to_fire_unload) = 0;
+ virtual void RenderProcessGoneFromRenderManager(
+ RenderViewHost* render_view_host) = 0;
+ virtual void UpdateRenderViewSizeForRenderManager() = 0;
+ virtual void NotifySwappedFromRenderManager(
+ RenderViewHost* old_render_view_host) = 0;
+ virtual NavigationControllerImpl&
+ GetControllerForRenderManager() = 0;
+
+ // Create swapped out RenderViews in the given SiteInstance for each tab in
+ // the opener chain of this tab, if any. This allows the current tab to
+ // make cross-process script calls to its opener(s). Returns the route ID
+ // of the immediate opener, if one exists (otherwise MSG_ROUTING_NONE).
+ virtual int CreateOpenerRenderViewsForRenderManager(
+ SiteInstance* instance) = 0;
+
+ // Creates a WebUI object for the given URL if one applies. Ownership of the
+ // returned pointer will be passed to the caller. If no WebUI applies,
+ // returns NULL.
+ virtual WebUIImpl* CreateWebUIForRenderManager(const GURL& url) = 0;
+
+ // Returns the navigation entry of the current navigation, or NULL if there
+ // is none.
+ virtual NavigationEntry*
+ GetLastCommittedNavigationEntryForRenderManager() = 0;
+
+ // Returns true if the location bar should be focused by default rather than
+ // the page contents. The view calls this function when the tab is focused
+ // to see what it should do.
+ virtual bool FocusLocationBarByDefault() = 0;
+
+ // Focuses the location bar.
+ virtual void SetFocusToLocationBar(bool select_all) = 0;
+
+ // Creates a view and sets the size for the specified RVH.
+ virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // All three delegate pointers must be non-NULL and are not owned by this
+ // class. They must outlive this class. The RenderViewHostDelegate and
+ // RenderWidgetHostDelegate are what will be installed into all
+ // RenderViewHosts that are created.
+ //
+ // You must call Init() before using this class.
+ RenderViewHostManager(
+ RenderViewHostDelegate* render_view_delegate,
+ RenderWidgetHostDelegate* render_widget_delegate,
+ Delegate* delegate);
+ virtual ~RenderViewHostManager();
+
+ // For arguments, see WebContentsImpl constructor.
+ void Init(BrowserContext* browser_context,
+ SiteInstance* site_instance,
+ int routing_id,
+ int main_frame_routing_id);
+
+ // Returns the currently active RenderViewHost.
+ //
+ // This will be non-NULL between Init() and Shutdown(). You may want to NULL
+ // check it in many cases, however. Windows can send us messages during the
+ // destruction process after it has been shut down.
+ RenderViewHostImpl* current_host() const;
+
+ // Returns the view associated with the current RenderViewHost, or NULL if
+ // there is no current one.
+ RenderWidgetHostView* GetRenderWidgetHostView() const;
+
+ // Returns the pending render view host, or NULL if there is no pending one.
+ RenderViewHostImpl* pending_render_view_host() const;
+
+ // Returns the current committed Web UI or NULL if none applies.
+ WebUIImpl* web_ui() const { return web_ui_.get(); }
+
+ // Returns the Web UI for the pending navigation, or NULL of none applies.
+ WebUIImpl* pending_web_ui() const {
+ return pending_web_ui_.get() ? pending_web_ui_.get() :
+ pending_and_current_web_ui_.get();
+ }
+
+ // Sets the pending Web UI for the pending navigation, ensuring that the
+ // bindings are appropriate for the given NavigationEntry.
+ void SetPendingWebUI(const NavigationEntryImpl& entry);
+
+ // Called when we want to instruct the renderer to navigate to the given
+ // navigation entry. It may create a new RenderViewHost or re-use an existing
+ // one. The RenderViewHost to navigate will be returned. Returns NULL if one
+ // could not be created.
+ RenderViewHostImpl* Navigate(const NavigationEntryImpl& entry);
+
+ // Instructs the various live views to stop. Called when the user directed the
+ // page to stop loading.
+ void Stop();
+
+ // Notifies the regular and pending RenderViewHosts that a load is or is not
+ // happening. Even though the message is only for one of them, we don't know
+ // which one so we tell both.
+ void SetIsLoading(bool is_loading);
+
+ // Whether to close the tab or not when there is a hang during an unload
+ // handler. If we are mid-crosssite navigation, then we should proceed
+ // with the navigation instead of closing the tab.
+ bool ShouldCloseTabOnUnresponsiveRenderer();
+
+ // The RenderViewHost has been swapped out, so we should resume the pending
+ // network response and allow the pending RenderViewHost to commit.
+ void SwappedOut(RenderViewHost* render_view_host);
+
+ // Called when a renderer's main frame navigates.
+ void DidNavigateMainFrame(RenderViewHost* render_view_host);
+
+ // Called when a renderer sets its opener to null.
+ void DidDisownOpener(RenderViewHost* render_view_host);
+
+ // Helper method to create a RenderViewHost. If |swapped_out| is true, it
+ // will be initially placed on the swapped out hosts list. Otherwise, it
+ // will be used for a pending cross-site navigation.
+ int CreateRenderView(SiteInstance* instance,
+ int opener_route_id,
+ bool swapped_out);
+
+ // Called when a provisional load on the given renderer is aborted.
+ void RendererAbortedProvisionalLoad(RenderViewHost* render_view_host);
+
+ // Sets the passed passed interstitial as the currently showing interstitial.
+ // |interstitial_page| should be non NULL (use the remove_interstitial_page
+ // method to unset the interstitial) and no interstitial page should be set
+ // when there is already a non NULL interstitial page set.
+ void set_interstitial_page(InterstitialPageImpl* interstitial_page) {
+ DCHECK(!interstitial_page_ && interstitial_page);
+ interstitial_page_ = interstitial_page;
+ }
+
+ // Unsets the currently showing interstitial.
+ void remove_interstitial_page() {
+ DCHECK(interstitial_page_);
+ interstitial_page_ = NULL;
+ }
+
+ // Returns the currently showing interstitial, NULL if no interstitial is
+ // showing.
+ InterstitialPageImpl* interstitial_page() const { return interstitial_page_; }
+
+ // RenderViewHostDelegate::RendererManagement implementation.
+ virtual void ShouldClosePage(
+ bool for_cross_site_transition,
+ bool proceed,
+ const base::TimeTicks& proceed_time) OVERRIDE;
+ virtual void OnCrossSiteResponse(
+ RenderViewHost* pending_render_view_host,
+ const GlobalRequestID& global_request_id) OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Called when a RenderViewHost is about to be deleted.
+ void RenderViewDeleted(RenderViewHost* rvh);
+
+ // Returns whether the given RenderViewHost is on the list of swapped out
+ // RenderViewHosts.
+ bool IsOnSwappedOutList(RenderViewHost* rvh) const;
+
+ // Returns the swapped out RenderViewHost for the given SiteInstance, if any.
+ RenderViewHostImpl* GetSwappedOutRenderViewHost(SiteInstance* instance);
+
+ // Runs the unload handler in the current page, when we know that a pending
+ // cross-process navigation is going to commit.
+ void SwapOutOldPage();
+
+ private:
+ friend class RenderViewHostManagerTest;
+ friend class TestWebContents;
+
+ // Tracks information about a navigation while a cross-process transition is
+ // in progress.
+ // TODO(creis): Add transfer navigation params for http://crbug.com/238331.
+ struct PendingNavigationParams {
+ PendingNavigationParams();
+ explicit PendingNavigationParams(const GlobalRequestID& global_request_id);
+
+ GlobalRequestID global_request_id;
+ };
+
+ // Returns whether this tab should transition to a new renderer for
+ // cross-site URLs. Enabled unless we see the --process-per-tab command line
+ // switch. Can be overridden in unit tests.
+ bool ShouldTransitionCrossSite();
+
+ // Returns true if the two navigation entries are incompatible in some way
+ // other than site instances. Cases where this can happen include Web UI
+ // to regular web pages. It will cause us to swap RenderViewHosts (and hence
+ // RenderProcessHosts) even if the site instance would otherwise be the same.
+ // As part of this, we'll also force new SiteInstances and BrowsingInstances.
+ // Either of the entries may be NULL.
+ bool ShouldSwapProcessesForNavigation(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const;
+
+ bool ShouldReuseWebUI(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const;
+
+ // Returns an appropriate SiteInstance object for the given NavigationEntry,
+ // possibly reusing the current SiteInstance.
+ // Never called if --process-per-tab is used.
+ SiteInstance* GetSiteInstanceForEntry(
+ const NavigationEntryImpl& entry,
+ SiteInstance* curr_instance);
+
+ // Sets up the necessary state for a new RenderViewHost with the given opener.
+ bool InitRenderView(RenderViewHost* render_view_host, int opener_route_id);
+
+ // Sets the pending RenderViewHost/WebUI to be the active one. Note that this
+ // doesn't require the pending render_view_host_ pointer to be non-NULL, since
+ // there could be Web UI switching as well. Call this for every commit.
+ void CommitPending();
+
+ // Shutdown all RenderViewHosts in a SiteInstance. This is called
+ // to shutdown views when all the views in a SiteInstance are
+ // confirmed to be swapped out.
+ void ShutdownRenderViewHostsInSiteInstance(int32 site_instance_id);
+
+ // Helper method to terminate the pending RenderViewHost.
+ void CancelPending();
+
+ RenderViewHostImpl* UpdateRendererStateForNavigate(
+ const NavigationEntryImpl& entry);
+
+ // Called when a renderer process is starting to close. We should not
+ // schedule new navigations in its swapped out RenderViewHosts after this.
+ void RendererProcessClosing(RenderProcessHost* render_process_host);
+
+ // Our delegate, not owned by us. Guaranteed non-NULL.
+ Delegate* delegate_;
+
+ // Whether a navigation requiring different RenderView's is pending. This is
+ // either cross-site request is (in the new process model), or when required
+ // for the view type (like view source versus not).
+ bool cross_navigation_pending_;
+
+ // Implemented by the owner of this class, these delegates are installed into
+ // all the RenderViewHosts that we create.
+ RenderViewHostDelegate* render_view_delegate_;
+ RenderWidgetHostDelegate* render_widget_delegate_;
+
+ // Our RenderView host and its associated Web UI (if any, will be NULL for
+ // non-DOM-UI pages). This object is responsible for all communication with
+ // a child RenderView instance.
+ RenderViewHostImpl* render_view_host_;
+ scoped_ptr<WebUIImpl> web_ui_;
+
+ // A RenderViewHost used to load a cross-site page. This remains hidden
+ // while a cross-site request is pending until it calls DidNavigate. It may
+ // have an associated Web UI, in which case the Web UI pointer will be non-
+ // NULL.
+ //
+ // The |pending_web_ui_| may be non-NULL even when the
+ // |pending_render_view_host_| is NULL. This will happen when we're
+ // transitioning between two Web UI pages: the RVH won't be swapped, so the
+ // pending pointer will be unused, but there will be a pending Web UI
+ // associated with the navigation.
+ RenderViewHostImpl* pending_render_view_host_;
+
+ // Tracks information about any current pending cross-process navigation.
+ scoped_ptr<PendingNavigationParams> pending_nav_params_;
+
+ // If either of these is non-NULL, the pending navigation is to a chrome:
+ // page. The scoped_ptr is used if pending_web_ui_ != web_ui_, the WeakPtr is
+ // used for when they reference the same object. If either is non-NULL, the
+ // other should be NULL.
+ scoped_ptr<WebUIImpl> pending_web_ui_;
+ base::WeakPtr<WebUIImpl> pending_and_current_web_ui_;
+
+ // A map of site instance ID to swapped out RenderViewHosts. This may include
+ // pending_render_view_host_ for navigations to existing entries.
+ typedef base::hash_map<int32, RenderViewHostImpl*> RenderViewHostMap;
+ RenderViewHostMap swapped_out_hosts_;
+
+ // The intersitial page currently shown if any, not own by this class
+ // (the InterstitialPage is self-owned, it deletes itself when hidden).
+ InterstitialPageImpl* interstitial_page_;
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_
diff --git a/chromium/content/browser/web_contents/render_view_host_manager_unittest.cc b/chromium/content/browser/web_contents/render_view_host_manager_unittest.cc
new file mode 100644
index 00000000000..ea35e6ba05f
--- /dev/null
+++ b/chromium/content/browser/web_contents/render_view_host_manager_unittest.cc
@@ -0,0 +1,1214 @@
+// 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 "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/render_view_host_manager.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host_observer.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/javascript_message_type.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/common/url_utils.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_notification_tracker.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class RenderViewHostManagerTestWebUIControllerFactory
+ : public WebUIControllerFactory {
+ public:
+ RenderViewHostManagerTestWebUIControllerFactory()
+ : should_create_webui_(false) {
+ }
+ virtual ~RenderViewHostManagerTestWebUIControllerFactory() {}
+
+ void set_should_create_webui(bool should_create_webui) {
+ should_create_webui_ = should_create_webui;
+ }
+
+ // WebUIFactory implementation.
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const OVERRIDE {
+ if (!(should_create_webui_ && HasWebUIScheme(url)))
+ return NULL;
+ return new WebUIController(web_ui);
+ }
+
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return WebUI::kNoWebUI;
+ }
+
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+
+ private:
+ bool should_create_webui_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestWebUIControllerFactory);
+};
+
+} // namespace
+
+class RenderViewHostManagerTest
+ : public RenderViewHostImplTestHarness {
+ public:
+ virtual void SetUp() OVERRIDE {
+ RenderViewHostImplTestHarness::SetUp();
+ WebUIControllerFactory::RegisterFactory(&factory_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ RenderViewHostImplTestHarness::TearDown();
+ WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
+ }
+
+ void set_should_create_webui(bool should_create_webui) {
+ factory_.set_should_create_webui(should_create_webui);
+ }
+
+ void NavigateActiveAndCommit(const GURL& url) {
+ // Note: we navigate the active RenderViewHost because previous navigations
+ // won't have committed yet, so NavigateAndCommit does the wrong thing
+ // for us.
+ controller().LoadURL(url, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ TestRenderViewHost* old_rvh = test_rvh();
+
+ // Simulate the ShouldClose_ACK that is received from the current renderer
+ // for a cross-site navigation.
+ if (old_rvh != active_rvh())
+ old_rvh->SendShouldCloseACK(true);
+
+ // Commit the navigation with a new page ID.
+ int32 max_page_id = contents()->GetMaxPageIDForSiteInstance(
+ active_rvh()->GetSiteInstance());
+
+ // Simulate the SwapOut_ACK that fires if you commit a cross-site
+ // navigation.
+ if (old_rvh != active_rvh())
+ old_rvh->OnSwappedOut(false);
+
+ active_test_rvh()->SendNavigate(max_page_id + 1, url);
+ }
+
+ bool ShouldSwapProcesses(RenderViewHostManager* manager,
+ const NavigationEntryImpl* cur_entry,
+ const NavigationEntryImpl* new_entry) const {
+ return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry);
+ }
+
+ // Creates a test RenderViewHost that's swapped out.
+ TestRenderViewHost* CreateSwappedOutRenderViewHost() {
+ const GURL kChromeURL("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Navigate to a cross-site URL.
+ contents()->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+
+ // Manually increase the number of active views in the
+ // SiteInstance that ntp_rvh belongs to, to prevent it from being
+ // destroyed when it gets swapped out.
+ static_cast<SiteInstanceImpl*>(ntp_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->pending_render_view_host());
+ CHECK(dest_rvh);
+ EXPECT_NE(ntp_rvh, dest_rvh);
+
+ // BeforeUnload finishes.
+ ntp_rvh->SendShouldCloseACK(true);
+
+ // Assume SwapOutACK times out, so the dest_rvh proceeds and commits.
+ dest_rvh->SendNavigate(101, kDestUrl);
+
+ EXPECT_TRUE(ntp_rvh->is_swapped_out());
+ return ntp_rvh;
+ }
+
+ private:
+ RenderViewHostManagerTestWebUIControllerFactory factory_;
+};
+
+// Tests that when you navigate from a chrome:// url to another page, and
+// then do that same thing in another tab, that the two resulting pages have
+// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
+// a regression test for bug 9364.
+TEST_F(RenderViewHostManagerTest, NewTabPageProcesses) {
+ set_should_create_webui(true);
+ const GURL kChromeUrl("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to the chrome url and then to the destination,
+ // ensuring we grant bindings to the chrome URL.
+ NavigateActiveAndCommit(kChromeUrl);
+ EXPECT_TRUE(active_rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+ NavigateActiveAndCommit(kDestUrl);
+
+ // Make a second tab.
+ scoped_ptr<TestWebContents> contents2(
+ TestWebContents::Create(browser_context(), NULL));
+
+ // Load the two URLs in the second tab. Note that the first navigation creates
+ // a RVH that's not pending (since there is no cross-site transition), so
+ // we use the committed one.
+ contents2->GetController().LoadURL(
+ kChromeUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ TestRenderViewHost* ntp_rvh2 = static_cast<TestRenderViewHost*>(
+ contents2->GetRenderManagerForTesting()->current_host());
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+ ntp_rvh2->SendNavigate(100, kChromeUrl);
+
+ // The second one is the opposite, creating a cross-site transition and
+ // requiring a beforeunload ack.
+ contents2->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents2->cross_navigation_pending());
+ TestRenderViewHost* dest_rvh2 = static_cast<TestRenderViewHost*>(
+ contents2->GetRenderManagerForTesting()->pending_render_view_host());
+ ASSERT_TRUE(dest_rvh2);
+
+ ntp_rvh2->SendShouldCloseACK(true);
+ ntp_rvh2->OnSwappedOut(false);
+ dest_rvh2->SendNavigate(101, kDestUrl);
+
+ // The two RVH's should be different in every way.
+ EXPECT_NE(active_rvh()->GetProcess(), dest_rvh2->GetProcess());
+ EXPECT_NE(active_rvh()->GetSiteInstance(), dest_rvh2->GetSiteInstance());
+ EXPECT_FALSE(active_rvh()->GetSiteInstance()->IsRelatedSiteInstance(
+ dest_rvh2->GetSiteInstance()));
+
+ // Navigate both to the new tab page, and verify that they share a
+ // RenderProcessHost (not a SiteInstance).
+ NavigateActiveAndCommit(kChromeUrl);
+
+ contents2->GetController().LoadURL(
+ kChromeUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ dest_rvh2->SendShouldCloseACK(true);
+ dest_rvh2->OnSwappedOut(false);
+ static_cast<TestRenderViewHost*>(contents2->GetRenderManagerForTesting()->
+ pending_render_view_host())->SendNavigate(102, kChromeUrl);
+
+ EXPECT_NE(active_rvh()->GetSiteInstance(),
+ contents2->GetRenderViewHost()->GetSiteInstance());
+ EXPECT_EQ(active_rvh()->GetSiteInstance()->GetProcess(),
+ contents2->GetRenderViewHost()->GetSiteInstance()->GetProcess());
+}
+
+// Ensure that the browser ignores most IPC messages that arrive from a
+// RenderViewHost that has been swapped out. We do not want to take
+// action on requests from a non-active renderer. The main exception is
+// for synchronous messages, which cannot be ignored without leaving the
+// renderer in a stuck state. See http://crbug.com/93427.
+TEST_F(RenderViewHostManagerTest, FilterMessagesWhileSwappedOut) {
+ const GURL kChromeURL("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Send an update title message and make sure it works.
+ const string16 ntp_title = ASCIIToUTF16("NTP Title");
+ WebKit::WebTextDirection direction = WebKit::WebTextDirectionLeftToRight;
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction)));
+ EXPECT_EQ(ntp_title, contents()->GetTitle());
+
+ // Navigate to a cross-site URL.
+ contents()->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->pending_render_view_host());
+ ASSERT_TRUE(dest_rvh);
+ EXPECT_NE(ntp_rvh, dest_rvh);
+
+ // Create one more view in the same SiteInstance where dest_rvh2
+ // exists so that it doesn't get deleted on navigation to another
+ // site.
+ static_cast<SiteInstanceImpl*>(ntp_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ // BeforeUnload finishes.
+ ntp_rvh->SendShouldCloseACK(true);
+
+ // Assume SwapOutACK times out, so the dest_rvh proceeds and commits.
+ dest_rvh->SendNavigate(101, kDestUrl);
+
+ // The new RVH should be able to update its title.
+ const string16 dest_title = ASCIIToUTF16("Google");
+ EXPECT_TRUE(dest_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 101, dest_title,
+ direction)));
+ EXPECT_EQ(dest_title, contents()->GetTitle());
+
+ // The old renderer, being slow, now updates the title. It should be filtered
+ // out and not take effect.
+ EXPECT_TRUE(ntp_rvh->is_swapped_out());
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction)));
+ EXPECT_EQ(dest_title, contents()->GetTitle());
+
+ // We cannot filter out synchronous IPC messages, because the renderer would
+ // be left waiting for a reply. We pick RunBeforeUnloadConfirm as an example
+ // that can run easily within a unit test, and that needs to receive a reply
+ // without showing an actual dialog.
+ MockRenderProcessHost* ntp_process_host =
+ static_cast<MockRenderProcessHost*>(ntp_rvh->GetProcess());
+ ntp_process_host->sink().ClearMessages();
+ const string16 msg = ASCIIToUTF16("Message");
+ bool result = false;
+ string16 unused;
+ ViewHostMsg_RunBeforeUnloadConfirm before_unload_msg(
+ rvh()->GetRoutingID(), kChromeURL, msg, false, &result, &unused);
+ // Enable pumping for check in BrowserMessageFilter::CheckCanDispatchOnUI.
+ before_unload_msg.EnableMessagePumping();
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(before_unload_msg));
+ EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
+
+ // Also test RunJavaScriptMessage.
+ ntp_process_host->sink().ClearMessages();
+ ViewHostMsg_RunJavaScriptMessage js_msg(
+ rvh()->GetRoutingID(), msg, msg, kChromeURL,
+ JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused);
+ js_msg.EnableMessagePumping();
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(js_msg));
+ EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
+}
+
+TEST_F(RenderViewHostManagerTest, WhiteListSwapCompositorFrame) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ TestRenderWidgetHostView* swapped_out_rwhv =
+ static_cast<TestRenderWidgetHostView*>(swapped_out_rvh->GetView());
+ EXPECT_FALSE(swapped_out_rwhv->did_swap_compositor_frame());
+
+ MockRenderProcessHost* process_host =
+ static_cast<MockRenderProcessHost*>(swapped_out_rvh->GetProcess());
+ process_host->sink().ClearMessages();
+
+ cc::CompositorFrame frame;
+ ViewHostMsg_SwapCompositorFrame msg(rvh()->GetRoutingID(), 0, frame);
+
+ EXPECT_TRUE(swapped_out_rvh->OnMessageReceived(msg));
+ EXPECT_TRUE(swapped_out_rwhv->did_swap_compositor_frame());
+}
+
+TEST_F(RenderViewHostManagerTest, WhiteListDidActivateAcceleratedCompositing) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+
+ MockRenderProcessHost* process_host =
+ static_cast<MockRenderProcessHost*>(swapped_out_rvh->GetProcess());
+ process_host->sink().ClearMessages();
+ ViewHostMsg_DidActivateAcceleratedCompositing msg(
+ rvh()->GetRoutingID(), true);
+ EXPECT_TRUE(swapped_out_rvh->OnMessageReceived(msg));
+ EXPECT_TRUE(swapped_out_rvh->is_accelerated_compositing_active());
+}
+
+// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
+// widgets.
+TEST_F(RenderViewHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ EXPECT_TRUE(swapped_out_rvh->is_swapped_out());
+
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ // We know that there is the only one active widget. Another view is
+ // now swapped out, so the swapped out view is not included in the
+ // list.
+ EXPECT_TRUE(widgets.size() == 1);
+ RenderViewHost* rvh = RenderViewHost::From(widgets[0]);
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(rvh)->is_swapped_out());
+}
+
+// Test if RenderViewHost::GetRenderWidgetHosts() returns a subset of
+// RenderViewHostImpl::GetAllRenderWidgetHosts().
+// RenderViewHost::GetRenderWidgetHosts() returns only active widgets, but
+// RenderViewHostImpl::GetAllRenderWidgetHosts() returns everything
+// including swapped out ones.
+TEST_F(RenderViewHostManagerTest,
+ GetRenderWidgetHostsWithinGetAllRenderWidgetHosts) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ EXPECT_TRUE(swapped_out_rvh->is_swapped_out());
+
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ RenderWidgetHost::List all_widgets =
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts();
+
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ bool found = false;
+ for (size_t j = 0; j < all_widgets.size(); ++j) {
+ if (widgets[i] == all_widgets[j]) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+}
+
+// Test if SiteInstanceImpl::active_view_count() is correctly updated
+// as views in a SiteInstance get swapped out and in.
+TEST_F(RenderViewHostManagerTest, ActiveViewCountWhileSwappingInandOut) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ SiteInstanceImpl* instance1 =
+ static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance());
+ EXPECT_EQ(instance1->active_view_count(), 1U);
+
+ // Create 2 new tabs and simulate them being the opener chain for the main
+ // tab. They should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), instance1));
+ contents()->SetOpener(opener1.get());
+
+ scoped_ptr<TestWebContents> opener2(
+ TestWebContents::Create(browser_context(), instance1));
+ opener1->SetOpener(opener2.get());
+
+ EXPECT_EQ(instance1->active_view_count(), 3U);
+
+ // Navigate to a cross-site URL (different SiteInstance but same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ SiteInstanceImpl* instance2 =
+ static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance());
+
+ // rvh2 is on chromium.org which is different from google.com on
+ // which other tabs are.
+ EXPECT_EQ(instance2->active_view_count(), 1U);
+
+ // There are two active views on google.com now.
+ EXPECT_EQ(instance1->active_view_count(), 2U);
+
+ // Navigate to the original origin (google.com).
+ contents()->NavigateAndCommit(kUrl1);
+
+ EXPECT_EQ(instance1->active_view_count(), 3U);
+}
+
+// This deletes a WebContents when the given RVH is deleted. This is
+// only for testing whether deleting an RVH does not cause any UaF in
+// other parts of the system. For now, this class is only used for the
+// next test cases to detect the bug mentioned at
+// http://crbug.com/259859.
+class RenderViewHostDestroyer : public content::RenderViewHostObserver {
+ public:
+ RenderViewHostDestroyer(RenderViewHost* render_view_host,
+ WebContents* web_contents)
+ : content::RenderViewHostObserver(render_view_host),
+ web_contents_(web_contents) {}
+
+ virtual void RenderViewHostDestroyed(RenderViewHost* render_view_host)
+ OVERRIDE {
+ delete web_contents_;
+ }
+
+ private:
+ WebContents* web_contents_;
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
+};
+
+// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
+// RenderWidget that has been freed while deleting a RenderViewHost in
+// a previous iteration. This is a regression test for
+// http://crbug.com/259859.
+TEST_F(RenderViewHostManagerTest,
+ DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance) {
+ const GURL kChromeURL("chrome://newtab");
+ const GURL kUrl1("http://www.google.com");
+ const GURL kUrl2("http://www.chromium.org");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Create one more tab and navigate to kUrl1. web_contents is not
+ // wrapped as scoped_ptr since it intentionally deleted by destroyer
+ // below as part of this test.
+ TestWebContents* web_contents =
+ TestWebContents::Create(browser_context(), ntp_rvh->GetSiteInstance());
+ web_contents->NavigateAndCommit(kUrl1);
+ RenderViewHostDestroyer destroyer(ntp_rvh, web_contents);
+
+ // This causes the first tab to navigate to kUrl2, which destroys
+ // the ntp_rvh in ShutdownRenderViewHostsInSiteInstance(). When
+ // ntp_rvh is destroyed, it also destroys the RVHs in web_contents
+ // too. This can test whether
+ // SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
+ // touch any object freed in this way or not while iterating through
+ // all widgets.
+ contents()->NavigateAndCommit(kUrl2);
+}
+
+// When there is an error with the specified page, renderer exits view-source
+// mode. See WebFrameImpl::DidFail(). We check by this test that
+// EnableViewSourceMode message is sent on every navigation regardless
+// RenderView is being newly created or reused.
+TEST_F(RenderViewHostManagerTest, AlwaysSendEnableViewSourceMode) {
+ const GURL kChromeUrl("chrome://foo");
+ const GURL kUrl("view-source:http://foo");
+
+ // We have to navigate to some page at first since without this, the first
+ // navigation will reuse the SiteInstance created by Init(), and the second
+ // one will create a new SiteInstance. Because current_instance and
+ // new_instance will be different, a new RenderViewHost will be created for
+ // the second navigation. We have to avoid this in order to exercise the
+ // target code patch.
+ NavigateActiveAndCommit(kChromeUrl);
+
+ // Navigate.
+ controller().LoadURL(
+ kUrl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Simulate response from RenderView for FirePageBeforeUnload.
+ base::TimeTicks now = base::TimeTicks::Now();
+ test_rvh()->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(
+ rvh()->GetRoutingID(), true, now, now));
+ ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created.
+ RenderViewHost* last_rvh = pending_rvh();
+ int32 new_id = contents()->GetMaxPageIDForSiteInstance(
+ active_rvh()->GetSiteInstance()) + 1;
+ pending_test_rvh()->SendNavigate(new_id, kUrl);
+ EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
+ ASSERT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->GetURL());
+ EXPECT_FALSE(controller().GetPendingEntry());
+ // Because we're using TestWebContents and TestRenderViewHost in this
+ // unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no
+ // EnableViewSourceMode message, here.
+
+ // Clear queued messages before load.
+ process()->sink().ClearMessages();
+ // Navigate, again.
+ controller().LoadURL(
+ kUrl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // The same RenderViewHost should be reused.
+ EXPECT_FALSE(pending_rvh());
+ EXPECT_TRUE(last_rvh == rvh());
+ test_rvh()->SendNavigate(new_id, kUrl); // The same page_id returned.
+ EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
+ EXPECT_FALSE(controller().GetPendingEntry());
+ // New message should be sent out to make sure to enter view-source mode.
+ EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
+ ViewMsg_EnableViewSourceMode::ID));
+}
+
+// Tests the Init function by checking the initial RenderViewHost.
+TEST_F(RenderViewHostManagerTest, Init) {
+ // Using TestBrowserContext.
+ SiteInstanceImpl* instance =
+ static_cast<SiteInstanceImpl*>(SiteInstance::Create(browser_context()));
+ EXPECT_FALSE(instance->HasSite());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host = manager.current_host();
+ ASSERT_TRUE(host);
+ EXPECT_EQ(instance, host->GetSiteInstance());
+ EXPECT_EQ(web_contents.get(), host->GetDelegate());
+ EXPECT_TRUE(manager.GetRenderWidgetHostView());
+ EXPECT_FALSE(manager.pending_render_view_host());
+}
+
+// Tests the Navigate function. We navigate three sites consecutively and check
+// how the pending/committed RenderViewHost are modified.
+TEST_F(RenderViewHostManagerTest, Navigate) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ notifications.ListenFor(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&web_contents->GetController()));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host;
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(
+ NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
+
+ // 2) Navigate to next site. -------------------------
+ const GURL kUrl2("http://www.google.com/foo");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(kUrl1, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ host = manager.Navigate(entry2);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+
+ // 3) Cross-site navigate to next site. --------------
+ const GURL kUrl3("http://webkit.org/");
+ NavigationEntryImpl entry3(
+ NULL /* instance */, -1 /* page_id */, kUrl3,
+ Referrer(kUrl2, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry3);
+
+ // A new RenderViewHost should be created.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host, manager.pending_render_view_host());
+
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(manager.pending_render_view_host());
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ // Check the pending RenderViewHost has been committed.
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+}
+
+// Tests the Navigate function. In this unit test we verify that the Navigate
+// function can handle a new navigation event before the previous navigation
+// has been committed. This is also a regression test for
+// http://crbug.com/104600.
+TEST_F(RenderViewHostManagerTest, NavigateWithEarlyReNavigation) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ notifications.ListenFor(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<NavigationController>(&web_contents->GetController()));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
+
+ // 2) Cross-site navigate to next site. -------------------------
+ const GURL kUrl2("http://www.example.com");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHostImpl* host2 = static_cast<RenderViewHostImpl*>(
+ manager.Navigate(entry2));
+ int host2_process_id = host2->GetProcess()->GetID();
+
+ // A new RenderViewHost should be created.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host2, manager.pending_render_view_host());
+ EXPECT_NE(host2, host);
+
+ // Check that the navigation is still suspended because the old RVH
+ // is not swapped out, yet.
+ EXPECT_TRUE(host2->are_navigations_suspended());
+ MockRenderProcessHost* test_process_host2 =
+ static_cast<MockRenderProcessHost*>(host2->GetProcess());
+ test_process_host2->sink().ClearMessages();
+ host2->NavigateToURL(kUrl2);
+ EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching(
+ ViewMsg_Navigate::ID));
+
+ // Allow closing the current Render View (precondition for swapping out
+ // the RVH): Simulate response from RenderView for ViewMsg_ShouldClose sent by
+ // FirePageBeforeUnload.
+ TestRenderViewHost* test_host = static_cast<TestRenderViewHost*>(host);
+ MockRenderProcessHost* test_process_host =
+ static_cast<MockRenderProcessHost*>(test_host->GetProcess());
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_ShouldClose::ID));
+ test_host->SendShouldCloseACK(true);
+
+ // CrossSiteResourceHandler::StartCrossSiteTransition triggers a
+ // call of RenderViewHostManager::SwapOutOldPage before
+ // RenderViewHostManager::DidNavigateMainFrame is called.
+ // The RVH is not swapped out until the commit.
+ manager.SwapOutOldPage();
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_SwapOut::ID));
+ test_host->OnSwappedOut(false);
+
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(
+ manager.current_host())->is_swapped_out());
+ EXPECT_EQ(host2, manager.pending_render_view_host());
+ // There should be still no navigation messages being sent.
+ EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching(
+ ViewMsg_Navigate::ID));
+
+ // 3) Cross-site navigate to next site before 2) has committed. --------------
+ const GURL kUrl3("http://webkit.org/");
+ NavigationEntryImpl entry3(NULL /* instance */, -1 /* page_id */, kUrl3,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ test_process_host->sink().ClearMessages();
+ RenderViewHost* host3 = manager.Navigate(entry3);
+
+ // A new RenderViewHost should be created. host2 is now deleted.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host3, manager.pending_render_view_host());
+ EXPECT_NE(host3, host);
+ EXPECT_NE(host3->GetProcess()->GetID(), host2_process_id);
+
+ // Navigations in the new RVH should be suspended, which is ok because the
+ // old RVH is not yet swapped out and can respond to a second beforeunload
+ // request.
+ EXPECT_TRUE(static_cast<RenderViewHostImpl*>(
+ host3)->are_navigations_suspended());
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(
+ manager.current_host())->is_swapped_out());
+
+ // Simulate a response to the second beforeunload request.
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_ShouldClose::ID));
+ test_host->SendShouldCloseACK(true);
+
+ // CrossSiteResourceHandler::StartCrossSiteTransition triggers a
+ // call of RenderViewHostManager::SwapOutOldPage before
+ // RenderViewHostManager::DidNavigateMainFrame is called.
+ // The RVH is not swapped out until the commit.
+ manager.SwapOutOldPage();
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_SwapOut::ID));
+ test_host->OnSwappedOut(false);
+
+ // Commit.
+ manager.DidNavigateMainFrame(host3);
+ EXPECT_TRUE(host3 == manager.current_host());
+ ASSERT_TRUE(host3);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host3->GetSiteInstance())->
+ HasSite());
+ // Check the pending RenderViewHost has been committed.
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(notifications.Check1AndReset(
+ NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+}
+
+// Tests WebUI creation.
+TEST_F(RenderViewHostManagerTest, WebUI) {
+ set_should_create_webui(true);
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ EXPECT_FALSE(manager.current_host()->IsRenderViewLive());
+
+ const GURL kUrl("chrome://foo");
+ NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host = manager.Navigate(entry);
+
+ // We commit the pending RenderViewHost immediately because the previous
+ // RenderViewHost was not live. We test a case where it is live in
+ // WebUIInNewTab.
+ EXPECT_TRUE(host);
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // It's important that the site instance get set on the Web UI page as soon
+ // as the navigation starts, rather than lazily after it commits, so we don't
+ // try to re-use the SiteInstance/process for non Web UI things that may
+ // get loaded in between.
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());
+
+ // The Web UI is committed immediately because the RenderViewHost has not been
+ // used yet. UpdateRendererStateForNavigate() took the short cut path.
+ EXPECT_FALSE(manager.pending_web_ui());
+ EXPECT_TRUE(manager.web_ui());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_TRUE(host->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+}
+
+// Tests that we can open a WebUI link in a new tab from a WebUI page and still
+// grant the correct bindings. http://crbug.com/189101.
+TEST_F(RenderViewHostManagerTest, WebUIInNewTab) {
+ set_should_create_webui(true);
+ SiteInstance* blank_instance = SiteInstance::Create(browser_context());
+
+ // Create a blank tab.
+ scoped_ptr<TestWebContents> web_contents1(
+ TestWebContents::Create(browser_context(), blank_instance));
+ RenderViewHostManager manager1(web_contents1.get(), web_contents1.get(),
+ web_contents1.get());
+ manager1.Init(
+ browser_context(), blank_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ // Test the case that new RVH is considered live.
+ manager1.current_host()->CreateRenderView(string16(), -1, -1);
+
+ // Navigate to a WebUI page.
+ const GURL kUrl1("chrome://foo");
+ NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host1 = manager1.Navigate(entry1);
+
+ // We should have a pending navigation to the WebUI RenderViewHost.
+ // It should already have bindings.
+ EXPECT_EQ(host1, manager1.pending_render_view_host());
+ EXPECT_NE(host1, manager1.current_host());
+ EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Commit and ensure we still have bindings.
+ manager1.DidNavigateMainFrame(host1);
+ SiteInstance* webui_instance = host1->GetSiteInstance();
+ EXPECT_EQ(host1, manager1.current_host());
+ EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Now simulate clicking a link that opens in a new tab.
+ scoped_ptr<TestWebContents> web_contents2(
+ TestWebContents::Create(browser_context(), webui_instance));
+ RenderViewHostManager manager2(web_contents2.get(), web_contents2.get(),
+ web_contents2.get());
+ manager2.Init(
+ browser_context(), webui_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ // Make sure the new RVH is considered live. This is usually done in
+ // RenderWidgetHost::Init when opening a new tab from a link.
+ manager2.current_host()->CreateRenderView(string16(), -1, -1);
+
+ const GURL kUrl2("chrome://foo/bar");
+ NavigationEntryImpl entry2(NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ RenderViewHost* host2 = manager2.Navigate(entry2);
+
+ // No cross-process transition happens because we are already in the right
+ // SiteInstance. We should grant bindings immediately.
+ EXPECT_EQ(host2, manager2.current_host());
+ EXPECT_TRUE(host2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ manager2.DidNavigateMainFrame(host2);
+}
+
+// Tests that we don't end up in an inconsistent state if a page does a back and
+// then reload. http://crbug.com/51680
+TEST_F(RenderViewHostManagerTest, PageDoesBackAndReload) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.evil-site.com/");
+
+ // Navigate to a safe site, then an evil site.
+ // This will switch RenderViewHosts. We cannot assert that the first and
+ // second RVHs are different, though, because the first one may be promptly
+ // deleted.
+ contents()->NavigateAndCommit(kUrl1);
+ contents()->NavigateAndCommit(kUrl2);
+ RenderViewHost* evil_rvh = contents()->GetRenderViewHost();
+
+ // Now let's simulate the evil page calling history.back().
+ contents()->OnGoToEntryAtOffset(-1);
+ // We should have a new pending RVH.
+ // Note that in this case, the navigation has not committed, so evil_rvh will
+ // not be deleted yet.
+ EXPECT_NE(evil_rvh, contents()->GetRenderManagerForTesting()->
+ pending_render_view_host());
+
+ // Before that RVH has committed, the evil page reloads itself.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = kUrl2;
+ params.transition = PAGE_TRANSITION_CLIENT_REDIRECT;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.was_within_same_page = false;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(kUrl2);
+ contents()->DidNavigate(evil_rvh, params);
+
+ // That should have cancelled the pending RVH, and the evil RVH should be the
+ // current one.
+ EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
+ pending_render_view_host() == NULL);
+ EXPECT_EQ(evil_rvh, contents()->GetRenderManagerForTesting()->current_host());
+
+ // Also we should not have a pending navigation entry.
+ NavigationEntry* entry = contents()->GetController().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_EQ(kUrl2, entry->GetURL());
+}
+
+// Ensure that we can go back and forward even if a SwapOut ACK isn't received.
+// See http://crbug.com/93427.
+TEST_F(RenderViewHostManagerTest, NavigateAfterMissingSwapOutACK) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+
+ // Navigate to two pages.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Keep active_view_count nonzero so that no swapped out views in
+ // this SiteInstance get forcefully deleted.
+ static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance())->
+ increment_active_view_count();
+
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance())->
+ increment_active_view_count();
+
+ // Now go back, but suppose the SwapOut_ACK isn't received. This shouldn't
+ // happen, but we have seen it when going back quickly across many entries
+ // (http://crbug.com/93427).
+ contents()->GetController().GoBack();
+ EXPECT_TRUE(rvh2->is_waiting_for_beforeunload_ack());
+ contents()->ProceedWithCrossSiteNavigation();
+ EXPECT_FALSE(rvh2->is_waiting_for_beforeunload_ack());
+ rvh2->SwapOut();
+ EXPECT_TRUE(rvh2->is_waiting_for_unload_ack());
+
+ // The back navigation commits. We should proactively clear the
+ // is_waiting_for_unload_ack state to be safe.
+ const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
+ rvh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
+ EXPECT_TRUE(rvh2->is_swapped_out());
+ EXPECT_FALSE(rvh2->is_waiting_for_unload_ack());
+
+ // We should be able to navigate forward.
+ contents()->GetController().GoForward();
+ contents()->ProceedWithCrossSiteNavigation();
+ const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry();
+ rvh2->SendNavigate(entry2->GetPageID(), entry2->GetURL());
+ EXPECT_EQ(rvh2, rvh());
+ EXPECT_FALSE(rvh2->is_swapped_out());
+ EXPECT_TRUE(rvh1->is_swapped_out());
+}
+
+// Test that we create swapped out RVHs for the opener chain when navigating an
+// opened tab cross-process. This allows us to support certain cross-process
+// JavaScript calls (http://crbug.com/99202).
+TEST_F(RenderViewHostManagerTest, CreateSwappedOutOpenerRVHs) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+ const GURL kChromeUrl("chrome://foo");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ RenderViewHostManager* manager = contents()->GetRenderManagerForTesting();
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Create 2 new tabs and simulate them being the opener chain for the main
+ // tab. They should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ scoped_ptr<TestWebContents> opener2(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener2_manager =
+ opener2->GetRenderManagerForTesting();
+ opener1->SetOpener(opener2.get());
+
+ // Navigate to a cross-site URL (different SiteInstance but same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
+ EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh2->GetSiteInstance()));
+
+ // Ensure rvh1 is placed on swapped out list of the current tab.
+ EXPECT_TRUE(manager->IsOnSwappedOutList(rvh1));
+ EXPECT_EQ(rvh1,
+ manager->GetSwappedOutRenderViewHost(rvh1->GetSiteInstance()));
+
+ // Ensure a swapped out RVH is created in the first opener tab.
+ TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
+ opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener1_manager->IsOnSwappedOutList(opener1_rvh));
+ EXPECT_TRUE(opener1_rvh->is_swapped_out());
+
+ // Ensure a swapped out RVH is created in the second opener tab.
+ TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
+ opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener2_manager->IsOnSwappedOutList(opener2_rvh));
+ EXPECT_TRUE(opener2_rvh->is_swapped_out());
+
+ // Navigate to a cross-BrowsingInstance URL.
+ contents()->NavigateAndCommit(kChromeUrl);
+ TestRenderViewHost* rvh3 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh3->GetSiteInstance());
+ EXPECT_FALSE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh3->GetSiteInstance()));
+
+ // No scripting is allowed across BrowsingInstances, so we should not create
+ // swapped out RVHs for the opener chain in this case.
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh3->GetSiteInstance()));
+ EXPECT_FALSE(opener2_manager->GetSwappedOutRenderViewHost(
+ rvh3->GetSiteInstance()));
+}
+
+// Test that we clean up swapped out RenderViewHosts when a process hosting
+// those associated RenderViews crashes. http://crbug.com/258993
+TEST_F(RenderViewHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash) {
+ const GURL kUrl1("http://www.google.com/");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Create a new tab as an opener for the main tab.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+ opener1->CreateSwappedOutRenderView(rvh1->GetSiteInstance());
+ EXPECT_TRUE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+
+ // Fake a process crash.
+ RenderProcessHost::RendererClosedDetails details(
+ rvh1->GetProcess()->GetHandle(),
+ base::TERMINATION_STATUS_PROCESS_CRASHED,
+ 0);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ Source<RenderProcessHost>(rvh1->GetProcess()),
+ Details<RenderProcessHost::RendererClosedDetails>(&details));
+ rvh1->set_render_view_created(false);
+
+ // Ensure that the swapped out RenderViewHost has been deleted.
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+
+ // Reload the initial tab. This should recreate the opener.
+ contents()->GetController().Reload(true);
+
+ EXPECT_EQ(opener1_manager->current_host()->GetRoutingID(),
+ test_rvh()->opener_route_id());
+}
+
+// Test that RenderViewHosts created for WebUI navigations are properly
+// granted WebUI bindings even if an unprivileged swapped out RenderViewHost
+// is in the same process (http://crbug.com/79918).
+TEST_F(RenderViewHostManagerTest, EnableWebUIWithSwappedOutOpener) {
+ set_should_create_webui(true);
+ const GURL kSettingsUrl("chrome://chrome/settings");
+ const GURL kPluginUrl("chrome://plugins");
+
+ // Navigate to an initial WebUI URL.
+ contents()->NavigateAndCommit(kSettingsUrl);
+
+ // Ensure the RVH has WebUI bindings.
+ TestRenderViewHost* rvh1 = test_rvh();
+ EXPECT_TRUE(rvh1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Create a new tab and simulate it being the opener for the main
+ // tab. It should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ // Navigate to a different WebUI URL (different SiteInstance, same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kPluginUrl);
+ TestRenderViewHost* rvh2 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
+ EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh2->GetSiteInstance()));
+
+ // Ensure a swapped out RVH is created in the first opener tab.
+ TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
+ opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener1_manager->IsOnSwappedOutList(opener1_rvh));
+ EXPECT_TRUE(opener1_rvh->is_swapped_out());
+
+ // Ensure the new RVH has WebUI bindings.
+ EXPECT_TRUE(rvh2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+}
+
+// Test that we reuse the same guest SiteInstance if we navigate across sites.
+TEST_F(RenderViewHostManagerTest, NoSwapOnGuestNavigations) {
+ TestNotificationTracker notifications;
+
+ GURL guest_url(std::string(chrome::kGuestScheme).append("://abc123"));
+ SiteInstance* instance =
+ SiteInstance::CreateForURL(browser_context(), guest_url);
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host;
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(
+ NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+ EXPECT_EQ(manager.current_host()->GetSiteInstance(), instance);
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_EQ(host, manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+
+ // 2) Navigate to a different domain. -------------------------
+ // Guests stay in the same process on navigation.
+ const GURL kUrl2("http://www.chromium.org");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(kUrl1, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ host = manager.Navigate(entry2);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_EQ(host, manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(static_cast<SiteInstanceImpl*>(host->GetSiteInstance()),
+ instance);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/touch_editable_impl_aura.cc b/chromium/content/browser/web_contents/touch_editable_impl_aura.cc
new file mode 100644
index 00000000000..fb430d092e0
--- /dev/null
+++ b/chromium/content/browser/web_contents/touch_editable_impl_aura.cc
@@ -0,0 +1,344 @@
+// Copyright (c) 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 "content/browser/web_contents/touch_editable_impl_aura.h"
+
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/render_widget_host.h"
+#include "grit/ui_strings.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/range/range.h"
+#include "ui/base/ui_base_switches_util.h"
+
+namespace content {
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEditableImplAura, public:
+
+TouchEditableImplAura::~TouchEditableImplAura() {
+ Cleanup();
+}
+
+// static
+TouchEditableImplAura* TouchEditableImplAura::Create() {
+ if (switches::IsTouchEditingEnabled())
+ return new TouchEditableImplAura();
+ return NULL;
+}
+
+void TouchEditableImplAura::AttachToView(RenderWidgetHostViewAura* view) {
+ if (rwhva_ == view)
+ return;
+
+ Cleanup();
+ if (!view)
+ return;
+
+ rwhva_ = view;
+ rwhva_->set_touch_editing_client(this);
+}
+
+void TouchEditableImplAura::UpdateEditingController() {
+ if (!rwhva_ || !rwhva_->HasFocus())
+ return;
+
+ // If touch editing handles were not visible, we bring them up only if
+ // there is non-zero selection on the page. And the current event is a
+ // gesture event (we dont want to show handles if the user is selecting
+ // using mouse or keyboard).
+ if (selection_gesture_in_process_ &&
+ selection_anchor_rect_ != selection_focus_rect_)
+ StartTouchEditing();
+
+ if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE ||
+ selection_anchor_rect_ != selection_focus_rect_) {
+ if (touch_selection_controller_)
+ touch_selection_controller_->SelectionChanged();
+ } else {
+ EndTouchEditing();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
+// implementation:
+
+void TouchEditableImplAura::StartTouchEditing() {
+ if (!touch_selection_controller_) {
+ touch_selection_controller_.reset(
+ ui::TouchSelectionController::create(this));
+ }
+ if (touch_selection_controller_)
+ touch_selection_controller_->SelectionChanged();
+}
+
+void TouchEditableImplAura::EndTouchEditing() {
+ if (touch_selection_controller_) {
+ if (touch_selection_controller_->IsHandleDragInProgress())
+ touch_selection_controller_->SelectionChanged();
+ else
+ touch_selection_controller_.reset();
+ }
+}
+
+void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor,
+ const gfx::Rect& focus) {
+ selection_anchor_rect_ = anchor;
+ selection_focus_rect_ = focus;
+ UpdateEditingController();
+}
+
+void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
+ text_input_type_ = type;
+}
+
+bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
+ DCHECK(rwhva_);
+ if (event->IsTouchEvent())
+ return false;
+
+ if (!event->IsGestureEvent()) {
+ EndTouchEditing();
+ return false;
+ }
+
+ const ui::GestureEvent* gesture_event =
+ static_cast<const ui::GestureEvent*>(event);
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP:
+ tap_gesture_tap_count_queue_.push(gesture_event->details().tap_count());
+ if (gesture_event->details().tap_count() > 1)
+ selection_gesture_in_process_ = true;
+ // When the user taps, we want to show touch editing handles if user
+ // tapped on selected text.
+ if (selection_anchor_rect_ != selection_focus_rect_) {
+ // UnionRects only works for rects with non-zero width.
+ gfx::Rect anchor(selection_anchor_rect_.origin(),
+ gfx::Size(1, selection_anchor_rect_.height()));
+ gfx::Rect focus(selection_focus_rect_.origin(),
+ gfx::Size(1, selection_focus_rect_.height()));
+ gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
+ if (selection_rect.Contains(gesture_event->location())) {
+ StartTouchEditing();
+ return true;
+ }
+ }
+ // For single taps, not inside selected region, we want to show handles
+ // only when the tap is on an already focused textfield.
+ is_tap_on_focused_textfield_ = false;
+ if (gesture_event->details().tap_count() == 1 &&
+ text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)
+ is_tap_on_focused_textfield_ = true;
+ break;
+ case ui::ET_GESTURE_LONG_PRESS:
+ selection_gesture_in_process_ = true;
+ break;
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ // If selection handles are currently visible, we want to get them back up
+ // when scrolling ends. So we set |handles_hidden_due_to_scroll_| so that
+ // we can re-start touch editing when we call |UpdateEditingController()|
+ // on scroll end gesture.
+ handles_hidden_due_to_scroll_ = false;
+ if (touch_selection_controller_)
+ handles_hidden_due_to_scroll_ = true;
+ EndTouchEditing();
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ if (handles_hidden_due_to_scroll_ &&
+ (selection_anchor_rect_ != selection_focus_rect_ ||
+ text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
+ StartTouchEditing();
+ UpdateEditingController();
+ }
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
+ DCHECK(rwhva_);
+ if (gesture_event_type == WebKit::WebInputEvent::GestureTap &&
+ text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
+ is_tap_on_focused_textfield_) {
+ StartTouchEditing();
+ if (touch_selection_controller_)
+ touch_selection_controller_->SelectionChanged();
+ }
+
+ if (gesture_event_type == WebKit::WebInputEvent::GestureLongPress)
+ selection_gesture_in_process_ = false;
+ if (gesture_event_type == WebKit::WebInputEvent::GestureTap) {
+ if (tap_gesture_tap_count_queue_.front() > 1)
+ selection_gesture_in_process_ = false;
+ tap_gesture_tap_count_queue_.pop();
+ }
+}
+
+void TouchEditableImplAura::OnViewDestroyed() {
+ Cleanup();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEditableImplAura, ui::TouchEditable implementation:
+
+void TouchEditableImplAura::SelectRect(const gfx::Point& start,
+ const gfx::Point& end) {
+ if (!rwhva_)
+ return;
+
+ RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
+ rwhva_->GetRenderWidgetHost());
+ host->SelectRange(start, end);
+}
+
+void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
+ if (!rwhva_)
+ return;
+
+ RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
+ rwhva_->GetRenderWidgetHost());
+ host->MoveCaret(point);
+}
+
+void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
+ gfx::Rect* p2) {
+ *p1 = selection_anchor_rect_;
+ *p2 = selection_focus_rect_;
+}
+
+gfx::Rect TouchEditableImplAura::GetBounds() {
+ return rwhva_ ? rwhva_->GetNativeView()->bounds() : gfx::Rect();
+}
+
+gfx::NativeView TouchEditableImplAura::GetNativeView() {
+ return rwhva_ ? rwhva_->GetNativeView()->GetRootWindow() : NULL;
+}
+
+void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
+ if (!rwhva_)
+ return;
+ aura::Window* window = rwhva_->GetNativeView();
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(window->GetRootWindow());
+ if (screen_position_client)
+ screen_position_client->ConvertPointToScreen(window, point);
+}
+
+void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
+ if (!rwhva_)
+ return;
+ aura::Window* window = rwhva_->GetNativeView();
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(window->GetRootWindow());
+ if (screen_position_client)
+ screen_position_client->ConvertPointFromScreen(window, point);
+}
+
+bool TouchEditableImplAura::DrawsHandles() {
+ return false;
+}
+
+void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
+ if (!rwhva_)
+ return;
+ gfx::Point point = anchor;
+ ConvertPointFromScreen(&point);
+ RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
+ host->Send(new ViewMsg_ShowContextMenu(host->GetRoutingID(), point));
+ EndTouchEditing();
+}
+
+bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
+ NOTREACHED();
+ return false;
+}
+
+bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
+ if (!rwhva_)
+ return false;
+ bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
+ ui::Range selection_range;
+ rwhva_->GetSelectionRange(&selection_range);
+ bool has_selection = !selection_range.is_empty();
+ switch (command_id) {
+ case IDS_APP_CUT:
+ return editable && has_selection;
+ case IDS_APP_COPY:
+ return has_selection;
+ case IDS_APP_PASTE: {
+ string16 result;
+ ui::Clipboard::GetForCurrentThread()->ReadText(
+ ui::Clipboard::BUFFER_STANDARD, &result);
+ return editable && !result.empty();
+ }
+ case IDS_APP_DELETE:
+ return editable && has_selection;
+ case IDS_APP_SELECT_ALL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool TouchEditableImplAura::GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) {
+ return false;
+}
+
+void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
+ if (!rwhva_)
+ return;
+ RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
+ switch (command_id) {
+ case IDS_APP_CUT:
+ host->Cut();
+ break;
+ case IDS_APP_COPY:
+ host->Copy();
+ break;
+ case IDS_APP_PASTE:
+ host->Paste();
+ break;
+ case IDS_APP_DELETE:
+ host->Delete();
+ break;
+ case IDS_APP_SELECT_ALL:
+ host->SelectAll();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ EndTouchEditing();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEditableImplAura, private:
+
+TouchEditableImplAura::TouchEditableImplAura()
+ : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
+ rwhva_(NULL),
+ selection_gesture_in_process_(false),
+ handles_hidden_due_to_scroll_(false),
+ is_tap_on_focused_textfield_(false) {
+}
+
+void TouchEditableImplAura::Cleanup() {
+ if (rwhva_) {
+ rwhva_->set_touch_editing_client(NULL);
+ rwhva_ = NULL;
+ }
+ touch_selection_controller_.reset();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/touch_editable_impl_aura.h b/chromium/content/browser/web_contents/touch_editable_impl_aura.h
new file mode 100644
index 00000000000..c31d86ae7a7
--- /dev/null
+++ b/chromium/content/browser/web_contents/touch_editable_impl_aura.h
@@ -0,0 +1,106 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEB_CONTENTS_TOUCH_EDITABLE_IMPL_AURA_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_TOUCH_EDITABLE_IMPL_AURA_H_
+
+#include <deque>
+#include <map>
+
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/touch/touch_editing_controller.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+namespace ui {
+class Accelerator;
+}
+
+namespace content {
+class TouchEditableImplAuraTest;
+
+// Aura specific implementation of ui::TouchEditable for a RenderWidgetHostView.
+class CONTENT_EXPORT TouchEditableImplAura
+ : public ui::TouchEditable,
+ public NON_EXPORTED_BASE(RenderWidgetHostViewAura::TouchEditingClient) {
+ public:
+ virtual ~TouchEditableImplAura();
+
+ static TouchEditableImplAura* Create();
+
+ void AttachToView(RenderWidgetHostViewAura* view);
+
+ // Updates the |touch_selection_controller_| or ends touch editing session
+ // depending on the current selection and cursor state.
+ void UpdateEditingController();
+
+ // Overridden from RenderWidgetHostViewAura::TouchEditingClient.
+ virtual void StartTouchEditing() OVERRIDE;
+ virtual void EndTouchEditing() OVERRIDE;
+ virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor,
+ const gfx::Rect& focus) OVERRIDE;
+ virtual void OnTextInputTypeChanged(ui::TextInputType type) OVERRIDE;
+ virtual bool HandleInputEvent(const ui::Event* event) OVERRIDE;
+ virtual void GestureEventAck(int gesture_event_type) OVERRIDE;
+ virtual void OnViewDestroyed() OVERRIDE;
+
+ // Overridden from ui::TouchEditable:
+ virtual void SelectRect(const gfx::Point& start,
+ const gfx::Point& end) OVERRIDE;
+ virtual void MoveCaretTo(const gfx::Point& point) OVERRIDE;
+ virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) OVERRIDE;
+ virtual gfx::Rect GetBounds() OVERRIDE;
+ virtual gfx::NativeView GetNativeView() OVERRIDE;
+ virtual void ConvertPointToScreen(gfx::Point* point) OVERRIDE;
+ virtual void ConvertPointFromScreen(gfx::Point* point) OVERRIDE;
+ virtual bool DrawsHandles() OVERRIDE;
+ virtual void OpenContextMenu(const gfx::Point& anchor) OVERRIDE;
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
+
+ protected:
+ TouchEditableImplAura();
+
+ private:
+ friend class TouchEditableImplAuraTest;
+
+ void Cleanup();
+
+ // Rectangles for the selection anchor and focus.
+ gfx::Rect selection_anchor_rect_;
+ gfx::Rect selection_focus_rect_;
+
+ // The current text input type.
+ ui::TextInputType text_input_type_;
+
+ RenderWidgetHostViewAura* rwhva_;
+ scoped_ptr<ui::TouchSelectionController> touch_selection_controller_;
+
+ // True if |rwhva_| is currently handling a gesture that could result in a
+ // change in selection (long press, double tap or triple tap).
+ bool selection_gesture_in_process_;
+
+ bool handles_hidden_due_to_scroll_;
+
+ // Used to track if the current tap gesture is on a focused textfield.
+ bool is_tap_on_focused_textfield_;
+
+ // When we receive ack for a ET_GESTURE_TAP, we do not know if the ack is for
+ // a tap or a double tap (we only get the event type in the ack). So we have
+ // this queue to keep track of the the tap count so that we can distinguish
+ // between double and single tap when we get the ack.
+ std::queue<int> tap_gesture_tap_count_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEditableImplAura);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_TOUCH_EDITABLE_IMPL_AURA_H_
diff --git a/chromium/content/browser/web_contents/touch_editable_impl_aura_browsertest.cc b/chromium/content/browser/web_contents/touch_editable_impl_aura_browsertest.cc
new file mode 100644
index 00000000000..ce0a7027c12
--- /dev/null
+++ b/chromium/content/browser/web_contents/touch_editable_impl_aura_browsertest.cc
@@ -0,0 +1,357 @@
+// 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 "content/browser/web_contents/touch_editable_impl_aura.h"
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_contents_view_aura.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+
+namespace content {
+
+class TestTouchEditableImplAura : public TouchEditableImplAura {
+ public:
+ TestTouchEditableImplAura()
+ : selection_changed_callback_arrived_(false),
+ waiting_for_selection_changed_callback_(false),
+ gesture_ack_callback_arrived_(false),
+ waiting_for_gesture_ack_callback_(false) {}
+
+ void Reset() {
+ selection_changed_callback_arrived_ = false;
+ waiting_for_selection_changed_callback_ = false;
+ gesture_ack_callback_arrived_ = false;
+ waiting_for_gesture_ack_callback_ = false;
+ }
+
+ virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor,
+ const gfx::Rect& focus) OVERRIDE {
+ selection_changed_callback_arrived_ = true;
+ TouchEditableImplAura::OnSelectionOrCursorChanged(anchor, focus);
+ if (waiting_for_selection_changed_callback_)
+ selection_changed_wait_run_loop_->Quit();
+ }
+
+ virtual void GestureEventAck(int gesture_event_type) OVERRIDE {
+ gesture_ack_callback_arrived_ = true;
+ TouchEditableImplAura::GestureEventAck(gesture_event_type);
+ if (waiting_for_gesture_ack_callback_)
+ gesture_ack_wait_run_loop_->Quit();
+ }
+
+ void WaitForSelectionChangeCallback() {
+ if (selection_changed_callback_arrived_)
+ return;
+ waiting_for_selection_changed_callback_ = true;
+ selection_changed_wait_run_loop_.reset(new base::RunLoop());
+ selection_changed_wait_run_loop_->Run();
+ }
+
+ void WaitForGestureAck() {
+ if (gesture_ack_callback_arrived_)
+ return;
+ waiting_for_gesture_ack_callback_ = true;
+ gesture_ack_wait_run_loop_.reset(new base::RunLoop());
+ gesture_ack_wait_run_loop_->Run();
+ }
+
+ protected:
+ virtual ~TestTouchEditableImplAura() {}
+
+ private:
+ bool selection_changed_callback_arrived_;
+ bool waiting_for_selection_changed_callback_;
+ bool gesture_ack_callback_arrived_;
+ bool waiting_for_gesture_ack_callback_;
+ scoped_ptr<base::RunLoop> selection_changed_wait_run_loop_;
+ scoped_ptr<base::RunLoop> gesture_ack_wait_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAura);
+};
+
+class TouchEditableImplAuraTest : public ContentBrowserTest {
+ public:
+ TouchEditableImplAuraTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitch(switches::kEnableTouchEditing);
+ }
+
+ // Executes the javascript synchronously and makes sure the returned value is
+ // freed properly.
+ void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) {
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(rvh, jscript);
+ }
+
+ // Starts the test server and navigates to the given url. Sets a large enough
+ // size to the root window. Returns after the navigation to the url is
+ // complete.
+ void StartTestWithPage(const std::string& url) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL test_url(test_server()->GetURL(url));
+ NavigateToURL(shell(), test_url);
+ aura::Window* content =
+ shell()->web_contents()->GetView()->GetContentNativeView();
+ content->GetRootWindow()->SetHostSize(gfx::Size(800, 600));
+ }
+
+ void TestTouchSelectionOriginatingFromWebpage() {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/touch_selection.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
+ view_aura->SetTouchEditableForTest(touch_editable);
+ RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
+ web_contents->GetRenderWidgetHostView());
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+
+ touch_editable->Reset();
+ ExecuteSyncJSFunction(view_host, "select_all_text()");
+ touch_editable->WaitForSelectionChangeCallback();
+
+ // Tap inside selection to bring up selection handles.
+ generator.GestureTapAt(gfx::Point(bounds.x() + 10, bounds.y() + 10));
+ EXPECT_EQ(touch_editable->rwhva_, rwhva);
+
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(view_host, "get_selection()");
+ std::string selection;
+ value->GetAsString(&selection);
+
+ // Check if selection handles are showing.
+ EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
+ EXPECT_STREQ("Some text we can select", selection.c_str());
+
+ // Lets move the handles a bit to modify the selection
+ touch_editable->Reset();
+ generator.GestureScrollSequence(
+ gfx::Point(10, 47),
+ gfx::Point(30, 47),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+ EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
+ value = content::ExecuteScriptAndGetValue(view_host, "get_selection()");
+ value->GetAsString(&selection);
+
+ // It is hard to tell what exactly the selection would be now. But it would
+ // definitely be less than whatever was selected before.
+ EXPECT_GT(std::strlen("Some text we can select"), selection.size());
+ }
+
+ void TestTouchSelectionOnLongPress() {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/touch_selection.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
+ view_aura->SetTouchEditableForTest(touch_editable);
+ RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
+ web_contents->GetRenderWidgetHostView());
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ EXPECT_EQ(touch_editable->rwhva_, rwhva);
+
+ // Long press to select word.
+ ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS,
+ 10,
+ 10,
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(
+ ui::ET_GESTURE_LONG_PRESS, 0, 0),
+ 1);
+ touch_editable->Reset();
+ rwhva->OnGestureEvent(&long_press);
+ touch_editable->WaitForSelectionChangeCallback();
+
+ // Check if selection handles are showing.
+ ui::TouchSelectionController* controller =
+ touch_editable->touch_selection_controller_.get();
+ EXPECT_TRUE(controller);
+
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(view_host, "get_selection()");
+ std::string selection;
+ value->GetAsString(&selection);
+ EXPECT_STREQ("Some", selection.c_str());
+ }
+
+ void TestTouchSelectionHiddenWhenScrolling() {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/touch_selection.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
+ view_aura->SetTouchEditableForTest(touch_editable);
+ RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
+ web_contents->GetRenderWidgetHostView());
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ EXPECT_EQ(touch_editable->rwhva_, rwhva);
+
+ // Long press to select word.
+ ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS,
+ 10,
+ 10,
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(
+ ui::ET_GESTURE_LONG_PRESS, 0, 0),
+ 1);
+ touch_editable->Reset();
+ rwhva->OnGestureEvent(&long_press);
+ touch_editable->WaitForSelectionChangeCallback();
+
+ // Check if selection handles are showing.
+ ui::TouchSelectionController* controller =
+ touch_editable->touch_selection_controller_.get();
+ EXPECT_TRUE(controller);
+
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(view_host, "get_selection()");
+ std::string selection;
+ value->GetAsString(&selection);
+ EXPECT_STREQ("Some", selection.c_str());
+
+ // Start scrolling. Handles should get hidden.
+ ui::GestureEvent scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN,
+ 10,
+ 10,
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(
+ ui::ET_GESTURE_LONG_PRESS, 0, 0),
+ 1);
+ rwhva->OnGestureEvent(&scroll_begin);
+ EXPECT_FALSE(touch_editable->touch_selection_controller_.get());
+
+ // Handles should come back after scroll ends.
+ ui::GestureEvent scroll_end(ui::ET_GESTURE_SCROLL_END,
+ 10,
+ 10,
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(
+ ui::ET_GESTURE_LONG_PRESS, 0, 0),
+ 1);
+ rwhva->OnGestureEvent(&scroll_end);
+ EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
+ }
+
+ void TestTouchCursorInTextfield() {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/touch_selection.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
+ view_aura->SetTouchEditableForTest(touch_editable);
+ RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
+ web_contents->GetRenderWidgetHostView());
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ EXPECT_EQ(touch_editable->rwhva_, rwhva);
+ ExecuteSyncJSFunction(view_host, "focus_textfield()");
+
+ // Tap textfield
+ touch_editable->Reset();
+ generator.GestureTapAt(gfx::Point(bounds.x() + 50, bounds.y() + 40));
+ touch_editable->WaitForGestureAck(); // Wait for Tap Down Ack
+ touch_editable->Reset();
+ touch_editable->WaitForGestureAck(); // Wait for Tap Ack.
+
+ // Check if cursor handle is showing.
+ ui::TouchSelectionController* controller =
+ touch_editable->touch_selection_controller_.get();
+ EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, touch_editable->text_input_type_);
+ EXPECT_TRUE(controller);
+
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(view_host, "get_cursor_position()");
+ int cursor_pos = -1;
+ value->GetAsInteger(&cursor_pos);
+ EXPECT_NE(-1, cursor_pos);
+
+ // Move the cursor handle.
+ generator.GestureScrollSequence(
+ gfx::Point(50, 59),
+ gfx::Point(10, 59),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+ EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
+ value = content::ExecuteScriptAndGetValue(
+ view_host, "get_cursor_position()");
+ int new_cursor_pos = -1;
+ value->GetAsInteger(&new_cursor_pos);
+ EXPECT_NE(-1, new_cursor_pos);
+ // Cursor should have moved.
+ EXPECT_NE(new_cursor_pos, cursor_pos);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TouchEditableImplAuraTest);
+};
+
+IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
+ TouchSelectionOriginatingFromWebpageTest) {
+ TestTouchSelectionOriginatingFromWebpage();
+}
+
+IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
+ TestTouchSelectionHiddenWhenScrolling) {
+ TestTouchSelectionHiddenWhenScrolling();
+}
+
+IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
+ TouchSelectionOnLongPressTest) {
+ TestTouchSelectionOnLongPress();
+}
+
+// TODO(miu): Disabled test due to flakiness. http://crbug.com/235991
+IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
+ DISABLED_TouchCursorInTextfieldTest) {
+ TestTouchCursorInTextfield();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_delegate_unittest.cc b/chromium/content/browser/web_contents/web_contents_delegate_unittest.cc
new file mode 100644
index 00000000000..d5d60c37252
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_delegate_unittest.cc
@@ -0,0 +1,66 @@
+// 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 "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class MockWebContentsDelegate : public WebContentsDelegate {
+};
+
+class WebContentsDelegateTest : public RenderViewHostImplTestHarness {
+};
+
+TEST_F(WebContentsDelegateTest, UnregisterInDestructor) {
+ scoped_ptr<WebContentsImpl> contents_a(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ scoped_ptr<WebContentsImpl> contents_b(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ EXPECT_EQ(NULL, contents_a->GetDelegate());
+ EXPECT_EQ(NULL, contents_b->GetDelegate());
+
+ scoped_ptr<MockWebContentsDelegate> delegate(new MockWebContentsDelegate());
+
+ // Setting a delegate should work correctly.
+ contents_a->SetDelegate(delegate.get());
+ EXPECT_EQ(delegate.get(), contents_a->GetDelegate());
+ EXPECT_TRUE(contents_b->GetDelegate() == NULL);
+
+ // A delegate can be a delegate to multiple WebContentsImpl.
+ contents_b->SetDelegate(delegate.get());
+ EXPECT_EQ(delegate.get(), contents_a->GetDelegate());
+ EXPECT_EQ(delegate.get(), contents_b->GetDelegate());
+
+ // Setting the same delegate multiple times should work correctly.
+ contents_b->SetDelegate(delegate.get());
+ EXPECT_EQ(delegate.get(), contents_a->GetDelegate());
+ EXPECT_EQ(delegate.get(), contents_b->GetDelegate());
+
+ // Setting delegate to NULL should work correctly.
+ contents_b->SetDelegate(NULL);
+ EXPECT_EQ(delegate.get(), contents_a->GetDelegate());
+ EXPECT_TRUE(contents_b->GetDelegate() == NULL);
+
+ // Destroying the delegate while it is still the delegate for a
+ // WebContentsImpl should unregister it.
+ contents_b->SetDelegate(delegate.get());
+ EXPECT_EQ(delegate.get(), contents_a->GetDelegate());
+ EXPECT_EQ(delegate.get(), contents_b->GetDelegate());
+ delegate.reset(NULL);
+ EXPECT_TRUE(contents_a->GetDelegate() == NULL);
+ EXPECT_TRUE(contents_b->GetDelegate() == NULL);
+
+ // Destroy the WebContentses and run the message loop to prevent leaks.
+ contents_a.reset();
+ contents_b.reset();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_drag_win.cc b/chromium/content/browser/web_contents/web_contents_drag_win.cc
new file mode 100644
index 00000000000..cb24210c3b9
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_drag_win.cc
@@ -0,0 +1,443 @@
+// 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 "content/browser/web_contents/web_contents_drag_win.h"
+
+#include <windows.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pickle.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "content/browser/download/drag_download_file.h"
+#include "content/browser/download/drag_download_util.h"
+#include "content/browser/web_contents/web_drag_dest_win.h"
+#include "content/browser/web_contents/web_drag_source_win.h"
+#include "content/browser/web_contents/web_drag_utils_win.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/browser/web_drag_dest_delegate.h"
+#include "content/public/common/drop_data.h"
+#include "net/base/net_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/dragdrop/drag_utils.h"
+#include "ui/base/layout.h"
+#include "ui/base/win/scoped_ole_initializer.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+
+using WebKit::WebDragOperationsMask;
+using WebKit::WebDragOperationCopy;
+using WebKit::WebDragOperationLink;
+using WebKit::WebDragOperationMove;
+
+namespace content {
+namespace {
+
+bool run_do_drag_drop = true;
+
+HHOOK msg_hook = NULL;
+DWORD drag_out_thread_id = 0;
+bool mouse_up_received = false;
+
+LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
+ if (code == base::MessagePumpForUI::kMessageFilterCode &&
+ !mouse_up_received) {
+ MSG* msg = reinterpret_cast<MSG*>(lparam);
+ // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key
+ // is pressed down on drag-and-drop, it means to create a link.
+ if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP ||
+ msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
+ // Forward the message from the UI thread to the drag-and-drop thread.
+ PostThreadMessage(drag_out_thread_id,
+ msg->message,
+ msg->wParam,
+ msg->lParam);
+
+ // If the left button is up, we do not need to forward the message any
+ // more.
+ if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
+ mouse_up_received = true;
+
+ return TRUE;
+ }
+ }
+ return CallNextHookEx(msg_hook, code, wparam, lparam);
+}
+
+void EnableBackgroundDraggingSupport(DWORD thread_id) {
+ // Install a hook procedure to monitor the messages so that we can forward
+ // the appropriate ones to the background thread.
+ drag_out_thread_id = thread_id;
+ mouse_up_received = false;
+ DCHECK(!msg_hook);
+ msg_hook = SetWindowsHookEx(WH_MSGFILTER,
+ MsgFilterProc,
+ NULL,
+ GetCurrentThreadId());
+
+ // Attach the input state of the background thread to the UI thread so that
+ // SetCursor can work from the background thread.
+ AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE);
+}
+
+void DisableBackgroundDraggingSupport() {
+ DCHECK(msg_hook);
+ AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
+ UnhookWindowsHookEx(msg_hook);
+ msg_hook = NULL;
+}
+
+bool IsBackgroundDraggingSupportEnabled() {
+ return msg_hook != NULL;
+}
+
+} // namespace
+
+class DragDropThread : public base::Thread {
+ public:
+ explicit DragDropThread(WebContentsDragWin* drag_handler)
+ : Thread("Chrome_DragDropThread"),
+ drag_handler_(drag_handler) {
+ }
+
+ virtual ~DragDropThread() {
+ Stop();
+ }
+
+ protected:
+ // base::Thread implementations:
+ virtual void Init() {
+ ole_initializer_.reset(new ui::ScopedOleInitializer());
+ }
+
+ virtual void CleanUp() {
+ ole_initializer_.reset();
+ }
+
+ private:
+ scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
+
+ // Hold a reference count to WebContentsDragWin to make sure that it is always
+ // alive in the thread lifetime.
+ scoped_refptr<WebContentsDragWin> drag_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDropThread);
+};
+
+WebContentsDragWin::WebContentsDragWin(
+ gfx::NativeWindow source_window,
+ WebContents* web_contents,
+ WebDragDest* drag_dest,
+ const base::Callback<void()>& drag_end_callback)
+ : drag_drop_thread_id_(0),
+ source_window_(source_window),
+ web_contents_(web_contents),
+ drag_dest_(drag_dest),
+ drag_ended_(false),
+ drag_end_callback_(drag_end_callback) {
+}
+
+WebContentsDragWin::~WebContentsDragWin() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!drag_drop_thread_.get());
+}
+
+void WebContentsDragWin::StartDragging(const DropData& drop_data,
+ WebDragOperationsMask ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ drag_source_ = new WebDragSource(source_window_, web_contents_);
+
+ const GURL& page_url = web_contents_->GetLastCommittedURL();
+ const std::string& page_encoding = web_contents_->GetEncoding();
+
+ // If it is not drag-out, do the drag-and-drop in the current UI thread.
+ if (drop_data.download_metadata.empty()) {
+ if (DoDragging(drop_data, ops, page_url, page_encoding,
+ image, image_offset))
+ EndDragging();
+ return;
+ }
+
+ // Start a background thread to do the drag-and-drop.
+ DCHECK(!drag_drop_thread_.get());
+ drag_drop_thread_.reset(new DragDropThread(this));
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_UI;
+ if (drag_drop_thread_->StartWithOptions(options)) {
+ drag_drop_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebContentsDragWin::StartBackgroundDragging, this,
+ drop_data, ops, page_url, page_encoding,
+ image, image_offset));
+ }
+
+ EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id());
+}
+
+void WebContentsDragWin::StartBackgroundDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask ops,
+ const GURL& page_url,
+ const std::string& page_encoding,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset) {
+ drag_drop_thread_id_ = base::PlatformThread::CurrentId();
+
+ if (DoDragging(drop_data, ops, page_url, page_encoding,
+ image, image_offset)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WebContentsDragWin::EndDragging, this));
+ } else {
+ // When DoDragging returns false, the contents view window is gone and thus
+ // WebContentsViewWin instance becomes invalid though WebContentsDragWin
+ // instance is still alive because the task holds a reference count to it.
+ // We should not do more than the following cleanup items:
+ // 1) Remove the background dragging support. This is safe since it does not
+ // access the instance at all.
+ // 2) Stop the background thread. This is done in OnDataObjectDisposed.
+ // Only drag_drop_thread_ member is accessed.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DisableBackgroundDraggingSupport));
+ }
+}
+
+void WebContentsDragWin::PrepareDragForDownload(
+ const DropData& drop_data,
+ ui::OSExchangeData* data,
+ const GURL& page_url,
+ const std::string& page_encoding) {
+ // Parse the download metadata.
+ string16 mime_type;
+ base::FilePath file_name;
+ GURL download_url;
+ if (!ParseDownloadMetadata(drop_data.download_metadata,
+ &mime_type,
+ &file_name,
+ &download_url))
+ return;
+
+ // Generate the file name based on both mime type and proposed file name.
+ std::string default_name =
+ GetContentClient()->browser()->GetDefaultDownloadName();
+ base::FilePath generated_download_file_name =
+ net::GenerateFileName(download_url,
+ std::string(),
+ std::string(),
+ UTF16ToUTF8(file_name.value()),
+ UTF16ToUTF8(mime_type),
+ default_name);
+ base::FilePath temp_dir_path;
+ if (!file_util::CreateNewTempDirectory(
+ FILE_PATH_LITERAL("chrome_drag"), &temp_dir_path))
+ return;
+ base::FilePath download_path =
+ temp_dir_path.Append(generated_download_file_name);
+
+ // We cannot know when the target application will be done using the temporary
+ // file, so schedule it to be deleted after rebooting.
+ base::DeleteFileAfterReboot(download_path);
+ base::DeleteFileAfterReboot(temp_dir_path);
+
+ // Provide the data as file (CF_HDROP). A temporary download file with the
+ // Zone.Identifier ADS (Alternate Data Stream) attached will be created.
+ scoped_refptr<DragDownloadFile> download_file =
+ new DragDownloadFile(
+ download_path,
+ scoped_ptr<net::FileStream>(),
+ download_url,
+ Referrer(page_url, drop_data.referrer_policy),
+ page_encoding,
+ web_contents_);
+ ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(),
+ download_file.get());
+ data->SetDownloadFileInfo(file_download);
+
+ // Enable asynchronous operation.
+ ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
+}
+
+void WebContentsDragWin::PrepareDragForFileContents(
+ const DropData& drop_data, ui::OSExchangeData* data) {
+ static const int kMaxFilenameLength = 255; // FAT and NTFS
+ base::FilePath file_name(drop_data.file_description_filename);
+
+ // Images without ALT text will only have a file extension so we need to
+ // synthesize one from the provided extension and URL.
+ if (file_name.BaseName().RemoveExtension().empty()) {
+ const string16 extension = file_name.Extension();
+ // Retrieve the name from the URL.
+ file_name = base::FilePath(
+ net::GetSuggestedFilename(drop_data.url, "", "", "", "", ""));
+ if (file_name.value().size() + extension.size() > kMaxFilenameLength) {
+ file_name = base::FilePath(file_name.value().substr(
+ 0, kMaxFilenameLength - extension.size()));
+ }
+ file_name = file_name.ReplaceExtension(extension);
+ }
+ data->SetFileContents(file_name, drop_data.file_contents);
+}
+
+void WebContentsDragWin::PrepareDragForUrl(const DropData& drop_data,
+ ui::OSExchangeData* data) {
+ if (drag_dest_->delegate() &&
+ drag_dest_->delegate()->AddDragData(drop_data, data)) {
+ return;
+ }
+
+ data->SetURL(drop_data.url, drop_data.url_title);
+}
+
+bool WebContentsDragWin::DoDragging(const DropData& drop_data,
+ WebDragOperationsMask ops,
+ const GURL& page_url,
+ const std::string& page_encoding,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset) {
+ ui::OSExchangeData data;
+
+ if (!drop_data.download_metadata.empty()) {
+ PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
+
+ // Set the observer.
+ ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
+ }
+
+ // We set the file contents before the URL because the URL also sets file
+ // contents (to a .URL shortcut). We want to prefer file content data over
+ // a shortcut so we add it first.
+ if (!drop_data.file_contents.empty())
+ PrepareDragForFileContents(drop_data, &data);
+ if (!drop_data.html.string().empty())
+ data.SetHtml(drop_data.html.string(), drop_data.html_base_url);
+ // We set the text contents before the URL because the URL also sets text
+ // content.
+ if (!drop_data.text.string().empty())
+ data.SetString(drop_data.text.string());
+ if (drop_data.url.is_valid())
+ PrepareDragForUrl(drop_data, &data);
+ if (!drop_data.custom_data.empty()) {
+ Pickle pickle;
+ ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle);
+ data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle);
+ }
+
+ // Set drag image.
+ if (!image.isNull()) {
+ drag_utils::SetDragImageOnDataObject(image,
+ gfx::Size(image.width(), image.height()), image_offset, &data);
+ }
+
+ // Use a local variable to keep track of the contents view window handle.
+ // It might not be safe to access the instance after DoDragDrop returns
+ // because the window could be disposed in the nested message loop.
+ HWND native_window = web_contents_->GetView()->GetNativeView();
+
+ // We need to enable recursive tasks on the message loop so we can get
+ // updates while in the system DoDragDrop loop.
+ DWORD effect = DROPEFFECT_NONE;
+ if (run_do_drag_drop) {
+ // Keep a reference count such that |drag_source_| will not get deleted
+ // if the contents view window is gone in the nested message loop invoked
+ // from DoDragDrop.
+ scoped_refptr<WebDragSource> retain_source(drag_source_);
+ retain_source->set_data(&data);
+ data.SetInDragLoop(true);
+
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
+ drag_source_,
+ WebDragOpMaskToWinDragOpMask(ops),
+ &effect);
+ retain_source->set_data(NULL);
+ }
+
+ // Bail out immediately if the contents view window is gone.
+ if (!IsWindow(native_window))
+ return false;
+
+ // Normally, the drop and dragend events get dispatched in the system
+ // DoDragDrop message loop so it'd be too late to set the effect to send back
+ // to the renderer here. However, we use PostTask to delay the execution of
+ // WebDragSource::OnDragSourceDrop, which means that the delayed dragend
+ // callback to the renderer doesn't run until this has been set to the correct
+ // value.
+ drag_source_->set_effect(effect);
+
+ return true;
+}
+
+void WebContentsDragWin::EndDragging() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (drag_ended_)
+ return;
+ drag_ended_ = true;
+
+ if (IsBackgroundDraggingSupportEnabled())
+ DisableBackgroundDraggingSupport();
+
+ drag_end_callback_.Run();
+}
+
+void WebContentsDragWin::CancelDrag() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ drag_source_->CancelDrag();
+}
+
+void WebContentsDragWin::CloseThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ drag_drop_thread_.reset();
+}
+
+void WebContentsDragWin::OnWaitForData() {
+ DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
+
+ // When the left button is release and we start to wait for the data, end
+ // the dragging before DoDragDrop returns. This makes the page leave the drag
+ // mode so that it can start to process the normal input events.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WebContentsDragWin::EndDragging, this));
+}
+
+void WebContentsDragWin::OnDataObjectDisposed() {
+ DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
+
+ // The drag-and-drop thread is only closed after OLE is done with
+ // DataObjectImpl.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WebContentsDragWin::CloseThread, this));
+}
+
+// static
+void WebContentsDragWin::DisableDragDropForTesting() {
+ run_do_drag_drop = false;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_drag_win.h b/chromium/content/browser/web_contents/web_contents_drag_win.h
new file mode 100644
index 00000000000..b6a98058196
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_drag_win.h
@@ -0,0 +1,122 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_DRAG_WIN_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_DRAG_WIN_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/platform_thread.h"
+#include "content/common/content_export.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+
+class SkBitmap;
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace content {
+class DragDropThread;
+class WebContents;
+class WebDragDest;
+class WebDragSource;
+struct DropData;
+
+// Windows-specific drag-and-drop handling in WebContentsView.
+// If we are dragging a virtual file out of the browser, we use a background
+// thread to do the drag-and-drop because we do not want to run nested
+// message loop in the UI thread. For all other cases, the drag-and-drop happens
+// in the UI thread.
+class CONTENT_EXPORT WebContentsDragWin
+ : NON_EXPORTED_BASE(public ui::DataObjectImpl::Observer),
+ public base::RefCountedThreadSafe<WebContentsDragWin> {
+ public:
+ WebContentsDragWin(gfx::NativeWindow source_window,
+ WebContents* web_contents,
+ WebDragDest* drag_dest,
+ const base::Callback<void()>& drag_end_callback);
+ virtual ~WebContentsDragWin();
+
+ // Called on UI thread.
+ void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset);
+ void CancelDrag();
+
+ // DataObjectImpl::Observer implementation.
+ // Called on drag-and-drop thread.
+ virtual void OnWaitForData();
+ virtual void OnDataObjectDisposed();
+
+ // Don't invoke OLE DoDragDrop during tests.
+ static void DisableDragDropForTesting();
+
+ private:
+ // Called on either UI thread or drag-and-drop thread.
+ void PrepareDragForDownload(const DropData& drop_data,
+ ui::OSExchangeData* data,
+ const GURL& page_url,
+ const std::string& page_encoding);
+ void PrepareDragForFileContents(const DropData& drop_data,
+ ui::OSExchangeData* data);
+ void PrepareDragForUrl(const DropData& drop_data,
+ ui::OSExchangeData* data);
+ // Returns false if the source window becomes invalid when the drag ends.
+ // This could happen when the window gets destroyed when the drag is still in
+ // progress. No further processing should be done beyond this return point
+ // because the instance has been destroyed.
+ bool DoDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask ops,
+ const GURL& page_url,
+ const std::string& page_encoding,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset);
+
+ // Called on drag-and-drop thread.
+ void StartBackgroundDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask ops,
+ const GURL& page_url,
+ const std::string& page_encoding,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset);
+ // Called on UI thread.
+ void EndDragging();
+ void CloseThread();
+
+ // For debug check only. Access only on drag-and-drop thread.
+ base::PlatformThreadId drag_drop_thread_id_;
+
+ // All the member variables below are accessed on UI thread.
+
+ gfx::NativeWindow source_window_;
+ WebContents* web_contents_;
+ WebDragDest* drag_dest_;
+
+ // |drag_source_| is our callback interface passed to the system when we
+ // want to initiate a drag and drop operation. We use it to tell if a
+ // drag operation is happening.
+ scoped_refptr<WebDragSource> drag_source_;
+
+ // The thread used by the drag-out download. This is because we want to avoid
+ // running nested message loop in main UI thread.
+ scoped_ptr<DragDropThread> drag_drop_thread_;
+
+ // The flag to guard that EndDragging is not called twice.
+ bool drag_ended_;
+
+ base::Callback<void()> drag_end_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsDragWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_DRAG_WIN_H_
diff --git a/chromium/content/browser/web_contents/web_contents_impl.cc b/chromium/content/browser/web_contents/web_contents_impl.cc
new file mode 100644
index 00000000000..157a17d6a8e
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_impl.cc
@@ -0,0 +1,3722 @@
+// 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 "content/browser/web_contents/web_contents_impl.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/time/time.h"
+#include "cc/base/switches.h"
+#include "content/browser/browser_plugin/browser_plugin_embedder.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/browser_plugin/browser_plugin_guest_manager.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/devtools/devtools_manager_impl.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/download/mhtml_generation_manager.h"
+#include "content/browser/download/save_package.h"
+#include "content/browser/gpu/gpu_data_manager_impl.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/host_zoom_map_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/power_save_blocker_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_view_guest.h"
+#include "content/browser/webui/generic_handler.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/browser/webui/web_ui_impl.h"
+#include "content/common/browser_plugin/browser_plugin_constants.h"
+#include "content/common/browser_plugin/browser_plugin_messages.h"
+#include "content/common/image_messages.h"
+#include "content/common/ssl_status_serialization.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/color_chooser.h"
+#include "content/public/browser/compositor_util.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/javascript_dialog_manager.h"
+#include "content/public/browser/load_from_memory_cache_details.h"
+#include "content/public/browser/load_notification_details.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/resource_request_details.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/page_zoom.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ui/base/layout.h"
+#include "ui/base/touch/touch_device.h"
+#include "ui/base/touch/touch_enabled.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/gl/gl_switches.h"
+#include "webkit/common/webpreferences.h"
+
+#if defined(OS_ANDROID)
+#include "content/browser/android/date_time_chooser_android.h"
+#include "content/public/browser/android/content_view_core.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/foundation_util.h"
+#include "ui/gl/io_surface_support_mac.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
+#endif
+
+// Cross-Site Navigations
+//
+// If a WebContentsImpl is told to navigate to a different web site (as
+// determined by SiteInstance), it will replace its current RenderViewHost with
+// a new RenderViewHost dedicated to the new SiteInstance. This works as
+// follows:
+//
+// - Navigate determines whether the destination is cross-site, and if so,
+// it creates a pending_render_view_host_.
+// - The pending RVH is "suspended," so that no navigation messages are sent to
+// its renderer until the onbeforeunload JavaScript handler has a chance to
+// run in the current RVH.
+// - The pending RVH tells CrossSiteRequestManager (a thread-safe singleton)
+// that it has a pending cross-site request. ResourceDispatcherHost will
+// check for this when the response arrives.
+// - The current RVH runs its onbeforeunload handler. If it returns false, we
+// cancel all the pending logic. Otherwise we allow the pending RVH to send
+// the navigation request to its renderer.
+// - ResourceDispatcherHost receives a ResourceRequest on the IO thread for the
+// main resource load on the pending RVH. It checks CrossSiteRequestManager
+// to see that it is a cross-site request, and installs a
+// CrossSiteResourceHandler.
+// - When RDH receives a response, the BufferedResourceHandler determines
+// whether it is a download. If so, it sends a message to the new renderer
+// causing it to cancel the request, and the download proceeds. For now, the
+// pending RVH remains until the next DidNavigate event for this
+// WebContentsImpl. This isn't ideal, but it doesn't affect any functionality.
+// - After RDH receives a response and determines that it is safe and not a
+// download, it pauses the response to first run the old page's onunload
+// handler. It does this by asynchronously calling the OnCrossSiteResponse
+// method of WebContentsImpl on the UI thread, which sends a SwapOut message
+// to the current RVH.
+// - Once the onunload handler is finished, a SwapOut_ACK message is sent to
+// the ResourceDispatcherHost, who unpauses the response. Data is then sent
+// to the pending RVH.
+// - The pending renderer sends a FrameNavigate message that invokes the
+// DidNavigate method. This replaces the current RVH with the
+// pending RVH.
+// - The previous renderer is kept swapped out in RenderViewHostManager in case
+// the user goes back. The process only stays live if another tab is using
+// it, but if so, the existing frame relationships will be maintained.
+
+namespace content {
+namespace {
+
+// Amount of time we wait between when a key event is received and the renderer
+// is queried for its state and pushed to the NavigationEntry.
+const int kQueryStateDelay = 5000;
+
+const int kSyncWaitDelay = 40;
+
+const char kDotGoogleDotCom[] = ".google.com";
+
+base::LazyInstance<std::vector<WebContents::CreatedCallback> >
+g_created_callbacks = LAZY_INSTANCE_INITIALIZER;
+
+static int StartDownload(content::RenderViewHost* rvh,
+ const GURL& url,
+ bool is_favicon,
+ uint32_t preferred_image_size,
+ uint32_t max_image_size) {
+ static int g_next_image_download_id = 0;
+ rvh->Send(new ImageMsg_DownloadImage(rvh->GetRoutingID(),
+ ++g_next_image_download_id,
+ url,
+ is_favicon,
+ preferred_image_size,
+ max_image_size));
+ return g_next_image_download_id;
+}
+
+ViewMsg_Navigate_Type::Value GetNavigationType(
+ BrowserContext* browser_context, const NavigationEntryImpl& entry,
+ NavigationController::ReloadType reload_type) {
+ switch (reload_type) {
+ case NavigationControllerImpl::RELOAD:
+ return ViewMsg_Navigate_Type::RELOAD;
+ case NavigationControllerImpl::RELOAD_IGNORING_CACHE:
+ return ViewMsg_Navigate_Type::RELOAD_IGNORING_CACHE;
+ case NavigationControllerImpl::RELOAD_ORIGINAL_REQUEST_URL:
+ return ViewMsg_Navigate_Type::RELOAD_ORIGINAL_REQUEST_URL;
+ case NavigationControllerImpl::NO_RELOAD:
+ break; // Fall through to rest of function.
+ }
+
+ // |RenderViewImpl::PopulateStateFromPendingNavigationParams| differentiates
+ // between |RESTORE_WITH_POST| and |RESTORE|.
+ if (entry.restore_type() ==
+ NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY) {
+ if (entry.GetHasPostData())
+ return ViewMsg_Navigate_Type::RESTORE_WITH_POST;
+ return ViewMsg_Navigate_Type::RESTORE;
+ }
+
+ return ViewMsg_Navigate_Type::NORMAL;
+}
+
+void MakeNavigateParams(const NavigationEntryImpl& entry,
+ const NavigationControllerImpl& controller,
+ WebContentsDelegate* delegate,
+ NavigationController::ReloadType reload_type,
+ ViewMsg_Navigate_Params* params) {
+ params->page_id = entry.GetPageID();
+ params->should_clear_history_list = entry.should_clear_history_list();
+ if (entry.should_clear_history_list()) {
+ // Set the history list related parameters to the same values a
+ // NavigationController would return before its first navigation. This will
+ // fully clear the RenderView's view of the session history.
+ params->pending_history_list_offset = -1;
+ params->current_history_list_offset = -1;
+ params->current_history_list_length = 0;
+ } else {
+ params->pending_history_list_offset = controller.GetIndexOfEntry(&entry);
+ params->current_history_list_offset =
+ controller.GetLastCommittedEntryIndex();
+ params->current_history_list_length = controller.GetEntryCount();
+ }
+ if (!entry.GetBaseURLForDataURL().is_empty()) {
+ params->base_url_for_data_url = entry.GetBaseURLForDataURL();
+ params->history_url_for_data_url = entry.GetVirtualURL();
+ }
+ params->referrer = entry.GetReferrer();
+ params->transition = entry.GetTransitionType();
+ params->page_state = entry.GetPageState();
+ params->navigation_type =
+ GetNavigationType(controller.GetBrowserContext(), entry, reload_type);
+ params->request_time = base::Time::Now();
+ params->extra_headers = entry.extra_headers();
+ params->transferred_request_child_id =
+ entry.transferred_global_request_id().child_id;
+ params->transferred_request_request_id =
+ entry.transferred_global_request_id().request_id;
+ params->is_overriding_user_agent = entry.GetIsOverridingUserAgent();
+ // Avoid downloading when in view-source mode.
+ params->allow_download = !entry.IsViewSourceMode();
+ params->is_post = entry.GetHasPostData();
+ if(entry.GetBrowserInitiatedPostData()) {
+ params->browser_initiated_post_data.assign(
+ entry.GetBrowserInitiatedPostData()->front(),
+ entry.GetBrowserInitiatedPostData()->front() +
+ entry.GetBrowserInitiatedPostData()->size());
+
+ }
+
+ if (reload_type == NavigationControllerImpl::RELOAD_ORIGINAL_REQUEST_URL &&
+ entry.GetOriginalRequestURL().is_valid() && !entry.GetHasPostData()) {
+ // We may have been redirected when navigating to the current URL.
+ // Use the URL the user originally intended to visit, if it's valid and if a
+ // POST wasn't involved; the latter case avoids issues with sending data to
+ // the wrong page.
+ params->url = entry.GetOriginalRequestURL();
+ } else {
+ params->url = entry.GetURL();
+ }
+
+ params->can_load_local_resources = entry.GetCanLoadLocalResources();
+ params->frame_to_navigate = entry.GetFrameToNavigate();
+
+ if (delegate)
+ delegate->AddNavigationHeaders(params->url, &params->extra_headers);
+}
+
+} // namespace
+
+WebContents* WebContents::Create(const WebContents::CreateParams& params) {
+ return WebContentsImpl::CreateWithOpener(
+ params, static_cast<WebContentsImpl*>(params.opener));
+}
+
+WebContents* WebContents::CreateWithSessionStorage(
+ const WebContents::CreateParams& params,
+ const SessionStorageNamespaceMap& session_storage_namespace_map) {
+ WebContentsImpl* new_contents = new WebContentsImpl(
+ params.browser_context, NULL);
+
+ for (SessionStorageNamespaceMap::const_iterator it =
+ session_storage_namespace_map.begin();
+ it != session_storage_namespace_map.end();
+ ++it) {
+ new_contents->GetController()
+ .SetSessionStorageNamespace(it->first, it->second.get());
+ }
+
+ new_contents->Init(params);
+ return new_contents;
+}
+
+void WebContents::AddCreatedCallback(const CreatedCallback& callback) {
+ g_created_callbacks.Get().push_back(callback);
+}
+
+void WebContents::RemoveCreatedCallback(const CreatedCallback& callback) {
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); ++i) {
+ if (g_created_callbacks.Get().at(i).Equals(callback)) {
+ g_created_callbacks.Get().erase(g_created_callbacks.Get().begin() + i);
+ return;
+ }
+ }
+}
+
+WebContents* WebContents::FromRenderViewHost(const RenderViewHost* rvh) {
+ return rvh->GetDelegate()->GetAsWebContents();
+}
+
+// WebContentsImpl::DestructionObserver ----------------------------------------
+
+class WebContentsImpl::DestructionObserver : public WebContentsObserver {
+ public:
+ DestructionObserver(WebContentsImpl* owner, WebContents* watched_contents)
+ : WebContentsObserver(watched_contents),
+ owner_(owner) {
+ }
+
+ // WebContentsObserver:
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE {
+ owner_->OnWebContentsDestroyed(static_cast<WebContentsImpl*>(web_contents));
+ }
+
+ private:
+ WebContentsImpl* owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DestructionObserver);
+};
+
+// WebContentsImpl -------------------------------------------------------------
+
+WebContentsImpl::WebContentsImpl(
+ BrowserContext* browser_context,
+ WebContentsImpl* opener)
+ : delegate_(NULL),
+ controller_(this, browser_context),
+ render_view_host_delegate_view_(NULL),
+ opener_(opener),
+#if defined(OS_WIN) && defined(USE_AURA)
+ accessible_parent_(NULL),
+#endif
+ render_manager_(this, this, this),
+ is_loading_(false),
+ crashed_status_(base::TERMINATION_STATUS_STILL_RUNNING),
+ crashed_error_code_(0),
+ waiting_for_response_(false),
+ load_state_(net::LOAD_STATE_IDLE, string16()),
+ upload_size_(0),
+ upload_position_(0),
+ displayed_insecure_content_(false),
+ capturer_count_(0),
+ should_normally_be_visible_(true),
+ is_being_destroyed_(false),
+ notify_disconnection_(false),
+ dialog_manager_(NULL),
+ is_showing_before_unload_dialog_(false),
+ closed_by_user_gesture_(false),
+ minimum_zoom_percent_(static_cast<int>(kMinimumZoomFactor * 100)),
+ maximum_zoom_percent_(static_cast<int>(kMaximumZoomFactor * 100)),
+ temporary_zoom_settings_(false),
+ color_chooser_identifier_(0),
+ message_source_(NULL),
+ fullscreen_widget_routing_id_(MSG_ROUTING_NONE) {
+ for (size_t i = 0; i < g_created_callbacks.Get().size(); i++)
+ g_created_callbacks.Get().at(i).Run(this);
+}
+
+WebContentsImpl::~WebContentsImpl() {
+ is_being_destroyed_ = true;
+
+ ClearAllPowerSaveBlockers();
+
+ for (std::set<RenderWidgetHostImpl*>::iterator iter =
+ created_widgets_.begin(); iter != created_widgets_.end(); ++iter) {
+ (*iter)->DetachDelegate();
+ }
+ created_widgets_.clear();
+
+ // Clear out any JavaScript state.
+ if (dialog_manager_)
+ dialog_manager_->WebContentsDestroyed(this);
+
+ if (color_chooser_)
+ color_chooser_->End();
+
+ NotifyDisconnected();
+
+ // Notify any observer that have a reference on this WebContents.
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_DESTROYED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+
+ // TODO(brettw) this should be moved to the view.
+#if defined(OS_WIN) && !defined(USE_AURA)
+ // If we still have a window handle, destroy it. GetNativeView can return
+ // NULL if this contents was part of a window that closed.
+ if (view_->GetNativeView()) {
+ RenderViewHost* host = GetRenderViewHost();
+ if (host && host->GetView())
+ RenderWidgetHostViewPort::FromRWHV(host->GetView())->WillWmDestroy();
+ }
+#endif
+
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ WebContentsImplDestroyed());
+
+ SetDelegate(NULL);
+
+ STLDeleteContainerPairSecondPointers(destruction_observers_.begin(),
+ destruction_observers_.end());
+}
+
+WebContentsImpl* WebContentsImpl::CreateWithOpener(
+ const WebContents::CreateParams& params,
+ WebContentsImpl* opener) {
+ TRACE_EVENT0("browser", "WebContentsImpl::CreateWithOpener");
+ WebContentsImpl* new_contents = new WebContentsImpl(
+ params.browser_context, opener);
+
+ new_contents->Init(params);
+ return new_contents;
+}
+
+// static
+BrowserPluginGuest* WebContentsImpl::CreateGuest(
+ BrowserContext* browser_context,
+ SiteInstance* site_instance,
+ int guest_instance_id,
+ scoped_ptr<base::DictionaryValue> extra_params) {
+ WebContentsImpl* new_contents = new WebContentsImpl(browser_context, NULL);
+
+ // This makes |new_contents| act as a guest.
+ // For more info, see comment above class BrowserPluginGuest.
+ BrowserPluginGuest::Create(
+ guest_instance_id, new_contents, extra_params.Pass());
+
+ WebContents::CreateParams create_params(browser_context, site_instance);
+ new_contents->Init(create_params);
+
+ // We are instantiating a WebContents for browser plugin. Set its subframe bit
+ // to true.
+ static_cast<RenderViewHostImpl*>(
+ new_contents->GetRenderViewHost())->set_is_subframe(true);
+
+ return new_contents->browser_plugin_guest_.get();
+}
+
+WebPreferences WebContentsImpl::GetWebkitPrefs(RenderViewHost* rvh,
+ const GURL& url) {
+ TRACE_EVENT0("browser", "WebContentsImpl::GetWebkitPrefs");
+ WebPreferences prefs;
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+
+ prefs.javascript_enabled =
+ !command_line.HasSwitch(switches::kDisableJavaScript);
+ prefs.web_security_enabled =
+ !command_line.HasSwitch(switches::kDisableWebSecurity);
+ prefs.plugins_enabled =
+ !command_line.HasSwitch(switches::kDisablePlugins);
+ prefs.java_enabled =
+ !command_line.HasSwitch(switches::kDisableJava);
+
+ prefs.remote_fonts_enabled =
+ !command_line.HasSwitch(switches::kDisableRemoteFonts);
+ prefs.xss_auditor_enabled =
+ !command_line.HasSwitch(switches::kDisableXSSAuditor);
+ prefs.application_cache_enabled =
+ !command_line.HasSwitch(switches::kDisableApplicationCache);
+
+ prefs.local_storage_enabled =
+ !command_line.HasSwitch(switches::kDisableLocalStorage);
+ prefs.databases_enabled =
+ !command_line.HasSwitch(switches::kDisableDatabases);
+ prefs.webaudio_enabled =
+ !command_line.HasSwitch(switches::kDisableWebAudio);
+
+ prefs.experimental_webgl_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisable3DAPIs) &&
+ !command_line.HasSwitch(switches::kDisableExperimentalWebGL);
+
+ prefs.flash_3d_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisableFlash3d);
+ prefs.flash_stage3d_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisableFlashStage3d);
+ prefs.flash_stage3d_baseline_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisableFlashStage3d);
+
+ prefs.gl_multisampling_enabled =
+ !command_line.HasSwitch(switches::kDisableGLMultisampling);
+ prefs.privileged_webgl_extensions_enabled =
+ command_line.HasSwitch(switches::kEnablePrivilegedWebGLExtensions);
+ prefs.site_specific_quirks_enabled =
+ !command_line.HasSwitch(switches::kDisableSiteSpecificQuirks);
+ prefs.allow_file_access_from_file_urls =
+ command_line.HasSwitch(switches::kAllowFileAccessFromFiles);
+
+ prefs.accelerated_compositing_for_overflow_scroll_enabled = false;
+ if (command_line.HasSwitch(switches::kEnableAcceleratedOverflowScroll))
+ prefs.accelerated_compositing_for_overflow_scroll_enabled = true;
+ if (command_line.HasSwitch(switches::kDisableAcceleratedOverflowScroll))
+ prefs.accelerated_compositing_for_overflow_scroll_enabled = false;
+
+ prefs.accelerated_compositing_for_scrollable_frames_enabled =
+ command_line.HasSwitch(switches::kEnableAcceleratedScrollableFrames);
+ prefs.composited_scrolling_for_frames_enabled =
+ command_line.HasSwitch(switches::kEnableCompositedScrollingForFrames);
+ prefs.show_paint_rects =
+ command_line.HasSwitch(switches::kShowPaintRects);
+ prefs.accelerated_compositing_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisableAcceleratedCompositing);
+ prefs.force_compositing_mode =
+ content::IsForceCompositingModeEnabled() &&
+ !command_line.HasSwitch(switches::kDisableForceCompositingMode);
+ prefs.accelerated_2d_canvas_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ !command_line.HasSwitch(switches::kDisableAccelerated2dCanvas);
+ prefs.antialiased_2d_canvas_disabled =
+ command_line.HasSwitch(switches::kDisable2dCanvasAntialiasing);
+ prefs.accelerated_filters_enabled =
+ GpuProcessHost::gpu_enabled() &&
+ command_line.HasSwitch(switches::kEnableAcceleratedFilters);
+ prefs.accelerated_compositing_for_3d_transforms_enabled =
+ prefs.accelerated_compositing_for_animation_enabled =
+ !command_line.HasSwitch(switches::kDisableAcceleratedLayers);
+ prefs.accelerated_compositing_for_plugins_enabled =
+ !command_line.HasSwitch(switches::kDisableAcceleratedPlugins);
+ prefs.accelerated_compositing_for_video_enabled =
+ !command_line.HasSwitch(switches::kDisableAcceleratedVideo);
+ prefs.fullscreen_enabled =
+ !command_line.HasSwitch(switches::kDisableFullScreen);
+ prefs.css_sticky_position_enabled =
+ command_line.HasSwitch(switches::kEnableExperimentalWebPlatformFeatures);
+ prefs.css_shaders_enabled =
+ command_line.HasSwitch(switches::kEnableCssShaders);
+ prefs.lazy_layout_enabled =
+ command_line.HasSwitch(switches::kEnableExperimentalWebPlatformFeatures);
+ prefs.region_based_columns_enabled =
+ command_line.HasSwitch(switches::kEnableRegionBasedColumns);
+ prefs.threaded_html_parser =
+ !command_line.HasSwitch(switches::kDisableThreadedHTMLParser);
+ prefs.experimental_websocket_enabled =
+ command_line.HasSwitch(switches::kEnableExperimentalWebSocket);
+ if (command_line.HasSwitch(cc::switches::kEnablePinchVirtualViewport)) {
+ prefs.pinch_virtual_viewport_enabled = true;
+ prefs.pinch_overlay_scrollbar_thickness = 10;
+ }
+
+#if defined(OS_ANDROID)
+ prefs.user_gesture_required_for_media_playback = !command_line.HasSwitch(
+ switches::kDisableGestureRequirementForMediaPlayback);
+#endif
+
+ prefs.touch_enabled = ui::AreTouchEventsEnabled();
+ prefs.device_supports_touch = prefs.touch_enabled &&
+ ui::IsTouchDevicePresent();
+#if defined(OS_ANDROID)
+ prefs.device_supports_mouse = false;
+#endif
+
+ prefs.touch_adjustment_enabled =
+ !command_line.HasSwitch(switches::kDisableTouchAdjustment);
+
+#if defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ bool default_enable_scroll_animator = true;
+#else
+ bool default_enable_scroll_animator = false;
+#endif
+ prefs.enable_scroll_animator = default_enable_scroll_animator;
+ if (command_line.HasSwitch(switches::kEnableSmoothScrolling))
+ prefs.enable_scroll_animator = true;
+ if (command_line.HasSwitch(switches::kDisableSmoothScrolling))
+ prefs.enable_scroll_animator = false;
+
+ prefs.visual_word_movement_enabled =
+ command_line.HasSwitch(switches::kEnableVisualWordMovement);
+
+ // Certain GPU features might have been blacklisted.
+ GpuDataManagerImpl::GetInstance()->UpdateRendererWebPrefs(&prefs);
+
+ if (ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
+ rvh->GetProcess()->GetID())) {
+ prefs.loads_images_automatically = true;
+ prefs.javascript_enabled = true;
+ }
+
+ prefs.is_online = !net::NetworkChangeNotifier::IsOffline();
+
+#if !defined(USE_AURA)
+ // Force accelerated compositing and 2d canvas off for chrome: and about:
+ // pages (unless it's specifically allowed).
+ if ((url.SchemeIs(chrome::kChromeUIScheme) ||
+ (url.SchemeIs(chrome::kAboutScheme) &&
+ url.spec() != kAboutBlankURL)) &&
+ !command_line.HasSwitch(switches::kAllowWebUICompositing)) {
+ prefs.accelerated_compositing_enabled = false;
+ prefs.accelerated_2d_canvas_enabled = false;
+ }
+#endif
+
+ prefs.fixed_position_creates_stacking_context = !command_line.HasSwitch(
+ switches::kDisableFixedPositionCreatesStackingContext);
+
+#if defined(OS_CHROMEOS)
+ prefs.gesture_tap_highlight_enabled = !command_line.HasSwitch(
+ switches::kDisableGestureTapHighlight);
+#else
+ prefs.gesture_tap_highlight_enabled = command_line.HasSwitch(
+ switches::kEnableGestureTapHighlight);
+#endif
+
+ prefs.number_of_cpu_cores = base::SysInfo::NumberOfProcessors();
+
+ prefs.viewport_enabled = command_line.HasSwitch(switches::kEnableViewport);
+
+ prefs.deferred_image_decoding_enabled =
+ command_line.HasSwitch(switches::kEnableDeferredImageDecoding) ||
+ cc::switches::IsImplSidePaintingEnabled();
+
+ prefs.spatial_navigation_enabled = command_line.HasSwitch(
+ switches::kEnableSpatialNavigation);
+
+ GetContentClient()->browser()->OverrideWebkitPrefs(rvh, url, &prefs);
+
+ // Disable compositing in guests until we have compositing path implemented
+ // for guests.
+ bool guest_compositing_enabled = !command_line.HasSwitch(
+ switches::kDisableBrowserPluginCompositing);
+ if (rvh->GetProcess()->IsGuest() && !guest_compositing_enabled) {
+ prefs.force_compositing_mode = false;
+ prefs.accelerated_compositing_enabled = false;
+ }
+
+ return prefs;
+}
+
+RenderViewHostManager* WebContentsImpl::GetRenderManagerForTesting() {
+ return &render_manager_;
+}
+
+bool WebContentsImpl::OnMessageReceived(RenderViewHost* render_view_host,
+ const IPC::Message& message) {
+ if (GetWebUI() &&
+ static_cast<WebUIImpl*>(GetWebUI())->OnMessageReceived(message)) {
+ return true;
+ }
+
+ ObserverListBase<WebContentsObserver>::Iterator it(observers_);
+ WebContentsObserver* observer;
+ while ((observer = it.GetNext()) != NULL)
+ if (observer->OnMessageReceived(message))
+ return true;
+
+ // Message handlers should be aware of which RenderViewHost sent the
+ // message, which is temporarily stored in message_source_.
+ message_source_ = render_view_host;
+ bool handled = true;
+ bool message_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(WebContentsImpl, message, message_is_ok)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidLoadResourceFromMemoryCache,
+ OnDidLoadResourceFromMemoryCache)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidDisplayInsecureContent,
+ OnDidDisplayInsecureContent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidRunInsecureContent,
+ OnDidRunInsecureContent)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentLoadedInFrame,
+ OnDocumentLoadedInFrame)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidFinishLoad, OnDidFinishLoad)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidFailLoadWithError,
+ OnDidFailLoadWithError)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GoToEntryAtOffset, OnGoToEntryAtOffset)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateZoomLimits, OnUpdateZoomLimits)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_EnumerateDirectory, OnEnumerateDirectory)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_JSOutOfMemory, OnJSOutOfMemory)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RegisterProtocolHandler,
+ OnRegisterProtocolHandler)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_Find_Reply, OnFindReply)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DidProgrammaticallyScroll,
+ OnDidProgrammaticallyScroll)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CrashedPlugin, OnCrashedPlugin)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_AppCacheAccessed, OnAppCacheAccessed)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_OpenColorChooser, OnOpenColorChooser)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_EndColorChooser, OnEndColorChooser)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetSelectedColorInColorChooser,
+ OnSetSelectedColorInColorChooser)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PepperPluginHung, OnPepperPluginHung)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_WebUISend, OnWebUISend)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_RequestPpapiBrokerPermission,
+ OnRequestPpapiBrokerPermission)
+ IPC_MESSAGE_HANDLER_GENERIC(BrowserPluginHostMsg_AllocateInstanceID,
+ OnBrowserPluginMessage(message))
+ IPC_MESSAGE_HANDLER_GENERIC(BrowserPluginHostMsg_Attach,
+ OnBrowserPluginMessage(message))
+ IPC_MESSAGE_HANDLER(ImageHostMsg_DidDownloadImage, OnDidDownloadImage)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFaviconURL, OnUpdateFaviconURL)
+#if defined(OS_ANDROID)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FindMatchRects_Reply,
+ OnFindMatchRectsReply)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_OpenDateTimeDialog,
+ OnOpenDateTimeDialog)
+#endif
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FrameAttached, OnFrameAttached)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FrameDetached, OnFrameDetached)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_MediaNotification, OnMediaNotification)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+ message_source_ = NULL;
+
+ if (!message_is_ok) {
+ RecordAction(UserMetricsAction("BadMessageTerminate_RVD"));
+ GetRenderProcessHost()->ReceivedBadMessage();
+ }
+
+ return handled;
+}
+
+void WebContentsImpl::RunFileChooser(
+ RenderViewHost* render_view_host,
+ const FileChooserParams& params) {
+ if (delegate_)
+ delegate_->RunFileChooser(this, params);
+}
+
+NavigationControllerImpl& WebContentsImpl::GetController() {
+ return controller_;
+}
+
+const NavigationControllerImpl& WebContentsImpl::GetController() const {
+ return controller_;
+}
+
+BrowserContext* WebContentsImpl::GetBrowserContext() const {
+ return controller_.GetBrowserContext();
+}
+
+const GURL& WebContentsImpl::GetURL() const {
+ // We may not have a navigation entry yet.
+ NavigationEntry* entry = controller_.GetVisibleEntry();
+ return entry ? entry->GetVirtualURL() : GURL::EmptyGURL();
+}
+
+const GURL& WebContentsImpl::GetVisibleURL() const {
+ // We may not have a navigation entry yet.
+ NavigationEntry* entry = controller_.GetVisibleEntry();
+ return entry ? entry->GetVirtualURL() : GURL::EmptyGURL();
+}
+
+const GURL& WebContentsImpl::GetLastCommittedURL() const {
+ // We may not have a navigation entry yet.
+ NavigationEntry* entry = controller_.GetLastCommittedEntry();
+ return entry ? entry->GetVirtualURL() : GURL::EmptyGURL();
+}
+
+WebContentsDelegate* WebContentsImpl::GetDelegate() {
+ return delegate_;
+}
+
+void WebContentsImpl::SetDelegate(WebContentsDelegate* delegate) {
+ // TODO(cbentzel): remove this debugging code?
+ if (delegate == delegate_)
+ return;
+ if (delegate_)
+ delegate_->Detach(this);
+ delegate_ = delegate;
+ if (delegate_) {
+ delegate_->Attach(this);
+ // Ensure the visible RVH reflects the new delegate's preferences.
+ if (view_)
+ view_->SetOverscrollControllerEnabled(delegate->CanOverscrollContent());
+ }
+}
+
+RenderProcessHost* WebContentsImpl::GetRenderProcessHost() const {
+ RenderViewHostImpl* host = render_manager_.current_host();
+ return host ? host->GetProcess() : NULL;
+}
+
+RenderViewHost* WebContentsImpl::GetRenderViewHost() const {
+ return render_manager_.current_host();
+}
+
+void WebContentsImpl::GetRenderViewHostAtPosition(
+ int x,
+ int y,
+ const base::Callback<void(RenderViewHost*, int, int)>& callback) {
+ BrowserPluginEmbedder* embedder = GetBrowserPluginEmbedder();
+ if (embedder)
+ embedder->GetRenderViewHostAtPosition(x, y, callback);
+ else
+ callback.Run(GetRenderViewHost(), x, y);
+}
+
+WebContents* WebContentsImpl::GetEmbedderWebContents() const {
+ BrowserPluginGuest* guest = GetBrowserPluginGuest();
+ if (guest)
+ return guest->embedder_web_contents();
+ return NULL;
+}
+
+int WebContentsImpl::GetEmbeddedInstanceID() const {
+ BrowserPluginGuest* guest = GetBrowserPluginGuest();
+ if (guest)
+ return guest->instance_id();
+ return 0;
+}
+
+int WebContentsImpl::GetRoutingID() const {
+ if (!GetRenderViewHost())
+ return MSG_ROUTING_NONE;
+
+ return GetRenderViewHost()->GetRoutingID();
+}
+
+int WebContentsImpl::GetFullscreenWidgetRoutingID() const {
+ return fullscreen_widget_routing_id_;
+}
+
+RenderWidgetHostView* WebContentsImpl::GetRenderWidgetHostView() const {
+ return render_manager_.GetRenderWidgetHostView();
+}
+
+RenderWidgetHostViewPort* WebContentsImpl::GetRenderWidgetHostViewPort() const {
+ BrowserPluginGuest* guest = GetBrowserPluginGuest();
+ if (guest && guest->embedder_web_contents()) {
+ return guest->embedder_web_contents()->GetRenderWidgetHostViewPort();
+ }
+ return RenderWidgetHostViewPort::FromRWHV(GetRenderWidgetHostView());
+}
+
+WebContentsView* WebContentsImpl::GetView() const {
+ return view_.get();
+}
+
+WebUI* WebContentsImpl::CreateWebUI(const GURL& url) {
+ WebUIImpl* web_ui = new WebUIImpl(this);
+ WebUIController* controller = WebUIControllerFactoryRegistry::GetInstance()->
+ CreateWebUIControllerForURL(web_ui, url);
+ if (controller) {
+ web_ui->AddMessageHandler(new GenericHandler());
+ web_ui->SetController(controller);
+ return web_ui;
+ }
+
+ delete web_ui;
+ return NULL;
+}
+
+WebUI* WebContentsImpl::GetWebUI() const {
+ return render_manager_.web_ui() ? render_manager_.web_ui()
+ : render_manager_.pending_web_ui();
+}
+
+WebUI* WebContentsImpl::GetCommittedWebUI() const {
+ return render_manager_.web_ui();
+}
+
+void WebContentsImpl::SetUserAgentOverride(const std::string& override) {
+ if (GetUserAgentOverride() == override)
+ return;
+
+ renderer_preferences_.user_agent_override = override;
+
+ // Send the new override string to the renderer.
+ RenderViewHost* host = GetRenderViewHost();
+ if (host)
+ host->SyncRendererPrefs();
+
+ // Reload the page if a load is currently in progress to avoid having
+ // different parts of the page loaded using different user agents.
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (is_loading_ && entry != NULL && entry->GetIsOverridingUserAgent())
+ controller_.ReloadIgnoringCache(true);
+
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ UserAgentOverrideSet(override));
+}
+
+const std::string& WebContentsImpl::GetUserAgentOverride() const {
+ return renderer_preferences_.user_agent_override;
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+void WebContentsImpl::SetParentNativeViewAccessible(
+gfx::NativeViewAccessible accessible_parent) {
+ accessible_parent_ = accessible_parent;
+ if (GetRenderViewHost())
+ GetRenderViewHostImpl()->SetParentNativeViewAccessible(accessible_parent);
+}
+#endif
+
+const string16& WebContentsImpl::GetTitle() const {
+ // Transient entries take precedence. They are used for interstitial pages
+ // that are shown on top of existing pages.
+ NavigationEntry* entry = controller_.GetTransientEntry();
+ std::string accept_languages =
+ GetContentClient()->browser()->GetAcceptLangs(
+ GetBrowserContext());
+ if (entry) {
+ return entry->GetTitleForDisplay(accept_languages);
+ }
+ WebUI* our_web_ui = render_manager_.pending_web_ui() ?
+ render_manager_.pending_web_ui() : render_manager_.web_ui();
+ if (our_web_ui) {
+ // Don't override the title in view source mode.
+ entry = controller_.GetVisibleEntry();
+ if (!(entry && entry->IsViewSourceMode())) {
+ // Give the Web UI the chance to override our title.
+ const string16& title = our_web_ui->GetOverriddenTitle();
+ if (!title.empty())
+ return title;
+ }
+ }
+
+ // We use the title for the last committed entry rather than a pending
+ // navigation entry. For example, when the user types in a URL, we want to
+ // keep the old page's title until the new load has committed and we get a new
+ // title.
+ entry = controller_.GetLastCommittedEntry();
+
+ // We make an exception for initial navigations, because we can have a
+ // committed entry for an initial navigation when doing a history navigation
+ // in a new tab, such as Ctrl+Back.
+ if (entry && controller_.IsInitialNavigation())
+ entry = controller_.GetVisibleEntry();
+
+ if (entry) {
+ return entry->GetTitleForDisplay(accept_languages);
+ }
+
+ // |page_title_when_no_navigation_entry_| is finally used
+ // if no title cannot be retrieved.
+ return page_title_when_no_navigation_entry_;
+}
+
+int32 WebContentsImpl::GetMaxPageID() {
+ return GetMaxPageIDForSiteInstance(GetSiteInstance());
+}
+
+int32 WebContentsImpl::GetMaxPageIDForSiteInstance(
+ SiteInstance* site_instance) {
+ if (max_page_ids_.find(site_instance->GetId()) == max_page_ids_.end())
+ max_page_ids_[site_instance->GetId()] = -1;
+
+ return max_page_ids_[site_instance->GetId()];
+}
+
+void WebContentsImpl::UpdateMaxPageID(int32 page_id) {
+ UpdateMaxPageIDForSiteInstance(GetSiteInstance(), page_id);
+}
+
+void WebContentsImpl::UpdateMaxPageIDForSiteInstance(
+ SiteInstance* site_instance, int32 page_id) {
+ if (GetMaxPageIDForSiteInstance(site_instance) < page_id)
+ max_page_ids_[site_instance->GetId()] = page_id;
+}
+
+void WebContentsImpl::CopyMaxPageIDsFrom(WebContentsImpl* web_contents) {
+ max_page_ids_ = web_contents->max_page_ids_;
+}
+
+SiteInstance* WebContentsImpl::GetSiteInstance() const {
+ return render_manager_.current_host()->GetSiteInstance();
+}
+
+SiteInstance* WebContentsImpl::GetPendingSiteInstance() const {
+ RenderViewHost* dest_rvh = render_manager_.pending_render_view_host() ?
+ render_manager_.pending_render_view_host() :
+ render_manager_.current_host();
+ return dest_rvh->GetSiteInstance();
+}
+
+bool WebContentsImpl::IsLoading() const {
+ return is_loading_;
+}
+
+bool WebContentsImpl::IsWaitingForResponse() const {
+ return waiting_for_response_;
+}
+
+const net::LoadStateWithParam& WebContentsImpl::GetLoadState() const {
+ return load_state_;
+}
+
+const string16& WebContentsImpl::GetLoadStateHost() const {
+ return load_state_host_;
+}
+
+uint64 WebContentsImpl::GetUploadSize() const {
+ return upload_size_;
+}
+
+uint64 WebContentsImpl::GetUploadPosition() const {
+ return upload_position_;
+}
+
+std::set<GURL> WebContentsImpl::GetSitesInTab() const {
+ BrowserContext* browser_context = GetBrowserContext();
+ std::set<GURL> sites;
+ if (!frame_tree_root_.get())
+ return sites;
+
+ // Iterates over the FrameTreeNodes to find each unique site URL that is
+ // currently committed.
+ FrameTreeNode* node = NULL;
+ std::queue<FrameTreeNode*> queue;
+ queue.push(frame_tree_root_.get());
+
+ while (!queue.empty()) {
+ node = queue.front();
+ queue.pop();
+ sites.insert(SiteInstance::GetSiteForURL(browser_context,
+ node->current_url()));
+
+ for (size_t i = 0; i < node->child_count(); ++i)
+ queue.push(node->child_at(i));
+ }
+
+ return sites;
+}
+
+const std::string& WebContentsImpl::GetEncoding() const {
+ return encoding_;
+}
+
+bool WebContentsImpl::DisplayedInsecureContent() const {
+ return displayed_insecure_content_;
+}
+
+void WebContentsImpl::IncrementCapturerCount() {
+ DCHECK(!is_being_destroyed_);
+ ++capturer_count_;
+ DVLOG(1) << "There are now " << capturer_count_
+ << " capturing(s) of WebContentsImpl@" << this;
+}
+
+void WebContentsImpl::DecrementCapturerCount() {
+ --capturer_count_;
+ DVLOG(1) << "There are now " << capturer_count_
+ << " capturing(s) of WebContentsImpl@" << this;
+ DCHECK_LE(0, capturer_count_);
+
+ if (is_being_destroyed_)
+ return;
+
+ // While capturer_count_ was greater than zero, the WasHidden() calls to RWHV
+ // were being prevented. If there are no more capturers, make the call now.
+ if (capturer_count_ == 0 && !should_normally_be_visible_) {
+ DVLOG(1) << "Executing delayed WasHidden().";
+ WasHidden();
+ }
+}
+
+int WebContentsImpl::GetCapturerCount() const {
+ return capturer_count_;
+}
+
+bool WebContentsImpl::IsCrashed() const {
+ return (crashed_status_ == base::TERMINATION_STATUS_PROCESS_CRASHED ||
+ crashed_status_ == base::TERMINATION_STATUS_ABNORMAL_TERMINATION ||
+ crashed_status_ == base::TERMINATION_STATUS_PROCESS_WAS_KILLED);
+}
+
+void WebContentsImpl::SetIsCrashed(base::TerminationStatus status,
+ int error_code) {
+ if (status == crashed_status_)
+ return;
+
+ crashed_status_ = status;
+ crashed_error_code_ = error_code;
+ NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
+}
+
+base::TerminationStatus WebContentsImpl::GetCrashedStatus() const {
+ return crashed_status_;
+}
+
+bool WebContentsImpl::IsBeingDestroyed() const {
+ return is_being_destroyed_;
+}
+
+void WebContentsImpl::NotifyNavigationStateChanged(unsigned changed_flags) {
+ if (delegate_)
+ delegate_->NavigationStateChanged(this, changed_flags);
+}
+
+base::TimeTicks WebContentsImpl::GetLastSelectedTime() const {
+ return last_selected_time_;
+}
+
+void WebContentsImpl::WasShown() {
+ controller_.SetActive(true);
+ RenderWidgetHostViewPort* rwhv =
+ RenderWidgetHostViewPort::FromRWHV(GetRenderWidgetHostView());
+ if (rwhv) {
+ rwhv->WasShown();
+#if defined(OS_MACOSX)
+ rwhv->SetActive(true);
+#endif
+ }
+
+ last_selected_time_ = base::TimeTicks::Now();
+
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, WasShown());
+
+ // The resize rect might have changed while this was inactive -- send the new
+ // one to make sure it's up to date.
+ RenderViewHostImpl* rvh =
+ static_cast<RenderViewHostImpl*>(GetRenderViewHost());
+ if (rvh) {
+ rvh->ResizeRectChanged(GetRootWindowResizerRect());
+ }
+
+ should_normally_be_visible_ = true;
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
+ Source<WebContents>(this),
+ Details<const bool>(&should_normally_be_visible_));
+}
+
+void WebContentsImpl::WasHidden() {
+ // If there are entities capturing screenshots or video (e.g., mirroring),
+ // don't activate the "disable rendering" optimization.
+ if (capturer_count_ == 0) {
+ // |GetRenderViewHost()| can be NULL if the user middle clicks a link to
+ // open a tab in the background, then closes the tab before selecting it.
+ // This is because closing the tab calls WebContentsImpl::Destroy(), which
+ // removes the |GetRenderViewHost()|; then when we actually destroy the
+ // window, OnWindowPosChanged() notices and calls WasHidden() (which
+ // calls us).
+ RenderWidgetHostViewPort* rwhv =
+ RenderWidgetHostViewPort::FromRWHV(GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->WasHidden();
+ }
+
+ should_normally_be_visible_ = false;
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
+ Source<WebContents>(this),
+ Details<const bool>(&should_normally_be_visible_));
+}
+
+bool WebContentsImpl::NeedToFireBeforeUnload() {
+ // TODO(creis): Should we fire even for interstitial pages?
+ return WillNotifyDisconnection() &&
+ !ShowingInterstitialPage() &&
+ !static_cast<RenderViewHostImpl*>(
+ GetRenderViewHost())->SuddenTerminationAllowed();
+}
+
+void WebContentsImpl::Stop() {
+ render_manager_.Stop();
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, StopNavigation());
+}
+
+WebContents* WebContentsImpl::Clone() {
+ // We use our current SiteInstance since the cloned entry will use it anyway.
+ // We pass our own opener so that the cloned page can access it if it was
+ // before.
+ CreateParams create_params(GetBrowserContext(), GetSiteInstance());
+ create_params.initial_size = view_->GetContainerSize();
+ WebContentsImpl* tc = CreateWithOpener(create_params, opener_);
+ tc->GetController().CopyStateFrom(controller_);
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ DidCloneToNewWebContents(this, tc));
+ return tc;
+}
+
+void WebContentsImpl::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
+ RenderWidgetHost* host = Source<RenderWidgetHost>(source).ptr();
+ for (PendingWidgetViews::iterator i = pending_widget_views_.begin();
+ i != pending_widget_views_.end(); ++i) {
+ if (host->GetView() == i->second) {
+ pending_widget_views_.erase(i);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void WebContentsImpl::Init(const WebContents::CreateParams& params) {
+ render_manager_.Init(
+ params.browser_context, params.site_instance, params.routing_id,
+ params.main_frame_routing_id);
+
+ view_.reset(GetContentClient()->browser()->
+ OverrideCreateWebContentsView(this, &render_view_host_delegate_view_));
+ if (view_) {
+ CHECK(render_view_host_delegate_view_);
+ } else {
+ WebContentsViewDelegate* delegate =
+ GetContentClient()->browser()->GetWebContentsViewDelegate(this);
+
+ if (browser_plugin_guest_) {
+ scoped_ptr<WebContentsViewPort> platform_view(CreateWebContentsView(
+ this, delegate, &render_view_host_delegate_view_));
+
+ WebContentsViewGuest* rv = new WebContentsViewGuest(
+ this, browser_plugin_guest_.get(), platform_view.Pass(),
+ render_view_host_delegate_view_);
+ render_view_host_delegate_view_ = rv;
+ view_.reset(rv);
+ } else {
+ // Regular WebContentsView.
+ view_.reset(CreateWebContentsView(
+ this, delegate, &render_view_host_delegate_view_));
+ }
+ CHECK(render_view_host_delegate_view_);
+ }
+ CHECK(view_.get());
+
+ gfx::Size initial_size = params.initial_size;
+ view_->CreateView(initial_size, params.context);
+
+ // Listen for whether our opener gets destroyed.
+ if (opener_)
+ AddDestructionObserver(opener_);
+
+ registrar_.Add(this,
+ NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ NotificationService::AllBrowserContextsAndSources());
+#if defined(OS_ANDROID)
+ java_bridge_dispatcher_host_manager_.reset(
+ new JavaBridgeDispatcherHostManager(this));
+#endif
+
+#if defined(OS_ANDROID)
+ date_time_chooser_.reset(new DateTimeChooserAndroid());
+#endif
+}
+
+void WebContentsImpl::OnWebContentsDestroyed(WebContentsImpl* web_contents) {
+ RemoveDestructionObserver(web_contents);
+
+ // Clear the opener if it has been closed.
+ if (web_contents == opener_) {
+ opener_ = NULL;
+ return;
+ }
+ // Clear a pending contents that has been closed before being shown.
+ for (PendingContents::iterator iter = pending_contents_.begin();
+ iter != pending_contents_.end();
+ ++iter) {
+ if (iter->second != web_contents)
+ continue;
+ pending_contents_.erase(iter);
+ return;
+ }
+ NOTREACHED();
+}
+
+void WebContentsImpl::AddDestructionObserver(WebContentsImpl* web_contents) {
+ if (!ContainsKey(destruction_observers_, web_contents)) {
+ destruction_observers_[web_contents] =
+ new DestructionObserver(this, web_contents);
+ }
+}
+
+void WebContentsImpl::RemoveDestructionObserver(WebContentsImpl* web_contents) {
+ DestructionObservers::iterator iter =
+ destruction_observers_.find(web_contents);
+ if (iter != destruction_observers_.end()) {
+ delete destruction_observers_[web_contents];
+ destruction_observers_.erase(iter);
+ }
+}
+
+void WebContentsImpl::AddObserver(WebContentsObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void WebContentsImpl::RemoveObserver(WebContentsObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void WebContentsImpl::Activate() {
+ if (delegate_)
+ delegate_->ActivateContents(this);
+}
+
+void WebContentsImpl::Deactivate() {
+ if (delegate_)
+ delegate_->DeactivateContents(this);
+}
+
+void WebContentsImpl::LostCapture() {
+ if (delegate_)
+ delegate_->LostCapture();
+}
+
+void WebContentsImpl::RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) {
+ if (is_being_destroyed_) {
+ // |created_widgets_| might have been destroyed.
+ return;
+ }
+
+ std::set<RenderWidgetHostImpl*>::iterator iter =
+ created_widgets_.find(render_widget_host);
+ if (iter != created_widgets_.end())
+ created_widgets_.erase(iter);
+
+ if (render_widget_host &&
+ render_widget_host->GetRoutingID() == fullscreen_widget_routing_id_) {
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ DidDestroyFullscreenWidget(
+ fullscreen_widget_routing_id_));
+ fullscreen_widget_routing_id_ = MSG_ROUTING_NONE;
+ }
+}
+
+bool WebContentsImpl::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return delegate_ &&
+ delegate_->PreHandleKeyboardEvent(this, event, is_keyboard_shortcut);
+}
+
+void WebContentsImpl::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
+ if (browser_plugin_embedder_ &&
+ browser_plugin_embedder_->HandleKeyboardEvent(event)) {
+ return;
+ }
+
+ if (delegate_)
+ delegate_->HandleKeyboardEvent(this, event);
+}
+
+bool WebContentsImpl::PreHandleWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+#if !defined(OS_MACOSX)
+ // On platforms other than Mac, control+mousewheel changes zoom. On Mac, this
+ // isn't done for two reasons:
+ // -the OS already has a gesture to do this through pinch-zoom
+ // -if a user starts an inertial scroll, let's go, and presses control
+ // (i.e. control+tab) then the OS's buffered scroll events will come in
+ // with control key set which isn't what the user wants
+ if (delegate_ &&
+ event.wheelTicksY &&
+ (event.modifiers & WebKit::WebInputEvent::ControlKey)) {
+ delegate_->ContentsZoomChange(event.wheelTicksY > 0);
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible WebContentsImpl::GetParentNativeViewAccessible() {
+ return accessible_parent_;
+}
+#endif
+
+void WebContentsImpl::HandleMouseDown() {
+ if (delegate_)
+ delegate_->HandleMouseDown();
+}
+
+void WebContentsImpl::HandleMouseUp() {
+ if (delegate_)
+ delegate_->HandleMouseUp();
+}
+
+void WebContentsImpl::HandlePointerActivate() {
+ if (delegate_)
+ delegate_->HandlePointerActivate();
+}
+
+void WebContentsImpl::HandleGestureBegin() {
+ if (delegate_)
+ delegate_->HandleGestureBegin();
+}
+
+void WebContentsImpl::HandleGestureEnd() {
+ if (delegate_)
+ delegate_->HandleGestureEnd();
+}
+
+void WebContentsImpl::ToggleFullscreenMode(bool enter_fullscreen) {
+ if (delegate_)
+ delegate_->ToggleFullscreenModeForTab(this, enter_fullscreen);
+}
+
+bool WebContentsImpl::IsFullscreenForCurrentTab() const {
+ return delegate_ ? delegate_->IsFullscreenForTabOrPending(this) : false;
+}
+
+void WebContentsImpl::RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) {
+ if (delegate_) {
+ delegate_->RequestToLockMouse(this, user_gesture, last_unlocked_by_target);
+ } else {
+ GotResponseToLockMouseRequest(false);
+ }
+}
+
+void WebContentsImpl::LostMouseLock() {
+ if (delegate_)
+ delegate_->LostMouseLock();
+}
+
+void WebContentsImpl::CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) {
+ // We usually create the new window in the same BrowsingInstance (group of
+ // script-related windows), by passing in the current SiteInstance. However,
+ // if the opener is being suppressed (in a non-guest), we create a new
+ // SiteInstance in its own BrowsingInstance.
+ bool is_guest = GetRenderProcessHost()->IsGuest();
+
+ scoped_refptr<SiteInstance> site_instance =
+ params.opener_suppressed && !is_guest ?
+ SiteInstance::CreateForURL(GetBrowserContext(), params.target_url) :
+ GetSiteInstance();
+
+ // We must assign the SessionStorageNamespace before calling Init().
+ //
+ // http://crbug.com/142685
+ const std::string& partition_id =
+ GetContentClient()->browser()->
+ GetStoragePartitionIdForSite(GetBrowserContext(),
+ site_instance->GetSiteURL());
+ StoragePartition* partition = BrowserContext::GetStoragePartition(
+ GetBrowserContext(), site_instance.get());
+ DOMStorageContextWrapper* dom_storage_context =
+ static_cast<DOMStorageContextWrapper*>(partition->GetDOMStorageContext());
+ SessionStorageNamespaceImpl* session_storage_namespace_impl =
+ static_cast<SessionStorageNamespaceImpl*>(session_storage_namespace);
+ CHECK(session_storage_namespace_impl->IsFromContext(dom_storage_context));
+
+ if (delegate_ &&
+ !delegate_->ShouldCreateWebContents(this,
+ route_id,
+ params.window_container_type,
+ params.frame_name,
+ params.target_url,
+ partition_id,
+ session_storage_namespace)) {
+ GetRenderViewHost()->GetProcess()->ResumeRequestsForView(route_id);
+ GetRenderViewHost()->GetProcess()->ResumeRequestsForView(
+ main_frame_route_id);
+ return;
+ }
+
+ // Create the new web contents. This will automatically create the new
+ // WebContentsView. In the future, we may want to create the view separately.
+ WebContentsImpl* new_contents =
+ new WebContentsImpl(GetBrowserContext(),
+ params.opener_suppressed ? NULL : this);
+
+ new_contents->GetController().SetSessionStorageNamespace(
+ partition_id,
+ session_storage_namespace);
+ CreateParams create_params(GetBrowserContext(), site_instance.get());
+ create_params.routing_id = route_id;
+ create_params.main_frame_routing_id = main_frame_route_id;
+ if (!is_guest) {
+ create_params.context = view_->GetNativeView();
+ create_params.initial_size = view_->GetContainerSize();
+ } else {
+ // This makes |new_contents| act as a guest.
+ // For more info, see comment above class BrowserPluginGuest.
+ int instance_id = GetBrowserPluginGuestManager()->get_next_instance_id();
+ WebContentsImpl* new_contents_impl =
+ static_cast<WebContentsImpl*>(new_contents);
+ BrowserPluginGuest::CreateWithOpener(instance_id, new_contents_impl,
+ GetBrowserPluginGuest(), !!new_contents_impl->opener());
+ }
+ new_contents->Init(create_params);
+
+ // Save the window for later if we're not suppressing the opener (since it
+ // will be shown immediately).
+ if (!params.opener_suppressed) {
+ if (!is_guest) {
+ WebContentsViewPort* new_view = new_contents->view_.get();
+
+ // TODO(brettw): It seems bogus that we have to call this function on the
+ // newly created object and give it one of its own member variables.
+ new_view->CreateViewForWidget(new_contents->GetRenderViewHost());
+ }
+ // Save the created window associated with the route so we can show it
+ // later.
+ DCHECK_NE(MSG_ROUTING_NONE, route_id);
+ pending_contents_[route_id] = new_contents;
+ AddDestructionObserver(new_contents);
+ }
+
+ if (delegate_) {
+ delegate_->WebContentsCreated(
+ this, params.opener_frame_id, params.frame_name,
+ params.target_url, new_contents);
+ }
+
+ if (params.opener_suppressed) {
+ // When the opener is suppressed, the original renderer cannot access the
+ // new window. As a result, we need to show and navigate the window here.
+ bool was_blocked = false;
+ if (delegate_) {
+ gfx::Rect initial_pos;
+ delegate_->AddNewContents(
+ this, new_contents, params.disposition, initial_pos,
+ params.user_gesture, &was_blocked);
+ }
+ if (!was_blocked) {
+ OpenURLParams open_params(params.target_url,
+ Referrer(),
+ CURRENT_TAB,
+ PAGE_TRANSITION_LINK,
+ true /* is_renderer_initiated */);
+ open_params.user_gesture = params.user_gesture;
+ new_contents->OpenURL(open_params);
+ }
+ }
+}
+
+void WebContentsImpl::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ CreateNewWidget(route_id, false, popup_type);
+}
+
+void WebContentsImpl::CreateNewFullscreenWidget(int route_id) {
+ CreateNewWidget(route_id, true, WebKit::WebPopupTypeNone);
+}
+
+void WebContentsImpl::CreateNewWidget(int route_id,
+ bool is_fullscreen,
+ WebKit::WebPopupType popup_type) {
+ RenderProcessHost* process = GetRenderProcessHost();
+ RenderWidgetHostImpl* widget_host =
+ new RenderWidgetHostImpl(this, process, route_id);
+ created_widgets_.insert(widget_host);
+
+ RenderWidgetHostViewPort* widget_view = RenderWidgetHostViewPort::FromRWHV(
+ view_->CreateViewForPopupWidget(widget_host));
+ if (!widget_view)
+ return;
+ if (!is_fullscreen) {
+ // Popups should not get activated.
+ widget_view->SetPopupType(popup_type);
+ }
+ // Save the created widget associated with the route so we can show it later.
+ pending_widget_views_[route_id] = widget_view;
+
+#if defined(OS_MACOSX)
+ // A RenderWidgetHostViewMac has lifetime scoped to the view. We'll retain it
+ // to allow it to survive the trip without being hosted.
+ base::mac::NSObjectRetain(widget_view->GetNativeView());
+#endif
+}
+
+void WebContentsImpl::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ WebContentsImpl* contents = GetCreatedWindow(route_id);
+ if (contents) {
+ WebContentsDelegate* delegate = GetDelegate();
+ if (delegate) {
+ delegate->AddNewContents(
+ this, contents, disposition, initial_pos, user_gesture, NULL);
+ }
+ }
+}
+
+void WebContentsImpl::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ ShowCreatedWidget(route_id, false, initial_pos);
+}
+
+void WebContentsImpl::ShowCreatedFullscreenWidget(int route_id) {
+ ShowCreatedWidget(route_id, true, gfx::Rect());
+
+ DCHECK_EQ(MSG_ROUTING_NONE, fullscreen_widget_routing_id_);
+ fullscreen_widget_routing_id_ = route_id;
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ DidShowFullscreenWidget(route_id));
+}
+
+void WebContentsImpl::ShowCreatedWidget(int route_id,
+ bool is_fullscreen,
+ const gfx::Rect& initial_pos) {
+ if (delegate_)
+ delegate_->RenderWidgetShowing();
+
+ RenderWidgetHostViewPort* widget_host_view =
+ RenderWidgetHostViewPort::FromRWHV(GetCreatedWidget(route_id));
+ if (!widget_host_view)
+ return;
+ if (is_fullscreen)
+ widget_host_view->InitAsFullscreen(GetRenderWidgetHostViewPort());
+ else
+ widget_host_view->InitAsPopup(GetRenderWidgetHostViewPort(), initial_pos);
+
+ RenderWidgetHostImpl* render_widget_host_impl =
+ RenderWidgetHostImpl::From(widget_host_view->GetRenderWidgetHost());
+ render_widget_host_impl->Init();
+ // Only allow privileged mouse lock for fullscreen render widget, which is
+ // used to implement Pepper Flash fullscreen.
+ render_widget_host_impl->set_allow_privileged_mouse_lock(is_fullscreen);
+
+#if defined(OS_MACOSX)
+ // A RenderWidgetHostViewMac has lifetime scoped to the view. Now that it's
+ // properly embedded (or purposefully ignored) we can release the retain we
+ // took in CreateNewWidget().
+ base::mac::NSObjectRelease(widget_host_view->GetNativeView());
+#endif
+}
+
+WebContentsImpl* WebContentsImpl::GetCreatedWindow(int route_id) {
+ PendingContents::iterator iter = pending_contents_.find(route_id);
+
+ // Certain systems can block the creation of new windows. If we didn't succeed
+ // in creating one, just return NULL.
+ if (iter == pending_contents_.end()) {
+ return NULL;
+ }
+
+ WebContentsImpl* new_contents = iter->second;
+ pending_contents_.erase(route_id);
+ RemoveDestructionObserver(new_contents);
+
+ // Don't initialize the guest WebContents immediately.
+ if (new_contents->GetRenderProcessHost()->IsGuest())
+ return new_contents;
+
+ if (!new_contents->GetRenderProcessHost()->HasConnection() ||
+ !new_contents->GetRenderViewHost()->GetView())
+ return NULL;
+
+ // TODO(brettw): It seems bogus to reach into here and initialize the host.
+ static_cast<RenderViewHostImpl*>(new_contents->GetRenderViewHost())->Init();
+ return new_contents;
+}
+
+RenderWidgetHostView* WebContentsImpl::GetCreatedWidget(int route_id) {
+ PendingWidgetViews::iterator iter = pending_widget_views_.find(route_id);
+ if (iter == pending_widget_views_.end()) {
+ DCHECK(false);
+ return NULL;
+ }
+
+ RenderWidgetHostView* widget_host_view = iter->second;
+ pending_widget_views_.erase(route_id);
+
+ RenderWidgetHost* widget_host = widget_host_view->GetRenderWidgetHost();
+ if (!widget_host->GetProcess()->HasConnection()) {
+ // The view has gone away or the renderer crashed. Nothing to do.
+ return NULL;
+ }
+
+ return widget_host_view;
+}
+
+void WebContentsImpl::ShowContextMenu(const ContextMenuParams& params) {
+ // Allow WebContentsDelegates to handle the context menu operation first.
+ if (delegate_ && delegate_->HandleContextMenu(params))
+ return;
+
+ render_view_host_delegate_view_->ShowContextMenu(params);
+}
+
+void WebContentsImpl::RequestMediaAccessPermission(
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) {
+ if (delegate_)
+ delegate_->RequestMediaAccessPermission(this, request, callback);
+ else
+ callback.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>());
+}
+
+SessionStorageNamespace* WebContentsImpl::GetSessionStorageNamespace(
+ SiteInstance* instance) {
+ return controller_.GetSessionStorageNamespace(instance);
+}
+
+void WebContentsImpl::DidSendScreenRects(RenderWidgetHostImpl* rwh) {
+ if (browser_plugin_embedder_)
+ browser_plugin_embedder_->DidSendScreenRects();
+}
+
+void WebContentsImpl::UpdatePreferredSize(const gfx::Size& pref_size) {
+ preferred_size_ = pref_size;
+ if (delegate_)
+ delegate_->UpdatePreferredSize(this, pref_size);
+}
+
+void WebContentsImpl::ResizeDueToAutoResize(const gfx::Size& new_size) {
+ if (delegate_)
+ delegate_->ResizeDueToAutoResize(this, new_size);
+}
+
+WebContents* WebContentsImpl::OpenURL(const OpenURLParams& params) {
+ if (!delegate_)
+ return NULL;
+
+ WebContents* new_contents = delegate_->OpenURLFromTab(this, params);
+ return new_contents;
+}
+
+bool WebContentsImpl::Send(IPC::Message* message) {
+ if (!GetRenderViewHost()) {
+ delete message;
+ return false;
+ }
+
+ return GetRenderViewHost()->Send(message);
+}
+
+bool WebContentsImpl::NavigateToPendingEntry(
+ NavigationController::ReloadType reload_type) {
+ return NavigateToEntry(
+ *NavigationEntryImpl::FromNavigationEntry(controller_.GetPendingEntry()),
+ reload_type);
+}
+
+void WebContentsImpl::RenderViewForInterstitialPageCreated(
+ RenderViewHost* render_view_host) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ RenderViewForInterstitialPageCreated(render_view_host));
+}
+
+void WebContentsImpl::AttachInterstitialPage(
+ InterstitialPageImpl* interstitial_page) {
+ DCHECK(interstitial_page);
+ render_manager_.set_interstitial_page(interstitial_page);
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidAttachInterstitialPage());
+}
+
+void WebContentsImpl::DetachInterstitialPage() {
+ if (GetInterstitialPage())
+ render_manager_.remove_interstitial_page();
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidDetachInterstitialPage());
+}
+
+bool WebContentsImpl::NavigateToEntry(
+ const NavigationEntryImpl& entry,
+ NavigationController::ReloadType reload_type) {
+ TRACE_EVENT0("browser", "WebContentsImpl::NavigateToEntry");
+
+ // The renderer will reject IPC messages with URLs longer than
+ // this limit, so don't attempt to navigate with a longer URL.
+ if (entry.GetURL().spec().size() > kMaxURLChars)
+ return false;
+
+ RenderViewHostImpl* dest_render_view_host =
+ static_cast<RenderViewHostImpl*>(render_manager_.Navigate(entry));
+ if (!dest_render_view_host)
+ return false; // Unable to create the desired render view host.
+
+ // For security, we should never send non-Web-UI URLs to a Web UI renderer.
+ // Double check that here.
+ int enabled_bindings = dest_render_view_host->GetEnabledBindings();
+ bool data_urls_allowed = delegate_ && delegate_->CanLoadDataURLsInWebUI();
+ bool is_allowed_in_web_ui_renderer =
+ WebUIControllerFactoryRegistry::GetInstance()->IsURLAcceptableForWebUI(
+ GetBrowserContext(), entry.GetURL(), data_urls_allowed);
+ if ((enabled_bindings & BINDINGS_POLICY_WEB_UI) &&
+ !is_allowed_in_web_ui_renderer) {
+ // Log the URL to help us diagnose any future failures of this CHECK.
+ GetContentClient()->SetActiveURL(entry.GetURL());
+ CHECK(0);
+ }
+
+ // Notify observers that we will navigate in this RV.
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ AboutToNavigateRenderView(dest_render_view_host));
+
+ // Used for page load time metrics.
+ current_load_start_ = base::TimeTicks::Now();
+
+ // Navigate in the desired RenderViewHost.
+ ViewMsg_Navigate_Params navigate_params;
+ MakeNavigateParams(entry, controller_, delegate_, reload_type,
+ &navigate_params);
+ dest_render_view_host->Navigate(navigate_params);
+
+ if (entry.GetPageID() == -1) {
+ // HACK!! This code suppresses javascript: URLs from being added to
+ // session history, which is what we want to do for javascript: URLs that
+ // do not generate content. What we really need is a message from the
+ // renderer telling us that a new page was not created. The same message
+ // could be used for mailto: URLs and the like.
+ if (entry.GetURL().SchemeIs(chrome::kJavaScriptScheme))
+ return false;
+ }
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ NavigateToPendingEntry(entry.GetURL(), reload_type));
+
+ if (delegate_)
+ delegate_->DidNavigateToPendingEntry(this);
+
+ return true;
+}
+
+void WebContentsImpl::SetHistoryLengthAndPrune(
+ const SiteInstance* site_instance,
+ int history_length,
+ int32 minimum_page_id) {
+ // SetHistoryLengthAndPrune doesn't work when there are pending cross-site
+ // navigations. Callers should ensure that this is the case.
+ if (render_manager_.pending_render_view_host()) {
+ NOTREACHED();
+ return;
+ }
+ RenderViewHostImpl* rvh = GetRenderViewHostImpl();
+ if (!rvh) {
+ NOTREACHED();
+ return;
+ }
+ if (site_instance && rvh->GetSiteInstance() != site_instance) {
+ NOTREACHED();
+ return;
+ }
+ Send(new ViewMsg_SetHistoryLengthAndPrune(GetRoutingID(),
+ history_length,
+ minimum_page_id));
+}
+
+void WebContentsImpl::FocusThroughTabTraversal(bool reverse) {
+ if (ShowingInterstitialPage()) {
+ render_manager_.interstitial_page()->FocusThroughTabTraversal(reverse);
+ return;
+ }
+ GetRenderViewHostImpl()->SetInitialFocus(reverse);
+}
+
+bool WebContentsImpl::ShowingInterstitialPage() const {
+ return render_manager_.interstitial_page() != NULL;
+}
+
+InterstitialPage* WebContentsImpl::GetInterstitialPage() const {
+ return render_manager_.interstitial_page();
+}
+
+bool WebContentsImpl::IsSavable() {
+ // WebKit creates Document object when MIME type is application/xhtml+xml,
+ // so we also support this MIME type.
+ return contents_mime_type_ == "text/html" ||
+ contents_mime_type_ == "text/xml" ||
+ contents_mime_type_ == "application/xhtml+xml" ||
+ contents_mime_type_ == "text/plain" ||
+ contents_mime_type_ == "text/css" ||
+ net::IsSupportedJavascriptMimeType(contents_mime_type_.c_str());
+}
+
+void WebContentsImpl::OnSavePage() {
+ // If we can not save the page, try to download it.
+ if (!IsSavable()) {
+ RecordDownloadSource(INITIATED_BY_SAVE_PACKAGE_ON_NON_HTML);
+ SaveFrame(GetURL(), Referrer());
+ return;
+ }
+
+ Stop();
+
+ // Create the save package and possibly prompt the user for the name to save
+ // the page as. The user prompt is an asynchronous operation that runs on
+ // another thread.
+ save_package_ = new SavePackage(this);
+ save_package_->GetSaveInfo();
+}
+
+// Used in automated testing to bypass prompting the user for file names.
+// Instead, the names and paths are hard coded rather than running them through
+// file name sanitation and extension / mime checking.
+bool WebContentsImpl::SavePage(const base::FilePath& main_file,
+ const base::FilePath& dir_path,
+ SavePageType save_type) {
+ // Stop the page from navigating.
+ Stop();
+
+ save_package_ = new SavePackage(this, save_type, main_file, dir_path);
+ return save_package_->Init(SavePackageDownloadCreatedCallback());
+}
+
+void WebContentsImpl::SaveFrame(const GURL& url,
+ const Referrer& referrer) {
+ if (!GetURL().is_valid())
+ return;
+ bool is_main_frame = (url == GetURL());
+
+ DownloadManager* dlm =
+ BrowserContext::GetDownloadManager(GetBrowserContext());
+ if (!dlm)
+ return;
+ int64 post_id = -1;
+ if (is_main_frame) {
+ const NavigationEntry* entry = controller_.GetActiveEntry();
+ if (entry)
+ post_id = entry->GetPostID();
+ }
+ scoped_ptr<DownloadUrlParameters> params(
+ DownloadUrlParameters::FromWebContents(this, url));
+ params->set_referrer(referrer);
+ params->set_post_id(post_id);
+ params->set_prefer_cache(true);
+ if (post_id >= 0)
+ params->set_method("POST");
+ params->set_prompt(true);
+ dlm->DownloadUrl(params.Pass());
+}
+
+void WebContentsImpl::GenerateMHTML(
+ const base::FilePath& file,
+ const base::Callback<void(const base::FilePath&, int64)>& callback) {
+ MHTMLGenerationManager::GetInstance()->GenerateMHTML(this, file, callback);
+}
+
+bool WebContentsImpl::IsActiveEntry(int32 page_id) {
+ NavigationEntryImpl* active_entry =
+ NavigationEntryImpl::FromNavigationEntry(controller_.GetActiveEntry());
+ return (active_entry != NULL &&
+ active_entry->site_instance() == GetSiteInstance() &&
+ active_entry->GetPageID() == page_id);
+}
+
+const std::string& WebContentsImpl::GetContentsMimeType() const {
+ return contents_mime_type_;
+}
+
+bool WebContentsImpl::WillNotifyDisconnection() const {
+ return notify_disconnection_;
+}
+
+void WebContentsImpl::SetOverrideEncoding(const std::string& encoding) {
+ SetEncoding(encoding);
+ Send(new ViewMsg_SetPageEncoding(GetRoutingID(), encoding));
+}
+
+void WebContentsImpl::ResetOverrideEncoding() {
+ encoding_.clear();
+ Send(new ViewMsg_ResetPageEncodingToDefault(GetRoutingID()));
+}
+
+RendererPreferences* WebContentsImpl::GetMutableRendererPrefs() {
+ return &renderer_preferences_;
+}
+
+void WebContentsImpl::Close() {
+ Close(GetRenderViewHost());
+}
+
+void WebContentsImpl::DragSourceEndedAt(int client_x, int client_y,
+ int screen_x, int screen_y, WebKit::WebDragOperation operation) {
+ if (browser_plugin_embedder_.get())
+ browser_plugin_embedder_->DragSourceEndedAt(client_x, client_y,
+ screen_x, screen_y, operation);
+ if (GetRenderViewHost())
+ GetRenderViewHostImpl()->DragSourceEndedAt(client_x, client_y,
+ screen_x, screen_y, operation);
+}
+
+void WebContentsImpl::DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y) {
+ if (browser_plugin_embedder_.get())
+ browser_plugin_embedder_->DragSourceMovedTo(client_x, client_y,
+ screen_x, screen_y);
+ if (GetRenderViewHost())
+ GetRenderViewHostImpl()->DragSourceMovedTo(client_x, client_y,
+ screen_x, screen_y);
+}
+
+void WebContentsImpl::SystemDragEnded() {
+ if (GetRenderViewHost())
+ GetRenderViewHostImpl()->DragSourceSystemDragEnded();
+ if (delegate_)
+ delegate_->DragEnded();
+ if (browser_plugin_embedder_.get())
+ browser_plugin_embedder_->SystemDragEnded();
+}
+
+void WebContentsImpl::UserGestureDone() {
+ OnUserGesture();
+}
+
+void WebContentsImpl::SetClosedByUserGesture(bool value) {
+ closed_by_user_gesture_ = value;
+}
+
+bool WebContentsImpl::GetClosedByUserGesture() const {
+ return closed_by_user_gesture_;
+}
+
+double WebContentsImpl::GetZoomLevel() const {
+ HostZoomMapImpl* zoom_map = static_cast<HostZoomMapImpl*>(
+ HostZoomMap::GetForBrowserContext(GetBrowserContext()));
+ if (!zoom_map)
+ return 0;
+
+ double zoom_level;
+ if (temporary_zoom_settings_) {
+ zoom_level = zoom_map->GetTemporaryZoomLevel(
+ GetRenderProcessHost()->GetID(), GetRenderViewHost()->GetRoutingID());
+ } else {
+ GURL url;
+ NavigationEntry* active_entry = GetController().GetActiveEntry();
+ // Since zoom map is updated using rewritten URL, use rewritten URL
+ // to get the zoom level.
+ url = active_entry ? active_entry->GetURL() : GURL::EmptyGURL();
+ zoom_level = zoom_map->GetZoomLevelForHostAndScheme(url.scheme(),
+ net::GetHostOrSpecFromURL(url));
+ }
+ return zoom_level;
+}
+
+int WebContentsImpl::GetZoomPercent(bool* enable_increment,
+ bool* enable_decrement) const {
+ *enable_decrement = *enable_increment = false;
+ // Calculate the zoom percent from the factor. Round up to the nearest whole
+ // number.
+ int percent = static_cast<int>(
+ ZoomLevelToZoomFactor(GetZoomLevel()) * 100 + 0.5);
+ *enable_decrement = percent > minimum_zoom_percent_;
+ *enable_increment = percent < maximum_zoom_percent_;
+ return percent;
+}
+
+void WebContentsImpl::ViewSource() {
+ if (!delegate_)
+ return;
+
+ NavigationEntry* active_entry = GetController().GetActiveEntry();
+ if (!active_entry)
+ return;
+
+ delegate_->ViewSourceForTab(this, active_entry->GetURL());
+}
+
+void WebContentsImpl::ViewFrameSource(const GURL& url,
+ const PageState& page_state) {
+ if (!delegate_)
+ return;
+
+ delegate_->ViewSourceForFrame(this, url, page_state);
+}
+
+int WebContentsImpl::GetMinimumZoomPercent() const {
+ return minimum_zoom_percent_;
+}
+
+int WebContentsImpl::GetMaximumZoomPercent() const {
+ return maximum_zoom_percent_;
+}
+
+gfx::Size WebContentsImpl::GetPreferredSize() const {
+ return preferred_size_;
+}
+
+bool WebContentsImpl::GotResponseToLockMouseRequest(bool allowed) {
+ return GetRenderViewHost() ?
+ GetRenderViewHostImpl()->GotResponseToLockMouseRequest(allowed) : false;
+}
+
+bool WebContentsImpl::HasOpener() const {
+ return opener_ != NULL;
+}
+
+void WebContentsImpl::DidChooseColorInColorChooser(SkColor color) {
+ Send(new ViewMsg_DidChooseColorResponse(
+ GetRoutingID(), color_chooser_identifier_, color));
+}
+
+void WebContentsImpl::DidEndColorChooser() {
+ Send(new ViewMsg_DidEndColorChooser(GetRoutingID(),
+ color_chooser_identifier_));
+ color_chooser_.reset();
+ color_chooser_identifier_ = 0;
+}
+
+int WebContentsImpl::DownloadImage(const GURL& url,
+ bool is_favicon,
+ uint32_t preferred_image_size,
+ uint32_t max_image_size,
+ const ImageDownloadCallback& callback) {
+ RenderViewHost* host = GetRenderViewHost();
+ int id = StartDownload(
+ host, url, is_favicon, preferred_image_size, max_image_size);
+ image_download_map_[id] = callback;
+ return id;
+}
+
+bool WebContentsImpl::FocusLocationBarByDefault() {
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (entry && entry->GetURL() == GURL(kAboutBlankURL))
+ return true;
+ return delegate_ && delegate_->ShouldFocusLocationBarByDefault(this);
+}
+
+void WebContentsImpl::SetFocusToLocationBar(bool select_all) {
+ if (delegate_)
+ delegate_->SetFocusToLocationBar(select_all);
+}
+
+void WebContentsImpl::DidStartProvisionalLoadForFrame(
+ RenderViewHost* render_view_host,
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& url) {
+ bool is_error_page = (url.spec() == kUnreachableWebDataURL);
+ bool is_iframe_srcdoc = (url.spec() == kAboutSrcDocURL);
+ GURL validated_url(url);
+ RenderProcessHost* render_process_host =
+ render_view_host->GetProcess();
+ RenderViewHost::FilterURL(render_process_host, false, &validated_url);
+
+ if (is_main_frame) {
+ DidChangeLoadProgress(0);
+
+ // If there is no browser-initiated pending entry for this navigation and it
+ // is not for the error URL, create a pending entry using the current
+ // SiteInstance, and ensure the address bar updates accordingly. We don't
+ // know the referrer or extra headers at this point, but the referrer will
+ // be set properly upon commit.
+ NavigationEntry* pending_entry = controller_.GetPendingEntry();
+ bool has_browser_initiated_pending_entry = pending_entry &&
+ !NavigationEntryImpl::FromNavigationEntry(pending_entry)->
+ is_renderer_initiated();
+ if (!has_browser_initiated_pending_entry && !is_error_page) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ controller_.CreateNavigationEntry(validated_url,
+ content::Referrer(),
+ content::PAGE_TRANSITION_LINK,
+ true /* is_renderer_initiated */,
+ std::string(),
+ GetBrowserContext()));
+ entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(GetSiteInstance()));
+ controller_.SetPendingEntry(entry);
+ NotifyNavigationStateChanged(content::INVALIDATE_TYPE_URL);
+ }
+ }
+
+ // Notify observers about the start of the provisional load.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidStartProvisionalLoadForFrame(frame_id, parent_frame_id,
+ is_main_frame, validated_url, is_error_page,
+ is_iframe_srcdoc, render_view_host));
+
+ if (is_main_frame) {
+ // Notify observers about the provisional change in the main frame URL.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ ProvisionalChangeToMainFrameUrl(validated_url,
+ render_view_host));
+ }
+}
+
+void WebContentsImpl::DidRedirectProvisionalLoad(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) {
+ // TODO(creis): Remove this method and have the pre-rendering code listen to
+ // the ResourceDispatcherHost's RESOURCE_RECEIVED_REDIRECT notification
+ // instead. See http://crbug.com/78512.
+ GURL validated_source_url(source_url);
+ GURL validated_target_url(target_url);
+ RenderProcessHost* render_process_host =
+ render_view_host->GetProcess();
+ RenderViewHost::FilterURL(render_process_host, false, &validated_source_url);
+ RenderViewHost::FilterURL(render_process_host, false, &validated_target_url);
+ NavigationEntry* entry;
+ if (page_id == -1) {
+ entry = controller_.GetPendingEntry();
+ } else {
+ entry = controller_.GetEntryWithPageID(render_view_host->GetSiteInstance(),
+ page_id);
+ }
+ if (!entry || entry->GetURL() != validated_source_url)
+ return;
+
+ // Notify observers about the provisional change in the main frame URL.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ ProvisionalChangeToMainFrameUrl(validated_target_url,
+ render_view_host));
+}
+
+void WebContentsImpl::DidFailProvisionalLoadWithError(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_DidFailProvisionalLoadWithError_Params& params) {
+ VLOG(1) << "Failed Provisional Load: " << params.url.possibly_invalid_spec()
+ << ", error_code: " << params.error_code
+ << ", error_description: " << params.error_description
+ << ", is_main_frame: " << params.is_main_frame
+ << ", showing_repost_interstitial: " <<
+ params.showing_repost_interstitial
+ << ", frame_id: " << params.frame_id;
+ GURL validated_url(params.url);
+ RenderProcessHost* render_process_host =
+ render_view_host->GetProcess();
+ RenderViewHost::FilterURL(render_process_host, false, &validated_url);
+
+ if (net::ERR_ABORTED == params.error_code) {
+ // EVIL HACK ALERT! Ignore failed loads when we're showing interstitials.
+ // This means that the interstitial won't be torn down properly, which is
+ // bad. But if we have an interstitial, go back to another tab type, and
+ // then load the same interstitial again, we could end up getting the first
+ // interstitial's "failed" message (as a result of the cancel) when we're on
+ // the second one.
+ //
+ // We can't tell this apart, so we think we're tearing down the current page
+ // which will cause a crash later one. There is also some code in
+ // RenderViewHostManager::RendererAbortedProvisionalLoad that is commented
+ // out because of this problem.
+ //
+ // http://code.google.com/p/chromium/issues/detail?id=2855
+ // Because this will not tear down the interstitial properly, if "back" is
+ // back to another tab type, the interstitial will still be somewhat alive
+ // in the previous tab type. If you navigate somewhere that activates the
+ // tab with the interstitial again, you'll see a flash before the new load
+ // commits of the interstitial page.
+ if (ShowingInterstitialPage()) {
+ LOG(WARNING) << "Discarding message during interstitial.";
+ return;
+ }
+
+ // Do not clear the pending entry if one exists, so that the user's typed
+ // URL is not lost when a navigation fails or is aborted. We'll allow
+ // the view to clear the pending entry and typed URL if the user requests.
+
+ render_manager_.RendererAbortedProvisionalLoad(render_view_host);
+ }
+
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ DidFailProvisionalLoad(params.frame_id,
+ params.is_main_frame,
+ validated_url,
+ params.error_code,
+ params.error_description,
+ render_view_host));
+}
+
+void WebContentsImpl::OnDidLoadResourceFromMemoryCache(
+ const GURL& url,
+ const std::string& security_info,
+ const std::string& http_method,
+ const std::string& mime_type,
+ ResourceType::Type resource_type) {
+ base::StatsCounter cache("WebKit.CacheHit");
+ cache.Increment();
+
+ // Send out a notification that we loaded a resource from our memory cache.
+ int cert_id = 0;
+ net::CertStatus cert_status = 0;
+ int security_bits = -1;
+ int connection_status = 0;
+ DeserializeSecurityInfo(security_info, &cert_id, &cert_status,
+ &security_bits, &connection_status);
+ LoadFromMemoryCacheDetails details(
+ url, GetRenderProcessHost()->GetID(), cert_id, cert_status, http_method,
+ mime_type, resource_type);
+
+ NotificationService::current()->Notify(
+ NOTIFICATION_LOAD_FROM_MEMORY_CACHE,
+ Source<NavigationController>(&controller_),
+ Details<LoadFromMemoryCacheDetails>(&details));
+}
+
+void WebContentsImpl::OnDidDisplayInsecureContent() {
+ RecordAction(UserMetricsAction("SSL.DisplayedInsecureContent"));
+ displayed_insecure_content_ = true;
+ SSLManager::NotifySSLInternalStateChanged(
+ GetController().GetBrowserContext());
+}
+
+void WebContentsImpl::OnDidRunInsecureContent(
+ const std::string& security_origin, const GURL& target_url) {
+ LOG(INFO) << security_origin << " ran insecure content from "
+ << target_url.possibly_invalid_spec();
+ RecordAction(UserMetricsAction("SSL.RanInsecureContent"));
+ if (EndsWith(security_origin, kDotGoogleDotCom, false))
+ RecordAction(UserMetricsAction("SSL.RanInsecureContentGoogle"));
+ controller_.ssl_manager()->DidRunInsecureContent(security_origin);
+ displayed_insecure_content_ = true;
+ SSLManager::NotifySSLInternalStateChanged(
+ GetController().GetBrowserContext());
+}
+
+void WebContentsImpl::OnDocumentLoadedInFrame(int64 frame_id) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DocumentLoadedInFrame(frame_id, message_source_));
+}
+
+void WebContentsImpl::OnDidFinishLoad(
+ int64 frame_id,
+ const GURL& url,
+ bool is_main_frame) {
+ GURL validated_url(url);
+ RenderProcessHost* render_process_host = message_source_->GetProcess();
+ RenderViewHost::FilterURL(render_process_host, false, &validated_url);
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidFinishLoad(frame_id, validated_url, is_main_frame,
+ message_source_));
+}
+
+void WebContentsImpl::OnDidFailLoadWithError(
+ int64 frame_id,
+ const GURL& url,
+ bool is_main_frame,
+ int error_code,
+ const string16& error_description) {
+ GURL validated_url(url);
+ RenderProcessHost* render_process_host = message_source_->GetProcess();
+ RenderViewHost::FilterURL(render_process_host, false, &validated_url);
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidFailLoad(frame_id, validated_url, is_main_frame,
+ error_code, error_description,
+ message_source_));
+}
+
+void WebContentsImpl::OnGoToEntryAtOffset(int offset) {
+ if (!delegate_ || delegate_->OnGoToEntryOffset(offset)) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ controller_.GetEntryAtOffset(offset));
+ if (!entry)
+ return;
+ // Note that we don't call NavigationController::GotToOffset() as we don't
+ // want to create a pending navigation entry (it might end up lingering
+ // http://crbug.com/51680).
+ entry->SetTransitionType(
+ PageTransitionFromInt(
+ entry->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToEntry(*entry, NavigationControllerImpl::NO_RELOAD);
+
+ // If the entry is being restored and doesn't have a SiteInstance yet, fill
+ // it in now that we know. This allows us to find the entry when it commits.
+ if (!entry->site_instance() &&
+ entry->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
+ entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(GetPendingSiteInstance()));
+ }
+ }
+}
+
+void WebContentsImpl::OnUpdateZoomLimits(int minimum_percent,
+ int maximum_percent,
+ bool remember) {
+ minimum_zoom_percent_ = minimum_percent;
+ maximum_zoom_percent_ = maximum_percent;
+ temporary_zoom_settings_ = !remember;
+}
+
+void WebContentsImpl::OnEnumerateDirectory(int request_id,
+ const base::FilePath& path) {
+ if (!delegate_)
+ return;
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (policy->CanReadDirectory(GetRenderProcessHost()->GetID(), path))
+ delegate_->EnumerateDirectory(this, request_id, path);
+}
+
+void WebContentsImpl::OnJSOutOfMemory() {
+ if (delegate_)
+ delegate_->JSOutOfMemory(this);
+}
+
+void WebContentsImpl::OnRegisterProtocolHandler(const std::string& protocol,
+ const GURL& url,
+ const string16& title,
+ bool user_gesture) {
+ if (!delegate_)
+ return;
+
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (policy->IsPseudoScheme(protocol))
+ return;
+
+ delegate_->RegisterProtocolHandler(this, protocol, url, title, user_gesture);
+}
+
+void WebContentsImpl::OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ if (delegate_) {
+ delegate_->FindReply(this, request_id, number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+ }
+}
+
+void WebContentsImpl::OnDidProgrammaticallyScroll(
+ const gfx::Vector2d& scroll_point) {
+ if (delegate_)
+ delegate_->DidProgrammaticallyScroll(this, scroll_point);
+}
+
+#if defined(OS_ANDROID)
+void WebContentsImpl::OnFindMatchRectsReply(
+ int version,
+ const std::vector<gfx::RectF>& rects,
+ const gfx::RectF& active_rect) {
+ if (delegate_)
+ delegate_->FindMatchRectsReply(this, version, rects, active_rect);
+}
+
+void WebContentsImpl::OnOpenDateTimeDialog(
+ const ViewHostMsg_DateTimeDialogValue_Params& value) {
+ date_time_chooser_->ShowDialog(
+ ContentViewCore::FromWebContents(this), GetRenderViewHost(),
+ value.dialog_type, value.year, value.month, value.day, value.hour,
+ value.minute, value.second, value.week, value.minimum, value.maximum);
+}
+
+#endif
+
+void WebContentsImpl::OnCrashedPlugin(const base::FilePath& plugin_path,
+ base::ProcessId plugin_pid) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ PluginCrashed(plugin_path, plugin_pid));
+}
+
+void WebContentsImpl::OnAppCacheAccessed(const GURL& manifest_url,
+ bool blocked_by_policy) {
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ AppCacheAccessed(manifest_url, blocked_by_policy));
+}
+
+void WebContentsImpl::OnOpenColorChooser(int color_chooser_id,
+ SkColor color) {
+ ColorChooser* new_color_chooser = delegate_->OpenColorChooser(this, color);
+ if (color_chooser_ == new_color_chooser)
+ return;
+ color_chooser_.reset(new_color_chooser);
+ color_chooser_identifier_ = color_chooser_id;
+}
+
+void WebContentsImpl::OnEndColorChooser(int color_chooser_id) {
+ if (color_chooser_ &&
+ color_chooser_id == color_chooser_identifier_)
+ color_chooser_->End();
+}
+
+void WebContentsImpl::OnSetSelectedColorInColorChooser(int color_chooser_id,
+ SkColor color) {
+ if (color_chooser_ &&
+ color_chooser_id == color_chooser_identifier_)
+ color_chooser_->SetSelectedColor(color);
+}
+
+void WebContentsImpl::OnPepperPluginHung(int plugin_child_id,
+ const base::FilePath& path,
+ bool is_hung) {
+ UMA_HISTOGRAM_COUNTS("Pepper.PluginHung", 1);
+
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ PluginHungStatusChanged(plugin_child_id, path, is_hung));
+}
+
+// This exists for render views that don't have a WebUI, but do have WebUI
+// bindings enabled.
+void WebContentsImpl::OnWebUISend(const GURL& source_url,
+ const std::string& name,
+ const base::ListValue& args) {
+ if (delegate_)
+ delegate_->WebUISend(this, source_url, name, args);
+}
+
+void WebContentsImpl::OnRequestPpapiBrokerPermission(
+ int routing_id,
+ const GURL& url,
+ const base::FilePath& plugin_path) {
+ if (!delegate_) {
+ OnPpapiBrokerPermissionResult(routing_id, false);
+ return;
+ }
+
+ if (!delegate_->RequestPpapiBrokerPermission(
+ this, url, plugin_path,
+ base::Bind(&WebContentsImpl::OnPpapiBrokerPermissionResult,
+ base::Unretained(this), routing_id))) {
+ NOTIMPLEMENTED();
+ OnPpapiBrokerPermissionResult(routing_id, false);
+ }
+}
+
+void WebContentsImpl::OnPpapiBrokerPermissionResult(int routing_id,
+ bool result) {
+ Send(new ViewMsg_PpapiBrokerPermissionResult(routing_id, result));
+}
+
+void WebContentsImpl::OnBrowserPluginMessage(const IPC::Message& message) {
+ // This creates a BrowserPluginEmbedder, which handles all the BrowserPlugin
+ // specific messages for this WebContents. This means that any message from
+ // a BrowserPlugin prior to this will be ignored.
+ // For more info, see comment above classes BrowserPluginEmbedder and
+ // BrowserPluginGuest.
+ CHECK(!browser_plugin_embedder_.get());
+ browser_plugin_embedder_.reset(BrowserPluginEmbedder::Create(this));
+ browser_plugin_embedder_->OnMessageReceived(message);
+}
+
+void WebContentsImpl::OnDidDownloadImage(
+ int id,
+ int http_status_code,
+ const GURL& image_url,
+ int requested_size,
+ const std::vector<SkBitmap>& bitmaps) {
+ ImageDownloadMap::iterator iter = image_download_map_.find(id);
+ if (iter == image_download_map_.end()) {
+ // Currently WebContents notifies us of ANY downloads so that it is
+ // possible to get here.
+ return;
+ }
+ if (!iter->second.is_null()) {
+ iter->second.Run(id, http_status_code, image_url, requested_size, bitmaps);
+ }
+ image_download_map_.erase(id);
+}
+
+void WebContentsImpl::OnUpdateFaviconURL(
+ int32 page_id,
+ const std::vector<FaviconURL>& candidates) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidUpdateFaviconURL(page_id, candidates));
+}
+
+FrameTreeNode* WebContentsImpl::FindFrameTreeNodeByID(int64 frame_id) {
+ // TODO(nasko): Remove this check once we move to creating the root node
+ // through RenderFrameHost creation.
+ if (!frame_tree_root_.get())
+ return NULL;
+
+ FrameTreeNode* node = NULL;
+ std::queue<FrameTreeNode*> queue;
+ queue.push(frame_tree_root_.get());
+
+ while (!queue.empty()) {
+ node = queue.front();
+ queue.pop();
+ if (node->frame_id() == frame_id)
+ return node;
+
+ for (size_t i = 0; i < node->child_count(); ++i)
+ queue.push(node->child_at(i));
+ }
+
+ return NULL;
+}
+
+void WebContentsImpl::OnFrameAttached(
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name) {
+ FrameTreeNode* parent = FindFrameTreeNodeByID(parent_frame_id);
+ if (!parent)
+ return;
+
+ FrameTreeNode* node = new FrameTreeNode(frame_id, frame_name);
+ parent->AddChild(node);
+}
+
+void WebContentsImpl::OnFrameDetached(int64 parent_frame_id, int64 frame_id) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ FrameDetached(message_source_, frame_id));
+
+ FrameTreeNode* parent = FindFrameTreeNodeByID(parent_frame_id);
+ if (!parent)
+ return;
+
+ parent->RemoveChild(frame_id);
+}
+
+void WebContentsImpl::OnMediaNotification(int64 player_cookie,
+ bool has_video,
+ bool has_audio,
+ bool is_playing) {
+ // Chrome OS does its own detection of audio and video.
+#if !defined(OS_CHROMEOS)
+ if (is_playing) {
+ scoped_ptr<PowerSaveBlocker> blocker;
+ if (has_video) {
+ blocker = PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep,
+ "Playing video");
+#if defined(OS_ANDROID)
+ static_cast<PowerSaveBlockerImpl*>(blocker.get())->
+ InitDisplaySleepBlocker(GetView()->GetNativeView());
+#endif
+ } else if (has_audio) {
+ blocker = PowerSaveBlocker::Create(
+ PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ "Playing audio");
+ }
+
+ if (blocker)
+ power_save_blockers_[message_source_][player_cookie] = blocker.release();
+ } else {
+ delete power_save_blockers_[message_source_][player_cookie];
+ power_save_blockers_[message_source_].erase(player_cookie);
+ }
+#endif // !defined(OS_CHROMEOS)
+}
+
+
+void WebContentsImpl::DidChangeVisibleSSLState() {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidChangeVisibleSSLState());
+}
+
+void WebContentsImpl::NotifyBeforeFormRepostWarningShow() {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ BeforeFormRepostWarningShow());
+}
+
+// Notifies the RenderWidgetHost instance about the fact that the page is
+// loading, or done loading and calls the base implementation.
+void WebContentsImpl::SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details) {
+ if (is_loading == is_loading_)
+ return;
+
+ if (!is_loading) {
+ load_state_ = net::LoadStateWithParam(net::LOAD_STATE_IDLE, string16());
+ load_state_host_.clear();
+ upload_size_ = 0;
+ upload_position_ = 0;
+ }
+
+ render_manager_.SetIsLoading(is_loading);
+
+ is_loading_ = is_loading;
+ waiting_for_response_ = is_loading;
+
+ if (delegate_)
+ delegate_->LoadingStateChanged(this);
+ NotifyNavigationStateChanged(INVALIDATE_TYPE_LOAD);
+
+ if (is_loading)
+ TRACE_EVENT_ASYNC_BEGIN0("browser", "WebContentsImpl Loading", this);
+ else
+ TRACE_EVENT_ASYNC_END0("browser", "WebContentsImpl Loading", this);
+ int type = is_loading ? NOTIFICATION_LOAD_START : NOTIFICATION_LOAD_STOP;
+ NotificationDetails det = NotificationService::NoDetails();
+ if (details)
+ det = Details<LoadNotificationDetails>(details);
+ NotificationService::current()->Notify(
+ type, Source<NavigationController>(&controller_), det);
+}
+
+void WebContentsImpl::DidNavigateMainFramePostCommit(
+ const LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (details.is_navigation_to_different_page()) {
+ // Clear the status bubble. This is a workaround for a bug where WebKit
+ // doesn't let us know that the cursor left an element during a
+ // transition (this is also why the mouse cursor remains as a hand after
+ // clicking on a link); see bugs 1184641 and 980803. We don't want to
+ // clear the bubble when a user navigates to a named anchor in the same
+ // page.
+ UpdateTargetURL(details.entry->GetPageID(), GURL());
+ }
+
+ if (!details.is_in_page) {
+ // Once the main frame is navigated, we're no longer considered to have
+ // displayed insecure content.
+ displayed_insecure_content_ = false;
+ SSLManager::NotifySSLInternalStateChanged(
+ GetController().GetBrowserContext());
+ }
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidNavigateMainFrame(details, params));
+}
+
+void WebContentsImpl::DidNavigateAnyFramePostCommit(
+ RenderViewHost* render_view_host,
+ const LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // If we navigate off the page, close all JavaScript dialogs.
+ if (dialog_manager_ && !details.is_in_page)
+ dialog_manager_->CancelActiveAndPendingDialogs(this);
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidNavigateAnyFrame(details, params));
+}
+
+bool WebContentsImpl::ShouldAssignSiteForURL(const GURL& url) {
+ // about:blank should not "use up" a new SiteInstance. The SiteInstance can
+ // still be used for a normal web site.
+ if (url == GURL(kAboutBlankURL))
+ return false;
+
+ // The embedder will then have the opportunity to determine if the URL
+ // should "use up" the SiteInstance.
+ return GetContentClient()->browser()->ShouldAssignSiteForURL(url);
+}
+
+void WebContentsImpl::UpdateMaxPageIDIfNecessary(RenderViewHost* rvh) {
+ // If we are creating a RVH for a restored controller, then we need to make
+ // sure the RenderView starts with a next_page_id_ larger than the number
+ // of restored entries. This must be called before the RenderView starts
+ // navigating (to avoid a race between the browser updating max_page_id and
+ // the renderer updating next_page_id_). Because of this, we only call this
+ // from CreateRenderView and allow that to notify the RenderView for us.
+ int max_restored_page_id = controller_.GetMaxRestoredPageID();
+ if (max_restored_page_id >
+ GetMaxPageIDForSiteInstance(rvh->GetSiteInstance()))
+ UpdateMaxPageIDForSiteInstance(rvh->GetSiteInstance(),
+ max_restored_page_id);
+}
+
+bool WebContentsImpl::UpdateTitleForEntry(NavigationEntryImpl* entry,
+ const string16& title) {
+ // For file URLs without a title, use the pathname instead. In the case of a
+ // synthesized title, we don't want the update to count toward the "one set
+ // per page of the title to history."
+ string16 final_title;
+ bool explicit_set;
+ if (entry && entry->GetURL().SchemeIsFile() && title.empty()) {
+ final_title = UTF8ToUTF16(entry->GetURL().ExtractFileName());
+ explicit_set = false; // Don't count synthetic titles toward the set limit.
+ } else {
+ TrimWhitespace(title, TRIM_ALL, &final_title);
+ explicit_set = true;
+ }
+
+ // If a page is created via window.open and never navigated,
+ // there will be no navigation entry. In this situation,
+ // |page_title_when_no_navigation_entry_| will be used for page title.
+ if (entry) {
+ if (final_title == entry->GetTitle())
+ return false; // Nothing changed, don't bother.
+
+ entry->SetTitle(final_title);
+ } else {
+ if (page_title_when_no_navigation_entry_ == final_title)
+ return false; // Nothing changed, don't bother.
+
+ page_title_when_no_navigation_entry_ = final_title;
+ }
+
+ // Lastly, set the title for the view.
+ view_->SetPageTitle(final_title);
+
+ std::pair<NavigationEntry*, bool> details =
+ std::make_pair(entry, explicit_set);
+
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(this),
+ Details<std::pair<NavigationEntry*, bool> >(&details));
+
+ return true;
+}
+
+void WebContentsImpl::NotifySwapped(RenderViewHost* old_render_view_host) {
+ // After sending out a swap notification, we need to send a disconnect
+ // notification so that clients that pick up a pointer to |this| can NULL the
+ // pointer. See Bug 1230284.
+ notify_disconnection_ = true;
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_SWAPPED,
+ Source<WebContents>(this),
+ Details<RenderViewHost>(old_render_view_host));
+
+ // Ensure that the associated embedder gets cleared after a RenderViewHost
+ // gets swapped, so we don't reuse the same embedder next time a
+ // RenderViewHost is attached to this WebContents.
+ RemoveBrowserPluginEmbedder();
+}
+
+void WebContentsImpl::NotifyConnected() {
+ notify_disconnection_ = true;
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_CONNECTED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+}
+
+void WebContentsImpl::NotifyDisconnected() {
+ if (!notify_disconnection_)
+ return;
+
+ notify_disconnection_ = false;
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ Source<WebContents>(this),
+ NotificationService::NoDetails());
+}
+
+void WebContentsImpl::NotifyNavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) {
+ FOR_EACH_OBSERVER(
+ WebContentsObserver, observers_, NavigationEntryCommitted(load_details));
+}
+
+RenderViewHostDelegateView* WebContentsImpl::GetDelegateView() {
+ return render_view_host_delegate_view_;
+}
+
+RenderViewHostDelegate::RendererManagement*
+WebContentsImpl::GetRendererManagementDelegate() {
+ return &render_manager_;
+}
+
+RendererPreferences WebContentsImpl::GetRendererPrefs(
+ BrowserContext* browser_context) const {
+ return renderer_preferences_;
+}
+
+WebContents* WebContentsImpl::GetAsWebContents() {
+ return this;
+}
+
+gfx::Rect WebContentsImpl::GetRootWindowResizerRect() const {
+ if (delegate_)
+ return delegate_->GetRootWindowResizerRect();
+ return gfx::Rect();
+}
+
+void WebContentsImpl::RemoveBrowserPluginEmbedder() {
+ if (browser_plugin_embedder_)
+ browser_plugin_embedder_.reset();
+}
+
+void WebContentsImpl::RenderViewCreated(RenderViewHost* render_view_host) {
+ // Don't send notifications if we are just creating a swapped-out RVH for
+ // the opener chain. These won't be used for view-source or WebUI, so it's
+ // ok to return early.
+ if (static_cast<RenderViewHostImpl*>(render_view_host)->is_swapped_out())
+ return;
+
+ if (delegate_)
+ view_->SetOverscrollControllerEnabled(delegate_->CanOverscrollContent());
+
+ NotificationService::current()->Notify(
+ NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
+ Source<WebContents>(this),
+ Details<RenderViewHost>(render_view_host));
+ NavigationEntry* entry = controller_.GetActiveEntry();
+ if (!entry)
+ return;
+
+ // When we're creating views, we're still doing initial setup, so we always
+ // use the pending Web UI rather than any possibly existing committed one.
+ if (render_manager_.pending_web_ui())
+ render_manager_.pending_web_ui()->RenderViewCreated(render_view_host);
+
+ if (entry->IsViewSourceMode()) {
+ // Put the renderer in view source mode.
+ render_view_host->Send(
+ new ViewMsg_EnableViewSourceMode(render_view_host->GetRoutingID()));
+ }
+
+ view_->RenderViewCreated(render_view_host);
+
+ FOR_EACH_OBSERVER(
+ WebContentsObserver, observers_, RenderViewCreated(render_view_host));
+}
+
+void WebContentsImpl::RenderViewReady(RenderViewHost* rvh) {
+ if (rvh != GetRenderViewHost()) {
+ // Don't notify the world, since this came from a renderer in the
+ // background.
+ return;
+ }
+
+ NotifyConnected();
+ bool was_crashed = IsCrashed();
+ SetIsCrashed(base::TERMINATION_STATUS_STILL_RUNNING, 0);
+
+ // Restore the focus to the tab (otherwise the focus will be on the top
+ // window).
+ if (was_crashed && !FocusLocationBarByDefault() &&
+ (!delegate_ || delegate_->ShouldFocusPageAfterCrash())) {
+ view_->Focus();
+ }
+
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, RenderViewReady());
+}
+
+void WebContentsImpl::RenderViewTerminated(RenderViewHost* rvh,
+ base::TerminationStatus status,
+ int error_code) {
+ if (rvh != GetRenderViewHost()) {
+ // The pending page's RenderViewHost is gone.
+ return;
+ }
+
+ ClearPowerSaveBlockers(rvh);
+ SetIsLoading(false, NULL);
+ NotifyDisconnected();
+ SetIsCrashed(status, error_code);
+ GetView()->OnTabCrashed(GetCrashedStatus(), crashed_error_code_);
+
+ FOR_EACH_OBSERVER(WebContentsObserver,
+ observers_,
+ RenderProcessGone(GetCrashedStatus()));
+}
+
+void WebContentsImpl::RenderViewDeleted(RenderViewHost* rvh) {
+ ClearPowerSaveBlockers(rvh);
+ render_manager_.RenderViewDeleted(rvh);
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, RenderViewDeleted(rvh));
+}
+
+void WebContentsImpl::DidNavigate(
+ RenderViewHost* rvh,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // If we don't have a frame tree root yet, this is the first navigation in
+ // using the current RenderViewHost, so we need to create it with the proper
+ // frame id.
+ if (!frame_tree_root_.get()) {
+ DCHECK(PageTransitionIsMainFrame(params.transition));
+ frame_tree_root_.reset(new FrameTreeNode(params.frame_id, std::string()));
+ }
+
+ if (PageTransitionIsMainFrame(params.transition)) {
+ // When overscroll navigation gesture is enabled, a screenshot of the page
+ // in its current state is taken so that it can be used during the
+ // nav-gesture. It is necessary to take the screenshot here, before calling
+ // RenderViewHostManager::DidNavigateMainFrame, because that can change
+ // WebContents::GetRenderViewHost to return the new host, instead of the one
+ // that may have just been swapped out.
+ if (delegate_ && delegate_->CanOverscrollContent())
+ controller_.TakeScreenshot();
+
+ render_manager_.DidNavigateMainFrame(rvh);
+ }
+
+ // We expect to have a valid frame tree root node at all times when
+ // navigating.
+ DCHECK(frame_tree_root_.get());
+
+ // Update the site of the SiteInstance if it doesn't have one yet, unless
+ // assigning a site is not necessary for this URL. In that case, the
+ // SiteInstance can still be considered unused until a navigation to a real
+ // page.
+ if (!static_cast<SiteInstanceImpl*>(GetSiteInstance())->HasSite() &&
+ ShouldAssignSiteForURL(params.url)) {
+ static_cast<SiteInstanceImpl*>(GetSiteInstance())->SetSite(params.url);
+ }
+
+ // Need to update MIME type here because it's referred to in
+ // UpdateNavigationCommands() called by RendererDidNavigate() to
+ // determine whether or not to enable the encoding menu.
+ // It's updated only for the main frame. For a subframe,
+ // RenderView::UpdateURL does not set params.contents_mime_type.
+ // (see http://code.google.com/p/chromium/issues/detail?id=2929 )
+ // TODO(jungshik): Add a test for the encoding menu to avoid
+ // regressing it again.
+ if (PageTransitionIsMainFrame(params.transition))
+ contents_mime_type_ = params.contents_mime_type;
+
+ LoadCommittedDetails details;
+ bool did_navigate = controller_.RendererDidNavigate(params, &details);
+
+ // For now, keep track of each frame's URL in its FrameTreeNode. This lets
+ // us estimate our process count for implementing OOP iframes.
+ // TODO(creis): Remove this when we track which pages commit in each frame.
+ FrameTreeNode* node = FindFrameTreeNodeByID(params.frame_id);
+ if (node)
+ node->set_current_url(params.url);
+
+ // Send notification about committed provisional loads. This notification is
+ // different from the NAV_ENTRY_COMMITTED notification which doesn't include
+ // the actual URL navigated to and isn't sent for AUTO_SUBFRAME navigations.
+ if (details.type != NAVIGATION_TYPE_NAV_IGNORE) {
+ // For AUTO_SUBFRAME navigations, an event for the main frame is generated
+ // that is not recorded in the navigation history. For the purpose of
+ // tracking navigation events, we treat this event as a sub frame navigation
+ // event.
+ bool is_main_frame = did_navigate ? details.is_main_frame : false;
+ PageTransition transition_type = params.transition;
+ // Whether or not a page transition was triggered by going backward or
+ // forward in the history is only stored in the navigation controller's
+ // entry list.
+ if (did_navigate &&
+ (controller_.GetActiveEntry()->GetTransitionType() &
+ PAGE_TRANSITION_FORWARD_BACK)) {
+ transition_type = PageTransitionFromInt(
+ params.transition | PAGE_TRANSITION_FORWARD_BACK);
+ }
+ // Notify observers about the commit of the provisional load.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidCommitProvisionalLoadForFrame(params.frame_id,
+ is_main_frame, params.url, transition_type, rvh));
+ }
+
+ if (!did_navigate)
+ return; // No navigation happened.
+
+ // DO NOT ADD MORE STUFF TO THIS FUNCTION! Your component should either listen
+ // for the appropriate notification (best) or you can add it to
+ // DidNavigateMainFramePostCommit / DidNavigateAnyFramePostCommit (only if
+ // necessary, please).
+
+ // Run post-commit tasks.
+ if (details.is_main_frame) {
+ DidNavigateMainFramePostCommit(details, params);
+ if (delegate_) {
+ delegate_->DidNavigateMainFramePostCommit(this);
+ view_->SetOverscrollControllerEnabled(delegate_->CanOverscrollContent());
+ }
+ }
+ DidNavigateAnyFramePostCommit(rvh, details, params);
+}
+
+void WebContentsImpl::UpdateState(RenderViewHost* rvh,
+ int32 page_id,
+ const PageState& page_state) {
+ // Ensure that this state update comes from either the active RVH or one of
+ // the swapped out RVHs. We don't expect to hear from any other RVHs.
+ DCHECK(rvh == GetRenderViewHost() || render_manager_.IsOnSwappedOutList(rvh));
+
+ // We must be prepared to handle state updates for any page, these occur
+ // when the user is scrolling and entering form data, as well as when we're
+ // leaving a page, in which case our state may have already been moved to
+ // the next page. The navigation controller will look up the appropriate
+ // NavigationEntry and update it when it is notified via the delegate.
+
+ int entry_index = controller_.GetEntryIndexWithPageID(
+ rvh->GetSiteInstance(), page_id);
+ if (entry_index < 0)
+ return;
+ NavigationEntry* entry = controller_.GetEntryAtIndex(entry_index);
+
+ if (page_state == entry->GetPageState())
+ return; // Nothing to update.
+ entry->SetPageState(page_state);
+ controller_.NotifyEntryChanged(entry, entry_index);
+}
+
+void WebContentsImpl::UpdateTitle(RenderViewHost* rvh,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) {
+ // If we have a title, that's a pretty good indication that we've started
+ // getting useful data.
+ SetNotWaitingForResponse();
+
+ // Try to find the navigation entry, which might not be the current one.
+ // For example, it might be from a pending RVH for the pending entry.
+ NavigationEntryImpl* entry = controller_.GetEntryWithPageID(
+ rvh->GetSiteInstance(), page_id);
+
+ // We can handle title updates when we don't have an entry in
+ // UpdateTitleForEntry, but only if the update is from the current RVH.
+ if (!entry && rvh != GetRenderViewHost())
+ return;
+
+ // TODO(evan): make use of title_direction.
+ // http://code.google.com/p/chromium/issues/detail?id=27094
+ if (!UpdateTitleForEntry(entry, title))
+ return;
+
+ // Broadcast notifications when the UI should be updated.
+ if (entry == controller_.GetEntryAtOffset(0))
+ NotifyNavigationStateChanged(INVALIDATE_TYPE_TITLE);
+}
+
+void WebContentsImpl::UpdateEncoding(RenderViewHost* render_view_host,
+ const std::string& encoding) {
+ SetEncoding(encoding);
+}
+
+void WebContentsImpl::UpdateTargetURL(int32 page_id, const GURL& url) {
+ if (delegate_)
+ delegate_->UpdateTargetURL(this, page_id, url);
+}
+
+void WebContentsImpl::Close(RenderViewHost* rvh) {
+#if defined(OS_MACOSX)
+ // The UI may be in an event-tracking loop, such as between the
+ // mouse-down and mouse-up in text selection or a button click.
+ // Defer the close until after tracking is complete, so that we
+ // don't free objects out from under the UI.
+ // TODO(shess): This could get more fine-grained. For instance,
+ // closing a tab in another window while selecting text in the
+ // current window's Omnibox should be just fine.
+ if (view_->IsEventTracking()) {
+ view_->CloseTabAfterEventTracking();
+ return;
+ }
+#endif
+
+ // Ignore this if it comes from a RenderViewHost that we aren't showing.
+ if (delegate_ && rvh == GetRenderViewHost())
+ delegate_->CloseContents(this);
+}
+
+void WebContentsImpl::SwappedOut(RenderViewHost* rvh) {
+ if (delegate_ && rvh == GetRenderViewHost())
+ delegate_->SwappedOut(this);
+
+ // Allow the navigation to proceed.
+ render_manager_.SwappedOut(rvh);
+}
+
+void WebContentsImpl::RequestMove(const gfx::Rect& new_bounds) {
+ if (delegate_ && delegate_->IsPopupOrPanel(this))
+ delegate_->MoveContents(this, new_bounds);
+}
+
+void WebContentsImpl::DidStartLoading(RenderViewHost* render_view_host) {
+ SetIsLoading(true, NULL);
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidStartLoading(render_view_host));
+}
+
+void WebContentsImpl::DidStopLoading(RenderViewHost* render_view_host) {
+ scoped_ptr<LoadNotificationDetails> details;
+
+ // Use the last committed entry rather than the active one, in case a
+ // pending entry has been created.
+ NavigationEntry* entry = controller_.GetLastCommittedEntry();
+
+ // An entry may not exist for a stop when loading an initial blank page or
+ // if an iframe injected by script into a blank page finishes loading.
+ if (entry) {
+ base::TimeDelta elapsed = base::TimeTicks::Now() - current_load_start_;
+
+ details.reset(new LoadNotificationDetails(
+ entry->GetVirtualURL(),
+ entry->GetTransitionType(),
+ elapsed,
+ &controller_,
+ controller_.GetCurrentEntryIndex()));
+ }
+
+ SetIsLoading(false, details.get());
+
+ // Notify observers about navigation.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidStopLoading(render_view_host));
+}
+
+void WebContentsImpl::DidCancelLoading() {
+ controller_.DiscardNonCommittedEntries();
+
+ // Update the URL display.
+ NotifyNavigationStateChanged(INVALIDATE_TYPE_URL);
+}
+
+void WebContentsImpl::DidChangeLoadProgress(double progress) {
+#if defined(OS_ANDROID)
+ if (delegate_)
+ delegate_->LoadProgressChanged(this, progress);
+#endif
+}
+
+void WebContentsImpl::DidDisownOpener(RenderViewHost* rvh) {
+ if (opener_) {
+ // Clear our opener so that future cross-process navigations don't have an
+ // opener assigned.
+ RemoveDestructionObserver(opener_);
+ opener_ = NULL;
+ }
+
+ // Notify all swapped out RenderViewHosts for this tab. This is important
+ // in case we go back to them, or if another window in those processes tries
+ // to access window.opener.
+ render_manager_.DidDisownOpener(rvh);
+}
+
+void WebContentsImpl::DidAccessInitialDocument() {
+ // Update the URL display.
+ NotifyNavigationStateChanged(content::INVALIDATE_TYPE_URL);
+}
+
+void WebContentsImpl::DocumentAvailableInMainFrame(
+ RenderViewHost* render_view_host) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DocumentAvailableInMainFrame());
+}
+
+void WebContentsImpl::DocumentOnLoadCompletedInMainFrame(
+ RenderViewHost* render_view_host,
+ int32 page_id) {
+ NotificationService::current()->Notify(
+ NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ Source<WebContents>(this),
+ Details<int>(&page_id));
+}
+
+void WebContentsImpl::RequestOpenURL(RenderViewHost* rvh,
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ bool should_replace_current_entry,
+ bool user_gesture) {
+ // If this came from a swapped out RenderViewHost, we only allow the request
+ // if we are still in the same BrowsingInstance.
+ if (static_cast<RenderViewHostImpl*>(rvh)->is_swapped_out() &&
+ !rvh->GetSiteInstance()->IsRelatedSiteInstance(GetSiteInstance())) {
+ return;
+ }
+
+ // Delegate to RequestTransferURL because this is just the generic
+ // case where |old_request_id| is empty.
+ RequestTransferURL(url, referrer, disposition, source_frame_id,
+ GlobalRequestID(),
+ should_replace_current_entry, user_gesture);
+}
+
+void WebContentsImpl::RequestTransferURL(
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ const GlobalRequestID& old_request_id,
+ bool should_replace_current_entry,
+ bool user_gesture) {
+ WebContents* new_contents = NULL;
+ PageTransition transition_type = PAGE_TRANSITION_LINK;
+ if (render_manager_.web_ui()) {
+ // When we're a Web UI, it will provide a page transition type for us (this
+ // is so the new tab page can specify AUTO_BOOKMARK for automatically
+ // generated suggestions).
+ //
+ // Note also that we hide the referrer for Web UI pages. We don't really
+ // want web sites to see a referrer of "chrome://blah" (and some
+ // chrome: URLs might have search terms or other stuff we don't want to
+ // send to the site), so we send no referrer.
+ OpenURLParams params(url, Referrer(), source_frame_id, disposition,
+ render_manager_.web_ui()->GetLinkTransitionType(),
+ false /* is_renderer_initiated */);
+ params.transferred_global_request_id = old_request_id;
+ new_contents = OpenURL(params);
+ transition_type = render_manager_.web_ui()->GetLinkTransitionType();
+ } else {
+ OpenURLParams params(url, referrer, source_frame_id, disposition,
+ PAGE_TRANSITION_LINK, true /* is_renderer_initiated */);
+ params.transferred_global_request_id = old_request_id;
+ params.should_replace_current_entry = should_replace_current_entry;
+ params.user_gesture = user_gesture;
+ new_contents = OpenURL(params);
+ }
+ if (new_contents) {
+ // Notify observers.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ DidOpenRequestedURL(new_contents,
+ url,
+ referrer,
+ disposition,
+ transition_type,
+ source_frame_id));
+ }
+}
+
+void WebContentsImpl::RouteCloseEvent(RenderViewHost* rvh) {
+ // Tell the active RenderViewHost to run unload handlers and close, as long
+ // as the request came from a RenderViewHost in the same BrowsingInstance.
+ // In most cases, we receive this from a swapped out RenderViewHost.
+ // It is possible to receive it from one that has just been swapped in,
+ // in which case we might as well deliver the message anyway.
+ if (rvh->GetSiteInstance()->IsRelatedSiteInstance(GetSiteInstance()))
+ GetRenderViewHost()->ClosePage();
+}
+
+void WebContentsImpl::RouteMessageEvent(
+ RenderViewHost* rvh,
+ const ViewMsg_PostMessage_Params& params) {
+ // Only deliver the message to the active RenderViewHost if the request
+ // came from a RenderViewHost in the same BrowsingInstance or if this
+ // WebContents is dedicated to a browser plugin guest.
+ // Note: This check means that an embedder could theoretically receive a
+ // postMessage from anyone (not just its own guests). However, this is
+ // probably not a risk for apps since other pages won't have references
+ // to App windows.
+ if (!rvh->GetSiteInstance()->IsRelatedSiteInstance(GetSiteInstance()) &&
+ !GetBrowserPluginGuest() && !GetBrowserPluginEmbedder())
+ return;
+
+ ViewMsg_PostMessage_Params new_params(params);
+
+ // If there is a source_routing_id, translate it to the routing ID for
+ // the equivalent swapped out RVH in the target process. If we need
+ // to create a swapped out RVH for the source tab, we create its opener
+ // chain as well, since those will also be accessible to the target page.
+ if (new_params.source_routing_id != MSG_ROUTING_NONE) {
+ // Try to look up the WebContents for the source page.
+ WebContentsImpl* source_contents = NULL;
+ RenderViewHostImpl* source_rvh = RenderViewHostImpl::FromID(
+ rvh->GetProcess()->GetID(), params.source_routing_id);
+ if (source_rvh) {
+ source_contents = static_cast<WebContentsImpl*>(
+ source_rvh->GetDelegate()->GetAsWebContents());
+ }
+
+ if (source_contents) {
+ if (GetBrowserPluginGuest()) {
+ // We create a swapped out RenderView for the embedder in the guest's
+ // render process but we intentionally do not expose the embedder's
+ // opener chain to it.
+ new_params.source_routing_id =
+ source_contents->CreateSwappedOutRenderView(GetSiteInstance());
+ } else {
+ new_params.source_routing_id =
+ source_contents->CreateOpenerRenderViews(GetSiteInstance());
+ }
+ } else {
+ // We couldn't find it, so don't pass a source frame.
+ new_params.source_routing_id = MSG_ROUTING_NONE;
+ }
+ }
+
+ // In most cases, we receive this from a swapped out RenderViewHost.
+ // It is possible to receive it from one that has just been swapped in,
+ // in which case we might as well deliver the message anyway.
+ Send(new ViewMsg_PostMessageEvent(GetRoutingID(), new_params));
+}
+
+void WebContentsImpl::RunJavaScriptMessage(
+ RenderViewHost* rvh,
+ const string16& message,
+ const string16& default_prompt,
+ const GURL& frame_url,
+ JavaScriptMessageType javascript_message_type,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message) {
+ // Suppress JavaScript dialogs when requested. Also suppress messages when
+ // showing an interstitial as it's shown over the previous page and we don't
+ // want the hidden page's dialogs to interfere with the interstitial.
+ bool suppress_this_message =
+ static_cast<RenderViewHostImpl*>(rvh)->is_swapped_out() ||
+ ShowingInterstitialPage() ||
+ !delegate_ ||
+ delegate_->ShouldSuppressDialogs() ||
+ !delegate_->GetJavaScriptDialogManager();
+
+ if (!suppress_this_message) {
+ std::string accept_lang = GetContentClient()->browser()->
+ GetAcceptLangs(GetBrowserContext());
+ dialog_manager_ = delegate_->GetJavaScriptDialogManager();
+ dialog_manager_->RunJavaScriptDialog(
+ this,
+ frame_url.GetOrigin(),
+ accept_lang,
+ javascript_message_type,
+ message,
+ default_prompt,
+ base::Bind(&WebContentsImpl::OnDialogClosed,
+ base::Unretained(this),
+ rvh,
+ reply_msg),
+ &suppress_this_message);
+ }
+
+ if (suppress_this_message) {
+ // If we are suppressing messages, just reply as if the user immediately
+ // pressed "Cancel".
+ OnDialogClosed(rvh, reply_msg, false, string16());
+ }
+
+ *did_suppress_message = suppress_this_message;
+}
+
+void WebContentsImpl::RunBeforeUnloadConfirm(RenderViewHost* rvh,
+ const string16& message,
+ bool is_reload,
+ IPC::Message* reply_msg) {
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(rvh);
+ if (delegate_)
+ delegate_->WillRunBeforeUnloadConfirm();
+
+ bool suppress_this_message =
+ rvhi->is_swapped_out() ||
+ !delegate_ ||
+ delegate_->ShouldSuppressDialogs() ||
+ !delegate_->GetJavaScriptDialogManager();
+ if (suppress_this_message) {
+ // The reply must be sent to the RVH that sent the request.
+ rvhi->JavaScriptDialogClosed(reply_msg, true, string16());
+ return;
+ }
+
+ is_showing_before_unload_dialog_ = true;
+ dialog_manager_ = delegate_->GetJavaScriptDialogManager();
+ dialog_manager_->RunBeforeUnloadDialog(
+ this, message, is_reload,
+ base::Bind(&WebContentsImpl::OnDialogClosed, base::Unretained(this), rvh,
+ reply_msg));
+}
+
+bool WebContentsImpl::AddMessageToConsole(int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id) {
+ if (!delegate_)
+ return false;
+ return delegate_->AddMessageToConsole(this, level, message, line_no,
+ source_id);
+}
+
+WebPreferences WebContentsImpl::GetWebkitPrefs() {
+ // We want to base the page config off of the real URL, rather than the
+ // display URL.
+ GURL url = controller_.GetActiveEntry()
+ ? controller_.GetActiveEntry()->GetURL() : GURL::EmptyGURL();
+ return GetWebkitPrefs(GetRenderViewHost(), url);
+}
+
+int WebContentsImpl::CreateSwappedOutRenderView(
+ SiteInstance* instance) {
+ return render_manager_.CreateRenderView(instance, MSG_ROUTING_NONE, true);
+}
+
+void WebContentsImpl::OnUserGesture() {
+ // Notify observers.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, DidGetUserGesture());
+
+ ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get();
+ if (rdh) // NULL in unittests.
+ rdh->OnUserGesture(this);
+}
+
+void WebContentsImpl::OnIgnoredUIEvent() {
+ // Notify observers.
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_, DidGetIgnoredUIEvent());
+}
+
+void WebContentsImpl::RendererUnresponsive(RenderViewHost* rvh,
+ bool is_during_unload) {
+ // Don't show hung renderer dialog for a swapped out RVH.
+ if (rvh != GetRenderViewHost())
+ return;
+
+ RenderViewHostImpl* rvhi = static_cast<RenderViewHostImpl*>(rvh);
+
+ // Ignore renderer unresponsive event if debugger is attached to the tab
+ // since the event may be a result of the renderer sitting on a breakpoint.
+ // See http://crbug.com/65458
+ if (DevToolsAgentHost::IsDebuggerAttached(this))
+ return;
+
+ if (is_during_unload) {
+ // Hang occurred while firing the beforeunload/unload handler.
+ // Pretend the handler fired so tab closing continues as if it had.
+ rvhi->set_sudden_termination_allowed(true);
+
+ if (!render_manager_.ShouldCloseTabOnUnresponsiveRenderer())
+ return;
+
+ // If the tab hangs in the beforeunload/unload handler there's really
+ // nothing we can do to recover. Pretend the unload listeners have
+ // all fired and close the tab. If the hang is in the beforeunload handler
+ // then the user will not have the option of cancelling the close.
+ Close(rvh);
+ return;
+ }
+
+ if (!GetRenderViewHostImpl() || !GetRenderViewHostImpl()->IsRenderViewLive())
+ return;
+
+ if (delegate_)
+ delegate_->RendererUnresponsive(this);
+}
+
+void WebContentsImpl::RendererResponsive(RenderViewHost* render_view_host) {
+ if (delegate_)
+ delegate_->RendererResponsive(this);
+}
+
+void WebContentsImpl::LoadStateChanged(
+ const GURL& url,
+ const net::LoadStateWithParam& load_state,
+ uint64 upload_position,
+ uint64 upload_size) {
+ load_state_ = load_state;
+ upload_position_ = upload_position;
+ upload_size_ = upload_size;
+ load_state_host_ = net::IDNToUnicode(url.host(),
+ GetContentClient()->browser()->GetAcceptLangs(
+ GetBrowserContext()));
+ if (load_state_.state == net::LOAD_STATE_READING_RESPONSE)
+ SetNotWaitingForResponse();
+ if (IsLoading()) {
+ NotifyNavigationStateChanged(INVALIDATE_TYPE_LOAD | INVALIDATE_TYPE_TAB);
+ }
+}
+
+void WebContentsImpl::WorkerCrashed() {
+ if (delegate_)
+ delegate_->WorkerCrashed(this);
+}
+
+void WebContentsImpl::BeforeUnloadFiredFromRenderManager(
+ bool proceed, const base::TimeTicks& proceed_time,
+ bool* proceed_to_fire_unload) {
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ BeforeUnloadFired(proceed_time));
+ if (delegate_)
+ delegate_->BeforeUnloadFired(this, proceed, proceed_to_fire_unload);
+ // Note: |this| might be deleted at this point.
+}
+
+void WebContentsImpl::RenderProcessGoneFromRenderManager(
+ RenderViewHost* render_view_host) {
+ DCHECK(crashed_status_ != base::TERMINATION_STATUS_STILL_RUNNING);
+ RenderViewTerminated(render_view_host, crashed_status_, crashed_error_code_);
+}
+
+void WebContentsImpl::UpdateRenderViewSizeForRenderManager() {
+ // TODO(brettw) this is a hack. See WebContentsView::SizeContents.
+ gfx::Size size = view_->GetContainerSize();
+ // 0x0 isn't a valid window size (minimal window size is 1x1) but it may be
+ // here during container initialization and normal window size will be set
+ // later. In case of tab duplication this resizing to 0x0 prevents setting
+ // normal size later so just ignore it.
+ if (!size.IsEmpty())
+ view_->SizeContents(size);
+}
+
+void WebContentsImpl::NotifySwappedFromRenderManager(RenderViewHost* rvh) {
+ NotifySwapped(rvh);
+
+ // Make sure the visible RVH reflects the new delegate's preferences.
+ if (delegate_)
+ view_->SetOverscrollControllerEnabled(delegate_->CanOverscrollContent());
+
+ view_->RenderViewSwappedIn(render_manager_.current_host());
+
+ FrameTreeNode* root = NULL;
+ RenderViewHostImpl* new_rvh = static_cast<RenderViewHostImpl*>(
+ render_manager_.current_host());
+
+ // We are doing a cross-site navigation and swapping processes. Since frame
+ // ids are unique to a process, we need to recreate the frame tree with the
+ // proper main frame id.
+ // Note that it is possible for this method to be called before the new RVH
+ // has committed a navigation (if RenderViewHostManager short-circuits the
+ // CommitPending call because the current RVH is dead). In that case, we
+ // haven't heard a valid frame id to use to initialize the root node, so clear
+ // out the root node and the first subsequent navigation message will set it
+ // correctly.
+ if (new_rvh->main_frame_id() != -1)
+ root = new FrameTreeNode(new_rvh->main_frame_id(), std::string());
+
+ frame_tree_root_.reset(root);
+}
+
+int WebContentsImpl::CreateOpenerRenderViewsForRenderManager(
+ SiteInstance* instance) {
+ if (!opener_)
+ return MSG_ROUTING_NONE;
+
+ // Recursively create RenderViews for anything else in the opener chain.
+ return opener_->CreateOpenerRenderViews(instance);
+}
+
+int WebContentsImpl::CreateOpenerRenderViews(SiteInstance* instance) {
+ int opener_route_id = MSG_ROUTING_NONE;
+
+ // If this tab has an opener, ensure it has a RenderView in the given
+ // SiteInstance as well.
+ if (opener_)
+ opener_route_id = opener_->CreateOpenerRenderViews(instance);
+
+ // If any of the renderers (current, pending, or swapped out) for this
+ // WebContents has the same SiteInstance, use it.
+ if (render_manager_.current_host()->GetSiteInstance() == instance)
+ return render_manager_.current_host()->GetRoutingID();
+
+ if (render_manager_.pending_render_view_host() &&
+ render_manager_.pending_render_view_host()->GetSiteInstance() == instance)
+ return render_manager_.pending_render_view_host()->GetRoutingID();
+
+ RenderViewHostImpl* rvh = render_manager_.GetSwappedOutRenderViewHost(
+ instance);
+ if (rvh)
+ return rvh->GetRoutingID();
+
+ // Create a swapped out RenderView in the given SiteInstance if none exists,
+ // setting its opener to the given route_id. Return the new view's route_id.
+ return render_manager_.CreateRenderView(instance, opener_route_id, true);
+}
+
+NavigationControllerImpl& WebContentsImpl::GetControllerForRenderManager() {
+ return GetController();
+}
+
+WebUIImpl* WebContentsImpl::CreateWebUIForRenderManager(const GURL& url) {
+ return static_cast<WebUIImpl*>(CreateWebUI(url));
+}
+
+NavigationEntry*
+ WebContentsImpl::GetLastCommittedNavigationEntryForRenderManager() {
+ return controller_.GetLastCommittedEntry();
+}
+
+bool WebContentsImpl::CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host, int opener_route_id) {
+ TRACE_EVENT0("browser", "WebContentsImpl::CreateRenderViewForRenderManager");
+ // Can be NULL during tests.
+ RenderWidgetHostView* rwh_view = view_->CreateViewForWidget(render_view_host);
+
+ // Now that the RenderView has been created, we need to tell it its size.
+ if (rwh_view)
+ rwh_view->SetSize(view_->GetContainerSize());
+
+ // Make sure we use the correct starting page_id in the new RenderView.
+ UpdateMaxPageIDIfNecessary(render_view_host);
+ int32 max_page_id =
+ GetMaxPageIDForSiteInstance(render_view_host->GetSiteInstance());
+
+ if (!static_cast<RenderViewHostImpl*>(
+ render_view_host)->CreateRenderView(string16(),
+ opener_route_id,
+ max_page_id)) {
+ return false;
+ }
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+ // Force a ViewMsg_Resize to be sent, needed to make plugins show up on
+ // linux. See crbug.com/83941.
+ if (rwh_view) {
+ if (RenderWidgetHost* render_widget_host = rwh_view->GetRenderWidgetHost())
+ render_widget_host->WasResized();
+ }
+#endif
+
+ return true;
+}
+
+void WebContentsImpl::OnDialogClosed(RenderViewHost* rvh,
+ IPC::Message* reply_msg,
+ bool success,
+ const string16& user_input) {
+ if (is_showing_before_unload_dialog_ && !success) {
+ // If a beforeunload dialog is canceled, we need to stop the throbber from
+ // spinning, since we forced it to start spinning in Navigate.
+ DidStopLoading(rvh);
+ controller_.DiscardNonCommittedEntries();
+
+ FOR_EACH_OBSERVER(WebContentsObserver, observers_,
+ BeforeUnloadDialogCancelled());
+ }
+ is_showing_before_unload_dialog_ = false;
+ static_cast<RenderViewHostImpl*>(
+ rvh)->JavaScriptDialogClosed(reply_msg, success, user_input);
+}
+
+void WebContentsImpl::SetEncoding(const std::string& encoding) {
+ encoding_ = GetContentClient()->browser()->
+ GetCanonicalEncodingNameByAliasName(encoding);
+}
+
+void WebContentsImpl::CreateViewAndSetSizeForRVH(RenderViewHost* rvh) {
+ RenderWidgetHostView* rwh_view = view_->CreateViewForWidget(rvh);
+ // Can be NULL during tests.
+ if (rwh_view)
+ rwh_view->SetSize(GetView()->GetContainerSize());
+}
+
+RenderViewHostImpl* WebContentsImpl::GetRenderViewHostImpl() {
+ return static_cast<RenderViewHostImpl*>(GetRenderViewHost());
+}
+
+BrowserPluginGuest* WebContentsImpl::GetBrowserPluginGuest() const {
+ return browser_plugin_guest_.get();
+}
+
+void WebContentsImpl::SetBrowserPluginGuest(BrowserPluginGuest* guest) {
+ CHECK(!browser_plugin_guest_);
+ browser_plugin_guest_.reset(guest);
+}
+
+BrowserPluginEmbedder* WebContentsImpl::GetBrowserPluginEmbedder() const {
+ return browser_plugin_embedder_.get();
+}
+
+BrowserPluginGuestManager*
+ WebContentsImpl::GetBrowserPluginGuestManager() const {
+ return static_cast<BrowserPluginGuestManager*>(
+ GetBrowserContext()->GetUserData(
+ browser_plugin::kBrowserPluginGuestManagerKeyName));
+}
+
+void WebContentsImpl::ClearPowerSaveBlockers(
+ RenderViewHost* render_view_host) {
+ STLDeleteValues(&power_save_blockers_[render_view_host]);
+ power_save_blockers_.erase(render_view_host);
+}
+
+void WebContentsImpl::ClearAllPowerSaveBlockers() {
+ for (PowerSaveBlockerMap::iterator i(power_save_blockers_.begin());
+ i != power_save_blockers_.end(); ++i)
+ STLDeleteValues(&power_save_blockers_[i->first]);
+ power_save_blockers_.clear();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_impl.h b/chromium/content/browser/web_contents/web_contents_impl.h
new file mode 100644
index 00000000000..21ec675da5f
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_impl.h
@@ -0,0 +1,963 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_IMPL_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_IMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/process/process.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/web_contents/frame_tree_node.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/render_view_host_manager.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/renderer_preferences.h"
+#include "content/public/common/three_d_api_types.h"
+#include "net/base/load_states.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d.h"
+#include "webkit/common/resource_type.h"
+
+struct BrowserPluginHostMsg_ResizeGuest_Params;
+struct ViewHostMsg_DateTimeDialogValue_Params;
+struct ViewMsg_PostMessage_Params;
+
+namespace content {
+class BrowserPluginEmbedder;
+class BrowserPluginGuest;
+class BrowserPluginGuestManager;
+class ColorChooser;
+class DateTimeChooserAndroid;
+class DownloadItem;
+class InterstitialPageImpl;
+class JavaBridgeDispatcherHostManager;
+class JavaScriptDialogManager;
+class PowerSaveBlocker;
+class RenderViewHost;
+class RenderViewHostDelegateView;
+class RenderViewHostImpl;
+class RenderWidgetHostImpl;
+class RenderWidgetHostViewPort;
+class SavePackage;
+class SessionStorageNamespaceImpl;
+class SiteInstance;
+class TestWebContents;
+class WebContentsDelegate;
+class WebContentsImpl;
+class WebContentsObserver;
+class WebContentsViewPort;
+class WebContentsViewDelegate;
+struct FaviconURL;
+struct LoadNotificationDetails;
+
+// Factory function for the implementations that content knows about. Takes
+// ownership of |delegate|.
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view);
+
+class CONTENT_EXPORT WebContentsImpl
+ : public NON_EXPORTED_BASE(WebContents),
+ public RenderViewHostDelegate,
+ public RenderWidgetHostDelegate,
+ public RenderViewHostManager::Delegate,
+ public NotificationObserver {
+ public:
+ virtual ~WebContentsImpl();
+
+ static WebContentsImpl* CreateWithOpener(
+ const WebContents::CreateParams& params,
+ WebContentsImpl* opener);
+
+ // Returns the opener WebContentsImpl, if any. This can be set to null if the
+ // opener is closed or the page clears its window.opener.
+ WebContentsImpl* opener() const { return opener_; }
+
+ // Creates a WebContents to be used as a browser plugin guest.
+ static BrowserPluginGuest* CreateGuest(
+ BrowserContext* browser_context,
+ content::SiteInstance* site_instance,
+ int guest_instance_id,
+ scoped_ptr<base::DictionaryValue> extra_params);
+
+ // Returns the content specific prefs for the given RVH.
+ static WebPreferences GetWebkitPrefs(
+ RenderViewHost* rvh, const GURL& url);
+
+ // Creates a swapped out RenderView. This is used by the browser plugin to
+ // create a swapped out RenderView in the embedder render process for the
+ // guest, to expose the guest's window object to the embedder.
+ // This returns the routing ID of the newly created swapped out RenderView.
+ int CreateSwappedOutRenderView(SiteInstance* instance);
+
+ // Complex initialization here. Specifically needed to avoid having
+ // members call back into our virtual functions in the constructor.
+ virtual void Init(const WebContents::CreateParams& params);
+
+ // Returns the SavePackage which manages the page saving job. May be NULL.
+ SavePackage* save_package() const { return save_package_.get(); }
+
+ // Updates the max page ID for the current SiteInstance in this
+ // WebContentsImpl to be at least |page_id|.
+ void UpdateMaxPageID(int32 page_id);
+
+ // Updates the max page ID for the given SiteInstance in this WebContentsImpl
+ // to be at least |page_id|.
+ void UpdateMaxPageIDForSiteInstance(SiteInstance* site_instance,
+ int32 page_id);
+
+ // Copy the current map of SiteInstance ID to max page ID from another tab.
+ // This is necessary when this tab adopts the NavigationEntries from
+ // |web_contents|.
+ void CopyMaxPageIDsFrom(WebContentsImpl* web_contents);
+
+ // Called by the NavigationController to cause the WebContentsImpl to navigate
+ // to the current pending entry. The NavigationController should be called
+ // back with RendererDidNavigate on success or DiscardPendingEntry on failure.
+ // The callbacks can be inside of this function, or at some future time.
+ //
+ // The entry has a PageID of -1 if newly created (corresponding to navigation
+ // to a new URL).
+ //
+ // If this method returns false, then the navigation is discarded (equivalent
+ // to calling DiscardPendingEntry on the NavigationController).
+ bool NavigateToPendingEntry(NavigationController::ReloadType reload_type);
+
+ // Called by InterstitialPageImpl when it creates a RenderViewHost.
+ void RenderViewForInterstitialPageCreated(RenderViewHost* render_view_host);
+
+ // Sets the passed interstitial as the currently showing interstitial.
+ // No interstitial page should already be attached.
+ void AttachInterstitialPage(InterstitialPageImpl* interstitial_page);
+
+ // Unsets the currently showing interstitial.
+ void DetachInterstitialPage();
+
+#if defined(OS_ANDROID)
+ JavaBridgeDispatcherHostManager* java_bridge_dispatcher_host_manager() const {
+ return java_bridge_dispatcher_host_manager_.get();
+ }
+#endif
+
+ // Expose the render manager for testing.
+ RenderViewHostManager* GetRenderManagerForTesting();
+
+ // Returns guest browser plugin object, or NULL if this WebContents is not a
+ // guest.
+ BrowserPluginGuest* GetBrowserPluginGuest() const;
+
+ // Sets a BrowserPluginGuest object for this WebContents. If this WebContents
+ // has a BrowserPluginGuest then that implies that it is being hosted by
+ // a BrowserPlugin object in an embedder renderer process.
+ void SetBrowserPluginGuest(BrowserPluginGuest* guest);
+
+ // Returns embedder browser plugin object, or NULL if this WebContents is not
+ // an embedder.
+ BrowserPluginEmbedder* GetBrowserPluginEmbedder() const;
+
+ // Returns the BrowserPluginGuestManager object, or NULL if this web contents
+ // does not have a BrowserPluginGuestManager.
+ BrowserPluginGuestManager* GetBrowserPluginGuestManager() const;
+
+ // Gets the current fullscreen render widget's routing ID. Returns
+ // MSG_ROUTING_NONE when there is no fullscreen render widget.
+ int GetFullscreenWidgetRoutingID() const;
+
+ // Invoked when visible SSL state (as defined by SSLStatus) changes.
+ void DidChangeVisibleSSLState();
+
+ // Invoked before a form repost warning is shown.
+ void NotifyBeforeFormRepostWarningShow();
+
+
+ // Informs the render view host and the BrowserPluginEmbedder, if present, of
+ // a Drag Source End.
+ void DragSourceEndedAt(int client_x, int client_y, int screen_x,
+ int screen_y, WebKit::WebDragOperation operation);
+
+ // Informs the render view host and the BrowserPluginEmbedder, if present, of
+ // a Drag Source Move.
+ void DragSourceMovedTo(int client_x, int client_y,
+ int screen_x, int screen_y);
+
+ FrameTreeNode* GetFrameTreeRootForTesting() {
+ return frame_tree_root_.get();
+ }
+
+ // WebContents ------------------------------------------------------
+ virtual WebContentsDelegate* GetDelegate() OVERRIDE;
+ virtual void SetDelegate(WebContentsDelegate* delegate) OVERRIDE;
+ virtual NavigationControllerImpl& GetController() OVERRIDE;
+ virtual const NavigationControllerImpl& GetController() const OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual RenderProcessHost* GetRenderProcessHost() const OVERRIDE;
+ virtual RenderViewHost* GetRenderViewHost() const OVERRIDE;
+ virtual void GetRenderViewHostAtPosition(
+ int x,
+ int y,
+ const GetRenderViewHostCallback& callback) OVERRIDE;
+ virtual WebContents* GetEmbedderWebContents() const OVERRIDE;
+ virtual int GetEmbeddedInstanceID() const OVERRIDE;
+ virtual int GetRoutingID() const OVERRIDE;
+ virtual RenderWidgetHostView* GetRenderWidgetHostView() const OVERRIDE;
+ virtual WebContentsView* GetView() const OVERRIDE;
+ virtual WebUI* CreateWebUI(const GURL& url) OVERRIDE;
+ virtual WebUI* GetWebUI() const OVERRIDE;
+ virtual WebUI* GetCommittedWebUI() const OVERRIDE;
+ virtual void SetUserAgentOverride(const std::string& override) OVERRIDE;
+ virtual const std::string& GetUserAgentOverride() const OVERRIDE;
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual void SetParentNativeViewAccessible(
+ gfx::NativeViewAccessible accessible_parent) OVERRIDE;
+#endif
+ virtual const string16& GetTitle() const OVERRIDE;
+ virtual int32 GetMaxPageID() OVERRIDE;
+ virtual int32 GetMaxPageIDForSiteInstance(
+ SiteInstance* site_instance) OVERRIDE;
+ virtual SiteInstance* GetSiteInstance() const OVERRIDE;
+ virtual SiteInstance* GetPendingSiteInstance() const OVERRIDE;
+ virtual bool IsLoading() const OVERRIDE;
+ virtual bool IsWaitingForResponse() const OVERRIDE;
+ virtual const net::LoadStateWithParam& GetLoadState() const OVERRIDE;
+ virtual const string16& GetLoadStateHost() const OVERRIDE;
+ virtual uint64 GetUploadSize() const OVERRIDE;
+ virtual uint64 GetUploadPosition() const OVERRIDE;
+ virtual std::set<GURL> GetSitesInTab() const OVERRIDE;
+ virtual const std::string& GetEncoding() const OVERRIDE;
+ virtual bool DisplayedInsecureContent() const OVERRIDE;
+ virtual void IncrementCapturerCount() OVERRIDE;
+ virtual void DecrementCapturerCount() OVERRIDE;
+ virtual int GetCapturerCount() const OVERRIDE;
+ virtual bool IsCrashed() const OVERRIDE;
+ virtual void SetIsCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual base::TerminationStatus GetCrashedStatus() const OVERRIDE;
+ virtual bool IsBeingDestroyed() const OVERRIDE;
+ virtual void NotifyNavigationStateChanged(unsigned changed_flags) OVERRIDE;
+ virtual base::TimeTicks GetLastSelectedTime() const OVERRIDE;
+ virtual void WasShown() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual bool NeedToFireBeforeUnload() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual WebContents* Clone() OVERRIDE;
+ virtual void FocusThroughTabTraversal(bool reverse) OVERRIDE;
+ virtual bool ShowingInterstitialPage() const OVERRIDE;
+ virtual InterstitialPage* GetInterstitialPage() const OVERRIDE;
+ virtual bool IsSavable() OVERRIDE;
+ virtual void OnSavePage() OVERRIDE;
+ virtual bool SavePage(const base::FilePath& main_file,
+ const base::FilePath& dir_path,
+ SavePageType save_type) OVERRIDE;
+ virtual void SaveFrame(const GURL& url,
+ const Referrer& referrer) OVERRIDE;
+ virtual void GenerateMHTML(
+ const base::FilePath& file,
+ const base::Callback<void(const base::FilePath&, int64)>& callback)
+ OVERRIDE;
+ virtual bool IsActiveEntry(int32 page_id) OVERRIDE;
+
+ virtual const std::string& GetContentsMimeType() const OVERRIDE;
+ virtual bool WillNotifyDisconnection() const OVERRIDE;
+ virtual void SetOverrideEncoding(const std::string& encoding) OVERRIDE;
+ virtual void ResetOverrideEncoding() OVERRIDE;
+ virtual RendererPreferences* GetMutableRendererPrefs() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual void SystemDragEnded() OVERRIDE;
+ virtual void UserGestureDone() OVERRIDE;
+ virtual void SetClosedByUserGesture(bool value) OVERRIDE;
+ virtual bool GetClosedByUserGesture() const OVERRIDE;
+ virtual double GetZoomLevel() const OVERRIDE;
+ virtual int GetZoomPercent(bool* enable_increment,
+ bool* enable_decrement) const OVERRIDE;
+ virtual void ViewSource() OVERRIDE;
+ virtual void ViewFrameSource(const GURL& url,
+ const PageState& page_state) OVERRIDE;
+ virtual int GetMinimumZoomPercent() const OVERRIDE;
+ virtual int GetMaximumZoomPercent() const OVERRIDE;
+ virtual gfx::Size GetPreferredSize() const OVERRIDE;
+ virtual bool GotResponseToLockMouseRequest(bool allowed) OVERRIDE;
+ virtual bool HasOpener() const OVERRIDE;
+ virtual void DidChooseColorInColorChooser(SkColor color) OVERRIDE;
+ virtual void DidEndColorChooser() OVERRIDE;
+ virtual int DownloadImage(const GURL& url,
+ bool is_favicon,
+ uint32_t preferred_image_size,
+ uint32_t max_image_size,
+ const ImageDownloadCallback& callback) OVERRIDE;
+
+ // Implementation of PageNavigator.
+ virtual WebContents* OpenURL(const OpenURLParams& params) OVERRIDE;
+
+ // Implementation of IPC::Sender.
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // RenderViewHostDelegate ----------------------------------------------------
+
+ virtual RenderViewHostDelegateView* GetDelegateView() OVERRIDE;
+ virtual RenderViewHostDelegate::RendererManagement*
+ GetRendererManagementDelegate() OVERRIDE;
+ virtual bool OnMessageReceived(RenderViewHost* render_view_host,
+ const IPC::Message& message) OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const GURL& GetVisibleURL() const OVERRIDE;
+ virtual const GURL& GetLastCommittedURL() const OVERRIDE;
+ virtual WebContents* GetAsWebContents() OVERRIDE;
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void RenderViewReady(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void RenderViewTerminated(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStartProvisionalLoadForFrame(
+ RenderViewHost* render_view_host,
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool main_frame,
+ const GURL& url) OVERRIDE;
+ virtual void DidRedirectProvisionalLoad(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const GURL& source_url,
+ const GURL& target_url) OVERRIDE;
+ virtual void DidFailProvisionalLoadWithError(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_DidFailProvisionalLoadWithError_Params& params)
+ OVERRIDE;
+ virtual void DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE;
+ virtual void UpdateState(RenderViewHost* render_view_host,
+ int32 page_id,
+ const PageState& page_state) OVERRIDE;
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) OVERRIDE;
+ virtual void UpdateEncoding(RenderViewHost* render_view_host,
+ const std::string& encoding) OVERRIDE;
+ virtual void UpdateTargetURL(int32 page_id, const GURL& url) OVERRIDE;
+ virtual void Close(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void RequestMove(const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void SwappedOut(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidCancelLoading() OVERRIDE;
+ virtual void DidChangeLoadProgress(double progress) OVERRIDE;
+ virtual void DidDisownOpener(RenderViewHost* rvh) OVERRIDE;
+ virtual void DidAccessInitialDocument() OVERRIDE;
+ virtual void DocumentAvailableInMainFrame(
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DocumentOnLoadCompletedInMainFrame(
+ RenderViewHost* render_view_host,
+ int32 page_id) OVERRIDE;
+ virtual void RequestOpenURL(RenderViewHost* rvh,
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ bool should_replace_current_entry,
+ bool user_gesture) OVERRIDE;
+ virtual void RequestTransferURL(
+ const GURL& url,
+ const Referrer& referrer,
+ WindowOpenDisposition disposition,
+ int64 source_frame_id,
+ const GlobalRequestID& transferred_global_request_id,
+ bool should_replace_current_entry,
+ bool user_gesture) OVERRIDE;
+ virtual void RouteCloseEvent(RenderViewHost* rvh) OVERRIDE;
+ virtual void RouteMessageEvent(
+ RenderViewHost* rvh,
+ const ViewMsg_PostMessage_Params& params) OVERRIDE;
+ virtual void RunJavaScriptMessage(RenderViewHost* rvh,
+ const string16& message,
+ const string16& default_prompt,
+ const GURL& frame_url,
+ JavaScriptMessageType type,
+ IPC::Message* reply_msg,
+ bool* did_suppress_message) OVERRIDE;
+ virtual void RunBeforeUnloadConfirm(RenderViewHost* rvh,
+ const string16& message,
+ bool is_reload,
+ IPC::Message* reply_msg) OVERRIDE;
+ virtual bool AddMessageToConsole(int32 level,
+ const string16& message,
+ int32 line_no,
+ const string16& source_id) OVERRIDE;
+ virtual RendererPreferences GetRendererPrefs(
+ BrowserContext* browser_context) const OVERRIDE;
+ virtual WebPreferences GetWebkitPrefs() OVERRIDE;
+ virtual void OnUserGesture() OVERRIDE;
+ virtual void OnIgnoredUIEvent() OVERRIDE;
+ virtual void RendererUnresponsive(RenderViewHost* render_view_host,
+ bool is_during_unload) OVERRIDE;
+ virtual void RendererResponsive(RenderViewHost* render_view_host) OVERRIDE;
+ virtual void LoadStateChanged(const GURL& url,
+ const net::LoadStateWithParam& load_state,
+ uint64 upload_position,
+ uint64 upload_size) OVERRIDE;
+ virtual void WorkerCrashed() OVERRIDE;
+ virtual void Activate() OVERRIDE;
+ virtual void Deactivate() OVERRIDE;
+ virtual void LostCapture() OVERRIDE;
+ virtual void HandleMouseDown() OVERRIDE;
+ virtual void HandleMouseUp() OVERRIDE;
+ virtual void HandlePointerActivate() OVERRIDE;
+ virtual void HandleGestureBegin() OVERRIDE;
+ virtual void HandleGestureEnd() OVERRIDE;
+ virtual void RunFileChooser(
+ RenderViewHost* render_view_host,
+ const FileChooserParams& params) OVERRIDE;
+ virtual void ToggleFullscreenMode(bool enter_fullscreen) OVERRIDE;
+ virtual bool IsFullscreenForCurrentTab() const OVERRIDE;
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size) OVERRIDE;
+ virtual void ResizeDueToAutoResize(const gfx::Size& new_size) OVERRIDE;
+ virtual void RequestToLockMouse(bool user_gesture,
+ bool last_unlocked_by_target) OVERRIDE;
+ virtual void LostMouseLock() OVERRIDE;
+ virtual void CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) OVERRIDE;
+ virtual void CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) OVERRIDE;
+ virtual void CreateNewFullscreenWidget(int route_id) OVERRIDE;
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) OVERRIDE;
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) OVERRIDE;
+ virtual void ShowCreatedFullscreenWidget(int route_id) OVERRIDE;
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void RequestMediaAccessPermission(
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) OVERRIDE;
+ virtual SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance) OVERRIDE;
+
+ // RenderWidgetHostDelegate --------------------------------------------------
+
+ virtual void RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) OVERRIDE;
+ virtual bool PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) OVERRIDE;
+ virtual void HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+ virtual bool PreHandleWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+ virtual void DidSendScreenRects(RenderWidgetHostImpl* rwh) OVERRIDE;
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual gfx::NativeViewAccessible GetParentNativeViewAccessible() OVERRIDE;
+#endif
+
+ // RenderViewHostManager::Delegate -------------------------------------------
+
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host, int opener_route_id) OVERRIDE;
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed, const base::TimeTicks& proceed_time,
+ bool* proceed_to_fire_unload) OVERRIDE;
+ virtual void RenderProcessGoneFromRenderManager(
+ RenderViewHost* render_view_host) OVERRIDE;
+ virtual void UpdateRenderViewSizeForRenderManager() OVERRIDE;
+ virtual void NotifySwappedFromRenderManager(
+ RenderViewHost* old_render_view_host) OVERRIDE;
+ virtual int CreateOpenerRenderViewsForRenderManager(
+ SiteInstance* instance) OVERRIDE;
+ virtual NavigationControllerImpl&
+ GetControllerForRenderManager() OVERRIDE;
+ virtual WebUIImpl* CreateWebUIForRenderManager(const GURL& url) OVERRIDE;
+ virtual NavigationEntry*
+ GetLastCommittedNavigationEntryForRenderManager() OVERRIDE;
+ virtual bool FocusLocationBarByDefault() OVERRIDE;
+ virtual void SetFocusToLocationBar(bool select_all) OVERRIDE;
+ virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh) OVERRIDE;
+
+ // NotificationObserver ------------------------------------------------------
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+
+ private:
+ friend class NavigationControllerImpl;
+ friend class WebContentsObserver;
+ friend class WebContents; // To implement factory methods.
+
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, NoJSMessageOnInterstitials);
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, UpdateTitle);
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, FindOpenerRVHWhenPending);
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest,
+ CrossSiteCantPreemptAfterUnload);
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, PendingContents);
+ FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, FrameTreeShape);
+ FRIEND_TEST_ALL_PREFIXES(FormStructureBrowserTest, HTMLFiles);
+ FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest, HistoryNavigate);
+ FRIEND_TEST_ALL_PREFIXES(RenderViewHostManagerTest, PageDoesBackAndReload);
+
+ // So InterstitialPageImpl can access SetIsLoading.
+ friend class InterstitialPageImpl;
+
+ // TODO(brettw) TestWebContents shouldn't exist!
+ friend class TestWebContents;
+
+ class DestructionObserver;
+
+ // See WebContents::Create for a description of these parameters.
+ WebContentsImpl(BrowserContext* browser_context,
+ WebContentsImpl* opener);
+
+ // Add and remove observers for page navigation notifications. Adding or
+ // removing multiple times has no effect. The order in which notifications
+ // are sent to observers is undefined. Clients must be sure to remove the
+ // observer before they go away.
+ void AddObserver(WebContentsObserver* observer);
+ void RemoveObserver(WebContentsObserver* observer);
+
+ // Clears this tab's opener if it has been closed.
+ void OnWebContentsDestroyed(WebContentsImpl* web_contents);
+
+ // Creates and adds to the map a destruction observer watching |web_contents|.
+ // No-op if such an observer already exists.
+ void AddDestructionObserver(WebContentsImpl* web_contents);
+
+ // Deletes and removes from the map a destruction observer
+ // watching |web_contents|. No-op if there is no such observer.
+ void RemoveDestructionObserver(WebContentsImpl* web_contents);
+
+ // Callback function when showing JS dialogs.
+ void OnDialogClosed(RenderViewHost* rvh,
+ IPC::Message* reply_msg,
+ bool success,
+ const string16& user_input);
+
+ // Callback function when requesting permission to access the PPAPI broker.
+ // |result| is true if permission was granted.
+ void OnPpapiBrokerPermissionResult(int routing_id, bool result);
+
+ // IPC message handlers.
+ void OnDidLoadResourceFromMemoryCache(const GURL& url,
+ const std::string& security_info,
+ const std::string& http_request,
+ const std::string& mime_type,
+ ResourceType::Type resource_type);
+ void OnDidDisplayInsecureContent();
+ void OnDidRunInsecureContent(const std::string& security_origin,
+ const GURL& target_url);
+ void OnDocumentLoadedInFrame(int64 frame_id);
+ void OnDidFinishLoad(int64 frame_id,
+ const GURL& url,
+ bool is_main_frame);
+ void OnDidFailLoadWithError(int64 frame_id,
+ const GURL& url,
+ bool is_main_frame,
+ int error_code,
+ const string16& error_description);
+ void OnGoToEntryAtOffset(int offset);
+ void OnUpdateZoomLimits(int minimum_percent,
+ int maximum_percent,
+ bool remember);
+ void OnEnumerateDirectory(int request_id, const base::FilePath& path);
+ void OnJSOutOfMemory();
+
+ void OnRegisterProtocolHandler(const std::string& protocol,
+ const GURL& url,
+ const string16& title,
+ bool user_gesture);
+ void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+ void OnDidProgrammaticallyScroll(const gfx::Vector2d& scroll_point);
+#if defined(OS_ANDROID)
+ void OnFindMatchRectsReply(int version,
+ const std::vector<gfx::RectF>& rects,
+ const gfx::RectF& active_rect);
+
+ void OnOpenDateTimeDialog(
+ const ViewHostMsg_DateTimeDialogValue_Params& value);
+#endif
+ void OnCrashedPlugin(const base::FilePath& plugin_path,
+ base::ProcessId plugin_pid);
+ void OnAppCacheAccessed(const GURL& manifest_url, bool blocked_by_policy);
+ void OnOpenColorChooser(int color_chooser_id, SkColor color);
+ void OnEndColorChooser(int color_chooser_id);
+ void OnSetSelectedColorInColorChooser(int color_chooser_id, SkColor color);
+ void OnPepperPluginHung(int plugin_child_id,
+ const base::FilePath& path,
+ bool is_hung);
+ void OnWebUISend(const GURL& source_url,
+ const std::string& name,
+ const base::ListValue& args);
+ void OnRequestPpapiBrokerPermission(int routing_id,
+ const GURL& url,
+ const base::FilePath& plugin_path);
+ void OnBrowserPluginMessage(const IPC::Message& message);
+ void OnDidDownloadImage(int id,
+ int http_status_code,
+ const GURL& image_url,
+ int requested_size,
+ const std::vector<SkBitmap>& bitmaps);
+ void OnUpdateFaviconURL(int32 page_id,
+ const std::vector<FaviconURL>& candidates);
+ void OnFrameAttached(int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name);
+ void OnFrameDetached(int64 parent_frame_id, int64 frame_id);
+
+ void OnMediaNotification(int64 player_cookie,
+ bool has_video,
+ bool has_audio,
+ bool is_playing);
+
+ // Changes the IsLoading state and notifies delegate as needed
+ // |details| is used to provide details on the load that just finished
+ // (but can be null if not applicable). Can be overridden.
+ void SetIsLoading(bool is_loading,
+ LoadNotificationDetails* details);
+
+ // Called by derived classes to indicate that we're no longer waiting for a
+ // response. This won't actually update the throbber, but it will get picked
+ // up at the next animation step if the throbber is going.
+ void SetNotWaitingForResponse() { waiting_for_response_ = false; }
+
+ // Navigation helpers --------------------------------------------------------
+ //
+ // These functions are helpers for Navigate() and DidNavigate().
+
+ // Handles post-navigation tasks in DidNavigate AFTER the entry has been
+ // committed to the navigation controller. Note that the navigation entry is
+ // not provided since it may be invalid/changed after being committed. The
+ // current navigation entry is in the NavigationController at this point.
+ void DidNavigateMainFramePostCommit(
+ const LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void DidNavigateAnyFramePostCommit(
+ RenderViewHost* render_view_host,
+ const LoadCommittedDetails& details,
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Specifies whether the passed in URL should be assigned as the site of the
+ // current SiteInstance, if it does not yet have a site.
+ bool ShouldAssignSiteForURL(const GURL& url);
+
+ // If our controller was restored, update the max page ID associated with the
+ // given RenderViewHost to be larger than the number of restored entries.
+ // This is called in CreateRenderView before any navigations in the RenderView
+ // have begun, to prevent any races in updating RenderView::next_page_id.
+ void UpdateMaxPageIDIfNecessary(RenderViewHost* rvh);
+
+ // Saves the given title to the navigation entry and does associated work. It
+ // will update history and the view for the new title, and also synthesize
+ // titles for file URLs that have none (so we require that the URL of the
+ // entry already be set).
+ //
+ // This is used as the backend for state updates, which include a new title,
+ // or the dedicated set title message. It returns true if the new title is
+ // different and was therefore updated.
+ bool UpdateTitleForEntry(NavigationEntryImpl* entry,
+ const string16& title);
+
+ // Causes the WebContentsImpl to navigate in the right renderer to |entry|,
+ // which must be already part of the entries in the navigation controller.
+ // This does not change the NavigationController state.
+ bool NavigateToEntry(const NavigationEntryImpl& entry,
+ NavigationController::ReloadType reload_type);
+
+ // Sets the history for this WebContentsImpl to |history_length| entries, and
+ // moves the current page_id to the last entry in the list if it's valid.
+ // This is mainly used when a prerendered page is swapped into the current
+ // tab. The method is virtual for testing.
+ virtual void SetHistoryLengthAndPrune(
+ const SiteInstance* site_instance,
+ int merge_history_length,
+ int32 minimum_page_id);
+
+ // Recursively creates swapped out RenderViews for this tab's opener chain
+ // (including this tab) in the given SiteInstance, allowing other tabs to send
+ // cross-process JavaScript calls to their opener(s). Returns the route ID of
+ // this tab's RenderView for |instance|.
+ int CreateOpenerRenderViews(SiteInstance* instance);
+
+ // Helper for CreateNewWidget/CreateNewFullscreenWidget.
+ void CreateNewWidget(int route_id,
+ bool is_fullscreen,
+ WebKit::WebPopupType popup_type);
+
+ // Helper for ShowCreatedWidget/ShowCreatedFullscreenWidget.
+ void ShowCreatedWidget(int route_id,
+ bool is_fullscreen,
+ const gfx::Rect& initial_pos);
+
+ // Finds the new RenderWidgetHost and returns it. Note that this can only be
+ // called once as this call also removes it from the internal map.
+ RenderWidgetHostView* GetCreatedWidget(int route_id);
+
+ // Finds the new WebContentsImpl by route_id, initializes it for
+ // renderer-initiated creation, and returns it. Note that this can only be
+ // called once as this call also removes it from the internal map.
+ WebContentsImpl* GetCreatedWindow(int route_id);
+
+ // Returns the RenderWidgetHostView that is associated with a native window
+ // and can be used in showing created widgets.
+ // If this WebContents belongs to a browser plugin guest, there is no native
+ // window 'view' associated with this WebContents. This method returns the
+ // 'view' of the embedder instead.
+ RenderWidgetHostViewPort* GetRenderWidgetHostViewPort() const;
+
+ // Misc non-view stuff -------------------------------------------------------
+
+ // Helper functions for sending notifications.
+ void NotifySwapped(RenderViewHost* old_render_view_host);
+ void NotifyConnected();
+ void NotifyDisconnected();
+ void NotifyNavigationEntryCommitted(const LoadCommittedDetails& load_details);
+
+ void SetEncoding(const std::string& encoding);
+
+ RenderViewHostImpl* GetRenderViewHostImpl();
+
+ FrameTreeNode* FindFrameTreeNodeByID(int64 frame_id);
+
+ // Removes browser plugin embedder if there is one.
+ void RemoveBrowserPluginEmbedder();
+
+ // Clear |render_view_host|'s PowerSaveBlockers.
+ void ClearPowerSaveBlockers(RenderViewHost* render_view_host);
+
+ // Clear all PowerSaveBlockers, leave power_save_blocker_ empty.
+ void ClearAllPowerSaveBlockers();
+
+ // Data for core operation ---------------------------------------------------
+
+ // Delegate for notifying our owner about stuff. Not owned by us.
+ WebContentsDelegate* delegate_;
+
+ // Handles the back/forward list and loading.
+ NavigationControllerImpl controller_;
+
+ // The corresponding view.
+ scoped_ptr<WebContentsViewPort> view_;
+
+ // The view of the RVHD. Usually this is our WebContentsView implementation,
+ // but if an embedder uses a different WebContentsView, they'll need to
+ // provide this.
+ RenderViewHostDelegateView* render_view_host_delegate_view_;
+
+ // Tracks created WebContentsImpl objects that have not been shown yet. They
+ // are identified by the route ID passed to CreateNewWindow.
+ typedef std::map<int, WebContentsImpl*> PendingContents;
+ PendingContents pending_contents_;
+
+ // These maps hold on to the widgets that we created on behalf of the renderer
+ // that haven't shown yet.
+ typedef std::map<int, RenderWidgetHostView*> PendingWidgetViews;
+ PendingWidgetViews pending_widget_views_;
+
+ typedef std::map<WebContentsImpl*, DestructionObserver*> DestructionObservers;
+ DestructionObservers destruction_observers_;
+
+ // A list of observers notified when page state changes. Weak references.
+ // This MUST be listed above render_manager_ since at destruction time the
+ // latter might cause RenderViewHost's destructor to call us and we might use
+ // the observer list then.
+ ObserverList<WebContentsObserver> observers_;
+
+ // The tab that opened this tab, if any. Will be set to null if the opener
+ // is closed.
+ WebContentsImpl* opener_;
+
+#if defined(OS_WIN) && defined(USE_AURA)
+ gfx::NativeViewAccessible accessible_parent_;
+#endif
+
+ // Helper classes ------------------------------------------------------------
+
+ // Maps the RenderViewHost to its media_player_cookie and PowerSaveBlocker
+ // pairs. Key is the RenderViewHost, value is the map which maps player_cookie
+ // on to PowerSaveBlocker.
+ typedef std::map<RenderViewHost*, std::map<int64, PowerSaveBlocker*> >
+ PowerSaveBlockerMap;
+ PowerSaveBlockerMap power_save_blockers_;
+
+ // Manages creation and swapping of render views.
+ RenderViewHostManager render_manager_;
+
+#if defined(OS_ANDROID)
+ // Manages injecting Java objects into all RenderViewHosts associated with
+ // this WebContentsImpl.
+ scoped_ptr<JavaBridgeDispatcherHostManager>
+ java_bridge_dispatcher_host_manager_;
+#endif
+
+ // SavePackage, lazily created.
+ scoped_refptr<SavePackage> save_package_;
+
+ // Data for loading state ----------------------------------------------------
+
+ // Indicates whether we're currently loading a resource.
+ bool is_loading_;
+
+ // Indicates if the tab is considered crashed.
+ base::TerminationStatus crashed_status_;
+ int crashed_error_code_;
+
+ // Whether this WebContents is waiting for a first-response for the
+ // main resource of the page. This controls whether the throbber state is
+ // "waiting" or "loading."
+ bool waiting_for_response_;
+
+ // Map of SiteInstance ID to max page ID for this tab. A page ID is specific
+ // to a given tab and SiteInstance, and must be valid for the lifetime of the
+ // WebContentsImpl.
+ std::map<int32, int32> max_page_ids_;
+
+ // System time at which the current load was started.
+ base::TimeTicks current_load_start_;
+
+ // The current load state and the URL associated with it.
+ net::LoadStateWithParam load_state_;
+ string16 load_state_host_;
+ // Upload progress, for displaying in the status bar.
+ // Set to zero when there is no significant upload happening.
+ uint64 upload_size_;
+ uint64 upload_position_;
+
+ // Data for current page -----------------------------------------------------
+
+ // When a title cannot be taken from any entry, this title will be used.
+ string16 page_title_when_no_navigation_entry_;
+
+ // When a navigation occurs, we record its contents MIME type. It can be
+ // used to check whether we can do something for some special contents.
+ std::string contents_mime_type_;
+
+ // Character encoding.
+ std::string encoding_;
+
+ // True if this is a secure page which displayed insecure content.
+ bool displayed_insecure_content_;
+
+ // The frame tree structure of the current page.
+ scoped_ptr<FrameTreeNode> frame_tree_root_;
+
+ // Data for misc internal state ----------------------------------------------
+
+ // When > 0, the WebContents is currently being captured (e.g., for
+ // screenshots or mirroring); and the underlying RenderWidgetHost should not
+ // be told it is hidden.
+ int capturer_count_;
+
+ // Tracks whether RWHV should be visible once capturer_count_ becomes zero.
+ bool should_normally_be_visible_;
+
+ // See getter above.
+ bool is_being_destroyed_;
+
+ // Indicates whether we should notify about disconnection of this
+ // WebContentsImpl. This is used to ensure disconnection notifications only
+ // happen if a connection notification has happened and that they happen only
+ // once.
+ bool notify_disconnection_;
+
+ // Pointer to the JavaScript dialog manager, lazily assigned. Used because the
+ // delegate of this WebContentsImpl is nulled before its destructor is called.
+ JavaScriptDialogManager* dialog_manager_;
+
+ // Set to true when there is an active "before unload" dialog. When true,
+ // we've forced the throbber to start in Navigate, and we need to remember to
+ // turn it off in OnJavaScriptMessageBoxClosed if the navigation is canceled.
+ bool is_showing_before_unload_dialog_;
+
+ // Settings that get passed to the renderer process.
+ RendererPreferences renderer_preferences_;
+
+ // The time that this tab was last selected.
+ base::TimeTicks last_selected_time_;
+
+ // See description above setter.
+ bool closed_by_user_gesture_;
+
+ // Minimum/maximum zoom percent.
+ int minimum_zoom_percent_;
+ int maximum_zoom_percent_;
+ // If true, the default zoom limits have been overriden for this tab, in which
+ // case we don't want saved settings to apply to it and we don't want to
+ // remember it.
+ bool temporary_zoom_settings_;
+
+ // The intrinsic size of the page.
+ gfx::Size preferred_size_;
+
+#if defined(OS_ANDROID)
+ // Date time chooser opened by this tab.
+ // Only used in Android since all other platforms use a multi field UI.
+ scoped_ptr<DateTimeChooserAndroid> date_time_chooser_;
+#endif
+
+ // Color chooser that was opened by this tab.
+ scoped_ptr<ColorChooser> color_chooser_;
+
+ // A unique identifier for the current color chooser. Identifiers are unique
+ // across a renderer process. This avoids race conditions in synchronizing
+ // the browser and renderer processes. For example, if a renderer closes one
+ // chooser and opens another, and simultaneously the user picks a color in the
+ // first chooser, the IDs can be used to drop the "chose a color" message
+ // rather than erroneously tell the renderer that the user picked a color in
+ // the second chooser.
+ int color_chooser_identifier_;
+
+ // Manages the embedder state for browser plugins, if this WebContents is an
+ // embedder; NULL otherwise.
+ scoped_ptr<BrowserPluginEmbedder> browser_plugin_embedder_;
+ // Manages the guest state for browser plugin, if this WebContents is a guest;
+ // NULL otherwise.
+ scoped_ptr<BrowserPluginGuest> browser_plugin_guest_;
+
+ // This must be at the end, or else we might get notifications and use other
+ // member variables that are gone.
+ NotificationRegistrar registrar_;
+
+ // Used during IPC message dispatching so that the handlers can get a pointer
+ // to the RVH through which the message was received.
+ RenderViewHost* message_source_;
+
+ // All live RenderWidgetHostImpls that are created by this object and may
+ // outlive it.
+ std::set<RenderWidgetHostImpl*> created_widgets_;
+
+ // Routing id of the shown fullscreen widget or MSG_ROUTING_NONE otherwise.
+ int fullscreen_widget_routing_id_;
+
+ // Maps the ids of pending image downloads to their callbacks
+ typedef std::map<int, ImageDownloadCallback> ImageDownloadMap;
+ ImageDownloadMap image_download_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_IMPL_H_
diff --git a/chromium/content/browser/web_contents/web_contents_impl_browsertest.cc b/chromium/content/browser/web_contents/web_contents_impl_browsertest.cc
new file mode 100644
index 00000000000..0cb9fe258ea
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -0,0 +1,162 @@
+// 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 "base/values.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/load_notification_details.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace content {
+
+class WebContentsImplBrowserTest : public ContentBrowserTest {
+ public:
+ WebContentsImplBrowserTest() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
+};
+
+// Keeps track of data from LoadNotificationDetails so we can later verify that
+// they are correct, after the LoadNotificationDetails object is deleted.
+class LoadStopNotificationObserver : public WindowedNotificationObserver {
+ public:
+ LoadStopNotificationObserver(NavigationController* controller)
+ : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(controller)),
+ session_index_(-1),
+ controller_(NULL) {
+ }
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ if (type == NOTIFICATION_LOAD_STOP) {
+ const Details<LoadNotificationDetails> load_details(details);
+ url_ = load_details->url;
+ session_index_ = load_details->session_index;
+ controller_ = load_details->controller;
+ }
+ WindowedNotificationObserver::Observe(type, source, details);
+ }
+
+ GURL url_;
+ int session_index_;
+ NavigationController* controller_;
+};
+
+// Starts a new navigation as soon as the current one commits, but does not
+// wait for it to complete. This allows us to observe DidStopLoading while
+// a pending entry is present.
+class NavigateOnCommitObserver : public WebContentsObserver {
+ public:
+ NavigateOnCommitObserver(Shell* shell, GURL url)
+ : WebContentsObserver(shell->web_contents()),
+ shell_(shell),
+ url_(url),
+ done_(false) {
+ }
+
+ // WebContentsObserver:
+ virtual void NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) OVERRIDE {
+ if (!done_) {
+ done_ = true;
+ shell_->LoadURL(url_);
+ }
+ }
+
+ Shell* shell_;
+ GURL url_;
+ bool done_;
+};
+
+// Test that DidStopLoading includes the correct URL in the details.
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, DidStopLoadingDetails) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ LoadStopNotificationObserver load_observer(
+ &shell()->web_contents()->GetController());
+ NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
+ load_observer.Wait();
+
+ EXPECT_EQ("/title1.html", load_observer.url_.path());
+ EXPECT_EQ(0, load_observer.session_index_);
+ EXPECT_EQ(&shell()->web_contents()->GetController(),
+ load_observer.controller_);
+}
+
+// Test that DidStopLoading includes the correct URL in the details when a
+// pending entry is present.
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+ DidStopLoadingDetailsWithPending) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ // Listen for the first load to stop.
+ LoadStopNotificationObserver load_observer(
+ &shell()->web_contents()->GetController());
+ // Start a new pending navigation as soon as the first load commits.
+ // We will hear a DidStopLoading from the first load as the new load
+ // is started.
+ NavigateOnCommitObserver commit_observer(
+ shell(), embedded_test_server()->GetURL("/title2.html"));
+ NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
+ load_observer.Wait();
+
+ EXPECT_EQ("/title1.html", load_observer.url_.path());
+ EXPECT_EQ(0, load_observer.session_index_);
+ EXPECT_EQ(&shell()->web_contents()->GetController(),
+ load_observer.controller_);
+}
+
+// Test that the browser receives the proper frame attach/detach messages from
+// the renderer and builds proper frame tree.
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, FrameTree) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ NavigateToURL(shell(),
+ embedded_test_server()->GetURL("/frame_tree/top.html"));
+
+ WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ wc->GetRenderViewHost());
+ FrameTreeNode* root = wc->GetFrameTreeRootForTesting();
+
+ // Check that the root node is properly created with the frame id of the
+ // initial navigation.
+ EXPECT_EQ(3UL, root->child_count());
+ EXPECT_EQ(std::string(), root->frame_name());
+ EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
+
+ EXPECT_EQ(2UL, root->child_at(0)->child_count());
+ EXPECT_STREQ("1-1-name", root->child_at(0)->frame_name().c_str());
+
+ // Verify the deepest node exists and has the right name.
+ EXPECT_EQ(2UL, root->child_at(2)->child_count());
+ EXPECT_EQ(1UL, root->child_at(2)->child_at(1)->child_count());
+ EXPECT_EQ(0UL, root->child_at(2)->child_at(1)->child_at(0)->child_count());
+ EXPECT_STREQ("3-1-id",
+ root->child_at(2)->child_at(1)->child_at(0)->frame_name().c_str());
+
+ // Navigate to about:blank, which should leave only the root node of the frame
+ // tree in the browser process.
+ NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
+
+ root = wc->GetFrameTreeRootForTesting();
+ EXPECT_EQ(0UL, root->child_count());
+ EXPECT_EQ(std::string(), root->frame_name());
+ EXPECT_EQ(rvh->main_frame_id(), root->frame_id());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_impl_unittest.cc b/chromium/content/browser/web_contents/web_contents_impl_unittest.cc
new file mode 100644
index 00000000000..aded7ae002f
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -0,0 +1,2225 @@
+// 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 "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/frame_tree_node.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/interstitial_page_delegate.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+const char kTestWebUIUrl[] = "chrome://blah";
+
+class WebContentsImplTestWebUIControllerFactory
+ : public WebUIControllerFactory {
+ public:
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const OVERRIDE {
+ if (!UseWebUI(url))
+ return NULL;
+ return new WebUIController(web_ui);
+ }
+
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return WebUI::kNoWebUI;
+ }
+
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return UseWebUI(url);
+ }
+
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return UseWebUI(url);
+ }
+
+ private:
+ bool UseWebUI(const GURL& url) const {
+ return url == GURL(kTestWebUIUrl);
+ }
+};
+
+class TestInterstitialPage;
+
+class TestInterstitialPageDelegate : public InterstitialPageDelegate {
+ public:
+ explicit TestInterstitialPageDelegate(TestInterstitialPage* interstitial_page)
+ : interstitial_page_(interstitial_page) {}
+ virtual void CommandReceived(const std::string& command) OVERRIDE;
+ virtual std::string GetHTMLContents() OVERRIDE { return std::string(); }
+ virtual void OnDontProceed() OVERRIDE;
+ virtual void OnProceed() OVERRIDE;
+ private:
+ TestInterstitialPage* interstitial_page_;
+};
+
+class TestInterstitialPage : public InterstitialPageImpl {
+ public:
+ enum InterstitialState {
+ INVALID = 0, // Hasn't yet been initialized.
+ UNDECIDED, // Initialized, but no decision taken yet.
+ OKED, // Proceed was called.
+ CANCELED // DontProceed was called.
+ };
+
+ class Delegate {
+ public:
+ virtual void TestInterstitialPageDeleted(
+ TestInterstitialPage* interstitial) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // IMPORTANT NOTE: if you pass stack allocated values for |state| and
+ // |deleted| (like all interstitial related tests do at this point), make sure
+ // to create an instance of the TestInterstitialPageStateGuard class on the
+ // stack in your test. This will ensure that the TestInterstitialPage states
+ // are cleared when the test finishes.
+ // Not doing so will cause stack trashing if your test does not hide the
+ // interstitial, as in such a case it will be destroyed in the test TearDown
+ // method and will dereference the |deleted| local variable which by then is
+ // out of scope.
+ TestInterstitialPage(WebContentsImpl* contents,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialState* state,
+ bool* deleted)
+ : InterstitialPageImpl(
+ contents, new_navigation, url,
+ new TestInterstitialPageDelegate(this)),
+ state_(state),
+ deleted_(deleted),
+ command_received_count_(0),
+ delegate_(NULL) {
+ *state_ = UNDECIDED;
+ *deleted_ = false;
+ }
+
+ virtual ~TestInterstitialPage() {
+ if (deleted_)
+ *deleted_ = true;
+ if (delegate_)
+ delegate_->TestInterstitialPageDeleted(this);
+ }
+
+ void OnDontProceed() {
+ if (state_)
+ *state_ = CANCELED;
+ }
+ void OnProceed() {
+ if (state_)
+ *state_ = OKED;
+ }
+
+ int command_received_count() const {
+ return command_received_count_;
+ }
+
+ void TestDomOperationResponse(const std::string& json_string) {
+ if (enabled())
+ CommandReceived();
+ }
+
+ void TestDidNavigate(int page_id, const GURL& url) {
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, page_id, url, PAGE_TRANSITION_TYPED);
+ DidNavigate(GetRenderViewHostForTesting(), params);
+ }
+
+ void TestRenderViewTerminated(base::TerminationStatus status,
+ int error_code) {
+ RenderViewTerminated(GetRenderViewHostForTesting(), status, error_code);
+ }
+
+ bool is_showing() const {
+ return static_cast<TestRenderWidgetHostView*>(
+ GetRenderViewHostForTesting()->GetView())->is_showing();
+ }
+
+ void ClearStates() {
+ state_ = NULL;
+ deleted_ = NULL;
+ delegate_ = NULL;
+ }
+
+ void CommandReceived() {
+ command_received_count_++;
+ }
+
+ void set_delegate(Delegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ protected:
+ virtual RenderViewHost* CreateRenderViewHost() OVERRIDE {
+ return new TestRenderViewHost(
+ SiteInstance::Create(web_contents()->GetBrowserContext()),
+ this, this, MSG_ROUTING_NONE, MSG_ROUTING_NONE, false);
+ }
+
+ virtual WebContentsView* CreateWebContentsView() OVERRIDE {
+ return NULL;
+ }
+
+ private:
+ InterstitialState* state_;
+ bool* deleted_;
+ int command_received_count_;
+ Delegate* delegate_;
+};
+
+void TestInterstitialPageDelegate::CommandReceived(const std::string& command) {
+ interstitial_page_->CommandReceived();
+}
+
+void TestInterstitialPageDelegate::OnDontProceed() {
+ interstitial_page_->OnDontProceed();
+}
+
+void TestInterstitialPageDelegate::OnProceed() {
+ interstitial_page_->OnProceed();
+}
+
+class TestInterstitialPageStateGuard : public TestInterstitialPage::Delegate {
+ public:
+ explicit TestInterstitialPageStateGuard(
+ TestInterstitialPage* interstitial_page)
+ : interstitial_page_(interstitial_page) {
+ DCHECK(interstitial_page_);
+ interstitial_page_->set_delegate(this);
+ }
+ virtual ~TestInterstitialPageStateGuard() {
+ if (interstitial_page_)
+ interstitial_page_->ClearStates();
+ }
+
+ virtual void TestInterstitialPageDeleted(
+ TestInterstitialPage* interstitial) OVERRIDE {
+ DCHECK(interstitial_page_ == interstitial);
+ interstitial_page_ = NULL;
+ }
+
+ private:
+ TestInterstitialPage* interstitial_page_;
+};
+
+class WebContentsImplTestBrowserClient : public TestContentBrowserClient {
+ public:
+ WebContentsImplTestBrowserClient()
+ : assign_site_for_url_(false) {}
+
+ virtual ~WebContentsImplTestBrowserClient() {}
+
+ virtual bool ShouldAssignSiteForURL(const GURL& url) OVERRIDE {
+ return assign_site_for_url_;
+ }
+
+ void set_assign_site_for_url(bool assign) {
+ assign_site_for_url_ = assign;
+ }
+
+ private:
+ bool assign_site_for_url_;
+};
+
+class WebContentsImplTest : public RenderViewHostImplTestHarness {
+ public:
+ virtual void SetUp() {
+ RenderViewHostImplTestHarness::SetUp();
+ WebUIControllerFactory::RegisterFactory(&factory_);
+ }
+
+ virtual void TearDown() {
+ WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
+ RenderViewHostImplTestHarness::TearDown();
+ }
+
+ private:
+ WebContentsImplTestWebUIControllerFactory factory_;
+};
+
+class TestWebContentsObserver : public WebContentsObserver {
+ public:
+ explicit TestWebContentsObserver(WebContents* contents)
+ : WebContentsObserver(contents) {
+ }
+ virtual ~TestWebContentsObserver() {}
+
+ virtual void DidFinishLoad(int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ RenderViewHost* render_view_host) OVERRIDE {
+ last_url_ = validated_url;
+ }
+ virtual void DidFailLoad(int64 frame_id,
+ const GURL& validated_url,
+ bool is_main_frame,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) OVERRIDE {
+ last_url_ = validated_url;
+ }
+
+ const GURL& last_url() const { return last_url_; }
+
+ private:
+ GURL last_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWebContentsObserver);
+};
+
+} // namespace
+
+// Test to make sure that title updates get stripped of whitespace.
+TEST_F(WebContentsImplTest, UpdateTitle) {
+ NavigationControllerImpl& cont =
+ static_cast<NavigationControllerImpl&>(controller());
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, 0, GURL(kAboutBlankURL), PAGE_TRANSITION_TYPED);
+
+ LoadCommittedDetails details;
+ cont.RendererDidNavigate(params, &details);
+
+ contents()->UpdateTitle(rvh(), 0, ASCIIToUTF16(" Lots O' Whitespace\n"),
+ base::i18n::LEFT_TO_RIGHT);
+ EXPECT_EQ(ASCIIToUTF16("Lots O' Whitespace"), contents()->GetTitle());
+}
+
+// Test view source mode for a webui page.
+TEST_F(WebContentsImplTest, NTPViewSource) {
+ NavigationControllerImpl& cont =
+ static_cast<NavigationControllerImpl&>(controller());
+ const char kUrl[] = "view-source:chrome://blah";
+ const GURL kGURL(kUrl);
+
+ process()->sink().ClearMessages();
+
+ cont.LoadURL(
+ kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ rvh()->GetDelegate()->RenderViewCreated(rvh());
+ // Did we get the expected message?
+ EXPECT_TRUE(process()->sink().GetFirstMessageMatching(
+ ViewMsg_EnableViewSourceMode::ID));
+
+ ViewHostMsg_FrameNavigate_Params params;
+ InitNavigateParams(&params, 0, kGURL, PAGE_TRANSITION_TYPED);
+ LoadCommittedDetails details;
+ cont.RendererDidNavigate(params, &details);
+ // Also check title and url.
+ EXPECT_EQ(ASCIIToUTF16(kUrl), contents()->GetTitle());
+}
+
+// Test to ensure UpdateMaxPageID is working properly.
+TEST_F(WebContentsImplTest, UpdateMaxPageID) {
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+ scoped_refptr<SiteInstance> instance2(SiteInstance::Create(NULL));
+
+ // Starts at -1.
+ EXPECT_EQ(-1, contents()->GetMaxPageID());
+ EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
+
+ // Make sure max_page_id_ is monotonically increasing per SiteInstance.
+ contents()->UpdateMaxPageID(3);
+ contents()->UpdateMaxPageID(1);
+ EXPECT_EQ(3, contents()->GetMaxPageID());
+ EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
+
+ contents()->UpdateMaxPageIDForSiteInstance(instance2.get(), 7);
+ EXPECT_EQ(3, contents()->GetMaxPageID());
+ EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(7, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
+}
+
+// Test simple same-SiteInstance navigation.
+TEST_F(WebContentsImplTest, SimpleNavigation) {
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+
+ // Navigate to URL
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(instance1, orig_rvh->GetSiteInstance());
+ // Controller's pending entry will have a NULL site instance until we assign
+ // it in DidNavigate.
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller().GetActiveEntry())->
+ site_instance() == NULL);
+
+ // DidNavigate from the page
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(instance1, orig_rvh->GetSiteInstance());
+ // Controller's entry should now have the SiteInstance, or else we won't be
+ // able to find it later.
+ EXPECT_EQ(
+ instance1,
+ NavigationEntryImpl::FromNavigationEntry(controller().GetActiveEntry())->
+ site_instance());
+}
+
+// Test that we reject NavigateToEntry if the url is over kMaxURLChars.
+TEST_F(WebContentsImplTest, NavigateToExcessivelyLongURL) {
+ // Construct a URL that's kMaxURLChars + 1 long of all 'a's.
+ const GURL url(std::string("http://example.org/").append(
+ kMaxURLChars + 1, 'a'));
+
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_GENERATED, std::string());
+ EXPECT_TRUE(controller().GetActiveEntry() == NULL);
+}
+
+// Test that navigating across a site boundary creates a new RenderViewHost
+// with a new SiteInstance. Going back should do the same.
+TEST_F(WebContentsImplTest, CrossSiteBoundaries) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ int orig_rvh_delete_count = 0;
+ orig_rvh->set_delete_counter(&orig_rvh_delete_count);
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Keep the number of active views in orig_rvh's SiteInstance
+ // non-zero so that orig_rvh doesn't get deleted when it gets
+ // swapped out.
+ static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(url, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url, contents()->GetVisibleURL());
+
+ // Navigate to new site
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_EQ(url, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, contents()->GetVisibleURL());
+ TestRenderViewHost* pending_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+ int pending_rvh_delete_count = 0;
+ pending_rvh->set_delete_counter(&pending_rvh_delete_count);
+
+ // Navigations should be suspended in pending_rvh until ShouldCloseACK.
+ EXPECT_TRUE(pending_rvh->are_navigations_suspended());
+ orig_rvh->SendShouldCloseACK(true);
+ EXPECT_FALSE(pending_rvh->are_navigations_suspended());
+
+ // DidNavigate from the pending page
+ contents()->TestDidNavigate(
+ pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+
+ // Keep the number of active views in pending_rvh's SiteInstance
+ // non-zero so that orig_rvh doesn't get deleted when it gets
+ // swapped out.
+ static_cast<SiteInstanceImpl*>(pending_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(url2, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, contents()->GetVisibleURL());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+ // We keep the original RVH around, swapped out.
+ EXPECT_TRUE(contents()->GetRenderManagerForTesting()->IsOnSwappedOutList(
+ orig_rvh));
+ EXPECT_EQ(orig_rvh_delete_count, 0);
+
+ // Going back should switch SiteInstances again. The first SiteInstance is
+ // stored in the NavigationEntry, so it should be the same as at the start.
+ // We should use the same RVH as before, swapping it back in.
+ controller().GoBack();
+ TestRenderViewHost* goback_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+ EXPECT_EQ(orig_rvh, goback_rvh);
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+
+ // Navigations should be suspended in goback_rvh until ShouldCloseACK.
+ EXPECT_TRUE(goback_rvh->are_navigations_suspended());
+ pending_rvh->SendShouldCloseACK(true);
+ EXPECT_FALSE(goback_rvh->are_navigations_suspended());
+
+ // DidNavigate from the back action
+ contents()->TestDidNavigate(
+ goback_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(goback_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(instance1, contents()->GetSiteInstance());
+ // The pending RVH should now be swapped out, not deleted.
+ EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
+ IsOnSwappedOutList(pending_rvh));
+ EXPECT_EQ(pending_rvh_delete_count, 0);
+
+ // Close contents and ensure RVHs are deleted.
+ DeleteContents();
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+ EXPECT_EQ(pending_rvh_delete_count, 1);
+}
+
+// Test that navigating across a site boundary after a crash creates a new
+// RVH without requiring a cross-site transition (i.e., PENDING state).
+TEST_F(WebContentsImplTest, CrossSiteBoundariesAfterCrash) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ int orig_rvh_delete_count = 0;
+ orig_rvh->set_delete_counter(&orig_rvh_delete_count);
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Crash the renderer.
+ orig_rvh->set_render_view_created(false);
+
+ // Navigate to new site. We should not go into PENDING.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ RenderViewHost* new_rvh = rvh();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+ EXPECT_NE(orig_rvh, new_rvh);
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+
+ // DidNavigate from the new page
+ contents()->TestDidNavigate(new_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(new_rvh, rvh());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+
+ // Close contents and ensure RVHs are deleted.
+ DeleteContents();
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+}
+
+// Test that opening a new contents in the same SiteInstance and then navigating
+// both contentses to a new site will place both contentses in a single
+// SiteInstance.
+TEST_F(WebContentsImplTest, NavigateTwoTabsCrossSite) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Open a new contents with the same SiteInstance, navigated to the same site.
+ scoped_ptr<TestWebContents> contents2(
+ TestWebContents::Create(browser_context(), instance1));
+ contents2->transition_cross_site = true;
+ contents2->GetController().LoadURL(url, Referrer(),
+ PAGE_TRANSITION_TYPED,
+ std::string());
+ // Need this page id to be 2 since the site instance is the same (which is the
+ // scope of page IDs) and we want to consider this a new page.
+ contents2->TestDidNavigate(
+ contents2->GetRenderViewHost(), 2, url, PAGE_TRANSITION_TYPED);
+
+ // Navigate first contents to a new site.
+ const GURL url2a("http://www.yahoo.com");
+ controller().LoadURL(
+ url2a, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ orig_rvh->SendShouldCloseACK(true);
+ TestRenderViewHost* pending_rvh_a =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+ contents()->TestDidNavigate(
+ pending_rvh_a, 1, url2a, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2a = contents()->GetSiteInstance();
+ EXPECT_NE(instance1, instance2a);
+
+ // Navigate second contents to the same site as the first tab.
+ const GURL url2b("http://mail.yahoo.com");
+ contents2->GetController().LoadURL(url2b, Referrer(),
+ PAGE_TRANSITION_TYPED,
+ std::string());
+ TestRenderViewHost* rvh2 =
+ static_cast<TestRenderViewHost*>(contents2->GetRenderViewHost());
+ rvh2->SendShouldCloseACK(true);
+ TestRenderViewHost* pending_rvh_b =
+ static_cast<TestRenderViewHost*>(contents2->GetPendingRenderViewHost());
+ EXPECT_TRUE(pending_rvh_b != NULL);
+ EXPECT_TRUE(contents2->cross_navigation_pending());
+
+ // NOTE(creis): We used to be in danger of showing a crash page here if the
+ // second contents hadn't navigated somewhere first (bug 1145430). That case
+ // is now covered by the CrossSiteBoundariesAfterCrash test.
+ contents2->TestDidNavigate(
+ pending_rvh_b, 2, url2b, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2b = contents2->GetSiteInstance();
+ EXPECT_NE(instance1, instance2b);
+
+ // Both contentses should now be in the same SiteInstance.
+ EXPECT_EQ(instance2a, instance2b);
+}
+
+TEST_F(WebContentsImplTest, NavigateDoesNotUseUpSiteInstance) {
+ WebContentsImplTestBrowserClient browser_client;
+ SetBrowserClientForTesting(&browser_client);
+
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ int orig_rvh_delete_count = 0;
+ orig_rvh->set_delete_counter(&orig_rvh_delete_count);
+ SiteInstanceImpl* orig_instance =
+ static_cast<SiteInstanceImpl*>(contents()->GetSiteInstance());
+
+ browser_client.set_assign_site_for_url(false);
+ // Navigate to an URL that will not assign a new SiteInstance.
+ const GURL native_url("non-site-url://stuffandthings");
+ controller().LoadURL(
+ native_url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, native_url, PAGE_TRANSITION_TYPED);
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
+ EXPECT_EQ(native_url, contents()->GetVisibleURL());
+ EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
+ EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL());
+ EXPECT_FALSE(orig_instance->HasSite());
+
+ browser_client.set_assign_site_for_url(true);
+ // Navigate to new site (should keep same site instance).
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url, contents()->GetVisibleURL());
+ EXPECT_FALSE(contents()->GetPendingRenderViewHost());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Keep the number of active views in orig_rvh's SiteInstance
+ // non-zero so that orig_rvh doesn't get deleted when it gets
+ // swapped out.
+ static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
+ EXPECT_TRUE(
+ contents()->GetSiteInstance()->GetSiteURL().DomainIs("google.com"));
+ EXPECT_EQ(url, contents()->GetLastCommittedURL());
+
+ // Navigate to another new site (should create a new site instance).
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_EQ(url, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, contents()->GetVisibleURL());
+ TestRenderViewHost* pending_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+ int pending_rvh_delete_count = 0;
+ pending_rvh->set_delete_counter(&pending_rvh_delete_count);
+
+ // Navigations should be suspended in pending_rvh until ShouldCloseACK.
+ EXPECT_TRUE(pending_rvh->are_navigations_suspended());
+ orig_rvh->SendShouldCloseACK(true);
+ EXPECT_FALSE(pending_rvh->are_navigations_suspended());
+
+ // DidNavigate from the pending page.
+ contents()->TestDidNavigate(
+ pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* new_instance = contents()->GetSiteInstance();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(url2, contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, contents()->GetVisibleURL());
+ EXPECT_NE(new_instance, orig_instance);
+ EXPECT_FALSE(contents()->GetPendingRenderViewHost());
+ // We keep the original RVH around, swapped out.
+ EXPECT_TRUE(contents()->GetRenderManagerForTesting()->IsOnSwappedOutList(
+ orig_rvh));
+ EXPECT_EQ(orig_rvh_delete_count, 0);
+
+ // Close contents and ensure RVHs are deleted.
+ DeleteContents();
+ EXPECT_EQ(orig_rvh_delete_count, 1);
+ EXPECT_EQ(pending_rvh_delete_count, 1);
+}
+
+// Test that we can find an opener RVH even if it's pending.
+// http://crbug.com/176252.
+TEST_F(WebContentsImplTest, FindOpenerRVHWhenPending) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+
+ // Navigate to a URL.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Start to navigate first tab to a new site, so that it has a pending RVH.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ orig_rvh->SendShouldCloseACK(true);
+ TestRenderViewHost* pending_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+
+ // While it is still pending, simulate opening a new tab with the first tab
+ // as its opener. This will call WebContentsImpl::CreateOpenerRenderViews
+ // on the opener to ensure that an RVH exists.
+ int opener_routing_id = contents()->CreateOpenerRenderViews(
+ pending_rvh->GetSiteInstance());
+
+ // We should find the pending RVH and not create a new one.
+ EXPECT_EQ(pending_rvh->GetRoutingID(), opener_routing_id);
+}
+
+// Tests that WebContentsImpl uses the current URL, not the SiteInstance's site,
+// to determine whether a navigation is cross-site.
+TEST_F(WebContentsImplTest, CrossSiteComparesAgainstCurrentPage) {
+ contents()->transition_cross_site = true;
+ RenderViewHost* orig_rvh = rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(
+ orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Open a related contents to a second site.
+ scoped_ptr<TestWebContents> contents2(
+ TestWebContents::Create(browser_context(), instance1));
+ contents2->transition_cross_site = true;
+ const GURL url2("http://www.yahoo.com");
+ contents2->GetController().LoadURL(url2, Referrer(),
+ PAGE_TRANSITION_TYPED,
+ std::string());
+ // The first RVH in contents2 isn't live yet, so we shortcut the cross site
+ // pending.
+ TestRenderViewHost* rvh2 = static_cast<TestRenderViewHost*>(
+ contents2->GetRenderViewHost());
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+ contents2->TestDidNavigate(rvh2, 2, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2 = contents2->GetSiteInstance();
+ EXPECT_NE(instance1, instance2);
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+
+ // Simulate a link click in first contents to second site. Doesn't switch
+ // SiteInstances, because we don't intercept WebKit navigations.
+ contents()->TestDidNavigate(
+ orig_rvh, 2, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance3 = contents()->GetSiteInstance();
+ EXPECT_EQ(instance1, instance3);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+
+ // Navigate to the new site. Doesn't switch SiteInstancees, because we
+ // compare against the current URL, not the SiteInstance's site.
+ const GURL url3("http://mail.yahoo.com");
+ controller().LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ contents()->TestDidNavigate(
+ orig_rvh, 3, url3, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance4 = contents()->GetSiteInstance();
+ EXPECT_EQ(instance1, instance4);
+}
+
+// Test that the onbeforeunload and onunload handlers run when navigating
+// across site boundaries.
+TEST_F(WebContentsImplTest, CrossSiteUnloadHandlers) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Navigate to new site, but simulate an onbeforeunload denial.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+ base::TimeTicks now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, false, now, now));
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Navigate again, but simulate an onbeforeunload approval.
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+ now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ TestRenderViewHost* pending_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost());
+
+ // We won't hear DidNavigate until the onunload handler has finished running.
+
+ // DidNavigate from the pending page.
+ contents()->TestDidNavigate(
+ pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, rvh());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+}
+
+// Test that during a slow cross-site navigation, the original renderer can
+// navigate to a different URL and have it displayed, canceling the slow
+// navigation.
+TEST_F(WebContentsImplTest, CrossSiteNavigationPreempted) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Navigate to new site, simulating an onbeforeunload approval.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+ base::TimeTicks now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+
+ // Suppose the original renderer navigates before the new one is ready.
+ orig_rvh->SendNavigate(2, GURL("http://www.google.com/foo"));
+
+ // Verify that the pending navigation is cancelled.
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, rvh());
+ EXPECT_EQ(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+}
+
+TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) {
+ contents()->transition_cross_site = true;
+
+ // Start with a web ui page, which gets a new RVH with WebUI bindings.
+ const GURL url1("chrome://blah");
+ controller().LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ TestRenderViewHost* ntp_rvh = test_rvh();
+ contents()->TestDidNavigate(ntp_rvh, 1, url1, PAGE_TRANSITION_TYPED);
+ NavigationEntry* entry1 = controller().GetLastCommittedEntry();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(ntp_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(url1, entry1->GetURL());
+ EXPECT_EQ(instance1,
+ NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
+ EXPECT_TRUE(ntp_rvh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Navigate to new site.
+ const GURL url2("http://www.google.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ TestRenderViewHost* google_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+
+ // Simulate beforeunload approval.
+ EXPECT_TRUE(ntp_rvh->is_waiting_for_beforeunload_ack());
+ base::TimeTicks now = base::TimeTicks::Now();
+ ntp_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+
+ // DidNavigate from the pending page.
+ contents()->TestDidNavigate(
+ google_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ NavigationEntry* entry2 = controller().GetLastCommittedEntry();
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_FALSE(contents()->GetPendingRenderViewHost());
+ EXPECT_EQ(url2, entry2->GetURL());
+ EXPECT_EQ(instance2,
+ NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
+ EXPECT_FALSE(google_rvh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Navigate to third page on same site.
+ const GURL url3("http://news.google.com");
+ controller().LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ contents()->TestDidNavigate(
+ google_rvh, 2, url3, PAGE_TRANSITION_TYPED);
+ NavigationEntry* entry3 = controller().GetLastCommittedEntry();
+ SiteInstance* instance3 = contents()->GetSiteInstance();
+
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(instance2, instance3);
+ EXPECT_FALSE(contents()->GetPendingRenderViewHost());
+ EXPECT_EQ(url3, entry3->GetURL());
+ EXPECT_EQ(instance3,
+ NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
+
+ // Go back within the site.
+ controller().GoBack();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(entry2, controller().GetPendingEntry());
+
+ // Before that commits, go back again.
+ controller().GoBack();
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost());
+ EXPECT_EQ(entry1, controller().GetPendingEntry());
+
+ // Simulate beforeunload approval.
+ EXPECT_TRUE(google_rvh->is_waiting_for_beforeunload_ack());
+ now = base::TimeTicks::Now();
+ google_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+
+ // DidNavigate from the first back. This aborts the second back's pending RVH.
+ contents()->TestDidNavigate(google_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+
+ // We should commit this page and forget about the second back.
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_FALSE(controller().GetPendingEntry());
+ EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
+ EXPECT_EQ(url2, controller().GetLastCommittedEntry()->GetURL());
+
+ // We should not have corrupted the NTP entry.
+ EXPECT_EQ(instance3,
+ NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
+ EXPECT_EQ(instance2,
+ NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
+ EXPECT_EQ(instance1,
+ NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
+ EXPECT_EQ(url1, entry1->GetURL());
+}
+
+// Test that during a slow cross-site navigation, a sub-frame navigation in the
+// original renderer will not cancel the slow navigation (bug 42029).
+TEST_F(WebContentsImplTest, CrossSiteNavigationNotPreemptedByFrame) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Start navigating to new site.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Simulate a sub-frame navigation arriving and ensure the RVH is still
+ // waiting for a before unload response.
+ orig_rvh->SendNavigateWithTransition(1, GURL("http://google.com/frame"),
+ PAGE_TRANSITION_AUTO_SUBFRAME);
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+
+ // Now simulate the onbeforeunload approval and verify the navigation is
+ // not canceled.
+ base::TimeTicks now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+}
+
+// Test that a cross-site navigation is not preempted if the previous
+// renderer sends a FrameNavigate message just before being told to stop.
+// We should only preempt the cross-site navigation if the previous renderer
+// has started a new navigation. See http://crbug.com/79176.
+TEST_F(WebContentsImplTest, CrossSiteNotPreemptedDuringBeforeUnload) {
+ contents()->transition_cross_site = true;
+
+ // Navigate to NTP URL.
+ const GURL url("chrome://blah");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ TestRenderViewHost* orig_rvh = test_rvh();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+
+ // Navigate to new site, with the beforeunload request in flight.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ TestRenderViewHost* pending_rvh =
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+
+ // Suppose the first navigation tries to commit now, with a
+ // ViewMsg_Stop in flight. This should not cancel the pending navigation,
+ // but it should act as if the beforeunload ack arrived.
+ orig_rvh->SendNavigate(1, GURL("chrome://blah"));
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+
+ // The pending navigation should be able to commit successfully.
+ contents()->TestDidNavigate(pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
+}
+
+// Test that the original renderer cannot preempt a cross-site navigation once
+// the unload request has been made. At this point, the cross-site navigation
+// is almost ready to be displayed, and the original renderer is only given a
+// short chance to run an unload handler. Prevents regression of bug 23942.
+TEST_F(WebContentsImplTest, CrossSiteCantPreemptAfterUnload) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Navigate to new site, simulating an onbeforeunload approval.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ base::TimeTicks now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ TestRenderViewHost* pending_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost());
+
+ // Simulate the pending renderer's response, which leads to an unload request
+ // being sent to orig_rvh.
+ contents()->GetRenderManagerForTesting()->OnCrossSiteResponse(
+ pending_rvh, GlobalRequestID(0, 0));
+
+ // Suppose the original renderer navigates now, while the unload request is in
+ // flight. We should ignore it, wait for the unload ack, and let the pending
+ // request continue. Otherwise, the contents may close spontaneously or stop
+ // responding to navigation requests. (See bug 23942.)
+ ViewHostMsg_FrameNavigate_Params params1a;
+ InitNavigateParams(&params1a, 2, GURL("http://www.google.com/foo"),
+ PAGE_TRANSITION_TYPED);
+ orig_rvh->SendNavigate(2, GURL("http://www.google.com/foo"));
+
+ // Verify that the pending navigation is still in progress.
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() != NULL);
+
+ // DidNavigate from the pending page should commit it.
+ contents()->TestDidNavigate(
+ pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(pending_rvh, rvh());
+ EXPECT_NE(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+}
+
+// Test that a cross-site navigation that doesn't commit after the unload
+// handler doesn't leave the contents in a stuck state. http://crbug.com/88562
+TEST_F(WebContentsImplTest, CrossSiteNavigationCanceled) {
+ contents()->transition_cross_site = true;
+ TestRenderViewHost* orig_rvh = test_rvh();
+ SiteInstance* instance1 = contents()->GetSiteInstance();
+
+ // Navigate to URL. First URL should use first RenderViewHost.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+
+ // Navigate to new site, simulating an onbeforeunload approval.
+ const GURL url2("http://www.yahoo.com");
+ controller().LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
+ base::TimeTicks now = base::TimeTicks::Now();
+ orig_rvh->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(0, true, now, now));
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+
+ // Simulate swap out message when the response arrives.
+ orig_rvh->set_is_swapped_out(true);
+
+ // Suppose the navigation doesn't get a chance to commit, and the user
+ // navigates in the current RVH's SiteInstance.
+ controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Verify that the pending navigation is cancelled and the renderer is no
+ // longer swapped out.
+ EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
+ SiteInstance* instance2 = contents()->GetSiteInstance();
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, rvh());
+ EXPECT_FALSE(orig_rvh->is_swapped_out());
+ EXPECT_EQ(instance1, instance2);
+ EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
+}
+
+// Test that NavigationEntries have the correct page state after going
+// forward and back. Prevents regression for bug 1116137.
+TEST_F(WebContentsImplTest, NavigationEntryContentState) {
+ TestRenderViewHost* orig_rvh = test_rvh();
+
+ // Navigate to URL. There should be no committed entry yet.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ NavigationEntry* entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry == NULL);
+
+ // Committed entry should have page state after DidNavigate.
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry->GetPageState().IsValid());
+
+ // Navigate to same site.
+ const GURL url2("http://images.google.com");
+ controller().LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry->GetPageState().IsValid());
+
+ // Committed entry should have page state after DidNavigate.
+ contents()->TestDidNavigate(orig_rvh, 2, url2, PAGE_TRANSITION_TYPED);
+ entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry->GetPageState().IsValid());
+
+ // Now go back. Committed entry should still have page state.
+ controller().GoBack();
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry->GetPageState().IsValid());
+}
+
+// Test that NavigationEntries have the correct page state and SiteInstance
+// state after opening a new window to about:blank. Prevents regression for
+// bugs b/1116137 and http://crbug.com/111975.
+TEST_F(WebContentsImplTest, NavigationEntryContentStateNewWindow) {
+ TestRenderViewHost* orig_rvh = test_rvh();
+
+ // When opening a new window, it is navigated to about:blank internally.
+ // Currently, this results in two DidNavigate events.
+ const GURL url(kAboutBlankURL);
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+ contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
+
+ // Should have a page state here.
+ NavigationEntry* entry = controller().GetLastCommittedEntry();
+ EXPECT_TRUE(entry->GetPageState().IsValid());
+
+ // The SiteInstance should be available for other navigations to use.
+ NavigationEntryImpl* entry_impl =
+ NavigationEntryImpl::FromNavigationEntry(entry);
+ EXPECT_FALSE(entry_impl->site_instance()->HasSite());
+ int32 site_instance_id = entry_impl->site_instance()->GetId();
+
+ // Navigating to a normal page should not cause a process swap.
+ const GURL new_url("http://www.google.com");
+ controller().LoadURL(new_url, Referrer(),
+ PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(contents()->cross_navigation_pending());
+ EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
+ contents()->TestDidNavigate(orig_rvh, 1, new_url, PAGE_TRANSITION_TYPED);
+ NavigationEntryImpl* entry_impl2 = NavigationEntryImpl::FromNavigationEntry(
+ controller().GetLastCommittedEntry());
+ EXPECT_EQ(site_instance_id, entry_impl2->site_instance()->GetId());
+ EXPECT_TRUE(entry_impl2->site_instance()->HasSite());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Interstitial Tests
+////////////////////////////////////////////////////////////////////////////////
+
+// Test navigating to a page (with the navigation initiated from the browser,
+// as when a URL is typed in the location bar) that shows an interstitial and
+// creates a new navigation entry, then hiding it without proceeding.
+TEST_F(WebContentsImplTest,
+ ShowInterstitialFromBrowserWithNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Initiate a browser navigation that will trigger the interstitial
+ controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
+ PAGE_TRANSITION_TYPED, std::string());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url2);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page (with the navigation initiated from the renderer,
+// as when clicking on a link in the page) that shows an interstitial and
+// creates a new navigation entry, then hiding it without proceeding.
+TEST_F(WebContentsImplTest,
+ ShowInterstitiaFromRendererlWithNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial (no pending entry, the interstitial would have been
+ // triggered by clicking on a link).
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url2);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial without creating a new
+// navigation entry (this happens when the interstitial is triggered by a
+// sub-resource in the page), then hiding it without proceeding.
+TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationDontProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), false, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ // The URL specified to the interstitial should have been ignored.
+ EXPECT_TRUE(entry->GetURL() == url1);
+
+ // Now don't proceed.
+ interstitial->DontProceed();
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page (with the navigation initiated from the browser,
+// as when a URL is typed in the location bar) that shows an interstitial and
+// creates a new navigation entry, then proceeding.
+TEST_F(WebContentsImplTest,
+ ShowInterstitialFromBrowserNewNavigationProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Initiate a browser navigation that will trigger the interstitial
+ controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
+ PAGE_TRANSITION_TYPED, std::string());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url2);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // The interstitial should show until the new navigation commits.
+ RunAllPendingInMessageLoop();
+ ASSERT_FALSE(deleted);
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+
+ // Simulate the navigation to the page, that's when the interstitial gets
+ // hidden.
+ GURL url3("http://www.thepage.com");
+ test_rvh()->SendNavigate(2, url3);
+
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url3);
+
+ EXPECT_EQ(2, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page (with the navigation initiated from the renderer,
+// as when clicking on a link in the page) that shows an interstitial and
+// creates a new navigation entry, then proceeding.
+TEST_F(WebContentsImplTest,
+ ShowInterstitialFromRendererNewNavigationProceed) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url2);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // The interstitial should show until the new navigation commits.
+ RunAllPendingInMessageLoop();
+ ASSERT_FALSE(deleted);
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+
+ // Simulate the navigation to the page, that's when the interstitial gets
+ // hidden.
+ GURL url3("http://www.thepage.com");
+ test_rvh()->SendNavigate(2, url3);
+
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url3);
+
+ EXPECT_EQ(2, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial without creating a new
+// navigation entry (this happens when the interstitial is triggered by a
+// sub-resource in the page), then proceeding.
+TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationProceed) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), false, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // The interstitial should not show until its navigation has committed.
+ EXPECT_FALSE(interstitial->is_showing());
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ // Let's commit the interstitial navigation.
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_TRUE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ // The URL specified to the interstitial should have been ignored.
+ EXPECT_TRUE(entry->GetURL() == url1);
+
+ // Then proceed.
+ interstitial->Proceed();
+ // Since this is not a new navigation, the previous page is dismissed right
+ // away and shows the original page.
+ EXPECT_EQ(TestInterstitialPage::OKED, state);
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == url1);
+
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial, then navigating away.
+TEST_F(WebContentsImplTest, ShowInterstitialThenNavigate) {
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+
+ // While interstitial showing, navigate to a new URL.
+ const GURL url2("http://www.yahoo.com");
+ test_rvh()->SendNavigate(1, url2);
+
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial, then going back.
+TEST_F(WebContentsImplTest, ShowInterstitialThenGoBack) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL interstitial_url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(2, interstitial_url);
+
+ // While the interstitial is showing, go back.
+ controller().GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ // Make sure we are back to the original page and that the interstitial is
+ // gone.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(url1.spec(), entry->GetURL().spec());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial, has a renderer crash,
+// and then goes back.
+TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenGoBack) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL interstitial_url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(2, interstitial_url);
+
+ // Crash the renderer
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_RenderProcessGone(
+ 0, base::TERMINATION_STATUS_PROCESS_CRASHED, -1));
+
+ // While the interstitial is showing, go back.
+ controller().GoBack();
+ test_rvh()->SendNavigate(1, url1);
+
+ // Make sure we are back to the original page and that the interstitial is
+ // gone.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(url1.spec(), entry->GetURL().spec());
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial, has the renderer crash,
+// and then navigates to the interstitial.
+TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenNavigate) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL interstitial_url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+
+ // Crash the renderer
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_RenderProcessGone(
+ 0, base::TERMINATION_STATUS_PROCESS_CRASHED, -1));
+
+ interstitial->TestDidNavigate(2, interstitial_url);
+}
+
+// Test navigating to a page that shows an interstitial, then close the
+// contents.
+TEST_F(WebContentsImplTest, ShowInterstitialThenCloseTab) {
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+
+ // Now close the contents.
+ DeleteContents();
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test navigating to a page that shows an interstitial, then close the
+// contents.
+TEST_F(WebContentsImplTest, ShowInterstitialThenCloseAndShutdown) {
+ // Show interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ interstitial->GetRenderViewHostForTesting());
+
+ // Now close the contents.
+ DeleteContents();
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+
+ // Before the interstitial has a chance to process its shutdown task,
+ // simulate quitting the browser. This goes through all processes and
+ // tells them to destruct.
+ rvh->OnMessageReceived(
+ ViewHostMsg_RenderProcessGone(0, 0, 0));
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test that after Proceed is called and an interstitial is still shown, no more
+// commands get executed.
+TEST_F(WebContentsImplTest, ShowInterstitialProceedMultipleCommands) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url2);
+
+ // Run a command.
+ EXPECT_EQ(0, interstitial->command_received_count());
+ interstitial->TestDomOperationResponse("toto");
+ EXPECT_EQ(1, interstitial->command_received_count());
+
+ // Then proceed.
+ interstitial->Proceed();
+ RunAllPendingInMessageLoop();
+ ASSERT_FALSE(deleted);
+
+ // While the navigation to the new page is pending, send other commands, they
+ // should be ignored.
+ interstitial->TestDomOperationResponse("hello");
+ interstitial->TestDomOperationResponse("hi");
+ EXPECT_EQ(1, interstitial->command_received_count());
+}
+
+// Test showing an interstitial while another interstitial is already showing.
+TEST_F(WebContentsImplTest, ShowInterstitialOnInterstitial) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL start_url("http://www.google.com");
+ test_rvh()->SendNavigate(1, start_url);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state1 =
+ TestInterstitialPage::INVALID;
+ bool deleted1 = false;
+ GURL url1("http://interstitial1");
+ TestInterstitialPage* interstitial1 =
+ new TestInterstitialPage(contents(), true, url1, &state1, &deleted1);
+ TestInterstitialPageStateGuard state_guard1(interstitial1);
+ interstitial1->Show();
+ interstitial1->TestDidNavigate(1, url1);
+
+ // Now show another interstitial.
+ TestInterstitialPage::InterstitialState state2 =
+ TestInterstitialPage::INVALID;
+ bool deleted2 = false;
+ GURL url2("http://interstitial2");
+ TestInterstitialPage* interstitial2 =
+ new TestInterstitialPage(contents(), true, url2, &state2, &deleted2);
+ TestInterstitialPageStateGuard state_guard2(interstitial2);
+ interstitial2->Show();
+ interstitial2->TestDidNavigate(1, url2);
+
+ // Showing interstitial2 should have caused interstitial1 to go away.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state1);
+ EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted1);
+ ASSERT_FALSE(deleted2);
+
+ // Let's make sure interstitial2 is working as intended.
+ interstitial2->Proceed();
+ GURL landing_url("http://www.thepage.com");
+ test_rvh()->SendNavigate(2, landing_url);
+
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == landing_url);
+ EXPECT_EQ(2, controller().GetEntryCount());
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted2);
+}
+
+// Test showing an interstitial, proceeding and then navigating to another
+// interstitial.
+TEST_F(WebContentsImplTest, ShowInterstitialProceedShowInterstitial) {
+ // Navigate to a page so we have a navigation entry in the controller.
+ GURL start_url("http://www.google.com");
+ test_rvh()->SendNavigate(1, start_url);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state1 =
+ TestInterstitialPage::INVALID;
+ bool deleted1 = false;
+ GURL url1("http://interstitial1");
+ TestInterstitialPage* interstitial1 =
+ new TestInterstitialPage(contents(), true, url1, &state1, &deleted1);
+ TestInterstitialPageStateGuard state_guard1(interstitial1);
+ interstitial1->Show();
+ interstitial1->TestDidNavigate(1, url1);
+
+ // Take action. The interstitial won't be hidden until the navigation is
+ // committed.
+ interstitial1->Proceed();
+ EXPECT_EQ(TestInterstitialPage::OKED, state1);
+
+ // Now show another interstitial (simulating the navigation causing another
+ // interstitial).
+ TestInterstitialPage::InterstitialState state2 =
+ TestInterstitialPage::INVALID;
+ bool deleted2 = false;
+ GURL url2("http://interstitial2");
+ TestInterstitialPage* interstitial2 =
+ new TestInterstitialPage(contents(), true, url2, &state2, &deleted2);
+ TestInterstitialPageStateGuard state_guard2(interstitial2);
+ interstitial2->Show();
+ interstitial2->TestDidNavigate(1, url2);
+
+ // Showing interstitial2 should have caused interstitial1 to go away.
+ EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted1);
+ ASSERT_FALSE(deleted2);
+
+ // Let's make sure interstitial2 is working as intended.
+ interstitial2->Proceed();
+ GURL landing_url("http://www.thepage.com");
+ test_rvh()->SendNavigate(2, landing_url);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted2);
+ EXPECT_FALSE(contents()->ShowingInterstitialPage());
+ EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
+ NavigationEntry* entry = controller().GetActiveEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_TRUE(entry->GetURL() == landing_url);
+ EXPECT_EQ(2, controller().GetEntryCount());
+}
+
+// Test that navigating away from an interstitial while it's loading cause it
+// not to show.
+TEST_F(WebContentsImplTest, NavigateBeforeInterstitialShows) {
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL interstitial_url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+
+ // Let's simulate a navigation initiated from the browser before the
+ // interstitial finishes loading.
+ const GURL url("http://www.google.com");
+ controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_FALSE(interstitial->is_showing());
+ RunAllPendingInMessageLoop();
+ ASSERT_FALSE(deleted);
+
+ // Now let's make the interstitial navigation commit.
+ interstitial->TestDidNavigate(1, interstitial_url);
+
+ // After it loaded the interstitial should be gone.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Test that a new request to show an interstitial while an interstitial is
+// pending does not cause problems. htp://crbug/29655 and htp://crbug/9442.
+TEST_F(WebContentsImplTest, TwoQuickInterstitials) {
+ GURL interstitial_url("http://interstitial");
+
+ // Show a first interstitial.
+ TestInterstitialPage::InterstitialState state1 =
+ TestInterstitialPage::INVALID;
+ bool deleted1 = false;
+ TestInterstitialPage* interstitial1 =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state1, &deleted1);
+ TestInterstitialPageStateGuard state_guard1(interstitial1);
+ interstitial1->Show();
+
+ // Show another interstitial on that same contents before the first one had
+ // time to load.
+ TestInterstitialPage::InterstitialState state2 =
+ TestInterstitialPage::INVALID;
+ bool deleted2 = false;
+ TestInterstitialPage* interstitial2 =
+ new TestInterstitialPage(contents(), true, interstitial_url,
+ &state2, &deleted2);
+ TestInterstitialPageStateGuard state_guard2(interstitial2);
+ interstitial2->Show();
+
+ // The first interstitial should have been closed and deleted.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state1);
+ // The 2nd one should still be OK.
+ EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
+
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted1);
+ ASSERT_FALSE(deleted2);
+
+ // Make the interstitial navigation commit it should be showing.
+ interstitial2->TestDidNavigate(1, interstitial_url);
+ EXPECT_EQ(interstitial2, contents()->GetInterstitialPage());
+}
+
+// Test showing an interstitial and have its renderer crash.
+TEST_F(WebContentsImplTest, InterstitialCrasher) {
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ // Simulate a renderer crash before the interstitial is shown.
+ interstitial->TestRenderViewTerminated(
+ base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
+ // The interstitial should have been dismissed.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+
+ // Now try again but this time crash the intersitial after it was shown.
+ interstitial =
+ new TestInterstitialPage(contents(), true, url, &state, &deleted);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url);
+ // Simulate a renderer crash.
+ interstitial->TestRenderViewTerminated(
+ base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
+ // The interstitial should have been dismissed.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+}
+
+// Tests that showing an interstitial as a result of a browser initiated
+// navigation while an interstitial is showing does not remove the pending
+// entry (see http://crbug.com/9791).
+TEST_F(WebContentsImplTest, NewInterstitialDoesNotCancelPendingEntry) {
+ const char kUrl[] = "http://www.badguys.com/";
+ const GURL kGURL(kUrl);
+
+ // Start a navigation to a page
+ contents()->GetController().LoadURL(
+ kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Simulate that navigation triggering an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, kGURL, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, kGURL);
+
+ // Initiate a new navigation from the browser that also triggers an
+ // interstitial.
+ contents()->GetController().LoadURL(
+ kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ TestInterstitialPage::InterstitialState state2 =
+ TestInterstitialPage::INVALID;
+ bool deleted2 = false;
+ TestInterstitialPage* interstitial2 =
+ new TestInterstitialPage(contents(), true, kGURL, &state2, &deleted2);
+ TestInterstitialPageStateGuard state_guard2(interstitial2);
+ interstitial2->Show();
+ interstitial2->TestDidNavigate(1, kGURL);
+
+ // Make sure we still have an entry.
+ NavigationEntry* entry = contents()->GetController().GetPendingEntry();
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kUrl, entry->GetURL().spec());
+
+ // And that the first interstitial is gone, but not the second.
+ EXPECT_EQ(TestInterstitialPage::CANCELED, state);
+ EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(deleted);
+ EXPECT_FALSE(deleted2);
+}
+
+// Tests that Javascript messages are not shown while an interstitial is
+// showing.
+TEST_F(WebContentsImplTest, NoJSMessageOnInterstitials) {
+ const char kUrl[] = "http://www.badguys.com/";
+ const GURL kGURL(kUrl);
+
+ // Start a navigation to a page
+ contents()->GetController().LoadURL(
+ kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // DidNavigate from the page
+ contents()->TestDidNavigate(rvh(), 1, kGURL, PAGE_TRANSITION_TYPED);
+
+ // Simulate showing an interstitial while the page is showing.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, kGURL, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, kGURL);
+
+ // While the interstitial is showing, let's simulate the hidden page
+ // attempting to show a JS message.
+ IPC::Message* dummy_message = new IPC::Message;
+ bool did_suppress_message = false;
+ contents()->RunJavaScriptMessage(contents()->GetRenderViewHost(),
+ ASCIIToUTF16("This is an informative message"), ASCIIToUTF16("OK"),
+ kGURL, JAVASCRIPT_MESSAGE_TYPE_ALERT, dummy_message,
+ &did_suppress_message);
+ EXPECT_TRUE(did_suppress_message);
+}
+
+// Makes sure that if the source passed to CopyStateFromAndPrune has an
+// interstitial it isn't copied over to the destination.
+TEST_F(WebContentsImplTest, CopyStateFromAndPruneSourceInterstitial) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(1, controller().GetEntryCount());
+
+ // Initiate a browser navigation that will trigger the interstitial
+ controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
+ PAGE_TRANSITION_TYPED, std::string());
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url2("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(contents(), true, url2, &state, &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url2);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_EQ(2, controller().GetEntryCount());
+
+ // Create another NavigationController.
+ GURL url3("http://foo2");
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ NavigationEntryImpl::FromNavigationEntry(
+ other_controller.GetEntryAtIndex(0))->site_instance(), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller());
+
+ // The merged controller should only have two entries: url1 and url2.
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+
+ // And the merged controller shouldn't be showing an interstitial.
+ EXPECT_FALSE(other_contents->ShowingInterstitialPage());
+}
+
+// Makes sure that CopyStateFromAndPrune cannot be called if the target is
+// showing an interstitial.
+TEST_F(WebContentsImplTest, CopyStateFromAndPruneTargetInterstitial) {
+ // Navigate to a page.
+ GURL url1("http://www.google.com");
+ contents()->NavigateAndCommit(url1);
+
+ // Create another NavigationController.
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+
+ // Navigate it to url2.
+ GURL url2("http://foo2");
+ other_contents->NavigateAndCommit(url2);
+
+ // Show an interstitial.
+ TestInterstitialPage::InterstitialState state =
+ TestInterstitialPage::INVALID;
+ bool deleted = false;
+ GURL url3("http://interstitial");
+ TestInterstitialPage* interstitial =
+ new TestInterstitialPage(other_contents.get(), true, url3, &state,
+ &deleted);
+ TestInterstitialPageStateGuard state_guard(interstitial);
+ interstitial->Show();
+ interstitial->TestDidNavigate(1, url3);
+ EXPECT_TRUE(interstitial->is_showing());
+ EXPECT_EQ(2, other_controller.GetEntryCount());
+
+ // Ensure that we do not allow calling CopyStateFromAndPrune when an
+ // interstitial is showing in the target.
+ EXPECT_FALSE(other_controller.CanPruneAllButVisible());
+}
+
+// Regression test for http://crbug.com/168611 - the URLs passed by the
+// DidFinishLoad and DidFailLoadWithError IPCs should get filtered.
+TEST_F(WebContentsImplTest, FilterURLs) {
+ TestWebContentsObserver observer(contents());
+
+ // A navigation to about:whatever should always look like a navigation to
+ // about:blank
+ GURL url_normalized(kAboutBlankURL);
+ GURL url_from_ipc("about:whatever");
+
+ // We navigate the test WebContents to about:blank, since NavigateAndCommit
+ // will use the given URL to create the NavigationEntry as well, and that
+ // entry should contain the filtered URL.
+ contents()->NavigateAndCommit(url_normalized);
+
+ // Check that an IPC with about:whatever is correctly normalized.
+ contents()->TestDidFinishLoad(1, url_from_ipc, true);
+
+ EXPECT_EQ(url_normalized, observer.last_url());
+
+ // Create and navigate another WebContents.
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ TestWebContentsObserver other_observer(other_contents.get());
+ other_contents->NavigateAndCommit(url_normalized);
+
+ // Check that an IPC with about:whatever is correctly normalized.
+ other_contents->TestDidFailLoadWithError(
+ 1, url_from_ipc, true, 1, string16());
+ EXPECT_EQ(url_normalized, other_observer.last_url());
+}
+
+// Test that if a pending contents is deleted before it is shown, we don't
+// crash.
+TEST_F(WebContentsImplTest, PendingContents) {
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ contents()->AddPendingContents(other_contents.get());
+ int route_id = other_contents->GetRenderViewHost()->GetRoutingID();
+ other_contents.reset();
+ EXPECT_EQ(NULL, contents()->GetCreatedWindow(route_id));
+}
+
+// This test asserts the shape of the frame tree is correct, based on incoming
+// frame attached/detached messages.
+TEST_F(WebContentsImplTest, FrameTreeShape) {
+ std::string no_children_node("no children node");
+ std::string deep_subtree("node with deep subtree");
+
+ // The initial navigation will create a frame_tree_root_ node with the top
+ // level frame id. Simulate that by just creating it here.
+ contents()->frame_tree_root_.reset(
+ new FrameTreeNode(5, std::string("top-level")));
+
+ // Let's send a series of messages for frame attached and build the
+ // frame tree.
+ contents()->OnFrameAttached(5, 14, std::string());
+ contents()->OnFrameAttached(5, 15, std::string());
+ contents()->OnFrameAttached(5, 16, std::string());
+
+ contents()->OnFrameAttached(14, 244, std::string());
+ contents()->OnFrameAttached(14, 245, std::string());
+
+ contents()->OnFrameAttached(15, 255, no_children_node);
+
+ contents()->OnFrameAttached(16, 264, std::string());
+ contents()->OnFrameAttached(16, 265, std::string());
+ contents()->OnFrameAttached(16, 266, std::string());
+ contents()->OnFrameAttached(16, 267, deep_subtree);
+ contents()->OnFrameAttached(16, 268, std::string());
+
+ contents()->OnFrameAttached(267, 365, std::string());
+ contents()->OnFrameAttached(365, 455, std::string());
+ contents()->OnFrameAttached(455, 555, std::string());
+ contents()->OnFrameAttached(555, 655, std::string());
+
+ // Now, verify the tree structure is as expected.
+ FrameTreeNode* root = contents()->frame_tree_root_.get();
+ EXPECT_EQ(5, root->frame_id());
+ EXPECT_EQ(3UL, root->child_count());
+
+ EXPECT_EQ(2UL, root->child_at(0)->child_count());
+ EXPECT_EQ(0UL, root->child_at(0)->child_at(0)->child_count());
+ EXPECT_EQ(0UL, root->child_at(0)->child_at(1)->child_count());
+
+ EXPECT_EQ(1UL, root->child_at(1)->child_count());
+ EXPECT_EQ(0UL, root->child_at(1)->child_at(0)->child_count());
+ EXPECT_STREQ(no_children_node.c_str(),
+ root->child_at(1)->child_at(0)->frame_name().c_str());
+
+ EXPECT_EQ(5UL, root->child_at(2)->child_count());
+ EXPECT_EQ(0UL, root->child_at(2)->child_at(0)->child_count());
+ EXPECT_EQ(0UL, root->child_at(2)->child_at(1)->child_count());
+ EXPECT_EQ(0UL, root->child_at(2)->child_at(2)->child_count());
+ EXPECT_EQ(1UL, root->child_at(2)->child_at(3)->child_count());
+ EXPECT_STREQ(deep_subtree.c_str(),
+ root->child_at(2)->child_at(3)->frame_name().c_str());
+ EXPECT_EQ(0UL, root->child_at(2)->child_at(4)->child_count());
+
+ FrameTreeNode* deep_tree = root->child_at(2)->child_at(3)->child_at(0);
+ EXPECT_EQ(365, deep_tree->frame_id());
+ EXPECT_EQ(1UL, deep_tree->child_count());
+ EXPECT_EQ(455, deep_tree->child_at(0)->frame_id());
+ EXPECT_EQ(1UL, deep_tree->child_at(0)->child_count());
+ EXPECT_EQ(555, deep_tree->child_at(0)->child_at(0)->frame_id());
+ EXPECT_EQ(1UL, deep_tree->child_at(0)->child_at(0)->child_count());
+ EXPECT_EQ(655, deep_tree->child_at(0)->child_at(0)->child_at(0)->frame_id());
+ EXPECT_EQ(0UL,
+ deep_tree->child_at(0)->child_at(0)->child_at(0)->child_count());
+
+ // Test removing of nodes.
+ contents()->OnFrameDetached(555, 655);
+ EXPECT_EQ(0UL, deep_tree->child_at(0)->child_at(0)->child_count());
+
+ contents()->OnFrameDetached(16, 265);
+ EXPECT_EQ(4UL, root->child_at(2)->child_count());
+
+ contents()->OnFrameDetached(5, 15);
+ EXPECT_EQ(2UL, root->child_count());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_screenshot_manager.cc b/chromium/content/browser/web_contents/web_contents_screenshot_manager.cc
new file mode 100644
index 00000000000..b2eeb464265
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_screenshot_manager.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 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 "content/browser/web_contents/web_contents_screenshot_manager.h"
+
+#include "base/command_line.h"
+#include "base/threading/worker_pool.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/content_switches.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace {
+
+// Minimum delay between taking screenshots.
+const int kMinScreenshotIntervalMS = 1000;
+
+}
+
+namespace content {
+
+// Encodes an SkBitmap to PNG data in a worker thread.
+class ScreenshotData : public base::RefCountedThreadSafe<ScreenshotData> {
+ public:
+ ScreenshotData() {
+ }
+
+ void EncodeScreenshot(const SkBitmap& bitmap, base::Closure callback) {
+ if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
+ base::Bind(&ScreenshotData::EncodeOnWorker,
+ this,
+ bitmap),
+ callback,
+ true)) {
+ callback.Run();
+ }
+ }
+
+ scoped_refptr<base::RefCountedBytes> data() const { return data_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<ScreenshotData>;
+ virtual ~ScreenshotData() {
+ }
+
+ void EncodeOnWorker(const SkBitmap& bitmap) {
+ std::vector<unsigned char> data;
+ if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &data))
+ data_ = new base::RefCountedBytes(data);
+ }
+
+ scoped_refptr<base::RefCountedBytes> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
+};
+
+WebContentsScreenshotManager::WebContentsScreenshotManager(
+ NavigationControllerImpl* owner)
+ : owner_(owner),
+ screenshot_factory_(this),
+ min_screenshot_interval_ms_(kMinScreenshotIntervalMS) {
+}
+
+WebContentsScreenshotManager::~WebContentsScreenshotManager() {
+}
+
+void WebContentsScreenshotManager::TakeScreenshot() {
+ static bool overscroll_enabled = CommandLine::ForCurrentProcess()->
+ GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
+ if (!overscroll_enabled)
+ return;
+
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
+ if (!entry)
+ return;
+
+ RenderViewHost* render_view_host =
+ owner_->web_contents()->GetRenderViewHost();
+ if (!static_cast<RenderViewHostImpl*>
+ (render_view_host)->overscroll_controller()) {
+ return;
+ }
+ content::RenderWidgetHostView* view = render_view_host->GetView();
+ if (!view)
+ return;
+
+ // Make sure screenshots aren't taken too frequently.
+ base::Time now = base::Time::Now();
+ if (now - last_screenshot_time_ <
+ base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) {
+ return;
+ }
+
+ last_screenshot_time_ = now;
+
+ TakeScreenshotImpl(render_view_host, entry);
+}
+
+// Implemented here and not in NavigationEntry because this manager keeps track
+// of the total number of screen shots across all entries.
+void WebContentsScreenshotManager::ClearAllScreenshots() {
+ int count = owner_->GetEntryCount();
+ for (int i = 0; i < count; ++i) {
+ ClearScreenshot(NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(i)));
+ }
+ DCHECK_EQ(GetScreenshotCount(), 0);
+}
+
+void WebContentsScreenshotManager::TakeScreenshotImpl(
+ RenderViewHost* host,
+ NavigationEntryImpl* entry) {
+ DCHECK(host && host->GetView());
+ DCHECK(entry);
+ host->CopyFromBackingStore(gfx::Rect(),
+ host->GetView()->GetViewBounds().size(),
+ base::Bind(&WebContentsScreenshotManager::OnScreenshotTaken,
+ screenshot_factory_.GetWeakPtr(),
+ entry->GetUniqueID()));
+}
+
+void WebContentsScreenshotManager::SetMinScreenshotIntervalMS(int interval_ms) {
+ DCHECK_GE(interval_ms, 0);
+ min_screenshot_interval_ms_ = interval_ms;
+}
+
+void WebContentsScreenshotManager::OnScreenshotTaken(int unique_id,
+ bool success,
+ const SkBitmap& bitmap) {
+ NavigationEntryImpl* entry = NULL;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntry* iter = owner_->GetEntryAtIndex(i);
+ if (iter->GetUniqueID() == unique_id) {
+ entry = NavigationEntryImpl::FromNavigationEntry(iter);
+ break;
+ }
+ }
+
+ if (!entry) {
+ LOG(ERROR) << "Invalid entry with unique id: " << unique_id;
+ return;
+ }
+
+ if (!success || bitmap.empty() || bitmap.isNull()) {
+ ClearScreenshot(entry);
+ return;
+ }
+
+ scoped_refptr<ScreenshotData> screenshot = new ScreenshotData();
+ screenshot->EncodeScreenshot(
+ bitmap,
+ base::Bind(&WebContentsScreenshotManager::OnScreenshotEncodeComplete,
+ screenshot_factory_.GetWeakPtr(),
+ unique_id,
+ screenshot));
+}
+
+int WebContentsScreenshotManager::GetScreenshotCount() const {
+ int screenshot_count = 0;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i));
+ if (entry->screenshot().get())
+ screenshot_count++;
+ }
+ return screenshot_count;
+}
+
+void WebContentsScreenshotManager::OnScreenshotEncodeComplete(
+ int unique_id,
+ scoped_refptr<ScreenshotData> screenshot) {
+ NavigationEntryImpl* entry = NULL;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntry* iter = owner_->GetEntryAtIndex(i);
+ if (iter->GetUniqueID() == unique_id) {
+ entry = NavigationEntryImpl::FromNavigationEntry(iter);
+ break;
+ }
+ }
+ if (!entry)
+ return;
+ entry->SetScreenshotPNGData(screenshot->data());
+ OnScreenshotSet(entry);
+}
+
+void WebContentsScreenshotManager::OnScreenshotSet(NavigationEntryImpl* entry) {
+ PurgeScreenshotsIfNecessary();
+}
+
+bool WebContentsScreenshotManager::ClearScreenshot(NavigationEntryImpl* entry) {
+ if (!entry->screenshot().get())
+ return false;
+
+ entry->SetScreenshotPNGData(NULL);
+ return true;
+}
+
+void WebContentsScreenshotManager::PurgeScreenshotsIfNecessary() {
+ // Allow only a certain number of entries to keep screenshots.
+ const int kMaxScreenshots = 10;
+ int screenshot_count = GetScreenshotCount();
+ if (screenshot_count < kMaxScreenshots)
+ return;
+
+ const int current = owner_->GetCurrentEntryIndex();
+ const int num_entries = owner_->GetEntryCount();
+ int available_slots = kMaxScreenshots;
+ if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current))
+ ->screenshot().get()) {
+ --available_slots;
+ }
+
+ // Keep screenshots closer to the current navigation entry, and purge the ones
+ // that are farther away from it. So in each step, look at the entries at
+ // each offset on both the back and forward history, and start counting them
+ // to make sure that the correct number of screenshots are kept in memory.
+ // Note that it is possible for some entries to be missing screenshots (e.g.
+ // when taking the screenshot failed for some reason). So there may be a state
+ // where there are a lot of entries in the back history, but none of them has
+ // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots|
+ // entries in the forward history list.
+ int back = current - 1;
+ int forward = current + 1;
+ while (available_slots > 0 && (back >= 0 || forward < num_entries)) {
+ if (back >= 0) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(back));
+ if (entry->screenshot().get())
+ --available_slots;
+ --back;
+ }
+
+ if (available_slots > 0 && forward < num_entries) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(forward));
+ if (entry->screenshot().get())
+ --available_slots;
+ ++forward;
+ }
+ }
+
+ // Purge any screenshot at |back| or lower indices, and |forward| or higher
+ // indices.
+ while (screenshot_count > kMaxScreenshots && back >= 0) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(back));
+ if (ClearScreenshot(entry))
+ --screenshot_count;
+ --back;
+ }
+
+ while (screenshot_count > kMaxScreenshots && forward < num_entries) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(forward));
+ if (ClearScreenshot(entry))
+ --screenshot_count;
+ ++forward;
+ }
+ CHECK_GE(screenshot_count, 0);
+ CHECK_LE(screenshot_count, kMaxScreenshots);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_screenshot_manager.h b/chromium/content/browser/web_contents/web_contents_screenshot_manager.h
new file mode 100644
index 00000000000..23df41ee757
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_screenshot_manager.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_SCREENSHOT_MANAGER_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_SCREENSHOT_MANAGER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+class SkBitmap;
+
+namespace content {
+
+class NavigationControllerImpl;
+class NavigationEntryImpl;
+class RenderViewHost;
+class ScreenshotData;
+
+// WebContentsScreenshotManager takes care of taking image-captures for the
+// current navigation entry of a NavigationControllerImpl, and managing these
+// captured images. These image-captures are used for history navigation using
+// overscroll gestures.
+class CONTENT_EXPORT WebContentsScreenshotManager {
+ public:
+ explicit WebContentsScreenshotManager(NavigationControllerImpl* controller);
+ virtual ~WebContentsScreenshotManager();
+
+ // Takes a screenshot of the last-committed entry of the controller.
+ void TakeScreenshot();
+
+ // Clears screenshots of all navigation entries.
+ void ClearAllScreenshots();
+
+ protected:
+ virtual void TakeScreenshotImpl(RenderViewHost* host,
+ NavigationEntryImpl* entry);
+
+ // Called after a screenshot has been set on an NavigationEntryImpl.
+ // Overridden in tests to get notified of when a screenshot is set.
+ virtual void OnScreenshotSet(NavigationEntryImpl* entry);
+
+ NavigationControllerImpl* owner() { return owner_; }
+
+ void SetMinScreenshotIntervalMS(int interval_ms);
+
+ // The callback invoked when taking the screenshot of the page is complete.
+ // This sets the screenshot on the navigation entry.
+ void OnScreenshotTaken(int unique_id,
+ bool success,
+ const SkBitmap& bitmap);
+
+ // Returns the number of entries with screenshots.
+ int GetScreenshotCount() const;
+
+ private:
+ // This is called when the screenshot data has beene encoded to PNG in a
+ // worker thread.
+ void OnScreenshotEncodeComplete(int unique_id,
+ scoped_refptr<ScreenshotData> data);
+
+ // Removes the screenshot for the entry, returning true if the entry had a
+ // screenshot.
+ bool ClearScreenshot(NavigationEntryImpl* entry);
+
+ // The screenshots in the NavigationEntryImpls can accumulate and consume a
+ // large amount of memory. This function makes sure that the memory
+ // consumption is within a certain limit.
+ void PurgeScreenshotsIfNecessary();
+
+ // The navigation controller that owns this screenshot-manager.
+ NavigationControllerImpl* owner_;
+
+ // Taking a screenshot and encoding them can be async. So use a weakptr for
+ // the callback to make sure that the screenshot/encoding completion callback
+ // does not trigger on a destroyed WebContentsScreenshotManager.
+ base::WeakPtrFactory<WebContentsScreenshotManager> screenshot_factory_;
+
+ base::Time last_screenshot_time_;
+ int min_screenshot_interval_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsScreenshotManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_SCREENSHOT_MANAGER_H_
diff --git a/chromium/content/browser/web_contents/web_contents_user_data_unittest.cc b/chromium/content/browser/web_contents/web_contents_user_data_unittest.cc
new file mode 100644
index 00000000000..acec01ded69
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_user_data_unittest.cc
@@ -0,0 +1,108 @@
+// 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 "content/public/browser/web_contents_user_data.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class WebContentsAttachedClass1
+ : public WebContentsUserData<WebContentsAttachedClass1> {
+ public:
+ virtual ~WebContentsAttachedClass1() {}
+ private:
+ explicit WebContentsAttachedClass1(WebContents* contents) {}
+ friend class WebContentsUserData<WebContentsAttachedClass1>;
+};
+
+class WebContentsAttachedClass2
+ : public WebContentsUserData<WebContentsAttachedClass2> {
+ public:
+ virtual ~WebContentsAttachedClass2() {}
+ private:
+ explicit WebContentsAttachedClass2(WebContents* contents) {}
+ friend class WebContentsUserData<WebContentsAttachedClass2>;
+};
+
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(WebContentsAttachedClass1);
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(WebContentsAttachedClass2);
+
+typedef RenderViewHostTestHarness WebContentsUserDataTest;
+
+TEST_F(WebContentsUserDataTest, OneInstanceTwoAttachments) {
+ WebContents* contents = web_contents();
+ WebContentsAttachedClass1* class1 =
+ WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_EQ(NULL, class1);
+ WebContentsAttachedClass2* class2 =
+ WebContentsAttachedClass2::FromWebContents(contents);
+ ASSERT_EQ(NULL, class2);
+
+ WebContentsAttachedClass1::CreateForWebContents(contents);
+ class1 = WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_TRUE(class1 != NULL);
+ class2 = WebContentsAttachedClass2::FromWebContents(contents);
+ ASSERT_EQ(NULL, class2);
+
+ WebContentsAttachedClass2::CreateForWebContents(contents);
+ WebContentsAttachedClass1* class1again =
+ WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_TRUE(class1again != NULL);
+ class2 = WebContentsAttachedClass2::FromWebContents(contents);
+ ASSERT_TRUE(class2 != NULL);
+ ASSERT_EQ(class1, class1again);
+ ASSERT_NE(static_cast<void*>(class1), static_cast<void*>(class2));
+}
+
+TEST_F(WebContentsUserDataTest, TwoInstancesOneAttachment) {
+ WebContents* contents1 = web_contents();
+ scoped_ptr<WebContents> contents2(
+ WebContentsTester::CreateTestWebContents(browser_context(), NULL));
+
+ WebContentsAttachedClass1* one_class =
+ WebContentsAttachedClass1::FromWebContents(contents1);
+ ASSERT_EQ(NULL, one_class);
+ WebContentsAttachedClass1* two_class =
+ WebContentsAttachedClass1::FromWebContents(contents2.get());
+ ASSERT_EQ(NULL, two_class);
+
+ WebContentsAttachedClass1::CreateForWebContents(contents1);
+ one_class = WebContentsAttachedClass1::FromWebContents(contents1);
+ ASSERT_TRUE(one_class != NULL);
+ two_class = WebContentsAttachedClass1::FromWebContents(contents2.get());
+ ASSERT_EQ(NULL, two_class);
+
+ WebContentsAttachedClass1::CreateForWebContents(contents2.get());
+ WebContentsAttachedClass1* one_class_again =
+ WebContentsAttachedClass1::FromWebContents(contents1);
+ ASSERT_TRUE(one_class_again != NULL);
+ two_class = WebContentsAttachedClass1::FromWebContents(contents2.get());
+ ASSERT_TRUE(two_class != NULL);
+ ASSERT_EQ(one_class, one_class_again);
+ ASSERT_NE(one_class, two_class);
+}
+
+TEST_F(WebContentsUserDataTest, Idempotence) {
+ WebContents* contents = web_contents();
+ WebContentsAttachedClass1* clazz =
+ WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_EQ(NULL, clazz);
+
+ WebContentsAttachedClass1::CreateForWebContents(contents);
+ clazz = WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_TRUE(clazz != NULL);
+
+ WebContentsAttachedClass1::CreateForWebContents(contents);
+ WebContentsAttachedClass1* class_again =
+ WebContentsAttachedClass1::FromWebContents(contents);
+ ASSERT_TRUE(class_again != NULL);
+ ASSERT_EQ(clazz, class_again);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_android.cc b/chromium/content/browser/web_contents/web_contents_view_android.cc
new file mode 100644
index 00000000000..1f7b247e606
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_android.cc
@@ -0,0 +1,227 @@
+// 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 "content/browser/web_contents/web_contents_view_android.h"
+
+#include "base/logging.h"
+#include "content/browser/android/content_view_core_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_android.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "media/base/android/media_player_manager.h"
+
+namespace content {
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view) {
+ WebContentsViewAndroid* rv = new WebContentsViewAndroid(
+ web_contents, delegate);
+ *render_view_host_delegate_view = rv;
+ return rv;
+}
+
+WebContentsViewAndroid::WebContentsViewAndroid(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate)
+ : web_contents_(web_contents),
+ content_view_core_(NULL),
+ delegate_(delegate) {
+}
+
+WebContentsViewAndroid::~WebContentsViewAndroid() {
+}
+
+void WebContentsViewAndroid::SetContentViewCore(
+ ContentViewCoreImpl* content_view_core) {
+ content_view_core_ = content_view_core;
+ RenderWidgetHostViewAndroid* rwhv = static_cast<RenderWidgetHostViewAndroid*>(
+ web_contents_->GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->SetContentViewCore(content_view_core_);
+
+ if (web_contents_->ShowingInterstitialPage()) {
+ rwhv = static_cast<RenderWidgetHostViewAndroid*>(
+ static_cast<InterstitialPageImpl*>(
+ web_contents_->GetInterstitialPage())->
+ GetRenderViewHost()->GetView());
+ if (rwhv)
+ rwhv->SetContentViewCore(content_view_core_);
+ }
+}
+
+#if defined(GOOGLE_TV)
+void WebContentsViewAndroid::NotifyExternalSurface(
+ int player_id, bool is_request, const gfx::RectF& rect) {
+ if (content_view_core_)
+ content_view_core_->NotifyExternalSurface(player_id, is_request, rect);
+}
+#endif
+
+gfx::NativeView WebContentsViewAndroid::GetNativeView() const {
+ return content_view_core_ ? content_view_core_->GetViewAndroid() : NULL;
+}
+
+gfx::NativeView WebContentsViewAndroid::GetContentNativeView() const {
+ return content_view_core_ ? content_view_core_->GetViewAndroid() : NULL;
+}
+
+gfx::NativeWindow WebContentsViewAndroid::GetTopLevelNativeWindow() const {
+ return content_view_core_ ? content_view_core_->GetWindowAndroid() : NULL;
+}
+
+void WebContentsViewAndroid::GetContainerBounds(gfx::Rect* out) const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ *out = rwhv->GetViewBounds();
+}
+
+void WebContentsViewAndroid::SetPageTitle(const string16& title) {
+ if (content_view_core_)
+ content_view_core_->SetTitle(title);
+}
+
+void WebContentsViewAndroid::OnTabCrashed(base::TerminationStatus status,
+ int error_code) {
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ if (rvh->media_player_manager())
+ rvh->media_player_manager()->DestroyAllMediaPlayers();
+ if (content_view_core_)
+ content_view_core_->OnTabCrashed();
+}
+
+void WebContentsViewAndroid::SizeContents(const gfx::Size& size) {
+ // TODO(klobag): Do we need to do anything else?
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+}
+
+void WebContentsViewAndroid::Focus() {
+ if (web_contents_->ShowingInterstitialPage())
+ web_contents_->GetInterstitialPage()->Focus();
+ else
+ web_contents_->GetRenderWidgetHostView()->Focus();
+}
+
+void WebContentsViewAndroid::SetInitialFocus() {
+ if (web_contents_->FocusLocationBarByDefault())
+ web_contents_->SetFocusToLocationBar(false);
+ else
+ Focus();
+}
+
+void WebContentsViewAndroid::StoreFocus() {
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewAndroid::RestoreFocus() {
+ NOTIMPLEMENTED();
+}
+
+DropData* WebContentsViewAndroid::GetDropData() const {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+gfx::Rect WebContentsViewAndroid::GetViewBounds() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ return rwhv->GetViewBounds();
+ else
+ return gfx::Rect();
+}
+
+void WebContentsViewAndroid::CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) {
+}
+
+RenderWidgetHostView* WebContentsViewAndroid::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+ // Note that while this instructs the render widget host to reference
+ // |native_view_|, this has no effect without also instructing the
+ // native view (i.e. ContentView) how to obtain a reference to this widget in
+ // order to paint it. See ContentView::GetRenderWidgetHostViewAndroid for an
+ // example of how this is achieved for InterstitialPages.
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(render_widget_host);
+ RenderWidgetHostView* view = new RenderWidgetHostViewAndroid(
+ rwhi, content_view_core_);
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewAndroid::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewAndroid::RenderViewCreated(RenderViewHost* host) {
+}
+
+void WebContentsViewAndroid::RenderViewSwappedIn(RenderViewHost* host) {
+}
+
+void WebContentsViewAndroid::SetOverscrollControllerEnabled(bool enabled) {
+}
+
+void WebContentsViewAndroid::ShowContextMenu(
+ const ContextMenuParams& params) {
+ if (delegate_)
+ delegate_->ShowContextMenu(params);
+}
+
+void WebContentsViewAndroid::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ if (content_view_core_) {
+ content_view_core_->ShowSelectPopupMenu(
+ items, selected_item, allow_multiple_selection);
+ }
+}
+
+void WebContentsViewAndroid::StartDragging(
+ const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewAndroid::UpdateDragCursor(WebKit::WebDragOperation op) {
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewAndroid::GotFocus() {
+ // This is only used in the views FocusManager stuff but it bleeds through
+ // all subclasses. http://crbug.com/21875
+}
+
+// This is called when we the renderer asks us to take focus back (i.e., it has
+// iterated past the last focusable element on the page).
+void WebContentsViewAndroid::TakeFocus(bool reverse) {
+ if (web_contents_->GetDelegate() &&
+ web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse))
+ return;
+ web_contents_->GetRenderWidgetHostView()->Focus();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_android.h b/chromium/content/browser/web_contents/web_contents_view_android.h
new file mode 100644
index 00000000000..594bc5c340d
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_android.h
@@ -0,0 +1,98 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_ANDROID_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_ANDROID_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "content/public/browser/web_contents_view_delegate.h"
+#include "content/public/common/context_menu_params.h"
+#include "ui/gfx/rect_f.h"
+
+namespace content {
+class ContentViewCoreImpl;
+
+// Android-specific implementation of the WebContentsView.
+class WebContentsViewAndroid : public WebContentsViewPort,
+ public RenderViewHostDelegateView {
+ public:
+ WebContentsViewAndroid(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate);
+ virtual ~WebContentsViewAndroid();
+
+ // Sets the interface to the view system. ContentViewCoreImpl is owned
+ // by its Java ContentViewCore counterpart, whose lifetime is managed
+ // by the UI frontend.
+ void SetContentViewCore(ContentViewCoreImpl* content_view_core);
+
+#if defined(GOOGLE_TV)
+ void NotifyExternalSurface(int player_id,
+ bool is_request,
+ const gfx::RectF& rect);
+#endif
+
+ // WebContentsView implementation --------------------------------------------
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect* out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+
+ // WebContentsViewPort implementation ----------------------------------------
+ virtual void CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+
+ // Backend implementation of RenderViewHostDelegateView.
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ private:
+ // The WebContents whose contents we display.
+ WebContentsImpl* web_contents_;
+
+ // ContentViewCoreImpl is our interface to the view system.
+ ContentViewCoreImpl* content_view_core_;
+
+ // Interface for extensions to WebContentsView. Used to show the context menu.
+ scoped_ptr<WebContentsViewDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_ANDROID_H_
diff --git a/chromium/content/browser/web_contents/web_contents_view_aura.cc b/chromium/content/browser/web_contents/web_contents_view_aura.cc
new file mode 100644
index 00000000000..043efca119a
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_aura.cc
@@ -0,0 +1,1607 @@
+// 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 "content/browser/web_contents/web_contents_view_aura.h"
+
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/overscroll_controller.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "content/browser/web_contents/aura/image_window_delegate.h"
+#include "content/browser/web_contents/aura/shadow_layer_delegate.h"
+#include "content/browser/web_contents/aura/window_slider.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/touch_editable_impl_aura.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/overscroll_configuration.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_view_delegate.h"
+#include "content/public/browser/web_drag_dest_delegate.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/drop_data.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/drag_drop_client.h"
+#include "ui/aura/client/drag_drop_delegate.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_observer.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/drag_utils.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/hit_test.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/screen.h"
+
+namespace content {
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view) {
+ WebContentsViewAura* rv = new WebContentsViewAura(web_contents, delegate);
+ *render_view_host_delegate_view = rv;
+ return rv;
+}
+
+namespace {
+
+bool IsScrollEndEffectEnabled() {
+ return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kScrollEndEffect) == "1";
+}
+
+bool ShouldNavigateForward(const NavigationController& controller,
+ OverscrollMode mode) {
+ return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) &&
+ controller.CanGoForward();
+}
+
+bool ShouldNavigateBack(const NavigationController& controller,
+ OverscrollMode mode) {
+ return mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST) &&
+ controller.CanGoBack();
+}
+
+RenderWidgetHostViewAura* ToRenderWidgetHostViewAura(
+ RenderWidgetHostView* view) {
+ if (!view || RenderViewHostFactory::has_factory())
+ return NULL; // Can't cast to RenderWidgetHostViewAura in unit tests.
+ RenderProcessHostImpl* process = static_cast<RenderProcessHostImpl*>(
+ view->GetRenderWidgetHost()->GetProcess());
+ if (process->IsGuest())
+ return NULL;
+ return static_cast<RenderWidgetHostViewAura*>(view);
+}
+
+// The window delegate for the overscroll window. This redirects trackpad events
+// to the web-contents window. The delegate destroys itself when the window is
+// destroyed.
+class OverscrollWindowDelegate : public ImageWindowDelegate {
+ public:
+ OverscrollWindowDelegate(WebContentsImpl* web_contents,
+ OverscrollMode overscroll_mode)
+ : web_contents_(web_contents),
+ forward_events_(true) {
+ const NavigationControllerImpl& controller = web_contents->GetController();
+ const NavigationEntryImpl* entry = NULL;
+ if (ShouldNavigateForward(controller, overscroll_mode)) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtOffset(1));
+ } else if (ShouldNavigateBack(controller, overscroll_mode)) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtOffset(-1));
+ }
+
+ gfx::Image image;
+ if (entry && entry->screenshot().get()) {
+ std::vector<gfx::ImagePNGRep> image_reps;
+ image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(),
+ ui::GetScaleFactorForNativeView(web_contents_window())));
+ image = gfx::Image(image_reps);
+ }
+ SetImage(image);
+ }
+
+ void stop_forwarding_events() { forward_events_ = false; }
+
+ private:
+ virtual ~OverscrollWindowDelegate() {}
+
+ aura::Window* web_contents_window() {
+ return web_contents_->GetView()->GetContentNativeView();
+ }
+
+ // Overridden from ui::EventHandler.
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE {
+ if (forward_events_ && web_contents_window())
+ web_contents_window()->delegate()->OnScrollEvent(event);
+ }
+
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
+ if (forward_events_ && web_contents_window())
+ web_contents_window()->delegate()->OnGestureEvent(event);
+ }
+
+ WebContents* web_contents_;
+
+ // The window is displayed both during the gesture, and after the gesture
+ // while the navigation is in progress. During the gesture, it is necessary to
+ // forward input events to the content page (e.g. when the overscroll window
+ // slides under the cursor and starts receiving scroll events). However, once
+ // the gesture is complete, and the window is being displayed as an overlay
+ // window during navigation, events should not be forwarded anymore.
+ bool forward_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverscrollWindowDelegate);
+};
+
+// Listens to all mouse drag events during a drag and drop and sends them to
+// the renderer.
+class WebDragSourceAura : public base::MessageLoopForUI::Observer,
+ public NotificationObserver {
+ public:
+ WebDragSourceAura(aura::Window* window, WebContentsImpl* contents)
+ : window_(window),
+ contents_(contents) {
+ base::MessageLoopForUI::current()->AddObserver(this);
+ registrar_.Add(this,
+ NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ Source<WebContents>(contents));
+ }
+
+ virtual ~WebDragSourceAura() {
+ base::MessageLoopForUI::current()->RemoveObserver(this);
+ }
+
+ // MessageLoop::Observer implementation:
+ virtual base::EventStatus WillProcessEvent(
+ const base::NativeEvent& event) OVERRIDE {
+ return base::EVENT_CONTINUE;
+ }
+ virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE {
+ if (!contents_)
+ return;
+ ui::EventType type = ui::EventTypeFromNative(event);
+ RenderViewHost* rvh = NULL;
+ switch (type) {
+ case ui::ET_MOUSE_DRAGGED:
+ rvh = contents_->GetRenderViewHost();
+ if (rvh) {
+ gfx::Point screen_loc_in_pixel = ui::EventLocationFromNative(event);
+ gfx::Point screen_loc = ConvertViewPointToDIP(rvh->GetView(),
+ screen_loc_in_pixel);
+ gfx::Point client_loc = screen_loc;
+ aura::Window* window = rvh->GetView()->GetNativeView();
+ aura::Window::ConvertPointToTarget(window->GetRootWindow(),
+ window, &client_loc);
+ contents_->DragSourceMovedTo(client_loc.x(), client_loc.y(),
+ screen_loc.x(), screen_loc.y());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ if (type != NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
+ return;
+
+ // Cancel the drag if it is still in progress.
+ aura::client::DragDropClient* dnd_client =
+ aura::client::GetDragDropClient(window_->GetRootWindow());
+ if (dnd_client && dnd_client->IsDragDropInProgress())
+ dnd_client->DragCancel();
+
+ window_ = NULL;
+ contents_ = NULL;
+ }
+
+ aura::Window* window() const { return window_; }
+
+ private:
+ aura::Window* window_;
+ WebContentsImpl* contents_;
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDragSourceAura);
+};
+
+// Utility to fill a ui::OSExchangeDataProvider object from DropData.
+void PrepareDragData(const DropData& drop_data,
+ ui::OSExchangeData::Provider* provider) {
+ if (!drop_data.text.string().empty())
+ provider->SetString(drop_data.text.string());
+ if (drop_data.url.is_valid())
+ provider->SetURL(drop_data.url, drop_data.url_title);
+ if (!drop_data.html.string().empty())
+ provider->SetHtml(drop_data.html.string(), drop_data.html_base_url);
+ if (!drop_data.filenames.empty()) {
+ std::vector<ui::OSExchangeData::FileInfo> filenames;
+ for (std::vector<DropData::FileInfo>::const_iterator it =
+ drop_data.filenames.begin();
+ it != drop_data.filenames.end(); ++it) {
+ filenames.push_back(
+ ui::OSExchangeData::FileInfo(
+ base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(it->path)),
+ base::FilePath::FromUTF8Unsafe(UTF16ToUTF8(it->display_name))));
+ }
+ provider->SetFilenames(filenames);
+ }
+ if (!drop_data.custom_data.empty()) {
+ Pickle pickle;
+ ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle);
+ provider->SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(),
+ pickle);
+ }
+}
+
+// Utility to fill a DropData object from ui::OSExchangeData.
+void PrepareDropData(DropData* drop_data, const ui::OSExchangeData& data) {
+ string16 plain_text;
+ data.GetString(&plain_text);
+ if (!plain_text.empty())
+ drop_data->text = base::NullableString16(plain_text, false);
+
+ GURL url;
+ string16 url_title;
+ data.GetURLAndTitle(&url, &url_title);
+ if (url.is_valid()) {
+ drop_data->url = url;
+ drop_data->url_title = url_title;
+ }
+
+ string16 html;
+ GURL html_base_url;
+ data.GetHtml(&html, &html_base_url);
+ if (!html.empty())
+ drop_data->html = base::NullableString16(html, false);
+ if (html_base_url.is_valid())
+ drop_data->html_base_url = html_base_url;
+
+ std::vector<ui::OSExchangeData::FileInfo> files;
+ if (data.GetFilenames(&files) && !files.empty()) {
+ for (std::vector<ui::OSExchangeData::FileInfo>::const_iterator
+ it = files.begin(); it != files.end(); ++it) {
+ drop_data->filenames.push_back(
+ DropData::FileInfo(
+ UTF8ToUTF16(it->path.AsUTF8Unsafe()),
+ UTF8ToUTF16(it->display_name.AsUTF8Unsafe())));
+ }
+ }
+
+ Pickle pickle;
+ if (data.GetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), &pickle))
+ ui::ReadCustomDataIntoMap(
+ pickle.data(), pickle.size(), &drop_data->custom_data);
+}
+
+// Utilities to convert between WebKit::WebDragOperationsMask and
+// ui::DragDropTypes.
+int ConvertFromWeb(WebKit::WebDragOperationsMask ops) {
+ int drag_op = ui::DragDropTypes::DRAG_NONE;
+ if (ops & WebKit::WebDragOperationCopy)
+ drag_op |= ui::DragDropTypes::DRAG_COPY;
+ if (ops & WebKit::WebDragOperationMove)
+ drag_op |= ui::DragDropTypes::DRAG_MOVE;
+ if (ops & WebKit::WebDragOperationLink)
+ drag_op |= ui::DragDropTypes::DRAG_LINK;
+ return drag_op;
+}
+
+WebKit::WebDragOperationsMask ConvertToWeb(int drag_op) {
+ int web_drag_op = WebKit::WebDragOperationNone;
+ if (drag_op & ui::DragDropTypes::DRAG_COPY)
+ web_drag_op |= WebKit::WebDragOperationCopy;
+ if (drag_op & ui::DragDropTypes::DRAG_MOVE)
+ web_drag_op |= WebKit::WebDragOperationMove;
+ if (drag_op & ui::DragDropTypes::DRAG_LINK)
+ web_drag_op |= WebKit::WebDragOperationLink;
+ return (WebKit::WebDragOperationsMask) web_drag_op;
+}
+
+int ConvertAuraEventFlagsToWebInputEventModifiers(int aura_event_flags) {
+ int web_input_event_modifiers = 0;
+ if (aura_event_flags & ui::EF_SHIFT_DOWN)
+ web_input_event_modifiers |= WebKit::WebInputEvent::ShiftKey;
+ if (aura_event_flags & ui::EF_CONTROL_DOWN)
+ web_input_event_modifiers |= WebKit::WebInputEvent::ControlKey;
+ if (aura_event_flags & ui::EF_ALT_DOWN)
+ web_input_event_modifiers |= WebKit::WebInputEvent::AltKey;
+ if (aura_event_flags & ui::EF_COMMAND_DOWN)
+ web_input_event_modifiers |= WebKit::WebInputEvent::MetaKey;
+ return web_input_event_modifiers;
+}
+
+// A LayerDelegate that paints an image for the layer.
+class ImageLayerDelegate : public ui::LayerDelegate {
+ public:
+ ImageLayerDelegate() {}
+
+ virtual ~ImageLayerDelegate() {}
+
+ void SetImage(const gfx::Image& image) {
+ image_ = image;
+ image_size_ = image.AsImageSkia().size();
+ }
+ const gfx::Image& image() const { return image_; }
+
+ private:
+ // Overridden from ui::LayerDelegate:
+ virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE {
+ if (image_.IsEmpty()) {
+ canvas->DrawColor(SK_ColorGRAY);
+ } else {
+ SkISize size = canvas->sk_canvas()->getDeviceSize();
+ if (size.width() != image_size_.width() ||
+ size.height() != image_size_.height()) {
+ canvas->DrawColor(SK_ColorWHITE);
+ }
+ canvas->DrawImageInt(image_.AsImageSkia(), 0, 0);
+ }
+ }
+
+ // Called when the layer's device scale factor has changed.
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {
+ }
+
+ // Invoked prior to the bounds changing. The returned closured is run after
+ // the bounds change.
+ virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE {
+ return base::Closure();
+ }
+
+ gfx::Image image_;
+ gfx::Size image_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageLayerDelegate);
+};
+
+} // namespace
+
+// When a history navigation is triggered at the end of an overscroll
+// navigation, it is necessary to show the history-screenshot until the page is
+// done navigating and painting. This class accomplishes this by showing the
+// screenshot window on top of the page until the page has completed loading and
+// painting.
+class OverscrollNavigationOverlay :
+ public RenderWidgetHostViewAura::PaintObserver,
+ public WindowSlider::Delegate {
+ public:
+ explicit OverscrollNavigationOverlay(WebContentsImpl* web_contents)
+ : web_contents_(web_contents),
+ image_delegate_(NULL),
+ view_(NULL),
+ loading_complete_(false),
+ received_paint_update_(false),
+ compositor_updated_(false),
+ slide_direction_(SLIDE_UNKNOWN),
+ need_paint_update_(true) {
+ }
+
+ virtual ~OverscrollNavigationOverlay() {
+ if (view_)
+ view_->set_paint_observer(NULL);
+ }
+
+ bool has_window() const { return !!window_.get(); }
+
+ void StartObservingView(RenderWidgetHostViewAura* view) {
+ if (view_)
+ view_->set_paint_observer(NULL);
+
+ loading_complete_ = false;
+ received_paint_update_ = false;
+ compositor_updated_ = false;
+ view_ = view;
+ if (view_)
+ view_->set_paint_observer(this);
+
+ // Make sure the overlay window is on top.
+ if (window_.get() && window_->parent())
+ window_->parent()->StackChildAtTop(window_.get());
+ }
+
+ void SetOverlayWindow(scoped_ptr<aura::Window> window,
+ ImageWindowDelegate* delegate) {
+ window_ = window.Pass();
+ if (window_.get() && window_->parent())
+ window_->parent()->StackChildAtTop(window_.get());
+ image_delegate_ = delegate;
+
+ if (window_.get() && delegate->has_image()) {
+ window_slider_.reset(new WindowSlider(this,
+ window_->parent(),
+ window_.get()));
+ slide_direction_ = SLIDE_UNKNOWN;
+ } else {
+ window_slider_.reset();
+ }
+ }
+
+ void SetupForTesting() {
+ need_paint_update_ = false;
+ }
+
+ private:
+ // Stop observing the page if the page-load has completed and the page has
+ // been painted, and a window-slide isn't in progress.
+ void StopObservingIfDone() {
+ // If there is a screenshot displayed in the overlay window, then wait for
+ // the navigated page to complete loading and some paint update before
+ // hiding the overlay.
+ // If there is no screenshot in the overlay window, then hide this view
+ // as soon as there is any new painting notification.
+ if ((need_paint_update_ && !received_paint_update_) ||
+ (image_delegate_->has_image() && !loading_complete_)) {
+ return;
+ }
+
+ // If a slide is in progress, then do not destroy the window or the slide.
+ if (window_slider_.get() && window_slider_->IsSlideInProgress())
+ return;
+
+ window_slider_.reset();
+ window_.reset();
+ image_delegate_ = NULL;
+ if (view_) {
+ view_->set_paint_observer(NULL);
+ view_ = NULL;
+ }
+ }
+
+ // Creates a layer to be used for window-slide. |offset| is the offset of the
+ // NavigationEntry for the screenshot image to display.
+ ui::Layer* CreateSlideLayer(int offset) {
+ const NavigationControllerImpl& controller = web_contents_->GetController();
+ const NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtOffset(offset));
+
+ gfx::Image image;
+ if (entry && entry->screenshot().get()) {
+ std::vector<gfx::ImagePNGRep> image_reps;
+ image_reps.push_back(gfx::ImagePNGRep(entry->screenshot(),
+ ui::GetScaleFactorForNativeView(window_.get())));
+ image = gfx::Image(image_reps);
+ }
+ layer_delegate_.SetImage(image);
+
+ ui::Layer* layer = new ui::Layer(ui::LAYER_TEXTURED);
+ layer->set_delegate(&layer_delegate_);
+ return layer;
+ }
+
+ // Overridden from WindowSlider::Delegate:
+ virtual ui::Layer* CreateBackLayer() OVERRIDE {
+ if (!web_contents_->GetController().CanGoBack())
+ return NULL;
+ slide_direction_ = SLIDE_BACK;
+ return CreateSlideLayer(-1);
+ }
+
+ virtual ui::Layer* CreateFrontLayer() OVERRIDE {
+ if (!web_contents_->GetController().CanGoForward())
+ return NULL;
+ slide_direction_ = SLIDE_FRONT;
+ return CreateSlideLayer(1);
+ }
+
+ virtual void OnWindowSlideComplete() OVERRIDE {
+ if (slide_direction_ == SLIDE_UNKNOWN) {
+ window_slider_.reset();
+ StopObservingIfDone();
+ return;
+ }
+
+ // Change the image used for the overlay window.
+ image_delegate_->SetImage(layer_delegate_.image());
+ window_->layer()->SetTransform(gfx::Transform());
+ window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size()));
+
+ SlideDirection direction = slide_direction_;
+ slide_direction_ = SLIDE_UNKNOWN;
+
+ // Reset state and wait for the new navigation page to complete
+ // loading/painting.
+ StartObservingView(ToRenderWidgetHostViewAura(
+ web_contents_->GetRenderWidgetHostView()));
+
+ // Perform the navigation.
+ if (direction == SLIDE_BACK)
+ web_contents_->GetController().GoBack();
+ else if (direction == SLIDE_FRONT)
+ web_contents_->GetController().GoForward();
+ else
+ NOTREACHED();
+ }
+
+ virtual void OnWindowSlideAborted() OVERRIDE {
+ StopObservingIfDone();
+ }
+
+ virtual void OnWindowSliderDestroyed() OVERRIDE {
+ // The slider has just been destroyed. Release the ownership.
+ WindowSlider* slider ALLOW_UNUSED = window_slider_.release();
+ StopObservingIfDone();
+ }
+
+ // Overridden from RenderWidgetHostViewAura::PaintObserver:
+ virtual void OnPaintComplete() OVERRIDE {
+ received_paint_update_ = true;
+ StopObservingIfDone();
+ }
+
+ virtual void OnCompositingComplete() OVERRIDE {
+ received_paint_update_ = compositor_updated_;
+ StopObservingIfDone();
+ }
+
+ virtual void OnUpdateCompositorContent() OVERRIDE {
+ compositor_updated_ = true;
+ }
+
+ virtual void OnPageLoadComplete() OVERRIDE {
+ loading_complete_ = true;
+ StopObservingIfDone();
+ }
+
+ virtual void OnViewDestroyed() OVERRIDE {
+ DCHECK(view_);
+ view_->set_paint_observer(NULL);
+ view_ = NULL;
+ }
+
+ // The WebContents which is being navigated.
+ WebContentsImpl* web_contents_;
+
+ scoped_ptr<aura::Window> window_;
+
+ // This is the WindowDelegate of |window_|. The delegate manages its own
+ // lifetime (destroys itself when |window_| is destroyed).
+ ImageWindowDelegate* image_delegate_;
+
+ RenderWidgetHostViewAura* view_;
+ bool loading_complete_;
+ bool received_paint_update_;
+ bool compositor_updated_;
+
+ enum SlideDirection {
+ SLIDE_UNKNOWN,
+ SLIDE_BACK,
+ SLIDE_FRONT
+ };
+
+ // The |WindowSlider| that allows sliding history layers while the page is
+ // being reloaded.
+ scoped_ptr<WindowSlider> window_slider_;
+
+ // The direction of the in-progress slide (if any).
+ SlideDirection slide_direction_;
+
+ // The LayerDelegate used for the back/front layers during a slide.
+ ImageLayerDelegate layer_delegate_;
+
+ // During tests, the aura windows don't get any paint updates. So the overlay
+ // container keeps waiting for a paint update it never receives, causing a
+ // timeout. So during tests, disable the wait for paint updates.
+ bool need_paint_update_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverscrollNavigationOverlay);
+};
+
+class WebContentsViewAura::WindowObserver
+ : public aura::WindowObserver, public aura::RootWindowObserver {
+ public:
+ explicit WindowObserver(WebContentsViewAura* view)
+ : view_(view),
+ parent_(NULL) {
+ view_->window_->AddObserver(this);
+ }
+
+ virtual ~WindowObserver() {
+ view_->window_->RemoveObserver(this);
+ if (view_->window_->GetRootWindow())
+ view_->window_->GetRootWindow()->RemoveRootWindowObserver(this);
+ if (parent_)
+ parent_->RemoveObserver(this);
+ }
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowParentChanged(aura::Window* window,
+ aura::Window* parent) OVERRIDE {
+ if (window == parent_)
+ return;
+ if (parent_)
+ parent_->RemoveObserver(this);
+ parent_ = parent;
+ if (parent)
+ parent->AddObserver(this);
+ }
+
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ SendScreenRects();
+ if (view_->touch_editable_)
+ view_->touch_editable_->UpdateEditingController();
+ }
+
+ virtual void OnWindowAddedToRootWindow(aura::Window* window) OVERRIDE {
+ if (window != parent_)
+ window->GetRootWindow()->AddRootWindowObserver(this);
+ }
+
+ virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE {
+ if (window != parent_)
+ window->GetRootWindow()->RemoveRootWindowObserver(this);
+ }
+
+ // Overridden RootWindowObserver:
+ virtual void OnRootWindowHostMoved(const aura::RootWindow* root,
+ const gfx::Point& new_origin) OVERRIDE {
+ // This is for the desktop case (i.e. Aura desktop).
+ SendScreenRects();
+ }
+
+ private:
+ void SendScreenRects() {
+ RenderWidgetHostImpl::From(view_->web_contents_->GetRenderViewHost())->
+ SendScreenRects();
+ }
+
+ WebContentsViewAura* view_;
+
+ // We cache the old parent so that we can unregister when it's not the parent
+ // anymore.
+ aura::Window* parent_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowObserver);
+};
+
+#if defined(OS_WIN)
+// Constrained windows are added as children of the WebContent's view which may
+// overlap with windowed NPAPI plugins. In that case, tell the RWHV so that it
+// can update the plugins' cutout rects accordingly.
+class WebContentsViewAura::ChildWindowObserver : public aura::WindowObserver,
+ public WebContentsObserver {
+ public:
+ explicit ChildWindowObserver(WebContentsViewAura* view)
+ : WebContentsObserver(view->web_contents_),
+ view_(view),
+ web_contents_destroyed_(false) {
+ view_->window_->AddObserver(this);
+ }
+
+ virtual ~ChildWindowObserver() {
+ view_->window_->RemoveObserver(this);
+ const aura::Window::Windows& children = view_->window_->children();
+ for (size_t i = 0; i < children.size(); ++i)
+ children[i]->RemoveObserver(this);
+ }
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE {
+ // If new child windows are added to the WebContent's view, tell the RWHV.
+ // We also start watching them to know when their size is updated. Of
+ // course, ignore the shadow window that contains the RWHV and child windows
+ // of the child windows that we are watching.
+ RenderWidgetHostViewAura* view = ToRenderWidgetHostViewAura(
+ view_->web_contents_->GetRenderWidgetHostView());
+ aura::Window* content_window = view ? view->GetNativeView() : NULL;
+ if (new_window->parent() == view_->window_ &&
+ new_window != content_window) {
+ new_window->AddObserver(this);
+ UpdateConstrainedWindows(NULL);
+ }
+ }
+
+ virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE {
+ RenderWidgetHostViewAura* view = ToRenderWidgetHostViewAura(
+ view_->web_contents_->GetRenderWidgetHostView());
+ aura::Window* content_window = view ? view->GetNativeView() : NULL;
+ if (window->parent() == view_->window_ &&
+ window != content_window) {
+ window->RemoveObserver(this);
+ UpdateConstrainedWindows(window);
+ }
+ }
+
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ if (window->parent() == view_->window_ &&
+ window != view_->GetContentNativeView()) {
+ UpdateConstrainedWindows(NULL);
+ }
+ }
+
+ // Overridden from WebContentsObserver:
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE {
+ web_contents_destroyed_ = true;
+ }
+
+ private:
+ void UpdateConstrainedWindows(aura::Window* exclude) {
+ if (web_contents_destroyed_)
+ return;
+
+ RenderWidgetHostViewAura* view = ToRenderWidgetHostViewAura(
+ view_->web_contents_->GetRenderWidgetHostView());
+ if (!view)
+ return;
+
+ std::vector<gfx::Rect> constrained_windows;
+ const aura::Window::Windows& children = view_->window_->children();
+ aura::Window* content = view_->GetContentNativeView();
+ for (size_t i = 0; i < children.size(); ++i) {
+ if (children[i] != content && children[i] != exclude)
+ constrained_windows.push_back(children[i]->GetBoundsInRootWindow());
+ }
+
+ view->UpdateConstrainedWindowRects(constrained_windows);
+ }
+
+ WebContentsViewAura* view_;
+ bool web_contents_destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildWindowObserver);
+};
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, public:
+
+WebContentsViewAura::WebContentsViewAura(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate)
+ : web_contents_(web_contents),
+ delegate_(delegate),
+ current_drag_op_(WebKit::WebDragOperationNone),
+ drag_dest_delegate_(NULL),
+ current_rvh_for_drag_(NULL),
+ overscroll_change_brightness_(false),
+ current_overscroll_gesture_(OVERSCROLL_NONE),
+ completed_overscroll_gesture_(OVERSCROLL_NONE),
+ touch_editable_(TouchEditableImplAura::Create()) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, private:
+
+WebContentsViewAura::~WebContentsViewAura() {
+ if (!window_)
+ return;
+
+ window_observer_.reset();
+#if defined(OS_WIN)
+ child_window_observer_.reset();
+#endif
+ // Window needs a valid delegate during its destructor, so we explicitly
+ // delete it here.
+ window_.reset();
+}
+
+void WebContentsViewAura::SetupOverlayWindowForTesting() {
+ if (navigation_overlay_)
+ navigation_overlay_->SetupForTesting();
+}
+
+void WebContentsViewAura::SetTouchEditableForTest(
+ TouchEditableImplAura* touch_editable) {
+ touch_editable_.reset(touch_editable);
+ AttachTouchEditableToRenderView();
+}
+
+void WebContentsViewAura::SizeChangedCommon(const gfx::Size& size) {
+ if (web_contents_->GetInterstitialPage())
+ web_contents_->GetInterstitialPage()->SetSize(size);
+ RenderWidgetHostView* rwhv =
+ web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+}
+
+void WebContentsViewAura::EndDrag(WebKit::WebDragOperationsMask ops) {
+ aura::RootWindow* root_window = GetNativeView()->GetRootWindow();
+ gfx::Point screen_loc =
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint();
+ gfx::Point client_loc = screen_loc;
+ RenderViewHost* rvh = web_contents_->GetRenderViewHost();
+ aura::Window* window = rvh->GetView()->GetNativeView();
+ aura::Window::ConvertPointToTarget(root_window, window, &client_loc);
+ if (!web_contents_)
+ return;
+ web_contents_->DragSourceEndedAt(client_loc.x(), client_loc.y(),
+ screen_loc.x(), screen_loc.y(), ops);
+}
+
+void WebContentsViewAura::PrepareOverscrollWindow() {
+ // If there is an existing |overscroll_window_| which is in the middle of an
+ // animation, then destroying the window here causes the animation to be
+ // completed immidiately, which triggers |OnImplicitAnimationsCompleted()|
+ // callback, and that tries to reset |overscroll_window_| again, causing a
+ // double-free. So use a temporary variable here.
+ if (overscroll_window_) {
+ base::AutoReset<OverscrollMode> reset_state(&current_overscroll_gesture_,
+ current_overscroll_gesture_);
+ scoped_ptr<aura::Window> reset_window(overscroll_window_.release());
+ }
+
+ OverscrollWindowDelegate* overscroll_delegate = new OverscrollWindowDelegate(
+ web_contents_,
+ current_overscroll_gesture_);
+ overscroll_window_.reset(new aura::Window(overscroll_delegate));
+ overscroll_window_->SetType(aura::client::WINDOW_TYPE_CONTROL);
+ overscroll_window_->SetTransparent(true);
+ overscroll_window_->Init(ui::LAYER_TEXTURED);
+ overscroll_window_->layer()->SetMasksToBounds(false);
+ overscroll_window_->SetName("OverscrollOverlay");
+
+ overscroll_change_brightness_ = overscroll_delegate->has_image();
+ window_->AddChild(overscroll_window_.get());
+
+ gfx::Rect bounds = gfx::Rect(window_->bounds().size());
+ if (ShouldNavigateForward(web_contents_->GetController(),
+ current_overscroll_gesture_)) {
+ // The overlay will be sliding in from the right edge towards the left in
+ // non-RTL, or sliding in from the left edge towards the right in RTL.
+ // So position the overlay window accordingly.
+ bounds.Offset(base::i18n::IsRTL() ? -bounds.width() : bounds.width(), 0);
+ }
+
+ aura::Window* animate_window = GetWindowToAnimateForOverscroll();
+ if (animate_window == overscroll_window_)
+ window_->StackChildAbove(overscroll_window_.get(), GetContentNativeView());
+ else
+ window_->StackChildBelow(overscroll_window_.get(), GetContentNativeView());
+
+ UpdateOverscrollWindowBrightness(0.f);
+
+ overscroll_window_->SetBounds(bounds);
+ overscroll_window_->Show();
+
+ overscroll_shadow_.reset(new ShadowLayerDelegate(animate_window->layer()));
+}
+
+void WebContentsViewAura::PrepareContentWindowForOverscroll() {
+ StopObservingImplicitAnimations();
+ aura::Window* content = GetContentNativeView();
+ content->layer()->GetAnimator()->AbortAllAnimations();
+ content->SetTransform(gfx::Transform());
+ content->layer()->SetLayerBrightness(0.f);
+}
+
+void WebContentsViewAura::ResetOverscrollTransform() {
+ if (!web_contents_->GetRenderWidgetHostView())
+ return;
+ aura::Window* target = GetWindowToAnimateForOverscroll();
+ if (!target)
+ return;
+ {
+ ui::ScopedLayerAnimationSettings settings(target->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.AddObserver(this);
+ target->SetTransform(gfx::Transform());
+ }
+ {
+ ui::ScopedLayerAnimationSettings settings(target->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ UpdateOverscrollWindowBrightness(0.f);
+ }
+}
+
+void WebContentsViewAura::CompleteOverscrollNavigation(OverscrollMode mode) {
+ if (!web_contents_->GetRenderWidgetHostView())
+ return;
+
+ // Animate out the current view first. Navigate to the requested history at
+ // the end of the animation.
+ if (current_overscroll_gesture_ == OVERSCROLL_NONE)
+ return;
+
+ UMA_HISTOGRAM_ENUMERATION("Overscroll.Navigated",
+ current_overscroll_gesture_, OVERSCROLL_COUNT);
+ OverscrollWindowDelegate* delegate = static_cast<OverscrollWindowDelegate*>(
+ overscroll_window_->delegate());
+ delegate->stop_forwarding_events();
+
+ completed_overscroll_gesture_ = mode;
+ aura::Window* target = GetWindowToAnimateForOverscroll();
+ ui::ScopedLayerAnimationSettings settings(target->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.AddObserver(this);
+ gfx::Transform transform;
+ int content_width =
+ web_contents_->GetRenderWidgetHostView()->GetViewBounds().width();
+ int translate_x = mode == OVERSCROLL_WEST ? -content_width : content_width;
+ transform.Translate(translate_x, 0);
+ target->SetTransform(transform);
+ UpdateOverscrollWindowBrightness(translate_x);
+}
+
+aura::Window* WebContentsViewAura::GetWindowToAnimateForOverscroll() {
+ if (current_overscroll_gesture_ == OVERSCROLL_NONE)
+ return NULL;
+
+ return ShouldNavigateForward(web_contents_->GetController(),
+ current_overscroll_gesture_) ?
+ overscroll_window_.get() : GetContentNativeView();
+}
+
+gfx::Vector2d WebContentsViewAura::GetTranslationForOverscroll(int delta_x,
+ int delta_y) {
+ if (current_overscroll_gesture_ == OVERSCROLL_NORTH ||
+ current_overscroll_gesture_ == OVERSCROLL_SOUTH) {
+ return gfx::Vector2d(0, delta_y);
+ }
+ // For horizontal overscroll, scroll freely if a navigation is possible. Do a
+ // resistive scroll otherwise.
+ const NavigationControllerImpl& controller = web_contents_->GetController();
+ const gfx::Rect& bounds = GetViewBounds();
+ if (ShouldNavigateForward(controller, current_overscroll_gesture_))
+ return gfx::Vector2d(std::max(-bounds.width(), delta_x), 0);
+ else if (ShouldNavigateBack(controller, current_overscroll_gesture_))
+ return gfx::Vector2d(std::min(bounds.width(), delta_x), 0);
+ return gfx::Vector2d();
+}
+
+void WebContentsViewAura::PrepareOverscrollNavigationOverlay() {
+ OverscrollWindowDelegate* delegate = static_cast<OverscrollWindowDelegate*>(
+ overscroll_window_->delegate());
+ overscroll_window_->SchedulePaintInRect(
+ gfx::Rect(overscroll_window_->bounds().size()));
+ overscroll_window_->SetBounds(gfx::Rect(window_->bounds().size()));
+ overscroll_window_->SetTransform(gfx::Transform());
+ navigation_overlay_->SetOverlayWindow(overscroll_window_.Pass(),
+ delegate);
+ navigation_overlay_->StartObservingView(ToRenderWidgetHostViewAura(
+ web_contents_->GetRenderWidgetHostView()));
+}
+
+void WebContentsViewAura::UpdateOverscrollWindowBrightness(float delta_x) {
+ if (!overscroll_change_brightness_)
+ return;
+
+ const float kBrightnessMin = -.1f;
+ const float kBrightnessMax = -.01f;
+
+ float ratio = fabs(delta_x) / GetViewBounds().width();
+ ratio = std::min(1.f, ratio);
+ if (base::i18n::IsRTL())
+ ratio = 1.f - ratio;
+ float brightness = current_overscroll_gesture_ == OVERSCROLL_WEST ?
+ kBrightnessMin + ratio * (kBrightnessMax - kBrightnessMin) :
+ kBrightnessMax - ratio * (kBrightnessMax - kBrightnessMin);
+ brightness = std::max(kBrightnessMin, brightness);
+ brightness = std::min(kBrightnessMax, brightness);
+ aura::Window* window = GetWindowToAnimateForOverscroll();
+ window->layer()->SetLayerBrightness(brightness);
+}
+
+void WebContentsViewAura::AttachTouchEditableToRenderView() {
+ if (!touch_editable_)
+ return;
+ RenderWidgetHostViewAura* rwhva = ToRenderWidgetHostViewAura(
+ web_contents_->GetRenderWidgetHostView());
+ touch_editable_->AttachToView(rwhva);
+}
+
+void WebContentsViewAura::OverscrollUpdateForWebContentsDelegate(int delta_y) {
+ if (web_contents_->GetDelegate() && IsScrollEndEffectEnabled())
+ web_contents_->GetDelegate()->OverscrollUpdate(delta_y);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, WebContentsView implementation:
+
+gfx::NativeView WebContentsViewAura::GetNativeView() const {
+ return window_.get();
+}
+
+gfx::NativeView WebContentsViewAura::GetContentNativeView() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ return rwhv ? rwhv->GetNativeView() : NULL;
+}
+
+gfx::NativeWindow WebContentsViewAura::GetTopLevelNativeWindow() const {
+ return window_->GetToplevelWindow();
+}
+
+void WebContentsViewAura::GetContainerBounds(gfx::Rect *out) const {
+ *out = window_->GetBoundsInScreen();
+}
+
+void WebContentsViewAura::OnTabCrashed(base::TerminationStatus status,
+ int error_code) {
+ // Set the focus to the parent because neither the view window nor this
+ // window can handle key events.
+ if (window_->HasFocus() && window_->parent())
+ window_->parent()->Focus();
+}
+
+void WebContentsViewAura::SizeContents(const gfx::Size& size) {
+ gfx::Rect bounds = window_->bounds();
+ if (bounds.size() != size) {
+ bounds.set_size(size);
+ window_->SetBounds(bounds);
+ } else {
+ // Our size matches what we want but the renderers size may not match.
+ // Pretend we were resized so that the renderers size is updated too.
+ SizeChangedCommon(size);
+ }
+}
+
+void WebContentsViewAura::Focus() {
+ if (web_contents_->GetInterstitialPage()) {
+ web_contents_->GetInterstitialPage()->Focus();
+ return;
+ }
+
+ if (delegate_.get() && delegate_->Focus())
+ return;
+
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->Focus();
+}
+
+void WebContentsViewAura::SetInitialFocus() {
+ if (web_contents_->FocusLocationBarByDefault())
+ web_contents_->SetFocusToLocationBar(false);
+ else
+ Focus();
+}
+
+void WebContentsViewAura::StoreFocus() {
+ if (delegate_)
+ delegate_->StoreFocus();
+}
+
+void WebContentsViewAura::RestoreFocus() {
+ if (delegate_)
+ delegate_->RestoreFocus();
+}
+
+DropData* WebContentsViewAura::GetDropData() const {
+ return current_drop_data_.get();
+}
+
+gfx::Rect WebContentsViewAura::GetViewBounds() const {
+ return window_->GetBoundsInScreen();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, WebContentsViewPort implementation:
+
+void WebContentsViewAura::CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) {
+ // NOTE: we ignore |initial_size| since in some cases it's wrong (such as
+ // if the bookmark bar is not shown and you create a new tab). The right
+ // value is set shortly after this, so its safe to ignore.
+
+ window_.reset(new aura::Window(this));
+ window_->set_owned_by_parent(false);
+ window_->SetType(aura::client::WINDOW_TYPE_CONTROL);
+ window_->SetTransparent(false);
+ window_->Init(ui::LAYER_NOT_DRAWN);
+ aura::RootWindow* root_window = context ? context->GetRootWindow() : NULL;
+ if (root_window) {
+ // There are places where there is no context currently because object
+ // hierarchies are built before they're attached to a Widget. (See
+ // views::WebView as an example; GetWidget() returns NULL at the point
+ // where we are created.)
+ //
+ // It should be OK to not set a default parent since such users will
+ // explicitly add this WebContentsViewAura to their tree after they create
+ // us.
+ if (root_window) {
+ window_->SetDefaultParentByRootWindow(
+ root_window, root_window->GetBoundsInScreen());
+ }
+ }
+ window_->layer()->SetMasksToBounds(true);
+ window_->SetName("WebContentsViewAura");
+
+ window_observer_.reset(new WindowObserver(this));
+#if defined(OS_WIN)
+ child_window_observer_.reset(new ChildWindowObserver(this));
+#endif
+
+ // delegate_->GetDragDestDelegate() creates a new delegate on every call.
+ // Hence, we save a reference to it locally. Similar model is used on other
+ // platforms as well.
+ if (delegate_)
+ drag_dest_delegate_ = delegate_->GetDragDestDelegate();
+}
+
+RenderWidgetHostView* WebContentsViewAura::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+
+ RenderWidgetHostView* view =
+ RenderWidgetHostView::CreateViewForWidget(render_widget_host);
+ view->InitAsChild(NULL);
+ GetNativeView()->AddChild(view->GetNativeView());
+
+ if (navigation_overlay_.get() && navigation_overlay_->has_window()) {
+ navigation_overlay_->StartObservingView(ToRenderWidgetHostViewAura(view));
+ }
+
+ view->Show();
+
+ // We listen to drag drop events in the newly created view's window.
+ aura::client::SetDragDropDelegate(view->GetNativeView(), this);
+
+ RenderWidgetHostImpl* host_impl =
+ RenderWidgetHostImpl::From(render_widget_host);
+ if (host_impl->overscroll_controller() &&
+ (!web_contents_->GetDelegate() ||
+ web_contents_->GetDelegate()->CanOverscrollContent())) {
+ host_impl->overscroll_controller()->set_delegate(this);
+ if (!navigation_overlay_)
+ navigation_overlay_.reset(new OverscrollNavigationOverlay(web_contents_));
+ }
+
+ AttachTouchEditableToRenderView();
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewAura::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewAura::SetPageTitle(const string16& title) {
+ window_->set_title(title);
+}
+
+void WebContentsViewAura::RenderViewCreated(RenderViewHost* host) {
+}
+
+void WebContentsViewAura::RenderViewSwappedIn(RenderViewHost* host) {
+ if (navigation_overlay_.get() && navigation_overlay_->has_window()) {
+ navigation_overlay_->StartObservingView(
+ ToRenderWidgetHostViewAura(host->GetView()));
+ }
+ AttachTouchEditableToRenderView();
+}
+
+void WebContentsViewAura::SetOverscrollControllerEnabled(bool enabled) {
+ RenderViewHostImpl* host = static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost());
+ if (host) {
+ host->SetOverscrollControllerEnabled(enabled);
+ if (enabled)
+ host->overscroll_controller()->set_delegate(this);
+ }
+
+ if (!enabled)
+ navigation_overlay_.reset();
+ else if (!navigation_overlay_)
+ navigation_overlay_.reset(new OverscrollNavigationOverlay(web_contents_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, RenderViewHostDelegateView implementation:
+
+void WebContentsViewAura::ShowContextMenu(const ContextMenuParams& params) {
+ if (delegate_)
+ delegate_->ShowContextMenu(params);
+ if (touch_editable_)
+ touch_editable_->EndTouchEditing();
+
+}
+
+void WebContentsViewAura::ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ // External popup menus are only used on Mac and Android.
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewAura::StartDragging(
+ const DropData& drop_data,
+ WebKit::WebDragOperationsMask operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ aura::RootWindow* root_window = GetNativeView()->GetRootWindow();
+ if (!aura::client::GetDragDropClient(root_window)) {
+ web_contents_->SystemDragEnded();
+ return;
+ }
+
+ if (touch_editable_)
+ touch_editable_->EndTouchEditing();
+
+ ui::OSExchangeData::Provider* provider = ui::OSExchangeData::CreateProvider();
+ PrepareDragData(drop_data, provider);
+
+ ui::OSExchangeData data(provider); // takes ownership of |provider|.
+
+ if (!image.isNull()) {
+ drag_utils::SetDragImageOnDataObject(image,
+ gfx::Size(image.width(), image.height()), image_offset, &data);
+ }
+
+ scoped_ptr<WebDragSourceAura> drag_source(
+ new WebDragSourceAura(GetNativeView(), web_contents_));
+
+ // We need to enable recursive tasks on the message loop so we can get
+ // updates while in the system DoDragDrop loop.
+ int result_op = 0;
+ {
+ gfx::NativeView content_native_view = GetContentNativeView();
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ result_op = aura::client::GetDragDropClient(root_window)
+ ->StartDragAndDrop(data,
+ root_window,
+ content_native_view,
+ event_info.event_location,
+ ConvertFromWeb(operations),
+ event_info.event_source);
+ }
+
+ // Bail out immediately if the contents view window is gone. Note that it is
+ // not safe to access any class members in this case since |this| may already
+ // be destroyed. The local variable |drag_source| will still be valid though,
+ // so we can use it to determine if the window is gone.
+ if (!drag_source->window()) {
+ // Note that in this case, we don't need to call SystemDragEnded() since the
+ // renderer is going away.
+ return;
+ }
+
+ EndDrag(ConvertToWeb(result_op));
+ web_contents_->SystemDragEnded();
+}
+
+void WebContentsViewAura::UpdateDragCursor(WebKit::WebDragOperation operation) {
+ current_drag_op_ = operation;
+}
+
+void WebContentsViewAura::GotFocus() {
+ if (web_contents_->GetDelegate())
+ web_contents_->GetDelegate()->WebContentsFocused(web_contents_);
+}
+
+void WebContentsViewAura::TakeFocus(bool reverse) {
+ if (web_contents_->GetDelegate() &&
+ !web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
+ delegate_.get()) {
+ delegate_->TakeFocus(reverse);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, OverscrollControllerDelegate implementation:
+
+void WebContentsViewAura::OnOverscrollUpdate(float delta_x, float delta_y) {
+ if (current_overscroll_gesture_ == OVERSCROLL_NONE)
+ return;
+
+ aura::Window* target = GetWindowToAnimateForOverscroll();
+ ui::ScopedLayerAnimationSettings settings(target->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
+ gfx::Vector2d translate = GetTranslationForOverscroll(delta_x, delta_y);
+ gfx::Transform transform;
+
+ // Vertical overscrolls don't participate in the navigation gesture.
+ if (current_overscroll_gesture_ != OVERSCROLL_NORTH &&
+ current_overscroll_gesture_ != OVERSCROLL_SOUTH) {
+ transform.Translate(translate.x(), translate.y());
+ target->SetTransform(transform);
+ UpdateOverscrollWindowBrightness(delta_x);
+ }
+
+ OverscrollUpdateForWebContentsDelegate(translate.y());
+}
+
+void WebContentsViewAura::OnOverscrollComplete(OverscrollMode mode) {
+ UMA_HISTOGRAM_ENUMERATION("Overscroll.Completed", mode, OVERSCROLL_COUNT);
+ OverscrollUpdateForWebContentsDelegate(0);
+ NavigationControllerImpl& controller = web_contents_->GetController();
+ if (ShouldNavigateForward(controller, mode) ||
+ ShouldNavigateBack(controller, mode)) {
+ CompleteOverscrollNavigation(mode);
+ return;
+ }
+
+ ResetOverscrollTransform();
+}
+
+void WebContentsViewAura::OnOverscrollModeChange(OverscrollMode old_mode,
+ OverscrollMode new_mode) {
+ // Reset any in-progress overscroll animation first.
+ ResetOverscrollTransform();
+
+ if (new_mode == OVERSCROLL_NONE ||
+ !GetContentNativeView() ||
+ ((new_mode == OVERSCROLL_EAST || new_mode == OVERSCROLL_WEST) &&
+ navigation_overlay_.get() && navigation_overlay_->has_window())) {
+ current_overscroll_gesture_ = OVERSCROLL_NONE;
+ OverscrollUpdateForWebContentsDelegate(0);
+ } else {
+ aura::Window* target = GetWindowToAnimateForOverscroll();
+ if (target) {
+ StopObservingImplicitAnimations();
+ target->layer()->GetAnimator()->AbortAllAnimations();
+ }
+ // Cleanup state of the content window first, because that can reset the
+ // value of |current_overscroll_gesture_|.
+ PrepareContentWindowForOverscroll();
+
+ current_overscroll_gesture_ = new_mode;
+ if (current_overscroll_gesture_ == OVERSCROLL_EAST ||
+ current_overscroll_gesture_ == OVERSCROLL_WEST)
+ PrepareOverscrollWindow();
+
+ UMA_HISTOGRAM_ENUMERATION("Overscroll.Started", new_mode, OVERSCROLL_COUNT);
+ }
+ completed_overscroll_gesture_ = OVERSCROLL_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, ui::ImplicitAnimationObserver implementation:
+
+void WebContentsViewAura::OnImplicitAnimationsCompleted() {
+ overscroll_shadow_.reset();
+
+ if (ShouldNavigateForward(web_contents_->GetController(),
+ completed_overscroll_gesture_)) {
+ PrepareOverscrollNavigationOverlay();
+ web_contents_->GetController().GoForward();
+ } else if (ShouldNavigateBack(web_contents_->GetController(),
+ completed_overscroll_gesture_)) {
+ PrepareOverscrollNavigationOverlay();
+ web_contents_->GetController().GoBack();
+ }
+
+ aura::Window* content = GetContentNativeView();
+ if (content) {
+ content->SetTransform(gfx::Transform());
+ content->layer()->SetLayerBrightness(0.f);
+ }
+ current_overscroll_gesture_ = OVERSCROLL_NONE;
+ completed_overscroll_gesture_ = OVERSCROLL_NONE;
+ overscroll_window_.reset();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, aura::WindowDelegate implementation:
+
+gfx::Size WebContentsViewAura::GetMinimumSize() const {
+ return gfx::Size();
+}
+
+gfx::Size WebContentsViewAura::GetMaximumSize() const {
+ return gfx::Size();
+}
+
+void WebContentsViewAura::OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ SizeChangedCommon(new_bounds.size());
+ if (delegate_)
+ delegate_->SizeChanged(new_bounds.size());
+
+ // Constrained web dialogs, need to be kept centered over our content area.
+ for (size_t i = 0; i < window_->children().size(); i++) {
+ if (window_->children()[i]->GetProperty(
+ aura::client::kConstrainedWindowKey)) {
+ gfx::Rect bounds = window_->children()[i]->bounds();
+ bounds.set_origin(
+ gfx::Point((new_bounds.width() - bounds.width()) / 2,
+ (new_bounds.height() - bounds.height()) / 2));
+ window_->children()[i]->SetBounds(bounds);
+ }
+ }
+}
+
+gfx::NativeCursor WebContentsViewAura::GetCursor(const gfx::Point& point) {
+ return gfx::kNullCursor;
+}
+
+int WebContentsViewAura::GetNonClientComponent(const gfx::Point& point) const {
+ return HTCLIENT;
+}
+
+bool WebContentsViewAura::ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) {
+ return true;
+}
+
+bool WebContentsViewAura::CanFocus() {
+ // Do not take the focus if the render widget host view is gone because
+ // neither the view window nor this window can handle key events.
+ return web_contents_->GetRenderWidgetHostView() != NULL;
+}
+
+void WebContentsViewAura::OnCaptureLost() {
+}
+
+void WebContentsViewAura::OnPaint(gfx::Canvas* canvas) {
+}
+
+void WebContentsViewAura::OnDeviceScaleFactorChanged(
+ float device_scale_factor) {
+}
+
+void WebContentsViewAura::OnWindowDestroying() {
+ // This means the destructor is going to be called soon. If there is an
+ // overscroll gesture in progress (i.e. |overscroll_window_| is not NULL),
+ // then destroying it in the WebContentsViewAura destructor can trigger other
+ // virtual functions to be called (e.g. OnImplicitAnimationsCompleted()). So
+ // destroy the overscroll window here.
+ navigation_overlay_.reset();
+ overscroll_window_.reset();
+}
+
+void WebContentsViewAura::OnWindowDestroyed() {
+}
+
+void WebContentsViewAura::OnWindowTargetVisibilityChanged(bool visible) {
+ if (visible)
+ web_contents_->WasShown();
+ else
+ web_contents_->WasHidden();
+}
+
+bool WebContentsViewAura::HasHitTestMask() const {
+ return false;
+}
+
+void WebContentsViewAura::GetHitTestMask(gfx::Path* mask) const {
+}
+
+scoped_refptr<ui::Texture> WebContentsViewAura::CopyTexture() {
+ // The layer we create doesn't have an external texture, so this should never
+ // get invoked.
+ NOTREACHED();
+ return scoped_refptr<ui::Texture>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, ui::EventHandler implementation:
+
+void WebContentsViewAura::OnKeyEvent(ui::KeyEvent* event) {
+}
+
+void WebContentsViewAura::OnMouseEvent(ui::MouseEvent* event) {
+ if (!web_contents_->GetDelegate())
+ return;
+
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ web_contents_->GetDelegate()->ActivateContents(web_contents_);
+ break;
+ case ui::ET_MOUSE_MOVED:
+ case ui::ET_MOUSE_EXITED:
+ web_contents_->GetDelegate()->ContentsMouseEvent(
+ web_contents_,
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint(),
+ event->type() == ui::ET_MOUSE_MOVED);
+ break;
+ default:
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebContentsViewAura, aura::client::DragDropDelegate implementation:
+
+void WebContentsViewAura::OnDragEntered(const ui::DropTargetEvent& event) {
+ if (drag_dest_delegate_)
+ drag_dest_delegate_->DragInitialize(web_contents_);
+
+ current_drop_data_.reset(new DropData());
+
+ PrepareDropData(current_drop_data_.get(), event.data());
+ WebKit::WebDragOperationsMask op = ConvertToWeb(event.source_operations());
+
+ gfx::Point screen_pt =
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint();
+ current_rvh_for_drag_ = web_contents_->GetRenderViewHost();
+ web_contents_->GetRenderViewHost()->DragTargetDragEnter(
+ *current_drop_data_.get(), event.location(), screen_pt, op,
+ ConvertAuraEventFlagsToWebInputEventModifiers(event.flags()));
+
+ if (drag_dest_delegate_) {
+ drag_dest_delegate_->OnReceiveDragData(event.data());
+ drag_dest_delegate_->OnDragEnter();
+ }
+}
+
+int WebContentsViewAura::OnDragUpdated(const ui::DropTargetEvent& event) {
+ DCHECK(current_rvh_for_drag_);
+ if (current_rvh_for_drag_ != web_contents_->GetRenderViewHost())
+ OnDragEntered(event);
+
+ WebKit::WebDragOperationsMask op = ConvertToWeb(event.source_operations());
+ gfx::Point screen_pt =
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint();
+ web_contents_->GetRenderViewHost()->DragTargetDragOver(
+ event.location(), screen_pt, op,
+ ConvertAuraEventFlagsToWebInputEventModifiers(event.flags()));
+
+ if (drag_dest_delegate_)
+ drag_dest_delegate_->OnDragOver();
+
+ return ConvertFromWeb(current_drag_op_);
+}
+
+void WebContentsViewAura::OnDragExited() {
+ DCHECK(current_rvh_for_drag_);
+ if (current_rvh_for_drag_ != web_contents_->GetRenderViewHost())
+ return;
+
+ web_contents_->GetRenderViewHost()->DragTargetDragLeave();
+ if (drag_dest_delegate_)
+ drag_dest_delegate_->OnDragLeave();
+
+ current_drop_data_.reset();
+}
+
+int WebContentsViewAura::OnPerformDrop(const ui::DropTargetEvent& event) {
+ DCHECK(current_rvh_for_drag_);
+ if (current_rvh_for_drag_ != web_contents_->GetRenderViewHost())
+ OnDragEntered(event);
+
+ web_contents_->GetRenderViewHost()->DragTargetDrop(
+ event.location(),
+ gfx::Screen::GetScreenFor(GetNativeView())->GetCursorScreenPoint(),
+ ConvertAuraEventFlagsToWebInputEventModifiers(event.flags()));
+ if (drag_dest_delegate_)
+ drag_dest_delegate_->OnDrop();
+ current_drop_data_.reset();
+ return current_drag_op_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_aura.h b/chromium/content/browser/web_contents/web_contents_view_aura.h
new file mode 100644
index 00000000000..ca1f99c0a06
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_aura.h
@@ -0,0 +1,237 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_AURA_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_AURA_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/overscroll_controller_delegate.h"
+#include "content/common/content_export.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "ui/aura/client/drag_drop_delegate.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/compositor/layer_animation_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class DropTargetEvent;
+}
+
+namespace content {
+class OverscrollNavigationOverlay;
+class ShadowLayerDelegate;
+class TouchEditableImplAura;
+class WebContentsViewDelegate;
+class WebContentsImpl;
+class WebDragDestDelegate;
+
+class CONTENT_EXPORT WebContentsViewAura
+ : public WebContentsViewPort,
+ public RenderViewHostDelegateView,
+ NON_EXPORTED_BASE(public OverscrollControllerDelegate),
+ public ui::ImplicitAnimationObserver,
+ public aura::WindowDelegate,
+ public aura::client::DragDropDelegate {
+ public:
+ WebContentsViewAura(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate);
+
+ void SetupOverlayWindowForTesting();
+
+ void SetTouchEditableForTest(TouchEditableImplAura* touch_editable);
+
+ private:
+ class WindowObserver;
+#if defined(OS_WIN)
+ class ChildWindowObserver;
+#endif
+
+ virtual ~WebContentsViewAura();
+
+ void SizeChangedCommon(const gfx::Size& size);
+
+ void EndDrag(WebKit::WebDragOperationsMask ops);
+
+ // Creates and sets up the overlay window that will be displayed during the
+ // overscroll gesture.
+ void PrepareOverscrollWindow();
+
+ // Sets up the content window in preparation for starting an overscroll
+ // gesture.
+ void PrepareContentWindowForOverscroll();
+
+ // Resets any in-progress animation for the overscroll gesture. Note that this
+ // doesn't immediately reset the internal states; that happens after an
+ // animation.
+ void ResetOverscrollTransform();
+
+ // Completes the navigation in response to a completed overscroll gesture.
+ // The navigation happens after an animation (either the overlay window
+ // animates in, or the content window animates out).
+ void CompleteOverscrollNavigation(OverscrollMode mode);
+
+ // Returns the window that should be animated for the overscroll gesture.
+ // (note that during the overscroll gesture, either the overlay window or the
+ // content window can be animated).
+ aura::Window* GetWindowToAnimateForOverscroll();
+
+ // Returns the amount the animating window should be translated in response to
+ // the overscroll gesture.
+ gfx::Vector2d GetTranslationForOverscroll(int delta_x, int delta_y);
+
+ // A window showing the screenshot is overlayed during a navigation triggered
+ // by overscroll. This function sets this up.
+ void PrepareOverscrollNavigationOverlay();
+
+ // Changes the brightness of the layer depending on the amount of horizontal
+ // overscroll (|delta_x|, in pixels).
+ void UpdateOverscrollWindowBrightness(float delta_x);
+
+ void AttachTouchEditableToRenderView();
+
+ void OverscrollUpdateForWebContentsDelegate(int delta_y);
+
+ // Overridden from WebContentsView:
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect *out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+
+ // Overridden from WebContentsViewPort:
+ virtual void CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+
+ // Overridden from RenderViewHostDelegateView:
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ // Overridden from OverscrollControllerDelegate:
+ virtual void OnOverscrollUpdate(float delta_x, float delta_y) OVERRIDE;
+ virtual void OnOverscrollComplete(OverscrollMode overscroll_mode) OVERRIDE;
+ virtual void OnOverscrollModeChange(OverscrollMode old_mode,
+ OverscrollMode new_mode) OVERRIDE;
+
+ // Overridden from ui::ImplicitAnimationObserver:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE;
+
+ // Overridden from aura::WindowDelegate:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE;
+ virtual gfx::Size GetMaximumSize() const OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE;
+ virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE;
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE;
+ virtual bool CanFocus() OVERRIDE;
+ virtual void OnCaptureLost() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE;
+ virtual void OnWindowDestroying() OVERRIDE;
+ virtual void OnWindowDestroyed() OVERRIDE;
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE;
+ virtual bool HasHitTestMask() const OVERRIDE;
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE;
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ // Overridden from aura::client::DragDropDelegate:
+ virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE;
+ virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE;
+ virtual void OnDragExited() OVERRIDE;
+ virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE;
+
+ scoped_ptr<aura::Window> window_;
+
+ // The window that shows the screenshot of the history page during an
+ // overscroll navigation gesture.
+ scoped_ptr<aura::Window> overscroll_window_;
+
+ scoped_ptr<WindowObserver> window_observer_;
+#if defined(OS_WIN)
+ scoped_ptr<ChildWindowObserver> child_window_observer_;
+#endif
+
+ // The WebContentsImpl whose contents we display.
+ WebContentsImpl* web_contents_;
+
+ scoped_ptr<WebContentsViewDelegate> delegate_;
+
+ WebKit::WebDragOperationsMask current_drag_op_;
+
+ scoped_ptr<DropData> current_drop_data_;
+
+ WebDragDestDelegate* drag_dest_delegate_;
+
+ // We keep track of the render view host we're dragging over. If it changes
+ // during a drag, we need to re-send the DragEnter message. WARNING:
+ // this pointer should never be dereferenced. We only use it for comparing
+ // pointers.
+ void* current_rvh_for_drag_;
+
+ bool overscroll_change_brightness_;
+
+ // The overscroll gesture currently in progress.
+ OverscrollMode current_overscroll_gesture_;
+
+ // This is the completed overscroll gesture. This is used for the animation
+ // callback that happens in response to a completed overscroll gesture.
+ OverscrollMode completed_overscroll_gesture_;
+
+ // This manages the overlay window that shows the screenshot during a history
+ // navigation triggered by the overscroll gesture.
+ scoped_ptr<OverscrollNavigationOverlay> navigation_overlay_;
+
+ scoped_ptr<ShadowLayerDelegate> overscroll_shadow_;
+
+ scoped_ptr<TouchEditableImplAura> touch_editable_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewAura);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_AURA_H_
diff --git a/chromium/content/browser/web_contents/web_contents_view_aura_browsertest.cc b/chromium/content/browser/web_contents/web_contents_view_aura_browsertest.cc
new file mode 100644
index 00000000000..e0f34abe4fc
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_aura_browsertest.cc
@@ -0,0 +1,607 @@
+// 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 "content/browser/web_contents/web_contents_view_aura.h"
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/navigation_controller_impl.h"
+#include "content/browser/web_contents/navigation_entry_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_contents_screenshot_manager.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+
+namespace content {
+
+// This class keeps track of the RenderViewHost whose screenshot was captured.
+class ScreenshotTracker : public WebContentsScreenshotManager {
+ public:
+ explicit ScreenshotTracker(NavigationControllerImpl* controller)
+ : WebContentsScreenshotManager(controller),
+ screenshot_taken_for_(NULL),
+ waiting_for_screenshots_(0) {
+ }
+
+ virtual ~ScreenshotTracker() {
+ }
+
+ RenderViewHost* screenshot_taken_for() { return screenshot_taken_for_; }
+
+ void Reset() {
+ screenshot_taken_for_ = NULL;
+ }
+
+ void SetScreenshotInterval(int interval_ms) {
+ SetMinScreenshotIntervalMS(interval_ms);
+ }
+
+ void WaitUntilScreenshotIsReady() {
+ if (!waiting_for_screenshots_)
+ return;
+ message_loop_runner_ = new content::MessageLoopRunner;
+ message_loop_runner_->Run();
+ }
+
+ private:
+ // Overridden from WebContentsScreenshotManager:
+ virtual void TakeScreenshotImpl(RenderViewHost* host,
+ NavigationEntryImpl* entry) OVERRIDE {
+ ++waiting_for_screenshots_;
+ screenshot_taken_for_ = host;
+ WebContentsScreenshotManager::TakeScreenshotImpl(host, entry);
+ }
+
+ virtual void OnScreenshotSet(NavigationEntryImpl* entry) OVERRIDE {
+ --waiting_for_screenshots_;
+ WebContentsScreenshotManager::OnScreenshotSet(entry);
+ if (waiting_for_screenshots_ == 0 && message_loop_runner_.get())
+ message_loop_runner_->Quit();
+ }
+
+ RenderViewHost* screenshot_taken_for_;
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+ int waiting_for_screenshots_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenshotTracker);
+};
+
+class WebContentsViewAuraTest : public ContentBrowserTest {
+ public:
+ WebContentsViewAuraTest()
+ : screenshot_manager_(NULL) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ // TODO(jbauman): Remove this. http://crbug.com/268644
+ UseRealGLContexts();
+ }
+
+ // Executes the javascript synchronously and makes sure the returned value is
+ // freed properly.
+ void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) {
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(rvh, jscript);
+ }
+
+ // Starts the test server and navigates to the given url. Sets a large enough
+ // size to the root window. Returns after the navigation to the url is
+ // complete.
+ void StartTestWithPage(const std::string& url) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL test_url(test_server()->GetURL(url));
+ NavigateToURL(shell(), test_url);
+ aura::Window* content =
+ shell()->web_contents()->GetView()->GetContentNativeView();
+ content->GetRootWindow()->SetHostSize(gfx::Size(800, 600));
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ NavigationControllerImpl* controller = &web_contents->GetController();
+
+ screenshot_manager_ = new ScreenshotTracker(controller);
+ controller->SetScreenshotManager(screenshot_manager_);
+ }
+
+ void TestOverscrollNavigation(bool touch_handler) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ NavigationController& controller = web_contents->GetController();
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ view_aura->SetupOverlayWindowForTesting();
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ int index = -1;
+ scoped_ptr<base::Value> value =
+ content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ ASSERT_TRUE(value->GetAsInteger(&index));
+ EXPECT_EQ(0, index);
+
+ if (touch_handler)
+ ExecuteSyncJSFunction(view_host, "install_touch_handler()");
+
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ ASSERT_TRUE(value->GetAsInteger(&index));
+ EXPECT_EQ(2, index);
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+
+ {
+ // Do a swipe-right now. That should navigate backwards.
+ string16 expected_title = ASCIIToUTF16("Title: #1");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ ASSERT_TRUE(value->GetAsInteger(&index));
+ EXPECT_EQ(1, index);
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+ }
+
+ {
+ // Do a fling-right now. That should navigate backwards.
+ string16 expected_title = ASCIIToUTF16("Title:");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 10);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ ASSERT_TRUE(value->GetAsInteger(&index));
+ EXPECT_EQ(0, index);
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+ }
+
+ {
+ // Do a swipe-left now. That should navigate forward.
+ string16 expected_title = ASCIIToUTF16("Title: #1");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 10);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ ASSERT_TRUE(value->GetAsInteger(&index));
+ EXPECT_EQ(1, index);
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+ }
+ }
+
+ int GetCurrentIndex() {
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ int index = -1;
+ scoped_ptr<base::Value> value;
+ value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
+ if (!value->GetAsInteger(&index))
+ index = -1;
+ return index;
+ }
+
+ protected:
+ ScreenshotTracker* screenshot_manager() { return screenshot_manager_; }
+ void set_min_screenshot_interval(int interval_ms) {
+ screenshot_manager_->SetScreenshotInterval(interval_ms);
+ }
+
+ private:
+ ScreenshotTracker* screenshot_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewAuraTest);
+};
+
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ OverscrollNavigation) {
+ TestOverscrollNavigation(false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ OverscrollNavigationWithTouchHandler) {
+ TestOverscrollNavigation(true);
+}
+
+// Disabled because the test always fails the first time it runs on the Win Aura
+// bots, and usually but not always passes second-try (See crbug.com/179532).
+#if defined(OS_WIN)
+#define MAYBE_QuickOverscrollDirectionChange \
+ DISABLED_QuickOverscrollDirectionChange
+#else
+#define MAYBE_QuickOverscrollDirectionChange QuickOverscrollDirectionChange
+#endif
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ MAYBE_QuickOverscrollDirectionChange) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+
+ // This test triggers a large number of animations. Speed them up to ensure
+ // the test completes within its time limit.
+ ui::ScopedAnimationDurationScaleMode fast_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
+
+ // Make sure the page has both back/forward history.
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(1, GetCurrentIndex());
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(2, GetCurrentIndex());
+ web_contents->GetController().GoBack();
+ EXPECT_EQ(1, GetCurrentIndex());
+
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ aura::RootWindow* root_window = content->GetRootWindow();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+
+ base::TimeDelta timestamp;
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
+ 0, timestamp);
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
+ EXPECT_EQ(1, GetCurrentIndex());
+
+ timestamp += base::TimeDelta::FromMilliseconds(10);
+ ui::TouchEvent move1(ui::ET_TOUCH_MOVED,
+ gfx::Point(bounds.right() - 10, bounds.y() + 5),
+ 0, timestamp);
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1);
+ EXPECT_EQ(1, GetCurrentIndex());
+
+ // Swipe back from the right edge, back to the left edge, back to the right
+ // edge.
+
+ for (int x = bounds.right() - 10; x >= bounds.x() + 10; x-= 10) {
+ timestamp += base::TimeDelta::FromMilliseconds(10);
+ ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
+ gfx::Point(x, bounds.y() + 5),
+ 0, timestamp);
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
+ EXPECT_EQ(1, GetCurrentIndex());
+ }
+
+ for (int x = bounds.x() + 10; x <= bounds.width() - 10; x+= 10) {
+ timestamp += base::TimeDelta::FromMilliseconds(10);
+ ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
+ gfx::Point(x, bounds.y() + 5),
+ 0, timestamp);
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
+ EXPECT_EQ(1, GetCurrentIndex());
+ }
+
+ for (int x = bounds.width() - 10; x >= bounds.x() + 10; x-= 10) {
+ timestamp += base::TimeDelta::FromMilliseconds(10);
+ ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
+ gfx::Point(x, bounds.y() + 5),
+ 0, timestamp);
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
+ EXPECT_EQ(1, GetCurrentIndex());
+ }
+
+ // Do not end the overscroll sequence.
+}
+
+// Tests that the page has has a screenshot when navigation happens:
+// - from within the page (from a JS function)
+// - interactively, when user does an overscroll gesture
+// - interactively, when user navigates in history without the overscroll
+// gesture.
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ OverscrollScreenshot) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+
+ set_min_screenshot_interval(0);
+
+ // Do a few navigations initiated by the page.
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(1, GetCurrentIndex());
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(2, GetCurrentIndex());
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+
+ // The current entry won't have any screenshots. But the entries in the
+ // history should now have screenshots.
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(2));
+ EXPECT_FALSE(entry->screenshot().get());
+
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(1));
+ EXPECT_TRUE(entry->screenshot().get());
+
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(0));
+ EXPECT_TRUE(entry->screenshot().get());
+
+ // Navigate again. Index 2 should now have a screenshot.
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(3, GetCurrentIndex());
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(2));
+ EXPECT_TRUE(entry->screenshot().get());
+
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(3));
+ EXPECT_FALSE(entry->screenshot().get());
+
+ {
+ // Now, swipe right to navigate backwards. This should navigate away from
+ // index 3 to index 2, and index 3 should have a screenshot.
+ string16 expected_title = ASCIIToUTF16("Title: #2");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ EXPECT_EQ(2, GetCurrentIndex());
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(3));
+ EXPECT_TRUE(entry->screenshot().get());
+ }
+
+ // Navigate a couple more times.
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(3, GetCurrentIndex());
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(4, GetCurrentIndex());
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(4));
+ EXPECT_FALSE(entry->screenshot().get());
+
+ {
+ // Navigate back in history.
+ string16 expected_title = ASCIIToUTF16("Title: #3");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+ web_contents->GetController().GoBack();
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ EXPECT_EQ(3, GetCurrentIndex());
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtIndex(4));
+ EXPECT_TRUE(entry->screenshot().get());
+ }
+}
+
+// Tests that screenshot is taken correctly when navigation causes a
+// RenderViewHost to be swapped out.
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ ScreenshotForSwappedOutRenderViews) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+ // Create a new server with a different site.
+ net::SpawnedTestServer https_server(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ set_min_screenshot_interval(0);
+
+ struct {
+ GURL url;
+ int transition;
+ } navigations[] = {
+ { https_server.GetURL("files/title1.html"),
+ PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR },
+ { test_server()->GetURL("files/title2.html"),
+ PAGE_TRANSITION_AUTO_BOOKMARK },
+ { https_server.GetURL("files/title3.html"),
+ PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR },
+ { GURL(), 0 }
+ };
+
+ screenshot_manager()->Reset();
+ for (int i = 0; !navigations[i].url.is_empty(); ++i) {
+ // Navigate via the user initiating a navigation from the UI.
+ NavigationController::LoadURLParams params(navigations[i].url);
+ params.transition_type = PageTransitionFromInt(navigations[i].transition);
+
+ RenderViewHost* old_host = web_contents->GetRenderViewHost();
+ web_contents->GetController().LoadURLWithParams(params);
+ WaitForLoadStop(web_contents);
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+
+ EXPECT_NE(old_host, web_contents->GetRenderViewHost())
+ << navigations[i].url.spec();
+ EXPECT_EQ(old_host, screenshot_manager()->screenshot_taken_for());
+ screenshot_manager()->Reset();
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetEntryAtOffset(-1));
+ EXPECT_TRUE(entry->screenshot().get());
+
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ web_contents->GetController().GetActiveEntry());
+ EXPECT_FALSE(entry->screenshot().get());
+ }
+
+ // Increase the minimum interval between taking screenshots.
+ set_min_screenshot_interval(60000);
+
+ // Navigate again. This should not take any screenshot because of the
+ // increased screenshot interval.
+ NavigationController::LoadURLParams params(navigations[0].url);
+ params.transition_type = PageTransitionFromInt(navigations[0].transition);
+ web_contents->GetController().LoadURLWithParams(params);
+ WaitForLoadStop(web_contents);
+ screenshot_manager()->WaitUntilScreenshotIsReady();
+
+ EXPECT_EQ(NULL, screenshot_manager()->screenshot_taken_for());
+}
+
+// Failing on win7_aura trybot (see crbug.com/260983).
+#if defined(OS_WIN)
+#define MAYBE_ContentWindowReparent \
+ DISABLED_ContentWindowReparent
+#else
+#define MAYBE_ContentWindowReparent ContentWindowReparent
+#endif
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ MAYBE_ContentWindowReparent) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+
+ scoped_ptr<aura::Window> window(new aura::Window(NULL));
+ window->Init(ui::LAYER_NOT_DRAWN);
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ ExecuteSyncJSFunction(web_contents->GetRenderViewHost(), "navigate_next()");
+ EXPECT_EQ(1, GetCurrentIndex());
+
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+
+ window->AddChild(shell()->web_contents()->GetView()->GetContentNativeView());
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ ContentWindowClose) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ ExecuteSyncJSFunction(web_contents->GetRenderViewHost(), "navigate_next()");
+ EXPECT_EQ(1, GetCurrentIndex());
+
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(20),
+ 1);
+
+ delete web_contents->GetView()->GetContentNativeView();
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
+ RepeatedQuickOverscrollGestures) {
+ ASSERT_NO_FATAL_FAILURE(
+ StartTestWithPage("files/overscroll_navigation.html"));
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ NavigationController& controller = web_contents->GetController();
+ RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
+ web_contents->GetRenderViewHost());
+ WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
+ web_contents->GetView());
+ view_aura->SetupOverlayWindowForTesting();
+ ExecuteSyncJSFunction(view_host, "install_touch_handler()");
+
+ // Navigate twice, then navigate back in history once.
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ ExecuteSyncJSFunction(view_host, "navigate_next()");
+ EXPECT_EQ(2, GetCurrentIndex());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ web_contents->GetController().GoBack();
+ WaitForLoadStop(web_contents);
+ EXPECT_EQ(1, GetCurrentIndex());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+
+ aura::Window* content = web_contents->GetView()->GetContentNativeView();
+ gfx::Rect bounds = content->GetBoundsInRootWindow();
+ aura::test::EventGenerator generator(content->GetRootWindow(), content);
+
+ // Do a swipe left to start a forward navigation. Then quickly do a swipe
+ // right.
+ string16 expected_title = ASCIIToUTF16("Title: #2");
+ content::TitleWatcher title_watcher(web_contents, expected_title);
+
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(2000),
+ 10);
+ // Make sure the GestureEventFilter's debouncing doesn't interfere.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromMilliseconds(100));
+ base::MessageLoop::current()->Run();
+ generator.GestureScrollSequence(
+ gfx::Point(bounds.x() + 2, bounds.y() + 10),
+ gfx::Point(bounds.right() - 10, bounds.y() + 10),
+ base::TimeDelta::FromMilliseconds(2000),
+ 10);
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+
+ EXPECT_EQ(2, GetCurrentIndex());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_gtk.cc b/chromium/content/browser/web_contents/web_contents_view_gtk.cc
new file mode 100644
index 00000000000..cdc36c8d11e
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_gtk.cc
@@ -0,0 +1,418 @@
+// 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 "content/browser/web_contents/web_contents_view_gtk.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <algorithm>
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_drag_dest_gtk.h"
+#include "content/browser/web_contents/web_drag_source_gtk.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_view_delegate.h"
+#include "content/public/common/drop_data.h"
+#include "ui/base/gtk/gtk_expanded_container.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace content {
+namespace {
+
+// Called when the mouse leaves the widget. We notify our delegate.
+gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
+ WebContentsImpl* web_contents) {
+ if (web_contents->GetDelegate())
+ web_contents->GetDelegate()->ContentsMouseEvent(
+ web_contents, gfx::Point(event->x_root, event->y_root), false);
+ return FALSE;
+}
+
+// Called when the mouse moves within the widget. We notify our delegate.
+gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event,
+ WebContentsImpl* web_contents) {
+ if (web_contents->GetDelegate())
+ web_contents->GetDelegate()->ContentsMouseEvent(
+ web_contents, gfx::Point(event->x_root, event->y_root), true);
+ return FALSE;
+}
+
+// See tab_contents_view_views.cc for discussion of mouse scroll zooming.
+gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
+ WebContentsImpl* web_contents) {
+ if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
+ GDK_CONTROL_MASK) {
+ return FALSE;
+ }
+
+ WebContentsDelegate* delegate = web_contents->GetDelegate();
+ if (!delegate)
+ return FALSE;
+
+ if (!(event->direction == GDK_SCROLL_DOWN ||
+ event->direction == GDK_SCROLL_UP)) {
+ return FALSE;
+ }
+
+ delegate->ContentsZoomChange(event->direction == GDK_SCROLL_UP);
+ return TRUE;
+}
+
+} // namespace
+
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view) {
+ WebContentsViewGtk* rv = new WebContentsViewGtk(web_contents, delegate);
+ *render_view_host_delegate_view = rv;
+ return rv;
+}
+
+WebContentsViewGtk::WebContentsViewGtk(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate)
+ : web_contents_(web_contents),
+ expanded_(gtk_expanded_container_new()),
+ delegate_(delegate) {
+ gtk_widget_set_name(expanded_.get(), "chrome-web-contents-view");
+ g_signal_connect(expanded_.get(), "size-allocate",
+ G_CALLBACK(OnSizeAllocateThunk), this);
+ g_signal_connect(expanded_.get(), "child-size-request",
+ G_CALLBACK(OnChildSizeRequestThunk), this);
+
+ gtk_widget_show(expanded_.get());
+ drag_source_.reset(new WebDragSourceGtk(web_contents));
+
+ if (delegate_)
+ delegate_->Initialize(expanded_.get(), &focus_store_);
+}
+
+WebContentsViewGtk::~WebContentsViewGtk() {
+ expanded_.Destroy();
+}
+
+gfx::NativeView WebContentsViewGtk::GetNativeView() const {
+ if (delegate_)
+ return delegate_->GetNativeView();
+
+ return expanded_.get();
+}
+
+gfx::NativeView WebContentsViewGtk::GetContentNativeView() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (!rwhv)
+ return NULL;
+ return rwhv->GetNativeView();
+}
+
+gfx::NativeWindow WebContentsViewGtk::GetTopLevelNativeWindow() const {
+ GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
+ return window ? GTK_WINDOW(window) : NULL;
+}
+
+void WebContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
+ // This is used for positioning the download shelf arrow animation,
+ // as well as sizing some other widgets in Windows. In GTK the size is
+ // managed for us, so it appears to be only used for the download shelf
+ // animation.
+ int x = 0;
+ int y = 0;
+ GdkWindow* expanded_window = gtk_widget_get_window(expanded_.get());
+ if (expanded_window)
+ gdk_window_get_origin(expanded_window, &x, &y);
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(expanded_.get(), &allocation);
+ out->SetRect(x + allocation.x, y + allocation.y,
+ requested_size_.width(), requested_size_.height());
+}
+
+void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
+ int error_code) {
+}
+
+void WebContentsViewGtk::Focus() {
+ if (web_contents_->ShowingInterstitialPage()) {
+ web_contents_->GetInterstitialPage()->Focus();
+ } else if (delegate_) {
+ delegate_->Focus();
+ }
+}
+
+void WebContentsViewGtk::SetInitialFocus() {
+ if (web_contents_->FocusLocationBarByDefault())
+ web_contents_->SetFocusToLocationBar(false);
+ else
+ Focus();
+}
+
+void WebContentsViewGtk::StoreFocus() {
+ focus_store_.Store(GetNativeView());
+}
+
+void WebContentsViewGtk::RestoreFocus() {
+ if (focus_store_.widget())
+ gtk_widget_grab_focus(focus_store_.widget());
+ else
+ SetInitialFocus();
+}
+
+DropData* WebContentsViewGtk::GetDropData() const {
+ return drag_dest_->current_drop_data();
+}
+
+gfx::Rect WebContentsViewGtk::GetViewBounds() const {
+ gfx::Rect rect;
+ GdkWindow* window = gtk_widget_get_window(GetNativeView());
+ if (!window) {
+ rect.SetRect(0, 0, requested_size_.width(), requested_size_.height());
+ return rect;
+ }
+ int x = 0, y = 0, w, h;
+ gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
+ rect.SetRect(x, y, w, h);
+ return rect;
+}
+
+void WebContentsViewGtk::CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) {
+ requested_size_ = initial_size;
+}
+
+RenderWidgetHostView* WebContentsViewGtk::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+
+ RenderWidgetHostView* view =
+ RenderWidgetHostView::CreateViewForWidget(render_widget_host);
+ view->InitAsChild(NULL);
+ gfx::NativeView content_view = view->GetNativeView();
+ g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
+ g_signal_connect(content_view, "leave-notify-event",
+ G_CALLBACK(OnLeaveNotify), web_contents_);
+ g_signal_connect(content_view, "motion-notify-event",
+ G_CALLBACK(OnMouseMove), web_contents_);
+ g_signal_connect(content_view, "scroll-event",
+ G_CALLBACK(OnMouseScroll), web_contents_);
+ gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
+ GDK_POINTER_MOTION_MASK);
+ InsertIntoContentArea(content_view);
+
+ if (render_widget_host->IsRenderView()) {
+ RenderViewHost* rvh = RenderViewHost::From(render_widget_host);
+ // If |rvh| is already the current render view host for the web contents, we
+ // need to initialize |drag_dest_| for drags to be properly handled.
+ // Otherwise, |drag_dest_| will be updated in RenderViewSwappedIn. The
+ // reason we can't simply check that this isn't a swapped-out view is
+ // because there are navigations that create non-swapped-out views that may
+ // never be displayed, e.g. a navigation that becomes a download.
+ if (rvh == web_contents_->GetRenderViewHost()) {
+ UpdateDragDest(rvh);
+ }
+ }
+
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewGtk::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewGtk::SetPageTitle(const string16& title) {
+ // Set the window name to include the page title so it's easier to spot
+ // when debugging (e.g. via xwininfo -tree).
+ gfx::NativeView content_view = GetContentNativeView();
+ if (content_view) {
+ GdkWindow* content_window = gtk_widget_get_window(content_view);
+ if (content_window) {
+ gdk_window_set_title(content_window, UTF16ToUTF8(title).c_str());
+ }
+ }
+}
+
+void WebContentsViewGtk::SizeContents(const gfx::Size& size) {
+ // We don't need to manually set the size of of widgets in GTK+, but we do
+ // need to pass the sizing information on to the RWHV which will pass the
+ // sizing information on to the renderer.
+ requested_size_ = size;
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+}
+
+void WebContentsViewGtk::RenderViewCreated(RenderViewHost* host) {
+}
+
+void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost* host) {
+ UpdateDragDest(host);
+}
+
+void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled) {
+}
+
+WebContents* WebContentsViewGtk::web_contents() {
+ return web_contents_;
+}
+
+void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
+ drag_dest_->UpdateDragStatus(operation);
+}
+
+void WebContentsViewGtk::GotFocus() {
+ // This is only used in the views FocusManager stuff but it bleeds through
+ // all subclasses. http://crbug.com/21875
+}
+
+// This is called when the renderer asks us to take focus back (i.e., it has
+// iterated past the last focusable element on the page).
+void WebContentsViewGtk::TakeFocus(bool reverse) {
+ if (!web_contents_->GetDelegate())
+ return;
+ if (!web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
+ GetTopLevelNativeWindow()) {
+ gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
+ reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
+ }
+}
+
+void WebContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
+ gtk_container_add(GTK_CONTAINER(expanded_.get()), widget);
+}
+
+void WebContentsViewGtk::UpdateDragDest(RenderViewHost* host) {
+ gfx::NativeView content_view = host->GetView()->GetNativeView();
+
+ // If the host is already used by the drag_dest_, there's no point in deleting
+ // the old one to create an identical copy.
+ if (drag_dest_.get() && drag_dest_->widget() == content_view)
+ return;
+
+ // Clear the currently connected drag drop signals by deleting the old
+ // drag_dest_ before creating the new one.
+ drag_dest_.reset();
+ // Create the new drag_dest_.
+ drag_dest_.reset(new WebDragDestGtk(web_contents_, content_view));
+
+ if (delegate_)
+ drag_dest_->set_delegate(delegate_->GetDragDestDelegate());
+}
+
+// Called when the content view gtk widget is tabbed to, or after the call to
+// gtk_widget_child_focus() in TakeFocus(). We return true
+// and grab focus if we don't have it. The call to
+// FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
+// webkit.
+gboolean WebContentsViewGtk::OnFocus(GtkWidget* widget,
+ GtkDirectionType focus) {
+ // Give our view wrapper first chance at this event.
+ if (delegate_) {
+ gboolean return_value = FALSE;
+ if (delegate_->OnNativeViewFocusEvent(widget, focus, &return_value))
+ return return_value;
+ }
+
+ // If we already have focus, let the next widget have a shot at it. We will
+ // reach this situation after the call to gtk_widget_child_focus() in
+ // TakeFocus().
+ if (gtk_widget_is_focus(widget))
+ return FALSE;
+
+ gtk_widget_grab_focus(widget);
+ bool reverse = focus == GTK_DIR_TAB_BACKWARD;
+ web_contents_->FocusThroughTabTraversal(reverse);
+ return TRUE;
+}
+
+void WebContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
+ if (delegate_)
+ delegate_->ShowContextMenu(params);
+ else
+ DLOG(ERROR) << "Cannot show context menus without a delegate.";
+}
+
+void WebContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ // External popup menus are only used on Mac and Android.
+ NOTIMPLEMENTED();
+}
+
+// Render view DnD -------------------------------------------------------------
+
+void WebContentsViewGtk::StartDragging(const DropData& drop_data,
+ WebDragOperationsMask ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ DCHECK(GetContentNativeView());
+
+ RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
+ web_contents_->GetRenderWidgetHostView());
+ if (!view_gtk || !view_gtk->GetLastMouseDown() ||
+ !drag_source_->StartDragging(drop_data, ops, view_gtk->GetLastMouseDown(),
+ *image.bitmap(), image_offset)) {
+ web_contents_->SystemDragEnded();
+ }
+}
+
+// -----------------------------------------------------------------------------
+
+void WebContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
+ GtkWidget* child,
+ GtkRequisition* requisition) {
+ if (web_contents_->GetDelegate()) {
+ requisition->height +=
+ web_contents_->GetDelegate()->GetExtraRenderViewHeight();
+ }
+}
+
+void WebContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ int width = allocation->width;
+ int height = allocation->height;
+ // |delegate()| can be NULL here during browser teardown.
+ if (web_contents_->GetDelegate())
+ height += web_contents_->GetDelegate()->GetExtraRenderViewHeight();
+ gfx::Size size(width, height);
+ requested_size_ = size;
+
+ // We manually tell our RWHV to resize the renderer content. This avoids
+ // spurious resizes from GTK+.
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+ if (web_contents_->GetInterstitialPage())
+ web_contents_->GetInterstitialPage()->SetSize(size);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_gtk.h b/chromium/content/browser/web_contents/web_contents_view_gtk.h
new file mode 100644
index 00000000000..4a456fcf0b2
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_gtk.h
@@ -0,0 +1,147 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GTK_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/common/drag_event_source_info.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "ui/base/gtk/focus_store_gtk.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/gtk/owned_widget_gtk.h"
+
+namespace content {
+
+class WebContents;
+class WebContentsImpl;
+class WebContentsViewDelegate;
+class WebDragDestDelegate;
+class WebDragDestGtk;
+class WebDragSourceGtk;
+
+class CONTENT_EXPORT WebContentsViewGtk
+ : public WebContentsViewPort,
+ public RenderViewHostDelegateView {
+ public:
+ // The corresponding WebContentsImpl is passed in the constructor, and manages
+ // our lifetime. This doesn't need to be the case, but is this way currently
+ // because that's what was easiest when they were split. We optionally take
+ // |wrapper| which creates an intermediary widget layer for features from the
+ // Embedding layer that lives with the WebContentsView.
+ WebContentsViewGtk(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate);
+ virtual ~WebContentsViewGtk();
+
+ WebContentsViewDelegate* delegate() const { return delegate_.get(); }
+ WebContents* web_contents();
+
+ // WebContentsView implementation --------------------------------------------
+
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect* out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+
+ // WebContentsViewPort implementation ----------------------------------------
+ virtual void CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+
+ // Backend implementation of RenderViewHostDelegateView.
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ private:
+ // Insert the given widget into the content area. Should only be used for
+ // web pages and the like (including interstitials and sad tab). Note that
+ // this will be perfectly happy to insert overlapping render views, so care
+ // should be taken that the correct one is hidden/shown.
+ void InsertIntoContentArea(GtkWidget* widget);
+
+ // Replaces, or updates, the existing WebDragDestGtk with one for |new_host|.
+ // This must be called when swapping in, or creating a swapped in, RVH.
+ void UpdateDragDest(RenderViewHost* new_host);
+
+ // Handle focus traversal on the render widget native view. Can be overridden
+ // by subclasses.
+ CHROMEGTK_CALLBACK_1(WebContentsViewGtk, gboolean, OnFocus, GtkDirectionType);
+
+ // Used to adjust the size of its children when the size of |expanded_| is
+ // changed.
+ CHROMEGTK_CALLBACK_2(WebContentsViewGtk, void, OnChildSizeRequest,
+ GtkWidget*, GtkRequisition*);
+
+ // Used to propagate the size change of |expanded_| to our RWHV to resize the
+ // renderer content.
+ CHROMEGTK_CALLBACK_1(WebContentsViewGtk, void, OnSizeAllocate,
+ GtkAllocation*);
+
+ // The WebContentsImpl whose contents we display.
+ WebContentsImpl* web_contents_;
+
+ // This container holds the tab's web page views. It is a GtkExpandedContainer
+ // so that we can control the size of the web pages.
+ ui::OwnedWidgetGtk expanded_;
+
+ ui::FocusStoreGtk focus_store_;
+
+ // The helper object that handles drag destination related interactions with
+ // GTK.
+ scoped_ptr<WebDragDestGtk> drag_dest_;
+
+ // Object responsible for handling drags from the page for us.
+ scoped_ptr<WebDragSourceGtk> drag_source_;
+
+ // Our optional views wrapper. If non-NULL, we return this widget as our
+ // GetNativeView() and insert |expanded_| as its child in the GtkWidget
+ // hierarchy.
+ scoped_ptr<WebContentsViewDelegate> delegate_;
+
+ // The size we want the view to be. We keep this in a separate variable
+ // because resizing in GTK+ is async.
+ gfx::Size requested_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GTK_H_
diff --git a/chromium/content/browser/web_contents/web_contents_view_guest.cc b/chromium/content/browser/web_contents/web_contents_view_guest.cc
new file mode 100644
index 00000000000..a9fc40f1894
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_guest.cc
@@ -0,0 +1,265 @@
+// 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 "content/browser/web_contents/web_contents_view_guest.h"
+
+#include "build/build_config.h"
+#include "content/browser/browser_plugin/browser_plugin_embedder.h"
+#include "content/browser/browser_plugin/browser_plugin_guest.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_guest.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/drag_messages.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/context_menu_params.h"
+#include "content/public/common/drop_data.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#endif
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace content {
+
+WebContentsViewGuest::WebContentsViewGuest(
+ WebContentsImpl* web_contents,
+ BrowserPluginGuest* guest,
+ scoped_ptr<WebContentsViewPort> platform_view,
+ RenderViewHostDelegateView* platform_view_delegate_view)
+ : web_contents_(web_contents),
+ guest_(guest),
+ platform_view_(platform_view.Pass()),
+ platform_view_delegate_view_(platform_view_delegate_view) {
+}
+
+WebContentsViewGuest::~WebContentsViewGuest() {
+}
+
+gfx::NativeView WebContentsViewGuest::GetNativeView() const {
+ return platform_view_->GetNativeView();
+}
+
+gfx::NativeView WebContentsViewGuest::GetContentNativeView() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (!rwhv)
+ return NULL;
+ return rwhv->GetNativeView();
+}
+
+gfx::NativeWindow WebContentsViewGuest::GetTopLevelNativeWindow() const {
+ return guest_->embedder_web_contents()->GetView()->GetTopLevelNativeWindow();
+}
+
+void WebContentsViewGuest::OnGuestInitialized(WebContentsView* parent_view) {
+#if defined(USE_AURA) || defined(OS_WIN)
+ // In aura and windows, ScreenPositionClient doesn't work properly if we do
+ // not have the native view associated with this WebContentsViewGuest in the
+ // view hierarchy. We add this view as embedder's child here.
+ // This would go in WebContentsViewGuest::CreateView, but that is too early to
+ // access embedder_web_contents(). Therefore, we do it here.
+#if defined(USE_AURA)
+ // This can be win aura or chromeos.
+ parent_view->GetNativeView()->AddChild(platform_view_->GetNativeView());
+#elif defined(OS_WIN)
+ SetParent(platform_view_->GetNativeView(), parent_view->GetNativeView());
+#endif
+#endif // defined(USE_AURA) || defined(OS_WIN)
+}
+
+void WebContentsViewGuest::GetContainerBounds(gfx::Rect* out) const {
+ // We need embedder container's bounds to calculate our bounds.
+ guest_->embedder_web_contents()->GetView()->GetContainerBounds(out);
+ gfx::Point guest_coordinates = guest_->GetScreenCoordinates(gfx::Point());
+ out->Offset(guest_coordinates.x(), guest_coordinates.y());
+ out->set_size(size_);
+}
+
+void WebContentsViewGuest::SizeContents(const gfx::Size& size) {
+ size_ = size;
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+}
+
+void WebContentsViewGuest::SetInitialFocus() {
+ platform_view_->SetInitialFocus();
+}
+
+gfx::Rect WebContentsViewGuest::GetViewBounds() const {
+ return gfx::Rect(size_);
+}
+
+#if defined(OS_MACOSX)
+void WebContentsViewGuest::SetAllowOverlappingViews(bool overlapping) {
+ platform_view_->SetAllowOverlappingViews(overlapping);
+}
+
+bool WebContentsViewGuest::GetAllowOverlappingViews() const {
+ return platform_view_->GetAllowOverlappingViews();
+}
+#endif
+
+void WebContentsViewGuest::CreateView(const gfx::Size& initial_size,
+ gfx::NativeView context) {
+ platform_view_->CreateView(initial_size, context);
+ size_ = initial_size;
+}
+
+RenderWidgetHostView* WebContentsViewGuest::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+
+ RenderWidgetHostView* platform_widget = NULL;
+ platform_widget = platform_view_->CreateViewForWidget(render_widget_host);
+
+ RenderWidgetHostView* view = new RenderWidgetHostViewGuest(
+ render_widget_host,
+ guest_,
+ platform_widget);
+
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewGuest::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewGuest::SetPageTitle(const string16& title) {
+}
+
+void WebContentsViewGuest::RenderViewCreated(RenderViewHost* host) {
+ platform_view_->RenderViewCreated(host);
+}
+
+void WebContentsViewGuest::RenderViewSwappedIn(RenderViewHost* host) {
+ platform_view_->RenderViewSwappedIn(host);
+}
+
+void WebContentsViewGuest::SetOverscrollControllerEnabled(bool enabled) {
+ // This should never override the setting of the embedder view.
+}
+
+#if defined(OS_MACOSX)
+bool WebContentsViewGuest::IsEventTracking() const {
+ return false;
+}
+
+void WebContentsViewGuest::CloseTabAfterEventTracking() {
+}
+#endif
+
+WebContents* WebContentsViewGuest::web_contents() {
+ return web_contents_;
+}
+
+void WebContentsViewGuest::RestoreFocus() {
+ platform_view_->RestoreFocus();
+}
+
+void WebContentsViewGuest::OnTabCrashed(base::TerminationStatus status,
+ int error_code) {
+}
+
+void WebContentsViewGuest::Focus() {
+ platform_view_->Focus();
+}
+
+void WebContentsViewGuest::StoreFocus() {
+ platform_view_->StoreFocus();
+}
+
+DropData* WebContentsViewGuest::GetDropData() const {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void WebContentsViewGuest::UpdateDragCursor(WebDragOperation operation) {
+ RenderViewHostImpl* embedder_render_view_host =
+ static_cast<RenderViewHostImpl*>(
+ guest_->embedder_web_contents()->GetRenderViewHost());
+ CHECK(embedder_render_view_host);
+ RenderViewHostDelegateView* view =
+ embedder_render_view_host->GetDelegate()->GetDelegateView();
+ if (view)
+ view->UpdateDragCursor(operation);
+}
+
+void WebContentsViewGuest::GotFocus() {
+}
+
+void WebContentsViewGuest::TakeFocus(bool reverse) {
+}
+
+void WebContentsViewGuest::ShowContextMenu(const ContextMenuParams& params) {
+#if defined(USE_AURA) || defined(OS_WIN)
+ // Context menu uses ScreenPositionClient::ConvertPointToScreen() in aura and
+ // windows to calculate popup position. Guest's native view
+ // (platform_view_->GetNativeView()) is part of the embedder's view hierarchy,
+ // but is placed at (0, 0) w.r.t. the embedder's position. Therefore, |offset|
+ // is added to |params|.
+ gfx::Rect embedder_bounds;
+ guest_->embedder_web_contents()->GetView()->GetContainerBounds(
+ &embedder_bounds);
+ gfx::Rect guest_bounds;
+ GetContainerBounds(&guest_bounds);
+
+ gfx::Vector2d offset = guest_bounds.origin() - embedder_bounds.origin();
+ ContextMenuParams params_in_embedder = params;
+ params_in_embedder.x += offset.x();
+ params_in_embedder.y += offset.y();
+ platform_view_delegate_view_->ShowContextMenu(params_in_embedder);
+#else
+ platform_view_delegate_view_->ShowContextMenu(params);
+#endif // defined(USE_AURA) || defined(OS_WIN)
+}
+
+void WebContentsViewGuest::ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ // External popup menus are only used on Mac and Android.
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewGuest::StartDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ WebContentsImpl* embedder_web_contents = guest_->embedder_web_contents();
+ embedder_web_contents->GetBrowserPluginEmbedder()->StartDrag(guest_);
+ RenderViewHostImpl* embedder_render_view_host =
+ static_cast<RenderViewHostImpl*>(
+ embedder_web_contents->GetRenderViewHost());
+ CHECK(embedder_render_view_host);
+ RenderViewHostDelegateView* view =
+ embedder_render_view_host->GetDelegate()->GetDelegateView();
+ if (view)
+ view->StartDragging(drop_data, ops, image, image_offset, event_info);
+ else
+ embedder_web_contents->SystemDragEnded();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_guest.h b/chromium/content/browser/web_contents/web_contents_view_guest.h
new file mode 100644
index 00000000000..81bf8d94693
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_guest.h
@@ -0,0 +1,112 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GUEST_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GUEST_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/common/drag_event_source_info.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+
+namespace content {
+
+class WebContents;
+class WebContentsImpl;
+class BrowserPluginGuest;
+
+class CONTENT_EXPORT WebContentsViewGuest
+ : public WebContentsViewPort,
+ public RenderViewHostDelegateView {
+ public:
+ // The corresponding WebContentsImpl is passed in the constructor, and manages
+ // our lifetime. This doesn't need to be the case, but is this way currently
+ // because that's what was easiest when they were split.
+ // WebContentsViewGuest always has a backing platform dependent view,
+ // |platform_view|.
+ WebContentsViewGuest(WebContentsImpl* web_contents,
+ BrowserPluginGuest* guest,
+ scoped_ptr<WebContentsViewPort> platform_view,
+ RenderViewHostDelegateView* platform_view_delegate_view);
+ virtual ~WebContentsViewGuest();
+
+ WebContents* web_contents();
+
+ void OnGuestInitialized(WebContentsView* parent_view);
+
+ // WebContentsView implementation --------------------------------------------
+
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect* out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+#if defined(OS_MACOSX)
+ virtual void SetAllowOverlappingViews(bool overlapping) OVERRIDE;
+ virtual bool GetAllowOverlappingViews() const OVERRIDE;
+#endif
+
+ // WebContentsViewPort implementation ----------------------------------------
+ virtual void CreateView(const gfx::Size& initial_size,
+ gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+#if defined(OS_MACOSX)
+ virtual bool IsEventTracking() const OVERRIDE;
+ virtual void CloseTabAfterEventTracking() OVERRIDE;
+#endif
+
+ // Backend implementation of RenderViewHostDelegateView.
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ private:
+ // The WebContentsImpl whose contents we display.
+ WebContentsImpl* web_contents_;
+ BrowserPluginGuest* guest_;
+ // The platform dependent view backing this WebContentsView.
+ // Calls to this WebContentsViewGuest are forwarded to |platform_view_|.
+ scoped_ptr<WebContentsViewPort> platform_view_;
+ gfx::Size size_;
+
+ // Delegate view for guest's platform view.
+ RenderViewHostDelegateView* platform_view_delegate_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewGuest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_GUEST_H_
diff --git a/chromium/content/browser/web_contents/web_contents_view_mac.h b/chromium/content/browser/web_contents/web_contents_view_mac.h
new file mode 100644
index 00000000000..a8e7bb33123
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_mac.h
@@ -0,0 +1,145 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_MAC_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include <string>
+#include <vector>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/common/drag_event_source_info.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "ui/base/cocoa/base_view.h"
+#include "ui/gfx/size.h"
+
+@class FocusTracker;
+class SkBitmap;
+@class WebDragDest;
+@class WebDragSource;
+
+namespace content {
+class WebContentsImpl;
+class WebContentsViewDelegate;
+class WebContentsViewMac;
+}
+
+namespace gfx {
+class Vector2d;
+}
+
+CONTENT_EXPORT
+@interface WebContentsViewCocoa : BaseView {
+ @private
+ content::WebContentsViewMac* webContentsView_; // WEAK; owns us
+ base::scoped_nsobject<WebDragSource> dragSource_;
+ base::scoped_nsobject<WebDragDest> dragDest_;
+ BOOL mouseDownCanMoveWindow_;
+}
+
+- (void)setMouseDownCanMoveWindow:(BOOL)canMove;
+
+// Expose this, since sometimes one needs both the NSView and the
+// WebContentsImpl.
+- (content::WebContentsImpl*)webContents;
+@end
+
+namespace content {
+
+// Mac-specific implementation of the WebContentsView. It owns an NSView that
+// contains all of the contents of the tab and associated child views.
+class WebContentsViewMac : public WebContentsViewPort,
+ public RenderViewHostDelegateView {
+ public:
+ // The corresponding WebContentsImpl is passed in the constructor, and manages
+ // our lifetime. This doesn't need to be the case, but is this way currently
+ // because that's what was easiest when they were split.
+ WebContentsViewMac(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate);
+ virtual ~WebContentsViewMac();
+
+ // WebContentsView implementation --------------------------------------------
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect* out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void SetAllowOverlappingViews(bool overlapping) OVERRIDE;
+ virtual bool GetAllowOverlappingViews() const OVERRIDE;
+
+ // WebContentsViewPort implementation ----------------------------------------
+ virtual void CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+ virtual bool IsEventTracking() const OVERRIDE;
+ virtual void CloseTabAfterEventTracking() OVERRIDE;
+
+ // Backend implementation of RenderViewHostDelegateView.
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ // A helper method for closing the tab in the
+ // CloseTabAfterEventTracking() implementation.
+ void CloseTab();
+
+ WebContentsImpl* web_contents() { return web_contents_; }
+ WebContentsViewDelegate* delegate() { return delegate_.get(); }
+
+ private:
+ // The WebContentsImpl whose contents we display.
+ WebContentsImpl* web_contents_;
+
+ // The Cocoa NSView that lives in the view hierarchy.
+ base::scoped_nsobject<WebContentsViewCocoa> cocoa_view_;
+
+ // Keeps track of which NSView has focus so we can restore the focus when
+ // focus returns.
+ base::scoped_nsobject<FocusTracker> focus_tracker_;
+
+ // Our optional delegate.
+ scoped_ptr<WebContentsViewDelegate> delegate_;
+
+ // Whether to allow overlapping views.
+ bool allow_overlapping_views_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewMac);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_MAC_H_
diff --git a/chromium/content/browser/web_contents/web_contents_view_mac.mm b/chromium/content/browser/web_contents/web_contents_view_mac.mm
new file mode 100644
index 00000000000..6a9459d3e95
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_mac.mm
@@ -0,0 +1,564 @@
+// 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.
+
+#import <Carbon/Carbon.h>
+
+#import "content/browser/web_contents/web_contents_view_mac.h"
+
+#include <string>
+
+#import "base/mac/scoped_sending_event.h"
+#include "base/message_loop/message_loop.h"
+#import "base/message_loop/message_pump_mac.h"
+#include "content/browser/renderer_host/popup_menu_helper_mac.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#import "content/browser/web_contents/web_drag_dest_mac.h"
+#import "content/browser/web_contents/web_drag_source_mac.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_view_delegate.h"
+#include "skia/ext/skia_utils_mac.h"
+#import "third_party/mozilla/NSPasteboard+Utils.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#import "ui/base/cocoa/focus_tracker.h"
+#include "ui/base/dragdrop/cocoa_dnd_util.h"
+#include "ui/gfx/image/image_skia_util_mac.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+using content::DropData;
+using content::PopupMenuHelper;
+using content::RenderViewHostFactory;
+using content::RenderWidgetHostView;
+using content::RenderWidgetHostViewMac;
+using content::WebContents;
+using content::WebContentsImpl;
+using content::WebContentsViewMac;
+
+// Ensure that the WebKit::WebDragOperation enum values stay in sync with
+// NSDragOperation constants, since the code below static_casts between 'em.
+#define COMPILE_ASSERT_MATCHING_ENUM(name) \
+ COMPILE_ASSERT(int(NS##name) == int(WebKit::Web##name), enum_mismatch_##name)
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete);
+COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery);
+
+@interface WebContentsViewCocoa (Private)
+- (id)initWithWebContentsViewMac:(WebContentsViewMac*)w;
+- (void)registerDragTypes;
+- (void)setCurrentDragOperation:(NSDragOperation)operation;
+- (DropData*)dropData;
+- (void)startDragWithDropData:(const DropData&)dropData
+ dragOperationMask:(NSDragOperation)operationMask
+ image:(NSImage*)image
+ offset:(NSPoint)offset;
+- (void)cancelDeferredClose;
+- (void)clearWebContentsView;
+- (void)closeTabAfterEvent;
+- (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
+@end
+
+namespace content {
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view) {
+ WebContentsViewMac* rv = new WebContentsViewMac(web_contents, delegate);
+ *render_view_host_delegate_view = rv;
+ return rv;
+}
+
+WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate)
+ : web_contents_(web_contents),
+ delegate_(delegate),
+ allow_overlapping_views_(false) {
+}
+
+WebContentsViewMac::~WebContentsViewMac() {
+ // This handles the case where a renderer close call was deferred
+ // while the user was operating a UI control which resulted in a
+ // close. In that case, the Cocoa view outlives the
+ // WebContentsViewMac instance due to Cocoa retain count.
+ [cocoa_view_ cancelDeferredClose];
+ [cocoa_view_ clearWebContentsView];
+}
+
+gfx::NativeView WebContentsViewMac::GetNativeView() const {
+ return cocoa_view_.get();
+}
+
+gfx::NativeView WebContentsViewMac::GetContentNativeView() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (!rwhv)
+ return NULL;
+ return rwhv->GetNativeView();
+}
+
+gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
+ return [cocoa_view_.get() window];
+}
+
+void WebContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
+ // Convert bounds to window coordinate space.
+ NSRect bounds =
+ [cocoa_view_.get() convertRect:[cocoa_view_.get() bounds] toView:nil];
+
+ // Convert bounds to screen coordinate space.
+ NSWindow* window = [cocoa_view_.get() window];
+ bounds.origin = [window convertBaseToScreen:bounds.origin];
+
+ // Flip y to account for screen flip.
+ NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
+ bounds.origin.y = [screen frame].size.height - bounds.origin.y
+ - bounds.size.height;
+ *out = gfx::Rect(NSRectToCGRect(bounds));
+}
+
+void WebContentsViewMac::StartDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask allowed_operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ // By allowing nested tasks, the code below also allows Close(),
+ // which would deallocate |this|. The same problem can occur while
+ // processing -sendEvent:, so Close() is deferred in that case.
+ // Drags from web content do not come via -sendEvent:, this sets the
+ // same flag -sendEvent: would.
+ base::mac::ScopedSendingEvent sending_event_scoper;
+
+ // The drag invokes a nested event loop, arrange to continue
+ // processing events.
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);
+ NSPoint offset = NSPointFromCGPoint(
+ gfx::PointAtOffsetFromOrigin(image_offset).ToCGPoint());
+ [cocoa_view_ startDragWithDropData:drop_data
+ dragOperationMask:mask
+ image:gfx::NSImageFromImageSkia(image)
+ offset:offset];
+}
+
+void WebContentsViewMac::OnTabCrashed(base::TerminationStatus /* status */,
+ int /* error_code */) {
+}
+
+void WebContentsViewMac::SizeContents(const gfx::Size& size) {
+ // TODO(brettw | japhet) This is a hack and should be removed.
+ // See web_contents_view.h.
+ gfx::Rect rect(gfx::Point(), size);
+ WebContentsViewCocoa* view = cocoa_view_.get();
+
+ NSPoint origin = [view frame].origin;
+ NSRect frame = [view flipRectToNSRect:rect];
+ frame.origin = NSMakePoint(NSMinX(frame) + origin.x,
+ NSMinY(frame) + origin.y);
+ [view setFrame:frame];
+}
+
+void WebContentsViewMac::Focus() {
+ NSWindow* window = [cocoa_view_.get() window];
+ [window makeFirstResponder:GetContentNativeView()];
+ if (![window isVisible])
+ return;
+ [window makeKeyAndOrderFront:nil];
+}
+
+void WebContentsViewMac::SetInitialFocus() {
+ if (web_contents_->FocusLocationBarByDefault())
+ web_contents_->SetFocusToLocationBar(false);
+ else
+ [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
+}
+
+void WebContentsViewMac::StoreFocus() {
+ // We're explicitly being asked to store focus, so don't worry if there's
+ // already a view saved.
+ focus_tracker_.reset(
+ [[FocusTracker alloc] initWithWindow:[cocoa_view_ window]]);
+}
+
+void WebContentsViewMac::RestoreFocus() {
+ // TODO(avi): Could we be restoring a view that's no longer in the key view
+ // chain?
+ if (!(focus_tracker_.get() &&
+ [focus_tracker_ restoreFocusInWindow:[cocoa_view_ window]])) {
+ // Fall back to the default focus behavior if we could not restore focus.
+ // TODO(shess): If location-bar gets focus by default, this will
+ // select-all in the field. If there was a specific selection in
+ // the field when we navigated away from it, we should restore
+ // that selection.
+ SetInitialFocus();
+ }
+
+ focus_tracker_.reset(nil);
+}
+
+DropData* WebContentsViewMac::GetDropData() const {
+ return [cocoa_view_ dropData];
+}
+
+void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
+ [cocoa_view_ setCurrentDragOperation: operation];
+}
+
+void WebContentsViewMac::GotFocus() {
+ // This is only used in the views FocusManager stuff but it bleeds through
+ // all subclasses. http://crbug.com/21875
+}
+
+// This is called when the renderer asks us to take focus back (i.e., it has
+// iterated past the last focusable element on the page).
+void WebContentsViewMac::TakeFocus(bool reverse) {
+ if (reverse) {
+ [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
+ } else {
+ [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
+ }
+}
+
+void WebContentsViewMac::ShowContextMenu(const ContextMenuParams& params) {
+ // Allow delegates to handle the context menu operation first.
+ if (web_contents_->GetDelegate() &&
+ web_contents_->GetDelegate()->HandleContextMenu(params)) {
+ return;
+ }
+
+ if (delegate())
+ delegate()->ShowContextMenu(params);
+ else
+ DLOG(ERROR) << "Cannot show context menus without a delegate.";
+}
+
+// Display a popup menu for WebKit using Cocoa widgets.
+void WebContentsViewMac::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ PopupMenuHelper popup_menu_helper(web_contents_->GetRenderViewHost());
+ popup_menu_helper.ShowPopupMenu(bounds, item_height, item_font_size,
+ selected_item, items, right_aligned,
+ allow_multiple_selection);
+}
+
+gfx::Rect WebContentsViewMac::GetViewBounds() const {
+ // This method is not currently used on mac.
+ NOTIMPLEMENTED();
+ return gfx::Rect();
+}
+
+void WebContentsViewMac::SetAllowOverlappingViews(bool overlapping) {
+ if (allow_overlapping_views_ == overlapping)
+ return;
+
+ allow_overlapping_views_ = overlapping;
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ web_contents_->GetRenderWidgetHostView());
+ if (view)
+ view->SetAllowOverlappingViews(allow_overlapping_views_);
+}
+
+bool WebContentsViewMac::GetAllowOverlappingViews() const {
+ return allow_overlapping_views_;
+}
+
+void WebContentsViewMac::CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) {
+ WebContentsViewCocoa* view =
+ [[WebContentsViewCocoa alloc] initWithWebContentsViewMac:this];
+ cocoa_view_.reset(view);
+}
+
+RenderWidgetHostView* WebContentsViewMac::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+
+ RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
+ RenderWidgetHostView::CreateViewForWidget(render_widget_host));
+ if (delegate()) {
+ NSObject<RenderWidgetHostViewMacDelegate>* rw_delegate =
+ delegate()->CreateRenderWidgetHostViewDelegate(render_widget_host);
+ view->SetDelegate(rw_delegate);
+ }
+ view->SetAllowOverlappingViews(allow_overlapping_views_);
+
+ // Fancy layout comes later; for now just make it our size and resize it
+ // with us. In case there are other siblings of the content area, we want
+ // to make sure the content area is on the bottom so other things draw over
+ // it.
+ NSView* view_view = view->GetNativeView();
+ [view_view setFrame:[cocoa_view_.get() bounds]];
+ [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ // Add the new view below all other views; this also keeps it below any
+ // overlay view installed.
+ [cocoa_view_.get() addSubview:view_view
+ positioned:NSWindowBelow
+ relativeTo:nil];
+ // For some reason known only to Cocoa, the autorecalculation of the key view
+ // loop set on the window doesn't set the next key view when the subview is
+ // added. On 10.6 things magically work fine; on 10.5 they fail
+ // <http://crbug.com/61493>. Digging into Cocoa key view loop code yielded
+ // madness; TODO(avi,rohit): look at this again and figure out what's really
+ // going on.
+ [cocoa_view_.get() setNextKeyView:view_view];
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewMac::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewMac::SetPageTitle(const string16& title) {
+ // Meaningless on the Mac; widgets don't have a "title" attribute
+}
+
+
+void WebContentsViewMac::RenderViewCreated(RenderViewHost* host) {
+ // We want updates whenever the intrinsic width of the webpage changes.
+ // Put the RenderView into that mode. The preferred width is used for example
+ // when the "zoom" button in the browser window is clicked.
+ host->EnablePreferredSizeMode();
+}
+
+void WebContentsViewMac::RenderViewSwappedIn(RenderViewHost* host) {
+}
+
+void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) {
+}
+
+bool WebContentsViewMac::IsEventTracking() const {
+ return base::MessagePumpMac::IsHandlingSendEvent();
+}
+
+// Arrange to call CloseTab() after we're back to the main event loop.
+// The obvious way to do this would be PostNonNestableTask(), but that
+// will fire when the event-tracking loop polls for events. So we
+// need to bounce the message via Cocoa, instead.
+void WebContentsViewMac::CloseTabAfterEventTracking() {
+ [cocoa_view_ cancelDeferredClose];
+ [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
+ withObject:nil
+ afterDelay:0.0];
+}
+
+void WebContentsViewMac::CloseTab() {
+ web_contents_->Close(web_contents_->GetRenderViewHost());
+}
+
+} // namespace content
+
+@implementation WebContentsViewCocoa
+
+- (id)initWithWebContentsViewMac:(WebContentsViewMac*)w {
+ self = [super initWithFrame:NSZeroRect];
+ if (self != nil) {
+ webContentsView_ = w;
+ dragDest_.reset(
+ [[WebDragDest alloc] initWithWebContentsImpl:[self webContents]]);
+ [self registerDragTypes];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(viewDidBecomeFirstResponder:)
+ name:kViewDidBecomeFirstResponder
+ object:nil];
+
+ if (webContentsView_->delegate()) {
+ [dragDest_ setDragDelegate:webContentsView_->delegate()->
+ GetDragDestDelegate()];
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ // Cancel any deferred tab closes, just in case.
+ [self cancelDeferredClose];
+
+ // This probably isn't strictly necessary, but can't hurt.
+ [self unregisterDraggedTypes];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+// Registers for the view for the appropriate drag types.
+- (void)registerDragTypes {
+ NSArray* types = [NSArray arrayWithObjects:
+ ui::kChromeDragDummyPboardType,
+ kWebURLsWithTitlesPboardType,
+ NSURLPboardType,
+ NSStringPboardType,
+ NSHTMLPboardType,
+ NSRTFPboardType,
+ NSFilenamesPboardType,
+ ui::kWebCustomDataPboardType,
+ nil];
+ [self registerForDraggedTypes:types];
+}
+
+- (void)setCurrentDragOperation:(NSDragOperation)operation {
+ [dragDest_ setCurrentOperation:operation];
+}
+
+- (DropData*)dropData {
+ return [dragDest_ currentDropData];
+}
+
+- (WebContentsImpl*)webContents {
+ if (webContentsView_ == NULL)
+ return NULL;
+ return webContentsView_->web_contents();
+}
+
+- (void)mouseEvent:(NSEvent*)theEvent {
+ WebContentsImpl* webContents = [self webContents];
+ if (webContents && webContents->GetDelegate()) {
+ NSPoint location = [NSEvent mouseLocation];
+ if ([theEvent type] == NSMouseMoved)
+ webContents->GetDelegate()->ContentsMouseEvent(
+ webContents, gfx::Point(location.x, location.y), true);
+ if ([theEvent type] == NSMouseExited)
+ webContents->GetDelegate()->ContentsMouseEvent(
+ webContents, gfx::Point(location.x, location.y), false);
+ }
+}
+
+- (void)setMouseDownCanMoveWindow:(BOOL)canMove {
+ mouseDownCanMoveWindow_ = canMove;
+}
+
+- (BOOL)mouseDownCanMoveWindow {
+ // This is needed to prevent mouseDowns from moving the window
+ // around. The default implementation returns YES only for opaque
+ // views. WebContentsViewCocoa does not draw itself in any way, but
+ // its subviews do paint their entire frames. Returning NO here
+ // saves us the effort of overriding this method in every possible
+ // subview.
+ return mouseDownCanMoveWindow_;
+}
+
+- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
+ [dragSource_ lazyWriteToPasteboard:sender
+ forType:type];
+}
+
+- (void)startDragWithDropData:(const DropData&)dropData
+ dragOperationMask:(NSDragOperation)operationMask
+ image:(NSImage*)image
+ offset:(NSPoint)offset {
+ dragSource_.reset([[WebDragSource alloc]
+ initWithContents:[self webContents]
+ view:self
+ dropData:&dropData
+ image:image
+ offset:offset
+ pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
+ dragOperationMask:operationMask]);
+ [dragSource_ startDrag];
+}
+
+// NSDraggingSource methods
+
+// Returns what kind of drag operations are available. This is a required
+// method for NSDraggingSource.
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
+ if (dragSource_)
+ return [dragSource_ draggingSourceOperationMaskForLocal:isLocal];
+ // No web drag source - this is the case for dragging a file from the
+ // downloads manager. Default to copy operation. Note: It is desirable to
+ // allow the user to either move or copy, but this requires additional
+ // plumbing to update the download item's path once its moved.
+ return NSDragOperationCopy;
+}
+
+// Called when a drag initiated in our view ends.
+- (void)draggedImage:(NSImage*)anImage
+ endedAt:(NSPoint)screenPoint
+ operation:(NSDragOperation)operation {
+ [dragSource_ endDragAt:screenPoint operation:operation];
+
+ // Might as well throw out this object now.
+ dragSource_.reset();
+}
+
+// Called when a drag initiated in our view moves.
+- (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
+ [dragSource_ moveDragTo:screenPoint];
+}
+
+// NSDraggingDestination methods
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+ return [dragDest_ draggingEntered:sender view:self];
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)sender {
+ [dragDest_ draggingExited:sender];
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+ return [dragDest_ draggingUpdated:sender view:self];
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+ return [dragDest_ performDragOperation:sender view:self];
+}
+
+- (void)cancelDeferredClose {
+ SEL aSel = @selector(closeTabAfterEvent);
+ [NSObject cancelPreviousPerformRequestsWithTarget:self
+ selector:aSel
+ object:nil];
+}
+
+- (void)clearWebContentsView {
+ webContentsView_ = NULL;
+ [dragSource_ clearWebContentsView];
+}
+
+- (void)closeTabAfterEvent {
+ webContentsView_->CloseTab();
+}
+
+- (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
+ NSView* view = [notification object];
+ if (![[self subviews] containsObject:view])
+ return;
+
+ NSSelectionDirection direction =
+ [[[notification userInfo] objectForKey:kSelectionDirection]
+ unsignedIntegerValue];
+ if (direction == NSDirectSelection)
+ return;
+
+ [self webContents]->
+ FocusThroughTabTraversal(direction == NSSelectingPrevious);
+}
+
+@end
diff --git a/chromium/content/browser/web_contents/web_contents_view_mac_unittest.mm b/chromium/content/browser/web_contents/web_contents_view_mac_unittest.mm
new file mode 100644
index 00000000000..71f7e48dcc2
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_mac_unittest.mm
@@ -0,0 +1,31 @@
+// 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.
+
+#import "content/browser/web_contents/web_contents_view_mac.h"
+
+#include "base/mac/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+namespace {
+
+class WebContentsViewCocoaTest : public ui::CocoaTest {
+};
+
+TEST_F(WebContentsViewCocoaTest, NonWebDragSourceTest) {
+ base::scoped_nsobject<WebContentsViewCocoa> view(
+ [[WebContentsViewCocoa alloc] init]);
+
+ // Tests that |draggingSourceOperationMaskForLocal:| returns the expected mask
+ // when dragging without a WebDragSource - i.e. when |startDragWithDropData:|
+ // hasn't yet been called. Dragging a file from the downloads manager, for
+ // example, requires this to work.
+ EXPECT_EQ(NSDragOperationCopy,
+ [view draggingSourceOperationMaskForLocal:YES]);
+ EXPECT_EQ(NSDragOperationCopy,
+ [view draggingSourceOperationMaskForLocal:NO]);
+}
+
+} // namespace
diff --git a/chromium/content/browser/web_contents/web_contents_view_win.cc b/chromium/content/browser/web_contents/web_contents_view_win.cc
new file mode 100644
index 00000000000..6a75e71cab5
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_win.cc
@@ -0,0 +1,464 @@
+// 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 "content/browser/web_contents/web_contents_view_win.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_vector.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_win.h"
+#include "content/browser/web_contents/interstitial_page_impl.h"
+#include "content/browser/web_contents/web_contents_drag_win.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_drag_dest_win.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_view_delegate.h"
+#include "ui/base/win/hidden_window.h"
+#include "ui/base/win/hwnd_subclass.h"
+#include "ui/gfx/screen.h"
+
+namespace content {
+WebContentsViewPort* CreateWebContentsView(
+ WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate,
+ RenderViewHostDelegateView** render_view_host_delegate_view) {
+ WebContentsViewWin* rv = new WebContentsViewWin(web_contents, delegate);
+ *render_view_host_delegate_view = rv;
+ return rv;
+}
+
+namespace {
+
+typedef std::map<HWND, WebContentsViewWin*> HwndToWcvMap;
+HwndToWcvMap hwnd_to_wcv_map;
+
+void RemoveHwndToWcvMapEntry(WebContentsViewWin* wcv) {
+ HwndToWcvMap::iterator it;
+ for (it = hwnd_to_wcv_map.begin(); it != hwnd_to_wcv_map.end();) {
+ if (it->second == wcv)
+ hwnd_to_wcv_map.erase(it++);
+ else
+ ++it;
+ }
+}
+
+BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam) {
+ HwndToWcvMap::iterator it = hwnd_to_wcv_map.find(hwnd);
+ if (it == hwnd_to_wcv_map.end())
+ return TRUE; // must return TRUE to continue enumeration.
+ WebContentsViewWin* wcv = it->second;
+ RenderWidgetHostViewWin* rwhv = static_cast<RenderWidgetHostViewWin*>(
+ wcv->web_contents()->GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->UpdateScreenInfo(rwhv->GetNativeView());
+
+ return TRUE; // must return TRUE to continue enumeration.
+}
+
+class PositionChangedMessageFilter : public ui::HWNDMessageFilter {
+ public:
+ PositionChangedMessageFilter() {}
+
+ private:
+ // Overridden from ui::HWNDMessageFilter:
+ virtual bool FilterMessage(HWND hwnd,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* l_result) OVERRIDE {
+ if (message == WM_WINDOWPOSCHANGED || message == WM_SETTINGCHANGE)
+ EnumChildWindows(hwnd, EnumChildProc, 0);
+
+ return false;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(PositionChangedMessageFilter);
+};
+
+void AddFilterToParentHwndSubclass(HWND hwnd, ui::HWNDMessageFilter* filter) {
+ HWND parent = ::GetAncestor(hwnd, GA_ROOT);
+ if (parent) {
+ ui::HWNDSubclass::RemoveFilterFromAllTargets(filter);
+ ui::HWNDSubclass::AddFilterToTarget(parent, filter);
+ }
+}
+
+} // namespace namespace
+
+WebContentsViewWin::WebContentsViewWin(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate)
+ : web_contents_(web_contents),
+ delegate_(delegate),
+ hwnd_message_filter_(new PositionChangedMessageFilter) {
+}
+
+WebContentsViewWin::~WebContentsViewWin() {
+ RemoveHwndToWcvMapEntry(this);
+
+ if (IsWindow(hwnd()))
+ DestroyWindow(hwnd());
+}
+
+gfx::NativeView WebContentsViewWin::GetNativeView() const {
+ return hwnd();
+}
+
+gfx::NativeView WebContentsViewWin::GetContentNativeView() const {
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ return rwhv ? rwhv->GetNativeView() : NULL;
+}
+
+gfx::NativeWindow WebContentsViewWin::GetTopLevelNativeWindow() const {
+ return ::GetAncestor(GetNativeView(), GA_ROOT);
+}
+
+void WebContentsViewWin::GetContainerBounds(gfx::Rect *out) const {
+ // Copied from NativeWidgetWin::GetClientAreaScreenBounds().
+ RECT r;
+ GetClientRect(hwnd(), &r);
+ POINT point = { r.left, r.top };
+ ClientToScreen(hwnd(), &point);
+ *out = gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top);
+}
+
+void WebContentsViewWin::OnTabCrashed(base::TerminationStatus status,
+ int error_code) {
+}
+
+void WebContentsViewWin::SizeContents(const gfx::Size& size) {
+ gfx::Rect bounds;
+ GetContainerBounds(&bounds);
+ if (bounds.size() != size) {
+ SetWindowPos(hwnd(), NULL, 0, 0, size.width(), size.height(),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
+ } else {
+ // Our size matches what we want but the renderers size may not match.
+ // Pretend we were resized so that the renderers size is updated too.
+ if (web_contents_->GetInterstitialPage())
+ web_contents_->GetInterstitialPage()->SetSize(size);
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->SetSize(size);
+ }
+}
+
+void WebContentsViewWin::CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) {
+ initial_size_ = initial_size;
+
+ set_window_style(WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
+
+ Init(ui::GetHiddenWindow(), gfx::Rect(initial_size_));
+
+ // Remove the root view drop target so we can register our own.
+ RevokeDragDrop(GetNativeView());
+ drag_dest_ = new WebDragDest(hwnd(), web_contents_);
+ if (delegate_) {
+ WebDragDestDelegate* delegate = delegate_->GetDragDestDelegate();
+ if (delegate)
+ drag_dest_->set_delegate(delegate);
+ }
+}
+
+void WebContentsViewWin::Focus() {
+ if (web_contents_->GetInterstitialPage()) {
+ web_contents_->GetInterstitialPage()->Focus();
+ return;
+ }
+
+ if (delegate_.get() && delegate_->Focus())
+ return;
+
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv)
+ rwhv->Focus();
+}
+
+void WebContentsViewWin::SetInitialFocus() {
+ if (web_contents_->FocusLocationBarByDefault())
+ web_contents_->SetFocusToLocationBar(false);
+ else
+ Focus();
+}
+
+void WebContentsViewWin::StoreFocus() {
+ if (delegate_)
+ delegate_->StoreFocus();
+}
+
+void WebContentsViewWin::RestoreFocus() {
+ if (delegate_)
+ delegate_->RestoreFocus();
+}
+
+DropData* WebContentsViewWin::GetDropData() const {
+ return drag_dest_->current_drop_data();
+}
+
+gfx::Rect WebContentsViewWin::GetViewBounds() const {
+ RECT r;
+ GetWindowRect(hwnd(), &r);
+ return gfx::Rect(r);
+}
+
+RenderWidgetHostView* WebContentsViewWin::CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) {
+ if (render_widget_host->GetView()) {
+ // During testing, the view will already be set up in most cases to the
+ // test view, so we don't want to clobber it with a real one. To verify that
+ // this actually is happening (and somebody isn't accidentally creating the
+ // view twice), we check for the RVH Factory, which will be set when we're
+ // making special ones (which go along with the special views).
+ DCHECK(RenderViewHostFactory::has_factory());
+ return render_widget_host->GetView();
+ }
+
+ RenderWidgetHostViewWin* view = static_cast<RenderWidgetHostViewWin*>(
+ RenderWidgetHostView::CreateViewForWidget(render_widget_host));
+ view->CreateWnd(GetNativeView());
+ view->ShowWindow(SW_SHOW);
+ view->SetSize(initial_size_);
+ return view;
+}
+
+RenderWidgetHostView* WebContentsViewWin::CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) {
+ return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
+}
+
+void WebContentsViewWin::SetPageTitle(const string16& title) {
+ // It's possible to get this after the hwnd has been destroyed.
+ if (GetNativeView())
+ ::SetWindowText(GetNativeView(), title.c_str());
+}
+
+void WebContentsViewWin::RenderViewCreated(RenderViewHost* host) {
+}
+
+void WebContentsViewWin::RenderViewSwappedIn(RenderViewHost* host) {
+}
+
+void WebContentsViewWin::SetOverscrollControllerEnabled(bool enabled) {
+}
+
+void WebContentsViewWin::ShowContextMenu(const ContextMenuParams& params) {
+ if (delegate_)
+ delegate_->ShowContextMenu(params);
+}
+
+void WebContentsViewWin::ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ // External popup menus are only used on Mac and Android.
+ NOTIMPLEMENTED();
+}
+
+void WebContentsViewWin::StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ drag_handler_ = new WebContentsDragWin(
+ GetNativeView(),
+ web_contents_,
+ drag_dest_,
+ base::Bind(&WebContentsViewWin::EndDragging, base::Unretained(this)));
+ drag_handler_->StartDragging(drop_data, operations, image, image_offset);
+}
+
+void WebContentsViewWin::UpdateDragCursor(WebKit::WebDragOperation operation) {
+ drag_dest_->set_drag_cursor(operation);
+}
+
+void WebContentsViewWin::GotFocus() {
+ if (web_contents_->GetDelegate())
+ web_contents_->GetDelegate()->WebContentsFocused(web_contents_);
+}
+
+void WebContentsViewWin::TakeFocus(bool reverse) {
+ if (web_contents_->GetDelegate() &&
+ !web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
+ delegate_.get()) {
+ delegate_->TakeFocus(reverse);
+ }
+}
+
+void WebContentsViewWin::EndDragging() {
+ drag_handler_ = NULL;
+ web_contents_->SystemDragEnded();
+}
+
+void WebContentsViewWin::CloseTab() {
+ RenderViewHost* rvh = web_contents_->GetRenderViewHost();
+ rvh->GetDelegate()->Close(rvh);
+}
+
+LRESULT WebContentsViewWin::OnCreate(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ hwnd_to_wcv_map.insert(std::make_pair(hwnd(), this));
+ AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnDestroy(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ if (drag_dest_) {
+ RevokeDragDrop(GetNativeView());
+ drag_dest_ = NULL;
+ }
+ if (drag_handler_) {
+ drag_handler_->CancelDrag();
+ drag_handler_ = NULL;
+ }
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnWindowPosChanged(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+
+ // Our parent might have changed. So we re-install our hwnd message filter.
+ AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
+
+ WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(lparam);
+ if (window_pos->flags & SWP_HIDEWINDOW) {
+ web_contents_->WasHidden();
+ return 0;
+ }
+
+ // The WebContents was shown by a means other than the user selecting a
+ // Tab, e.g. the window was minimized then restored.
+ if (window_pos->flags & SWP_SHOWWINDOW)
+ web_contents_->WasShown();
+
+ RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
+ if (rwhv) {
+ RenderWidgetHostViewWin* view = static_cast<RenderWidgetHostViewWin*>(rwhv);
+ view->UpdateScreenInfo(view->GetNativeView());
+ }
+
+ // Unless we were specifically told not to size, cause the renderer to be
+ // sized to the new bounds, which forces a repaint. Not required for the
+ // simple minimize-restore case described above, for example, since the
+ // size hasn't changed.
+ if (window_pos->flags & SWP_NOSIZE)
+ return 0;
+
+ gfx::Size size(window_pos->cx, window_pos->cy);
+ if (web_contents_->GetInterstitialPage())
+ web_contents_->GetInterstitialPage()->SetSize(size);
+ if (rwhv)
+ rwhv->SetSize(size);
+
+ if (delegate_)
+ delegate_->SizeChanged(size);
+
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnMouseDown(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ // Make sure this WebContents is activated when it is clicked on.
+ if (web_contents_->GetDelegate())
+ web_contents_->GetDelegate()->ActivateContents(web_contents_);
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnMouseMove(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ // Let our delegate know that the mouse moved (useful for resetting status
+ // bubble state).
+ if (web_contents_->GetDelegate()) {
+ web_contents_->GetDelegate()->ContentsMouseEvent(
+ web_contents_,
+ gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
+ true);
+ }
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnNCCalcSize(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ // Hack for ThinkPad mouse wheel driver. We have set the fake scroll bars
+ // to receive scroll messages from ThinkPad touch-pad driver. Suppress
+ // painting of scrollbars by returning 0 size for them.
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnNCHitTest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ return HTTRANSPARENT;
+}
+
+LRESULT WebContentsViewWin::OnScroll(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ int scroll_type = LOWORD(wparam);
+ short position = HIWORD(wparam);
+ HWND scrollbar = reinterpret_cast<HWND>(lparam);
+ // This window can receive scroll events as a result of the ThinkPad's
+ // touch-pad scroll wheel emulation.
+ // If ctrl is held, zoom the UI. There are three issues with this:
+ // 1) Should the event be eaten or forwarded to content? We eat the event,
+ // which is like Firefox and unlike IE.
+ // 2) Should wheel up zoom in or out? We zoom in (increase font size), which
+ // is like IE and Google maps, but unlike Firefox.
+ // 3) Should the mouse have to be over the content area? We zoom as long as
+ // content has focus, although FF and IE require that the mouse is over
+ // content. This is because all events get forwarded when content has
+ // focus.
+ if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
+ int distance = 0;
+ switch (scroll_type) {
+ case SB_LINEUP:
+ distance = WHEEL_DELTA;
+ break;
+ case SB_LINEDOWN:
+ distance = -WHEEL_DELTA;
+ break;
+ // TODO(joshia): Handle SB_PAGEUP, SB_PAGEDOWN, SB_THUMBPOSITION,
+ // and SB_THUMBTRACK for completeness
+ default:
+ break;
+ }
+
+ web_contents_->GetDelegate()->ContentsZoomChange(distance > 0);
+ return 0;
+ }
+
+ // Reflect scroll message to the view() to give it a chance
+ // to process scrolling.
+ SendMessage(GetContentNativeView(), message, wparam, lparam);
+ return 0;
+}
+
+LRESULT WebContentsViewWin::OnSize(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
+ // NOTE: Because we handle OnWindowPosChanged without calling DefWindowProc,
+ // OnSize is NOT called on window resize. This handler is called only once
+ // when the window is created.
+ // Don't call base class OnSize to avoid useless layout for 0x0 size.
+ // We will get OnWindowPosChanged later and layout root view in WasSized.
+
+ // Hack for ThinkPad touch-pad driver.
+ // Set fake scrollbars so that we can get scroll messages,
+ SCROLLINFO si = {0};
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL;
+
+ si.nMin = 1;
+ si.nMax = 100;
+ si.nPage = 10;
+ si.nPos = 50;
+
+ ::SetScrollInfo(hwnd(), SB_HORZ, &si, FALSE);
+ ::SetScrollInfo(hwnd(), SB_VERT, &si, FALSE);
+
+ return 1;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_contents_view_win.h b/chromium/content/browser/web_contents/web_contents_view_win.h
new file mode 100644
index 00000000000..3ca6b48765a
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_contents_view_win.h
@@ -0,0 +1,147 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_WIN_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_WIN_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "base/win/win_util.h"
+#include "content/common/content_export.h"
+#include "content/common/drag_event_source_info.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "ui/base/win/window_impl.h"
+
+namespace ui {
+class HWNDMessageFilter;
+}
+
+namespace content {
+class WebContentsDragWin;
+class WebContentsImpl;
+class WebContentsViewDelegate;
+class WebDragDest;
+
+// An implementation of WebContentsView for Windows.
+class CONTENT_EXPORT WebContentsViewWin
+ : public WebContentsViewPort,
+ public RenderViewHostDelegateView,
+ public ui::WindowImpl {
+ public:
+ WebContentsViewWin(WebContentsImpl* web_contents,
+ WebContentsViewDelegate* delegate);
+ virtual ~WebContentsViewWin();
+
+ BEGIN_MSG_MAP_EX(WebContentsViewWin)
+ MESSAGE_HANDLER(WM_CREATE, OnCreate)
+ MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+ MESSAGE_HANDLER(WM_WINDOWPOSCHANGED, OnWindowPosChanged)
+ MESSAGE_HANDLER(WM_LBUTTONDOWN, OnMouseDown)
+ MESSAGE_HANDLER(WM_MBUTTONDOWN, OnMouseDown)
+ MESSAGE_HANDLER(WM_RBUTTONDOWN, OnMouseDown)
+ MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
+ // Hacks for old ThinkPad touchpads/scroll points.
+ MESSAGE_HANDLER(WM_NCCALCSIZE, OnNCCalcSize)
+ MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest)
+ MESSAGE_HANDLER(WM_HSCROLL, OnScroll)
+ MESSAGE_HANDLER(WM_VSCROLL, OnScroll)
+ MESSAGE_HANDLER(WM_SIZE, OnSize)
+ END_MSG_MAP()
+
+ // Overridden from WebContentsView:
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeView GetContentNativeView() const OVERRIDE;
+ virtual gfx::NativeWindow GetTopLevelNativeWindow() const OVERRIDE;
+ virtual void GetContainerBounds(gfx::Rect *out) const OVERRIDE;
+ virtual void OnTabCrashed(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void SizeContents(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void SetInitialFocus() OVERRIDE;
+ virtual void StoreFocus() OVERRIDE;
+ virtual void RestoreFocus() OVERRIDE;
+ virtual DropData* GetDropData() const OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+
+ // Overridden from WebContentsViewPort:
+ virtual void CreateView(
+ const gfx::Size& initial_size, gfx::NativeView context) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual RenderWidgetHostView* CreateViewForPopupWidget(
+ RenderWidgetHost* render_widget_host) OVERRIDE;
+ virtual void SetPageTitle(const string16& title) OVERRIDE;
+ virtual void RenderViewCreated(RenderViewHost* host) OVERRIDE;
+ virtual void RenderViewSwappedIn(RenderViewHost* host) OVERRIDE;
+ virtual void SetOverscrollControllerEnabled(bool enabled) OVERRIDE;
+
+ // Implementation of RenderViewHostDelegateView.
+ virtual void ShowContextMenu(const ContextMenuParams& params) OVERRIDE;
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+
+ WebContentsImpl* web_contents() const { return web_contents_; }
+
+ private:
+ void EndDragging();
+ void CloseTab();
+
+ LRESULT OnCreate(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnDestroy(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnWindowPosChanged(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnMouseDown(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnMouseMove(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnReflectedMessage(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnNCCalcSize(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnNCHitTest(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnScroll(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+ LRESULT OnSize(
+ UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled);
+
+ gfx::Size initial_size_;
+
+ // The WebContentsImpl whose contents we display.
+ WebContentsImpl* web_contents_;
+
+ scoped_ptr<WebContentsViewDelegate> delegate_;
+
+ // The helper object that handles drag destination related interactions with
+ // Windows.
+ scoped_refptr<WebDragDest> drag_dest_;
+
+ // Used to handle the drag-and-drop.
+ scoped_refptr<WebContentsDragWin> drag_handler_;
+
+ scoped_ptr<ui::HWNDMessageFilter> hwnd_message_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsViewWin);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_CONTENTS_VIEW_WIN_H_
diff --git a/chromium/content/browser/web_contents/web_drag_dest_gtk.cc b/chromium/content/browser/web_contents/web_drag_dest_gtk.cc
new file mode 100644
index 00000000000..55f0d9c04a2
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_gtk.cc
@@ -0,0 +1,338 @@
+// 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 "content/browser/web_contents/web_drag_dest_gtk.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/drag_utils_gtk.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_drag_dest_delegate.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/net_util.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/dragdrop/gtk_dnd_util.h"
+#include "ui/base/gtk/gtk_screen_util.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationNone;
+
+namespace content {
+
+namespace {
+const int kNumGtkHandlers = 5;
+
+int GetModifierFlags(GtkWidget* widget) {
+ int modifier_state = 0;
+ GdkModifierType state;
+ gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, &state);
+
+ if (state & GDK_SHIFT_MASK)
+ modifier_state |= WebKit::WebInputEvent::ShiftKey;
+ if (state & GDK_CONTROL_MASK)
+ modifier_state |= WebKit::WebInputEvent::ControlKey;
+ if (state & GDK_MOD1_MASK)
+ modifier_state |= WebKit::WebInputEvent::AltKey;
+ if (state & GDK_META_MASK)
+ modifier_state |= WebKit::WebInputEvent::MetaKey;
+ return modifier_state;
+}
+
+} // namespace
+
+WebDragDestGtk::WebDragDestGtk(WebContents* web_contents, GtkWidget* widget)
+ : web_contents_(web_contents),
+ widget_(widget),
+ context_(NULL),
+ data_requests_(0),
+ delegate_(NULL),
+ canceled_(false),
+ method_factory_(this) {
+ gtk_drag_dest_set(widget, static_cast<GtkDestDefaults>(0),
+ NULL, 0,
+ static_cast<GdkDragAction>(GDK_ACTION_COPY |
+ GDK_ACTION_LINK |
+ GDK_ACTION_MOVE));
+
+ // If adding a handler, make sure to update kNumGtkHandlers and add it to the
+ // |handlers_| array so that it can be disconnected later on.
+ handlers_.reset(new int[kNumGtkHandlers]);
+ handlers_.get()[0] = g_signal_connect(
+ widget, "drag-motion", G_CALLBACK(OnDragMotionThunk), this);
+ handlers_.get()[1] = g_signal_connect(
+ widget, "drag-leave", G_CALLBACK(OnDragLeaveThunk), this);
+ handlers_.get()[2] = g_signal_connect(
+ widget, "drag-drop", G_CALLBACK(OnDragDropThunk), this);
+ handlers_.get()[3] = g_signal_connect(
+ widget, "drag-data-received", G_CALLBACK(OnDragDataReceivedThunk), this);
+ // TODO(tony): Need a drag-data-delete handler for moving content out of
+ // the WebContents. http://crbug.com/38989
+
+ handlers_.get()[4] = g_signal_connect(
+ widget, "destroy", G_CALLBACK(gtk_widget_destroyed), &widget_);
+}
+
+WebDragDestGtk::~WebDragDestGtk() {
+ if (widget_) {
+ gtk_drag_dest_unset(widget_);
+ for (int i = 0; i < kNumGtkHandlers; ++i)
+ g_signal_handler_disconnect(widget_, handlers_.get()[i]);
+ }
+}
+
+void WebDragDestGtk::UpdateDragStatus(WebDragOperation operation) {
+ if (context_) {
+ is_drop_target_ = operation != WebDragOperationNone;
+ gdk_drag_status(context_, WebDragOpToGdkDragAction(operation),
+ drag_over_time_);
+ }
+}
+
+void WebDragDestGtk::DragLeave() {
+ GetRenderViewHost()->DragTargetDragLeave();
+ if (delegate())
+ delegate()->OnDragLeave();
+
+ drop_data_.reset();
+}
+
+gboolean WebDragDestGtk::OnDragMotion(GtkWidget* sender,
+ GdkDragContext* context,
+ gint x, gint y,
+ guint time) {
+ if (context_ != context) {
+ context_ = context;
+ drop_data_.reset(new DropData);
+ is_drop_target_ = false;
+
+ if (delegate())
+ delegate()->DragInitialize(web_contents_);
+
+ // text/plain must come before text/uri-list. This is a hack that works in
+ // conjunction with OnDragDataReceived. Since some file managers populate
+ // text/plain with file URLs when dragging files, we want to handle
+ // text/uri-list after text/plain so that the plain text can be cleared if
+ // it's a file drag.
+ static int supported_targets[] = {
+ ui::TEXT_PLAIN,
+ ui::TEXT_URI_LIST,
+ ui::TEXT_HTML,
+ ui::NETSCAPE_URL,
+ ui::CHROME_NAMED_URL,
+ // TODO(estade): support image drags?
+ ui::CUSTOM_DATA,
+ };
+
+ // Add the delegate's requested target if applicable. Need to do this here
+ // since gtk_drag_get_data will dispatch to our drag-data-received.
+ data_requests_ = arraysize(supported_targets) + (delegate() ? 1 : 0);
+ for (size_t i = 0; i < arraysize(supported_targets); ++i) {
+ gtk_drag_get_data(widget_, context,
+ ui::GetAtomForTarget(supported_targets[i]),
+ time);
+ }
+
+ if (delegate()) {
+ gtk_drag_get_data(widget_, context, delegate()->GetBookmarkTargetAtom(),
+ time);
+ }
+ } else if (data_requests_ == 0) {
+ if (canceled_)
+ return FALSE;
+
+ GetRenderViewHost()->DragTargetDragOver(
+ ui::ClientPoint(widget_),
+ ui::ScreenPoint(widget_),
+ GdkDragActionToWebDragOp(context->actions),
+ GetModifierFlags(widget_));
+
+ if (delegate())
+ delegate()->OnDragOver();
+
+ drag_over_time_ = time;
+ }
+
+ // Pretend we are a drag destination because we don't want to wait for
+ // the renderer to tell us if we really are or not.
+ return TRUE;
+}
+
+void WebDragDestGtk::OnDragDataReceived(
+ GtkWidget* sender, GdkDragContext* context, gint x, gint y,
+ GtkSelectionData* data, guint info, guint time) {
+ // We might get the data from an old get_data() request that we no longer
+ // care about.
+ if (context != context_)
+ return;
+
+ data_requests_--;
+
+ // Decode the data.
+ gint data_length = gtk_selection_data_get_length(data);
+ const guchar* raw_data = gtk_selection_data_get_data(data);
+ GdkAtom target = gtk_selection_data_get_target(data);
+ if (raw_data && data_length > 0) {
+ // If the source can't provide us with valid data for a requested target,
+ // raw_data will be NULL.
+ if (target == ui::GetAtomForTarget(ui::TEXT_PLAIN)) {
+ guchar* text = gtk_selection_data_get_text(data);
+ if (text) {
+ drop_data_->text = base::NullableString16(
+ UTF8ToUTF16(std::string(reinterpret_cast<const char*>(text))),
+ false);
+ g_free(text);
+ }
+ } else if (target == ui::GetAtomForTarget(ui::TEXT_URI_LIST)) {
+ gchar** uris = gtk_selection_data_get_uris(data);
+ if (uris) {
+ drop_data_->url = GURL();
+ for (gchar** uri_iter = uris; *uri_iter; uri_iter++) {
+ // Most file managers populate text/uri-list with file URLs when
+ // dragging files. To avoid exposing file system paths to web content,
+ // file URLs are never set as the URL content for the drop.
+ // TODO(estade): Can the filenames have a non-UTF8 encoding?
+ GURL url(*uri_iter);
+ base::FilePath file_path;
+ if (url.SchemeIs(chrome::kFileScheme) &&
+ net::FileURLToFilePath(url, &file_path)) {
+ drop_data_->filenames.push_back(
+ DropData::FileInfo(UTF8ToUTF16(file_path.value()), string16()));
+ // This is a hack. Some file managers also populate text/plain with
+ // a file URL when dragging files, so we clear it to avoid exposing
+ // it to the web content.
+ drop_data_->text = base::NullableString16();
+ } else if (!drop_data_->url.is_valid()) {
+ // Also set the first non-file URL as the URL content for the drop.
+ drop_data_->url = url;
+ }
+ }
+ g_strfreev(uris);
+ }
+ } else if (target == ui::GetAtomForTarget(ui::TEXT_HTML)) {
+ // TODO(estade): Can the html have a non-UTF8 encoding?
+ drop_data_->html = base::NullableString16(
+ UTF8ToUTF16(std::string(reinterpret_cast<const char*>(raw_data),
+ data_length)),
+ false);
+ // We leave the base URL empty.
+ } else if (target == ui::GetAtomForTarget(ui::NETSCAPE_URL)) {
+ std::string netscape_url(reinterpret_cast<const char*>(raw_data),
+ data_length);
+ size_t split = netscape_url.find_first_of('\n');
+ if (split != std::string::npos) {
+ drop_data_->url = GURL(netscape_url.substr(0, split));
+ if (split < netscape_url.size() - 1)
+ drop_data_->url_title = UTF8ToUTF16(netscape_url.substr(split + 1));
+ }
+ } else if (target == ui::GetAtomForTarget(ui::CHROME_NAMED_URL)) {
+ ui::ExtractNamedURL(data, &drop_data_->url, &drop_data_->url_title);
+ } else if (target == ui::GetAtomForTarget(ui::CUSTOM_DATA)) {
+ ui::ReadCustomDataIntoMap(
+ raw_data, data_length, &drop_data_->custom_data);
+ }
+ }
+
+ if (data_requests_ == 0) {
+ // Give the delegate an opportunity to cancel the drag.
+ canceled_ = !web_contents_->GetDelegate()->CanDragEnter(
+ web_contents_,
+ *drop_data_,
+ GdkDragActionToWebDragOp(context->actions));
+ if (canceled_) {
+ drag_over_time_ = time;
+ UpdateDragStatus(WebDragOperationNone);
+ drop_data_.reset();
+ return;
+ }
+ }
+
+ // For CHROME_BOOKMARK_ITEM, we have to handle the case where the drag source
+ // doesn't have any data available for us. In this case we try to synthesize a
+ // URL bookmark.
+ // Note that bookmark drag data is encoded in the same format for both
+ // GTK and Views, hence we can share the same logic here.
+ if (delegate() && target == delegate()->GetBookmarkTargetAtom()) {
+ if (raw_data && data_length > 0) {
+ delegate()->OnReceiveDataFromGtk(data);
+ } else {
+ delegate()->OnReceiveProcessedData(drop_data_->url,
+ drop_data_->url_title);
+ }
+ }
+
+ if (data_requests_ == 0) {
+ // Tell the renderer about the drag.
+ // |x| and |y| are seemingly arbitrary at this point.
+ GetRenderViewHost()->DragTargetDragEnter(
+ *drop_data_.get(),
+ ui::ClientPoint(widget_),
+ ui::ScreenPoint(widget_),
+ GdkDragActionToWebDragOp(context->actions),
+ GetModifierFlags(widget_));
+
+ if (delegate())
+ delegate()->OnDragEnter();
+
+ drag_over_time_ = time;
+ }
+}
+
+// The drag has left our widget; forward this information to the renderer.
+void WebDragDestGtk::OnDragLeave(GtkWidget* sender, GdkDragContext* context,
+ guint time) {
+ // Set |context_| to NULL to make sure we will recognize the next DragMotion
+ // as an enter.
+ context_ = NULL;
+
+ if (canceled_)
+ return;
+
+ // Sometimes we get a drag-leave event before getting a drag-data-received
+ // event. In that case, we don't want to bother the renderer with a
+ // DragLeave event.
+ if (data_requests_ != 0)
+ return;
+
+ // When GTK sends us a drag-drop signal, it is shortly (and synchronously)
+ // preceded by a drag-leave. The renderer doesn't like getting the signals
+ // in this order so delay telling it about the drag-leave till we are sure
+ // we are not getting a drop as well.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebDragDestGtk::DragLeave, method_factory_.GetWeakPtr()));
+}
+
+// Called by GTK when the user releases the mouse, executing a drop.
+gboolean WebDragDestGtk::OnDragDrop(GtkWidget* sender, GdkDragContext* context,
+ gint x, gint y, guint time) {
+ // Cancel that drag leave!
+ method_factory_.InvalidateWeakPtrs();
+
+ GetRenderViewHost()->
+ DragTargetDrop(ui::ClientPoint(widget_), ui::ScreenPoint(widget_),
+ GetModifierFlags(widget_));
+
+ if (delegate())
+ delegate()->OnDrop();
+
+ // The second parameter is just an educated guess as to whether or not the
+ // drag succeeded, but at least we will get the drag-end animation right
+ // sometimes.
+ gtk_drag_finish(context, is_drop_target_, FALSE, time);
+
+ return TRUE;
+}
+
+RenderViewHostImpl* WebDragDestGtk::GetRenderViewHost() const {
+ return static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_dest_gtk.h b/chromium/content/browser/web_contents/web_drag_dest_gtk.h
new file mode 100644
index 00000000000..b912afa9d3c
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_gtk.h
@@ -0,0 +1,114 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_GTK_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/common/drop_data.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "ui/base/gtk/gtk_signal.h"
+
+namespace content {
+
+class RenderViewHostImpl;
+class WebContents;
+class WebDragDestDelegate;
+
+// A helper class that handles DnD for drops in the renderer. In GTK parlance,
+// this handles destination-side DnD, but not source-side DnD.
+class CONTENT_EXPORT WebDragDestGtk {
+ public:
+ WebDragDestGtk(WebContents* web_contents, GtkWidget* widget);
+ ~WebDragDestGtk();
+
+ DropData* current_drop_data() const { return drop_data_.get(); }
+
+ // This is called when the renderer responds to a drag motion event. We must
+ // update the system drag cursor.
+ void UpdateDragStatus(WebKit::WebDragOperation operation);
+
+ // Informs the renderer when a system drag has left the render view.
+ // See OnDragLeave().
+ void DragLeave();
+
+ WebDragDestDelegate* delegate() const { return delegate_; }
+ void set_delegate(WebDragDestDelegate* delegate) { delegate_ = delegate; }
+
+ GtkWidget* widget() const { return widget_; }
+
+ private:
+ RenderViewHostImpl* GetRenderViewHost() const;
+
+ // Called when a system drag crosses over the render view. As there is no drag
+ // enter event, we treat it as an enter event (and not a regular motion event)
+ // when |context_| is NULL.
+ CHROMEGTK_CALLBACK_4(WebDragDestGtk, gboolean, OnDragMotion, GdkDragContext*,
+ gint, gint, guint);
+
+ // We make a series of requests for the drag data when the drag first enters
+ // the render view. This is the callback that is used to give us the data
+ // for each individual target. When |data_requests_| reaches 0, we know we
+ // have attained all the data, and we can finally tell the renderer about the
+ // drag.
+ CHROMEGTK_CALLBACK_6(WebDragDestGtk, void, OnDragDataReceived,
+ GdkDragContext*, gint, gint, GtkSelectionData*,
+ guint, guint);
+
+ // The drag has left our widget; forward this information to the renderer.
+ CHROMEGTK_CALLBACK_2(WebDragDestGtk, void, OnDragLeave, GdkDragContext*,
+ guint);
+
+ // Called by GTK when the user releases the mouse, executing a drop.
+ CHROMEGTK_CALLBACK_4(WebDragDestGtk, gboolean, OnDragDrop, GdkDragContext*,
+ gint, gint, guint);
+
+ WebContents* web_contents_;
+
+ // The render view.
+ GtkWidget* widget_;
+
+ // The current drag context for system drags over our render view, or NULL if
+ // there is no system drag or the system drag is not over our render view.
+ GdkDragContext* context_;
+
+ // The data for the current drag, or NULL if |context_| is NULL.
+ scoped_ptr<DropData> drop_data_;
+
+ // The number of outstanding drag data requests we have sent to the drag
+ // source.
+ int data_requests_;
+
+ // The last time we sent a message to the renderer related to a drag motion.
+ gint drag_over_time_;
+
+ // Whether the cursor is over a drop target, according to the last message we
+ // got from the renderer.
+ bool is_drop_target_;
+
+ // Stores Handler IDs for the gtk signal handlers. We have to cancel the
+ // signal handlers when this WebDragDestGtk is deleted so that if, later on,
+ // we re-create the drag dest with the same widget, we don't get callbacks to
+ // deleted functions.
+ scoped_ptr<int[]> handlers_;
+
+ // A delegate that can receive drag information about drag events.
+ WebDragDestDelegate* delegate_;
+
+ // True if the drag has been canceled.
+ bool canceled_;
+
+ base::WeakPtrFactory<WebDragDestGtk> method_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDragDestGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_GTK_H_
diff --git a/chromium/content/browser/web_contents/web_drag_dest_mac.h b/chromium/content/browser/web_contents/web_drag_dest_mac.h
new file mode 100644
index 00000000000..63a9efa417d
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_mac.h
@@ -0,0 +1,88 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/public/common/drop_data.h"
+
+namespace content {
+class RenderViewHost;
+class WebContentsImpl;
+class WebDragDestDelegate;
+}
+
+// A typedef for a RenderViewHost used for comparison purposes only.
+typedef content::RenderViewHost* RenderViewHostIdentifier;
+
+// A class that handles tracking and event processing for a drag and drop
+// over the content area. Assumes something else initiates the drag, this is
+// only for processing during a drag.
+CONTENT_EXPORT
+@interface WebDragDest : NSObject {
+ @private
+ // Our associated WebContentsImpl. Weak reference.
+ content::WebContentsImpl* webContents_;
+
+ // Delegate; weak.
+ content::WebDragDestDelegate* delegate_;
+
+ // Updated asynchronously during a drag to tell us whether or not we should
+ // allow the drop.
+ NSDragOperation currentOperation_;
+
+ // Keep track of the render view host we're dragging over. If it changes
+ // during a drag, we need to re-send the DragEnter message.
+ RenderViewHostIdentifier currentRVH_;
+
+ // The data for the current drag, or NULL if none is in progress.
+ scoped_ptr<content::DropData> dropData_;
+
+ // True if the drag has been canceled.
+ bool canceled_;
+}
+
+// |contents| is the WebContentsImpl representing this tab, used to communicate
+// drag&drop messages to WebCore and handle navigation on a successful drop
+// (if necessary).
+- (id)initWithWebContentsImpl:(content::WebContentsImpl*)contents;
+
+- (content::DropData*)currentDropData;
+
+- (void)setDragDelegate:(content::WebDragDestDelegate*)delegate;
+
+// Sets the current operation negotiated by the source and destination,
+// which determines whether or not we should allow the drop. Takes effect the
+// next time |-draggingUpdated:| is called.
+- (void)setCurrentOperation:(NSDragOperation)operation;
+
+// Messages to send during the tracking of a drag, ususally upon receiving
+// calls from the view system. Communicates the drag messages to WebCore.
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
+ view:(NSView*)view;
+- (void)draggingExited:(id<NSDraggingInfo>)info;
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
+ view:(NSView*)view;
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
+ view:(NSView*)view;
+
+@end
+
+// Public use only for unit tests.
+@interface WebDragDest(Testing)
+// Given |data|, which should not be nil, fill it in using the contents of the
+// given pasteboard.
+- (void)populateDropData:(content::DropData*)data
+ fromPasteboard:(NSPasteboard*)pboard;
+// Given a point in window coordinates and a view in that window, return a
+// flipped point in the coordinate system of |view|.
+- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint
+ view:(NSView*)view;
+// Given a point in window coordinates and a view in that window, return a
+// flipped point in screen coordinates.
+- (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint
+ view:(NSView*)view;
+@end
diff --git a/chromium/content/browser/web_contents/web_drag_dest_mac.mm b/chromium/content/browser/web_contents/web_drag_dest_mac.mm
new file mode 100644
index 00000000000..8a529e838fd
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_mac.mm
@@ -0,0 +1,310 @@
+// 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.
+
+#import "content/browser/web_contents/web_drag_dest_mac.h"
+
+#import <Carbon/Carbon.h>
+
+#include "base/strings/sys_string_conversions.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_drag_dest_delegate.h"
+#include "content/public/common/drop_data.h"
+#import "third_party/mozilla/NSPasteboard+Utils.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#import "ui/base/dragdrop/cocoa_dnd_util.h"
+#include "ui/base/window_open_disposition.h"
+
+using WebKit::WebDragOperationsMask;
+using content::DropData;
+using content::OpenURLParams;
+using content::Referrer;
+using content::WebContentsImpl;
+
+int GetModifierFlags() {
+ int modifier_state = 0;
+ UInt32 currentModifiers = GetCurrentKeyModifiers();
+ if (currentModifiers & ::shiftKey)
+ modifier_state |= WebKit::WebInputEvent::ShiftKey;
+ if (currentModifiers & ::controlKey)
+ modifier_state |= WebKit::WebInputEvent::ControlKey;
+ if (currentModifiers & ::optionKey)
+ modifier_state |= WebKit::WebInputEvent::AltKey;
+ if (currentModifiers & ::cmdKey)
+ modifier_state |= WebKit::WebInputEvent::MetaKey;
+ return modifier_state;
+}
+
+@implementation WebDragDest
+
+// |contents| is the WebContentsImpl representing this tab, used to communicate
+// drag&drop messages to WebCore and handle navigation on a successful drop
+// (if necessary).
+- (id)initWithWebContentsImpl:(WebContentsImpl*)contents {
+ if ((self = [super init])) {
+ webContents_ = contents;
+ canceled_ = false;
+ }
+ return self;
+}
+
+- (DropData*)currentDropData {
+ return dropData_.get();
+}
+
+- (void)setDragDelegate:(content::WebDragDestDelegate*)delegate {
+ delegate_ = delegate;
+}
+
+// Call to set whether or not we should allow the drop. Takes effect the
+// next time |-draggingUpdated:| is called.
+- (void)setCurrentOperation:(NSDragOperation)operation {
+ currentOperation_ = operation;
+}
+
+// Given a point in window coordinates and a view in that window, return a
+// flipped point in the coordinate system of |view|.
+- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint
+ view:(NSView*)view {
+ DCHECK(view);
+ NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
+ NSRect viewFrame = [view frame];
+ viewPoint.y = viewFrame.size.height - viewPoint.y;
+ return viewPoint;
+}
+
+// Given a point in window coordinates and a view in that window, return a
+// flipped point in screen coordinates.
+- (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint
+ view:(NSView*)view {
+ DCHECK(view);
+ NSPoint screenPoint = [[view window] convertBaseToScreen:windowPoint];
+ NSScreen* screen = [[view window] screen];
+ NSRect screenFrame = [screen frame];
+ screenPoint.y = screenFrame.size.height - screenPoint.y;
+ return screenPoint;
+}
+
+// Return YES if the drop site only allows drops that would navigate. If this
+// is the case, we don't want to pass messages to the renderer because there's
+// really no point (i.e., there's nothing that cares about the mouse position or
+// entering and exiting). One example is an interstitial page (e.g., safe
+// browsing warning).
+- (BOOL)onlyAllowsNavigation {
+ return webContents_->ShowingInterstitialPage();
+}
+
+// Messages to send during the tracking of a drag, usually upon receiving
+// calls from the view system. Communicates the drag messages to WebCore.
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
+ view:(NSView*)view {
+ // Save off the RVH so we can tell if it changes during a drag. If it does,
+ // we need to send a new enter message in draggingUpdated:.
+ currentRVH_ = webContents_->GetRenderViewHost();
+
+ // Fill out a DropData from pasteboard.
+ scoped_ptr<DropData> dropData;
+ dropData.reset(new DropData());
+ [self populateDropData:dropData.get()
+ fromPasteboard:[info draggingPasteboard]];
+
+ NSDragOperation mask = [info draggingSourceOperationMask];
+
+ // Give the delegate an opportunity to cancel the drag.
+ canceled_ = !webContents_->GetDelegate()->CanDragEnter(
+ webContents_,
+ *dropData,
+ static_cast<WebDragOperationsMask>(mask));
+ if (canceled_)
+ return NSDragOperationNone;
+
+ if ([self onlyAllowsNavigation]) {
+ if ([[info draggingPasteboard] containsURLData])
+ return NSDragOperationCopy;
+ return NSDragOperationNone;
+ }
+
+ if (delegate_) {
+ delegate_->DragInitialize(webContents_);
+ delegate_->OnDragEnter();
+ }
+
+ dropData_.swap(dropData);
+
+ // Create the appropriate mouse locations for WebCore. The draggingLocation
+ // is in window coordinates. Both need to be flipped.
+ NSPoint windowPoint = [info draggingLocation];
+ NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+ NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
+ webContents_->GetRenderViewHost()->DragTargetDragEnter(
+ *dropData_,
+ gfx::Point(viewPoint.x, viewPoint.y),
+ gfx::Point(screenPoint.x, screenPoint.y),
+ static_cast<WebDragOperationsMask>(mask),
+ GetModifierFlags());
+
+ // We won't know the true operation (whether the drag is allowed) until we
+ // hear back from the renderer. For now, be optimistic:
+ currentOperation_ = NSDragOperationCopy;
+ return currentOperation_;
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)info {
+ DCHECK(currentRVH_);
+ if (currentRVH_ != webContents_->GetRenderViewHost())
+ return;
+
+ if (canceled_)
+ return;
+
+ if ([self onlyAllowsNavigation])
+ return;
+
+ if (delegate_)
+ delegate_->OnDragLeave();
+
+ webContents_->GetRenderViewHost()->DragTargetDragLeave();
+ dropData_.reset();
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
+ view:(NSView*)view {
+ DCHECK(currentRVH_);
+ if (currentRVH_ != webContents_->GetRenderViewHost())
+ [self draggingEntered:info view:view];
+
+ if (canceled_)
+ return NSDragOperationNone;
+
+ if ([self onlyAllowsNavigation]) {
+ if ([[info draggingPasteboard] containsURLData])
+ return NSDragOperationCopy;
+ return NSDragOperationNone;
+ }
+
+ // Create the appropriate mouse locations for WebCore. The draggingLocation
+ // is in window coordinates.
+ NSPoint windowPoint = [info draggingLocation];
+ NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+ NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
+ NSDragOperation mask = [info draggingSourceOperationMask];
+ webContents_->GetRenderViewHost()->DragTargetDragOver(
+ gfx::Point(viewPoint.x, viewPoint.y),
+ gfx::Point(screenPoint.x, screenPoint.y),
+ static_cast<WebDragOperationsMask>(mask),
+ GetModifierFlags());
+
+ if (delegate_)
+ delegate_->OnDragOver();
+
+ return currentOperation_;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
+ view:(NSView*)view {
+ if (currentRVH_ != webContents_->GetRenderViewHost())
+ [self draggingEntered:info view:view];
+
+ // Check if we only allow navigation and navigate to a url on the pasteboard.
+ if ([self onlyAllowsNavigation]) {
+ NSPasteboard* pboard = [info draggingPasteboard];
+ if ([pboard containsURLData]) {
+ GURL url;
+ ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
+ webContents_->OpenURL(OpenURLParams(
+ url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK,
+ false));
+ return YES;
+ } else {
+ return NO;
+ }
+ }
+
+ if (delegate_)
+ delegate_->OnDrop();
+
+ currentRVH_ = NULL;
+
+ // Create the appropriate mouse locations for WebCore. The draggingLocation
+ // is in window coordinates. Both need to be flipped.
+ NSPoint windowPoint = [info draggingLocation];
+ NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+ NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
+ webContents_->GetRenderViewHost()->DragTargetDrop(
+ gfx::Point(viewPoint.x, viewPoint.y),
+ gfx::Point(screenPoint.x, screenPoint.y),
+ GetModifierFlags());
+
+ dropData_.reset();
+
+ return YES;
+}
+
+// Given |data|, which should not be nil, fill it in using the contents of the
+// given pasteboard. The types handled by this method should be kept in sync
+// with [WebContentsViewCocoa registerDragTypes].
+- (void)populateDropData:(DropData*)data
+ fromPasteboard:(NSPasteboard*)pboard {
+ DCHECK(data);
+ DCHECK(pboard);
+ NSArray* types = [pboard types];
+
+ // Get URL if possible. To avoid exposing file system paths to web content,
+ // filenames in the drag are not converted to file URLs.
+ ui::PopulateURLAndTitleFromPasteboard(&data->url,
+ &data->url_title,
+ pboard,
+ NO);
+
+ // Get plain text.
+ if ([types containsObject:NSStringPboardType]) {
+ data->text = base::NullableString16(
+ base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]),
+ false);
+ }
+
+ // Get HTML. If there's no HTML, try RTF.
+ if ([types containsObject:NSHTMLPboardType]) {
+ NSString* html = [pboard stringForType:NSHTMLPboardType];
+ data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
+ } else if ([types containsObject:ui::kChromeDragImageHTMLPboardType]) {
+ NSString* html = [pboard stringForType:ui::kChromeDragImageHTMLPboardType];
+ data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
+ } else if ([types containsObject:NSRTFPboardType]) {
+ NSString* html = [pboard htmlFromRtf];
+ data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
+ }
+
+ // Get files.
+ if ([types containsObject:NSFilenamesPboardType]) {
+ NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
+ if ([files isKindOfClass:[NSArray class]] && [files count]) {
+ for (NSUInteger i = 0; i < [files count]; i++) {
+ NSString* filename = [files objectAtIndex:i];
+ BOOL exists = [[NSFileManager defaultManager]
+ fileExistsAtPath:filename];
+ if (exists) {
+ data->filenames.push_back(
+ DropData::FileInfo(
+ base::SysNSStringToUTF16(filename), string16()));
+ }
+ }
+ }
+ }
+
+ // TODO(pinkerton): Get file contents. http://crbug.com/34661
+
+ // Get custom MIME data.
+ if ([types containsObject:ui::kWebCustomDataPboardType]) {
+ NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType];
+ ui::ReadCustomDataIntoMap([customData bytes],
+ [customData length],
+ &data->custom_data);
+ }
+}
+
+@end
diff --git a/chromium/content/browser/web_contents/web_drag_dest_mac_unittest.mm b/chromium/content/browser/web_contents/web_drag_dest_mac_unittest.mm
new file mode 100644
index 00000000000..48719de7b75
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_mac_unittest.mm
@@ -0,0 +1,165 @@
+// 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 "base/mac/scoped_nsautorelease_pool.h"
+#import "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#import "content/browser/web_contents/web_drag_dest_mac.h"
+#include "content/public/common/drop_data.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "third_party/mozilla/NSPasteboard+Utils.h"
+#import "ui/base/dragdrop/cocoa_dnd_util.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+using content::DropData;
+using content::RenderViewHostImplTestHarness;
+
+namespace {
+NSString* const kCrCorePasteboardFlavorType_url =
+ @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
+NSString* const kCrCorePasteboardFlavorType_urln =
+ @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
+} // namespace
+
+class WebDragDestTest : public RenderViewHostImplTestHarness {
+ public:
+ virtual void SetUp() {
+ RenderViewHostImplTestHarness::SetUp();
+ drag_dest_.reset([[WebDragDest alloc] initWithWebContentsImpl:contents()]);
+ }
+
+ void PutURLOnPasteboard(NSString* urlString, NSPasteboard* pboard) {
+ [pboard declareTypes:[NSArray arrayWithObject:NSURLPboardType]
+ owner:nil];
+ NSURL* url = [NSURL URLWithString:urlString];
+ EXPECT_TRUE(url);
+ [url writeToPasteboard:pboard];
+ }
+
+ void PutCoreURLAndTitleOnPasteboard(NSString* urlString, NSString* title,
+ NSPasteboard* pboard) {
+ [pboard
+ declareTypes:[NSArray arrayWithObjects:kCrCorePasteboardFlavorType_url,
+ kCrCorePasteboardFlavorType_urln,
+ nil]
+ owner:nil];
+ [pboard setString:urlString
+ forType:kCrCorePasteboardFlavorType_url];
+ [pboard setString:title
+ forType:kCrCorePasteboardFlavorType_urln];
+ }
+
+ base::mac::ScopedNSAutoreleasePool pool_;
+ base::scoped_nsobject<WebDragDest> drag_dest_;
+};
+
+// Make sure nothing leaks.
+TEST_F(WebDragDestTest, Init) {
+ EXPECT_TRUE(drag_dest_);
+}
+
+// Test flipping of coordinates given a point in window coordinates.
+TEST_F(WebDragDestTest, Flip) {
+ NSPoint windowPoint = NSZeroPoint;
+ base::scoped_nsobject<NSWindow> window([[CocoaTestHelperWindow alloc] init]);
+ NSPoint viewPoint =
+ [drag_dest_ flipWindowPointToView:windowPoint
+ view:[window contentView]];
+ NSPoint screenPoint =
+ [drag_dest_ flipWindowPointToScreen:windowPoint
+ view:[window contentView]];
+ EXPECT_EQ(0, viewPoint.x);
+ EXPECT_EQ(600, viewPoint.y);
+ EXPECT_EQ(0, screenPoint.x);
+ // We can't put a value on the screen size since everyone will have a
+ // different one.
+ EXPECT_NE(0, screenPoint.y);
+}
+
+TEST_F(WebDragDestTest, URL) {
+ NSPasteboard* pboard = nil;
+ NSString* url = nil;
+ NSString* title = nil;
+ GURL result_url;
+ string16 result_title;
+
+ // Put a URL on the pasteboard and check it.
+ pboard = [NSPasteboard pasteboardWithUniqueName];
+ url = @"http://www.google.com/";
+ PutURLOnPasteboard(url, pboard);
+ EXPECT_TRUE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, NO));
+ EXPECT_EQ(base::SysNSStringToUTF8(url), result_url.spec());
+ [pboard releaseGlobally];
+
+ // Put a 'url ' and 'urln' on the pasteboard and check it.
+ pboard = [NSPasteboard pasteboardWithUniqueName];
+ url = @"http://www.google.com/";
+ title = @"Title of Awesomeness!",
+ PutCoreURLAndTitleOnPasteboard(url, title, pboard);
+ EXPECT_TRUE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, NO));
+ EXPECT_EQ(base::SysNSStringToUTF8(url), result_url.spec());
+ EXPECT_EQ(base::SysNSStringToUTF16(title), result_title);
+ [pboard releaseGlobally];
+
+ // Also check that it passes file:// via 'url '/'urln' properly.
+ pboard = [NSPasteboard pasteboardWithUniqueName];
+ url = @"file:///tmp/dont_delete_me.txt";
+ title = @"very important";
+ PutCoreURLAndTitleOnPasteboard(url, title, pboard);
+ EXPECT_TRUE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, NO));
+ EXPECT_EQ(base::SysNSStringToUTF8(url), result_url.spec());
+ EXPECT_EQ(base::SysNSStringToUTF16(title), result_title);
+ [pboard releaseGlobally];
+
+ // And javascript:.
+ pboard = [NSPasteboard pasteboardWithUniqueName];
+ url = @"javascript:open('http://www.youtube.com/')";
+ title = @"kill some time";
+ PutCoreURLAndTitleOnPasteboard(url, title, pboard);
+ EXPECT_TRUE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, NO));
+ EXPECT_EQ(base::SysNSStringToUTF8(url), result_url.spec());
+ EXPECT_EQ(base::SysNSStringToUTF16(title), result_title);
+ [pboard releaseGlobally];
+
+ pboard = [NSPasteboard pasteboardWithUniqueName];
+ url = @"/bin/sh";
+ [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
+ owner:nil];
+ [pboard setPropertyList:[NSArray arrayWithObject:url]
+ forType:NSFilenamesPboardType];
+ EXPECT_FALSE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, NO));
+ EXPECT_TRUE(ui::PopulateURLAndTitleFromPasteboard(
+ &result_url, &result_title, pboard, YES));
+ EXPECT_EQ("file://localhost/bin/sh", result_url.spec());
+ EXPECT_EQ("sh", UTF16ToUTF8(result_title));
+ [pboard releaseGlobally];
+}
+
+TEST_F(WebDragDestTest, Data) {
+ DropData data;
+ NSPasteboard* pboard = [NSPasteboard pasteboardWithUniqueName];
+
+ PutURLOnPasteboard(@"http://www.google.com", pboard);
+ [pboard addTypes:[NSArray arrayWithObjects:NSHTMLPboardType,
+ NSStringPboardType, nil]
+ owner:nil];
+ NSString* htmlString = @"<html><body><b>hi there</b></body></html>";
+ NSString* textString = @"hi there";
+ [pboard setString:htmlString forType:NSHTMLPboardType];
+ [pboard setString:textString forType:NSStringPboardType];
+ [drag_dest_ populateDropData:&data fromPasteboard:pboard];
+ EXPECT_EQ(data.url.spec(), "http://www.google.com/");
+ EXPECT_EQ(base::SysNSStringToUTF16(textString), data.text.string());
+ EXPECT_EQ(base::SysNSStringToUTF16(htmlString), data.html.string());
+
+ [pboard releaseGlobally];
+}
diff --git a/chromium/content/browser/web_contents/web_drag_dest_win.cc b/chromium/content/browser/web_contents/web_drag_dest_win.cc
new file mode 100644
index 00000000000..60f0c0593a9
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_win.cc
@@ -0,0 +1,286 @@
+// 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 "content/browser/web_contents/web_drag_dest_win.h"
+
+#include <windows.h>
+#include <shlobj.h>
+
+#include "base/win/win_util.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_drag_utils_win.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_drag_dest_delegate.h"
+#include "content/public/common/drop_data.h"
+#include "net/base/net_util.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/clipboard/clipboard_util_win.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
+#include "ui/base/window_open_disposition.h"
+#include "ui/gfx/point.h"
+#include "url/gurl.h"
+
+using WebKit::WebDragOperationNone;
+using WebKit::WebDragOperationCopy;
+using WebKit::WebDragOperationLink;
+using WebKit::WebDragOperationMove;
+using WebKit::WebDragOperationGeneric;
+
+namespace content {
+namespace {
+
+const unsigned short kHighBitMaskShort = 0x8000;
+
+// A helper method for getting the preferred drop effect.
+DWORD GetPreferredDropEffect(DWORD effect) {
+ if (effect & DROPEFFECT_COPY)
+ return DROPEFFECT_COPY;
+ if (effect & DROPEFFECT_LINK)
+ return DROPEFFECT_LINK;
+ if (effect & DROPEFFECT_MOVE)
+ return DROPEFFECT_MOVE;
+ return DROPEFFECT_NONE;
+}
+
+int GetModifierFlags() {
+ int modifier_state = 0;
+ if (base::win::IsShiftPressed())
+ modifier_state |= WebKit::WebInputEvent::ShiftKey;
+ if (base::win::IsCtrlPressed())
+ modifier_state |= WebKit::WebInputEvent::ControlKey;
+ if (base::win::IsAltPressed())
+ modifier_state |= WebKit::WebInputEvent::AltKey;
+ if (::GetKeyState(VK_LWIN) & kHighBitMaskShort)
+ modifier_state |= WebKit::WebInputEvent::MetaKey;
+ if (::GetKeyState(VK_RWIN) & kHighBitMaskShort)
+ modifier_state |= WebKit::WebInputEvent::MetaKey;
+ return modifier_state;
+}
+
+// Helper method for converting Window's specific IDataObject to a DropData
+// object.
+void PopulateDropData(IDataObject* data_object, DropData* drop_data) {
+ base::string16 url_str;
+ if (ui::ClipboardUtil::GetUrl(
+ data_object, &url_str, &drop_data->url_title, false)) {
+ GURL test_url(url_str);
+ if (test_url.is_valid())
+ drop_data->url = test_url;
+ }
+ std::vector<base::string16> filenames;
+ ui::ClipboardUtil::GetFilenames(data_object, &filenames);
+ for (size_t i = 0; i < filenames.size(); ++i)
+ drop_data->filenames.push_back(
+ DropData::FileInfo(filenames[i], base::string16()));
+ base::string16 text;
+ ui::ClipboardUtil::GetPlainText(data_object, &text);
+ if (!text.empty()) {
+ drop_data->text = base::NullableString16(text, false);
+ }
+ base::string16 html;
+ std::string html_base_url;
+ ui::ClipboardUtil::GetHtml(data_object, &html, &html_base_url);
+ if (!html.empty()) {
+ drop_data->html = base::NullableString16(html, false);
+ }
+ if (!html_base_url.empty()) {
+ drop_data->html_base_url = GURL(html_base_url);
+ }
+ ui::ClipboardUtil::GetWebCustomData(data_object, &drop_data->custom_data);
+}
+
+} // namespace
+
+// InterstitialDropTarget is like a ui::DropTargetWin implementation that
+// WebDragDest passes through to if an interstitial is showing. Rather than
+// passing messages on to the renderer, we just check to see if there's a link
+// in the drop data and handle links as navigations.
+class InterstitialDropTarget {
+ public:
+ explicit InterstitialDropTarget(WebContents* web_contents)
+ : web_contents_(web_contents) {}
+
+ DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
+ return ui::ClipboardUtil::HasUrl(data_object) ?
+ GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
+ }
+
+ DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
+ return ui::ClipboardUtil::HasUrl(data_object) ?
+ GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
+ }
+
+ void OnDragLeave(IDataObject* data_object) {
+ }
+
+ DWORD OnDrop(IDataObject* data_object, DWORD effect) {
+ if (!ui::ClipboardUtil::HasUrl(data_object))
+ return DROPEFFECT_NONE;
+
+ std::wstring url;
+ std::wstring title;
+ ui::ClipboardUtil::GetUrl(data_object, &url, &title, true);
+ OpenURLParams params(GURL(url), Referrer(), CURRENT_TAB,
+ PAGE_TRANSITION_AUTO_BOOKMARK, false);
+ web_contents_->OpenURL(params);
+ return GetPreferredDropEffect(effect);
+ }
+
+ private:
+ WebContents* web_contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialDropTarget);
+};
+
+WebDragDest::WebDragDest(HWND source_hwnd, WebContents* web_contents)
+ : ui::DropTargetWin(source_hwnd),
+ web_contents_(web_contents),
+ current_rvh_(NULL),
+ drag_cursor_(WebDragOperationNone),
+ interstitial_drop_target_(new InterstitialDropTarget(web_contents)),
+ delegate_(NULL),
+ canceled_(false) {
+}
+
+WebDragDest::~WebDragDest() {
+}
+
+DWORD WebDragDest::OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effects) {
+ current_rvh_ = web_contents_->GetRenderViewHost();
+
+ // TODO(tc): PopulateDropData can be slow depending on what is in the
+ // IDataObject. Maybe we can do this in a background thread.
+ scoped_ptr<DropData> drop_data;
+ drop_data.reset(new DropData());
+ PopulateDropData(data_object, drop_data.get());
+
+ if (drop_data->url.is_empty())
+ ui::OSExchangeDataProviderWin::GetPlainTextURL(data_object,
+ &drop_data->url);
+
+ // Give the delegate an opportunity to cancel the drag.
+ canceled_ = !web_contents_->GetDelegate()->CanDragEnter(
+ web_contents_,
+ *drop_data,
+ WinDragOpMaskToWebDragOpMask(effects));
+ if (canceled_)
+ return DROPEFFECT_NONE;
+
+ if (delegate_)
+ delegate_->DragInitialize(web_contents_);
+
+ // Don't pass messages to the renderer if an interstitial page is showing
+ // because we don't want the interstitial page to navigate. Instead,
+ // pass the messages on to a separate interstitial DropTarget handler.
+ if (web_contents_->ShowingInterstitialPage())
+ return interstitial_drop_target_->OnDragEnter(data_object, effects);
+
+ drop_data_.swap(drop_data);
+ drag_cursor_ = WebDragOperationNone;
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->GetRenderViewHost()->DragTargetDragEnter(*drop_data_,
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y),
+ WinDragOpMaskToWebDragOpMask(effects),
+ GetModifierFlags());
+
+ if (delegate_)
+ delegate_->OnDragEnter(data_object);
+
+ // We lie here and always return a DROPEFFECT because we don't want to
+ // wait for the IPC call to return.
+ return WebDragOpToWinDragOp(drag_cursor_);
+}
+
+DWORD WebDragDest::OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effects) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->GetRenderViewHost())
+ OnDragEnter(data_object, key_state, cursor_position, effects);
+
+ if (canceled_)
+ return DROPEFFECT_NONE;
+
+ if (web_contents_->ShowingInterstitialPage())
+ return interstitial_drop_target_->OnDragOver(data_object, effects);
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->GetRenderViewHost()->DragTargetDragOver(
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y),
+ WinDragOpMaskToWebDragOpMask(effects),
+ GetModifierFlags());
+
+ if (delegate_)
+ delegate_->OnDragOver(data_object);
+
+ return WebDragOpToWinDragOp(drag_cursor_);
+}
+
+void WebDragDest::OnDragLeave(IDataObject* data_object) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->GetRenderViewHost())
+ return;
+
+ if (canceled_)
+ return;
+
+ if (web_contents_->ShowingInterstitialPage()) {
+ interstitial_drop_target_->OnDragLeave(data_object);
+ } else {
+ web_contents_->GetRenderViewHost()->DragTargetDragLeave();
+ }
+
+ if (delegate_)
+ delegate_->OnDragLeave(data_object);
+
+ drop_data_.reset();
+}
+
+DWORD WebDragDest::OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ DCHECK(current_rvh_);
+ if (current_rvh_ != web_contents_->GetRenderViewHost())
+ OnDragEnter(data_object, key_state, cursor_position, effect);
+
+ if (web_contents_->ShowingInterstitialPage())
+ interstitial_drop_target_->OnDragOver(data_object, effect);
+
+ if (web_contents_->ShowingInterstitialPage())
+ return interstitial_drop_target_->OnDrop(data_object, effect);
+
+ POINT client_pt = cursor_position;
+ ScreenToClient(GetHWND(), &client_pt);
+ web_contents_->GetRenderViewHost()->DragTargetDrop(
+ gfx::Point(client_pt.x, client_pt.y),
+ gfx::Point(cursor_position.x, cursor_position.y),
+ GetModifierFlags());
+
+ if (delegate_)
+ delegate_->OnDrop(data_object);
+
+ current_rvh_ = NULL;
+
+ // This isn't always correct, but at least it's a close approximation.
+ // For now, we always map a move to a copy to prevent potential data loss.
+ DWORD drop_effect = WebDragOpToWinDragOp(drag_cursor_);
+ DWORD result = drop_effect != DROPEFFECT_MOVE ? drop_effect : DROPEFFECT_COPY;
+
+ drop_data_.reset();
+ return result;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_dest_win.h b/chromium/content/browser/web_contents/web_drag_dest_win.h
new file mode 100644
index 00000000000..5ddb7edc2c8
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_dest_win.h
@@ -0,0 +1,89 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_WIN_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_WIN_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "content/public/common/drop_data.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "ui/base/dragdrop/drop_target_win.h"
+
+namespace content {
+class InterstitialDropTarget;
+class RenderViewHost;
+class WebContents;
+class WebDragDestDelegate;
+
+// A helper object that provides drop capabilities to a WebContentsImpl. The
+// DropTarget handles drags that enter the region of the WebContents by
+// passing on the events to the renderer.
+class CONTENT_EXPORT WebDragDest : public ui::DropTargetWin {
+ public:
+ // Create a new WebDragDest associating it with the given HWND and
+ // WebContents.
+ WebDragDest(HWND source_hwnd, WebContents* contents);
+ virtual ~WebDragDest();
+
+ DropData* current_drop_data() const { return drop_data_.get(); }
+
+ void set_drag_cursor(WebKit::WebDragOperation op) {
+ drag_cursor_ = op;
+ }
+
+ WebDragDestDelegate* delegate() const { return delegate_; }
+ void set_delegate(WebDragDestDelegate* d) { delegate_ = d; }
+
+ protected:
+ virtual DWORD OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ virtual DWORD OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ virtual void OnDragLeave(IDataObject* data_object);
+
+ virtual DWORD OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ private:
+ // Our associated WebContents.
+ WebContents* web_contents_;
+
+ // We keep track of the render view host we're dragging over. If it changes
+ // during a drag, we need to re-send the DragEnter message. WARNING:
+ // this pointer should never be dereferenced. We only use it for comparing
+ // pointers.
+ RenderViewHost* current_rvh_;
+
+ // Used to determine what cursor we should display when dragging over web
+ // content area. This can be updated async during a drag operation.
+ WebKit::WebDragOperation drag_cursor_;
+
+ // A special drop target handler for when we try to d&d while an interstitial
+ // page is showing.
+ scoped_ptr<InterstitialDropTarget> interstitial_drop_target_;
+
+ // A delegate that can receive drag information about drag events.
+ WebDragDestDelegate* delegate_;
+
+ // The data for the current drag, or NULL if |context_| is NULL.
+ scoped_ptr<DropData> drop_data_;
+
+ // True if the drag has been canceled.
+ bool canceled_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDragDest);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_DEST_WIN_H_
diff --git a/chromium/content/browser/web_contents/web_drag_source_gtk.cc b/chromium/content/browser/web_contents/web_drag_source_gtk.cc
new file mode 100644
index 00000000000..0f1d9d57876
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_gtk.cc
@@ -0,0 +1,403 @@
+// 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 "content/browser/web_contents/web_drag_source_gtk.h"
+
+#include <string>
+
+#include "base/nix/mime_util_xdg.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/download/drag_download_file.h"
+#include "content/browser/download/drag_download_util.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/drag_utils_gtk.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/drop_data.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/dragdrop/gtk_dnd_util.h"
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/base/gtk/gtk_screen_util.h"
+#include "ui/gfx/gtk_util.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+using WebKit::WebDragOperationNone;
+
+namespace content {
+
+WebDragSourceGtk::WebDragSourceGtk(WebContents* web_contents)
+ : web_contents_(static_cast<WebContentsImpl*>(web_contents)),
+ drag_pixbuf_(NULL),
+ drag_failed_(false),
+ drag_widget_(gtk_invisible_new()),
+ drag_context_(NULL),
+ drag_icon_(gtk_window_new(GTK_WINDOW_POPUP)) {
+ signals_.Connect(drag_widget_, "drag-failed",
+ G_CALLBACK(OnDragFailedThunk), this);
+ signals_.Connect(drag_widget_, "drag-begin",
+ G_CALLBACK(OnDragBeginThunk),
+ this);
+ signals_.Connect(drag_widget_, "drag-end",
+ G_CALLBACK(OnDragEndThunk), this);
+ signals_.Connect(drag_widget_, "drag-data-get",
+ G_CALLBACK(OnDragDataGetThunk), this);
+
+ signals_.Connect(drag_icon_, "expose-event",
+ G_CALLBACK(OnDragIconExposeThunk), this);
+}
+
+WebDragSourceGtk::~WebDragSourceGtk() {
+ // Break the current drag, if any.
+ if (drop_data_) {
+ gtk_grab_add(drag_widget_);
+ gtk_grab_remove(drag_widget_);
+ base::MessageLoopForUI::current()->RemoveObserver(this);
+ drop_data_.reset();
+ }
+
+ gtk_widget_destroy(drag_widget_);
+ gtk_widget_destroy(drag_icon_);
+}
+
+bool WebDragSourceGtk::StartDragging(const DropData& drop_data,
+ WebDragOperationsMask allowed_ops,
+ GdkEventButton* last_mouse_down,
+ const SkBitmap& image,
+ const gfx::Vector2d& image_offset) {
+ // Guard against re-starting before previous drag completed.
+ if (drag_context_) {
+ NOTREACHED();
+ return false;
+ }
+
+ int targets_mask = 0;
+
+ if (!drop_data.text.string().empty())
+ targets_mask |= ui::TEXT_PLAIN;
+ if (drop_data.url.is_valid()) {
+ targets_mask |= ui::TEXT_URI_LIST;
+ targets_mask |= ui::CHROME_NAMED_URL;
+ targets_mask |= ui::NETSCAPE_URL;
+ }
+ if (!drop_data.html.string().empty())
+ targets_mask |= ui::TEXT_HTML;
+ if (!drop_data.file_contents.empty())
+ targets_mask |= ui::CHROME_WEBDROP_FILE_CONTENTS;
+ if (!drop_data.download_metadata.empty() &&
+ ParseDownloadMetadata(drop_data.download_metadata,
+ &wide_download_mime_type_,
+ &download_file_name_,
+ &download_url_)) {
+ targets_mask |= ui::DIRECT_SAVE_FILE;
+ }
+ if (!drop_data.custom_data.empty())
+ targets_mask |= ui::CUSTOM_DATA;
+
+ // NOTE: Begin a drag even if no targets present. Otherwise, things like
+ // draggable list elements will not work.
+
+ drop_data_.reset(new DropData(drop_data));
+
+ // The image we get from WebKit makes heavy use of alpha-shading. This looks
+ // bad on non-compositing WMs. Fall back to the default drag icon.
+ if (!image.isNull() && ui::IsScreenComposited())
+ drag_pixbuf_ = gfx::GdkPixbufFromSkBitmap(image);
+ image_offset_ = image_offset;
+
+ GtkTargetList* list = ui::GetTargetListFromCodeMask(targets_mask);
+ if (targets_mask & ui::CHROME_WEBDROP_FILE_CONTENTS) {
+ // Looking up the mime type can hit the disk. http://crbug.com/84896
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ drag_file_mime_type_ = gdk_atom_intern(
+ base::nix::GetDataMimeType(drop_data.file_contents).c_str(), FALSE);
+ gtk_target_list_add(list, drag_file_mime_type_,
+ 0, ui::CHROME_WEBDROP_FILE_CONTENTS);
+ }
+
+ drag_failed_ = false;
+ // If we don't pass an event, GDK won't know what event time to start grabbing
+ // mouse events. Technically it's the mouse motion event and not the mouse
+ // down event that causes the drag, but there's no reliable way to know
+ // *which* motion event initiated the drag, so this will have to do.
+ // TODO(estade): This can sometimes be very far off, e.g. if the user clicks
+ // and holds and doesn't start dragging for a long time. I doubt it matters
+ // much, but we should probably look into the possibility of getting the
+ // initiating event from webkit.
+ drag_context_ = gtk_drag_begin(drag_widget_, list,
+ WebDragOpToGdkDragAction(allowed_ops),
+ 1, // Drags are always initiated by the left button.
+ reinterpret_cast<GdkEvent*>(last_mouse_down));
+ // The drag adds a ref; let it own the list.
+ gtk_target_list_unref(list);
+
+ // Sometimes the drag fails to start; |context| will be NULL and we won't
+ // get a drag-end signal.
+ if (!drag_context_) {
+ drag_failed_ = true;
+ drop_data_.reset();
+ return false;
+ }
+
+ base::MessageLoopForUI::current()->AddObserver(this);
+ return true;
+}
+
+void WebDragSourceGtk::WillProcessEvent(GdkEvent* event) {
+ // No-op.
+}
+
+void WebDragSourceGtk::DidProcessEvent(GdkEvent* event) {
+ if (event->type != GDK_MOTION_NOTIFY)
+ return;
+
+ GdkEventMotion* event_motion = reinterpret_cast<GdkEventMotion*>(event);
+ gfx::Point client = ui::ClientPoint(GetContentNativeView());
+
+ if (web_contents_) {
+ web_contents_->DragSourceMovedTo(
+ client.x(), client.y(),
+ static_cast<int>(event_motion->x_root),
+ static_cast<int>(event_motion->y_root));
+ }
+}
+
+void WebDragSourceGtk::OnDragDataGet(GtkWidget* sender,
+ GdkDragContext* context,
+ GtkSelectionData* selection_data,
+ guint target_type,
+ guint time) {
+ const int kBitsPerByte = 8;
+
+ switch (target_type) {
+ case ui::TEXT_PLAIN: {
+ std::string utf8_text = UTF16ToUTF8(drop_data_->text.string());
+ gtk_selection_data_set_text(selection_data, utf8_text.c_str(),
+ utf8_text.length());
+ break;
+ }
+
+ case ui::TEXT_HTML: {
+ // TODO(estade): change relative links to be absolute using
+ // |html_base_url|.
+ std::string utf8_text = UTF16ToUTF8(drop_data_->html.string());
+ gtk_selection_data_set(selection_data,
+ ui::GetAtomForTarget(ui::TEXT_HTML),
+ kBitsPerByte,
+ reinterpret_cast<const guchar*>(utf8_text.c_str()),
+ utf8_text.length());
+ break;
+ }
+
+ case ui::TEXT_URI_LIST:
+ case ui::CHROME_NAMED_URL:
+ case ui::NETSCAPE_URL: {
+ ui::WriteURLWithName(selection_data, drop_data_->url,
+ drop_data_->url_title, target_type);
+ break;
+ }
+
+ case ui::CHROME_WEBDROP_FILE_CONTENTS: {
+ gtk_selection_data_set(
+ selection_data,
+ drag_file_mime_type_, kBitsPerByte,
+ reinterpret_cast<const guchar*>(drop_data_->file_contents.data()),
+ drop_data_->file_contents.length());
+ break;
+ }
+
+ case ui::DIRECT_SAVE_FILE: {
+ char status_code = 'E';
+
+ // Retrieves the full file path (in file URL format) provided by the
+ // drop target by reading from the source window's XdndDirectSave0
+ // property.
+ gint file_url_len = 0;
+ guchar* file_url_value = NULL;
+ if (gdk_property_get(context->source_window,
+ ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
+ ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
+ 0,
+ 1024,
+ FALSE,
+ NULL,
+ NULL,
+ &file_url_len,
+ &file_url_value) &&
+ file_url_value) {
+ // Convert from the file url to the file path.
+ GURL file_url(std::string(reinterpret_cast<char*>(file_url_value),
+ file_url_len));
+ g_free(file_url_value);
+ base::FilePath file_path;
+ if (net::FileURLToFilePath(file_url, &file_path)) {
+ // Open the file as a stream.
+ scoped_ptr<net::FileStream> file_stream(
+ CreateFileStreamForDrop(
+ &file_path,
+ GetContentClient()->browser()->GetNetLog()));
+ if (file_stream) {
+ // Start downloading the file to the stream.
+ scoped_refptr<DragDownloadFile> drag_file_downloader =
+ new DragDownloadFile(
+ file_path,
+ file_stream.Pass(),
+ download_url_,
+ Referrer(web_contents_->GetURL(),
+ drop_data_->referrer_policy),
+ web_contents_->GetEncoding(),
+ web_contents_);
+ drag_file_downloader->Start(
+ new PromiseFileFinalizer(drag_file_downloader.get()));
+
+ // Set the status code to success.
+ status_code = 'S';
+ }
+ }
+
+ // Return the status code to the file manager.
+ gtk_selection_data_set(selection_data,
+ gtk_selection_data_get_target(selection_data),
+ kBitsPerByte,
+ reinterpret_cast<guchar*>(&status_code),
+ 1);
+ }
+ break;
+ }
+
+ case ui::CUSTOM_DATA: {
+ Pickle custom_data;
+ ui::WriteCustomDataToPickle(drop_data_->custom_data, &custom_data);
+ gtk_selection_data_set(
+ selection_data,
+ ui::GetAtomForTarget(ui::CUSTOM_DATA),
+ kBitsPerByte,
+ reinterpret_cast<const guchar*>(custom_data.data()),
+ custom_data.size());
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+}
+
+gboolean WebDragSourceGtk::OnDragFailed(GtkWidget* sender,
+ GdkDragContext* context,
+ GtkDragResult result) {
+ drag_failed_ = true;
+
+ gfx::Point root = ui::ScreenPoint(GetContentNativeView());
+ gfx::Point client = ui::ClientPoint(GetContentNativeView());
+
+ if (web_contents_) {
+ web_contents_->DragSourceEndedAt(
+ client.x(), client.y(), root.x(), root.y(),
+ WebDragOperationNone);
+ }
+
+ // Let the native failure animation run.
+ return FALSE;
+}
+
+void WebDragSourceGtk::OnDragBegin(GtkWidget* sender,
+ GdkDragContext* drag_context) {
+ if (!download_url_.is_empty()) {
+ // Generate the file name based on both mime type and proposed file name.
+ std::string default_name =
+ GetContentClient()->browser()->GetDefaultDownloadName();
+ base::FilePath generated_download_file_name =
+ net::GenerateFileName(download_url_,
+ std::string(),
+ std::string(),
+ download_file_name_.value(),
+ UTF16ToUTF8(wide_download_mime_type_),
+ default_name);
+
+ // Pass the file name to the drop target by setting the source window's
+ // XdndDirectSave0 property.
+ gdk_property_change(drag_context->source_window,
+ ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE),
+ ui::GetAtomForTarget(ui::TEXT_PLAIN_NO_CHARSET),
+ 8,
+ GDK_PROP_MODE_REPLACE,
+ reinterpret_cast<const guchar*>(
+ generated_download_file_name.value().c_str()),
+ generated_download_file_name.value().length());
+ }
+
+ if (drag_pixbuf_) {
+ gtk_widget_set_size_request(drag_icon_,
+ gdk_pixbuf_get_width(drag_pixbuf_),
+ gdk_pixbuf_get_height(drag_pixbuf_));
+
+ // We only need to do this once.
+ if (!gtk_widget_get_realized(drag_icon_)) {
+ GdkScreen* screen = gtk_widget_get_screen(drag_icon_);
+ GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen);
+ if (rgba)
+ gtk_widget_set_colormap(drag_icon_, rgba);
+ }
+
+ gtk_drag_set_icon_widget(drag_context, drag_icon_,
+ image_offset_.x(), image_offset_.y());
+ }
+}
+
+void WebDragSourceGtk::OnDragEnd(GtkWidget* sender,
+ GdkDragContext* drag_context) {
+ if (drag_pixbuf_) {
+ g_object_unref(drag_pixbuf_);
+ drag_pixbuf_ = NULL;
+ }
+
+ base::MessageLoopForUI::current()->RemoveObserver(this);
+
+ if (!download_url_.is_empty()) {
+ gdk_property_delete(drag_context->source_window,
+ ui::GetAtomForTarget(ui::DIRECT_SAVE_FILE));
+ }
+
+ if (!drag_failed_) {
+ gfx::Point root = ui::ScreenPoint(GetContentNativeView());
+ gfx::Point client = ui::ClientPoint(GetContentNativeView());
+
+ if (web_contents_) {
+ web_contents_->DragSourceEndedAt(
+ client.x(), client.y(), root.x(), root.y(),
+ GdkDragActionToWebDragOp(drag_context->action));
+ }
+ }
+
+ web_contents_->SystemDragEnded();
+
+ drop_data_.reset();
+ drag_context_ = NULL;
+}
+
+gfx::NativeView WebDragSourceGtk::GetContentNativeView() const {
+ return web_contents_->GetView()->GetContentNativeView();
+}
+
+gboolean WebDragSourceGtk::OnDragIconExpose(GtkWidget* sender,
+ GdkEventExpose* event) {
+ cairo_t* cr = gdk_cairo_create(event->window);
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_clip(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ gdk_cairo_set_source_pixbuf(cr, drag_pixbuf_, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+
+ return TRUE;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_source_gtk.h b/chromium/content/browser/web_contents/web_drag_source_gtk.h
new file mode 100644
index 00000000000..bcf9a01725a
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_gtk.h
@@ -0,0 +1,115 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_GTK_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/gtk/gtk_signal_registrar.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/vector2d.h"
+#include "url/gurl.h"
+
+class SkBitmap;
+
+namespace content {
+
+class RenderViewHostImpl;
+class WebContentsImpl;
+struct DropData;
+
+// WebDragSourceGtk takes care of managing the drag from a WebContents
+// with Gtk.
+class CONTENT_EXPORT WebDragSourceGtk :
+ public base::MessageLoopForUI::Observer {
+ public:
+ explicit WebDragSourceGtk(WebContents* web_contents);
+ virtual ~WebDragSourceGtk();
+
+ // Starts a drag for the WebContents this WebDragSourceGtk was created for.
+ // Returns false if the drag could not be started.
+ bool StartDragging(const DropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops,
+ GdkEventButton* last_mouse_down,
+ const SkBitmap& image,
+ const gfx::Vector2d& image_offset);
+
+ // MessageLoop::Observer implementation:
+ virtual void WillProcessEvent(GdkEvent* event) OVERRIDE;
+ virtual void DidProcessEvent(GdkEvent* event) OVERRIDE;
+
+ private:
+ CHROMEGTK_CALLBACK_2(WebDragSourceGtk, gboolean, OnDragFailed,
+ GdkDragContext*, GtkDragResult);
+ CHROMEGTK_CALLBACK_1(WebDragSourceGtk, void, OnDragBegin,
+ GdkDragContext*);
+ CHROMEGTK_CALLBACK_1(WebDragSourceGtk, void, OnDragEnd,
+ GdkDragContext*);
+ CHROMEGTK_CALLBACK_4(WebDragSourceGtk, void, OnDragDataGet,
+ GdkDragContext*, GtkSelectionData*, guint, guint);
+ CHROMEGTK_CALLBACK_1(WebDragSourceGtk, gboolean, OnDragIconExpose,
+ GdkEventExpose*);
+
+ gfx::NativeView GetContentNativeView() const;
+
+ // The tab we're manging the drag for.
+ WebContentsImpl* web_contents_;
+
+ // The drop data for the current drag (for drags that originate in the render
+ // view). Non-NULL iff there is a current drag.
+ scoped_ptr<DropData> drop_data_;
+
+ // The image used for depicting the drag, and the offset between the cursor
+ // and the top left pixel.
+ GdkPixbuf* drag_pixbuf_;
+ gfx::Vector2d image_offset_;
+
+ // The mime type for the file contents of the current drag (if any).
+ GdkAtom drag_file_mime_type_;
+
+ // Whether the current drag has failed. Meaningless if we are not the source
+ // for a current drag.
+ bool drag_failed_;
+
+ // This is the widget we use to initiate drags. Since we don't use the
+ // renderer widget, we can persist drags even when our contents is switched
+ // out. We can't use an OwnedWidgetGtk because the GtkInvisible widget
+ // initialization code sinks the reference.
+ GtkWidget* drag_widget_;
+
+ // Context created once drag starts. A NULL value indicates that there is
+ // no drag currently in progress.
+ GdkDragContext* drag_context_;
+
+ // The file mime type for a drag-out download.
+ string16 wide_download_mime_type_;
+
+ // The file name to be saved to for a drag-out download.
+ base::FilePath download_file_name_;
+
+ // The URL to download from for a drag-out download.
+ GURL download_url_;
+
+ // The widget that provides visual feedback for the drag. We can't use
+ // an OwnedWidgetGtk because the GtkWindow initialization code sinks
+ // the reference.
+ GtkWidget* drag_icon_;
+
+ ui::GtkSignalRegistrar signals_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDragSourceGtk);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_GTK_H_
diff --git a/chromium/content/browser/web_contents/web_drag_source_mac.h b/chromium/content/browser/web_contents/web_drag_source_mac.h
new file mode 100644
index 00000000000..4b18953abf6
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_mac.h
@@ -0,0 +1,89 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/files/file_path.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContentsImpl;
+struct DropData;
+}
+
+// A class that handles tracking and event processing for a drag and drop
+// originating from the content area.
+CONTENT_EXPORT
+@interface WebDragSource : NSObject {
+ @private
+ // Our contents. Weak reference (owns or co-owns us).
+ content::WebContentsImpl* contents_;
+
+ // The view from which the drag was initiated. Weak reference.
+ NSView* contentsView_;
+
+ // Our drop data. Should only be initialized once.
+ scoped_ptr<content::DropData> dropData_;
+
+ // The image to show as drag image. Can be nil.
+ base::scoped_nsobject<NSImage> dragImage_;
+
+ // The offset to draw |dragImage_| at.
+ NSPoint imageOffset_;
+
+ // Our pasteboard.
+ base::scoped_nsobject<NSPasteboard> pasteboard_;
+
+ // A mask of the allowed drag operations.
+ NSDragOperation dragOperationMask_;
+
+ // The file name to be saved to for a drag-out download.
+ base::FilePath downloadFileName_;
+
+ // The URL to download from for a drag-out download.
+ GURL downloadURL_;
+
+ // The file UTI associated with the file drag, if any.
+ base::ScopedCFTypeRef<CFStringRef> fileUTI_;
+}
+
+// Initialize a WebDragSource object for a drag (originating on the given
+// contentsView and with the given dropData and pboard). Fill the pasteboard
+// with data types appropriate for dropData.
+- (id)initWithContents:(content::WebContentsImpl*)contents
+ view:(NSView*)contentsView
+ dropData:(const content::DropData*)dropData
+ image:(NSImage*)image
+ offset:(NSPoint)offset
+ pasteboard:(NSPasteboard*)pboard
+ dragOperationMask:(NSDragOperation)dragOperationMask;
+
+// Call when the web contents is gone.
+- (void)clearWebContentsView;
+
+// Returns a mask of the allowed drag operations.
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
+
+// Call when asked to do a lazy write to the pasteboard; hook up to
+// -pasteboard:provideDataForType: (on the contentsView).
+- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard
+ forType:(NSString*)type;
+
+// Start the drag (on the originally provided contentsView); can do this right
+// after -initWithContentsView:....
+- (void)startDrag;
+
+// End the drag and clear the pasteboard; hook up to
+// -draggedImage:endedAt:operation:.
+- (void)endDragAt:(NSPoint)screenPoint
+ operation:(NSDragOperation)operation;
+
+// Drag moved; hook up to -draggedImage:movedTo:.
+- (void)moveDragTo:(NSPoint)screenPoint;
+
+@end
diff --git a/chromium/content/browser/web_contents/web_drag_source_mac.mm b/chromium/content/browser/web_contents/web_drag_source_mac.mm
new file mode 100644
index 00000000000..6a31fe26c46
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_mac.mm
@@ -0,0 +1,511 @@
+// 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.
+
+#import "content/browser/web_contents/web_drag_source_mac.h"
+
+#include <sys/param.h>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/pickle.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/browser/browser_thread_impl.h"
+#include "content/browser/download/drag_download_file.h"
+#include "content/browser/download/drag_download_util.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/common/url_constants.h"
+#include "grit/ui_resources.h"
+#include "net/base/escape.h"
+#include "net/base/file_stream.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_util.h"
+#include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/dragdrop/cocoa_dnd_util.h"
+#include "ui/gfx/image/image.h"
+
+using base::SysNSStringToUTF8;
+using base::SysUTF8ToNSString;
+using base::SysUTF16ToNSString;
+using content::BrowserThread;
+using content::DragDownloadFile;
+using content::DropData;
+using content::PromiseFileFinalizer;
+using content::RenderViewHostImpl;
+using net::FileStream;
+
+namespace {
+
+// An unofficial standard pasteboard title type to be provided alongside the
+// |NSURLPboardType|.
+NSString* const kNSURLTitlePboardType = @"public.url-name";
+
+// Converts a string16 into a FilePath. Use this method instead of
+// -[NSString fileSystemRepresentation] to prevent exceptions from being thrown.
+// See http://crbug.com/78782 for more info.
+base::FilePath FilePathFromFilename(const string16& filename) {
+ NSString* str = SysUTF16ToNSString(filename);
+ char buf[MAXPATHLEN];
+ if (![str getFileSystemRepresentation:buf maxLength:sizeof(buf)])
+ return base::FilePath();
+ return base::FilePath(buf);
+}
+
+// Returns a filename appropriate for the drop data
+// TODO(viettrungluu): Refactor to make it common across platforms,
+// and move it somewhere sensible.
+base::FilePath GetFileNameFromDragData(const DropData& drop_data) {
+ base::FilePath file_name(
+ FilePathFromFilename(drop_data.file_description_filename));
+
+ // Images without ALT text will only have a file extension so we need to
+ // synthesize one from the provided extension and URL.
+ if (file_name.empty()) {
+ // Retrieve the name from the URL.
+ string16 suggested_filename =
+ net::GetSuggestedFilename(drop_data.url, "", "", "", "", "");
+ const std::string extension = file_name.Extension();
+ file_name = FilePathFromFilename(suggested_filename);
+ file_name = file_name.ReplaceExtension(extension);
+ }
+
+ return file_name;
+}
+
+// This helper's sole task is to write out data for a promised file; the caller
+// is responsible for opening the file. It takes the drop data and an open file
+// stream.
+void PromiseWriterHelper(const DropData& drop_data,
+ scoped_ptr<FileStream> file_stream) {
+ DCHECK(file_stream);
+ file_stream->WriteSync(drop_data.file_contents.data(),
+ drop_data.file_contents.length());
+}
+
+// Returns the drop location from a pasteboard.
+NSString* GetDropLocation(NSPasteboard* pboard) {
+ // The API to get the drop location during a callback from
+ // kPasteboardTypeFileURLPromise is PasteboardCopyPasteLocation, which takes
+ // a PasteboardRef, which isn't bridged with NSPasteboard. Ugh.
+
+ PasteboardRef pb_ref = NULL;
+ OSStatus status = PasteboardCreate(base::mac::NSToCFCast([pboard name]),
+ &pb_ref);
+ if (status != noErr || !pb_ref) {
+ OSSTATUS_DCHECK(false, status) << "Error finding Carbon pasteboard";
+ return nil;
+ }
+ base::ScopedCFTypeRef<PasteboardRef> pb_ref_scoper(pb_ref);
+ PasteboardSynchronize(pb_ref);
+
+ CFURLRef drop_url = NULL;
+ status = PasteboardCopyPasteLocation(pb_ref, &drop_url);
+ if (status != noErr || !drop_url) {
+ OSSTATUS_DCHECK(false, status) << "Error finding drop location";
+ return nil;
+ }
+ base::ScopedCFTypeRef<CFURLRef> drop_url_scoper(drop_url);
+
+ NSString* drop_path = [base::mac::CFToNSCast(drop_url) path];
+ return drop_path;
+}
+
+} // namespace
+
+
+@interface WebDragSource(Private)
+
+- (void)writePromisedFileTo:(NSString*)path;
+- (void)fillPasteboard;
+- (NSImage*)dragImage;
+
+@end // @interface WebDragSource(Private)
+
+
+@implementation WebDragSource
+
+- (id)initWithContents:(content::WebContentsImpl*)contents
+ view:(NSView*)contentsView
+ dropData:(const DropData*)dropData
+ image:(NSImage*)image
+ offset:(NSPoint)offset
+ pasteboard:(NSPasteboard*)pboard
+ dragOperationMask:(NSDragOperation)dragOperationMask {
+ if ((self = [super init])) {
+ contents_ = contents;
+ DCHECK(contents_);
+
+ contentsView_ = contentsView;
+ DCHECK(contentsView_);
+
+ dropData_.reset(new DropData(*dropData));
+ DCHECK(dropData_.get());
+
+ dragImage_.reset([image retain]);
+ imageOffset_ = offset;
+
+ pasteboard_.reset([pboard retain]);
+ DCHECK(pasteboard_.get());
+
+ dragOperationMask_ = dragOperationMask;
+
+ [self fillPasteboard];
+ }
+
+ return self;
+}
+
+- (void)clearWebContentsView {
+ contents_ = nil;
+ contentsView_ = nil;
+}
+
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
+ return dragOperationMask_;
+}
+
+- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard forType:(NSString*)type {
+ // NSHTMLPboardType requires the character set to be declared. Otherwise, it
+ // assumes US-ASCII. Awesome.
+ const string16 kHtmlHeader = ASCIIToUTF16(
+ "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">");
+
+ // Be extra paranoid; avoid crashing.
+ if (!dropData_) {
+ NOTREACHED();
+ return;
+ }
+
+ // HTML.
+ if ([type isEqualToString:NSHTMLPboardType] ||
+ [type isEqualToString:ui::kChromeDragImageHTMLPboardType]) {
+ DCHECK(!dropData_->html.string().empty());
+ // See comment on |kHtmlHeader| above.
+ [pboard setString:SysUTF16ToNSString(kHtmlHeader + dropData_->html.string())
+ forType:type];
+
+ // URL.
+ } else if ([type isEqualToString:NSURLPboardType]) {
+ DCHECK(dropData_->url.is_valid());
+ NSURL* url = [NSURL URLWithString:SysUTF8ToNSString(dropData_->url.spec())];
+ // If NSURL creation failed, check for a badly-escaped JavaScript URL.
+ // Strip out any existing escapes and then re-escape uniformly.
+ if (!url && dropData_->url.SchemeIs(chrome::kJavaScriptScheme)) {
+ net::UnescapeRule::Type unescapeRules =
+ net::UnescapeRule::SPACES |
+ net::UnescapeRule::URL_SPECIAL_CHARS |
+ net::UnescapeRule::CONTROL_CHARS;
+ std::string unescapedUrlString =
+ net::UnescapeURLComponent(dropData_->url.spec(), unescapeRules);
+ std::string escapedUrlString =
+ net::EscapeUrlEncodedData(unescapedUrlString, false);
+ url = [NSURL URLWithString:SysUTF8ToNSString(escapedUrlString)];
+ }
+ [url writeToPasteboard:pboard];
+ // URL title.
+ } else if ([type isEqualToString:kNSURLTitlePboardType]) {
+ [pboard setString:SysUTF16ToNSString(dropData_->url_title)
+ forType:kNSURLTitlePboardType];
+
+ // File contents.
+ } else if ([type isEqualToString:base::mac::CFToNSCast(fileUTI_)]) {
+ [pboard setData:[NSData dataWithBytes:dropData_->file_contents.data()
+ length:dropData_->file_contents.length()]
+ forType:base::mac::CFToNSCast(fileUTI_.get())];
+
+ // Plain text.
+ } else if ([type isEqualToString:NSStringPboardType]) {
+ DCHECK(!dropData_->text.string().empty());
+ [pboard setString:SysUTF16ToNSString(dropData_->text.string())
+ forType:NSStringPboardType];
+
+ // Custom MIME data.
+ } else if ([type isEqualToString:ui::kWebCustomDataPboardType]) {
+ Pickle pickle;
+ ui::WriteCustomDataToPickle(dropData_->custom_data, &pickle);
+ [pboard setData:[NSData dataWithBytes:pickle.data() length:pickle.size()]
+ forType:ui::kWebCustomDataPboardType];
+
+ // Dummy type.
+ } else if ([type isEqualToString:ui::kChromeDragDummyPboardType]) {
+ // The dummy type _was_ promised and someone decided to call the bluff.
+ [pboard setData:[NSData data]
+ forType:ui::kChromeDragDummyPboardType];
+
+ // File promise.
+ } else if ([type isEqualToString:
+ base::mac::CFToNSCast(kPasteboardTypeFileURLPromise)]) {
+ NSString* destination = GetDropLocation(pboard);
+ if (destination) {
+ [self writePromisedFileTo:destination];
+
+ // And set some data.
+ [pboard setData:[NSData data]
+ forType:base::mac::CFToNSCast(kPasteboardTypeFileURLPromise)];
+ }
+
+ // Oops!
+ } else {
+ // Unknown drag pasteboard type.
+ NOTREACHED();
+ }
+}
+
+- (NSPoint)convertScreenPoint:(NSPoint)screenPoint {
+ DCHECK([contentsView_ window]);
+ NSPoint basePoint = [[contentsView_ window] convertScreenToBase:screenPoint];
+ return [contentsView_ convertPoint:basePoint fromView:nil];
+}
+
+- (void)startDrag {
+ NSEvent* currentEvent = [NSApp currentEvent];
+
+ // Synthesize an event for dragging, since we can't be sure that
+ // [NSApp currentEvent] will return a valid dragging event.
+ NSWindow* window = [contentsView_ window];
+ NSPoint position = [window mouseLocationOutsideOfEventStream];
+ NSTimeInterval eventTime = [currentEvent timestamp];
+ NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
+ location:position
+ modifierFlags:NSLeftMouseDraggedMask
+ timestamp:eventTime
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:1.0];
+
+ if (dragImage_) {
+ position.x -= imageOffset_.x;
+ // Deal with Cocoa's flipped coordinate system.
+ position.y -= [dragImage_.get() size].height - imageOffset_.y;
+ }
+ // Per kwebster, offset arg is ignored, see -_web_DragImageForElement: in
+ // third_party/WebKit/Source/WebKit/mac/Misc/WebNSViewExtras.m.
+ [window dragImage:[self dragImage]
+ at:position
+ offset:NSZeroSize
+ event:dragEvent
+ pasteboard:pasteboard_
+ source:contentsView_
+ slideBack:YES];
+}
+
+- (void)endDragAt:(NSPoint)screenPoint
+ operation:(NSDragOperation)operation {
+ if (!contents_)
+ return;
+ contents_->SystemDragEnded();
+
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ contents_->GetRenderViewHost());
+ if (rvh) {
+ // Convert |screenPoint| to view coordinates and flip it.
+ NSPoint localPoint = NSZeroPoint;
+ if ([contentsView_ window])
+ localPoint = [self convertScreenPoint:screenPoint];
+ NSRect viewFrame = [contentsView_ frame];
+ localPoint.y = viewFrame.size.height - localPoint.y;
+ // Flip |screenPoint|.
+ NSRect screenFrame = [[[contentsView_ window] screen] frame];
+ screenPoint.y = screenFrame.size.height - screenPoint.y;
+
+ // If AppKit returns a copy and move operation, mask off the move bit
+ // because WebCore does not understand what it means to do both, which
+ // results in an assertion failure/renderer crash.
+ if (operation == (NSDragOperationMove | NSDragOperationCopy))
+ operation &= ~NSDragOperationMove;
+
+ contents_->DragSourceEndedAt(localPoint.x, localPoint.y, screenPoint.x,
+ screenPoint.y, static_cast<WebKit::WebDragOperation>(operation));
+ }
+
+ // Make sure the pasteboard owner isn't us.
+ [pasteboard_ declareTypes:[NSArray array] owner:nil];
+}
+
+- (void)moveDragTo:(NSPoint)screenPoint {
+ if (!contents_)
+ return;
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ contents_->GetRenderViewHost());
+ if (rvh) {
+ // Convert |screenPoint| to view coordinates and flip it.
+ NSPoint localPoint = NSZeroPoint;
+ if ([contentsView_ window])
+ localPoint = [self convertScreenPoint:screenPoint];
+ NSRect viewFrame = [contentsView_ frame];
+ localPoint.y = viewFrame.size.height - localPoint.y;
+ // Flip |screenPoint|.
+ NSRect screenFrame = [[[contentsView_ window] screen] frame];
+ screenPoint.y = screenFrame.size.height - screenPoint.y;
+
+ contents_->DragSourceMovedTo(localPoint.x, localPoint.y,
+ screenPoint.x, screenPoint.y);
+ }
+}
+
+@end // @implementation WebDragSource
+
+@implementation WebDragSource (Private)
+
+- (void)writePromisedFileTo:(NSString*)path {
+ // Be extra paranoid; avoid crashing.
+ if (!dropData_) {
+ NOTREACHED() << "No drag-and-drop data available for promised file.";
+ return;
+ }
+
+ base::FilePath filePath(SysNSStringToUTF8(path));
+ filePath = filePath.Append(downloadFileName_);
+
+ // CreateFileStreamForDrop() will call base::PathExists(),
+ // which is blocking. Since this operation is already blocking the
+ // UI thread on OSX, it should be reasonable to let it happen.
+ base::ThreadRestrictions::ScopedAllowIO allowIO;
+ scoped_ptr<FileStream> fileStream(content::CreateFileStreamForDrop(
+ &filePath, content::GetContentClient()->browser()->GetNetLog()));
+ if (!fileStream)
+ return;
+
+ if (downloadURL_.is_valid()) {
+ scoped_refptr<DragDownloadFile> dragFileDownloader(new DragDownloadFile(
+ filePath,
+ fileStream.Pass(),
+ downloadURL_,
+ content::Referrer(contents_->GetLastCommittedURL(),
+ dropData_->referrer_policy),
+ contents_->GetEncoding(),
+ contents_));
+
+ // The finalizer will take care of closing and deletion.
+ dragFileDownloader->Start(new PromiseFileFinalizer(
+ dragFileDownloader.get()));
+ } else {
+ // The writer will take care of closing and deletion.
+ BrowserThread::PostTask(BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&PromiseWriterHelper,
+ *dropData_,
+ base::Passed(&fileStream)));
+ }
+}
+
+- (void)fillPasteboard {
+ DCHECK(pasteboard_.get());
+
+ [pasteboard_ declareTypes:@[ui::kChromeDragDummyPboardType]
+ owner:contentsView_];
+
+ // URL (and title).
+ if (dropData_->url.is_valid()) {
+ [pasteboard_ addTypes:@[NSURLPboardType, kNSURLTitlePboardType]
+ owner:contentsView_];
+ }
+
+ // MIME type.
+ std::string mimeType;
+
+ // File.
+ if (!dropData_->file_contents.empty() ||
+ !dropData_->download_metadata.empty()) {
+ if (dropData_->download_metadata.empty()) {
+ downloadFileName_ = GetFileNameFromDragData(*dropData_);
+ net::GetMimeTypeFromExtension(downloadFileName_.Extension(), &mimeType);
+ } else {
+ string16 mimeType16;
+ base::FilePath fileName;
+ if (content::ParseDownloadMetadata(
+ dropData_->download_metadata,
+ &mimeType16,
+ &fileName,
+ &downloadURL_)) {
+ // Generate the file name based on both mime type and proposed file
+ // name.
+ std::string defaultName =
+ content::GetContentClient()->browser()->GetDefaultDownloadName();
+ mimeType = UTF16ToUTF8(mimeType16);
+ downloadFileName_ =
+ net::GenerateFileName(downloadURL_,
+ std::string(),
+ std::string(),
+ fileName.value(),
+ mimeType,
+ defaultName);
+ }
+ }
+
+ if (!mimeType.empty()) {
+ base::ScopedCFTypeRef<CFStringRef> mimeTypeCF(
+ base::SysUTF8ToCFStringRef(mimeType));
+ fileUTI_.reset(UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassMIMEType, mimeTypeCF.get(), NULL));
+
+ NSArray* types =
+ @[base::mac::CFToNSCast(kPasteboardTypeFileURLPromise),
+ base::mac::CFToNSCast(kPasteboardTypeFilePromiseContent)];
+ [pasteboard_ addTypes:types owner:contentsView_];
+
+ [pasteboard_ setPropertyList:@[base::mac::CFToNSCast(fileUTI_.get())]
+ forType:base::mac::CFToNSCast(kPasteboardTypeFilePromiseContent)];
+
+ if (!dropData_->file_contents.empty()) {
+ NSArray* types = @[base::mac::CFToNSCast(fileUTI_.get())];
+ [pasteboard_ addTypes:types owner:contentsView_];
+ }
+ }
+ }
+
+ // HTML.
+ bool hasHTMLData = !dropData_->html.string().empty();
+ // Mail.app and TextEdit accept drags that have both HTML and image flavors on
+ // them, but don't process them correctly <http://crbug.com/55879>. Therefore,
+ // if there is an image flavor, don't put the HTML data on as HTML, but rather
+ // put it on as this Chrome-only flavor.
+ //
+ // (The only time that Blink fills in the DropData::file_contents is with
+ // an image drop, but the MIME time is tested anyway for paranoia's sake.)
+ bool hasImageData = !dropData_->file_contents.empty() &&
+ fileUTI_ &&
+ UTTypeConformsTo(fileUTI_.get(), kUTTypeImage);
+ if (hasHTMLData) {
+ if (hasImageData) {
+ [pasteboard_ addTypes:@[ui::kChromeDragImageHTMLPboardType]
+ owner:contentsView_];
+ } else {
+ [pasteboard_ addTypes:@[NSHTMLPboardType] owner:contentsView_];
+ }
+ }
+
+ // Plain text.
+ if (!dropData_->text.string().empty()) {
+ [pasteboard_ addTypes:@[NSStringPboardType]
+ owner:contentsView_];
+ }
+
+ if (!dropData_->custom_data.empty()) {
+ [pasteboard_ addTypes:@[ui::kWebCustomDataPboardType]
+ owner:contentsView_];
+ }
+}
+
+- (NSImage*)dragImage {
+ if (dragImage_)
+ return dragImage_;
+
+ // Default to returning a generic image.
+ return content::GetContentClient()->GetNativeImageNamed(
+ IDR_DEFAULT_FAVICON).ToNSImage();
+}
+
+@end // @implementation WebDragSource (Private)
diff --git a/chromium/content/browser/web_contents/web_drag_source_mac_unittest.mm b/chromium/content/browser/web_contents/web_drag_source_mac_unittest.mm
new file mode 100644
index 00000000000..036c9268f8e
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_mac_unittest.mm
@@ -0,0 +1,41 @@
+// 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.
+
+#import "content/browser/web_contents/web_drag_source_mac.h"
+
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/common/drop_data.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+typedef RenderViewHostTestHarness WebDragSourceMacTest;
+
+TEST_F(WebDragSourceMacTest, DragInvalidlyEscapedBookmarklet) {
+ scoped_ptr<WebContents> contents(CreateTestWebContents());
+ base::scoped_nsobject<NSView> view(
+ [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 10, 10)]);
+
+ scoped_ptr<DropData> dropData(new DropData);
+ dropData->url = GURL("javascript:%");
+
+ WebContentsImpl* contentsImpl = static_cast<WebContentsImpl*>(contents.get());
+ base::scoped_nsobject<WebDragSource> source([[WebDragSource alloc]
+ initWithContents:contentsImpl
+ view:view
+ dropData:dropData.get()
+ image:nil
+ offset:NSZeroPoint
+ pasteboard:[NSPasteboard pasteboardWithUniqueName]
+ dragOperationMask:NSDragOperationCopy]);
+
+ // Test that this call doesn't throw any exceptions: http://crbug.com/128371
+ base::scoped_nsobject<NSPasteboard> pasteboard(
+ [NSPasteboard pasteboardWithUniqueName]);
+ [source lazyWriteToPasteboard:pasteboard forType:NSURLPboardType];
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_source_win.cc b/chromium/content/browser/web_contents/web_drag_source_win.cc
new file mode 100644
index 00000000000..53df4e81725
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_win.cc
@@ -0,0 +1,130 @@
+// 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 "content/browser/web_contents/web_drag_source_win.h"
+
+#include "base/bind.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/web_contents/web_drag_utils_win.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+
+using WebKit::WebDragOperationNone;
+
+namespace content {
+namespace {
+
+static void GetCursorPositions(gfx::NativeWindow wnd, gfx::Point* client,
+ gfx::Point* screen) {
+ POINT cursor_pos;
+ GetCursorPos(&cursor_pos);
+ screen->SetPoint(cursor_pos.x, cursor_pos.y);
+ ScreenToClient(wnd, &cursor_pos);
+ client->SetPoint(cursor_pos.x, cursor_pos.y);
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// WebDragSource, public:
+
+WebDragSource::WebDragSource(gfx::NativeWindow source_wnd,
+ WebContents* web_contents)
+ : ui::DragSourceWin(),
+ source_wnd_(source_wnd),
+ web_contents_(static_cast<WebContentsImpl*>(web_contents)),
+ effect_(DROPEFFECT_NONE),
+ data_(NULL) {
+ registrar_.Add(this, NOTIFICATION_WEB_CONTENTS_SWAPPED,
+ Source<WebContents>(web_contents));
+ registrar_.Add(this, NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
+ Source<WebContents>(web_contents));
+}
+
+WebDragSource::~WebDragSource() {
+}
+
+void WebDragSource::OnDragSourceCancel() {
+ // Delegate to the UI thread if we do drag-and-drop in the background thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebDragSource::OnDragSourceCancel, this));
+ return;
+ }
+
+ if (!web_contents_)
+ return;
+
+ gfx::Point client;
+ gfx::Point screen;
+ GetCursorPositions(source_wnd_, &client, &screen);
+ web_contents_->DragSourceEndedAt(client.x(), client.y(),
+ screen.x(), screen.y(),
+ WebDragOperationNone);
+}
+
+void WebDragSource::OnDragSourceDrop() {
+ DCHECK(data_);
+ data_->SetInDragLoop(false);
+ // On Windows, we check for drag end in IDropSource::QueryContinueDrag which
+ // happens before IDropTarget::Drop is called. HTML5 requires the "dragend"
+ // event to happen after the "drop" event. Since Windows calls these two
+ // directly after each other we can just post a task to handle the
+ // OnDragSourceDrop after the current task.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebDragSource::DelayedOnDragSourceDrop, this));
+}
+
+void WebDragSource::DelayedOnDragSourceDrop() {
+ if (!web_contents_)
+ return;
+
+ gfx::Point client;
+ gfx::Point screen;
+ GetCursorPositions(source_wnd_, &client, &screen);
+ web_contents_->DragSourceEndedAt(client.x(), client.y(), screen.x(),
+ screen.y(), WinDragOpToWebDragOp(effect_));
+}
+
+void WebDragSource::OnDragSourceMove() {
+ // Delegate to the UI thread if we do drag-and-drop in the background thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WebDragSource::OnDragSourceMove, this));
+ return;
+ }
+
+ if (!web_contents_)
+ return;
+
+ gfx::Point client;
+ gfx::Point screen;
+ GetCursorPositions(source_wnd_, &client, &screen);
+ web_contents_->DragSourceMovedTo(client.x(), client.y(),
+ screen.x(), screen.y());
+}
+
+void WebDragSource::Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NOTIFICATION_WEB_CONTENTS_SWAPPED) {
+ // When the WebContents get swapped, our render view host goes away.
+ // That's OK, we can continue the drag, we just can't send messages back to
+ // our drag source.
+ web_contents_ = NULL;
+ } else if (type == NOTIFICATION_WEB_CONTENTS_DISCONNECTED) {
+ // This could be possible when we close the tab and the source is still
+ // being used in DoDragDrop at the time that the virtual file is being
+ // downloaded.
+ web_contents_ = NULL;
+ }
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_source_win.h b/chromium/content/browser/web_contents/web_drag_source_win.h
new file mode 100644
index 00000000000..72d4a1692a2
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_source_win.h
@@ -0,0 +1,79 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_WIN_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_WIN_H_
+
+#include "base/basictypes.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/base/dragdrop/drag_source_win.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+class OSExchangeData;
+} // namespace ui
+
+namespace content {
+class RenderViewHost;
+class WebContents;
+class WebContentsImpl;
+
+// An IDropSource implementation for a WebContentsImpl. Handles notifications
+// sent by an active drag-drop operation as the user mouses over other drop
+// targets on their system. This object tells Windows whether or not the drag
+// should continue, and supplies the appropriate cursors.
+class WebDragSource : public ui::DragSourceWin,
+ public NotificationObserver {
+ public:
+ // Create a new DragSource for a given HWND and WebContents.
+ WebDragSource(gfx::NativeWindow source_wnd, WebContents* web_contents);
+ virtual ~WebDragSource();
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ void set_effect(DWORD effect) { effect_ = effect; }
+ // Used to set the active data object for the current drag operation. The
+ // caller must ensure that |data| is not destroyed before the nested drag loop
+ // terminates.
+ void set_data(ui::OSExchangeData* data) { data_ = data; }
+
+ protected:
+ // ui::DragSourceWin
+ virtual void OnDragSourceCancel();
+ virtual void OnDragSourceDrop();
+ virtual void OnDragSourceMove();
+
+ private:
+ // Cannot construct thusly.
+ WebDragSource();
+
+ // OnDragSourceDrop schedules its main work to be done after IDropTarget::Drop
+ // by posting a task to this function.
+ void DelayedOnDragSourceDrop();
+
+ // Keep a reference to the window so we can translate the cursor position.
+ gfx::NativeWindow source_wnd_;
+
+ // We use this as a channel to the renderer to tell it about various drag
+ // drop events that it needs to know about (such as when a drag operation it
+ // initiated terminates).
+ WebContentsImpl* web_contents_;
+
+ NotificationRegistrar registrar_;
+
+ DWORD effect_;
+
+ ui::OSExchangeData* data_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDragSource);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_SOURCE_WIN_H_
diff --git a/chromium/content/browser/web_contents/web_drag_utils_win.cc b/chromium/content/browser/web_contents/web_drag_utils_win.cc
new file mode 100644
index 00000000000..bada2456f86
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_utils_win.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2010 The Chromium 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 "content/browser/web_contents/web_drag_utils_win.h"
+
+#include <oleidl.h>
+#include "base/logging.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+using WebKit::WebDragOperationNone;
+using WebKit::WebDragOperationCopy;
+using WebKit::WebDragOperationLink;
+using WebKit::WebDragOperationMove;
+using WebKit::WebDragOperationGeneric;
+
+namespace content {
+
+WebDragOperation WinDragOpToWebDragOp(DWORD effect) {
+ DCHECK(effect == DROPEFFECT_NONE || effect == DROPEFFECT_COPY ||
+ effect == DROPEFFECT_LINK || effect == DROPEFFECT_MOVE);
+
+ return WinDragOpMaskToWebDragOpMask(effect);
+}
+
+WebDragOperationsMask WinDragOpMaskToWebDragOpMask(DWORD effects) {
+ WebDragOperationsMask ops = WebDragOperationNone;
+ if (effects & DROPEFFECT_COPY)
+ ops = static_cast<WebDragOperationsMask>(ops | WebDragOperationCopy);
+ if (effects & DROPEFFECT_LINK)
+ ops = static_cast<WebDragOperationsMask>(ops | WebDragOperationLink);
+ if (effects & DROPEFFECT_MOVE)
+ ops = static_cast<WebDragOperationsMask>(ops | WebDragOperationMove |
+ WebDragOperationGeneric);
+ return ops;
+}
+
+DWORD WebDragOpToWinDragOp(WebDragOperation op) {
+ DCHECK(op == WebDragOperationNone ||
+ op == WebDragOperationCopy ||
+ op == WebDragOperationLink ||
+ op == WebDragOperationMove ||
+ op == (WebDragOperationMove | WebDragOperationGeneric));
+
+ return WebDragOpMaskToWinDragOpMask(op);
+}
+
+DWORD WebDragOpMaskToWinDragOpMask(WebDragOperationsMask ops) {
+ DWORD win_ops = DROPEFFECT_NONE;
+ if (ops & WebDragOperationCopy)
+ win_ops |= DROPEFFECT_COPY;
+ if (ops & WebDragOperationLink)
+ win_ops |= DROPEFFECT_LINK;
+ if (ops & (WebDragOperationMove | WebDragOperationGeneric))
+ win_ops |= DROPEFFECT_MOVE;
+ return win_ops;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/web_contents/web_drag_utils_win.h b/chromium/content/browser/web_contents/web_drag_utils_win.h
new file mode 100644
index 00000000000..8721da94511
--- /dev/null
+++ b/chromium/content/browser/web_contents/web_drag_utils_win.h
@@ -0,0 +1,22 @@
+// 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 CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_UTILS_WIN_H_
+#define CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_UTILS_WIN_H_
+
+#include "third_party/WebKit/public/web/WebDragOperation.h"
+
+#include <windows.h>
+
+namespace content {
+
+WebKit::WebDragOperation WinDragOpToWebDragOp(DWORD effect);
+WebKit::WebDragOperationsMask WinDragOpMaskToWebDragOpMask(DWORD effects);
+
+DWORD WebDragOpToWinDragOp(WebKit::WebDragOperation op);
+DWORD WebDragOpMaskToWinDragOpMask(WebKit::WebDragOperationsMask ops);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEB_CONTENTS_WEB_DRAG_UTILS_WIN_H_
diff --git a/chromium/content/browser/webkit_browsertest.cc b/chromium/content/browser/webkit_browsertest.cc
new file mode 100644
index 00000000000..24c24a60caa
--- /dev/null
+++ b/chromium/content/browser/webkit_browsertest.cc
@@ -0,0 +1,94 @@
+// 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 "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/web_contents.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+#include "content/test/net/url_request_abort_on_end_job.h"
+
+namespace content {
+
+typedef ContentBrowserTest WebKitBrowserTest;
+
+const char kAsyncScriptThatAbortsOnEndPage[] =
+ "files/webkit/async_script_abort_on_end.html";
+
+// This is a browser test because it is hard to reproduce reliably in a
+// layout test without races. http://crbug.com/75604 deals with a request
+// for an async script which gets data in the response and immediately
+// after aborts. This test creates that condition, and it is passed
+// if chrome does not crash.
+
+IN_PROC_BROWSER_TEST_F(WebKitBrowserTest, AbortOnEnd) {
+ ASSERT_TRUE(test_server()->Start());
+ URLRequestAbortOnEndJob::AddUrlHandler();
+ GURL url = test_server()->GetURL(kAsyncScriptThatAbortsOnEndPage);
+
+ NavigateToURL(shell(), url);
+
+ // If you are seeing this test fail, please strongly investigate the
+ // possibility that http://crbug.com/75604 and
+ // https://bugs.webkit.org/show_bug.cgi?id=71122 have reverted before
+ // marking this as flakey.
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+// This is a browser test because the DumpRenderTree framework holds
+// onto a Document* reference that blocks this reproduction from
+// destroying the Document, so it is not a use after free unless
+// you don't have DumpRenderTree loaded.
+
+// TODO(gavinp): remove this browser_test if we can get good LayoutTest
+// coverage of the same issue.
+const char kXsltBadImportPage[] =
+ "files/webkit/xslt-bad-import.html";
+IN_PROC_BROWSER_TEST_F(WebKitBrowserTest, XsltBadImport) {
+ ASSERT_TRUE(test_server()->Start());
+ URLRequestAbortOnEndJob::AddUrlHandler();
+ GURL url = test_server()->GetURL(kXsltBadImportPage);
+
+ NavigateToURL(shell(), url);
+
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+// This is a browser test because DumpRenderTree has a PrerendererClient
+// implementation, and the purpose of this test is to ensure that content_shell
+// does not crash when prerender elements are encountered with no Prererering
+// implementation supplied to WebKit.
+
+// TODO(gavinp,jochen): This browser_test depends on there not being a
+// prerendering client and prerendering platform provided by the test_shell.
+// But both will exist when we use content_shell to run layout tests. We must
+// then add a mechanism to start content_shell without these, or else this
+// test is not very interesting.
+const char kPrerenderNoCrashPage[] =
+ "files/prerender/prerender-no-crash.html";
+IN_PROC_BROWSER_TEST_F(WebKitBrowserTest, PrerenderNoCrash) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(kPrerenderNoCrashPage);
+
+ NavigateToURL(shell(), url);
+
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+// This is a browser test because DumpRenderTree doesn't run nested message
+// loops. The failure case was that a nested message triggered from an element
+// that has signalled an error but had an open request would receive a body for
+// the request and crash/fail an assertion.
+const char kErrorBodyNoCrash[] =
+ "files/error-body-no-crash.html";
+IN_PROC_BROWSER_TEST_F(WebKitBrowserTest, ErrorBodyNoCrash) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(kErrorBodyNoCrash);
+
+ NavigateToURL(shell(), url);
+
+ EXPECT_FALSE(shell()->web_contents()->IsCrashed());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/OWNERS b/chromium/content/browser/webui/OWNERS
new file mode 100644
index 00000000000..7661dde6cd0
--- /dev/null
+++ b/chromium/content/browser/webui/OWNERS
@@ -0,0 +1,3 @@
+arv@chromium.org
+estade@chromium.org
+jhawkins@chromium.org
diff --git a/chromium/content/browser/webui/content_web_ui_controller_factory.cc b/chromium/content/browser/webui/content_web_ui_controller_factory.cc
new file mode 100644
index 00000000000..45a38adb707
--- /dev/null
+++ b/chromium/content/browser/webui/content_web_ui_controller_factory.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 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 "content/browser/webui/content_web_ui_controller_factory.h"
+
+#include "content/browser/accessibility/accessibility_ui.h"
+#include "content/browser/gpu/gpu_internals_ui.h"
+#include "content/browser/indexed_db/indexed_db_internals_ui.h"
+#include "content/browser/media/media_internals_ui.h"
+#include "content/browser/media/webrtc_internals_ui.h"
+#include "content/browser/tracing/tracing_ui.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/common/url_constants.h"
+
+namespace content {
+
+WebUI::TypeID ContentWebUIControllerFactory::GetWebUIType(
+ BrowserContext* browser_context, const GURL& url) const {
+ if (url.host() == kChromeUIWebRTCInternalsHost ||
+#if !defined(OS_ANDROID)
+ url.host() == kChromeUITracingHost ||
+#endif
+ url.host() == kChromeUIGpuHost ||
+ url.host() == kChromeUIIndexedDBInternalsHost ||
+ url.host() == kChromeUIMediaInternalsHost ||
+ url.host() == kChromeUIAccessibilityHost) {
+ return const_cast<ContentWebUIControllerFactory*>(this);
+ }
+ return WebUI::kNoWebUI;
+}
+
+bool ContentWebUIControllerFactory::UseWebUIForURL(
+ BrowserContext* browser_context, const GURL& url) const {
+ return GetWebUIType(browser_context, url) != WebUI::kNoWebUI;
+}
+
+bool ContentWebUIControllerFactory::UseWebUIBindingsForURL(
+ BrowserContext* browser_context, const GURL& url) const {
+ return UseWebUIForURL(browser_context, url);
+}
+
+WebUIController* ContentWebUIControllerFactory::CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const {
+ if (url.host() == kChromeUIWebRTCInternalsHost)
+ return new WebRTCInternalsUI(web_ui);
+ if (url.host() == kChromeUIGpuHost)
+ return new GpuInternalsUI(web_ui);
+ if (url.host() == kChromeUIIndexedDBInternalsHost)
+ return new IndexedDBInternalsUI(web_ui);
+ if (url.host() == kChromeUIMediaInternalsHost)
+ return new MediaInternalsUI(web_ui);
+ if (url.host() == kChromeUIAccessibilityHost)
+ return new AccessibilityUI(web_ui);
+#if !defined(OS_ANDROID)
+ if (url.host() == kChromeUITracingHost)
+ return new TracingUI(web_ui);
+#endif
+
+ return NULL;
+}
+
+// static
+ContentWebUIControllerFactory* ContentWebUIControllerFactory::GetInstance() {
+ return Singleton<ContentWebUIControllerFactory>::get();
+}
+
+ContentWebUIControllerFactory::ContentWebUIControllerFactory() {
+}
+
+ContentWebUIControllerFactory::~ContentWebUIControllerFactory() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/content_web_ui_controller_factory.h b/chromium/content/browser/webui/content_web_ui_controller_factory.h
new file mode 100644
index 00000000000..cee0ec728b0
--- /dev/null
+++ b/chromium/content/browser/webui/content_web_ui_controller_factory.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEBUI_CONTENT_WEB_UI_CONTROLLER_FACTORY_H_
+#define CONTENT_BROWSER_WEBUI_CONTENT_WEB_UI_CONTROLLER_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_controller_factory.h"
+
+namespace content {
+
+class ContentWebUIControllerFactory : public WebUIControllerFactory {
+ public:
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui,
+ const GURL& url) const OVERRIDE;
+
+ static ContentWebUIControllerFactory* GetInstance();
+
+ protected:
+ ContentWebUIControllerFactory();
+ virtual ~ContentWebUIControllerFactory();
+
+ private:
+ friend struct DefaultSingletonTraits<ContentWebUIControllerFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentWebUIControllerFactory);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_CONTENT_WEB_UI_CONTROLLER_FACTORY_H_
diff --git a/chromium/content/browser/webui/generic_handler.cc b/chromium/content/browser/webui/generic_handler.cc
new file mode 100644
index 00000000000..56b9e0766c8
--- /dev/null
+++ b/chromium/content/browser/webui/generic_handler.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/webui/generic_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace content {
+
+GenericHandler::GenericHandler() {
+}
+
+GenericHandler::~GenericHandler() {
+}
+
+void GenericHandler::RegisterMessages() {
+ web_ui()->RegisterMessageCallback("navigateToUrl",
+ base::Bind(&GenericHandler::HandleNavigateToUrl, base::Unretained(this)));
+}
+
+void GenericHandler::HandleNavigateToUrl(const ListValue* args) {
+ std::string url_string;
+ std::string target_string;
+ double button;
+ bool alt_key;
+ bool ctrl_key;
+ bool meta_key;
+ bool shift_key;
+
+ CHECK(args->GetString(0, &url_string));
+ CHECK(args->GetString(1, &target_string));
+ CHECK(args->GetDouble(2, &button));
+ CHECK(args->GetBoolean(3, &alt_key));
+ CHECK(args->GetBoolean(4, &ctrl_key));
+ CHECK(args->GetBoolean(5, &meta_key));
+ CHECK(args->GetBoolean(6, &shift_key));
+
+ CHECK(button == 0.0 || button == 1.0);
+ bool middle_button = (button == 1.0);
+
+ WindowOpenDisposition disposition = ui::DispositionFromClick(
+ middle_button, alt_key, ctrl_key, meta_key, shift_key);
+ if (disposition == CURRENT_TAB && target_string == "_blank")
+ disposition = NEW_FOREGROUND_TAB;
+
+ web_ui()->GetWebContents()->OpenURL(OpenURLParams(
+ GURL(url_string), Referrer(), disposition, PAGE_TRANSITION_LINK, false));
+
+ // This may delete us!
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/generic_handler.h b/chromium/content/browser/webui/generic_handler.h
new file mode 100644
index 00000000000..431373dd3b1
--- /dev/null
+++ b/chromium/content/browser/webui/generic_handler.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_WEBUI_GENERIC_HANDLER_H_
+#define CONTENT_BROWSER_WEBUI_GENERIC_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+
+// A place to add handlers for messages shared across all WebUI pages.
+class GenericHandler : public WebUIMessageHandler {
+ public:
+ GenericHandler();
+ virtual ~GenericHandler();
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ void HandleNavigateToUrl(const base::ListValue* args);
+
+ DISALLOW_COPY_AND_ASSIGN(GenericHandler);
+};
+
+} // namespace content
+
+#endif // namespace content
diff --git a/chromium/content/browser/webui/shared_resources_data_source.cc b/chromium/content/browser/webui/shared_resources_data_source.cc
new file mode 100644
index 00000000000..23cdc3d23a3
--- /dev/null
+++ b/chromium/content/browser/webui/shared_resources_data_source.cc
@@ -0,0 +1,63 @@
+// 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 "content/browser/webui/shared_resources_data_source.h"
+
+#include "base/logging.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/url_constants.h"
+#include "grit/webui_resources_map.h"
+#include "net/base/mime_util.h"
+
+namespace {
+
+int PathToIDR(const std::string& path) {
+ int idr = -1;
+ for (size_t i = 0; i < kWebuiResourcesSize; ++i) {
+ if (kWebuiResources[i].name == path) {
+ idr = kWebuiResources[i].value;
+ break;
+ }
+ }
+
+ return idr;
+}
+
+} // namespace
+
+SharedResourcesDataSource::SharedResourcesDataSource() {
+}
+
+SharedResourcesDataSource::~SharedResourcesDataSource() {
+}
+
+std::string SharedResourcesDataSource::GetSource() const {
+ return content::kChromeUIResourcesHost;
+}
+
+void SharedResourcesDataSource::StartDataRequest(
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ const content::URLDataSource::GotDataCallback& callback) {
+ int idr = PathToIDR(path);
+ DCHECK_NE(-1, idr) << " path: " << path;
+ scoped_refptr<base::RefCountedStaticMemory> bytes(
+ content::GetContentClient()->GetDataResourceBytes(idr));
+
+ callback.Run(bytes.get());
+}
+
+std::string SharedResourcesDataSource::GetMimeType(
+ const std::string& path) const {
+ // Requests should not block on the disk! On POSIX this goes to disk.
+ // http://code.google.com/p/chromium/issues/detail?id=59849
+
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ std::string mime_type;
+ net::GetMimeTypeFromFile(base::FilePath().AppendASCII(path), &mime_type);
+ return mime_type;
+}
diff --git a/chromium/content/browser/webui/shared_resources_data_source.h b/chromium/content/browser/webui/shared_resources_data_source.h
new file mode 100644
index 00000000000..cf9c24c6eec
--- /dev/null
+++ b/chromium/content/browser/webui/shared_resources_data_source.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_WEBUI_SHARED_RESOURCES_DATA_SOURCE_H_
+#define CONTENT_BROWSER_WEBUI_SHARED_RESOURCES_DATA_SOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "content/public/browser/url_data_source.h"
+
+// A DataSource for chrome://resources/ URLs.
+class SharedResourcesDataSource : public content::URLDataSource {
+ public:
+ SharedResourcesDataSource();
+
+ // content::URLDataSource implementation.
+ virtual std::string GetSource() const OVERRIDE;
+ virtual void StartDataRequest(
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ const content::URLDataSource::GotDataCallback& callback) OVERRIDE;
+ virtual std::string GetMimeType(const std::string&) const OVERRIDE;
+
+ private:
+ virtual ~SharedResourcesDataSource();
+
+ DISALLOW_COPY_AND_ASSIGN(SharedResourcesDataSource);
+};
+
+#endif // CONTENT_BROWSER_WEBUI_SHARED_RESOURCES_DATA_SOURCE_H_
diff --git a/chromium/content/browser/webui/url_data_manager.cc b/chromium/content/browser/webui/url_data_manager.cc
new file mode 100644
index 00000000000..74fa0a67e1d
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_manager.cc
@@ -0,0 +1,133 @@
+// 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 "content/browser/webui/url_data_manager.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/webui/url_data_manager_backend.h"
+#include "content/browser/webui/url_data_source_impl.h"
+#include "content/browser/webui/web_ui_data_source_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/url_data_source.h"
+
+namespace content {
+namespace {
+
+const char kURLDataManagerKeyName[] = "url_data_manager";
+
+base::LazyInstance<base::Lock>::Leaky g_delete_lock = LAZY_INSTANCE_INITIALIZER;
+
+URLDataManager* GetFromBrowserContext(BrowserContext* context) {
+ if (!context->GetUserData(kURLDataManagerKeyName)) {
+ context->SetUserData(kURLDataManagerKeyName, new URLDataManager(context));
+ }
+ return static_cast<URLDataManager*>(
+ context->GetUserData(kURLDataManagerKeyName));
+}
+
+// Invoked on the IO thread to do the actual adding of the DataSource.
+static void AddDataSourceOnIOThread(
+ ResourceContext* resource_context,
+ scoped_refptr<URLDataSourceImpl> data_source) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ GetURLDataManagerForResourceContext(resource_context)->AddDataSource(
+ data_source.get());
+}
+
+} // namespace
+
+// static
+URLDataManager::URLDataSources* URLDataManager::data_sources_ = NULL;
+
+URLDataManager::URLDataManager(BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+}
+
+URLDataManager::~URLDataManager() {
+}
+
+void URLDataManager::AddDataSource(URLDataSourceImpl* source) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&AddDataSourceOnIOThread,
+ browser_context_->GetResourceContext(),
+ make_scoped_refptr(source)));
+}
+
+// static
+void URLDataManager::DeleteDataSources() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ URLDataSources sources;
+ {
+ base::AutoLock lock(g_delete_lock.Get());
+ if (!data_sources_)
+ return;
+ data_sources_->swap(sources);
+ }
+ for (size_t i = 0; i < sources.size(); ++i)
+ delete sources[i];
+}
+
+// static
+void URLDataManager::DeleteDataSource(const URLDataSourceImpl* data_source) {
+ // Invoked when a DataSource is no longer referenced and needs to be deleted.
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ // We're on the UI thread, delete right away.
+ delete data_source;
+ return;
+ }
+
+ // We're not on the UI thread, add the DataSource to the list of DataSources
+ // to delete.
+ bool schedule_delete = false;
+ {
+ base::AutoLock lock(g_delete_lock.Get());
+ if (!data_sources_)
+ data_sources_ = new URLDataSources();
+ schedule_delete = data_sources_->empty();
+ data_sources_->push_back(data_source);
+ }
+ if (schedule_delete) {
+ // Schedule a task to delete the DataSource back on the UI thread.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&URLDataManager::DeleteDataSources));
+ }
+}
+
+// static
+void URLDataManager::AddDataSource(BrowserContext* browser_context,
+ URLDataSource* source) {
+ GetFromBrowserContext(browser_context)->
+ AddDataSource(new URLDataSourceImpl(source->GetSource(), source));
+}
+
+// static
+void URLDataManager::AddWebUIDataSource(BrowserContext* browser_context,
+ WebUIDataSource* source) {
+ WebUIDataSourceImpl* impl = static_cast<WebUIDataSourceImpl*>(source);
+ GetFromBrowserContext(browser_context)->AddDataSource(impl);
+}
+
+// static
+bool URLDataManager::IsScheduledForDeletion(
+ const URLDataSourceImpl* data_source) {
+ base::AutoLock lock(g_delete_lock.Get());
+ if (!data_sources_)
+ return false;
+ return std::find(data_sources_->begin(), data_sources_->end(), data_source) !=
+ data_sources_->end();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/url_data_manager.h b/chromium/content/browser/webui/url_data_manager.h
new file mode 100644
index 00000000000..e94568d7c82
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_manager.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_H_
+#define CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/supports_user_data.h"
+#include "content/common/content_export.h"
+
+namespace content {
+class BrowserContext;
+class URLDataSource;
+class URLDataSourceImpl;
+class WebUIDataSource;
+
+// To serve dynamic data off of chrome: URLs, implement the
+// URLDataManager::DataSource interface and register your handler
+// with AddDataSource. DataSources must be added on the UI thread (they are also
+// deleted on the UI thread). Internally the DataSources are maintained by
+// URLDataManagerBackend, see it for details.
+class CONTENT_EXPORT URLDataManager : public base::SupportsUserData::Data {
+ public:
+ explicit URLDataManager(BrowserContext* browser_context);
+ virtual ~URLDataManager();
+
+ // Adds a DataSource to the collection of data sources. This *must* be invoked
+ // on the UI thread.
+ //
+ // If |AddDataSource| is called more than once for a particular name it will
+ // release the old |DataSource|, most likely resulting in it getting deleted
+ // as there are no other references to it. |DataSource| uses the
+ // |DeleteOnUIThread| trait to insure that the destructor is called on the UI
+ // thread. This is necessary as some |DataSource|s notably |FileIconSource|
+ // and |FaviconSource|, have members that will DCHECK if they are not
+ // destructed in the same thread as they are constructed (the UI thread).
+ void AddDataSource(URLDataSourceImpl* source);
+
+ // Deletes any data sources no longer referenced. This is normally invoked
+ // for you, but can be invoked to force deletion (such as during shutdown).
+ static void DeleteDataSources();
+
+ // Convenience wrapper function to add |source| to |browser_context|'s
+ // |URLDataManager|. Creates a URLDataSourceImpl to wrap the given
+ // source.
+ static void AddDataSource(BrowserContext* browser_context,
+ URLDataSource* source);
+
+ // Adds a WebUI data source to |browser_context|'s |URLDataManager|.
+ static void AddWebUIDataSource(BrowserContext* browser_context,
+ WebUIDataSource* source);
+
+ private:
+ friend class URLDataSourceImpl;
+ friend struct DeleteURLDataSource;
+ typedef std::vector<const URLDataSourceImpl*> URLDataSources;
+
+ // If invoked on the UI thread the DataSource is deleted immediatlye,
+ // otherwise it is added to |data_sources_| and a task is scheduled to handle
+ // deletion on the UI thread. See note abouve DeleteDataSource for more info.
+ static void DeleteDataSource(const URLDataSourceImpl* data_source);
+
+ // Returns true if |data_source| is scheduled for deletion (|DeleteDataSource|
+ // was invoked).
+ static bool IsScheduledForDeletion(const URLDataSourceImpl* data_source);
+
+ BrowserContext* browser_context_;
+
+ // |data_sources_| that are no longer referenced and scheduled for deletion.
+ // Protected by g_delete_lock in the .cc file.
+ static URLDataSources* data_sources_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLDataManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_H_
diff --git a/chromium/content/browser/webui/url_data_manager_backend.cc b/chromium/content/browser/webui/url_data_manager_backend.cc
new file mode 100644
index 00000000000..7c4c77b9bb9
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_manager_backend.cc
@@ -0,0 +1,655 @@
+// 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 "content/browser/webui/url_data_manager_backend.h"
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/histogram_internals_request_job.h"
+#include "content/browser/net/view_blob_internals_job_factory.h"
+#include "content/browser/net/view_http_cache_job_factory.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/tcmalloc_internals_request_job.h"
+#include "content/browser/webui/shared_resources_data_source.h"
+#include "content/browser/webui/url_data_source_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "url/url_util.h"
+#include "webkit/browser/appcache/view_appcache_internals_job.h"
+
+using appcache::AppCacheService;
+
+namespace content {
+
+namespace {
+
+// TODO(tsepez) remove unsafe-eval when bidichecker_packaged.js fixed.
+const char kChromeURLContentSecurityPolicyHeaderBase[] =
+ "Content-Security-Policy: script-src chrome://resources "
+ "'self' 'unsafe-eval'; ";
+
+const char kChromeURLXFrameOptionsHeader[] = "X-Frame-Options: DENY";
+
+bool SchemeIsInSchemes(const std::string& scheme,
+ const std::vector<std::string>& schemes) {
+ return std::find(schemes.begin(), schemes.end(), scheme) != schemes.end();
+}
+
+// Parse a URL into the components used to resolve its request. |source_name|
+// is the hostname and |path| is the remaining portion of the URL.
+void URLToRequest(const GURL& url, std::string* source_name,
+ std::string* path) {
+ std::vector<std::string> additional_schemes;
+ DCHECK(url.SchemeIs(chrome::kChromeDevToolsScheme) ||
+ url.SchemeIs(chrome::kChromeUIScheme) ||
+ (GetContentClient()->browser()->GetAdditionalWebUISchemes(
+ &additional_schemes),
+ SchemeIsInSchemes(url.scheme(), additional_schemes)));
+
+ if (!url.is_valid()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Our input looks like: chrome://source_name/extra_bits?foo .
+ // So the url's "host" is our source, and everything after the host is
+ // the path.
+ source_name->assign(url.host());
+
+ const std::string& spec = url.possibly_invalid_spec();
+ const url_parse::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
+ // + 1 to skip the slash at the beginning of the path.
+ int offset = parsed.CountCharactersBefore(url_parse::Parsed::PATH, false) + 1;
+
+ if (offset < static_cast<int>(spec.size()))
+ path->assign(spec.substr(offset));
+}
+
+} // namespace
+
+// URLRequestChromeJob is a net::URLRequestJob that manages running
+// chrome-internal resource requests asynchronously.
+// It hands off URL requests to ChromeURLDataManager, which asynchronously
+// calls back once the data is available.
+class URLRequestChromeJob : public net::URLRequestJob,
+ public base::SupportsWeakPtr<URLRequestChromeJob> {
+ public:
+ // |is_incognito| set when job is generated from an incognito profile.
+ URLRequestChromeJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLDataManagerBackend* backend,
+ bool is_incognito);
+
+ // net::URLRequestJob implementation.
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
+
+ // Used to notify that the requested data's |mime_type| is ready.
+ void MimeTypeAvailable(const std::string& mime_type);
+
+ // Called by ChromeURLDataManager to notify us that the data blob is ready
+ // for us.
+ void DataAvailable(base::RefCountedMemory* bytes);
+
+ void set_mime_type(const std::string& mime_type) {
+ mime_type_ = mime_type;
+ }
+
+ void set_allow_caching(bool allow_caching) {
+ allow_caching_ = allow_caching;
+ }
+
+ void set_add_content_security_policy(bool add_content_security_policy) {
+ add_content_security_policy_ = add_content_security_policy;
+ }
+
+ void set_content_security_policy_object_source(
+ const std::string& data) {
+ content_security_policy_object_source_ = data;
+ }
+
+ void set_content_security_policy_frame_source(
+ const std::string& data) {
+ content_security_policy_frame_source_ = data;
+ }
+
+ void set_deny_xframe_options(bool deny_xframe_options) {
+ deny_xframe_options_ = deny_xframe_options;
+ }
+
+ // Returns true when job was generated from an incognito profile.
+ bool is_incognito() const {
+ return is_incognito_;
+ }
+
+ private:
+ virtual ~URLRequestChromeJob();
+
+ // Helper for Start(), to let us start asynchronously.
+ // (This pattern is shared by most net::URLRequestJob implementations.)
+ void StartAsync();
+
+ // Do the actual copy from data_ (the data we're serving) into |buf|.
+ // Separate from ReadRawData so we can handle async I/O.
+ void CompleteRead(net::IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // The actual data we're serving. NULL until it's been fetched.
+ scoped_refptr<base::RefCountedMemory> data_;
+ // The current offset into the data that we're handing off to our
+ // callers via the Read interfaces.
+ int data_offset_;
+
+ // For async reads, we keep around a pointer to the buffer that
+ // we're reading into.
+ scoped_refptr<net::IOBuffer> pending_buf_;
+ int pending_buf_size_;
+ std::string mime_type_;
+
+ // If true, set a header in the response to prevent it from being cached.
+ bool allow_caching_;
+
+ // If true, set the Content Security Policy (CSP) header.
+ bool add_content_security_policy_;
+
+ // These are used with the CSP.
+ std::string content_security_policy_object_source_;
+ std::string content_security_policy_frame_source_;
+
+ // If true, sets the "X-Frame-Options: DENY" header.
+ bool deny_xframe_options_;
+
+ // True when job is generated from an incognito profile.
+ const bool is_incognito_;
+
+ // The backend is owned by ChromeURLRequestContext and always outlives us.
+ URLDataManagerBackend* backend_;
+
+ base::WeakPtrFactory<URLRequestChromeJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestChromeJob);
+};
+
+URLRequestChromeJob::URLRequestChromeJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ URLDataManagerBackend* backend,
+ bool is_incognito)
+ : net::URLRequestJob(request, network_delegate),
+ data_offset_(0),
+ pending_buf_size_(0),
+ allow_caching_(true),
+ add_content_security_policy_(true),
+ content_security_policy_object_source_("object-src 'none';"),
+ content_security_policy_frame_source_("frame-src 'none';"),
+ deny_xframe_options_(true),
+ is_incognito_(is_incognito),
+ backend_(backend),
+ weak_factory_(this) {
+ DCHECK(backend);
+}
+
+URLRequestChromeJob::~URLRequestChromeJob() {
+ CHECK(!backend_->HasPendingJob(this));
+}
+
+void URLRequestChromeJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestChromeJob::StartAsync, weak_factory_.GetWeakPtr()));
+
+ TRACE_EVENT_ASYNC_BEGIN1("browser", "DataManager:Request", this, "URL",
+ request_->url().possibly_invalid_spec());
+}
+
+void URLRequestChromeJob::Kill() {
+ backend_->RemoveRequest(this);
+}
+
+bool URLRequestChromeJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = mime_type_;
+ return !mime_type_.empty();
+}
+
+int URLRequestChromeJob::GetResponseCode() const {
+ return net::HTTP_OK;
+}
+
+void URLRequestChromeJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ DCHECK(!info->headers.get());
+ // Set the headers so that requests serviced by ChromeURLDataManager return a
+ // status code of 200. Without this they return a 0, which makes the status
+ // indistiguishable from other error types. Instant relies on getting a 200.
+ info->headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK");
+
+ // Determine the least-privileged content security policy header, if any,
+ // that is compatible with a given WebUI URL, and append it to the existing
+ // response headers.
+ if (add_content_security_policy_) {
+ std::string base = kChromeURLContentSecurityPolicyHeaderBase;
+ base.append(content_security_policy_object_source_);
+ base.append(content_security_policy_frame_source_);
+ info->headers->AddHeader(base);
+ }
+
+ if (deny_xframe_options_)
+ info->headers->AddHeader(kChromeURLXFrameOptionsHeader);
+
+ if (!allow_caching_)
+ info->headers->AddHeader("Cache-Control: no-cache");
+}
+
+void URLRequestChromeJob::MimeTypeAvailable(const std::string& mime_type) {
+ set_mime_type(mime_type);
+ NotifyHeadersComplete();
+}
+
+void URLRequestChromeJob::DataAvailable(base::RefCountedMemory* bytes) {
+ TRACE_EVENT_ASYNC_END0("browser", "DataManager:Request", this);
+ if (bytes) {
+ // The request completed, and we have all the data.
+ // Clear any IO pending status.
+ SetStatus(net::URLRequestStatus());
+
+ data_ = bytes;
+ int bytes_read;
+ if (pending_buf_.get()) {
+ CHECK(pending_buf_->data());
+ CompleteRead(pending_buf_.get(), pending_buf_size_, &bytes_read);
+ pending_buf_ = NULL;
+ NotifyReadComplete(bytes_read);
+ }
+ } else {
+ // The request failed.
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_FAILED));
+ }
+}
+
+bool URLRequestChromeJob::ReadRawData(net::IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ if (!data_.get()) {
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ DCHECK(!pending_buf_.get());
+ CHECK(buf->data());
+ pending_buf_ = buf;
+ pending_buf_size_ = buf_size;
+ return false; // Tell the caller we're still waiting for data.
+ }
+
+ // Otherwise, the data is available.
+ CompleteRead(buf, buf_size, bytes_read);
+ return true;
+}
+
+void URLRequestChromeJob::CompleteRead(net::IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ int remaining = static_cast<int>(data_->size()) - data_offset_;
+ if (buf_size > remaining)
+ buf_size = remaining;
+ if (buf_size > 0) {
+ memcpy(buf->data(), data_->front() + data_offset_, buf_size);
+ data_offset_ += buf_size;
+ }
+ *bytes_read = buf_size;
+}
+
+void URLRequestChromeJob::StartAsync() {
+ if (!request_)
+ return;
+
+ if (!backend_->StartRequest(request_, this)) {
+ NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ net::ERR_INVALID_URL));
+ }
+}
+
+namespace {
+
+// Gets mime type for data that is available from |source| by |path|.
+// After that, notifies |job| that mime type is available. This method
+// should be called on the UI thread, but notification is performed on
+// the IO thread.
+void GetMimeTypeOnUI(URLDataSourceImpl* source,
+ const std::string& path,
+ const base::WeakPtr<URLRequestChromeJob>& job) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::string mime_type = source->source()->GetMimeType(path);
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestChromeJob::MimeTypeAvailable, job, mime_type));
+}
+
+} // namespace
+
+namespace {
+
+class ChromeProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // |is_incognito| should be set for incognito profiles.
+ ChromeProtocolHandler(ResourceContext* resource_context,
+ bool is_incognito,
+ AppCacheService* appcache_service,
+ ChromeBlobStorageContext* blob_storage_context)
+ : resource_context_(resource_context),
+ is_incognito_(is_incognito),
+ appcache_service_(appcache_service),
+ blob_storage_context_(blob_storage_context) {}
+ virtual ~ChromeProtocolHandler() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ DCHECK(request);
+
+ // Check for chrome://view-http-cache/*, which uses its own job type.
+ if (ViewHttpCacheJobFactory::IsSupportedURL(request->url()))
+ return ViewHttpCacheJobFactory::CreateJobForRequest(request,
+ network_delegate);
+
+ // Next check for chrome://appcache-internals/, which uses its own job type.
+ if (request->url().SchemeIs(chrome::kChromeUIScheme) &&
+ request->url().host() == kChromeUIAppCacheInternalsHost) {
+ return appcache::ViewAppCacheInternalsJobFactory::CreateJobForRequest(
+ request, network_delegate, appcache_service_);
+ }
+
+ // Next check for chrome://blob-internals/, which uses its own job type.
+ if (ViewBlobInternalsJobFactory::IsSupportedURL(request->url())) {
+ return ViewBlobInternalsJobFactory::CreateJobForRequest(
+ request, network_delegate, blob_storage_context_->controller());
+ }
+
+#if defined(USE_TCMALLOC)
+ // Next check for chrome://tcmalloc/, which uses its own job type.
+ if (request->url().SchemeIs(chrome::kChromeUIScheme) &&
+ request->url().host() == kChromeUITcmallocHost) {
+ return new TcmallocInternalsRequestJob(request, network_delegate);
+ }
+#endif
+
+ // Next check for chrome://histograms/, which uses its own job type.
+ if (request->url().SchemeIs(chrome::kChromeUIScheme) &&
+ request->url().host() == kChromeUIHistogramHost) {
+ return new HistogramInternalsRequestJob(request, network_delegate);
+ }
+
+ // Fall back to using a custom handler
+ return new URLRequestChromeJob(
+ request, network_delegate,
+ GetURLDataManagerForResourceContext(resource_context_), is_incognito_);
+ }
+
+ virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE {
+ return false;
+ }
+
+ private:
+ // These members are owned by ProfileIOData, which owns this ProtocolHandler.
+ content::ResourceContext* const resource_context_;
+
+ // True when generated from an incognito profile.
+ const bool is_incognito_;
+ AppCacheService* appcache_service_;
+ ChromeBlobStorageContext* blob_storage_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeProtocolHandler);
+};
+
+} // namespace
+
+URLDataManagerBackend::URLDataManagerBackend()
+ : next_request_id_(0) {
+ URLDataSource* shared_source = new SharedResourcesDataSource();
+ URLDataSourceImpl* source_impl =
+ new URLDataSourceImpl(shared_source->GetSource(), shared_source);
+ AddDataSource(source_impl);
+}
+
+URLDataManagerBackend::~URLDataManagerBackend() {
+ for (DataSourceMap::iterator i = data_sources_.begin();
+ i != data_sources_.end(); ++i) {
+ i->second->backend_ = NULL;
+ }
+ data_sources_.clear();
+}
+
+// static
+net::URLRequestJobFactory::ProtocolHandler*
+URLDataManagerBackend::CreateProtocolHandler(
+ content::ResourceContext* resource_context,
+ bool is_incognito,
+ AppCacheService* appcache_service,
+ ChromeBlobStorageContext* blob_storage_context) {
+ DCHECK(resource_context);
+ return new ChromeProtocolHandler(
+ resource_context, is_incognito, appcache_service, blob_storage_context);
+}
+
+void URLDataManagerBackend::AddDataSource(
+ URLDataSourceImpl* source) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DataSourceMap::iterator i = data_sources_.find(source->source_name());
+ if (i != data_sources_.end()) {
+ if (!source->source()->ShouldReplaceExistingSource())
+ return;
+ i->second->backend_ = NULL;
+ }
+ data_sources_[source->source_name()] = source;
+ source->backend_ = this;
+}
+
+bool URLDataManagerBackend::HasPendingJob(
+ URLRequestChromeJob* job) const {
+ for (PendingRequestMap::const_iterator i = pending_requests_.begin();
+ i != pending_requests_.end(); ++i) {
+ if (i->second == job)
+ return true;
+ }
+ return false;
+}
+
+bool URLDataManagerBackend::StartRequest(const net::URLRequest* request,
+ URLRequestChromeJob* job) {
+ // Parse the URL into a request for a source and path.
+ std::string source_name;
+ std::string path;
+ URLToRequest(request->url(), &source_name, &path);
+
+ // Look up the data source for the request.
+ DataSourceMap::iterator i = data_sources_.find(source_name);
+ if (i == data_sources_.end())
+ return false;
+
+ URLDataSourceImpl* source = i->second.get();
+
+ if (!source->source()->ShouldServiceRequest(request))
+ return false;
+ source->source()->WillServiceRequest(request, &path);
+
+ // Save this request so we know where to send the data.
+ RequestID request_id = next_request_id_++;
+ pending_requests_.insert(std::make_pair(request_id, job));
+
+ job->set_allow_caching(source->source()->AllowCaching());
+ job->set_add_content_security_policy(
+ source->source()->ShouldAddContentSecurityPolicy());
+ job->set_content_security_policy_object_source(
+ source->source()->GetContentSecurityPolicyObjectSrc());
+ job->set_content_security_policy_frame_source(
+ source->source()->GetContentSecurityPolicyFrameSrc());
+ job->set_deny_xframe_options(
+ source->source()->ShouldDenyXFrameOptions());
+
+ // Look up additional request info to pass down.
+ int render_process_id = -1;
+ int render_view_id = -1;
+ ResourceRequestInfo::GetRenderViewForRequest(request,
+ &render_process_id,
+ &render_view_id);
+
+ // Forward along the request to the data source.
+ base::MessageLoop* target_message_loop =
+ source->source()->MessageLoopForRequestPath(path);
+ if (!target_message_loop) {
+ job->MimeTypeAvailable(source->source()->GetMimeType(path));
+ // Eliminate potentially dangling pointer to avoid future use.
+ job = NULL;
+
+ // The DataSource is agnostic to which thread StartDataRequest is called
+ // on for this path. Call directly into it from this thread, the IO
+ // thread.
+ source->source()->StartDataRequest(
+ path, render_process_id, render_view_id,
+ base::Bind(&URLDataSourceImpl::SendResponse, source, request_id));
+ } else {
+ // URLRequestChromeJob should receive mime type before data. This
+ // is guaranteed because request for mime type is placed in the
+ // message loop before request for data. And correspondingly their
+ // replies are put on the IO thread in the same order.
+ target_message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&GetMimeTypeOnUI,
+ scoped_refptr<URLDataSourceImpl>(source),
+ path, job->AsWeakPtr()));
+
+ // The DataSource wants StartDataRequest to be called on a specific thread,
+ // usually the UI thread, for this path.
+ target_message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&URLDataManagerBackend::CallStartRequest,
+ make_scoped_refptr(source), path, render_process_id,
+ render_view_id, request_id));
+ }
+ return true;
+}
+
+void URLDataManagerBackend::CallStartRequest(
+ scoped_refptr<URLDataSourceImpl> source,
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ int request_id) {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI) &&
+ render_process_id != -1 &&
+ !RenderProcessHost::FromID(render_process_id)) {
+ // Make the request fail if its initiating renderer is no longer valid.
+ // This can happen when the IO thread posts this task just before the
+ // renderer shuts down.
+ source->SendResponse(request_id, NULL);
+ return;
+ }
+ source->source()->StartDataRequest(
+ path,
+ render_process_id,
+ render_view_id,
+ base::Bind(&URLDataSourceImpl::SendResponse, source, request_id));
+}
+
+void URLDataManagerBackend::RemoveRequest(URLRequestChromeJob* job) {
+ // Remove the request from our list of pending requests.
+ // If/when the source sends the data that was requested, the data will just
+ // be thrown away.
+ for (PendingRequestMap::iterator i = pending_requests_.begin();
+ i != pending_requests_.end(); ++i) {
+ if (i->second == job) {
+ pending_requests_.erase(i);
+ return;
+ }
+ }
+}
+
+void URLDataManagerBackend::DataAvailable(RequestID request_id,
+ base::RefCountedMemory* bytes) {
+ // Forward this data on to the pending net::URLRequest, if it exists.
+ PendingRequestMap::iterator i = pending_requests_.find(request_id);
+ if (i != pending_requests_.end()) {
+ URLRequestChromeJob* job(i->second);
+ pending_requests_.erase(i);
+ job->DataAvailable(bytes);
+ }
+}
+
+namespace {
+
+class DevToolsJobFactory
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // |is_incognito| should be set for incognito profiles.
+ DevToolsJobFactory(content::ResourceContext* resource_context,
+ bool is_incognito);
+ virtual ~DevToolsJobFactory();
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ // |resource_context_| and |network_delegate_| are owned by ProfileIOData,
+ // which owns this ProtocolHandler.
+ content::ResourceContext* const resource_context_;
+
+ // True when generated from an incognito profile.
+ const bool is_incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevToolsJobFactory);
+};
+
+DevToolsJobFactory::DevToolsJobFactory(
+ content::ResourceContext* resource_context,
+ bool is_incognito)
+ : resource_context_(resource_context),
+ is_incognito_(is_incognito) {
+ DCHECK(resource_context_);
+}
+
+DevToolsJobFactory::~DevToolsJobFactory() {}
+
+net::URLRequestJob*
+DevToolsJobFactory::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ return new URLRequestChromeJob(
+ request, network_delegate,
+ GetURLDataManagerForResourceContext(resource_context_), is_incognito_);
+}
+
+} // namespace
+
+net::URLRequestJobFactory::ProtocolHandler*
+CreateDevToolsProtocolHandler(content::ResourceContext* resource_context,
+ bool is_incognito) {
+ return new DevToolsJobFactory(resource_context, is_incognito);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/url_data_manager_backend.h b/chromium/content/browser/webui/url_data_manager_backend.h
new file mode 100644
index 00000000000..e38fe1ee3f9
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_manager_backend.h
@@ -0,0 +1,113 @@
+// 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 CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_BACKEND_H_
+#define CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_BACKEND_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/supports_user_data.h"
+#include "content/browser/webui/url_data_manager.h"
+#include "content/public/browser/url_data_source.h"
+#include "net/url_request/url_request_job_factory.h"
+
+class GURL;
+
+namespace appcache {
+class AppCacheService;
+}
+
+namespace base {
+class RefCountedMemory;
+}
+
+namespace content {
+class ChromeBlobStorageContext;
+class ResourceContext;
+class URLDataManagerBackend;
+class URLDataSourceImpl;
+class URLRequestChromeJob;
+
+// URLDataManagerBackend is used internally by ChromeURLDataManager on the IO
+// thread. In most cases you can use the API in ChromeURLDataManager and ignore
+// this class. URLDataManagerBackend is owned by ResourceContext.
+class URLDataManagerBackend : public base::SupportsUserData::Data {
+ public:
+ typedef int RequestID;
+
+ URLDataManagerBackend();
+ virtual ~URLDataManagerBackend();
+
+ // Invoked to create the protocol handler for chrome://. |is_incognito| should
+ // be set for incognito profiles. Called on the UI thread.
+ static net::URLRequestJobFactory::ProtocolHandler* CreateProtocolHandler(
+ content::ResourceContext* resource_context,
+ bool is_incognito,
+ appcache::AppCacheService* appcache_service,
+ ChromeBlobStorageContext* blob_storage_context);
+
+ // Adds a DataSource to the collection of data sources.
+ void AddDataSource(URLDataSourceImpl* source);
+
+ // DataSource invokes this. Sends the data to the URLRequest.
+ void DataAvailable(RequestID request_id, base::RefCountedMemory* bytes);
+
+ static net::URLRequestJob* Factory(net::URLRequest* request,
+ const std::string& scheme);
+
+ private:
+ friend class URLRequestChromeJob;
+
+ typedef std::map<std::string,
+ scoped_refptr<URLDataSourceImpl> > DataSourceMap;
+ typedef std::map<RequestID, URLRequestChromeJob*> PendingRequestMap;
+
+ // Called by the job when it's starting up.
+ // Returns false if |url| is not a URL managed by this object.
+ bool StartRequest(const net::URLRequest* request, URLRequestChromeJob* job);
+
+ // Helper function to call StartDataRequest on |source|'s delegate. This is
+ // needed because while we want to call URLDataSourceDelegate's method, we
+ // need to add a refcount on the source.
+ static void CallStartRequest(scoped_refptr<URLDataSourceImpl> source,
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ int request_id);
+
+ // Remove a request from the list of pending requests.
+ void RemoveRequest(URLRequestChromeJob* job);
+
+ // Returns true if the job exists in |pending_requests_|. False otherwise.
+ // Called by ~URLRequestChromeJob to verify that |pending_requests_| is kept
+ // up to date.
+ bool HasPendingJob(URLRequestChromeJob* job) const;
+
+ // Custom sources of data, keyed by source path (e.g. "favicon").
+ DataSourceMap data_sources_;
+
+ // All pending URLRequestChromeJobs, keyed by ID of the request.
+ // URLRequestChromeJob calls into this object when it's constructed and
+ // destructed to ensure that the pointers in this map remain valid.
+ PendingRequestMap pending_requests_;
+
+ // The ID we'll use for the next request we receive.
+ RequestID next_request_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLDataManagerBackend);
+};
+
+// Creates protocol handler for chrome-devtools://. |is_incognito| should be
+// set for incognito profiles.
+net::URLRequestJobFactory::ProtocolHandler*
+CreateDevToolsProtocolHandler(content::ResourceContext* resource_context,
+ bool is_incognito);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_URL_DATA_MANAGER_BACKEND_H_
diff --git a/chromium/content/browser/webui/url_data_source_impl.cc b/chromium/content/browser/webui/url_data_source_impl.cc
new file mode 100644
index 00000000000..31e45adcd2a
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_source_impl.cc
@@ -0,0 +1,59 @@
+// 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 "content/browser/webui/url_data_source_impl.h"
+
+#include "base/bind.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/string_util.h"
+#include "content/browser/webui/url_data_manager_backend.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/url_data_source.h"
+
+namespace content {
+
+URLDataSourceImpl::URLDataSourceImpl(const std::string& source_name,
+ URLDataSource* source)
+ : source_name_(source_name),
+ backend_(NULL),
+ source_(source) {
+}
+
+URLDataSourceImpl::~URLDataSourceImpl() {
+}
+
+void URLDataSourceImpl::SendResponse(
+ int request_id,
+ base::RefCountedMemory* bytes) {
+ // Take a ref-pointer on entry so byte->Release() will always get called.
+ scoped_refptr<base::RefCountedMemory> bytes_ptr(bytes);
+ if (URLDataManager::IsScheduledForDeletion(this)) {
+ // We're scheduled for deletion. Servicing the request would result in
+ // this->AddRef being invoked, even though the ref count is 0 and 'this' is
+ // about to be deleted. If the AddRef were allowed through, when 'this' is
+ // released it would be deleted again.
+ //
+ // This scenario occurs with DataSources that make history requests. Such
+ // DataSources do a history query in |StartDataRequest| and the request is
+ // live until the object is deleted (history requests don't up the ref
+ // count). This means it's entirely possible for the DataSource to invoke
+ // |SendResponse| between the time when there are no more refs and the time
+ // when the object is deleted.
+ return;
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLDataSourceImpl::SendResponseOnIOThread, this, request_id,
+ bytes_ptr));
+}
+
+void URLDataSourceImpl::SendResponseOnIOThread(
+ int request_id,
+ scoped_refptr<base::RefCountedMemory> bytes) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (backend_)
+ backend_->DataAvailable(request_id, bytes.get());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/url_data_source_impl.h b/chromium/content/browser/webui/url_data_source_impl.h
new file mode 100644
index 00000000000..4785b37a194
--- /dev/null
+++ b/chromium/content/browser/webui/url_data_source_impl.h
@@ -0,0 +1,97 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEBUI_URL_DATA_SOURCE_IMPL_H_
+#define CONTENT_BROWSER_WEBUI_URL_DATA_SOURCE_IMPL_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/browser/webui/url_data_manager.h"
+#include "content/common/content_export.h"
+
+namespace base {
+class RefCountedMemory;
+}
+
+namespace content {
+class URLDataManagerBackend;
+class URLDataSource;
+class URLDataSourceImpl;
+
+// Trait used to handle deleting a URLDataSource. Deletion happens on the UI
+// thread.
+//
+// Implementation note: the normal shutdown sequence is for the UI loop to
+// stop pumping events then the IO loop and thread are stopped. When the
+// URLDataSources are no longer referenced (which happens when IO thread stops)
+// they get added to the UI message loop for deletion. But because the UI loop
+// has stopped by the time this happens the URLDataSources would be leaked.
+//
+// To make sure URLDataSources are properly deleted URLDataManager manages
+// deletion of the URLDataSources. When a URLDataSource is no longer referenced
+// it is added to |data_sources_| and a task is posted to the UI thread to
+// handle the actual deletion. During shutdown |DeleteDataSources| is invoked so
+// that all pending URLDataSources are properly deleted.
+struct DeleteURLDataSource {
+ static void Destruct(const URLDataSourceImpl* data_source) {
+ URLDataManager::DeleteDataSource(data_source);
+ }
+};
+
+// A URLDataSource is an object that can answer requests for data
+// asynchronously. URLDataSources are collectively owned with refcounting smart
+// pointers and should never be deleted on the IO thread, since their calls
+// are handled almost always on the UI thread and there's a possibility of a
+// data race. The |DeleteDataSource| trait above is used to enforce this.
+class URLDataSourceImpl : public base::RefCountedThreadSafe<
+ URLDataSourceImpl, DeleteURLDataSource> {
+ public:
+ // See source_name_ below for docs on that parameter. Takes ownership of
+ // |source|.
+ URLDataSourceImpl(const std::string& source_name,
+ URLDataSource* source);
+
+ // Report that a request has resulted in the data |bytes|.
+ // If the request can't be satisfied, pass NULL for |bytes| to indicate
+ // the request is over.
+ virtual void SendResponse(int request_id, base::RefCountedMemory* bytes);
+
+ const std::string& source_name() const { return source_name_; }
+ URLDataSource* source() const { return source_.get(); }
+
+ protected:
+ virtual ~URLDataSourceImpl();
+
+ private:
+ friend class URLDataManager;
+ friend class URLDataManagerBackend;
+ friend class base::DeleteHelper<URLDataSourceImpl>;
+
+ // SendResponse invokes this on the IO thread. Notifies the backend to
+ // handle the actual work of sending the data.
+ virtual void SendResponseOnIOThread(
+ int request_id,
+ scoped_refptr<base::RefCountedMemory> bytes);
+
+ // The name of this source.
+ // E.g., for favicons, this could be "favicon", which results in paths for
+ // specific resources like "favicon/34" getting sent to this source.
+ const std::string source_name_;
+
+ // This field is set and maintained by URLDataManagerBackend. It is set when
+ // the DataSource is added, and unset if the DataSource is removed. A
+ // DataSource can be removed in two ways: the URLDataManagerBackend is
+ // deleted, or another DataSource is registered with the same name. backend_
+ // should only be accessed on the IO thread. This reference can't be via a
+ // scoped_refptr else there would be a cycle between the backend and data
+ // source.
+ URLDataManagerBackend* backend_;
+
+ scoped_ptr<URLDataSource> source_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_URL_DATA_SOURCE_IMPL_H_
diff --git a/chromium/content/browser/webui/web_ui_controller_factory_registry.cc b/chromium/content/browser/webui/web_ui_controller_factory_registry.cc
new file mode 100644
index 00000000000..675bd8f8d0b
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_controller_factory_registry.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 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 "content/browser/webui/web_ui_controller_factory_registry.h"
+
+#include "base/lazy_instance.h"
+#include "content/public/common/url_constants.h"
+#include "url/gurl.h"
+
+namespace content {
+
+base::LazyInstance<std::vector<WebUIControllerFactory*> > g_factories =
+ LAZY_INSTANCE_INITIALIZER;
+
+void WebUIControllerFactory::RegisterFactory(WebUIControllerFactory* factory) {
+ g_factories.Pointer()->push_back(factory);
+}
+
+void WebUIControllerFactory::UnregisterFactoryForTesting(
+ WebUIControllerFactory* factory) {
+ std::vector<WebUIControllerFactory*>* factories = g_factories.Pointer();
+ for (size_t i = 0; i < factories->size(); ++i) {
+ if ((*factories)[i] == factory) {
+ factories->erase(factories->begin() + i);
+ return;
+ }
+ }
+ NOTREACHED() << "Tried to unregister a factory but it wasn't found";
+}
+
+WebUIControllerFactoryRegistry* WebUIControllerFactoryRegistry::GetInstance() {
+ return Singleton<WebUIControllerFactoryRegistry>::get();
+}
+
+WebUIController* WebUIControllerFactoryRegistry::CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const {
+ std::vector<WebUIControllerFactory*>* factories = g_factories.Pointer();
+ for (size_t i = 0; i < factories->size(); ++i) {
+ WebUIController* controller = (*factories)[i]->CreateWebUIControllerForURL(
+ web_ui, url);
+ if (controller)
+ return controller;
+ }
+ return NULL;
+}
+
+WebUI::TypeID WebUIControllerFactoryRegistry::GetWebUIType(
+ BrowserContext* browser_context, const GURL& url) const {
+ std::vector<WebUIControllerFactory*>* factories = g_factories.Pointer();
+ for (size_t i = 0; i < factories->size(); ++i) {
+ WebUI::TypeID type = (*factories)[i]->GetWebUIType(browser_context, url);
+ if (type != WebUI::kNoWebUI)
+ return type;
+ }
+ return WebUI::kNoWebUI;
+}
+
+bool WebUIControllerFactoryRegistry::UseWebUIForURL(
+ BrowserContext* browser_context, const GURL& url) const {
+ std::vector<WebUIControllerFactory*>* factories = g_factories.Pointer();
+ for (size_t i = 0; i < factories->size(); ++i) {
+ if ((*factories)[i]->UseWebUIForURL(browser_context, url))
+ return true;
+ }
+ return false;
+}
+
+bool WebUIControllerFactoryRegistry::UseWebUIBindingsForURL(
+ BrowserContext* browser_context, const GURL& url) const {
+ std::vector<WebUIControllerFactory*>* factories = g_factories.Pointer();
+ for (size_t i = 0; i < factories->size(); ++i) {
+ if ((*factories)[i]->UseWebUIBindingsForURL(browser_context, url))
+ return true;
+ }
+ return false;
+}
+
+bool WebUIControllerFactoryRegistry::IsURLAcceptableForWebUI(
+ BrowserContext* browser_context,
+ const GURL& url,
+ bool data_urls_allowed) const {
+ return UseWebUIForURL(browser_context, url) ||
+ // javascript: URLs are allowed to run in Web UI pages.
+ url.SchemeIs(chrome::kJavaScriptScheme) ||
+ // It's possible to load about:blank in a Web UI renderer.
+ // See http://crbug.com/42547
+ url.spec() == kAboutBlankURL ||
+ // Chrome URLs crash, kill, hang, and shorthang are allowed.
+ url == GURL(kChromeUICrashURL) ||
+ url == GURL(kChromeUIKillURL) ||
+ url == GURL(kChromeUIHangURL) ||
+ url == GURL(kChromeUIShorthangURL) ||
+ // Data URLs are usually not allowed in WebUI for security reasons.
+ // BalloonHosts are one exception needed by ChromeOS, and are safe because
+ // they cannot be scripted by other pages.
+ (data_urls_allowed && url.SchemeIs(chrome::kDataScheme));
+}
+
+WebUIControllerFactoryRegistry::WebUIControllerFactoryRegistry() {
+}
+
+WebUIControllerFactoryRegistry::~WebUIControllerFactoryRegistry() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/web_ui_controller_factory_registry.h b/chromium/content/browser/webui/web_ui_controller_factory_registry.h
new file mode 100644
index 00000000000..c2dd9bb7fab
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_controller_factory_registry.h
@@ -0,0 +1,49 @@
+// Copyright (c) 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 CONTENT_BROWSER_WEBUI_WEB_UI_CONTROLLER_FACTORY_REGISTRY_H_
+#define CONTENT_BROWSER_WEBUI_WEB_UI_CONTROLLER_FACTORY_REGISTRY_H_
+
+#include "base/memory/singleton.h"
+#include "content/public/browser/web_ui_controller_factory.h"
+
+namespace content {
+
+// A singleton which holds on to all the registered WebUIControllerFactory
+// instances.
+class CONTENT_EXPORT WebUIControllerFactoryRegistry
+ : public WebUIControllerFactory {
+ public:
+ static WebUIControllerFactoryRegistry* GetInstance();
+
+ // WebUIControllerFactory implementation. Each method loops through the same
+ // method on all the factories.
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const OVERRIDE;
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE;
+
+ // Returns true if the given URL can be loaded by Web UI system. This allows
+ // URLs that UseWebUIForURL returns true for, and also URLs that can be loaded
+ // by normal tabs such as javascript: URLs or about:hang.
+ bool IsURLAcceptableForWebUI(BrowserContext* browser_context,
+ const GURL& url,
+ bool data_urls_allowed) const;
+
+ private:
+ friend struct DefaultSingletonTraits<WebUIControllerFactoryRegistry>;
+
+ WebUIControllerFactoryRegistry();
+ virtual ~WebUIControllerFactoryRegistry();
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIControllerFactoryRegistry);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_WEB_UI_CONTROLLER_FACTORY_REGISTRY_H_
diff --git a/chromium/content/browser/webui/web_ui_data_source_impl.cc b/chromium/content/browser/webui/web_ui_data_source_impl.cc
new file mode 100644
index 00000000000..b564e57599b
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_data_source_impl.cc
@@ -0,0 +1,219 @@
+// 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 "content/browser/webui/web_ui_data_source_impl.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/string_util.h"
+#include "content/public/common/content_client.h"
+#include "ui/webui/jstemplate_builder.h"
+#include "ui/webui/web_ui_util.h"
+
+namespace content {
+
+WebUIDataSource* WebUIDataSource::Create(const std::string& source_name) {
+ return new WebUIDataSourceImpl(source_name);
+}
+
+void WebUIDataSource::Add(BrowserContext* browser_context,
+ WebUIDataSource* source) {
+ URLDataManager::AddWebUIDataSource(browser_context, source);
+}
+
+// Internal class to hide the fact that WebUIDataSourceImpl implements
+// URLDataSource.
+class WebUIDataSourceImpl::InternalDataSource : public URLDataSource {
+ public:
+ InternalDataSource(WebUIDataSourceImpl* parent) : parent_(parent) {
+ }
+
+ virtual ~InternalDataSource() {
+ }
+
+ // URLDataSource implementation.
+ virtual std::string GetSource() const OVERRIDE {
+ return parent_->GetSource();
+ }
+ virtual std::string GetMimeType(const std::string& path) const OVERRIDE {
+ return parent_->GetMimeType(path);
+ }
+ virtual void StartDataRequest(
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ const URLDataSource::GotDataCallback& callback) OVERRIDE {
+ return parent_->StartDataRequest(path, render_process_id, render_view_id,
+ callback);
+ }
+ virtual bool ShouldAddContentSecurityPolicy() const OVERRIDE {
+ return parent_->add_csp_;
+ }
+ virtual std::string GetContentSecurityPolicyObjectSrc() const OVERRIDE {
+ if (parent_->object_src_set_)
+ return parent_->object_src_;
+ return URLDataSource::GetContentSecurityPolicyObjectSrc();
+ }
+ virtual std::string GetContentSecurityPolicyFrameSrc() const OVERRIDE {
+ if (parent_->frame_src_set_)
+ return parent_->frame_src_;
+ return URLDataSource::GetContentSecurityPolicyFrameSrc();
+ }
+ virtual bool ShouldDenyXFrameOptions() const OVERRIDE {
+ return parent_->deny_xframe_options_;
+ }
+
+ private:
+ WebUIDataSourceImpl* parent_;
+};
+
+WebUIDataSourceImpl::WebUIDataSourceImpl(const std::string& source_name)
+ : URLDataSourceImpl(
+ source_name,
+ new InternalDataSource(this)),
+ source_name_(source_name),
+ default_resource_(-1),
+ json_js_format_v2_(false),
+ add_csp_(true),
+ object_src_set_(false),
+ frame_src_set_(false),
+ deny_xframe_options_(true),
+ disable_set_font_strings_(false) {
+}
+
+WebUIDataSourceImpl::~WebUIDataSourceImpl() {
+}
+
+void WebUIDataSourceImpl::AddString(const std::string& name,
+ const string16& value) {
+ localized_strings_.SetString(name, value);
+}
+
+void WebUIDataSourceImpl::AddString(const std::string& name,
+ const std::string& value) {
+ localized_strings_.SetString(name, value);
+}
+
+void WebUIDataSourceImpl::AddLocalizedString(const std::string& name,
+ int ids) {
+ localized_strings_.SetString(
+ name, GetContentClient()->GetLocalizedString(ids));
+}
+
+void WebUIDataSourceImpl::AddLocalizedStrings(
+ const base::DictionaryValue& localized_strings) {
+ localized_strings_.MergeDictionary(&localized_strings);
+}
+
+void WebUIDataSourceImpl::AddBoolean(const std::string& name, bool value) {
+ localized_strings_.SetBoolean(name, value);
+}
+
+void WebUIDataSourceImpl::SetJsonPath(const std::string& path) {
+ json_path_ = path;
+}
+
+void WebUIDataSourceImpl::SetUseJsonJSFormatV2() {
+ json_js_format_v2_ = true;
+}
+
+void WebUIDataSourceImpl::AddResourcePath(const std::string &path,
+ int resource_id) {
+ path_to_idr_map_[path] = resource_id;
+}
+
+void WebUIDataSourceImpl::SetDefaultResource(int resource_id) {
+ default_resource_ = resource_id;
+}
+
+void WebUIDataSourceImpl::SetRequestFilter(
+ const WebUIDataSource::HandleRequestCallback& callback) {
+ filter_callback_ = callback;
+}
+
+void WebUIDataSourceImpl::DisableContentSecurityPolicy() {
+ add_csp_ = false;
+}
+
+void WebUIDataSourceImpl::OverrideContentSecurityPolicyObjectSrc(
+ const std::string& data) {
+ object_src_set_ = true;
+ object_src_ = data;
+}
+
+void WebUIDataSourceImpl::OverrideContentSecurityPolicyFrameSrc(
+ const std::string& data) {
+ frame_src_set_ = true;
+ frame_src_ = data;
+}
+
+void WebUIDataSourceImpl::DisableDenyXFrameOptions() {
+ deny_xframe_options_ = false;
+}
+
+std::string WebUIDataSourceImpl::GetSource() const {
+ return source_name_;
+}
+
+std::string WebUIDataSourceImpl::GetMimeType(const std::string& path) const {
+ if (EndsWith(path, ".js", false))
+ return "application/javascript";
+
+ if (EndsWith(path, ".json", false))
+ return "application/json";
+
+ if (EndsWith(path, ".pdf", false))
+ return "application/pdf";
+
+ return "text/html";
+}
+
+void WebUIDataSourceImpl::StartDataRequest(
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ const URLDataSource::GotDataCallback& callback) {
+ if (!filter_callback_.is_null() &&
+ filter_callback_.Run(path, callback)) {
+ return;
+ }
+
+ if (!json_path_.empty() && path == json_path_) {
+ SendLocalizedStringsAsJSON(callback);
+ return;
+ }
+
+ int resource_id = default_resource_;
+ std::map<std::string, int>::iterator result;
+ result = path_to_idr_map_.find(path);
+ if (result != path_to_idr_map_.end())
+ resource_id = result->second;
+ DCHECK_NE(resource_id, -1);
+ SendFromResourceBundle(callback, resource_id);
+}
+
+void WebUIDataSourceImpl::SendLocalizedStringsAsJSON(
+ const URLDataSource::GotDataCallback& callback) {
+ std::string template_data;
+ if (!disable_set_font_strings_)
+ webui::SetFontAndTextDirection(&localized_strings_);
+
+ scoped_ptr<webui::UseVersion2> version2;
+ if (json_js_format_v2_)
+ version2.reset(new webui::UseVersion2);
+
+ webui::AppendJsonJS(&localized_strings_, &template_data);
+ callback.Run(base::RefCountedString::TakeString(&template_data));
+}
+
+void WebUIDataSourceImpl::SendFromResourceBundle(
+ const URLDataSource::GotDataCallback& callback, int idr) {
+ scoped_refptr<base::RefCountedStaticMemory> response(
+ GetContentClient()->GetDataResourceBytes(idr));
+ callback.Run(response.get());
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/web_ui_data_source_impl.h b/chromium/content/browser/webui/web_ui_data_source_impl.h
new file mode 100644
index 00000000000..0b6f8a355d3
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_data_source_impl.h
@@ -0,0 +1,108 @@
+// 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 CONTENT_BROWSER_WEBUI_WEB_UI_DATA_SOURCE_IMPL_H_
+#define CONTENT_BROWSER_WEBUI_WEB_UI_DATA_SOURCE_IMPL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/values.h"
+#include "content/browser/webui/url_data_manager.h"
+#include "content/browser/webui/url_data_source_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/url_data_source.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace content {
+
+// A data source that can help with implementing the common operations
+// needed by the chrome WEBUI settings/history/downloads pages.
+class CONTENT_EXPORT WebUIDataSourceImpl
+ : public NON_EXPORTED_BASE(URLDataSourceImpl),
+ public NON_EXPORTED_BASE(WebUIDataSource) {
+ public:
+ // WebUIDataSource implementation:
+ virtual void AddString(const std::string& name,
+ const string16& value) OVERRIDE;
+ virtual void AddString(const std::string& name,
+ const std::string& value) OVERRIDE;
+ virtual void AddLocalizedString(const std::string& name, int ids) OVERRIDE;
+ virtual void AddLocalizedStrings(
+ const base::DictionaryValue& localized_strings) OVERRIDE;
+ virtual void AddBoolean(const std::string& name, bool value) OVERRIDE;
+ virtual void SetJsonPath(const std::string& path) OVERRIDE;
+ virtual void SetUseJsonJSFormatV2() OVERRIDE;
+ virtual void AddResourcePath(const std::string &path,
+ int resource_id) OVERRIDE;
+ virtual void SetDefaultResource(int resource_id) OVERRIDE;
+ virtual void SetRequestFilter(
+ const WebUIDataSource::HandleRequestCallback& callback) OVERRIDE;
+ virtual void DisableContentSecurityPolicy() OVERRIDE;
+ virtual void OverrideContentSecurityPolicyObjectSrc(
+ const std::string& data) OVERRIDE;
+ virtual void OverrideContentSecurityPolicyFrameSrc(
+ const std::string& data) OVERRIDE;
+ virtual void DisableDenyXFrameOptions() OVERRIDE;
+
+ protected:
+ virtual ~WebUIDataSourceImpl();
+
+ // Completes a request by sending our dictionary of localized strings.
+ void SendLocalizedStringsAsJSON(
+ const URLDataSource::GotDataCallback& callback);
+
+ // Completes a request by sending the file specified by |idr|.
+ void SendFromResourceBundle(
+ const URLDataSource::GotDataCallback& callback, int idr);
+
+ private:
+ class InternalDataSource;
+ friend class InternalDataSource;
+ friend class WebUIDataSource;
+ friend class WebUIDataSourceTest;
+
+ explicit WebUIDataSourceImpl(const std::string& source_name);
+
+ // Methods that match URLDataSource which are called by
+ // InternalDataSource.
+ std::string GetSource() const;
+ std::string GetMimeType(const std::string& path) const;
+ void StartDataRequest(
+ const std::string& path,
+ int render_process_id,
+ int render_view_id,
+ const URLDataSource::GotDataCallback& callback);
+
+ void disable_set_font_strings_for_testing() {
+ disable_set_font_strings_ = true;
+ }
+
+ // The name of this source.
+ // E.g., for favicons, this could be "favicon", which results in paths for
+ // specific resources like "favicon/34" getting sent to this source.
+ std::string source_name_;
+ int default_resource_;
+ bool json_js_format_v2_;
+ std::string json_path_;
+ std::map<std::string, int> path_to_idr_map_;
+ base::DictionaryValue localized_strings_;
+ WebUIDataSource::HandleRequestCallback filter_callback_;
+ bool add_csp_;
+ bool object_src_set_;
+ std::string object_src_;
+ bool frame_src_set_;
+ std::string frame_src_;
+ bool deny_xframe_options_;
+ bool disable_set_font_strings_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIDataSourceImpl);
+};
+
+} // content
+
+#endif // CONTENT_BROWSER_WEBUI_WEB_UI_DATA_SOURCE_IMPL_H_
diff --git a/chromium/content/browser/webui/web_ui_data_source_unittest.cc b/chromium/content/browser/webui/web_ui_data_source_unittest.cc
new file mode 100644
index 00000000000..f384a43e94a
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_data_source_unittest.cc
@@ -0,0 +1,154 @@
+// 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 "base/bind.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/webui/web_ui_data_source_impl.h"
+#include "content/test/test_content_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+const int kDummyStringId = 123;
+const int kDummyDefaultResourceId = 456;
+const int kDummyResourceId = 789;
+
+const char kDummyString[] = "foo";
+const char kDummyDefaultResource[] = "<html>foo</html>";
+const char kDummytResource[] = "<html>blah</html>";
+
+class TestClient : public TestContentClient {
+ public:
+ TestClient() {}
+ virtual ~TestClient() {}
+
+ virtual string16 GetLocalizedString(int message_id) const OVERRIDE {
+ if (message_id == kDummyStringId)
+ return UTF8ToUTF16(kDummyString);
+ return string16();
+
+ }
+
+ virtual base::RefCountedStaticMemory* GetDataResourceBytes(
+ int resource_id) const OVERRIDE {
+ base::RefCountedStaticMemory* bytes = NULL;
+ if (resource_id == kDummyDefaultResourceId) {
+ bytes = new base::RefCountedStaticMemory(
+ reinterpret_cast<const unsigned char*>(kDummyDefaultResource),
+ arraysize(kDummyDefaultResource));
+ } else if (resource_id == kDummyResourceId) {
+ bytes = new base::RefCountedStaticMemory(
+ reinterpret_cast<const unsigned char*>(kDummytResource),
+ arraysize(kDummytResource));
+ }
+ return bytes;
+ }
+};
+
+}
+
+class WebUIDataSourceTest : public testing::Test {
+ public:
+ WebUIDataSourceTest() : result_data_(NULL) {}
+ virtual ~WebUIDataSourceTest() {}
+ WebUIDataSourceImpl* source() { return source_.get(); }
+
+ void StartDataRequest(const std::string& path) {
+ source_->StartDataRequest(
+ path,
+ 0, 0,
+ base::Bind(&WebUIDataSourceTest::SendResult,
+ base::Unretained(this)));
+ }
+
+ std::string GetMimeType(const std::string& path) const {
+ return source_->GetMimeType(path);
+ }
+
+ scoped_refptr<base::RefCountedMemory> result_data_;
+
+ private:
+ virtual void SetUp() {
+ SetContentClient(&client_);
+ WebUIDataSource* source = WebUIDataSourceImpl::Create("host");
+ WebUIDataSourceImpl* source_impl = static_cast<WebUIDataSourceImpl*>(
+ source);
+ source_impl->disable_set_font_strings_for_testing();
+ source_ = make_scoped_refptr(source_impl);
+ }
+
+ // Store response for later comparisons.
+ void SendResult(base::RefCountedMemory* data) {
+ result_data_ = data;
+ }
+
+ scoped_refptr<WebUIDataSourceImpl> source_;
+ TestClient client_;
+};
+
+TEST_F(WebUIDataSourceTest, EmptyStrings) {
+ source()->SetJsonPath("strings.js");
+ StartDataRequest("strings.js");
+ std::string result(reinterpret_cast<const char*>(
+ result_data_->front()), result_data_->size());
+ EXPECT_NE(result.find("var templateData = {"), std::string::npos);
+ EXPECT_NE(result.find("};"), std::string::npos);
+}
+
+TEST_F(WebUIDataSourceTest, SomeStrings) {
+ source()->SetJsonPath("strings.js");
+ source()->AddString("planet", ASCIIToUTF16("pluto"));
+ source()->AddLocalizedString("button", kDummyStringId);
+ StartDataRequest("strings.js");
+ std::string result(reinterpret_cast<const char*>(
+ result_data_->front()), result_data_->size());
+ EXPECT_NE(result.find("\"planet\":\"pluto\""), std::string::npos);
+ EXPECT_NE(result.find("\"button\":\"foo\""), std::string::npos);
+}
+
+TEST_F(WebUIDataSourceTest, DefaultResource) {
+ source()->SetDefaultResource(kDummyDefaultResourceId);
+ StartDataRequest("foobar" );
+ std::string result(
+ reinterpret_cast<const char*>(result_data_->front()),
+ result_data_->size());
+ EXPECT_NE(result.find(kDummyDefaultResource), std::string::npos);
+ StartDataRequest("strings.js");
+ result = std::string(
+ reinterpret_cast<const char*>(result_data_->front()),
+ result_data_->size());
+ EXPECT_NE(result.find(kDummyDefaultResource), std::string::npos);
+}
+
+TEST_F(WebUIDataSourceTest, NamedResource) {
+ source()->SetDefaultResource(kDummyDefaultResourceId);
+ source()->AddResourcePath("foobar", kDummyResourceId);
+ StartDataRequest("foobar");
+ std::string result(
+ reinterpret_cast<const char*>(result_data_->front()),
+ result_data_->size());
+ EXPECT_NE(result.find(kDummytResource), std::string::npos);
+ StartDataRequest("strings.js");
+ result = std::string(
+ reinterpret_cast<const char*>(result_data_->front()),
+ result_data_->size());
+ EXPECT_NE(result.find(kDummyDefaultResource), std::string::npos);
+}
+
+TEST_F(WebUIDataSourceTest, MimeType) {
+ const char* html = "text/html";
+ const char* js = "application/javascript";
+ EXPECT_EQ(GetMimeType(std::string()), html);
+ EXPECT_EQ(GetMimeType("foo"), html);
+ EXPECT_EQ(GetMimeType("foo.html"), html);
+ EXPECT_EQ(GetMimeType(".js"), js);
+ EXPECT_EQ(GetMimeType("foo.js"), js);
+ EXPECT_EQ(GetMimeType("js"), html);
+ EXPECT_EQ(GetMimeType("foojs"), html);
+ EXPECT_EQ(GetMimeType("foo.jsp"), html);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/web_ui_impl.cc b/chromium/content/browser/webui/web_ui_impl.cc
new file mode 100644
index 00000000000..e54a6d8c27d
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_impl.cc
@@ -0,0 +1,239 @@
+// 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 "content/browser/webui/web_ui_impl.h"
+
+#include "base/json/json_writer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/renderer_host/dip_util.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_view.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/browser/web_ui_message_handler.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_client.h"
+
+namespace content {
+
+const WebUI::TypeID WebUI::kNoWebUI = NULL;
+
+// static
+string16 WebUI::GetJavascriptCall(
+ const std::string& function_name,
+ const std::vector<const Value*>& arg_list) {
+ string16 parameters;
+ std::string json;
+ for (size_t i = 0; i < arg_list.size(); ++i) {
+ if (i > 0)
+ parameters += char16(',');
+
+ base::JSONWriter::Write(arg_list[i], &json);
+ parameters += UTF8ToUTF16(json);
+ }
+ return ASCIIToUTF16(function_name) +
+ char16('(') + parameters + char16(')') + char16(';');
+}
+
+WebUIImpl::WebUIImpl(WebContents* contents)
+ : link_transition_type_(PAGE_TRANSITION_LINK),
+ bindings_(BINDINGS_POLICY_WEB_UI),
+ web_contents_(contents) {
+ DCHECK(contents);
+}
+
+WebUIImpl::~WebUIImpl() {
+ // Delete the controller first, since it may also be keeping a pointer to some
+ // of the handlers and can call them at destruction.
+ controller_.reset();
+}
+
+// WebUIImpl, public: ----------------------------------------------------------
+
+bool WebUIImpl::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(WebUIImpl, message)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_WebUISend, OnWebUISend)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void WebUIImpl::OnWebUISend(const GURL& source_url,
+ const std::string& message,
+ const ListValue& args) {
+ WebContentsDelegate* delegate = web_contents_->GetDelegate();
+ bool data_urls_allowed = delegate && delegate->CanLoadDataURLsInWebUI();
+ if (!ChildProcessSecurityPolicyImpl::GetInstance()->
+ HasWebUIBindings(web_contents_->GetRenderProcessHost()->GetID()) ||
+ !WebUIControllerFactoryRegistry::GetInstance()->IsURLAcceptableForWebUI(
+ web_contents_->GetBrowserContext(), source_url, data_urls_allowed)) {
+ NOTREACHED() << "Blocked unauthorized use of WebUIBindings.";
+ return;
+ }
+
+ ProcessWebUIMessage(source_url, message, args);
+}
+
+void WebUIImpl::RenderViewCreated(RenderViewHost* render_view_host) {
+ controller_->RenderViewCreated(render_view_host);
+
+ // Do not attempt to set the toolkit property if WebUI is not enabled, e.g.,
+ // the bookmarks manager page.
+ if (!(bindings_ & BINDINGS_POLICY_WEB_UI))
+ return;
+
+#if defined(TOOLKIT_VIEWS)
+ render_view_host->SetWebUIProperty("toolkit", "views");
+#elif defined(TOOLKIT_GTK)
+ render_view_host->SetWebUIProperty("toolkit", "GTK");
+#endif // defined(TOOLKIT_VIEWS)
+}
+
+WebContents* WebUIImpl::GetWebContents() const {
+ return web_contents_;
+}
+
+ui::ScaleFactor WebUIImpl::GetDeviceScaleFactor() const {
+ return GetScaleFactorForView(web_contents_->GetRenderWidgetHostView());
+}
+
+const string16& WebUIImpl::GetOverriddenTitle() const {
+ return overridden_title_;
+}
+
+void WebUIImpl::OverrideTitle(const string16& title) {
+ overridden_title_ = title;
+}
+
+PageTransition WebUIImpl::GetLinkTransitionType() const {
+ return link_transition_type_;
+}
+
+void WebUIImpl::SetLinkTransitionType(PageTransition type) {
+ link_transition_type_ = type;
+}
+
+int WebUIImpl::GetBindings() const {
+ return bindings_;
+}
+
+void WebUIImpl::SetBindings(int bindings) {
+ bindings_ = bindings;
+}
+
+void WebUIImpl::SetFrameXPath(const std::string& xpath) {
+ frame_xpath_ = xpath;
+}
+
+WebUIController* WebUIImpl::GetController() const {
+ return controller_.get();
+}
+
+void WebUIImpl::SetController(WebUIController* controller) {
+ controller_.reset(controller);
+}
+
+void WebUIImpl::CallJavascriptFunction(const std::string& function_name) {
+ DCHECK(IsStringASCII(function_name));
+ string16 javascript = ASCIIToUTF16(function_name + "();");
+ ExecuteJavascript(javascript);
+}
+
+void WebUIImpl::CallJavascriptFunction(const std::string& function_name,
+ const Value& arg) {
+ DCHECK(IsStringASCII(function_name));
+ std::vector<const Value*> args;
+ args.push_back(&arg);
+ ExecuteJavascript(GetJavascriptCall(function_name, args));
+}
+
+void WebUIImpl::CallJavascriptFunction(
+ const std::string& function_name,
+ const Value& arg1, const Value& arg2) {
+ DCHECK(IsStringASCII(function_name));
+ std::vector<const Value*> args;
+ args.push_back(&arg1);
+ args.push_back(&arg2);
+ ExecuteJavascript(GetJavascriptCall(function_name, args));
+}
+
+void WebUIImpl::CallJavascriptFunction(
+ const std::string& function_name,
+ const Value& arg1, const Value& arg2, const Value& arg3) {
+ DCHECK(IsStringASCII(function_name));
+ std::vector<const Value*> args;
+ args.push_back(&arg1);
+ args.push_back(&arg2);
+ args.push_back(&arg3);
+ ExecuteJavascript(GetJavascriptCall(function_name, args));
+}
+
+void WebUIImpl::CallJavascriptFunction(
+ const std::string& function_name,
+ const Value& arg1,
+ const Value& arg2,
+ const Value& arg3,
+ const Value& arg4) {
+ DCHECK(IsStringASCII(function_name));
+ std::vector<const Value*> args;
+ args.push_back(&arg1);
+ args.push_back(&arg2);
+ args.push_back(&arg3);
+ args.push_back(&arg4);
+ ExecuteJavascript(GetJavascriptCall(function_name, args));
+}
+
+void WebUIImpl::CallJavascriptFunction(
+ const std::string& function_name,
+ const std::vector<const Value*>& args) {
+ DCHECK(IsStringASCII(function_name));
+ ExecuteJavascript(GetJavascriptCall(function_name, args));
+}
+
+void WebUIImpl::RegisterMessageCallback(const std::string &message,
+ const MessageCallback& callback) {
+ message_callbacks_.insert(std::make_pair(message, callback));
+}
+
+void WebUIImpl::ProcessWebUIMessage(const GURL& source_url,
+ const std::string& message,
+ const base::ListValue& args) {
+ if (controller_->OverrideHandleWebUIMessage(source_url, message, args))
+ return;
+
+ // Look up the callback for this message.
+ MessageCallbackMap::const_iterator callback =
+ message_callbacks_.find(message);
+ if (callback != message_callbacks_.end()) {
+ // Forward this message and content on.
+ callback->second.Run(&args);
+ } else {
+ NOTREACHED() << "Unhandled chrome.send(\"" << message << "\");";
+ }
+}
+
+// WebUIImpl, protected: -------------------------------------------------------
+
+void WebUIImpl::AddMessageHandler(WebUIMessageHandler* handler) {
+ DCHECK(!handler->web_ui());
+ handler->set_web_ui(this);
+ handler->RegisterMessages();
+ handlers_.push_back(handler);
+}
+
+void WebUIImpl::ExecuteJavascript(const string16& javascript) {
+ static_cast<RenderViewHostImpl*>(
+ web_contents_->GetRenderViewHost())->ExecuteJavascriptInWebFrame(
+ ASCIIToUTF16(frame_xpath_), javascript);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/web_ui_impl.h b/chromium/content/browser/webui/web_ui_impl.h
new file mode 100644
index 00000000000..de3059fcd49
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_impl.h
@@ -0,0 +1,111 @@
+// 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 CONTENT_BROWSER_WEBUI_WEB_UI_IMPL_H_
+#define CONTENT_BROWSER_WEBUI_WEB_UI_IMPL_H_
+
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_ui.h"
+#include "ipc/ipc_listener.h"
+
+namespace content {
+class RenderViewHost;
+
+class CONTENT_EXPORT WebUIImpl : public WebUI,
+ public IPC::Listener,
+ public base::SupportsWeakPtr<WebUIImpl> {
+ public:
+ explicit WebUIImpl(WebContents* contents);
+ virtual ~WebUIImpl();
+
+ // Called by WebContentsImpl when the RenderView is first created. This is
+ // *not* called for every page load because in some cases
+ // RenderViewHostManager will reuse RenderView instances.
+ void RenderViewCreated(RenderViewHost* render_view_host);
+
+ // WebUI implementation:
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual WebUIController* GetController() const OVERRIDE;
+ virtual void SetController(WebUIController* controller) OVERRIDE;
+ virtual ui::ScaleFactor GetDeviceScaleFactor() const OVERRIDE;
+ virtual const string16& GetOverriddenTitle() const OVERRIDE;
+ virtual void OverrideTitle(const string16& title) OVERRIDE;
+ virtual PageTransition GetLinkTransitionType() const OVERRIDE;
+ virtual void SetLinkTransitionType(PageTransition type) OVERRIDE;
+ virtual int GetBindings() const OVERRIDE;
+ virtual void SetBindings(int bindings) OVERRIDE;
+ virtual void SetFrameXPath(const std::string& xpath) OVERRIDE;
+ virtual void AddMessageHandler(WebUIMessageHandler* handler) OVERRIDE;
+ typedef base::Callback<void(const base::ListValue*)> MessageCallback;
+ virtual void RegisterMessageCallback(
+ const std::string& message,
+ const MessageCallback& callback) OVERRIDE;
+ virtual void ProcessWebUIMessage(const GURL& source_url,
+ const std::string& message,
+ const base::ListValue& args) OVERRIDE;
+ virtual void CallJavascriptFunction(
+ const std::string& function_name) OVERRIDE;
+ virtual void CallJavascriptFunction(const std::string& function_name,
+ const base::Value& arg) OVERRIDE;
+ virtual void CallJavascriptFunction(const std::string& function_name,
+ const base::Value& arg1,
+ const base::Value& arg2) OVERRIDE;
+ virtual void CallJavascriptFunction(const std::string& function_name,
+ const base::Value& arg1,
+ const base::Value& arg2,
+ const base::Value& arg3) OVERRIDE;
+ virtual void CallJavascriptFunction(const std::string& function_name,
+ const base::Value& arg1,
+ const base::Value& arg2,
+ const base::Value& arg3,
+ const base::Value& arg4) OVERRIDE;
+ virtual void CallJavascriptFunction(
+ const std::string& function_name,
+ const std::vector<const base::Value*>& args) OVERRIDE;
+
+ // IPC::Listener implementation:
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ private:
+ // IPC message handling.
+ void OnWebUISend(const GURL& source_url,
+ const std::string& message,
+ const base::ListValue& args);
+
+ // Execute a string of raw Javascript on the page.
+ void ExecuteJavascript(const string16& javascript);
+
+ // A map of message name -> message handling callback.
+ typedef std::map<std::string, MessageCallback> MessageCallbackMap;
+ MessageCallbackMap message_callbacks_;
+
+ // Options that may be overridden by individual Web UI implementations. The
+ // bool options default to false. See the public getters for more information.
+ string16 overridden_title_; // Defaults to empty string.
+ PageTransition link_transition_type_; // Defaults to LINK.
+ int bindings_; // The bindings from BindingsPolicy that should be enabled for
+ // this page.
+
+ // The WebUIMessageHandlers we own.
+ ScopedVector<WebUIMessageHandler> handlers_;
+
+ // Non-owning pointer to the WebContents this WebUI is associated with.
+ WebContents* web_contents_;
+
+ // The path for the iframe this WebUI is embedded in (empty if not in an
+ // iframe).
+ std::string frame_xpath_;
+
+ scoped_ptr<WebUIController> controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WEBUI_WEB_UI_IMPL_H_
diff --git a/chromium/content/browser/webui/web_ui_message_handler.cc b/chromium/content/browser/webui/web_ui_message_handler.cc
new file mode 100644
index 00000000000..db554c0ed85
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_message_handler.cc
@@ -0,0 +1,47 @@
+// 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 "content/public/browser/web_ui_message_handler.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+
+namespace content {
+
+bool WebUIMessageHandler::ExtractIntegerValue(const ListValue* value,
+ int* out_int) {
+ std::string string_value;
+ if (value->GetString(0, &string_value))
+ return base::StringToInt(string_value, out_int);
+ double double_value;
+ if (value->GetDouble(0, &double_value)) {
+ *out_int = static_cast<int>(double_value);
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool WebUIMessageHandler::ExtractDoubleValue(const ListValue* value,
+ double* out_value) {
+ std::string string_value;
+ if (value->GetString(0, &string_value))
+ return base::StringToDouble(string_value, out_value);
+ if (value->GetDouble(0, out_value))
+ return true;
+ NOTREACHED();
+ return false;
+}
+
+string16 WebUIMessageHandler::ExtractStringValue(const ListValue* value) {
+ string16 string16_value;
+ if (value->GetString(0, &string16_value))
+ return string16_value;
+ NOTREACHED();
+ return string16();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/webui/web_ui_message_handler_unittest.cc b/chromium/content/browser/webui/web_ui_message_handler_unittest.cc
new file mode 100644
index 00000000000..e38acd7e311
--- /dev/null
+++ b/chromium/content/browser/webui/web_ui_message_handler_unittest.cc
@@ -0,0 +1,97 @@
+// 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 "content/public/browser/web_ui_message_handler.h"
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+TEST(WebUIMessageHandlerTest, ExtractIntegerValue) {
+ ListValue list;
+ int value, zero_value = 0, neg_value = -1234, pos_value = 1234;
+ string16 zero_string(UTF8ToUTF16("0"));
+ string16 neg_string(UTF8ToUTF16("-1234"));
+ string16 pos_string(UTF8ToUTF16("1234"));
+
+ list.Append(new base::FundamentalValue(zero_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, zero_value);
+ list.Clear();
+
+ list.Append(new base::FundamentalValue(neg_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, neg_value);
+ list.Clear();
+
+ list.Append(new base::FundamentalValue(pos_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, pos_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(zero_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, zero_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(neg_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, neg_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(pos_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractIntegerValue(&list, &value));
+ EXPECT_EQ(value, pos_value);
+}
+
+TEST(WebUIMessageHandlerTest, ExtractDoubleValue) {
+ base::ListValue list;
+ double value, zero_value = 0.0, neg_value = -1234.5, pos_value = 1234.5;
+ string16 zero_string(UTF8ToUTF16("0"));
+ string16 neg_string(UTF8ToUTF16("-1234.5"));
+ string16 pos_string(UTF8ToUTF16("1234.5"));
+
+ list.Append(new base::FundamentalValue(zero_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, zero_value);
+ list.Clear();
+
+ list.Append(new base::FundamentalValue(neg_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, neg_value);
+ list.Clear();
+
+ list.Append(new base::FundamentalValue(pos_value));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, pos_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(zero_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, zero_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(neg_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, neg_value);
+ list.Clear();
+
+ list.Append(new base::StringValue(pos_string));
+ EXPECT_TRUE(WebUIMessageHandler::ExtractDoubleValue(&list, &value));
+ EXPECT_DOUBLE_EQ(value, pos_value);
+}
+
+TEST(WebUIMessageHandlerTest, ExtractStringValue) {
+ base::ListValue list;
+ string16 in_string(UTF8ToUTF16(
+ "The facts, though interesting, are irrelevant."));
+ list.Append(new base::StringValue(in_string));
+ string16 out_string = WebUIMessageHandler::ExtractStringValue(&list);
+ EXPECT_EQ(in_string, out_string);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker.sb b/chromium/content/browser/worker.sb
new file mode 100644
index 00000000000..2e408883e87
--- /dev/null
+++ b/chromium/content/browser/worker.sb
@@ -0,0 +1,12 @@
+;;
+;; Copyright (c) 2011 The Chromium 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 is the Sandbox configuration file used for safeguarding the worker
+; process which is used to run web workers in a sandboxed environment.
+;
+; This is the most restrictive sandbox profile and only enables just enough
+; to allow basic use of Cocoa.
+
+; *** The contents of content/common/common.sb are implicitly included here. ***
diff --git a/chromium/content/browser/worker_host/OWNERS b/chromium/content/browser/worker_host/OWNERS
new file mode 100644
index 00000000000..3e63b46257a
--- /dev/null
+++ b/chromium/content/browser/worker_host/OWNERS
@@ -0,0 +1 @@
+atwilson@chromium.org
diff --git a/chromium/content/browser/worker_host/message_port_service.cc b/chromium/content/browser/worker_host/message_port_service.cc
new file mode 100644
index 00000000000..d0563f0c23a
--- /dev/null
+++ b/chromium/content/browser/worker_host/message_port_service.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2009 The Chromium 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 "content/browser/worker_host/message_port_service.h"
+
+#include "content/browser/worker_host/worker_message_filter.h"
+#include "content/common/worker_messages.h"
+
+namespace content {
+
+MessagePortService* MessagePortService::GetInstance() {
+ return Singleton<MessagePortService>::get();
+}
+
+MessagePortService::MessagePortService()
+ : next_message_port_id_(0) {
+}
+
+MessagePortService::~MessagePortService() {
+}
+
+void MessagePortService::UpdateMessagePort(
+ int message_port_id,
+ WorkerMessageFilter* filter,
+ int routing_id) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ MessagePort& port = message_ports_[message_port_id];
+ port.filter = filter;
+ port.route_id = routing_id;
+}
+
+void MessagePortService::OnWorkerMessageFilterClosing(
+ WorkerMessageFilter* filter) {
+ // Check if the (possibly) crashed process had any message ports.
+ for (MessagePorts::iterator iter = message_ports_.begin();
+ iter != message_ports_.end();) {
+ MessagePorts::iterator cur_item = iter++;
+ if (cur_item->second.filter == filter) {
+ Erase(cur_item->first);
+ }
+ }
+}
+
+void MessagePortService::Create(int route_id,
+ WorkerMessageFilter* filter,
+ int* message_port_id) {
+ *message_port_id = ++next_message_port_id_;
+
+ MessagePort port;
+ port.filter = filter;
+ port.route_id = route_id;
+ port.message_port_id = *message_port_id;
+ port.entangled_message_port_id = MSG_ROUTING_NONE;
+ port.queue_messages = false;
+ message_ports_[*message_port_id] = port;
+}
+
+void MessagePortService::Destroy(int message_port_id) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(message_ports_[message_port_id].queued_messages.empty());
+ Erase(message_port_id);
+}
+
+void MessagePortService::Entangle(int local_message_port_id,
+ int remote_message_port_id) {
+ if (!message_ports_.count(local_message_port_id) ||
+ !message_ports_.count(remote_message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(message_ports_[remote_message_port_id].entangled_message_port_id ==
+ MSG_ROUTING_NONE);
+ message_ports_[remote_message_port_id].entangled_message_port_id =
+ local_message_port_id;
+}
+
+void MessagePortService::PostMessage(
+ int sender_message_port_id,
+ const string16& message,
+ const std::vector<int>& sent_message_port_ids) {
+ if (!message_ports_.count(sender_message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ int entangled_message_port_id =
+ message_ports_[sender_message_port_id].entangled_message_port_id;
+ if (entangled_message_port_id == MSG_ROUTING_NONE)
+ return; // Process could have crashed.
+
+ if (!message_ports_.count(entangled_message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ PostMessageTo(entangled_message_port_id, message, sent_message_port_ids);
+}
+
+void MessagePortService::PostMessageTo(
+ int message_port_id,
+ const string16& message,
+ const std::vector<int>& sent_message_port_ids) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+ for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
+ if (!message_ports_.count(sent_message_port_ids[i])) {
+ NOTREACHED();
+ return;
+ }
+ }
+
+ MessagePort& entangled_port = message_ports_[message_port_id];
+
+ std::vector<MessagePort*> sent_ports(sent_message_port_ids.size());
+ for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
+ sent_ports[i] = &message_ports_[sent_message_port_ids[i]];
+ sent_ports[i]->queue_messages = true;
+ }
+
+ if (entangled_port.queue_messages) {
+ entangled_port.queued_messages.push_back(
+ std::make_pair(message, sent_message_port_ids));
+ return;
+ }
+
+ if (!entangled_port.filter) {
+ NOTREACHED();
+ return;
+ }
+
+ // If a message port was sent around, the new location will need a routing
+ // id. Instead of having the created port send us a sync message to get it,
+ // send along with the message.
+ std::vector<int> new_routing_ids(sent_message_port_ids.size());
+ for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
+ new_routing_ids[i] = entangled_port.filter->GetNextRoutingID();
+ sent_ports[i]->filter = entangled_port.filter;
+
+ // Update the entry for the sent port as it can be in a different process.
+ sent_ports[i]->route_id = new_routing_ids[i];
+ }
+
+ // Now send the message to the entangled port.
+ entangled_port.filter->Send(new WorkerProcessMsg_Message(
+ entangled_port.route_id, message, sent_message_port_ids,
+ new_routing_ids));
+}
+
+void MessagePortService::QueueMessages(int message_port_id) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ MessagePort& port = message_ports_[message_port_id];
+ if (port.filter) {
+ port.filter->Send(new WorkerProcessMsg_MessagesQueued(port.route_id));
+ port.queue_messages = true;
+ port.filter = NULL;
+ }
+}
+
+void MessagePortService::SendQueuedMessages(
+ int message_port_id,
+ const QueuedMessages& queued_messages) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ // Send the queued messages to the port again. This time they'll reach the
+ // new location.
+ MessagePort& port = message_ports_[message_port_id];
+ port.queue_messages = false;
+ port.queued_messages.insert(port.queued_messages.begin(),
+ queued_messages.begin(),
+ queued_messages.end());
+ SendQueuedMessagesIfPossible(message_port_id);
+}
+
+void MessagePortService::SendQueuedMessagesIfPossible(int message_port_id) {
+ if (!message_ports_.count(message_port_id)) {
+ NOTREACHED();
+ return;
+ }
+
+ MessagePort& port = message_ports_[message_port_id];
+ if (port.queue_messages || !port.filter)
+ return;
+
+ for (QueuedMessages::iterator iter = port.queued_messages.begin();
+ iter != port.queued_messages.end(); ++iter) {
+ PostMessageTo(message_port_id, iter->first, iter->second);
+ }
+ port.queued_messages.clear();
+}
+
+void MessagePortService::Erase(int message_port_id) {
+ MessagePorts::iterator erase_item = message_ports_.find(message_port_id);
+ DCHECK(erase_item != message_ports_.end());
+
+ int entangled_id = erase_item->second.entangled_message_port_id;
+ if (entangled_id != MSG_ROUTING_NONE) {
+ // Do the disentanglement (and be paranoid about the other side existing
+ // just in case something unusual happened during entanglement).
+ if (message_ports_.count(entangled_id)) {
+ message_ports_[entangled_id].entangled_message_port_id = MSG_ROUTING_NONE;
+ }
+ }
+ message_ports_.erase(erase_item);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/message_port_service.h b/chromium/content/browser/worker_host/message_port_service.h
new file mode 100644
index 00000000000..e6bf71767bb
--- /dev/null
+++ b/chromium/content/browser/worker_host/message_port_service.h
@@ -0,0 +1,90 @@
+// 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 CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_
+#define CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string16.h"
+#include "ipc/ipc_message.h"
+
+namespace content {
+class WorkerMessageFilter;
+
+class MessagePortService {
+ public:
+ typedef std::vector<std::pair<string16, std::vector<int> > > QueuedMessages;
+
+ // Returns the MessagePortService singleton.
+ static MessagePortService* GetInstance();
+
+ // These methods correspond to the message port related IPCs.
+ void Create(int route_id, WorkerMessageFilter* filter, int* message_port_id);
+ void Destroy(int message_port_id);
+ void Entangle(int local_message_port_id, int remote_message_port_id);
+ void PostMessage(int sender_message_port_id,
+ const string16& message,
+ const std::vector<int>& sent_message_port_ids);
+ void QueueMessages(int message_port_id);
+ void SendQueuedMessages(int message_port_id,
+ const QueuedMessages& queued_messages);
+
+ // Updates the information needed to reach a message port when it's sent to a
+ // (possibly different) process.
+ void UpdateMessagePort(
+ int message_port_id,
+ WorkerMessageFilter* filter,
+ int routing_id);
+
+ void OnWorkerMessageFilterClosing(WorkerMessageFilter* filter);
+
+ // Attempts to send the queued messages for a message port.
+ void SendQueuedMessagesIfPossible(int message_port_id);
+
+ private:
+ friend struct DefaultSingletonTraits<MessagePortService>;
+
+ MessagePortService();
+ ~MessagePortService();
+
+ void PostMessageTo(int message_port_id,
+ const string16& message,
+ const std::vector<int>& sent_message_port_ids);
+
+ // Handles the details of removing a message port id. Before calling this,
+ // verify that the message port id exists.
+ void Erase(int message_port_id);
+
+ struct MessagePort {
+ // |filter| and |route_id| are what we need to send messages to the port.
+ // |filter| is just a weak pointer since we get notified when its process has
+ // gone away and remove it.
+ WorkerMessageFilter* filter;
+ int route_id;
+ // A globally unique id for this message port.
+ int message_port_id;
+ // The globally unique id of the entangled message port.
+ int entangled_message_port_id;
+ // If true, all messages to this message port are queued and not delivered.
+ bool queue_messages;
+ QueuedMessages queued_messages;
+ };
+
+ typedef std::map<int, MessagePort> MessagePorts;
+ MessagePorts message_ports_;
+
+ // We need globally unique identifiers for each message port.
+ int next_message_port_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePortService);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_MESSAGE_PORT_SERVICE_H_
diff --git a/chromium/content/browser/worker_host/worker_document_set.cc b/chromium/content/browser/worker_host/worker_document_set.cc
new file mode 100644
index 00000000000..48eaf867c23
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_document_set.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2009 The Chromium 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 "content/browser/worker_host/worker_document_set.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+WorkerDocumentSet::WorkerDocumentSet() {
+}
+
+void WorkerDocumentSet::Add(WorkerMessageFilter* parent,
+ unsigned long long document_id,
+ int render_process_id,
+ int render_view_id) {
+ DocumentInfo info(parent, document_id, render_process_id, render_view_id);
+ document_set_.insert(info);
+}
+
+bool WorkerDocumentSet::Contains(WorkerMessageFilter* parent,
+ unsigned long long document_id) const {
+ for (DocumentInfoSet::const_iterator i = document_set_.begin();
+ i != document_set_.end(); ++i) {
+ if (i->filter() == parent && i->document_id() == document_id)
+ return true;
+ }
+ return false;
+}
+
+void WorkerDocumentSet::Remove(WorkerMessageFilter* parent,
+ unsigned long long document_id) {
+ for (DocumentInfoSet::iterator i = document_set_.begin();
+ i != document_set_.end(); i++) {
+ if (i->filter() == parent && i->document_id() == document_id) {
+ document_set_.erase(i);
+ break;
+ }
+ }
+ // Should not be duplicate copies in the document set.
+ DCHECK(!Contains(parent, document_id));
+}
+
+void WorkerDocumentSet::RemoveAll(WorkerMessageFilter* parent) {
+ for (DocumentInfoSet::iterator i = document_set_.begin();
+ i != document_set_.end();) {
+
+ // Note this idiom is somewhat tricky - calling document_set_.erase(iter)
+ // invalidates any iterators that point to the element being removed, so
+ // bump the iterator beyond the item being removed before calling erase.
+ if (i->filter() == parent) {
+ DocumentInfoSet::iterator item_to_delete = i++;
+ document_set_.erase(item_to_delete);
+ } else {
+ ++i;
+ }
+ }
+}
+
+WorkerDocumentSet::DocumentInfo::DocumentInfo(
+ WorkerMessageFilter* filter, unsigned long long document_id,
+ int render_process_id, int render_view_id)
+ : filter_(filter),
+ document_id_(document_id),
+ render_process_id_(render_process_id),
+ render_view_id_(render_view_id) {
+}
+
+WorkerDocumentSet::~WorkerDocumentSet() {
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/worker_document_set.h b/chromium/content/browser/worker_host/worker_document_set.h
new file mode 100644
index 00000000000..a4b35ae5ce8
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_document_set.h
@@ -0,0 +1,92 @@
+// 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 CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_
+#define CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+
+namespace content {
+class WorkerMessageFilter;
+
+// The WorkerDocumentSet tracks all of the DOM documents associated with a
+// set of workers. With nested workers, multiple workers can share the same
+// WorkerDocumentSet (meaning that they all share the same lifetime, and will
+// all exit when the last document in that set exits, per the WebWorkers spec).
+class WorkerDocumentSet : public base::RefCounted<WorkerDocumentSet> {
+ public:
+ WorkerDocumentSet();
+
+ // The information we track for each document
+ class DocumentInfo {
+ public:
+ DocumentInfo(WorkerMessageFilter* filter, unsigned long long document_id,
+ int renderer_process_id, int render_view_id);
+ WorkerMessageFilter* filter() const { return filter_; }
+ unsigned long long document_id() const { return document_id_; }
+ int render_process_id() const { return render_process_id_; }
+ int render_view_id() const { return render_view_id_; }
+
+ // Define operator "<", which is used to determine uniqueness within
+ // the set.
+ bool operator <(const DocumentInfo& other) const {
+ // Items are identical if the sender and document_id are identical,
+ // otherwise create an arbitrary stable ordering based on the document
+ // id/filter.
+ if (filter() == other.filter()) {
+ return document_id() < other.document_id();
+ } else {
+ return reinterpret_cast<unsigned long long>(filter()) <
+ reinterpret_cast<unsigned long long>(other.filter());
+ }
+ }
+
+ private:
+ WorkerMessageFilter* filter_;
+ unsigned long long document_id_;
+ int render_process_id_;
+ int render_view_id_;
+ };
+
+ // Adds a document to a shared worker's document set. Also includes the
+ // associated render_process_id the document is associated with, to enable
+ // communication with the parent tab for things like http auth dialogs.
+ void Add(WorkerMessageFilter* parent,
+ unsigned long long document_id,
+ int render_process_id,
+ int render_view_id);
+
+ // Checks to see if a document is in a shared worker's document set.
+ bool Contains(WorkerMessageFilter* parent,
+ unsigned long long document_id) const;
+
+ // Removes a specific document from a worker's document set when that document
+ // is detached.
+ void Remove(WorkerMessageFilter* parent, unsigned long long document_id);
+
+ // Invoked when a render process exits, to remove all associated documents
+ // from a worker's document set.
+ void RemoveAll(WorkerMessageFilter* parent);
+
+ bool IsEmpty() const { return document_set_.empty(); }
+
+ // Define a typedef for convenience here when declaring iterators, etc.
+ typedef std::set<DocumentInfo> DocumentInfoSet;
+
+ // Returns the set of documents associated with this worker.
+ const DocumentInfoSet& documents() { return document_set_; }
+
+ private:
+ friend class base::RefCounted<WorkerDocumentSet>;
+ virtual ~WorkerDocumentSet();
+
+ DocumentInfoSet document_set_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_DOCUMENT_SET_H_
diff --git a/chromium/content/browser/worker_host/worker_message_filter.cc b/chromium/content/browser/worker_host/worker_message_filter.cc
new file mode 100644
index 00000000000..faedbdce08b
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_message_filter.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2011 The Chromium 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 "content/browser/worker_host/worker_message_filter.h"
+
+#include "content/browser/worker_host/message_port_service.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/view_messages.h"
+#include "content/common/worker_messages.h"
+#include "content/public/browser/resource_context.h"
+
+namespace content {
+
+WorkerMessageFilter::WorkerMessageFilter(
+ int render_process_id,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition,
+ const NextRoutingIDCallback& callback)
+ : render_process_id_(render_process_id),
+ resource_context_(resource_context),
+ partition_(partition),
+ next_routing_id_(callback) {
+ // Note: This constructor is called on both IO or UI thread.
+ DCHECK(resource_context);
+}
+
+WorkerMessageFilter::~WorkerMessageFilter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+}
+
+void WorkerMessageFilter::OnChannelClosing() {
+ BrowserMessageFilter::OnChannelClosing();
+
+ MessagePortService::GetInstance()->OnWorkerMessageFilterClosing(this);
+ WorkerServiceImpl::GetInstance()->OnWorkerMessageFilterClosing(this);
+}
+
+bool WorkerMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(WorkerMessageFilter, message, *message_was_ok)
+ // Worker messages.
+ // Only sent from renderer for now, until we have nested workers.
+ IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWorker, OnCreateWorker)
+ // Only sent from renderer for now, until we have nested workers.
+ IPC_MESSAGE_HANDLER(ViewHostMsg_LookupSharedWorker, OnLookupSharedWorker)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_ForwardToWorker, OnForwardToWorker)
+ // Only sent from renderer.
+ IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentDetached, OnDocumentDetached)
+ // Message Port related messages.
+ IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_CreateMessagePort,
+ OnCreateMessagePort)
+ IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_DestroyMessagePort,
+ MessagePortService::GetInstance(),
+ MessagePortService::Destroy)
+ IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_Entangle,
+ MessagePortService::GetInstance(),
+ MessagePortService::Entangle)
+ IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_PostMessage,
+ MessagePortService::GetInstance(),
+ MessagePortService::PostMessage)
+ IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_QueueMessages,
+ MessagePortService::GetInstance(),
+ MessagePortService::QueueMessages)
+ IPC_MESSAGE_FORWARD(WorkerProcessHostMsg_SendQueuedMessages,
+ MessagePortService::GetInstance(),
+ MessagePortService::SendQueuedMessages)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+int WorkerMessageFilter::GetNextRoutingID() {
+ return next_routing_id_.Run();
+}
+
+void WorkerMessageFilter::OnCreateWorker(
+ const ViewHostMsg_CreateWorker_Params& params,
+ int* route_id) {
+ *route_id = params.route_id != MSG_ROUTING_NONE ?
+ params.route_id : next_routing_id_.Run();
+ WorkerServiceImpl::GetInstance()->CreateWorker(
+ params, *route_id, this, resource_context_, partition_);
+}
+
+void WorkerMessageFilter::OnLookupSharedWorker(
+ const ViewHostMsg_CreateWorker_Params& params,
+ bool* exists,
+ int* route_id,
+ bool* url_error) {
+ *route_id = next_routing_id_.Run();
+
+ WorkerServiceImpl::GetInstance()->LookupSharedWorker(
+ params, *route_id, this, resource_context_, partition_, exists,
+ url_error);
+}
+
+void WorkerMessageFilter::OnForwardToWorker(const IPC::Message& message) {
+ WorkerServiceImpl::GetInstance()->ForwardToWorker(message, this);
+}
+
+void WorkerMessageFilter::OnDocumentDetached(unsigned long long document_id) {
+ WorkerServiceImpl::GetInstance()->DocumentDetached(document_id, this);
+}
+
+void WorkerMessageFilter::OnCreateMessagePort(int *route_id,
+ int* message_port_id) {
+ *route_id = next_routing_id_.Run();
+ MessagePortService::GetInstance()->Create(*route_id, this, message_port_id);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/worker_message_filter.h b/chromium/content/browser/worker_host/worker_message_filter.h
new file mode 100644
index 00000000000..47f96efc517
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_message_filter.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2011 The Chromium 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 CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_
+
+#include "base/callback.h"
+#include "content/browser/worker_host/worker_storage_partition.h"
+#include "content/public/browser/browser_message_filter.h"
+
+class ResourceDispatcherHost;
+struct ViewHostMsg_CreateWorker_Params;
+
+namespace content {
+class ResourceContext;
+
+class WorkerMessageFilter : public BrowserMessageFilter {
+ public:
+ typedef base::Callback<int(void)> NextRoutingIDCallback;
+
+ // |next_routing_id| is owned by this object. It can be used up until
+ // OnChannelClosing.
+ WorkerMessageFilter(int render_process_id,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition,
+ const NextRoutingIDCallback& callback);
+
+ // BrowserMessageFilter implementation.
+ virtual void OnChannelClosing() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ int GetNextRoutingID();
+ int render_process_id() const { return render_process_id_; }
+
+ private:
+ virtual ~WorkerMessageFilter();
+
+ // Message handlers.
+ void OnCreateWorker(const ViewHostMsg_CreateWorker_Params& params,
+ int* route_id);
+ void OnLookupSharedWorker(const ViewHostMsg_CreateWorker_Params& params,
+ bool* exists,
+ int* route_id,
+ bool* url_error);
+ void OnForwardToWorker(const IPC::Message& message);
+ void OnDocumentDetached(unsigned long long document_id);
+ void OnCreateMessagePort(int* route_id, int* message_port_id);
+
+ int render_process_id_;
+ ResourceContext* const resource_context_;
+ WorkerStoragePartition partition_;
+
+ // This is guaranteed to be valid until OnChannelClosing is closed, and it's
+ // not used after.
+ NextRoutingIDCallback next_routing_id_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WorkerMessageFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_MESSAGE_FILTER_H_
diff --git a/chromium/content/browser/worker_host/worker_process_host.cc b/chromium/content/browser/worker_host/worker_process_host.cc
new file mode 100644
index 00000000000..fa37574a772
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_process_host.cc
@@ -0,0 +1,727 @@
+// 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 "content/browser/worker_host/worker_process_host.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/appcache/appcache_dispatcher_host.h"
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/browser_child_process_host_impl.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/devtools/worker_devtools_manager.h"
+#include "content/browser/devtools/worker_devtools_message_filter.h"
+#include "content/browser/fileapi/fileapi_message_filter.h"
+#include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
+#include "content/browser/mime_registry_message_filter.h"
+#include "content/browser/quota_dispatcher_host.h"
+#include "content/browser/renderer_host/database_message_filter.h"
+#include "content/browser/renderer_host/file_utilities_message_filter.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/renderer_host/socket_stream_dispatcher_host.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/browser/worker_host/message_port_service.h"
+#include "content/browser/worker_host/worker_message_filter.h"
+#include "content/browser/worker_host/worker_service_impl.h"
+#include "content/common/child_process_host_impl.h"
+#include "content/common/view_messages.h"
+#include "content/common/worker_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "ipc/ipc_switches.h"
+#include "net/base/mime_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ui/base/ui_base_switches.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/common/resource_type.h"
+
+#if defined(OS_WIN)
+#include "content/common/sandbox_win.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#endif
+
+namespace content {
+namespace {
+
+#if defined(OS_WIN)
+// NOTE: changes to this class need to be reviewed by the security team.
+class WorkerSandboxedProcessLauncherDelegate
+ : public content::SandboxedProcessLauncherDelegate {
+ public:
+ WorkerSandboxedProcessLauncherDelegate() {}
+ virtual ~WorkerSandboxedProcessLauncherDelegate() {}
+
+ virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
+ bool* success) {
+ AddBaseHandleClosePolicy(policy);
+ }
+};
+#endif // OS_WIN
+
+// Helper class that we pass to SocketStreamDispatcherHost so that it can find
+// the right net::URLRequestContext for a request.
+class URLRequestContextSelector
+ : public ResourceMessageFilter::URLRequestContextSelector {
+ public:
+ explicit URLRequestContextSelector(
+ net::URLRequestContextGetter* url_request_context,
+ net::URLRequestContextGetter* media_url_request_context)
+ : url_request_context_(url_request_context),
+ media_url_request_context_(media_url_request_context) {
+ }
+ virtual ~URLRequestContextSelector() {}
+
+ virtual net::URLRequestContext* GetRequestContext(
+ ResourceType::Type resource_type) OVERRIDE {
+ if (resource_type == ResourceType::MEDIA)
+ return media_url_request_context_->GetURLRequestContext();
+ return url_request_context_->GetURLRequestContext();
+ }
+
+ private:
+ net::URLRequestContextGetter* url_request_context_;
+ net::URLRequestContextGetter* media_url_request_context_;
+};
+
+} // namespace
+
+// Notifies RenderViewHost that one or more worker objects crashed.
+void WorkerCrashCallback(int render_process_unique_id, int render_view_id) {
+ RenderViewHostImpl* host =
+ RenderViewHostImpl::FromID(render_process_unique_id, render_view_id);
+ if (host)
+ host->GetDelegate()->WorkerCrashed();
+}
+
+WorkerProcessHost::WorkerProcessHost(
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition)
+ : resource_context_(resource_context),
+ partition_(partition),
+ process_launched_(false) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(resource_context_);
+ process_.reset(
+ new BrowserChildProcessHostImpl(PROCESS_TYPE_WORKER, this));
+}
+
+WorkerProcessHost::~WorkerProcessHost() {
+ // If we crashed, tell the RenderViewHosts.
+ for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
+ const WorkerDocumentSet::DocumentInfoSet& parents =
+ i->worker_document_set()->documents();
+ for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
+ parents.begin(); parent_iter != parents.end(); ++parent_iter) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WorkerCrashCallback, parent_iter->render_process_id(),
+ parent_iter->render_view_id()));
+ }
+ WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
+ this, i->worker_route_id());
+ }
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->Remove(
+ process_->GetData().id);
+}
+
+bool WorkerProcessHost::Send(IPC::Message* message) {
+ return process_->Send(message);
+}
+
+bool WorkerProcessHost::Init(int render_process_id) {
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+#if defined(OS_LINUX)
+ int flags = ChildProcessHost::CHILD_ALLOW_SELF;
+#else
+ int flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
+ if (exe_path.empty())
+ return false;
+
+ CommandLine* cmd_line = new CommandLine(exe_path);
+ cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kWorkerProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+ std::string locale = GetContentClient()->browser()->GetApplicationLocale();
+ cmd_line->AppendSwitchASCII(switches::kLang, locale);
+
+ static const char* const kSwitchNames[] = {
+ switches::kDisableApplicationCache,
+ switches::kDisableDatabases,
+#if defined(OS_WIN)
+ switches::kDisableDesktopNotifications,
+#endif
+ switches::kDisableFileSystem,
+ switches::kDisableSeccompFilterSandbox,
+ switches::kEnableExperimentalWebPlatformFeatures,
+#if defined(OS_MACOSX)
+ switches::kEnableSandboxLogging,
+#endif
+ };
+ cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), kSwitchNames,
+ arraysize(kSwitchNames));
+
+#if defined(OS_POSIX)
+ bool use_zygote = true;
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kWaitForDebuggerChildren)) {
+ // Look to pass-on the kWaitForDebugger flag.
+ std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kWaitForDebuggerChildren);
+ if (value.empty() || value == switches::kWorkerProcess) {
+ cmd_line->AppendSwitch(switches::kWaitForDebugger);
+ use_zygote = false;
+ }
+ }
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren)) {
+ // Look to pass-on the kDebugOnStart flag.
+ std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kDebugChildren);
+ if (value.empty() || value == switches::kWorkerProcess) {
+ // launches a new xterm, and runs the worker process in gdb, reading
+ // optional commands from gdb_chrome file in the working directory.
+ cmd_line->PrependWrapper("xterm -e gdb -x gdb_chrome --args");
+ use_zygote = false;
+ }
+ }
+#endif
+
+ process_->Launch(
+#if defined(OS_WIN)
+ new WorkerSandboxedProcessLauncherDelegate,
+#elif defined(OS_POSIX)
+ use_zygote,
+ base::EnvironmentVector(),
+#endif
+ cmd_line);
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->AddWorker(
+ process_->GetData().id, render_process_id);
+ CreateMessageFilters(render_process_id);
+
+ return true;
+}
+
+void WorkerProcessHost::CreateMessageFilters(int render_process_id) {
+ ChromeBlobStorageContext* blob_storage_context =
+ GetChromeBlobStorageContextForResourceContext(resource_context_);
+ StreamContext* stream_context =
+ GetStreamContextForResourceContext(resource_context_);
+
+ net::URLRequestContextGetter* url_request_context =
+ partition_.url_request_context();
+ net::URLRequestContextGetter* media_url_request_context =
+ partition_.url_request_context();
+
+ ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter(
+ process_->GetData().id, PROCESS_TYPE_WORKER, resource_context_,
+ partition_.appcache_service(),
+ blob_storage_context,
+ partition_.filesystem_context(),
+ new URLRequestContextSelector(url_request_context,
+ media_url_request_context));
+ process_->GetHost()->AddFilter(resource_message_filter);
+
+ worker_message_filter_ = new WorkerMessageFilter(
+ render_process_id, resource_context_, partition_,
+ base::Bind(&WorkerServiceImpl::next_worker_route_id,
+ base::Unretained(WorkerServiceImpl::GetInstance())));
+ process_->GetHost()->AddFilter(worker_message_filter_.get());
+ process_->GetHost()->AddFilter(new AppCacheDispatcherHost(
+ partition_.appcache_service(), process_->GetData().id));
+ process_->GetHost()->AddFilter(new FileAPIMessageFilter(
+ process_->GetData().id,
+ url_request_context,
+ partition_.filesystem_context(),
+ blob_storage_context,
+ stream_context));
+ process_->GetHost()->AddFilter(new FileUtilitiesMessageFilter(
+ process_->GetData().id));
+ process_->GetHost()->AddFilter(new MimeRegistryMessageFilter());
+ process_->GetHost()->AddFilter(
+ new DatabaseMessageFilter(partition_.database_tracker()));
+ process_->GetHost()->AddFilter(new QuotaDispatcherHost(
+ process_->GetData().id,
+ partition_.quota_manager(),
+ GetContentClient()->browser()->CreateQuotaPermissionContext()));
+
+ SocketStreamDispatcherHost* socket_stream_dispatcher_host =
+ new SocketStreamDispatcherHost(
+ render_process_id,
+ new URLRequestContextSelector(url_request_context,
+ media_url_request_context),
+ resource_context_);
+ socket_stream_dispatcher_host_ = socket_stream_dispatcher_host;
+ process_->GetHost()->AddFilter(socket_stream_dispatcher_host);
+ process_->GetHost()->AddFilter(
+ new WorkerDevToolsMessageFilter(process_->GetData().id));
+ process_->GetHost()->AddFilter(new IndexedDBDispatcherHost(
+ process_->GetData().id, partition_.indexed_db_context()));
+}
+
+void WorkerProcessHost::CreateWorker(const WorkerInstance& instance) {
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL(
+ process_->GetData().id, instance.url());
+
+ instances_.push_back(instance);
+
+ WorkerProcessMsg_CreateWorker_Params params;
+ params.url = instance.url();
+ params.name = instance.name();
+ params.route_id = instance.worker_route_id();
+ params.creator_process_id = instance.parent_process_id();
+ params.shared_worker_appcache_id = instance.main_resource_appcache_id();
+ Send(new WorkerProcessMsg_CreateWorker(params));
+
+ UpdateTitle();
+
+ // Walk all pending filters and let them know the worker has been created
+ // (could be more than one in the case where we had to queue up worker
+ // creation because the worker process limit was reached).
+ for (WorkerInstance::FilterList::const_iterator i =
+ instance.filters().begin();
+ i != instance.filters().end(); ++i) {
+ CHECK(i->first);
+ i->first->Send(new ViewMsg_WorkerCreated(i->second));
+ }
+}
+
+bool WorkerProcessHost::FilterMessage(const IPC::Message& message,
+ WorkerMessageFilter* filter) {
+ for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
+ if (!i->closed() && i->HasFilter(filter, message.routing_id())) {
+ RelayMessage(message, worker_message_filter_.get(), i->worker_route_id());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void WorkerProcessHost::OnProcessLaunched() {
+ process_launched_ = true;
+
+ WorkerServiceImpl::GetInstance()->NotifyWorkerProcessCreated();
+}
+
+bool WorkerProcessHost::OnMessageReceived(const IPC::Message& message) {
+ bool msg_is_ok = true;
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(WorkerProcessHost, message, msg_is_ok)
+ IPC_MESSAGE_HANDLER(WorkerHostMsg_WorkerContextClosed,
+ OnWorkerContextClosed)
+ IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowDatabase, OnAllowDatabase)
+ IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowFileSystem, OnAllowFileSystem)
+ IPC_MESSAGE_HANDLER(WorkerProcessHostMsg_AllowIndexedDB, OnAllowIndexedDB)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ if (!msg_is_ok) {
+ NOTREACHED();
+ RecordAction(UserMetricsAction("BadMessageTerminate_WPH"));
+ base::KillProcess(
+ process_->GetData().handle, RESULT_CODE_KILLED_BAD_MESSAGE, false);
+ }
+
+ if (handled)
+ return true;
+
+ if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
+ WorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
+ this, message.routing_id());
+ }
+
+ for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
+ if (i->worker_route_id() == message.routing_id()) {
+ if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
+ instances_.erase(i);
+ UpdateTitle();
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+// Sent to notify the browser process when a worker context invokes close(), so
+// no new connections are sent to shared workers.
+void WorkerProcessHost::OnWorkerContextClosed(int worker_route_id) {
+ for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
+ if (i->worker_route_id() == worker_route_id) {
+ // Set the closed flag - this will stop any further messages from
+ // being sent to the worker (messages can still be sent from the worker,
+ // for exception reporting, etc).
+ i->set_closed(true);
+ break;
+ }
+ }
+}
+
+void WorkerProcessHost::OnAllowDatabase(int worker_route_id,
+ const GURL& url,
+ const string16& name,
+ const string16& display_name,
+ unsigned long estimated_size,
+ bool* result) {
+ *result = GetContentClient()->browser()->AllowWorkerDatabase(
+ url, name, display_name, estimated_size, resource_context_,
+ GetRenderViewIDsForWorker(worker_route_id));
+}
+
+void WorkerProcessHost::OnAllowFileSystem(int worker_route_id,
+ const GURL& url,
+ bool* result) {
+ *result = GetContentClient()->browser()->AllowWorkerFileSystem(
+ url, resource_context_, GetRenderViewIDsForWorker(worker_route_id));
+}
+
+void WorkerProcessHost::OnAllowIndexedDB(int worker_route_id,
+ const GURL& url,
+ const string16& name,
+ bool* result) {
+ *result = GetContentClient()->browser()->AllowWorkerIndexedDB(
+ url, name, resource_context_, GetRenderViewIDsForWorker(worker_route_id));
+}
+
+void WorkerProcessHost::RelayMessage(
+ const IPC::Message& message,
+ WorkerMessageFilter* filter,
+ int route_id) {
+ if (message.type() == WorkerMsg_PostMessage::ID) {
+ // We want to send the receiver a routing id for the new channel, so
+ // crack the message first.
+ string16 msg;
+ std::vector<int> sent_message_port_ids;
+ std::vector<int> new_routing_ids;
+ if (!WorkerMsg_PostMessage::Read(
+ &message, &msg, &sent_message_port_ids, &new_routing_ids)) {
+ return;
+ }
+ if (sent_message_port_ids.size() != new_routing_ids.size())
+ return;
+
+ for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
+ new_routing_ids[i] = filter->GetNextRoutingID();
+ MessagePortService::GetInstance()->UpdateMessagePort(
+ sent_message_port_ids[i], filter, new_routing_ids[i]);
+ }
+
+ filter->Send(new WorkerMsg_PostMessage(
+ route_id, msg, sent_message_port_ids, new_routing_ids));
+
+ // Send any queued messages to the sent message ports. We can only do this
+ // after sending the above message, since it's the one that sets up the
+ // message port route which the queued messages are sent to.
+ for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
+ MessagePortService::GetInstance()->
+ SendQueuedMessagesIfPossible(sent_message_port_ids[i]);
+ }
+ } else if (message.type() == WorkerMsg_Connect::ID) {
+ // Crack the SharedWorker Connect message to setup routing for the port.
+ int sent_message_port_id;
+ int new_routing_id;
+ if (!WorkerMsg_Connect::Read(
+ &message, &sent_message_port_id, &new_routing_id)) {
+ return;
+ }
+ new_routing_id = filter->GetNextRoutingID();
+ MessagePortService::GetInstance()->UpdateMessagePort(
+ sent_message_port_id, filter, new_routing_id);
+
+ // Resend the message with the new routing id.
+ filter->Send(new WorkerMsg_Connect(
+ route_id, sent_message_port_id, new_routing_id));
+
+ // Send any queued messages for the sent port.
+ MessagePortService::GetInstance()->SendQueuedMessagesIfPossible(
+ sent_message_port_id);
+ } else {
+ IPC::Message* new_message = new IPC::Message(message);
+ new_message->set_routing_id(route_id);
+ filter->Send(new_message);
+ if (message.type() == WorkerMsg_StartWorkerContext::ID) {
+ WorkerDevToolsManager::GetInstance()->WorkerContextStarted(
+ this, route_id);
+ }
+ return;
+ }
+}
+
+void WorkerProcessHost::ShutdownSocketStreamDispatcherHostIfNecessary() {
+ if (!instances_.size() && socket_stream_dispatcher_host_.get()) {
+ // We can assume that this object is going to delete, because
+ // currently a WorkerInstance will never be added to a WorkerProcessHost
+ // once it is initialized.
+
+ // SocketStreamDispatcherHost should be notified now that the worker
+ // process will shutdown soon.
+ socket_stream_dispatcher_host_->Shutdown();
+ socket_stream_dispatcher_host_ = NULL;
+ }
+}
+
+void WorkerProcessHost::FilterShutdown(WorkerMessageFilter* filter) {
+ for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
+ bool shutdown = false;
+ i->RemoveFilters(filter);
+
+ i->worker_document_set()->RemoveAll(filter);
+ if (i->worker_document_set()->IsEmpty()) {
+ shutdown = true;
+ }
+ if (shutdown) {
+ Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
+ i = instances_.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ ShutdownSocketStreamDispatcherHostIfNecessary();
+}
+
+bool WorkerProcessHost::CanShutdown() {
+ return instances_.empty();
+}
+
+void WorkerProcessHost::UpdateTitle() {
+ std::set<std::string> titles;
+ for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
+ // Allow the embedder first crack at special casing the title.
+ std::string title = GetContentClient()->browser()->
+ GetWorkerProcessTitle(i->url(), resource_context_);
+
+ if (title.empty()) {
+ title = net::registry_controlled_domains::GetDomainAndRegistry(
+ i->url(),
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ }
+
+ // Use the host name if the domain is empty, i.e. localhost or IP address.
+ if (title.empty())
+ title = i->url().host();
+
+ // If the host name is empty, i.e. file url, use the path.
+ if (title.empty())
+ title = i->url().path();
+ titles.insert(title);
+ }
+
+ std::string display_title;
+ for (std::set<std::string>::iterator i = titles.begin();
+ i != titles.end(); ++i) {
+ if (!display_title.empty())
+ display_title += ", ";
+ display_title += *i;
+ }
+
+ process_->SetName(UTF8ToUTF16(display_title));
+}
+
+void WorkerProcessHost::DocumentDetached(WorkerMessageFilter* filter,
+ unsigned long long document_id) {
+ // Walk all instances and remove the document from their document set.
+ for (Instances::iterator i = instances_.begin(); i != instances_.end();) {
+ i->worker_document_set()->Remove(filter, document_id);
+ if (i->worker_document_set()->IsEmpty()) {
+ // This worker has no more associated documents - shut it down.
+ Send(new WorkerMsg_TerminateWorkerContext(i->worker_route_id()));
+ i = instances_.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ ShutdownSocketStreamDispatcherHostIfNecessary();
+}
+
+void WorkerProcessHost::TerminateWorker(int worker_route_id) {
+ Send(new WorkerMsg_TerminateWorkerContext(worker_route_id));
+}
+
+void WorkerProcessHost::SetBackgrounded(bool backgrounded) {
+ process_->SetBackgrounded(backgrounded);
+}
+
+const ChildProcessData& WorkerProcessHost::GetData() {
+ return process_->GetData();
+}
+
+std::vector<std::pair<int, int> > WorkerProcessHost::GetRenderViewIDsForWorker(
+ int worker_route_id) {
+ std::vector<std::pair<int, int> > result;
+ WorkerProcessHost::Instances::const_iterator i;
+ for (i = instances_.begin(); i != instances_.end(); ++i) {
+ if (i->worker_route_id() != worker_route_id)
+ continue;
+ const WorkerDocumentSet::DocumentInfoSet& documents =
+ i->worker_document_set()->documents();
+ for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
+ documents.begin(); doc != documents.end(); ++doc) {
+ result.push_back(
+ std::make_pair(doc->render_process_id(), doc->render_view_id()));
+ }
+ break;
+ }
+ return result;
+}
+
+WorkerProcessHost::WorkerInstance::WorkerInstance(
+ const GURL& url,
+ const string16& name,
+ int worker_route_id,
+ int parent_process_id,
+ int64 main_resource_appcache_id,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition)
+ : url_(url),
+ closed_(false),
+ name_(name),
+ worker_route_id_(worker_route_id),
+ parent_process_id_(parent_process_id),
+ main_resource_appcache_id_(main_resource_appcache_id),
+ worker_document_set_(new WorkerDocumentSet()),
+ resource_context_(resource_context),
+ partition_(partition) {
+ DCHECK(resource_context_);
+}
+
+WorkerProcessHost::WorkerInstance::WorkerInstance(
+ const GURL& url,
+ bool shared,
+ const string16& name,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition)
+ : url_(url),
+ closed_(false),
+ name_(name),
+ worker_route_id_(MSG_ROUTING_NONE),
+ parent_process_id_(0),
+ main_resource_appcache_id_(0),
+ worker_document_set_(new WorkerDocumentSet()),
+ resource_context_(resource_context),
+ partition_(partition) {
+ DCHECK(resource_context_);
+}
+
+WorkerProcessHost::WorkerInstance::~WorkerInstance() {
+}
+
+// Compares an instance based on the algorithm in the WebWorkers spec - an
+// instance matches if the origins of the URLs match, and:
+// a) the names are non-empty and equal
+// -or-
+// b) the names are both empty, and the urls are equal
+bool WorkerProcessHost::WorkerInstance::Matches(
+ const GURL& match_url,
+ const string16& match_name,
+ const WorkerStoragePartition& partition,
+ ResourceContext* resource_context) const {
+ // Only match open shared workers.
+ if (closed_)
+ return false;
+
+ // ResourceContext equivalence is being used as a proxy to ensure we only
+ // matched shared workers within the same BrowserContext.
+ if (resource_context_ != resource_context)
+ return false;
+
+ // We must be in the same storage partition otherwise sharing will violate
+ // isolation.
+ if (!partition_.Equals(partition))
+ return false;
+
+ if (url_.GetOrigin() != match_url.GetOrigin())
+ return false;
+
+ if (name_.empty() && match_name.empty())
+ return url_ == match_url;
+
+ return name_ == match_name;
+}
+
+void WorkerProcessHost::WorkerInstance::AddFilter(WorkerMessageFilter* filter,
+ int route_id) {
+ CHECK(filter);
+ if (!HasFilter(filter, route_id)) {
+ FilterInfo info(filter, route_id);
+ filters_.push_back(info);
+ }
+}
+
+void WorkerProcessHost::WorkerInstance::RemoveFilter(
+ WorkerMessageFilter* filter, int route_id) {
+ for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
+ if (i->first == filter && i->second == route_id)
+ i = filters_.erase(i);
+ else
+ ++i;
+ }
+ // Should not be duplicate copies in the filter set.
+ DCHECK(!HasFilter(filter, route_id));
+}
+
+void WorkerProcessHost::WorkerInstance::RemoveFilters(
+ WorkerMessageFilter* filter) {
+ for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
+ if (i->first == filter)
+ i = filters_.erase(i);
+ else
+ ++i;
+ }
+}
+
+bool WorkerProcessHost::WorkerInstance::HasFilter(
+ WorkerMessageFilter* filter, int route_id) const {
+ for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
+ ++i) {
+ if (i->first == filter && i->second == route_id)
+ return true;
+ }
+ return false;
+}
+
+bool WorkerProcessHost::WorkerInstance::RendererIsParent(
+ int render_process_id, int render_view_id) const {
+ const WorkerDocumentSet::DocumentInfoSet& parents =
+ worker_document_set()->documents();
+ for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
+ parents.begin();
+ parent_iter != parents.end(); ++parent_iter) {
+ if (parent_iter->render_process_id() == render_process_id &&
+ parent_iter->render_view_id() == render_view_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+WorkerProcessHost::WorkerInstance::FilterInfo
+WorkerProcessHost::WorkerInstance::GetFilter() const {
+ DCHECK(NumFilters() == 1);
+ return *filters_.begin();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/worker_process_host.h b/chromium/content/browser/worker_host/worker_process_host.h
new file mode 100644
index 00000000000..226d3454f55
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_process_host.h
@@ -0,0 +1,250 @@
+// 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 CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_
+#define CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_
+
+#include <list>
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/worker_host/worker_document_set.h"
+#include "content/browser/worker_host/worker_storage_partition.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_sender.h"
+#include "url/gurl.h"
+
+namespace fileapi {
+class FileSystemContext;
+} // namespace fileapi
+
+namespace webkit_database {
+class DatabaseTracker;
+} // namespace webkit_database
+
+namespace content {
+class BrowserChildProcessHostImpl;
+class IndexedDBContextImpl;
+class ResourceContext;
+class SocketStreamDispatcherHost;
+class WorkerServiceImpl;
+
+// The WorkerProcessHost is the interface that represents the browser side of
+// the browser <-> worker communication channel. There will be one
+// WorkerProcessHost per worker process. Currently each worker runs in its own
+// process, but that may change. However, we do assume (by storing a
+// net::URLRequestContext) that a WorkerProcessHost serves a single
+// BrowserContext.
+class WorkerProcessHost : public BrowserChildProcessHostDelegate,
+ public IPC::Sender {
+ public:
+ // Contains information about each worker instance, needed to forward messages
+ // between the renderer and worker processes.
+ class WorkerInstance {
+ public:
+ WorkerInstance(const GURL& url,
+ const string16& name,
+ int worker_route_id,
+ int parent_process_id,
+ int64 main_resource_appcache_id,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition);
+ // Used for pending instances. Rest of the parameters are ignored.
+ WorkerInstance(const GURL& url,
+ bool shared,
+ const string16& name,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition);
+ ~WorkerInstance();
+
+ // Unique identifier for a worker client.
+ typedef std::pair<WorkerMessageFilter*, int> FilterInfo;
+
+ // APIs to manage the filter list for a given instance.
+ void AddFilter(WorkerMessageFilter* filter, int route_id);
+ void RemoveFilter(WorkerMessageFilter* filter, int route_id);
+ void RemoveFilters(WorkerMessageFilter* filter);
+ bool HasFilter(WorkerMessageFilter* filter, int route_id) const;
+ bool RendererIsParent(int render_process_id, int render_view_id) const;
+ int NumFilters() const { return filters_.size(); }
+ // Returns the single filter (must only be one).
+ FilterInfo GetFilter() const;
+
+ typedef std::list<FilterInfo> FilterList;
+ const FilterList& filters() const { return filters_; }
+
+ // Checks if this WorkerInstance matches the passed url/name params
+ // (per the comparison algorithm in the WebWorkers spec). This API only
+ // applies to shared workers.
+ bool Matches(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& partition,
+ ResourceContext* resource_context) const;
+
+ // Shares the passed instance's WorkerDocumentSet with this instance. This
+ // instance's current WorkerDocumentSet is dereferenced (and freed if this
+ // is the only reference) as a result.
+ void ShareDocumentSet(const WorkerInstance& instance) {
+ worker_document_set_ = instance.worker_document_set_;
+ };
+
+ // Accessors
+ bool closed() const { return closed_; }
+ void set_closed(bool closed) { closed_ = closed; }
+ const GURL& url() const { return url_; }
+ const string16 name() const { return name_; }
+ int worker_route_id() const { return worker_route_id_; }
+ int parent_process_id() const { return parent_process_id_; }
+ int64 main_resource_appcache_id() const {
+ return main_resource_appcache_id_;
+ }
+ WorkerDocumentSet* worker_document_set() const {
+ return worker_document_set_.get();
+ }
+ ResourceContext* resource_context() const {
+ return resource_context_;
+ }
+ const WorkerStoragePartition& partition() const {
+ return partition_;
+ }
+
+ private:
+ // Set of all filters (clients) associated with this worker.
+ GURL url_;
+ bool closed_;
+ string16 name_;
+ int worker_route_id_;
+ int parent_process_id_;
+ int64 main_resource_appcache_id_;
+ FilterList filters_;
+ scoped_refptr<WorkerDocumentSet> worker_document_set_;
+ ResourceContext* const resource_context_;
+ WorkerStoragePartition partition_;
+ };
+
+ WorkerProcessHost(ResourceContext* resource_context,
+ const WorkerStoragePartition& partition);
+ virtual ~WorkerProcessHost();
+
+ // IPC::Sender implementation:
+ virtual bool Send(IPC::Message* message) OVERRIDE;
+
+ // Starts the process. Returns true iff it succeeded.
+ // |render_process_id| is the renderer process responsible for starting this
+ // worker.
+ bool Init(int render_process_id);
+
+ // Creates a worker object in the process.
+ void CreateWorker(const WorkerInstance& instance);
+
+ // Returns true iff the given message from a renderer process was forwarded to
+ // the worker.
+ bool FilterMessage(const IPC::Message& message, WorkerMessageFilter* filter);
+
+ void FilterShutdown(WorkerMessageFilter* filter);
+
+ // Shuts down any shared workers that are no longer referenced by active
+ // documents.
+ void DocumentDetached(WorkerMessageFilter* filter,
+ unsigned long long document_id);
+
+ // Terminates the given worker, i.e. based on a UI action.
+ CONTENT_EXPORT void TerminateWorker(int worker_route_id);
+
+ // Callers can reduce the WorkerProcess' priority.
+ void SetBackgrounded(bool backgrounded);
+
+ CONTENT_EXPORT const ChildProcessData& GetData();
+
+ typedef std::list<WorkerInstance> Instances;
+ const Instances& instances() const { return instances_; }
+
+ ResourceContext* resource_context() const {
+ return resource_context_;
+ }
+
+ bool process_launched() const { return process_launched_; }
+
+ protected:
+ friend class WorkerServiceImpl;
+
+ Instances& mutable_instances() { return instances_; }
+
+ private:
+ // BrowserChildProcessHostDelegate implementation:
+ virtual void OnProcessLaunched() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // Creates and adds the message filters.
+ void CreateMessageFilters(int render_process_id);
+
+ void OnWorkerContextClosed(int worker_route_id);
+ void OnAllowDatabase(int worker_route_id,
+ const GURL& url,
+ const string16& name,
+ const string16& display_name,
+ unsigned long estimated_size,
+ bool* result);
+ void OnAllowFileSystem(int worker_route_id,
+ const GURL& url,
+ bool* result);
+ void OnAllowIndexedDB(int worker_route_id,
+ const GURL& url,
+ const string16& name,
+ bool* result);
+
+ // Relays a message to the given endpoint. Takes care of parsing the message
+ // if it contains a message port and sending it a valid route id.
+ void RelayMessage(const IPC::Message& message,
+ WorkerMessageFilter* filter,
+ int route_id);
+
+ void ShutdownSocketStreamDispatcherHostIfNecessary();
+
+ virtual bool CanShutdown() OVERRIDE;
+
+ // Updates the title shown in the task manager.
+ void UpdateTitle();
+
+ // Return a vector of all the render process/render view IDs that use the
+ // given worker.
+ std::vector<std::pair<int, int> > GetRenderViewIDsForWorker(int route_id);
+
+ Instances instances_;
+
+ ResourceContext* const resource_context_;
+ WorkerStoragePartition partition_;
+
+ // A reference to the filter associated with this worker process. We need to
+ // keep this around since we'll use it when forward messages to the worker
+ // process.
+ scoped_refptr<WorkerMessageFilter> worker_message_filter_;
+
+ scoped_ptr<BrowserChildProcessHostImpl> process_;
+ bool process_launched_;
+
+ scoped_refptr<SocketStreamDispatcherHost> socket_stream_dispatcher_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerProcessHost);
+};
+
+class WorkerProcessHostIterator
+ : public BrowserChildProcessHostTypeIterator<WorkerProcessHost> {
+ public:
+ WorkerProcessHostIterator()
+ : BrowserChildProcessHostTypeIterator<WorkerProcessHost>(
+ PROCESS_TYPE_WORKER) {
+ }
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_PROCESS_HOST_H_
diff --git a/chromium/content/browser/worker_host/worker_service_impl.cc b/chromium/content/browser/worker_host/worker_service_impl.cc
new file mode 100644
index 00000000000..5fb8e62bedb
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_service_impl.cc
@@ -0,0 +1,718 @@
+// 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 "content/browser/worker_host/worker_service_impl.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/threading/thread.h"
+#include "content/browser/devtools/worker_devtools_manager.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/worker_host/worker_message_filter.h"
+#include "content/browser/worker_host/worker_process_host.h"
+#include "content/common/view_messages.h"
+#include "content/common/worker_messages.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/worker_service_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+
+namespace content {
+
+const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
+const int WorkerServiceImpl::kMaxWorkersPerTabWhenSeparate = 16;
+
+class WorkerPrioritySetter
+ : public NotificationObserver,
+ public base::RefCountedThreadSafe<WorkerPrioritySetter,
+ BrowserThread::DeleteOnUIThread> {
+ public:
+ WorkerPrioritySetter();
+
+ // Posts a task to the UI thread to register to receive notifications.
+ void Initialize();
+
+ // Invoked by WorkerServiceImpl when a worker process is created.
+ void NotifyWorkerProcessCreated();
+
+ private:
+ friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
+ friend class base::DeleteHelper<WorkerPrioritySetter>;
+ virtual ~WorkerPrioritySetter();
+
+ // Posts a task to perform a worker priority update.
+ void PostTaskToGatherAndUpdateWorkerPriorities();
+
+ // Gathers up a list of the visible tabs and then updates priorities for
+ // all the shared workers.
+ void GatherVisibleIDsAndUpdateWorkerPriorities();
+
+ // Registers as an observer to receive notifications about
+ // widgets being shown.
+ void RegisterObserver();
+
+ // Sets priorities for shared workers given a set of visible tabs (as a
+ // std::set of std::pair<render_process, render_view> ids.
+ void UpdateWorkerPrioritiesFromVisibleSet(
+ const std::set<std::pair<int, int> >* visible);
+
+ // Called to refresh worker priorities when focus changes between tabs.
+ void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ NotificationRegistrar registrar_;
+};
+
+WorkerPrioritySetter::WorkerPrioritySetter() {
+}
+
+WorkerPrioritySetter::~WorkerPrioritySetter() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+void WorkerPrioritySetter::Initialize() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
+}
+
+void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
+ PostTaskToGatherAndUpdateWorkerPriorities();
+}
+
+void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
+ this));
+}
+
+void WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::set<std::pair<int, int> >* visible_renderer_ids =
+ new std::set<std::pair<int, int> >();
+
+ // Gather up all the visible renderer process/view pairs
+ RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts();
+ for (size_t i = 0; i < widgets.size(); ++i) {
+ if (widgets[i]->GetProcess()->VisibleWidgetCount() == 0)
+ continue;
+
+ RenderWidgetHostView* render_view = widgets[i]->GetView();
+ if (render_view && render_view->IsShowing()) {
+ visible_renderer_ids->insert(
+ std::pair<int, int>(widgets[i]->GetProcess()->GetID(),
+ widgets[i]->GetRoutingID()));
+ }
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
+ this, base::Owned(visible_renderer_ids)));
+}
+
+void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
+ const std::set<std::pair<int, int> >* visible_renderer_ids) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (!iter->process_launched())
+ continue;
+ bool throttle = true;
+
+ for (WorkerProcessHost::Instances::const_iterator instance =
+ iter->instances().begin(); instance != iter->instances().end();
+ ++instance) {
+
+ // This code assumes one worker per process
+ WorkerProcessHost::Instances::const_iterator first_instance =
+ iter->instances().begin();
+ if (first_instance == iter->instances().end())
+ continue;
+
+ WorkerDocumentSet::DocumentInfoSet::const_iterator info =
+ first_instance->worker_document_set()->documents().begin();
+
+ for (; info != first_instance->worker_document_set()->documents().end();
+ ++info) {
+ std::pair<int, int> id(
+ info->render_process_id(), info->render_view_id());
+ if (visible_renderer_ids->find(id) != visible_renderer_ids->end()) {
+ throttle = false;
+ break;
+ }
+ }
+
+ if (!throttle ) {
+ break;
+ }
+ }
+
+ iter->SetBackgrounded(throttle);
+ }
+}
+
+void WorkerPrioritySetter::OnRenderWidgetVisibilityChanged(
+ std::pair<int, int> id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ std::set<std::pair<int, int> > visible_renderer_ids;
+
+ visible_renderer_ids.insert(id);
+
+ UpdateWorkerPrioritiesFromVisibleSet(&visible_renderer_ids);
+}
+
+void WorkerPrioritySetter::RegisterObserver() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
+ NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED,
+ NotificationService::AllBrowserContextsAndSources());
+}
+
+void WorkerPrioritySetter::Observe(int type,
+ const NotificationSource& source, const NotificationDetails& details) {
+ if (type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) {
+ bool visible = *Details<bool>(details).ptr();
+
+ if (visible) {
+ int render_widget_id =
+ Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
+ int render_process_pid =
+ Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&WorkerPrioritySetter::OnRenderWidgetVisibilityChanged,
+ this, std::pair<int, int>(render_process_pid, render_widget_id)));
+ }
+ }
+ else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
+ PostTaskToGatherAndUpdateWorkerPriorities();
+ }
+}
+
+WorkerService* WorkerService::GetInstance() {
+ return WorkerServiceImpl::GetInstance();
+}
+
+WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return Singleton<WorkerServiceImpl>::get();
+}
+
+WorkerServiceImpl::WorkerServiceImpl()
+ : priority_setter_(new WorkerPrioritySetter()),
+ next_worker_route_id_(0) {
+ priority_setter_->Initialize();
+}
+
+WorkerServiceImpl::~WorkerServiceImpl() {
+ // The observers in observers_ can't be used here because they might be
+ // gone already.
+}
+
+void WorkerServiceImpl::PerformTeardownForTesting() {
+ priority_setter_ = NULL;
+}
+
+void WorkerServiceImpl::OnWorkerMessageFilterClosing(
+ WorkerMessageFilter* filter) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ iter->FilterShutdown(filter);
+ }
+
+ // See if that process had any queued workers.
+ for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
+ i != queued_workers_.end();) {
+ i->RemoveFilters(filter);
+ if (i->NumFilters() == 0) {
+ i = queued_workers_.erase(i);
+ } else {
+ ++i;
+ }
+ }
+
+ for (WorkerProcessHost::Instances::iterator i =
+ pending_shared_workers_.begin();
+ i != pending_shared_workers_.end(); ) {
+ i->RemoveFilters(filter);
+ if (i->NumFilters() == 0) {
+ i = pending_shared_workers_.erase(i);
+ } else {
+ ++i;
+ }
+ }
+
+ // Also, see if that process had any pending shared workers.
+ for (WorkerProcessHost::Instances::iterator iter =
+ pending_shared_workers_.begin();
+ iter != pending_shared_workers_.end(); ) {
+ iter->worker_document_set()->RemoveAll(filter);
+ if (iter->worker_document_set()->IsEmpty()) {
+ iter = pending_shared_workers_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ // Either a worker proceess has shut down, in which case we can start one of
+ // the queued workers, or a renderer has shut down, in which case it doesn't
+ // affect anything. We call this function in both scenarios because then we
+ // don't have to keep track which filters are from worker processes.
+ TryStartingQueuedWorker();
+}
+
+void WorkerServiceImpl::CreateWorker(
+ const ViewHostMsg_CreateWorker_Params& params,
+ int route_id,
+ WorkerMessageFilter* filter,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Generate a unique route id for the browser-worker communication that's
+ // unique among all worker processes. That way when the worker process sends
+ // a wrapped IPC message through us, we know which WorkerProcessHost to give
+ // it to.
+ WorkerProcessHost::WorkerInstance instance(
+ params.url,
+ params.name,
+ next_worker_route_id(),
+ 0,
+ params.script_resource_appcache_id,
+ resource_context,
+ partition);
+ instance.AddFilter(filter, route_id);
+ instance.worker_document_set()->Add(
+ filter, params.document_id, filter->render_process_id(),
+ params.render_view_route_id);
+
+ CreateWorkerFromInstance(instance);
+}
+
+void WorkerServiceImpl::LookupSharedWorker(
+ const ViewHostMsg_CreateWorker_Params& params,
+ int route_id,
+ WorkerMessageFilter* filter,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition,
+ bool* exists,
+ bool* url_mismatch) {
+ *exists = true;
+ WorkerProcessHost::WorkerInstance* instance = FindSharedWorkerInstance(
+ params.url, params.name, partition, resource_context);
+
+ if (!instance) {
+ // If no worker instance currently exists, we need to create a pending
+ // instance - this is to make sure that any subsequent lookups passing a
+ // mismatched URL get the appropriate url_mismatch error at lookup time.
+ // Having named shared workers was a Really Bad Idea due to details like
+ // this.
+ instance = CreatePendingInstance(params.url, params.name,
+ resource_context, partition);
+ *exists = false;
+ }
+
+ // Make sure the passed-in instance matches the URL - if not, return an
+ // error.
+ if (params.url != instance->url()) {
+ *url_mismatch = true;
+ *exists = false;
+ } else {
+ *url_mismatch = false;
+ // Add our route ID to the existing instance so we can send messages to it.
+ instance->AddFilter(filter, route_id);
+
+ // Add the passed filter/document_id to the worker instance.
+ // TODO(atwilson): This won't work if the message is from a worker process.
+ // We don't support that yet though (this message is only sent from
+ // renderers) but when we do, we'll need to add code to pass in the current
+ // worker's document set for nested workers.
+ instance->worker_document_set()->Add(
+ filter, params.document_id, filter->render_process_id(),
+ params.render_view_route_id);
+ }
+}
+
+void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
+ WorkerMessageFilter* filter) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter->FilterMessage(message, filter))
+ return;
+ }
+
+ // TODO(jabdelmalek): tell filter that callee is gone
+}
+
+void WorkerServiceImpl::DocumentDetached(unsigned long long document_id,
+ WorkerMessageFilter* filter) {
+ // Any associated shared workers can be shut down.
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter)
+ iter->DocumentDetached(filter, document_id);
+
+ // Remove any queued shared workers for this document.
+ for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
+ iter != queued_workers_.end();) {
+
+ iter->worker_document_set()->Remove(filter, document_id);
+ if (iter->worker_document_set()->IsEmpty()) {
+ iter = queued_workers_.erase(iter);
+ continue;
+ }
+ ++iter;
+ }
+
+ // Remove the document from any pending shared workers.
+ for (WorkerProcessHost::Instances::iterator iter =
+ pending_shared_workers_.begin();
+ iter != pending_shared_workers_.end(); ) {
+ iter->worker_document_set()->Remove(filter, document_id);
+ if (iter->worker_document_set()->IsEmpty()) {
+ iter = pending_shared_workers_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+bool WorkerServiceImpl::CreateWorkerFromInstance(
+ WorkerProcessHost::WorkerInstance instance) {
+ if (!CanCreateWorkerProcess(instance)) {
+ queued_workers_.push_back(instance);
+ return true;
+ }
+
+ // Check to see if this shared worker is already running (two pages may have
+ // tried to start up the worker simultaneously).
+ // See if a worker with this name already exists.
+ WorkerProcessHost::WorkerInstance* existing_instance =
+ FindSharedWorkerInstance(
+ instance.url(), instance.name(), instance.partition(),
+ instance.resource_context());
+ WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
+ instance.GetFilter();
+ // If this worker is already running, no need to create a new copy. Just
+ // inform the caller that the worker has been created.
+ if (existing_instance) {
+ // Walk the worker's filter list to see if this client is listed. If not,
+ // then it means that the worker started by the client already exited so
+ // we should not attach to this new one (http://crbug.com/29243).
+ if (!existing_instance->HasFilter(filter_info.first, filter_info.second))
+ return false;
+ filter_info.first->Send(new ViewMsg_WorkerCreated(filter_info.second));
+ return true;
+ }
+
+ // Look to see if there's a pending instance.
+ WorkerProcessHost::WorkerInstance* pending = FindPendingInstance(
+ instance.url(), instance.name(), instance.partition(),
+ instance.resource_context());
+ // If there's no instance *and* no pending instance (or there is a pending
+ // instance but it does not contain our filter info), then it means the
+ // worker started up and exited already. Log a warning because this should
+ // be a very rare occurrence and is probably a bug, but it *can* happen so
+ // handle it gracefully.
+ if (!pending ||
+ !pending->HasFilter(filter_info.first, filter_info.second)) {
+ DLOG(WARNING) << "Pending worker already exited";
+ return false;
+ }
+
+ // Assign the accumulated document set and filter list for this pending
+ // worker to the new instance.
+ DCHECK(!pending->worker_document_set()->IsEmpty());
+ instance.ShareDocumentSet(*pending);
+ for (WorkerProcessHost::WorkerInstance::FilterList::const_iterator i =
+ pending->filters().begin();
+ i != pending->filters().end(); ++i) {
+ instance.AddFilter(i->first, i->second);
+ }
+ RemovePendingInstances(instance.url(), instance.name(),
+ instance.partition(), instance.resource_context());
+
+ // Remove any queued instances of this worker and copy over the filter to
+ // this instance.
+ for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
+ iter != queued_workers_.end();) {
+ if (iter->Matches(instance.url(), instance.name(),
+ instance.partition(), instance.resource_context())) {
+ DCHECK(iter->NumFilters() == 1);
+ WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
+ iter->GetFilter();
+ instance.AddFilter(filter_info.first, filter_info.second);
+ iter = queued_workers_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ WorkerMessageFilter* first_filter = instance.filters().begin()->first;
+ WorkerProcessHost* worker = new WorkerProcessHost(
+ instance.resource_context(), instance.partition());
+ // TODO(atwilson): This won't work if the message is from a worker process.
+ // We don't support that yet though (this message is only sent from
+ // renderers) but when we do, we'll need to add code to pass in the current
+ // worker's document set for nested workers.
+ if (!worker->Init(first_filter->render_process_id())) {
+ delete worker;
+ return false;
+ }
+
+ worker->CreateWorker(instance);
+ FOR_EACH_OBSERVER(
+ WorkerServiceObserver, observers_,
+ WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
+ instance.worker_route_id()));
+ WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance);
+ return true;
+}
+
+bool WorkerServiceImpl::CanCreateWorkerProcess(
+ const WorkerProcessHost::WorkerInstance& instance) {
+ // Worker can be fired off if *any* parent has room.
+ const WorkerDocumentSet::DocumentInfoSet& parents =
+ instance.worker_document_set()->documents();
+
+ for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
+ parents.begin();
+ parent_iter != parents.end(); ++parent_iter) {
+ bool hit_total_worker_limit = false;
+ if (TabCanCreateWorkerProcess(parent_iter->render_process_id(),
+ parent_iter->render_view_id(),
+ &hit_total_worker_limit)) {
+ return true;
+ }
+ // Return false if already at the global worker limit (no need to continue
+ // checking parent tabs).
+ if (hit_total_worker_limit)
+ return false;
+ }
+ // If we've reached here, none of the parent tabs is allowed to create an
+ // instance.
+ return false;
+}
+
+bool WorkerServiceImpl::TabCanCreateWorkerProcess(
+ int render_process_id,
+ int render_view_id,
+ bool* hit_total_worker_limit) {
+ int total_workers = 0;
+ int workers_per_tab = 0;
+ *hit_total_worker_limit = false;
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ for (WorkerProcessHost::Instances::const_iterator cur_instance =
+ iter->instances().begin();
+ cur_instance != iter->instances().end(); ++cur_instance) {
+ total_workers++;
+ if (total_workers >= kMaxWorkersWhenSeparate) {
+ *hit_total_worker_limit = true;
+ return false;
+ }
+ if (cur_instance->RendererIsParent(render_process_id, render_view_id)) {
+ workers_per_tab++;
+ if (workers_per_tab >= kMaxWorkersPerTabWhenSeparate)
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void WorkerServiceImpl::TryStartingQueuedWorker() {
+ if (queued_workers_.empty())
+ return;
+
+ for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
+ i != queued_workers_.end();) {
+ if (CanCreateWorkerProcess(*i)) {
+ WorkerProcessHost::WorkerInstance instance = *i;
+ queued_workers_.erase(i);
+ CreateWorkerFromInstance(instance);
+
+ // CreateWorkerFromInstance can modify the queued_workers_ list when it
+ // coalesces queued instances after starting a shared worker, so we
+ // have to rescan the list from the beginning (our iterator is now
+ // invalid). This is not a big deal as having any queued workers will be
+ // rare in practice so the list will be small.
+ i = queued_workers_.begin();
+ } else {
+ ++i;
+ }
+ }
+}
+
+bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
+ int* render_process_id,
+ int* render_view_id) const {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter.GetData().id != worker_process_id)
+ continue;
+
+ // This code assumes one worker per process, see function comment in header!
+ WorkerProcessHost::Instances::const_iterator first_instance =
+ iter->instances().begin();
+ if (first_instance == iter->instances().end())
+ return false;
+
+ WorkerDocumentSet::DocumentInfoSet::const_iterator info =
+ first_instance->worker_document_set()->documents().begin();
+ *render_process_id = info->render_process_id();
+ *render_view_id = info->render_view_id();
+ return true;
+ }
+ return false;
+}
+
+const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
+ int worker_process_id) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter.GetData().id != worker_process_id)
+ continue;
+
+ WorkerProcessHost::Instances::const_iterator instance =
+ iter->instances().begin();
+ return instance == iter->instances().end() ? NULL : &*instance;
+ }
+ return NULL;
+}
+
+bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ if (iter.GetData().id == process_id) {
+ iter->TerminateWorker(route_id);
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
+ std::vector<WorkerService::WorkerInfo> results;
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ const WorkerProcessHost::Instances& instances = (*iter)->instances();
+ for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
+ i != instances.end(); ++i) {
+ WorkerService::WorkerInfo info;
+ info.url = i->url();
+ info.name = i->name();
+ info.route_id = i->worker_route_id();
+ info.process_id = iter.GetData().id;
+ info.handle = iter.GetData().handle;
+ results.push_back(info);
+ }
+ }
+ return results;
+}
+
+void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ observers_.AddObserver(observer);
+}
+
+void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ observers_.RemoveObserver(observer);
+}
+
+void WorkerServiceImpl::NotifyWorkerDestroyed(
+ WorkerProcessHost* process,
+ int worker_route_id) {
+ WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
+ process, worker_route_id);
+ FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
+ WorkerDestroyed(process->GetData().id, worker_route_id));
+}
+
+void WorkerServiceImpl::NotifyWorkerProcessCreated() {
+ priority_setter_->NotifyWorkerProcessCreated();
+}
+
+WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& partition,
+ ResourceContext* resource_context) {
+ for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
+ for (WorkerProcessHost::Instances::iterator instance_iter =
+ iter->mutable_instances().begin();
+ instance_iter != iter->mutable_instances().end();
+ ++instance_iter) {
+ if (instance_iter->Matches(url, name, partition, resource_context))
+ return &(*instance_iter);
+ }
+ }
+ return NULL;
+}
+
+WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindPendingInstance(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& partition,
+ ResourceContext* resource_context) {
+ // Walk the pending instances looking for a matching pending worker.
+ for (WorkerProcessHost::Instances::iterator iter =
+ pending_shared_workers_.begin();
+ iter != pending_shared_workers_.end();
+ ++iter) {
+ if (iter->Matches(url, name, partition, resource_context))
+ return &(*iter);
+ }
+ return NULL;
+}
+
+
+void WorkerServiceImpl::RemovePendingInstances(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& partition,
+ ResourceContext* resource_context) {
+ // Walk the pending instances looking for a matching pending worker.
+ for (WorkerProcessHost::Instances::iterator iter =
+ pending_shared_workers_.begin();
+ iter != pending_shared_workers_.end(); ) {
+ if (iter->Matches(url, name, partition, resource_context)) {
+ iter = pending_shared_workers_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+WorkerProcessHost::WorkerInstance* WorkerServiceImpl::CreatePendingInstance(
+ const GURL& url,
+ const string16& name,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& partition) {
+ // Look for an existing pending shared worker.
+ WorkerProcessHost::WorkerInstance* instance =
+ FindPendingInstance(url, name, partition, resource_context);
+ if (instance)
+ return instance;
+
+ // No existing pending worker - create a new one.
+ WorkerProcessHost::WorkerInstance pending(
+ url, true, name, resource_context, partition);
+ pending_shared_workers_.push_back(pending);
+ return &pending_shared_workers_.back();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/worker_service_impl.h b/chromium/content/browser/worker_host/worker_service_impl.h
new file mode 100644
index 00000000000..9da0d19cd21
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_service_impl.h
@@ -0,0 +1,149 @@
+// 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 CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_
+#define CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "base/threading/non_thread_safe.h"
+#include "content/browser/worker_host/worker_process_host.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/worker_service.h"
+
+class GURL;
+struct ViewHostMsg_CreateWorker_Params;
+
+namespace content {
+class ResourceContext;
+class WorkerServiceObserver;
+class WorkerStoragePartition;
+class WorkerPrioritySetter;
+
+class CONTENT_EXPORT WorkerServiceImpl
+ : public NON_EXPORTED_BASE(WorkerService) {
+ public:
+ // Returns the WorkerServiceImpl singleton.
+ static WorkerServiceImpl* GetInstance();
+
+ // Releases the priority setter to avoid memory leak error.
+ void PerformTeardownForTesting();
+
+ // WorkerService implementation:
+ virtual bool TerminateWorker(int process_id, int route_id) OVERRIDE;
+ virtual std::vector<WorkerInfo> GetWorkers() OVERRIDE;
+ virtual void AddObserver(WorkerServiceObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(WorkerServiceObserver* observer) OVERRIDE;
+
+ // These methods correspond to worker related IPCs.
+ void CreateWorker(const ViewHostMsg_CreateWorker_Params& params,
+ int route_id,
+ WorkerMessageFilter* filter,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& worker_partition);
+ void LookupSharedWorker(const ViewHostMsg_CreateWorker_Params& params,
+ int route_id,
+ WorkerMessageFilter* filter,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& worker_partition,
+ bool* exists,
+ bool* url_error);
+ void ForwardToWorker(const IPC::Message& message,
+ WorkerMessageFilter* filter);
+ void DocumentDetached(unsigned long long document_id,
+ WorkerMessageFilter* filter);
+
+ void OnWorkerMessageFilterClosing(WorkerMessageFilter* filter);
+
+ int next_worker_route_id() { return ++next_worker_route_id_; }
+
+ // Given a worker's process id, return the IDs of the renderer process and
+ // render view that created it. For shared workers, this returns the first
+ // parent.
+ // TODO(dimich): This code assumes there is 1 worker per worker process, which
+ // is how it is today until V8 can run in separate threads.
+ bool GetRendererForWorker(int worker_process_id,
+ int* render_process_id,
+ int* render_view_id) const;
+ const WorkerProcessHost::WorkerInstance* FindWorkerInstance(
+ int worker_process_id);
+
+ void NotifyWorkerDestroyed(
+ WorkerProcessHost* process,
+ int worker_route_id);
+
+ void NotifyWorkerProcessCreated();
+
+ // Used when we run each worker in a separate process.
+ static const int kMaxWorkersWhenSeparate;
+ static const int kMaxWorkersPerTabWhenSeparate;
+
+ private:
+ friend struct DefaultSingletonTraits<WorkerServiceImpl>;
+
+ WorkerServiceImpl();
+ virtual ~WorkerServiceImpl();
+
+ // Given a WorkerInstance, create an associated worker process.
+ bool CreateWorkerFromInstance(WorkerProcessHost::WorkerInstance instance);
+
+ // Checks if we can create a worker process based on the process limit when
+ // we're using a strategy of one process per core.
+ bool CanCreateWorkerProcess(
+ const WorkerProcessHost::WorkerInstance& instance);
+
+ // Checks if the tab associated with the passed RenderView can create a
+ // worker process based on the process limit when we're using a strategy of
+ // one worker per process.
+ bool TabCanCreateWorkerProcess(
+ int render_process_id, int render_route_id, bool* hit_total_worker_limit);
+
+ // Tries to see if any of the queued workers can be created.
+ void TryStartingQueuedWorker();
+
+ // APIs for manipulating our set of pending shared worker instances.
+ WorkerProcessHost::WorkerInstance* CreatePendingInstance(
+ const GURL& url,
+ const string16& name,
+ ResourceContext* resource_context,
+ const WorkerStoragePartition& worker_partition);
+ WorkerProcessHost::WorkerInstance* FindPendingInstance(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& worker_partition,
+ ResourceContext* resource_context);
+ void RemovePendingInstances(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& worker_partition,
+ ResourceContext* resource_context);
+
+ WorkerProcessHost::WorkerInstance* FindSharedWorkerInstance(
+ const GURL& url,
+ const string16& name,
+ const WorkerStoragePartition& worker_partition,
+ ResourceContext* resource_context);
+
+ scoped_refptr<WorkerPrioritySetter> priority_setter_;
+
+ int next_worker_route_id_;
+
+ WorkerProcessHost::Instances queued_workers_;
+
+ // These are shared workers that have been looked up, but not created yet.
+ // We need to keep a list of these to synchronously detect shared worker
+ // URL mismatches when two pages launch shared workers simultaneously.
+ WorkerProcessHost::Instances pending_shared_workers_;
+
+ ObserverList<WorkerServiceObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerServiceImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_SERVICE_H_
diff --git a/chromium/content/browser/worker_host/worker_storage_partition.cc b/chromium/content/browser/worker_host/worker_storage_partition.cc
new file mode 100644
index 00000000000..2c9105721f3
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_storage_partition.cc
@@ -0,0 +1,71 @@
+// 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 "content/browser/worker_host/worker_storage_partition.h"
+
+#include <string>
+
+#include "content/browser/appcache/chrome_appcache_service.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "webkit/browser/database/database_tracker.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+namespace content {
+
+WorkerStoragePartition::WorkerStoragePartition(
+ net::URLRequestContextGetter* url_request_context,
+ net::URLRequestContextGetter* media_url_request_context,
+ ChromeAppCacheService* appcache_service,
+ quota::QuotaManager* quota_manager,
+ fileapi::FileSystemContext* filesystem_context,
+ webkit_database::DatabaseTracker* database_tracker,
+ IndexedDBContextImpl* indexed_db_context)
+ : url_request_context_(url_request_context),
+ media_url_request_context_(media_url_request_context),
+ appcache_service_(appcache_service),
+ quota_manager_(quota_manager),
+ filesystem_context_(filesystem_context),
+ database_tracker_(database_tracker),
+ indexed_db_context_(indexed_db_context) {
+}
+
+WorkerStoragePartition::WorkerStoragePartition(
+ const WorkerStoragePartition& other) {
+ Copy(other);
+}
+
+const WorkerStoragePartition& WorkerStoragePartition::operator=(
+ const WorkerStoragePartition& rhs) {
+ Copy(rhs);
+ return *this;
+}
+
+bool WorkerStoragePartition::Equals(
+ const WorkerStoragePartition& other) const {
+ return url_request_context_.get() == other.url_request_context_.get() &&
+ media_url_request_context_.get() ==
+ other.media_url_request_context_.get() &&
+ appcache_service_.get() == other.appcache_service_.get() &&
+ quota_manager_.get() == other.quota_manager_.get() &&
+ filesystem_context_.get() == other.filesystem_context_.get() &&
+ database_tracker_.get() == other.database_tracker_.get() &&
+ indexed_db_context_.get() == other.indexed_db_context_.get();
+}
+
+WorkerStoragePartition::~WorkerStoragePartition() {
+}
+
+void WorkerStoragePartition::Copy(const WorkerStoragePartition& other) {
+ url_request_context_ = other.url_request_context_;
+ media_url_request_context_ = other.media_url_request_context_;
+ appcache_service_ = other.appcache_service_;
+ quota_manager_ = other.quota_manager_;
+ filesystem_context_ = other.filesystem_context_;
+ database_tracker_ = other.database_tracker_;
+ indexed_db_context_ = other.indexed_db_context_;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/worker_host/worker_storage_partition.h b/chromium/content/browser/worker_host/worker_storage_partition.h
new file mode 100644
index 00000000000..49c2deae248
--- /dev/null
+++ b/chromium/content/browser/worker_host/worker_storage_partition.h
@@ -0,0 +1,104 @@
+// 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 CONTENT_BROWSER_WORKER_HOST_WORKER_STORAGE_PARTITION_H_
+#define CONTENT_BROWSER_WORKER_HOST_WORKER_STORAGE_PARTITION_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace quota {
+class QuotaManager;
+}
+
+namespace fileapi {
+class FileSystemContext;
+} // namespace fileapi
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace webkit_database {
+class DatabaseTracker;
+} // namespace webkit_database
+
+namespace content {
+class ChromeAppCacheService;
+class IndexedDBContextImpl;
+
+// Contains the data from StoragePartition for use by Worker APIs.
+//
+// StoragePartition meant for the UI so we can't use it directly in many
+// Worker APIs that run on the IO thread. While we could make it RefCounted,
+// the Worker system is the only place that really needs access on the IO
+// thread. Instead of changing the lifetime semantics of StoragePartition,
+// we just create a parallel struct here to simplify the APIs of various
+// methods in WorkerServiceImpl.
+//
+// This class is effectively a struct, but we make it a class because we want to
+// define copy constructors, assignment operators, and an Equals() function for
+// it which makes it look awkward as a struct.
+class WorkerStoragePartition {
+ public:
+ WorkerStoragePartition(
+ net::URLRequestContextGetter* url_request_context,
+ net::URLRequestContextGetter* media_url_request_context,
+ ChromeAppCacheService* appcache_service,
+ quota::QuotaManager* quota_manager,
+ fileapi::FileSystemContext* filesystem_context,
+ webkit_database::DatabaseTracker* database_tracker,
+ IndexedDBContextImpl* indexed_db_context);
+ ~WorkerStoragePartition();
+
+ // Declaring so these don't get inlined which has the unfortunate effect of
+ // requiring all including classes to have the full definition of every member
+ // type.
+ WorkerStoragePartition(const WorkerStoragePartition& other);
+ const WorkerStoragePartition& operator=(const WorkerStoragePartition& rhs);
+
+ bool Equals(const WorkerStoragePartition& other) const;
+
+ net::URLRequestContextGetter* url_request_context() const {
+ return url_request_context_.get();
+ }
+
+ net::URLRequestContextGetter* media_url_request_context() const {
+ return media_url_request_context_.get();
+ }
+
+ ChromeAppCacheService* appcache_service() const {
+ return appcache_service_.get();
+ }
+
+ quota::QuotaManager* quota_manager() const {
+ return quota_manager_.get();
+ }
+
+ fileapi::FileSystemContext* filesystem_context() const {
+ return filesystem_context_.get();
+ }
+
+ webkit_database::DatabaseTracker* database_tracker() const {
+ return database_tracker_.get();
+ }
+
+ IndexedDBContextImpl* indexed_db_context() const {
+ return indexed_db_context_.get();
+ }
+
+ private:
+ void Copy(const WorkerStoragePartition& other);
+
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_;
+ scoped_refptr<net::URLRequestContextGetter> media_url_request_context_;
+ scoped_refptr<ChromeAppCacheService> appcache_service_;
+ scoped_refptr<quota::QuotaManager> quota_manager_;
+ scoped_refptr<fileapi::FileSystemContext> filesystem_context_;
+ scoped_refptr<webkit_database::DatabaseTracker> database_tracker_;
+ scoped_refptr<IndexedDBContextImpl> indexed_db_context_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_WORKER_HOST_WORKER_STORAGE_PARTITION_H_
diff --git a/chromium/content/browser/zygote_host/OWNERS b/chromium/content/browser/zygote_host/OWNERS
new file mode 100644
index 00000000000..602e00e0750
--- /dev/null
+++ b/chromium/content/browser/zygote_host/OWNERS
@@ -0,0 +1,3 @@
+markus@chromium.org
+cevans@chromium.org
+jln@chromium.org
diff --git a/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc b/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc
new file mode 100644
index 00000000000..bb84e62ce3a
--- /dev/null
+++ b/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc
@@ -0,0 +1,514 @@
+// 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 "content/browser/zygote_host/zygote_host_impl_linux.h"
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/linux_util.h"
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/posix/unix_domain_socket_linux.h"
+#include "base/process/launch.h"
+#include "base/process/memory.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/render_sandbox_host_linux.h"
+#include "content/common/zygote_commands_linux.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/result_codes.h"
+#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
+#include "sandbox/linux/suid/common/sandbox.h"
+#include "ui/base/ui_base_switches.h"
+
+#if defined(USE_TCMALLOC)
+#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
+#endif
+
+namespace content {
+
+// static
+ZygoteHost* ZygoteHost::GetInstance() {
+ return ZygoteHostImpl::GetInstance();
+}
+
+ZygoteHostImpl::ZygoteHostImpl()
+ : control_fd_(-1),
+ pid_(-1),
+ init_(false),
+ using_suid_sandbox_(false),
+ have_read_sandbox_status_word_(false),
+ sandbox_status_(0) {}
+
+ZygoteHostImpl::~ZygoteHostImpl() {
+ if (init_)
+ close(control_fd_);
+}
+
+// static
+ZygoteHostImpl* ZygoteHostImpl::GetInstance() {
+ return Singleton<ZygoteHostImpl>::get();
+}
+
+void ZygoteHostImpl::Init(const std::string& sandbox_cmd) {
+ DCHECK(!init_);
+ init_ = true;
+
+ base::FilePath chrome_path;
+ CHECK(PathService::Get(base::FILE_EXE, &chrome_path));
+ CommandLine cmd_line(chrome_path);
+
+ cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess);
+
+ int fds[2];
+#if defined(OS_FREEBSD) || defined(OS_OPENBSD)
+ // The BSDs often don't support SOCK_SEQPACKET yet, so fall back to
+ // SOCK_DGRAM if necessary.
+ if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) != 0)
+ CHECK(socketpair(PF_UNIX, SOCK_DGRAM, 0, fds) == 0);
+#else
+ CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0);
+#endif
+ base::FileHandleMappingVector fds_to_map;
+ fds_to_map.push_back(std::make_pair(fds[1], kZygoteSocketPairFd));
+
+ const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+ if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) {
+ cmd_line.PrependWrapper(
+ browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix));
+ }
+ // Append any switches from the browser process that need to be forwarded on
+ // to the zygote/renderers.
+ // Should this list be obtained from browser_render_process_host.cc?
+ static const char* kForwardSwitches[] = {
+ switches::kAllowSandboxDebugging,
+ switches::kLoggingLevel,
+ switches::kEnableLogging, // Support, e.g., --enable-logging=stderr.
+ switches::kV,
+ switches::kVModule,
+ switches::kRegisterPepperPlugins,
+ switches::kDisableSeccompFilterSandbox,
+
+ // Zygote process needs to know what resources to have loaded when it
+ // becomes a renderer process.
+ switches::kForceDeviceScaleFactor,
+ switches::kTouchOptimizedUI,
+
+ switches::kNoSandbox,
+ };
+ cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches,
+ arraysize(kForwardSwitches));
+
+ GetContentClient()->browser()->AppendExtraCommandLineSwitches(&cmd_line, -1);
+
+ sandbox_binary_ = sandbox_cmd.c_str();
+
+ // A non empty sandbox_cmd means we want a SUID sandbox.
+ using_suid_sandbox_ = !sandbox_cmd.empty();
+
+ if (using_suid_sandbox_) {
+ struct stat st;
+ if (stat(sandbox_binary_.c_str(), &st) != 0) {
+ LOG(FATAL) << "The SUID sandbox helper binary is missing: "
+ << sandbox_binary_ << " Aborting now.";
+ }
+
+ if (access(sandbox_binary_.c_str(), X_OK) == 0 &&
+ (st.st_uid == 0) &&
+ (st.st_mode & S_ISUID) &&
+ (st.st_mode & S_IXOTH)) {
+ cmd_line.PrependWrapper(sandbox_binary_);
+
+ scoped_ptr<sandbox::SetuidSandboxClient>
+ sandbox_client(sandbox::SetuidSandboxClient::Create());
+ sandbox_client->SetupLaunchEnvironment();
+ } else {
+ LOG(FATAL) << "The SUID sandbox helper binary was found, but is not "
+ "configured correctly. Rather than run without sandboxing "
+ "I'm aborting now. You need to make sure that "
+ << sandbox_binary_ << " is owned by root and has mode 4755.";
+ }
+ }
+
+ // Start up the sandbox host process and get the file descriptor for the
+ // renderers to talk to it.
+ const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
+ fds_to_map.push_back(std::make_pair(sfd, kZygoteRendererSocketFd));
+
+ int dummy_fd = -1;
+ if (using_suid_sandbox_) {
+ dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
+ CHECK(dummy_fd >= 0);
+ fds_to_map.push_back(std::make_pair(dummy_fd, kZygoteIdFd));
+ }
+
+ base::ProcessHandle process = -1;
+ base::LaunchOptions options;
+ options.fds_to_remap = &fds_to_map;
+ base::LaunchProcess(cmd_line.argv(), options, &process);
+ CHECK(process != -1) << "Failed to launch zygote process";
+
+ if (using_suid_sandbox_) {
+ // In the SUID sandbox, the real zygote is forked from the sandbox.
+ // We need to look for it.
+ // But first, wait for the zygote to tell us it's running.
+ // The sending code is in content/browser/zygote_main_linux.cc.
+ std::vector<int> fds_vec;
+ const int kExpectedLength = sizeof(kZygoteHelloMessage);
+ char buf[kExpectedLength];
+ const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf),
+ &fds_vec);
+ CHECK(len == kExpectedLength) << "Incorrect zygote magic length";
+ CHECK(0 == strcmp(buf, kZygoteHelloMessage))
+ << "Incorrect zygote hello";
+
+ std::string inode_output;
+ ino_t inode = 0;
+ // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end,
+ // and find the zygote process holding |dummy_fd|.
+ if (base::FileDescriptorGetInode(&inode, dummy_fd)) {
+ close(dummy_fd);
+ std::vector<std::string> get_inode_cmdline;
+ get_inode_cmdline.push_back(sandbox_binary_);
+ get_inode_cmdline.push_back(base::kFindInodeSwitch);
+ get_inode_cmdline.push_back(base::Int64ToString(inode));
+ CommandLine get_inode_cmd(get_inode_cmdline);
+ if (base::GetAppOutput(get_inode_cmd, &inode_output)) {
+ base::StringToInt(inode_output, &pid_);
+ }
+ }
+ CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary "
+ << sandbox_binary_ << ")";
+
+ if (process != pid_) {
+ // Reap the sandbox.
+ base::EnsureProcessGetsReaped(process);
+ }
+ } else {
+ // Not using the SUID sandbox.
+ pid_ = process;
+ }
+
+ close(fds[1]);
+ control_fd_ = fds[0];
+
+ Pickle pickle;
+ pickle.WriteInt(kZygoteCommandGetSandboxStatus);
+ if (!SendMessage(pickle, NULL))
+ LOG(FATAL) << "Cannot communicate with zygote";
+ // We don't wait for the reply. We'll read it in ReadReply.
+}
+
+bool ZygoteHostImpl::SendMessage(const Pickle& data,
+ const std::vector<int>* fds) {
+ CHECK(data.size() <= kZygoteMaxMessageLength)
+ << "Trying to send too-large message to zygote (sending " << data.size()
+ << " bytes, max is " << kZygoteMaxMessageLength << ")";
+ CHECK(!fds || fds->size() <= UnixDomainSocket::kMaxFileDescriptors)
+ << "Trying to send message with too many file descriptors to zygote "
+ << "(sending " << fds->size() << ", max is "
+ << UnixDomainSocket::kMaxFileDescriptors << ")";
+
+ return UnixDomainSocket::SendMsg(control_fd_,
+ data.data(), data.size(),
+ fds ? *fds : std::vector<int>());
+}
+
+ssize_t ZygoteHostImpl::ReadReply(void* buf, size_t buf_len) {
+ // At startup we send a kZygoteCommandGetSandboxStatus request to the zygote,
+ // but don't wait for the reply. Thus, the first time that we read from the
+ // zygote, we get the reply to that request.
+ if (!have_read_sandbox_status_word_) {
+ if (HANDLE_EINTR(read(control_fd_, &sandbox_status_,
+ sizeof(sandbox_status_))) !=
+ sizeof(sandbox_status_)) {
+ return -1;
+ }
+ have_read_sandbox_status_word_ = true;
+ }
+
+ return HANDLE_EINTR(read(control_fd_, buf, buf_len));
+}
+
+pid_t ZygoteHostImpl::ForkRequest(
+ const std::vector<std::string>& argv,
+ const std::vector<FileDescriptorInfo>& mapping,
+ const std::string& process_type) {
+ DCHECK(init_);
+ Pickle pickle;
+
+ pickle.WriteInt(kZygoteCommandFork);
+ pickle.WriteString(process_type);
+ pickle.WriteInt(argv.size());
+ for (std::vector<std::string>::const_iterator
+ i = argv.begin(); i != argv.end(); ++i)
+ pickle.WriteString(*i);
+
+ pickle.WriteInt(mapping.size());
+
+ std::vector<int> fds;
+ // Scoped pointers cannot be stored in containers, so we have to use a
+ // linked_ptr.
+ std::vector<linked_ptr<file_util::ScopedFD> > autodelete_fds;
+ for (std::vector<FileDescriptorInfo>::const_iterator
+ i = mapping.begin(); i != mapping.end(); ++i) {
+ pickle.WriteUInt32(i->id);
+ fds.push_back(i->fd.fd);
+ if (i->fd.auto_close) {
+ // Auto-close means we need to close the FDs after they have been passed
+ // to the other process.
+ linked_ptr<file_util::ScopedFD> ptr(
+ new file_util::ScopedFD(&(fds.back())));
+ autodelete_fds.push_back(ptr);
+ }
+ }
+
+ pid_t pid;
+ {
+ base::AutoLock lock(control_lock_);
+ if (!SendMessage(pickle, &fds))
+ return base::kNullProcessHandle;
+
+ // Read the reply, which pickles the PID and an optional UMA enumeration.
+ static const unsigned kMaxReplyLength = 2048;
+ char buf[kMaxReplyLength];
+ const ssize_t len = ReadReply(buf, sizeof(buf));
+
+ Pickle reply_pickle(buf, len);
+ PickleIterator iter(reply_pickle);
+ if (len <= 0 || !reply_pickle.ReadInt(&iter, &pid))
+ return base::kNullProcessHandle;
+
+ // If there is a nonempty UMA name string, then there is a UMA
+ // enumeration to record.
+ std::string uma_name;
+ int uma_sample;
+ int uma_boundary_value;
+ if (reply_pickle.ReadString(&iter, &uma_name) &&
+ !uma_name.empty() &&
+ reply_pickle.ReadInt(&iter, &uma_sample) &&
+ reply_pickle.ReadInt(&iter, &uma_boundary_value)) {
+ // We cannot use the UMA_HISTOGRAM_ENUMERATION macro here,
+ // because that's only for when the name is the same every time.
+ // Here we're using whatever name we got from the other side.
+ // But since it's likely that the same one will be used repeatedly
+ // (even though it's not guaranteed), we cache it here.
+ static base::HistogramBase* uma_histogram;
+ if (!uma_histogram || uma_histogram->histogram_name() != uma_name) {
+ uma_histogram = base::LinearHistogram::FactoryGet(
+ uma_name, 1,
+ uma_boundary_value,
+ uma_boundary_value + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ }
+ uma_histogram->Add(uma_sample);
+ }
+
+ if (pid <= 0)
+ return base::kNullProcessHandle;
+ }
+
+#if !defined(OS_OPENBSD)
+ // This is just a starting score for a renderer or extension (the
+ // only types of processes that will be started this way). It will
+ // get adjusted as time goes on. (This is the same value as
+ // chrome::kLowestRendererOomScore in chrome/chrome_constants.h, but
+ // that's not something we can include here.)
+ const int kLowestRendererOomScore = 300;
+ AdjustRendererOOMScore(pid, kLowestRendererOomScore);
+#endif
+
+ return pid;
+}
+
+#if !defined(OS_OPENBSD)
+void ZygoteHostImpl::AdjustRendererOOMScore(base::ProcessHandle pid,
+ int score) {
+ // 1) You can't change the oom_score_adj of a non-dumpable process
+ // (EPERM) unless you're root. Because of this, we can't set the
+ // oom_adj from the browser process.
+ //
+ // 2) We can't set the oom_score_adj before entering the sandbox
+ // because the zygote is in the sandbox and the zygote is as
+ // critical as the browser process. Its oom_adj value shouldn't
+ // be changed.
+ //
+ // 3) A non-dumpable process can't even change its own oom_score_adj
+ // because it's root owned 0644. The sandboxed processes don't
+ // even have /proc, but one could imagine passing in a descriptor
+ // from outside.
+ //
+ // So, in the normal case, we use the SUID binary to change it for us.
+ // However, Fedora (and other SELinux systems) don't like us touching other
+ // process's oom_score_adj (or oom_adj) values
+ // (https://bugzilla.redhat.com/show_bug.cgi?id=581256).
+ //
+ // The offical way to get the SELinux mode is selinux_getenforcemode, but I
+ // don't want to add another library to the build as it's sure to cause
+ // problems with other, non-SELinux distros.
+ //
+ // So we just check for files in /selinux. This isn't foolproof, but it's not
+ // bad and it's easy.
+
+ static bool selinux;
+ static bool selinux_valid = false;
+
+ if (!selinux_valid) {
+ const base::FilePath kSelinuxPath("/selinux");
+ base::FileEnumerator en(kSelinuxPath, false, base::FileEnumerator::FILES);
+ bool has_selinux_files = !en.Next().empty();
+
+ selinux = access(kSelinuxPath.value().c_str(), X_OK) == 0 &&
+ has_selinux_files;
+ selinux_valid = true;
+ }
+
+ if (using_suid_sandbox_ && !selinux) {
+#if defined(USE_TCMALLOC)
+ // If heap profiling is running, these processes are not exiting, at least
+ // on ChromeOS. The easiest thing to do is not launch them when profiling.
+ // TODO(stevenjb): Investigate further and fix.
+ if (IsHeapProfilerRunning())
+ return;
+#endif
+ std::vector<std::string> adj_oom_score_cmdline;
+ adj_oom_score_cmdline.push_back(sandbox_binary_);
+ adj_oom_score_cmdline.push_back(sandbox::kAdjustOOMScoreSwitch);
+ adj_oom_score_cmdline.push_back(base::Int64ToString(pid));
+ adj_oom_score_cmdline.push_back(base::IntToString(score));
+
+ base::ProcessHandle sandbox_helper_process;
+ if (base::LaunchProcess(adj_oom_score_cmdline, base::LaunchOptions(),
+ &sandbox_helper_process)) {
+ base::EnsureProcessGetsReaped(sandbox_helper_process);
+ }
+ } else if (!using_suid_sandbox_) {
+ if (!base::AdjustOOMScore(pid, score))
+ PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid;
+ }
+}
+#endif
+
+void ZygoteHostImpl::AdjustLowMemoryMargin(int64 margin_mb) {
+#if defined(OS_CHROMEOS)
+ // You can't change the low memory margin unless you're root. Because of this,
+ // we can't set the low memory margin from the browser process.
+ // So, we use the SUID binary to change it for us.
+ if (using_suid_sandbox_) {
+#if defined(USE_TCMALLOC)
+ // If heap profiling is running, these processes are not exiting, at least
+ // on ChromeOS. The easiest thing to do is not launch them when profiling.
+ // TODO(stevenjb): Investigate further and fix.
+ if (IsHeapProfilerRunning())
+ return;
+#endif
+ std::vector<std::string> adj_low_mem_commandline;
+ adj_low_mem_commandline.push_back(sandbox_binary_);
+ adj_low_mem_commandline.push_back(sandbox::kAdjustLowMemMarginSwitch);
+ adj_low_mem_commandline.push_back(base::Int64ToString(margin_mb));
+
+ base::ProcessHandle sandbox_helper_process;
+ if (base::LaunchProcess(adj_low_mem_commandline, base::LaunchOptions(),
+ &sandbox_helper_process)) {
+ base::EnsureProcessGetsReaped(sandbox_helper_process);
+ } else {
+ LOG(ERROR) << "Unable to run suid sandbox to set low memory margin.";
+ }
+ }
+ // Don't adjust memory margin if we're not running with the sandbox: this
+ // isn't very common, and not doing it has little impact.
+#else
+ // Low memory notification is currently only implemented on ChromeOS.
+ NOTREACHED() << "AdjustLowMemoryMargin not implemented";
+#endif // defined(OS_CHROMEOS)
+}
+
+
+void ZygoteHostImpl::EnsureProcessTerminated(pid_t process) {
+ DCHECK(init_);
+ Pickle pickle;
+
+ pickle.WriteInt(kZygoteCommandReap);
+ pickle.WriteInt(process);
+ if (!SendMessage(pickle, NULL))
+ LOG(ERROR) << "Failed to send Reap message to zygote";
+}
+
+base::TerminationStatus ZygoteHostImpl::GetTerminationStatus(
+ base::ProcessHandle handle,
+ bool known_dead,
+ int* exit_code) {
+ DCHECK(init_);
+ Pickle pickle;
+ pickle.WriteInt(kZygoteCommandGetTerminationStatus);
+ pickle.WriteBool(known_dead);
+ pickle.WriteInt(handle);
+
+ // Set this now to handle the early termination cases.
+ if (exit_code)
+ *exit_code = RESULT_CODE_NORMAL_EXIT;
+
+ static const unsigned kMaxMessageLength = 128;
+ char buf[kMaxMessageLength];
+ ssize_t len;
+ {
+ base::AutoLock lock(control_lock_);
+ if (!SendMessage(pickle, NULL))
+ LOG(ERROR) << "Failed to send GetTerminationStatus message to zygote";
+ len = ReadReply(buf, sizeof(buf));
+ }
+
+ if (len == -1) {
+ LOG(WARNING) << "Error reading message from zygote: " << errno;
+ return base::TERMINATION_STATUS_NORMAL_TERMINATION;
+ } else if (len == 0) {
+ LOG(WARNING) << "Socket closed prematurely.";
+ return base::TERMINATION_STATUS_NORMAL_TERMINATION;
+ }
+
+ Pickle read_pickle(buf, len);
+ int status, tmp_exit_code;
+ PickleIterator iter(read_pickle);
+ if (!read_pickle.ReadInt(&iter, &status) ||
+ !read_pickle.ReadInt(&iter, &tmp_exit_code)) {
+ LOG(WARNING) << "Error parsing GetTerminationStatus response from zygote.";
+ return base::TERMINATION_STATUS_NORMAL_TERMINATION;
+ }
+
+ if (exit_code)
+ *exit_code = tmp_exit_code;
+
+ return static_cast<base::TerminationStatus>(status);
+}
+
+pid_t ZygoteHostImpl::GetPid() const {
+ return pid_;
+}
+
+pid_t ZygoteHostImpl::GetSandboxHelperPid() const {
+ return RenderSandboxHostLinux::GetInstance()->pid();
+}
+
+int ZygoteHostImpl::GetSandboxStatus() const {
+ if (have_read_sandbox_status_word_)
+ return sandbox_status_;
+ return 0;
+}
+
+} // namespace content
diff --git a/chromium/content/browser/zygote_host/zygote_host_impl_linux.h b/chromium/content/browser/zygote_host/zygote_host_impl_linux.h
new file mode 100644
index 00000000000..9027fe706e3
--- /dev/null
+++ b/chromium/content/browser/zygote_host/zygote_host_impl_linux.h
@@ -0,0 +1,84 @@
+// 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 CONTENT_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_
+#define CONTENT_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_
+
+#include <string>
+#include <vector>
+
+#include "base/pickle.h"
+#include "base/process/kill.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/file_descriptor_info.h"
+#include "content/public/browser/zygote_host_linux.h"
+
+template<typename Type>
+struct DefaultSingletonTraits;
+
+namespace content {
+
+class CONTENT_EXPORT ZygoteHostImpl : public ZygoteHost {
+ public:
+ // Returns the singleton instance.
+ static ZygoteHostImpl* GetInstance();
+
+ void Init(const std::string& sandbox_cmd);
+
+ // Tries to start a process of type indicated by process_type.
+ // Returns its pid on success, otherwise
+ // base::kNullProcessHandle;
+ pid_t ForkRequest(const std::vector<std::string>& command_line,
+ const std::vector<FileDescriptorInfo>& mapping,
+ const std::string& process_type);
+ void EnsureProcessTerminated(pid_t process);
+
+ // Get the termination status (and, optionally, the exit code) of
+ // the process. |exit_code| is set to the exit code of the child
+ // process. (|exit_code| may be NULL.)
+ // Unfortunately the Zygote can not accurately figure out if a process
+ // is already dead without waiting synchronously for it.
+ // |known_dead| should be set to true when we already know that the process
+ // is dead.
+ base::TerminationStatus GetTerminationStatus(base::ProcessHandle handle,
+ bool known_dead,
+ int* exit_code);
+
+ // ZygoteHost implementation:
+ virtual pid_t GetPid() const OVERRIDE;
+ virtual pid_t GetSandboxHelperPid() const OVERRIDE;
+ virtual int GetSandboxStatus() const OVERRIDE;
+ virtual void AdjustRendererOOMScore(base::ProcessHandle process_handle,
+ int score) OVERRIDE;
+ virtual void AdjustLowMemoryMargin(int64 margin_mb) OVERRIDE;
+
+ private:
+ friend struct DefaultSingletonTraits<ZygoteHostImpl>;
+
+ ZygoteHostImpl();
+ virtual ~ZygoteHostImpl();
+
+ // Sends |data| to the zygote via |control_fd_|. If |fds| is non-NULL, the
+ // included file descriptors will also be passed. The caller is responsible
+ // for acquiring |control_lock_|.
+ bool SendMessage(const Pickle& data, const std::vector<int>* fds);
+
+ ssize_t ReadReply(void* buf, size_t buflen);
+
+ int control_fd_; // the socket to the zygote
+ // A lock protecting all communication with the zygote. This lock must be
+ // acquired before sending a command and released after the result has been
+ // received.
+ base::Lock control_lock_;
+ pid_t pid_;
+ bool init_;
+ bool using_suid_sandbox_;
+ std::string sandbox_binary_;
+ bool have_read_sandbox_status_word_;
+ int sandbox_status_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_